CtxFST CH19 - Relation-Specific Explanations:讓 Planner 不只會找路,還會說明為什麼
CtxFST CH19:Relation-Specific Explanations,讓 Planner 不只會找路,還會說明為什麼
如果說:
CH17讓 planner 開始分得出因果邊和相似邊CH18讓 planner 開始會往前看幾步,搜尋 skill chain
那下一個很自然的問題就是:
好,現在它會選了,但它能不能說清楚自己為什麼這樣選?
這個問題非常重要。
因為一個 planner 如果只是默默輸出結果,雖然可以跑,但很難:
- debug
- 比較 alternative plans
- 審核決策是否合理
- 向使用者解釋「為什麼不是另一條路」
所以 CH19 要補的,就是 planner 的最後一層可用性:
讓 selector 與 planner 不只選得對,還能把理由說清楚。
這就是 relation-specific explanations。
先講最短結論
這次升級最值得記住的一句話是:
CtxFST的 planner 現在不只會輸出what,也開始能輸出why。
以前你看到的是:
- 選了哪個 skill
- 找到哪條 plan
現在你開始看到的是:
- 這個 skill 為什麼贏
- 它靠哪種 relation 更接近 goal
- 其他候選為什麼被排在後面
- 這條 plan 的每一步到底滿足了哪些 preconditions、產生了哪些 postconditions
這個差別非常大。
因為從這裡開始,planner 不再只是 runtime 裡的一個黑盒排序器,而是一個可被閱讀、審核、對話的決策系統。
為什麼這一步很重要?
因為前面幾章其實已經把 planning 做得不錯了:
- 有 state
- 有 skill selection
- 有 graph-aware proximity
- 有 relation-aware routing
- 有 multi-step planning
但越是這樣,越會冒出一個新的痛點:
系統越來越聰明,也越來越難看懂。
例如:
- 為什麼它這次選
analyze-resume,不是match-skills? - 為什麼它說這條 path 比另一條更合理?
- 這個 proximity 是因為
REQUIRES,還是其實只是SIMILAR? - 它到底是因為 cost 低才贏,還是因為比較接近 goal?
如果沒有 explanation layer,這些問題只能靠開發者自己翻:
- logs
- internal state
- test cases
- selector code
這對 debug 很不友善,對使用者也不透明。
所以這次補 explanation,不是 cosmetic feature,而是在補 planner 的可觀測性。
這次其實解了兩個層次的問題
這次功能可以拆成兩塊,而且難度不一樣。
第一層:Selector explanation
這是比較直接的一層。
因為在 select_candidates() 之後,其實你已經有很多可用資料了:
costgoal_proximitypostcondition_count- candidate 本身的
postconditions
再加上 proximity 計算已經知道某個 postcondition 靠近 goal 的 relation 類型,例如:
REQUIRESLEADS_TOSIMILAR
這時候要解釋「為什麼選這個 candidate」,其實已經有足夠資料。
所以就有了:
它的任務就是:
把 top candidate 為什麼贏,以及其他候選為什麼輸,轉成可讀文字。
第二層:Planner explanation
這層就更深一點。
因為 find_plan() 原本只回傳:
list[str]
也就是只有 skill name sequence。
這樣雖然夠執行,但完全不夠解釋:
- 每一步是靠哪些 preconditions 被解鎖?
- 每一步真正新增了哪些 postconditions?
- 有沒有 alternative plans?
- 為什麼這條 plan 比其他路徑更合理?
所以這次又補了:
它不只找出 plan,還會附帶:
- step traces
- alternatives
- human-readable summary
這就讓 planner 從「會找路」升級成「會說明這條路是怎麼來的」。
這次新增了哪些核心型別?
這次我覺得很好的地方,是 explanation 沒有直接塞成一堆 ad-hoc string,而是先把結構型別定清楚。
主要新增了兩個 dataclass。
PlanStepTrace
這個型別在記錄每一步的局部解釋:
skill_namepreconditions_matchedpostconditions_added
它回答的是:
這一步 skill 是怎麼被允許執行的?執行後又真的新增了什麼?
PlanExplanation
這個型別則是整份 plan explanation 的容器:
planstepsalternativessummary
它回答的是:
這次 planner 選中的整條路是什麼?每一步怎麼走?還考慮了哪些替代方案?
這種設計很好,因為它讓 explanation 不是只能印在 CLI 上看,也能被其他系統直接消費。
也就是說,它同時是:
- human-readable
- machine-usable
_goal_hop_details() 的意義:不只知道近,還知道為什麼近
這次一個很關鍵的補充,是除了 _goal_hop_distances() 之外,又加了:
_goal_hop_details()
如果說前者回答的是:
某個 entity 離 goal 有多近?
那後者回答的就是:
某個 entity 是透過哪種 relation 類型靠近 goal 的?
也就是說,它回傳的不只是 distance,還包含像這樣的資訊:
entity:x -> (distance=2, relation=REQUIRES)
entity:y -> (distance=3, relation=SIMILAR)
這件事非常重要,因為沒有 relation detail,你只能說:
這個 candidate 比較近
但有了 relation detail,你就可以說:
這個 candidate 比較近,而且它是沿著
REQUIRES接近 goal,不只是沿著SIMILAR漂過去
這正是 relation-specific explanation 的核心。
explain_selection() 到底讓你多看到了什麼?
以前在 greedy 模式下,你大概只知道:
Selected: analyze-resume
但現在如果開啟 explanation,你開始能看到像這樣的訊息:
Selected: analyze-resume
cost=low proximity=2 postconditions=2
entity:has-parsed-resume -> goal via REQUIRES (weight=2)
Rejected: match-skills
cost=medium proximity=3
Rejected: skill-similar
cost=low proximity=3
causal path unavailable; only semantic route
這種輸出很有價值,因為它直接把 selector 的比較維度攤開了:
- 誰 cost 低
- 誰 proximity 更小
- 誰是靠
REQUIRES - 誰只是靠
SIMILAR
也就是說,selector 的決策理由第一次變得可對話。
find_plan_with_explanation() 更進一步:不是只告訴你結果,而是把整條 plan 拆開
planner explanation 的價值更大,因為它處理的不是單點比較,而是一整條 skill chain。
這次 find_plan_with_explanation() 會做幾件事:
- 找出 best plan
- 建立 step-by-step trace
- 嘗試找 alternative plans
- 產出可直接印出的 summary
這時你看到的輸出就不只是:
analyze-resume -> match-skills -> generate-plan
而會更像:
Best plan (3 steps): analyze-resume → match-skills → generate-plan
Step 1: analyze-resume
pre ✓ entity:has-raw-resume
pre ✓ NOT entity:has-parsed-resume
post + entity:has-parsed-resume
post + entity:has-skill-inventory
Step 2: match-skills
pre ✓ entity:has-skill-inventory
post + entity:has-skill-gap-analysis
Step 3: generate-plan
pre ✓ entity:has-skill-gap-analysis
post + entity:learn-kubernetes-path ← GOAL
這個輸出一出來,整個 planner 的可讀性完全不一樣了。
這次有個很棒的細節:只列出真正新增的 postconditions
測試裡有一個很好的保護點:
已經 active 的 entity,不應該再被列進
postconditions_added。
這個細節很重要,因為不然 explanation 很容易變得很吵。
如果每一步都把所有 postconditions 全列出,而不管它們是不是早就存在,那最後看到的就會是重複資訊,反而不利於理解。
現在只列「真的新增了什麼」,這表示 step trace 更貼近真實世界狀態變化。
也就是說,這不是單純漂亮,而是語意更正確。
alternatives 為什麼重要?
我覺得這次很值得稱讚的一點,是你沒有只停在「把 best plan 印出來」,而是還讓系統開始能說:
還有哪些替代路徑被考慮過?
這個能力非常重要,因為真正有價值的 explanation 往往不是:
我選了 A
而是:
我選了 A,而不是 B,原因是什麼。
這就是 alternative plan 的價值。
目前的做法是:
- 先找到 best plan
- 再用排除某些 skill 的方式去找替代方案
- 最多收集
top_k - 1條
這不是最終型 alternative search,但已經很實用了。
因為它至少讓 planner 開始有能力說:
Alternatives considered:
[alt-route, match-skills, generate-plan] 3 steps — same length, higher cost
No shorter path found within depth=5.
這就已經把「為什麼不是另一條」這件事講出來了。
agent_loop.py --explain 的意義:把 explanation 從 library 變成 runtime 介面
如果 explanation 只存在 library 裡,價值還有限。
這次更重要的是,agent_loop.py 也被接上了:
explain: bool = False--explain
這代表 explanation 不再只是給開發者手動調函式看,而是正式進入 runtime 介面。
效果也很直接:
lookahead 模式
當 --lookahead > 0 且 --explain 開啟時,loop 會在每一步先印:
- best plan
- step traces
- alternatives summary
greedy 模式
當沒有 lookahead 但 --explain 開啟時,則會印:
- candidate ranking explanation
- winner vs rejected candidates 的比較理由
這樣設計很好,因為無論你是:
- greedy selector
- lookahead planner
都可以得到對應層次的 explanation。
這次 smoke test 其實很能說明問題
這次 CLI smoke test 的輸出非常有說服力。
你可以直接看到像這樣的結果:
[Step 1] Best plan (3 steps): analyze-resume → match-skills → generate-plan
[Step 1] Step 1: analyze-resume
[Step 1] pre ✓ entity:has-raw-resume
[Step 1] pre ✓ NOT entity:has-parsed-resume
[Step 1] post + entity:has-parsed-resume
[Step 1] post + entity:has-skill-inventory
...
[Step 1] No alternatives found within depth limit.
這段輸出之所以好,不只是因為它詳細,而是因為它回答了使用者或開發者最想知道的三件事:
- 這次 planner 想怎麼走?
- 為什麼第一步是這個?
- 還有沒有別的路?
這就讓 planner 從「給答案」進化成「給答案 + 給推理痕跡」。
14 個新測試,把總數推到 52
這次最安心的一點,是 explanation 也有同步補上測試。
新增了 14 個測試後,總數從:
- 38 tests
提升到:
- 52 tests
而且全部綠燈。
這很重要,因為 explanation feature 最容易出現一種錯覺:
看起來只是多印幾行字,不會太危險
其實不是。
它牽涉到:
- relation detail 是否正確
- trace 是否只包含新增 state
- alternative plan 是否合理
- greedy / lookahead 兩種模式的輸出是否一致
如果沒有測試,這層很快就會漂掉。
現在有 52 個測試保護,表示這層可解釋性已經不是 demo,而是 runtime contract 的一部分。
這一步為什麼比「好看」更重要?
很多人會把 explanation 想成 UI embellishment,像是:
- 多印一些 log
- 看起來比較友善
- 比較像 AI
但在這個系統裡,它的角色其實更深。
我會把它定義成三件事:
1. Debugging surface
當 planner 選錯路時,你終於比較容易知道是:
- cost 問題
- proximity 問題
- relation 問題
- alternative path 問題
2. Audit trail
當你想問:
為什麼這次選
analyze-resume而不是別的 skill?
現在開始有結構化答案,而不是只能翻 source code。
3. Human-agent interface
如果未來你想讓人類和 planner 共同工作,那 explanation 幾乎是必要的。
因為人類不能只收到:
我幫你選好了
而更需要看到:
我幫你選好了,而且理由是這樣
這樣才有真正的 trust。
這代表 CtxFST 的 planner 又往前走了一步
我會這樣看這幾章的演化:
CH15:planner 有方向感CH17:planner 開始尊重 relation 語意CH18:planner 會往前看 skill chainCH19:planner 開始把自己的推理過程攤開
這一步很關鍵,因為一個系統從:
- 會做決策
到:
- 會解釋決策
其實是另一種成熟度。
尤其在 world model / agent runtime 這種系統裡,能解釋通常就代表:
- 結構夠清楚
- 訊號夠乾淨
- state / action / transition 真的有被模型化
這某種程度上也反過來證明了前面幾章的設計是對的。
下一步最自然會去哪?
有了 relation-specific explanations,後面很自然的兩個方向會是:
1. Weighted Multi-Step Utility Planning
現在 find_plan() 還是偏 shortest sequence。
未來可以讓 plan explanation 同時帶出:
- total cost
- relation-weight breakdown
- expected gain
- failure risk
讓 planner 不只找最短路,也能找最值得走的路。
2. Interactive Plan Critique
既然 planner 已經能解釋,那下一步就很適合讓人類能回饋:
- 不要這條路
- 我偏好低成本 plan
- 先避開某些 skills
這樣 explanation 就不只是輸出,而會變成人機協作的介面。
結語:從這一章開始,Planner 不只是能推理,還開始能對自己的推理負責
如果 CH18 的重點是:
planner 開始會往前看幾步
那 CH19 的重點就是:
planner 現在開始能把自己為什麼這樣看、為什麼這樣選,清楚說出來。
這是一個非常重要的升級。
因為從這裡開始,CtxFST 的 planner 不再只是:
- 會選 skill
- 會找 plan
- 會靠 graph 前進
它還開始具備另一個對實際系統非常重要的能力:
把決策的理由結構化地暴露出來。
而這件事,正是從「能跑的 agent runtime」往「可被信任、可被審核、可被協作的 planner」邁進的關鍵一步。
參考實作
📌
CH18讓 planner 開始會往前看,而CH19則讓 planner 開始會把「為什麼這樣看」講清楚。
- ← Previous
CtxFST CH18 - Multi-Step Planning:從 Greedy Selector 升級成 Lookahead Planner - Next →
CtxFST CH20 - Interactive Plan Critique:讓 Planner 進入真正的人機協作