1. 项目缘起:为什么要做 Agent-Reach 这个代理触达框架
做 AI Agent 开发的人可能都有一个共同的感受:单个大模型能力再强,落不到实际业务场景里面都是白搭。真正让 Agent 变得有用的,是它能够触达外部的系统、工具和数据源,完成一次真实的业务操作。我这里说的"触达",可不是简单调一个 API 就算完事,而是需要经过任务拆解、参数校验、工具选择、结果回传、失败重试这一整套流程。
Agent-Reach 这个名字,核心就是"让 Agent 够得着它该够得着的东西"。整个项目解决的问题非常具体:我在做企业内部的知识库问答机器人和多步骤数据分析助手时,发现不管用 ReAct 模式还是 Plan-and-Execute 模式,Agent 在调用外部工具(比如搜索 Elasticsearch、查 MySQL、调内部审批接口)的时候,总会出现参数传错、工具选错、中途崩了不知道怎么办这些毛病。市面上的 LangChain 确实强大,但对中小团队来说太重了,依赖一堆、抽象绕来绕去,出了问题排查成本太高。于是我用一个周末搞了个轻量级的代理触达框架,后来逐步完善成 Agent-Reach 这个项目。
这个框架适合谁?我觉得三类人可以从中找到共鸣:一是正在裸调大模型 API、发现 Token 消耗高得离谱的开发者,Agent-Reach 的重试和校验机制能省不少无效 Token;二是接了 LangChain 但被抽象层折腾得头大的团队,这套框架的思路更透明,代码量小,能快速改出自己想要的行为;三是想自己动手撸一个 Agent 骨架、学习 Agent 内部机制的人,整个项目结构非常集中,看完基本就能理解 Agent 干活的全流程。
我不会说它是万能的,毕竟每个团队的业务场景不一样,但如果你想解决的是"如何让 Agent 稳定且可控地调用我们自己的工具",这篇文章里的设计思路和踩坑记录应该能给你不少参考。
2. 核心设计思路:Agent 触达能力的三个圈层
2.1 统一工具协议层:先把"手"长规范了
开发 Agent 最常犯的错,是让模型直接去猜函数的参数。一开始我也这么干,给模型塞了一大段自然语言描述说"这个函数接收 start_time 和 end_time,格式是 ISO 8601",结果模型经常传成时间戳,或者把时间范围为写成单个时间点。与其让模型靠理解去拼参数,不如把每个工具的定义做成严格的结构化 Schema,让模型在几个选项里选参数,而不是凭空生成。
Agent-Reach 里,我把每个外部能力都封装成一个统一协议的工具描述,底层依赖 JSON Schema。举个例子,一个查订单数据服务的工具,它对外暴露的 Schema 长这样:
{ "name": "query_order", "description": "根据日期范围和订单状态查询订单数据", "parameters": { "type": "object", "properties": { "start_date": {"type": "string", "format": "date", "description": "起始日期,格式 YYYY-MM-DD"}, "end_date": {"type": "string", "format": "date", "description": "结束日期,格式 YYYY-MM-DD"}, "status": {"type": "string", "enum": ["pending", "paid", "shipped", "cancelled"], "description": "订单状态"} }, "required": ["start_date", "end_date"] } }之前我觉得写这种描述是浪费时间,后来发现这恰恰是 Agent 能稳定工作的基石。模型虽然笨,但只要你给它的选择足够明确——枚举值、格式、必填项都写清楚,它的正确率能提升到 95% 左右。为什么?因为这让模型的任务从"回忆并生成正确的参数"变成了"从给定约束中选择符合条件的值",后者的难度低得多。
这一层还顺手解决了另外一个问题:不同的工具可能有不同风格的参数定义,统一协议之外,外部系统的适配器可以各写各的,互不干扰。现在这个项目里已经适配了 PostgreSQL、Elasticsearch、HTTP 接口、本地文件系统四类数据源,每类就是一个独立目录,新增一个数据源只需要实现一个invoke()方法加一个 Schema,十几分钟能搞定一个新的能力入口。
2.2 规划执行分离层:把"做什么"和"怎么做"分开管
最早的 Agent-Reach 只有一个循环:模型收到任务,直接尝试调用工具。后来发现,一个复杂任务比如"把上个月的订单汇总按渠道分析并生成图表",让模型自己一口气干完,大概率干到一半就上下文混乱了,要不就是中间少算了一个渠道。
于是我参考了 Plan-and-Execute 的思想,但做了一个更务实的调整:规划器和执行器用同一个模型,但分成两个独立的阶段。模型先看到任务是"分析订单并生成图表"时,它的第一轮输出不是直接调工具,而是一个包含步骤的 Plan。这个 Plan 会被我压缩成一串精简摘要留在上下文里,真正的执行过程则是逐工具调用的。
阶段隔离的好处是上下文干净了。我实测了一个项目:一个需要连续查询四次数据库、第三次查询依赖前两次结果的复杂任务,在传统 ReAct 模式下,每轮都要重新读入全部历史,Token 消耗至少 6000 多,而在规划执行分离的模式下,中间摘要只占了不足 1500 Token,最终结果还更稳定。Token 费用的节省是次要的,关键是有时候模型在超长上下文里会"忘掉"自己最初的目标,做成一半就把任务改了,规划器把这个风险也一并规避了。
class SimplePlanner: """一个实用主义的规划器:拆步骤,不写花哨的 Prompt""" def build_plan(self, task: str, tool_schemas: list) -> list[str]: plan_prompt = f"请把下面的任务拆成不超过5个步骤,每步只做一件事,最后一步必须是一个明确的输出动作。\n任务:{task}\n可用工具:{tool_schemas}" ...拆步骤的时候有个小门道:不是每个任务都值得拆。单步任务比如"查一下订单数为 100 的客户有哪些",要是拆成三步反而增加延迟。实际实现里我加了一个判断,如果工具只有一到两个且任务描述里没有嵌套关系,直接跳过规划阶段,进入执行阶段。这个优化能让简单场景的响应速度提升 40%。
2.3 触达结果回传层:最终产出要回到业务手上
框架跑完一个 Agent 任务之后,原始的返回值往往没法直接用,可能是 Elasticsearch 的 JSON 聚合结果,可能是 SQL 查询后的表格数据。Agent-Reach 做了一个统一的结果标准化,把不同来源的数据最终都转成两类格式:结构化表格数据(用于统计分析、图表展示)或自然语言摘要(用于汇报、通知)。
这块是我个人很骄傲的设计,因为它把"Agent 干完活"和"业务方拿到结果"之间无缝对接了。你一分钟后可以调用AgentReachResult拿到一个 DataFrame,也可以直接调用.summary()得到一段类似人话的结论汇报。实际上多数企业里,Agent 干活的终点不是输出一句"我完成了",而是要接上下游系统,比如发送企业微信通知、填入 BI 报表系统。结果标准化这一步,等于给下游少造了很多适配轮子。
3. 从零搭建一个 Agent-Reach 实例:关键环节拆解
3.1 环境准备和项目结构
我用三个月时间把 Agent-Reach 打磨成一个可运行的小型框架。建议你用 Python 3.11,因为新版里tomllib直接可用,配置文件解析方便不少。依赖方面我只用了三个必须的包:openai(或者你对接任何 OpenAI 兼容接口的 SDK)、pydantic用来做工具参数校验、pandas用来处理结果表格。相比动不动就拖几十个传递依赖的框架,这个干净程度算很理想了。
项目目录被我压到这个程度,方便维护:
agent_reach/ ├── core/ # 核心循环:规划、执行、校验、重试 ├── tools/ # 各类工具适配器 │ ├── postgres_tool.py │ ├── es_tool.py │ └── http_tool.py ├── memory/ # 轻量级会话记忆 ├── schemas/ # JSON Schema 定义 └── server.py # 提供阻塞式 API 调用入口我特意没引入异步框架和消息队列,因为多数 Agent 任务是同步的,引入异步只会增加心智负担。等你跑通了核心逻辑,再考虑并发调度也不迟。
环境里需要配置好模型 API 的密钥,我一般用一个.env文件加载:
MODEL_API_KEY=sk-xxxx MODEL_BASE_URL=https://your-api-endpoint MODEL_NAME=gpt-4o-mini3.2 定义第一个触达工具:连接本地文件
我们先用最简单的文件读取来走通整个流程。我需要一个工具,它能读取一个 CSV 文件并返回其内容摘要。定义 Schema 时,我故意把path描述得特别明确,这样模型不会传错路径:
@register_tool class CsvReaderTool: schema = { "name": "read_csv", "description": "读取本地 CSV 文件,返回文件名、行列数和前5行数据摘要", "parameters": { "type": "object", "properties": { "path": {"type": "string", "description": "CSV 文件的完整本地路径,例如 /data/sales.csv"} }, "required": ["path"] } } def invoke(self, path: str) -> dict: import pandas as pd df = pd.read_csv(path) return { "filename": path, "shape": list(df.shape), "head": df.head(5).to_dict(orient="records") }这里的关键是@register_tool装饰器,它会在程序启动时自动扫描所有工具,收集其 Schema 存入一个全局注册表中。执行时,模型能看到的工具列表就是从这个注册表生成的,不会多也不会漏。
你可能会说,read_csv 这么简单的工具模型自己可能就会写代码模拟,为什么要注册成工具?此言差矣,注册成工具后,模型不用去猜 CSV 里有几列、列名是什么,直接通过工具调用拿回精确结果,效率和准确率都高很多。这也体现了 Agent 工具设计的基本原则:把不确定的操作交给真实执行系统,模型只做决策,不做猜测。
3.3 核心循环:规划-执行-校验-重试
Agent 的主循环我保持在五十行以内,因为逻辑越简单越不容易出问题。核心流程是:
- 收到用户任务后,用规划器生成执行步骤(如果需要规划);
- 每步执行时,把步骤描述、可用工具 Schema 和上一轮的执行结果一并交给模型,让模型返回一个工具调用请求;
- 用 pydantic 按 Schema 严格校验模型输出的参数,类型不对就重新要求模型生成,绝不宽容;
- 调用真实工具,拿到原始结果;
- 把结果转成结构化的
AgentResult,并填写回上下文用于下一轮决策; - 如果整个计划步骤完成了,最终调一次模型生成自然语言总结。
def run(task: str, max_steps: int = 8) -> AgentResult: context = [{"role": "user", "content": task}] steps = planner.build_plan(task, registry.schemas()) for step in steps: response = llm_call(context, step, registry.schemas()) action = parse_action(response) validated = validate_params(action.arguments, registry.schema_of(action.name)) if validated.error: context.append({"role": "assistant", "content": f"参数错误:{validated.error_msg},请重新提供参数"}) continue raw_result = registry.invoke(action.name, validated.data) context.append({"role": "tool", "tool_name": action.name, "content": format_raw(raw_result)}) final = llm_call(context, "请基于以上所有工具的返回结果,给出最终回答。") return AgentResult(conclusion=final, raw_steps=context)max_steps这个参数特别要关注。如果模型陷在"调用工具-发现错误-重新调用"的循环里,没有上限会烧掉大量 Token。我设了 8 步的上限,实测对大多数多步骤任务足够了。超过上限后的兜底动作也很直白:停止执行,返回"任务超过最大步骤数,请缩小问题范围"。
校验这步我吃过亏。一开始我不校验模型的输出,直接拿它给的参数去调数据库,结果有一次模型把end_date传成了中文日期格式,数据库直接报错。后来加上了基于pydantic的严格校验,格式不对当场打回重试。这里要注意一个细节:打回重试时,要把错误原因写得很明确,比如"参数 start_date 格式错误,应为 YYYY-MM-DD,你提供的是 4月1日",模型一般下一次就能修正。
3.4 上下文记忆管理:别让 Agent"失忆"
多步骤任务里,工具调用结果会不断填充上下文。如果不加控制,上下文会膨胀。我在 memory 模块里用了最简单的策略:只保留最近三轮工具结果,更早的历史结果会被压缩成一句摘要,比如"已完成:查询了4月订单总数,返回12345条"。
def compress_old_steps(context: list, keep_latest: int = 3) -> list: tool_calls = [msg for msg in context if msg["role"] == "tool"] if len(tool_calls) > keep_latest: dropped = tool_calls[:-keep_latest] summary = summarize_calls(dropped) context = [{"role": "system", "content": f"旧的执行记录已经压缩,摘要:{summary}"}] + tool_calls[-keep_latest:] + [msg for msg in context if msg["role"] not in ("tool", "system")] return context这个方法说实话挺粗暴,但胜在便宜有效。压缩摘要的生成成本远低于保留完整工具返回值的成本。在很多问答场景里,模型只需要知道"前面查过什么,结论是什么",不需要每一条原始数据都在脑子里轮播一遍。
如果你要处理的场景是记忆高度敏感的(比如多轮对话里用户改口了需求),那我觉得可以顺序记录用户的最近五条消息,然后单独走一次摘要生成。我还没做这种复杂记忆,但 Agent-Reach 预留了memory插槽,你可以替换成任意向量库,看你的需求。
4. 实战记录:用 Agent-Reach 做一个销售数据汇总智能体
4.1 场景设定与工具准备
光讲框架太抽象,我拿一套实际场景来演示。假设公司内部要求:销售运营每天上午要给负责人发一条销售汇总,内容包括昨日订单量、销售额、各渠道占比,以及异常波动的提醒。
用 Agent-Reach 实现这个场景,我需要准备好两个工具:
- PostgreSQL 查询工具:执行 SQL 并返回结果;
- 企业微信机器人发送工具:给指定群发文本消息。
PostgreSQL 工具的 Schema 比 read_csv 复杂一丢丢,关键在于query参数的编写约束。我不会让模型自由写复杂 SQL,而是让它在预置的若干合法查询模板里选择,并填入参数。这能避免模型捣鼓出笛卡尔积之类的大坑:
@register_tool class PostgresQueryTool: schema = { "name": "query_sales", "description": "执行一个预置的销售统计SQL查询", "parameters": { "type": "object", "properties": { "query_id": {"type": "string", "enum": ["daily_summary", "channel_breakdown", "trend_anomaly"], "description": "预置查询的标识符"}, "date": {"type": "string", "format": "date", "description": "统计日期 YYYY-MM-DD"} }, "required": ["query_id", "date"] } } ...为什么不用自然语言 SQL?因为我踩过模型生成 SQL 带语法错误的坑。预置模板的方式虽然限制了模型灵活性,但换来的是正确率接近 99%。在内部工具里,稳定压倒一切。你如果非要让模型自由写 SQL,请务必在你自己的 SQL 解析层做白名单限制。
发消息工具几乎不需要参数灵活性,只需要message和group_id两个字段。真正的消息内容我建议让 Agent 自己组织语言,因为模型生成自然语言的功底比模板拼接强太多了。
4.2 完整任务执行链路与 Token 消耗
请求是一次模拟:"请生成昨天(2024-06-19)的销售日报并发送到销售管理群。"
Agent-Reach 的处理链路是这样的:
- 第一步,规划器根据任务和可用工具,生成四步计划:查日汇总 -> 查渠道占比 -> 检查是否有异常 -> 发送日报到群;
- 第二步,模型调用
query_sales(query_id="daily_summary", date="2024-06-19"),返回订单总数、总销售额等字段; - 第三步,模型看到日汇总后再调用
query_sales(query_id="channel_breakdown", date="2024-06-19"),这一步是嵌套依赖的:只有先知道昨天的销售规模,模型才知道需不需要深挖渠道维度; - 第四步,调用
query_sales(query_id="trend_anomaly", date="2024-06-19"),结果发现销售额比前一日下降 12%; - 第五步,模型综合三步结果,生成一段人类可读的日报文本;
- 第六步,调用
send_wecom_message把日报发送到指定群。
我记录了一次完整的实际运行日志,Token 消耗明细大概是:规划阶段 800,前面三步决策消耗 2600,最终总结生成 1500,加上压缩后的历史摘要大约 2000 多,总共不到 7000 Token。相比让模型一次性大乱炖地进行自由 ReAct 循环,这个方案的 Token 命中效率高了不少,而且每一轮调用的目的都高度可控。
有个细节值得记录:上下文压缩发生在第三步和第四步之间。因为第一步查出来的原始日汇总数据占了不少 Token,等到要生成渠道占比的时候,旧数据只保留一个摘要"已获取 6 月 19 日订单量 4216 单,总销售额 83.2 万元"就足够了。这样后面模型在写日报的时候,不用担心上下文里原始数据太多导致注意力分散。
4.3 执行中途模型"飘了"怎么办:纠偏与兜底
实测中最大的一次意外是:模型在执行完第三步后,自己脑补了一个"趋势对比"目标,想要再查一次上周同期的数据,但工具列表里根本没有这个查询模板。正常情况下,模型应该老老实实继续后续步骤,但它偏不。
Agent-Reach 的应对很简单——不允许调用不在注册列表里的工具。模型即使输出一个陌生的函数名,解析器也会直接拒绝并提示它只能在注册表中选择。加上步骤上限的兜底,这次"飘了"的模型在被拒绝一次后,很快回到了正轨。
另一个兜底动作是步骤完成度检查。模型完成所有计划步骤后,如果某个步骤没说成功,我不让它进最终总结阶段。比如发送企业微信消息失败或者超时,链路会重试一次,第二次还失败就直接终止并把错误原样抛出,不生成误导性的"任务已完成"。
这个设计是吸取了真实事故的教训。有一次,调用一个内部 HTTP 工具超时了,但 Agent 居然自己脑补说"消息已发送成功,请查收"。排查了半天才发现是模型在结果不可用的时候"编"了一个成功结果。现在我在结果回传层里强制要求:工具的真实返回状态必须是成功,Agent 才能基于它做总结;如果工具层出错,Agent 只有两个选择:重试或者上报失败。
5. 工具选型与调优细节:Agent 能否真正落地的关键
5.1 模型选择对触达成功率的影响
Agent-Reach 目前我主要测试了两类模型:通用旗舰模型和轻量模型。结论很有意思:触达成功率这个指标上,轻量模型和旗舰模型的差距没有想象中那么大——前提是你把工具 Schema 写得足够细致。
我用一个包含 12 个工具、总参数字段 38 个的系统做了对照,轻量模型参数错误率大约 6%,旗舰模型大约 2%。在加了 pydantic 强校验和错误打回之后,两者的最终执行成功率都提升到了 97% 以上。区别体现在另一个维度:复杂任务的理解能力。当任务的步骤数超过 5 个时,轻量模型偶尔会在中途丢掉某个步骤,而旗舰模型基本能跟完。所以我的建议是,步骤不超过 3 的简单任务用轻量模型,响应快成本低;多步骤长链路任务用旗舰模型兜底。
你可能会纠结模型 API 的稳定性。我在 server 层做了最简单的重试策略:网络抖动导致的请求失败,指数退避重试三次,间隔分别为 1 秒、2 秒、4 秒。这个后端的策略在实际运行中帮了大忙,毕竟工具触达链路里,模型 API 的网络波动是最大变量之一。
5.2 参数校验:宁可慢一点,不可错一次
校验层是我反复打磨过的地方。除了格式校验,我还加了一层业务语义校验。比如日期参数,不能只是合法的 YYYY-MM-DD 格式,还不能晚于今天的日期。这个语义校验写在各个工具适配器内部,不由核心层统一处理。理由很简单:不同工具的业务约束千差万别,核心层不应该变成一个大杂烩。
语义校验与格式校验分开还有个好处:错误提示能更精准。格式错误统一返回给模型,让它重试;语义错误则直接拦截,因为这种错误模型一般自己也能修正,但如果次数太多,我会在上下文里附上一句"你多次传了无效参数,请仔细阅读工具 Schema 中的描述",给模型一个强提示。
现在这个校验模块的测试用例我已经攒了 80 多个,覆盖了各种刁钻的传参方式:传入对象当字符串、少传必填项、枚举值大小写错、日期格式混用。每次改代码我都会把测试用例重跑一遍,这个习惯省了无数救火时间。
5.3 结构化输出与可观测性:别让 Agent 变成黑盒
Agent 系统最忌讳的就是黑盒:你问它"刚才为什么调用了这个工具",它答不上来。Agent-Reach 在每次工具调用前后都会打日志,内容包括:模型决策的原生输出、工具名称、参数、结果状态、耗时。日志我统一转成 JSON 格式,方便后续导入 ClickHouse 或 ELK 做分析。
{ "ts": "2024-06-19T11:23:45", "step": 2, "tool": "query_sales", "params": {"query_id": "channel_breakdown", "date": "2024-06-19"}, "status": "success", "latency_ms": 340, "token_usage": 420 }这些日志不仅是排查问题依据,也是我做成本分析的数据源。通过一段时间的日志聚合,我能统计出每个 Agent 任务的均摊 Token 成本、每类工具被调用的频率、哪些工具的失败率最高。这是持续调优的基础,没有日志,优化就是空谈。
我还做了一个轻量可视化面板,但只是用streamlit快速搭的,主要展示近 24 小时的触达成功率、工具调用分布、平均耗时三个指标。对内部使用足够,不追求花哨。
6. 实操中最常见的五个坑与对应的排查方法
6.1 模型不按 Schema 输出:该不该生气?
经常遇到模型明明看到了 Schema 和工具描述,却仍然返回一个不存在的参数名。早期我以为是模型理解能力问题,后来发现多数原因是 Prompt 里工具描述和系统指令的描述顺序不对,或者工具数量超过十个后模型开始"混淆"。解决办法是每次只给模型暴露与当前步骤最可能相关的 3-5 个工具的 Schema,而不是一次性把所有 12 个工具全部塞进去。这个"工具按需暴露"的策略,直接把参数错误率降了一半以上。
6.2 工具返回了超长结果:上下文爆了
查询数据库返回一千行结果,模型拿到这些原始数据既不能全记住也没必要全读。我在每个工具适配器里都内置了一个summarize_before_return开关,默认打开。当返回结果超过大约 1500 字符时,会自动用摘要模型先生成一段精简结论,再把这段结论填入上下文,原始长数据可以落盘存档。这保证了多步骤任务中每步的信息增量不至于撑爆上下文窗口。
6.3 重试循环太频繁:把 Agent 逼疯
我发现重试逻辑写不好,Agent 会像一个偏执狂一样反复用同一组错误参数调用工具。后来我在核心循环里加了一个"连续错误次数"计数器:同一工具连续失败三次,就不再允许该工具进入本轮后续的决策,转而要求 Agent 换一个思路或者直接向用户请求澄清。这个机制显著提升了跑路率——这里说的"跑路"是指 Agent 被迫诚实承认"我搞不定",而不是硬编一个结果。
6.4 结果格式化不一致:下游解析困难
Elasticsearch 的聚合结果和 SQL 查询结果格式差异巨大,下游如果做统一分析会头疼。Agent-Reach 在结果回传层做了标准化:统一转成columns + rows的表格结构。如果你拿到的工具结果是嵌套 JSON,我也会在适配器里先摊平成表格。这个标准化处理的价值,等你要把 Agent 的结果接入 BI 工具或自动化报表时候才会真正感受到。
6.5 任务步骤顺序被模型打乱:规划器被无视
有时候规划器明明给出了顺序,但执行器模型会自作主张调整步骤顺序(比如先发消息再查数据)。我的处理是,在每轮模型调用前,把"当前要执行的步骤"单独放在一条 system 级别的 prompt 里,并明确写着"请只执行这一步骤,不需要提前执行将来的步骤"。这样约束下来,模型就不会越权乱序了。同时也保留一个例外机制:如果模型能明确解释调整步骤顺序的原因(比如"先获取全部数据再统一分析"更合理),并且不超过步骤上限,执行器允许微调。这个灵活性一开始没有,是后面加的,因为有些任务的步骤之间确实没有强依赖关系,太死板反而影响效率。
7. 当前局限与后续演进方向
Agent-Reach 目前在单机同步场景下跑得很稳,但它肯定称不上一个生产级的高并发 Agent 平台。在持续压测中我发现,如果一个进程同时接到几十个任务,排队就意味着响应时间直线上升。后续如果要扩展成服务化,需要引入两样东西:一个是任务队列,把 Agent 任务拆成"可异步执行"的单元;一个是分布式会话存储,替换掉现有的内存级记忆管理。
另外,现在的规划器还是比较朴素的 Prompt 式拆分。我在考虑要不要引入一个专门的小模型做最终校验,针对复杂任务的规划结果进行"步骤依赖检查"。这个想法目前还是实验性的,因为训练数据和评估指标都还不成熟,贸然上可能适得其反。
从使用角度看,Agent-Reach 的定位是"启发式参考实现"——它不是给你一个开箱即用、包治百病的框架,而是给你一套触达工具调用链路上可以复用的完整思路。你可以把它当骨架,根据自己业务场景填充血肉,这在 AI Agent 技术仍然快速演进的当下,可能是更务实的选择。
最后分享一个我个人的体会:在做 Agent 项目的时候,别一开始就想着把所有模型能力都用上、把框架做得大而全,最优先的目标是让链条跑通——尤其是让 Agent 能稳定地触达一个外部工具并把结果带回。当这个最小闭环成立后,后续的每一层优化(记忆、规划、并发)才有意义。这套 Agent-Reach 实际上就是沿着这个思路,从一个"工具调用示例"长成了一个"轻量触达框架",项目再往后怎么走,我目前的想法是继续保持轻盈和透明,有余力的话再打磨一下规划器的自适应能力,让框架在复杂任务上的表现更进一步。