Query Transition Model
This document is not available in the current locale. Showing fallback: zh
When This Page Helps
Why each continuation needs an explicit reason. Best read alongside s11 (Error Recovery).
Best Read Alongside
这篇桥接文档专门解决一个问题:
为什么一个只会
continue的 agent,不足以支撑完整系统,而必须显式知道“为什么继续到下一轮”?
这一篇为什么要存在
主线里:
s01先教你最小循环s06开始教上下文压缩s11开始教错误恢复
这些都对。
但如果你只分别学这几章,脑子里很容易还是停留在一种过于粗糙的理解:
“反正
continue了就继续呗。”
这在最小 demo 里能跑。
但当系统开始长出恢复、压缩和外部控制以后,这样理解会很快失灵。
因为系统继续下一轮的原因其实很多,而且这些原因不是一回事:
- 工具刚执行完,要把结果喂回模型
- 输出被截断了,要续写
- 上下文刚压缩完,要重试
- 运输层刚超时了,要退避后重试
- stop hook 要求当前 turn 先不要结束
- token budget 还允许继续推进
如果你不把这些“继续原因”从一开始拆开,后面会出现三个大问题:
- 日志看不清
- 测试不好写
- 教学心智会越来越模糊
先解释几个名词
什么叫 transition
这里的 transition,你可以先把它理解成:
上一轮为什么转移到了下一轮。
它不是“消息内容”,而是“流程原因”。
什么叫 continuation
continuation 就是:
这条 query 当前还没有结束,要继续推进。
但 continuation 不止一种。
什么叫 query boundary
query boundary 就是一轮和下一轮之间的边界。
每次跨过这个边界,系统最好都知道:
- 这次为什么继续
- 这次继续前有没有修改状态
- 这次继续后应该怎么读主循环
最小心智模型
先不要把 query 想成一条线。
更接近真实情况的理解是:
一条 query
= 一组“继续原因”串起来的状态转移
例如:
用户输入
->
模型产生 tool_use
->
工具执行完
->
tool_result_continuation
->
模型输出过长
->
max_tokens_recovery
->
压缩后继续
->
compact_retry
->
最终结束
这样看,你会更容易理解:
系统不是单纯在 while loop 里转圈,而是在一串显式的转移原因里推进。
关键数据结构
1. QueryState 里的 transition
最小版建议就把这类字段显式放进状态里:
state = {
"messages": [...],
"turn_count": 3,
"has_attempted_compact": False,
"continuation_count": 1,
"transition": None,
}
这里的 transition 不是可有可无。
它的意义是:
- 当前这轮为什么会出现
- 下一轮日志应该怎么解释
- 测试时应该断言哪条路径被走到
2. TransitionReason
教学版最小可以先这样分:
TRANSITIONS = (
"tool_result_continuation",
"max_tokens_recovery",
"compact_retry",
"transport_retry",
"stop_hook_continuation",
"budget_continuation",
)
这几种原因的本质不一样:
tool_result_continuation是正常主线继续max_tokens_recovery是输出被截断后的恢复继续compact_retry是上下文处理后的恢复继续transport_retry是基础设施抖动后的恢复继续stop_hook_continuation是外部控制逻辑阻止本轮结束budget_continuation是系统主动利用预算继续推进
3. Continuation Budget
更完整的 query 状态不只会说“继续”,还会限制:
- 最多续写几次
- 最多压缩后重试几次
- 某类恢复是不是已经尝试过
例如:
state = {
"max_output_tokens_recovery_count": 2,
"has_attempted_reactive_compact": True,
}
这些字段的本质都是:
continuation 不是无限制的。
最小实现
第一步:把 continue site 显式化
很多初学者写主循环时,所有继续逻辑都长这样:
continue
教学版应该往前走一步:
state["transition"] = "tool_result_continuation"
continue
第二步:不同继续原因,配不同状态修改
if response.stop_reason == "tool_use":
state["messages"] = append_tool_results(...)
state["turn_count"] += 1
state["transition"] = "tool_result_continuation"
continue
if response.stop_reason == "max_tokens":
state["messages"].append({
"role": "user",
"content": CONTINUE_MESSAGE,
})
state["max_output_tokens_recovery_count"] += 1
state["transition"] = "max_tokens_recovery"
continue
重点不是“多写一行”。
重点是:
每次继续之前,你都要知道自己做了什么状态更新,以及为什么继续。
第三步:把恢复继续和正常继续分开
if should_retry_transport(error):
time.sleep(backoff(...))
state["transition"] = "transport_retry"
continue
if should_recompact(error):
state["messages"] = compact_messages(state["messages"])
state["transition"] = "compact_retry"
continue
这时候你就开始得到一条非常清楚的控制链:
继续
不再是一个动作
而是一类带原因的转移
一张真正应该建立的图
query loop
|
+-- tool executed --------------------> transition = tool_result_continuation
|
+-- output truncated -----------------> transition = max_tokens_recovery
|
+-- compact just happened -----------> transition = compact_retry
|
+-- network / transport retry -------> transition = transport_retry
|
+-- stop hook blocked termination ---> transition = stop_hook_continuation
|
+-- budget says keep going ----------> transition = budget_continuation
它和逆向仓库主脉络为什么对得上
如果你去看更完整系统的查询入口,会发现它真正难的地方从来不是:
- 再调一次模型
而是:
- 什么时候该继续
- 继续前改哪份状态
- 继续属于哪一种路径
所以这篇桥接文档讲的,不是额外装饰,而是完整 query engine 的主骨架之一。
它和主线章节怎么接
s01让你先把 loop 跑起来s06让你知道为什么上下文管理会介入继续路径s11让你知道为什么恢复路径不是一种- 这篇则把“继续原因”统一抬成显式状态
所以你可以把它理解成:
给前后几章之间补上一条“为什么继续”的统一主线。
初学者最容易犯的错
1. 只有 continue,没有 transition
这样日志和测试都会越来越难看。
2. 把所有继续都当成一种
这样会把:
- 正常主线继续
- 错误恢复继续
- 压缩后重试
全部混成一锅。
3. 没有 continuation budget
没有预算,系统就会在某些坏路径里无限试下去。
4. 把 transition 写进消息文本,而不是流程状态
消息是给模型看的。
transition 是给系统自己看的。
5. 压缩、恢复、hook 都发生了,却没有统一的查询状态
这会导致控制逻辑散落在很多局部变量里,越长越乱。
教学边界
这篇最重要的,不是一次枚举完所有 transition 名字,而是先让你守住三件事:
continue最好总能对应一个显式的transition reason- 正常继续、恢复继续、压缩后重试,不应该被混成同一种路径
- continuation 需要预算和状态,而不是无限重来
只要这三点成立,你就已经能把 s01 / s06 / s11 真正串成一条完整主线。
更细的 transition taxonomy、预算策略和日志分类,可以放到你把最小 query 状态机写稳以后再补。
读完这一篇你应该能说清楚
至少能完整说出这句话:
一条 query 不是简单 while loop,而是一串显式 continuation reason 驱动的状态转移。
如果这句话你已经能稳定说清,那么你再回头看 s11、s19,心智会顺很多。