让模型通过工具写数据库,怎么防止它写错?
简化版
提示词里写「先检测再写入」「只能用候选里的 ID」都不够,模型是否照做无法强制,写入工具必须自己校验。几条做法:写入只留一个工具,所有校验集中在这里;参数逐项校验(范围、枚举、格式、先后关系),不合格就返回模型能看懂的原因;引用校验:模型给的 ID 必须在本次允许的范围内,名称、金额、时长这些能从 ID 推出来的字段由代码反查或计算,不用模型写的;重复写入要挡住,但按「已经写过了」回复,不当成失败,否则模型会反复重试同一条;分清写入时拦什么、事后判什么:局部、确定的约束在写入时拦,需要看整体才能判断的约束排完再统一校验;最后以数据库里的结果判定成败,而不是模型的总结。
详细版
写入工具内部的检查顺序:
1. 参数校验 天数在范围内、类型在词表里、时间格式正确、结束晚于开始
2. 引用校验 引用的资源 ID 在本次允许的范围内;不在就丢弃并记录原因
3. 派生字段 名称按 ID 反查、时长由代码算,不用模型写的值
4. 重复检查 同一条已经写过 → 返回「已存在,接着排」,影响行数记 0
5. 局部约束 写入这一条就能判断的约束(如落地后的缓冲时间),不满足就拒绝并给出改法
6. 落库 返回新记录 ID,让模型知道这一条确实写进去了
事后: 排完整体后跑全局校验;以库里的记录判定成败
| 检查 | 放在写入时 | 放在事后 |
|---|---|---|
| 参数格式、枚举、范围 | ✓ | |
| ID 是否在允许范围内 | ✓ | |
| 重复写入 | ✓ | |
| 只看这一条就能判断的业务约束 | ✓ | |
| 预算、单日总时长、整体完整性等全局约束 | ✓ | |
| 需要反馈给模型改进的规则类问题 | ✓(作为教训交给下一轮) |
完整版教学
一、为什么提示词约束不够
提示词里可以写得很清楚:「写入前先调用检测工具」「只能使用候选列表里的 ID」。但模型在几十轮往返后可能忘了、可能跳过、也可能记错 ID。而写入是有副作用的:一条错的记录进了库,后面的校验、展示、统计都会被它污染。
提示词约束:告诉模型应该怎么做 → 大多数时候有效,但不保证
工具内校验:不管模型怎么做,错误的数据都进不了库 → 保证
所以两者都要有:提示词减少错误发生的次数,工具校验保证错误不会落库。工具调用的完整校验框架见「如何为 AI Agent 设计可靠的工具调用机制?」。
记忆钩子:提示词是「请你」,工具校验是「不允许」。写库这一步只能靠「不允许」。
二、写入只留一个入口
如果每类数据都有自己的写入工具,校验逻辑就分散在好几个地方,某一个漏了就是漏洞。集中到一个写入工具里:
saveItem(day, type, refType, refId, start, end, cost, reason)
type 取值:交通 / 景点 / 餐饮 / 住宿 / 自由活动
refType 取值:poi / hotel / route
按类型处理不同的引用,但所有检查都在同一个方法里,也方便统计「这次一共写了几条」。
三、引用校验:ID 必须在允许范围内
模型给出的 ID 是最容易出错的地方:可能是它「觉得存在」的一个数字,可能是上一轮查到但不在本次范围里的,也可能来自提示注入。所以写入前要核对:
本次允许的点位 ID:{1, 5, 12, 37, ...}(候选池)
模型要写入 poiId = 88 → 不在集合内 → 丢弃这一条,返回原因,并记录「第 2 天第 3 条因不在候选池被丢弃」
记录被丢弃的原因很重要:事后校验时可以把它列进问题清单,反馈给下一轮。同时,名称这类能由 ID 推出的字段一律由代码反查:模型写的名称可能和 ID 对不上,展示出来就是「ID 是 A、名字写的是 B」。时长也由开始和结束时间算,花费由代码汇总,模型报的数字不采信。
四、重复写入:挡住,但不要当成失败
模型经常把同一天已经写过的明细整段重报一遍。如果不挡,库里会出现两条完全相同的记录,事后校验会把它判成「这一条和自己时间重叠」,而这种问题模型根本改不动。
挡住的方式也有讲究:
| 回复方式 | 模型的反应 |
|---|---|
| 返回错误「写入失败」 | 以为没写进去,用同样的参数反复重试 |
| 返回「已存在,第 2 天第 3 条,接着排下一项」 | 知道目标已经达成,继续往下排 |
模型要的结果(这一条在行程里)已经达成,所以按成功回复,但标明是重复、影响行数记 0,工具日志上看得出这次没有真正写库。
五、写入时拦什么、事后判什么
不是所有规则都适合在写入时拦截:
写入时拦(局部、确定):
城际交通 14:30 到达,缓冲 30 分钟 → 当天目的地城市的景点不能早于 15:00
模型要写 14:00 的景点 → 拒绝,并告诉它最早几点、可以怎么改
事后判(全局、需要看整体):
整个行程的总花费是否超预算
某一天的总车程是否超上限
必去的点位有没有漏排
全局约束在写某一条时还无法判断(后面还没排),硬要在写入时拦,要么拦错,要么根本拦不住。它们适合在一版排完后统一校验,把问题写成教训交给下一轮改进。还有一类规则虽然单条就能判断(比如点位排在闭馆日),也可以选择不在写入时拦:写入工具要求模型先查详情确认开放时间,但事后校验兜底,把这类问题作为明确的教训反馈,模型才有机会自己学会改。
六、提交与落地分开
另一种防写错的模式是:模型只提交决策,事实由代码落地。
模型调用 submit_recommendations:
[{"productId": 12, "reason": "适合编程、续航长"}, {"productId": 31, "reason": "..."}]
代码落地:
逐个按 ID 查商品是否存在且在售
用真实工具重新查价格、库存、优惠 → 库存不足的剔除
推荐理由用模型的,价格库存优惠写真实查询结果
全部被剔除 → 本次任务判为失败,而不是写一条空推荐
模型即使在理由里把价格写错,也不会进入推荐明细,因为明细里的价格来自真实查询。
七、以库里的结果判定成败
最后一道关:任务结束后回到数据库数一数:
模型总结:「已完成 5 天行程规划,共安排 18 个项目」
库里实际:trip_item 0 条 → 判失败,不管总结写得多好
同时新写入的数据最好先处于「待确认」状态,由后续的冲突检测、人工审核再转为正式状态,给错误数据留一道人工拦截的机会。
八、常见误区与追问
- 误区:提示词里要求先检测再写入就够了。 模型不一定照做,写入工具必须自己再校验一次。
- 误区:模型给的名称和 ID 一起存就行。 名称应由代码按 ID 反查,模型写的可能和 ID 对不上。
- 误区:重复写入应该返回失败。 返回失败会让模型反复重试同一条,应按「已存在」回复并记 0 行影响。
- 误区:所有规则都应该在写入时拦截。 预算、总时长这类全局约束只能在排完整体后判断。
- 误区:模型说写完了就是写完了。 成败要以数据库里的记录为准。
- 追问:被候选池校验丢弃的明细怎么让模型知道? 返回原因给模型,同时记下具体哪天哪条被丢,事后列进问题清单。
- 追问:模型只提交 ID 和理由时,事实字段从哪来? 落库前用真实工具重新查询,模型提交的只作为选择依据。
九、加强记忆
写库不能靠提示词,写入工具必须自己校验:只留一个写入入口;参数逐项校验并返回可读原因;引用的 ID 必须在允许范围内,不在就丢弃并记录,名称、时长、花费由代码算;重复写入按「已存在」回复、记 0 行,不当失败;局部确定的约束写入时拦,全局约束排完整体再判并作为教训反馈;也可以让模型只提交决策、代码用真实数据落地。最后以库里的记录判成败,新数据先待确认。
项目实战落地
项目里怎么做的
《AI Agent旅游行程智能规划平台》的 8 个工具里只有 saveTripItemTool 写库,所有检查集中在这一个方法里:
- 参数校验:天数越界、城市不在本次行程、明细类型不在词表、时间格式不对、结束不晚于开始,都返回告诉模型怎么改的原因;
- 候选池核对:引用的点位、酒店、班次不在候选池内就丢弃这一条,并记下带天数和原因的记录,事后补记进冲突清单;名称由代码按 ID 查,时长由代码算;
- 重复写入:模型常把同一天已经写过的明细整段重报,工具自己挡住,回复里标明「重复」和已有明细的位置,提示接着排,影响行数记 0,不算失败;
- 到达缓冲:城际交通落地后要留缓冲,当天目的地城市的景点不能排在到达之前,排早了就告诉模型最早几点和三种改法,口径和整版硬校验一致;
- 事后以库为准:每一版排完反查
trip_item,条数为 0 判失败,再跑 14 条硬校验。
《AI Agent电商平台导购与运营增长系统》用的是提交与落地分开:模型通过 submit_recommendations 只提交商品 ID 和推荐理由,materializeRecommendations 逐个用真实工具重查价格、库存、优惠,库存不足的剔除,价格库存优惠快照全部写真实数据。
《AI 多Agent智能相亲交友匹配平台》的八个工具里有两个写库:筛选官的 save_candidates 写候选池,审核官的 save_audit_result 回填审核结论。业务边界全部在工具里校验,不通过就把原因回给模型,让它改一版再来:
- 写候选池:一次写入条数不超过
POOL_CANDIDATE_LIMIT(默认 30);候选必须有资料、资料已通过审核、不是申请方本人。 - 放宽说明:筛选官这一轮放宽了条件,就要把放宽说明带进写候选池的调用,写成「维度+幅度」,如「年龄上限+2」「学历+1档」。维度只能是申请方标为「加分」、而且确实有约束的那几个;一轮只动一个维度;整次运行的放宽轮数不超过
SCREENER_MAX_RELAX(默认 3);单次和累计幅度查代码里的规则表:
| 维度 | 单次最大幅度 | 累计上限 |
|---|---|---|
| 年龄 | 2 岁 | 6 岁 |
| 身高 | 3 厘米 | 9 厘米 |
| 地域 | 1 级 | 2 级 |
| 学历 | 1 档 | 2 档 |
| 收入 | 1 档 | 2 档 |
| 婚史 | 1 级 | 1 级 |
- 回填审核结论:结论只能是通过或否决;候选必须在本次任务的候选池里;否决必须带三种分类之一和理由,判「画像不够」时理由里必须点名缺的是哪个维度。
选哪个维度放宽由模型决定,边界由代码定死:说好的年龄范围被悄悄放宽 10 岁,会员不会觉得匹配更好,只会觉得平台在凑数。
为什么这样取舍
- 重复写入不报错:报错会让模型反复重试同一条;写进去又会被校验判成和自己时间重叠,反思拿着这种问题改不动任何东西。
- 推荐落地时重查:模型即使在理由里写错价格,也污染不了推荐明细。
面试官还会追问
- 点位排在了闭馆日,为什么写入时不拦,而是等整版排完再由代码判?
- 模型按中转建议把一段交通拆成两段班次来排,写入时会发生什么?它该怎么改?
学完《AI Agent旅游行程智能规划平台》,上面这些追问你都会迎刃而解。