IronClaw OpenAI 兼容层深度解析Chat Completions 与 Responses 适配器的边界、幂等与流式设计【免费下载链接】ironclawIronClaw is an Agent OS focused on privacy, security and extensibility项目地址: https://gitcode.com/gh_mirrors/iro/ironclaw本篇技术指南围绕 IronClaw 开源仓库中ironclaw_openai_compatcrate 展开完整讲解这一 OpenAI 形状的入站适配器它如何在 Product Surface 之上提供/v1/chat/completions与/v1/responses两套 OpenAI 兼容 API如何通过不透明引用Opaque Ref与幂等键保证重试安全以及如何在不绑定 socket、不直连 LLM 的前提下完成路由装配、模型列表与 SSE 流式翻译。读完你将掌握该适配器的架构边界、九个路由入口的策略细节、错误分类表和可复制的验证命令可直接用于二次开发与问题排查。一、Crate 定位产品面的入站协议皮肤ironclaw_openai_compat位于 crates/product/ironclaw_openai_compat/是 IronClaw 的产品层productfamily中负责对外提供 OpenAI 兼容 API 的独立 crate。其官方 README 定位为The OpenAI-shaped ingress adapter over the product surface——即架设在产品边界ProductSurface之上的入站协议适配器它只负责线协议不负责能力实现。从源码结构看crate 拥有完整的自洽边界见 src/lib.rs可以定义DTOChat / Responses / Models 三组请求响应结构、路由描述符route descriptors、脱敏错误信封、供宿主组合层挂载的 axum 路由片段必须禁止绑定 socketTcpListener::bind、axum::serve、直接代理到ironclaw_llm、直接调用ironclaw_turns/ironclaw_event_streams/ 运行时 / lane crate。这份职责划分不是口头约定而是被架构测试锁死crate 的BoundaryRule禁止引入ironclaw_turns、ironclaw_event_streams及运行时/lane crate因此投影projection适配器必须存放在ironclaw_composition中、以 port 形式注入唯一的域级例外是ironclaw_threads——预备上下文通道prepared lane需要借用 accept door 的种子词汇表并复用其validate_prepared_seed_content校验器且线程服务本身仍然以 port 形式注入绝不直接 import。架构边界由 crates/app/ironclaw_architecture_tests/tests/reborn_dependency_boundaries.rs 等测试守护。与之配套crate 对ironclaw_assistant的依赖被刻意压缩到三个冻结的命令描述符常量SUBMIT_TURN_COMMAND、CREATE_THREAD_COMMAND、CANCEL_RUN_COMMAND。这份清单被 reborn_transport_product_boundary.rs 以精确匹配、只减不增shrink-only的方式钉死新增任何ironclaw_assistant导入都会导致该测试失败。这是 WS5 传输层反转2026-08-01后的既定格局适配器只说自己该说的协议具体命令语义全部通过ironclaw_product_contracts的surface/inbound_requests/inbound/outbound/projection/product_wire契约交互外加ironclaw_extension_contracts中唯一的通道枚举ProductTriggerReason。二、路由装配Mount 归本 crate实现归组合层WS6 OpenAI 兼容层驱逐2026-08-05之后路由器装配的责任从组合层composition移交给了本 crate。核心入口是 src/mount.rs 中的openai_compat_route_mount它接收组合层填充的OpenAiCompatRouteMountPorts返回一个ironclaw_host_ingress::ProtectedRouteMount。OpenAiCompatRouteMountPorts共八个字段全部是 port 而非具体实现字段类型用途product_surfaceArcdyn ProductSurface两个工作流共同依赖的产品边界ref_storeArcdyn OpenAiCompatRefStorePort公开 ID ↔ 内部引用映射的持久化存储chat_projection_readerArcdyn OpenAiChatCompletionProjectionReaderChat 投影读取responses_projection_readerArcdyn OpenAiResponsesProjectionReaderResponses 投影读取external_tool_storeArcdyn OpenAiCompatExternalToolStore客户端工具规格注册与输出提交external_tool_resumeArcdyn OpenAiCompatExternalToolResume暂停在客户端工具调用上的运行恢复llm_configOptionArcdyn LlmConfigService支撑GET /v1/models的算子 LLM 配置prepared_turn_portArcdyn OpenAiCompatPreparedTurnPort结构化输出/工具历史的预备上下文门装配顺序是 mount 自身拥有的规则openai_compat_route_mount先用product_surface构建投影流式器projection streamer再分别构造OpenAiChatCompletionsWorkflow与OpenAiResponsesWorkflow然后通过OpenAiCompatRouterState::with_chat_completions(...).with_responses_workflow(...)注入路由状态若llm_config存在则额外包装成LlmConfigModelCatalog并调用with_models_catalog。组合层只负责提供 port 实现它们会命名ironclaw_turns/ironclaw_event_streams恰在本 crate 的禁止列表上不再知道 builder 顺序。装配的默认姿态是fail-closedOpenAiCompatRouterState::default()即not_wired()下三个工作流均为NoneChat、Responses 与 Models 路由全部返回501直到宿主组合层注入对应依赖。生产环境ironclaw serve通过ironclaw_composition::build_openai_compat_route_mount完成这份宿主接线。三、路由表与入口策略九个端点的完整参数路由模式常量定义在 src/descriptors.rsopenai_compat_routes()返回九个IngressRouteDescriptor路由 ID方法路径模式Body 上限限流效果路径openai.compat.chat_completionsPOST/v1/chat/completions14 MiBMAX_CHAT_BODY_BYTES60 次/60 秒/调用者ProductSurfaceopenai.compat.models.listGET/v1/models无 Body120 次/60 秒/调用者ProjectionOnlyopenai.compat.models_api.listGET/api/v1/models无 Body120 次/60 秒/调用者ProjectionOnlyopenai.compat.responses_api.createPOST/api/v1/responses1 MiB60 次/60 秒/调用者ProductSurfaceopenai.compat.responses_v1.createPOST/v1/responses1 MiB60 次/60 秒/调用者ProductSurfaceopenai.compat.responses_api.retrieveGET/api/v1/responses/{response_id}无 Body120 次/60 秒/调用者ProjectionOnlyopenai.compat.responses_v1.retrieveGET/v1/responses/{response_id}无 Body120 次/60 秒/调用者ProjectionOnlyopenai.compat.responses_api.cancelPOST/api/v1/responses/{response_id}/cancel4 KiB60 次/60 秒/调用者ProductSurfaceopenai.compat.responses_v1.cancelPOST/v1/responses/{response_id}/cancel4 KiB60 次/60 秒/调用者ProductSurface所有端点共享以下入口策略create_policy/retrieve_policy/cancel_policy认证强制 Bearer TokenIngressAuthScheme::BearerToken且作用域取自已认证调用者IngressScopeSource::AuthenticatedCallerCORSHostConfiguredAllowlist宿主配置白名单监听类LocalGateway创建端点支持 SSE 流式StreamingMode::Sse查询与取消不支持审计统一AuditTraceClass::UserAction。值得注意的两处细节一是 Chat Completions 的 14 MiB body 上限由MAX_CHAT_BODY_BYTES单一事实源决定同时被入口描述符的body_limit与工作流内parse_chat_request的 body 检查共用二者不可能漂移——这个容量是为了容纳 base64 内联图片视觉输入issue #4644二是取消端点的 4 KiB body 上限足以容纳空的或极小请求体。四、不透明引用与幂等性公开 ID 的完整生命周期src/refs.rs 是 OpenAI 兼容身份契约的产权登记处。4.1 公开 ID 前缀与生成规则OpenAiChatCompletionIdchatcmpl-*前缀chatcmpl- UUIDv4 简写后缀OpenAiResponseIdresp_*前缀resp_ UUIDv4 简写后缀。生成的 ID 使用宿主熵Uuid::new_v4()禁止编码租户、用户、线程、运行、产品动作、投影、游标或宿主路径等任何内部值——这是隐私边界的基础。validate_public_ref还会拒绝空后缀、非 ASCII 字母数字_/-除外的后缀且公开引用总长不得超过 96 字节。4.2 幂等键的作用域语义客户端幂等键OpenAiCompatIdempotencyKey最长 256 字节的冲突判定按actor 作用域 路由面 请求体指纹三元组进行请求体指纹OpenAiCompatRequestFingerprint是原始 body 的 SHA-256 摘要格式sha256:64位十六进制相同 key 相同指纹 → 重放replay同一映射返回同一结果相同 key 不同指纹 → 返回脱敏后的409 Conflict缺失幂等键 → 每次 POST 都新建映射。4.3 引用校验与防泄露哨兵所有引用公开 ID、幂等键、内部引用都经过validate_bounded_clean_ref校验非空、无首尾空白、按字节限长、无 NUL/控制字符、无路径分隔符/与\、无冒号内部引用允许冒号。此外还维护了一份NO_EXPOSURE_SENTINELS哨兵清单RAW_PROMPT_SENTINEL、SECRET_SENTINEL、secret-token、sk-live、/host/path、/Users/任何引用包含这些片段一律拒绝——从机制上杜绝敏感信息经 ID 回显泄露。4.4 映射生命周期OpenAiCompatResourceMapping以Pending状态创建随后被 ProductSurface 接线切片绑定到内部 product-action / turn-run / projection 引用Bound状态。关键安全属性lookup / cancel / stream-resume 的鉴权检查使用 actor 作用域未授权与不存在的引用对 API 调用者刻意不可区分统一返回404。存储侧提供了 side-effect-free 的OpenAiCompatRefStorePort与持久化的OpenAiCompatRefStore适配器基于通用RootFilesystemport且本 crate 不声明任何 cargo feature——所有代码无条件编译ironclaw_filesystem是普通[dependencies]条目任何消费者都会拉入。五、Chat Completions 工作流双通道与投影等待核心服务是 src/chat_workflow.rs 中的OpenAiChatCompletionsWorkflow。POST /v1/chat/completions的整体流程解析与校验parse_chat_request检查 body 不超 14 MiB、反序列化 DTO、validate_model_name校验模型名通道决策lane decision先行每个非流式且未声明客户端工具的请求走预备上下文通道prepared_turn.rs——通道决策、消息映射、门校验全部在幂等预留之前执行因此门会拒绝的请求体永远不会消耗调用者的幂等键声明了实时客户端工具或设置stream: true的请求留在会话通道conversation lane幂等预留以 actor 作用域 ChatCompletions路由面 body 指纹预留chatcmpl-*引用Created / Replayed / Conflict 三态提交通过通道中立的ProductSurface服务提交用户消息。会话通道内部会先调用CREATE_THREAD_COMMAND建立线程再以SUBMIT_TURN_COMMAND提交ProductSubmitTurnRequest并将返回的ProductInboundAck记录到 ref storerecord_accepted_ack供幂等重放复用投影等待路由从已认证调用者与 ProductSurface 线程响应构造规范化的投影读取请求再通过组合层提供的OpenAiChatCompletionProjectionReader等待结果默认等待超时 30 秒DEFAULT_CHAT_WAIT_TIMEOUT超时返回可重试的脱敏503且不取消、不脱离底层产品 turn绑定内部引用投影结果中的internal_refs在 2 秒超时窗口内绑定到 ref storebind_internal_refs超时仅告警不阻塞响应。两个必须遵守的约束投影读取的 actor/scope 必须与已认证调用者一致ensure_projection_read_matches_callertools/tool_choice仅是模型提示model hints被转发到投影读取请求作为仅模型元数据OpenAiChatModelOnlyTools本 crate 绝不把它们当作 Reborn 能力执行。关于stream: true只有当组合层注入OpenAiCompatProjectionStreamer时才启用。路由将投影安全的 outbound 信封翻译为 OpenAI 兼容的 SSE 事件抑制keepalive/控制帧、内部引用、投影游标与脱敏的后端细节。流式创建同样先做幂等预留与提交然后进入 src/streaming.rs 的chat_sse_response翻译管道。六、模型列表GET /v1/models的 fail-closed 设计GET /v1/models与别名/api/v1/models路由 IDopenai.compat.models.list/openai.compat.models_api.list面向 OpenAI 兼容客户端模型选择器等列出部署配置的模型。其设计要点见 src/models.rs 与 src/mount.rs 的LlmConfigModelCatalog先认证后查询缺失OpenAiCompatAuthenticatedCaller时在查询目录前即 fail-closed 返回401目录来源是 portOpenAiCompatModelCatalog与投影 reader/streamer 一样由宿主注入未接线时路由 fail-closed 返回501与 Chat/Responses 表面未接线时的行为完全一致单一事实源mount::LlmConfigModelCatalog是算子LlmConfigService快照的投影——与算子 WebUI 使用的模型来源相同。OpenAiCompatRouteMountPorts::llm_config是OptionNone即产生上面的501错误映射表LlmConfigServiceError → OpenAI 信封的映射含状态码与可重试性位于本 crateInvalidRequest → 400、NotFound → 404、Unavailable → 可重试 503、Internal → 500且错误消息全部脱敏。crate 将目录条目映射为 OpenAI 列表信封{ object: list, data: [{ id, object: model, created, owned_by }] }条目排序规则为活动选择active selection优先随后是各 provider 的活动或默认模型按模型 id 去重、保持顺序owned_by取 provider id对应 src/mount.rs 的model_entries_from_snapshot并有单测覆盖去重与回退默认模型的场景。model字段本身在解析边界被 src/model_validation.rs 的validate_model_name校验非空、无首尾空白、无控制字符、不超过 256 字节#2673 有界资源约束。违规返回点名model参数的脱敏400。Chat 与 Responses 的 create 请求共用这条规则。七、Responses 工作流Create / Retrieve / Cancel 与续跑src/responses_workflow.rs 的OpenAiResponsesWorkflow实现 Responses 切片四条路径分别对应POST /v1/responses与/api/v1/responses预留resp_*引用actor 作用域幂等通过ProductSurface提交 create 请求再经组合层提供的OpenAiResponsesProjectionReader等待完成默认 30 秒超时超时为可重试脱敏503。请求体上限 1 MiB上下文上限 10 KiB输入条目上限 1000GET /v1/responses/{id}与/api/v1/responses/{id}通过授权的 opaque-ref 查找读取投影支持的最终状态禁止从历史消息重构状态POST /v1/responses/{id}/cancel与/api/v1/responses/{id}/cancel对已授权且已绑定的响应引用提交类型化的 ProductSurface 取消动作——内部构造CANCEL_RUN_COMMAND的ProductCancelRunRequest含run_id、thread_id、原因 cancelled by OpenAI-compatible Responses API随后返回取消后的投影状态。未授权与不存在的引用在 API 边界保持不可区分stream: true走同一条 ProductSurface 提交与 opaque ref 预留路径随后将组合层提供的投影流式器排空为 OpenAI 兼容的 Responses SSE 事件停滞的流受工作流等待超时约束失败时返回脱敏的可重试服务错误。Responses 切片在工具支持上做了明确的渐进策略请求中的tools/tool_choice默认不支持空的tools: []视同省略当组合层同时注入external_tool_store与external_tool_resume两个 port 时启用外部工具能力——提交时注册客户端工具规格register_tools携带function_call_output的续写请求会复用暂停运行的绑定引用恢复resume而非新建 turn暂停的运行仍持有线程活动锁新建提交会被 busy 拒绝恢复幂等由external_tool_resume_completed标记保证。仅注入其中一个 port 时tools相关请求 fail-closed 返回稳定的400。客户端控制的 Responses 输入被序列化为UserMessagePayload文本内的结构化openai_compat.responses_input.v1JSON 载荷使得 CR/LF 分隔的角色伪装无法制造合成对话行同时保留function_call的call_id与arguments字段。八、DTO 策略与脱敏错误分类DTO 策略宽松入、严格出请求 DTO 有意容忍未知字段使携带更新可选参数的 OpenAI 兼容客户端不会在反序列化阶段失败而影响 Reborn 策略的字段tools、tool_choice、stream、model被显式建模以便后续切片用稳定错误拒绝不支持的行为。响应与错误 DTO 则保持窄小narrow。错误信封由 src/error.rs 统一构造禁止外泄原始后端消息、宿主路径、密钥、provider/运行时诊断或原始用户内容。错误模型为{ message, type, param, code }param通过clean_param白名单清洗仅允许body、idempotency_key、input、messages、metadata、model、previous_response_id、response_id、stream、temperature、tool_choice、tools等根字段及其合法下标HTTP 状态码经sanitize_status_code收敛到 4xx / 500 / 501 / 503 白名单。核心错误分类映射源码与单测双重确认场景状态码可重试ErrorKind请求体/模型名校验失败400否Validation认证证据缺失401否Authenticationsubject/tenant 与 scope 不一致403否PermissionDenied引用不存在/未授权404否NotFound幂等键指纹冲突 / StaleGate / AmbiguousResolution409否ConflictDeferredBusy闸门429是RateLimitedRejectedBusy闸门429否RateLimited投影等待超时 / StoreUnavailable / UnknownInstallation503是ServiceUnavailable未接线的工作流或目录501否Unsupported内部错误500否Internal其中StaleGate映射为不可重试的 409 尤其关键审批门已解决通过/拒绝后再对已解决状态重放同一审批必然冲突客户端不应重试同一审批动作。九、验证命令与守护测试本 crate 的边界与契约由三层测试守护来自 CLAUDE.md 与 README.md# 单元与契约测试DTO、refs、错误信封、流式处理器、Responses 路径前缀等 cargo test -p ironclaw_openai_compat # Clippy 全目标全特性严格检查 cargo clippy -p ironclaw_openai_compat --all-targets --all-features -- -D warnings # 架构边界BoundaryRule禁止 turns/event_streams/运行时/lane 依赖 cargo test -p ironclaw_architecture_tests reborn_crate_dependency_boundaries_holdcrate 内tests/目录还包含chat_workflow_handlers_contract.rs、descriptors_contract.rs、dto_contract.rs、error_contract.rs、models_handlers_contract.rs、ref_store_contract.rs、refs_contract.rs、responses_path_prefix_contract.rs、responses_temperature_contract.rs、responses_workflow_handlers_contract.rs、streaming_handlers_contract.rs、stub_handlers_contract.rs等契约测试文件锁定线协议稳定性承诺。架构套件侧的 reborn_dependency_boundaries.rs 与 reborn_transport_product_boundary.rs 则分别守护产品 API crate 永不绑定 HTTP 入站与产品残留仅三个冻结命令常量两条不变量。十、实践提示与变更指南改路由或入口策略前先读 src/descriptors.rs路由模式与策略的唯一事实源改 HTTP 错误形状前先读 src/error.rs改组合层需提供的内容前先读 src/mount.rs。决定一个改动属于哪个 crate 的分界线改wire 契约DTO、路由描述符、错误信封、ref/幂等语义、SSE 翻译、mount 装配顺序→ 本 crate改命令行为本身→ironclaw_assistant经ironclaw_product_contracts契约交互绑定监听器或认证调用者→ironclaw_webui构建 port 实现会命名ironclaw_turns/ironclaw_event_streams在本 crate 禁止列表上→ironclaw_composition。不要在本 crate 内铸造认证证据auth evidence 由宿主中间件铸造不要为客户端提供的 OpenAI 工具执行 Reborn 能力不要让超时的投影等待取消底层 product turn——这些约束都是隐私、安全与可扩展性IronClaw 的三大产品支柱在协议边界上的具体落点。【免费下载链接】ironclawIronClaw is an Agent OS focused on privacy, security and extensibility项目地址: https://gitcode.com/gh_mirrors/iro/ironclaw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
