ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent 大结果治理:Artifact 化与分页续读

Agent 大结果治理:Artifact 化与分页续读 ▼ 封面预览2.35:1发布时上传最近在做 Agent 工具结果治理这一块遇到一个很实际的问题Agent 调工具一查经常就是几万行数据。这些结果直接塞给模型会出事。这篇整理成学习笔记把「大结果怎么放、怎么让模型正确用」这套方案梳理清楚。叙事主线背景目的 → 技术调研 → 实现细节 → 稳定性 → 后续优化。01背景工具返回大结果会出什么事Agent 调工具是天经地义的但工具返回的结果经常大得离谱一次数据库查询返回几万行、一次代码执行 stdout 几 MB、一次 API 抓取整页 HTML、一个数据分析子 Agent 产出完整 CSV。这些结果如果原样塞进模型上下文会引发一串连锁灾难**上下文被单个结果撑爆。**一个工具结果占了模型窗口的 60%剩下的历史和推理预算所剩无几模型开始失忆或答非所问。**截断后被模型当成完整事实最危险。**结果被截成半截 JSON模型不知道这是半截把它当成完整业务数据继续推理得出错误结论还自信满满。**“请继续读提示反而帮倒忙。**截断后附一句结果已截断请继续读”结果模型把这句提示也当成了业务事实的一部分。**多轮积累。**一个 Agent 跑 10 轮每轮调一个返回大结果的工具10 个大结果堆在上下文里窗口早爆了触发频繁上下文压缩质量下降。这些问题的本质是工具结果的大小和模型上下文的大小是两个量级的东西不能用塞进去解决。那提高模型窗口行不行也是伪解。窗口涨了成本也涨token 都要付费大结果挤占其他预算历史、System Prompt、工具定义、推理共用一个窗口注意力还会被稀释“lost in the middle”上下文越长对中间部分的注意力越弱而且窗口再大也有上限——数据动辄百万行塞不下。所以正确方向不是塞更多而是把大结果外置只给模型一个引用需要时按需回读。这层治理要守住四个不变量上下文不被撑爆、模型永远知道结果是否完整、大结果可按需回读、权限与脱敏可控。核心是一个**仓库提货单模型**——大结果是仓库里的货模型手里只拿提货单Artifact 引用要看哪件货就凭单去取取不到权限没了/货删了就明确失败。02技术调研为什么外置是唯一正解四种方案对比 直接塞全文 → 爆上下文、成本失控、注意力稀释 截断提示已截断→ 模型常忽略把半截当完整事实 截断请继续读→ 半截JSON被当完整业务数据下错结论 外置引用(Artifact)→ 需要基建但能守住全部不变量前三种的共同毛病它们都把不完整的结果当作完整结果喂给模型只是换了不同的提示语。模型本质上无法可靠地区分这是完整数据和这是被截断的数据——尤其是当截断点恰好落在一个看起来合理的结构边界上时。调研下来最重要的发现是——**完整性不能靠提示语传达必须靠结构化信号。**工具结果进模型上下文时必须带上一个明确的完整性状态字段complete 完整结果可直接用 truncated 已截断只是预览完整内容在外调 fetch 取 partial_failed外置失败只有这部分不保证完整模型被训练来识别这种结构化字段比识别自然语言提示可靠得多。truncated 状态下模型知道我看到的不全会主动调 fetch 去取细节而不是基于半截数据瞎推理。图1仓库提货单模型——大结果外置的核心心智Artifact 容易和附件、日志、长期记忆混淆必须分清Artifact 是给模型读的中间产物会话级附件是给用户下载的最终交付持久日志是排障用的不进模型长期记忆是模型跨会话复用的。最常搞混的是 Artifact 和附件——一个工具结果可能先当 Artifact 供模型继续推理最后再导出成附件供用户下载但这是两个动作、两个对象不能混。03实现细节三档分流与分页续读总体结构是工具执行返回结果 → 大小判定三档→ 小直接进 Tool Result / 中预览摘要 / 大外置 Artifact → 模型要细节时调 fetch_artifact 按页取回标 Ephemeral不写回历史。图2三档分流——小直接嵌 / 中预览摘要 / 大外置 Artifact核心是三档阈值不是超不超一个值的二分。4KB 直接嵌外置的往返成本比直接给还高4KB~50KB 给预览 结构化摘要预览是前 N 行摘要是列名 统计行数模型多数看摘要就够50KB 或 1000 行外置 Artifact。为什么中档单独处理直接外置太浪费频繁 fetch直接塞又占地方给个预览 摘要最划算。工具结果统一用一个信封表达content预览文本 structuredContentcompleteness、result_summary、artifact_ref、fetch_hint。关键字段completeness 让模型判断能不能直接用result_summary 让模型不 fetch 也能掌握轮廓artifact_ref 是大结果的提货单fetch_hint 引导如何取细节。注意artifact_ref 只是引用不是万能钥匙拿到 ref 不代表能读到内容还要过权限校验模型不能把它当图片 URL 或下载链接输出给用户。不是所有工具结果都走 Artifact要按工具类型配策略矩阵计算器/时间解析极小直接 complete单条记录读取小直接 complete列表查询中~大预览摘要大则 Artifact数据库查询大必走 Artifact代码执行 stdout 大可能巨量必走 Artifact 行数上限搜索结果聚合中预览摘要子 Agent 返回按实际大小判定。策略可配置挂在工具注册信息上不写死在中间件里。**存储结构一个 Artifact 分两处存。**关系库存可检索的元数据artifact_id、owner、source_tool、size、checksum、status不存正文对象存储存加密压缩的完整 Payload。为什么不把正文也放关系库关系库不适合存 MB 级 blob查询和备份都受影响元数据进关系库是为了可检索按 user/session/tool 查正文进对象存储是为了大文件友好。图3有界分页——字节 offset、UTF-8 边界、next_offset、Ephemeral模型取细节用 fetch_artifact关键是按页取不是一次取全文。几个反直觉但关键的设计offset 用字节不用字符多字节编码下字符级 offset 会切出半个字符要配合只在完整字符边界截断limit0 不能表示读到结尾要用 has_more: false 判断next_offset 由服务端给客户端自己算会跳页/重复fetch 结果标 Ephemeral不写回历史否则分页几次历史又爆了。// 中文一个字占 3 字节。按字节 offset 切极易切出半个字 → 乱码// 这里回退到最近的合法 UTF-8 字符边界funcsafeSlice(b[]byte,off,sizeint)[]byte{ifofflen(b){returnnil}end:offsizeifendlen(b){endlen(b)}forendoffendlen(b)!utf8.RuneStart(b[end]){end--}returnb[off:end]}**幂等保存与写完成态。**Artifact 保存是异步过程要处理保存到一半的状态用 sha256 当幂等键工具被重试多次相同结果只存一份顺序铁律是先写对象存储后写元库否则进程在写 payload 前崩了ref 指向空 悬空引用写完才置 complete 状态reader 看到 writing 会等待/失败绝不把半截数据喂给模型。//1)幂等键相同内容同id重试不产生多份sum:sha256.Sum256([]byte(raw))id:hex.EncodeToString(sum[:])ifmetaStore.Exists(id){returnid,nil}//2)顺序铁律先对象存储后元库防悬空引用 objStore.Put(id,encrypt(raw))metaStore.Insert(...{Status:writing})//3)写完成态读到 writing 会等/报错防读半截 metaStore.UpdateStatus(id,complete)returnid,nil**权限撤销与 Ref 生命周期安全命门。**提货单发出后权限可能变化用户撤权了某数据源 → 基于它的 Artifact 应失效会话结束 → 会话级 Artifact 应清理TTL 到期 → 自动失效。实现上fetch 时实时校验权限不依赖 ref 发出时的权限快照校验失败返回明确的 forbidden而不是返回旧数据。这能防一种攻击模型在有权时拿到了 ref存进上下文撤权后试图 fetch。04稳定性Artifact 层自己也要稳Artifact 层如果自身不稳就从解决大结果变成新的故障源。三个不变量服务故障不能阻断主请求外置失败要降级保存和回读都要可降级Artifact 不成为上下文新负担fetch 结果标 Ephemeral 不入历史。**保存失败不阻断主请求。**工具执行已经成功了拿到了结果这时候 Artifact 保存失败不能让整个工具调用失败——那等于白执行。降级链保存成功 → 预览 artifact_ref保存超时/存储故障 → 预览 摘要partial_failed连预览都构不出 → 极简预览 失败标记。无论哪条路工具调用的成功语义不丢——工具确实执行了只是完整结果没能持久化。**回读失败层层回退。**模型调 fetch 取不到时也要有回退成功 → 返回该页权限撤销 → forbidden告知用户不重试存储暂时故障 → 重试几次重试仍失败 → 从关系库元数据给摘要行数/列名/统计元数据也没有 → 明确 not_found。回退的每一步都要告诉模型现在能给到什么程度让模型能决策换工具、澄清还是告知用户。**核心原则工具成功 ≠ 外置成功。**外置失败绝不伪装成工具失败去触发工具重试那是重复副作用。权限失败更不等于没搜到必须分开语义冒泡。05后续优化方向**智能预取。**模型拿到 truncated 的表格结果大概率先看表头和前几行——返回 ref 时预取第一页减少一次往返按查询模式预取某类结果平均 fetch 3 页就预取前 3 页缓存。预取要带命中率指标命中率低就关掉。**自适应分页。**宽表列多每页行数少窄表列少每页行数多文本类按字符数分页目标是让单页大小落在合理区间。**冷热分层。**刚产生的几分钟内最常被 fetch热之后骤降冷。热数据放快速存储冷数据降级到便宜存储超 TTL 清理。**流式 Artifact。**当前是工具执行完才整体保存。对流式工具子 Agent 边产出边返回可演进为边产出边写、模型边按需 fetch 已写部分超长产出的子 Agent 不会让模型干等。06一分钟讲清楚 面试高频追问**一分钟版面试开场**Agent 调工具一查经常就是几万行。直接塞给模型有三个坑上下文塞爆、成本翻几十倍、最致命的是截断——模型不知道自己拿到的是半截把半截当完整事实去推理错了还特别自信。我做的核心工作就是大结果不进上下文存外面给模型一张提货单ArtifactRef它要细节自己按页取。提货单上有个 completeness 字段模型一眼就知道拿到的全不全。**收尾句**让模型永远知道自己拿到的数据完不完整大结果要得起、读得起、放得下。Q为什么不用直接提高模型窗口成本翻几十倍 注意力被稀释长结果里关键结论反而被淹没。而且数据动辄百万行窗口再大也塞不下。Q截断了模型怎么知道completeness 字段结构化信号比已截断这种提示语可靠——模型被训练来识别结构化字段。Q分页中文怎么切字节 offset 回退到 RuneStart 字符边界。中文一字占 3 字节盲切必出乱码safeSlice 单测覆盖每个字节位置。Q取回来的内容为什么不留历史标 ephemeral留了外置省下的 token 又被分页历史吃回去上下文又爆了外置白做。Q保存失败为什么不直接报错工具已成功执行报错会触发上层重试工具 → 重复执行重复副作用。正确语义是成功但不完整降级成预览。Q权限怎么保证实时校验 ref不靠发出时的快照。存之前脱敏ref 是不透明 ID取货时再验身份——防有权时拿 ref、撤权后 fetch。Q工具要改吗不用。中间件在框架层统一拦截分流工具作者无感知所以能套到任意工具上。**落地顺序照这个写一遍**先做完整性标记三个状态这是最关键的一个字段模型靠它决定信不信手里的数据→ 三档分流 → 外置存储指纹去重 两步状态→ 分页取货重点测中文边界→ 兜底故意把存储搞挂验证不报错、模型收到不完整标记。总结一下大结果治理的本质是把模型能看到什么和实际数据在哪解耦。模型上下文里只有预览和提货单实际数据在对象存储里按需取。落地原则是先让完整性信号可靠再让外置回读可用——如果模型连这个结果完不完整都判断不了外置做得再好也没用模型照样基于半截数据瞎推理。学习笔记如有错漏欢迎指正寻码札记
返回列表