news 2026/10/14 17:37:17

Semantic Router 多协议适配层(Protocol-Neutral Codec Matrix)实现解析:从 Wire JSON 到中立请求的一次解码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Semantic Router 多协议适配层(Protocol-Neutral Codec Matrix)实现解析:从 Wire JSON 到中立请求的一次解码
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

Semantic Router(仓库根目录 README.md)是一个开放、可编程的"模型与计算决策层"。其数据面(Router data plane)面临一个核心问题:客户端可能以 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages 等多种线协议(wire format)发来请求,而最终选中的后端模型可能只原生支持其中一种协议,响应又必须回到客户端原始协议。本文围绕仓库中已落地(status: Implemented)的提案 website/docs/proposals/multi-protocol-adaptor.md 展开,系统讲解"协议中立编解码矩阵"的设计:一次解码进入中立语义、一次编码离开中立语义、codec 与路由策略严格分层,以及支撑它的能力声明、翻译规则、流式引擎、usage 结算与 18 格 E2E 验证矩阵。读完本文,你将掌握该数据面如何在不增加路由策略协议分支的前提下,为任意客户端格式与任意后端协议提供全配对翻译。

一、总体设计:一次解码、一次编码、职责三分

提案给出的数据流如下(mermaid 图来自 multi-protocol-adaptor.md):

其核心结论(Outcome)可以概括为三点:

  1. 一次解码、一次编码:无论客户端或后端采用何种线格式,Router 只对一份"协议中立(protocol-neutral)语义请求"做路由决策。Wire JSON 在入口(ingress)被解码一次,在 provider 边界被编码一次;响应体与流式事件沿反向路径解码/编码后返回客户端。原始 wire 对象从不进入语义路由,也就不会带着未经验证的字段穿透策略层。
  2. 职责三分:传输(transport)拥有监听器、连接、重试与转发;路由核心(routing core)拥有语义模型选择与请求/响应策略;codec只负责把 wire 契约映射到中立类型,不拥有任何一方的生命周期。Standalone 是默认传输;--gateway extproc则把同一套路由核心挂接到 Envoy。
  3. 注册表不可变:codec 注册表(registry)在构造后不可变;新增一种线格式只需新增一对 buffered/streaming codec、声明能力并补齐矩阵测试,路由策略不会因为新增 codec 而长出协议分支。

仓库实现中,这一分工落在两个 Go 包上:pkg/llmprotocol(中立语义契约)与pkg/protocolcodec(线协议编解码引擎),详见 src/semantic-router/pkg/protocolcodec/engine.go 与 src/semantic-router/pkg/protocolcodec/registry.go。

二、中立契约(Neutral contract):pkg/llmprotocol 是什么

pkg/llmprotocol是 codec 与 Router 策略共享的唯一语义契约(src/semantic-router/pkg/llmprotocol/types.go 包注释明确写道:"Wire JSON belongs to codecs; it must not leak into this package.")。它表示:

  • 有序的 instructions、messages 与多模态内容块(Content按Kind闭合,区分text、image、audio、video、file、tool_call、tool_result、reasoning,以及独立的generated_image);
  • 工具定义、工具调用、工具结果、托管式图像生成控制(hosted image-generation)与 tool choice(auto/none/required/named/image_generation);
  • 采样与结构化输出约束(Sampling中的 temperature、top_p、top_k、min_p、repetition_penalty、max_output_tokens、seed、penalties、stop 序列;OutputFormat的text/json_object/json_schema);
  • reasoning 控制与 reasoning 内容(模式enabled/disabled/adaptive、effort、budget、display,以及区分完整文本与摘要的ReasoningScope);
  • 响应备选(Alternatives)与停止原因(StopReason,共 9 种枚举);
  • provider 请求身份与类型化传输错误(TransportError与ProtocolError);
  • 带 provenance 的 token usage——标准输入、缓存读、缓存写、reasoning 输出、其他输出与合计;
  • 受信任的传输元数据(TrustedMetadata:namespace/actor/subject/session/agent/task/turn/correlation 标识),codec 不能从客户端输入填充这些字段。

值得强调的边界:原始 wire 对象不进入语义路由。Envelope虽然可以保留有界的同格式表示细节(src/semantic-router/pkg/llmprotocol/policy.go 定义其为 bounded、ephemeral 的 wire fidelity 状态,且注释明确禁止它被序列化进日志、快照、YAML 或 usage 记录),但它不是路由状态,不能用于绕过校验。

