Skip to content

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_metaadditional_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 中执行):

  1. conversation 注入:倒序遍历 conversation,找到最多 last_n 条有非空 trace 的 UserMessage,对每条调用 _expand_trace_for_msg() 展开 pre/post 插入对话
  2. current_input 注入:无条件展开(不受 last_n 限制),pre 追加到 conversation 末尾,post 插入到 intermediate_msgs 前面
  3. 去重守卫:若 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) IntermediateTraceTraceView 的数据结构定义
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 消耗