From 15623b59561b1d172e3e76aa69c5b6c4b1ce563b Mon Sep 17 00:00:00 2001 From: Lucas Holmquist Date: Thu, 5 Sep 2024 12:53:08 -0400 Subject: [PATCH] chore: some small organizing of the ai related code --- ai/ai.mjs | 81 ------------------------------------- ai/chatbot.mjs | 63 +++++++++++++++++++++++++++++ routes/chatbot-ws-route.mjs | 59 +++++++++++++++++++++++++++ server.mjs | 65 ++++------------------------- 4 files changed, 130 insertions(+), 138 deletions(-) create mode 100644 ai/chatbot.mjs create mode 100644 routes/chatbot-ws-route.mjs diff --git a/ai/ai.mjs b/ai/ai.mjs index c61a2c5..a79f59b 100644 --- a/ai/ai.mjs +++ b/ai/ai.mjs @@ -1,10 +1,4 @@ const { ChatOpenAI } = await import("@langchain/openai"); -import { RunnableWithMessageHistory } from '@langchain/core/runnables'; -import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; -import { ChatMessageHistory } from 'langchain/stores/message/in_memory'; - -let sessions = {}; -let chainWithHistory; export function getModel(options = {}) { return new ChatOpenAI({ @@ -15,78 +9,3 @@ export function getModel(options = {}) { baseURL: options.baseURL || process.env.AI_BASE_URL || 'http://localhost:8000/v1' }); } - - -export function createChain(model) { - //////////////////////////////// - // CREATE CHAIN - const prompt = ChatPromptTemplate.fromMessages([ - [ 'system', - 'You are a helpful, respectful and honest assistant named "Parasol Assistant".' + - 'You will be given a claim summary, references to provide you with information, and a question. You must answer the question based as much as possible on this claim with the help of the references.' + - 'Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.' + - 'If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\'t know the answer to a question, please don\'t share false information.' + - 'Don\'t make up policy term limits by yourself' - ], - new MessagesPlaceholder('history'), - [ 'human', '{input}' ] - ]); - - const chain = prompt.pipe(model); - - chainWithHistory = new RunnableWithMessageHistory({ - runnable: chain, - getMessageHistory: (sessionId) => { - if (sessions[sessionId] === undefined) { - sessions[sessionId] = new ChatMessageHistory(); - } - return sessions[sessionId]; - }, - inputMessagesKey: 'input', - historyMessagesKey: 'history', - }); - -} - -export async function answerQuestion(question, sessionId) { - const result = await chainWithHistory.stream( - { input: createQuestion(question) }, - { configurable: { sessionId: sessionId } } - ); - - return result; -} - -export function resetSessions(sessionId) { - delete sessions[sessionId]; -} - -function createQuestion(rawQuestion) { - return `Claim ID: ${rawQuestion.claimId} - - Claim Inception Date: ${rawQuestion.inceptionDate} - - Claim Summary: - - ${rawQuestion.claim} - - Question: ${rawQuestion.query} - ` -} - - - -// @SystemMessage(""" -// You are a helpful, respectful and honest assistant named "Parasol Assistant". -// You will be given a claim summary, references to provide you with information, and a question. You must answer the question based as much as possible on this claim with the help of the references. -// Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature. - -// If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information. -// """ -// ) -// @UserMessage(""" -// Claim Summary: -// {{query.claim}} - -// Question: {{query.query}} -// """) \ No newline at end of file diff --git a/ai/chatbot.mjs b/ai/chatbot.mjs new file mode 100644 index 0000000..c41b01a --- /dev/null +++ b/ai/chatbot.mjs @@ -0,0 +1,63 @@ +import { RunnableWithMessageHistory } from '@langchain/core/runnables'; +import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; +import { ChatMessageHistory } from 'langchain/stores/message/in_memory'; + +let sessions = {}; +let chainWithHistory; + +export function createChain(model) { + //////////////////////////////// + // CREATE CHAIN + const prompt = ChatPromptTemplate.fromMessages([ + [ 'system', + 'You are a helpful, respectful and honest assistant named "Parasol Assistant".' + + 'You will be given a claim summary, references to provide you with information, and a question. You must answer the question based as much as possible on this claim with the help of the references.' + + 'Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.' + + 'If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\'t know the answer to a question, please don\'t share false information.' + + 'Don\'t make up policy term limits by yourself' + ], + new MessagesPlaceholder('history'), + [ 'human', '{input}' ] + ]); + + const chain = prompt.pipe(model); + + chainWithHistory = new RunnableWithMessageHistory({ + runnable: chain, + getMessageHistory: (sessionId) => { + if (sessions[sessionId] === undefined) { + sessions[sessionId] = new ChatMessageHistory(); + } + return sessions[sessionId]; + }, + inputMessagesKey: 'input', + historyMessagesKey: 'history', + }); + +} + +export async function answerQuestion(question, sessionId) { + const result = await chainWithHistory.stream( + { input: createQuestion(question) }, + { configurable: { sessionId: sessionId } } + ); + + return result; +} + +export function resetSessions(sessionId) { + delete sessions[sessionId]; +} + +function createQuestion(rawQuestion) { + return `Claim ID: ${rawQuestion.claimId} + + Claim Inception Date: ${rawQuestion.inceptionDate} + + Claim Summary: + + ${rawQuestion.claim} + + Question: ${rawQuestion.query} + ` +} diff --git a/routes/chatbot-ws-route.mjs b/routes/chatbot-ws-route.mjs new file mode 100644 index 0000000..df9b5d9 --- /dev/null +++ b/routes/chatbot-ws-route.mjs @@ -0,0 +1,59 @@ +import { getModel } from '../ai/ai.mjs'; +import { createChain, answerQuestion, resetSessions } from '../ai/chatbot.mjs'; + +async function chatbotWSRoute (fastify, options) { + fastify.get('/ws/query', { websocket: true }, (ws, req) => { + const controller = new AbortController(); + + ws.on('close', () => { + resetSessions(ws); + controller.abort(); + console.log('connection closed'); + }); + + ws.on('error', console.error); + + ws.on('message', async (data) => { + const stringData = data.toString(); + + // This should be JSON + let JSONmessage; + try { + JSONmessage = JSON.parse(stringData); + } catch(err) { + console.log(err); + } + + console.log('Query from the Client', JSONmessage); + + console.log('Starting to Ask', new Date()); + + try { + const answerStream = await answerQuestion(JSONmessage, ws); + + for await (const chunk of answerStream) { + console.log(`Got Chat Response: ${chunk.content}`); + + //'{"type":"token","token":" Hello","source":""}' + const formattedAnswer = { + type: 'token', + token: chunk.content, + source: '' + }; + + ws.send(JSON.stringify(formattedAnswer)); + } + } catch (err) { + console.log(err); + } + + console.log('Done Asking', new Date()); + }); + + // AI Related Setup + const model = getModel().bind({ signal: controller.signal }); + createChain(model); + }); +} + +export default chatbotWSRoute; diff --git a/server.mjs b/server.mjs index 6574f4d..7290759 100644 --- a/server.mjs +++ b/server.mjs @@ -9,14 +9,15 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); import claimsRoute from './routes/claims-route.mjs'; +import chatbotWSRoute from './routes/chatbot-ws-route.mjs'; import sqliteConnector from './plugins/db/sqlite-connector.mjs'; -import { getModel, createChain, answerQuestion, resetSessions } from './ai/ai.mjs'; - +// Setup Logging const fastify = Fastify({ logger: true }); +// Register the Fastify ENV plugin for reading the .env files await fastify.register(fastifyEnv, { schema: { type: 'object' @@ -24,6 +25,7 @@ await fastify.register(fastifyEnv, { dotenv: true }); +// WebUI related setup and serving const webuiLocation = '../parasol-insurance/app/src/main/webui/dist'; fastify.register(fastifyStatic, { @@ -33,65 +35,14 @@ fastify.register(fastifyStatic, { fastify.get('/*', (req, res) => { res.send(fs.createReadStream(path.join(__dirname, webuiLocation, 'index.html'))); -}) +}); +// Register plugins and Routes fastify.register(sqliteConnector); -fastify.register(claimsRoute); fastify.register(fastifyWebsocket); -fastify.register(async function (fastify) { - fastify.get('/ws/query', { websocket: true }, (ws, req) => { - const controller = new AbortController(); - - ws.on('close', () => { - resetSessions(ws); - controller.abort(); - console.log('connection closed'); - }); - - ws.on('error', console.error); - - ws.on('message', async (data) => { - const stringData = data.toString(); - - // This should be JSON - let JSONmessage; - try { - JSONmessage = JSON.parse(stringData); - } catch(err) { - console.log(err); - } - - console.log('Query from the Client', JSONmessage); - console.log('Starting to Ask', new Date()); - - try { - const answerStream = await answerQuestion(JSONmessage, ws); - - for await (const chunk of answerStream) { - console.log(`Got Chat Response: ${chunk.content}`); - - //'{"type":"token","token":" Hello","source":""}' - const formattedAnswer = { - type: 'token', - token: chunk.content, - source: '' - }; - - ws.send(JSON.stringify(formattedAnswer)); - } - } catch (err) { - console.log(err); - } - - console.log('Done Asking', new Date()); - }); - - // AI Related Setup - const model = getModel().bind({ signal: controller.signal }); - createChain(model); - }); -}); +fastify.register(claimsRoute); +fastify.register(chatbotWSRoute); /** * Run the server!