使用 Go 语言构建本地 LLM 聊天机器人:Ollama + Go 完全实战指南

## 背景介绍

AI 火得很快,大语言模型几乎成了每个开发者的必备工具。但用 OpenAI API 要花钱,网络访问也不太稳定。Ollama 解决了这个问题——它让你在本地就能跑各种开源 LLM,完全免费,而且数据都在自己机器上,隐私也有保障。

Ollama 支持 llama2、mistral、qwen、deepseek 这些主流模型,一条命令就能启动。本地跑 LLM 的好处很实际:不用网络、不用付费、数据不外流、模型参数随便调。对于学习或者做项目来说,这简直是完美的选择。

Go 语言性能好,并发模型也简洁,拿来写 AI 应用非常合适。这篇文章就从头到尾教你用 Go 调用 Ollama API,写一个能用的本地聊天机器人。

## 问题描述

很多开发者想自己搞一个本地 AI 应用,但卡在几个地方:

1. 不知道从哪儿开始集成 Ollama
2. 流式响应该怎么处理摸不着头脑
3. 错误处理和重试机制怎么写
4. 对话上下文怎么管理

这篇文章就来解决这些问题,给出一个能上生产环境的 Go 实现。

## 详细步骤

### 步骤一:安装 Ollama

macOS 和 Linux 上安装特别简单,一行命令:

“`bash
curl -fsSL https://ollama.com/install.sh | sh
“`

装好以后,启动 Ollama 服务:

“`bash
ollama serve
“`

再开一个终端下载模型,比如 qwen2.5:

“`bash
ollama pull qwen2.5:7b
“`

### 步骤二:创建 Go 项目

初始化 Go 项目:

“`bash
mkdir go-ollama-chat && cd go-ollama-chat
go mod init github.com/yourname/go-ollama-chat
“`

安装依赖。这里直接用标准库的 net/http,不需要额外装库:

“`bash
go get
“`

### 步骤三:编写核心代码

新建 main.go,把下面这些代码复制进去:

