一个基于 Go 的 Agent 智能体终端工具,定位为通用编码助手(类 Claude Code / Cursor)。
- 技术栈:Go + eino(Agent 编排) + bubbletea(TUI)
- 模型:多 provider 可切换(OpenAI 兼容 / 火山 Ark 豆包 / Claude / Ollama 等)
- 工具:文件读写、Shell 执行、联网搜索/HTTP、MCP 自定义工具扩展
本质是三层:TUI 交互层(bubbletea)、Agent 编排层(eino)、工具执行层(自实现)。 难点不在 eino —— 它把 Agent 循环封装好了 —— 而在 TUI 与流式 Agent 的异步桥接 和 工具的权限/安全控制。
┌─────────────────────────────────────────────┐
│ TUI 层 (bubbletea) │
│ - 输入框 / 消息流渲染 / 工具调用确认弹窗 │
│ - 通过 channel 接收 Agent 的流式事件 │
└──────────────────┬──────────────────────────┘
│ tea.Cmd ⇄ event channel
┌──────────────────┴──────────────────────────┐
│ Agent 编排层 (eino react.Agent) │
│ - ToolCallingChatModel (多 provider 可切换) │
│ - ToolsNode (注册所有工具) │
│ - 流式 Recv() 循环 → 转成 TUI 事件 │
└──────────────────┬──────────────────────────┘
┌──────────────────┴──────────────────────────┐
│ 工具执行层 (自实现 tool.InvokableTool) │
│ 文件读写 / Shell / HTTP / MCP 客户端 │
│ ↑ 每个工具执行前经过「权限网关」 │
└──────────────────────────────────────────────┘
核心设计决策:eino 的 agent.Stream() 在一个 goroutine 里跑,通过 channel 把事件(文本片段、
工具调用请求、工具结果)发给 bubbletea 的 Update。工具执行前如需用户确认,工具函数会阻塞
等待 TUI 回传授权信号 —— 这是整个交互的关键耦合点。
| 关注点 | 方案 |
|---|---|
| Agent 循环 | react.NewAgent + react.AgentConfig,不用自己写 ReAct 循环 |
| 多模型切换 | 统一到 ToolCallingChatModel 接口,启动时按配置选 provider(openai/ark/claude/ollama)。OpenAI 兼容接口可覆盖 DeepSeek 等大多数国产模型 |
| 工具抽象 | tool.InvokableTool,用 utils.NewTool(toolInfo, fn) 快速构造 |
| MCP 扩展 | eino-ext 的 MCP 工具适配,把 MCP server 的工具桥接成 eino tool |
| 流式 | agent.Stream() → StreamReader.Recv() 循环 |
| 多工具并发 | eino 的 ToolsNode 内部已并发执行,只需保证工具本身线程安全 |
关键 eino 类型参考:
- Agent 包:
github.com/cloudwego/eino/flow/agent/react react.NewAgent(ctx, &react.AgentConfig{...}),必填ToolCallingModel和ToolsConfig- 模型接口:
ToolCallingChatModel(含WithTools),基类BaseChatModel提供Generate/Stream - 工具配置:
ToolsConfig类型为compose.ToolsNodeConfig - 上下文钩子:
MessageModifier(每轮注入,不持久)、MessageRewriter(持久,做压缩)
严格按依赖顺序推进,每阶段独立验证,避免一上来就纠缠 TUI+Agent 的异步问题。
纯命令行:读 stdin → eino ReactAgent → 打印流式输出。先接一个模型,挂一个假工具(如 get_time),
确认 Agent 循环和工具调用跑通。这一步用 fmt.Println 即可,不碰 bubbletea。
实现文件读(Read)、文件写(Write/Edit)、Shell 执行、HTTP 请求四个工具。此时还没权限控制,在隔离目录测。
工具执行前的确认机制。Shell 和文件写是危险操作,设计「自动允许 / 单次确认 / 拒绝」三态。 先用命令行 y/n 验证。
bubbletea 替换命令行交互。最复杂的一步:消息流渲染、流式打字机效果、工具确认弹窗、 把 M2 的 y/n 换成 TUI 交互。
配置文件(模型 provider、API key、工具开关),运行时切换模型。
接入 MCP 客户端,让用户挂外部工具。
-
bubbletea 与流式的桥接是真正的难点。bubbletea 的
Update是单线程消息循环, Agent 在另一个 goroutine。标准做法:Agent 把事件发到 channel,再用一个tea.Cmd不断从 channel 读并转成tea.Msg。建议 M0/M1 完全不碰 TUI,把这个难点孤立到 M3。 -
工具确认的阻塞问题:工具函数运行在 eino 的 goroutine,要等用户在 TUI 点确认。 需要工具持有「请求授权 channel」+「接收结果 channel」,小心死锁。
-
StreamToolCallChecker:Claude 和 OpenAI 流式吐 tool call 的方式不同(Claude 会先吐文本)。 多模型切换时这是个隐藏配置点。 -
多轮上下文管理:
MessageModifier(每轮注入 system prompt,不持久) vsMessageRewriter(持久,做上下文压缩)。编码助手对话很长,M4 之后要考虑压缩。