forked from langchain-ai/langchain-nextjs-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathroute.ts
139 lines (122 loc) · 4.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
import { NextRequest, NextResponse } from "next/server";
import { Message as VercelChatMessage, StreamingTextResponse } from "ai";
import { createClient } from "@supabase/supabase-js";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { SupabaseVectorStore } from "langchain/vectorstores/supabase";
import { AIMessage, ChatMessage, HumanMessage } from "langchain/schema";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import {
createRetrieverTool,
OpenAIAgentTokenBufferMemory,
} from "langchain/agents/toolkits";
import { ChatMessageHistory } from "langchain/memory";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
export const runtime = "edge";
const convertVercelMessageToLangChainMessage = (message: VercelChatMessage) => {
if (message.role === "user") {
return new HumanMessage(message.content);
} else if (message.role === "assistant") {
return new AIMessage(message.content);
} else {
return new ChatMessage(message.content, message.role);
}
};
const TEMPLATE = `You are a stereotypical robot named Robbie and must answer all questions like a stereotypical robot. Use lots of interjections like "BEEP" and "BOOP".
If you don't know how to answer a question, use the available tools to look up relevant information. You should particularly do this for questions about LangChain.`;
/**
* This handler initializes and calls a retrieval agent. It requires an OpenAI
* Functions model. See the docs for more information:
*
* https://js.langchain.com/docs/use_cases/question_answering/conversational_retrieval_agents
*/
export async function POST(req: NextRequest) {
try {
const body = await req.json();
/**
* We represent intermediate steps as system messages for display purposes,
* but don't want them in the chat history.
*/
const messages = (body.messages ?? []).filter(
(message: VercelChatMessage) =>
message.role === "user" || message.role === "assistant",
);
const returnIntermediateSteps = body.show_intermediate_steps;
const previousMessages = messages.slice(0, -1);
const currentMessageContent = messages[messages.length - 1].content;
const model = new ChatOpenAI({
modelName: "gpt-4",
});
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 chatHistory = new ChatMessageHistory(
previousMessages.map(convertVercelMessageToLangChainMessage),
);
/**
* This is a special type of memory specifically for conversational
* retrieval agents.
* It tracks intermediate steps as well as chat history up to a
* certain number of tokens.
*
* The default OpenAI Functions agent prompt has a placeholder named
* "chat_history" where history messages get injected - this is why
* we set "memoryKey" to "chat_history". This will be made clearer
* in a future release.
*/
const memory = new OpenAIAgentTokenBufferMemory({
llm: model,
memoryKey: "chat_history",
outputKey: "output",
chatHistory,
});
const retriever = vectorstore.asRetriever();
/**
* Wrap the retriever in a tool to present it to the agent in a
* usable form.
*/
const tool = createRetrieverTool(retriever, {
name: "search_latest_knowledge",
description: "Searches and returns up-to-date general information.",
});
const executor = await initializeAgentExecutorWithOptions([tool], model, {
agentType: "openai-functions",
memory,
returnIntermediateSteps: true,
verbose: true,
agentArgs: {
prefix: TEMPLATE,
},
});
const result = await executor.call({
input: currentMessageContent,
});
if (returnIntermediateSteps) {
return NextResponse.json(
{ output: result.output, intermediate_steps: result.intermediateSteps },
{ status: 200 },
);
} else {
// Agent executors don't support streaming responses (yet!), so stream back the complete response one
// character at a time to simluate it.
const textEncoder = new TextEncoder();
const fakeStream = new ReadableStream({
async start(controller) {
for (const character of result.output) {
controller.enqueue(textEncoder.encode(character));
await new Promise((resolve) => setTimeout(resolve, 20));
}
controller.close();
},
});
return new StreamingTextResponse(fakeStream);
}
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 });
}
}