前六天,我們從一個最小可用的 Agent Runner 開始,逐步替它加上工具呼叫、Trace 資料模型、SQLite 儲存,以及簡單的 Trace Viewer。
第一週不是要讓 Agent 變得很聰明,而是先解決一個更基礎的問題:
當 Agent 執行任務時,我們能不能看見它中間做了什麼?
如果只能看到 final answer,Agent 就很像黑盒。
現在我們已經做到:
User Task
-> LLM Response
-> Tool Call
-> Tool Result
-> Final Answer
-> Save Trace
-> Trace Viewer
也就是說,Agent 的執行過程已經可以被記錄、保存與查看。
今天是第一週回顧。我會整理目前系統架構、展示一筆完整 trace,並說明第二週為什麼要進入 Eval。
Day 7 是回顧篇,不會新增大型功能。
會完成:
今天先不做:
這些從 Day 8 開始處理。
第一週主要完成五個基礎模組。
| 天數 | 主題 | 完成內容 |
|---|---|---|
| Day 2 | Agent Runner | 建立 SimpleAgent,讓系統可以接收任務並回傳答案 |
| Day 3 | Tool Calling | 加入 calculator tool,讓 Agent 可以使用工具 |
| Day 4 | Trace Model | 設計 TraceStep 和 AgentTrace |
| Day 5 | Trace Storage | 使用 SQLite 保存 trace |
| Day 6 | Trace Viewer | 使用 Streamlit 查看歷史 session 與 steps |
這些模組串起來後,Agent 不再只是:
Input -> Output
而是變成:
Input
-> LLM Response
-> Tool Call
-> Tool Result
-> Final Answer
-> Trace Storage
-> Trace Viewer
這就是本系列標題中「從黑盒到可驗證」的第一步。
第一週結束後,專案大致會長這樣:
agent-testing-platform/
app.py
trace_viewer_app.py
agents/
__init__.py
simple_agent.py
prompts.py
fake_llm.py
tools/
__init__.py
calculator.py
tracing/
__init__.py
models.py
storage/
__init__.py
database.py
schema.sql
ui/
__init__.py
trace_viewer.py
data/
agent_traces.db
每個資料夾的責任如下:
| 資料夾或檔案 | 責任 |
|---|---|
app.py |
命令列入口,負責執行 Agent 並保存 trace |
trace_viewer_app.py |
Streamlit 入口,負責開啟 Trace Viewer |
agents/ |
Agent Runner、prompt、LLM client |
tools/ |
Agent 可以使用的工具 |
tracing/ |
Trace 資料模型 |
storage/ |
SQLite 初始化、儲存與查詢 |
ui/ |
Streamlit UI |
data/ |
SQLite database 檔案 |
這個結構目前還很小,但已經先把責任切開。
第二週開始會加入 Eval。如果前面全部都寫在同一個檔案,後面會很難維護。
Day 2 建立的主要介面是:
SimpleAgent.run(user_task) -> AgentResult
SimpleAgent 的責任是:
後來 Day 3 和 Day 4 又逐步把 AgentResult 擴充成可以包含:
這個設計讓後面的模組可以用一致的方式取得 Agent 執行結果。
例如 Day 5 儲存 trace 時,只需要做:
save_trace(result.trace)
這表示 Agent Runner 和 Storage 之間的界線是清楚的。
Day 3 加入了第一個工具:calculator。
新增的工具放在 tools/calculator.py。
它的用途很單純:
輸入算式 -> 回傳計算結果
例如:
135 * 28 -> 3780
這個工具本身不複雜,但它帶出一個重要概念:
Agent 不應該把所有事情都交給 LLM 猜,而是應該在適合的時候使用可靠工具。
目前我們用 FakeLLMClient 模擬工具呼叫。
也就是說,現在還不是使用真正 LLM API 的 function calling,而是先用可控方式建立 Agent 內部流程:
LLM Client 回傳 tool_call request
-> SimpleAgent 查找 tool registry
-> 執行 calculator
-> 回傳 final answer
這樣一來,初期不會被不同模型供應商的 API 格式綁住,可以先把 Agent 架構設計清楚。
Day 4 設計了兩個資料模型:
AgentTrace
TraceStep
AgentTrace 代表一次 Agent 執行。
它包含:
session_id
steps
TraceStep 代表一次執行中的其中一個步驟。
它包含:
step_type
name
input_data
output_data
error
timestamp
也就是說,一次任務不再只有最後答案,而是可以被拆成多個 step。
例如:
0. user_input
1. llm_response
2. tool_call
3. tool_result
4. final_answer
這個設計是後面 Eval 和 Failure Analysis 的基礎。
如果沒有 trace,我們即使知道 Agent 答錯,也很難知道錯在哪一步。
Day 5 把 trace 寫進 SQLite。
新增的資料表是:
sessions
steps
sessions 負責記錄一次 Agent 執行。
steps 負責記錄該 session 裡面的每個 step。
兩者的關係是:
sessions 1 -> many steps
這個設計讓我們可以查詢:
雖然目前還沒有做複雜查詢,但資料結構已經先準備好。
Day 6 使用 Streamlit 建立了簡單 Trace Viewer。
目前 Trace Viewer 可以做到:
啟動方式是:
streamlit run trace_viewer_app.py
如果資料庫中已經有 trace,就可以從下拉選單選擇 session,查看該次 Agent 執行過程。
這讓 trace 從「資料庫裡的資料」變成「人看得懂的紀錄」。
先在專案根目錄執行:
python3 app.py
輸入:
請計算 135 * 28
Agent 會回傳:
Answer:
The result is 3780
同時,trace 裡會記錄類似這樣的 steps:
{
"session_id": "8a2b2f21-d1a4-4d8d-9a57-0db43a4e9d23",
"steps": [
{
"step_type": "user_input",
"name": "User Task",
"input_data": {
"content": "請計算 135 * 28"
},
"output_data": {},
"error": null
},
{
"step_type": "llm_response",
"name": "LLM Response",
"input_data": {
"messages": [
{
"role": "system",
"content": "..."
},
{
"role": "user",
"content": "請計算 135 * 28"
}
]
},
"output_data": {
"response": {
"type": "tool_call",
"tool_name": "calculator",
"tool_input": "135 * 28"
}
},
"error": null
},
{
"step_type": "tool_call",
"name": "Tool Call",
"input_data": {
"tool_name": "calculator",
"tool_input": "135 * 28"
},
"output_data": {},
"error": null
},
{
"step_type": "tool_result",
"name": "Tool Result",
"input_data": {
"tool_name": "calculator"
},
"output_data": {
"tool_output": "3780"
},
"error": null
},
{
"step_type": "final_answer",
"name": "Final Answer",
"input_data": {},
"output_data": {
"answer": "The result is 3780"
},
"error": null
}
]
}
這筆 trace 可以回答幾個問題。
第一,使用者輸入是什麼?
請計算 135 * 28
第二,Agent 為什麼會用工具?
因為 llm_response 回傳了:
{
"type": "tool_call",
"tool_name": "calculator",
"tool_input": "135 * 28"
}
第三,工具實際收到什麼輸入?
135 * 28
第四,工具實際回傳什麼?
3780
第五,最後答案如何產生?
The result is 3780
這就是 Trace 的用處:不是只知道答案,而是知道答案背後的執行路徑。
接著啟動 Trace Viewer:
streamlit run trace_viewer_app.py
在瀏覽器中,選擇剛剛產生的 session。
畫面上應該會看到:
user_input - User Task
llm_response - LLM Response
tool_call - Tool Call
tool_result - Tool Result
final_answer - Final Answer
每個 step 都可以展開查看 input 和 output。
這表示我們已經可以用 UI 回放一次 Agent 執行。
雖然目前 UI 很簡單,但對除錯已經有幫助。
第一週完成後,平台已經能回答:
這由 user_input step 記錄。
這由 llm_response step 記錄。
目前我們使用 FakeLLMClient,所以這個決策是規則模擬出來的。未來換成真正 LLM API 後,這個 step 會更重要。
這由 tool_call step 記錄。
這由 tool_result step 記錄。
這由 final_answer step 記錄。
Day 5 已經把 trace 存進 SQLite。
Day 6 已經用 Streamlit 做出簡單 Trace Viewer。
這些能力讓 Agent 從原本的黑盒,變成至少可以被觀察的系統。
第一週完成的是基礎,不是完整平台。
目前仍有幾個限制。
目前使用的是 FakeLLMClient。
這讓我們可以穩定測試流程,但也表示目前的 Agent 還不是由真實模型做決策。
後續可以把 FakeLLMClient 換成 OpenAI、Claude 或 Gemini client。
目前工具呼叫是透過 fake client 回傳固定格式:
{
"type": "tool_call",
"tool_name": "calculator",
"tool_input": "135 * 28"
}
這還不是正式的 function calling。
不過它已經足夠讓我們先建立工具執行、工具紀錄與 trace 儲存流程。
如果輸入:
請計算 135 28
目前程式仍可能因為找不到算式而拋出 exception。
理想狀態應該是:
錯誤被捕捉
-> 寫入 trace
-> 回傳可讀的錯誤訊息
-> 後續分類成 failure type
這會在後面的 Failure Analysis 和 Guardrails 階段再處理。
目前平台能回答:
Agent 做了什麼?
但還不能回答:
Agent 做得對不對?
這就是第二週要處理的 Eval。
Trace 解決的是可觀測性問題。
也就是:
我們能不能看見 Agent 做了什麼?
但 Agent Testing Platform 不能只停在「看見」。
接下來還要能回答:
Agent 有沒有完成任務?
假設我們有一個任務:
請計算 135 * 28
Agent 回答:
The result is 3780
人可以看出這是對的。
但平台要能自動判斷:
expected: 3780
actual: The result is 3780
result: pass
這就需要 Evaluation。
第二週的主軸是 Eval。
預計會完成:
| 天數 | 主題 | 內容 |
|---|---|---|
| Day 8 | 什麼是 Agent Evaluation? | 定義 eval dataset、success rate、format accuracy |
| Day 9 | 設計 Test Case 格式 | 定義 input、expected、grading_method |
| Day 10 | 建立第一組 Eval Dataset | 建立 10 到 20 筆測試案例 |
| Day 11 | Batch Evaluation Runner | 讓 Agent 一次跑完整組測試 |
| Day 12 | 最簡單的自動評分 | 實作 exact match 和 keyword match |
| Day 13 | JSON 格式驗證 | 檢查 structured output 是否符合格式 |
| Day 14 | 第二週回顧 | 產出第一份 baseline evaluation report |
第二週結束後,平台就不只會記錄 Agent 做了什麼,也會開始評估 Agent 是否完成任務。
第一週最主要的成果是:
讓 Agent 從黑盒變成可觀察。
目前我們已經完成:
這些功能讓我們可以看到一次 Agent 執行中的關鍵步驟:
user_input
llm_response
tool_call
tool_result
final_answer
但這還只是 Agent Testing Platform 的第一層。
接下來第二週要開始建立 Eval,讓平台不只會看過程,也能判斷結果是否正確。
Day 8 會進入 Agent Evaluation。
下一篇會先不急著寫 evaluator,而是先定義幾個基本問題:
從 Day 8 開始,我們會讓這個平台從「看得到 Agent 過程」,進一步走向「測得出 Agent 表現」。