From 657d0d87c103228fb7d42ac285c9cad838971f98 Mon Sep 17 00:00:00 2001 From: tosoham Date: Wed, 22 Jan 2025 01:38:18 +0530 Subject: [PATCH 1/2] website /ask db implemented --- client/.env.example | 1 - client/app/api/ask/route.ts | 230 +++++++++++++++++++++++++++--------- 2 files changed, 177 insertions(+), 54 deletions(-) diff --git a/client/.env.example b/client/.env.example index c9643fd7..d728be21 100644 --- a/client/.env.example +++ b/client/.env.example @@ -1,5 +1,4 @@ BRIAN_API_KEY = "" DATABASE_URL="" RPC_URL ='' -BOT_USERNAME= MY_TOKEN = '' \ No newline at end of file diff --git a/client/app/api/ask/route.ts b/client/app/api/ask/route.ts index 15c8cb32..28065fb5 100644 --- a/client/app/api/ask/route.ts +++ b/client/app/api/ask/route.ts @@ -6,13 +6,77 @@ import axios from 'axios'; import { ChatOpenAI } from "@langchain/openai"; import { ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate } from "@langchain/core/prompts"; import { START, END, MessagesAnnotation, MemorySaver, StateGraph } from "@langchain/langgraph"; -import { RemoveMessage } from "@langchain/core/messages"; +// import { RemoveMessage } from "@langchain/core/messages"; +import prisma from '@/lib/db'; const BRIAN_API_KEY = process.env.BRIAN_API_KEY || ''; const OPENAI_API_KEY = process.env.OPENAI_API_KEY || ''; const BRIAN_API_URL = 'https://api.brianknows.org/api/v0/agent/knowledge'; const BRIAN_DEFAULT_RESPONSE: string = "🤖 Sorry, I don’t know how to answer. The AskBrian feature allows you to ask for information on a custom-built knowledge base of resources. Contact the Brian team if you want to add new resources!"; +async function getOrCreateUser(address: string) { + try { + let user = await prisma.user.findUnique({ + where: { id: address }, + }); + + if (!user) { + user = await prisma.user.create({ + data: { + id: address, + email: null, + name: null, + }, + }); + } + + return user; + } catch (error) { + console.error('Error in getOrCreateUser:', error); + throw error; + } +} + +async function storeMessage({ + content, + chatId, + userId, +}: { + content: any[]; + chatId: string; + userId: string; +}) { + try { + const message = await prisma.message.create({ + data: { + content, + chatId, + userId, + }, + }); + return message; + } catch (error) { + console.error('Error storing message:', error); + throw error; + } +} + +async function createOrGetChat(userId: string) { + try { + await getOrCreateUser(userId); + + const chat = await prisma.chat.create({ + data: { + userId, + }, + }); + return chat; + } catch (error) { + console.error('Error creating chat:', error); + throw error; + } +} + const systemPrompt = ASK_OPENAI_AGENT_PROMPT + `\nThe provided chat history includes a summary of the earlier conversation.`; const systemMessage = SystemMessagePromptTemplate.fromTemplate([ @@ -34,6 +98,41 @@ const agent = new ChatOpenAI({ }); const prompt = askAgentPromptTemplate; // const chain = prompt.pipe(agent); +async function getChatHistory(chatId: string | { configurable?: { additional_args?: { chatId?: string } } }) { + try { + const actualChatId = typeof chatId === 'object' && chatId.configurable?.additional_args?.chatId + ? chatId.configurable.additional_args.chatId + : chatId; + + if (!actualChatId || typeof actualChatId !== 'string') { + console.warn('Invalid chat ID provided:', chatId); + return []; + } + + const messages = await prisma.message.findMany({ + where: { + chatId: actualChatId + }, + orderBy: { + id: 'asc' + } + }); + + const formattedHistory = messages.flatMap(msg => { + const content = msg.content as any[]; + return content.map(c => ({ + role: c.role, + content: c.content + })); + }); + + return formattedHistory; + } catch (error) { + console.error('Error fetching chat history:', error); + return []; + } +} + const initialCallModel = async (state: typeof MessagesAnnotation.State) => { const messages = [ await systemMessage.format({brianai_answer: BRIAN_DEFAULT_RESPONSE}), @@ -42,33 +141,36 @@ const initialCallModel = async (state: typeof MessagesAnnotation.State) => { const response = await agent.invoke(messages); return { messages: response }; }; -const callModel = async (state: typeof MessagesAnnotation.State ) => { - const messageHistory = state.messages.slice(0, -1); - if ( messageHistory.length >= 3 ) { - const lastHumanMessage = state.messages[state.messages.length - 1]; + +const callModel = async (state: typeof MessagesAnnotation.State, chatId?: any) => { + if (!chatId) { + return await initialCallModel(state); + } + const actualChatId = chatId?.configurable?.additional_args?.chatId || chatId; + const chatHistory = await getChatHistory(actualChatId); + const currentMessage = state.messages[state.messages.length - 1]; + + if (chatHistory.length > 0) { const summaryPrompt = ` - Distill the above chat messages into a single summary message. + Distill the following chat history into a single summary message. Include as many specific details as you can. IMPORTANT NOTE: Include all information related to user's nature about trading and what kind of trader he/she is. `; - // const summaryMessage = HumanMessagePromptTemplate.fromTemplate([summaryPrompt]); - const summary = await agent.invoke([ - ...messageHistory, + + const summary = await agent.invoke([ + ...chatHistory, { role: "user", content: summaryPrompt }, ]); - const deleteMessages = state.messages.map( - (m) => m.id ? new RemoveMessage({ id: m.id }) : null - ); - const humanMessage = { role: "user", content: lastHumanMessage.content }; + const response = await agent.invoke([ await systemMessage.format({brianai_answer: BRIAN_DEFAULT_RESPONSE}), summary, - humanMessage, + currentMessage, ]); - //console.log(response); + return { - messages: [summary, humanMessage, response, ...deleteMessages], - }; + messages: [summary, currentMessage, response], + }; } else { return await initialCallModel(state); } @@ -80,9 +182,15 @@ const workflow = new StateGraph(MessagesAnnotation) .addEdge("model", END); const app = workflow.compile({ checkpointer: new MemorySaver() }); -async function queryOpenAI({userQuery, brianaiResponse}: - {userQuery: string, brianaiResponse: string}): - Promise { +async function queryOpenAI({ + userQuery, + brianaiResponse, + chatId +}: { + userQuery: string, + brianaiResponse: string, + chatId?: string +}): Promise { try { const response = await app.invoke( { @@ -91,10 +199,12 @@ async function queryOpenAI({userQuery, brianaiResponse}: ], }, { - configurable: { thread_id: "1" }, + configurable: { + thread_id: chatId || "1", + additional_args: { chatId } + }, }, ); - console.log(response); return response.messages[response.messages.length-1].content as string; } catch (error) { console.error('OpenAI Error:', error); @@ -102,7 +212,8 @@ async function queryOpenAI({userQuery, brianaiResponse}: } } -async function queryBrianAI(prompt: string): Promise { + +async function queryBrianAI(prompt: string, chatId?: string): Promise { try { const response = await axios.post( BRIAN_API_URL, @@ -118,7 +229,11 @@ async function queryBrianAI(prompt: string): Promise { } ); const brianaiAnswer = response.data.result.answer; - const openaiAnswer = await queryOpenAI({brianaiResponse: brianaiAnswer, userQuery: prompt}); + const openaiAnswer = await queryOpenAI({ + brianaiResponse: brianaiAnswer, + userQuery: prompt, + chatId + }); return openaiAnswer; } catch (error) { console.error("Brian AI Error:", error); @@ -128,54 +243,63 @@ async function queryBrianAI(prompt: string): Promise { export async function POST(request: Request) { try { - const { prompt, address, messages } = await request.json(); + const { prompt, address, messages, chatId } = await request.json(); + const userId = address || "0x0"; + await getOrCreateUser(userId); - // Filter out duplicate messages and only keep user messages + let currentChatId = chatId; + if (!currentChatId) { + const newChat = await createOrGetChat(userId); + currentChatId = newChat.id; + } + const uniqueMessages = messages .filter((msg: any) => msg.sender === "user") .reduce((acc: any[], curr: any) => { - // Only add if message content isn't already present if (!acc.some(msg => msg.content === curr.content)) { acc.push({ - sender: "user", + role: "user", content: curr.content }); } return acc; }, []); - const payload = { - prompt, - address: address || "0x0", - chainId: "4012", - messages: uniqueMessages - }; - - console.log('Request payload:', JSON.stringify(payload, null, 2)); - - const response = await queryBrianAI(payload.prompt); - - console.log('API Response:', response); + await storeMessage({ + content: uniqueMessages, + chatId: currentChatId, + userId, + }); - // Extract the answer from the result array + const response = await queryBrianAI(prompt, currentChatId); if (response) { - return NextResponse.json({ answer: response }); + await storeMessage({ + content: [{ + role: "assistant", + content: response + }], + chatId: currentChatId, + userId, + }); + + return NextResponse.json({ + answer: response, + chatId: currentChatId + }); } else { throw new Error('Unexpected API response format'); } } catch (error: any) { - console.error('Detailed error:', { - message: error.message, - response: error.response?.data, - status: error.response?.status, - }); - + console.error('Error:', error); + if (error.code === 'P2003') { + return NextResponse.json( + { error: 'User authentication required', details: 'Please ensure you are logged in.' }, + { status: 401 } + ); + } return NextResponse.json( - { - error: 'Unable to get response from Brian\'s API', - details: error.response?.data || error.message - }, - { status: error.response?.status || 500 } + { error: 'Unable to process request', details: error.message }, + { status: 500 } ); } } \ No newline at end of file From 420648612d1052bb879d76e8c33dd866fdc16249 Mon Sep 17 00:00:00 2001 From: tosoham Date: Wed, 22 Jan 2025 01:48:02 +0530 Subject: [PATCH 2/2] website /ask db implemented --- client/app/api/ask/route.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/app/api/ask/route.ts b/client/app/api/ask/route.ts index 786e3aed..6b494f54 100644 --- a/client/app/api/ask/route.ts +++ b/client/app/api/ask/route.ts @@ -9,6 +9,11 @@ import { START, END, MessagesAnnotation, MemorySaver, StateGraph } from "@langch // import { RemoveMessage } from "@langchain/core/messages"; import prisma from '@/lib/db'; +const BRIAN_API_KEY = process.env.BRIAN_API_KEY || ""; +const OPENAI_API_KEY = process.env.OPENAI_API_KEY || ""; +const BRIAN_API_URL = "https://api.brianknows.org/api/v0/agent/knowledge"; +const BRIAN_DEFAULT_RESPONSE: string = + "🤖 Sorry, I don’t know how to answer. The AskBrian feature allows you to ask for information on a custom-built knowledge base of resources. Contact the Brian team if you want to add new resources!"; async function getOrCreateUser(address: string) { try { @@ -73,8 +78,6 @@ async function createOrGetChat(userId: string) { } } -const systemPrompt = ASK_OPENAI_AGENT_PROMPT + `\nThe provided chat history includes a summary of the earlier conversation.`; - const systemPrompt = ASK_OPENAI_AGENT_PROMPT + `\nThe provided chat history includes a summary of the earlier conversation.`;