跳到主要内容

让 agent 动手与记忆:工具生态、记忆、RAG 与人机协作

30 秒导读: 前几章讲的是 agent 怎么"想"和"跑"(Agent/Action、工作流、自动构建、自演化)。 这一章讲的是让 agent 走出模型、连上真实世界的横向能力层:用统一契约调用工具、跨对话轮 记住东西、从大文档库里检索知识、并在危险动作前插入人工审批。它们不属于某条主执行线, 而是被各层"取用"的公共货架。

本章是能力层总览,和这些兄弟章互补、不重叠:

  • 核心地基 —— BaseModule/消息/LLM 输出解析(本章的类都继承 BaseModule)。
  • 执行单元 —— Agent 与 Action(工具/HITL 最终被 Action 调用)。
  • 工作流引擎 —— HITL 拦截器就挂在工作流的执行点上。
  • 自动构建 / 自演化 —— 主执行/优化线。

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

一句话定义: 这一章讲 EvoAgentX 里让 agent"手能动、脑能记、事能查、险能拦"的四组基础设施。

用一个类比先建立直觉。 把一个 LLM 想成一个很聪明但被关在小黑屋里、还有严重健忘症的人:

它缺什么这一层补什么对应模块
手脚(只能说话,不能动)工具:执行代码、搜网页、开浏览器、查数据库tools/
记性(说完就忘)记忆:短期滑动窗口 + 长期可检索存储memory/ + storages/
查资料的能力RAG:把大文档切块、嵌入、按相似度召回rag/
危险动作的刹车人机协作:关键点暂停,等人批准hitl/

它能做什么(功能):

  • 用一套统一的 JSON schema 契约暴露几十种工具(搜索族、代码沙箱、浏览器、数据库、MCP……)。
  • 给 agent 挂上短期记忆(最近 N 条消息)和长期记忆(向量库 + 数据库,支持增删改查 + 语义搜索)。
  • 把任意文件读进来、切块、嵌入、索引,之后按查询召回相关片段喂给 LLM。
  • 在工作流的某个节点前后拦下来,弹出 CLI 审批框,让人 approve / reject / 修改。

用起来什么样(最小示例):

# 示意,非源码 —— 感受一下"工具"这一层怎么用
from evoagentx.tools import WikipediaSearchToolkit

toolkit = WikipediaSearchToolkit() # 一个 Toolkit 打包了若干 Tool
tool = toolkit.get_tool("wikipedia_search") # 按名字取出具体工具
schema = tool.get_tool_schema() # 得到 OpenAI function-calling 格式的 JSON schema
result = tool(query="EvoAgentX") # 直接像函数一样调用

一句话直觉: 工具是"函数 + 一份能喂给 LLM 的说明书";记忆是"上下文窗口当内存、数据库当磁盘"; RAG 是"给 agent 配了个可搜索的图书馆";HITL 是"在流水线上装了个人工闸门"。


2. 顶层全景(它大概怎么转)

这四组能力彼此独立,但共享同一条底座:都建在 BaseModule 上, 长期记忆和 RAG 又共享同一个持久化后端 StorageHandler

怎么读这张图: 上半区是四组能力,下半区是它们共用的存储底座;箭头表示"依赖 / 数据流向"。

┌──────────────────────────────────────────────────────────┐
│ Agent / Action 层 │ ← 第 2 章
│ (真正发起"调用工具 / 读写记忆 / 请人审批") │
└───┬───────────────┬──────────────┬──────────────┬─────────┘
│ 调用 │ 读写 │ 检索 │ 拦截
▼ ▼ ▼ ▼
┌───────────┐ ┌────────────┐ ┌───────────┐ ┌────────────┐
│ 工具 tools │ │ 记忆 memory │ │ RAG rag │ │ 人机 hitl │
│ Tool/Toolkit│ │ Short/Long │ │ RAGEngine │ │ HITLManager│
└─────┬─────┘ └─────┬──────┘ └─────┬─────┘ └────────────┘
│ │ 长期记忆 │ 复用
│ └───────┬───────┘
│ ▼
│ ┌─────────────────────┐
└───────────▶│ StorageHandler │ 统一持久化后端
│ DB / 向量 / 图 存储 │ storages/
└─────────────────────┘