从实现看,中立契约对"缺省值显式化"也很讲究:applyRequestSemanticDefaults会在解码后把"有工具就默认tool_choice=auto"这一 provider 文档化缺省显式写入中立请求(engine.go),从而避免路由插件因客户端是否显式写出缺省而行为不同。

三、Codec 契约与不可变注册表

3.1 每个 codec 要做什么

每个注册的 codec 都是无状态、可并发安全的,声明自己的线格式与能力,并实现四个缓冲操作(文档列出的四项):

  1. 把请求解码为中立请求(DecodeRequest);
  2. 把中立请求编码给后端(EncodeRequest);
  3. 把响应解码为中立响应(DecodeResponse);
  4. 把中立响应编码给客户端(EncodeResponse)。

在 registry.go 中,这四个操作对应MessageCodec接口,此外还有独立的TransportErrorCodec(DecodeTransportError/EncodeTransportError)。传输错误使用独立的类型化契约,因为 HTTP 错误不是失败的模型响应资源——这一点在 pkg/llmprotocol/errors.go 中有明确注释:TransportError代表非 2xx API 响应,而Response.Error是模型生成结果(例如 Responses 资源 status 为failed)。

流式 codec 创建请求作用域(request-scoped)的 decoder/encoder,二者交换中立事件(stream.go 的StreamDecoder.Push/Finalize与StreamEncoder.Push/Finalize);注册表中不存任何可变流状态。

3.2 注册表构建:不可变、强制无状态

Registry构造后不可变,Resolve从不暴露底层 map(registry.go)。NewRegistry会强制校验:拒绝 nil codec;拒绝Stateless() == false的 codec(因为一个注册 codec 可能被并发调用);格式标识必须匹配^[a-z][a-z0-9._-]{0,127}$;每个格式必须实现流式;拒绝重复注册;拒绝空注册表。内建注册表由四种 codec 组成:

NewRegistry(OpenAIChatCodec{}, OpenAIResponsesCodec{}, AnthropicMessagesCodec{}, ImagesCodec{})

(见 NewBuiltinRegistry。)其中openai.images.v1是 sink 方言:Responses 的托管图像生成请求会被重编码到/v1/images/generations,生成的图像再解码回中立GeneratedImage输出项(types.go)。

3.3 能力声明:编码前显式检查

能力(capability)以不可变位集(bitset)形式声明(src/semantic-router/pkg/llmprotocol/capabilities.go),覆盖文本、图像/音频/视频/文件输入输出、工具与并行工具、reasoning(含 adaptive/signature/display/budget)、结构化输出(JSON object / strict schema / strict tool schema)、流式、缓存记账、权威 usage、多候选、采样控制(top_k/min_p/seed/penalties/stop)、缓存指令与隔离、对话状态、自定义工具、matched stop sequence、图像生成、文本 verbosity 等 40+ 项。RequiredCapabilities(request)会从请求中推导所需能力集(例如请求含 strict tool schema、自定义工具、reasoning 或图像生成时分别置位),而能力检查发生在编码之前(RequireCapabilities在EncodeRequest/EncodeResponse/TranslateRequest中均在 encode 前调用,见 engine.go),使不支持的 tools、媒体、多候选、严格 schema、reasoning 或流式行为变得显式、可测试。

四、支持矩阵与翻译规则

4.1 支持矩阵

Wire formatBuffered requestBuffered responseStreamingToolsImagesStructured outputUsage
OpenAI Chat CompletionsDecode and encodeDecode and encodeSSE decode and encodeYesInputJSON object and schemaAuthoritative when present
OpenAI ResponsesDecode and encodeDecode and encodeEvent decode and encodeYesInput plus hosted image-generation lifecycleJSON schemaAuthoritative when present
Anthropic MessagesDecode and encodeDecode and encodeEvent decode and encodeYesInputSupported schema subsetAuthoritative when present

完整的两两配对矩阵(pairwise matrix)——请求、响应、传输错误、流式——都被测试覆盖(matrix_test.go 对三种内建格式两两执行TranslateRequest后再用目标格式解码回验)。请求可以以任意受支持的客户端格式进入,以所选 provider 模型声明的格式离开;响应则回到客户端原始格式。

4.2 翻译是语义翻译,不是字段复制

翻译规则(Translation rules)是文档中的关键约定:

  • 具有等效中立含义的字段被保留;
  • 目标不支持的字段以类型化unsupported_feature错误失败;
  • 未知字段只允许在未修改的同格式(same-format)缓冲往返中存活;
  • 跨格式翻译与语义变更(semantic mutation)会拒绝未知字段,而不是静默丢弃;
  • 请求与响应的Generation在语义变更后递增;
  • 诊断是有界的,通过既有 Router 可观测性契约返回;
  • codec 从不抓取 URL、解析文件、认证调用者或调用 provider。

