Ian Chou's Blog

CtxFST CH19 - Relation-Specific Explanations:讓 Planner 不只會找路,還會說明為什麼

CtxFST CH19:Relation-Specific Explanations,讓 Planner 不只會找路,還會說明為什麼

如果說:

那下一個很自然的問題就是:

好,現在它會選了,但它能不能說清楚自己為什麼這樣選?

這個問題非常重要。

因為一個 planner 如果只是默默輸出結果,雖然可以跑,但很難:

所以 CH19 要補的,就是 planner 的最後一層可用性:

讓 selector 與 planner 不只選得對,還能把理由說清楚。

這就是 relation-specific explanations。


先講最短結論

這次升級最值得記住的一句話是:

CtxFST 的 planner 現在不只會輸出 what,也開始能輸出 why

以前你看到的是:

現在你開始看到的是:

這個差別非常大。

因為從這裡開始,planner 不再只是 runtime 裡的一個黑盒排序器,而是一個可被閱讀、審核、對話的決策系統。


為什麼這一步很重要?

因為前面幾章其實已經把 planning 做得不錯了:

但越是這樣,越會冒出一個新的痛點:

系統越來越聰明,也越來越難看懂。

例如:

如果沒有 explanation layer,這些問題只能靠開發者自己翻:

這對 debug 很不友善,對使用者也不透明。

所以這次補 explanation,不是 cosmetic feature,而是在補 planner 的可觀測性。


這次其實解了兩個層次的問題

這次功能可以拆成兩塊,而且難度不一樣。

第一層:Selector explanation

這是比較直接的一層。

因為在 select_candidates() 之後,其實你已經有很多可用資料了:

再加上 proximity 計算已經知道某個 postcondition 靠近 goal 的 relation 類型,例如:

這時候要解釋「為什麼選這個 candidate」,其實已經有足夠資料。

所以就有了:

它的任務就是:

把 top candidate 為什麼贏,以及其他候選為什麼輸,轉成可讀文字。

第二層:Planner explanation

這層就更深一點。

因為 find_plan() 原本只回傳:

list[str]

也就是只有 skill name sequence。

這樣雖然夠執行,但完全不夠解釋:

所以這次又補了:

它不只找出 plan,還會附帶:

這就讓 planner 從「會找路」升級成「會說明這條路是怎麼來的」。


這次新增了哪些核心型別?

這次我覺得很好的地方,是 explanation 沒有直接塞成一堆 ad-hoc string,而是先把結構型別定清楚。

主要新增了兩個 dataclass。

PlanStepTrace

這個型別在記錄每一步的局部解釋:

它回答的是:

這一步 skill 是怎麼被允許執行的?執行後又真的新增了什麼?

PlanExplanation

這個型別則是整份 plan explanation 的容器:

它回答的是:

這次 planner 選中的整條路是什麼?每一步怎麼走?還考慮了哪些替代方案?

這種設計很好,因為它讓 explanation 不是只能印在 CLI 上看,也能被其他系統直接消費。

也就是說,它同時是:


_goal_hop_details() 的意義:不只知道近,還知道為什麼近

這次一個很關鍵的補充,是除了 _goal_hop_distances() 之外,又加了:

如果說前者回答的是:

某個 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 的比較維度攤開了:

也就是說,selector 的決策理由第一次變得可對話。


find_plan_with_explanation() 更進一步:不是只告訴你結果,而是把整條 plan 拆開

planner explanation 的價值更大,因為它處理的不是單點比較,而是一整條 skill chain。

這次 find_plan_with_explanation() 會做幾件事:

  1. 找出 best plan
  2. 建立 step-by-step trace
  3. 嘗試找 alternative plans
  4. 產出可直接印出的 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 的價值。

目前的做法是:

這不是最終型 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 也被接上了:

這代表 explanation 不再只是給開發者手動調函式看,而是正式進入 runtime 介面。

效果也很直接:

lookahead 模式

--lookahead > 0--explain 開啟時,loop 會在每一步先印:

greedy 模式

當沒有 lookahead 但 --explain 開啟時,則會印:

這樣設計很好,因為無論你是:

都可以得到對應層次的 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.

這段輸出之所以好,不只是因為它詳細,而是因為它回答了使用者或開發者最想知道的三件事:

  1. 這次 planner 想怎麼走?
  2. 為什麼第一步是這個?
  3. 還有沒有別的路?

這就讓 planner 從「給答案」進化成「給答案 + 給推理痕跡」。


14 個新測試,把總數推到 52

這次最安心的一點,是 explanation 也有同步補上測試。

新增了 14 個測試後,總數從:

提升到:

而且全部綠燈。

這很重要,因為 explanation feature 最容易出現一種錯覺:

看起來只是多印幾行字,不會太危險

其實不是。

它牽涉到:

如果沒有測試,這層很快就會漂掉。

現在有 52 個測試保護,表示這層可解釋性已經不是 demo,而是 runtime contract 的一部分。


這一步為什麼比「好看」更重要?

很多人會把 explanation 想成 UI embellishment,像是:

但在這個系統裡,它的角色其實更深。

我會把它定義成三件事:

1. Debugging surface

當 planner 選錯路時,你終於比較容易知道是:

2. Audit trail

當你想問:

為什麼這次選 analyze-resume 而不是別的 skill?

現在開始有結構化答案,而不是只能翻 source code。

3. Human-agent interface

如果未來你想讓人類和 planner 共同工作,那 explanation 幾乎是必要的。

因為人類不能只收到:

我幫你選好了

而更需要看到:

我幫你選好了,而且理由是這樣

這樣才有真正的 trust。


這代表 CtxFST 的 planner 又往前走了一步

我會這樣看這幾章的演化:

這一步很關鍵,因為一個系統從:

到:

其實是另一種成熟度。

尤其在 world model / agent runtime 這種系統裡,能解釋通常就代表:

這某種程度上也反過來證明了前面幾章的設計是對的。


下一步最自然會去哪?

有了 relation-specific explanations,後面很自然的兩個方向會是:

1. Weighted Multi-Step Utility Planning

現在 find_plan() 還是偏 shortest sequence。

未來可以讓 plan explanation 同時帶出:

讓 planner 不只找最短路,也能找最值得走的路。

2. Interactive Plan Critique

既然 planner 已經能解釋,那下一步就很適合讓人類能回饋:

這樣 explanation 就不只是輸出,而會變成人機協作的介面。


結語:從這一章開始,Planner 不只是能推理,還開始能對自己的推理負責

如果 CH18 的重點是:

planner 開始會往前看幾步

CH19 的重點就是:

planner 現在開始能把自己為什麼這樣看、為什麼這樣選,清楚說出來。

這是一個非常重要的升級。

因為從這裡開始,CtxFST 的 planner 不再只是:

它還開始具備另一個對實際系統非常重要的能力:

把決策的理由結構化地暴露出來。

而這件事,正是從「能跑的 agent runtime」往「可被信任、可被審核、可被協作的 planner」邁進的關鍵一步。


參考實作


📌 CH18 讓 planner 開始會往前看,而 CH19 則讓 planner 開始會把「為什麼這樣看」講清楚。