部件一句话职责:

部件干什么在哪个文件
Tool / Toolkit所有工具的契约:JSON schema + 签名校验 + __call__tools/tool.py
各工具族搜索 / 代码沙箱 / 浏览器 / 数据库 / MCP 的具体实现tools/search_*.pyinterpreter_*.py
ShortTermMemory内存里的滑动窗口,只留最近 N 条消息memory/memory.py
LongTermMemory消息落库 + 索引 + 语义检索(去重、增删改查)memory/long_term_memory.py
MemoryManager更高层:用 LLM 决策记什么、组装带记忆上下文的消息memory/memory_manager.py
StorageHandler统一读写 DB / 向量 / 图三种后端storages/base.py
RAGEngine读→切块→嵌入→索引→检索的完整管线rag/rag.py
HITLManager在关键点请求人工审批 / 收集输入hitl/approval_manager.py

主线走一遍(高层): 一次"带记忆的、可动手的、需审批的"回合大概是—— Agent 把用户输入交给 MemoryManager 组装出"输入 + 召回的历史"→ LLM 决定调某个工具 → Toolkit 按名字取出 Tool__call__ → 若该动作被 HITL 盯上,先经 HITLManager 等人批 → 执行结果作为新消息写回 LongTermMemory(经 RAGEngine 索引、经 StorageHandler 落库)。


3. 核心机制一:工具契约 Tool / Toolkit

这一节讲整个工具货架里工程含量最高、也最该读透的一支:所有工具遵守的那份契约。

3.1 它要解决的小问题

LLM 要"调用工具",前提是拿到一份它能读懂的说明书(参数叫什么、什么类型、哪些必填), 业界事实标准就是 OpenAI function-calling 的 JSON schema。同时工程上还要保证:说明书和真实 函数签名不会对不上——否则 LLM 按 schema 传参、Python 却抛 TypeError

Tool 就是把这两件事焊死的契约:一份 schema,一次签名校验,一个统一入口 __call__

3.2 契约的三根支柱

Tool 继承 BaseModule,声明四个类属性:namedescriptioninputsrequired (tools/tool.py:24-28,class Tool)。它做三件事:

支柱一:生成 schema。 get_tool_schema() 把这几个属性拼成标准的 type: function 结构 (tools/tool.py:38-50),这正是喂给 LLM 的说明书:

# 示意,非源码 —— get_tool_schema() 产出的形状
{
"type": "function",
"function": {
"name": "wikipedia_search",
"description": "Search Wikipedia ...",
"parameters": {"type": "object", "properties": {...}, "required": ["query"]}
}
}

支柱二:统一入口。 基类的 __call__ 直接 raise NotImplementedError (tools/tool.py:94-95)——强制每个工具把"怎么执行"实现成 __call__,于是所有工具对调用方 长一个样:tool(**kwargs)

支柱三:构造期签名校验(最巧的一环)。 Tool.__init_subclass__子类一被定义时就调用 validate_attributes()(tools/tool.py:34-36)。这个类方法逐条核对(tools/tool.py:52-92):

怎么读:从上到下是校验顺序,任一条不过就 raise ValueError,类根本定义不出来
① name/description/inputs 必须存在且类型正确
② 每个 input 必须有 "type" 和 "description"
③ type 必须 ∈ ALLOWED_TYPES(string/number/integer/boolean/object/array)
④ ★ 每个 input 名必须出现在 __call__ 的参数签名里
⑤ ★ input 的 JSON 类型必须和 __call__ 里该参数的 Python 注解一致
⑥ required 里的名字必须都在 inputs 里

