数据截至 (上游 commit fd6b9a6352af)
执行单元:Agent、Action 与 CustomizeAgent
30 秒导读: EvoAgentX 里,真正"干活的最小单位"不是 agent,而是 Action——一次"拼 prompt → 调 LLM → 把回复解析成结构化字段"的操作。Agent 是一层封装:它持有若干 Action、挂着短/长期记忆、按最近 n 条消息拼上下文,再把这一步交给某个 Action 执行。CustomizeAgent 则让你完全不写代码,只用一段 prompt + 输入输出字段声明,就自动生成一个这样的 agent——这正是自动构建时"凭空造 出一个 agent"的落点。
本章只回答一个问题:一个 agent 如何执行一步? 图调度、多 agent 编排属于第 3 章,自动构建属于第 4 章,工具/记忆的内部实现属于第 6 章;本章只在"挂载点"上点到它们。阅读前建议先看第 1 章的 BaseModule、Message、LLMOutputParser——它们是这里所有类的地基。
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
注意三件事,它们贯穿全章:
- 声明,不编码。 你没写任何"怎么调 LLM""怎么解析"的逻辑,只填了 prompt 和输入声明。
- agent 可直接当函数调用
agent(inputs=...)——背后是__call__。 - 返回的是
Message对象,不是裸字符串;它的.content是一个解析好的结构化对象(LLMOutputParser,见第 1 章)。
本节不碰任何底层。目标:你现在应该能说清"agent 是持有技能和记忆的执行体,Action 是那项技能,CustomizeAgent 让你声明式造 agent"。
2. 顶层全景(一步执行怎么转)
部件一句话职责
| 部件 | 干什么 | 在哪 |
|---|---|---|
Agent | 持有 actions + 记忆,按 n 条消息拼上下文,派活给 Action | agents/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 + 字段编译成一个 CustomizeAction | agents/customize_agent.py:36 |
PromptTemplate | 把 instruction/输入/输出格式/工具组装成最终 prompt | prompts/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 让它知道"该产出什么"。
关键细节:
ActionInput和ActionOutput都继承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_execute 用 run_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(ContextManager 在 memory/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_calls和role: 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)做的事:
- 校验并归一化输入输出:把
inputs/outputs里的 dict 统一转成Parameter对象,校验parse_mode合法性(validate_data,:380)。 - 动态造 input/output 类:
create_action_input/create_action_output(:573,577)用 pydantic 的create_model在运行时凭字段声明生成ActionInput/ActionOutput子类(_create_action_parser,:549)。这就是"你只填字段、它变出类型"的魔法。 - 组装成
CustomizeAction:create_customize_action(:451)把 prompt、动态 input/output 类、parse_mode、tools 全塞进一个CustomizeAction实例。 - 挂到 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 字段挂 Tool 或 Toolkit(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_execute抛NotImplementedError(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.py | Action、ActionInput、ActionOutput |
| 输入必填字段自省 | evoagentx/actions/action.py | ActionInput.get_required_input_names |
| 从上下文抽输入 | evoagentx/actions/action.py | ContextExtraction、ContextExtraction.execute |
| 带工具循环的执行引擎 | evoagentx/actions/customize_action.py | CustomizeAction、CustomizeAction.async_execute |
| 工具挂载 / 并发调用 | evoagentx/actions/customize_action.py | CustomizeAction.add_tools、_call_single_tool、_calling_tools |
| 解析失败回喂自纠 | evoagentx/actions/customize_action.py | CustomizeAction._async_extract_output |
| Agent 主体 | evoagentx/agents/agent.py | Agent、Agent.execute、Agent.async_execute |
| LLM 初始化 | evoagentx/agents/agent.py | Agent.init_llm |
| 按 n 条消息取上下文 | evoagentx/agents/agent.py | Agent.get_action_inputs |
| 记忆读写辅助 | evoagentx/agents/agent.py | _prepare_execution、_create_output_message |
| 声明式 agent | evoagentx/agents/customize_agent.py | CustomizeAgent、CustomizeAgent.__init__ |
| 运行时造 input/output 类 | evoagentx/agents/customize_agent.py | create_action_input、create_action_output、_create_action_parser |
| 编译成 CustomizeAction | evoagentx/agents/customize_agent.py | CustomizeAgent.create_customize_action |
| 序列化往返 | evoagentx/agents/customize_agent.py | CustomizeAgent.from_dict、get_customize_agent_info |
| agent 花名册 / 复用 | evoagentx/agents/agent_manager.py | AgentManager、AgentManager.add_agent |
| 并发状态协调 | evoagentx/agents/agent_manager.py | AgentState、set_agent_state、wait_for_agent_available |
| prompt 组装 | evoagentx/prompts/template.py | PromptTemplate、StringTemplate、ChatTemplate |
| 输出格式渲染 | evoagentx/prompts/template.py | render_outputs、get_output_template |
| 五种解析模式 | evoagentx/models/base_model.py | LLMOutputParser.parse、PARSER_VALID_MODE |
| 工具调用提示模板 | evoagentx/prompts/tool_calling.py | TOOL_CALLING_TEMPLATE |
| native/default 上下文 | evoagentx/memory/context_manager.py | ContextManager、ContextManager.add_prompt_template |