本地大模型编程实战(32)用websocket显示大模型的流式输出

在与 LLM(大语言模型) 对话时,如果每次都等 LLM 处理完毕再返回给客户端,会显得比较卡顿,不友好。如何能够像主流的AI平台那样:可以一点一点吐出字符呢?

本文将模仿后端流式输出文字,前端一块一块的显示文字。主要的实现路径是:

  • LLM 采用 qwen3 ,使用 stream 方式输出
  • 后端使用 langchain 框架
  • 使用 fastapi 实现后端接口
  • 前后端之间使用 websocket 长连接通信
  • 前端使用一个简单的 html5 网页做演示

下面是最终实现的效果:

文章目录

LLM流式输出

langchain 框架中,LLM(大语言模型) 可以用 stream 的方式一点一点吐出内容。请看代码:

python 复制代码
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。

python 复制代码
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)

从上面的代码我们可以看出:fastapiwebsocket 支持的不错,实现起来也比较简洁。

实现前端页面

为了方便演示,我们做了一个 html5 静态网页,并实现一个 get 方法将网页发送给浏览器。

  • 发送网页的接口
python 复制代码
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
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 ,体验与大语言模型聊天的快乐了。

总结

使用 qwen3langchianfastapiwebsockethtml5 实现一个像主流AI工具那样与 LLM(大语言模型) 聊天的功能很有意思。

当我看到前端一块一块的显示大语言模型的回复的时候,心底不由得涌出一点小震撼:没错,它在改变世界!


代码

本文涉及的所有代码以及相关资源都已经共享,参见:

为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。

🪐感谢您观看,祝好运🪐

相关推荐
云泽野1 小时前
【Java|集合类】list遍历的6种方式
java·python·list
AI大模型1 小时前
LangGraph官方文档笔记(七)——Agent的输入输出
langchain·llm·agent
knqiufan2 小时前
深度解析影响 RAG 召回率的四大支柱——模型、数据、索引与检索
llm·milvus·向量数据库·rag
IMPYLH2 小时前
Python 的内置函数 reversed
笔记·python
小赖同学啊4 小时前
物联网数据安全区块链服务
开发语言·python·区块链
码荼5 小时前
学习开发之hashmap
java·python·学习·哈希算法·个人开发·小白学开发·不花钱不花时间crud
小陈phd6 小时前
李宏毅机器学习笔记——梯度下降法
人工智能·python·机器学习
kk爱闹6 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
游戏开发爱好者86 小时前
iOS App首次启动请求异常调试:一次冷启动链路抓包与初始化流程修复
websocket·网络协议·tcp/ip·http·网络安全·https·udp
2501_915106326 小时前
频繁迭代下完成iOS App应用上架App Store:一次快速交付项目的完整回顾
websocket·网络协议·tcp/ip·http·网络安全·https·udp