【Golang玩转本地大模型实战(二):基于Golang + Web实现AI对话页面】 ...

打印 上一主题 下一主题

主题 1976|帖子 1976|积分 5928


前言

在上一篇文章中,我们学习了如何通过 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文件下:
  1. package main
  2. import (
  3.         "bufio"
  4.         "bytes"
  5.         "encoding/json"
  6.         "fmt"
  7.         "net/http"
  8. )
  9. type ChatRequest struct {
  10.         Model    string `json:"model"`
  11.         Stream   bool   `json:"stream"`
  12.         Messages []struct {
  13.                 Role    string `json:"role"`
  14.                 Content string `json:"content"`
  15.         } `json:"messages"`
  16. }
  17. func streamChatHandler(w http.ResponseWriter, r *http.Request) {
  18.         // 设置SSE响应头
  19.         w.Header().Set("Content-Type", "text/event-stream")
  20.         w.Header().Set("Cache-Control", "no-cache")
  21.         w.Header().Set("Connection", "keep-alive")
  22.         // 读取用户提交的问题
  23.         userInput := r.URL.Query().Get("question")
  24.         if userInput == "" {
  25.                 http.Error(w, "missing question param", http.StatusBadRequest)
  26.                 return
  27.         }
  28.         // 准备请求体
  29.         reqData := ChatRequest{
  30.                 Model:  "deepseek-r1:8b",
  31.                 Stream: true,
  32.         }
  33.         reqData.Messages = append(reqData.Messages, struct {
  34.                 Role    string `json:"role"`
  35.                 Content string `json:"content"`
  36.         }{
  37.                 Role:    "user",
  38.                 Content: userInput,
  39.         })
  40.         jsonData, err := json.Marshal(reqData)
  41.         if err != nil {
  42.                 http.Error(w, "json marshal error", http.StatusInternalServerError)
  43.                 return
  44.         }
  45.         // 调用本地Ollama服务
  46.         resp, err := http.Post("http://localhost:11434/api/chat", "application/json", bytes.NewBuffer(jsonData))
  47.         if err != nil {
  48.                 http.Error(w, "call ollama error", http.StatusInternalServerError)
  49.                 return
  50.         }
  51.         defer resp.Body.Close()
  52.         // 流式读取模型输出
  53.         scanner := bufio.NewScanner(resp.Body)
  54.         flusher, _ := w.(http.Flusher)
  55.         for scanner.Scan() {
  56.                 line := scanner.Text()
  57.                 if line == "" {
  58.                         continue
  59.                 }
  60.                 var chunk struct {
  61.                         Message struct {
  62.                                 Content string `json:"content"`
  63.                         } `json:"message"`
  64.                         Done bool `json:"done"`
  65.                 }
  66.                 if err := json.Unmarshal([]byte(line), &chunk); err != nil {
  67.                         continue
  68.                 }
  69.                 // 通过SSE格式发送到前端
  70.                 fmt.Fprintf(w, "data: %s\n\n", chunk.Message.Content)
  71.                 flusher.Flush()
  72.                 if chunk.Done {
  73.                         break
  74.                 }
  75.         }
  76. }
  77. func main() {
  78.         http.Handle("/", http.FileServer(http.Dir("./static"))) // 静态文件
  79.         http.HandleFunc("/chat", streamChatHandler)             // SSE接口
  80.         fmt.Println("Server running at http://localhost:8080")
  81.         http.ListenAndServe(":8080", nil)
  82. }
复制代码
这段代码的主要功能是:

  • 提供静态文件服务(网页)

    • 使用 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>

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

罪恶克星

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表