Day 5 我們把 Agent 執行產生的 trace 存進 SQLite。
目前系統已經可以做到:
Agent 執行
-> 產生 AgentTrace
-> 寫入 SQLite
-> 在終端機列出 Saved Sessions
也就是說,trace 已經不會隨著程式結束而消失。
但現在還有一個問題:
Trace 雖然被存起來了,但閱讀方式還不夠直覺。
如果每次都要用 SQL 查詢,或是在終端機看一大段 JSON,對除錯來說並不方便。
所以 Day 6 來做一個簡單的 Trace Viewer。
今天要做到的是:
用 Streamlit 做一個簡單介面,讓我們可以選擇某次 Agent session,並查看它的每個執行步驟。
會完成:
storage/database.py 新增查詢單一 session steps 的 function。ui/trace_viewer.py,負責顯示 trace。trace_viewer_app.py,作為 Streamlit 入口。今天先不做:
這些放到後面的 Eval 和 Failure Analysis 階段。
Day 4 和 Day 5 已經讓我們能記錄 trace,但記錄本身不是目的。
Trace 真正派上用場的地方在於:
當 Agent 行為不符合預期時,我們可以快速回頭看它每一步做了什麼。
例如某次計算任務失敗時,我們希望能看到:
User Task:
請計算 135 * 28
LLM Response:
{
"type": "tool_call",
"tool_name": "calculator",
"tool_input": "135 * 28"
}
Tool Result:
3780
Final Answer:
The result is 3780
這比直接看一大段 JSON 更適合除錯。
今天的 Trace Viewer 不會做得很漂亮,先做到「可閱讀」就好。
今天新增 ui/ 資料夾,並新增一個 Streamlit 入口檔。
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
新增:
| 檔案 | 用途 |
|---|---|
ui/__init__.py |
讓 ui 成為 Python package |
ui/trace_viewer.py |
Trace Viewer 的主要畫面邏輯 |
trace_viewer_app.py |
Streamlit app 入口 |
修改:
| 檔案 | 修改內容 |
|---|---|
storage/database.py |
新增查詢單一 session steps 的 function |
如果還沒有安裝 Streamlit,可以在專案環境中執行:
pip3 install streamlit
如果你有使用 virtual environment,請先啟用環境,再安裝:
python3 -m venv .venv
source .venv/bin/activate
pip install streamlit
這個系列後面也會使用 Streamlit 做簡單 dashboard,所以今天先把基本介面跑起來。
Day 5 的 storage/database.py 已經有 list_sessions(),可以列出所有 session。
今天還需要新增一個 function,用來取得某個 session 的所有 steps。
修改 storage/database.py,在檔案最後新增 get_session_steps():
def get_session_steps(session_id: str) -> list[dict]:
with get_connection() as conn:
conn.row_factory = sqlite3.Row
rows = conn.execute(
"""
SELECT
step_order,
step_type,
name,
input_data,
output_data,
error,
timestamp
FROM steps
WHERE session_id = ?
ORDER BY step_order ASC
""",
(session_id,),
).fetchall()
steps = []
for row in rows:
step = dict(row)
step["input_data"] = json.loads(step["input_data"])
step["output_data"] = json.loads(step["output_data"])
steps.append(step)
return steps
這個 function 做了幾件事。
第一,根據 session_id 從 steps table 查出該次執行的所有步驟。
第二,使用 ORDER BY step_order ASC,確保 step 按照執行順序排列。
第三,把 Day 5 存成 JSON 字串的 input_data 和 output_data 轉回 Python dict。
這樣 UI 顯示時就不用自己處理 JSON parsing。
新增 ui/__init__.py:
這個檔案今天可以先留空,不需要放任何程式碼。
它的作用是讓 Python 把 ui 資料夾視為 package,這樣 trace_viewer_app.py 才能匯入:
from ui.trace_viewer import render_trace_viewer
新增 ui/trace_viewer.py:
import json
import streamlit as st
from storage.database import get_session_steps, init_db, list_sessions
def render_trace_viewer() -> None:
init_db()
st.title("Agent Trace Viewer")
st.caption("查看每一次 Agent 執行時記錄下來的 steps")
sessions = list_sessions()
if not sessions:
st.info("目前還沒有任何 trace。請先執行 python3 app.py 產生一筆 session。")
return
session_options = {
f"{session['created_at']} | {session['session_id']} | steps={session['step_count']}": session[
"session_id"
]
for session in sessions
}
selected_label = st.selectbox(
"選擇一筆 session",
options=list(session_options.keys()),
)
selected_session_id = session_options[selected_label]
st.subheader("Session")
st.code(selected_session_id)
steps = get_session_steps(selected_session_id)
st.subheader("Steps")
for step in steps:
title = f"{step['step_order']}. {step['step_type']} - {step['name']}"
with st.expander(title, expanded=True):
st.write(f"Timestamp: `{step['timestamp']}`")
if step["error"]:
st.error(step["error"])
st.markdown("**Input**")
st.json(step["input_data"])
st.markdown("**Output**")
st.json(step["output_data"])
def format_json(data: dict) -> str:
return json.dumps(data, ensure_ascii=False, indent=2)
這個檔案負責 Trace Viewer 的主要畫面。
幾個重點:
第一,程式一開始呼叫:
init_db()
確保 database 和 table 已經建立。
第二,使用 list_sessions() 取得所有歷史 session。
如果目前沒有任何 session,就提示讀者先執行:
python3 app.py
產生一筆 trace。
第三,使用 st.selectbox() 讓使用者選擇要查看哪一次 session。
第四,使用 get_session_steps(selected_session_id) 讀出該 session 的所有 steps。
第五,每個 step 用 st.expander() 顯示。
這樣畫面不會一次展開得太長,也可以清楚看到每一步的 input、output 與 error。
新增 trace_viewer_app.py:
from ui.trace_viewer import render_trace_viewer
render_trace_viewer()
這個檔案很短,只負責呼叫 render_trace_viewer()。
之所以另外建立 trace_viewer_app.py,而不是直接改 app.py,是因為目前 app.py 是命令列版本,負責執行 Agent 並產生 trace。
現在先把兩個入口分開:
| 檔案 | 用途 |
|---|---|
app.py |
執行 Agent,產生 trace |
trace_viewer_app.py |
查看已儲存的 trace |
這樣 Day 6 的範圍會比較清楚。
如果資料庫目前還沒有 trace,先在專案根目錄執行:
python3 app.py
輸入:
請計算 135 * 28
確認終端機有出現:
Saved Sessions:
- ...
這表示 trace 已經被寫進 SQLite。
接著在專案根目錄執行:
streamlit run trace_viewer_app.py
Streamlit 啟動後,瀏覽器會打開一個本機頁面。
你應該會看到:
Agent Trace Viewer
每個 step 會包含:
step_order
step_type
name
timestamp
input_data
output_data
error
以 請計算 135 * 28 這個任務為例,Trace Viewer 應該會顯示類似內容:
Agent Trace Viewer
選擇一筆 session:
2026-09-06T10:30:00 | 8a2b2f21-d1a4-4d8d-9a57-0db43a4e9d23 | steps=5
Session:
8a2b2f21-d1a4-4d8d-9a57-0db43a4e9d23
Steps:
0. user_input - User Task
Input:
{
"content": "請計算 135 * 28"
}
1. llm_response - LLM Response
Input:
{
"messages": [...]
}
Output:
{
"response": {
"type": "tool_call",
"tool_name": "calculator",
"tool_input": "135 * 28"
}
}
2. tool_call - Tool Call
Input:
{
"tool_name": "calculator",
"tool_input": "135 * 28"
}
3. tool_result - Tool Result
Output:
{
"tool_output": "3780"
}
4. final_answer - Final Answer
Output:
{
"answer": "The result is 3780"
}
這就是最小版 Trace Viewer。
它還不是完整 dashboard,但已經能幫助我們從畫面上理解一次 Agent 執行過程。
看到 Streamlit 之後,很容易想直接開始做 dashboard,例如:
但這些都還不是 Day 6 的任務。
因為目前還沒有 eval dataset,也沒有大量測試結果。如果現在做 dashboard,只能顯示幾筆 trace,意義不大。
今天的重點是:
先讓單次 Agent 執行紀錄變得可閱讀。
等第二週建立 Eval Runner 之後,才會有足夠資料做 Reliability Dashboard。
今天的 Trace Viewer 已經預留了 error 顯示:
if step["error"]:
st.error(step["error"])
也就是說,如果某個 step 有錯誤訊息,畫面上會用錯誤樣式顯示。
不過目前 Day 4 和 Day 5 的 Agent 還沒有完整包住所有 exception。
例如故意輸入:
請計算 135 28
程式可能仍然會在 FakeLLMClient 抽取算式時中斷,所以不一定會產生完整 trace。
這不是 Trace Viewer 的問題,而是錯誤處理流程還沒完成。
後面進入 Failure Analysis 和 Guardrails 時,會再把這類錯誤整理成可保存、可分類、可顯示的 failure record。
今天完成後,系統具備:
目前還沒有:
Day 4 設計了 trace 資料格式。
Day 5 把 trace 存進 SQLite。
Day 6 則讓 trace 變得可閱讀。
目前我們已經完成第一週最重要的三個基礎:
Agent Runner
-> Trace Recorder
-> Trace Storage
-> Trace Viewer
到這裡,Agent 已經不再只是:
Input -> Output
而是可以被拆解成:
Input
-> LLM Response
-> Tool Call
-> Tool Result
-> Final Answer
而且這些步驟都能被保存與查看。
Day 7 會做第一週回顧。
我們會整理目前完成的 Agent Runner、Tool Calling、Trace Model、SQLite Storage 和 Trace Viewer,並用一筆完整執行紀錄回顧整個流程。
第一週結束後,平台已經能回答:
Agent 執行過程中發生了什麼?
第二週開始,會進入 Eval 階段,開始處理另一個問題:
Agent 到底有沒有完成任務?