跳到主要内容

执行单元:Agent、Action 与 CustomizeAgent

30 秒导读: EvoAgentX 里,真正"干活的最小单位"不是 agent,而是 Action——一次"拼 prompt → 调 LLM → 把回复解析成结构化字段"的操作。Agent 是一层封装:它持有若干 Action、挂着短/长期记忆、按最近 n 条消息拼上下文,再把这一步交给某个 Action 执行。CustomizeAgent 则让你完全不写代码,只用一段 prompt + 输入输出字段声明,就自动生成一个这样的 agent——这正是自动构建时"凭空造出一个 agent"的落点。

本章只回答一个问题:一个 agent 如何执行一步? 图调度、多 agent 编排属于第 3 章,自动构建属于第 4 章,工具/记忆的内部实现属于第 6 章;本章只在"挂载点"上点到它们。阅读前建议先看第 1 章的 BaseModuleMessageLLMOutputParser——它们是这里所有类的地基。


1. 这是什么(零基础也能懂)

先建立一个心智模型

把一个 agent 想成一名专职员工:

  • 他有一项或几项技能(Action)——比如"写代码""审代码""做规划";
  • 他有记忆(短期记忆记住这次工作流里发生了什么,长期记忆可跨会话);
  • 派给他一个任务,他从记忆里翻出相关上下文,挑一项技能,组织语言问 LLM,再把 LLM 那段自由文本整理成规整的字段交回来。

"整理成规整的字段"是关键。LLM 吐出来的是一大段文本,但工作流下一环需要的是结构化数据(比如 {"code": "..."})。EvoAgentX 把"组织问法 + 解析回答"这件事封装成 Action,把"持有技能 + 记忆 + 派活"这件事封装成 Agent

一句话定义

概念一句话类比
Action一次"拼 prompt → 调 LLM → 解析成结构化输出"的可复用操作员工的一项"技能"
Agent持有若干 Action、挂着记忆、按上下文派活的执行体员工本人
CustomizeAgent用 prompt + 输入输出字段声明式定义出来的 Agent,无需写代码填张"岗位说明书"就上岗的员工
AgentManager按名字登记、复用、加锁调度所有 agent 实例的花名册HR / 花名册

用起来什么样

最小的一个 agent,只需要一段 prompt(真实示例,来自 examples/customize_agent.py:39-53):

from evoagentx.agents import CustomizeAgent

simple_agent = CustomizeAgent(
name="SimpleAgent",
description="A basic agent that responds to queries",
prompt="Answer the following question: {question}", # {question} 是输入占位符
llm_config=model_config,
inputs=[
{"name": "question", "type": "string", "description": "The question to answer"}
],
)
response = simple_agent(inputs={"question": "What is a language model?"}) # 直接当函数调用
print(response.content.content) # response 是 Message,.content 是解析后的 LLMOutputParser

注意三件事,它们贯穿全章:

  1. 声明,不编码。 你没写任何"怎么调 LLM""怎么解析"的逻辑,只填了 prompt 和输入声明。
  2. agent 可直接当函数调用 agent(inputs=...)——背后是 __call__
  3. 返回的是 Message 对象,不是裸字符串;它的 .content 是一个解析好的结构化对象(LLMOutputParser,见第 1 章)。

本节不碰任何底层。目标:你现在应该能说清"agent 是持有技能和记忆的执行体,Action 是那项技能,CustomizeAgent 让你声明式造 agent"。


2. 顶层全景(一步执行怎么转)

部件一句话职责

部件干什么在哪
Agent持有 actions + 记忆,按 n 条消息拼上下文,派活给 Actionagents/agent.py:22
Action定义输入/输出规格 + execute,是真正调 LLM 的地方actions/action.py:66
ActionInput / ActionOutput输入规格 / 输出解析(都继承 LLMOutputParser)actions/action.py:16,49
ContextExtraction一个特殊 Action:从最近消息里"抽"出目标 Action 需要的输入actions/action.py:151
CustomizeAction带工具调用循环的通用 Action,CustomizeAgent 用它当引擎actions/customize_action.py:35
CustomizeAgent声明式 agent:把你的 prompt + 字段编译成一个 CustomizeActionagents/customize_agent.py:36
PromptTemplate把 instruction/输入/输出格式/工具组装成最终 promptprompts/template.py:22
AgentManager按名字登记/复用 agent,并用状态锁协调并发调度agents/agent_manager.py:20

主线走一遍(高层,不进代码)

一次 agent.execute("SomeAction", msgs=[...]) 大致这样流动:

