From fdc3add53cf12f4a4215b3c251e811d24de2196e Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Wed, 21 Aug 2024 15:25:47 -0700 Subject: [PATCH] Api session id support (#2158) * Refactor api endpoint chat handler to its own function remove legacy `chatWithWorkspace` and cleanup `index.js` * Add `sessionId` in dev API to partition chats logically statelessly --- server/endpoints/api/workspace/index.js | 12 ++++++---- server/endpoints/api/workspaceThread/index.js | 1 + server/endpoints/workspaceThreads.js | 1 + server/endpoints/workspaces.js | 1 + server/models/workspaceChats.js | 6 ++++- .../20240821215625_init/migration.sql | 2 ++ server/prisma/schema.prisma | 23 ++++++++++--------- server/swagger/openapi.json | 6 +++-- server/utils/agents/index.js | 1 + server/utils/chats/apiChatHandler.js | 13 ++++++++++- server/utils/chats/index.js | 2 ++ server/utils/helpers/chat/convertTo.js | 6 ++++- 12 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 server/prisma/migrations/20240821215625_init/migration.sql diff --git a/server/endpoints/api/workspace/index.js b/server/endpoints/api/workspace/index.js index 1fe9ad8dc..694baea98 100644 --- a/server/endpoints/api/workspace/index.js +++ b/server/endpoints/api/workspace/index.js @@ -550,7 +550,8 @@ function apiWorkspaceEndpoints(app) { "application/json": { example: { message: "What is AnythingLLM?", - mode: "query | chat" + mode: "query | chat", + sessionId: "identifier-to-partition-chats-by-external-id" } } } @@ -580,7 +581,7 @@ function apiWorkspaceEndpoints(app) { */ try { const { slug } = request.params; - const { message, mode = "query" } = reqBody(request); + const { message, mode = "query", sessionId = null } = reqBody(request); const workspace = await Workspace.get({ slug: String(slug) }); if (!workspace) { @@ -615,6 +616,7 @@ function apiWorkspaceEndpoints(app) { mode, user: null, thread: null, + sessionId: !!sessionId ? String(sessionId) : null, }); await Telemetry.sendTelemetry("sent_chat", { @@ -658,7 +660,8 @@ function apiWorkspaceEndpoints(app) { "application/json": { example: { message: "What is AnythingLLM?", - mode: "query | chat" + mode: "query | chat", + sessionId: "identifier-to-partition-chats-by-external-id" } } } @@ -706,7 +709,7 @@ function apiWorkspaceEndpoints(app) { */ try { const { slug } = request.params; - const { message, mode = "query" } = reqBody(request); + const { message, mode = "query", sessionId = null } = reqBody(request); const workspace = await Workspace.get({ slug: String(slug) }); if (!workspace) { @@ -748,6 +751,7 @@ function apiWorkspaceEndpoints(app) { mode, user: null, thread: null, + sessionId: !!sessionId ? String(sessionId) : null, }); await Telemetry.sendTelemetry("sent_chat", { LLMSelection: diff --git a/server/endpoints/api/workspaceThread/index.js b/server/endpoints/api/workspaceThread/index.js index cdc4d598c..f8552d73c 100644 --- a/server/endpoints/api/workspaceThread/index.js +++ b/server/endpoints/api/workspaceThread/index.js @@ -299,6 +299,7 @@ function apiWorkspaceThreadEndpoints(app) { { workspaceId: workspace.id, thread_id: thread.id, + api_session_id: null, // Do not include API session chats. include: true, }, null, diff --git a/server/endpoints/workspaceThreads.js b/server/endpoints/workspaceThreads.js index 4e071992b..426503963 100644 --- a/server/endpoints/workspaceThreads.js +++ b/server/endpoints/workspaceThreads.js @@ -138,6 +138,7 @@ function workspaceThreadEndpoints(app) { workspaceId: workspace.id, user_id: user?.id || null, thread_id: thread.id, + api_session_id: null, // Do not include API session chats. include: true, }, null, diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index 43b093679..6a6df1934 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -793,6 +793,7 @@ function workspaceEndpoints(app) { user_id: user?.id, include: true, // only duplicate visible chats thread_id: threadId, + api_session_id: null, // Do not include API session chats. id: { lte: Number(chatId) }, }, null, diff --git a/server/models/workspaceChats.js b/server/models/workspaceChats.js index 52d96c400..ef474c4ef 100644 --- a/server/models/workspaceChats.js +++ b/server/models/workspaceChats.js @@ -8,6 +8,7 @@ const WorkspaceChats = { user = null, threadId = null, include = true, + apiSessionId = null, }) { try { const chat = await prisma.workspace_chats.create({ @@ -17,6 +18,7 @@ const WorkspaceChats = { response: JSON.stringify(response), user_id: user?.id || null, thread_id: threadId, + api_session_id: apiSessionId, include, }, }); @@ -40,6 +42,7 @@ const WorkspaceChats = { workspaceId, user_id: userId, thread_id: null, // this function is now only used for the default thread on workspaces and users + api_session_id: null, // do not include api-session chats in the frontend for anyone. include: true, }, ...(limit !== null ? { take: limit } : {}), @@ -63,6 +66,7 @@ const WorkspaceChats = { where: { workspaceId, thread_id: null, // this function is now only used for the default thread on workspaces + api_session_id: null, // do not include api-session chats in the frontend for anyone. include: true, }, ...(limit !== null ? { take: limit } : {}), @@ -196,7 +200,7 @@ const WorkspaceChats = { const user = res.user_id ? await User.get({ id: res.user_id }) : null; res.user = user ? { username: user.username } - : { username: "unknown user" }; + : { username: res.api_session_id !== null ? "API" : "unknown user" }; } return results; diff --git a/server/prisma/migrations/20240821215625_init/migration.sql b/server/prisma/migrations/20240821215625_init/migration.sql new file mode 100644 index 000000000..35bce1b30 --- /dev/null +++ b/server/prisma/migrations/20240821215625_init/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "workspace_chats" ADD COLUMN "api_session_id" TEXT; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index f385e66f4..b45e29119 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -170,17 +170,18 @@ model workspace_suggested_messages { } model workspace_chats { - id Int @id @default(autoincrement()) - workspaceId Int - prompt String - response String - include Boolean @default(true) - user_id Int? - thread_id Int? // No relation to prevent whole table migration - createdAt DateTime @default(now()) - lastUpdatedAt DateTime @default(now()) - feedbackScore Boolean? - users users? @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade) + id Int @id @default(autoincrement()) + workspaceId Int + prompt String + response String + include Boolean @default(true) + user_id Int? + thread_id Int? // No relation to prevent whole table migration + api_session_id String? // String identifier for only the dev API to parition chats in any mode. + createdAt DateTime @default(now()) + lastUpdatedAt DateTime @default(now()) + feedbackScore Boolean? + users users? @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Cascade) } model workspace_agent_invocations { diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index ef4144922..078e38a88 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -1972,7 +1972,8 @@ "application/json": { "example": { "message": "What is AnythingLLM?", - "mode": "query | chat" + "mode": "query | chat", + "sessionId": "identifier-to-partition-chats-by-external-id" } } } @@ -2064,7 +2065,8 @@ "application/json": { "example": { "message": "What is AnythingLLM?", - "mode": "query | chat" + "mode": "query | chat", + "sessionId": "identifier-to-partition-chats-by-external-id" } } } diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index f6c9b3e85..b0654eae1 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -42,6 +42,7 @@ class AgentHandler { workspaceId: this.invocation.workspace_id, user_id: this.invocation.user_id || null, thread_id: this.invocation.thread_id || null, + api_session_id: null, include: true, }, limit, diff --git a/server/utils/chats/apiChatHandler.js b/server/utils/chats/apiChatHandler.js index a52e2da14..bce341bac 100644 --- a/server/utils/chats/apiChatHandler.js +++ b/server/utils/chats/apiChatHandler.js @@ -23,6 +23,7 @@ const { chatPrompt, sourceIdentifier, recentChatHistory } = require("./index"); * mode: "chat"|"query", * user: import("@prisma/client").users|null, * thread: import("@prisma/client").workspace_threads|null, + * sessionId: string|null, * }} parameters * @returns {Promise} */ @@ -32,6 +33,7 @@ async function chatSync({ mode = "chat", user = null, thread = null, + sessionId = null, }) { const uuid = uuidv4(); const chatMode = mode ?? "chat"; @@ -60,6 +62,7 @@ async function chatSync({ type: chatMode, }, include: false, + apiSessionId: sessionId, }); return { @@ -83,7 +86,7 @@ async function chatSync({ workspace, thread, messageLimit, - chatMode, + apiSessionId: sessionId, }); await new DocumentManager({ @@ -168,6 +171,7 @@ async function chatSync({ }, threadId: thread?.id || null, include: false, + apiSessionId: sessionId, user, }); @@ -214,6 +218,7 @@ async function chatSync({ prompt: message, response: { text: textResponse, sources, type: chatMode }, threadId: thread?.id || null, + apiSessionId: sessionId, user, }); @@ -237,6 +242,7 @@ async function chatSync({ * mode: "chat"|"query", * user: import("@prisma/client").users|null, * thread: import("@prisma/client").workspace_threads|null, + * sessionId: string|null, * }} parameters * @returns {Promise} */ @@ -247,6 +253,7 @@ async function streamChat({ mode = "chat", user = null, thread = null, + sessionId = null, }) { const uuid = uuidv4(); const chatMode = mode ?? "chat"; @@ -285,6 +292,7 @@ async function streamChat({ attachments: [], }, threadId: thread?.id || null, + apiSessionId: sessionId, include: false, user, }); @@ -303,6 +311,7 @@ async function streamChat({ workspace, thread, messageLimit, + apiSessionId: sessionId, }); // Look for pinned documents and see if the user decided to use this feature. We will also do a vector search @@ -402,6 +411,7 @@ async function streamChat({ attachments: [], }, threadId: thread?.id || null, + apiSessionId: sessionId, include: false, user, }); @@ -453,6 +463,7 @@ async function streamChat({ prompt: message, response: { text: completeText, sources, type: chatMode }, threadId: thread?.id || null, + apiSessionId: sessionId, user, }); diff --git a/server/utils/chats/index.js b/server/utils/chats/index.js index 17fbd1569..387b70ce7 100644 --- a/server/utils/chats/index.js +++ b/server/utils/chats/index.js @@ -37,6 +37,7 @@ async function recentChatHistory({ workspace, thread = null, messageLimit = 20, + apiSessionId = null, }) { const rawHistory = ( await WorkspaceChats.where( @@ -44,6 +45,7 @@ async function recentChatHistory({ workspaceId: workspace.id, user_id: user?.id || null, thread_id: thread?.id || null, + api_session_id: apiSessionId || null, include: true, }, messageLimit, diff --git a/server/utils/helpers/chat/convertTo.js b/server/utils/helpers/chat/convertTo.js index 962cdc4b6..a1c0a1bcb 100644 --- a/server/utils/helpers/chat/convertTo.js +++ b/server/utils/helpers/chat/convertTo.js @@ -50,7 +50,11 @@ async function prepareWorkspaceChatsForExport(format = "jsonl") { const responseJson = JSON.parse(chat.response); return { id: chat.id, - username: chat.user ? chat.user.username : "unknown user", + username: chat.user + ? chat.user.username + : chat.api_session_id !== null + ? "API" + : "unknown user", workspace: chat.workspace ? chat.workspace.name : "unknown workspace", prompt: chat.prompt, response: responseJson.text,