status: done created: 2026-07-10 complexity: 🔴复杂
Forge 当前通过 Spring AI ChatClient 提供统一 AI 调用入口,并在数据库中维护多租户 AI 供应商、模型和 Agent 配置。但底层模型创建固定使用 OpenAiApi + OpenAiChatModel + OpenAiChatOptions,providerType 只承担展示和筛选作用。阿里百炼虽然可以借助 OpenAI Compatible API 使用,系统却无法利用 Spring AI Alibaba 的原生 DashScope 模型、原生参数、推理内容和后续 Tool Calling 扩展能力。
本变更在不改变 AiClient.call/stream 公共协议、不切换 Java/Spring 主干、不破坏现有供应商配置的前提下,完成以下可验证结果:
1.1.2.3 依赖基线和 DashScope 核心模型模块;openai_compatible、dashscope_native 两种适配器,既有数据默认保持原链路;DashScopeChatModel;openai_compatible,行为和接口路径保持不变;dashscope_native 供应商后,普通调用和流式调用均通过 DashScopeChatModel 完成;AiClientImpl、ChatClientCache、AiProviderService 不再直接构造具体供应商模型;ChatClient;1.1.2,Spring AI Alibaba 与 Extensions 收敛到 1.1.2.3;每个结论均给出当前工程或本地官方源码出处。
code-copilot/rules/project-context.md仍记录旧 Spring Boot3.2.9,本变更以实际 Maven POM 为版本事实来源。
forge-server/forge-framework/forge-plugin-parent/forge-plugin-ai/src/main/java/com/mdframe/forge/plugin/ai/client/AiClient.java 定义同步与流式调用协议;AiClientImpl.call/stream 负责解析 Agent/供应商/模型、创建 ChatClient、注入会话记忆并持久化会话。client/AiInvocationResolver.java 的 resolve 方法决定显式请求、Agent 配置和默认供应商之间的优先级;resolveProvider 当前统一要求 baseUrl/apiKey 非空。client/ChatClientCache.java 的 getOrCreateBase 缓存基础 ChatClient,createSessionClient 叠加 MessageChatMemoryAdvisor。provider/controller/AiProviderController.java 提供 /ai/provider/page、详情、新增、修改、删除、连接测试和设为默认接口;provider/service/AiProviderService.java#testConnection 直接创建模型进行探活。forge-server/forge-admin-server/sql/初始化脚本.sql 的“供应商管理”资源组件路径为 /ai/provider-model;对应页面是 forge-admin-ui/src/views/ai/provider-model.vue。forge-admin-ui/src/views/ai/provider.vue 是未被当前菜单绑定的旧页面,本变更不删除它。forge-server/pom.xml 当前为 Spring Boot 3.5.13、Spring AI 1.1.2,并仅声明未使用的 spring-ai-alibaba.version=1.1.2.0。本地官方 docs/vendor/spring-ai-alibaba-1.1.2.3-release/pom.xml 使用 Spring AI 1.1.2、Boot 3.5.8;docs/vendor/spring-ai-extensions-1.1.2.3-release/pom.xml 使用 Spring AI 1.1.2、Boot 3.5.10。forge-plugin-ai/pom.xml 显式依赖 spring-ai-openai、spring-ai-client-chat 并逐项写版本,没有导入 Spring AI、Alibaba Extensions、Alibaba 三个 BOM。client/ChatClientCache.java#buildBaseChatClient 和 provider/service/AiProviderService.java#testConnection 都直接创建 OpenAiApi、OpenAiChatModel、OpenAiChatOptions;provider/domain/AiProvider.java#providerType 未参与模型路由。AiClientImpl#buildOptions 返回 OpenAiChatOptions,导致通用调用层无法传递 DashScope 原生 DashScopeChatOptions。ChatClientCache#evict/evictByProvider 已实现,但工程内除定义外没有引用;AiProviderController#update/delete 更新或删除配置后不会主动失效旧客户端。forge-admin-ui/src/views/ai/provider-model.vue#handleTestConnection 从列表行读取 row.apiKey 并传给 /ai/provider/test。改为安全脱敏后,该链路必须以供应商 ID 在服务端解析真实密钥。AiProviderController#fillAndMaskProvider 只调用 fillModelsFromAiModel,没有处理 apiKey;@ApiEncrypt 只负责报文加密,不能替代字段脱敏。forge-plugin-ai/src/main/resources/sql/ai_model.sql 使用 alibaba,forge-admin-server/sql/初始化脚本.sql 使用 dashscope。因此 providerType 不适合作为底层协议路由键。AiProviderController#templates 返回 https://dashscope.aliyuncs.com/compatible-mode;原生 DashScope 源码 DashScopeApiConstants 使用 Base URL https://dashscope.aliyuncs.com 和路径 /api/v1/services/aigc/text-generation/generation,两者不能混用。spring-ai-alibaba/examples/chatbot/pom.xml 同时导入 spring-ai-bom:1.1.2、spring-ai-alibaba-extensions-bom:1.1.2.3、spring-ai-alibaba-bom:1.1.2.3。docs/vendor/spring-ai-extensions-1.1.2.3-release/models/dashscope/src/main/java/com/alibaba/cloud/ai/dashscope/chat/DashScopeChatModel.java 实现 Spring AI ChatModel;其 Builder 支持动态传入 DashScopeApi 和 DashScopeChatOptions。DashScopeApi.java#Builder 支持运行时设置 apiKey、baseUrl、Header、Workspace 和 HTTP Client,不要求使用 Spring Boot 全局配置 Bean。DashScopeChatOptions.java 实现 ToolCallingChatOptions,支持 model、temperature、maxToken、enableThinking、工具回调等原生选项。DashScopeChatModel 将推理内容写入 AssistantMessage metadata 的 reasoningContent;现有 AiClientImpl#extractReasoningContent 已识别该键。spring-ai-alibaba-starter-dashscope 会引入自动配置;DashScopeChatAutoConfiguration 与 ConditionalOnDashScopeEnabled 均为 matchIfMissing=true,并尝试从 spring.ai.dashscope.* 或环境变量创建全局模型。Forge 使用租户数据库动态配置,因此本变更选择核心模块 spring-ai-alibaba-dashscope,不引入 Starter。dependency:tree、目标模块测试和主应用装配验证,不以“同一 minor”代替证据;forge-dependencies/pom.xml 内仍保留旧 Boot 3.2.9 BOM,根 POM 先导入 Boot 3.5.13;本变更不顺带重构全局 BOM,但必须检查 effective dependency tree 是否被旧 BOM 污染;com.alibaba.cloud.ai 产物,本地 vendor 源码只作审计参考,正式构建仍应使用 Maven 发布坐标;仓库不可达时才在开发机临时安装对应 release,不把 vendor 源码加入 Forge modules;1.1.2、Spring AI Alibaba 1.1.2.3、Extensions 1.1.2.3,AI 插件只声明无版本依赖。ai_provider 增加非空 adapter_code,既有数据全部回填 openai_compatible,通过新字典维护展示值。adapterCode 选择模型实现,未知值抛出 BusinessException,不依据 providerType 或 URL 猜测。OpenAiApi/OpenAiChatModel 行为,覆盖 OpenAI、DeepSeek、智谱、Moonshot、Ollama、自定义及旧阿里兼容模式。DashScopeApi/DashScopeChatModel/DashScopeChatOptions,支持同步、流式、推理内容和 Tool Calling 基础协议。AiClientImpl 和 ChatClientCache 只使用协议无关运行参数与 ChatModel,不再导入供应商 Options 类型。**** + 后 4 位;短密钥全部掩码;提交未变化的脱敏值时保留数据库原值。provider-model.vue 使用字典展示和选择适配器;新建默认为 openai_compatible,选择原生 DashScope 时使用原生 Base URL。providerType 继续表示供应商品牌/分类,adapterCode 唯一决定底层协议;二者不得混用。adapterCode 仅允许字典中的稳定值:openai_compatible、dashscope_native。openai_compatible,升级过程不自动把任何现有阿里供应商切为原生协议。null 时:新增按 openai_compatible,更新保留数据库原值;显式提交空字符串或纯空白一律拒绝,禁止把原生供应商静默改回 Compatible。AiProviderAdapterCode.require 与 Registry 遇到 null、blank、unknown 或重复注册必须失败关闭,不允许回退到默认 Adapter。AiProviderAdapterRegistry#createChatModel 是正式模型创建的唯一入口,固定执行 getRequired → validate → createChatModel;正式调用、连接测试和缓存层不得直接调用具体 Adapter 绕过校验。model/temperature/maxTokens,各 Adapter 负责映射供应商 Options;apiKey/baseUrl/model;DashScope Native 要求 apiKey/model,Base URL 为空时使用官方默认值 https://dashscope.aliyuncs.com;trim、URI 解析、HTTP/HTTPS scheme 校验并移除比较用尾斜杠;禁止 query、fragment 和 userInfo。归一化只处理格式,不猜测或改写自定义代理域名;dashscope_native 在官方域名下只接受空路径/根路径,拒绝 /compatible-mode;自定义域名只做通用 URI 校验,由其代理实现负责原生 DashScope Path;openai_compatible 的 Base URL 必填;当 host 为 dashscope.aliyuncs.com 时只接受 /compatible-mode(允许尾斜杠),拒绝原生根地址和 /compatible-mode/v1;自定义域名只做通用 URI 校验,不自动追加 /compatible-mode;DashScopeChatOptions.Builder#maxToken,不能误用 OpenAI Builder;extraBody、Header、Workspace 配置,避免在没有字段级治理前形成自由透传入口。****;长度不大于 8 时返回固定掩码;id != null 时只允许提交 ID,出现 adapter/baseUrl/apiKey/model 等配置字段即拒绝;id == null 时必须提交 adapterCode、apiKey、model 及 Adapter 要求的其余完整配置;两种模式不得合并字段;@ApiDecrypt;连接测试 SDK 异常统一转换为不含密钥、Header、完整请求体的安全错误,日志只记录 providerId、adapterCode 和异常类型;getOrCreateBase 只接收完整 AiProvider 与运行参数,缓存键 tenantId 必须在内部取自 AiProvider.tenantId,不能由调用方单独传入或从可能为空的线程会话推断;ai_provider 继续受 TenantLineInnerInterceptor 管理,测试连接按当前租户加载记录,不能接受请求体覆盖 tenantId。| 操作 | 表名 | 字段/索引 | 说明 |
|---|---|---|---|
| 新增字段 | ai_provider |
adapter_code varchar(32) NOT NULL DEFAULT 'openai_compatible' |
显式保存模型连接协议;Flyway 使用 information_schema 防重复 |
| 数据回填 | ai_provider |
空值补为 openai_compatible |
首次加列由默认值覆盖历史记录;部分部署重跑只处理 NULL/blank,不覆盖已有 Native |
| 新增字典类型 | sys_dict_type |
ai_provider_adapter_type |
tenant_id 固定为 1,使用 NOT EXISTS 防重复 |
| 新增字典数据 | sys_dict_data |
openai_compatible、dashscope_native |
tenant_id 固定为 1,值与后端 Adapter Code 完全一致 |
迁移脚本固定为 forge-server/db/migration/V1.0.17__add_ai_provider_adapter_code.sql。字段首次新增时由默认值把历史记录置为 Compatible;若字段已由部分部署创建,只允许更新 adapter_code IS NULL OR TRIM(adapter_code)='' 的记录,不得覆盖任何已有非空值。脚本重跑或修复执行不得把 dashscope_native 重置为 Compatible。不修改已执行历史 SQL;旧初始化脚本产生的数据库在执行 Flyway 后达到相同结构。
数据库结构采用前向兼容,不执行破坏性降级,也不删除已写入的 adapter_code 和字典。应用版本回退存在明确前置条件:必须先查询并确认不存在 dashscope_native 记录;若存在,需先由管理员切回 openai_compatible 并恢复 Compatible Base URL、完成连接测试后才能回退旧应用。未经该检查直接回退会让旧代码把原生 URL 当作 OpenAI Compatible 使用。需要物理回退时另行编写受审查的前向修复脚本。
| 操作 | 接口 | 方法 | 变更内容 |
|---|---|---|---|
| 兼容增强 | /ai/provider/page |
GET | 响应新增 adapterCode;apiKey 改为脱敏值 |
| 兼容增强 | /ai/provider/{id} |
GET | 响应新增 adapterCode;apiKey 改为脱敏值 |
| 兼容增强 | /ai/provider |
POST | 请求新增 adapterCode,缺省按 openai_compatible;校验 Adapter 与 Base URL |
| 兼容增强 | /ai/provider |
PUT | 请求新增 adapterCode;缺失时保留原值;支持脱敏值不覆盖原密钥;成功后失效缓存 |
| 安全调整 | /ai/provider/test |
POST | 严格 one-of:已保存记录只能传 id;未保存测试只能传无 ID 的完整配置;混合请求拒绝,SDK 错误安全化 |
| 兼容增强 | /ai/provider/templates |
GET | 模板新增 adapterCode;新增/调整 DashScope Native 模板使用原生 Base URL |
接口 URL、HTTP Method、RespInfo 外层协议和前端 API 函数名保持不变。AiClient 的公开 Java 接口不变。
forge-server/pom.xml:AI 依赖版本与三个 BOM;forge-plugin-ai/pom.xml:增加 DashScope 核心模型依赖,移除显式版本;forge-plugin-ai/client:通用运行参数、缓存与调用层解耦;forge-plugin-ai/provider:Adapter SPI、原生 DashScope、供应商生命周期、DTO/VO 和密钥保护;forge-server/db/migration:字段与字典迁移;forge-admin-server:通过现有插件聚合加载新依赖,无新模块。forge-admin-ui/src/views/ai/provider-model.vue:适配器字段、标签、默认值、原生 URL 和 ID 连接测试;forge-admin-ui/src/api/ai.js:接口路径不变,仅补充请求语义注释或辅助参数。forge-dependencies BOM 重构;forge-admin-ui/src/views/ai/provider.vue;docs/vendor 源码加入 Maven reactor 或生产制品。1.1.2;spring-ai-alibaba-starter-dashscope,默认自动配置可能因没有全局 API Key 导致启动失败;POM 测试必须确认只依赖核心模型模块;maxTokens,前端明确提示;测试失败不跨 Adapter 重试;1.1.2.3 为准;test-spec.md;AiClientImplTest 使用真实 ChatClientCache、Mock Registry 和可控 Fake ChatModel,离线强制覆盖 Native 供应商的同步、流式和 reasoningContent 输出链路;AI_DASHSCOPE_API_KEY 且网络可用时运行一次 qwen-plus 同步与流式探活,日志不得记录密钥;AiInvocationResolverTest 基线,再跑目标模块、主应用装配、Flyway 静态检查和前端构建。openai_compatible;本次只接入 Chat/DashScope Native;使用核心模块而非 Starter;Agent/MCP/Nacos 另立变更。| 决策 | 选择 | 放弃方案 | 原因 |
|---|---|---|---|
| 框架关系 | Spring AI 为通用接口,Spring AI Alibaba 作为增强层 | 删除 Spring AI、整体换框架 | Alibaba 本身建立在 Spring AI ChatModel/ChatClient 上,替换会制造无意义重写 |
| DashScope 依赖 | spring-ai-alibaba-dashscope 核心模块 |
spring-ai-alibaba-starter-dashscope |
Forge API Key 来自租户数据库,不适合全局自动配置单例 |
| 路由依据 | 新增显式 adapter_code |
根据 providerType 或 Base URL 推断 |
现有 alibaba/dashscope 和 Compatible/Native 地址存在历史差异,推断不稳定 |
| 历史迁移 | 全部回填 openai_compatible |
自动把阿里记录切为 Native | 保证升级零行为变化,由管理员显式切换并测试 |
| Adapter 失败策略 | Fail-closed | 自动换另一个 Adapter 重试 | 防止重复调用、重复计费和隐藏配置错误 |
| API Key 回显 | 脱敏 + 同值保留 | 返回明文或固定占位直接保存 | 满足安全规范并避免脱敏占位覆盖真实值 |
| Vendor 源码用途 | 版本/API 审计参考 | 加入 Forge reactor | 保持依赖边界清晰,使用正式 Maven 坐标 |
| Task | 状态 | 实际改动文件 | 备注 |
|---|---|---|---|
| Research / Proposal | 完成 | 本变更四份文档 | 已核对 Forge 当前实现及两个 1.1.2.3 官方源码目录 |
| Task 1 | 完成 | forge-server/pom.xml、forge-plugin-ai/pom.xml |
三个 BOM 已导入;DashScope Core 1.1.2.3 编译通过,未引入 Starter |
| Task 2 | 完成 | V1.0.17__add_ai_provider_adapter_code.sql、AiProvider.java、AiProviderAdapterCode.java、对应测试 |
历史数据保持 Compatible;字典 tenant_id=1;Adapter Code 失败关闭测试通过 |
| Task 3 | 完成 | provider/adapter/*、对应测试 |
Registry、URL Policy、Compatible/Native Adapter 已完成,12 个测试通过;统一调用入口已由 Task 4/5 接通 |
| Task 4 | 完成 | AiClientImpl、ChatClientCache、AiInvocationResolver、AiProviderCacheEvictionScheduler、对应测试 |
调用层与供应商实现解耦;租户安全缓存键、after-commit 失效及事务同步异常失败关闭已验证 |
| Task 5 | 完成 | 供应商 DTO/VO、Service、Controller、Mapper XML、密钥与缓存支持类及测试 | 生命周期、严格 one-of 连接测试、密钥脱敏回写、SDK 异常安全化均已完成 |
| Task 6 | 完成 | provider-model.vue、src/api/ai.js |
活动供应商页面支持字典协议选择、受控 URL 联动、加密写请求和仅 ID 测试 |
| Task 7 | 完成 | AI 插件测试、装配、迁移和前端构建相关文件 | AI 最终 45 tests;AI/Admin package、Flyway 静态检查、Mapper XML 和前端构建通过;公网调用按条件跳过 |
| Task 8 | 完成 | 两份 AI 中枢文档、本变更四份文档、decisions.md、pitfalls.md |
已回填实际版本、阶段边界、回退前置条件和审查结论,进入 review |
1.1.2,Alibaba/Extensions 收敛到 1.1.2.3,未引入 DashScope Starter;20.19.0 前端构建通过;未提供 AI_DASHSCOPE_API_KEY,公网真实调用按 Test Spec 跳过;既有 JVM/编译/Vite 警告保留在 execution-log.md;done,已归档、未推送。origin/main..HEAD 检查 44 个变更文件,依赖、迁移、Adapter SPI、统一调用链、缓存、供应商生命周期、密钥边界和管理端协议均与 Spec 一致;adapter_code 列,可在后续迁移中补列类型/default/nullability 校验;未来新增 Adapter 时,可进一步拆分连接测试的配置校验与网络调用异常边界;1.1.2 和 DashScope Core 1.1.2.3;Node 20.19.0 前端 8485 modules 构建成功;Mapper XML、Flyway placeholder、Starter 缺失和 diff whitespace 检查通过;供应商连接测试实际返回 400 invalid_request_error,错误表明系统向仅支持
deepseek-v4-pro/deepseek-v4-flash 的供应商发送了 gpt-3.5-turbo。根因是已保存供应商没有可用默认模型时,
AiProviderService 与 AiInvocationResolver 都会跨供应商回退到固定 OpenAI 模型;同时连接测试只读取
ai_provider.default_model 双写字段,没有把 ai_model 中“启用且默认”的记录作为权威来源。
本轮固定以下规则:
gpt-3.5-turbo 通用兜底,不允许根据连接协议或品牌猜测模型;ai_model 中读取 status='0' AND is_default='1' AND del_flag='0' 的记录;defaultModel,因为此时尚无 ai_model 记录;ChatModel 和发起网络请求前抛出 BusinessException("请为供应商设置默认模型");httpStatus/errorCode,禁止记录响应正文、
API Key、Authorization Header、完整请求体或供应商错误 message。活动页面 forge-admin-ui/src/views/ai/provider-model.vue 参考主流供应商配置控制台重构为克制的主从工作区:左侧供应商列表负责筛选、
分页和选择,右侧通过“基础配置 / 模型管理”页签展示所选供应商信息;窄屏自动改为上下布局。页面禁止堆叠无业务价值的
大标题、统计卡、英文装饰标签、渐变和引导步骤,遵循以下验收标准:
pageNum/pageSize/itemCount,支持总条数、每页条数与快速跳页;pageSize=100 截断;模型数量来自总条数而非当前页长度;accept 使用 .png,.jpg,.jpeg,.svg,.webp;供应商与模型弹窗使用 max-width + 视口宽度;code-copilot/changes/archive/2026-07-11-spring-ai-alibaba-provider-adapter/code-copilot/memory/decisions.md 第 16 条、code-copilot/memory/pitfalls.md 第 104 条、code-copilot/memory/preferences.md 第 10 条