第 2 卷
复制代码
| 1 | constref =awaitctx.spillStore.saveText({ |
|---|---|
| 2 | owner: {sessionId:SessionId('sess-demo') }, // |
| 3 | source: {toolName:'web_fetch',callId:CallId('call-1'),label:'result'},/ |
| 4 | suggestedName:'web_fetch.txt', // |
| 5 | content: bigText, // |
| 6 | }) |
| 7 | // → { locator, bytes, retrievalHint } |
示例里这一步由策略插件代劳,它判出超了阈值,就替你去调 saveText。跑 npm run start:ch12:spill:
复制代码 1--- ① 工具跑完,原始输出多大 ---
2纯文本:60016 字节
| 3 | 阈值maxInlineBytes:50000 | ←超了,策略插件动手 |
|---|---|---|
| 4 | ---②模型眼前那份(换过之后)--- | |
| 5 | 头:##Section | |
| 6 | This document explains how contextoverflowhappens and whattodo about it. | |
| 7 | ## Secti… | |
| 8 | …(中间被预览截掉了) | |
| 9 | 尾:…/...-web_fetch.txt.Useread withoffset/limit, or grep thispathtosearch | |
| 10 | 替换后:50000字节 | |
| 11 | ---③全文存在哪--- | |
| 12 | 定位符(locator):/var/folders/.../dsh-ch12-spill-.../session-2f1617f23ca8/...-w | |
| 13 | 落盘:60016字节,权限600 | ←一个字节都没少 |
| 14 | 检索提示(retrievalHint):Useread withoffset/limit, or grep thispathtosearc |
返回的三样,各有各的用处,对着输出看:
消费方不解析它、只当黑盒。本地后端把它渲染成件路径,换后端可能是 URI 或键;路径形如 <…
locator后端给出的不透明定位符 >/<sha256(会话 id) 前 12 位 >/< 随机前缀 >-
<suggestedName>
用来对账。示例里落盘 60016 字节,跟工具原本吐
三样东⻄都在手上了,那是存的一方拿到的。存到外面之后,模型怎么看见它?文本这条路是 “告诉模型自己去拿”。
那条结果被换成头尾各一小段预览,外加一句通知,就是前面说的那句“去哪儿读全文”,由 locator(全文在哪)和 retrievalHint(怎么读)两样拼成;剩下那一样 bytes 只用
来对账,不进上下文。都是纯文本,模型后面还想看,用 grep 就能翻。中间那部分一直在文件里、随时能取回,外置不是丢弃,是把它换成“需要时取得到”。
图片的外部持久化:存成附件
第二个问题:用户拖进来的一张截图,也该整份进上下文吗?
不该。图片的字节不落进日志,上下文里也只留一个引用,而且不论大小,一律外置。官方点名四种东⻄绝不允许进这本只追加的日志:
三种引用:blob: URL、宿主临时路径、提供方 URL,指向的东⻄易失效或不可控;
base64 数据:它则相反,不是引用,是数据本身,塞进日志会膨胀、还删不掉。
上面四类都被禁了,图片走的是另一条通道,dsh 给出的答案是附件(attachment):先把字节存成不可变对象,日志里只留一个引用。这个引用和前面那三种不一样:它指向的是 dsh
自己先落盘的字节,对象就在 dsh 的盘上、随时能读回;那三种指向的是 dsh 掌控之外的东 ⻄,说没就没。
了,直接报错(类型由后端按字节识别出的实际格式说了算,不采信声明的 mediaType)。
存进去有条硬规矩——先落盘,后追加事件:宿主必须先让图片真的落到磁盘上,才能把 user/message 写进日志(模型输出图片时同理),否则日志里就会留下一条指向不存在的
对象的引用。
规矩说完了,校验、落盘和取回都调附件服务 ctx.attachments:
复制代码 1// 校验(不落盘)
| 2 | awaitctx.attachments.validateImage({ data, mediaType }) |
|---|---|
| 3 | //存一张 |
| 4 | constref=awaitctx.attachments.saveImage({ data, mediaType, name }) |
| 5 | //存一批 |
| 6 | constrefs =awaitctx.attachments.saveImages([one, two]) |
| 7 | //读回图片字节 |
| 8 | conststored =awaitctx.attachments.readImage(ref) |
跑 npm run start:ch12:attachment:
复制代码 1--- ① 一张图存进去,拿到什么 ---
| 2 | attachmentId:sha256:c2ab983c5681efb075d3b64315727791fdcf91bc61338f83f6a327a5ec |
|---|---|
| 3 | 类型/尺寸/字节:image/png 8x8 95字节 |
| 4 | name:pixel.png ←传进去的是/Users/alice/secret/pixel.png,路径被剥掉了 |
5 6--- ② 字节落在哪 ---
| 7 | .../attachments/v1/objects/c2/c2ab983c…206a34 |
|---|---|
| 8 | 文件权限600,目录权限700 |
| 9 | 落盘95字节,与ref.bytes一致:true |
10 11--- ③ 同一张图再存一次 ---
12attachmentId 相同:true← 内容寻址,天然去重 13objects 下的对象数:1