
Next.js 连接 Angthing llm 实现本地大模型的流式传输
我使用的模型是 deepseek-r1-distill-qwen-14b@q5_k_m ,显存 12G ,使用 Lm Studio 本地部署的。我们需要对每行的数据去掉前面的“data:”,让它变成 json 格式,方便提取 textResponse 中的文本。经过我的摸索,终于不用fetch-event-source 完成了对接本地部署的大模型,特此记录共勉。还有可以优化的点,就是前端要支持 m
网上针对 Angthing llm 的流式传输教程比较少,几乎都是 Next.js 用 fetch-event-source 连接 Openai 的api
经过我的摸索,终于不用 fetch-event-source 完成了对接本地部署的大模型,特此记录共勉
这里说一下我的需求,如果你想直接看解决方案,请跳过此部分
我使用 Lm Studio 做的 deepseek 本地大模型部署,使用 Angthing llm 进行 RAG 检索
最后需要调用 Angthing llm 的 api 完成对话
首先,为了预防跨域问题,我们把请求封装到后端调用
在 app/api/chat 文件夹下新建 route.js 文件
使用 ReadableStream 来处理流式响应,代码如下
import { NextResponse } from "next/server";
export async function POST(req) {
// 构建请求体
const apiKey = "HDJF33W-0PN9N2D-H8JCXM9-M92E6MY"; // 这里换成你的api key
const workspace = "yuwen"; // 这里换成你的工作区名称
const ip = "http://localhost:3001" // 这里换成你的端口
const url = `${ip}/api/v1/workspace/${workspace}/stream-chat`;
const headers = {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
const body = await req.json();
const response = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(body),
});
// 检查响应状态
if (!response.ok) {
throw new Error(`Server responded with status ${response.status}`);
}
// 创建一个 ReadableStream 来处理流式响应
const stream = new ReadableStream({
start(controller) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
const read = () => {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
const chunk = decoder.decode(value, { stream: true });
controller.enqueue(chunk);
read();
});
};
read();
},
});
return new NextResponse(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
}
在 app 文件夹下的 page.tsx 中添加点击发送按钮实现对话的功能(这里我默认你已经实现了和ai对话的前端)
流式传输的数据如下所示
我们需要对每行的数据去掉前面的“data:”,让它变成 json 格式,方便提取 textResponse 中的文本
具体代码如下,实现了点击按钮提交用户输入的函数,关键是中间部分对流式数据的读取和处理
const [studentInput, setStudentInput] = useState("")
const handleStudentSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (studentInput.trim()) {
const newUserMessage = { role: "user", content: studentInput };
const userInput = studentInput;
setStudentInput("");
const newStuMessage = { role: "assistant", content: "正在思考中..." };
setStudentMessages([...studentMessages,newUserMessage, newStuMessage]);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message: userInput, mode: "chat" }),
});
// 检查 response.body 是否为 null
if (response.body === null) {
throw new Error("响应体为空,无法读取数据");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
// 标记是否是第一次读取数据
var mark = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// 按换行符分割字符串
const lines = chunk.split("\n");
// 遍历每一行
for (const line of lines) {
if (line.trim()) {
// 去除每行开头的 "data: "
const jsonStr = line.replace("data: ", "");
try {
// 解析 JSON 字符串
const data = JSON.parse(jsonStr);
// 提取 textResponse 字段的值
if (data.textResponse) {
if(mark){
setStudentMessages((prevMessages) => {
return [
...prevMessages.slice(0, -1),
{
role: "assistant",
content:
prevMessages[prevMessages.length - 1].content +
data.textResponse,
},
];
});
}else{
mark = true;
setStudentMessages((prevMessages) => {
return [
...prevMessages.slice(0, -1),
{
role: "assistant",
content: data.textResponse,
},
];
});
}
}
} catch (error) {
console.error("解析 JSON 时出错:", error);
}
}
}
}
} catch (error) {
console.error("Error:", error);
}
}
};
现在就已经实现了流式传输,而且没有跨域问题
还有可以优化的点,就是前端要支持 markdown 的文本格式,这样更美观一些
你可以使用 react-markdown
库,需要安装这个库:
npm install react-markdown remark-gfm
remark-gfm
是一个插件,用于支持 GitHub Flavored Markdown(GFM),包含表格、任务列表等扩展语法
然后,在你的 page.tsx
文件中引入并使用 react-markdown
// ... existing code ...
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
// ... existing code ...
{studentMessages.map((message, index) => (
<div key={index} className={`flex ${message.role === "user" ? "justify-end" : "justify-start"}`}>
<div
className={`max-w-[80%] rounded-lg p-3 ${message.role === "user" ? "bg-primary text-primary-foreground" : "bg-background"
}`}
>
{/* 修改为使用 ReactMarkdown 解析 Markdown 文本 */}
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{message.content}
</ReactMarkdown>
</div>
</div>
))}
// ... existing code ...
其实就是加了一个 <ReactMarkdown> 包装
最后的效果如下
我使用的模型是 deepseek-r1-distill-qwen-14b@q5_k_m ,显存 12G ,使用 Lm Studio 本地部署的
特别夸一下:Angthing llm 的 RAG 确实好用,很赞!!!!
最后大家有什么不懂的欢迎私信我,我看到会解答的,就这样啦!
更多推荐
所有评论(0)