罪恶克星 发表于 前天 02:07

【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]
查看完整版本: 【Golang玩转本地大模型实战(二):基于Golang + Web实现AI对话页面】