Query Transition Model
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,心智會順很多。