在与 LLM(大语言模型)
对话时,如果每次都等 LLM
处理完毕再返回给客户端,会显得比较卡顿,不友好。如何能够像主流的AI平台那样:可以一点一点吐出字符呢?
本文将模仿后端流式输出文字,前端一块一块的显示文字。主要的实现路径是:
LLM
采用qwen3
,使用stream
方式输出- 后端使用
langchain
框架 - 使用
fastapi
实现后端接口 - 前后端之间使用
websocket
长连接通信 - 前端使用一个简单的
html5
网页做演示
下面是最终实现的效果:
LLM流式输出
在 langchain
框架中,LLM(大语言模型)
可以用 stream
的方式一点一点吐出内容。请看代码:
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage,AIMessage
model_name = "qwen3"
llm = ChatOllama(model=model_name,temperature=0.3,verbose=True)
import asyncio
async def ask_stream(question,websocket=None):
"""与大模型聊天,流式输出"""
for chunk in llm.stream([HumanMessage(content=question)]):
if isinstance(chunk, AIMessage) and chunk.content !='':
print(chunk.content,end="^")
if websocket is not None:
await websocket.send_json({"reply": chunk.content})
await asyncio.sleep(0.1) # sleep一下后,前端就可以一点一点显示内容。
在
ask_stream
中使用websocket
做参数只是为了演示便利,不适合用在实际生产环境。
实现后端接口
下面使用 fastapi
实现后端的 websocket
接口,前后端通信使用 json
格式,用 uvicorn
可以启动api。
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
data = await websocket.receive_json()
user_message = data.get("message", "")
print(f"收到用户消息: {user_message}")
await ask_stream(user_message,websocket=websocket)
"""
reply_message = ask(user_message)
await websocket.send_json({"reply": reply_message})
"""
except Exception as e:
print(f"连接关闭: {e}")
import uvicorn
if __name__ == '__main__':
# 交互式API文档地址:
# http://127.0.0.1:8000/docs/
# http://127.0.0.1:8000/redoc/
uvicorn.run(app, host="0.0.0.0", port=8000)
从上面的代码我们可以看出:fastapi
对 websocket 支持的不错,实现起来也比较简洁。
实现前端页面
为了方便演示,我们做了一个 html5
静态网页,并实现一个 get
方法将网页发送给浏览器。
- 发送网页的接口
import os
@app.get("/")
async def get():
"""返回聊天页面"""
file_path = os.path.join(os.path.dirname(__file__), "chat.html")
with open(file_path, "r", encoding="utf-8") as f:
html_content = f.read()
return HTMLResponse(content=html_content)
- chat.html
<!DOCTYPE html>
<html>
<head>
<title>用WebSocket与大模型聊天</title>
<style>
#chat-box {
width: 90%;
height: 600px;
border: 1px solid #ccc;
overflow-y: scroll;
margin-bottom: 10px;
padding: 10px;
}
#user-input {
width: 80%;
padding: 5px;
}
#send-button {
padding: 5px 10px;
}
.user-message {
color: blue;
}
.server-message {
color: green;
}
</style>
</head>
<body>
<h1>WebSocket 聊天测试</h1>
<div id="chat-box"></div>
<input type="text" id="user-input" placeholder="请输入你的消息..." />
<button id="send-button" onclick="sendMessage()">发送</button>
<script>
var ws = new WebSocket("ws://localhost:8000/ws");
var chatBox = document.getElementById("chat-box");
var input = document.getElementById("user-input");
var currentServerMessageDiv = null; // 记录正在追加的服务器消息元素
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
handleServerReply(data.reply);
};
function sendMessage() {
var message = input.value.trim();
if (message === "") return;
appendMessage("你", message, "user-message");
ws.send(JSON.stringify({ "message": message }));
input.value = "";
// 清空服务器回复正在构建的div
currentServerMessageDiv = null;
}
function appendMessage(sender, message, className) {
var messageElement = document.createElement("div");
messageElement.className = className;
messageElement.textContent = sender + ": " + message;
chatBox.appendChild(messageElement);
chatBox.scrollTop = chatBox.scrollHeight;
return messageElement;
}
function handleServerReply(partialText) {
if (!currentServerMessageDiv) {
// 第一次,创建一个新的div
currentServerMessageDiv = appendMessage("服务器", partialText, "server-message");
} else {
// 后续,直接在当前div后面追加
currentServerMessageDiv.textContent += partialText;
chatBox.scrollTop = chatBox.scrollHeight;
}
}
// 按回车发送消息
input.addEventListener("keydown", function(event) {
if (event.key === "Enter") {
sendMessage();
}
});
</script>
</body>
</html>
见证效果
现在我们可以启动后端接口,然后打开浏览器,输入地址:http://127.0.0.1:8000
,体验与大语言模型聊天的快乐了。
总结
使用 qwen3
、langchian
、fastapi
、websocket
、html5
实现一个像主流AI工具那样与 LLM(大语言模型)
聊天的功能很有意思。
当我看到前端一块一块的显示大语言模型的回复的时候,心底不由得涌出一点小震撼:没错,它在改变世界!
代码
本文涉及的所有代码以及相关资源都已经共享,参见:
为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。
🪐感谢您观看,祝好运🪐