開發個人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