Intermediate Trace 控制能力¶
Intermediate Trace 是 TFRobot 的执行记录机制:Chain 每次运行产生的中间消息(LLM 输出、工具调用与返回)被采集到 UserMessage.intermediate_trace.records,在后续 LLM 调用时按策略展开注入上下文,使 LLM 能感知多轮对话间的执行历史。
这一机制跨越三层控制——采集(Chain 层决定是否写入)、注入(LLM 层决定是否展开到上下文)和展开策略(per-message 的视图配置),并提供运行时动态扩容能力。
设计理念¶
LLM 的推理质量高度依赖上下文信息量。在多轮对话中,前序 Chain 运行的中间产物(如搜索结果、工具调用、子链输出)对后续推理至关重要。如果这些信息丢失,LLM 会陷入"信息黑洞",反复调用相同工具或做出已推翻的判断。
Intermediate Trace 的设计目标是在 信息保真 与 Token 预算 之间取得平衡:
- 全量采集:Chain 运行结束后批量写入所有
intermediate_msgs,不遗漏 - 按需展开:通过
TraceView控制 LLM 实际看到的记录子集,避免上下文膨胀 - 运行时扩容:LLM 可通过
ExpandContextTool主动请求查看更多折叠记录
核心概念¶
IntermediateTrace¶
每条 UserMessage 持有一个 IntermediateTrace 实例,是该消息生命周期内所有 Chain 执行记录的容器。
class IntermediateTrace(TFBaseModel):
records: list["Message"] = Field(default_factory=list) # 按时序排列的执行记录
view: TraceView = Field(default_factory=TraceView) # 控制注入行为的视图配置
TraceView¶
TraceView 挂载在 IntermediateTrace 上,控制该消息的 trace 在 LLM 上下文中如何展开。
class TraceView(TFBaseModel):
mode: Literal["head_tail", "all", "span"] = "head_tail"
head_records: int = 10 # head_tail 模式下头部条数
tail_records: int = 20 # head_tail 模式下尾部条数
span_ids: list[str] = Field(default_factory=list) # span 模式下的过滤 ID
Trace Record 的元数据¶
每条 record 写入时会注入 __trace_meta 到 additional_kwargs,包含 OpenTelemetry span 信息:
{
"span_id": "0abc1234def56778",
"parent_span_id": "1fedcba09876554",
"chain_name": "search_chain",
"state": "succeeded"
}
这些元数据用于 span 模式过滤和树摘要生成,不会注入到 LLM 上下文中。
三层控制模型¶
第一层:采集(Chain 级别)¶
| 配置 | 类型 | 默认值 | 定义位置 | 说明 |
|---|---|---|---|---|
enable_trace_intermediate |
bool |
True |
Chain (brain/chain/base.py) |
控制 _batch_append_to_trace() 是否将 intermediate_msgs 写入 intermediate_trace.records |
行为细节:
- 在
run()/async_run()的finally块中执行,确保 chain 完成后才写入 - 仅对
UserMessage类型的current_input采集;非用户消息(如AssistantTextMessage)跳过 - 可被
run()/async_run()的同名参数enable_trace_intermediate在运行时覆盖
# 定义 Chain 实例时关闭采集
chain = SomeChain(
llm=my_llm,
enable_trace_intermediate=False, # 该 chain 的中间消息不写入 trace
)
# 运行时临时覆盖
chain.run(current_input=msg, enable_trace_intermediate=False)
第二层:注入(LLM 级别)¶
| 配置 | 类型 | 默认值 | 定义位置 | 说明 |
|---|---|---|---|---|
inject_trace |
bool |
True |
BaseLLM (brain/chain/llms/base.py) |
总开关。False 时对话格式与无 trace 行为完全一致 |
TFROBOT_INJECT_TRACE_LAST_N |
int(环境变量) |
1 |
环境变量 | conversation 中最多对多少条"非空 trace 的 UserMessage"展开 |
注入逻辑(BaseLLM.construct_prompt_context 中执行):
- conversation 注入:倒序遍历 conversation,找到最多
last_n条有非空 trace 的UserMessage,对每条调用_expand_trace_for_msg()展开 pre/post 插入对话 - current_input 注入:无条件展开(不受 last_n 限制),pre 追加到 conversation 末尾,post 插入到
intermediate_msgs前面 - 去重守卫:若
current_input恰好也在 conversation 中(同对象),conversation 遍历时跳过它,避免 pre/post 被注入两次
# LLM 实例级配置
llm = GPT(name="gpt-4o", inject_trace=False) # 该 LLM 不注入任何 trace
# 环境变量控制(进程级)
# export TFROBOT_INJECT_TRACE_LAST_N=3 # 注入最近 3 条有 trace 的 UserMessage
# export TFROBOT_INJECT_TRACE_LAST_N=0 # 不注入历史 trace,仅展开 current_input
第三层:展开策略(per-UserMessage 的 TraceView)¶
展开策略通过 TraceView 配置,控制每条消息的 trace records 以何种方式呈现给 LLM。
mode: head_tail(默认)¶
按时序选取前 head_records 条 + 后 tail_records 条,中间被折叠的记录用一行占位标注 msg_id:
【执行记录(msg_id=5, 展示 30/100 条,共 12.5k 字符)】
[depth=0] search_chain (span=abc123, 12 msgs, 3.2k chars, succeeded)
[depth=0] file_reader_chain (span=def456, 5 msgs, 8.1k chars, succeeded)
...(前 10 条记录)
【折叠 70 条记录, msg_id=5】
...(后 20 条记录)
LLM 看到折叠占位后,可调用 ExpandContextTool 定向扩容。
mode: all¶
返回全部记录,不折叠。适用于 trace 记录较少、需要完整信息的场景。
mode: span¶
按 span_ids 过滤记录,仅返回指定 span 的消息。保留作为调试能力,ExpandContextTool 不暴露此模式。
pre/post 分组¶
展开后的记录按时序与 UserMessage 自身的 create_ts 对比,分为两组:
- pre(历史上下文):
create_ts < msg.create_ts的记录 → 追加到 UserMessage 前面 - post(执行记录):
create_ts >= msg.create_ts的记录 → 追加到 UserMessage 后面
运行时动态扩容¶
ExpandContextTool¶
LLM 在推理过程中可调用 ExpandContextTool 扩大某条消息的 trace 可见范围:
# LLM 自主调用(通过 tool_call)
expand_context(op="tail", increment=10, msg_id=5)
# → 将 msg_id=5 的 tail_records 增加 10
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
op |
"head" \| "tail" |
扩展方向 |
increment |
int(>=1,默认 10) |
本次追加条数,累加到 TraceView 对应字段 |
msg_id |
int \| None |
操作目标。None 操作当前输入消息;指定值从会话历史查找 |
设计约束:
- 工具不会修改
TraceView.mode,仅调整head_records/tail_records - 当
msg_id=None时,改动会随消息入库持久化;指定msg_id时改动仅在本次Robot.run内生效
配置速查表¶
| 控制点 | 配置项 | 类型 | 默认值 | 生效范围 | 可运行时覆盖 |
|---|---|---|---|---|---|
| 采集 | Chain.enable_trace_intermediate |
bool |
True |
Chain 实例 | 是(run() 参数) |
| 注入总开关 | BaseLLM.inject_trace |
bool |
True |
LLM 实例 | 否 |
| 注入历史条数 | TFROBOT_INJECT_TRACE_LAST_N |
int |
1 |
进程级(环境变量) | 否 |
| 展开模式 | TraceView.mode |
str |
"head_tail" |
per-UserMessage | 是 |
| 头部条数 | TraceView.head_records |
int |
10 |
per-UserMessage | 是(ExpandContextTool) |
| 尾部条数 | TraceView.tail_records |
int |
20 |
per-UserMessage | 是(ExpandContextTool) |
| Span 过滤 | TraceView.span_ids |
list[str] |
[] |
per-UserMessage | 是 |
数据流全景¶
Chain.run() 完成
│
▼ enable_trace_intermediate=True?
│
▼ _batch_append_to_trace()
│ └─ intermediate_msgs → intermediate_trace.records
│ (每条 record 注入 __trace_meta)
│
▼ 下一次 LLM 调用
│
▼ BaseLLM.construct_prompt_context()
│
├─ inject_trace=True?
│ │
│ ├─ conversation 注入
│ │ └─ 倒序选 last_n 条有 trace 的 UserMessage
│ │ └─ _expand_trace_for_msg() → pre + post
│ │
│ └─ current_input 注入(不受 last_n 限制)
│ └─ _expand_trace_for_msg() → pre + post
│
▼ LLM 看到展开后的完整上下文
│
▼ (可选)LLM 调用 ExpandContextTool
│ └─ 扩大 TraceView.head_records / tail_records
│ └─ 下一次注入时按新容量展开
与其他模块的协作¶
| 模块 | 协作方式 |
|---|---|
| Chain | 通过 enable_trace_intermediate 控制采集;在 finally 块中批量写入 records |
| BaseLLM | 通过 inject_trace 控制注入;construct_prompt_context() 负责展开和插入 |
| DCBrain | 在 plan 模板中渲染 intermediate_trace.records,通过 parent_span_id 过滤仅展示顶层调用 |
| ExpandContextTool | LLM 可调用的工具,运行时扩容 TraceView 的容量字段 |
| Schema (UserMessage) | IntermediateTrace 和 TraceView 的数据结构定义 |
| Telemetry (OTel) | 采集时自动注入 span_id / parent_span_id,支持树摘要和 span 模式过滤 |
使用建议¶
何时关闭采集¶
enable_trace_intermediate=False 适用于:
- 内部产物不需要暴露给外层的工具型 Chain(如纯格式转换)
- 对 Token 消耗极其敏感且确定不需要历史执行记录的场景
何时关闭注入¶
inject_trace=False 适用于:
- 专注型链,不需要感知之前的执行历史
- 对话格式需要与无 trace 行为严格一致的测试场景
调整 last-N¶
TFROBOT_INJECT_TRACE_LAST_N 的典型设置:
| 值 | 场景 |
|---|---|
1(默认) |
仅注入最近一条有 trace 的历史消息,适合大多数场景 |
0 |
不注入历史 trace,仅展开当前输入的 trace |
>1 |
需要更多历史上下文的长对话场景,注意 Token 消耗 |