项目地址: github.com/datastax/ra…
这是一个基于 rag 的聊天机器人的网页项目,使用了astra db,next,react等技术栈。
1. 运行
如何运行:执行 npm run dev 。
但是报错,提示找不到next。
安装一下:
npm install next react react-dom
界面:
2. 分析源码:
核心源码:
app/page.tsx
app/api/chat/route.ts
app/hooks/useConfiguration.ts
点击Send之后,是谁处理了事件?
看起来事件传递顺序是:handleSend -> handleSubmit -> useChat
import { useChat, Message } from 'ai/react';
const { append, messages, input, handleInputChange, handleSubmit } = useChat();
const { useRag, llm, similarityMetric, setConfiguration } = useConfiguration();
const handleSend = (e) => {
handleSubmit(e, { options: { body: { useRag, llm, similarityMetric}}});
}
app/api/chat/route.ts 里的核心代码是一个POST函数,看起来是真正处理点击事件的地方:
export async function POST(req: Request) {
try {
const {messages, useRag, llm, similarityMetric} = await req.json();
const latestMessage = messages[messages?.length - 1]?.content;
...
}
可是 handleSubmit 里的参数是如何传递给 app/api/chat/route.ts 里的POST函数的呢?
useChat 是如何跟 handleSubmit 产生关联的呢?
import { useChat, Message } from 'ai/react'; 这一句很关键。
它源自这个库:"ai": "^2.2.20",这个依赖库其实是 vercel 发布的一个库。
库的源码:github.com/vercel/ai
与 rag 相关的核心代码(app/api/chat/route.ts)
SimilarityMetric 是什么?
SimilarityMetric 的取值有:余弦 cosine,欧几里得距离 euclidean,点积 dot_product。
首先,基于聊天列表里的最后一条信息创建 embeddings:
const latestMessage = messages[messages?.length - 1]?.content;
const {data} = await openai.embeddings.create({input: latestMessage, model: 'text-embedding-ada-002'});
基于刚创建的 embeddings,从 astraDb 查询出前5条相关的内容:
const collection = await astraDb.collection(`chat_${similarityMetric}`);//此处similarityMetric的值默认是 cosine。
//这里有个疑问,collection 会不会占用内存?或者说它其实就是个 dbDao?还没真正开始查询?
const cursor= collection.find(null, {
sort: {
$vector: data[0]?.embedding,
},
limit: 5,
});
把前5条记录,拼成一个 docContext:
const documents = await cursor.toArray();
docContext = `
START CONTEXT
${documents?.map(doc => doc.content).join("\n")}
END CONTEXT
`
拼成 ragPrompt,意思是让 chatgpt 从指定的5条记录里总结出问题的答案:
const ragPrompt = [
{
role: 'system',
content: `You are an AI assistant answering questions about Cassandra and Astra DB. Format responses using markdown where applicable.
${docContext}
If the answer is not provided in the context, the AI assistant will say, "I'm sorry, I don't know the answer".
`,
},
]
最后发送请求给 gpt3.5:
const response = await openai.chat.completions.create(
{
model: llm ?? 'gpt-3.5-turbo',
stream: true,
messages: [...ragPrompt, ...messages],
}
);
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
全局配置 useConfiguration.ts 的对应界面:
3. 总结
通过本项目,进一步熟悉了 nextjs 的语法,同时也了解了 rag 的实际应用。