引擎层通过translationDecodePolicy落实这一规则(engine.go):只有当UnknownFields == UnknownPreserveSameFormat且源格式等于目标格式且未被变更时,才允许保留未知字段;一旦跨格式或存在 mutation,立即切换到UnknownReject+SourceDisabled。DecodeRequestForMutation则是 Router 入口使用的严格契约——模型选择与策略插件必然会在解码后修改请求,因此入口绝不允许接受会被静默擦除的不透明字段。TranslateTransportError特意不走DecodeResponse:失败的模型 Response 资源与 API 传输错误是两种 wire 对象(engine.go)。

类型化错误类别在 pkg/llmprotocol/errors.go 中枚举:invalid_request、authentication、permission、not_found、conflict、unsupported_feature、rate_limited、upstream_unavailable、upstream_timeout、internal。ProtocolError支持Unwrap以透传Cause。嵌套字段的拒绝行为由测试固化,例如 official_schema_contract_test.go:Chat message name、prompt cache breakpoint、Responses defer_loading/reasoning mode、Anthropic eager tool 等嵌套字段均须返回ErrorUnsupportedFeature。

五、流式引擎:事件先解码、终结唯一、usage 结算

5.1 中立事件与请求作用域

每个 provider 事件在进入策略或客户端编码前,先被解码为中立事件(stream.go 的EventType:response.started、output.item.started、output.text.delta、output.reasoning.delta、tool.call.delta、image_generation.progress、output.item.completed、usage.updated、response.completed、response.failed、provider.opaque)。事件带序号(Sequence)、内容索引(ContentIndex,独立于 ItemIndex)与失败作用域(FailureTransport/FailureResponse)。分片网络帧由 wire decoder 缓冲——Router 策略从不解析不完整的 JSON 或 SSE 记录。

StreamEngine(engine.go)强制:

  • 终结状态唯一:response.completed或response.failed出现后流即 terminal,再次 Push 返回stream_terminal冲突错误;
  • 成功终结延迟发布(deferSuccessfulTerminal):provider 的语义成功事件被暂存,直到 HTTP body 干净地到达流尾——否则"终结帧 + 后续分片中的畸形残片"可能在尾部字节被校验前就发布成功(engine.go);
  • 有界诊断:诊断数量被Limits.Diagnostics截断,超限时写入diagnostic_truncated;
  • 最终 usage 结算:流终止时,最终 provider usage 替换中间估计值。

流式场景没有同格式字节级重放路径,因此strictStreamPolicy会拒绝未知字段——接受未知字段就意味着静默丢弃 provider 语义(engine.go)。

5.2 图像生成的生命周期状态机

托管式图像生成复用同一流引擎。输出项从in_progress开始,进度可经generating与有序部分图像推进,最终恰好一次以completed或failed结束。以下情况会在客户端成功终结发布前失败:向后转换(backward transitions)、稀疏的部分图像索引(sparse partial indexes)、冲突的终结状态、以及进度事件上携带结果数据(result data on a progress event)。图像生成测试与 fixture 覆盖了每个已发布选项、null结果与空 payload 的区分、有序进度、连续的部分图像索引、终结成功/失败、畸形 base64 与目标能力拒绝(见 src/semantic-router/pkg/protocolcodec/image_generation_test.go)。

5.3 Router 自产响应不走 provider 形状

Router 自产响应(如拒绝、诊断、合成回复)通过NewEventStreamEncoder直接用中立事件编码到公开线格式(engine.go),不构造中间 provider 形状的流。取消与背压(backpressure)跟随所选传输的请求生命周期。编码器同样受能力检查约束(RequiredEventCapabilities),如 target 无法表达 matched stop sequence 或 custom tool,事件会以类型化错误失败。

六、Usage 与成本:provenance 优先,pricing 是部署元数据

6.1 Token 计数保留 provenance

Token 计数是带指针的(Value *int64),使"缺省"与"权威零值"可区分;Provenance区分authoritative/derived/estimated/unknown(types.go)。这样记账(accounting)可以区分权威 provider usage 与派生、估算或不可用的值。Usage结构体覆盖标准输入、缓存读、缓存写、reasoning 输出、其他输出、输入合计、输出合计与总计。

6.2 定价留在 provider model 上

