|
|
const { v4: uuidv4 } = require("uuid"); |
|
|
const { DocumentManager } = require("../DocumentManager"); |
|
|
const { WorkspaceChats } = require("../../models/workspaceChats"); |
|
|
const { WorkspaceParsedFiles } = require("../../models/workspaceParsedFiles"); |
|
|
const { getVectorDbClass, getLLMProvider } = require("../helpers"); |
|
|
const { writeResponseChunk } = require("../helpers/chat/responses"); |
|
|
const { grepAgents } = require("./agents"); |
|
|
const { |
|
|
grepCommand, |
|
|
VALID_COMMANDS, |
|
|
chatPrompt, |
|
|
recentChatHistory, |
|
|
sourceIdentifier, |
|
|
} = require("./index"); |
|
|
|
|
|
const VALID_CHAT_MODE = ["chat", "query"]; |
|
|
|
|
|
async function streamChatWithWorkspace( |
|
|
response, |
|
|
workspace, |
|
|
message, |
|
|
chatMode = "chat", |
|
|
user = null, |
|
|
thread = null, |
|
|
attachments = [] |
|
|
) { |
|
|
const uuid = uuidv4(); |
|
|
const updatedMessage = await grepCommand(message, user); |
|
|
|
|
|
if (Object.keys(VALID_COMMANDS).includes(updatedMessage)) { |
|
|
const data = await VALID_COMMANDS[updatedMessage]( |
|
|
workspace, |
|
|
message, |
|
|
uuid, |
|
|
user, |
|
|
thread |
|
|
); |
|
|
writeResponseChunk(response, data); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const isAgentChat = await grepAgents({ |
|
|
uuid, |
|
|
response, |
|
|
message: updatedMessage, |
|
|
user, |
|
|
workspace, |
|
|
thread, |
|
|
}); |
|
|
if (isAgentChat) return; |
|
|
|
|
|
const LLMConnector = getLLMProvider({ |
|
|
provider: workspace?.chatProvider, |
|
|
model: workspace?.chatModel, |
|
|
}); |
|
|
const VectorDb = getVectorDbClass(); |
|
|
|
|
|
const messageLimit = workspace?.openAiHistory || 20; |
|
|
const hasVectorizedSpace = await VectorDb.hasNamespace(workspace.slug); |
|
|
const embeddingsCount = await VectorDb.namespaceCount(workspace.slug); |
|
|
|
|
|
|
|
|
|
|
|
if ((!hasVectorizedSpace || embeddingsCount === 0) && chatMode === "query") { |
|
|
const textResponse = |
|
|
workspace?.queryRefusalResponse ?? |
|
|
"There is no relevant information in this workspace to answer your query."; |
|
|
writeResponseChunk(response, { |
|
|
id: uuid, |
|
|
type: "textResponse", |
|
|
textResponse, |
|
|
sources: [], |
|
|
attachments, |
|
|
close: true, |
|
|
error: null, |
|
|
}); |
|
|
await WorkspaceChats.new({ |
|
|
workspaceId: workspace.id, |
|
|
prompt: message, |
|
|
response: { |
|
|
text: textResponse, |
|
|
sources: [], |
|
|
type: chatMode, |
|
|
attachments, |
|
|
}, |
|
|
threadId: thread?.id || null, |
|
|
include: false, |
|
|
user, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let completeText; |
|
|
let metrics = {}; |
|
|
let contextTexts = []; |
|
|
let sources = []; |
|
|
let pinnedDocIdentifiers = []; |
|
|
const { rawHistory, chatHistory } = await recentChatHistory({ |
|
|
user, |
|
|
workspace, |
|
|
thread, |
|
|
messageLimit, |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await new DocumentManager({ |
|
|
workspace, |
|
|
maxTokens: LLMConnector.promptWindowLimit(), |
|
|
}) |
|
|
.pinnedDocs() |
|
|
.then((pinnedDocs) => { |
|
|
pinnedDocs.forEach((doc) => { |
|
|
const { pageContent, ...metadata } = doc; |
|
|
pinnedDocIdentifiers.push(sourceIdentifier(doc)); |
|
|
contextTexts.push(doc.pageContent); |
|
|
sources.push({ |
|
|
text: |
|
|
pageContent.slice(0, 1_000) + |
|
|
"...continued on in source document...", |
|
|
...metadata, |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const parsedFiles = await WorkspaceParsedFiles.getContextFiles( |
|
|
workspace, |
|
|
thread || null, |
|
|
user || null |
|
|
); |
|
|
parsedFiles.forEach((doc) => { |
|
|
const { pageContent, ...metadata } = doc; |
|
|
contextTexts.push(doc.pageContent); |
|
|
sources.push({ |
|
|
text: |
|
|
pageContent.slice(0, 1_000) + "...continued on in source document...", |
|
|
...metadata, |
|
|
}); |
|
|
}); |
|
|
|
|
|
const vectorSearchResults = |
|
|
embeddingsCount !== 0 |
|
|
? await VectorDb.performSimilaritySearch({ |
|
|
namespace: workspace.slug, |
|
|
input: updatedMessage, |
|
|
LLMConnector, |
|
|
similarityThreshold: workspace?.similarityThreshold, |
|
|
topN: workspace?.topN, |
|
|
filterIdentifiers: pinnedDocIdentifiers, |
|
|
rerank: workspace?.vectorSearchMode === "rerank", |
|
|
}) |
|
|
: { |
|
|
contextTexts: [], |
|
|
sources: [], |
|
|
message: null, |
|
|
}; |
|
|
|
|
|
|
|
|
if (!!vectorSearchResults.message) { |
|
|
writeResponseChunk(response, { |
|
|
id: uuid, |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: vectorSearchResults.message, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
const { fillSourceWindow } = require("../helpers/chat"); |
|
|
const filledSources = fillSourceWindow({ |
|
|
nDocs: workspace?.topN || 4, |
|
|
searchResults: vectorSearchResults.sources, |
|
|
history: rawHistory, |
|
|
filterIdentifiers: pinnedDocIdentifiers, |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
contextTexts = [...contextTexts, ...filledSources.contextTexts]; |
|
|
sources = [...sources, ...vectorSearchResults.sources]; |
|
|
|
|
|
|
|
|
|
|
|
if (chatMode === "query" && contextTexts.length === 0) { |
|
|
const textResponse = |
|
|
workspace?.queryRefusalResponse ?? |
|
|
"There is no relevant information in this workspace to answer your query."; |
|
|
writeResponseChunk(response, { |
|
|
id: uuid, |
|
|
type: "textResponse", |
|
|
textResponse, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: null, |
|
|
}); |
|
|
|
|
|
await WorkspaceChats.new({ |
|
|
workspaceId: workspace.id, |
|
|
prompt: message, |
|
|
response: { |
|
|
text: textResponse, |
|
|
sources: [], |
|
|
type: chatMode, |
|
|
attachments, |
|
|
}, |
|
|
threadId: thread?.id || null, |
|
|
include: false, |
|
|
user, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const messages = await LLMConnector.compressMessages( |
|
|
{ |
|
|
systemPrompt: await chatPrompt(workspace, user), |
|
|
userPrompt: updatedMessage, |
|
|
contextTexts, |
|
|
chatHistory, |
|
|
attachments, |
|
|
}, |
|
|
rawHistory |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (LLMConnector.streamingEnabled() !== true) { |
|
|
console.log( |
|
|
`\x1b[31m[STREAMING DISABLED]\x1b[0m Streaming is not available for ${LLMConnector.constructor.name}. Will use regular chat method.` |
|
|
); |
|
|
const { textResponse, metrics: performanceMetrics } = |
|
|
await LLMConnector.getChatCompletion(messages, { |
|
|
temperature: workspace?.openAiTemp ?? LLMConnector.defaultTemp, |
|
|
}); |
|
|
|
|
|
completeText = textResponse; |
|
|
metrics = performanceMetrics; |
|
|
writeResponseChunk(response, { |
|
|
uuid, |
|
|
sources, |
|
|
type: "textResponseChunk", |
|
|
textResponse: completeText, |
|
|
close: true, |
|
|
error: false, |
|
|
metrics, |
|
|
}); |
|
|
} else { |
|
|
const stream = await LLMConnector.streamGetChatCompletion(messages, { |
|
|
temperature: workspace?.openAiTemp ?? LLMConnector.defaultTemp, |
|
|
}); |
|
|
completeText = await LLMConnector.handleStream(response, stream, { |
|
|
uuid, |
|
|
sources, |
|
|
}); |
|
|
metrics = stream.metrics; |
|
|
} |
|
|
|
|
|
if (completeText?.length > 0) { |
|
|
const { chat } = await WorkspaceChats.new({ |
|
|
workspaceId: workspace.id, |
|
|
prompt: message, |
|
|
response: { |
|
|
text: completeText, |
|
|
sources, |
|
|
type: chatMode, |
|
|
attachments, |
|
|
metrics, |
|
|
}, |
|
|
threadId: thread?.id || null, |
|
|
user, |
|
|
}); |
|
|
|
|
|
writeResponseChunk(response, { |
|
|
uuid, |
|
|
type: "finalizeResponseStream", |
|
|
close: true, |
|
|
error: false, |
|
|
chatId: chat.id, |
|
|
metrics, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
writeResponseChunk(response, { |
|
|
uuid, |
|
|
type: "finalizeResponseStream", |
|
|
close: true, |
|
|
error: false, |
|
|
metrics, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
module.exports = { |
|
|
VALID_CHAT_MODE, |
|
|
streamChatWithWorkspace, |
|
|
}; |
|
|
|