@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
163 lines (135 loc) • 4.99 kB
text/typescript
import { StateCreator } from 'zustand/vanilla';
import { chainRewriteQuery } from '@/chains/rewriteQuery';
import { chatService } from '@/services/chat';
import { ragService } from '@/services/rag';
import { useAgentStore } from '@/store/agent';
import { agentSelectors } from '@/store/agent/selectors';
import { ChatStore } from '@/store/chat';
import { chatSelectors } from '@/store/chat/selectors';
import { toggleBooleanList } from '@/store/chat/utils';
import { useUserStore } from '@/store/user';
import { systemAgentSelectors } from '@/store/user/selectors';
import { ChatSemanticSearchChunk } from '@/types/chunk';
export interface ChatRAGAction {
deleteUserMessageRagQuery: (id: string) => Promise<void>;
/**
* Retrieve chunks from semantic search
*/
internal_retrieveChunks: (
id: string,
userQuery: string,
messages: string[],
) => Promise<{ chunks: ChatSemanticSearchChunk[]; queryId?: string; rewriteQuery?: string }>;
/**
* Rewrite user content to better RAG query
*/
internal_rewriteQuery: (id: string, content: string, messages: string[]) => Promise<string>;
/**
* Check if we should use RAG
*/
internal_shouldUseRAG: () => boolean;
internal_toggleMessageRAGLoading: (loading: boolean, id: string) => void;
rewriteQuery: (id: string) => Promise<void>;
}
const knowledgeIds = () => agentSelectors.currentKnowledgeIds(useAgentStore.getState());
const hasEnabledKnowledge = () => agentSelectors.hasEnabledKnowledge(useAgentStore.getState());
export const chatRag: StateCreator<ChatStore, [['zustand/devtools', never]], [], ChatRAGAction> = (
set,
get,
) => ({
deleteUserMessageRagQuery: async (id) => {
const message = chatSelectors.getMessageById(id)(get());
if (!message || !message.ragQueryId) return;
// optimistic update the message's ragQuery
get().internal_dispatchMessage({
id,
type: 'updateMessage',
value: { ragQuery: null },
});
await ragService.deleteMessageRagQuery(message.ragQueryId);
await get().refreshMessages();
},
internal_retrieveChunks: async (id, userQuery, messages) => {
get().internal_toggleMessageRAGLoading(true, id);
const message = chatSelectors.getMessageById(id)(get());
// 1. get the rewrite query
let rewriteQuery = message?.ragQuery as string | undefined;
// if there is no ragQuery and there is a chat history
// we need to rewrite the user message to get better results
if (!message?.ragQuery && messages.length > 0) {
rewriteQuery = await get().internal_rewriteQuery(id, userQuery, messages);
}
// 2. retrieve chunks from semantic search
const files = chatSelectors.currentUserFiles(get()).map((f) => f.id);
try {
const { chunks, queryId } = await ragService.semanticSearchForChat({
fileIds: knowledgeIds().fileIds.concat(files),
knowledgeIds: knowledgeIds().knowledgeBaseIds,
messageId: id,
rewriteQuery: rewriteQuery || userQuery,
userQuery,
});
get().internal_toggleMessageRAGLoading(false, id);
return { chunks, queryId, rewriteQuery };
} catch {
get().internal_toggleMessageRAGLoading(false, id);
return { chunks: [] };
}
},
internal_rewriteQuery: async (id, content, messages) => {
let rewriteQuery = content;
const queryRewriteConfig = systemAgentSelectors.queryRewrite(useUserStore.getState());
if (!queryRewriteConfig.enabled) return content;
const rewriteQueryParams = {
model: queryRewriteConfig.model,
provider: queryRewriteConfig.provider,
...chainRewriteQuery(
content,
messages,
!!queryRewriteConfig.customPrompt ? queryRewriteConfig.customPrompt : undefined,
),
};
let ragQuery = '';
await chatService.fetchPresetTaskResult({
onFinish: async (text) => {
rewriteQuery = text;
},
onMessageHandle: (chunk) => {
if (chunk.type !== 'text') return;
ragQuery += chunk.text;
get().internal_dispatchMessage({
id,
type: 'updateMessage',
value: { ragQuery },
});
},
params: rewriteQueryParams,
});
return rewriteQuery;
},
internal_shouldUseRAG: () => {
// if there is enabled knowledge, try with ragQuery
return hasEnabledKnowledge();
},
internal_toggleMessageRAGLoading: (loading, id) => {
set(
{
messageRAGLoadingIds: toggleBooleanList(get().messageRAGLoadingIds, id, loading),
},
false,
'internal_toggleMessageLoading',
);
},
rewriteQuery: async (id) => {
const message = chatSelectors.getMessageById(id)(get());
if (!message) return;
// delete the current ragQuery
await get().deleteUserMessageRagQuery(id);
const chats = chatSelectors.mainAIChatsWithHistoryConfig(get());
await get().internal_rewriteQuery(
id,
message.content,
chats.map((m) => m.content),
);
},
});