本地大模型编程实战(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 小时前
语义分块提升RAG检索精度
python
小陈工3 小时前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
A__tao7 小时前
Elasticsearch Mapping 一键生成 Java 实体类(支持嵌套 + 自动过滤注释)
java·python·elasticsearch
研究点啥好呢7 小时前
Github热门项目推荐 | 创建你的像素风格!
c++·python·node.js·github·开源软件
迷藏4947 小时前
**发散创新:基于Rust实现的开源合规权限管理框架设计与实践**在现代软件架构中,**权限控制(RBAC)** 已成为保障
java·开发语言·python·rust·开源
明日清晨8 小时前
python扫码登录dy
开发语言·python
bazhange8 小时前
python如何像matlab一样使用向量化替代for循环
开发语言·python·matlab
人工干智能8 小时前
科普:python中你写的模块找不到了——`ModuleNotFoundError`
服务器·python
unicrom_深圳市由你创科技8 小时前
做虚拟示波器这种实时波形显示的上位机,用什么语言?
c++·python·c#
小敬爱吃饭8 小时前
Ragflow Docker部署及问题解决方案(界面为Welcome to nginx,ragflow上传文件失败,Docker中的ragflow-cpu-1一直重启)
人工智能·python·nginx·docker·语言模型·容器·数据挖掘