Ollama 的 Go 客户端:SDK 对比与 Qwen3/GPT-OSS 示例

将 Ollama 与 Go 集成:SDK 指南、示例及生产最佳实践。

目录

本指南全面概述了可用于 Ollama 的 Go SDK,并比较了它们的功能集。

我们将探讨通过 原始 REST API 调用官方 Go 客户端 调用在 Ollama 上托管的 Qwen3GPT-OSS 模型的实用 Go 示例,包括 Qwen3 中 思考非思考 模式的详细处理。

go and ollama

为什么选择 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)可能被忽略——如果需要,请计划客户端过滤。

生成 —— Qwen3GPT-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 非思考 的实际指导

  • 两个杠杆

    1. Ollama 的思考功能支持的布尔 thinking 标志;以及
    2. Qwen3 的 软开关 命令 /think/no_think 在最新系统/用户消息中。最新 指令控制下一个回合(或多个回合)。
  • 默认姿态非思考 用于快速回复;对于需要逐步推理的任务(数学、规划、调试、复杂代码分析),升级到 思考

  • 流式 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:性能强劲,但需要防御性处理推理输出
  • 其他模型:彻底测试;思考行为因模型家族而异

参考资料与进一步阅读

官方文档

Go SDK 替代方案

模型特定资源

相关主题

其他有用链接