大家好!歡迎來到「Build on Google AI」工程挑戰的第 25 天。
在昨天的實作中,我們成功設定了條件路由 (Conditional Edges),讓吵架的「行銷 Agent」與「法務 Agent」在修改 3 次後強制暫停,並將任務升級 (Escalation) 給人類處理。
但是,當你以「人類主管」的身分坐在電腦前,準備介入這場紛爭時,你面臨了一個巨大的尷尬:「我根本不知道他們前面 3 輪到底吵了什麼,我該怎麼修改?」
在傳統軟體工程中,程式碼是確定性的;但在 Agentic System 中,LLM 的行為是動態且隨機的。你不能只看「最終結果」,你必須具備透視「思考過程」的能力。這在 LLMOps 中被稱為 可觀測性 (Observability)。
今天,我們要學習如何使用 Google ADK 的終極除錯武器:Trace (軌跡追蹤),並直接利用追蹤結果修復系統的隱藏 Bug。
第一步:打破 Multi-Agent 的「黑盒子」
在單一 Agent 時代,我們還能靠 print() 把 Prompt 印在終端機上來除錯。但在複雜的 Workflow 中,資料在多個節點之間穿梭,甚至經歷了迴圈。如果只依賴終端機的 Log,畫面會是一團混亂。
Google ADK 在底層自動攔截了每一次的 Agent 呼叫、工具執行與狀態改變,我們只需要啟動開發者介面:
# 在你的專案根目錄下執行
adk web ./ --port 8000
打開瀏覽器進入 localhost:8000 後,將目光移向介面側邊欄的 「Runs (執行紀錄)」 或 「Trace (追蹤)」 面板。
第二步:透視思考軌跡 (Reasoning Trace) 的三大關鍵
點開昨天那筆觸發升級機制的執行紀錄,ADK 會為你展開一個極度詳盡的樹狀圖 (Tree View)。身為 AI 工程師,你需要學會看懂這三個維度的資訊:
節點狀態變化 (State Transitions)
你可以清楚看到 Workflow 的走向:
START ➡️ marketing_agent (次數 1) ➡️ legal_agent ➡️ marketing_agent (次數 2) ➡️ legal_agent ➡️ human_intervention。
點擊任意節點,ADK 會顯示那一刻的 CampaignState 白板內容。你可以親眼見證 revision_count 是如何一步步增加的。
底層 API Payload (Request / Response)
點擊 legal_agent 的執行節點,你可以展開深層的 Payload:
System Instruction: 確保 Agent 真的有拿到法務的人設。
Prompt: 看到行銷 Agent 究竟傳了什麼誇張的文案過來。
Raw JSON Response: 看到法務 Agent 是如何嚴格地輸出 {"is_approved": false, "review_feedback": "使用『絕對』字眼,有廣告不實嫌疑"}。
第三步:基於 Trace 進行工程優化 (完整實作程式碼)
透過 ADK 的 Trace 功能,我們發現了昨天無窮迴圈的真正原因:行銷 Agent 雖然看到了法務的退件原因,但因為我們沒有在 Context 中保留「歷史對話」,導致它像金魚腦一樣,換句話說又把誇大的詞語寫了進去!
【優化解法】: 在傳遞 State 時,確保 review_history 是一個陣列 (List),累積歷次的法務意見,行銷 Agent 才不會重蹈覆轍。
以下是結合了 Trace 除錯優化與防迴圈機制的完整 main.py 程式碼:
import logging
from typing import List
from pydantic import BaseModel, Field
from google.adk import Agent, Workflow
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# ==========================================
# 1. 定義全局白板 (Shared State) - 優化版
# ==========================================
class CampaignState(BaseModel):
user_request: str = Field(default="", description="初始行銷需求")
marketing_copy: str = Field(default="", description="行銷 Agent 產出的文案")
# 【Trace 除錯優化】使用 List 保留所有歷史意見,避免 Agent 像金魚腦一樣重複犯錯
review_history: List[str] = Field(default_factory=list, description="法務歷次的退件意見紀錄")
is_approved: bool = Field(default=False, description="法務是否核准")
revision_count: int = Field(default=0, description="被法務退件的次數")
class LegalReviewResult(BaseModel):
is_approved: bool = Field(description="是否無誇大不實?")
feedback: str = Field(description="具體說明違反哪條規則;若核准請填寫 'OK'")
# ==========================================
# 2. 定義會「談判」的 Agents
# ==========================================
marketing_agent = Agent(
name="marketing_agent",
model="gemini-2.5-pro",
instruction=(
"你是資深行銷總監。請根據 user_request 撰寫吸引人的社群文案。"
"【極度重要】:請務必閱讀 review_history 中的『所有』法務退件紀錄,"
"修改文案時,絕對不能再使用曾被法務禁止的字眼或概念!"
),
state_mapping={
"inputs": ["user_request", "review_history"],
"outputs": ["marketing_copy"]
}
)
legal_agent = Agent(
name="legal_agent",
model="gemini-2.5-pro",
instruction="你是嚴格的法務審核員。請審查 marketing_copy 是否包含誇大不實的絕對字眼。請據實回報審核結果。",
response_schema=LegalReviewResult
)
def execute_legal_review(state: CampaignState) -> CampaignState:
logger.info("⚖️ 法務 Agent 進行審核中...")
review_result = legal_agent.invoke(f"請審核以下文案:\n{state.marketing_copy}")
state.is_approved = review_result.is_approved
if not state.is_approved:
# 將最新退件理由「加入」歷史紀錄陣列
state.review_history.append(f"第 {state.revision_count + 1} 次退件原因:{review_result.feedback}")
logger.warning(f"⚠️ 法務退件:{review_result.feedback}")
else:
logger.info("✅ 法務核准通過!")
return state
# ==========================================
# 3. 實作條件路由 (Conditional Routing)
# ==========================================
def review_router(state: CampaignState) -> str:
if state.is_approved:
return "END"
if state.revision_count >= 3:
logger.error(f"🚨 已達修改上限 ({state.revision_count}次),進入升級人類介入節點。")
return "human_intervention_node"
state.revision_count += 1
logger.info(f"🔄 退回重寫。目前修改次數: {state.revision_count}")
return "marketing_agent"
def human_intervention_mock(state: CampaignState) -> CampaignState:
logger.critical("🛑 系統已暫停:等待人類主管介入 (Human-in-the-loop)")
return state
# ==========================================
# 4. 組裝工作流 (Cyclic Graph)
# ==========================================
campaign_workflow = Workflow(name="Conflict_Resolution_Workflow", state_schema=CampaignState)
campaign_workflow.add_node("marketing_agent", marketing_agent)
campaign_workflow.add_node("legal_agent", execute_legal_review)
campaign_workflow.add_node("human_intervention_node", human_intervention_mock)
campaign_workflow.set_entry_point("marketing_agent")
campaign_workflow.add_edge("marketing_agent", "legal_agent")
campaign_workflow.add_conditional_edges(
source="legal_agent",
routing_function=review_router,
path_map={
"END": "END",
"marketing_agent": "marketing_agent",
"human_intervention_node": "human_intervention_node"
}
)
app = campaign_workflow.compile()
# ==========================================
# 本地測試
# ==========================================
if __name__ == "__main__":
initial_state = CampaignState(user_request="寫貼文宣傳產品,告訴大家它 100% 絕對不會當機。")
print("🚀 啟動具備 Trace 優化與防迴圈機制的系統...\n")
try:
final_state = app.run(initial_state)
print("\n🏁 工作流執行結束!最終核准狀態:", final_state.is_approved)
except Exception as e:
logger.error(f"錯誤: {e}")

小結
「沒有 Trace 能力的 Agent 系統,就像是一台沒有儀表板的失控飛機。」
今天,我們補齊了開發企業級 Agent 最重要的一項基本功:利用 ADK Web UI 進行深度可觀測性除錯。現在,你完全掌握了 Agent 之間對話的來龍去脈。