DeepSeek Harness StreamChunk 协议与错误处理(官方完整规范)
StreamChunk 是 DeepSeek Harness 定义的大模型流式输出唯一标准契约。所有 LLM 适配器无论对接 DeepSeek、OpenAI、Ollama、vLLM 或私有化自研模型,必须将异构模型流式响应,统一转换为标准 StreamChunk 分片格式。
配合官方标准化 LlmError 错误体系,Harness 实现了流式输出统一渲染、结构化解析、链路可观测、故障自动自愈,彻底解决不同模型返回格式不统一、流式截断、异常无规范、重试逻辑混乱的行业痛点。
本文完整拆解 StreamChunk 六阶段协议、字段约束、流转顺序、闭合规则、分片语义,以及全套官方错误码、异常捕获、容错处理实战规范。
一、核心价值:为什么必须统一 StreamChunk 协议?
传统流式 SSE 存在致命缺陷:不同模型厂商返回字段、结构、结束标识、错误格式完全不统一,上层 Agent、前端渲染、日志回放、Token 统计需要写大量兼容逻辑,耦合严重、维护成本极高。
Harness StreamChunk 协议的核心设计目标:下层模型无限异构,上层逻辑完全统一。
- 渲染统一:前端通过 delta 增量实现丝滑打字机效果,通过完整 block 实现结构化回放
- 解析统一:文本、思维链、工具调用多通道输出,协议原生支持,无需特殊适配
- 统计统一:全局唯一 usage 分片规范,统一统计输入输出 Token
- 终止统一:finish 唯一结束标识,杜绝流式挂起、半截截断
- 错误统一:标准化 LlmError 错误码,支撑 Agent 自动重试、熔断、降级策略
二、StreamChunk 六阶段完整协议(官方强制规范)
一次完整的模型流式推理,必须严格遵循 6 类分片、固定顺序、对称闭合、严格递增 的官方协议,缺一不可、顺序不可颠倒。
完整流转顺序:block-start → 增量分片 → block-end → usage → finish
1. 阶段一:block-start 块起始分片
作用:声明一个全新内容块开始,支持文本、思维链、工具调用多种块类型,是流式结构的起始标记。
{
type: 'block-start',
index: 0,
blockType: 'text' // text / thinking / tool-call
}
强制约束:
- 全局 index 从 0 开始,严格单调递增,不重复、不跳号
- 每一个 block-start 必须对应一个同 index、同类型的 block-end
- 不允许无起始直接推送增量 delta 分片
2. 阶段二:增量 delta 分片(实时输出核心)
分为文本增量与工具调用增量,用于实时流式推送,支撑前端毫秒级打字机渲染。
文本增量分片
{
type: 'text-delta',
index: 0,
text: '增量文本内容'
}
工具调用增量分片
{
type: 'tool-call-delta',
index: 1,
id: CallId('唯一调用ID'),
name: 'execute_bash',
argumentsDelta: '{"command":"ls'
}
强制约束:
- delta 分片必须隶属于对应 index 的已开启未闭合块
- argumentsDelta 为 JSON 字符串切片,框架自动拼接完整参数,适配器无需手动拼装
- 支持多片连续增量推送,适配模型流式分段输出
3. 阶段三:block-end 块结束分片
作用:闭合对应内容块,输出完整、可校验、可持久化的结构化数据,用于日志存储、轨迹回放、参数校验。
{
type: 'block-end',
index: 0,
block: {
type: 'text',
text: '完整拼接后的文本内容'
}
}
强制约束:
- 必须携带完整 block 结构体,不能为空
- 闭合后该 index 块禁止再推送任何 delta 分片
- 工具调用块必须携带完整 id、name、arguments 结构化参数
4. 阶段四:usage Token 统计分片
作用:统一统计本轮推理输入、输出 Token,用于计费、限流、性能监控、上下文管理。
{
type: 'usage',
usage: {
inputTokens: 120,
outputTokens: 68
}
}
强制约束:
- 必须在所有 block-end 之后、finish 之前推送
- 一轮完整推理仅允许出现一次 usage 分片
5. 阶段五:finish 结束分片(流终止唯一标识)
作用:标记本轮模型流式推理彻底结束,是整条流的最后一个分片,杜绝挂起残留。
{
type: 'finish',
reason: { kind: 'stop' }
}
结束原因枚举(官方标准):
- stop:正常自然结束,内容输出完整
- tool-calls:触发工具调用,模型主动终止文本输出
- length:触发最大 Token 长度限制截断
- cancel:用户手动点击停止、前端主动中断
强制约束:finish 分片必须是整条流式输出的绝对最后一个分片,其后禁止任何数据输出。
三、协议核心铁律(开发必守,违规必Bug)
1. 对称闭合铁律
所有 block-start 必须一一对应同 index、同类型的 block-end,禁止有头无尾、无头有尾、交叉闭合。
2. Index 递增铁律
所有内容块 index 从 0 开始,严格递增、顺序不可打乱、不可重复、不可跳号。
3. 分片顺序铁律
块流转固定:start → delta → end;全局流转固定:所有块结束 → usage → finish。
4. 唯一终止铁律
仅 finish 分片代表流结束,不能通过超时、空数据、HTTP 结束替代终止标识。
四、完整标准流式输出示例
包含文本输出 + 工具调用的完整标准流,可直接作为适配器开发模板:
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm';async function* standardStreamExample(): AsyncIterable<StreamChunk> {
// 1. 文本块开始
yield { type: 'block-start', index: 0, blockType: 'text' };
yield { type: 'text-delta', index: 0, text: '已完成代码分析,准备执行' };
yield { type: 'block-end', index: 0, block: { type: 'text', text: '已完成代码分析,准备执行' } };
// 2. 工具调用块开始
yield { type: 'block-start', index: 1, blockType: 'tool-call' };
yield { type: 'tool-call-delta', index: 1, id: CallId('call-001'), name: 'execute_bash', argumentsDelta: '{"command":"ls -la"}' };
yield {
type: 'block-end',
index: 1,
block: { type: 'tool-call', id: CallId('call-001'), name: 'execute_bash', arguments: '{"command":"ls -la"}' }
};
// 3. Token统计
yield { type: 'usage', usage: { inputTokens: 156, outputTokens: 89 } };
// 4. 流结束
yield { type: 'finish', reason: { kind: 'tool-calls' } };
}
五、LlmError 官方标准化错误处理体系
Harness 不允许适配器抛出原生 Error,所有模型层异常必须抛出带标准错误码的 LlmError。Agent 主循环会根据错误码执行自动重试、熔断、降级、上下文截断等自愈策略。
1. 标准 LlmError 结构
import { LlmError } from '@deepseek-ai/dsh-llm';// 标准抛出格式
throw new LlmError('模型接口请求失败', 'PROVIDER_HTTP_ERROR');
2. 官方完整错误码清单
| 错误码 | 场景说明 | 自愈策略 |
|---|---|---|
| PROVIDER_HTTP_ERROR | 模型接口返回非200状态码、网络异常 | 自动重试3次,指数退避 |
| PROVIDER_TIMEOUT | 模型请求超时、流式长时间无响应 | 超时重试,超限终止 |
| PROVIDER_RATE_LIMIT | 接口限流、配额不足 | 静默等待退让后重试 |
| PROVIDER_AUTH_FAILED | API密钥错误、鉴权失败 | 直接终止,不重试,抛出配置异常 |
| PROVIDER_INVALID_FORMAT | 模型返回数据格式非法、解析失败 | 截断异常分片,尝试恢复输出 |
| REQUEST_ABORTED | 用户主动停止、前端触发AbortSignal | 优雅终止,不报错、不重试 |
3. 实战异常处理模板
整合请求头、中止信号、异常捕获、标准报错,生产级可用:
import { attributionHeaders, LlmAdapter, LlmError, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm';export class StandardAdapter extends LlmAdapter {
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
try {
const res = await fetch('你的模型接口地址', {
method: 'POST',
headers: {
'content-type': 'application/json',
...attributionHeaders()
},
body: JSON.stringify({
model: options.model,
messages: options.messages,
tools: options.tools,
stream: true
}),
signal: options.signal
}); // HTTP状态异常捕获 if (!res.ok) { if (res.status === 401) throw new LlmError('密钥鉴权失败', 'PROVIDER_AUTH_FAILED'); if (res.status === 429) throw new LlmError('接口限流', 'PROVIDER_RATE_LIMIT'); throw new LlmError(`请求异常${res.status}`, 'PROVIDER_HTTP_ERROR'); } // 流式解析逻辑... } catch (err: any) { // 用户主动取消,优雅结束流 if (err.name === 'AbortError') { yield { type: 'finish', reason: { kind: 'cancel' } }; return; } // 抛出标准化异常 throw err instanceof LlmError ? err : new LlmError('未知模型异常', 'PROVIDER_HTTP_ERROR'); } }
}
六、高频踩坑与官方规避方案
1. 流式挂起、页面卡死
坑:忘记推送 finish 分片,框架判定流未结束,会话一直处于生成中。
方案:无论正常结束、异常结束、用户取消,必须兜底推送 finish 分片。
2. 工具调用参数解析失败
坑:只推送 delta 增量,未在 block-end 补全完整 JSON 参数。
方案:block-end 阶段必须拼装完整结构化 arguments,保证框架可直接解析调用。
3. 异常后无限重试
坑:抛出原生 Error,框架无法识别异常类型,默认无限重试。
方案:严格使用 LlmError 分类报错,鉴权类、格式类错误禁止重试。
4. Token 统计丢失
坑:usage 分片放在 finish 之后,被框架丢弃。
方案:固定顺序:块结束 → usage → finish。
5. 链路追踪失效
坑:未携带 attributionHeaders,丢失会话 TraceID,无法排查云端问题。
方案:所有模型请求强制合并归属请求头。
七、开发最佳实践
- 严格遵守六阶段协议顺序与闭合规则,不自定义分片、不省略阶段。
- 所有异常统一使用 LlmError,精准匹配官方错误码,适配框架自愈策略。
- 监听 options.signal 中止信号,用户取消时优雅终止流,释放网络资源。
- 统一携带 attributionHeaders,保障链路可观测、可审计。
- 异常场景必须兜底输出 finish 分片,杜绝流式挂起。
- usage 分片唯一且前置 finish,保证 Token 统计精准。
本篇小结
StreamChunk 协议是 Harness 流式能力的统一语法契约,通过六阶段标准化分片,抹平了所有大模型后端的输出差异,实现上层业务、渲染、统计、回放完全统一。
LlmError 标准化错误体系是智能体自愈能力的基础,精准分类异常、适配重试策略,让 Agent 从“简单调用模型”升级为“可容错、可自愈、可观测”的生产级智能体。
掌握协议规范与错误处理,是开发生产级自定义 LLM 适配器、解决流式兼容问题、提升智能体稳定性的核心关键。
0 条笔记