Skip to content

feat: 兼容 OpenAI Responses API#544

Open
WinSalty wants to merge 1 commit into
spring-ai-alibaba:mainfrom
WinSalty:feat/responses-api
Open

feat: 兼容 OpenAI Responses API#544
WinSalty wants to merge 1 commit into
spring-ai-alibaba:mainfrom
WinSalty:feat/responses-api

Conversation

@WinSalty

@WinSalty WinSalty commented Jun 11, 2026

Copy link
Copy Markdown

概述

解决 #531:部分模型提供商仅支持 OpenAI Responses API(POST /v1/responses),而当前项目只走 Chat Completions 协议,无法对接这些服务端。

本 PR 在不修改上层任何服务/节点代码的前提下,新增 Responses API 适配层,让用户在模型配置中选择接口协议即可完成切换。

改动内容

  • 新增 ChatApiProtocol 枚举:定义 CHAT_COMPLETIONS(默认)和 RESPONSES 两种协议
  • 新增 ResponsesApi:Responses API 低层 HTTP 客户端,支持同步调用和 SSE 流式调用,所有协议 DTO 以内嵌 record 定义
  • 新增 ResponsesApiChatModel:实现 ChatModel 接口,将 Spring AI Prompt 与 Responses API 请求/响应互转;流式输出与 OpenAiChatModel 的行为对齐(delta → 文本块,completed → 空文本 + usage 的终包)
  • 修改 DynamicModelFactory:按 chatApiProtocol 配置分支,RESPONSES 走 ResponsesApiChatModel,其余走原有 OpenAiChatModel
  • 数据层ModelConfig 实体、DTO、Converter、Mapper、Service 全链路增加 chatApiProtocol 字段
  • 数据库:MySQL 和 H2 schema 均新增 chat_api_protocol 列,默认值 CHAT_COMPLETIONS
  • 前端:模型配置表单在 CHAT 模型类型下新增"接口协议"下拉选项,选择 RESPONSES 时路径标签自动切换

设计要点

  • 对上层完全透明ResponsesApiChatModel 实现 ChatModel 接口,AiModelRegistryLlmService、全部 16 个图节点零改动
  • 流式终包为空文本:completed/incomplete 事件的 response 携带完整全文,而上层 FluxUtil 对每个 chunk 做文本聚合,终包只携带 finishReason 和 usage,避免聚合结果全文重复
  • 容错一致性:同步调用接入 RetryUtils.DEFAULT_RETRY_TEMPLATE;SSE 解析对畸形事件(含结构不匹配)记录日志后跳过,不中断流;未知事件类型静默忽略保证向前兼容
  • 代理能力天然继承:复用 DynamicModelFactory 已有的 proxy RestClient/WebClient 构建链路

存量部署升级说明

schema.sqlCREATE TABLE IF NOT EXISTS 仅对全新安装生效。已有 MySQL 部署升级时需手动执行:

ALTER TABLE model_config
    ADD COLUMN chat_api_protocol varchar(32) DEFAULT 'CHAT_COMPLETIONS'
    COMMENT 'Chat模型接口协议:CHAT_COMPLETIONS(默认)、RESPONSES(OpenAI Responses API格式)。仅model_type=CHAT时生效';

H2 开发环境每次启动自动重建表结构,无需处理。

测试方案

  • 新增 18 个单元测试:ResponsesApiTest(SSE 事件解析各分支 + 畸形事件容错)、ResponsesApiChatModelTest(请求映射、响应转换、流式终包空文本回归、错误传播)
  • 全部 1804 个单元测试通过(mvn test:0 failures, 0 errors)
  • spring-javaformat:apply 格式化通过
  • checkstyle:check 0 violations
  • 前端 npm run lint 0 errors
  • 端到端集成测试通过(见下方详情)

集成测试结果

使用 Qwen(通义千问)的 Responses API 端点进行端到端验证,模型 qwen-plus,两条路径均通过:

测试场景 输入 结果
非流式 call() SystemMessage="用一句话回答" + UserMessage="1+1等于几?" 回复: "1+1等于2。" · finishReason=STOP · promptTokens=53, completionTokens=6
流式 stream() SystemMessage="用一句话回答" + UserMessage="中国的首都是哪里?" 聚合文本: "中国的首都是北京。" · 终包空文本✓ · finishReason=STOP · promptTokens=52, completionTokens=5

验证要点:

  • ✅ 非流式:文本、finishReason、usage 均正确返回
  • ✅ 流式 delta 逐块到达,文本拼接完整
  • ✅ 流式终包为空文本(P0 回归验证),上层 FluxUtil 聚合不会全文重复
  • ✅ usage(input_tokens → promptTokens,output_tokens → completionTokens)映射正确
  • ✅ SSE 事件解析正确处理 response.output_text.deltaresponse.completed 事件

🤖 Generated with Claude Code

新增 Responses API 适配层(ResponsesApi + ResponsesApiChatModel),
实现 ChatModel 接口使上层服务无感切换;前端模型配置增加接口协议选项。

实现要点:
- 同步调用挂载统一错误处理并接入重试模板,5xx/网络瞬时错误自动重试,
  与 OpenAiChatModel 容错行为对齐
- 流式终包空文本,避免上层 FluxUtil 聚合时全文重复;
  未收到任何增量事件时终包降级携带全文,兼容不推送 delta 的服务端
- 多条 SystemMessage 按出现顺序拼接为 instructions
- chatApiProtocol 保存入口校验并归一化,新增配置空值回填默认协议,
  避免 INSERT 显式 NULL 绕过数据库列默认值
- 运行时 options 按 ChatOptions 接口取值,兼容任意实现
- SSE 畸形事件按单条损坏跳过不中断流,日志截断负载
- 单元测试覆盖协议映射、SSE 事件解析与上述边界行为

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@WinSalty WinSalty force-pushed the feat/responses-api branch from 2f9bfd9 to 3d9130c Compare June 11, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant