agent_runner_loop 8 个 Hooks 详细分析
1. agent_before (第 49 行)
触发时机: 整个 agent 任务开始前,进入 while 循环之前
当前用途:
- Langfuse 追踪: 创建顶层
agent.tasktrace observation,记录user_input - Project Mode: 在 user message 末尾追加项目上下文(L1 层注入),包括项目规则、记忆文件指针、收尾纪律
优化空间:
- ✅ 两个插件职责清晰,互不干扰
- ⚠️ 当前
ctx是locals()快照,project_mode 直接 mutatectx['messages']列表来注入内容,这种副作用式注入依赖messages是 list 引用的事实,比较脆弱。如果未来messages变成不可变结构会出问题
2. turn_before (第 57 行)
触发时机: 每个 turn 开始,LLM 调用之前
当前用途: 目前没有任何插件注册此 hook,是预留的空 hook
优化空间:
- ⚠️ 完全空置,可以考虑删除,或者明确文档化它的用途
- 💡 潜在用途:turn 级别的限流、安全检查、上下文压缩触发点
3. llm_before (第 58 行)
触发时机: 每次 LLM 调用前
当前用途:
- Langfuse 追踪: 创建
llm.chatgeneration span,记录输入 messages(截断到 20000 字符)
优化空间:
- ✅ 职责单一
- ⚠️
str(ctx.get('messages', ''))[:20000]这种粗暴截断可能丢失关键信息,可考虑更智能的截断策略(如保留 system prompt 开头 + 最后几条 user message)
4. llm_after (第 67 行)
触发时机: LLM 返回响应后,工具执行前
当前用途:
- Langfuse 追踪: 结束 generation span,记录 response 内容(截断到 20000 字符)和 usage_details
优化空间:
- ✅ 职责单一
- ⚠️ 同样存在截断问题。另外
usage_details依赖_tls._usage,这个变量是在 SSE parser wrapper 中异步填充的,存在时序风险——如果 parser 还没跑完,usage 可能为空
5. tool_before (第 22 行,在 BaseHandler.dispatch 内)
触发时机: 每个工具调用前
当前用途:
- Langfuse 追踪: 创建 tool span,记录工具名和参数(过滤掉
_开头的内部参数)
优化空间:
- ✅ 职责单一
- ⚠️ 参数过滤
{k: v for k, v in args.items() if not k.startswith('_')}是合理的,但要注意某些工具可能确实有以_开头的业务参数
6. tool_after (第 24 行,在 BaseHandler.dispatch 内)
触发时机: 每个工具执行完成后
当前用途:
- Langfuse 追踪: 结束 tool span,记录返回值
ret.data、ret.next_prompt、ret.should_exit
优化空间:
- ✅ 职责单一
- ⚠️ 当前只记录
ret(StepOutcome),但如果工具是 generator(yield 中间结果),中间输出没有被追踪到
7. turn_after (第 103 行)
触发时机: 每个 turn 完全结束后(工具执行完、next_prompt 确定后)
当前用途: 目前没有任何插件注册此 hook,是预留的空 hook
优化空间:
- ⚠️ 完全空置,与
turn_before一样是预留位 - 💡 潜在用途:turn 级别的统计(token 消耗汇总)、异常检测、自动保存 checkpoint
8. agent_after (第 106 行)
触发时机: 整个 agent 任务结束后(while 循环退出后)
当前用途:
- Langfuse 追踪: 结束顶层 trace,记录
exit_reason,flush 上报 - Eval 评估: 轨迹匹配 + 终态断言 + 挂分 Langfuse + 落盘
优化空间:
- ✅ 两个插件职责清晰
- ⚠️ eval 插件的耦合风险:
_eval_trajectory通过handler.parent.llmclient.backend.history深层链访问 history,这是 4 层属性穿透(handler → parent → llmclient → backend → history),极其脆弱,任何一个中间对象为 None 就会静默跳过。建议改为通过 hook ctx 直接传递 history 引用 - ⚠️ Langfuse flush 时机:
_on_agent_after中调用_lf.flush()是同步阻塞的网络 IO,如果网络慢会拖慢整个 agent 结束流程。可考虑异步 flush 或移到后台线程