From b4e3f601ab17654ca7817e6011c1662b779b044e Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Wed, 30 Oct 2024 02:47:16 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20perf:=20improve=20knowledg?= =?UTF-8?q?e=20base=20RAG=20prompts=20(#4544)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ perf: improve knowledge base qa prompts * ⚡️ perf: improve knowledge base qa performance * ⚡️ perf: improve knowledge base qa performance * ✅ test: add tests * ✅ test: add tests for rag actions * ✅ test: fix tests --- src/database/server/models/chunk.ts | 19 +- .../__snapshots__/index.test.ts.snap | 26 ++ src/prompts/knowledgeBaseQA/chunk.ts | 15 + src/prompts/knowledgeBaseQA/index.test.ts | 146 ++++++++++ src/prompts/knowledgeBaseQA/index.ts | 33 +++ src/prompts/knowledgeBaseQA/knowledge.ts | 15 + src/prompts/knowledgeBaseQA/userQuery.ts | 8 + ...{action.test.ts => generateAIChat.test.ts} | 0 .../aiChat/actions/__tests__/rag.test.ts | 261 ++++++++++++++++++ .../slices/aiChat/actions/generateAIChat.ts | 27 +- src/store/chat/slices/aiChat/actions/rag.ts | 11 +- src/types/chunk/index.ts | 2 + 12 files changed, 537 insertions(+), 26 deletions(-) create mode 100644 src/prompts/knowledgeBaseQA/__snapshots__/index.test.ts.snap create mode 100644 src/prompts/knowledgeBaseQA/chunk.ts create mode 100644 src/prompts/knowledgeBaseQA/index.test.ts create mode 100644 src/prompts/knowledgeBaseQA/index.ts create mode 100644 src/prompts/knowledgeBaseQA/knowledge.ts create mode 100644 src/prompts/knowledgeBaseQA/userQuery.ts rename src/store/chat/slices/aiChat/actions/__tests__/{action.test.ts => generateAIChat.test.ts} (100%) create mode 100644 src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts diff --git a/src/database/server/models/chunk.ts b/src/database/server/models/chunk.ts index 2acc0a3875d4..9c4dc7000def 100644 --- a/src/database/server/models/chunk.ts +++ b/src/database/server/models/chunk.ts @@ -3,7 +3,7 @@ import { and, desc, isNull } from 'drizzle-orm/expressions'; import { chunk } from 'lodash-es'; import { serverDB } from '@/database/server'; -import { ChunkMetadata, FileChunk, SemanticSearchChunk } from '@/types/chunk'; +import { ChunkMetadata, FileChunk } from '@/types/chunk'; import { NewChunkItem, @@ -148,6 +148,8 @@ export class ChunkModel { const data = await serverDB .select({ + fileId: fileChunks.fileId, + fileName: files.name, id: chunks.id, index: chunks.index, metadata: chunks.metadata, @@ -158,16 +160,15 @@ export class ChunkModel { .from(chunks) .leftJoin(embeddings, eq(chunks.id, embeddings.chunkId)) .leftJoin(fileChunks, eq(chunks.id, fileChunks.chunkId)) + .leftJoin(files, eq(fileChunks.fileId, files.id)) .where(fileIds ? inArray(fileChunks.fileId, fileIds) : undefined) .orderBy((t) => desc(t.similarity)) .limit(30); - return data.map( - (item): SemanticSearchChunk => ({ - ...item, - metadata: item.metadata as ChunkMetadata, - }), - ); + return data.map((item) => ({ + ...item, + metadata: item.metadata as ChunkMetadata, + })); } async semanticSearchForChat({ @@ -187,7 +188,7 @@ export class ChunkModel { const result = await serverDB .select({ fileId: files.id, - filename: files.name, + fileName: files.name, id: chunks.id, index: chunks.index, metadata: chunks.metadata, @@ -205,6 +206,8 @@ export class ChunkModel { return result.map((item) => { return { + fileId: item.fileId, + fileName: item.fileName, id: item.id, index: item.index, similarity: item.similarity, diff --git a/src/prompts/knowledgeBaseQA/__snapshots__/index.test.ts.snap b/src/prompts/knowledgeBaseQA/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..5118715a2bae --- /dev/null +++ b/src/prompts/knowledgeBaseQA/__snapshots__/index.test.ts.snap @@ -0,0 +1,26 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`knowledgeBaseQAPrompts > should generate prompt with all parameters 1`] = ` +" +You are also a helpful assistant good answering questions related to Test Knowledge. And you'll be provided with a question and several passages that might be relevant. And currently your task is to provide answer based on the question and passages. + +- Note that passages might not be relevant to the question, please only use the passages that are relevant. +- if there is no relevant passage, please answer using your knowledge. +- Answer should use the same original language as the question and follow markdown syntax. + + +here are the knowledge base scope we retrieve chunks from: +Test description + + +here are retrived chunks you can refer to: +This is a test chunk + + +to make result better, we may rewrite user's question.If there is a rewrite query, it will be wrapper with \`rewrite_query\` tag. + +What is the test about? +Could you explain the content of the test? + +" +`; diff --git a/src/prompts/knowledgeBaseQA/chunk.ts b/src/prompts/knowledgeBaseQA/chunk.ts new file mode 100644 index 000000000000..9aa970ee2827 --- /dev/null +++ b/src/prompts/knowledgeBaseQA/chunk.ts @@ -0,0 +1,15 @@ +import { ChatSemanticSearchChunk } from '@/types/chunk'; + +const chunkPrompt = (item: ChatSemanticSearchChunk) => + `${item.text}`; + +export const chunkPrompts = (fileList: ChatSemanticSearchChunk[]) => { + if (fileList.length === 0) return ''; + + const prompt = ` +here are retrived chunks you can refer to: +${fileList.map((item) => chunkPrompt(item)).join('\n')} +`; + + return prompt.trim(); +}; diff --git a/src/prompts/knowledgeBaseQA/index.test.ts b/src/prompts/knowledgeBaseQA/index.test.ts new file mode 100644 index 000000000000..64936c1d920e --- /dev/null +++ b/src/prompts/knowledgeBaseQA/index.test.ts @@ -0,0 +1,146 @@ +import { describe, expect, it } from 'vitest'; + +import { ChatSemanticSearchChunk } from '@/types/chunk'; +import { KnowledgeItem, KnowledgeType } from '@/types/knowledgeBase'; + +import { knowledgeBaseQAPrompts } from './index'; + +describe('knowledgeBaseQAPrompts', () => { + // Define test data + const mockChunks: ChatSemanticSearchChunk[] = [ + { + id: '1', + fileId: 'file1', + fileName: 'test.txt', + text: 'This is a test chunk', + similarity: 0.8, + pageNumber: 1, + }, + ]; + + const mockKnowledge: KnowledgeItem[] = [ + { + id: 'kb1', + name: 'Test Knowledge', + type: KnowledgeType.File, + fileType: 'txt', + description: 'Test description', + }, + ]; + + const userQuery = 'What is the test about?'; + const rewriteQuery = 'Could you explain the content of the test?'; + + it('should return empty string if chunks is empty', () => { + const result = knowledgeBaseQAPrompts({ + chunks: [], + knowledge: mockKnowledge, + userQuery, + }); + + expect(result).toBe(''); + }); + + it('should return empty string if chunks is undefined', () => { + const result = knowledgeBaseQAPrompts({ + knowledge: mockKnowledge, + userQuery, + }); + + expect(result).toBe(''); + }); + + it('should generate prompt with all parameters', () => { + const result = knowledgeBaseQAPrompts({ + chunks: mockChunks, + knowledge: mockKnowledge, + userQuery, + rewriteQuery, + }); + + // Verify the prompt structure and content + expect(result).toMatchSnapshot(); + }); + + it('should generate prompt without rewriteQuery', () => { + const result = knowledgeBaseQAPrompts({ + chunks: mockChunks, + knowledge: mockKnowledge, + userQuery, + }); + + expect(result).toContain('What is the test about?'); + expect(result).not.toContain(''); + }); + + it('should generate prompt without knowledge', () => { + const result = knowledgeBaseQAPrompts({ + chunks: mockChunks, + userQuery, + }); + + expect(result).toContain( + 'You are also a helpful assistant good answering questions related to', + ); + expect(result).not.toContain(''); + }); + + it('should handle empty knowledge array', () => { + const result = knowledgeBaseQAPrompts({ + chunks: mockChunks, + knowledge: [], + userQuery, + }); + + expect(result).toContain( + 'You are also a helpful assistant good answering questions related to', + ); + expect(result).not.toContain(''); + }); + + it('should properly escape special characters in input', () => { + const specialChunks: ChatSemanticSearchChunk[] = [ + { + id: '1', + fileId: 'file1', + fileName: 'test&.txt', + text: 'This is a test with & < > "quotes"', + similarity: 0.8, + }, + ]; + + const result = knowledgeBaseQAPrompts({ + chunks: specialChunks, + userQuery: 'Test with & < > "quotes"', + }); + + expect(result).toContain('test&.txt'); + expect(result).toContain('This is a test with & < > "quotes"'); + expect(result).toContain('Test with & < > "quotes"'); + }); + + it('should handle multiple knowledge items', () => { + const multipleKnowledge: KnowledgeItem[] = [ + { + id: 'kb1', + name: 'Knowledge 1', + type: KnowledgeType.File, + }, + { + id: 'kb2', + name: 'Knowledge 2', + type: KnowledgeType.KnowledgeBase, + }, + ]; + + const result = knowledgeBaseQAPrompts({ + chunks: mockChunks, + knowledge: multipleKnowledge, + userQuery, + }); + + expect(result).toContain('Knowledge 1/Knowledge 2'); + expect(result).toContain(' { + if ((chunks || [])?.length === 0) return ''; + + const domains = (knowledge || []).map((v) => v.name).join('/'); + + return ` +You are also a helpful assistant good answering questions related to ${domains}. And you'll be provided with a question and several passages that might be relevant. And currently your task is to provide answer based on the question and passages. + +- Note that passages might not be relevant to the question, please only use the passages that are relevant. +- if there is no relevant passage, please answer using your knowledge. +- Answer should use the same original language as the question and follow markdown syntax. + +${knowledgePrompts(knowledge)} +${chunks ? chunkPrompts(chunks) : ''} +${userQueryPrompt(userQuery, rewriteQuery)} +`; +}; diff --git a/src/prompts/knowledgeBaseQA/knowledge.ts b/src/prompts/knowledgeBaseQA/knowledge.ts new file mode 100644 index 000000000000..3be434baf64f --- /dev/null +++ b/src/prompts/knowledgeBaseQA/knowledge.ts @@ -0,0 +1,15 @@ +import { KnowledgeItem } from '@/types/knowledgeBase'; + +const knowledgePrompt = (item: KnowledgeItem) => + `${item.description || ''}`; + +export const knowledgePrompts = (list?: KnowledgeItem[]) => { + if ((list || []).length === 0) return ''; + + const prompt = ` +here are the knowledge base scope we retrieve chunks from: +${list?.map((item) => knowledgePrompt(item)).join('\n')} +`; + + return prompt.trim(); +}; diff --git a/src/prompts/knowledgeBaseQA/userQuery.ts b/src/prompts/knowledgeBaseQA/userQuery.ts new file mode 100644 index 000000000000..5a939f17a930 --- /dev/null +++ b/src/prompts/knowledgeBaseQA/userQuery.ts @@ -0,0 +1,8 @@ +export const userQueryPrompt = (userQuery: string, rewriteQuery?: string) => { + return ` +to make result better, we may rewrite user's question.If there is a rewrite query, it will be wrapper with \`rewrite_query\` tag. + +${userQuery.trim()} +${rewriteQuery ? `${rewriteQuery.trim()}` : ''} +`; +}; diff --git a/src/store/chat/slices/aiChat/actions/__tests__/action.test.ts b/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts similarity index 100% rename from src/store/chat/slices/aiChat/actions/__tests__/action.test.ts rename to src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts diff --git a/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts b/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts new file mode 100644 index 000000000000..3ee68f2a0bdb --- /dev/null +++ b/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts @@ -0,0 +1,261 @@ +import { act, renderHook } from '@testing-library/react'; +import { Mock, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { chatService } from '@/services/chat'; +import { ragService } from '@/services/rag'; +import { useAgentStore } from '@/store/agent'; +import { agentSelectors } from '@/store/agent/selectors'; +import { chatSelectors } from '@/store/chat/selectors'; +import { systemAgentSelectors } from '@/store/user/selectors'; +import { ChatMessage } from '@/types/message'; +import { QueryRewriteSystemAgent } from '@/types/user/settings'; + +import { useChatStore } from '../../../../store'; + +// Mock services +vi.mock('@/services/chat', () => ({ + chatService: { + fetchPresetTaskResult: vi.fn(), + }, +})); + +vi.mock('@/services/rag', () => ({ + ragService: { + deleteMessageRagQuery: vi.fn(), + semanticSearchForChat: vi.fn(), + }, +})); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('chatRAG actions', () => { + describe('deleteUserMessageRagQuery', () => { + it('should not delete if message not found', async () => { + const { result } = renderHook(() => useChatStore()); + + await act(async () => { + await result.current.deleteUserMessageRagQuery('non-existent-id'); + }); + + expect(ragService.deleteMessageRagQuery).not.toHaveBeenCalled(); + }); + + it('should not delete if message has no ragQueryId', async () => { + const { result } = renderHook(() => useChatStore()); + const messageId = 'message-id'; + + act(() => { + useChatStore.setState({ + messagesMap: { + default: [{ id: messageId }] as ChatMessage[], + }, + }); + }); + + await act(async () => { + await result.current.deleteUserMessageRagQuery(messageId); + }); + + expect(ragService.deleteMessageRagQuery).not.toHaveBeenCalled(); + }); + }); + + describe('internal_retrieveChunks', () => { + it('should retrieve chunks with existing ragQuery', async () => { + const { result } = renderHook(() => useChatStore()); + const messageId = 'message-id'; + const existingRagQuery = 'existing-query'; + const userQuery = 'user-query'; + + // Mock the message with existing ragQuery + vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue( + () => + ({ + id: messageId, + ragQuery: existingRagQuery, + }) as ChatMessage, + ); + + // Mock the semantic search response + (ragService.semanticSearchForChat as Mock).mockResolvedValue({ + chunks: [{ id: 'chunk-1' }], + queryId: 'query-id', + }); + + vi.spyOn(agentSelectors, 'currentKnowledgeIds').mockReturnValue({ + fileIds: [], + knowledgeBaseIds: [], + }); + + const result1 = await act(async () => { + return await result.current.internal_retrieveChunks(messageId, userQuery, []); + }); + + expect(result1).toEqual({ + chunks: [{ id: 'chunk-1' }], + queryId: 'query-id', + rewriteQuery: existingRagQuery, + }); + expect(ragService.semanticSearchForChat).toHaveBeenCalledWith( + expect.objectContaining({ + rewriteQuery: existingRagQuery, + userQuery, + }), + ); + }); + + it('should rewrite query if no existing ragQuery', async () => { + const { result } = renderHook(() => useChatStore()); + const messageId = 'message-id'; + const userQuery = 'user-query'; + const rewrittenQuery = 'rewritten-query'; + + // Mock the message without ragQuery + vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue( + () => + ({ + id: messageId, + }) as ChatMessage, + ); + + // Mock the rewrite query function + vi.spyOn(result.current, 'internal_rewriteQuery').mockResolvedValueOnce(rewrittenQuery); + + // Mock the semantic search response + (ragService.semanticSearchForChat as Mock).mockResolvedValue({ + chunks: [{ id: 'chunk-1' }], + queryId: 'query-id', + }); + + vi.spyOn(agentSelectors, 'currentKnowledgeIds').mockReturnValue({ + fileIds: [], + knowledgeBaseIds: [], + }); + + const result2 = await act(async () => { + return await result.current.internal_retrieveChunks(messageId, userQuery, ['message']); + }); + + expect(result2).toEqual({ + chunks: [{ id: 'chunk-1' }], + queryId: 'query-id', + rewriteQuery: rewrittenQuery, + }); + expect(result.current.internal_rewriteQuery).toHaveBeenCalledWith(messageId, userQuery, [ + 'message', + ]); + }); + }); + + describe('internal_rewriteQuery', () => { + it('should return original content if query rewrite is disabled', async () => { + const { result } = renderHook(() => useChatStore()); + const content = 'original content'; + + vi.spyOn(systemAgentSelectors, 'queryRewrite').mockReturnValueOnce({ + enabled: false, + } as QueryRewriteSystemAgent); + + const rewrittenQuery = await result.current.internal_rewriteQuery('id', content, []); + + expect(rewrittenQuery).toBe(content); + expect(chatService.fetchPresetTaskResult).not.toHaveBeenCalled(); + }); + + it('should rewrite query if enabled', async () => { + const { result } = renderHook(() => useChatStore()); + const messageId = 'message-id'; + const content = 'original content'; + const rewrittenContent = 'rewritten content'; + + vi.spyOn(systemAgentSelectors, 'queryRewrite').mockReturnValueOnce({ + enabled: true, + model: 'gpt-3.5', + provider: 'openai', + }); + + (chatService.fetchPresetTaskResult as Mock).mockImplementation(({ onFinish }) => { + onFinish(rewrittenContent); + }); + + const rewrittenQuery = await result.current.internal_rewriteQuery(messageId, content, []); + + expect(rewrittenQuery).toBe(rewrittenContent); + expect(chatService.fetchPresetTaskResult).toHaveBeenCalled(); + }); + }); + + describe('internal_shouldUseRAG', () => { + it('should return true if has enabled knowledge', () => { + const { result } = renderHook(() => useChatStore()); + + vi.spyOn(agentSelectors, 'hasEnabledKnowledge').mockReturnValue(true); + vi.spyOn(chatSelectors, 'currentUserFiles').mockReturnValue([]); + + expect(result.current.internal_shouldUseRAG()).toBe(true); + }); + + it('should return true if has user files', () => { + const { result } = renderHook(() => useChatStore()); + + vi.spyOn(agentSelectors, 'hasEnabledKnowledge').mockReturnValue(false); + vi.spyOn(chatSelectors, 'currentUserFiles').mockReturnValue([{ id: 'file-1' }] as any); + + expect(result.current.internal_shouldUseRAG()).toBe(true); + }); + + it('should return false if no knowledge or files', () => { + const { result } = renderHook(() => useChatStore()); + + vi.spyOn(agentSelectors, 'hasEnabledKnowledge').mockReturnValue(false); + vi.spyOn(chatSelectors, 'currentUserFiles').mockReturnValue([]); + + expect(result.current.internal_shouldUseRAG()).toBe(false); + }); + }); + + describe('rewriteQuery', () => { + it('should not rewrite if message not found', async () => { + const { result } = renderHook(() => useChatStore()); + + vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue(() => undefined); + const rewriteSpy = vi.spyOn(result.current, 'internal_rewriteQuery'); + + await act(async () => { + await result.current.rewriteQuery('non-existent-id'); + }); + + expect(rewriteSpy).not.toHaveBeenCalled(); + }); + + it('should rewrite query for existing message', async () => { + const { result } = renderHook(() => useChatStore()); + const messageId = 'message-id'; + const content = 'message content'; + + vi.spyOn(chatSelectors, 'getMessageById').mockReturnValue( + () => + ({ + id: messageId, + content, + }) as ChatMessage, + ); + + vi.spyOn(chatSelectors, 'currentChatsWithHistoryConfig').mockReturnValue([ + { content: 'history' }, + ] as ChatMessage[]); + + const rewriteSpy = vi.spyOn(result.current, 'internal_rewriteQuery'); + const deleteSpy = vi.spyOn(result.current, 'deleteUserMessageRagQuery'); + + await act(async () => { + await result.current.rewriteQuery(messageId); + }); + + expect(deleteSpy).toHaveBeenCalledWith(messageId); + expect(rewriteSpy).toHaveBeenCalledWith(messageId, content, ['history']); + }); + }); +}); diff --git a/src/store/chat/slices/aiChat/actions/generateAIChat.ts b/src/store/chat/slices/aiChat/actions/generateAIChat.ts index 9ba3eb321e83..898da2c69bde 100644 --- a/src/store/chat/slices/aiChat/actions/generateAIChat.ts +++ b/src/store/chat/slices/aiChat/actions/generateAIChat.ts @@ -4,10 +4,10 @@ import { produce } from 'immer'; import { template } from 'lodash-es'; import { StateCreator } from 'zustand/vanilla'; -import { chainAnswerWithContext } from '@/chains/answerWithContext'; import { LOADING_FLAT, MESSAGE_CANCEL_FLAT } from '@/const/message'; import { TraceEventType, TraceNameMap } from '@/const/trace'; import { isServerMode } from '@/const/version'; +import { knowledgeBaseQAPrompts } from '@/prompts/knowledgeBaseQA'; import { chatService } from '@/services/chat'; import { messageService } from '@/services/message'; import { useAgentStore } from '@/store/agent'; @@ -269,10 +269,11 @@ export const generateAIChat: StateCreator< let fileChunks: MessageSemanticSearchChunk[] | undefined; let ragQueryId; + // go into RAG flow if there is ragQuery flag if (params?.ragQuery) { // 1. get the relative chunks from semantic search - const { chunks, queryId } = await get().internal_retrieveChunks( + const { chunks, queryId, rewriteQuery } = await get().internal_retrieveChunks( userMessageId, params?.ragQuery, // should skip the last content @@ -281,19 +282,21 @@ export const generateAIChat: StateCreator< ragQueryId = queryId; + const lastMsg = messages.pop() as ChatMessage; + // 2. build the retrieve context messages - const retrieveContext = chainAnswerWithContext({ - context: chunks.map((c) => c.text as string), - question: params?.ragQuery, - knowledge: getAgentKnowledge().map((knowledge) => knowledge.name), + const knowledgeBaseQAContext = knowledgeBaseQAPrompts({ + chunks, + userQuery: lastMsg.content, + rewriteQuery, + knowledge: getAgentKnowledge(), }); // 3. add the retrieve context messages to the messages history - if (retrieveContext.messages && retrieveContext.messages?.length > 0) { - // remove the last message due to the query is in the retrieveContext - messages.pop(); - retrieveContext.messages?.forEach((m) => messages.push(m as ChatMessage)); - } + messages.push({ + ...lastMsg, + content: (lastMsg.content + '\n\n' + knowledgeBaseQAContext).trim(), + }); fileChunks = chunks.map((c) => ({ id: c.id, similarity: c.similarity })); } @@ -499,7 +502,7 @@ export const generateAIChat: StateCreator< await internal_coreProcessMessage(contextMessages, latestMsg.id, { traceId, - ragQuery: get().internal_shouldUseRAG() ? currentMessage.content : undefined, + ragQuery: get().internal_shouldUseRAG() ? latestMsg.content : undefined, }); }, diff --git a/src/store/chat/slices/aiChat/actions/rag.ts b/src/store/chat/slices/aiChat/actions/rag.ts index 012a21947f38..ac81d4b49fff 100644 --- a/src/store/chat/slices/aiChat/actions/rag.ts +++ b/src/store/chat/slices/aiChat/actions/rag.ts @@ -21,7 +21,7 @@ export interface ChatRAGAction { id: string, userQuery: string, messages: string[], - ) => Promise<{ chunks: ChatSemanticSearchChunk[]; queryId: string }>; + ) => Promise<{ chunks: ChatSemanticSearchChunk[]; queryId: string; rewriteQuery?: string }>; /** * Rewrite user content to better RAG query */ @@ -64,12 +64,11 @@ export const chatRag: StateCreator 0) { + if (!message?.ragQuery && messages.length > 0) { rewriteQuery = await get().internal_rewriteQuery(id, userQuery, messages); } @@ -79,13 +78,13 @@ export const chatRag: StateCreator { let rewriteQuery = content; diff --git a/src/types/chunk/index.ts b/src/types/chunk/index.ts index e6b34d0f2c26..a498c1b967b2 100644 --- a/src/types/chunk/index.ts +++ b/src/types/chunk/index.ts @@ -39,6 +39,8 @@ export interface FileChunk { } export interface SemanticSearchChunk { + fileId: string | null; + fileName: string | null; id: string; metadata: ChunkMetadata | null; pageNumber?: number | null;