数据截至 (上游 commit 2c5560339cd7)
会话、追踪与人在环路
本章讲三个横切整个 run 的机制:会话记忆(自动管理多轮历史)、追踪(自动可观测)、人在环路(让工具调用暂停等人类批准再继续)。
4.1 会话(Session):自动记住多轮对话
它要解决的小问题
多轮对话里,你不想每次 Runner.run 都手动把上一轮的全部历史拼进 input。你希望「把这轮的新输入丢进去,SDK 自己接着上次的历史跑,并把这轮产物存回去」。
思路:一个极简的存储协议
Session 是个 Protocol(memory/session.py:16),只要求四个方法:
| 方法 | 干什么 |
|---|---|
get_items(limit) | 取历史 item |
add_items(items) | 追加新 item |
pop_item() | 弹出最后一个(用于回滚/重试) |
clear_session() | 清空 |
就这么薄。任何实现了这四个方法的对象都能当会 话用。SDK 内置 SQLiteSession(memory/sqlite_session.py),pyproject.toml 里还有 Redis / SQLAlchemy / MongoDB 等可选实现(pyproject.toml:46-50 的 optional 依赖)。
run 循环怎么用它
传了 session 后,AgentRunner.run 在开头用 prepare_input_with_session(run.py:639、run.py:655)把「会话历史 + 本轮新输入」合并成喂给模型的 input;run 过程中用 save_result_to_session(run.py:920 等多处)把新产物持久化。
注意一个互斥: 会话持久化只在「没用 OpenAI 服务端对话追踪」时启用(run.py:679 的 session_persistence_enabled = session is not None and server_conversation_tracker is None)。如果你用了 conversation_id / previous_response_id(让 OpenAI 服务端管历史),本地 Session 就不再重复存。
4.2 追踪(Tracing):每一步都留痕
它要解决的小问题
agent 跑起来是个黑盒:调了几次模型?每次花多少 token?跑了哪些工具?哪一步交接的?出问题要能回放。
思路:trace + 嵌套 span
SDK 借用分布式追踪的模型:一次 run 是一个 trace,里面嵌套一棵 span 树。你在 01 章的循环里其实已经见过它们:task_span、agent_span、turn_span 在 run.py 里被层层 start(run.py:645、run.py:1288、run.py:1166)。
常见 span 类型(tracing/__init__.py):
| span | 包住什么 |
|---|---|
agent_span | 某个 agent 的整段执行 |
turn_span | 一个回合 |
generation_span / response_span | 一次模型调用 |
function_span | 一次工具调用 |
handoff_span / guardrail_span | 交接 / 护栏 |
上报机制:批量异步导出
默认的处理器是 BatchTraceProcessor(tracing/processors.py:541),默认导出器 BackendSpanExporter(tracing/processors.py:44)会把 span 批量 POST 到 OpenAI 的追踪后端(tracing/processors.py:45 的 _OPENAI_TRACING_INGEST_ENDPOINT = "https://api.openai.com/v1/traces/ingest"),所以你在 OpenAI 控制台能看到可视化的 run。
你可以换处理器(add_trace_processor / set_trace_processors,tracing/__init__.py:46、58)接到自己的可观测系统,或用 set_tracing_disabled / run_config.tracing_disabled 关掉。
4.3 人在环路(HITL):工具调用暂停等审批
这是 01 章那个 NextStepInterruption 的完整故事,也是 SDK 最体现「可恢复」设计的地方。
它要解决的小问题
有些工具很危险(删库、转账、发邮件)。你想在模型决定调用、但尚未执行时暂停,弹给人类「批准 / 拒绝」,批准了才真跑。
思路:中断 → 序列化状态 → 审批 → 续跑
整个流程是一个「run 可以中途存档再读档」的故事:
1. 工具标了 needs_approval=True (tool.py:426)
│
▼
2. 执行该工具前,产生一个 ToolApprovalItem(中断),不执行工具
│ execute_tools_and_side_effects 收集到 interruptions
▼
3. 返回 NextStepInterruption → run 提前结束
│ RunResult.interruptions 里是待审批的工具调用 (result.py:367)
▼
4. 调用方: result.to_state() 拿到可序列化的 RunState (result.py:393)
│ 对每个中断: state.approve(item) 或 state.reject(item)
▼
5. 把 RunState 当 input 再传给 Runner.run(state) → 从断点续跑
│ run.py:469 检测 isinstance(input, RunState) → is_resumed_state
▼
6. resolve_interrupted_turn(run.py:849) 用已审批的决定继续执行那些工具
审批的 API
中断信息是 ToolApprovalItem(items.py:508)。审批决定挂在两个层面:
RunState.approve(item)/RunState.reject(item)(run_state.py:1254/run_state.py:1269)——给「读档续跑」用。RunContextWrapper.approve_tool/reject_tool/get_approval_status(run_context.py:1038/363/377)——运行时上下文里的审批记录。
always_approve=True 可让某工具后续调用都自动通过,不再每次问。
续跑如何不重跑已完成的工作
RunState 保存了 _current_step、_last_processed_response、已生成的 items、模型响应等(result.py:71 的 _populate_state_from_result)。续跑时 run.py:1030 检测到 _current_step 是 NextStepInterruption,直接进 resolve_interrupted_turn 接着那个回合往下走,而不是从头来过——这就是为什么「下一步即数据 + 可序列化状态」(01 章 §8)是基础设施级的设 计。
这套设计也支撑嵌套审批
当被 Agent.as_tool 包装的子 agent 内部触发审批时,父 run 的审批会被镜像进子 run(agent.py:694 的 _apply_nested_approvals),保证嵌套场景下审批语义一致。
4.4 巧妙之处
- Session 协议极简(4 个方法),把「历史存哪」完全交给实现,所以能从内存 SQLite 一路扩到 Redis/Mongo/SQLAlchemy 而内核不变。
- Tracing 复用分布式追踪心智模型(trace + span 树 + 批量导出),既能在官方控制台可视化,又能接自家系统。
- HITL 不是特例而是
RunState可序列化的自然结果:中断只是「一种提前返回的 next_step」,审批只是「往状态里写决定」,续跑只是「拿状态当输入再跑」——一套机制干三件事。
4.5 代码地图
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 会话协议 | src/agents/memory/session.py | Session、SessionABC |
| SQLite 实现 | src/agents/memory/sqlite_session.py | SQLiteSession |
| 会话合并/持久化 | src/agents/run_internal/session_persistence.py | prepare_input_with_session、save_result_to_session |
| 追踪入口 | src/agents/tracing/__init__.py | trace、agent_span、generation_span |
| 批量导出 | src/agents/tracing/processors.py | BatchTraceProcessor、BackendSpanExporter |
| 中断/下一步 | src/agents/run_internal/run_steps.py | NextStepInterruption |
| 审批 item | src/agents/items.py | ToolApprovalItem |
| 运行状态 | src/agents/run_state.py | RunState、RunState.approve、RunState.reject |
| 续跑 | src/agents/run.py | AgentRunner.run(is_resumed_state 分支)、resolve_interrupted_turn |
| 上下文审批 | src/agents/run_context.py | RunContextWrapper.approve_tool、get_approval_status |