先放一个结论:Agent-Reach不是一个模型,也不打算替代任何大模型,它解决的是 Agent 在生产环境里“够不着”系统的问题。如果你做过 AI Agent 落地的项目,大概率遇到过这种尴尬——模型很聪明,逻辑推理、任务拆解都做得漂漂亮亮,真让它去查一笔订单、改一条数据、调一个内部接口,它却像隔着一堵墙,要么没权限、要么拿不到需要的信息、要么返回的数据格式乱到没法用。我们把这块单独拎出来做了一套中间层,起名 Agent-Reach,这篇文章把整个项目的设计思路、核心机制、落地细节和一些踩坑经验完整讲一遍,希望对正在做 Agent 应用的人有参考价值。
1. 项目概述与设计思路拆解
1.1 Agent-Reach 到底是什么,这次不玩概念
Agent-Reach,字面上是“智能体的触达范围”。它想解决一个很朴素的问题:Agent 有能力做这件事,但它有没有一个安全、可控、可追踪的路径去做这件事。
我见过太多团队把 Agent 做成“聊天机器人 2.0”,模型能说会道,但一问到业务数据就哑火。问题从来不在模型本身,而在模型与业务系统之间缺一层东西。Agent-Reach 就是这一层:它负责把大模型连接到真实的业务系统和数据源,把自然的语言意图转换成受控的操作请求,同时把整个过程的上下文、权限、审计完整地沉淀下来。
具体能做三件事。第一,让 Agent 在权限受控的情况下调用业务接口,而不是直接把整个系统的 API Key 扔给模型。第二,把各个系统凌乱的接口返回统一成结构化数据,让模型拿到的是干净、可理解的内容。第三,把 Agent 每一次“触达”都记录在案,出问题能查、能追溯、能复盘。
这个项目不是给模型提供知识,不是做 RAG,也不抢工作流编排平台的饭碗。它更像是 Agent 世界里的“基础设施”,给所有 Agent 一条通往外部世界的路。适合谁看呢?正在做 AI 应用落地的工程师、需要把智能体接入企业系统的架构师,以及所有被“Agent 能说但不能干”折磨过的实战派。
1.2 Agent“够不着”系统的四类典型障碍
我在做第一个客服 Agent 项目时,产品经理给我提了一个需求:Agent 要能查订单、改地址、申请退款。听起来人畜无害,结果联调的时候我人麻了。
第一类障碍是权限障碍。生产系统的服务账号权限要么全给、要么全不给,如果直接把账号授权给 Agent,那意味着 Agent 拥有改库、删单、提现的权限,这在生产环境里想想都冒冷汗。但权限给得太少,Agent 什么实事都干不了。
第二类障碍是上下文障碍。业务系统接口的返回字段动不动几千上万个,订单详情、物流轨迹、商品明细全部铺开,一次工具调用就能把上下文窗口吃掉大半,模型根本没空间做推理。更讨厌的是,很多字段对当前任务而言完全没用,却白白占着 token 预算。
第三类障碍是操作障碍。很多企业内部系统根本没有为外部调用设计稳定的 API,有的只有内部 HTTP 接口,有的返回格式随版本变化,有的还要求先换 token 再调数据。Agent 作为调用方,光是处理这些“破事”就耗尽精力。
第四类障碍是治理障碍。Agent 调用了一个接口,执行了一个操作,到底是谁在什么时候让 Agent 做的?如果没有统一的审计日志,事后出问题根本无从查起,合规和风控部门也绷得很紧。
所以 Agent-Reach 的核心判断是:Agent 的能力瓶颈不在“想得到”,而在“够得着”。把“触达”这件事做好,Agent 的可用性会提升一个量级。
1.3 整体架构怎么分层,每一层干什么
Agent-Reach 的架构我按五个层次来组织,把边界划清楚,后面扩展和排查都省力。
最上面是模型层,负责对话理解、意图识别、任务规划,技术上无论是 GPT 还是开源模型都可以。再往下是编排层,负责决定调用哪些工具、按什么顺序处理,类似 LangChain 或自研的规划器。这两层属于 Agent 自己的“大脑”。
关键在下面三层。Reach Core 是核心层,它做的是权限裁决、上下文制备、工具路由和审计记录。这一层不参与业务逻辑,只负责“把关”和“摆渡”。适配层把订单系统、物流系统、CRM 等外部系统翻译成统一的工具契约,每个系统一个适配器,改一个系统不影响其他。目标系统层就是实际的业务接口、数据库和第三方服务。
这个分层的好处在于:模型可以换,编排器可以换,甚至整个 Agent 宿主都可以换,只要 Reach Core 的对外契约不破坏,下面的业务适配器就能稳定复用。反过来说,业务系统升级接口,只需要改适配器,上层 Agent 完全无感。
1.4 为什么用治理式触达,而不是全放开
一开始有人提过,既然模型那么强,直接把所有工具定义发给它,让它自由调用不就行了。我的回答是:技术上可行,生产上不敢。
全放开意味着把系统的所有能力一次性暴露给模型,包括那些有副作用的高危操作。模型在推理时可能出现幻觉,可能因为 prompt 注入被诱导调用危险工具,也可能在某个错误分支里连续触发一连串操作。没有中间层的约束,就相当于把油门踩死,但方向盘握在一个偶尔走神的驾驶员手里。
Agent-Reach 的思路是“治理式触达”:能力可以开放,但每一次触达都要经过权限检查、上下文约束和行为审计。这并不妨碍 Agent 的自主性,恰恰相反,边界越清晰,Agent 反而越敢操作,因为它知道自己不会越界。
做一个对比就能看明白:
| 方案 | 权限控制 | 可观测性 | 迭代效率 | 生产风险 |
|---|---|---|---|---|
| 硬编码调用 | 强,但工作量大 | 一般 | 低,每次加功能都要改代码 | 低 |
| 工具全放开给模型 | 弱 | 弱 | 高,模型自己试 | 很高 |
| Agent-Reach 治理式触达 | 强,且集中管理 | 强,全链路日志 | 中高,通过注册表扩展 | 可控 |
我并不是说硬编码一无是处,固定流程的场景它依然高效。但一旦你的 Agent 需要面对开放式的用户请求,需要动态决定调哪个工具、传什么参数,治理式触达就是更优解。
2. 核心机制与实操要点
2.1 Reach Context:在提问之前先把“地图”发给 Agent
Agent 触达系统时经常失败,是因为它在“没地图”的状态下瞎撞。Reach Context 要做的,就是在 Agent 发起工具调用之前,先把当前场景所需的“局部地图”准备好。
所谓局部地图,包括三部分内容:当前角色可用的工具清单、每个工具的调用说明和参数约束、目标系统当前的状态摘要(比如对接的订单系统版本、接口是否熔断等)。这些信息以结构化文本注入到模型上下文里,让模型知道:我现在能做什么、工具长什么样、边界在哪里。
实操中有一个关键心得:上下文制备不是把所有东西都塞进去。模型的总上下文窗口是有限的,要给推理和工具返回留足空间。我们内部有一个经验值,Reach Context 占模型总窗口的比例控制在八分之一到四分之一之间。举个例子,如果模型是 128K 窗口,Reach Context 准备阶段最多分配 16K 到 32K,其余留给多轮对话历史和工具返回结果。
制备逻辑里的核心参数有两个。context_budget 控制注入内容的上限,summary_strategy 控制信息压缩方式。我建议优先使用“字段裁剪 + 摘要生成”组合:先根据任务意图裁剪掉无关字段,再对长文本字段生成摘要,而不是直接做暴力截断。暴力截断会把关键信息截断,模型后续推理就是空中楼阁。
2.2 工具注册表:把系统能力翻译成模型看得懂的语言
工具注册表是 Agent-Reach 的“能力字典”。每一个业务接口要接入 Agent,第一步不是写代码,而是在注册表里登记一条工具记录。
记录的核心字段有四个:name 是工具在模型世界里唯一的名称,description 是对工具能力的自然语言描述,parameters 是 JSON Schema 格式的参数定义,execute 是真正执行操作的函数或接口引用。这套设计跟 OpenAI Function Calling 的契约天然兼容,很多开源模型的工具调用也支持同样的格式。
这里我想多说一句 description 的重要性。模型判断要不要调用这个工具,本质上是语义匹配。描述写得模糊,模型就会犹豫或者选错工具。我见过一个项目,两个工具一个叫“获取订单”,一个叫“获取订单列表”,描述写得很像,模型经常调错。后来在描述里明确加了适用场景和区别说明,成功率立刻提升。比如“获取订单列表:用于根据手机号或时间段查询订单概要,返回多个订单的简略信息,适合列表展示”“获取订单详情:用于根据订单号查询单个订单的完整信息,包括商品明细、收货地址、物流单号等”,模型选错的概率就小很多。
在中文场景里,description 一定用中文写。别觉得英文更“标准”,模型对中文语义的理解在工具匹配上足够可靠,而且中文描述能准确映射企业内部业务用语,减少歧义。
2.3 权限裁决:让 Agent 只做它该做的事
权限模型是 Agent-Reach 里最不能妥协的部分。我们的思路是把 Agent 当作一个特殊的“用户”,接入企业现有的 RBAC 体系,而不是另起炉灶。
每个工具在注册表里对应一组原子权限点,例如 get_order 对应 order:query:read,apply_refund 对应 order:refund:write。角色再绑定具体的权限策略。客服角色可能只有查询权限,售后角色才有退款权限,财务角色有更敏感的操作权限。权限裁决发生在工具路由之前,不是调用之后——也就是说,如果 Agent 尝试调用一个没有权限的工具,Reach Core 直接拒绝,模型根本不会收到“执行结果”或“报错信息”。
这里有一个容易被忽略的细节:拒绝也需要表达得体。Agent 收到权限拒绝后,应该对用户说“这个操作需要售后同事处理,我帮你转接一下”或者“当前账号没有退款权限”,而不是直接报“403 Forbidden”。这是因为权限拒绝本身也是一种用户体验,模型知道了边界,才能提供交代得过去的回复。
权限策略的维护要有版本管理。我极力反对直接在配置文件里改一行就上线。策略和工具注册表一样,属于代码的一部分,要走 MR 评审、测试、发版流程。否则改错一个权限点,可能导致 Agent 在线上做出危险操作,或者相反,把正常能力全锁死。
2.4 编排护栏:别让 Agent 在工具链里跑飞
Agent 在处理复杂任务时,经常要把多个工具串起来,比如先查订单号、再查物流轨迹、最后算预计到达时间。这个链路本身没问题,但如果不加护栏,Agent 可能陷入死循环或无限制的连锁调用。
我们的做法是三道护栏。第一,max_iterations 限制单轮任务里最多工具调用次数,超过就强制终止并收敛到已获得的信息上。第二,tool_timeout 控制单次调用的超时时间,生产环境里设置 3 秒到 5 秒,防止某个系统慢查询拖垮整个对话。第三,circuit_breaker 实现熔断,当某个工具连续失败多次时,自动标记为不可用,后续请求直接返回“当前系统暂时不可用”,避免 Agent 在故障系统上反复重试。
还有一个非常实用的经验:优先让 Agent 使用只读工具做试探,再把写操作放在最后。比如处理退款场景,先查询订单状态和退款规则,确认无误后再执行退款动作。这样即使中间推理出错,最多是查错了数据,不会直接产生资金变动。对于高危写操作,我们还要求 Agent 先输出操作摘要,面向用户二次确认后才能真正执行,这属于永远不能省的一道保险。
3. 从零跑通一个最小 Agent-Reach
3.1 环境准备与目录结构
这个最小版本不需要复杂的集群,一台开发机就够了。系统环境建议 Python 3.10 以上,依赖只需要 fastapi、uvicorn、pydantic,再加上可选的 openai 库用于真实模型调用。我用 venv 隔离依赖,避免污染全局环境。
目录结构很简洁,核心就三个文件:reach_core.py 放工具注册表、权限策略和路由逻辑;app.py 是 FastAPI 入口,暴露 HTTP 接口;config.py 放配置参数,这里的参数后续会聊。
先提前说明,这里展示的是教学骨架,生产环境需要考虑的东西多得多,比如高可用、分布式一致性、动态凭证管理,这些后面单独说。
3.2 核心实现:上下文制备、权限检查与工具路由
我先把工具注册表写出来。这里定义两个工具,一个查订单,一个申请退款。重点是每个工具都有 name、description、parameters、execute 四件套,方便后续统一处理。
# reach_core.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel class OrderQueryTool: name = "get_order" description = "根据订单号查询订单基本信息,包括收货人、商品、订单状态。客服场景高频使用。" parameters = { "type": "object", "properties": { "order_no": {"type": "string", "description": "订单号,例如 SO20240511001"} }, "required": ["order_no"] } def execute(self, order_no: str) -> dict: # 实际项目里这里会去调用订单系统的HTTP接口,并做异常捕获 return {"order_no": order_no, "status": "shipped", "status_text": "已发货"} class RefundTool: name = "apply_refund" description = "为指定订单申请退款。注意:该操作会产生资金变动,需要用户二次确认后方可执行。" parameters = { "type": "object", "properties": { "order_no": {"type": "string"}, "reason": {"type": "string"} }, "required": ["order_no", "reason"] } def execute(self, order_no: str, reason: str) -> dict: return {"order_no": order_no, "refund_status": "created"}然后把工具注册表和权限策略挂上。权限策略这里用了最简单的角色到工具名的映射,生产环境建议使用策略引擎或数据库表管理,但对于骨架来说,这份代码能完整表达思路。
TOOL_REGISTRY = {tool.name: tool for tool in [OrderQueryTool(), RefundTool()]} POLICIES = { "support_staff": { "get_order": True, "apply_refund": False }, "after_sales": { "get_order": True, "apply_refund": True } }接下来是上下文制备器。它的作用是在 Agent 发起对话或工具选择前,把允许调用的工具清单准备好,供模型读取。这一步同时起到了权限过滤的作用——模型看不到无权限的工具,自然就不会去调用。
def build_context(role: str) -> dict: allowed_names = [name for name, allowed in POLICIES.get(role, {}).items() if allowed] allowed_tools = [TOOL_REGISTRY[name] for name in allowed_names] return { "tools": [ { "name": tool.name, "description": tool.description, "parameters": tool.parameters } for tool in allowed_tools ] }然后写执行入口。先做权限裁决,再做路由,这个顺序绝对不能反。权限裁决失败时直接抛 403,但正如前面说的,这里只是 HTTP 层面的表现,Agent 在对话里如何回应,需要一层额外的错误映射。
def execute_tool(role: str, tool_name: str, args: dict): if not POLICIES.get(role, {}).get(tool_name, False): raise HTTPException(status_code=403, detail="permission denied") tool = TOOL_REGISTRY[tool_name] return { "result": tool.execute(**args), "trace_id": "trace_xxx" }最后是 FastAPI 入口,暴露一个 /reach/execute 接口。实际应用中,前端通常是 Agent 编排层,它会调用这个接口来执行工具。更接近真实场景的流程是:编排层先调用 /reach/context 拿到可用的工具清单,拼进模型消息里让模型做出工具选择,再把模型选择的工具名和参数发给 /reach/execute 执行。
app = FastAPI() class AgentRequest(BaseModel): role: str tool_name: str args: dict @app.get("/reach/context") def reach_context(role: str): return build_context(role) @app.post("/reach/execute") def reach_execute(req: AgentRequest): return execute_tool(req.role, req.tool_name, req.args)这就跑起来了一个最简 Agent-Reach。你可以用 uvicorn 启动,先请求 context 看看当前角色能看到哪些工具,再用 curl 模拟工具调用。完整的模型接入,只需要把 context 接口返回的 tools 数组,原样塞给 OpenAI 的 tools 参数或开源模型的工具调用接口即可。
uvicorn app:app --host 0.0.0.0 --port 8010curl http://127.0.0.1:8010/reach/context?role=support_staffcurl -X POST http://127.0.0.1:8010/reach/execute \ -H "Content-Type: application/json" \ -d '{"role": "after_sales", "tool_name": "apply_refund", "args": {"order_no": "SO20240511001", "reason": "用户申请"}}'3.3 关键配置项与参数选型
骨架跑通以后,要建立配置意识。我整理了一张参数表,都是我们落地时反复调整过的项。
| 参数名 | 建议默认值 | 作用 | 选型逻辑 |
|---|---|---|---|
| context_budget | 16384 | 限制 Reach Context 注入的 token 上限 | 一般取模型总窗口的八分之一到四分之一,还要给工具返回留空间 |
| max_iterations | 5 | 单轮任务最多工具调用次数 | 太少完不成复杂链路,太多容易跑飞,先按任务链路长度评估 |
| tool_timeout | 3000ms | 单次工具调用超时 | 内部系统要求 P99 小于 200ms,外部服务视 SLA 而定 |
| permission_mode | strict | 权限不足时直接拒绝 | 上线初期可用 observe 模式观看高危险操作,生产环境必须切 strict |
| enable_circuit_breaker | true | 连续失败时熔断 | 防止 Agent 在故障系统上反复重试造成雪崩 |
这里重点解释 context_budget 的计算逻辑。假设模型是 128K 窗口,多轮对话历史在长对话场景下可能占到 60K,模型本身输出需要保留 8K 到 16K,那么 Reach Context 如果超过 32K,其他部分就捉襟见肘了。所以我们把默认值压到 16K,宁可少放一些工具,也要保证模型有空间做推理。
3.4 完整复现一个“查单”场景
我用一个最常见的场景走一遍完整链路。用户对客服 Agent 说:“昨天下午的订单现在到哪儿了?”
第一步,编排层把这句话和 context 接口返回的工具清单一起交给模型。因为客服角色只有 get_order 工具,模型收到工具清单时也只能看到这一个,选错的概率很小。模型解析出的工具调用是 get_order,参数 order_no 需要从对话上下文里提取。如果用户没有提供订单号,模型会反问用户。
第二步,编排层把工具名和参数发给 /reach/execute。权限裁决返回允许,执行结果是一个结构化 dict。
第三步,这里缺了一个物流工具,真实场景里 get_order 返回里会带物流单号,再调用 get_logistics 获取轨迹。为了演示,我在执行结果里加一个模拟字段。最终 Agent 整合结果回复用户:“您的订单已经发出,目前物流显示在杭州分拨中心,预计后天送达。”
这个过程的日志大概长这样:
{ "ts": "2025-05-11T14:32:10.128Z", "trace_id": "trace_8f3a2b", "role": "support_staff", "tool": "get_order", "decision": "allow", "duration_ms": 128 }如果换成 support_staff 角色请求 apply_refund,权限裁决会返回 deny,模型不会看到退款成功或失败,只会收到“操作未获授权”。Agent 此时应该回复:“当前账号没有退款权限,我帮你转接到售后顾问。”这个体验就比直接报错温和得多。
4. 落地中遇到的坑与排查实录
4.1 高频问题速查表
我在多个项目里整理过一份高频问题清单,这些问题可以说每一个都真实遇到过。
| 现象 | 根本原因 | 解决办法 | 避坑建议 |
|---|---|---|---|
| Agent 声称调用了工具,但系统没有记录 | trace_id 没有贯穿全链路 | 入口生成 trace_id,所有日志和调用都带上 | 从第一天就统一日志结构 |
| 权限配置过细后响应变慢 | 每次调用都查权限表 | 增加权限结果缓存,并做异步刷新 | 权限缓存失效时间不超过 60 秒 |
| 模型总选错工具 | 工具 description 写得太模糊 | 重写描述,明确适用场景和区别 | 建一个小样本集,A/B 测试描述效果 |
| 工具调用把上下文塞爆 | 结果返回字段过多且未摘要 | 按任务裁剪字段加摘要器 | 给每个工具配置 result_schema |
| 模型反复调用同一个工具 | 模型不知道操作已生效 | 返回结果里加 action_confirmed 字段 | 在 system prompt 中强调幂等性 |
| 某个接口故障引发连锁报错 | 没有熔断机制 | 开启 circuit_breaker | 故障恢复后手动放行,不让 Agent 自动重试 |
| 改权限配置导致线上全部不通 | 策略与工具注册表不一致 | 策略纳入版本管理 | 配一套自动化测试用例覆盖核心权限点 |
排查这些问题的第一原则:先查日志,不要猜。Agent 出问题,80% 能从结构化日志里找到线索。日志必须包含决策是 allow 还是 deny、耗时多少、返回结果摘要、是哪一轮对话触发的,缺少任何一块,排查时都要多花一倍时间。
4.2 排查利器:结构化日志与轨迹回放
做 Agent 项目最头疼的场景是:用户反馈说“刚才 Agent 做了一件很奇怪的事”,但无法复现。我强烈建议在一开始就给每个任务生成唯一的 trace_id,并且贯穿整个链路传播。
在 FastAPI 的骨架里,可以在中间件里生成 trace_id,然后在权限检查、工具路由、外部调用、结果返回的所有日志里都带上它。这样一条 trace_id 就能串起一次 Agent 决策的所有环节:模型收到了什么 prompt、看到了哪些工具、选择了什么参数、权限是否允许、外部系统返回了什么、最终回复又是什么。
更进一步,可以做一个轨迹回放工具。把 trace_id 对应的所有日志按时间排序,以类似聊天记录的结构展示出来,方便逐帧复盘。我们内部做了一个很简单的网页端回放器,效果却出奇的好,产品、测试、研发都能用它定位问题。很多问题根本不用猜,一眼就能看出是模型误判、权限配置错误还是接口返回脏数据。
4.3 那些不写进文档的避坑建议
以下几条是我们真金白银换来的经验,特意放在这里。
第一,不要把所有系统密码直接放进 Agent 的环境变量。Agent 触达系统时需要身份凭证,但这些凭证最好是动态下发的临时凭证,用完即失效,而不是长期有效的全局密钥。短期看多一步凭证交换流程很麻烦,长期看能避免一次泄露引发的神仙级事故。
第二,生产环境必须把工具注册表与代码版本一起发版。有人图方便,直接在数据库里改工具配置,结果模型引用了新工具但权限策略还在旧版本,或者描述被改乱,问题极其隐蔽。工具注册表和策略都属于业务文档,要跟代码一样走版本管理和测试流程。
第三,权限不足时不要直接暴露接口报错。模型拿到 403 后如果硬邦邦地回复“没有权限”,用户体验很差,而且容易让用户以为系统坏了。要在编排层做一层“错误翻译”,让 Agent 有能力向用户解释限制原因,并提供替代方案。
第四,上线初期用 observe 模式先跑一周。这个模式不做真正的权限阻断,而是把 Agent 会请求什么工具、哪些权限不足、哪些操作有风险记录下来。一周之后,数据会告诉你真实的需求边界,这时候再收紧权限,比拍脑袋配置靠谱得多。
5. 可能的扩展方向与我的个人体会
5.1 从一个项目到一套可复用平台
Agent-Reach 在单个项目里跑通后,我最大的感受是:它完全可以脱离某个具体 Agent 产品,沉淀为一套企业内部的 Agent 触达基础设施。工具注册表可以扩展成“能力市场”,各个团队把业务接口登记上去,标注权限、SLA、负责人,让模型在组织内按需调用。
这样一来,多个 Agent 可以共享同一个 Reach Core。客服 Agent、销售助手、内部问答机器人,只要走同一个触达层,权限政策统一,审计日志统一,后续迭代成本会大幅降低。新 Agent 上线时,不再需要从零写接口对接,只需申请权限绑定工具即可。
5.2 后续可以做的几个扩展点
基于当前骨架,我觉得有三件事特别值得做。第一,把权限裁决升级为动态策略引擎,支持按时间、按用户级别、按风险等级动态调整权限。第二,把工具调用的成功率和失败原因沉淀成反馈数据,反哺一个轻量的路由模型,让模型在多个相似工具之间的选择更准。第三,与可观测平台打通,把 Reach 的 trace 数据导出到 Grafana 或类似系统,形成可视化的监控大盘。
如果团队有精力,还可以做更细粒度的字段级权限控制。同一个 get_order 工具,不同角色看到的字段可以不同。客服角色能看到用户手机号用于沟通,数据分析师角色则脱敏。这个做起来复杂,但对金融、医疗等强合规场景几乎是刚需。
5.3 几点个人体会
项目做下来,我的体会是:Agent 落地的难点从来不是模型本身,而是边界建设。你在模型能力上投入十成功力,可能只换来两成效果;但把触达能力补全之后,同样的模型能立刻释放出八成的价值。所以我一直对团队强调一个原则:先解决“够得着”,再谈“想得清”,最后才谈“做得好”。
另一个感受是权限治理必须从第一天就做。很多团队先放权再收紧,结果遇到一次事故被迫全部锁死,所有 Agent 直接变废。倒不如一开始就把权限模型建好,让 Agent 在清晰的边界内自由发挥,既安全又高效。
最后想分享一个小技巧:当你觉得 Agent 表现不佳时,先别急着换更大的模型,看看它的上下文里有没有放够它需要的信息。我处理过的绝大多数“模型笨”的 case,最后都发现是上下文制备出了问题,字段缺失、信息超载、描述不清。把这一层做好,往往比升级模型参数更省成本、更见效。