定价是部署元数据,挂在每个 provider model 上,而routing.modelCards只描述语义能力,不拥有连接或定价数据(src/semantic-router/pkg/catalog/types.go 的Pricing结构与此对应):

providers: models: - name: local/fast pricing: currency: USD prompt_per_1m: 0.20 cached_input_per_1m: 0.02 cache_write_per_1m: 0.25 completion_per_1m: 0.80

规则:货币可选,省略时按 USD 记账;配置的费率必须有限且非负;显式 0 表示免费费率。语义能力(modelCards)与连接/定价数据的分层,使得能力声明可复用于路由决策,而定价只服务于成本结算。

七、安全边界:可信元数据只能由传输填充

  • 客户端可控的 header 与 body 元数据一律不可信。只有经过认证的传输边界才能填充 trusted identity / session / task / correlation 字段(TrustedMetadata),且必须在传输层确立这些字段之后;
  • codec 不能把 wire 元数据提升为 trusted 元数据——中立请求的Trusted字段(types.go)注释明确:"populated by the Router after transport authentication. Codecs never populate trusted fields from client headers";
  • 公共推理监听器与管理监听器保持分离;codec 层本身不认证调用者、不代理请求、不管理模型 worker——这些分别是前端与运行时的职责。

八、验证契约:schema 钉扎 + fixture 语料 + 18 格 E2E

8.1 Schema 契约钉扎到上游修订版

模式契约钉扎到已发布的上游修订版:OpenAI OpenAPI690521b1753dce0c6d6b275f583d22537679cff9、Anthropic SDKd19dea9ed85bbb5fdb2d6f20fb6f903920ed23fa。测试将每个顶层字段、嵌套对象字段与已发布的 union 判别符闭合到显式的语义(semantic)、仅传输(transport-only)、扩展(extension)或不支持(unsupported)处置类别。顶层请求/响应清单对已发布的 OpenAI OpenAPI schema 与生成的 Anthropic Messages 类型封闭——新发布的字段不能穿过未类型化的 JSON 桶:它必须映射到中立契约、作为显式不支持特性失败、或被记录为有意从客户端表示中省略的有界 provider 元数据(相关测试见 official_schema_contract_test.go 与 official_schema_inventory_test.go)。

8.2 E2E provider 模拟器与 Go 一致性测试

E2E provider 模拟器属于同一契约:其原生 Chat、Responses、Messages 边界使用修订钉扎的闭合清单,对未知顶层字段以 provider 原生错误拒绝,并在无未类型化规范化步骤的情况下保留嵌套 wire 对象。Go 一致性测试 provider_simulator_contract_test.go 把模拟器清单(schema_contract.json,含schema_revision、官方/扩展请求响应字段、usage 字段与 required 字段)与 codec 的请求/响应/usage wire 清单直接比对。模拟器测试重放每个已发布顶层请求字段,codec goldens 闭合嵌套字段与 union 判别符——这防止"宽松的 mock 隐藏 ExtProc 丢弃或发明的字段"。

8.3 人类可读 fixture 约定

fixture 采用稳定的输入/输出命名约定:

NNN-{client-protocol}-{case}-in.json NNN-{client-protocol}-{case}-{backend-protocol}-out.json

每个请求、响应、传输错误、流、能力边界与类型化拒绝输入,对每种内建目标协议恰好有一个期望输出。流 fixture 保留精确的 SSE transcript,并逐字节重放一次,以证明传输 chunk 边界不改变语义(golden 测试见 golden_matrix_test.go)。语料覆盖:畸形与截断 JSON、重复字段、非法 union 与枚举、有序多模态内容、工具与工具结果、结构化输出、reasoning、usage、取消、超时、不完整流、流中途失败、身份变更、序列违规、托管图像生成与资源限制。

8.4 部署级 18 格矩阵

部署级覆盖是必需的 18 格矩阵:

3 client protocols x 3 native backend protocols x 2 modes = 18 E2E cells

每个格穿越 Envoy 与 ExtProc:验证客户端原生缓冲信封或 SSE 生命周期,要求翻译后的输出带确定性后端标记,并拒绝泄漏的后端 wire 形状。另有附加 E2E 契约覆盖每个后端协议的结构化输出、缓冲 provider 错误、工具调用延续、不完整流与部分输出后的错误。--gateway extproc与 standalone 两种模式的差异与选用见 website/docs/installation/k8s/gateways.md(Helm chart 中gateway.mode: extproc时 Router 在 50051 端口服务 ext_proc gRPC)。

九、扩展清单:新增一种线格式要做什么

