简单聊天
// 代码地址 https://github.com/langchain-ai/langchain-nextjs-template/blob/main/app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";// 导入 Request/Response 类型 ,React是单页应用,Next有req和res功能,https://blog.youkuaiyun.com/ResumeProject/article/details/151250286?
import { Message as VercelChatMessage, StreamingTextResponse } from "ai"; // 从 Vercel 的 AI SDK 导入消息类型和流式响应工具
// 从 LangChain 导入 OpenAI模型、Prompt 模板、输出解析器
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { HttpResponseOutputParser } from "langchain/output_parsers";
export const runtime = "edge"; // 指定运行环境为 Edge Runtime(低延迟,适合流式输出)
const formatMessage = (message: VercelChatMessage) => { return `${message.role}: ${message.content}`; }; // 消息格式化工具函数:把消息对象转成 "role: 内容" 格式的字符串
// 定义 Prompt 模板 —— 机器人扮演一个啰嗦的海盗 🏴☠️
const TEMPLATE = `You are a pirate named Patchy. All responses must be extremely verbose and in pirate dialect.
Current conversation: \n {chat_history} \n User: {input} \n AI:`;
// POST 接口:处理聊天请求,解析前端传来的消息,流式返回结果给前端
export async function POST(req: NextRequest) {
try {
// 从请求体中取出 messages
const body = await req.json();
const messages = body.messages ?? [];
const formattedPreviousMessages = messages.slice(0, -1).map(formatMessage); // 历史消息(除最后一条外) => 格式化成字符串
const currentMessageContent = messages[messages.length - 1].content; // 当前用户输入 => 取最后一条消息的内容
// LangChain组合拳
const prompt = PromptTemplate.fromTemplate(TEMPLATE);// 用模板生成 Prompt 实例
const model = new ChatOpenAI({ // 初始化 ChatOpenAI 模型,temperature=0.8 表示回答更有创造性,使用 gpt-4o-mini 作为底层模型
temperature: 0.8, model: "gpt-4o-mini",
});
const outputParser = new HttpResponseOutputParser(); // 定义输出解析器 Chat返回的消息流=》浏览器能消费的 HTTP 流
const chain = prompt.pipe(model).pipe(outputParser);// 组装链(Pipeline) : Prompt -> ChatOpenAI -> OutputParser
const stream = await chain.stream({ // 执行链,传入历史记录和当前输入填充模板
chat_history: formattedPreviousMessages.join("\n"),
input: currentMessageContent,
});
// vercel ai库的返回流式响应,前端可逐块接收内容。搭配HttpResponseOutputParser()。 类似langchain中的for await (const chunk of stream) {process.stdout.write(chunk);}
return new StreamingTextResponse(stream);
} catch (e: any) {
// 错误处理:返回 JSON 格式错误信息
return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
}
}
安装依赖
npm install langchain
npm install @langchain/openai @langchain/core
npm install ai@3.4.33 # https://www.npmjs.com/package/ai
代码结构
-
官方初始化命令:
npx create-next-app@latest my-app -
代码结构

-
Next.js 默认的 API 路由规范是
src/app/api/chat/route.ts或src/pages/api/chat.ts。这样前端请求/api/chat能自动匹配到你的接口。如果你放在其他路径,需要确保该文件被正确导出为 API 路由。
添加界面相关代码
"use client";# 声明这是一个 客户端组件(在浏览器中运行,而不是只在服务器渲染)
import Image from "next/image";
import { useState } from "react";
export default function Home() {
const [messages, setMessages] = useState([
{ role: "system", content: "Ahoy! Patchy the Pirate be at yer service. What be yer question?" }
]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
# sendMessage函数
const sendMessage = async () => {
if (!input.trim()) return;
setLoading(true);
const newMessages = [...messages, { role: "user", content: input }];// [...messages, ...] 使用扩展运算符 (Spread Operator) 将现有消息数组 messages 中的所有元素复制到新数组中
setMessages(newMessages);
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: newMessages }),
});
if (res.ok) {
const reader = res.body?.getReader(); # 以 流式读取(streaming) 的方式接收服务器返回的数据(常见于 AI 模型的逐字生成)
let aiReply = "";
if (reader) {
const decoder = new TextDecoder(); # 把二进制数据转成字符串
while (true) {
const { done, value } = await reader.read();
if (done) break;
aiReply += decoder.decode(value);
setMessages(msgs => [ # 每次读到新的数据,就更新最后一条 assistant 的回复,实现“边生成边显示”,用state实现https://blog.youkuaiyun.com/ResumeProject/article/details/143633857
...msgs.slice(0, -1),
msgs[msgs.length - 1],
{ role: "assistant", content: aiReply }
]);
}
}
} else {
setMessages(msgs => [
...msgs,
{ role: "assistant", content: "Arrr! There be an error with yer request." }
]);
}
setInput("");
setLoading(false);
};
# UI 部分
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
{/* Chat UI */}
<div className="w-full max-w-md bg-white dark:bg-gray-900 rounded-lg shadow p-4 flex flex-col gap-4">
<div className="flex flex-col gap-2 h-64 overflow-y-auto border-b pb-2">
{messages.map((msg, idx) => (
<div key={idx} className={`text-sm ${msg.role === "user" ? "text-blue-700" : "text-green-700"}`}>
<b>{msg.role === "user" ? "You" : "Patchy"}:</b> {msg.content}
</div>
))}
</div>
<div className="flex gap-2">
<input
className="flex-1 border rounded px-2 py-1"
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === "Enter" && sendMessage()}
disabled={loading}
placeholder="Type your message..."
/>
<button
className="bg-blue-600 text-white px-4 py-1 rounded"
onClick={sendMessage}
disabled={loading}
>
Send
</button>
</div>
</div>
{/* ...existing code... */}
</main>
{/* ...existing code... */}
</div>
);
}
显示结果:

