Agent Content Protocol: 一种基于 RFC 2046 的内容封装规范
2026-08-22
Agent Content Protocol: 一种基于 RFC 2046 的内容封装规范
版本:0.5.0-draft
1. 协议定位与核心宣言
1.1 定位
ACP 定义了一种可互操作的 Agent 消息内容信封。它规定了如何将一个 JSON 控制文档与零个或多个 MIME 内容部分组合在同一报文中。
ACP 不定义 Agent 语义(tool calling、意图识别)、执行语义(重试、超时、流式传输)或传输语义(HTTP 方法、路由、认证)。这些全部属于上层协议或应用层约定。ACP 仅解决一个问题:如何让结构化控制数据与原生内容数据在标准 MIME 框架下安全、高效地共存。
1.2 核心设计宣言
控制数据保持 JSON 结构化;内容数据保持其原生 MIME 表示;两者通过 Content-Disposition 的name参数连接。
传统方案将二进制嵌入 JSON(base64 / data URI),导致编码膨胀、解析负担、类型丢失、调试困难。ACP 让内容以原始字节存在于独立的 MIME part 中,JSON 仅持有引用指针。这是"零转换"设计的本质。
1.3 为什么不使用 base64
base64 不应成为默认封装方式。当内容本身已有成熟的 MIME 表示时,重复编码带来约 33% 体积膨胀、JSON parser 处理巨大字符串的压力、类型信息丢失、大文件无法流式处理、调试工具不可读等问题。MIME part 天然解决所有这些问题。
备注:ACP 并不禁止 base64。在 JSON 表示(application/json)中,上层协议完全可以将内容以 base64 字符串内嵌在 JSON 字段内——这依然是合法的 ACP 消息。ACP 仅提供一种更优的 multipart 替代方案,而非排斥 base64 的使用场景。
1.4 为什么选择 multipart/form-data
RFC 2046 定义了多种 multipart 子类型。ACP 选择multipart/form-data而非multipart/mixed或multipart/related,理由如下:
- 生态兼容性:主流 Web 框架(Express/FastAPI/Spring/Gin)、HTTP 客户端(axios/fetch/curl/requests)、网关(Nginx/Envoy/Kong)、浏览器原生 FormData API 对
multipart/form-data的支持是一等公民,开箱即用。multipart/mixed在多数场景需开发者自行实现解析器。 - 语义适配:
form-data的语义是"一组命名键值对,值为文件或文本",与 ACP "JSON 控制文档 + 若干命名内容载荷"的模型高度吻合。每个内容部分可通过name参数标识,便于中间件按名称提取。 - 引用机制兼容:
multipart/form-data的Content-Disposition: form-data; name=...参数天然提供命名空间,可直接作为 ACP 的引用锚点。
2. 核心不变量
以下三条是整个协议的公理,所有后续规则均由此推导:
- 单一控制文档:每条 ACP 消息恰好包含一个
application/json控制文档。 - 内容引用绑定:每个参与消息语义的内容部分必须拥有唯一的
name参数,且被控制文档显式引用。 - 传输无关性:ACP 是内容封装规范,不绑定任何特定传输协议。HTTP、SMTP、消息队列、WebSocket 均可承载。
3. 消息表示形式
ACP 消息有两种序列化表示,语义完全等价:
| 表示形式 | Content-Type | 结构 | 适用场景 |
|---|---|---|---|
| JSON 表示 | application/json | 单个 JSON 文档 | 无内容载荷时的简化形式;或内容以 base64 内嵌于 JSON 字段时 |
| Multipart 表示 | multipart/form-data | JSON 控制文档 + 0…N 内容部分 | 通用形式,含或不含载荷 |
关键说明:
- JSON 表示与"仅含 JSON 的 multipart 表示"在语义上完全等价。
- 接收方必须同等处理两种形式。
- 发送方可根据接收方能力或自身偏好任选其一。
- 这不是三个模型,而是同一模型的两种序列化选择。
- base64 内嵌:在 JSON 表示中,上层协议可将二进制内容以 base64 编码后内嵌在任意 JSON 字段中。ACP 协议层对此无限制,也不提供特殊语义——base64 字符串就是普通 JSON 字符串值。当需要引用外部 MIME part 时,才使用
$ref机制。
4. Multipart 表示的严格解析规则
4.1 第一个 Part 必须是 JSON
- 第一个 part 的 media type 必须是
application/json。 - 比较规则:提取 Content-Type 的 media type 部分(忽略参数)。即
application/json; charset=utf-8视为合法。 - 第一个 part 的
Content-Disposition中name参数建议为_acp_control,但不强制。 - 第一个 part 不需要被
$ref引用(它是控制文档本身)。 - 若第一个 part 不是
application/json,接收方必须拒绝该消息。
4.2 后续 Part 为内容载荷
- Part 2…N 是内容载荷,可使用任何合法的 MIME media type。
- ACP 不根据内容是文本还是二进制赋予不同语义。
text/plain、text/csv、application/xml、image/png、application/pdf等均作为平等的内容载荷对待。 - 内容类型完全由 MIME 自身负责,ACP 不做二次分类。
- 每个内容部分必须携带
Content-Disposition: form-data; name="...",其name参数作为该 part 的全局唯一标识符,供 JSON 中的$ref引用。
4.3 字符集与编码声明
- JSON 控制文档默认使用 UTF-8 编码。若显式声明
charset参数,以声明值为准。 - 内容载荷的字符集处理完全遵循 RFC 2046 和 RFC 6657 的既有规则。
text/*类型的内容载荷若无charset参数,接收方应按 RFC 6657 默认规则处理(通常为 US-ASCII 或 UTF-8,取决于具体 media type 注册)。- ACP 不做任何额外的字符集推断或自动检测。编码责任完全归属于 MIME 层。
4.4name参数规则
ACP 使用Content-Disposition: form-data; name="..."中的name参数作为内容部分的标识符和引用锚点。
ACP 施加以下约束:
- 存在性:每个被 JSON 引用的内容部分必须携带
Content-Disposition: form-data; name="..."。 - 唯一性:在同一消息信封内,
name参数必须唯一。出现重复name时,接收方必须拒绝该消息。 - 作用域:
name的作用域严格限定在当前消息信封内。不允许跨消息引用。 - 匹配:JSON 中的
$ref值必须与某个内容部分的name参数精确匹配(区分大小写)。 - 自引用禁止:
name不得指向 JSON 控制文档本身(即不应与控制文档的name值冲突)。 - 嵌套禁止:
name不得指向另一个 multipart 容器。ACP 不支持递归嵌套引用。 - 字符限制:
name参数值应仅使用 ASCII 字母、数字、连字符、下划线和句点,避免特殊字符和空格,以确保跨平台兼容性。
name生成推荐策略
在分布式 Agent 系统中,多个 Agent 可能并行构造消息并合并,name冲突风险真实存在。推荐以下生成模式:
首选:UUIDv7
0192a3b4-c5d6-7e8f-9a0b-1c2d3e4f5a6b优势:时间有序、全局唯一概率极高、仅含连字符和字母数字、无需协调。
备选:{agent-id}-{timestamp-ms}-{random}
planner-alpha-1724338200000-x9k2m优势:人类可读、便于日志关联。注意 agent-id 和 random 部分必须仅含 ASCII 字母、数字、连字符、下划线。
4.5 内容引用机制
内容引用是从 JSON 控制文档到恰好一个 MIME 内容部分的逻辑指针,通过{ "$ref": "<name>" }对象建立绑定。
显式引用对象
ACP 采用严格校验 + 显式声明策略:只有包裹在{ "$ref": "..." }对象中的值才会被解析为引用。纯字符串(如"image-001")永远被视为普通文本,不产生引用语义。
{"payload":{"instruction":"分析这张图片","image":{"$ref":"image-001"}}}机制:解析器在遍历 JSON 时,遇到键值为{ "$ref": "<name>" }的对象,即视为对该name对应的内容部分的引用。该对象所在位置的业务语义由上层协议定义。
优势:
- 零歧义:字符串值与引用值严格区分,不存在"猜测"字符串是否为引用的场景。
- 零冲突:普通文本中即使出现类似引用的字符串,也不会被误解析。
- 无需转义:引用是结构化的,不是字符串前缀或特殊格式。
- 认知成本低:与 JSON Schema / OpenAPI 的
$ref惯例一致,开发者无需学习新的引用语法。
代价:JSON 结构稍显冗长,但换来的是不可比拟的正确性和互操作性。
业务引用字段
协议不规定 JSON 中承载业务引用的具体字段名。上层可使用image、attachment、input、result、payload等任意命名。只要字段值为{ "$ref": "<name>" }对象,即构成有效引用。
引用解析规则
- 深度遍历:解析器必须对 JSON 控制文档进行深度遍历,发现所有
{ "$ref": "..." }对象。 - 值校验:
$ref的值必须是合法的name字符串(符合 §4.4 的字符限制)。 - 存在性校验:每个被引用的
name必须在当前消息的某个内容部分的Content-Disposition中存在。引用不存在的name为致命错误。 - 重复引用:JSON 中多个位置可引用同一
name,这是允许的。 - 非对象值:任何非
{ "$ref": "..." }形式的值(包括纯字符串name、数组、其他对象)均不产生引用语义。
4.6 未引用内容的处理
未被 JSON 控制文档中任何{ "$ref": "..." }对象引用的内容部分不得影响消息语义。接收方可将其暴露用于诊断,但不得将其解释为应用载荷的一部分。
5. 错误处理模型
ACP 在协议层保持严格验证,同时为上层提供降级指导。
5.1 致命错误(必须拒绝整条消息)
| 错误类型 | 说明 |
|---|---|
| Part 1 非 application/json | 控制文档缺失或格式错误 |
重复name参数 | 引用歧义,无法确定绑定目标 |
$ref引用不存在的name | 消息不完整,语义无法成立 |
$ref值语法非法 | 引用格式非法(含非法字符等) |
name指向 JSON part | 违反自引用禁止规则 |
name指向嵌套 multipart | 违反嵌套禁止规则 |
| MIME header 注入 / boundary 非法 | 安全风险 |
| 超出实现方声明的尺寸/数量限制 | 资源保护 |
5.2 可恢复警告(记录并继续)
| 情况 | 处理方式 |
|---|---|
| 未引用的内容部分 | 不影响语义,可丢弃或记日志 |
| text/* 内容缺少 charset 参数 | 按 RFC 6657 默认规则处理 |
5.3 上层降级指导
ACP 本身不提供"部分成功"语义。但在容错场景中,上层协议可定义降级策略:
- 收到 ACP 致命错误时,上层可选择:提取 JSON 控制文档(若可解析)并忽略内容载荷,转为纯文本模式继续处理。
- 此降级行为完全由上层定义,不属于 ACP 协议范畴。
- 降级后的消息不再是合法的 ACP 消息,应在上层日志中标注。
6. 安全模型与约束
6.1 引用安全
| 场景 | 规定行为 |
|---|---|
重复name参数 | 拒绝消息 |
$ref引用不存在的name | 拒绝消息 |
$ref值语法非法 | 拒绝消息 |
name指向 JSON part | 拒绝消息 |
name指向嵌套 multipart | 拒绝消息 |
跨消息name引用 | 拒绝消息 |
JSON 多处$ref引用同一name | 允许 |
| 未引用的内容部分 | 不影响语义,可诊断暴露 |
纯字符串形式的name | 视为普通文本,不产生引用 |
6.2 尺寸与数量限制
ACP 本身不设硬性上限,但强烈建议实现方设定以下防护阈值:
| 参数 | 推荐默认值 | 理由 |
|---|---|---|
| 最大内容部分数量 | 64 | 防止 DoS / 解析爆炸 |
| 单 part 最大大小 | 100 MB | 防止内存耗尽 |
| 消息总大小 | 500 MB | 与网关/代理限制对齐 |
| boundary 最大长度 | 70 字符 | RFC 2046 上限 |
| JSON 控制文档最大大小 | 1 MB | 控制信令不应过大 |
实现方应在文档中声明自身限制。超出限制的消息应被拒绝并返回明确错误。
6.3 Header 注入防护
- 所有 MIME header 值必须进行合法性校验。
- boundary 参数不得包含换行符、空字节或控制字符。
name参数值应符合 ASCII 安全字符集。- 不符合语法的 header 应导致消息被拒绝。
7. 报文示例
7.1 JSON 表示(无内容载荷)
POST /messages HTTP/1.1 Host: gateway.example.com Content-Type: application/json { "id": "msg_001", "from": "planner-alpha", "to": "reasoner-beta", "payload": { "text": "请总结以下要点", "items": ["要点1", "要点2"] } }7.2 JSON 表示(含 base64 内嵌内容)
POST /messages HTTP/1.1 Host: gateway.example.com Content-Type: application/json { "id": "msg_001b", "from": "planner-alpha", "to": "vision-worker-01", "payload": { "instruction": "识别图片中的所有文字", "image": { "mime_type": "image/png", "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" } } }注:此形式是合法的 ACP JSON 表示。base64 内嵌由上层协议定义字段结构,ACP 协议层不做特殊处理。当需要避免 base64 膨胀时,应使用 multipart 表示。
7.3 Multipart 表示(含内容载荷)
POST /messages HTTP/1.1 Host: gateway.example.com Content-Type: multipart/form-data; boundary=ACP-B2 --ACP-B2 Content-Type: application/json Content-Disposition: form-data; name="_acp_control" { "id": "msg_002", "from": "planner-alpha", "to": "vision-worker-01", "payload": { "instruction": "识别图片中的所有文字", "image": { "$ref": "input_image" } }, "refs": [ {"name": "input_image", "content_ref": { "$ref": "input_image" }}, {"name": "reference_doc", "content_ref": { "$ref": "reference_doc" }} ] } --ACP-B2 Content-Type: image/png Content-Disposition: form-data; name="input_image" <二进制 PNG 数据> --ACP-B2 Content-Type: application/pdf Content-Disposition: form-data; name="reference_doc" <二进制 PDF 数据> --ACP-B2--7.4 请求/响应中的引用示例
以下示例展示上层协议如何在业务数据中使用$ref:
请求(Tool Calling):
{"requests":[{"id":"1","action":"read","arguments":{"filename":"a.txt","line":10}},{"id":"2","action":"read","arguments":{"filename":"b.txt","line":10},"after":["1"]}]}响应(结果引用):
{"responses":[{"id":"1","operate":"read","status":"ok","result":{"$ref":"a.txt"}},{"id":"2","operate":"read","status":"ok","result":{"$ref":"b.txt"}}]}7.5 Multipart 表示(仅 JSON,无载荷)
POST /messages HTTP/1.1 Host: gateway.example.com Content-Type: multipart/form-data; boundary=ACP-B3 --ACP-B3 Content-Type: application/json Content-Disposition: form-data; name="_acp_control" { "id": "msg_003", "from": "planner-alpha", "to": "reasoner-beta", "payload": {"text": "hello"} } --ACP-B3--此形式与 §7.1 的 JSON 表示语义完全等价。
8. 与现有 Agent 生态的适配指南
主流 Agent API(OpenAI、Anthropic、Google 等)目前普遍使用 base64 内嵌方式。ACP 提供双向适配路径。
8.1 ACP → 厂商 API(出站适配)
当需要将 ACP 消息发送给仅支持 base64 的 API 时:
- 解析 ACP multipart 消息,提取 JSON 控制文档和所有内容部分。
- 对 JSON 进行深度遍历,定位所有
{ "$ref": "<name>" }对象。 - 对每个被引用的内容部分:读取原始字节,执行 base64 编码,获取 Content-Type。
- 将编码后的数据注入厂商 API 要求的字段结构中(如 OpenAI 的
image_url.url: "data:{mime};base64,{data}"),并替换原$ref对象为厂商要求的内联格式。 - 发送转换后的请求。
8.2 厂商 API → ACP(入站适配)
当接收到厂商 API 返回的 base64 内容时:
- 从响应中提取 base64 数据和对应的 MIME type。
- 解码 base64 得到原始字节。
- 生成唯一的
name(推荐使用 UUIDv7)。 - 构造 ACP multipart 消息:JSON 控制文档 + 解码后的内容部分。
- 在 JSON 中需要引用内容的位置,插入
{ "$ref": "<name>" }对象。
8.3 注意事项
- 适配层是有损转换:ACP 的零传输开销优势在 base64 回退时丧失。
- 大文件场景下,适配层应考虑流式 base64 编解码,避免全量加载。
- 适配层应记录转换日志,便于排查问题。
- 长期目标是推动厂商原生支持 multipart/form-data,适配层作为过渡方案。
9. 与上层协议的关系
ACP 是封装层,不是语义层。上层协议在 JSON 控制文档中自由定义 tool calling 格式、动作类型、错误码、重试策略、流式分片、会话关联、能力协商、认证令牌等。ACP 仅保证:无论 JSON 内部结构如何变化,其与内容载荷的组合、引用、传输机制始终稳定且互操作。
上层协议在使用$ref时应注意:
$ref是 ACP 的保留键,在 JSON 控制文档的任何位置出现{ "$ref": "..." }对象,均会被 ACP 层解析为内容引用。- 上层协议不应将
$ref用于非引用语义,以避免与 ACP 解析器冲突。
附录 A:设计决策记录
| 决策 | 理由 |
|---|---|
| 使用 multipart/form-data 而非 mixed | 一等公民级别的生态支持,name 参数天然适配命名载荷 |
| 不使用自定义 MIME 类型 | 最大化基础设施兼容性 |
| 不内置 intent/action 语义 | 保持封装层纯粹性 |
使用Content-Disposition的name作为引用锚点 | 直接利用 form-data 已有机制,无需额外的 Content-ID header,简化解析链路 |
name值使用 ASCII 安全字符集 | 确保跨平台、跨框架兼容性,避免编码歧义 |
采用{ "$ref": "..." }显式引用对象 | 零歧义、零冲突、与 JSON Schema / OpenAPI 惯例一致,开发者认知成本低 |
| 区分致命错误与可恢复警告 | 协议层严格,上层可灵活降级 |
| 不做字符集推断 | 编码责任归属 MIME 层,避免歧义 |
| 允许 JSON 表示中 base64 内嵌 | ACP 不排斥 base64,仅提供更优的 multipart 替代方案 |
| 提供厂商 API 适配指南 | 降低采用门槛,承认 base64 现状 |
附录 B:术语表
| 术语 | 定义 |
|---|---|
| 控制文档 | 消息中恰好一个 application/json part,承载上层语义 |
| 内容部分 | 消息中除控制文档外的 MIME part,承载原生内容数据 |
| 内容引用 | JSON 控制文档中通过{ "$ref": "<name>" }对象指向某个内容部分name的逻辑指针 |
| 消息信封 | 一条完整的 ACP 消息,无论是 JSON 表示还是 multipart 表示 |
| 零转换 | 内容数据以原始 MIME 表示进入消息,无需 base64 等二次编码 |
| 致命错误 | 导致整条消息被拒绝的协议违规 |
| 可恢复警告 | 不影响消息语义、可记录并继续处理的非致命情况 |