派活: execute(action_name, msgs 或 action_input_data)


┌─────────────────────────────────────────────────────────┐
│ Agent (agents/agent.py) │
│ │
│ 1. _prepare_execution: │
│ · 把 msgs 写进【短期记忆】 │
│ · 若没给现成输入 → 用 ContextExtraction 从最近 │
│ n 条消息里"抽"出这个 Action 需要的输入字段 │
│ │
│ 2. 取出 Action 对象,调 action.execute( │
│ llm=self.llm, inputs=..., sys_msg=system_prompt) │
└───────────────────────────────┬─────────────────────────┘
│ inputs (dict)

┌─────────────────────────────────────────────────────────┐
│ Action.execute (actions/customize_action.py) │
│ │
│ a. PromptTemplate/prompt 把 instruction + 输入 + │
│ 输出格式 + 工具 拼成最终 prompt │
│ b. llm.generate(prompt) → 一段自由文本 │
│ c. 按 parse_mode 把文本解析成 outputs_format 的字段 │
│ (title / json / xml / str / custom) │
│ (若挂了工具:进入"调用工具→喂回结果"的多轮循环) │
└───────────────────────────────┬─────────────────────────┘
│ 结构化 ActionOutput

3. Agent 把输出包成【Message】,写回短期记忆,返回

一句话: Agent 负责"记忆 + 上下文 + 派活",Action 负责"拼 prompt + 调 LLM + 解析"。下面逐层拆开。


3. 核心原理

3.1 Action:一次 LLM 调用的"输入规格 / 输出解析"三件套

它要解决的小问题: LLM 的输入输出都是自由文本,但工作流要的是有类型、有字段名的结构化数据。Action 用三个类把这件事夹住两端。

三件套:

角色关键点
ActionInput输入规格声明"这个动作需要哪些字段",供拼 prompt 和抽取输入
ActionOutput输出解析声明"应该解析出哪些字段",to_str() 转成 JSON 字符串
Action执行体持有上面两者 + prompt/prompt_template + tools,定义 execute

Action 基类本身很薄,它主要是"声明字段 + 留空执行钩子"(actions/action.py:82-88):

class Action(BaseModule):
name: str
description: str
prompt: Optional[str] = None
prompt_template: Optional[PromptTemplate] = None
tools: Optional[List[Union[Tool, Toolkit]]] = None # 可挂工具
inputs_format: Optional[Type[ActionInput]] = None # 输入规格
outputs_format: Optional[Type[Parser]] = None # 输出解析

基类的 execute/async_execute 只抛 NotImplementedError(actions/action.py:140,149)——真正的执行逻辑由子类实现。这是有意的:Action 定框架,CustomizeAction(§3.4)填肉。

输入规格怎么用? ActionInput 继承自 LLMOutputParser,所以能自省自己的字段。get_required_input_names 把"没有默认值"的字段挑出来当必填(actions/action.py:30-46);get_specification(models/base_model.py:279)把字段名 + 类型 + 描述打成一段 JSON 规格,喂给 LLM 让它知道"该产出什么"。

关键细节: ActionInputActionOutput 都继承 LLMOutputParser——所以"输入声明"和"输出声明"用的是同一套自省机制(get_attrs / get_specification,见第 1 章)。这让"拼 prompt 时描述输入"与"解析回复成输出"共享一份字段元数据。

3.2 输出解析:同一段文本,五种解析模式

它要解决的小问题: 让 LLM 严格吐 JSON 常常失败,不同任务适合不同"约定格式"。EvoAgentX 不强求 JSON,而是提供 5 种 parse_mode,由 LLMOutputParser.parse 分发(models/base_model.py:551,合法值见 PARSER_VALID_MODE,models/base_model.py:29):