第 ④⑤ 条是精髓:它用 inspect.signature(cls.__call__) 反射出真实签名,把 JSON 类型 ("string")映射回 Python 类型(str)逐一比对(tools/tool.py:83-87):

# tools/tool.py:83-87 —— schema 与真实签名必须对齐,否则类定义即失败
call_signature = inspect.signature(cls.__call__)
if input_name not in call_signature.parameters:
raise ValueError(f"Input '{input_name}' is not found in __call__")
if call_signature.parameters[input_name].annotation != json_to_python[input_content["type"]]:
raise ValueError(f"Input '{input_name}' has a type mismatch in __call__")

为什么这很妙: schema 漂移(说明书和代码不一致)是工具生态最常见、最难查的 bug。EvoAgentX 把它提前到类定义时就炸——写错的工具连 import 都过不了,根本进不了运行时。

3.3 Toolkit:工具的打包与命名空间

单个工具往往共享一份"重资源"(一个搜索客户端、一个解释器实例)。Toolkit 就是把一组相关 Tool + 共享后端打包成一个可挂载单元(tools/tool.py:97-127)。它提供按名字取工具 (get_tool)、批量拿 schema(get_tool_schemas)等便捷方法。

典型模式(以维基搜索为例):Toolkit.__init__ 先建一个共享的 SearchWiki 实例,再把它注入到 薄薄的 WikipediaSearchTool 里(search_wiki/search_wiki.pysearch_wiki.py:140-167)。 于是**"重活在 SearchBase、契约在 Tool、打包在 Toolkit"**三层分工清晰。

Toolkit (WikipediaSearchToolkit)
├── 持有共享后端: SearchWiki 实例 (继承 SearchBase,含 html2text/BeautifulSoup)
└── tools: [ WikipediaSearchTool ] ← 每个 Tool 只负责"schema + 把调用转发给后端"

4. 核心机制二:代表性工具族(一览 + 深读一个)

这一节按"能力类型"把工具货架分门别类,只对代码沙箱做深读,其余列举——因为它们都遵守第 3 节的 同一契约,读懂一个就懂全部。

4.1 五大工具族速览

代表实现(文件)干什么代表 Toolkit
搜索search_wiki.pysearch_google.pysearch_ddgs.pysearch_exa.pysearch_serpapi.pysearch_serperapi.pysearch_google_f.py检索网页/维基/学术,抓正文WikipediaSearchToolkit
代码执行interpreter_python.pyinterpreter_docker.py在受控/沙箱环境跑代码PythonInterpreterToolkitDockerInterpreterToolkit
浏览器browser_tool.pybrowser_use.py驱动真实浏览器点点点、抓页面BrowserToolkitBrowserUseToolkit
数据库database_base.pydatabase_faiss.pydatabase_mongodb.pydatabase_postgresql.py增删改查关系库/文档库/向量库MongoDBToolkitPostgreSQLToolkit
MCP 接入mcp.py把外部 MCP 服务器的工具动态接进来MCPToolkit

货架远不止这些:还有 request_arxiv.pygmail_tools.pytelegram_tools.pygoogle_maps_tool.pyfinance_tool.pyimage_tools/rss_feed.pycmd_toolkit.pyfile_tool.py 等,全部在 tools/__init__.py 里统一导出(tools/__init__.py:1-74)。

4.2 深读:Python 代码沙箱 PythonInterpreterToolkit

它要解决的小问题: 让 agent 能跑一段它自己写的 Python,但不能让它 import os; os.system(...) 把机器搞崩。所以核心矛盾是"能执行"和"安全"的平衡。

思路: 用一份导入白名单 allowed_imports 做静态门禁——先 AST 静态分析代码里所有 import,凡不在白名单里的直接拒绝,再谈执行。

真实实现: execute() 是主入口(interpreter_python.py:285-342,PythonInterpreter.execute)。 它的门禁—执行两段值得看:

