开发个人Go-ChatGP–5 模型管理 (一)
背景
开发一个chatGPT的网站,后端服务如何实现与大模型的对话?是整个项目中开发困难较大的点。
如何实现上图的聊天对话功能?在开发后端的时候,如何实现stream的响应呢?本文就先介绍后端的原理,逐步攻克这个课题。
环境部署
-
启动
ollama
:docker 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
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
前端
....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();...
ollama
的open-webui
前端项目实现和人类一样沟通的方法,使用的是stream
监听 messages
事件收到的响应,保持长连接的状态,逐渐将收到的消息显示到前端,直到后端响应结束。
后端
gin.Stream
...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 truecase <-c.Done():// 如果客户端连接关闭,则结束流式传输return false}})
...
ollama
响应
...// 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}
...
- 完整代码
package mainimport ("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 Promptif 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 truecase <-c.Done():// 如果客户端连接关闭,则结束流式传输return false}})
}var llaClient *ollama.LLMfunc 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 = llafmt.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}// 确保在所有数据处理完毕后关闭 msgChanclose(msgChan)
}
项目地址
jackwillsmith/openui-svelte-build (github.com)
GitHub - jackwillsmith/openui-backend-go: openui-backend-go