本文阅读动手学Ollama 课程 以及对应代码,使用ai做了一些理解


一、这套程序总共包含哪些部分?

  • app.py
    FastAPI 服务端入口,负责 HTTP GET(返回页面)+ WebSocket(实时对话)。

  • websocket_handler.py
    专门处理和浏览器之间的双向流式通道,调用 Ollama。

  • index.html(在 static/ 文件夹)
    浏览器端界面,用户输入、显示对话、JS 脚本管理前后端通信。


阶段 0:运行程序,FastAPI 启动

执行:

python app.py

内部其实是:

uvicorn.run("app:app", host="127.0.0.1", port=5001, reload=True)

这时:

  • FastAPI 挂载了静态文件夹 /static
  • 定义了 / 的 GET 请求
  • 定义了 /ws 的 WebSocket 通道

阶段 1:用户输入网址,触发 HTTP GET

用户在浏览器输入:

http://127.0.0.1:5001/

浏览器发起:

GET /

FastAPI 用:

@app.get("/", response_class=HTMLResponse)
async def get_form():
    with open("static/index.html") as f:
        html_content = f.read()
    return HTMLResponse(content=html_content)

读取本地 static/index.html,把它原封不动发给浏览器。


阶段 2:浏览器收到 HTML,渲染页面

在这里插入图片描述

index.html 里包含:

  • 样式(CSS)
  • 输入框 <textarea>
  • 发送按钮 <button>
  • 核心:<script> 里的 JS 脚本,负责前端和后端如何实时对话。

阶段 3:用户输入内容 + JS 脚本逐步执行

先看点击按钮和按下回车都触发 submitForm()

<textarea id="user_input" ... onkeydown="handleKeyDown(event)"></textarea>
<button onclick="submitForm()">Send</button>

解释:

  • 点击 Send 👉 onclick="submitForm()"

  • Enter 👉 onkeydown 检测到 Enter,触发 handleKeyDown,它内部:

    function handleKeyDown(event) {
        if (event.key === 'Enter' && !event.shiftKey) {
            event.preventDefault();
            submitForm();
        }
    }
    

    ⏩ 所以两种方式都会调用 submitForm()


submitForm() 的每一步

async function submitForm() {
    const userInput = document.getElementById('user_input').value;
    if (!userInput) return; // 如果输入为空,不做事

    addMessage("User", userInput, "user"); // 把用户输入写到对话框

    document.getElementById('user_input').value = ''; // 清空输入框

    const websocket = new WebSocket(`ws://${location.host}/ws`); // 新建 WebSocket

    websocket.onopen = () => {
        websocket.send(userInput); // 连接成功后把输入发给后端
    };

    websocket.onmessage = (event) => {
        if (!botMessageDiv) {
            botMessageDiv = document.createElement('div');
            botMessageDiv.className = 'message bot';
            conversationDiv.appendChild(botMessageDiv);
        }
        botMessageDiv.textContent += event.data; // 追加收到的 chunk
        scrollToBottom();
    };

    websocket.onclose = () => {
        console.log('WebSocket connection closed');
        botMessageDiv = null; // 一次会话结束
    };
}

addMessage() 的作用

function addMessage(sender, text, className) {
    const messageDiv = document.createElement('div');
    messageDiv.className = 'message ' + className;
    messageDiv.textContent = sender + ": " + text;
    conversationDiv.appendChild(messageDiv);
    scrollToBottom();
}

作用:

  • 动态生成一条 <div>
  • "User: 你好" 写进去。
  • 附加 CSS 类(userbot),区别样式。
  • conversationDiv 追加一条消息。
  • 滚动到最底部。

scrollToBottom() 的作用

function scrollToBottom() {
    conversationDiv.scrollTop = conversationDiv.scrollHeight;
}

保证对话窗口总是滚动到最新一条消息。


阶段 4:WebSocket 客户端如何连接后端

new WebSocket(...)app.websocket(...) 的关系

const websocket = new WebSocket(`ws://${location.host}/ws`);

这里告诉浏览器:

ws:// 协议连接 http://127.0.0.1:5001/ws

FastAPI 在 app.py 里:

app.websocket("/ws")(websocket_endpoint)

意思是:

  • 所有到 /ws 的 WebSocket 请求,都交给 websocket_endpoint 处理。
  • websocket_endpointfastapi.WebSocket 做参数,先执行 accept(),然后收消息、调用 Ollama、再一块块发回去。

阶段 5:后端收到消息,调用 Ollama

user_input = await websocket.receive_text()

stream = ollama.chat(
    model='llama3.1',
    messages=[{'role': 'user', 'content': user_input}],
    stream=True
)

这里:

  • 把用户输入丢给 Ollama 大模型。
  • stream=True 代表模型边生成边返回。

阶段 6:后端流式发送,前端实时接收

for chunk in stream:
    model_output = chunk['message']['content']
    await websocket.send_text(model_output)

每收到一个 chunk,立刻发给前端。

前端:

websocket.onmessage = (event) => {
    ...
    botMessageDiv.textContent += event.data;
};

一点点把 LLM 输出拼出来,用户实时看到回复。


总结

阶段 发生了啥
0 启动 Uvicorn
1 浏览器 GET /,FastAPI 返回 HTML
2 HTML 里 JS 脚本准备好
3 用户输入,点击按钮或回车都触发 submitForm()
4 submitForm 把输入写到页面,创建 WebSocket
5 FastAPI /ws 接收消息,调用 Ollama
6 Ollama 流式生成,FastAPI 一段段发,JS 一段段接

流程图(Mermaid)

User Browser FastAPI Ollama GET / 返回 index.html 建立 ws://127.0.0.1:5001/ws 升级 WebSocket 发送用户输入 调用 Ollama(chat stream=True) 返回 chunk send chunk onmessage 更新页面 loop [流式输出] User Browser FastAPI Ollama

✨ 结论

✔️ GET 负责返回页面
✔️ WebSocket 负责实时对话
✔️ Ollama 负责生成内容
✔️ JS 负责前端渲染和用户输入处理


Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