# interpreter_python.py:324-327 —— 先静态查违规,有违规就直接返回、不执行
if self.allowed_imports:
violations = self._analyze_code(code)
if violations:
return "\n".join(violations)

真正执行时用 exec()重定向 stdout 捕获输出(interpreter_python.py:330-342):

# interpreter_python.py:330-339 —— 捕获 stdout;异常则把 traceback 当结果返回
stdout_capture = io.StringIO()
with contextlib.redirect_stdout(stdout_capture):
try:
exec(code, {"__builtins__": __builtins__})
except Exception:
... # 组装 traceback 字符串返回

关键细节 / 坑(诚实): 源码里明确写了警告——exec()完整权限运行,白名单只挡 import, 不是真正的沙箱(interpreter_python.py:293-295 的 WARNING 注释:"Do not use with untrusted code")。若 allowed_imports 为空,if self.allowed_imports: 为假,门禁整段被跳过——即默认 放行一切导入。真要隔离,应改用基于容器的 interpreter_docker.py(DockerInterpreterToolkit)。

契约怎么落地: PythonExecuteTool 就是第 3 节契约的教科书实例——声明 inputs (code/language)、required=["code"],把 __call__ 转发给持有的 PythonInterpreter (interpreter_python.py:374-401)。它的 __call__(self, code: str, language: str = "python") 签名和 inputs 完全对齐,所以能通过 3.2 的校验。

4.3 MCP:把外部工具生态动态接进来

它要解决的小问题: 别人已经用 MCP(Model Context Protocol,模型上下文协议——一套让工具服务器 和 LLM 客户端对话的标准)写好了工具服务器,EvoAgentX 想直接复用,而不是每个都手写 Tool

思路: MCPToolkit 读一份配置(含 mcpServers 键),为每个服务器建 MCPClient 连接, 把服务器自报的工具 schema 动态包成 MCPTool(mcp.py:28-49,class MCPTool),再由 get_toolkits() 汇总成标准 Toolkit 列表(mcp.py:377)——于是外部工具和本地工具长一个样

配置解析见 mcp.py:346-365(_from_config):要求顶层必须有 mcpServers 键,再把每个命名服务器 拆成独立配置。连接失败的服务器会被剔除、不至于拖垮整体(mcp.py:317-332)。

config(mcpServers) ──▶ MCPToolkit ──▶ 每个服务器一个 MCPClient ──▶ _connect()

动态读取服务器上报的 inputSchema ◀──────────────────────┘


包成 MCPTool(遵守 Tool 契约) ──▶ get_toolkits() 返回标准 Toolkit

5. 核心机制三:记忆(短期 vs 长期 vs 管理器)

这一节讲让 agent"跨轮记住东西"的三层。先给一张对比表建立心智模型,再逐层看。

ShortTermMemoryLongTermMemoryMemoryManager
存哪纯内存 deque向量库 + 数据库(经 RAG/Storage)不存,编排 LongTermMemory
保留多少最近 max_size 条(默认 5)全部,可检索——
能语义搜索吗是(转发)
典型用途当前对话上下文跨会话知识/历史用 LLM 决策记什么、组装上下文
位置memory/memory.py:188memory/long_term_memory.py:16memory/memory_manager.py:17

5.1 短期记忆:一个会自动丢旧的滑动窗口

ShortTermMemory 就是包了个定长 deque 的对象(memory/memory.py:188-262)。它在 model_post_init 里把 buffer 转成 deque(maxlen=max_size)(memory/memory.py:217-222)——于是 超过容量的旧消息自动被挤掉,天然实现"只记最近 N 条"。它刻意不做持久化(类 docstring 明说 "does not persist to storage_handler or vector DB",memory/memory.py:194-195)。

5.2 长期记忆:落库 + 索引 + 检索的一整套 CRUD

LongTermMemory 继承 BaseMemory,把每条 Message 变成一个 RAG Chunk 存进向量库,同时 维护内存副本(memory/long_term_memory.py:16-34)。它对外提供完整的 add/get/delete/update/search

