第三章:让 Agent “更能看见”(Reasoning 展示、TUI)
欢迎来到第三章!在第二章的基础上,本章继续完善一个更“可观察”的 Agent:
- 可视化展示推理与工具调用过程(便于调试)
- 兼容推理模型的
reasoning_content/reasoning/thinking字段
本章依旧保留最小 Agent Loop 的结构,但在“输出可观察性”和“工具生态扩展性”上向前迈了一步。
🎯 你将学到什么
- 推理字段兼容处理:如何从流式响应中解析
reasoning_content、reasoning、thinking(以及不同厂商字段差异的处理思路)。 - TUI 可视化层:用 Bubble Tea 实现一个轻量的可视化输出(这不是本课重点,仅为后续调试服务)。
🛠 准备工作
复用根目录的 .env 配置(见项目根目录 README.md)。
OPENAI_API_KEY=sk-your-api-key-here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-5.2如果你本地使用的是 Ollama + Qwen3,可以改成:
OPENAI_API_KEY=ollama
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_MODEL=qwen3:8b此外,本章默认在 mcp-server.json 中配置了一个 MCP 文件系统服务器(基于 @modelcontextprotocol/server-filesystem)。这需要本地能够运行 npx(通常意味着安装 Node.js)。
📖 核心原理解析
1. 推理模型的 reasoning_content 兼容处理
推理模型 vs 非推理模型
- 推理模型:强调“步骤化思考”,可能输出额外的推理字段,适合复杂任务、工具编排与多步规划,但输出更慢、成本更高。
- 非推理模型:更偏“直接生成”,通常只有最终内容字段,适合简单问答与低延迟场景。
部分“推理模型”在流式响应里会返回额外的推理字段,用于展示中间思考过程。但不同厂商字段命名不一致,常见情况包括:
reasoning_content(OpenAI 兼容推理接口常见)reasoning(例如 Ollama 的 OpenAI 兼容接口)thinking(例如 Ollama 原生接口)
ch03/agent.go 中的 RunStreaming 通过 RawJSON() 解析增量消息,尝试抽取 reasoning_content、reasoning、thinking,并将其单独作为 MessageTypeReasoning 发送给 TUI。这样就能把“推理过程”和“最终内容”分开显示,利于调试与对齐。
例如,本地使用 Ollama 跑
qwen3时,OpenAI 兼容接口通常返回reasoning;若直接调用 Ollama 原生接口,则常见字段名为thinking。
相关代码:ch03/agent.go
2. 推理模型与非推理模型的 Chat Template 差异 (可选阅读)
在第二章中,我们介绍了 Chat Template 将 API 的结构化消息转换为模型输入文本的过程。但对于推理模型(Reasoning Models)和非推理模型(Standard Models),它们在 Chat Template 的使用上存在显著差异。
推理模型的特殊 Template 格式
推理模型(如 OpenAI o1/o3 系列、DeepSeek-R1 等)需要特殊的 Prompt 结构来触发推理模式。它们的 Chat Template 通常包含:
1. 推理触发标记 推理模型需要特定的系统提示词或特殊格式来激活推理能力:
<thinking>
请按照以下步骤思考问题:
1. 理解用户需求
2. 分析可用工具
3. 规划执行步骤
4. 逐步执行并验证
</thinking>2. 推理过程与最终输出的分离
推理模型的 Chat Template 会将输出分为两个阶段:
<|im_start|>assistant
根据 README.md 的内容,这个项目的目标是...
<|im_end|>API 响应中的 reasoning_content 字段对应 <think> 标签内的内容,而 content 字段对应最终输出。
不同模型的 Template 对比
| 模型类型 | Template 特点 | 示例格式 |
|---|---|---|
| 标准模型 (GPT-4, Claude-3) | 单阶段输出 直接生成最终答案 | <|im_start|>assistant\n答案是:42<|im_end|> |
| 推理模型 (o1, o3, R1) | 双阶段输出 先推理后答案 | <|im_start|>assistant\n<think>...推理过程...</think>\n最终答案<|im_end|> |
| 混合模型 (某些支持 reasoning 模式的模型) | 可选推理 根据指令决定是否推理 | 根据系统指令决定是否生成 reasoning 字段 |
推理模型的 Token 消耗特点
理解推理模型的 Chat Template 后,就能解释为什么推理模型的 Token 消耗更高:
标准模型对话:
User (10 tokens) → Assistant (100 tokens)
总计:110 tokens
推理模型对话:
User (10 tokens) → Assistant [thinking] (5000 tokens) → Assistant (100 tokens)
总计:5110 tokens推理模型的 <think> 内容通常会被计入推理 Token(Reasoning Tokens),这部分可能有单独的计费标准。
实际开发中的处理策略
1. 自动适配不同模型
// 根据模型类型选择不同的系统提示词
if isReasoningModel(modelName) {
systemPrompt = "你是一个推理型助手。请在 <think> 标签中展示思考过程。"
} else {
systemPrompt = "你是一个直接的助手。请简洁回答问题。"
}2. Template 解析的兼容性
// 解析流式响应时,需要区分 reasoning_content / reasoning / thinking 和 content
if chunk.ReasoningContent != nil {
// 推理模型:显示在推理区域
displayInThinkingArea(*chunk.ReasoningContent)
}
if chunk.Content != nil {
// 最终内容:显示在答案区域
displayInAnswerArea(*chunk.Content)
}3. 成本优化建议
- 简单任务使用标准模型,避免推理 Token 浪费
- 复杂任务才使用推理模型,充分利用其推理能力
- 根据模型文档了解其推理 Token 计费规则
主流推理模型的 Template 差异
| 模型系列 | Reasoning 字段名 | Template 特点 |
|---|---|---|
| OpenAI o1/o3 | reasoning_content | 内置格式,无需特殊标签 |
| DeepSeek-R1 | reasoning_content | 兼容 OpenAI 格式 |
| Ollama + Qwen3 | reasoning / thinking | 取决于 OpenAI 兼容接口或原生接口 |
| 自建推理模型 | 自定义字段 | 需根据文档调整解析逻辑 |
3. TUI 只是"可视化外壳"
在 ch03/tui/tui.go 中,使用 Bubble Tea 搭建了一个轻量的 TUI:
- 以流式方式展示推理、工具调用、错误和最终内容
- 便于观察 Agent Loop 的执行轨迹
流式输出与 UI 协程
本章让 Agent 以流式方式输出,并通过 Go 的 channel 将增量消息传递给 UI 协程。RunStreaming 会持续向 viewCh 写入 MessageVO,而 TUI 侧以事件循环消费这些消息并渲染。这种“流式 + 通道”的模式可以在响应尚未完成时就持续显示过程输出。
这部分不是本课程的核心内容,也不需要深入理解 Bubble Tea 的内部机制。你只需要知道:TUI 的存在是为了让调试更直观,后续章节会频繁用到“可视化输出”。
相关代码:ch03/tui/tui.go
💻 代码结构速览
ch03/agent.go:增强后的 Agent Loop(流式 + 多种 reasoning 字段解析 + MCP 支持)ch03/tui/tui.go:Bubble Tea TUI 可视化界面
🚀 动手运行
进入项目根目录,执行:
go run ./ch03/main示例:
- “请读取 README.md 并总结项目目标”
- “列出当前目录下的文件”
如果服务正常启动,你会看到工具调用日志出现在 TUI 中。
📚 扩展阅读与参考资料
以下资料可帮助你进一步理解 TUI 相关内容:
- Bubble Tea(TUI 框架):
https://github.com/charmbracelet/bubbletea