iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0

前言

Day 5 我們把 Agent 執行產生的 trace 存進 SQLite。

目前系統已經可以做到:

Agent 執行
  -> 產生 AgentTrace
  -> 寫入 SQLite
  -> 在終端機列出 Saved Sessions

也就是說,trace 已經不會隨著程式結束而消失。

但現在還有一個問題:

Trace 雖然被存起來了,但閱讀方式還不夠直覺。

如果每次都要用 SQL 查詢,或是在終端機看一大段 JSON,對除錯來說並不方便。

所以 Day 6 來做一個簡單的 Trace Viewer

今天要做到的是:

用 Streamlit 做一個簡單介面,讓我們可以選擇某次 Agent session,並查看它的每個執行步驟。


今天要完成什麼?

會完成:

  1. 安裝或確認 Streamlit。
  2. storage/database.py 新增查詢單一 session steps 的 function。
  3. 新增 ui/trace_viewer.py,負責顯示 trace。
  4. 新增 trace_viewer_app.py,作為 Streamlit 入口。
  5. 顯示 session list。
  6. 顯示 step timeline。
  7. 顯示 tool call、final answer 與 error。

今天先不做:

  • 成功率 dashboard。
  • Eval result dashboard。
  • Failure type 統計圖。
  • Prompt A/B testing。

這些放到後面的 Eval 和 Failure Analysis 階段。


為什麼需要 Trace Viewer?

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

如果還沒有安裝 Streamlit,可以在專案環境中執行:

pip3 install streamlit

如果你有使用 virtual environment,請先啟用環境,再安裝:

python3 -m venv .venv
source .venv/bin/activate
pip install streamlit

這個系列後面也會使用 Streamlit 做簡單 dashboard,所以今天先把基本介面跑起來。


修改 database.py:查詢單一 session 的 steps

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_idsteps table 查出該次執行的所有步驟。

第二,使用 ORDER BY step_order ASC,確保 step 按照執行順序排列。

第三,把 Day 5 存成 JSON 字串的 input_dataoutput_data 轉回 Python dict。

這樣 UI 顯示時就不用自己處理 JSON parsing。


建立 ui package

新增 ui/__init__.py

這個檔案今天可以先留空,不需要放任何程式碼。

它的作用是讓 Python 把 ui 資料夾視為 package,這樣 trace_viewer_app.py 才能匯入:

from ui.trace_viewer import render_trace_viewer

建立 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。


建立 Streamlit 入口

新增 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

如果資料庫目前還沒有 trace,先在專案根目錄執行:

python3 app.py

輸入:

請計算 135 * 28

確認終端機有出現:

Saved Sessions:
- ...

這表示 trace 已經被寫進 SQLite。


啟動 Trace Viewer

接著在專案根目錄執行:

streamlit run trace_viewer_app.py

Streamlit 啟動後,瀏覽器會打開一個本機頁面。

你應該會看到:

  • 頁面標題:Agent Trace Viewer
  • 一個 session 下拉選單
  • 選到 session 後顯示 session id
  • 下方顯示多個 step

每個 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,例如:

  • 成功率圖表。
  • 錯誤類型統計。
  • latency 分布。
  • token 成本分析。

但這些都還不是 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。


今天完成後的系統狀態

今天完成後,系統具備:

  • 可以從 SQLite 查詢歷史 session。
  • 可以查詢單一 session 的所有 steps。
  • 有一個 Streamlit Trace Viewer。
  • 可以用下拉選單選擇 session。
  • 可以用 step timeline 查看 Agent 執行過程。
  • 可以顯示 tool call、tool result、final answer。
  • 可以顯示已經被寫入 step 的 error。

目前還沒有:

  • Eval dataset。
  • 批次測試。
  • 成功率統計。
  • Failure type dashboard。
  • Prompt A/B testing。

今天的重點整理

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 到底有沒有完成任務?


上一篇
Day 5|把 Trace 存進 SQLite
下一篇
Day 7|第一週回顧:Agent 不再是黑盒
系列文
從黑盒到可驗證:30 天打造 AI Agent 的 Trace、Eval 與 Guardrails 系統8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言