三方OpenAPI对接指南

三方OpenAPI对接指南

麦芽 Agent 智能体第三方 Open API 对接文档(v1.6.0),含会话初始化、需求录入、任务查询、任务终止、文件下载与结果回调等接口的完整对接说明。

最近更新:2026.08.31 17:31

<style>table{border-collapse:collapse;width:100%;margin:8px 0;font-size:14px}th,td{border:1px solid #d0d0d0;padding:6px 10px;text-align:left;vertical-align:top}thead th{background:#f0f0f0;font-weight:700}pre{background:#f6f8fa;padding:10px 14px;border-radius:6px;overflow-x:auto;font-size:13px;line-height:1.5}pre code{font-family:Consolas,Menlo,monospace;white-space:pre-wrap;word-break:break-all;background:none;padding:0}code{font-family:Consolas,Menlo,monospace;background:#f6f8fa;padding:1px 4px;border-radius:3px;font-size:13px}blockquote{margin:8px 0;padding:6px 14px;border-left:4px solid #e0e0e0;color:#555;background:#fafafa}</style><h1>Agent智能体 Open API 对接文档(第三方)</h1><table><thead><tr><th><strong>项</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>文档版本</td><td>v1.6.0</td></tr><tr><td>更新日期</td><td>2026-08-12</td></tr><tr><td>适用产品</td><td>麦芽Agent智能体 Open API</td></tr><tr><td>文档性质</td><td><strong>正式对接文档</strong>(与当前线上实现一致)</td></tr></tbody></table><h2>1. 概述</h2><h3>1.1 能力说明</h3><p>第三方业务系统可通过本 Open API,向已开通的Agent智能体助手提交用户咨询(<strong>支持附件 URL 传入</strong>)、查询任务进度与结果、下载 AI 产出文件,并接收平台终态回调。</p><table><thead><tr><th><strong>#</strong></th><th><strong>接口</strong></th><th><strong>方向</strong></th><th><strong>方法</strong></th><th><strong>路径 / 地址</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>0</td><td>会话初始化</td><td>三方 → 平台</td><td><code>POST</code></td><td><code>/api/open/session/init</code></td><td><strong>可选</strong>。提前创建 <code>session_code</code> 并异步预热 CLI,缩短首问冷启动;不创建任务</td></tr><tr><td>1</td><td>需求录入</td><td>三方 → 平台</td><td><code>POST</code></td><td><code>/api/open/demand/create</code></td><td>创建客服任务;默认异步 JSON,可选 <code>sync=true</code> SSE;可用 <code>stream_output</code> 控制文本是否增量落库</td></tr><tr><td>2</td><td>任务查询</td><td>三方 → 平台</td><td><code>POST</code></td><td><code>/api/open/demand/query</code></td><td>查询任务状态、动作流、成果</td></tr><tr><td>3</td><td>任务终止</td><td>三方 → 平台</td><td><code>POST</code></td><td><code>/api/open/demand/stop</code></td><td>终止未完成任务;终态后触发回调(与 query 一致)</td></tr><tr><td>4</td><td>文件下载</td><td>三方 → 平台</td><td><code>GET</code></td><td><code>/api/open/demand/file/download</code></td><td>凭短期 token 下载产出文件</td></tr><tr><td>5</td><td>结果回调</td><td>平台 → 三方</td><td><code>POST</code></td><td>贵方配置的 <code>callback_url</code></td><td>任务终态后主动推送 TaskSnapshot</td></tr></tbody></table><blockquote>接口 0~4 由贵方调用平台;接口 5 由平台调用贵方服务。六者共同构成完整对接面。</blockquote><blockquote>不调用会话初始化仍可直接 <code>demand/create</code>(首问可能冷启动);调用 init 后首问须携带返回的 <code>session_code</code>。</blockquote><h3>1.2 接入凭证</h3><p>开通接入后,平台向贵方提供:</p><table><thead><tr><th><strong>凭证</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>third_party_id</code></td><td>接入标识(写入请求体,须与 ACCESSKEY 归属一致)</td></tr><tr><td><code>ACCESSKEY</code></td><td>访问密钥,放在请求头 <code>X-Access-Key</code></td></tr><tr><td><code>SECRETKEY</code></td><td>签名密钥,<strong>仅用于本地计算签名,禁止通过网络传输</strong></td></tr><tr><td>Base URL</td><td>形如 <code>https://{host}/api/open</code>(以实际开通环境为准)</td></tr></tbody></table><p>可选配置(由平台侧维护):</p><ul><li>IP 白名单</li><li>回调地址 <code>callback_url</code></li><li>内容长度、<strong>是否允许附件</strong>、附件数量上限等(<code>message_input_config</code>)</li><li>测试环境是否允许 HTTP 附件 URL(<code>other_config.attachment_allow_http</code>)</li></ul><p><strong>附件能力说明:</strong></p><table><thead><tr><th><strong>方向</strong></th><th><strong>方式</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>三方 → 平台(输入)</td><td><code>create.attachments[].file_url</code></td><td>录入时由平台拉取并挂载;须开通 <code>allow_attachments=true</code></td></tr><tr><td>平台 → 三方(输出)</td><td><code>query/callback</code> 的 <code>outputs[].view_url</code></td><td>AI 生成文件后,凭 token GET 下载;与输入附件无关</td></tr></tbody></table><h3>1.3 会话与任务</h3><table><thead><tr><th><strong>概念</strong></th><th><strong>字段</strong></th><th><strong>含义</strong></th></tr></thead><tbody><tr><td>会话</td><td><code>session_code</code></td><td>多轮对话容器;不传则新建,传入则续聊</td></tr><tr><td>任务</td><td><code>demand_id</code></td><td><strong>单次提交</strong>对应的执行单元;查询/下载/回调的主键之一</td></tr></tbody></table><p>规则:</p><ul><li><strong>每次</strong>调用录入接口都会新建一条任务(<code>demand_id</code>)。</li><li><strong>不传</strong> <code>session_code</code> → 新建会话(首问可能冷启动)。</li><li><strong>传入</strong>有效 <code>session_code</code> → 在同一会话下续提新任务(须与 <code>biz_user_id</code> 匹配)。</li><li><strong>推荐</strong>:打开客服页时先调 <code>POST /session/init</code> 拿到 <code>session_code</code> 并后台预热;用户首问 <code>demand/create</code> 携带该码,可复用已启动的 CLI。<code>biz_session_id</code> 可做三方侧幂等键。</li></ul><pre><code>(可选)POST /session/init → session_code + 后台预热 session_code ├── demand_id = 10001 (首问,传入 init 返回的 session_code) └── demand_id = 10002 (追问,传入同一 session_code)</code></pre><h2>2. 通用约定</h2><h3>2.1 协议与编码</h3><table><thead><tr><th><strong>项</strong></th><th><strong>约定</strong></th></tr></thead><tbody><tr><td>协议</td><td>HTTPS(测试环境可能为 HTTP,以开通说明为准)</td></tr><tr><td>字符编码</td><td>UTF-8</td></tr><tr><td>请求体</td><td>JSON(<code>Content-Type: application/json; charset=utf-8</code>)</td></tr><tr><td>成功业务码</td><td>响应体 <code>code = 200</code></td></tr></tbody></table><h3>2.2 统一响应结构</h3><p><strong>JSON 接口</strong>(会话初始化 / 录入 / 查询)成功与失败均返回:</p><pre><code>{ "code": 200, "message": "success", "data": {} }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>code</code></td><td>int</td><td>业务码;成功为 <code>200</code>,失败见 §10</td></tr><tr><td><code>message</code></td><td>string</td><td>说明信息</td></tr><tr><td><code>data</code></td><td>object / null</td><td>业务数据;失败时可能为 <code>null</code> 或附加信息</td></tr></tbody></table><p>HTTP 状态码与业务码对应关系见 §10。</p><p><strong>文件下载接口</strong>成功时直接返回文件二进制流(非上述 JSON 包装);失败时返回纯文本错误信息。</p><h3>2.3 时钟同步</h3><p>签名依赖请求头中的 Unix 毫秒时间戳。请保证调用方服务器与标准时间偏差不超过 <strong>±300 秒(5 分钟)</strong>,否则返回 <code>40004</code>。</p><h2>3. 鉴权与签名(HMAC-SHA256)</h2><blockquote>会话初始化、录入、查询、终止接口必须按本节签名。</blockquote><blockquote>文件下载接口使用查询结果中的短期 <code>token</code>,<strong>无需</strong>再带 HMAC 头(见 §8)。</blockquote><h3>3.1 请求头</h3><table><thead><tr><th><strong>Header</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>Content-Type</code></td><td>是</td><td><code>application/json; charset=utf-8</code></td></tr><tr><td><code>X-Access-Key</code></td><td>是</td><td>ACCESSKEY</td></tr><tr><td><code>X-Timestamp</code></td><td>是</td><td>Unix 毫秒时间戳字符串,如 <code>1722757920123</code></td></tr><tr><td><code>X-Nonce</code></td><td>是</td><td>随机串,长度 <strong>16~64</strong>;同一 ACCESSKEY 下短期内不可重复</td></tr><tr><td><code>X-Signature</code></td><td>是</td><td>按 §3.2 计算的十六进制签名(小写)</td></tr></tbody></table><h3>3.2 签名算法</h3><p>算法:<strong>HMAC-SHA256</strong></p><ol start="1"><li>取 <strong>原始请求体字节</strong>(<code>raw body</code>)。空 body 按空字节序列处理。</li></ol><ul><li>必须对<strong>即将发送的原始字节</strong>做哈希;不要先 parse 再重新序列化(字段顺序、空格变化会导致验签失败)。</li></ul><ol start="2"><li>计算 Body 摘要:</li></ol><pre><code>body_hash = HEX( SHA256(raw_body) )</code></pre><ol start="3"><li>构造规范串(Canonical String),共 6 行,行之间以 <strong>单个换行符 `\n`(LF,0x0A)</strong> 连接,<strong>末尾不加多余换行</strong>:</li></ol><pre><code>HTTP_METHOD PATH ACCESS_KEY TIMESTAMP NONCE BODY_HASH</code></pre><table><thead><tr><th><strong>行</strong></th><th><strong>取值规则</strong></th></tr></thead><tbody><tr><td><code>HTTP_METHOD</code></td><td>大写,如 <code>POST</code></td></tr><tr><td><code>PATH</code></td><td>对外公开路径,<strong>必须</strong>为 <code>/api/open/session/init</code>、<code>/api/open/demand/create</code>、<code>/api/open/demand/query</code> 或 <code>/api/open/demand/stop</code>(含 <code>/api</code> 前缀,<strong>不含</strong> query string)</td></tr><tr><td><code>ACCESS_KEY</code></td><td>与 <code>X-Access-Key</code> 相同</td></tr><tr><td><code>TIMESTAMP</code></td><td>与 <code>X-Timestamp</code> 相同</td></tr><tr><td><code>NONCE</code></td><td>与 <code>X-Nonce</code> 相同</td></tr><tr><td><code>BODY_HASH</code></td><td>步骤 2 的十六进制小写字符串</td></tr></tbody></table><ol start="4"><li>使用 SECRETKEY(UTF-8)对规范串做 HMAC-SHA256,输出十六进制小写字符串,写入 <code>X-Signature</code>:</li></ol><pre><code>signature = HEX( HMAC_SHA256(key=SECRETKEY, message=canonical_string) )</code></pre><h3>3.3 签名示例</h3><p>假设:</p><pre><code>ACCESSKEY = AK_demo_001 SECRETKEY = SK_demo_secret METHOD = POST PATH = /api/open/demand/create TIMESTAMP = 1722757920123 NONCE = a1b2c3d4e5f6789012345678abcdef01 BODY = {"third_party_id":"demo-tp","biz_user_id":"u1","content":"你好"}</code></pre><ol start="1"><li><code>body_hash = SHA256(UTF8(BODY))</code> 的 hex</li><li>Canonical:</li></ol><pre><code>POST /api/open/demand/create AK_demo_001 1722757920123 a1b2c3d4e5f6789012345678abcdef01 &lt;body_hash&gt;</code></pre><ol start="3"><li><code>X-Signature = HMAC_SHA256(SK_demo_secret, canonical)</code> 的 hex</li></ol><h3>3.4 代码示例(Python)</h3><pre><code>import hashlib import hmac import json import time import uuid import requests</code></pre><pre><code>ACCESS_KEY = "AK_xxx" SECRET_KEY = "SK_xxx" BASE = "https://{host}/api/open"</code></pre><pre><code>def sign(method: str, path: str, access_key: str, timestamp: str, nonce: str, raw_body: bytes) -&gt; str: body_hash = hashlib.sha256(raw_body or b"").hexdigest() canonical = "\n".join([ method.upper(), path, access_key, timestamp, nonce, body_hash, ]) return hmac.new( SECRET_KEY.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256, ).hexdigest()</code></pre><pre><code>def post_open(path_suffix: str, body: dict): # path_suffix 例: "/demand/create" path = f"/api/open{path_suffix}" raw_body = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode("utf-8") ts = str(int(time.time() * 1000)) nonce = uuid.uuid4().hex # 32 位,满足 16~64 signature = sign("POST", path, ACCESS_KEY, ts, nonce, raw_body) headers = { "Content-Type": "application/json; charset=utf-8", "X-Access-Key": ACCESS_KEY, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": signature, } url = f"{BASE.rstrip('/')}{path_suffix}" return requests.post(url, data=raw_body, headers=headers, timeout=30)</code></pre><h3>3.5 常见验签失败原因</h3><table><thead><tr><th><strong>现象</strong></th><th><strong>常见原因</strong></th></tr></thead><tbody><tr><td><code>40003</code> 签名错误</td><td>PATH 写成了 <code>/open/...</code> 或带了域名/query;body 被重新格式化;SECRETKEY 错误</td></tr><tr><td><code>40004</code> 请求已过期</td><td>客户端时钟偏差超过 5 分钟;Timestamp 误用秒而非毫秒</td></tr><tr><td><code>40005</code> 重复请求</td><td>同一 ACCESSKEY 下 Nonce 被重放(Nonce TTL 约 5 分钟)</td></tr><tr><td><code>40002</code> ACCESSKEY 无效</td><td>密钥停用、未生效或已过期</td></tr><tr><td><code>40006</code> IP 不在白名单</td><td>出口 IP 未登记</td></tr><tr><td><code>40001</code> 接入标识不一致</td><td>Body 中 <code>third_party_id</code> 与 ACCESSKEY 所属接入不匹配</td></tr></tbody></table><h2>4. 接口〇:会话初始化(可选)</h2><p>提前创建会话并<strong>异步预热</strong>客服 CLI。<strong>不创建</strong> <code>demand</code>。适合在打开客服页、用户尚未输入时调用,缩短首问等待。</p><ul><li><strong>URL</strong>:<code>POST /api/open/session/init</code></li><li><strong>鉴权</strong>:§3 HMAC 签名(PATH=<code>/api/open/session/init</code>)</li></ul><h3>4.1 请求体</h3><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "biz_session_id": "optional-idempotency-key", "display_name": "张三", "title": "退款咨询", "stream_output": true, "ext": {} }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>third_party_id</code></td><td>string</td><td>是</td><td>接入标识</td></tr><tr><td><code>biz_user_id</code></td><td>string</td><td>是</td><td>三方用户 ID</td></tr><tr><td><code>biz_session_id</code></td><td>string</td><td>否</td><td>三方会话幂等键(≤64)。同一接入下重复调用复用同一 <code>session_code</code> 并再次触发预热</td></tr><tr><td><code>display_name</code></td><td>string</td><td>否</td><td>展示名</td></tr><tr><td><code>title</code></td><td>string</td><td>否</td><td>会话标题</td></tr><tr><td><code>stream_output</code></td><td>boolean</td><td>否</td><td>默认 <code>true</code>。须与后续首问 <code>demand/create</code> 保持一致(进预热指纹)。<strong>幂等重试</strong>未传时继承已有会话值;首问 <code>create</code> 未传时亦沿用 init 落库值</td></tr><tr><td><code>ext</code></td><td>object</td><td>否</td><td>扩展字段</td></tr></tbody></table><h3>4.2 成功响应</h3><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260810120000-x9y8z7w6", "prewarm_status": "preparing", "stream_output": true, "created": true } }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>session_code</code></td><td>后续 <code>demand/create</code> / 续聊须携带</td></tr><tr><td><code>prewarm_status</code></td><td><code>preparing</code> / <code>ready</code> / <code>failed</code>(同进程瞬时状态;<code>preparing</code> 为常态,勿强依赖轮询)</td></tr><tr><td><code>stream_output</code></td><td>本次会话预热使用的流式开关</td></tr><tr><td><code>created</code></td><td><code>true</code>=新建会话;<code>false</code>=<code>biz_session_id</code> 命中已有会话</td></tr></tbody></table><h3>4.3 推荐时序</h3><pre><code>打开客服页 → POST /session/init → 拿到 session_code 用户输入 → POST /demand/create(带同一 session_code、建议相同 stream_output) 追问 → 继续带同一 session_code</code></pre><blockquote>预热为同进程 best-effort:失败不影响后续录入(自动冷启动)。多实例部署时需会话粘性/owner,否则首问仍可能冷启。</blockquote><h2>5. 接口一:需求录入</h2><p>创建一条Agent智能体任务。默认<strong>异步</strong>执行;可选 <code>sync=true</code> 走 <strong>SSE 同步流式</strong>。</p><ul><li><strong>URL</strong>:<code>POST /api/open/demand/create</code></li><li><strong>鉴权</strong>:§3 HMAC 签名</li></ul><h3>5.0 执行模式(<code>sync</code> / <code>stream_output</code>)</h3><table><thead><tr><th><code><strong>sync</strong></code></th><th><strong>响应</strong></th><th><strong>取结果方式</strong></th><th><strong>终态 callback</strong></th></tr></thead><tbody><tr><td>不传 / <code>false</code></td><td><code>application/json</code>(立即返回 <code>demand_id</code>)</td><td>三方轮询 <code>POST /demand/query</code></td><td>会推送(若配置了 <code>callback_url</code>)</td></tr><tr><td><code>true</code></td><td><code>text/event-stream</code>(同连接推流)</td><td>同一 POST 内收 <code>created</code>/<code>action</code>/<code>finished</code></td><td><strong>不推送</strong>(结果已由 SSE 交付)</td></tr><tr><td><code><strong>stream_output</strong></code></td><td><strong>含义</strong></td></tr><tr><td>不传 / <code>true</code>(默认)</td><td>Agent 文本按增量(delta)写入 action,SSE/<code>query</code> 可更快看到首字</td></tr><tr><td><code>false</code></td><td>Agent 关闭增量流式,文本整段落库后再可见(首字更晚,action 条数更少)</td></tr></tbody></table><blockquote><code>sync</code> / <code>stream_output</code> 必须为 JSON boolean。字符串 <code>"true"</code> 会返回 <code>40007</code>。</blockquote><blockquote>二者独立:<code>sync</code> 控制 HTTP 是否 SSE 同连接推送;<code>stream_output</code> 控制模型文本是否增量落库。</blockquote><blockquote>增加字段会改变 Body,须对<strong>新原始字节</strong>重新签名。</blockquote><blockquote>SSE 断开<strong>不会取消</strong>后台任务;请用首个 <code>created</code> 事件中的 <code>demand_id</code> + <code>session_code</code> 调用 query 兜底。</blockquote><blockquote>建议同步模式带上 <code>biz_demand_id</code>,便于断线重试对账(当前版本不自动幂等)。</blockquote><h3>5.1 请求体</h3><p><strong>新会话:</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "content": "我想咨询退款流程", "title": "退款咨询", "display_name": "张三", "biz_demand_id": "biz-order-001", "ext": { "order_id": "ORD-20260801" } }</code></pre><p><strong>续聊(传入上次返回的 `session_code`):</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "session_code": "CS-S-20260805101200-x9y8z7w6", "content": "那退款大概多久能到账?", "title": "退款咨询-追问" }</code></pre><p><strong>带附件(三方提供 HTTPS 临时下载链接):</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "content": "请阅读附件并总结退款条款", "attachments": [ { "file_url": "https://partner-cdn.example.com/tmp/contract.pdf", "file_name": "合同.pdf", "mime_type": "application/pdf" } ] }</code></pre><blockquote>平台会在录入时拉取 <code>file_url</code> 并注册为任务附件,AI 在工作区 <code>resources/</code> 目录读取。<strong>须</strong>在接入配置中开启 <code>message_input_config.allow_attachments=true</code>。单文件默认最大 <strong>20MB</strong>;数量以 <code>max_attachment_count</code> 为准(默认 5)。PDF 等二进制文件由 AI 通过 Skill / 命令行工具解析,<strong>请勿</strong>假设平台会原样转发文件内容。</blockquote><p><strong>附件相关接入配置示例(平台侧):</strong></p><pre><code>{ "message_input_config": { "allow_attachments": true, "max_attachment_count": 5, "max_content_length": 32000 }, "other_config": { "attachment_allow_http": false } }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>third_party_id</code></td><td>string</td><td>是</td><td>接入标识,须与 ACCESSKEY 一致</td></tr><tr><td><code>biz_user_id</code></td><td>string</td><td>是</td><td>贵方用户唯一 ID</td></tr><tr><td><code>content</code></td><td>string</td><td>是</td><td>本轮用户输入;默认最大长度 32000(以接入配置为准)</td></tr><tr><td><code>session_code</code></td><td>string</td><td>否</td><td>不传=新会话;传入=<code>/session/init</code> 返回或续聊码(须属于该 <code>biz_user_id</code>)</td></tr><tr><td><code>title</code></td><td>string</td><td>否</td><td>任务标题;缺省自动生成</td></tr><tr><td><code>display_name</code></td><td>string</td><td>否</td><td>用户展示名</td></tr><tr><td><code>biz_demand_id</code></td><td>string</td><td>否</td><td>贵方侧业务单号,最长 64</td></tr><tr><td><code>attachments</code></td><td>array</td><td>否</td><td>附件列表;受接入配置 <code>message_input_config</code> 约束</td></tr><tr><td><code>attachments[].file_url</code></td><td>string</td><td>条件</td><td>附件下载 URL(HTTPS;测试环境可在 <code>other_config.attachment_allow_http=true</code> 时允许 HTTP)</td></tr><tr><td><code>attachments[].file_name</code></td><td>string</td><td>否</td><td>文件名;缺省从 URL 推断</td></tr><tr><td><code>attachments[].mime_type</code></td><td>string</td><td>否</td><td>MIME 类型,可选</td></tr><tr><td><code>ext</code></td><td>object</td><td>否</td><td>扩展字段;平台会关联存储。另:<code>stream_output</code> 会合并写入任务侧 <code>ext_json</code> 供 Job 读取(顶层字段优先于 <code>ext.stream_output</code>)</td></tr><tr><td><code>sync</code></td><td>boolean</td><td>否</td><td>默认 <code>false</code>。<code>true</code> 时响应改为 SSE 同步流式(§5.3)</td></tr><tr><td><code>stream_output</code></td><td>boolean</td><td>否</td><td>默认 <code>true</code>。控制客服 Job 是否增量流式写入文本 action;与 <code>sync</code> 独立</td></tr></tbody></table><h3>5.2 成功响应(异步,<code>sync=false</code>)</h3><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345, "status": 0, "status_name": "pending", "created_at": "2026-08-05T09:12:00", "sync": false, "stream_output": true } }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>session_code</code></td><td>会话编码;续聊时带回</td></tr><tr><td><code>demand_id</code></td><td>本次任务 ID;查询/对账使用</td></tr><tr><td><code>status</code> / <code>status_name</code></td><td>初始一般为 <code>0</code> / <code>pending</code></td></tr><tr><td><code>created_at</code></td><td>创建时间(ISO 风格本地时间字符串)</td></tr><tr><td><code>sync</code></td><td>回显请求中的 <code>sync</code>(异步一般为 <code>false</code>)</td></tr><tr><td><code>stream_output</code></td><td>回显本次任务实际使用的流式开关</td></tr></tbody></table><blockquote>异步任务请通过 <strong>查询接口</strong> 或 <strong>回调</strong> 获取最终结果,勿在录入 JSON 响应中期待完整答复。</blockquote><h3>5.3 成功响应(同步 SSE,<code>sync=true</code>)</h3><ul><li><strong>HTTP</strong>:<code>200</code></li><li><strong>Content-Type</strong>:<code>text/event-stream</code></li><li><strong>响应头</strong>:<code>Cache-Control: no-cache</code>、<code>X-Accel-Buffering: no</code>(请代理关闭缓冲并拉长读超时,建议 ≥ 15 分钟)</li><li><strong>最大连接时长</strong>:15 分钟;超时发送 <code>stream_error</code> 后关闭,任务继续执行</li></ul><p><strong>请求示例:</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "content": "请总结退款流程", "sync": true, "stream_output": true, "biz_demand_id": "biz-order-001" }</code></pre><p><strong>事件顺序:</strong> <code>created</code> → <code>action</code>* → <code>finished</code>(或 <code>stream_error</code>)</p><pre><code>id: created:12345 event: created data: {"request_id":"...","session_code":"CS-S-...","demand_id":12345,"status":0,"status_name":"pending","created_at":"..."}</code></pre><pre><code>id: action:788001 event: action data: {"id":788001,"action_name":"text","action_type":"unified_demand_action","action_detail":{"execution_result":"..."},"create_time":"..."}</code></pre><pre><code>: ping</code></pre><pre><code>id: finished:12345 event: finished data: {"session_code":"...","demand_id":12345,"status":20,"status_name":"completed","finished":true,"result_summary":"...","outputs":[],"..."}</code></pre><table><thead><tr><th><strong>事件</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>created</code></td><td>建单成功后立即发送;含 <code>demand_id</code>/<code>session_code</code>,断线后用于 query</td></tr><tr><td><code>action</code></td><td><strong>一条公开 action 一帧</strong>;白名单与 query 一致:<code>thinking</code>/<code>text</code>/<code>interaction_question</code>/<code>interaction_reply</code></td></tr><tr><td><code>finished</code></td><td>终态(20/90/99);字段对齐 TaskSnapshot,<strong>不含</strong>完整 <code>actions</code> 数组</td></tr><tr><td><code>stream_error</code></td><td>流传输/超时等;<strong>不</strong>表示任务已失败,请改用 query</td></tr><tr><td>注释 <code>: ping</code></td><td>约每 15 秒心跳,保持代理连接</td></tr></tbody></table><p><strong>流前错误</strong>(验签/参数等):仍返回普通 JSON + HTTP 4xx/5xx,不会进入 SSE。</p><p><strong>curl 联调:</strong></p><pre><code>curl -N -X POST 'http://HOST/api/open/demand/create' \ -H 'Content-Type: application/json; charset=utf-8' \ -H 'X-Access-Key: ...' \ -H 'X-Timestamp: ...' \ -H 'X-Nonce: ...' \ -H 'X-Signature: ...' \ -d '{"third_party_id":"...","biz_user_id":"...","content":"...","sync":true}'</code></pre><h3>5.4 录入场景示例</h3><h4>场景 A:纯文本新会话(无附件)</h4><p><strong>请求:</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "content": "我想咨询退款流程", "title": "退款咨询" }</code></pre><p><strong>响应:</strong></p><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345, "status": 0, "status_name": "pending", "created_at": "2026-08-05T09:12:00" } }</code></pre><h4>场景 B:带附件新会话(输入附件)</h4><p>贵方先将文件上传至 OSS/CDN,生成 <strong>HTTPS 临时链接</strong>(建议 5~15 分钟有效),再调用录入:</p><p><strong>请求:</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "content": "请阅读附件,用三句话总结文档内容", "title": "合同摘要", "attachments": [ { "file_url": "https://partner-cdn.example.com/tmp/contract.pdf", "file_name": "合同.pdf", "mime_type": "application/pdf" } ] }</code></pre><p><strong>响应:</strong> 与场景 A 结构相同(仅 <code>demand_id</code> / <code>session_code</code> 不同)。附件是否拉取成功在录入阶段即确定;拉取失败返回 <code>40009</code>,不会创建任务。</p><p><strong>附件拉取失败示例:</strong></p><pre><code>{ "code": 40009, "message": "当前接入配置不允许上传附件", "data": null } { "code": 40009, "message": "下载附件失败", "data": null }</code></pre><h4>场景 C:续聊(同 session,无新附件)</h4><p><strong>请求:</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "session_code": "CS-S-20260805101200-x9y8z7w6", "content": "那退款大概多久能到账?", "title": "退款咨询-追问" }</code></pre><p><strong>响应:</strong> 返回<strong>同一</strong> <code>session_code</code>、<strong>新的</strong> <code>demand_id</code>(每次录入均新建任务)。</p><h4>场景 D:续聊并补充新附件</h4><p><strong>请求:</strong></p><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "biz_user_id": "user_10086", "session_code": "CS-S-20260805101200-x9y8z7w6", "content": "请再看一下这份补充材料", "attachments": [ { "file_url": "https://partner-cdn.example.com/tmp/supplement.txt", "file_name": "补充说明.txt" } ] }</code></pre><blockquote>每一轮 <code>demand_id</code> 独立挂载附件;历史轮次的附件不会自动带入新一轮,除非用户在续聊中再次传入。</blockquote><h2>6. 接口二:任务查询</h2><p>按录入返回的 <code>session_code</code> + <code>demand_id</code> 查询<strong>该次任务</strong>的执行快照。</p><ul><li><strong>URL</strong>:<code>POST /api/open/demand/query</code></li><li><strong>鉴权</strong>:§3 HMAC 签名</li></ul><h3>6.1 请求体</h3><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345 }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>third_party_id</code></td><td>string</td><td>是</td><td>接入标识</td></tr><tr><td><code>session_code</code></td><td>string</td><td>是</td><td>录入返回的会话编码</td></tr><tr><td><code>demand_id</code></td><td>int</td><td>是</td><td>录入返回的任务 ID</td></tr></tbody></table><h3>6.2 成功响应(<code>data</code> = TaskSnapshot)</h3><p>以下按典型场景分别示例。<strong>查询请求体在各场景中相同</strong>(见 §6.1)。</p><h4>场景 A:执行中(无终态、无产出)</h4><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345, "status": 10, "status_name": "running", "finished": false, "error_code": null, "error_message": null, "result_summary": null, "actions": [ { "id": 90001, "action_name": "text", "action_type": "unified_demand_action", "action_detail": { "execution_result": "您好,正在为您查询退款政策..." }, "create_time": "2026-08-05T09:12:18" } ], "outputs": [] } }</code></pre><h4>场景 B:已完成 — 纯文本答复(无输出文件)</h4><p>常见于咨询类,或「阅读输入附件后文字总结」(create 有附件,但 AI <strong>未生成</strong>可下载文件):</p><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805164503-5573652c", "demand_id": 37412, "status": 20, "status_name": "completed", "finished": true, "error_code": null, "error_message": null, "result_summary": "该附件文档内容非常简短……(三句话总结正文)", "actions": [ { "id": 782271, "action_name": "text", "action_type": "unified_demand_action", "action_detail": { "execution_result": "1. 文件性质:这是一份测试文档……" }, "create_time": "2026-08-05T16:49:30" } ], "outputs": [] } }</code></pre><h4>场景 C:已完成 — 含 AI 产出文件(需下载)</h4><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345, "status": 20, "status_name": "completed", "finished": true, "result_summary": "已为您生成退款流程说明文档。", "actions": [ { "id": 90001, "action_name": "text", "action_type": "unified_demand_action", "action_detail": { "execution_result": "已为您整理退款流程,详见附件 PDF。" }, "create_time": "2026-08-05T09:15:28" } ], "outputs": [ { "output_type": "file", "output_id": 32019, "output_detail": { "file_name": "退款流程说明.pdf", "file_type": "pdf", "view_url": "https://{host}/api/open/demand/file/download?token=eyJ...", "expires_at": "2026-08-05T10:15:30+08:00" } } ] } }</code></pre><h4>场景 D:已完成 — 失败终态</h4><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805163303-69e817db", "demand_id": 37408, "status": 99, "status_name": "failed", "finished": true, "error_code": "SESSION_CONTEXT_CORRUPTED", "error_message": "系统无法继续读取当前会话,是否清空上下文后继续执行。", "result_summary": "系统无法继续读取当前会话,是否清空上下文后继续执行。", "actions": [], "outputs": [] } }</code></pre><h4>场景对照速查</h4><table><thead><tr><th><strong>场景</strong></th><th><strong>create 附件</strong></th><th><strong>finished</strong></th><th><strong>outputs</strong></th><th><strong>主要阅读字段</strong></th></tr></thead><tbody><tr><td>纯文本咨询</td><td>无</td><td>true</td><td><code>[]</code></td><td><code>result_summary</code> / <code>actions</code></td></tr><tr><td>阅读附件后文字答复</td><td>有</td><td>true</td><td><code>[]</code></td><td><code>result_summary</code> / <code>actions</code></td></tr><tr><td>AI 生成文件</td><td>可有可无</td><td>true</td><td>非空</td><td><code>outputs[].view_url</code> + <code>result_summary</code></td></tr><tr><td>执行中</td><td>可有可无</td><td>false</td><td><code>[]</code></td><td><code>actions</code>(进度)</td></tr><tr><td>失败</td><td>可有可无</td><td>true</td><td><code>[]</code></td><td><code>error_code</code> / <code>error_message</code></td></tr></tbody></table><h3>6.3 TaskSnapshot 字段</h3><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>session_code</code></td><td>string</td><td>会话编码</td></tr><tr><td><code>demand_id</code></td><td>int</td><td>任务 ID</td></tr><tr><td><code>third_party_id</code></td><td>string</td><td>接入标识</td></tr><tr><td><code>status</code></td><td>int</td><td>状态码,见 §6.4</td></tr><tr><td><code>status_name</code></td><td>string</td><td>状态名</td></tr><tr><td><code>finished</code></td><td>bool</td><td>是否终态</td></tr><tr><td><code>biz_user_id</code></td><td>string</td><td>贵方用户 ID</td></tr><tr><td><code>assistant_id</code></td><td>int</td><td>执行助手 ID</td></tr><tr><td><code>created_at</code> / <code>updated_at</code></td><td>string</td><td>时间</td></tr><tr><td><code>error_code</code> / <code>error_message</code></td><td>string / null</td><td>失败时错误信息</td></tr><tr><td><code>result_summary</code></td><td>string / null</td><td>结果摘要</td></tr><tr><td><code>actions</code></td><td>array</td><td>公开动作流(未完成时也会返回)</td></tr><tr><td><code>outputs</code></td><td>array</td><td>文件成果;<strong>仅 `finished=true` 且本次 `demand_id` 有登记产出时有值</strong>,否则 <code>[]</code></td></tr></tbody></table><h3>6.4 任务状态</h3><table><thead><tr><th><strong>status</strong></th><th><strong>status_name</strong></th><th><strong>finished</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>0</td><td>pending</td><td>false</td><td>排队中</td></tr><tr><td>10</td><td>running</td><td>false</td><td>执行中</td></tr><tr><td>11</td><td>analyzing</td><td>false</td><td>分析中</td></tr><tr><td>12</td><td>processing</td><td>false</td><td>处理中</td></tr><tr><td>16</td><td>resuming</td><td>false</td><td>恢复中</td></tr><tr><td>20</td><td>completed</td><td>true</td><td>成功完成</td></tr><tr><td>90</td><td>interrupted</td><td>true</td><td>中断</td></tr><tr><td>99</td><td>failed</td><td>true</td><td>失败</td></tr></tbody></table><h3>6.5 actions 说明</h3><p>仅返回公开白名单动作:</p><table><thead><tr><th><strong>action_name</strong></th><th><strong>action_detail 公开字段</strong></th></tr></thead><tbody><tr><td><code>thinking</code></td><td><code>execution_result</code>(文本,最长约 8000 字符)</td></tr><tr><td><code>text</code></td><td><code>execution_result</code></td></tr><tr><td><code>interaction_question</code></td><td><code>title</code>, <code>content</code></td></tr><tr><td><code>interaction_reply</code></td><td><code>response.text</code> / <code>selected_label</code> / <code>selected_labels</code></td></tr></tbody></table><p>内部工具调用、绝对路径、密钥等不会出现在公开 <code>action_detail</code> 中。</p><h3>6.6 outputs 说明</h3><table><thead><tr><th><strong>字段</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>output_type</code></td><td>固定 <code>file</code></td></tr><tr><td><code>output_id</code></td><td>产出记录 ID</td></tr><tr><td><code>output_detail.file_name</code></td><td>文件名</td></tr><tr><td><code>output_detail.file_type</code></td><td>扩展名(如 <code>pdf</code>)</td></tr><tr><td><code>output_detail.view_url</code></td><td>文件下载地址(含短期 token),见 §8</td></tr><tr><td><code>output_detail.expires_at</code></td><td>token 过期时间(ISO8601,含时区)</td></tr></tbody></table><p>注意:</p><ul><li><strong>未完成</strong>任务:<code>outputs</code> 恒为 <code>[]</code>(即便后台已生成文件)。</li><li><strong>输入附件</strong>(create 传入)不在 <code>outputs</code> 中返回;<code>outputs</code> 仅含 <strong>AI 产出</strong> 的可下载文件。</li><li><strong>按任务隔离</strong>:<code>outputs</code> 只返回<strong>本次 `demand_id`</strong> 登记的文件,<strong>不包含</strong>同 <code>session_code</code> 下其他轮次(其他 <code>demand_id</code>)的产出。续聊时请分别 query 各 <code>demand_id</code> 获取对应文件。</li><li>同一 <code>demand_id</code> 若产出多个文件,<code>outputs</code> 为数组,每项对应一个可下载文件。</li><li><code>view_url</code> <strong>每次查询重新签发</strong>,请勿长期缓存;过期后重新调用本接口刷新。</li><li><strong>不会</strong>返回服务器本地绝对路径。</li></ul><h2>7. 接口三:任务终止</h2><p>按录入返回的 <code>session_code</code> + <code>demand_id</code> 终止<strong>该次任务</strong>。终止成功后任务进入终态 <code>status=90</code>(<code>interrupted</code>),平台会异步触发 §9 结果回调(若已配置 <code>callback_url</code>);<code>sync=true</code> 的 SSE 连接也会收到终态事件。</p><ul><li><strong>URL</strong>:<code>POST /api/open/demand/stop</code></li><li><strong>鉴权</strong>:§3 HMAC 签名</li></ul><h3>7.1 请求体</h3><pre><code>{ "third_party_id": "agent-maiya-prod-xxxx", "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345 }</code></pre><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>third_party_id</code></td><td>string</td><td>是</td><td>接入标识</td></tr><tr><td><code>session_code</code></td><td>string</td><td>是</td><td>录入返回的会话编码</td></tr><tr><td><code>demand_id</code></td><td>int</td><td>是</td><td>录入返回的任务 ID</td></tr></tbody></table><h3>7.2 成功响应</h3><pre><code>{ "code": 200, "message": "success", "data": { "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345, "status": 90, "status_name": "interrupted", "finished": true } }</code></pre><h3>7.3 业务规则</h3><table><thead><tr><th><strong>规则</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>可终止状态</td><td><code>pending</code> / <code>running</code> / <code>processing</code> / <code>resuming</code> 等非终态</td></tr><tr><td>不可终止</td><td>已完成(20)、已中断(90)、失败(99)→ 返回 <code>40011</code></td></tr><tr><td>权限</td><td>须与 query 相同:<code>session_code</code> + <code>demand_id</code> 须属于当前 <code>third_party_id</code></td></tr><tr><td>执行中任务</td><td>SSE(<code>sync=true</code>)检测到 <code>90</code> 后<strong>立刻</strong>推送 <code>finished</code> 并结束,不再继续推 <code>action</code>;Job 仍会在数秒内收尾</td></tr><tr><td>排队任务</td><td>尚未被 Job 领取的任务会立即变为 <code>interrupted</code>,并触发回调</td></tr></tbody></table><h2>8. 接口四:文件下载</h2><p>任务完成后,通过查询(或回调)得到的 <code>view_url</code> 直接下载文件。</p><ul><li><strong>URL</strong>:<code>GET /api/open/demand/file/download?token={token}</code></li><li><strong>鉴权</strong>:<strong>无需</strong> HMAC 头;凭 query 参数 <code>token</code> 鉴权</li><li><strong>成功响应</strong>:文件二进制流</li><li><code>Content-Type</code>:按文件类型推断(未知则为 <code>application/octet-stream</code>)</li><li><code>Content-Disposition: attachment; filename="..."</code></li></ul><h3>7.1 请求参数</h3><table><thead><tr><th><strong>参数</strong></th><th><strong>位置</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>token</code></td><td>Query</td><td>是</td><td>由平台在 <code>outputs[].output_detail.view_url</code> 中签发</td></tr></tbody></table><p>示例:</p><pre><code>GET /api/open/demand/file/download?token=eyJ2IjoxLCJkZW1hbmRfaWQiOjEyMzQ1...&lt;sig&gt; HTTP/1.1 Host: {host}</code></pre><p>浏览器、curl、HTTP 客户端均可直接 GET,无需额外签名头。</p><p><strong>下载成功:</strong> HTTP <code>200</code>,响应体为文件二进制流。</p><pre><code>HTTP/1.1 200 OK Content-Type: application/pdf Content-Disposition: attachment; filename="退款流程说明.pdf"</code></pre><pre><code>&lt;binary data&gt;</code></pre><p><strong>下载失败示例:</strong></p><pre><code>HTTP/1.1 401 Unauthorized Content-Type: text/plain; charset=utf-8</code></pre><pre><code>token 已过期</code></pre><h3>7.2 Token 规则</h3><table><thead><tr><th><strong>项</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>有效期</td><td>默认 <strong>3600 秒(1 小时)</strong></td></tr><tr><td>绑定信息</td><td><code>demand_id</code>、<code>session_code</code>、接入配置、<code>file_name</code></td></tr><tr><td>签名</td><td>使用接入配置当前有效 SECRETKEY 做 HMAC-SHA256(平台签发,贵方无需自行构造)</td></tr><tr><td>刷新</td><td>过期后重新调用 <strong>任务查询</strong>,使用新的 <code>view_url</code></td></tr></tbody></table><h3>7.3 失败响应</h3><p>下载失败时返回 <strong>纯文本</strong>(非 JSON),常见情况:</p><table><thead><tr><th><strong>HTTP</strong></th><th><strong>含义</strong></th></tr></thead><tbody><tr><td>400</td><td>token 无效 / 参数不完整 / 文件名非法</td></tr><tr><td>401</td><td>token 签名校验失败或已过期</td></tr><tr><td>403</td><td>无权访问该任务文件</td></tr><tr><td>404</td><td>任务或文件不存在</td></tr><tr><td>500</td><td>服务内部错误</td></tr></tbody></table><h3>7.4 安全说明</h3><ul><li>Token 具备时效与归属绑定,请勿公开传播长期有效的下载链接。</li><li>服务端校验任务归属,并限制仅能下载该会话产出目录内文件,禁止路径穿越。</li><li>响应中不会暴露服务器磁盘路径。</li></ul><h2>9. 接口五:结果回调(平台 → 第三方)</h2><p>任务进入终态后,平台主动将结果推送到贵方服务。这是正式对接接口之一,方向与录入/查询相反。</p><table><thead><tr><th><strong>项</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>方向</td><td><strong>平台 → 第三方</strong></td></tr><tr><td>方法</td><td><code>POST</code></td></tr><tr><td>URL</td><td>接入配置中的 <code>callback_url</code>(由平台侧开通时登记)</td></tr><tr><td>Content-Type</td><td><code>application/json; charset=utf-8</code></td></tr><tr><td>触发条件</td><td><code>demand.status ∈ {20, 90, 99}</code>,且该任务尚未同步成功</td></tr><tr><td>触发时机</td><td>终态写入成功后<strong>立即异步</strong> POST 一次</td></tr><tr><td>数据体</td><td>与 §6 任务查询的 <code>data</code>(TaskSnapshot)<strong>完全一致</strong></td></tr></tbody></table><h3>8.1 前置配置</h3><p>开通时向平台提供可公网访问的 HTTPS 回调地址,例如:</p><pre><code>https://partner.example.com/hooks/mai/customer-service</code></pre><p>要求:</p><ul><li>建议 HTTPS;若确需 HTTP,须由平台侧显式放行。</li><li>地址须可被平台服务器访问(注意防火墙 / 专线)。</li><li>未配置 <code>callback_url</code> 时,平台视为无需投递(不影响录入与查询)。</li></ul><h3>8.2 回调请求头</h3><table><thead><tr><th><strong>Header</strong></th><th><strong>必填</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>Content-Type</code></td><td>是</td><td><code>application/json; charset=utf-8</code></td></tr><tr><td><code>X-MAI-Access-Key</code></td><td>是</td><td>接入配置当前有效 ACCESSKEY</td></tr><tr><td><code>X-MAI-Timestamp</code></td><td>是</td><td>Unix 毫秒时间戳</td></tr><tr><td><code>X-MAI-Nonce</code></td><td>是</td><td>随机串</td></tr><tr><td><code>X-MAI-Signature</code></td><td>是</td><td>对原始回调 Body 的 HMAC-SHA256 签名(算法同 §3.2)</td></tr></tbody></table><blockquote>注意:回调头前缀为 <strong>`X-MAI-`</strong>,与贵方调用平台时使用的 <code>X-Access-Key</code> 等命名不同,请勿混用。</blockquote><h3>8.3 回调请求体</h3><p>外层结构固定;<code>data</code> 与 §6 查询的 TaskSnapshot <strong>完全一致</strong>(含各场景差异)。</p><p><strong>通用结构:</strong></p><table><thead><tr><th><strong>字段</strong></th><th><strong>类型</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td><code>event</code></td><td>string</td><td>固定 <code>task.finished</code></td></tr><tr><td><code>timestamp</code></td><td>int</td><td>回调发出时间(Unix 毫秒)</td></tr><tr><td><code>data</code></td><td>object</td><td>TaskSnapshot,见 §6.3~§6.6</td></tr></tbody></table><h4>回调场景 A:成功 + 有产出文件(<code>outputs</code> 非空)</h4><p>对应 §6.2 场景 C。贵方收到后应下载 <code>data.outputs[].output_detail.view_url</code>:</p><pre><code>{ "event": "task.finished", "timestamp": 1722758772000, "data": { "session_code": "CS-S-20260805101200-x9y8z7w6", "demand_id": 12345, "status": 20, "status_name": "completed", "finished": true, "result_summary": "已为您生成退款流程说明文档。", "outputs": [ { "output_type": "file", "output_id": 32019, "output_detail": { "file_name": "退款流程说明.pdf", "file_type": "pdf", "view_url": "https://{host}/api/open/demand/file/download?token=eyJ...", "expires_at": "2026-08-05T10:15:30+08:00" } } ] } }</code></pre><h4>回调场景 B:成功 + 仅文字答复(含「阅读输入附件」)</h4><p>对应 §6.2 场景 B。create 时可能传了输入附件,但 <strong>`outputs` 仍为空</strong>:</p><pre><code>{ "event": "task.finished", "timestamp": 1722758976000, "data": { "session_code": "CS-S-20260805164503-5573652c", "demand_id": 37412, "status": 20, "status_name": "completed", "finished": true, "result_summary": "该附件文档内容非常简短……(三句话总结)", "outputs": [] } }</code></pre><h4>回调场景 C:失败终态</h4><pre><code>{ "event": "task.finished", "timestamp": 1722758850000, "data": { "session_code": "CS-S-20260805163303-69e817db", "demand_id": 37408, "status": 99, "status_name": "failed", "finished": true, "error_code": "SESSION_CONTEXT_CORRUPTED", "error_message": "系统无法继续读取当前会话,是否清空上下文后继续执行。", "outputs": [] } }</code></pre><blockquote>回调仅在终态触发一次;<code>data.actions</code> 可能为空数组(与查询接口一致,以 <code>result_summary</code> / <code>outputs</code> / <code>error_*</code> 为准)。</blockquote><h3>8.4 贵方验签步骤(强烈建议)</h3><p>算法与 §3.2 <strong>完全相同</strong>,区别仅在于 Header 名称与 PATH 取值:</p><ol start="1"><li>读取<strong>原始请求 Body 字节</strong>(不要先 parse 再序列化)。</li><li><code>body_hash = HEX(SHA256(raw_body))</code></li><li>构造规范串:</li></ol><pre><code>POST {callback_url 的 path} {X-MAI-Access-Key} {X-MAI-Timestamp} {X-MAI-Nonce} {body_hash}</code></pre><p>示例:若 <code>callback_url = https://partner.example.com/hooks/mai/customer-service</code>,则 PATH = <code>/hooks/mai/customer-service</code>(不含域名与 query)。</p><ol start="4"><li><code>expected = HEX(HMAC_SHA256(SECRETKEY, canonical))</code></li><li>与 <code>X-MAI-Signature</code> 做常量时间比较;并校验 Timestamp 偏差不超过 5 分钟。</li></ol><p>Python 验签示例:</p><pre><code>import hashlib import hmac from urllib.parse import urlparse</code></pre><pre><code>def verify_mai_callback(secret_key: str, callback_url: str, headers: dict, raw_body: bytes) -&gt; bool: access_key = headers.get("X-MAI-Access-Key", "") timestamp = headers.get("X-MAI-Timestamp", "") nonce = headers.get("X-MAI-Nonce", "") signature = (headers.get("X-MAI-Signature") or "").lower() path = urlparse(callback_url).path or "/" body_hash = hashlib.sha256(raw_body or b"").hexdigest() canonical = "\n".join(["POST", path, access_key, timestamp, nonce, body_hash]) expected = hmac.new( secret_key.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature)</code></pre><h3>8.5 贵方响应约定</h3><p>建议返回:</p><pre><code>{ "code": 0, "message": "ok" }</code></pre><p>平台判定成功条件:</p><ol start="1"><li>HTTP 状态码为 <strong>2xx</strong>;且</li><li>若响应体为 JSON 且包含 <code>code</code> 字段,则 <code>code</code> 须为 <strong>`0` 或 `200`</strong>。</li></ol><p>不满足上述条件视为本次回调失败。</p><h3>8.6 投递语义与幂等</h3><table><thead><tr><th><strong>项</strong></th><th><strong>行为</strong></th></tr></thead><tbody><tr><td>成功</td><td>标记该 <code>demand_id</code> 已同步,不再重复推送</td></tr><tr><td>失败</td><td><strong>不自动重试</strong>;仅记录尝试次数;<code>sync_status</code> 保持未同步</td></tr><tr><td>幂等</td><td>贵方须按 <code>data.demand_id</code> 幂等处理(网络超时可能导致贵方已收、平台判失败)</td></tr><tr><td>超时</td><td>默认约 10 秒(以接入配置为准)</td></tr><tr><td>与查询关系</td><td>回调失败不影响贵方继续调用 §6 查询拉取结果</td></tr></tbody></table><h2>10. 错误码</h2><table><thead><tr><th><strong>code</strong></th><th><strong>HTTP</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>200</td><td>200</td><td>成功</td></tr><tr><td>40001</td><td>403</td><td>接入标识不存在/已停用,或与 ACCESSKEY 不一致</td></tr><tr><td>40002</td><td>401</td><td>ACCESSKEY 无效或已过期</td></tr><tr><td>40003</td><td>401</td><td>签名错误</td></tr><tr><td>40004</td><td>401</td><td>请求已过期(时间戳偏差过大)</td></tr><tr><td>40005</td><td>401</td><td>重复请求(Nonce 重放)</td></tr><tr><td>40006</td><td>403</td><td>IP 不在白名单</td></tr><tr><td>40007</td><td>400</td><td>参数校验失败</td></tr><tr><td>40008</td><td>404</td><td>demand_id 不存在或无权访问</td></tr><tr><td>40009</td><td>400</td><td>附件不符合要求(未开启、数量/大小/类型/URL 不合法或下载失败)</td></tr><tr><td>40010</td><td>400</td><td>session 校验失败:<code>session_code</code> 不存在或无权访问;<code>session_code</code> / <code>biz_session_id</code> 与 <code>biz_user_id</code> 不匹配</td></tr><tr><td>40011</td><td>400</td><td>需求状态不允许终止(已完成/已中断/失败)</td></tr><tr><td>50001</td><td>500</td><td>服务内部错误</td></tr></tbody></table><p>示例:</p><pre><code>{ "code": 40003, "message": "签名错误", "data": null }</code></pre><h2>11. 推荐对接流程</h2><h3>10.1 纯文本咨询(无附件、无产出文件)</h3><p><strong>推荐(预热首问):</strong></p><pre><code>1. POST /session/init → 保存 session_code(stream_output 与后续保持一致) 2. POST /demand/create(带 session_code,无 attachments) 3. POST /demand/query 轮询至 finished=true 4. 读取 result_summary / actions(outputs 为 []) 5. (可选)收到 callback,data 与 query 一致</code></pre><p><strong>兼容(不预热,首问可能冷启动):</strong></p><pre><code>1. POST /demand/create(无 session_code、无 attachments) 2. POST /demand/query 轮询至 finished=true 3. 读取 result_summary / actions(outputs 为 []) 4. (可选)收到 callback,data 与 query 一致</code></pre><h3>10.2 带输入附件(三方传 file_url)</h3><pre><code>1. 贵方 OSS 生成 HTTPS 临时链接 2. POST /demand/create(带 attachments[]) └─ 失败 40009 → 检查 allow_attachments / URL 可达性 3. POST /demand/query 轮询 4. finished=true 且 outputs=[] → 文字结论在 result_summary(常见) 5. finished=true 且 outputs 非空 → 另有 AI 生成文件,GET view_url 下载</code></pre><h3>10.3 要求 AI 生成文件(输出附件)</h3><pre><code>1. POST /demand/create,content 中明确说明「请生成 PDF/文档给我」 2. 轮询或等 callback 3. finished=true 且 outputs[].view_url 非空 4. GET view_url 下载(§8);token 过期则重新 query 刷新</code></pre><h3>10.4 续聊</h3><pre><code>1. 再次 POST /demand/create,带同一 session_code + biz_user_id 2. 得到新 demand_id;每轮 attachments 独立,不自动继承历史附件 3. 分别 query 各 demand_id</code></pre><h3>10.5 同步 SSE(<code>sync=true</code>)</h3><pre><code>1. POST /demand/create,Body 含 "sync": true 2. 解析 text/event-stream:created → action* → finished 3. 若中途断开:用 created 中的 demand_id/session_code 调 query 兜底 4. 本模式不会发送终态 callback</code></pre><p>取结果方式:</p><ul><li><strong>异步</strong>:回调为主 + 查询兜底。</li><li><strong>同步 SSE</strong>:同连接收流;断线用 query 兜底。</li></ul><h2>12. 联调检查清单</h2><ul><li>[ ] 可选:打开页先调 <code>/session/init</code>,首问携带返回的 <code>session_code</code>,<code>stream_output</code> 与 init 一致</li><li>[ ] 签名 PATH 使用 <code>/api/open/session/init</code>、<code>/api/open/demand/create</code> 或 <code>/api/open/demand/query</code></li><li>[ ] 对<strong>原始发送字节</strong>计算 SHA256,避免 JSON 二次序列化改变内容</li><li>[ ] Timestamp 为 <strong>毫秒</strong></li><li>[ ] Nonce 长度 16~64,且不重放</li><li>[ ] Body 含正确 <code>third_party_id</code></li><li>[ ] 出口 IP 已加入白名单(若已启用)</li><li>[ ] 异步 create 后轮询或等 callback;勿在 JSON 响应中期待完整答复</li><li>[ ] <code>sync=true</code> 时按 SSE 解析,代理关闭缓冲并用 <code>curl -N</code> 验证</li><li>[ ] 按需传 <code>stream_output</code>(默认 true);与 <code>sync</code> 独立,改 Body 后须重签</li><li>[ ] SSE 断线后可用 query 兜底;同步模式不依赖 callback</li><li>[ ] 接入已开启 <code>allow_attachments=true</code>(若需传输入附件)</li><li>[ ] 输入附件使用 HTTPS 临时 URL;平台服务器须能访问</li><li>[ ] 区分<strong>输入附件</strong>(create)与<strong>输出文件</strong>(outputs);后者才需下载</li><li>[ ] 查询时同时传 <code>session_code</code> 与 <code>demand_id</code></li><li>[ ] 仅在 <code>finished=true</code> 时期待 <code>outputs</code> 非空</li><li>[ ] 下载使用完整 <code>view_url</code>;过期后重新 query</li><li>[ ] 已配置可访问的 <code>callback_url</code>(仅异步模式需要)</li><li>[ ] 回调验签使用 <code>X-MAI-*</code> 头,PATH 取回调 URL 的 path</li><li>[ ] 回调按 <code>demand_id</code> 幂等;成功响应 HTTP 2xx 且 <code>code</code> 为 0 或 200</li></ul><h2>13. 修订记录</h2><table><thead><tr><th><strong>版本</strong></th><th><strong>日期</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>v1.6.0</td><td>2026-08-12</td><td>新增 <code>POST /demand/stop</code> 任务终止接口;错误码 <code>40011</code></td></tr><tr><td>v1.5.2</td><td>2026-08-10</td><td>明确 outputs 按 demand_id 隔离,续聊各轮 query 仅返回本轮产出文件</td></tr><tr><td>v1.5.1</td><td>2026-08-10</td><td>文档与实现对齐:session/init 鉴权说明、<code>create</code> 响应字段、40010/移除 40011、推荐流程与 Postman 签名 PATH</td></tr><tr><td>v1.5</td><td>2026-08-10</td><td>新增可选 <code>POST /session/init</code> 会话预热;首问可携带 init 返回的 <code>session_code</code></td></tr><tr><td>v1.4</td><td>2026-08-06</td><td>create 支持 <code>stream_output</code>(默认 true)透传客服 Job <code>include_partial_messages</code></td></tr><tr><td>v1.3</td><td>2026-08-06</td><td>create 支持 <code>sync=true</code> SSE 同步流式;同步模式不发送终态 callback</td></tr><tr><td>v1.2</td><td>2026-08-05</td><td>支持输入附件(file_url);补充多场景请求/响应示例;区分输入附件与输出文件</td></tr><tr><td>v1.1</td><td>2026-08-05</td><td>将结果回调提升为正式接口四;补充请求头、验签示例、投递语义</td></tr><tr><td>v1.0</td><td>2026-08-05</td><td>首版:录入、查询、文件下载与签名说明</td></tr></tbody></table>
业务
咨询