“`go
package main

import (
“bufio”
“bytes”
“encoding/json”
“fmt”
“net/http”
“os”
“strings”
“time”
)

// Message 表示对话消息
type Message struct {
Role string `json:”role”`
Content string `json:”content”`
}

// ChatRequest 发送聊天请求的结构
type ChatRequest struct {
Model string `json:”model”`
Messages []Message `json:”messages”`
Stream bool `json:”stream”`
}

// ChatResponse 聊天响应结构
type ChatResponse struct {
Model string `json:”model”`
CreatedAt time.Time `json:”created_at”`
Message Message `json:”message”`
Done bool `json:”done”`
}

// OllamaClient Ollama 客户端
type OllamaClient struct {
baseURL string
model string
client *http.Client
messages []Message
}

// NewOllamaClient 创建新的客户端
func NewOllamaClient(model string) *OllamaClient {
return &OllamaClient{
baseURL: “http://localhost:11434”,
model: model,
client: &http.Client{
Timeout: 120 * time.Second,
},
messages: make([]Message, 0),
}
}

// Chat 发送聊天请求
func (c *OllamaClient) Chat(content string) (string, error) {
// 添加用户消息
c.messages = append(c.messages, Message{
Role: “user”,
Content: content,
})

reqBody := ChatRequest{
Model: c.model,
Messages: c.messages,
Stream: false,
}

jsonData, err := json.Marshal(reqBody)
if err != nil {
return “”, fmt.Errorf(“序列化请求失败: %v”, err)
}

req, err := http.NewRequest(“POST”,
c.baseURL+”/api/chat”,
bytes.NewBuffer(jsonData),
)
if err != nil {
return “”, fmt.Errorf(“创建请求失败: %v”, err)
}

req.Header.Set(“Content-Type”, “application/json”)

resp, err := c.client.Do(req)
if err != nil {
return “”, fmt.Errorf(“发送请求失败: %v”, err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return “”, fmt.Errorf(“请求失败,状态码: %d”, resp.StatusCode)
}

var chatResp ChatResponse
if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
return “”, fmt.Errorf(“解析响应失败: %v”, err)
}

// 添加助手消息到上下文
c.messages = append(c.messages, chatResp.Message)

return chatResp.Message.Content, nil
}

// ChatWithRetry 带重试的聊天
func (c *OllamaClient) ChatWithRetry(content string, maxRetries int) (string, error) {
var lastError error

for i := 0; i < maxRetries; i++ { result, err := c.Chat(content) if err == nil { return result, nil } lastError = err fmt.Printf("重试 %d/%d: %v\n", i+1, maxRetries, err) time.Sleep(time.Duration(i+1) * time.Second) } return "", fmt.Errorf("达到最大重试次数: %v", lastError) } // StreamChat 流式聊天 func (c *OllamaClient) StreamChat(content string) error { c.messages = append(c.messages, Message{ Role: "user", Content: content, }) reqBody := ChatRequest{ Model: c.model, Messages: c.messages, Stream: true, } jsonData, err := json.Marshal(reqBody) if err != nil { return err } req, err := http.NewRequest("POST", c.baseURL+"/api/chat", bytes.NewBuffer(jsonData), ) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.client.Do(req) if err != nil { return err } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) var fullResponse string for scanner.Scan() { var chatResp ChatResponse if err := json.Unmarshal(scanner.Bytes(), &chatResp); err != nil { continue } fmt.Print(chatResp.Message.Content) fullResponse += chatResp.Message.Content if chatResp.Done { break } } c.messages = append(c.messages, Message{ Role: "assistant", Content: fullResponse, }) return scanner.Err() } // ClearHistory 清除对话历史 func (c *OllamaClient) ClearHistory() { c.messages = make([]Message, 0) } // GetModels 获取可用模型列表 func (c *OllamaClient) GetModels() ([]string, error) { req, err := http.NewRequest("GET", c.baseURL+"/api/tags", nil) if err != nil { return nil, err } resp, err := c.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var result struct { Models []struct { Name string `json:"name"` } `json:"models"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } models := make([]string, len(result.Models)) for i, m := range result.Models { models[i] = m.Name } return models, nil } func main() { fmt.Println("=== Go + Ollama 本地聊天机器人 ===") fmt.Println("输入 'quit' 退出,'clear' 清除历史,'models' 查看模型") // 创建客户端 client := NewOllamaClient("qwen2.5:7b") // 查看可用模型 models, err := client.GetModels() if err != nil { fmt.Printf("获取模型列表失败: %v\n", err) } else { fmt.Printf("可用模型: %v\n", models) } fmt.Println("\n开始聊天吧!") fmt.Println("---") reader := bufio.NewReader(os.Stdin) for { fmt.Print("\n你: ") input, err := reader.ReadString('\n') if err != nil { fmt.Printf("读取输入失败: %v\n", err) continue } input = strings.TrimSpace(input) if input == "" { continue } switch input { case "quit", "exit", "q": fmt.Println("再见!") return case "clear": client.ClearHistory() fmt.Println("对话历史已清除") continue case "models": models, err := client.GetModels() if err != nil { fmt.Printf("获取模型失败: %v\n", err) } else { fmt.Printf("可用模型: %v\n", models) } continue } // 带重试的聊天 fmt.Print("\nAI: ") response, err := client.ChatWithRetry(input, 3) if err != nil { fmt.Printf("错误: %v\n", err) continue } fmt.Println(response) } } ``` ### 步骤四:运行程序 编译并运行: ```bash go run main.go ``` ## 运行结果 程序跑起来以后,输出大概是这么个样子: ``` === Go + Ollama 本地聊天机器人 === 输入 'quit' 退出,'clear' 清除历史,'models' 查看模型 可用模型: [qwen2.5:7b llama2:7b mistral:7b] 开始聊天吧! --- 你: 你好,请介绍一下自己 AI: 你好!我是基于本地运行的 Qwen2.5 模型。我是一个大型语言模型,可以帮助你完成各种任务,包括回答问题、写作、编程等。 由于运行在本地,你可以: - 无需网络连接 - 保护隐私,数据不离开你的机器 - 无限使用,无需 API 费用 有什么我可以帮助你的吗? ``` ## 进阶功能:流式输出 上面的代码用的是非流式响应。如果想像 ChatGPT 那样一个字一个字蹦出来,可以换 StreamChat 方法: ```go // 在 main 函数中替换聊天调用 fmt.Print("\nAI: ") if err := client.StreamChat(input); err != nil { fmt.Printf("错误: %v\n", err) } ``` 这样 AI 的回答会实时显示,体验好很多。 ## 总结 这篇文章从头到尾讲了一遍怎么用 Go 集成 Ollama,写一个本地跑的聊天机器人。做完了你能得到: 1. 完全免费的开源 LLM,不用花 API 钱 2. 数据全部本地处理,隐私有保证 3. Go 性能好,并发能力强,写出来的 AI 应用速度快 4. 支持对话上下文,体验连贯 这只是个开头。后面可以加语音输入输出、做 RAG(检索增强生成)、接向量数据库,怎么玩都行。本地 AI 的可能性太多了,动手试试就知道有多有意思了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