一次 add 的路径(读它就懂整套设计):

add(messages) long_term_memory.py:77

├─ 归一化:str → Message,过滤空内容 :81-82
├─ ★ 基于 SHA-256 内容哈希去重 :88-109
│ 从 storage 读出已存 content_hash 集合
│ 命中 → 复用旧 memory_id,不重复索引
├─ Message → Chunk (_create_memory_chunk) :36-59
│ 元数据带上 action/wf_goal/agent/timestamp...
├─ super().add_message() 写内存副本 :121-122
└─ rag_engine.add(...) 索引进向量库 :125-126

精髓:内容哈希去重。 add 先把已有记录的 content_hash 收成集合,新消息算 SHA-256,命中就 复用旧 id、跳过索引(long_term_memory.py:100-109)——避免同一段内容反复占用向量库。

检索是异步的。 search_async 把查询包成 Query、调 rag_engine.query_async,再把召回的 Chunk 还原成 Message(long_term_memory.py:232-257);search 只是 asyncio.run 的同步壳 (long_term_memory.py:259-262)。注意 delete/update 内部也靠 asyncio.run(self.get(...)) 把异步检索拉回同步(long_term_memory.py:168:200)。

5.3 记忆管理器:让 LLM 决定"记什么"

MemoryManager 是长期记忆之上的"大脑"(memory/memory_manager.py:17-30)。它多做两件事:

其一,LLM 门控。use_llm_management=True 且有 llm 时,add/update/delete 前会先问 LLM "该不该做这个记忆操作",LLM 返回结构化 JSON(action ∈ add/update/delete),被拒的就跳过 (memory_manager.py:35-56_prompt_llm_for_memory_operation,以及 :105-116 的用法)。 LLM 出错时回退到原始输入,不阻断主流程(memory_manager.py:54-56)。

其二,组装带记忆的消息。 create_message / create_conversation_message 先按 user_prompt 检索相关记忆,再把"用户输入 + 召回上下文/历史"拼成一条新 Message 交给 agent (memory_manager.py:188-206:208-250)——这就是"让模型看见它记得的东西"那一步。


6. 核心机制四:统一持久化 StorageHandler

长期记忆、RAG 索引、agent/workflow 的存档,底层都走同一个 StorageHandler (storages/base.py:15-24)。这一节讲它怎么把"三种后端"藏在一个门面后。

思路:一个门面,三个工厂。 init_module 依配置分别初始化 DB / 向量 / 图三种存储,各自用工厂 按名字创建(storages/base.py:27-75):

后端支持的实现配置来源
关系型 DBSQLite / PostgreSQLDBConfig(storages_config.py:8)
向量库FAISS / QdrantVectorStoreConfig(storages_config.py:18)
图库Neo4jGraphStoreConfig(storages_config.py:30)

读写按"表类型"分发。 数据分成五张逻辑表,由枚举 TableType 定义:agent/workflow/ memory/history/indexing(storages/schema.py:7-16)。StorageHandler 为每类提供 load_*/save_* 对(如 save_memoryload_index,storages/base.py:153-187:329-344), 内部统一走 "存在则 update、否则 insert" 的 upsert 逻辑。

一个易读细节:JSON 字段自动反序列化。 parse_result 遍历 Pydantic 模型字段,凡不是纯 str 的字段就尝试 json.loads(storages/base.py:133-151)——于是存进去的 dict/list 取出来还是 dict/list,调用方不用手动解析。


7. 核心机制五:RAG 检索引擎 RAGEngine

这一节讲"给 agent 配一个可搜索图书馆"的那台机器。长期记忆的索引/检索全靠它。

思路:一条五段管线,每段可插拔。 RAGEngine 在构造时按 RAGConfig 把五个工厂各装配一个实现 (rag/rag.py:25-93):

