最佳实践与陷阱汇总
全手册最佳实践与常见陷阱汇总,按主题分类。
通用原则
| 💡 | 说明 |
|---|---|
| Agent 必须通过 Runner 运行 | 不要直接调用 Agent.Run()——Runner 管理生命周期、Checkpoint 和中断 |
| 优先用 Handlers(ChatModelAgentMiddleware) | AgentMiddleware 已废弃,新代码一律用 Handler 接口 |
| 自定义类型注册 gob | 存入 InterruptInfo / RunLocalValue 的自定义类型需 schema.RegisterName[T]() |
| 语言设置尽早调用 | adk.SetLanguage() 应在 init() 或 main() 开头调用 |
| 编译期类型安全 | Graph 编译时检查节点间类型兼容,复杂编排优先用 Graph 而非手写 |
组件使用
| 💡 | 说明 |
|---|---|
| 优先使用 Stream | model.Stream() 打字机效果体验更好;仅在不支持或无需流式时用 Generate |
| Tool 描述要清晰 | LLM 靠 ToolInfo.Desc 判断何时调用,描述模糊则 Tool 白注册 |
| 🚨 | 说明 |
|---|---|
| Stream 必须 Close | stream.Close() 必须调用,即使提前退出循环,否则 goroutine 泄漏 |
| ToolMessage 需正确 CallID | ToolMessage("result", "call_id") 中 call_id 须与 ToolCall.ID 一致 |
| Embedding 维度不匹配 | 换模型后向量维度可能变化,须重建向量索引 |
| 混淆 Generate / Stream | Generate 是 Stream 的子集;框架对不支持 Stream 的组件自动降级 |
编排与 Graph
| 💡 | 说明 |
|---|---|
| 简单管道用 Chain,复杂编排用 Graph | Chain 内部封装 Graph,代码更简洁但无分支能力 |
| 字段精确映射用 Workflow | 控制流与数据流分离,避免大 struct 全量拷贝 |
| ReAct 环中确保退出路径 | 分支函数必须保留通向 END 的路径 |
| 🚨 | 说明 |
|---|---|
| StreamReader 未 Copy 就并发读 | 一个 StreamReader 单消费者;多消费者需先 Copy(n) |
| 类型不匹配导致编译失败 | Graph 编译期校验,上游输出必须与下游输入兼容 |
| Graph 中手动管理 goroutine | 编排框架自动处理并发;不要在图内自行 fork goroutine |
ChatModelAgent
| 💡 | 说明 |
|---|---|
用 BaseChatModelAgentMiddleware 嵌入 |
只需覆盖需要的方法,避免实现全部接口 |
| WrapModel 做重试 / Failover | 这两个是模型调用维度的关注点,不应放在其他 Hook 中 |
BeforeModelRewriteState 修改工具列表 |
它是 ToolInfos 的 source of truth,不要在其他 Hook 中改 |
| ToolSearch 减少常驻工具 | 工具过多时用 ToolSearch Middleware 而非全部塞入 prompt |
| 🚨 | 说明 |
|---|---|
| WrapModel 中修改 Messages | 修改不被持久化,须在 BeforeModelRewriteState 中修改 |
| MaxIterations 过小 | 复杂多步任务可能执行不完整 |
| ReturnDirectly 误用 | Tool 命中后直接返回,不再给模型修正机会 |
| Tools 过多且无 ToolSearch | 稀释模型注意力,导致调用决策出错 |
Model 容错 (Retry / Failover)
| 💡 | 说明 |
|---|---|
| Retry + Failover 组合 | 顺序:Retry 耗尽 → Failover。ShouldFailover 收到的 err 是 *RetryExhaustedError |
| 流式 Failover 去重 | 失败尝试已发送的事件不撤回,客户端应按元数据去重 |
ShouldFailover 与 GetFailoverModel 都是必填 |
缺一在 NewChatModelAgent 时返回错误 |
| 🚨 | 说明 |
|---|---|
| 流式 Failover 事件重复 | 客户端需自行处理;失败尝试的事件不会自动撤回 |
ErrStreamCanceled 触发 Failover |
它不会触发 Failover,但需在 ShouldFailover 中排除 |
中断与恢复
| 💡 | 说明 |
|---|---|
| CheckPointID 用业务语义 | 如 session-{id}-{step},便于恢复时定位 |
推荐实现 ResumableAgent |
自定义 Agent 要支持 Resume 需实现此接口 |
| 🚨 | 说明 |
|---|---|
| 未配 CheckPointStore 用 Resume | 必须创建 Runner 时配置;否则中断状态丢失 |
| 忘记 gob.RegisterName | 序列化时 panic,排查困难 |
| CheckPointID 复用覆盖 | Resume 后旧状态被新状态覆盖,如需保留须用新 ID |
多 Agent 协作
| 💡 | 说明 |
|---|---|
| 委派用 AgentAsTool | 父 Agent 保持控制权,子 Agent 上下文隔离 |
| 固定流程用 Workflow | Sequential / Parallel / Loop — 确定性编排 |
| DeepAgent 适合复杂代码任务 | 内置文件系统 + Shell + 子 Agent + TODO 管理 |
| 🚨 | 说明 |
|---|---|
| LoopAgent 无退出条件 | 必须产出 BreakLoop 或设 MaxIterations |
| Parallel 共享状态 | 并发写 SessionValue 需自行加锁 |
DeepAgent 中 WithoutWriteTodos |
对简单任务可关闭 TODO 减少不必要的步骤 |
Callback 与可观测性
| 💡 | 说明 |
|---|---|
| 全局 Handler 一次性注册 | AppendGlobalHandlers 非并发安全,初始化阶段调用 |
| Context 传递 Span | OnStart 返回的 ctx 会传入 OnEnd / OnError,用来关联 span |
| CozeLoop 用于可视化 | 字节跳动内部可直接用 eino-ext/callbacks/cozeloop |
| 🚨 | 说明 |
|---|---|
| StreamReader 在 Callback 中泄漏 | 流式回调中收到的 StreamReader 必须关闭 |
| OnStart 中修改 input | 值拷贝,修改不影响实际流程;不要在此做数据变更 |
生产部署清单
| # | 检查项 |
|---|---|
| 1 | 全局 Handler(Tracing / Logging / Metrics)已注册 |
| 2 | adk.SetLanguage() 已调用 |
| 3 | init() 中已注册所有自定义 gob 类型 |
| 4 | 所有 Stream 有对应的 defer Close() |
| 5 | CheckPointStore 已配置(如需中断恢复) |
| 6 | Middleware 使用 Handler 接口而非废弃的 AgentMiddleware |
| 7 | Graph / Chain 编译错误已处理(Compile 返回值) |
| 8 | 模型容错配置完整(ShouldFailover + GetFailoverModel 双必填) |
| 9 | 自定义 Agent 实现了 ResumableAgent(如需 Resume) |
| 10 | 工具过多时启用 ToolSearch Middleware |