开发个人Go-ChatGPT--5 模型管理 (一)

开发个人Go-ChatGPT--5 模型管理 (一)

背景

开发一个chatGPT的网站,后端服务如何实现与大模型的对话?是整个项目中开发困难较大的点。

如何实现上图的聊天对话功能?在开发后端的时候,如何实现stream的响应呢?本文就先介绍后端的原理,逐步攻克这个课题。

环境部署

  • 启动ollamadocker run -d -p 3000:8080 -p 11434:11434 -v ollama:/root/.ollama -v open-webui:/app/backend/data --name open-webui --restart always ollama/ollama

  • ollama 下载对话模型: docker exec -it open-webui ollama run gemma:2b

    bash 复制代码
    pulling manifest 
    pulling c1864a5eb193... 100% ▕████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏ 1.7 GB                         
    pulling 097a36493f71... 100% ▕████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏ 8.4 KB                         
    pulling 109037bec39c... 100% ▕████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏  136 B                         
    pulling 22a838ceb7fb... 100% ▕████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏   84 B                         
    pulling 887433b89a90... 100% ▕████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏  483 B                         
    verifying sha256 digest 
    writing manifest 
    removing any unused layers 
    success

Stream reponse

前端

svelte 复制代码
        ....
        const [res, controller] = await generateChatCompletion(localStorage.token, {
            model: model,
            messages: messagesBody,
            options: {
                ...($settings.options ?? {})
            },
            format: $settings.requestFormat ?? undefined,
            keep_alive: $settings.keepAlive ?? undefined,
            docs: docs.length > 0 ? docs : undefined
        });

        if (res && res.ok) {
            console.log('controller', controller);

            const reader = res.body
                .pipeThrough(new TextDecoderStream())
                .pipeThrough(splitStream('\n'))
                .getReader();
        ...

ollamaopen-webui 前端项目实现和人类一样沟通的方法,使用的是stream监听 messages事件收到的响应,保持长连接的状态,逐渐将收到的消息显示到前端,直到后端响应结束。

后端

  • gin.Stream
go 复制代码
...
    c.Stream(func(w io.Writer) bool {
        select {
        case msg, ok := <-msgChan:
            if !ok {
                // 如果msgChan被关闭,则结束流式传输
                return false
            }
            fmt.Print(msg)
            // 流式响应,发送给 messages 事件,和前端进行交互
            c.SSEvent("messages", msg)
            return true
        case <-c.Done():
            // 如果客户端连接关闭,则结束流式传输
            return false
        }
    })
...
  • ollama 响应
go 复制代码
...
    // llms.WithStreamingFunc 将ollama api 的响应内容逐渐返回,而不是一次性全部返回
    callOp := llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
        select {
        case msgChan <- string(chunk):
        case <-ctx.Done():
            return ctx.Err() // 返回上下文的错误
        }
        return nil
    })

    _, err := llaClient.Call(context.Background(), prompt, callOp)
    if err != nil {
        log.Fatalf("Call failed: %v", err) // 处理错误,而不是 panic
    }
...
  • 完整代码
go 复制代码
package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/tmc/langchaingo/llms"
    "github.com/tmc/langchaingo/llms/ollama"
)

func main() {
    router := gin.Default()

    router.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "OK",
        })
    })

    router.POST("/chat", chat)

    router.Run(":8083")
}

type Prompt struct {
    Text string `json:"text"`
}

func chat(c *gin.Context) {
    var prompt Prompt
    if err := c.BindJSON(&prompt); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return


    }

    var msgChan = make(chan string)
    // 通过chan 将ollama 响应返回给前端
    go Generate(prompt.Text, msgChan)

    c.Stream(func(w io.Writer) bool {
        select {
        case msg, ok := <-msgChan:
            if !ok {
                // 如果msgChan被关闭,则结束流式传输
                return false
            }
            // fmt.Print(msg)
            c.SSEvent("messages", msg)
            return true
        case <-c.Done():
            // 如果客户端连接关闭,则结束流式传输
            return false
        }
    })
}

var llaClient *ollama.LLM

func init() {
    // Create a new Ollama instance
    // The model is set to "gemma:2b"
    // remote url is set to "http://ollama-ip:11434"
    url := ollama.WithServerURL("http://ollama-ip:11434")
    lla, err := ollama.New(ollama.WithModel("gemma:2b"), url)
    if err != nil {
        panic(err)
    }

    llaClient = lla

    fmt.Println("connect to ollama server successfully")
}

func Generate(prompt string, msgChan chan string) {
    // ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) // 设置超时
    // defer cancel()                                                          // 确保在函数结束时取消上下文

    callOp := llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {
        select {
        case msgChan <- string(chunk):
        case <-ctx.Done():
            return ctx.Err() // 返回上下文的错误
        }
        return nil
    })

    _, err := llaClient.Call(context.Background(), prompt, callOp)
    if err != nil {
        log.Fatalf("Call failed: %v", err) // 处理错误,而不是 panic
    }

    // 确保在所有数据处理完毕后关闭 msgChan
    close(msgChan)
}

项目地址

jackwillsmith/openui-svelte-build (github.com)

GitHub - jackwillsmith/openui-backend-go: openui-backend-go

相关推荐
爱吃的小肥羊3 小时前
ChatGPT又双叒叕升级了,AI编程能联网,记忆功能免费用!
chatgpt·openai
我教你啊4 小时前
不连网也能跑大模型?
chatgpt
余厌厌厌5 小时前
go语言学习 第4章:流程控制
go
DemonAvenger5 小时前
Go 大对象与小对象分配策略优化
性能优化·架构·go
seth7 小时前
一个基于 Go 语言 开发的命令行版 V2EX 客户端,支持在终端内快速浏览主题、查看评论、切换节点及基础配置管理
go·iterm·v2ex
fashia8 小时前
Java转Go日记(五十七):gin 中间件
开发语言·后端·golang·go·gin
余厌厌厌8 小时前
go语言学习 第5章:函数
开发语言·学习·golang·go
程序员爱钓鱼10 小时前
Go语言 并发编程基础:Goroutine 的创建与调度
后端·go·排序算法
DemonAvenger10 小时前
Go内存对齐:提升访问效率的关键
性能优化·架构·go
江湖十年12 小时前
在 Go 语言中如何实现协程池
后端·面试·go