2026/9/16 14:20:49

Electric Agents 附件(Attachments)机制详解:上传、读取、Hydrate 与 Manifest 存储

Electric Agents 附件(Attachments)机制详解:上传、读取、Hydrate 与 Manifest 存储 Electric Agents 附件Attachments机制详解上传、读取、Hydrate 与 Manifest 存储【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric附件Attachments是 Electric Agents 平台中与实体Entity关联的文件数据它解决了图片输入、用户上传文件、生成产物、工具输出需要随实体时间线一起被追踪这一核心诉求。本文基于 attachments.md 文档结合electric-ax/agents-runtime包的源码实现系统讲解附件从客户端上传、Manifest 持久化、Handler 侧读取到图片上下文 Hydrate 的完整链路。读完本文你将掌握createAttachment()/readAttachment()客户端 API、ctx.attachmentsHandler 侧 API 的全部用法并理解附件流、Manifest 行、subject/role 语义以及图片上下文注入的护栏机制。附件机制的核心设计实体路由上传、私有附件流存储、Manifest 引用在深入 API 之前先建立一个整体认知。根据文档定义附件机制由三部分构成实体路由上传附件通过实体的路由entity routes上传即客户端调用指向/{entityType}/{entityId}的 HTTP 接口私有附件流存储附件字节内容存放在私有的附件流private attachment streams中与实体主时间线隔离避免大块二进制数据污染流数据Manifest 行引用附件在实体流上通过一条kind: attachment的 manifest 行即内置manifests集合中的一条记录被引用Manifest 行保存的是元数据id、流路径、mimeType、字节数、sha256 等而非二进制本体。这样的设计让附件元数据可以与实体时间线一起被同步、投影和查询而二进制内容则按需按 id 读取兼顾了时间线可读性与大文件存储效率。从源码结构看这一设计在类型层面有直接体现。entity-schema.ts 中定义了AttachmentStatusValuepending | complete | failed、AttachmentSubjectTypeValueinbox | run | text | tool_call | context、AttachmentRoleValueinput | output以及完整的ManifestAttachmentEntryValue类型该类型同时被 Zod schema 校验kind: z.literal(attachment)、status: z.enum([...])、byteLength: z.number().int().nonnegative().optional()保证了写入流中的数据形状合法。从客户端上传附件createAttachment()外部应用宿主 App、CLI、测试或集成代码通过createRuntimeServerClient().createAttachment()上传附件。它是 Programmatic runtime client 提供的能力之一与spawnEntity()、sendEntityMessage()、registerWake()等属于同一套 HTTP 客户端。import { createRuntimeServerClient } from electric-ax/agents-runtime const client createRuntimeServerClient({ baseUrl: http://localhost:4437, principalKey: user:sam, }) const { attachment } await client.createAttachment({ entityUrl: /horton/onboarding, attachment: { bytes: imageBytes, mimeType: image/png, filename: screenshot.png, subject: { type: inbox, key: message-1 }, role: input, meta: { source: upload }, }, })其中principalKey作为Electric-Principal请求头发送用于声明上传者的主体身份详见 programmatic-runtime-client.md 中RuntimeServerClientConfig的字段说明。输入字段与底层实现AttachmentCreateInput的完整定义位于 types.tsexport type AttachmentCreateInput { bytes: Uint8Array | ArrayBuffer | Blob mimeType?: string filename?: string subject: { type: inbox | run | text | tool_call | context key: string } role?: input | output meta?: Recordstring, JsonValue }字段说明字段类型说明bytesUint8Array \| ArrayBuffer \| Blob文件二进制内容三种形式均可mimeTypestring可选MIME 类型如image/png缺失时底层默认application/octet-streamfilenamestring可选文件名缺失或空白时底层默认attachmentsubject{ type, key }将附件关联到时间线上某个对象见下文 Subjects 章节roleinput \| output可选附件方向默认inputmetaRecordstring, JsonValue可选任意元数据如{ source: upload }看 runtime-server-client.ts 的实现createAttachment()实际是构造一个multipart/form-data请求POST 到{entityUrl}/attachmentsbytes会被统一包装为BlobUint8Array会先拷贝一份再构造 Blob避免外部后续修改影响请求体file字段携带字节内容与文件名mimeType、filename、subjectJSON 序列化、role、metaJSON 序列化分别作为表单字段返回{ txid: string, attachment: ManifestAttachmentEntry }其中txid是写入事务 idattachment是已写入的 Manifest 行。服务端写入的 Manifest 条目上传成功后服务端会在实体的manifests集合中写入一条kind: attachment的行其结构如下interface ManifestAttachmentEntry { kind: attachment id: string streamPath: string status: pending | complete | failed subject: { type: inbox | run | text | tool_call | context key: string } role: input | output mimeType: string filename?: string byteLength?: number sha256?: string createdAt: string createdBy?: string error?: string meta?: Recordstring, JsonValue }各字段含义字段说明id附件唯一标识后续读取字节时使用streamPath附件字节实际存储的私有附件流路径status上传状态机pending处理中→complete成功或failed失败subject附件归属的时间线对象roleinput用户/宿主提供或outputHandler/工具产出byteLength/sha256字节长度与内容摘要便于校验与去重error状态为failed时的错误信息meta自定义元数据该条目也是内置manifests集合的 9 种 Manifest 判别联合成员之一完整的联合定义child、source、shared-state、effect、attachment、context、schedule、goal 等可参考 built-in-collections.md 中Manifest一节。从客户端读取附件readAttachment()读取字节同样通过客户端完成需要提供实体 URL 与附件 idconst bytes await client.readAttachment({ entityUrl: /horton/onboarding, id: attachment.id, })底层实现runtime-server-client.ts对{entityUrl}/attachments/{encodeURIComponent(id)}发起GET请求成功后将响应体转换为Uint8Array返回。需要注意的是调用方需要对该实体具备读权限——这是文档明确强调的访问控制前提未授权调用会收到 HTTP 错误。Handler 侧访问ctx.attachments实体 Handler 内部通过ctx.attachments访问附件无需走 HTTP 客户端。ctx.attachments是HandlerContext的一个属性handler-context.md由运行时的 context-factory.ts 创建。使用示例async handler(ctx) { const inputs ctx.attachments.list({ role: input }) const first inputs[0] if (!first) return const bytes await ctx.attachments.read(first.id) // Use bytes in a custom tool or external API call. }支持的操作方法作用list(filter?)列出 Manifest 支撑的附件可按role或subject过滤get(id)按 id 返回单条附件 Manifest 条目read(id)读取附件字节create(input)为当前实体创建新附件AttachmentsApi接口的精确签名见 types.tslist的过滤条件支持{ subject?, role? }read返回PromiseUint8Arraycreate接收AttachmentCreateInput并返回PromiseManifestAttachmentEntry。源码级实现细节list 的过滤逻辑context-factory.tslistAttachments从实体 DB 的manifests集合中筛出entry.kind attachment的行再按role与subject.type/subject.key精确匹配过滤。可见list返回的是 Manifest 元数据而非字节读取字节需进一步调用read(id)。读写接线process-wake.tsctx.attachments底层通过doCreateAttachment/doReadAttachment回调调用serverClient.createAttachment()与serverClient.readAttachment()即 Handler 侧与客户端侧最终复用同一套服务端附件路由。运行时集成ctx.attachments正是运行时用来执行图片/文件上下文 Hydrate 的入口见下文也允许自定义 Handler 或工具检查用户上传的文件。Subjects 与 Roles附件如何挂到时间线上subject把附件关联到它所属的时间线对象role则标明方向。文档给出两张核心表格Subject 类型与典型用途Subject 类型典型用途inbox用户上传的输入附着在某条消息上run与一次 Agent 运行关联的产物text链接到生成文本的文件tool_call工具的输入或输出产物context持久化的上下文素材Role 语义Role含义input通常由用户或宿主应用提供如上传的截图output通常由 Handler 或工具创建如生成的报告、渲染结果在类型层面subject.type被严格限定为inbox | run | text | tool_call | context五个字面量见 entity-schema.ts同时作为list()过滤条件时也按此语义精确匹配。设计意图很清晰附件不是孤立文件而是时间线叙事的一部分——UI 可以按 subject 找到这条消息带了哪些文件工具也可以按 role 区分输入给我的素材与我产出的结果。图片进入 Agent 上下文Hydrate 与护栏机制当图片附件关联到 inbox 消息时运行时可以把受支持的图片输入水合hydrate进模型消息让多模态模型直接看到图片内容。文档强调了两个要点UI 能力协商UI 应当对不声明支持图片输入的模型隐藏图片上传控件避免上传后无法消费上下文有界图片水合采用最新优先 字节/数量护栏newest-first byte/count guardrails大图或旧图可能保持为附件描述符attachment descriptor而不是内联模型内容从而控制上下文窗口开销。源码中的具体实现在 context-factory.ts 中selectHydratableImageAttachmentIds()完整实现了这一策略从最新消息开始倒序遍历newest-first保证优先水合最近的图片每个type attachment的内容块通过attachmentsApi.get(block.id)取 Manifest 行只有同时满足status complete且mimeType.startsWith(image/)才候选受两个运行时常量约束已选数量达到MAX_HYDRATED_IMAGE_ATTACHMENTS或累计字节数超过MAX_HYDRATED_IMAGE_ATTACHMENT_BYTES时跳过后续图片即文档所述 byte/count guardrails未入选的图片仍保留附件描述符形式——context-factory.ts 中的attachmentDescriptor()会生成形如filename, typemimeType, sizebyteLength的描述文本让模型知道存在该附件但看不到内联字节。随后hydrateAttachmentBlocks()同文件 L659 起遍历消息内容块将选中的附件 id 替换为 base64 内联图片数据最终由agent.run()前的消息组装流程调用L1051/L1055。这也解释了为什么文档要求 UI 感知模型能力是否真的能看到图片取决于模型是否被声明支持以及图片是否通过护栏筛选。失败与回滚附件上传与消息发送的独立性文档特别提醒附件上传可能独立于消息发送失败。这是异步系统中常见的部分成功问题文档给出了两种处理策略回滚已上传附件若 UI 流程中引用这些附件的发送操作失败则应回滚删除已上传的附件避免留下孤儿文件显式留下 failed 行当失败需要对实体可见时则保留一条status: failed的 Manifest 行error字段记录失败原因让实体时间线如实呈现这次失败。从数据模型看这正是status: pending | complete | failed三态存在的意义——pending表示上传进行中complete表示字节已落盘且可读failed表示最终失败且错误可追溯。UI 与 Handler 都应基于该状态字段做分支处理而不是假设上传调用成功 附件可用。相关 API 与进一步阅读附件功能与以下文档/能力联动密切handler-context.mdctx.attachments的完整 API 说明以及 HandlerContext 整体结构state、db、spawn、send、useAgent等built-in-collections.mdmanifests内置集合及其 9 种 Manifest 行的完整类型定义附件行是其中之一programmatic-runtime-client.mdcreateRuntimeServerClient()的配置项baseUrl、headers、writeTokenHeader、principalKey、track等以及附件之外的实体生命周期、消息、唤醒、调度等完整客户端能力。实践要点回顾上传用createAttachment()并理解pending/complete/failed三态读取用readAttachment()需实体读权限Handler 内统一走ctx.attachments.list/get/read/create用subject把附件挂到具体时间线对象、用role区分输入输出图片进入上下文遵循最新优先的字节/数量护栏UI 需按模型能力隐藏上传入口消息发送失败时按场景选择回滚附件或保留failed行。【免费下载链接】electricThe agent platform built on sync.项目地址: https://gitcode.com/GitHub_Trending/el/electric创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考