前言
在上一篇文章中,我们学习了如何通过 Ollama 在本地部署大模型,并使用 Golang 实现了流式与非流式的 API 调用。
本篇将继承实战,目标是打造一个完备的 网页端 AI 对话系统。该系统基于前后端协作,通过 WebSocket 和 SSE(Server-Sent Events)两种流式传输协议,实现模型回复的实时展示效果。
最终实现效果为:
用户在网页输入题目,模型实时天生回答,前端页面逐字展示,交互体验类似 ChatGPT。
一、团体实现思绪
本次项目标根本流程如下:
- 用户在前端页面输入题目并提交;
- Golang 后端吸收请求,调用本地 Ollama 模型 API;
- 模型输出以流式方式返回;
- 后端通过 SSE 或 WebSocket 协议,将模型输出逐步推送到前端;
- 前端吸收并实时渲染,实现流通的“打字机式”回答效果。
二、SSE 协议实现的 AI 对话
什么是 SSE?
SSE(Server-Sent Events)是一种基于 HTTP 的单向推送协议,答应服务器连续向浏览器发送数据。
- 特点:
- 建立在 HTTP 协议之上;
- 天然支持流式传输,顺序性好;
- 实现简单、浏览器原生支持;
- 仅支持服务器向客户端单向推送(不支持客户端主动通讯);
- 适用于天生式模型这类连续输出的场景。
结论:SSE 是构建大模型“逐字输出”效果的抱负协议。
实现流程概述
1. 后端 Golang 服务
main.go文件下:
- package main
- import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "net/http"
- )
- type ChatRequest struct {
- Model string `json:"model"`
- Stream bool `json:"stream"`
- Messages []struct {
- Role string `json:"role"`
- Content string `json:"content"`
- } `json:"messages"`
- }
- func streamChatHandler(w http.ResponseWriter, r *http.Request) {
- // 设置SSE响应头
- w.Header().Set("Content-Type", "text/event-stream")
- w.Header().Set("Cache-Control", "no-cache")
- w.Header().Set("Connection", "keep-alive")
- // 读取用户提交的问题
- userInput := r.URL.Query().Get("question")
- if userInput == "" {
- http.Error(w, "missing question param", http.StatusBadRequest)
- return
- }
- // 准备请求体
- reqData := ChatRequest{
- Model: "deepseek-r1:8b",
- Stream: true,
- }
- reqData.Messages = append(reqData.Messages, struct {
- Role string `json:"role"`
- Content string `json:"content"`
- }{
- Role: "user",
- Content: userInput,
- })
- jsonData, err := json.Marshal(reqData)
- if err != nil {
- http.Error(w, "json marshal error", http.StatusInternalServerError)
- return
- }
- // 调用本地Ollama服务
- resp, err := http.Post("http://localhost:11434/api/chat", "application/json", bytes.NewBuffer(jsonData))
- if err != nil {
- http.Error(w, "call ollama error", http.StatusInternalServerError)
- return
- }
- defer resp.Body.Close()
- // 流式读取模型输出
- scanner := bufio.NewScanner(resp.Body)
- flusher, _ := w.(http.Flusher)
- for scanner.Scan() {
- line := scanner.Text()
- if line == "" {
- continue
- }
- var chunk struct {
- Message struct {
- Content string `json:"content"`
- } `json:"message"`
- Done bool `json:"done"`
- }
- if err := json.Unmarshal([]byte(line), &chunk); err != nil {
- continue
- }
- // 通过SSE格式发送到前端
- fmt.Fprintf(w, "data: %s\n\n", chunk.Message.Content)
- flusher.Flush()
- if chunk.Done {
- break
- }
- }
- }
- func main() {
- http.Handle("/", http.FileServer(http.Dir("./static"))) // 静态文件
- http.HandleFunc("/chat", streamChatHandler) // SSE接口
- fmt.Println("Server running at http://localhost:8080")
- http.ListenAndServe(":8080", nil)
- }
复制代码 这段代码的主要功能是:
- 提供静态文件服务(网页)
- 使用 http.FileServer 让浏览器访问 ./static 目录下的 HTML 页面。
- 实现 /chat 接口
- 吸收前端输入的题目(通过 URL 参数 ?question=xxx);
- 构造请求体,调用本地 Ollama API(模型推理);
- 使用 Scanner 流式读取模型输出;
- 将每段输出通过 SSE 协议 推送给前端浏览器,实现打字机式显示效果。
2. 前端页面
在 static 目录下,新建一个简单页面:
static/index.html:
[code]<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title> |