Agent-Reach 是我这段时间在做 AI 智能体工程化的时候给自己造的中间层起的代号。拆开看Agent 是智能体Reach 是触达范围合起来就是一句话智能体能真正够到什么工具、读到什么数据、驱动什么系统。我把这层“够不到就白搭”的能力抽出来单独做了个基建专门处理模型和外部世界的交互。这篇内容不聊模型推理不堆提示词花活只讲怎么把 Agent 从聊天框里放出来让它能调接口、查数据库、发通知、操作内部系统同时还能管住权限、看得见日志。适合正在做智能体落地、做自动化流程、想把自己的工具接入大模型的人参考。1. Agent-Reach 到底要解决什么问题1.1 智能体的“手”和“脚”触达层现在很多团队一说做 AI Agent第一反应就是“用哪个大模型”。但真正跑过几个业务场景之后你会发现模型本身的可辨识度已经越来越低真正拉开效果差距的是模型之外的触达能力。一个 LLM 本质上是一个概率化的文本推断引擎它没有手也不能直接读取你的订单库、工单系统、IM 机器人。它要完成一次“查订单并通知用户”的任务中间必须有人帮它把动作接出去查询工具、通知接口、权限校验、结果回传这条链路就是 Agent-Reach 想做的触达层。我最早接触这个问题是在几个内部场景里。售后团队想把工单自动分类并回复但回复前要查订单状态、查物流轨迹还要把处理结果写回工单系统运营团队想要一个自然语言查数入口让业务人员直接问“本周退货率是多少”后台要翻译成 SQL 去查数仓还有一个小团队想让机器人能在钉钉群里自动发提醒。每个场景都是“模型一句话后面一坨系统”。如果每次都临时拼代码你会发现大量重复劳动而且很容易出权限漏洞。于是我把这层东西统一沉淀成了 Agent-Reach。1.2 没有触达层的智能体等于高级聊天机器人拿一个非常常见的例子来说。用户问 Agent“帮我退掉订单 SO-2025-0003把钱原路退回。”如果 Agent 只能回答那它顶多告诉你“请点击这里手动退款”这根本不算智能体只是套了一层皮的高级聊天机器人。有了触达层之后模型先生成动作意图调用refund_order这个工具参数是订单号触达层接收这个动作先校验参数格式再检查当前用户有没有退款权限接着发出退款请求然后把退款结果以结构化数据回传给模型模型根据结果做下一步解释。整个过程是一个闭环Agent 才能从“建议者”变成“执行者”。所以说Agent-Reach 解决的核心问题不是让模型更聪明而是让模型的动作能够安全、可靠、可追踪地落下去。它负责的是大模型与所有外部系统之间的“最后一公里”。这一公里没铺好模型再强也干不了实事这一公里铺好了哪怕模型版本换了你的业务能力照样能复用它过去积累的工具触达经验。1.3 什么人适合参考这个项目我把这次实践写成文字主要面向三类人。第一类是后端工程师你已经有一套系统现在想给系统加一个自然语言入口但不知道工具调用、权限校验、超时回填这些细节怎么设计。第二类是 AI 应用开发者你不想一上来就套一个很重的 Agent 框架想自己掌控从“模型决策”到“执行动作”之间的每一行代码方便出问题时能快速定位。第三类是内部效率工具负责人你想让文档、权限、审计都能管得住而不是临时起意上线一个“什么都能干”的 Agent。我自己在落地时的体会是不要把 Agent-Reach 想成一个庞大的平台它更像一层轻量级的“动作总线”。先把 3 到 5 个核心工具接进来跑通闭环再去慢慢加工具这个节奏最稳妥。2. Agent-Reach 的整体架构与设计思路2.1 四条设计原则做这层基建之前我给自己定了四条必须遵守的原则后面所有代码和配置都围绕这四条展开。第一条是工具优先。对模型来说工具描述就是它对世界的认知方式。同一个工具描述写清楚了模型调用就准描述写含糊了再强的模型也会乱点。所以 Agent-Reach 不能把工具当成随便填的函数而是要把工具定义当成第一等公民来设计。第二条是权限可见。不是所有工具都该让所有角色看到。普通用户不该看到“退款”工具运营人员不该看到“改数据库字段”的工具。更关键的是权限过滤要在执行器里做而不是只靠提示词约束。因为提示词是软的随时可能被绕过或遗忘代码层面的权限判断才是硬的。第三条是失败可控。工具调用一定会失败模型参数可能传错接口可能超时数据库可能没数据。Agent-Reach 要保证所有失败都以结构化结果返回给模型而不是让模型在日志里查异常。只有模型能理解失败原因它才可能采取正确的补救动作。第四条是可观测。Agent 是自主行动的它每调用一个工具动过什么参数、返回什么结果、花了多长时间都必须留有痕迹。出了问题不是先去问模型为什么而是先看触达层留下的调用日志。没有这套追踪能力Agent 根本不敢放上线。2.2 核心组件注册表、调度器、执行器、追踪器Agent-Reach 的逻辑被拆成了四个组件。注册表负责存放所有工具定义和实现。每个工具包含名称、描述、参数 JSON Schema、安全标签、超时时间、对应的处理函数。注册表还负责按角色返回“可见工具列表”这样模型拿到的工具集合本身就过滤过一轮权限出口前移。调度器负责接收模型返回的工具调用意图。模型说“我想调用 refund_order参数是 ...”调度器先找到这个工具验证它存在再做一次权限复核然后把调用转给执行器。调度器不写业务逻辑只做路由。执行器负责真正跑代码。它先用 Pydantic 校验参数再做超时控制捕获异常把结果统一包装成标准格式。所有工具的高风险操作都在这一步加护栏比如危险操作要求二次确认。追踪器负责记录。每次工具调用都生成一个 trace_id记录工具名、入参摘要、出参状态、耗时、token 开销最后写入日志系统或者链路追踪服务。权限拒绝也需要记录这是审计敏感动作的基础。这四个组件组合起来就形成了 Agent 和外部世界之间的“手”注册表决定能触达什么调度器决定这次触达合不合法执行器决定触达动作怎么跑追踪器决定触达过程能不能追溯。2.3 技术选型为什么是 Python FastAPI Pydantic技术选型上我最后定了 Python 3.11 FastAPI Pydantic v2。选 Python 没什么悬念Agent 生态和 AI 库基本都在 Python 这边工具函数用 Python 写最顺手方便团队里的算法工程师一起维护。FastAPI 本身不是必须的Agent-Reach 的核心并不绑定 HTTP。但 FastAPI 的异步支持、类型提示和依赖注入让后面接 API 网关、Webhook、消息队列都很顺。测试也很方便用httpx.AsyncClient可以直接测触达层接口。Pydantic v2 是参数校验的关键。每个工具的 JSON Schema 都可以转成 Pydantic 模型模型传进来的 JSON 参数会被严格校验类型不对、字段缺失、枚举值非法都能在调用业务函数之前被挡下来。错误信息还会被回填给模型让它有机会修正。至于为什么不直接用 LangChain 或 AutoGen 这类框架我的理由是它们抽象偏重出了问题要翻几层包装才能定位。个人项目和小团队项目我更想掌控工具注册、权限过滤、执行链路越直接越容易排查。Agent-Reach 只保留一个轻量模型适配层不同厂商的大模型接口通过统一的 ToolCall 协议接进来后续换模型几乎不动业务代码。2.4 一次 Agent 调用的完整路径把这四个组件串起来一次完整调用大概是这样的用户发来一句“帮我查一下订单 SO-2025-0003 到哪了”。Agent-Reach 先把工具定义列表和当前对话历史一起交给模型。模型读到query_order工具的描述后返回一个工具调用指令内容是query_order(order_idSO-2025-0003)。调度器收到后先确认这个工具存在再检查当前用户角色是否有order:read标签通过后交给执行器。执行器用 Pydantic 把传进来的参数做类型校验然后调用真正的订单查询函数拿到物流信息。结果被包装成{status: success, data: {...}}的结构返回给模型。模型看到结果后生成面向用户的回复“您的订单已从杭州发出预计明天下午送达。”如果查询失败比如订单号不存在执行器返回的结构化错误信息也会被交给模型模型可以据此说“查不到这个订单请您确认订单号”。整个过程通常会循环多轮。第一回合可能是查订单第二回合可能是查用户信息第三回合再生成最终回复。Agent-Reach 会设置最大轮次比如 6 轮防止模型在工具之间来回打转。3. 核心细节与实操要点工具描述与函数调用3.1 工具描述怎么写模型才不容易犯错一开始我总觉得模型靠“理解”工具后来踩了几次坑才发现模型其实是在“匹配描述”。工具描述写得越像它的调用场景它就越容易在正确答案附近命中。这里有三个最实用的经验。第一工具名称要语义充足。叫func1或者query_data对模型来说信息量太少改成get_user_order_status这种带业务语义的名称模型看一眼就知道该什么时候调用。第二描述要以动词开头把入参、出参、触发条件讲清楚。比如“根据订单编号查询订单当前状态和物流轨迹当用户询问订单状态时使用”。第三尽量避免在描述里写“不要用于查询价格”因为模型对否定句的吸收能力比较弱你越说“不要用”它反而越容易在边界场景里联想到这个工具。正确做法是给工具加一个“when to use”字段把适用条件写在正面。我建议的工具定义长这样from dataclasses import dataclass from typing import Callable dataclass class Tool: name: str description: str params_schema: dict handler: Callable tags: set[str] timeout: float 5.0 def schema_dict(self) - dict: return { type: function, function: { name: self.name, description: self.description, parameters: self.params_schema, }, }这个schema_dict就是最终发给模型的工具定义。模型不看我写的 Python 函数只看这段 JSON。所以描述里的每句话都是给模型看的“操作手册”。3.2 参数校验的边界与陷阱模型生成的参数不能直接信。它可能把日期传成字符串把数字传成数组甚至编造一个不存在的字段。所以执行器必须在调用业务函数之前做一次严格校验。我用 Pydantic v2 构建校验器把每个工具的params_schema转换成可执行的 validator。实际代码里不需要为每个工具手写模型可以用TypeAdapter动态构造。from pydantic import TypeAdapter, ValidationError def validate_args(schema: dict, args: dict): try: adapter TypeAdapter(dict) # 这里示意真实场景会把 schema 转成对应的 pydantic 模型 parsed adapter.validate_python(args) return parsed, None except ValidationError as e: return None, str(e)这里有个很容易踩的坑正则校验和格式约束不要设计得太死。比如订单号字段如果你要求必须以SO-开头模型传了个SO20250003校验失败后如果错误信息只说“格式不对”模型会自行猜测正确格式容易反复试错。更好的做法是在错误信息里附上标准示例“订单号应为 SO- 开头并带年份例如 SO-2025-0003”。模型读到这个格式信息下一轮就能自我修正。还有一类校验是语义校验比如查询的时间范围不能超过一年。这种规则不适合写在 JSON Schema 里应该放在工具函数内部由业务代码做二次判断再把语义错误包装成结构化结果返回。Agent-Reach 的原则是结构校验用 Pydantic 做语义校验留在函数内部做两层都要有。3.3 Agent 的动作循环与并行策略Agent 的核心是一个循环模型决策执行工具结果回填再让模型决策。这个循环我一般管它叫“动作循环”。代码骨架不复杂。async def run_agent(question: str, role: str, max_steps: int 6): messages [{role: user, content: question}] tools [t.schema_dict() for t in registry.visible_tools(role)] for step in range(max_steps): resp await llm.chat(messages, toolstools) messages.append(resp.message) if not resp.tool_calls: return resp.content # 并行执行无依赖的工具调用 results await asyncio.gather( *[executor.run(c.name, c.arguments, role) for c in resp.tool_calls] ) for call, result in zip(resp.tool_calls, results): messages.append({ role: tool, tool_call_id: call.id, content: json.dumps(result), }) return 已达到最大步骤数任务未完成请重试或联系人工。这里有一个策略问题什么时候该并行什么时候必须串行。asyncio.gather可以同时执行多个工具调用减少模型等待时间。但如果后一个工具的参数依赖前一个工具的结果就必须等前一个执行完后把结果回填给模型模型再决定如何调用下一个工具。比如“查订单 → 根据物流状态发通知”这个链路天然有依赖不能盲目并行。我的经验是保守起见第一版先做全部串行把可靠性跑通再针对耗时长、无依赖的工具做并行优化。等真正遇到性能瓶颈再动而不是一开始就并行否则请求链路会变得很难排查。3.4 工具结果回填的格式设计工具返回给模型的数据不是裸数据要统一包装。Agent-Reach 定义了两种结果结构。成功时返回{ status: success, data: { order_id: SO-2025-0003, status: shipped, tracking_no: SF1234567890 } }失败时返回{ status: error, error: { type: NotFound, message: 订单 SO-2025-0003 不存在请确认订单号 } }为什么不能直接把原始接口返回塞给模型因为外部系统返回的结构五花八门模型读起来成本高也容易被无关字段干扰。包装成统一结构后模型只需要关注status和data失败原因也足够明确下一步该做什么就有依据。另外回填给模型的数据要做敏感信息过滤。比如查询用户信息时内部用户 ID、手机号这些字段如果没有必要就不该出现在结果里。这不只是隐私问题也是降低模型上下文噪声。Agent-Reach 会在执行器里给每个工具配一个“返回字段白名单”只放行必要字段。4. 实操过程Agent-Reach 落地全流程4.1 准备工具清单与权限矩阵落地之前先别写代码先画一张工具权限表。我实际的例子是给一个客服场景接了四个工具。| 工具名 | 作用域 | 权限标签 | 超时(秒) | 安全级别 | 备注 | |---|---|---|---|---| | query_order | 订单查询 | order:read | 5 | 低 | 只读操作 | | refund_order | 订单退款 | order:write | 10 | 高 | 需人工确认 | | create_issue | 创建工单 | crm:write | 8 | 中 | 写操作但可追溯 | | send_message | 发送IM通知 | message:write | 5 | 中 | 需频率限制 |这张表有两个作用。第一它让你清楚知道每个工具能触达什么系统风险等级一目了然。第二它可以直接映射到注册表的tags字段角色只有带上对应标签调度器才会把工具放行。比如普通客服角色有order:read和crm:write但没有order:write所以模型从源头就看不到退款工具更不会触发退款。4.2 用注册表管住工具定义注册表的实现要支持“按角色返回可见工具”。有了这层过滤模型每次发起的调用都是可见集合里的省掉了后续大量的越权判断。class Registry: def __init__(self): self._tools {} def register(self, tool: Tool): if tool.name in self._tools: raise KeyError(f工具 {tool.name} 重复注册) self._tools[tool.name] tool def get(self, name: str) - Tool | None: return self._tools.get(name) def visible_tools(self, role: str) - list[Tool]: # 角色的标签集合决定可见工具 return [t for t in self._tools.values() if role in t.tags]注册的时候我会顺手做几道检查。参数 schema 必须是一个合法的 JSON Schema不能是空 dictdescription 必须以动词开头timeout 必须大于 0handler 必须可调用。这些检查虽然在项目初期看起来啰嗦但等到工具数量涨到几十个的时候能帮你提前发现不少低级错误。4.3 执行器校验、超时、异常统一处理执行器是整个触达层里最需要细节的模块。它要同时处理权限、参数校验、超时、异步转同步、异常包装。class Executor: def __init__(self, registry: Registry, tracerNone): self.registry registry self.tracer tracer async def run(self, tool_name: str, args: dict, role: str): tool self.registry.get(tool_name) if tool is None: return {status: error, error: {type: NotFound, message: f工具 {tool_name} 不存在}} if role not in tool.tags: return {status: error, error: {type: Forbidden, message: 当前角色无权调用该工具}} try: parsed self._validate(tool.params_schema, args) except ValidationError as e: return {status: error, error: {type: ValidationError, message: str(e)}} try: if asyncio.iscoroutinefunction(tool.handler): result await asyncio.wait_for(tool.handler(**parsed), timeouttool.timeout) else: result await asyncio.wait_for(asyncio.to_thread(tool.handler, **parsed), timeouttool.timeout) return {status: success, data: result} except asyncio.TimeoutError: return {status: error, error: {type: Timeout, message: f工具 {tool_name} 执行超时}} except Exception as e: return {status: error, error: {type: InternalError, message: f工具执行异常: {str(e)}}}这里有两个细节我特别想说。一是asyncio.to_thread用来执行同步函数避免阻塞事件循环。如果你的工具函数里有数据库查询、HTTP 请求一定不要直接在协程里同步调用否则整个 Agent 服务都会卡住。二是asyncio.wait_for包住调用保证超时一定会触发不能让一个卡死的接口拖住整轮对话。4.4 把 Agent 主循环跑起来主循环前面写过骨架真正落地时还要注意消息格式的一致性。不同模型的 tool call 返回格式不一样Agent-Reach 在模型适配层统一转成自己的ToolCalldataclassdataclass class ToolCall: id: str name: str arguments: dict模型接口适配层负责把厂商响应转换成这个标准格式。OpenAI 的function_call、Anthropic 的tool_use、国内模型的tools_calls都在适配层里归一化。后续主循环只认ToolCall不关心背后是哪个大模型。这样换模型的时候只需要写一个新的 adapter业务代码和触达层完全不动。主循环里我还会给每一步加一个小的 token 预算检查。比如一个任务最多消耗多少 token如果累计超额就中断循环并返回“任务处理成本过高请精简目标”。这个保护看起来不起眼但能防止模型在一个失败任务上来回重试烧掉大量费用。4.5 一个复合任务的完整走读拿客服场景举一个完整的例子。用户问“帮我查一下订单 SO-2025-0003 到哪了如果已经在派送就发个短信提醒收货。”第一轮模型看到工具列表里有query_order和send_message。它先调用query_order参数是{order_id: SO-2025-0003}。执行器校验通过后返回物流状态已到派送中包含快递单号。Agent-Reach 把这个结果回填给模型。第二轮模型看到如果订单在派送中可以调用send_message发送提醒。它调用send_message参数是手机号和通知文案。执行器校验后调短信接口返回发送成功。第三轮模型生成最终回复“您的订单正在派送中预计今天下午送达提醒短信已发送。”如果第一轮查询返回错误比如订单不存在模型会直接生成回复“查不到这个订单请核对订单号”而不会继续调用send_message。这就是动作循环的自我纠偏能力也是触达层存在的价值不是让模型每一步都对而是让它能在错误结果上做出合理下一步。4.6 可观测性给每次触达留痕我强烈建议你在第一天就加上追踪器而不是等项目跑大了再补。Agent-Reach 的追踪器记录的是事件字段{ trace_id: tr_abc123, agent_id: support_bot, role: customer_service, step: 2, tool_name: query_order, args_preview: SO-2025-0003, status: success, latency_ms: 128, token_cost: 342 }这些日志可以直接打到 stdout也可以接到 OpenTelemetry按需往消息队列里丢。权限拒绝也要记录成日志比如Forbidden: rolecustomer_service, toolrefund_order这是安全审计的重要依据。实际排查问题时只要顺着trace_id一搜整个 Agent 从提问到最终结果的每一步都能对上效率比黑盒调试高得多。5. 常见问题与排查技巧实录5.1 高频问题速查表先放一张速查表把我在实际运行中遇到最多的问题列出来方便你直接对照。现象可能原因快速排查建议方案模型传了不存在的参数工具描述不清晰参数命名模糊查看工具 schema 描述简化参数只保留必要字段明明该调 A 工具却调了 B两个工具描述相似或边界场景含糊对比工具描述差异增加 when to use 字段模型反复调用同一个失败工具错误回填信息不充分查看回填的 error 内容在错误信息中给出正确示例上下文被工具结果撑爆工具返回了长列表、大文本查看日志中 token 消耗截断结果、返回摘要、限制条数工具调用超时但模型一直等超时设置过长或接口卡死查看 latency_ms缩短 timeout失败回填权限拒绝后模型反复尝试工具虽然不可见但模型猜到了名称查看日志中的 Forbidden将权限过滤前移到工具列表5.2 模型编造参数怎么办我最常遇到的一个问题是模型会编造一个工具 schema 里根本不存在的字段。比如定义的params_schema只有order_id模型非要传一个user_id。这不是模型笨而是它从对话历史里看到了用户 ID 信息自然联想到工具可能也需要。解决方案是在错误回填里明确列出当前工具支持的字段列表。错误返回可以写成{ status: error, error: { type: ValidationError, message: 参数 user_id 不在支持范围内query_order 只支持 order_id, support_fields: [order_id] } }模型读到support_fields后下一轮基本都会修正。还有一个更治本的办法把工具参数个数控制在 3 个以内。参数越多模型出错概率越高。能通过上下文隐式传递的信息就不要放进参数里。5.3 工具选错模型该用 A 却用了 B工具一多选错就很难避免。比如有两个工具一个叫get_user_order_status一个叫get_user_refund_status描述都很像。模型可能因为“退货”这个词把退款状态查询当成了普通订单查询。解决思路有两个方向。一是调整描述在when to use里写得再精确一点比如“当用户的问题涉及退款、退货进度时使用”。二是推荐做两层工具调度先让模型选对“工具组”再在组内选择具体工具而不是把几十个工具一股脑丢给模型。这种分层路由对超过 20 个工具的场景尤其有用。Agent-Reach 的注册表可以加一个group字段第一阶段的调度先决定进入哪个组第二阶段再具体到工具。5.4 上下文被工具结果撑爆工具调用结果会被回填进对话历史下一次模型请求又要把这些历史全部带上去。如果工具返回了一个几百行的列表第一轮没问题第二轮上下文直接翻倍几轮之后就可能爆窗口。我在 Agent-Reach 里做了三类保护。第一工具返回时只返回摘要和关键计数比如“查询到 128 条记录仅展示前 5 条”。第二如果工具本身支持分页就允许模型传页码参数而不是一次拉全量。第三执行器在回填前做truncate超过 1000 字符的部分直接截断并告诉模型“结果已截断如需详情请缩小查询范围”。这些保护逻辑要写在触达层而不是依赖提示词。5.5 权限与合规不能把问责交给模型这是整个项目里我最强调的一点模型是概率系统它的判断不能当作授权依据。Agent-Reach 在权限设计上有三层护栏。第一层工具列表按角色动态生成。普通角色看不到危险工具模型压根不会发起这类调用。第二层执行器里再做一次权限校验即使模型通过某种方式知道了工具名调用也会被拦截。第三层对refund_order这种高风险动作设置强制人工确认。执行器不会直接执行而是返回结果{status: need_approval, approval_url: ...}由用户在审批页面确认后才真正调用退款接口。这层设计可能让一些人觉得“不够智能”但实际经验是高风险操作一旦出事成本远高于那一点点便利。Agent 适合做“建议者”和“执行者”但授权官还是得人来当。5.6 超时、重试与不同模型接口的兼容超时和重试是触达层最容易写错的地方。我的建议是只对幂等操作自动重试。查询类、状态类操作重试两次没问题创建、退款、发消息这类非幂等操作坚决不自动重试最多把超时错误返回给模型让模型判断是否换个思路。asyncio.wait_for是必选项默认超时可以设成 10 秒具体按工具风险等级调整。不同模型接口的兼容性也是绕不开的。OpenAI 的 function calling 返回tool_callsAnthropic 返回tool_useblock一些国产模型返回的字段又不一样。Agent-Reach 的做法是定义标准ToolCall为每个模型写一个薄薄的适配器把厂商响应转换成统一结构。适配器里需要特别注意消息格式把工具结果回填给模型时不同模型的字段名也不同统一封装能减少大量重复代码。6. Agent-Reach 还能往哪里延伸6.1 多 Agent 协作场景把 Agent-Reach 用得再深一点就可以做多 Agent 协作。每个 Agent 是独立的对话上下文但它们共享同一个注册表只是各自的工具可见范围不同。比如客服 Agent 能看到订单查询、工单创建数据分析 Agent 能看到数据仓库查询、报表生成。两个 Agent 之间通过消息总线传递任务A 查完之后把订单号传给 B 做数据分析。触达层在这里的价值是工具只有一份权限各自隔离任务交接清晰。6.2 从工具调用扩展到业务流程编排工具调用只是触达层的第一层。再往上还可以把触达层嵌入到业务流程引擎里。比如和 Camunda、Temporal 这些流程引擎配合Agent 变成一个决策节点它根据用户输入判断下一步走哪个分支随后触达层负责执行分支里的具体步骤。这种做法的好处是流程的状态、超时、重试、补偿都由流程引擎管理Agent 不用自己维护一套状态机。触达层只专注“动作能不能执行”流程引擎负责“这个动作该不该按这个顺序执行”。6.3 轻量级触达层往端上走Agent-Reach 的架构目前主要在服务器端但稍微改一改也能往端上走。比如把注册表换成 SQLite 版本把工具校验和超时逻辑塞进移动端 SDK让手机上的 Agent 能触达本机日历、短信、文件系统。这种场景更强调权限隔离因为端上的数据更敏感。不过核心的“注册、校验、执行留痕”思路是不变的只是把运行环境从服务器挪到了终端。最后说一点我自己的体会。Agent-Reach 这个项目给我最大的收获不是“模型又变强了”而是“工具触达才是工程成本的大头”。一次 Agent 调用的背后要考虑工具描述怎么写、参数怎么校验、权限怎么过滤、超时怎么办、日志怎么追踪。把这些做好模型能力反而变成了相对可控的一环。如果你的项目也在做智能体接入建议先从最小的 3 个工具开始把触达层跑通再去扩数量。宁可工具少而稳不可多而乱。这是我在一次次踩坑之后最深的感受。
阅读完成 · 觉得有帮助?