CrewAI:從原始碼看「角色扮演」怎麼成為架構決策
讀 CrewAI 原始碼,第一個讓我停下來的,是 prompt 生成的最底層。
role、goal、backstory 三個欄位,不是放在某個「設定 Agent 人格」的工具函式裡,而是貫穿所有 prompt 組建邏輯的核心。每次 Agent 要執行 Task,這三個欄位就在組建 prompt 的最底層被拼進去。這說明 CrewAI 在做一個很具體的賭注:告訴 LLM 它是誰,比告訴它能做什麼,更重要。
752 個 Python 檔案、4 層抽象(Tool → Agent → Crew → Flow)、一個完整的記憶系統、一套事件驅動的 DAG 引擎——這些數字背後,是一個從設計起點就和 hermes-agent、OpenClaw 完全不同的框架。不是因為 CrewAI 解了更難的問題,而是因為它在問一個完全不同的問題。
讀完這篇,你會理解:
role/goal/backstory為什麼是架構決策而不只是 prompt 技巧- Crew / Agent / Task 三元組怎麼協作,以及 Sequential vs Hierarchical 的本質差異
- Task Guardrail 如何讓「驗收標準」變成可執行的約束,以及它和 retry 的本質差別
- Memory 的 EncodingFlow 揭露的「記憶越智慧,成本越難預測」
- Flow 事件驅動 DAG 解決的是 Crew 解決不了的哪個問題
角色語義:這不是 prompt 技巧,是架構選擇
大多數 Agent 框架設定 LLM 身份的方式,是一行 system prompt:"You are a helpful assistant",或者一個可以自由填寫的 system_prompt 欄位。CrewAI 的做法不一樣:
agent = Agent(
role="Senior Research Analyst",
goal="Uncover cutting-edge insights from complex data",
backstory="You work at a leading tech think tank. "
"Your expertise spans multiple domains, and you have "
"a knack for finding patterns others miss.",
tools=[web_search, data_analysis],
)
這三個欄位不是 system_prompt 的別名。它們在 Agent 的整個 lifecycle 裡以不同的方式被使用:role 決定 Task 的委派邏輯(hierarchical 模式下 Manager 用 role 判斷誰適合做某件事);goal 在 context 壓縮時被用來作為「這個 Agent 在意什麼」的錨點;backstory 在 prompt 組建時塑造推理風格。
這個設計的核心假設是:LLM 的輸出品質和它對「自己是誰」的理解高度相關。給 LLM 一個具體的角色語義,不只是讓輸出的語氣更「像那個角色」,而是在長鏈的 Tool 呼叫和推理過程中,維持一種判斷標準的一致性——在遇到模糊情況時,Agent 知道「以我的角色,應該怎麼選擇」。
拿這個系列前面讀過的兩個框架做對比,差異就很清楚了。
hermes-agent 的設計是能力導向的:Agent 有哪些工具、能執行哪些任務,是設計的核心。身份是 SOUL.md 這個外部文件,可以有也可以沒有,不影響框架的核心執行邏輯。OpenClaw 是事件導向的:Gateway → Session → Runner,Agent 的行為是由訊息觸發、平台整合、生命週期管理決定的,「這個 Agent 是誰」不是架構關心的問題。
CrewAI 問的是一個完全不同的問題:如果一組 AI 要像一個組織一樣工作,「誰做什麼」之前,必須先有「誰是誰」。
這個賭注有它的代價。角色語義讓 Agent 的行為更可預測,但也讓它更難偵錯——當 Task 跑錯了,可能是 role 的 prompt 設計問題、可能是 backstory 和 Task context 之間的衝突、可能是 goal 的措辭影響了優先順序的判斷。這些問題不像 tool call failed 那樣有明確的錯誤訊號,它們在 LLM 的推理過程裡悄悄發生。
Crew / Agent / Task 三元組:誰做什麼
CrewAI 的執行單位是 Crew。一個 Crew 持有一組 Agent 和一組 Task,kickoff() 啟動整個流程。三者的關係聽起來很直觀——Agent 執行 Task——但 Crew 怎麼決定「誰執行哪個 Task」,有兩種截然不同的模式。
Sequential(流水線):每個 Task 在定義時就指定負責的 Agent,任務依序執行,前一個 Task 的輸出作為下一個 Task 的 context:
Task1 → Agent_A → output_1
Task2 → Agent_B → output_2 (context 包含 output_1)
Task3 → Agent_C → output_3 (context 包含 output_1 + output_2)
Hierarchical(分工制):沒有預先指派。系統自動建立(或使用者提供)一個 Manager Agent,Manager 動態決定把哪個 Task 交給哪個 Worker Agent,Worker 完成後回傳給 Manager 整合。
Manager Agent
├─ 決定:Task1 交給 Agent_A
│ └─ Agent_A 執行,回傳給 Manager
└─ 決定:Task2 交給 Agent_B
└─ Agent_B 執行,Manager 整合最終輸出
兩種模式背後是不同的前提假設。Sequential 適合任務分工已知的場景——開發者在設計時就能決定誰做什麼;Hierarchical 適合任務邊界模糊的場景——Manager 在執行時動態判斷。代價是對稱的:Sequential 在設計時需要更多思考,Hierarchical 在執行時需要額外的 LLM 呼叫(Manager 的每次分派決策都是一次 LLM 呼叫)。
Task 的設計細節中,最值得注意的是 expected_output 這個欄位:
task = Task(
description="Analyze the provided dataset and identify key trends.",
expected_output="A detailed report with 3-5 key trends and supporting statistics.",
agent=analyst,
)
expected_output 不只是描述,它是驗收規格。Agent 在執行時,expected_output 會被一起送進 prompt,告訴 LLM「這次任務成功的樣子是什麼」。這個設計強迫開發者在設計時就想清楚要什麼,而不是執行完再判斷結果好不好。
Context 傳遞有一個小細節:Task 的 context 欄位有一個 NOT_SPECIFIED sentinel 值(不是 None)。當 context 未設定時,系統預設把所有前序 Task 的輸出累積作為 context;當 context=[](空 list,也就是 None 以外的值)時,這個 Task 沒有任何 context。這個區分讓「不需要 context」和「忘記設定 context」這兩件事有了明確的語義差異。
關於 Agent 怎麼執行 Task,CrewAI 有兩種模式:如果 LLM 支援 Function Calling,走原生工具呼叫,支援多工具並行執行;如果不支援,走傳統的 ReAct 文字迴圈——LLM 輸出 "Observation:" 時停止生成,等待工具結果,再繼續推理。選擇哪條路的判斷是自動的,但結果截然不同:原生模式下 Agent 可以同時呼叫多個工具,文字模式下只能一個一個來。兩者都有 max_iter=25 的硬上限,超過就強制輸出最終答案。
Guardrail:把驗收標準變成可執行的約束
expected_output 定義了 Task 成功的樣子,guardrail 定義了怎麼驗證這個樣子是否真的達到了。
def validate_report(output: TaskOutput) -> tuple[bool, str | TaskOutput]:
if len(output.raw.split("\n")) < 10:
return (False, "Report is too short, needs at least 10 lines with specific data")
return (True, output)
task = Task(
description="...",
expected_output="...",
agent=analyst,
guardrail=validate_report,
guardrail_max_retries=3,
)
Guardrail 函數回傳兩種結果:(True, output) 代表驗證通過;(False, "error message") 代表驗證失敗。失敗時,CrewAI 不是直接拋出錯誤或重試相同的輸入——它把失敗原因作為 context 重新執行整個 Agent:
# 驗證失敗 → 重新執行的 context 長這樣:
context = f"Previous output failed validation: {error_message}\n{previous_output}"
result = agent.execute_task(task=self, context=context, tools=tools)
Agent 收到的訊息是「你上次的輸出有這個問題,請重新做」,而不只是「再試一次」。這個區別很重要:純 retry 是希望隨機性能解決問題,Guardrail 的重試是把失敗資訊帶回去,讓 Agent 有機會針對具體問題調整。
這和前面讀過的框架處理錯誤的層次完全不同。hermes-agent 和 OpenClaw 的錯誤處理都在基礎設施層——API 呼叫失敗、rate limit、工具執行出錯,這些是系統層的錯誤,靠 retry、credential rotation、fallback chain 處理。CrewAI 的 Guardrail 處理的是更高一層的問題:工具執行成功、LLM 也回應了,但輸出不符合業務要求。 這個問題在基礎設施層根本看不見,因為 LLM 沒有報錯。
Guardrail 放在 Task-level 而不是 Agent-level,也是有意識的設計選擇。同一個 Agent 在不同的 Task 可能需要不同的驗證邏輯——同樣的「資料分析師」Agent,分析市場趨勢和分析財務數據的驗收標準可能完全不同。把 Guardrail 綁在 Task 上,讓驗證邏輯跟著工作內容走,而不是跟著執行者走。
Memory:智慧有成本
大多數 Agent 框架的記憶系統,本質上是一個向量資料庫加上相似度搜尋。存進去,查出來,注入 prompt。CrewAI 做的事情不只這些,但代價是顯而易見的。
CrewAI 把「記憶」和「知識」分成兩個完全獨立的系統,因為它們在解不同的問題:
Knowledge 是靜態的背景資料——開發者在初始化時提供的 PDF、CSV、文字文件。它不會在執行時被修改,用 ChromaDB 做向量搜尋,每次 Task 執行時查詢相關 chunk 注入 prompt。沒有 LLM 參與,延遲和成本都是可預測的。
Memory 是動態的執行記憶——Agent 在跑完 Task 之後,把這次的輸出存進去,下次執行時可以召回。這裡有意思的地方,是 CrewAI 在「記什麼」這件事上,引入了 LLM 的判斷。
Memory 的寫入流程(EncodingFlow)會對每條記憶做分類分析。核心是一個 4-group 的決策路徑:
Group A:呼叫者自己填好了 scope/categories/importance,而且沒有重複的記憶
→ 直接寫入,0 次 LLM 呼叫
Group B:呼叫者自己填好了欄位,但發現有相似記憶需要整合
→ 1 次 LLM 呼叫(整合邏輯)
Group C:呼叫者沒有填欄位,需要讓 LLM 推斷 scope/categories/importance
→ 1 次 LLM 呼叫(欄位推斷)
Group D:呼叫者沒有填欄位,而且有相似記憶
→ 2 次並行 LLM 呼叫(欄位推斷 + 整合判斷)
這個設計揭露了一個根本的 trade-off:讓系統自己判斷「這條記憶該放哪裡、有多重要、是否和之前的記憶重複」,和「讓記憶有可預測的成本」,兩件事不能同時完全達成。
如果呼叫者每次都自己填好 scope、categories、importance,就走 Group A,成本完全可控。如果依賴系統自動推斷,就走 Group C 或 Group D,每次記憶寫入都可能觸發 1-2 次 LLM 呼叫,在高頻執行的 Crew 裡,這個成本會在不預期的地方累積起來。
相比之下,hermes-agent 的 Memory Fencing 解的是另一個問題:怎麼讓召回的記憶在語義上不污染當前對話。兩個框架的記憶設計關心的是不同層次的問題——hermes 關心語義隔離,CrewAI 關心記憶的品質和智慧整合。
Memory 的複合相關性分數也體現了同樣的思路:
composite = (
0.5 * semantic_similarity # 向量距離
+ 0.3 * exp(-age_days / 30) # 時效衰減(半衰期 30 天)
+ 0.2 * record.importance # LLM 推斷的重要性
)
語義相關性只佔一半的權重,時效和重要性各佔一部分。這說明 CrewAI 的記憶召回,不只是「找最相似的」,而是「找最值得記住的」——這個判斷更智慧,但 importance 這個欄位是 LLM 打分的,它的品質取決於那次推斷。
Flow:當 Crew 不夠用
Crew 解決的是一次任務執行內部的分工問題:誰做哪些 Task,按什麼順序。但有一類問題,Crew 的結構天然不好處理:任務要不要執行,取決於前一個任務的結果。
比如:先抓取資料,如果資料量足夠就分析,如果資料量不足就換一個來源重試,分析完再決定要寫報告還是要補充調查。這是一個條件分支的問題,用 Crew 的 ConditionalTask 勉強可以做,但它的靈活度有限。
Flow 是 CrewAI 在 Crew 之上加的一層,它讓你用 Python decorator 定義一個事件驅動的 DAG:
class ResearchFlow(Flow[MyState]):
@start()
def fetch_data(self):
... # 抓取資料
@router(fetch_data)
def check_data_quality(self) -> Literal["sufficient", "insufficient"]:
return "sufficient" if len(self.state.data) > 100 else "insufficient"
@listen("sufficient")
def analyze(self):
crew = AnalysisCrew()
return crew.kickoff(inputs={"data": self.state.data})
@listen("insufficient")
def fetch_alternative(self):
... # 換一個資料來源
每個方法是 DAG 的一個節點,decorator 定義邊。@start 是入口,@listen 是「當某個節點完成後觸發」,@router 是「完成後根據返回值決定下一步走哪條路」。
這裡有一個有意思的實作細節:@router 的可能返回值,是在 class 定義時就用 AST 解析決定的。框架掃描 router 方法的原始碼,找出所有 return "value" 的字面量——這也是 flow.plot() 能靜態渲染完整路由圖的原因。不需要執行 Flow,就能看到所有可能的執行路徑。
Flow 還支援 AND/OR 的觸發條件:
@listen(or_(method_a, method_b)) # 任一完成即觸發
def on_any(self): ...
@listen(and_(method_a, method_b)) # 兩者都完成才觸發
def on_both(self): ...
or_ 觸發後,未完成的方法會被 task.cancel() 取消——這是 racing 語義,先到先贏。and_ 則是 barrier 語義,等齊才繼續。
HITL(Human-in-the-Loop)在 Flow 層的設計也比 Crew 的 human_input=True 更靈活。@human_feedback 裝飾器可以讓任何 Flow 節點在完成後暫停,等待人工確認,然後把回饋分類成預設的幾個選項,再路由到對應的後續節點。Crew 層的 human_input 只是在 Task 完成後請人確認最終輸出,Flow 的 HITL 可以暫停在任意位置、影響後續的分支走向。
Crew 和 Flow 的職責分界,在原始碼裡的表述很清楚:Crew 回答「誰做什麼」,Flow 回答「什麼時候做」。大多數應用從 Crew 開始就夠了;當需要條件路由、多個 Crew 組合、或是長時間運行的工作流程,Flow 才有必要。
這些設計在問什麼問題
把這幾個機制放在一起看,CrewAI 的設計哲學的輪廓就很清楚了。
hermes-agent 在問「怎麼讓 Agent 越用越好」——Trajectory 收集、RL 訓練迴路、Memory Fencing、Pluggable Context Engine,每個設計都在服務這個目標。OpenClaw 在問「怎麼讓 Agent 永遠在線」——Orphan Recovery、Auth Profile Rotation、Identifier-Preserving Compaction,每個設計都在問「這個系統在沒人看的時候,能自己活下去嗎」。
CrewAI 在問的是:怎麼讓一組 AI 像一個組織一樣工作。
role/goal/backstory 是職位說明,Task 的 expected_output 是工作交付規格,Guardrail 是驗收機制,Memory 是跨次執行的工作記憶,Flow 是跨部門的流程圖。這個組織比喻不只是 UI 層的語言設計,它被內化進了架構的每一層。
這個設計方向帶來了 4 層抽象(Tool → Agent → Crew → Flow),讓入門很快,因為每一層的概念都很直觀;但也讓 debug 的路徑變長,因為一個問題可能發生在任何一層,而且不同層之間的互動是隱性的。
還有一個值得一提的賭注:CrewAI 是這個系列裡第一個採納 Google A2A 開放協議的框架——Agent 可以跨進程、甚至跨框架通信,只要雙方都實作這個協議。這說明 CrewAI 在賭「跨框架的 Agent 協作」早晚會成為標準需求。這個問題目前還沒有答案。
結語:CrewAI 最讓我印象深刻的設計,不是 Flow 的 DAG 有多聰明,也不是 EncodingFlow 的分組邏輯有多細緻,而是它從最底層就決定了:每個 Agent 是「誰」,比它「能做什麼」更先被定義。這個選擇,決定了後面幾乎所有設計的走向——包括它的優點,和它的代價。