资源有几千条时,怎么避免把数据一次全塞给模型?
简化版
一次把几千条数据交给模型,要么超出上下文直接失败,要么装得下但注意力被无关条目稀释,模型更容易报出一个「感觉存在」的 ID。做法是分四层收窄,按顺序叠加:候选池预热(Agent 启动前由代码按用户画像、预算、偏好筛出每类几十条,工具只在池内查)→ 字段裁剪(列表工具只返回决策需要的字段,细节交给一次查一个的详情工具)→ 返回上限(每个列表工具单次最多返回 N 条,上限可配置)→ hasMore 与下一步提示(告诉模型还有多少没返回、应该换什么条件再查、不要重复同样的参数)。第一层决定问题规模,后三层控制每一次调用的体积。
详细版
原始资源:1828 个点位 + 850 家酒店 + 4240 条班次
↓ 第一层 候选池预热(启动前,不经过模型)
每个城市:点位约 60 条 + 必玩清单、酒店 12 家、每段城际班次 8 趟
↓ 第二层 字段裁剪
列表每行只给 ID、名称、类型、城区、价格、时长、热度、标签;详情单独查
↓ 第三层 返回上限
点位每次最多 25 条、酒店 10 条、班次 8 条
↓ 第四层 hasMore 提示
{"total": 63, "returned": 25, "hasMore": true, "hint": "还有 38 条没返回,可换标签或城区再查,不要重复相同参数"}
| 层 | 在哪做 | 解决什么 |
|---|---|---|
| 候选池预热 | 业务代码,启动前 | 把「几千里选」变成「几十里选」 |
| 字段裁剪 | 列表 / 详情两个工具 | 单条记录不膨胀 |
| 返回上限 | 工具执行时读配置 | 单次调用不超量 |
| hasMore 提示 | 返回结构 | 模型知道自己看到的是子集,换条件而不是重复调用 |
完整版教学
一、一次给全为什么不行
先算体积。一条点位只保留名称、类型、城区、门票、时长、热度、标签,也有六七十个字符:
1828 条点位 × 约 65 字符 ≈ 12 万字符
再加 850 家酒店、4240 条班次,总量几十万字符
这已经会占满很多模型的上下文窗口,请求直接失败。即使窗口装得下,问题也没解决:
候选太多,模型容易报一个「感觉存在」的 ID,写入时被校验打回
注意力分散到大量无关条目上,排出来的方案质量反而下降
每一轮往返都要带着这几十万字符,成本按轮数翻倍
所以要在工具的设计上收窄,而不是指望模型「自己看着办」。
二、第一层:候选池预热
最有效的一层是在 Agent 启动之前,由业务代码按确定的规则筛出候选,不经过模型:
每个目的地城市:
① 取启用的全部点位
② 按预算档过滤门票:经济档去掉门票超过 150 元的,舒适档去掉超过 300 元的,品质档不过滤
③ 按兴趣标签命中个数从多到少、再按热度从高到低排序
④ 取前 60 条
⑤ 用户必玩清单里的点位无条件补进来,不受 ② 和 ④ 的限制
这一层把问题从「1828 条里选」变成「每个城市几十条里选」。之后所有查询工具都只在候选池里查,SQL 上按候选池的 ID 集合限定,绕过候选池直接全表查的工具就是实现错误。
预热的风险是把用户真正想要的筛掉了,所以要有三层保护:用户明确要去的(必玩清单)无条件进池;池子大小是配置项可以调;候选池写进本次运行的快照,出问题能拿原始快照复现。
记忆钩子:能用规则确定的筛选,就不要交给模型。代码先把范围缩小,模型只在小范围里做需要判断的选择。
三、第二层:列表只给决策字段
候选池里每一条都相关了,但每条还带着很多决策用不上的字段。解决办法是把一个工具拆成两个:
| 工具 | 返回 | 用途 | 调用方式 |
|---|---|---|---|
| 列表工具 | ID、名称、类型、城区、门票、建议时长、热度、标签 | 在候选里挑 | 按条件筛,每次多条 |
| 详情工具 | 开放星期、开放时段、最晚入场、闭园季、经纬度、简介 | 写入前确认 | 一次查一个 |
列表阶段模型只需要比较;真要写入某一项时,再花一次调用确认它当天开不开门。详情工具的描述要写明「只在准备写入之前调用,一次查一个;不要批量调用,也不要用来搜索」。
四、第三层:返回上限
候选池里每个城市还有几十条,列表工具也不一次全给:
点位列表 每次最多 25 条
酒店列表 每次最多 10 条
城际班次 每次最多 8 条
上限放在工具注册表里,工具执行时读取;表里没填就用代码里的默认值。每次调用都现读,调整上限后,正在跑的任务下一次调用就按新值截断。
五、第四层:告诉模型「还有更多」
只截断还不够。模型看到 25 条,会以为这就是全部,然后要么据此做决定,要么用同样的参数反复调用想「翻页」。返回结构要明确说明:
{
"total": 63,
"returned": 25,
"hasMore": true,
"limit": 25,
"hint": "候选池中还有 38 条没返回。可换 tags 或 area 条件再查,不要重复调用相同参数。",
"rows": [ ... ]
}
hint 按情况给出下一步:
| 情况 | 提示 |
|---|---|
| 一条都没匹配上 | 放宽条件,去掉部分筛选,不要逐个试探 |
| 还有没返回的 | 换条件再查,不要重复相同参数 |
| 全部返回了 | 进入详情确认和写入 |
六、收窄的效果要能看见
四层收窄让数字链条变得清楚,也是很好的排查依据:
全量 1828 个点位 → 进入候选池 63 个 → 模型最终选用 21 个
把这组数字展示在页面上,能直观看出收窄是否过度(候选太少排不出合规行程)或不足(候选太多模型反复查询)。如果轮数用尽仍排不出合格方案的比例偏高,往往就是候选池太小,需要调大池子。
七、其他场景怎么套用
商城导购:候选商品按预算过滤后最多返回 8 条,价格库存另用工具单查
客服知识:先按业务线、产品过滤知识库,再做语义检索取前几条
数据分析:先用元数据选出相关的几张表,再把这几张表的字段交给模型
核心思路一致:先用确定规则缩小范围,再让模型在小范围里判断;每次调用有上限,并告诉模型还有多少。
八、常见误区与追问
- 误区:上下文窗口够大就可以一次全给。 装得下不代表用得好,候选太多会稀释注意力、增加编造 ID 的概率,还按轮数放大成本。
- 误区:收窄交给模型自己用查询条件完成。 能用规则确定的筛选应在启动前由代码完成,模型只在候选里判断。
- 误区:列表工具把所有字段都带上最方便。 细节字段应拆到详情工具,列表只给决策需要的字段。
- 误区:截断到 N 条就够了。 不告诉模型还有更多,它会以为这是全部,或者用相同参数反复调用。
- 误区:候选池筛得越小越好。 太小会排不出合格方案,要保留用户明确要求的项并允许调整池子大小。
- 追问:怎么防止预热把用户真正想要的筛掉? 必玩清单无条件进池,池子大小可配置,候选池写进运行快照便于复现。
- 追问:返回上限怎么定? 按一次决策需要比较的数量定,放进配置表,根据调用日志里的查询次数调整。
九、加强记忆
几千条数据一次给全会撑满上下文、稀释注意力、放大成本。四层收窄按顺序叠加:启动前由代码按画像和预算预热候选池,必玩项无条件进池、池子可配、写进快照,工具只在池内查;列表只给决策字段,细节交给一次查一个的详情工具;每个列表工具有可配置的返回上限;返回结构带 total、returned、hasMore 和下一步提示,让模型换条件而不是重复调用。全量、进池、选用三个数字要能看见。
项目实战落地
项目里怎么做的
《AI Agent旅游行程智能规划平台》的资源库有 1828 个点位、850 家酒店、4240 条城际班次,行程编排 Agent 按四层收窄:
- 候选池预热:
CandidatePoolService在 Agent 启动前按城市预热:点位按预算档过滤门票(经济档去掉门票高于 150 元、舒适档去掉高于 300 元),按标签命中数和热度排序取前POOL_POI_SIZE(60)条,必玩清单无条件加入;酒店按预算档映射星级、每城取 12 家;班次按出发时间分散取,每段 8 趟;三个池子大小都在系统参数里; - 快照与池内查询:候选池写进
agent_run.input_snapshot,同时把 ID 集合放进运行上下文,所有查询工具只在池内查,写入工具核对 ID 在池内; - 列表和详情分开:点位列表工具每行只返回决策字段,开放时间、闭园季、经纬度放在一次查一个的详情工具里;
- 返回上限与提示:上限来自
function_tool.max_return_rows(点位 25、酒店 10、班次 8),buildPageResult统一返回total、returned、hasMore、limit、hint,点位工具的hint按「一条没匹配上 / 还有没返回的 / 全部返回了」三种情况生成。
为什么这样取舍
- 池子由代码预热:把问题从「1828 条里选」变成「每城几十条里选」,后面三层才有意义;页面能展示「从 1828 个点位里筛出 63 个候选,Agent 从中选了 21 个」。
- 提示写明不要重复参数:模型知道自己看到的是子集,会换一个标签或城区再查,而不是用同样的参数反复调用把轮数打满。
面试官还会追问
- 候选池把资源筛掉了一大部分,会不会把用户本来想去的地方也筛没了?项目怎么兜底?
- 两座城市之间没有直达班次时,班次工具返回什么?会不会编一趟出来?
学完《AI Agent旅游行程智能规划平台》,上面这些追问你都会迎刃而解。