From 6c3d44807791a7099ef1ec5191407e18f44bc809 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Thu, 12 Oct 2023 19:59:56 +0530 Subject: [PATCH 1/3] use session to protect public bots --- app/ui/src/services/api.ts | 75 ++--- docker/.env | 6 +- server/package.json | 1 + server/prisma/migrations/q_14/migration.sql | 8 + server/prisma/schema.prisma | 14 +- server/src/app.ts | 27 +- server/src/routes/bot/handlers/get.handler.ts | 5 +- .../src/routes/bot/handlers/post.handler.ts | 262 ++++++++++++------ server/src/utils/session.ts | 21 ++ server/yarn.lock | 8 + 10 files changed, 287 insertions(+), 140 deletions(-) create mode 100644 server/prisma/migrations/q_14/migration.sql create mode 100644 server/src/utils/session.ts diff --git a/app/ui/src/services/api.ts b/app/ui/src/services/api.ts index 3c7e5e65..3c7f9715 100644 --- a/app/ui/src/services/api.ts +++ b/app/ui/src/services/api.ts @@ -1,52 +1,53 @@ -import axios from 'axios'; -import { getToken } from './cookie'; +import axios from "axios"; +import { getToken } from "./cookie"; -export const baseURL = import.meta.env.VITE_API_URL || '/api/v1'; +export const baseURL = import.meta.env.VITE_API_URL || "/api/v1"; const instance = axios.create({ - baseURL, - headers: { - "Content-Type": "application/json", - }, + baseURL, + headers: { + "Content-Type": "application/json", + }, }); instance.interceptors.request.use( - (config) => { - const token = getToken() - if (token) { - config.headers!.Authorization = `Bearer ${token}`; - } - return config; - }, - (error) => { - return Promise.reject(error); + (config) => { + const token = getToken(); + if (token) { + config.headers!.Authorization = `Bearer ${token}`; } + return config; + }, + (error) => { + return Promise.reject(error); + }, ); instance.interceptors.response.use( - (res) => { - return res; - }, - async (err) => { - const originalConfig = err.config; - if (err.response) { - if (err.response.status === 401 && !originalConfig._retry) { - originalConfig._retry = true; - try { - return instance(originalConfig); - } catch (_error) { - - return Promise.reject(_error); - } - } - - if (err.response.status === 403 && err.response.data) { - return Promise.reject(err.response.data); - } + (res) => { + return res; + }, + async (err) => { + const originalConfig = err.config; + if (err.response) { + if (err.response.status === 401 && !originalConfig._retry) { + originalConfig._retry = true; + try { + return instance(originalConfig); + } catch (_error) { + return Promise.reject(_error); } + } else if (err.response.status === 401 && originalConfig._retry) { + window.location.href = "/#/login"; + } - return Promise.reject(err); + if (err.response.status === 403 && err.response.data) { + return Promise.reject(err.response.data); + } } + + return Promise.reject(err); + }, ); -export default instance; \ No newline at end of file +export default instance; diff --git a/docker/.env b/docker/.env index 535c0a78..8d522af2 100644 --- a/docker/.env +++ b/docker/.env @@ -16,4 +16,8 @@ GOOGLE_API_KEY="" # Eleven labs API Key -> https://elevenlabs.io/ ELEVENLABS_API_KEY="" # Dialoqbase Q Concurency -DB_QUEUE_CONCURRENCY=1 \ No newline at end of file +DB_QUEUE_CONCURRENCY=1 +# Dialoqbase Session Secret +DB_SESSION_SECRET="super-secret-key" +# Dialoqbase Session Secure +DB_SESSION_SECURE="false" diff --git a/server/package.json b/server/package.json index 0557e20e..5a59620b 100644 --- a/server/package.json +++ b/server/package.json @@ -23,6 +23,7 @@ "license": "MIT", "dependencies": { "@fastify/autoload": "^5.0.0", + "@fastify/cookie": "^9.1.0", "@fastify/cors": "^8.3.0", "@fastify/jwt": "^7.0.0", "@fastify/multipart": "^7.6.0", diff --git a/server/prisma/migrations/q_14/migration.sql b/server/prisma/migrations/q_14/migration.sql new file mode 100644 index 00000000..5549442a --- /dev/null +++ b/server/prisma/migrations/q_14/migration.sql @@ -0,0 +1,8 @@ +-- AlterTable +ALTER TABLE "Bot" ADD COLUMN "options" JSON DEFAULT '{}', +ADD COLUMN "use_rag" BOOLEAN NOT NULL DEFAULT false; + +ALTER TABLE "Bot" ADD COLUMN "bot_protect" BOOLEAN NOT NULL DEFAULT false; + + +ALTER TABLE "Bot" ADD COLUMN "bot_api_key" TEXT NULL DEFAULT NULL; \ No newline at end of file diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index ae3a4a7a..380fcc48 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -31,6 +31,10 @@ model Bot { text_to_voice_type_metadata Json @default("{}") @db.Json use_hybrid_search Boolean @default(false) haveDataSourcesBeenAdded Boolean @default(false) + use_rag Boolean @default(false) + bot_protect Boolean @default(false) + bot_api_key String? + options Json? @default("{}") @db.Json BotAppearance BotAppearance[] document BotDocument[] BotIntegration BotIntegration[] @@ -103,12 +107,12 @@ model BotIntegration { } model BotTelegramHistory { - id Int @id @default(autoincrement()) - chat_id Int? + id Int @id @default(autoincrement()) + chat_id Int? new_chat_id String? - identifier String? - human String? - bot String? + identifier String? + human String? + bot String? } model BotDiscordHistory { diff --git a/server/src/app.ts b/server/src/app.ts index 85ec4a2f..bae34591 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -5,6 +5,15 @@ import cors from "@fastify/cors"; import fastifyStatic from "@fastify/static"; import fastifyMultipart from "@fastify/multipart"; import { FastifySSEPlugin } from "@waylaidwanderer/fastify-sse-v2"; +import fastifyCookie from "@fastify/cookie"; +import fastifySession from "@fastify/session"; +import { getSessionSecret, isCookieSecure } from "./utils/session"; + +declare module "fastify" { + interface Session { + is_bot_allowed: boolean; + } +} export type AppOptions = {} & Partial; @@ -40,13 +49,21 @@ const app: FastifyPluginAsync = async ( preCompressed: true, }); - await fastify.register(import('fastify-raw-body'), { - field: 'rawBody', // change the default request.rawBody property name + fastify.register(fastifyCookie); + fastify.register(fastifySession, { + secret: getSessionSecret(), + cookie: { + secure: isCookieSecure(), + }, + }); + + await fastify.register(import("fastify-raw-body"), { + field: "rawBody", // change the default request.rawBody property name global: false, // add the rawBody to every request. **Default true** - encoding: 'utf8', // set it to false to set rawBody as a Buffer **Default utf8** + encoding: "utf8", // set it to false to set rawBody as a Buffer **Default utf8** runFirst: true, // get the body before any preParsing hook change/uncompress it. **Default false** - routes: [] // array of routes, **`global`** will be ignored, wildcard routes not supported - }) + routes: [], // array of routes, **`global`** will be ignored, wildcard routes not supported + }); }; export default app; diff --git a/server/src/routes/bot/handlers/get.handler.ts b/server/src/routes/bot/handlers/get.handler.ts index b5ee1dab..07e19b2c 100644 --- a/server/src/routes/bot/handlers/get.handler.ts +++ b/server/src/routes/bot/handlers/get.handler.ts @@ -1,6 +1,5 @@ import { FastifyReply, FastifyRequest } from "fastify"; -import { ChatStyleRequest } from "./types"; - +import { ChatStyleRequest } from "./types"; export const getChatStyleByIdHandler = async ( request: FastifyRequest, @@ -51,7 +50,7 @@ export const getChatStyleByIdHandler = async ( }, }; } - + request.session.is_bot_allowed = true; return { data: { background_color: "#ffffff", diff --git a/server/src/routes/bot/handlers/post.handler.ts b/server/src/routes/bot/handlers/post.handler.ts index 5fcbe6fb..ffe3ee7b 100644 --- a/server/src/routes/bot/handlers/post.handler.ts +++ b/server/src/routes/bot/handlers/post.handler.ts @@ -9,22 +9,134 @@ export const chatRequestHandler = async ( request: FastifyRequest, reply: FastifyReply, ) => { - const public_id = request.params.id; - const { message, history, history_id } = request.body; + try { + const public_id = request.params.id; - const prisma = request.server.prisma; + const prisma = request.server.prisma; - const bot = await prisma.bot.findFirst({ - where: { - publicId: public_id, - }, - }); + const bot = await prisma.bot.findFirst({ + where: { + publicId: public_id, + }, + }); + + if (!bot) { + return { + bot: { + text: "You are in the wrong place, buddy.", + sourceDocuments: [], + }, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "You are in the wrong place, buddy.", + }, + ], + }; + } - if (!bot) { + if (bot.bot_protect) { + if (!request.session.get("is_bot_allowed")) { + return { + bot: { + text: "You are not allowed to chat with this bot.", + sourceDocuments: [], + }, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "You are not allowed to chat with this bot.", + }, + ], + }; + } + } + + const temperature = bot.temperature; + + const sanitizedQuestion = message.trim().replaceAll("\n", " "); + const embeddingModel = embeddings(bot.embedding); + + const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( + embeddingModel, + { + botId: bot.id, + sourceId: null, + }, + ); + + const model = chatModelProvider(bot.provider, bot.model, temperature); + + const chain = ConversationalRetrievalQAChain.fromLLM( + model, + vectorstore.asRetriever(), + { + qaTemplate: bot.qaPrompt, + questionGeneratorTemplate: bot.questionGeneratorPrompt, + returnSourceDocuments: true, + }, + ); + + const chat_history = history + .map((chatMessage: any) => { + if (chatMessage.type === "human") { + return `Human: ${chatMessage.text}`; + } else if (chatMessage.type === "ai") { + return `Assistant: ${chatMessage.text}`; + } else { + return `${chatMessage.text}`; + } + }) + .join("\n"); + + const response = await chain.call({ + question: sanitizedQuestion, + chat_history: chat_history, + }); + + await prisma.botWebHistory.create({ + data: { + chat_id: history_id, + bot_id: bot.id, + bot: response.text, + human: message, + metadata: { + ip: request?.ip, + user_agent: request?.headers["user-agent"], + }, + sources: response?.sources, + }, + }); + + return { + bot: response, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.text, + }, + ], + }; + } catch (e) { return { bot: { - text: "You are in the wrong place, buddy.", + text: "There was an error processing your request.", sourceDocuments: [], }, history: [ @@ -35,84 +147,11 @@ export const chatRequestHandler = async ( }, { type: "ai", - text: "You are in the wrong place, buddy.", + text: "There was an error processing your request.", }, ], }; } - - const temperature = bot.temperature; - - const sanitizedQuestion = message.trim().replaceAll("\n", " "); - const embeddingModel = embeddings(bot.embedding); - - const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( - embeddingModel, - { - botId: bot.id, - sourceId: null, - }, - ); - - const model = chatModelProvider(bot.provider, bot.model, temperature); - - const chain = ConversationalRetrievalQAChain.fromLLM( - model, - vectorstore.asRetriever(), - { - qaTemplate: bot.qaPrompt, - questionGeneratorTemplate: bot.questionGeneratorPrompt, - returnSourceDocuments: true, - }, - ); - - const chat_history = history - .map((chatMessage: any) => { - if (chatMessage.type === "human") { - return `Human: ${chatMessage.text}`; - } else if (chatMessage.type === "ai") { - return `Assistant: ${chatMessage.text}`; - } else { - return `${chatMessage.text}`; - } - }) - .join("\n"); - - console.log(chat_history); - - const response = await chain.call({ - question: sanitizedQuestion, - chat_history: chat_history, - }); - - await prisma.botWebHistory.create({ - data: { - chat_id: history_id, - bot_id: bot.id, - bot: response.text, - human: message, - metadata: { - ip: request?.ip, - user_agent: request?.headers["user-agent"], - }, - sources: response?.sources, - }, - }); - - return { - bot: response, - history: [ - ...history, - { - type: "human", - text: message, - }, - { - type: "ai", - text: response.text, - }, - ], - }; }; function nextTick() { @@ -123,19 +162,19 @@ export const chatRequestStreamHandler = async ( request: FastifyRequest, reply: FastifyReply, ) => { + const { message, history, history_id } = request.body; + try { const public_id = request.params.id; // get user meta info from request // const meta = request.headers["user-agent"]; // ip address - const { message, history, history_id } = request.body; // const history = JSON.parse(chatHistory) as { // type: string; // text: string; // }[]; - console.log("history", history); const prisma = request.server.prisma; const bot = await prisma.bot.findFirst({ @@ -164,6 +203,33 @@ export const chatRequestStreamHandler = async ( }; } + if (bot.bot_protect) { + if (!request.session.get("is_bot_allowed")) { + console.log("not allowed"); + return reply.sse({ + event: "result", + id: "", + data: JSON.stringify({ + bot: { + text: "You are not allowed to chat with this bot.", + sourceDocuments: [], + }, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "You are not allowed to chat with this bot.", + }, + ], + }), + }); + } + } + const temperature = bot.temperature; const sanitizedQuestion = message.trim().replaceAll("\n", " "); @@ -243,13 +309,11 @@ export const chatRequestStreamHandler = async ( console.log("Waiting for response..."); - response = await chain.call({ question: sanitizedQuestion, chat_history: chat_history, }); - await prisma.botWebHistory.create({ data: { chat_id: history_id, @@ -286,6 +350,26 @@ export const chatRequestStreamHandler = async ( return reply.raw.end(); } catch (e) { console.log(e); - return reply.status(500).send(e); + return reply.sse({ + event: "result", + id: "", + data: JSON.stringify({ + bot: { + text: "There was an error processing your request.", + sourceDocuments: [], + }, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "There was an error processing your request.", + }, + ], + }), + }); } }; diff --git a/server/src/utils/session.ts b/server/src/utils/session.ts new file mode 100644 index 00000000..aa4b8427 --- /dev/null +++ b/server/src/utils/session.ts @@ -0,0 +1,21 @@ +export const getSessionSecret = () => { + if (!process.env.DB_SESSION_SECRET) { + return "a8F2h6T9j4Kl0Pz8W7eX3rB5y1VcQ6mN"; + } + + if (process.env.DB_SESSION_SECRET.length < 32) { + console.warn("WARNING: Session secret should be 32 characters long."); + } + + return process.env.DB_SESSION_SECRET; +}; + + + +export const isCookieSecure = () => { + if (!process.env.DB_SESSION_SECURE) { + return false; + } + + return process.env.DB_SESSION_SECURE === "true"; +} \ No newline at end of file diff --git a/server/yarn.lock b/server/yarn.lock index 25b1564c..c6801011 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -373,6 +373,14 @@ dependencies: text-decoding "^1.0.0" +"@fastify/cookie@^9.1.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@fastify/cookie/-/cookie-9.1.0.tgz#b9ca95fcb934a21915ab6f228a63dd73013738df" + integrity sha512-w/LlQjj7cmYlQNhEKNm4jQoLkFXCL73kFu1Jy3aL7IFbYEojEKur0f7ieCKUxBBaU65tpaWC83UM8xW7AzY6uw== + dependencies: + cookie "^0.5.0" + fastify-plugin "^4.0.0" + "@fastify/cors@^8.3.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-8.3.0.tgz#f03d745731b770793a1a15344da7220ca0d19619" From 3d378735733a955d211401c5f72b62584eb89553 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Thu, 12 Oct 2023 20:37:32 +0530 Subject: [PATCH 2/3] enable disable --- app/ui/src/@types/bot.ts | 15 + .../components/Bot/Settings/SettingsCard.tsx | 29 +- app/ui/src/routes/bot/settings.tsx | 16 +- .../src/routes/api/v1/bot/handlers/types.ts | 2 + .../bot/playground/handlers/post.handler.ts | 566 ++++++++++-------- server/src/routes/api/v1/bot/schema/index.ts | 8 +- .../src/routes/bot/handlers/post.handler.ts | 17 +- 7 files changed, 360 insertions(+), 293 deletions(-) create mode 100644 app/ui/src/@types/bot.ts diff --git a/app/ui/src/@types/bot.ts b/app/ui/src/@types/bot.ts new file mode 100644 index 00000000..8dda624e --- /dev/null +++ b/app/ui/src/@types/bot.ts @@ -0,0 +1,15 @@ +export type BotSettings = { + id: string; + name: string; + model: string; + public_id: string; + temperature: number; + embedding: string; + qaPrompt: string; + questionGeneratorPrompt: string; + streaming: boolean; + showRef: boolean; + use_hybrid_search: boolean; + bot_protect: boolean; + use_rag: boolean; +}; diff --git a/app/ui/src/components/Bot/Settings/SettingsCard.tsx b/app/ui/src/components/Bot/Settings/SettingsCard.tsx index 8eeaf6da..a7f1d37d 100644 --- a/app/ui/src/components/Bot/Settings/SettingsCard.tsx +++ b/app/ui/src/components/Bot/Settings/SettingsCard.tsx @@ -13,24 +13,9 @@ import { HELPFUL_ASSISTANT_WITH_CONTEXT_PROMPT, HELPFUL_ASSISTANT_WITHOUT_CONTEXT_PROMPT, } from "../../../utils/prompts"; +import { BotSettings } from "../../../@types/bot"; -export const SettingsCard = ({ - data, -}: { - data: { - id: string; - name: string; - model: string; - public_id: string; - temperature: number; - embedding: string; - qaPrompt: string; - questionGeneratorPrompt: string; - streaming: boolean; - showRef: boolean; - use_hybrid_search: boolean; - }; -}) => { +export const SettingsCard = ({ data }: { data: BotSettings }) => { const [form] = Form.useForm(); const [disableStreaming, setDisableStreaming] = React.useState(false); const params = useParams<{ id: string }>(); @@ -133,6 +118,7 @@ export const SettingsCard = ({ streaming: data.streaming, showRef: data.showRef, use_hybrid_search: data.use_hybrid_search, + bot_protect: data.bot_protect, }} form={form} requiredMark={false} @@ -317,6 +303,15 @@ export const SettingsCard = ({ > + + + + diff --git a/app/ui/src/routes/bot/settings.tsx b/app/ui/src/routes/bot/settings.tsx index 3eb65b57..748d0730 100644 --- a/app/ui/src/routes/bot/settings.tsx +++ b/app/ui/src/routes/bot/settings.tsx @@ -4,6 +4,7 @@ import api from "../../services/api"; import React from "react"; import { SkeletonLoading } from "../../components/Common/SkeletonLoading"; import { SettingsCard } from "../../components/Bot/Settings/SettingsCard"; +import { BotSettings } from "../../@types/bot"; export default function BotSettingsRoot() { const param = useParams<{ id: string }>(); @@ -14,19 +15,7 @@ export default function BotSettingsRoot() { async () => { const response = await api.get(`/bot/${param.id}`); return response.data as { - data: { - id: string; - name: string; - model: string; - public_id: string; - temperature: number; - embedding: string; - qaPrompt: string; - questionGeneratorPrompt: string; - streaming: boolean; - showRef: boolean; - use_hybrid_search: boolean; - }; + data: BotSettings }; }, { @@ -41,7 +30,6 @@ export default function BotSettingsRoot() { }, [status]); return (
- {status === "loading" && } {status === "success" && }
diff --git a/server/src/routes/api/v1/bot/handlers/types.ts b/server/src/routes/api/v1/bot/handlers/types.ts index 741dc27f..90722df0 100644 --- a/server/src/routes/api/v1/bot/handlers/types.ts +++ b/server/src/routes/api/v1/bot/handlers/types.ts @@ -64,5 +64,7 @@ export interface UpdateBotById { streaming: boolean; showRef: boolean; use_hybrid_search: boolean; + bot_protect: boolean; + use_rag: boolean; }; } diff --git a/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts b/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts index 733ec12f..fea1c419 100644 --- a/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts +++ b/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts @@ -14,135 +14,154 @@ export const chatRequestHandler = async ( const bot_id = request.params.id; const { message, history, history_id } = request.body; + try { + const prisma = request.server.prisma; - const prisma = request.server.prisma; - - const bot = await prisma.bot.findFirst({ - where: { - id: bot_id, - user_id: request.user.user_id, - }, - }); - - if (!bot) { - return { - bot: { - text: "You are in the wrong place, buddy.", - sourceDocuments: [], + const bot = await prisma.bot.findFirst({ + where: { + id: bot_id, + user_id: request.user.user_id, }, - history: [ - ...history, - { - type: "human", - text: message, - }, - { - type: "ai", + }); + + if (!bot) { + return { + bot: { text: "You are in the wrong place, buddy.", + sourceDocuments: [], }, - ], - }; - } + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "You are in the wrong place, buddy.", + }, + ], + }; + } - const temperature = bot.temperature; + const temperature = bot.temperature; - const sanitizedQuestion = message.trim().replaceAll("\n", " "); - const embeddingModel = embeddings(bot.embedding); + const sanitizedQuestion = message.trim().replaceAll("\n", " "); + const embeddingModel = embeddings(bot.embedding); - let retriever: BaseRetriever; + let retriever: BaseRetriever; - if (bot.use_hybrid_search) { - retriever = new DialoqbaseHybridRetrival(embeddingModel, { - botId: bot.id, - sourceId: null, - }); - } else { - const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( - embeddingModel, - { + if (bot.use_hybrid_search) { + retriever = new DialoqbaseHybridRetrival(embeddingModel, { botId: bot.id, sourceId: null, - }, - ); - - retriever = vectorstore.asRetriever(); - } - - const model = chatModelProvider(bot.provider, bot.model, temperature); + }); + } else { + const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( + embeddingModel, + { + botId: bot.id, + sourceId: null, + }, + ); - const chain = ConversationalRetrievalQAChain.fromLLM( - model, - retriever, - { - qaTemplate: bot.qaPrompt, - questionGeneratorTemplate: bot.questionGeneratorPrompt, - returnSourceDocuments: true, - }, - ); - - const chat_history = history - .map((chatMessage: any) => { - if (chatMessage.type === "human") { - return `Human: ${chatMessage.text}`; - } else if (chatMessage.type === "ai") { - return `Assistant: ${chatMessage.text}`; - } else { - return `${chatMessage.text}`; - } - }) - .join("\n"); - - console.log(chat_history); - - const response = await chain.call({ - question: sanitizedQuestion, - chat_history: chat_history, - }); + retriever = vectorstore.asRetriever(); + } - let historyId = history_id; + const model = chatModelProvider(bot.provider, bot.model, temperature); - if (!historyId) { - const newHistory = await prisma.botPlayground.create({ - data: { - botId: bot.id, - title: message, + const chain = ConversationalRetrievalQAChain.fromLLM( + model, + retriever, + { + qaTemplate: bot.qaPrompt, + questionGeneratorTemplate: bot.questionGeneratorPrompt, + returnSourceDocuments: true, }, + ); + + const chat_history = history + .map((chatMessage: any) => { + if (chatMessage.type === "human") { + return `Human: ${chatMessage.text}`; + } else if (chatMessage.type === "ai") { + return `Assistant: ${chatMessage.text}`; + } else { + return `${chatMessage.text}`; + } + }) + .join("\n"); + + console.log(chat_history); + + const response = await chain.call({ + question: sanitizedQuestion, + chat_history: chat_history, }); - historyId = newHistory.id; - } - await prisma.botPlaygroundMessage.create({ - data: { - type: "human", - message: message, - botPlaygroundId: historyId, - }, - }); + let historyId = history_id; - await prisma.botPlaygroundMessage.create({ - data: { - type: "ai", - message: response.text, - botPlaygroundId: historyId, - isBot: true, - sources: response?.sourceDocuments, - }, - }); + if (!historyId) { + const newHistory = await prisma.botPlayground.create({ + data: { + botId: bot.id, + title: message, + }, + }); + historyId = newHistory.id; + } - return { - bot: response, - history: [ - ...history, - { + await prisma.botPlaygroundMessage.create({ + data: { type: "human", - text: message, + message: message, + botPlaygroundId: historyId, }, - { + }); + + await prisma.botPlaygroundMessage.create({ + data: { type: "ai", - text: response.text, + message: response.text, + botPlaygroundId: historyId, + isBot: true, + sources: response?.sourceDocuments, }, - ], - }; + }); + + return { + bot: response, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.text, + }, + ], + }; + } catch (e) { + return { + bot: { + text: "There was an error processing your request.", + sourceDocuments: [], + }, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "There was an error processing your request.", + }, + ], + }; + } }; function nextTick() { @@ -160,183 +179,212 @@ export const chatRequestStreamHandler = async ( // type: string; // text: string; // }[]; + try { + console.log("history", history); + const prisma = request.server.prisma; - console.log("history", history); - const prisma = request.server.prisma; - - const bot = await prisma.bot.findFirst({ - where: { - id: bot_id, - user_id: request.user.user_id, - }, - }); - - if (!bot) { - return { - bot: { - text: "You are in the wrong place, buddy.", - sourceDocuments: [], + const bot = await prisma.bot.findFirst({ + where: { + id: bot_id, + user_id: request.user.user_id, }, - history: [ - ...history, - { - type: "human", - text: message, - }, - { - type: "ai", + }); + + if (!bot) { + return { + bot: { text: "You are in the wrong place, buddy.", + sourceDocuments: [], }, - ], - }; - } + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "You are in the wrong place, buddy.", + }, + ], + }; + } - const temperature = bot.temperature; + const temperature = bot.temperature; - const sanitizedQuestion = message.trim().replaceAll("\n", " "); - const embeddingModel = embeddings(bot.embedding); + const sanitizedQuestion = message.trim().replaceAll("\n", " "); + const embeddingModel = embeddings(bot.embedding); - let retriever: BaseRetriever; + let retriever: BaseRetriever; - if (bot.use_hybrid_search) { - retriever = new DialoqbaseHybridRetrival(embeddingModel, { - botId: bot.id, - sourceId: null, - }); - } else { - const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( - embeddingModel, - { + if (bot.use_hybrid_search) { + retriever = new DialoqbaseHybridRetrival(embeddingModel, { botId: bot.id, sourceId: null, - }, - ); + }); + } else { + const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( + embeddingModel, + { + botId: bot.id, + sourceId: null, + }, + ); - retriever = vectorstore.asRetriever(); - } + retriever = vectorstore.asRetriever(); + } - let response: any = null; + let response: any = null; - reply.raw.on("close", () => { - console.log("closed"); - }); + reply.raw.on("close", () => { + console.log("closed"); + }); - const streamedModel = chatModelProvider( - bot.provider, - bot.model, - temperature, - { - streaming: true, - callbacks: [ - { - handleLLMNewToken(token: string) { - // if (token !== '[DONE]') { - // console.log(token); - return reply.sse({ - id: "", - event: "chunk", - data: JSON.stringify({ - message: token || "", - }), - }); - // } else { - // console.log("done"); - // } + const streamedModel = chatModelProvider( + bot.provider, + bot.model, + temperature, + { + streaming: true, + callbacks: [ + { + handleLLMNewToken(token: string) { + // if (token !== '[DONE]') { + // console.log(token); + return reply.sse({ + id: "", + event: "chunk", + data: JSON.stringify({ + message: token || "", + }), + }); + // } else { + // console.log("done"); + // } + }, }, + ], + }, + ); + + const nonStreamingModel = chatModelProvider( + bot.provider, + bot.model, + temperature, + ); + + const chain = ConversationalRetrievalQAChain.fromLLM( + streamedModel, + retriever, + { + qaTemplate: bot.qaPrompt, + questionGeneratorTemplate: bot.questionGeneratorPrompt, + returnSourceDocuments: true, + questionGeneratorChainOptions: { + llm: nonStreamingModel, }, - ], - }, - ); - - const nonStreamingModel = chatModelProvider( - bot.provider, - bot.model, - temperature, - ); - - const chain = ConversationalRetrievalQAChain.fromLLM( - streamedModel, - retriever, - { - qaTemplate: bot.qaPrompt, - questionGeneratorTemplate: bot.questionGeneratorPrompt, - returnSourceDocuments: true, - questionGeneratorChainOptions: { - llm: nonStreamingModel, }, - }, - ); - - const chat_history = history - .map((chatMessage: any) => { - if (chatMessage.type === "human") { - return `Human: ${chatMessage.text}`; - } else if (chatMessage.type === "ai") { - return `Assistant: ${chatMessage.text}`; - } else { - return `${chatMessage.text}`; - } - }) - .join("\n"); - - console.log("Waiting for response..."); - - response = await chain.call({ - question: sanitizedQuestion, - chat_history: chat_history, - }); + ); + + const chat_history = history + .map((chatMessage: any) => { + if (chatMessage.type === "human") { + return `Human: ${chatMessage.text}`; + } else if (chatMessage.type === "ai") { + return `Assistant: ${chatMessage.text}`; + } else { + return `${chatMessage.text}`; + } + }) + .join("\n"); + + console.log("Waiting for response..."); + + response = await chain.call({ + question: sanitizedQuestion, + chat_history: chat_history, + }); + + let historyId = history_id; - let historyId = history_id; + if (!historyId) { + const newHistory = await prisma.botPlayground.create({ + data: { + botId: bot.id, + title: message, + }, + }); + historyId = newHistory.id; + } - if (!historyId) { - const newHistory = await prisma.botPlayground.create({ + await prisma.botPlaygroundMessage.create({ data: { - botId: bot.id, - title: message, + type: "human", + message: message, + botPlaygroundId: historyId, }, }); - historyId = newHistory.id; - } - await prisma.botPlaygroundMessage.create({ - data: { - type: "human", - message: message, - botPlaygroundId: historyId, - }, - }); - - await prisma.botPlaygroundMessage.create({ - data: { - type: "ai", - message: response.text, - botPlaygroundId: historyId, - isBot: true, - sources: response?.sourceDocuments, - }, - }); + await prisma.botPlaygroundMessage.create({ + data: { + type: "ai", + message: response.text, + botPlaygroundId: historyId, + isBot: true, + sources: response?.sourceDocuments, + }, + }); - reply.sse({ - event: "result", - id: "", - data: JSON.stringify({ - bot: response, - history: [ - ...history, - { - type: "human", - text: message, - }, - { - type: "ai", - text: response.text, + reply.sse({ + event: "result", + id: "", + data: JSON.stringify({ + bot: response, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.text, + }, + ], + history_id: historyId, + }), + }); + await nextTick(); + return reply.raw.end(); + } catch (e) { + console.log(e); + reply.raw.setHeader("Content-Type", "text/event-stream"); + + reply.sse({ + event: "result", + id: "", + data: JSON.stringify({ + bot: { + text: "There was an error processing your request.", + sourceDocuments: [], }, - ], - history_id: historyId, - }), - }); - await nextTick(); - return reply.raw.end(); + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: "There was an error processing your request.", + }, + ], + }), + }); + await nextTick(); + + return reply.raw.end(); + } }; export const updateBotAudioSettingsHandler = async ( diff --git a/server/src/routes/api/v1/bot/schema/index.ts b/server/src/routes/api/v1/bot/schema/index.ts index 2d03f9c4..6170007f 100644 --- a/server/src/routes/api/v1/bot/schema/index.ts +++ b/server/src/routes/api/v1/bot/schema/index.ts @@ -122,7 +122,13 @@ export const updateBotByIdSchema: FastifySchema = { }, use_hybrid_search: { type: "boolean", - } + }, + bot_protect: { + type: "boolean", + }, + use_rag: { + type: "boolean", + }, }, }, }; diff --git a/server/src/routes/bot/handlers/post.handler.ts b/server/src/routes/bot/handlers/post.handler.ts index ffe3ee7b..6317f090 100644 --- a/server/src/routes/bot/handlers/post.handler.ts +++ b/server/src/routes/bot/handlers/post.handler.ts @@ -206,7 +206,10 @@ export const chatRequestStreamHandler = async ( if (bot.bot_protect) { if (!request.session.get("is_bot_allowed")) { console.log("not allowed"); - return reply.sse({ + + reply.raw.setHeader("Content-Type", "text/event-stream"); + + reply.sse({ event: "result", id: "", data: JSON.stringify({ @@ -227,6 +230,11 @@ export const chatRequestStreamHandler = async ( ], }), }); + + + await nextTick(); + + return reply.raw.end(); } } @@ -350,7 +358,9 @@ export const chatRequestStreamHandler = async ( return reply.raw.end(); } catch (e) { console.log(e); - return reply.sse({ + reply.raw.setHeader("Content-Type", "text/event-stream"); + + reply.sse({ event: "result", id: "", data: JSON.stringify({ @@ -371,5 +381,8 @@ export const chatRequestStreamHandler = async ( ], }), }); + await nextTick(); + + return reply.raw.end(); } }; From 1b88dd48422fbcf05bf03b30df7d83e7f4698402 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Thu, 12 Oct 2023 23:54:55 +0530 Subject: [PATCH 3/3] add suspport for rag on playground ui --- app/ui/package.json | 2 +- .../components/Bot/Settings/SettingsCard.tsx | 9 + package.json | 2 +- .../bot/playground/handlers/post.handler.ts | 532 +++++++++++++----- 4 files changed, 402 insertions(+), 143 deletions(-) diff --git a/app/ui/package.json b/app/ui/package.json index 28cd2bd0..04ec6130 100644 --- a/app/ui/package.json +++ b/app/ui/package.json @@ -1,7 +1,7 @@ { "name": "app", "private": true, - "version": "1.0.4", + "version": "1.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/app/ui/src/components/Bot/Settings/SettingsCard.tsx b/app/ui/src/components/Bot/Settings/SettingsCard.tsx index a7f1d37d..879530e2 100644 --- a/app/ui/src/components/Bot/Settings/SettingsCard.tsx +++ b/app/ui/src/components/Bot/Settings/SettingsCard.tsx @@ -119,6 +119,7 @@ export const SettingsCard = ({ data }: { data: BotSettings }) => { showRef: data.showRef, use_hybrid_search: data.use_hybrid_search, bot_protect: data.bot_protect, + use_rag: data.use_rag }} form={form} requiredMark={false} @@ -312,6 +313,14 @@ export const SettingsCard = ({ data }: { data: BotSettings }) => { > + + + + diff --git a/package.json b/package.json index a4712920..117f2df9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dialoqbase", - "version": "1.0.4", + "version": "1.1.0", "description": "Create chatbots with ease", "scripts": { "ui:dev": "pnpm run --filter ui dev", diff --git a/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts b/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts index fea1c419..9173fcbc 100644 --- a/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts +++ b/server/src/routes/api/v1/bot/playground/handlers/post.handler.ts @@ -6,6 +6,42 @@ import { embeddings } from "../../../../../../utils/embeddings"; import { chatModelProvider } from "../../../../../../utils/models"; import { DialoqbaseHybridRetrival } from "../../../../../../utils/hybrid"; import { BaseRetriever } from "langchain/schema/retriever"; +import { + RunnablePassthrough, + RunnableSequence, +} from "langchain/schema/runnable"; +import { StringOutputParser } from "langchain/schema/output_parser"; +import { PromptTemplate } from "langchain/prompts"; +import { Document } from "langchain/document"; + +type ChatMessage = { + type: "human" | "ai" | "other"; + text: string; +}; + +type ConversationalRetrievalQAChainInput = { + question: string; + chat_history: ChatMessage[]; +}; + +const formatChatHistory = (history: ChatMessage[]): string => { + return history + .map((chatMessage: ChatMessage) => { + if (chatMessage.type === "human") { + return `Human: ${chatMessage.text}`; + } else if (chatMessage.type === "ai") { + return `Assistant: ${chatMessage.text}`; + } else { + return `${chatMessage.text}`; + } + }) + .join("\n"); +}; + +const combineDocumentsFn = (docs: Document[], separator = "\n\n") => { + const serializedDocs = docs.map((doc) => doc.pageContent); + return serializedDocs.join(separator); +}; export const chatRequestHandler = async ( request: FastifyRequest, @@ -48,13 +84,22 @@ export const chatRequestHandler = async ( const sanitizedQuestion = message.trim().replaceAll("\n", " "); const embeddingModel = embeddings(bot.embedding); - let retriever: BaseRetriever; - + let resolveWithDocuments: (value: Document[]) => void; + const documentPromise = new Promise((resolve) => { + resolveWithDocuments = resolve; + }); if (bot.use_hybrid_search) { retriever = new DialoqbaseHybridRetrival(embeddingModel, { botId: bot.id, sourceId: null, + callbacks: [ + { + handleRetrieverEnd(documents) { + resolveWithDocuments(documents); + }, + }, + ], }); } else { const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( @@ -65,84 +110,182 @@ export const chatRequestHandler = async ( }, ); - retriever = vectorstore.asRetriever(); + retriever = vectorstore.asRetriever({ + callbacks: [ + { + handleRetrieverEnd(documents) { + resolveWithDocuments(documents); + }, + }, + ], + }); } const model = chatModelProvider(bot.provider, bot.model, temperature); - const chain = ConversationalRetrievalQAChain.fromLLM( - model, - retriever, - { - qaTemplate: bot.qaPrompt, - questionGeneratorTemplate: bot.questionGeneratorPrompt, - returnSourceDocuments: true, - }, - ); + if (!bot.use_rag) { + const chain = ConversationalRetrievalQAChain.fromLLM( + model, + retriever, + { + qaTemplate: bot.qaPrompt, + questionGeneratorTemplate: bot.questionGeneratorPrompt, + returnSourceDocuments: true, + }, + ); - const chat_history = history - .map((chatMessage: any) => { - if (chatMessage.type === "human") { - return `Human: ${chatMessage.text}`; - } else if (chatMessage.type === "ai") { - return `Assistant: ${chatMessage.text}`; - } else { - return `${chatMessage.text}`; - } - }) - .join("\n"); - - console.log(chat_history); - - const response = await chain.call({ - question: sanitizedQuestion, - chat_history: chat_history, - }); + const chat_history = history + .map((chatMessage: any) => { + if (chatMessage.type === "human") { + return `Human: ${chatMessage.text}`; + } else if (chatMessage.type === "ai") { + return `Assistant: ${chatMessage.text}`; + } else { + return `${chatMessage.text}`; + } + }) + .join("\n"); + + console.log(chat_history); + + const response = await chain.call({ + question: sanitizedQuestion, + chat_history: chat_history, + }); + + let historyId = history_id; - let historyId = history_id; + if (!historyId) { + const newHistory = await prisma.botPlayground.create({ + data: { + botId: bot.id, + title: message, + }, + }); + historyId = newHistory.id; + } - if (!historyId) { - const newHistory = await prisma.botPlayground.create({ + await prisma.botPlaygroundMessage.create({ data: { - botId: bot.id, - title: message, + type: "human", + message: message, + botPlaygroundId: historyId, }, }); - historyId = newHistory.id; - } - await prisma.botPlaygroundMessage.create({ - data: { - type: "human", - message: message, - botPlaygroundId: historyId, - }, - }); + await prisma.botPlaygroundMessage.create({ + data: { + type: "ai", + message: response.text, + botPlaygroundId: historyId, + isBot: true, + sources: response?.sourceDocuments, + }, + }); - await prisma.botPlaygroundMessage.create({ - data: { - type: "ai", - message: response.text, - botPlaygroundId: historyId, - isBot: true, - sources: response?.sourceDocuments, - }, - }); + return { + bot: response, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.text, + }, + ], + }; + } else { + const standaloneQuestionChain = RunnableSequence.from([ + { + question: (input: ConversationalRetrievalQAChainInput) => + input.question, + chat_history: (input: ConversationalRetrievalQAChainInput) => + formatChatHistory(input.chat_history), + }, + PromptTemplate.fromTemplate(bot.questionGeneratorPrompt), + model, + new StringOutputParser(), + ]); - return { - bot: response, - history: [ - ...history, + //@ts-ignore + const answerChain = RunnableSequence.from([ { + context: retriever.pipe(combineDocumentsFn), + question: new RunnablePassthrough(), + }, + PromptTemplate.fromTemplate(bot.qaPrompt), + model, + ]); + + const chain = standaloneQuestionChain.pipe( + answerChain, + ); + const botResponse = await chain.invoke({ + question: sanitizedQuestion, + chat_history: history as ChatMessage[], + }); + + const documents = await documentPromise; + let hh = history_id; + + if (!hh) { + const newHistory = await prisma.botPlayground.create({ + data: { + botId: bot.id, + title: message, + }, + }); + hh = newHistory.id; + } + + await prisma.botPlaygroundMessage.create({ + data: { type: "human", - text: message, + message: message, + botPlaygroundId: hh, }, - { + }); + + await prisma.botPlaygroundMessage.create({ + data: { type: "ai", - text: response.text, + message: botResponse.content, + botPlaygroundId: hh, + isBot: true, + sources: documents.map((doc) => { + return { + ...doc, + }; + }), }, - ], - }; + }); + + const response = await chain.invoke({ + question: sanitizedQuestion, + chat_history: history as ChatMessage[], + }); + + return { + bot: { + text: response.content, + sourceDocuments: documents, + }, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.content, + }, + ], + }; + } } catch (e) { return { bot: { @@ -180,7 +323,6 @@ export const chatRequestStreamHandler = async ( // text: string; // }[]; try { - console.log("history", history); const prisma = request.server.prisma; const bot = await prisma.bot.findFirst({ @@ -216,11 +358,21 @@ export const chatRequestStreamHandler = async ( const embeddingModel = embeddings(bot.embedding); let retriever: BaseRetriever; - + let resolveWithDocuments: (value: Document[]) => void; + const documentPromise = new Promise((resolve) => { + resolveWithDocuments = resolve; + }); if (bot.use_hybrid_search) { retriever = new DialoqbaseHybridRetrival(embeddingModel, { botId: bot.id, sourceId: null, + callbacks: [ + { + handleRetrieverEnd(documents) { + resolveWithDocuments(documents); + }, + }, + ], }); } else { const vectorstore = await DialoqbaseVectorStore.fromExistingIndex( @@ -231,15 +383,18 @@ export const chatRequestStreamHandler = async ( }, ); - retriever = vectorstore.asRetriever(); + retriever = vectorstore.asRetriever({ + callbacks: [ + { + handleRetrieverEnd(documents) { + resolveWithDocuments(documents); + }, + }, + ], + }); } let response: any = null; - - reply.raw.on("close", () => { - console.log("closed"); - }); - const streamedModel = chatModelProvider( bot.provider, bot.model, @@ -273,89 +428,184 @@ export const chatRequestStreamHandler = async ( temperature, ); - const chain = ConversationalRetrievalQAChain.fromLLM( - streamedModel, - retriever, - { - qaTemplate: bot.qaPrompt, - questionGeneratorTemplate: bot.questionGeneratorPrompt, - returnSourceDocuments: true, - questionGeneratorChainOptions: { - llm: nonStreamingModel, + reply.raw.on("close", () => { + console.log("closed"); + }); + + if (!bot.use_rag) { + const chain = ConversationalRetrievalQAChain.fromLLM( + streamedModel, + retriever, + { + qaTemplate: bot.qaPrompt, + questionGeneratorTemplate: bot.questionGeneratorPrompt, + returnSourceDocuments: true, + questionGeneratorChainOptions: { + llm: nonStreamingModel, + }, }, - }, - ); + ); - const chat_history = history - .map((chatMessage: any) => { - if (chatMessage.type === "human") { - return `Human: ${chatMessage.text}`; - } else if (chatMessage.type === "ai") { - return `Assistant: ${chatMessage.text}`; - } else { - return `${chatMessage.text}`; - } - }) - .join("\n"); - - console.log("Waiting for response..."); - - response = await chain.call({ - question: sanitizedQuestion, - chat_history: chat_history, - }); + const chat_history = history + .map((chatMessage: any) => { + if (chatMessage.type === "human") { + return `Human: ${chatMessage.text}`; + } else if (chatMessage.type === "ai") { + return `Assistant: ${chatMessage.text}`; + } else { + return `${chatMessage.text}`; + } + }) + .join("\n"); + + response = await chain.call({ + question: sanitizedQuestion, + chat_history: chat_history, + }); + + let historyId = history_id; - let historyId = history_id; + if (!historyId) { + const newHistory = await prisma.botPlayground.create({ + data: { + botId: bot.id, + title: message, + }, + }); + historyId = newHistory.id; + } - if (!historyId) { - const newHistory = await prisma.botPlayground.create({ + await prisma.botPlaygroundMessage.create({ data: { - botId: bot.id, - title: message, + type: "human", + message: message, + botPlaygroundId: historyId, }, }); - historyId = newHistory.id; - } - await prisma.botPlaygroundMessage.create({ - data: { - type: "human", - message: message, - botPlaygroundId: historyId, - }, - }); + await prisma.botPlaygroundMessage.create({ + data: { + type: "ai", + message: response.text, + botPlaygroundId: historyId, + isBot: true, + sources: response?.sourceDocuments, + }, + }); - await prisma.botPlaygroundMessage.create({ - data: { - type: "ai", - message: response.text, - botPlaygroundId: historyId, - isBot: true, - sources: response?.sourceDocuments, - }, - }); + reply.sse({ + event: "result", + id: "", + data: JSON.stringify({ + bot: response, + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.text, + }, + ], + history_id: historyId, + }), + }); + await nextTick(); + return reply.raw.end(); + } else { + const standaloneQuestionChain = RunnableSequence.from([ + { + question: (input: ConversationalRetrievalQAChainInput) => + input.question, + chat_history: (input: ConversationalRetrievalQAChainInput) => + formatChatHistory(input.chat_history), + }, + PromptTemplate.fromTemplate(bot.questionGeneratorPrompt), + nonStreamingModel, + new StringOutputParser(), + ]); - reply.sse({ - event: "result", - id: "", - data: JSON.stringify({ - bot: response, - history: [ - ...history, - { - type: "human", - text: message, + //@ts-ignore + const answerChain = RunnableSequence.from([ + { + context: retriever.pipe(combineDocumentsFn), + question: new RunnablePassthrough(), + }, + PromptTemplate.fromTemplate(bot.qaPrompt), + streamedModel, + ]); + + const chain = standaloneQuestionChain.pipe( + answerChain, + ); + response = await chain.invoke({ + question: sanitizedQuestion, + chat_history: history as ChatMessage[], + }); + + let historyId = history_id; + const documents = await documentPromise; + console.log(response); + + if (!historyId) { + const newHistory = await prisma.botPlayground.create({ + data: { + botId: bot.id, + title: message, }, - { - type: "ai", - text: response.text, + }); + historyId = newHistory.id; + } + + await prisma.botPlaygroundMessage.create({ + data: { + type: "human", + message: message, + botPlaygroundId: historyId, + }, + }); + + await prisma.botPlaygroundMessage.create({ + data: { + type: "ai", + message: response.content, + botPlaygroundId: historyId, + isBot: true, + sources: documents.map((doc) => { + return { + ...doc, + }; + }), + }, + }); + + reply.sse({ + event: "result", + id: "", + data: JSON.stringify({ + bot: { + text: response.content, + sourceDocuments: documents, }, - ], - history_id: historyId, - }), - }); - await nextTick(); - return reply.raw.end(); + history: [ + ...history, + { + type: "human", + text: message, + }, + { + type: "ai", + text: response.text, + }, + ], + history_id: historyId, + }), + }); + await nextTick(); + return reply.raw.end(); + } } catch (e) { console.log(e); reply.raw.setHeader("Content-Type", "text/event-stream");