forked from langchain-ai/langchain-nextjs-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathroute.ts
193 lines (170 loc) · 5.72 KB
/
route.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import { NextRequest, NextResponse } from "next/server";
import { Message as VercelChatMessage, StreamingTextResponse } from "ai";
import { createClient } from "@supabase/supabase-js";
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { SupabaseVectorStore } from "@langchain/community/vectorstores/supabase";
import { Document } from "@langchain/core/documents";
import { RunnableSequence } from "@langchain/core/runnables";
import {
BytesOutputParser,
StringOutputParser,
} from "@langchain/core/output_parsers";
/**
* 运行时环境设置为边缘服务器。
*/
export const runtime = "edge";
/**
* 将文档数组合并为一个字符串,每个文档之间用空行分隔。
* @param docs - 文档数组
* @returns 合并后的字符串
*/
const combineDocumentsFn = (docs: Document[]) => {
const serializedDocs = docs.map((doc) => doc.pageContent);
return serializedDocs.join("\n\n");
};
/**
* 格式化Vercel聊天历史记录,将每条消息根据角色格式化为"角色: 内容"的格式。
* @param chatHistory - 聊天历史记录数组
* @returns 格式化后的对话字符串
*/
const formatVercelMessages = (chatHistory: VercelChatMessage[]) => {
const formattedDialogueTurns = chatHistory.map((message) => {
if (message.role === "user") {
return `Human: ${message.content}`;
} else if (message.role === "assistant") {
return `Assistant: ${message.content}`;
} else {
return `${message.role}: ${message.content}`;
}
});
return formattedDialogueTurns.join("\n");
};
/**
* 编写用于将后续问题压缩为独立问题的提示模板。
*/
const CONDENSE_QUESTION_TEMPLATE = `Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
<chat_history>
{chat_history}
</chat_history>
Follow Up Input: {question}
Standalone question:`;
const condenseQuestionPrompt = PromptTemplate.fromTemplate(
CONDENSE_QUESTION_TEMPLATE,
);
/**
* 编写用于回答问题的提示模板,设定回答的风格为一只活泼的会说话的小狗。
*/
const ANSWER_TEMPLATE = `You are an energetic talking puppy named Dana, and must answer all questions like a happy, talking dog would.
Use lots of puns!
Answer the question based only on the following context and chat history:
<context>
{context}
</context>
<chat_history>
{chat_history}
</chat_history>
Question: {question}
`;
const answerPrompt = PromptTemplate.fromTemplate(ANSWER_TEMPLATE);
/**
* 处理POST请求,执行对话问答逻辑。
* @param req - Next.js的请求对象
* @returns 响应流和相关元数据
*/
export async function POST(req: NextRequest) {
try {
// 解析请求体中的消息
const body = await req.json();
const messages = body.messages ?? [];
const previousMessages = messages.slice(0, -1);
const currentMessageContent = messages[messages.length - 1].content;
// 初始化对话模型和OpenAI嵌入
const model = new ChatOpenAI({
modelName: "gpt-3.5-turbo-1106",
temperature: 0.2,
});
// 初始化Supabase客户端
const client = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PRIVATE_KEY!,
);
const vectorstore = new SupabaseVectorStore(new OpenAIEmbeddings(), {
client,
tableName: "documents",
queryName: "match_documents",
});
// 构建可执行序列,用于压缩问题
const standaloneQuestionChain = RunnableSequence.from([
condenseQuestionPrompt,
model,
new StringOutputParser(),
]);
// 用于异步获取文档的Promise
let resolveWithDocuments: (value: Document[]) => void;
const documentPromise = new Promise<Document[]>((resolve) => {
resolveWithDocuments = resolve;
});
// 设置文档检索回调
const retriever = vectorstore.asRetriever({
callbacks: [
{
handleRetrieverEnd(documents) {
resolveWithDocuments(documents);
},
},
],
});
// 构建检索和回答的可执行序列
const retrievalChain = retriever.pipe(combineDocumentsFn);
const answerChain = RunnableSequence.from([
{
context: RunnableSequence.from([
(input) => input.question,
retrievalChain,
]),
chat_history: (input) => input.chat_history,
question: (input) => input.question,
},
answerPrompt,
model,
]);
// 构建完整的对话检索和回答序列
const conversationalRetrievalQAChain = RunnableSequence.from([
{
question: standaloneQuestionChain,
chat_history: (input) => input.chat_history,
},
answerChain,
new BytesOutputParser(),
]);
// 执行序列并生成响应流
const stream = await conversationalRetrievalQAChain.stream({
question: currentMessageContent,
chat_history: formatVercelMessages(previousMessages),
});
// 等待文档Promise解析
const documents = await documentPromise;
// 序列化文档源以供返回
const serializedSources = Buffer.from(
JSON.stringify(
documents.map((doc) => {
return {
pageContent: doc.pageContent.slice(0, 50) + "...",
metadata: doc.metadata,
};
}),
),
).toString("base64");
// 返回带有文档源和消息索引的响应
return new StreamingTextResponse(stream, {
headers: {
"x-message-index": (previousMessages.length + 1).toString(),
"x-sources": serializedSources,
},
});
} catch (e: any) {
// 捕获并返回错误响应
return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
}
}