AG Day 42 文件與交接:README 與架構圖
執行需求:CPU 可跑。AG Day 38(原文連結)把 research-agent 變成 FastAPI 服務,AG Day 39(原文連結)把它裝進容器,AG Day 40(原文連結)做出 CLI 與 Streamlit 前端,AG Day 41(原文連結)做了一輪壓測看承載量。現在這個專案已經是一個「能跑、能部署、有介面」的完整系統,但對一位新接手的同事來說,他打開 research-agent/ 目錄看到的只是一堆檔案:不知道該先讀哪一個、不知道每個指令是做什麼的、不知道 RESEARCH_AGENT_MODEL 沒設會發生什麼事。今天的任務是把這一切整理成一份「接手的人讀完就能動手」的說明——一份好的 README、一張能一眼看懂模組與資料流向的架構圖、一份把所有環境變數、指令、輸出位置列清楚的交接清單。這些都不需要 API 金鑰、不需要 Docker,只要文字編輯器就能完成,是這個系列最「安靜」卻也最決定專案壽命的一篇。
引言
在軟體工程裡有句常聽到的話:「程式碼會被讀很多次,但只會被寫幾次。」對一個花了四十多篇才長出來的專案來說更是如此——我們花了 AG Day 6 手刻代理迴圈、AG Day 11 引入 LangGraph、AG Day 18 拆子圖、AG Day 28 接上 MCP、AG Day 29 拆多代理、AG Day 30 定義通訊協議,每一步都加進了新概念、新檔案、新環境變數。如果沒有一份 README,新成員只能從 git log 一路爬,或直接問作者。作者如果在、還能回答;作者如果轉換了,專案就變成孤兒。今天我們要把這件事徹底處理好:寫出一份新成員讀 30 分鐘就能動手改的 README,畫出一張能掛在專案首頁的架構圖,並建立一份對應 AG Day 43(原文連結)已知限制的對照索引。
README 不是「抽象宣言」,而是一份具體的契約:寫下「這個專案是什麼、要解決什麼問題、怎麼跑起來、常見問題在哪裡」。架構圖也不是裝飾品,而是把模組邊界、資料流向、外部依賴視覺化的方式,讓新成員在三秒之內知道「這個系統有幾塊、資料從哪裡進、會從哪裡出」。今天會用 Python 內建的 graphviz 介面包裝(透過 subprocess 呼叫 dot 指令)產出 Mermaid 與 PNG 兩種格式的架構圖,並把整個流程整合進 scripts/render_arch.py,讓之後架構有變動時只要重跑一支腳本就能更新圖。
原理/觀念
為什麼 README 是專案的門面
當一位新工程師第一次接觸一個專案,他的閱讀順序通常是這樣的:先看 README 確認「這在做什麼」,再看依賴清單(pyproject.toml)確認「用什麼寫的」,接著看目錄結構與關鍵模組確認「從哪裡開始」,最後才開始讀程式碼細節。README 是這條閱讀路徑的第一站,如果它寫得不好,新成員可能誤判這個專案能解決(或不能解決)他的問題,也可能花好幾天繞路才找到正確的入口。我們在 AG Day 2 已經有了最簡版的 README,今天要把它擴充成完整版,涵蓋動機、安裝、設定、執行、測試、部署、已知限制等段落。
一份好的 README 應該具備「讀完就能動手」的特質。這代表它必須包含:安裝指令(不要只寫「請安裝相依套件」)、執行指令(具體到指令名與參數)、環境變數清單(每一個都要寫用途與預設值)、輸出位置(跑完會產生什麼檔案、放在哪裡)、故障排除入口(出問題時先看哪一段)。少了任何一項,新成員都得自己摸索,而摸索的時間通常不會被記在專案的工時裡。
為什麼要畫架構圖
程式碼是「細節」,架構圖是「輪廓」。當專案超過 10 個模組,光看檔案名稱與匯入關係很難一眼掌握全貌;這時候一張能顯示「外部使用者 → CLI / API / Streamlit → supervisor → workers → 工具 / 資料庫 / 向量庫」這種流向的圖,就能讓人馬上理解系統的分層與依賴。我們用 Mermaid 文字格式(.mmd 檔)來描述架構,因為它能用純文字版本管理,也能直接渲染成 PNG 或 SVG;同時也產出 Graphviz 的 dot 格式圖檔,作為另一種工具鏈的選擇。
架構圖不是裝飾品,它其實是一種「對模組邊界的宣告」。當你畫下「supervisor 透過 TaskSpec 與 worker 通訊」這條線時,你其實就在表達「我不允許 worker 直接呼叫另一個 worker」。同樣地,「所有外部輸入都先進入 CLI / API / Streamlit」這條限制,表示「不要在 worker 裡直接讀環境變數或打 API」。這些限制在程式碼裡散落各處,只有靠架構圖才能一次看清楚。
把交接當成一種測試
README 與架構圖寫得夠不夠好,可以用一個簡單的方法測試:請一位沒參與這個專案的同事照 README 跑一次,從頭到尾只靠 README 與架構圖。如果他能在 30 分鐘內成功啟動服務、跑一次範例問題、看到報告輸出,那就是合格的;超過一小時還卡住,則代表 README 還缺東西。我們今天會把這個測試的精神寫進 scripts/smoke_test.sh,把「照 README 跑一次」這件事自動化,下一位接手的人只要執行這支腳本就能驗證自己手上的環境是否健全。
完整實作
我們會在 research-agent/ 根目錄建立一份完整的 README.md、一個 docs/architecture.mmd 架構圖原始檔、一個 scripts/render_arch.py 渲染腳本,以及一個 scripts/smoke_test.sh 接手驗證腳本。先建立目錄結構:
mkdir -p research-agent/docs research-agent/scripts
touch research-agent/scripts/render_arch.py
touch research-agent/scripts/smoke_test.sh
chmod +x research-agent/scripts/smoke_test.sh
第一步:寫一份完整的 README.md。它不是把 pyproject.toml 的內容複製貼上,而是從「新成員視角」重新組織所有資訊。每個段落都要能獨立回答一個具體問題:動機、安裝、設定、執行、測試、部署、已知限制。
# research-agent/README.md(完整版,僅示意結構)
# 實作時請依貴公司命名與流程微調段落
> 研究助理 Agent:給定研究問題 → 自動搜尋、擷取、建知識庫 → 多代理分工 → 產出帶引用的報告。
## 這個專案在做什麼
一個可在本機 CPU 執行的多代理研究助理:使用者輸入研究主題,supervisor 拆解任務並指派給
search / retrieval / writer 三個 worker,最後產出帶來源的 Markdown 報告。可透過 CLI、FastAPI、
Streamlit 三種介面呼叫,並支援 MCP 工具與 Langfuse 觀測。
## 安裝
git clone <repo> research-agent
cd research-agent
uv venv --python 3.13
source .venv/bin/activate
uv sync
## 設定
環境變數請參考 docs/env.md;至少需要設定其中之一:
- OPENAI_API_KEY:OpenAI 相容對話 API
- ANTHROPIC_API_KEY:Anthropic Claude 相容對話 API
未設定時請加 --dry-run 走離線模擬模式。
## 執行
uv run research-agent "研究主題" # CLI
uv run uvicorn research_agent.server:app # API(http://127.0.0.1:8000)
uv run streamlit run research_agent/ui.py # 前端
## 測試
uv run pytest # 全部測試
bash scripts/smoke_test.sh # 接手驗證(推薦先跑這支)
## 部署
請見 docs/deploy.md(涵蓋 Docker 與雲端建議)。
## 已知限制
請見 AG Day 43(posts-ag/43-ag-day-43.html)整理的 limitations 段落,或 docs/limitations.md。
## 授權
MIT(建議依貴公司政策調整)。
這份 README 涵蓋了接手時最常被問的 7 個問題,每一個都用一兩個具體指令回答,而不是用抽象描述帶過。請特別注意第三段「設定」與第四段「執行」之間的界線:設定是「需要做哪些事」,執行是「設定完做哪些事」;混在一起會讓讀者搞不清楚現在該編輯哪個檔案。
第二步:畫架構圖。這張圖要把外部使用者、三種介面、supervisor、三個 worker、工具、儲存、觀測平台都放進來,並用顏色或粗細區分層級。我們用 Mermaid 的 flowchart 語法撰寫,因為它可以直接被 GitHub、GitLab、Streamlit 等多數平台渲染:
# research-agent/docs/architecture.mmd
flowchart LR
subgraph 使用者
U[研究問題]
end
subgraph 介面層
CLI[research-agent CLI]
API[FastAPI]
UI[Streamlit]
end
subgraph 代理層
SUP[supervisor]
SW[search_worker]
RW[retrieval_worker]
WW[writer_worker]
end
subgraph 工具與外部
TAV[Tavily Search]
MCP[MCP Servers]
LLM[LLM Provider]
end
subgraph 儲存層
DB[(SQLite: documents/chunks/runs/events)]
CH[(Chroma 向量庫)]
end
subgraph 觀測層
LF[Langfuse]
end
U --> CLI
U --> API
U --> UI
CLI --> SUP
API --> SUP
UI --> SUP
SUP --> SW
SUP --> RW
SUP --> WW
SW --> TAV
RW --> CH
WW --> LLM
SUP --> LF
SW --> LF
RW --> LF
WW --> LF
SUP --> DB
WW --> DB
RW --> DB
這張圖分四個子圖(subgraph)分層:使用者 → 介面層 → 代理層 → 工具與儲存/觀測。箭頭一律由左往右或由上往下,避免在視覺上交叉;事件流與資料流用不同顏色或不同線條表示(在這份純文字版本裡以註解說明,渲染時可以用 Graphviz 屬性加上 stroke 區分)。請特別注意:所有介面(CLI、API、UI)都只連到 supervisor,沒有直接連 worker,這正是 AG Day 29 強調的「集中式路由」原則的視覺化。
第三步:寫一個渲染腳本,把 Mermaid 轉成 PNG 與 SVG。這個專案不一定預先裝 mermaid-cli,所以腳本會用兩條路:本地有 mmdc(Mermaid CLI)就走它;沒有的話就退回到「只產出 Mermaid 原始檔、提示使用者用線上工具或自行安裝」的友善模式:
# research-agent/scripts/render_arch.py
"""把 docs/architecture.mmd 渲染成 PNG / SVG;找不到 mmdc 時只檢查語法。"""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "docs" / "architecture.mmd"
def has_mmdc() -> bool:
return shutil.which("mmdc") is not None
def render(out_format: str) -> Path:
out_path = SRC.with_suffix(f".{out_format}")
if not has_mmdc():
print(f"[warn] 找不到 mmdc,略過 {out_format} 渲染;請安裝 @mermaid-js/mermaid-cli 後重跑")
return out_path
subprocess.run(
["mmdc", "-i", str(SRC), "-o", str(out_path), "-e", "png" if out_format == "png" else "svg"],
check=True,
)
return out_path
def main() -> int:
if not SRC.exists():
print(f"[error] 找不到 {SRC}")
return 1
for fmt in ("svg", "png"):
path = render(fmt)
print(f"[info] 產出 {path}({'完成' if path.exists() else '略過'})")
return 0
if __name__ == "__main__":
sys.exit(main())
這個腳本故意寫得很短,因為它只是把 Mermaid 轉成圖檔;真正的架構內容都在 architecture.mmd。一旦日後新增 worker(例如加一個 critique_worker 檢查報告品質)、或換掉某一層(例如把 Streamlit 換成 Gradio),只要改 .mmd 再重跑腳本就好,程式邏輯一行都不用動。
第四步:寫一份環境變數清單 docs/env.md。這份清單對接手的人非常重要,因為 research-agent 同時使用 OpenAI/Anthropic/Tavily/Langfuse 等多個外部服務,每個服務都有獨立的 API 金鑰,少設一個就會在某個節點失敗。我們用一張表格列清楚:
# research-agent/docs/env.md(環境變數清單)
| 變數 | 必填 | 用途 | 預設 |
|---|---|---|---|
| RESEARCH_AGENT_MODEL | 否 | 選擇對話模型;正式名稱由各 LLM Provider 決定 | 視 provider 而定 |
| RESEARCH_AGENT_MAX_STEPS | 否 | supervisor 派工步數上限 | 8 |
| OPENAI_API_KEY | 視 provider | OpenAI 相容 API 金鑰 | 無 |
| ANTHROPIC_API_KEY | 視 provider | Anthropic API 金鑰 | 無 |
| TAVILY_API_KEY | 搜尋時必填 | Tavily 搜尋 API 金鑰 | 無 |
| LANGFUSE_PUBLIC_KEY | 觀測時必填 | Langfuse 公開金鑰 | 無 |
| LANGFUSE_SECRET_KEY | 觀測時必填 | Langfuse 秘密金鑰 | 無 |
| RESEARCH_AGENT_DRY_RUN | 否 | 設為 1 強制走離線模擬模式 | 0 |
設定範例(~/.bashrc 或 .env):
export RESEARCH_AGENT_MODEL="<provider>/<model-name>"
export OPENAI_API_KEY="sk-..."
export TAVILY_API_KEY="tvly-..."
export LANGFUSE_PUBLIC_KEY="pk-..."
export LANGFUSE_SECRET_KEY="sk-..."
這份清單的價值在於「一目了然」:接手的人只要看這張表,就能知道哪些金鑰是必填、哪些是選用、各自做什麼。請特別注意 RESEARCH_AGENT_MODEL 的填法:用 <provider>/<model-name> 的形式描述,但實際值依當時各家 Provider 公開的命名為準——這呼應 AG Day 3 的環境變數取用原則,不要把模型版本號寫死在程式裡。
第五步:寫一份接手驗證腳本 scripts/smoke_test.sh。它要做的事情很單純:模擬一個新成員「剛把專案 clone 下來、剛裝好環境」的狀態,從安裝一路跑到看到第一份報告,把整條路徑走一次:
#!/usr/bin/env bash
# research-agent/scripts/smoke_test.sh
# 接手驗證:模擬新成員從零開始跑一次完整流程。
set -euo pipefail
cd "$(dirname "$0")/.."
echo "== 1. 環境檢查 =="
uv --version || { echo "請先安裝 uv"; exit 1; }
python -V
echo "== 2. 安裝相依 =="
uv sync --quiet
echo "== 3. 離線模式跑一次範例 =="
RESEARCH_AGENT_DRY_RUN=1 uv run research-agent "smoke test topic" \
--max-steps 3 --output reports/smoke.md
test -f reports/smoke.md || { echo "找不到 reports/smoke.md"; exit 1; }
echo "== 4. 確認資料表存在 =="
uv run python -c "from research_agent.storage import init_db; init_db(); print('SQLite OK')"
echo "== 5. 確認 Langfuse 觀測程式能載入(無金鑰時應略過、不報錯) =="
uv run python -c "from research_agent.observability import setup_tracing; setup_tracing(); print('tracing OK')"
echo "== smoke test 通過 =="
這支腳本的關鍵設計是「每一階段都印出 [OK] 或錯誤訊息,否則就退出」。這讓接手的人能在第一個失敗點就停下來,而不是跑到最後才發現某個階段早就有問題。同時,我們把「離線模式跑範例」這個步驟放在所有依賴外部服務的檢查之前——這樣即使沒有任何 API 金鑰,也能驗證整條管線的程式碼本身是健康的。
第六步:在 README 末尾補上一段「接手清單(Onboarding Checklist)」,把驗證腳本的輸出與後續動作綁在一起。新成員照這份清單一條一條打勾,就能在 30 分鐘內完成接手:
# 接手清單(請複製到自己的筆記本逐項打勾)
- [ ] 讀完 README.md(10 分鐘)
- [ ] 讀完 docs/architecture.mmd 與對應 PNG(5 分鐘)
- [ ] 設定 uv 與 Python 3.13 環境(5 分鐘)
- [ ] 複製 .env.example 為 .env 並填入至少一個 provider 的 API 金鑰(5 分鐘)
- [ ] 執行 bash scripts/smoke_test.sh 確認本地可跑(3 分鐘)
- [ ] 用 --dry-run 跑一次 uv run research-agent "hello" 看輸出(2 分鐘)
- [ ] 啟動 Streamlit 看前端介面(2 分鐘)
- [ ] 看 docs/limitations.md 了解目前已知限制(5 分鐘)
- [ ] 加入 AG Day 43(posts-ag/43-ag-day-43.html)的除錯紀錄樣板(10 分鐘)
合計:約 45 分鐘
這份清單的價值是把「接手」從抽象的工作變成可勾選的具體動作。每一條都附上預估時間,讓接手的人能預期總投入。新成員照著走完一次,下次換他交接給別人時,這份清單就會是很好的範本。
第七步:建立一個 CHANGELOG.md,記錄每一篇發布日對應的版本變更。雖然這個專案沒有套用嚴格的語意化版本(SemVer),但對「教學系列」來說,CHANGELOG 能讓讀者快速對應「我看到的程式碼是哪一天的版本」,對 AG Day 43 的已知限制與 AG Day 44 的維運情境都是很好的索引:
# research-agent/CHANGELOG.md
## v1.0(AG Day 38-44)
- 加入 FastAPI 介面(AG Day 38)
- 加入 Docker 容器化(AG Day 39)
- 加入 CLI 與 Streamlit(AG Day 40)
- 壓測與容量規劃(AG Day 41)
- README、架構圖、接手清單(AG Day 42)
- 已知限制彙整(AG Day 43)
- 維運情境手冊(AG Day 44)
## v0.9(AG Day 30-37)
- 多代理通訊協議(AG Day 30)
- 長任務檢查點(AG Day 31)
- 評估集與 LLM-as-judge(AG Day 33-34)
- Langfuse 觀測(AG Day 35)
## v0.5(AG Day 18-29)
- 子圖模組化(AG Day 18)
- 串流輸出(AG Day 19)
- RAG 與檢索(AG Day 20-23)
- 搜尋工具(AG Day 24)
- MCP 整入(AG Day 25-28)
- 多代理架構(AG Day 29)
這份 CHANGELOG 的分段對應到 AG 系列的能力演進(Day 6 手刻迴圈 → Day 14 ReAct → Day 18 子圖 → Day 23 引用 → Day 28 接 MCP → Day 30 多代理 → Day 33 評估 → Day 38 API → Day 39 容器 → Day 41 壓測 → Day 44 交付手冊)。接手的人讀 CHANGELOG,就能用 5 分鐘理解這個專案是怎麼一路長大的,並對應回教學系列裡的某一篇去深入。
常見錯誤與踩雷
第一個常見錯誤是把 README 寫成「功能列表」而不是「操作指南」。很多人寫 README 時,會不自覺地把所有功能都列舉一遍(支援 MCP、支援 Langfuse、支援多種 LLM…),但讀者真正想知道的是「我下一步該做什麼」。一份好的 README 應該以動作為單位組織:安裝、設定、執行、測試、部署,每一段都從讀者「要做什麼」的視角出發,而不是從「程式能做什麼」出發。今天的 README 草稿就是刻意用動詞開頭:安裝、設定、執行、測試、部署,每個動詞對應一段。
第二個常見錯誤是架構圖畫完就不更新。隨著專案演進,原本的架構圖會漸漸失真:可能加了新 worker、可能換掉了某個工具、可能把 SQLite 換成 Postgres。但很多人不會回去改架構圖,導致圖與程式碼脫節,新成員看了圖反而被誤導。解法是把架構圖納入版控,並在每次新增模組時要求「圖也要跟著改」。今天的 render_arch.py 腳本讓更新成本降到最低——只要改 .mmd 一個檔案,就能重新產出 PNG/SVG。
第三個常見錯誤是接手清單寫得太抽象。例如「熟悉專案結構」「讀懂核心模組」這類描述,讀者根本不知道怎麼做就算完成。今天的清單每一條都是「做完某個具體動作並看到某個具體輸出」的層次,例如「執行 bash scripts/smoke_test.sh 確認本地可跑」——這就是可以被勾選的動作,不是抽象的要求。
第四個常見錯誤是忘記放「已知限制」段。許多 README 寫到「怎麼用」就停了,但沒寫「什麼時候不該用」。一個寫得好的 README 應該明確告訴讀者:哪些情境目前還沒支援、哪些情境下需要額外的設定、哪些情境會遇到預期外的行為。今天的 README 留了一段指向 AG Day 43 的「已知限制」段,明天就會把具體內容補上。
效能與實務提醒
寫 README 與架構圖看起來不花時間,但寫得好的版本通常需要反覆修改。建議的節奏是:寫完第一版後,找一位沒參與專案的同事試讀一次,請他照著做並記錄卡住的地方,再依據回饋修第二版。這個流程通常要重複 2-3 次才能得到一份真正「讀完就能動手」的 README。第一版往往會假設讀者已經知道太多背景知識,必須靠實際測試才能抓出這些隱性假設。
架構圖的分層數量建議控制在 3-4 層。我們今天的版本是 4 層(使用者、介面、代理、儲存/觀測)。超過 5 層會讓箭頭開始交叉、看不清楚;少於 3 層則會把太多細節塞在同一層,失去分層的意義。如果專案繼續長大,與其把層數增加,不如把某一層(例如「代理層」)再拆成子圖(subgraph),並在主圖與子圖之間留超連結。
Mermaid 與 Graphviz 兩種格式可以同時保留:Mermaid 用於 README 與網頁(GitHub、GitLab 預設都能渲染),Graphviz 用於投影片或技術簡報。兩份原始檔(.mmd 與 .dot)可以由同一支渲染腳本產出,維護成本幾乎是零。今天先建立 Mermaid 版本,Graphviz 版本留給未來的同事依需求擴充。
最後,環境變數清單要與程式碼裡的 load_settings() 保持同步。每當新增一個環境變數、就要同時更新 docs/env.md 與 .env.example。建議把這個對應關係寫進 CI:在 CI 裡加一支檢查腳本,比對程式碼中讀取的環境變數與清單是否一致,缺一個就報錯。這是維護 README 與環境變數表的「強制對齊」機制,避免說明與程式碼日漸脫節。
小結
今天把 research-agent 的「門面」整理好了:一份完整的 README、一張分四層的 Mermaid 架構圖、一個把圖渲染成 PNG/SVG 的腳本、一份環境變數清單、一支接手驗證腳本、一份接手清單,以及一份對應到 AG 系列各篇章的 CHANGELOG。這些產出都不是新功能,但它們決定了專案能不能被順利接手、能不能被長期維護,是技術債最少、報酬率最高的一類工作。
新增的術語:接手清單(onboarding checklist,新成員按表打勾的工作清單)、架構圖(architecture diagram,視覺化模組與資料流向的圖)、README 契約(README contract,README 對讀者承諾的「讀完就能動手」保證)。
結語
README 寫好了、架構圖畫好了、接手清單也列好了。但一份真正完整的交接還缺一塊:我們有沒有誠實告訴接手的人「這個專案現在做不到哪些事」?任何一個運行中的系統都有邊界,把這些邊界寫清楚,比讓新成員自己去撞牆要好得多。AG Day 43(原文連結)會把目前已知的限制、踩過的雷、以及一份除錯日誌的範本整理成 docs/limitations.md,讓接手的人第一眼就能看到這個專案的「不可行之處」,避免在錯誤的方向上花時間。
明天,我們會進入「AG Day 43 已知限制與除錯日誌」,把這 45 天累積下來的踩雷經驗整理成一張對照表:每一條限制對應到 AG 系列的哪一篇、每一條雷都附上重現步驟與建議解法;同時建立一份「除錯日誌範本」,讓日後真的碰到問題時,能用一致的格式留下紀錄,累積成專案的除錯手冊。
延伸資源
- Mermaid 官方語法參考:
https://mermaid.js.org/。本篇架構圖的flowchart與subgraph用法以官方文件為準。 - Graphviz 官方說明:
https://graphviz.org。若之後想用 dot 格式產圖,官方文件提供完整的屬性與渲染參數。 - 「README 寫作指南」(參考 Read the Docs、GitHub Open Source Guides 等社群範本):具體段落順序可依貴公司政策微調。
- Semantic Versioning 2.0.0:
https://semver.org/。本系列以教學為主沒有套用嚴格 SemVer,但接手大型專案時這份規範值得參考。
留言
張貼留言