读取 Reader ─▶ 切块 Chunker ─▶ 嵌入 Embedding ─▶ 索引 Index ─▶ 检索 Retriever ─▶ 重排 Postprocessor
readers/ chunkers/ embeddings/ indexings/ retrievers/ postprocessors/

每段的策略都由 RAGConfig 的子配置决定(rag/rag_config.py:61-69):ChunkerConfig(块大小 1024 / 重叠 20)、EmbeddingConfig(默认 OpenAI text-embedding-ada-002)、IndexConfig (默认 VECTOR)、RetrievalConfig(top_k=5similarity_cutoff=0.7)。

两个核心动作:

add:把内容索引进去。(corpus_id, index_type) 惰性建索引和检索器,首次见到某个 corpus 才创建(rag/rag.py:158-206,RAGEngine.add)。给每个 node 打上 corpus_id/index_type 元数据 再插入。corpus_id 是"库分区"的概念——不同会话/不同来源可隔离检索。

query_async:检索 + 重排。 值得读它怎么并发检索多个索引(rag/rag.py:608-697, query_async):

# rag/rag.py:660-669 —— 把每个 (corpus, index) 的检索任务并发跑,再收集
retrieval_tasks = [task for task, _, _ in tasks]
retrieval_results = await asyncio.gather(*retrieval_tasks, return_exceptions=True)
for (_, cid, idx_type), result in zip(tasks, retrieval_results):
if isinstance(result, Exception):
logger.error(...) # 单个失败不拖垮整体
else:
results.append(result)

检索完再过一遍 postprocessor 重排(rag/rag.py:679-683),最后按 metadata_filters 做精确过滤 (rag/rag.py:685-691)。queryasyncio.run(query_async) 的同步壳(rag/rag.py:699-702)。

一处巧妙:维度自愈。 构造时若发现向量库配置的维度和实际嵌入模型维度不一致,会改配置并重建 向量库(rag/rag.py:71-75)——避免"1536 维索引塞 768 维向量"这类静默错误。


8. 核心机制六:人机协作 HITL(简述)

最后一层是"刹车":在工作流的危险动作前后插入人工闸门。这一节简述其拦截机制。

数据模型(hitl/hitl.py)。 三个枚举定义了"人能怎么回、在哪拦、拦来干嘛":

枚举取值含义
HITLDecisionapprove / reject / modify / continue人的决定(hitl/hitl.py:6-10)
HITLModepre_execution / post_execution在动作还是拦(hitl/hitl.py:19-21)
HITLInteractionTypeapprove_reject / collect_user_input / review_edit_state / review_tool_calls / multi_turn_conversation交互形态(hitl/hitl.py:12-17)

核心闸门 HITLManager(hitl/approval_manager.py:10)。 一个字段 active 是总开关。它的 request_approval 是所有审批的入口(approval_manager.py:42-106),精髓在默认放行:

# approval_manager.py:57-63 —— HITL 没激活时,一切自动通过,零侵入
if not self.active:
return HITLResponse(request_id="auto_approved",
decision=HITLDecision.APPROVE,
feedback="HITL not active, auto-approved")

激活后,它构造 HITLContext + HITLRequest,建一个 asyncio.Future 挂起,弹出 CLI 审批框等人 回应;超时(默认 1800s)则按 REJECT 处理(approval_manager.py:79-106)。

怎么挂进工作流(hitl/interceptor_agent.py)。 HITLInterceptorAgent 是一个特殊 agent,创建时 声明"我要盯住哪个目标 agent 的哪个 action、在 pre 还是 post 拦"(interceptor_agent.py:110-151)。 它内部的 HITLInterceptorAction.async_execute 就是去调 hitl_manager.request_approval (interceptor_agent.py:50-77)。使用时把 HITLManager 作为参数传给 WorkFlow (见 approval_manager.py:12-14 的类注释)——于是审批点被声明式地织进工作流, 而不是硬编码在业务逻辑里。