parse_mode约定 LLM 怎么输出解析方式
title用 Markdown 小标题分节(默认 ## 字段名)按标题切段(默认模式)
json输出一个 JSON 对象抽第一个合法 JSON
xml<字段>…</字段> 包裹按 XML 标签取值
str随便输出原文赋给每个字段
custom随便输出交给你写的 parse_func(content)

思路/直觉: 为什么默认是 title 而不是 json?因为 Markdown 标题对 LLM 最自然、最不容易格式崩,而人也能直接读。只有当输出字段本身是 object/array 这类复杂结构时,才强制切到 json(见 §3.5 的自动纠正)。

演示(示意,非源码): 假设 outputs = [thought, code],parse_mode="title",LLM 被要求这样答,解析器按 ## 切段:

## thought → thought 字段 = "先分析需求,再写函数"
先分析需求,再写函数

## code → code 字段 = "def add(a,b): return a+b"
def add(a,b): return a+b

兜底机制(巧妙处): 如果按约定格式解析失败,CustomizeAction 不直接报错,而是再问一次 LLM——用一个"抽取 prompt"把原始文本 + 目标字段规格塞回去,让 LLM 自己把字段抽出来(actions/customize_action.py:233-243_async_extract_output fallback 分支)。这是"解析失败 → 让模型自纠"的实用设计。

3.3 PromptTemplate:把碎片组装成最终 prompt

它要解决的小问题: 一条完整 prompt 由很多部分拼成——系统提示、指令、上下文、约束、few-shot 示例、历史、本次输入、输出格式说明、工具说明。手写容易乱。PromptTemplate 把每部分做成一个 render_xxx 方法,再按固定顺序拼起来。

三种模板(继承链):

PromptTemplate (基类, format() 未实现) prompts/template.py:22
└─ StringTemplate → format() 返回【一整段字符串】 :370
└─ ChatTemplate → format() 返回【消息列表】 :488
└─ MiproPromptTemplate → 兼容 dspy 优化 :660
  • StringTemplate.format(prompts/template.py:419)把所有部分拼成一大段文本,当单条 user prompt。
  • ChatTemplate.format(prompts/template.py:575)把它拆成 [{role:system}, {role:user}, {role:assistant}, ...] 消息列表——系统消息装指令/工具/约束/输出格式,few-shot 示例变成交替的 user/assistant 轮,最后一条 user 装本次输入。

输出格式这段最关键。 render_outputs(prompts/template.py:276)根据 parse_mode 现场生成一个"你必须这样输出"的模板 + 例子。比如 title 模式下,它会列出每个字段该用什么标题、必填还是可选(get_output_template,prompts/template.py:195-229)。这保证了"prompt 里要求的格式"和"解析器期待的格式"由同一份 parse_mode 驱动、天然对齐

占位符防串味(细节): 用户的 instruction 里可能有 {xxx} 花括号,而输入是单独一节渲染的。clear_placeholders(prompts/template.py:104)会把 instruction 里裸露的 {field} 替换成带反引号的 `field`,避免它们被误当成待填占位符。

3.4 CustomizeAction:带工具调用循环的执行引擎

它要解决的小问题: §3.1 的 Action 只是框架,谁来真正"调 LLM + 解析 + 可能还要调工具"?答案是 CustomizeAction(actions/customize_action.py:35)——CustomizeAgent 背后的引擎。

它比基础 Action 多了: parse_mode / parse_func / title_format(怎么解析)、tools(能用哪些工具)、max_steps(最多几轮 LLM 调用)、max_tool_call_concurrency(工具并发上限)——见 actions/customize_action.py:37-44

执行主循环(async_execute,actions/customize_action.py:327): 这是全章工程含量最高的一段。execute(同步版)只是把 async_executerun_coroutine_sync 跑起来(:309-325),真正逻辑都在异步版里。循环大意:

prepare_context: 拼好 system + user prompt(+工具说明)放进 ContextManager


┌──────────────── while iter < max_steps ────────────────┐
│ │
│ llm.async_generate(messages=上下文, tools=…) │
│ │ │
│ ▼ │
│ 从回复里找 <tool_call> 块? │
│ ├─ 有 → 并发执行这些工具,把结果喂回上下文,继续循环│
│ └─ 无 → 找 <answer> 块? │
│ ├─ 有 → 收尾,跳出循环 │
│ └─ 无 → 追加"请给出答案"提示,继续循环 │
└────────────────────────────────────────────────────────┘


_async_extract_output: 把最终 <answer> 文本按 parse_mode 解析成 ActionOutput

怎么读这张流程: 一轮里 LLM 要么"要求调工具",要么"给最终答案",要么"还在想"。工具调用会把结果拼回上下文再问一轮,直到模型给出 <answer> 或撞上 max_steps(撞上就抛 NoAnswerError,:354)。

native vs default 两种工具模式(巧妙处): 是否把工具 schema 直接交给模型,取决于 provider 支不支持原生 function calling(ContextManagermemory/context_manager.py:24-27 里判定 mode):

模式工具怎么给模型怎么拿回工具调用
native通过 llm.async_generate(tools=schema) 原生传入provider 返回结构化 tool_calls
default把工具说明写进 prompt(TOOL_CALLING_TEMPLATE)从回复文本里正则抠 <tool_call>

这让同一套 agent 逻辑对 provider 无感:CustomizeAction 只管发工具、收结果,具体走哪条路由 LLM 层和 ContextManager 决定。工具本身(Tool / Toolkit)如何挂载见 §5,内部实现留给第 6 章

一个真实的坑(有注释记录): 每个工具调用必须带 id。原生模式下 id 由 provider 发放,用来把 assistant 的 tool_callsrole: tool 结果配对;但模型有时会手写一个没 id 的 <tool_call> 块,于是代码在 actions/customize_action.py:186-189 给缺 id 的调用补一个 uuid,否则 provider 会拒收。

3.5 CustomizeAgent:声明式造一个 agent

它要解决的小问题: 手写一个 agent 要定义 Action 子类、写 execute、声明 input/output 类……太重。自动构建时更是要"凭一句话目标凭空生成 agent",根本没法手写代码。CustomizeAgent 就是那个声明式落点:给它 prompt + 输入输出字段,它自动编译出一个能跑的 agent。

核心一步:把声明"编译"成一个 CustomizeAction。 CustomizeAgent.__init__(agents/customize_agent.py:79)做的事:

  1. 校验并归一化输入输出:把 inputs/outputs 里的 dict 统一转成 Parameter 对象,校验 parse_mode 合法性(validate_data,:380)。
  2. 动态造 input/output 类: create_action_input / create_action_output(:573,577)用 pydantic 的 create_model 在运行时凭字段声明生成 ActionInput/ActionOutput 子类(_create_action_parser,:549)。这就是"你只填字段、它变出类型"的魔法。
  3. 组装成 CustomizeAction: create_customize_action(:451)把 prompt、动态 input/output 类、parse_mode、tools 全塞进一个 CustomizeAction 实例。
  4. 挂到 Agent 上: super().__init__(actions=[customize_action])(:147-154)——这个 agent 就持有了这一个主 action。

调用入口: CustomizeAgent.__call__(agents/customize_agent.py:643)让你能 agent(inputs={...}),它转调 Agent.__call__execute/async_execute,action_name 固定指向那个编译出来的主 action(customize_action_name,:170)。

从文件/字典重建: from_dict(:720)让 agent 可序列化往返——自动构建生成的 agent 配置,和 AgentManager 从存储加载 agent,走的都是这条路。

智能纠正(细节): 若输出字段含 object/array 这类复杂类型、且用的是 prompt_template,validate_data自动把 parse_mode 纠正成 json(agents/customize_agent.py:435-441)——因为复杂结构只有 JSON 扛得住。裸 prompt 则不纠正,因为它自带输出格式约定。


4. Agent:记忆 + 上下文 + 派活

前面讲的是"技能"(Action),这节讲"员工本人"(Agent)怎么把技能、记忆、派活串起来。

4.1 Agent 持有什么

Agent(agents/agent.py:22)的关键字段(:40-54):

字段作用
actions持有的 Action 列表;init_module 把它建成 _action_map(名字→action)供快速查找(:61-62)
short_term_memory短期记忆,存单次工作流内的消息(ShortTermMemory,默认自动创建)
long_term_memory + ..._manager长期记忆,可跨会话;仅当 use_long_term_memory=True 才初始化
llm / llm_config语言模型实例 / 配置
n取最近 n 条消息当上下文;默认 None = 用短期记忆里全部消息
system_prompt派给 Action 时作为 sys_msg

4.2 init_llm:两种给模型的方式,一致性检查

init_module 在实例化后被调,非人类 agent 会 init_llm()(agents/agent.py:56-64)。init_llm(:294)处理"你给了 llm 还是 llm_config":

  • 两个都给 → 校验 llm.config == llm_config,不一致就报错(:303-310);
  • 只给 config → 用它 set_llm_config 造出 llm(:313);
  • 只给 llm → 反向 set_llm 同步 config(:315);
  • 都没给 → 报错。

init_module 里还会 init_context_extractor()(:64):每个 agent 都自动挂一个 ContextExtraction action(:334-340),这是下节"抽取输入"的关键。人类 agent(is_human=True)则跳过 LLM 初始化。

4.3 一步执行:execute / async_execute

execute(agents/agent.py:231)和 async_execute(:170)结构对称,分三步(都复用 _prepare_execution_create_output_message 两个辅助函数):

第一步 _prepare_execution(:84): 更新记忆 + 拿到输入。

  • 传进来的 msgs 直接写进短期记忆(:111-113);若传的是现成 action_input_data,则包成一条 INPUT 类型 Message 入记忆(:114-124);
  • 若没给现成输入 → get_action_inputs(:393)用 ContextExtraction最近 n 条消息里抽出这个 action 需要的字段(:405-407)。这正是 §4.4 要讲的"按 n 条消息取上下文"。

第二步 派活:action.execute(llm=self.llm, inputs=..., sys_msg=self.system_prompt, return_prompt=True)(:263-269)。异步版更聪明——用 is_method_overridden 判断这个 action 到底实现了 async_execute 还是 execute,选能用的那个(:201-208)。

第三步 _create_output_message(:131): 把 action 的结构化输出包成一条 Message(带 agent/action/prompt/工作流元数据),写回短期记忆并返回(:154-168)。所以 agent 的产出永远是 Message,且每一步都留痕在记忆里。

同步还是异步? __call__(:74)会探测当前有没有运行中的事件循环:有就走 async_execute,没有就走 execute(:76-82)——让 agent 在同步和异步环境里都能直接当函数调。

4.4 按 n 条消息取上下文 + 从上下文抽输入

这是 agent"记忆驱动"的精髓。当你不手把手喂输入时,agent 会自己从最近对话里凑出输入。

短期记忆 (一串 Message)
[msg1, msg2, ..., msg_k]
│ get(n=self.n) ← n=None 取全部,否则取最近 n 条

最近 n 条消息 ─────────► ContextExtraction.execute
│ (actions/action.py:169)
│ 用 LLM 读这段上下文,
│ 按目标 action 的 inputs_format 规格
▼ 抽出所需字段
action_input_data (dict)


交给目标 Action 执行

ContextExtraction(actions/action.py:151)本身也是个 Action:它把最近消息拼成文本,连同目标 action 的名字/描述/输入规格(get_specification)塞进抽取 prompt,让 LLM 产出一个匹配 inputs_format 的字典(:200-214)。"n"这个旋钮决定了 agent 一步能"看见"多久以前的对话——这是 agent 在工作流里衔接上下游的核心机制。


5. Action 如何挂载工具(点到为止)

只讲挂载点,细节留第 6 章 一个 Action 通过 tools 字段挂 ToolToolkit(tools/tool.py 里的两个类):

  • Tool = 单个可调用工具;Toolkit = 一组工具的集合(get_tools() 取出里面的 Tool)。
  • CustomizeAction.add_tools(actions/customize_action.py:96)负责把传入的 tool/toolkit 摊平成 Tool 列表,登记进 tools_caller(名字→工具),并 compile_tool_schemas 生成给 LLM 看的 schema(:73,134)。名字重复会覆盖并告警(:119-120)。
  • 执行时,_call_single_tool(:245)按名字从 tools_caller 取工具、并发调用(受 max_tool_call_concurrency 信号量约束,:293-307),失败则把 {"error": ...} 喂回给模型让它重试(:414-420)。

一句话:工具在 Action 这一层挂载和调用,但"工具本身怎么实现、有哪些工具"是第 6 章的事。


6. AgentManager:agent 的花名册与调度锁

它要解决的小问题: 工作流运行时,多个节点可能引用同名 agent,还可能并发执行。谁来保证"同一个 agent 实例被复用、不重复造、并发时不打架"?答案是 AgentManager(agents/agent_manager.py:20)。

6.1 按名字登记与复用

AgentManager 持有 agents 列表和 agent_states(名字→状态,:29-30)。add_agent(:173)是核心入口:

  • 已存在同名 agent → 直接跳过(靠名字去重,:188-189),这就是"复用"。
  • 否则 create_agent(:152)按传入类型造实例:传 str → 从存储加载或从已有列表取;传 dict → 走 create_agent_from_dict 造一个 CustomizeAgent;传 Agent 实例 → 直接用(:154-171)。
  • 造好后登记进列表,状态设为 AVAILABLE,并 check_agents 校验无重名(:191-195)。

从工作流批量登记: add_agents_from_workflow(:204)遍历 WorkFlowGraph 每个节点上的 agents 逐个 add_agent——这是第 3/4 章"图里的节点变成可执行 agent"的对接点。

6.2 AgentState:两态 + 并发协调

AgentState 只有两个值(agents/agent_manager.py:15-17):

状态含义
AVAILABLE空闲,可被调度
RUNNING正在执行,别人得等

调度时,set_agent_state(:307)在加锁下改状态并 notify_all 唤醒等待者;想用某 agent 的线程调 wait_for_agent_available(:366)在条件变量上阻塞,直到它变 AVAILABLE(:380-384)。这套 threading.Condition 机制让多线程工作流能安全地"抢占—释放"同一个 agent 实例。并发怎么被工作流调度器驱动,见第 3 章


7. 巧妙之处(可带走的技术)

  • 输入规格与输出解析共用一套自省。 ActionInput/ActionOutput 同继承 LLMOutputParser,拼 prompt 用的字段描述(get_specification)和解析回复用的字段(get_attrs)是同一份元数据——声明一次,两端复用(actions/action.py:16,49)。
  • prompt 格式与解析器格式由同一个 parse_mode 驱动。 render_outputs(prompts/template.py:276)按 parse_mode 生成"你要这样输出"的说明,解析器也按同一个 parse_mode 解析——从根上避免"要求 JSON 却按标题解析"的错位(prompts/template.py:195)。
  • 解析失败先自纠再报错。 直接解析崩了就把原文回喂 LLM 抽字段(actions/customize_action.py:233-243),比直接 500 健壮得多。
  • 工具模式对 provider 透明。 native / default 两条路由 ContextManager.mode 自动选(memory/context_manager.py:24-27),agent 逻辑不感知 provider 差异。
  • 运行时用 create_model 造类型。 CustomizeAgent 凭字段声明动态生成 pydantic 类(agents/customize_agent.py:549-571),这是"声明式造 agent"和自动构建得以成立的技术底座。
  • 给手写 <tool_call> 补 id。 一个真实 provider 兼容坑的解法(actions/customize_action.py:186-189)。

8. 边界与局限

  • Action 基类不能直接跑。 execute/async_executeNotImplementedError(actions/action.py:140,149);必须用 CustomizeAction 或自己实现子类。
  • 工具循环有硬上限。 超过 max_steps(默认 20)还没拿到 <answer> 就抛 NoAnswerError(actions/customize_action.py:351-354),不会无限打转。
  • clear_short_term_memory 是空实现。 agents/agent.py:439-443 目前 pass,短期记忆不会被真正清空——长跑要留意。
  • load_all_agents / save_all_agents 未实现。 agent_manager.py:118,347 都是 pass;持久化能力尚不完整。
  • 图调度、自动构建、工具/记忆内部不在本章。 分别见第 3第 4第 6 章

9. 代码地图(导航索引)

主题文件路径符号名
Action 基类 / 三件套evoagentx/actions/action.pyActionActionInputActionOutput
输入必填字段自省evoagentx/actions/action.pyActionInput.get_required_input_names
从上下文抽输入evoagentx/actions/action.pyContextExtractionContextExtraction.execute
带工具循环的执行引擎evoagentx/actions/customize_action.pyCustomizeActionCustomizeAction.async_execute
工具挂载 / 并发调用evoagentx/actions/customize_action.pyCustomizeAction.add_tools_call_single_tool_calling_tools
解析失败回喂自纠evoagentx/actions/customize_action.pyCustomizeAction._async_extract_output
Agent 主体evoagentx/agents/agent.pyAgentAgent.executeAgent.async_execute
LLM 初始化evoagentx/agents/agent.pyAgent.init_llm
按 n 条消息取上下文evoagentx/agents/agent.pyAgent.get_action_inputs
记忆读写辅助evoagentx/agents/agent.py_prepare_execution_create_output_message
声明式 agentevoagentx/agents/customize_agent.pyCustomizeAgentCustomizeAgent.__init__
运行时造 input/output 类evoagentx/agents/customize_agent.pycreate_action_inputcreate_action_output_create_action_parser
编译成 CustomizeActionevoagentx/agents/customize_agent.pyCustomizeAgent.create_customize_action
序列化往返evoagentx/agents/customize_agent.pyCustomizeAgent.from_dictget_customize_agent_info
agent 花名册 / 复用evoagentx/agents/agent_manager.pyAgentManagerAgentManager.add_agent
并发状态协调evoagentx/agents/agent_manager.pyAgentStateset_agent_statewait_for_agent_available
prompt 组装evoagentx/prompts/template.pyPromptTemplateStringTemplateChatTemplate
输出格式渲染evoagentx/prompts/template.pyrender_outputsget_output_template
五种解析模式evoagentx/models/base_model.pyLLMOutputParser.parsePARSER_VALID_MODE
工具调用提示模板evoagentx/prompts/tool_calling.pyTOOL_CALLING_TEMPLATE
native/default 上下文evoagentx/memory/context_manager.pyContextManagerContextManager.add_prompt_template