AG Day 1 系列導覽:AI Agent 工程的學習地圖
執行需求:CPU 可跑。歡迎來到《AI Agent 工程實戰:LangGraph、MCP 與多代理系統》系列專欄。如果你一路跟著我們先前六個技術系列的腳步——從 Python 基礎、PyTorch 深度學習、電腦視覺、自然語言處理到 Web 後端與資料工程,並且在 2026 年 4 月 30 日剛結束的前端實戰系列(FE 系列)中打下了完整的使用者介面基礎,那麼今天這個第七系列,正是為你將所有碎片化技術拼裝成全自主智慧系統的關鍵一步。在這個系列中,我們不再只把大型語言模型(LLM)當成一個被動的有問必答聊天機器人,而是要將它作為決策的核心大腦,配合工具呼叫、狀態機排程、外部知識庫檢索、通訊協定以及跨代理協作,打造真正能在生產環境穩定運行的「自主研究助理(Research Agent)」。
引言
許多人在初次接觸大型語言模型時,往往驚豔於其回答各類開放式問題的流暢度。然而,當工程團隊試圖把模型落地到企業真實的業務流程時,常常會迅速遭遇殘酷的現實:模型會產生幻覺、無法直接查詢公司內部即時資料庫、面對需要十幾個步驟的複雜任務時容易迷失方向、無法主動糾正呼叫 API 時發生的錯誤,而且單次呼叫的輸出欠缺可預測的結構。簡單地寫一段迴圈呼叫 API,往往在執行兩三輪後就因為上下文爆掉或邏輯發散而崩潰。
這正是「AI Agent(人工智慧代理)工程」所要解決的核心痛點。Agent 的本質,是賦予模型環境感知能力(Perception)、多步驟規劃能力(Planning)、工具執行能力(Action)與持久化記憶(Memory)。工程師的工作,不再只是撰寫精巧的提示詞(Prompt),而是要打造一套嚴謹的控制迴圈與狀態管理框架,讓模型能夠在安全、可觀測且具備容錯機制的軌道上自主執行。本專欄規劃了 45 篇深度實戰文章,寫作時間設定於 2026 年 7 月 15 日至 8 月 28 日,基於 Python 3.13、uv、LangGraph 1.x、FastMCP 2.x 等 2026 年當前業界成熟穩定的工具鏈,帶領大家建立真正能上線交付的工程實力。
原理/觀念
什麼是 AI Agent?解構四大核心元件
在軟體工程的角度下,AI Agent 不是魔法,而是一個由大型語言模型驅動的「狀態機(State Machine)」。如果傳統程式的邏輯是 if-else 與預設演算法,那麼 Agent 的邏輯則是將環境的當前狀態呈現給模型,由模型根據上下文推理出下一個動作,程式再代替模型執行該動作並將結果回傳,反覆推進直到目標達成。一個標準的工業級 Agent 系統包含四大核心柱石:
- 感知與輸入(Perception):接收使用者的原始任務描述,並結合外在環境的即時資訊,包括檔案系統、網頁內容或 API 資料。
- 規劃與推理(Planning):模型評估當前進度與最終目標的差距,決定是否需要將大任務拆解成子目標,或是決定呼叫哪一個具體工具。
- 行動與工具(Action):透過 Function calling 或 Model Context Protocol(MCP)等協定,執行具體的 Python 函式、網路檢索、資料庫寫入或終端機指令。
- 狀態與記憶(Memory):包含短期對話訊息佇列,以及長期狀態儲存(如 SQLite 關聯資料表與向量資料庫 Chroma),確保代理能跨回合、跨流程記住關鍵事實。
貫穿專案:全自動研究助理(research-agent)
為了避免學習過程流於零散的語法教學,本系列將全程圍繞同一個具備實用價值的企業級專案展開:全自動研究助理(research-agent)。想像一下,你丟給系統一個複雜的命題,例如「分析 2026 年第三季邊緣 AI 晶片的架構演進與供應鏈瓶頸」。這項任務無法透過一次 LLM 呼叫完成,助理必須自主完成以下流程:
- 拆解研究維度,規劃檢索策略。
- 呼叫網路搜尋工具與網頁內容擷取工具,取得具備公信力的資料。
- 將擷取到的文字切塊並向量化,寫入本地向量資料庫與關聯資料表。
- 啟動子圖(Subgraph)與多代理協作(Multi-agent),分別進行交叉比對與撰寫段落。
- 生成嚴格標注出處與引用來源的 Markdown 研究報告,並寫入本機檔案系統。
- 提供 FastAPI 介面與 Streamlit 儀表板,並透過 Langfuse 平台進行追蹤與品質評估。
45 天學習地圖的六大階段規劃
45 篇內容環環相扣,不跳躍、不造假,每一篇都以前一篇產出的程式碼為基礎逐步推進:
- 導論與工具鏈(Day 1–2):建立學習地圖、使用 uv 建置 Python 3.13 虛擬環境、專案骨架與環境變數規範。
- 基礎架構與手刻迴圈(Day 3–9):深入對話 API、Prompt 工程規範、Function calling 函式呼叫、手刻多輪代理迴圈、容錯重試、Pydantic 結構化契約與初步觀測。
- LangGraph 狀態圖編排(Day 10–19):剖析手刻極限、StateGraph 節點與邊緣、TypedDict 與 reducer、條件邊、ReAct Agent、ToolNode、檢查點持久化記憶、人工介入核准(Human-in-the-loop)、子圖與串流輸出。
- 知識檢索與工具生態(Day 20–28):向量化與 Embedding、網頁爬取與分塊、RAG 檢索最佳化與 Rerank、引用追溯、搜尋引擎工具、MCP(Model Context Protocol)架構、FastMCP 伺服器建置與 Client 整合。
- 多代理協作與長任務(Day 29–31):Supervisor 與 Worker 分工、代理間訊息交接協定、長任務斷點復原與背景執行。
- 品質評估、觀測與安全防護(Day 32–37):黃金測試集評估、LLM-as-judge 自動評分、Langfuse 追蹤觀測平台、快取最佳化、Prompt Injection 安全防禦。
- 工程交付與部署(Day 38–45):FastAPI 封裝、Docker 容器化打包、Streamlit 前端整合、負載壓測、架構規格書交付與全系列總結。
完整實作
在今天的第一篇實作中,我們將使用純 Python 3.13 建立一個最基礎的「自主代理核心骨架原型」。這個原型不依賴昂貴的第三方框架,而是直接模擬代理的內部狀態轉移、工具呼叫派送以及步數限制保護機制,讓大家在進入複雜框架之前,先透徹理解代理運行的底層邏輯。同時,所有需要呼叫模型的地方,我們都透過環境變數 RESEARCH_AGENT_MODEL 讀取,並內建離線模擬(mock)模式,確保即使沒有外部 API 金鑰的讀者也能在本機完全執行。
第一步,我們先定義代理的狀態結構與基礎工具。建立 minimal_agent_demo.py:
# research-agent/minimal_agent_demo.py
import os
import json
from typing import TypedDict, Literal, List, Dict, Any
class AgentMessage(TypedDict):
role: Literal["system", "user", "assistant", "tool"]
content: str
tool_name: str | None
tool_call_id: str | None
class AgentState(TypedDict):
task: str
messages: List[AgentMessage]
step_count: int
max_steps: int
is_finished: bool
final_result: str | None
這段型別定義勾勒出了代理的基本狀態(State)。在任何嚴謹的代理系統中,我們都必須限制最大步驟數 max_steps,否則模型一旦陷入死迴圈,不僅會耗盡伺服器資源,更會產生難以預估的呼叫費用。第二步,我們實作兩個模擬的研究工具:計算器與本地資料查詢。
def tool_calculator(expression: str) -> str:
"""計算數學運算式的簡易工具"""
try:
# 僅允許基礎數學運算字元,防止不安全程式碼執行
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return "錯誤:運算式包含不合法字元"
result = eval(expression, {"__builtins__": None}, {})
return str(result)
except Exception as exc:
return f"計算出錯:{exc}"
def tool_database_lookup(keyword: str) -> str:
"""模擬內部研究知識庫查詢工具"""
mock_db = {
"晶片架構": "2026年主流邊緣晶片普遍採用混合式 NPU 與存算一體設計,提升能源效率 40%。",
"供應鏈": "先進封裝產能預估在 2026 下半年緩解,高頻寬記憶體依然供不應求。",
}
for k, v in mock_db.items():
if keyword in k:
return v
return "知識庫中未查找到相符的資料。"
這兩個工具展示了代理如何跳脫靜態知識限制。第三步,我們實作核心的「代理決策模擬器」。在離線環境或展示模式下,我們透過規則模擬模型的思考過程(在後續章節則會切換為真實 API 呼叫):
def mock_llm_reasoning(messages: List[AgentMessage], model_name: str) -> Dict[str, Any]:
"""模擬大型語言模型的推理動作"""
last_msg = messages[-1]
last_content = last_msg["content"]
# 判斷是否為剛開始的使用者提問
if last_msg["role"] == "user":
return {
"thought": "收到研究任務,需要先查詢晶片架構的最新發展情況。",
"action": "call_tool",
"tool_name": "database_lookup",
"tool_args": {"keyword": "晶片架構"}
}
# 若上一輪是工具回傳,則進行下一步綜合分析或計算
if last_msg["role"] == "tool" and last_msg["tool_name"] == "database_lookup":
return {
"thought": "已取得架構資訊,現在針對能源效率數值計算年增率。",
"action": "call_tool",
"tool_name": "calculator",
"tool_args": {"expression": "100 * (1 + 0.4)"}
}
# 最後彙整結論
if last_msg["role"] == "tool" and last_msg["tool_name"] == "calculator":
return {
"thought": "所有資訊蒐集完畢,可以撰寫研究摘要回覆給使用者。",
"action": "finish",
"final_answer": (
f"依據 [{model_name}] 的分析,2026 年邊緣晶片主流採用混合 NPU,"
f"相較基準效能提升至 {last_content} 點,能源效率提升顯著。"
)
}
return {"action": "finish", "final_answer": "無法解析的流程狀態。"}
第四步,實作工具派送器(Tool Dispatcher),將模型給出的動作字串轉發給具體的 Python 函式:
def dispatch_tool(tool_name: str, args: Dict[str, Any]) -> str:
"""工具分派中樞,將參數安全地對應至本地執行函式"""
if tool_name == "calculator":
return tool_calculator(args.get("expression", ""))
elif tool_name == "database_lookup":
return tool_database_lookup(args.get("keyword", ""))
else:
return f"錯誤:未知的工具名稱 [{tool_name}]"
第五步,撰寫驅動整個代理生命週期的核心迴圈(Agent Loop):
def run_agent_loop(initial_task: str, dry_run: bool = True) -> AgentState:
"""執行代理核心決策迴圈"""
model_name = os.environ.get("RESEARCH_AGENT_MODEL", "mock-agent-v1")
max_steps = int(os.environ.get("RESEARCH_AGENT_MAX_STEPS", "5"))
state: AgentState = {
"task": initial_task,
"messages": [
{"role": "system", "content": "你是一個專業的研究助理,負責蒐集資訊並產出精確摘要。", "tool_name": None, "tool_call_id": None},
{"role": "user", "content": initial_task, "tool_name": None, "tool_call_id": None}
],
"step_count": 0,
"max_steps": max_steps,
"is_finished": False,
"final_result": None
}
print(f"=== 啟動代理任務:{initial_task} ===")
print(f"使用的決策模型變數:{model_name},最大步數:{max_steps}\n")
while not state["is_finished"] and state["step_count"] < state["max_steps"]:
state["step_count"] += 1
current_step = state["step_count"]
print(f"--- [步驟 {current_step}] 思考與決策中 ---")
decision = mock_llm_reasoning(state["messages"], model_name)
thought = decision.get("thought", "無具體思考紀錄")
print(f"思考過程:{thought}")
action = decision.get("action")
if action == "call_tool":
t_name = decision["tool_name"]
t_args = decision["tool_args"]
print(f"執行工具呼叫:{t_name},傳入參數:{t_args}")
tool_output = dispatch_tool(t_name, t_args)
print(f"工具回傳結果:{tool_output}\n")
# 將工具回傳值作為對話紀錄儲存進狀態
state["messages"].append({
"role": "tool",
"content": tool_output,
"tool_name": t_name,
"tool_call_id": f"call_{current_step}"
})
elif action == "finish":
state["is_finished"] = True
state["final_result"] = decision.get("final_answer")
print("代理判斷任務已完成,產出最終結果。\n")
break
if not state["is_finished"]:
state["final_result"] = "超過最大允許步數,強制終止執行。"
print("警告:流程觸發安全步數閥值!\n")
return state
第六步,我們在主程式進入點呼叫這套最小代理系統,並印出最終成果:
if __name__ == "__main__":
task_prompt = "請幫我調查 2026 邊緣晶片架構的最新突破並估算其效能增益指標。"
final_state = run_agent_loop(task_prompt, dry_run=True)
print("=== 最終執行成果報告 ===")
print(f"總執行步數:{final_state['step_count']}")
print(f"報告內容:{final_state['final_result']}")
讀者可以在終端機中直接以 Python 執行上述完整程式碼,預期會看到如下示範輸出:
=== 啟動代理任務:請幫我調查 2026 邊緣晶片架構的最新突破並估算其效能增益指標。 ===
使用的決策模型變數:mock-agent-v1,最大步數:5
--- [步驟 1] 思考與決策中 ---
思考過程:收到研究任務,需要先查詢晶片架構的最新發展情況。
執行工具呼叫:database_lookup,傳入參數:{'keyword': '晶片架構'}
工具回傳結果:2026年主流邊緣晶片普遍採用混合式 NPU 與存算一體設計,提升能源效率 40%。
--- [步驟 2] 思考與決策中 ---
思考過程:已取得架構資訊,現在針對能源效率數值計算年增率。
執行工具呼叫:calculator,傳入參數:{'expression': '100 * (1 + 0.4)'}
工具回傳結果:140.0
--- [步驟 3] 思考與決策中 ---
思考過程:所有資訊蒐集完畢,可以撰寫研究摘要回覆給使用者。
代理判斷任務已完成,產出最終結果。
=== 最終執行成果報告 ===
總執行步數:3
報告內容:依據 [mock-agent-v1] 的分析,2026 年邊緣晶片主流採用混合 NPU,相較基準效能提升至 140.0 點,能源效率提升顯著。
常見錯誤與踩雷
在剛開始開發 AI Agent 時,工程師非常容易踏入以下幾個典型雷區:
- 將聊天機器人(Chatbot)與代理(Agent)混為一談:Chatbot 只是「輸入問題、輸出回答」的單向對話,缺乏自主決定何時停下、何時去呼叫外部系統的能力。如果沒有嚴謹的狀態轉移設計,模型就無法進行多階段排程。
- 缺乏最大步數限制導致無窮迴圈:當模型給出的工具參數格式不正確,工具回傳錯誤訊息,模型又用同樣的錯誤參數再次重試時,代理迴圈會無限進行下去。在實務上,必須隨時綁定
RESEARCH_AGENT_MAX_STEPS,一旦超標立即發出告警並中斷。 - 寫死特定商業模型的專屬參數:有些開發者習慣在程式碼中寫死特定供應商的特定型號與計費邏輯。一旦模型下架或企業政策轉變,整套系統將難以抽換。我們堅持透過環境變數
RESEARCH_AGENT_MODEL來統一解耦。 - 工具回傳過於龐大的原始資料:如果網頁爬蟲工具一口氣將十萬字元的 HTML 標籤直接塞入使用者的
tool訊息中,很容易直接打爆模型的上下文視窗(Context Window),甚至產生鉅額呼叫費用。工具在回傳前必須進行適度摘要或分塊。
效能與實務提醒
當代理系統逐步演進為正式產品時,架構層面需要注意三個關鍵指標:
第一,Token 消耗與延遲累積(Latency Accumulation):Agent 的每一次思考與工具呼叫都是一次獨立的網路請求。一個需要 5 步完成的任務,延遲可能高達 10 到 20 秒。在後續架構中,我們會探討如何透過平行工具呼叫(Parallel Tool Calls)與提示詞快取(Prompt Caching)技術來壓低延遲與費用。
第二,狀態持久化與斷點復原:今天展示的原型將狀態放在 Python 行程記憶體中,一旦主機重啟,執行到一半的任務就會全部遺失。在未來的 Day 16 與 Day 31 中,我們將會引入 LangGraph 的 Checkpointer 機制與 SQLite 資料表,讓每個執行步驟都被完整儲存,即使伺服器斷電也能無縫復原。
第三,人機協同核准機制(Human-in-the-loop):並非所有動作都能完全放手讓 Agent 自主執行。如果代理打算執行高風險動作(例如刪除資料庫或寄出正式郵件),系統必須能夠在特定節點暫停並等待人類工程師點選確認。這部分我們會在 Day 17 詳細展開。
小結
今天作為系列的首篇,我們確立了 45 篇的整體架構、核心專案 research-agent 的使命,並親手寫出了具備「思考—行動—觀察」基本閉環的最小可行代理程式碼。我們確立了不依賴黑盒子、重視工程穩健性的最高原則。
為了方便大家在接下來的學習中保持概念的一致性,我們將 Agent 工程中最重要的台灣術語與概念整理如下表:
- 代理(Agent):由大型語言模型擔任決策大腦,具備狀態感知與工具執行能力的自主軟體實體。
- 工具呼叫(Tool Call):模型依據需求輸出特定格式的結構化參數,由系統執行對應程式邏輯的機制。
- 狀態(State):代理在多輪執行過程中累積的全部上下文、步驟計數與中間變數集合。
- 檢索(Retrieval):從內部知識庫、檔案、資料庫或搜尋引擎中查詢與當前問題相關資訊的過程。
- 向量化(Vectorization / Embedding):將文字轉化為高維稠密向量,以便進行語意相似度比對的運算。
- 檢查點(Checkpoint):將代理執行狀態持久化至硬碟或資料庫的存檔點,用於除錯與斷點復原。
- 並發(Concurrency):多個工具呼叫或多代理同時間執行工作,提升系統整體吞吐量。
- 佇列(Queue):在長任務與背景工作中負責排隊與傳遞訊息的資料結構。
- 閘道(Gateway)與權限:保護外部 API 與工具,限制代理執行特定敏感指令的防護層。
結語
踏出這第一步之後,你已經對現代 AI Agent 的全貌有了宏觀的認識。但要真正建構出能處理真實世界複雜資料的系統,我們需要一套現代化、標準化的開發環境與專案結構。我們不能只把程式碼散落在單一檔案中,而是必須以模組化套件進行嚴謹管理。
明天,我們會進入「AG Day 2 環境與工具鏈:uv、API key 與專案骨架」,帶大家使用高效能的 uv 工具鏈建立乾淨的 Python 3.13 虛擬環境、規範金鑰安全管理策略、並完整搭建出 research-agent 專案模組架構與 SQLite 初始化腳本,為接下來的實戰打下堅若磐石的工程基礎!
延伸資源
- Anthropic 官方技術指南:Building Effective Agents(2024)。闡述現代 Agent 架構模式與實務準則。
- LangGraph 官方文件:
https://langchain-ai.github.io/langgraph/。狀態圖驅動多代理應用的權威參考手冊。 - Model Context Protocol 規範(2025-06-18 版):
https://modelcontextprotocol.io/。大模型連接外部資料與工具的開放協定。 - FastMCP 官方文件:基於 Python 非同步架構的輕量級 MCP 伺服器建置框架。
留言
張貼留言