跳到主要内容

跨端流式差异

核对日期:2026-08-26。各端基础库对分块传输的支持会变,上线前按目标版本复核。

1. 定义与边界

跨端流式差异是指同一套 Agent 网关事件,在 H5、微信小程序、支付宝小程序、App 上不能共用一套传输实现。差异来自运行时:有无 fetch 流、有无 EventSource、是否允许自定义 header、合法域名、TLS、后台挂起。

本文件给协议矩阵和降级。事件契约仍用 流式协议与中断.mdAgentUiEvent。不要为每个端发明一套业务事件。

2. 为什么重要

只在 Chrome 里用 fetch + ReadableStream 调通,不代表小程序能用:

  • 微信、支付宝没有浏览器 EventSource
  • 小程序请求受合法域名、TLS、内容安全策略限制。
  • 部分基础库不支持分块响应,只能一次返回或轮询。
  • App WebView 与原生 uni.request 能力不一致,条件编译写错会静默退回非流式。

把 H5 实现直接 wx.request 包一层,是跨端 Agent 最常见的假流式。

3. 核心机制

交互层分三截:

业务 UI(卡片 / 审批 / 产物)
↑ 同一套 AgentUiEvent
传输适配器(H5 fetch 流 / 微信分块 / 轮询)
↑ 同一套 run API
Agent 网关

传输适配器可以换;卡片状态机不能换。

4. 架构模式

推荐传输次选不要用
H5(现代浏览器)fetch POST + 解析 SSEWebSocket(需强打断时)把 token 放 query 的 EventSource
React Native / 常规 App WebView与 H5 相同,先测流原生 SSE 库假设 WKWebView 与 Chrome 一致
微信小程序wx.request({ enableChunked: true })短轮询 GET /runs/:id/events?after=EventSource、浏览器 fetch
支付宝小程序确认基础库后的分块 / 流式 HTTP短轮询直接复用微信 API
uni-app条件编译到上述实现各端降级表一套 uni.request 冒充全端流式

EventSource 即使在 H5 也不适合作为生产 Agent 默认:只支持 GET,鉴权与 JSON body 都别扭。

5. 工程实现

5.1 传输适配器接口

interface AgentStreamClient {
start: (input: { goal: string; sessionId: string }) => Promise<void>;
stop: () => void;
onEvent: (handler: (event: { type: string; runId?: string }) => void) => void;
}

interface ResumeCursor {
runId: string;
afterSeq: number;
}

H5、微信、轮询各自实现 AgentStreamClient,页面只依赖接口。

5.2 微信小程序分块

微信侧用 enableChunked 收二进制 / 文本块,自行按 SSE 帧切分。不要假设块边界等于事件边界。

function startWechatAgentRun(options) {
const requestTask = wx.request({
url: 'https://api.example.com/agent/runs',
method: 'POST',
enableChunked: true,
header: {
'Content-Type': 'application/json',
Authorization: options.token,
},
data: { goal: options.goal, sessionId: options.sessionId },
fail: options.onError,
});

let buffer = '';
requestTask.onChunkReceived((res) => {
const chunk = decodeUtf8(res.data);
buffer += chunk;
const frames = buffer.split('\n\n');
buffer = frames.pop() || '';
frames.forEach((frame) => options.onFrame(frame));
});

return {
stop() {
requestTask.abort();
},
};
}

约束:

  • 域名必须配在 request 合法域名里,且 HTTPS。
  • enableChunked 依赖基础库版本,低版本走轮询。
  • 不要在小程序里读 document / window 上的流 API。
  • 工具卡片和审批页用原生组件,不要用 web-view 加载未隔离的模型 HTML。

5.3 支付宝小程序

支付宝与微信的请求对象、分块回调、abort 方法都不同。用 my.request 及当前基础库文档核对是否支持分块;不支持就用同一套 afterSeq 轮询。禁止 wx.* 直接套到支付宝。

uni-app 里必须条件编译:

// #ifdef MP-WEIXIN
export { startWechatAgentRun as startAgentRun };
// #endif

// #ifdef MP-ALIPAY
export { startAlipayAgentRun as startAgentRun };
// #endif

// #ifdef H5
export { startH5AgentRun as startAgentRun };
// #endif

5.4 轮询降级

async function pollEvents(cursor: ResumeCursor, signal: AbortSignal): Promise<void> {
const url = `/api/agent/runs/${cursor.runId}/events?after=${cursor.afterSeq}`;
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`poll_http_${response.status}`);
}
}

轮询间隔建议 1–2 秒,带抖动;服务端必须支持 afterSeq 幂等续传。写工具仍只执行一次,见 ../09-Agent工程化/幂等性设计.md

5.5 App 与后台

  • iOS / 安卓把 App 切后台可能切断长连接;回到前台用 run_id 续事件,不要自动重发 goal。
  • WKWebView 对流式与 cookie 分区与 Safari 接近,需真机测,不能只在 Chrome DevTools 过关。
  • 原生 App 若自建 SSE,TLS 证书校验不要在调试包里长期关闭。

6. 生产实践

实践说明
能力探测启动时探测分块是否可用,失败静默切轮询
统一心跳小程序也要能识别「仍在推理」否则用户会反复点击发送
合法域名清单模型厂商域名、网关域名、对象存储域名分开配置
包体积流解析器放公共包,避免每页复制一套 buffer 逻辑
日志端类型、基础库版本、传输模式(chunked / poll)写入 trace
审批小程序用全屏审批页,避免半屏 confirm

7. 常见反模式

反模式表现后果修正
假跨端uni.request 等完整 body小程序永远转圈或一次吐出分端适配器
微信代码搬支付宝直接 wx.request运行失败分端 API
轮询重跑 Agent每次 poll 新建 run重复写操作poll 只拉事件
H5 示例当小程序文档EventSource无法实现本页矩阵
web-view 渲染对话用 H5 聊天嵌进小程序登录态、注入、性能原生卡片
忽略后台切断切后台后当失败重试重复提交run_id

8. 评测方法

指标说明
First EventH5 / 微信 / 支付宝首包是否在 SLA 内
Chunk Integrity微信 onChunkReceived跨块拼接后事件是否可解析
Fallback Rate全端回退轮询的比例与是否预期内
Abort Works全端停止后服务端 span 结束
Duplicate Write弱网 / 后台重连不增加写工具次数
Domain Fail小程序合法域名误配导致的失败分类

没有真机与目标基础库,不能宣称小程序流式已完成。

9. 安全与治理

  • 小程序 token 放 header 或安全存储,不要拼进 query。
  • 所有端共用同一套数据权限;不能因为是小程序就减少审批。
  • web-view 打开的外部页按不可信内容处理。
  • 轮询接口必须鉴权且按用户隔离 run_id
  • 日志里记录端类型,便于安全事件定位,但不记完整报文。

10. 权威资料