具体来说,我们将通过以下几个步骤来实现:
首先,我们需要构建一个基于 dubbogo 的服务端,支持与大语言模型(如 Ollama)进行交互。通过 Gin 框架为客户端提供一个接口,支持 Web 端与后端的多轮对话。
在服务端,我们主要实现了一个支持多轮对话和上下文管理的 Chat 服务。首先,创建一个上下文管理器 ContextManager,用于在不同会话之间存储聊天历史记录,并根据上下文进行会话切换。
type ContextManager struct {Contexts map[string][]*chat.ChatMessageMu sync.RWMutex}func NewContextManager() *ContextManager {return &ContextManager{Contexts: make(map[string][]*chat.ChatMessage),}}func (m *ContextManager) CreateContext() string {m.Mu.Lock()defer m.Mu.Unlock()ctxID := nowIDnowID++m.Contexts[strconv.Itoa(int(ctxID))] = []*chat.ChatMessage{}return strconv.Itoa(int(ctxID))}func (m *ContextManager) GetHistory(ctxID string) []*chat.ChatMessage {m.Mu.RLock()defer m.Mu.RUnlock()return m.Contexts[ctxID]}func (m *ContextManager) AppendMessage(ctxID string, msg *chat.ChatMessage) {m.Mu.Lock()defer m.Mu.Unlock()if len(m.Contexts[ctxID]) >= 3 {m.Contexts[ctxID] = m.Contexts[ctxID][1:]}m.Contexts[ctxID] = append(m.Contexts[ctxID], msg)}
sync.RWMutex) 保护共享的上下文数据,并限制上下文长度为 3 条消息,以防止上下文过大。ContextManager 的作用是保存多个会话的上下文,每个上下文对应一组聊天记录。当客户端发送新的消息时,ChatHandler 会根据 ContextManager 获取当前会话的历史记录,并将新消息追加到历史记录中。
我们使用了 Ollama 模型来生成聊天内容,以下是 ChatServer 的实现代码。
type ChatServer struct {llm *ollama.LLM}func NewChatServer() (*ChatServer, error) {llm, err := ollama.New(ollama.WithModel("deepseek-r1:1.5b"))if err != nil {return nil, err}return &ChatServer{llm: llm}, nil}
func (s *ChatServer) Chat(ctx context.Context, req *chat.ChatRequest, stream chat.ChatService_ChatServer) (err error) {// ... prepare messages ..._, err = s.llm.GenerateContent(ctx,messages,llms.WithStreamingFunc(func(ctx context.Context, chunk []byte) error {return stream.Send(&chat.ChatResponse{Content: string(chunk),})}),)// ...}
ollama 库与 LLM 模型交互。它将前端发送的消息转换为 LLM 可以理解的格式,并使用 llms.WithStreamingFunc 处理 LLM 的流式回复。ChatServer 通过与 Ollama LLM 进行交互来生成响应。当服务端收到请求时,它会根据用户的历史消息以及当前消息生成新的响应,并通过流式返回(streaming)发送给客户端。
Web 前端使用了 Gin 框架的模板引擎以及 SSE 协议来实时展示聊天内容。以下是前端部分的关键 JavaScript 代码,它通过与后端的 /api/chat 接口进行交互,支持多轮对话。
const generateResponse = (chatElement) => {const API_URL = "/api/chat";const messageElement = chatElement.querySelector("p");fetch(API_URL, {method: "POST",headers: {"Content-Type": "application/json",},body: JSON.stringify({ message: userMessage, bin: userBin }),}).then(response => {const reader = response.body.getReader();const decoder = new TextDecoder();function readStream() {return reader.read().then(({ done, value }) => {if (done) {return;}const chunk = decoder.decode(value);const events = chunk.split('\n\n');events.forEach(event => {if (event.startsWith('event:message')) {const dataLine = event.split('\n')[1];if (dataLine && dataLine.startsWith('data:')) {try {const data = JSON.parse(dataLine.replace('data:', ''));accumulatedResponse += data.content;messageElement.textContent = accumulatedResponse;chatbox.scrollTo(0, chatbox.scrollHeight);} catch (error) {console.error('Failed to parse event data:', error);}}}});return readStream();});}return readStream();}).catch(error => {console.error('Error:', error);messageElement.classList.add("error");messageElement.textContent = "Oops! Something went wrong. Please try again.";});}
fetch API 发送 POST 请求,并将用户输入的消息和图片数据以 JSON 格式发送到后端。然后,它使用 response.body.getReader() 和 TextDecoder 处理 SSE 数据流,实时显示 LLM 的回复。通过以上代码,前端实现了与后端的实时通信。当用户发送消息时,前端通过 SSE 协议异步接收服务端返回的聊天内容,并动态更新页面。
用户在与 LLM 交互时,可以上传图片或其他二进制文件,前端将这些文件编码为 Base64 格式,并与消息一起发送到后端。
function filesToBlob(file) {let reader = new FileReader();reader.readAsDataURL(file);reader.onload = e => {fileBlobArr.push(e.target.result);// 处理上传的文件};}
本文展示了如何使用 dubbogo 框架与 Gin 框架集成实现一个多轮对话的系统。通过与 Ollama LLM 的交互,用户可以与智能聊天系统进行流畅的对话。前端支持 Web 页面与服务端进行实时通讯,并且能够传输图片或其他二进制文件,与大模型进行交互。
这个示例为将来构建更加复杂的 AI 驱动的应用提供了良好的基础,能够在多种场景下应用,例如客服机器人、智能问答等。
快来体验吧!