一个新 codec 只有在提供以下全部内容时才算完成:

  • 稳定的 wire-format 标识符(匹配^[a-z][a-z0-9._-]{0,127}$);
  • 显式的能力声明(capability declaration);
  • 严格的 buffered 请求、响应与传输错误 codec;
  • 请求作用域的流解码与编码(request-scoped stream decoding and encoding);
  • 畸形输入与不支持特性的测试;
  • 对每种内建格式的两两矩阵覆盖;
  • 权威 usage 与终结事件测试;
  • ExtProc 回归覆盖,证明路由行为不变。

由于注册表不可变、能力检查前置、未知字段策略严格,扩展者不需要触碰任何路由策略代码——这正是该设计"策略无协议分支"的工程回报。

十、关键文件导航

  • 提案原文:website/docs/proposals/multi-protocol-adaptor.md
  • 中立语义契约:src/semantic-router/pkg/llmprotocol/types.go、src/semantic-router/pkg/llmprotocol/errors.go、src/semantic-router/pkg/llmprotocol/capabilities.go、src/semantic-router/pkg/llmprotocol/policy.go、src/semantic-router/pkg/llmprotocol/stream.go
  • 编解码引擎与注册表:src/semantic-router/pkg/protocolcodec/engine.go、src/semantic-router/pkg/protocolcodec/registry.go
  • 各线格式 codec:codec_openai_chat.go、codec_openai_responses.go、codec_anthropic_messages.go、images_codec.go(均位于 src/semantic-router/pkg/protocolcodec)
  • 矩阵与契约测试:matrix_test.go、official_schema_contract_test.go、provider_simulator_contract_test.go、golden_matrix_test.go
  • 定价元数据:src/semantic-router/pkg/catalog/types.go
  • 相关文档:Router API、Semantic Router 系统总览、Kubernetes 网关部署选项
  • 后端
  • API网关
  • 模型推理服务
  • AI Agent

【免费下载链接】semantic-router

An open, programmable decision layer for models and compute.

项目地址:https://gitcode.com/gh_mirrors/sem/semantic-router
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:37:16

绕线式异步电动机转子串电阻分级起动仿真:建模、参数与避坑指南

简介:这份资源围绕绕线式异步电动机转子串电阻分级起动过程,提供基于MATLAB/Simulink搭建的瞬态仿真模型,面向电气工程、电机控制方向的学生与工程师,用于在实机投运前评估起动电流冲击、优化各级起动电阻并确定断路器闭合时间。模…

作者头像 李华
网站建设 2026/10/14 17:36:25

高强度紧固件迎来价值重估

高强度紧固件正在经历一轮结构性的价值重估。2025年中国高强度紧固件行业市场规模达到641.35亿元,同比增长4%,产量攀升至336.85万吨,需求量达508.4万吨。全球高强度螺栓市场同年销售额约91.25亿美元,预计2032年将达到140亿美元&am…

作者头像 李华
网站建设 2026/10/14 17:34:59

Proteus仿真STM32:从最小系统搭建到固件加载与避坑指南

简介:这份资源面向嵌入式初学者与电子设计开发者,提供在Proteus中完成STM32单片机仿真的完整工程,帮助读者在无实物硬件的情况下验证电路与程序,降低学习与开发成本。压缩包共433个文件,约9.04MB,以290个C源…

作者头像 李华
网站建设 2026/10/14 17:34:52

2026年智能社会、数字经济与信息系统国际会议(SDIS 2026)

2026年智能社会、数字经济与信息系统国际会议(SDIS 2026) 2026 International Conference on Smart Society, Digital Economy, and Information Systems【一】、会议信息 会议地点:中国武汉 收录保障:提交至Ei Compendex,CPCI,CN…

作者头像 李华
网站建设 2026/10/14 17:34:19

脑机接口医疗健康应用白皮书:原理、政策与落地场景

简介:中国人工智能产业发展联盟于2021年7月发布的《脑机接口技术在医疗健康领域应用白皮书》,是一份面向脑科学、人工智能与医疗健康交叉领域从业者和研究者的AI产业研报,系统梳理脑机接口关键技术、政策环境、典型应用场景与优秀案例&#x…

作者头像 李华
网站建设 2026/10/14 17:33:00

ponytail skill与插件实战:从零搭建可复用工作流

1. 从“ponytail”这个词说起:它到底是什么第一次看到“ponytail”这个词,很多人脑子里蹦出来的画面大概是扎起来的马尾辫。但在技术圈和工具圈里,ponytail 早就不是发型那么简单了。最近一段时间,ponytail skill、ponytail 插件、…

作者头像 李华