【Golang玩转本地大模型实战(二):基于Golang + Web实现AI对话页面】
前言在上一篇文章中,我们学习了如何通过 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:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>
页:
[1]