边界(诚实): 多轮对话交互 _handle_conversation 在源码里还是 TODO: implement (approval_manager.py:243-245),尚未实现。


9. 边界与局限(诚实)

  • 代码解释器不是真沙箱。 interpreter_python.pyexec() 全权限执行,allowed_imports 只做静态 import 白名单;白名单为空即全放行。隔离需求应上 interpreter_docker.py
  • 长期记忆的同步 API 靠 asyncio.run 包异步。 search/delete/update 内部多处 asyncio.run(...)(long_term_memory.py:168:200:262),在已有事件循环里调用会冲突。
  • HITL 多轮对话未实现(见 §8),request_approval 的交互目前主要走 CLI。
  • 记忆去重只按内容 SHA-256(long_term_memory.py:100),语义近似但字面不同的重复不会被识别。
  • RAG 默认走 OpenAI 嵌入(rag_config.py:34-35),离线/自托管需换 EmbeddingConfig.provider

10. 横向对比

  • 与本 shelf 的记忆型项目(如 Letta/MemGPT): EvoAgentX 的记忆是"短期 deque + 长期 向量库"的直白两层,LLM 门控是可选增强;它没有 MemGPT 那种"操作系统式分页/自我编辑上下文"的 中央循环——记忆在这里是被工作流取用的能力层,而非 agent 的主循环。
  • 与工具型框架: EvoAgentX 的差异化在 Tool.validate_attributes构造期 schema/签名校验 (tools/tool.py:52-92),把"说明书对不上代码"的错前移到类定义时,而不少框架是运行时才发现。
  • MCP 接入 让它能复用外部工具生态(mcp.py),与本章其它自研工具共用同一 Toolkit 契约。

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

主题文件路径关键符号
工具契约:schema + 签名校验evoagentx/tools/tool.pyToolvalidate_attributesget_tool_schema__init_subclass__
工具打包 / 命名空间evoagentx/tools/tool.pyToolkitget_toolget_tool_schemas
工具族总导出evoagentx/tools/__init__.py__all__
代表工具:Python 沙箱evoagentx/tools/interpreter_python.pyPythonInterpreter.execute_analyze_codePythonExecuteTool
代表工具:Docker 沙箱evoagentx/tools/interpreter_docker.pyDockerInterpreterToolkit
代表工具:搜索族evoagentx/tools/search_wiki.pysearch_base.pyWikipediaSearchToolkitSearchBase._scrape_page
代表工具:数据库族evoagentx/tools/database_*.pyMongoDBToolkitPostgreSQLToolkit
MCP 接入evoagentx/tools/mcp.pyMCPToolkitMCPClientMCPTool
短期记忆evoagentx/memory/memory.pyShortTermMemoryBaseMemory
长期记忆(CRUD + 去重)evoagentx/memory/long_term_memory.pyLongTermMemory.addsearch_async_create_memory_chunk
记忆管理器(LLM 门控)evoagentx/memory/memory_manager.pyMemoryManagerhandle_memory_prompt_llm_for_memory_operation
上下文管理(native/default 工具协议)evoagentx/memory/context_manager.pyContextManageradd_tool_results
统一持久化后端evoagentx/storages/base.pyStorageHandlersave_memoryload_indexparse_result
存储配置 / schemaevoagentx/storages/storages_config.pystorages/schema.pyStoreConfigTableTypeMemoryStore
RAG 引擎evoagentx/rag/rag.pyRAGEngine.addquery_async
RAG 配置evoagentx/rag/rag_config.pyRAGConfigChunkerConfigRetrievalConfig
HITL 数据模型evoagentx/hitl/hitl.pyHITLDecisionHITLModeHITLInteractionType
HITL 闸门evoagentx/hitl/approval_manager.pyHITLManager.request_approval
HITL 拦截器 agentevoagentx/hitl/interceptor_agent.pyHITLInterceptorAgentHITLInterceptorAction