获取apikey & 调用
OPENAI APIKEY


QWen APIKEY
- 步骤1:注册阿里云账号 仅支持中国移动、中国联通和中国电信的手机号码。





- 实名认证

调用QWEN
const model = new ChatOpenAI({
// 此处以qwen-plus为例,您可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
model: "qwen-plus",
apiKey: "sk-0cff***********373f1",
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
// other params...
},
// other params...
});
DEEPSEEK APIKEY

CG
实际使用示例
import { NextRequest, NextResponse } from "next/server";// 导入 Request/Response 类型 ,React是单页应用,Next有req和res功能,https://blog.youkuaiyun.com/ResumeProject/article/details/151250286?
import { Message as VercelChatMessage, StreamingTextResponse } from "ai"; // 从 Vercel 的 AI SDK 导入消息类型和流式响应工具
// 从 LangChain 导入 OpenAI模型、Prompt 模板、输出解析器
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { HttpResponseOutputParser } from "langchain/output_parsers";
export const runtime = "edge"; // 指定运行环境为 Edge Runtime(低延迟,适合流式输出)
const formatMessage = (message: VercelChatMessage) => { return `${message.role}: ${message.content}`; }; // 消息格式化工具函数:把消息对象转成 "role: 内容" 格式的字符串
// 定义 Prompt 模板 —— 机器人扮演一个啰嗦的海盗 🏴☠️
// const TEMPLATE = `You are a pirate named Patchy. All responses must be extremely verbose and in pirate dialect.
// Current conversation: \n {chat_history} \n User: {input} \n AI:`;
// POST 接口:处理聊天请求,解析前端传来的消息,流式返回结果给前端
export async function POST(req: NextRequest) {
try {
// 从请求体中取出 messages
const body = await req.json();
const messages = body.messages ?? [];
const formattedPreviousMessages = messages.slice(0, -1).map(formatMessage); // 历史消息(除最后一条外) => 格式化成字符串
const currentMessageContent = messages[messages.length - 1].content; // 当前用户输入 => 取最后一条消息的内容
// LangChain组合拳
const prompt = PromptTemplate.fromTemplate(process.env.ONE_TEMPLATE);// 用模板生成 Prompt 实例
// const model = new ChatOpenAI({ // 初始化 ChatOpenAI 模型,temperature=0.8 表示回答更有创造性,使用 gpt-4o-mini 作为底层模型
// temperature: 0.8, model: "gpt-4o-mini",openAIApiKey: "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",
// });
const model = new ChatOpenAI({
// 此处以qwen-plus为例,您可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
model: "qwen-plus",
apiKey: process.env.MODEL_API_KEY,
configuration: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
// other params...
},
// other params...
});
const outputParser = new HttpResponseOutputParser(); // 定义输出解析器 Chat返回的消息流=》浏览器能消费的 HTTP 流
const chain = prompt.pipe(model).pipe(outputParser);// 组装链(Pipeline) : Prompt -> ChatOpenAI -> OutputParser
const stream = await chain.stream({ // 执行链,传入历史记录和当前输入填充模板
chat_history: formattedPreviousMessages.join("\n"),
input: currentMessageContent,
});
// vercel ai库的返回流式响应,前端可逐块接收内容。搭配HttpResponseOutputParser()。 类似langchain中的for await (const chunk of stream) {process.stdout.write(chunk);}
return new StreamingTextResponse(stream);
} catch (e: any) {
// 错误处理:返回 JSON 格式错误信息
return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
}
}


被折叠的 条评论
为什么被折叠?



