Agent 是什么:拆开这个"AI 员工"
昨天你写了 Agent(role=..., goal=..., backstory=..., llm=...) 就跑起来了。但 Agent 类其实有几十个字段。今天不逐个背,而是分组认识:它是怎么两层继承来的、哪三个字段是"人设三件套"、哪一批是"能力开关"、哪些是 CrewAI 特有的高级能力(推理/规划/多模态),最后看藏在字段里那个真正干活的 agent_executor。源码在 agent/core.py(1931 行)与父类 agents/agent_builder/base_agent.py。
痛点:光有一个 LLM,凑不成一个"角色"
Agent。类的 docstring 开门见山(agent/core.py:170):# agent/core.py:170
class Agent(BaseAgent):
"""Represents an agent in a system.
Each agent has a role, a goal, a backstory, and an optional language model (llm).
The agent can also have memory, can operate in verbose mode, and can delegate
tasks to other agents.
"""
两层继承:Agent ← BaseAgent
字段分布在两层。通用字段(任何 agent 都有的:角色、工具、循环上限……)在抽象基类 BaseAgent;CrewAI 官方实现特有的字段(推理、规划、多模态……)在子类 Agent。
# agents/agent_builder/base_agent.py:200
class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
# ...通用字段都在这里...
# agent/core.py:170
class Agent(BaseAgent): # ← 继承通用字段,再加自己的高级字段
...
BaseModel两层都是 Pydantic 模型——意味着你传参会被自动校验(类型不对直接报错),且能序列化成 JSON。ABC(抽象基类)BaseAgent 是抽象的,规定了子类必须实现 execute_task / create_agent_executor 等方法(:644、:662 有抽象声明)。你不能直接 BaseAgent(...),只能用具体子类 Agent。metaclass=AgentMeta用了元类——CrewAI 借它在类创建时做一些注册/增强(进阶细节,先知道有这么个机制)。agent_adapters/)。抽出 BaseAgent 定义"一个 agent 至少要能干什么"(execute_task、create_agent_executor 等抽象方法),就等于定了一份契约:无论你是官方 Agent、还是包了别家框架的适配器,只要实现这份契约,Crew 就能一视同仁地调度你。代价是初学者要跨两个文件找字段;回报是可扩展性——Crew 的调度代码只依赖 BaseAgent 契约,不关心具体是谁。这就是"面向接口而非实现"。人设三件套:role / goal / backstory
这三个是必填字段,在 BaseAgent 里(agents/agent_builder/base_agent.py:263):
# agents/agent_builder/base_agent.py:262
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True) # 每个 agent 一个不变的身份证
role: str = Field(description="Role of the agent") # 角色:"资深市场分析师"
goal: str = Field(description="Objective of the agent") # 目标:他要达成什么
backstory: str = Field(description="Backstory of the agent") # 背景故事:塑造语气/专长
role角色名。会写进 system prompt,告诉模型"你现在扮演谁"。也用于日志和委派时的身份标识。goal这个角色的总目标。注意它和"任务"不同:goal 是长期人设目标,Task 才是这次具体要交付的活。backstory背景故事。看似"废话",实际很影响输出风格和专业度——给模型足够的"人设锚点",它更能稳定地扮演该角色。id 是 frozen 的★frozen=True 表示身份证一旦生成就不可改。委派、记忆、事件追踪都靠它认人。role="Senior Market Analyst"、goal="Conduct deep market analysis with expert insight"、backstory="You're a veteran analyst known for identifying subtle market patterns"。这三句最终会被塞进送给模型的 system prompt,让它"入戏"。能力/行为开关字段群(多在 BaseAgent)
三件套之外,一大批字段是"给这个员工配能力、定行为边界"(agents/agent_builder/base_agent.py:269 起):
# agents/agent_builder/base_agent.py:269
cache: bool = Field(default=...) # 是否缓存工具调用结果
verbose: bool = Field(default=...) # 是否打印详细执行日志
max_rpm: int | None = Field(default=...) # 每分钟最多请求数(限速)
allow_delegation: bool = Field(default=...) # ★能否把活委派给别的 agent
tools: list[BaseTool] | None = Field(default=...)# 手里的工具箱
max_iter: int = Field(default=...) # ★最多推理循环几次(防死循环)
# ...
tools_handler: ToolsHandler = Field(default=...) # 工具调用的管理器
security_config: SecurityConfig = Field(...) # 安全配置
knowledge: Knowledge | None = Field(default=...) # 挂载的知识库
| 字段 | 作用 | 不填会怎样 |
|---|---|---|
tools | Agent 能调用的工具列表 | 没工具,只能靠模型自己的知识答 |
max_iter | 思考→用工具→再思考 的最大循环次数 | 用默认值兜底,防止无限循环烧钱 |
allow_delegation | 允许把子任务转派给团队其他成员 | 默认 False,自己干到底 |
max_rpm | 限速,保护你的 API 额度 | None = 不限速 |
cache | 相同工具调用复用结果 | 省钱省时的小开关 |
verbose | 是否刷执行日志 | 调试期打开,生产关掉 |
Agent 独有的高级字段(在 agent/core.py)
下面这些是官方 Agent 子类特有的"加强能力"(agent/core.py:202 起):
# agent/core.py:202
max_execution_time: int | None = Field(default=None, ...) # 单个任务最长执行秒数(超时报错)
use_system_prompt: bool | None = Field(default=True, ...) # 是否用 system 角色下 prompt
llm: ... = Field(default=None, ...) # ★大脑:可传字符串或 LLM 对象
respect_context_window: bool = Field(default=..., ...) # 自动裁剪以适配上下文窗口
max_retry_limit: int = Field(default=..., ...) # 出错重试次数上限
multimodal: bool = Field(default=..., ...) # 是否多模态(能看图等)
planning: bool = Field(default=..., ...) # 执行前先做规划
reasoning: bool = Field(default=..., ...) # ★启用"先推理再动手"
max_reasoning_attempts: int | None = Field(default=None,..)# 推理尝试上限
inject_date: bool = Field(default=..., ...) # 自动把当前日期注入 prompt
llm★最重要。它的类型是 str | BaseLLM | None,还挂了一个 BeforeValidator(_validate_llm_ref)——所以你传字符串 "gpt-4o" 时,Pydantic 校验阶段就会把它解析成一个 LLM 对象。这正是 D02 里"只写模型名就行"的原理。reasoning / planningCrewAI 的"先想后做"能力:开 reasoning,agent 会先产出推理再执行;开 planning,crew 级别会先规划任务顺序。阶段2/4 深挖。respect_context_window当对话/上下文太长超过模型窗口时,自动裁剪,避免"token 超限"报错。生产环境很实用。max_execution_time给单个任务设超时。配合 execute_task 里的超时执行分支(L06 会提到)。multimodal / inject_date多模态(看图)、自动注入当前日期(让模型知道"今天几号")等便利开关。agent_executor:藏在字段里的"干活引擎"
字段里有一个特别的成员:agent_executor(agent/core.py:333)。它不是配置,而是真正执行任务的引擎对象。Agent 自己不直接跟模型对话,而是委托给它。
# agent/core.py:333
agent_executor: CrewAgentExecutor | AgentExecutor | None = Field(
default=None, description="An instance of the CrewAgentExecutor class.")
# agent/core.py:1028 —— 用之前先"装配"好这个引擎
def create_agent_executor(self, tools=None, task=None) -> None:
raw_tools = tools or self.tools or []
parsed_tools = parse_tools(raw_tools)
prompt, stop_words, rpm_limit_fn = self._build_execution_prompt(raw_tools) # ★用三件套等拼 prompt
if self.agent_executor is not None:
self._update_executor_parameters(...) # 已有就更新参数
else:
if not isinstance(self.llm, BaseLLM):
raise RuntimeError("LLM must be resolved before creating agent executor.")
self.agent_executor = self.executor_class(
llm=self.llm, task=task, agent=self, crew=self.crew, tools=parsed_tools, ...)
_build_execution_prompt★把 role/goal/backstory + 工具说明拼成最终 system prompt——这就是 L03 说的"三件套如何变成 prompt"的入口。已有则更新,否则新建幂等设计:executor 是懒创建的,第二次任务只更新参数不重建,省开销。LLM must be resolved边界:创建引擎前,self.llm 必须已经是真正的 BaseLLM 对象(字符串 "gpt-4o" 得先被解析)。没解析好就直接 raise RuntimeError,不让你带着"半成品大脑"上岗。execute_task(:740)真正执行时,execute_task 会拼 prompt、按 max_execution_time 选"带超时/不带超时"执行,最后把结果整理返回。阶段2 会逐行拆这个循环。CrewAgentExecutor,Agent 只持有它的引用。好处:① 配置对象轻、可序列化——Agent 能被存档/复制/传递,而重量级的运行时状态(消息历史、迭代计数)都在 executor 里;② 可换引擎——字段类型是 CrewAgentExecutor | AgentExecutor,说明执行引擎是可替换的(还有个实验性的 AgentExecutor)。代价是"一个 agent 干活"要跨 Agent 和 Executor 两个对象理解,但这换来了清晰的"配置/运行"分离。边界 + 今日小结
Agent(llm="gpt-4o"),此刻 self.llm 还是字符串吗?看 L05:llm 字段挂了 BeforeValidator(_validate_llm_ref),所以在 Pydantic 构造/校验阶段字符串就会被尝试解析。但 L06 又检查 if not isinstance(self.llm, BaseLLM): raise RuntimeError——这说明存在"尚未解析成对象"的时刻(比如某些延迟解析路径)。坑点:如果你自定义 LLM 或用了不认识的 model 名,解析可能失败,报错往往出现在"创建 executor"这一步而不是"构造 Agent"这一步。记住这条链路:字符串 → BeforeValidator 解析 → 必须是 BaseLLM 才能建 executor,排查模型相关报错就有方向了。👶 小白:goal 和 Task 的 description 有啥区别?都在说"要干嘛"啊。
👨🏫 老师:goal 是 agent 的长期人设目标("我这个分析师追求什么"),跟具体任务无关,一个 agent 只有一个 goal。Task.description 是 这一次要交付的具体活,一个 agent 可以接很多不同的 task。类比:goal 是"我的职业理想",task 是"今天这张派工单"。明天 D04 专门讲 Task。
🧠 今天你应该能回答
- Agent 类在哪个文件?父类是谁?(
agent/core.py;父类BaseAgent) - 为什么分两层继承?(BaseAgent 定契约、支持适配外部框架;Agent 加官方高级能力)
- 人设三件套是哪三个?为什么必填?(role/goal/backstory;结构化角色设定,拼进 prompt)
id为什么 frozen?(身份证不可变,委派/记忆/事件靠它认人)llm="gpt-4o"为什么能只写字符串?(字段挂了 BeforeValidator,校验时解析成 LLM 对象)- agent_executor 是干嘛的?为什么和 Agent 分开?(真正的执行引擎;分离配置与运行,轻量可序列化+可换引擎)
✋ 10 分钟动手
# 1. 看两层类定义与三件套
sed -n '200,296p' lib/crewai/src/crewai/agents/agent_builder/base_agent.py
sed -n '170,335p' lib/crewai/src/crewai/agent/core.py
# 2. Python 里打印一个 agent 有哪些字段
uv run python -c "
from crewai import Agent
a = Agent(role='R', goal='G', backstory='B', llm='gpt-4o')
print(sorted(a.model_fields.keys())) # 一览所有字段名
print(type(a.llm)) # 看 llm 被解析成了什么
"
task.py,看 Task 模型的全字段——description / expected_output / agent / context / async_execution / output_pydantic……以及一个任务是怎么通过 execute_sync → _execute_core 交给 agent 去干、再包成 TaskOutput 的。