Ollama 的 Go 客户端:SDK 对比与 Qwen3/GPT-OSS 示例
将 Ollama 与 Go 集成:SDK 指南、示例及生产最佳实践。
本指南全面概述了可用于 Ollama 的 Go SDK,并比较了它们的功能集。
我们将探讨通过 原始 REST API 调用 和 官方 Go 客户端 调用在 Ollama 上托管的 Qwen3 和 GPT-OSS 模型的实用 Go 示例,包括 Qwen3 中 思考 和 非思考 模式的详细处理。
为什么选择 Ollama + Go?
Ollama 暴露 了一个小型、务实的 HTTP API(通常运行在 http://localhost:11434
),专为 生成 和 聊天 工作负载设计,具有内置的流式传输支持和模型管理功能。官方文档详细介绍了 /api/generate
和 /api/chat
请求/响应结构和流式传输语义。
Go 是 构建 Ollama 客户端 的绝佳选择,因为它具有强大的标准库支持 HTTP、出色的 JSON 处理、原生并发原语以及静态类型接口,可以在编译时捕获错误。
截至 2025 年 10 月,以下是您最可能考虑的 Go SDK 选项。
Ollama 的 Go SDK —— 有哪些可用?
SDK / 包 | 状态与“所有者” | 范围(生成/聊天/流式传输) | 模型管理(拉取/列出等) | 其他 / 注释 |
---|---|---|---|---|
github.com/ollama/ollama/api |
官方 包,位于 Ollama 仓库内;由 ollama CLI 本身使用 |
完整 覆盖映射到 REST;支持流式传输 | 是 | 被视为 规范 Go 客户端;API 与文档紧密对应。 |
LangChainGo (github.com/tmc/langchaingo/llms/ollama ) |
社区框架(LangChainGo)带有 Ollama LLM 模块 | 聊天/完成 + 通过框架抽象的流式传输 | 有限(模型管理不是主要目标) | 如果您想要链、工具、向量存储在 Go 中,这是个好选择;但不是一个原始 SDK。 |
github.com/swdunlop/ollama-client |
社区客户端 | 专注于聊天;良好的 工具调用 实验 | 部分 | 为实验工具调用而构建;不是 1:1 完整表面。 |
其他社区 SDK(例如,ollamaclient ,第三方“go-ollama-sdk”) |
社区 | 各不相同 | 各不相同 | 质量和覆盖范围各异;请根据仓库进行评估。 |
建议:在生产环境中,优先选择 github.com/ollama/ollama/api
—— 它与核心项目一起维护,并与 REST API 镜像。
Ollama 上的 Qwen3 与 GPT-OSS:思考 vs 非思考(需要了解的内容)
- Ollama 的思考模式 在启用时将模型的“推理”与最终输出分开。Ollama 文档 在支持的模型上提供了 启用/禁用思考 的一流行为。
- (https://www.glukhov.org/zh-cn/post/2025/10/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: 技术细节、性能和速度比较”) 支持动态切换:在系统/用户消息中添加
/think
或/no_think
以逐轮切换模式;最新 指令获胜。 - GPT-OSS:用户报告称 禁用 思考(例如,
/set nothink
或--think=false
)在gpt-oss:20b
上可能不可靠;计划 过滤/隐藏 任何不应显示在 UI 中的推理。
第 1 部分 —— 通过 原始 REST 调用 Ollama(Go, net/http)
共享类型
首先,让我们定义在示例中将使用的通用类型和辅助函数:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// ---- 聊天 API 类型 ----
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []ChatMessage `json:"messages"`
// 一些服务器将思考控制作为布尔标志暴露。
// 即使省略,您仍可通过 /think 或 /no_think 标签控制 Qwen3。
Think *bool `json:"think,omitempty"`
Stream *bool `json:"stream,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
type ChatResponse struct {
Model string `json:"model"`
CreatedAt string `json:"created_at"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
Thinking string `json:"thinking,omitempty"` // 当启用思考时存在
} `json:"message"`
Done bool `json:"done"`
}
// ---- 生成 API 类型 ----
type GenerateRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
Think *bool `json:"think,omitempty"`
Stream *bool `json:"stream,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
type GenerateResponse struct {
Model string `json:"model"`
CreatedAt string `json:"created_at"`
Response string `json:"response"` // 非流式传输的最终文本
Thinking string `json:"thinking,omitempty"` // 当启用思考时存在
Done bool `json:"done"`
}
// ---- 辅助函数 ----
func httpPostJSON(url string, payload any) ([]byte, error) {
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
c := &http.Client{Timeout: 60 * time.Second}
resp, err := c.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// bptr 返回布尔值的指针
func bptr(b bool) *bool { return &b }
聊天 —— Qwen3 与 思考开启(以及如何关闭)
func chatQwen3Thinking() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "qwen3:8b-thinking", // 任何 :*-thinking 标签您已拉取
Think: bptr(true),
Stream: bptr(false),
Messages: []ChatMessage{
{Role: "system", Content: "你是一个精确的助手。"},
{Role: "user", Content: "用一个简短的 Go 示例解释递归。"},
},
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out ChatResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
fmt.Println("🧠 思考:\n", out.Message.Thinking)
fmt.Println("\n💬 回答:\n", out.Message.Content)
return nil
}
// 通过以下方式关闭下一个回合的思考:
// (a) 设置 Think=false,和/或
// (b) 在最近的系统/用户消息中添加 "/no_think"(Qwen3 软开关)。
// Qwen3 在多回合聊天中遵循最新的 /think 或 /no_think 指令。
func chatQwen3NoThinking() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "qwen3:8b-thinking",
Think: bptr(false),
Stream: bptr(false),
Messages: []ChatMessage{
{Role: "system", Content: "你很简短。/no_think"},
{Role: "user", Content: "用一句话解释递归。"},
},
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out ChatResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
// 期望思考为空;仍需防御性处理。
if out.Message.Thinking != "" {
fmt.Println("🧠 思考(意外):\n", out.Message.Thinking)
}
fmt.Println("\n💬 回答:\n", out.Message.Content)
return nil
}
(Qwen3 的 /think
和 /no_think
软开关由 Qwen 团队记录;在多回合聊天中,最后 的指令获胜。)
聊天 —— GPT-OSS 与思考(以及注意事项)
func chatGptOss() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "gpt-oss:20b",
Think: bptr(true), // 请求分离推理(如果支持)
Stream: bptr(false),
Messages: []ChatMessage{
{Role: "user", Content: "什么是动态规划?解释核心思想。"},
},
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out ChatResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
// 已知的怪癖:在 gpt-oss:20b 上禁用思考可能不会完全抑制推理。
// 如果您不想显示推理,请始终在 UI 中过滤/隐藏。
fmt.Println("🧠 思考:\n", out.Message.Thinking)
fmt.Println("\n💬 回答:\n", out.Message.Content)
return nil
}
用户报告称在 gpt-oss:20b 上 禁用 思考(例如,/set nothink
或 --think=false
)可能被忽略——如果需要,请计划客户端过滤。
生成 —— Qwen3 和 GPT-OSS
func generateQwen3() error {
endpoint := "http://localhost:11434/api/generate"
req := GenerateRequest{
Model: "qwen3:4b-thinking",
Prompt: "在 2–3 句话中,数据库中 B-Tree 的用途是什么?",
Think: bptr(true),
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out GenerateResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
if out.Thinking != "" {
fmt.Println("🧠 思考:\n", out.Thinking)
}
fmt.Println("\n💬 回答:\n", out.Response)
return nil
}
func generateGptOss() error {
endpoint := "http://localhost:11434/api/generate"
req := GenerateRequest{
Model: "gpt-oss:20b",
Prompt: "简要解释神经网络中的反向传播。",
Think: bptr(true),
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out GenerateResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
if out.Thinking != "" {
fmt.Println("🧠 思考:\n", out.Thinking)
}
fmt.Println("\n💬 回答:\n", out.Response)
return nil
}
REST 形状和流式传输行为直接来自 Ollama API 参考。
第 2 部分 —— 通过 官方 Go SDK (github.com/ollama/ollama/api
) 调用 Ollama
官方包暴露了一个 Client
,其方法对应于 REST API。Ollama CLI 本身 使用此包与服务通信,这使其成为兼容性最安全的选择。
安装
go get github.com/ollama/ollama/api
聊天 —— Qwen3(思考开启 / 关闭)
package main
import (
"context"
"fmt"
"log"
"github.com/ollama/ollama/api"
)
func chatWithQwen3Thinking(ctx context.Context, thinking bool) error {
client, err := api.ClientFromEnvironment() // 如果设置,遵循 OLLAMA_HOST
if err != nil {
return err
}
req := &api.ChatRequest{
Model: "qwen3:8b-thinking",
// 许多服务器构建将思考作为顶级标志暴露;
// 此外,您可以通过消息中的 /think 或 /no_think 控制 Qwen3。
Think: api.Ptr(thinking),
Messages: []api.Message{
{Role: "system", Content: "你是一个精确的助手。"},
{Role: "user", Content: "用一个简短的 Go 片段解释归并排序。"},
},
}
var resp api.ChatResponse
if err := client.Chat(ctx, req, &resp); err != nil {
return err
}
if resp.Message.Thinking != "" {
fmt.Println("🧠 思考:\n", resp.Message.Thinking)
}
fmt.Println("\n💬 回答:\n", resp.Message.Content)
return nil
}
func main() {
ctx := context.Background()
if err := chatWithQwen3Thinking(ctx, true); err != nil {
log.Fatal(err)
}
// 示例:非思考
if err := chatWithQwen3Thinking(ctx, false); err != nil {
log.Fatal(err)
}
}
聊天 —— GPT-OSS(防御性处理推理)
func chatWithGptOss(ctx context.Context) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
}
req := &api.ChatRequest{
Model: "gpt-oss:20b",
Think: api.Ptr(true),
Messages: []api.Message{
{Role: "user", Content: "什么是记忆化以及何时有用?"},
},
}
var resp api.ChatResponse
if err := client.Chat(ctx, req, &resp); err != nil {
return err
}
// 如果您打算隐藏推理,请在此处执行,无论标志如何。
if resp.Message.Thinking != "" {
fmt.Println("🧠 思考:\n", resp.Message.Thinking)
}
fmt.Println("\n💬 回答:\n", resp.Message.Content)
return nil
}
生成 —— Qwen3 & GPT-OSS
func generateWithQwen3(ctx context.Context) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
}
req := &api.GenerateRequest{
Model: "qwen3:4b-thinking",
Prompt: "总结 B-Tree 在索引中的作用。",
Think: api.Ptr(true),
}
var resp api.GenerateResponse
if err := client.Generate(ctx, req, &resp); err != nil {
return err
}
if resp.Thinking != "" {
fmt.Println("🧠 思考:\n", resp.Thinking)
}
fmt.Println("\n💬 回答:\n", resp.Response)
return nil
}
func generateWithGptOss(ctx context.Context) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
}
req := &api.GenerateRequest{
Model: "gpt-oss:20b",
Prompt: "用简单的话解释梯度下降。",
Think: api.Ptr(true),
}
var resp api.GenerateResponse
if err := client.Generate(ctx, req, &resp); err != nil {
return err
}
if resp.Thinking != "" {
fmt.Println("🧠 思考:\n", resp.Thinking)
}
fmt.Println("\n💬 回答:\n", resp.Response)
return nil
}
官方包的表面与 REST 文档镜像,并与核心项目一起更新。
流式响应
对于实时流式传输,请在请求中设置 Stream: bptr(true)
。响应将作为换行分隔的 JSON 块交付:
func streamChatExample() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "qwen3:8b-thinking",
Think: bptr(true),
Stream: bptr(true), // 启用流式传输
Messages: []ChatMessage{
{Role: "user", Content: "逐步解释快速排序算法。"},
},
}
body, _ := json.Marshal(req)
resp, err := http.Post(endpoint, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
for {
var chunk ChatResponse
if err := decoder.Decode(&chunk); err == io.EOF {
break
} else if err != nil {
return err
}
// 在到达时处理思考和内容
if chunk.Message.Thinking != "" {
fmt.Print(chunk.Message.Thinking)
}
fmt.Print(chunk.Message.Content)
if chunk.Done {
break
}
}
return nil
}
使用官方 SDK 时,使用回调函数处理流式传输块:
func streamWithOfficialSDK(ctx context.Context) error {
client, _ := api.ClientFromEnvironment()
req := &api.ChatRequest{
Model: "qwen3:8b-thinking",
Think: api.Ptr(true),
Messages: []api.Message{
{Role: "user", Content: "解释二叉搜索树。"},
},
}
err := client.Chat(ctx, req, func(resp api.ChatResponse) error {
if resp.Message.Thinking != "" {
fmt.Print(resp.Message.Thinking)
}
fmt.Print(resp.Message.Content)
return nil
})
return err
}
Qwen3 思考 vs 非思考 的实际指导
-
两个杠杆:
- Ollama 的思考功能支持的布尔
thinking
标志;以及 - Qwen3 的 软开关 命令
/think
和/no_think
在最新系统/用户消息中。最新 指令控制下一个回合(或多个回合)。
- Ollama 的思考功能支持的布尔
-
默认姿态:非思考 用于快速回复;对于需要逐步推理的任务(数学、规划、调试、复杂代码分析),升级到 思考。
-
流式 UI:当启用思考时,您可能会在流式帧中看到交错的推理/内容——缓冲或分别渲染它们,并为用户提供“显示推理”切换。 (参见 API 文档了解流式格式。)
-
多回合对话:Qwen3 记得之前回合的思考模式。如果您想在对话中切换它,请使用标志和软开关命令以确保可靠性。
GPT-OSS 的注意事项
- 即使您尝试禁用它,也要将推理视为存在;如果您的用户体验不应显示它,请在客户端进行过滤。
- 对于使用 GPT-OSS 的生产应用程序,请实现客户端过滤逻辑,如果需要,可以检测并剥离推理模式。
- 彻底测试您的特定 GPT-OSS 模型变体,因为行为可能因不同的量化和版本而有所不同。
最佳实践和生产提示
错误处理和超时
始终实现适当的超时处理和错误恢复:
func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
// 设置合理的超时
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
client, err := api.ClientFromEnvironment()
if err != nil {
return nil, fmt.Errorf("创建客户端: %w", err)
}
req := &api.ChatRequest{
Model: model,
Messages: messages,
Options: map[string]interface{}{
"temperature": 0.7,
"num_ctx": 4096, // 上下文窗口大小
},
}
var resp api.ChatResponse
if err := client.Chat(ctx, req, &resp); err != nil {
return nil, fmt.Errorf("聊天请求失败: %w", err)
}
return &resp, nil
}
连接池和重用
跨请求重用 Ollama 客户端,而不是每次创建一个新客户端:
type OllamaService struct {
client *api.Client
}
func NewOllamaService() (*OllamaService, error) {
client, err := api.ClientFromEnvironment()
if err != nil {
return nil, err
}
return &OllamaService{client: client}, nil
}
func (s *OllamaService) Chat(ctx context.Context, req *api.ChatRequest) (*api.ChatResponse, error) {
var resp api.ChatResponse
if err := s.client.Chat(ctx, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
环境配置
使用环境变量进行灵活部署:
export OLLAMA_HOST=http://localhost:11434
export OLLAMA_NUM_PARALLEL=2
export OLLAMA_MAX_LOADED_MODELS=2
官方 SDK 通过 api.ClientFromEnvironment()
自动尊重 OLLAMA_HOST
。
监控和日志
为生产系统实现结构化日志:
func loggedChat(ctx context.Context, logger *log.Logger, req *api.ChatRequest) error {
start := time.Now()
client, _ := api.ClientFromEnvironment()
var resp api.ChatResponse
err := client.Chat(ctx, req, &resp)
duration := time.Since(start)
logger.Printf("model=%s duration=%v error=%v tokens=%d",
req.Model, duration, err, len(resp.Message.Content))
return err
}
结论
-
对于 Go 项目,
github.com/ollama/ollama/api
是最完整、生产就绪的选择。它与 Ollama 核心项目一起维护,由官方 CLI 使用,并提供全面的 API 覆盖,保证兼容性。 -
对于更高层次的抽象,当您需要链、工具、向量存储和 RAG 管道时,考虑 LangChainGo —— 虽然您会牺牲一些底层控制以换取便利。
-
Qwen3 给您提供了通过标志和消息级切换(
/think
,/no_think
)对 思考模式 的干净、可靠的控制,使其非常适合需要快速响应和深度推理的应用程序。 -
对于 GPT-OSS,在必要时始终计划 在客户端清理推理输出,因为禁用思考标志可能不会被一致遵守。
-
在生产中,实现适当的错误处理、连接池、超时和监控,以构建强大的 Ollama 驱动应用程序。
Go 的强类型、出色的并发支持和 Ollama 的简单 API 的结合,使其成为构建 AI 驱动应用程序的理想堆栈,从简单的聊天机器人到复杂的 RAG 系统。
关键要点
以下是选择方法的快速参考:
用例 | 推荐方法 | 原因 |
---|---|---|
生产应用程序 | github.com/ollama/ollama/api |
官方支持,完整的 API 覆盖,与核心项目一起维护 |
RAG/链/工具管道 | LangChainGo | 高层次抽象,与向量存储集成 |
学习/实验 | 使用 net/http 的原始 REST | 完全透明,无依赖,教育用途 |
快速原型 | 官方 SDK | 简单与强大之间的平衡 |
流式聊天 UI | 带回调的官方 SDK | 内置流式传输支持,干净的 API |
模型选择指南:
- Qwen3:最适合需要可控思考模式和可靠多回合对话的应用程序
- GPT-OSS:性能强劲,但需要防御性处理推理输出
- 其他模型:彻底测试;思考行为因模型家族而异
参考资料与进一步阅读
官方文档
- Ollama API 参考 — 完整的 REST API 文档
- 官方 Ollama Go 包 — Go SDK 文档
- Ollama GitHub 仓库 — 源代码和问题跟踪
Go SDK 替代方案
- LangChainGo Ollama 集成 — 适用于链式应用
- swdunlop/ollama-client — 带有工具调用的社区客户端
- xyproto/ollamaclient — 另一个社区选项
模型特定资源
- Qwen 文档 — 官方 Qwen 模型信息
- GPT-OSS 信息 — GPT-OSS 模型详情
相关主题
- 使用 Go 构建 RAG 应用 — LangChainGo 示例
- Go context 包 — 超时和取消操作必不可少
- Go HTTP 客户端最佳实践 — 标准库文档