1. 当AI能力变成基础设施,为什么“换供应商”成了企业最头疼的事
过去两年,我参与过好几个企业级AI功能的落地项目,从智能客服到文档摘要,再到代码辅助。几乎每一个项目在立项时都会讨论一个绕不开的问题:模型选型。选OpenAI、选Claude、选国产大模型,还是干脆本地部署一个开源模型?一开始大家关注的是效果、价格、响应速度。但项目上线半年后,真正让团队焦虑的往往不是这些,而是迁移成本。
你可能会觉得,换个API而已,改个endpoint和key不就行了?现实远比这复杂。一个已经跑在生产环境的AI功能,背后往往绑定了特定厂商的SDK、特定的prompt格式、特定的function calling协议、特定的向量化接口,甚至特定的错误码和限流策略。当你想从A厂商换到B厂商时,代码里散落各处的调用逻辑、业务层对返回结构的假设、监控告警对特定错误码的依赖,全都要动。这就是所谓的“昂贵的重建成本”。
Eclipse基金会最近推动的一件事,正是冲着这个痛点去的。简单说,它想做的事情是:让企业能够自由更换AI提供商,而不用把整个应用推倒重来。这个方向听起来很朴素,但如果你真正在企业里做过AI集成,就会知道它有多重要。它解决的不是“哪个模型更强”的问题,而是“我能不能不被某一家绑死”的问题。
这篇文章适合几类人看:正在做AI功能架构设计的工程师、负责技术选型的技术管理者、以及任何担心自己项目被单一AI厂商锁定的开发者。我会从问题本质、Eclipse的思路、具体的技术拆解、实操中的坑,以及我自己的经验几个角度,把这件事讲透。关键词包括Eclipse、AI、开源、AI提供商切换、抽象层、多AI协作等,都会在文中自然展开。
2. 企业AI集成的真实困境:不是模型不够好,而是换不起
2.1 一个典型的“换供应商”场景有多麻烦
我拿一个真实经历过的项目举例。那是一个基于Spring Boot的Java后端服务,集成了某家AI厂商的对话接口,用来做工单自动分类和回复建议。代码结构大概是这样的:Controller层接收请求,Service层组装prompt并调用厂商SDK,然后解析返回的JSON,提取分类标签和回复文本,最后写入数据库。
上线三个月后,因为成本和合规原因,公司决定换成另一家国内厂商。我们一开始评估的工作量是“两天”。结果实际花了将近三周。问题出在哪?
第一,两家厂商的SDK设计完全不同。原来用的是官方Java SDK,封装了重试、超时、流式返回;新厂商只提供HTTP接口,这些都要自己实现。第二,prompt的格式假设不同。原来那家对system message和user message的拼接有特定要求,新厂商对角色定义更严格,导致分类准确率一度下降。第三,返回结构差异巨大。原来返回的是嵌套的JSON,新厂商返回的是扁平结构,字段名也不一样,解析层几乎重写。第四,错误码和限流策略不同,原来的告警规则全部失效,需要重新梳理。
这就是“昂贵的重建成本”的真实含义。它不是某一行代码的问题,而是整个调用链路、业务假设、运维体系都要跟着变。
2.2 为什么抽象层总是做不好
很多团队其实一开始就想过做抽象层。定义一个AIService接口,里面放chat()、embed()、classify()这些方法,然后每个厂商写一个实现类。听起来很美好,但实际落地时往往会走样。
原因有几个。一是抽象层设计得太薄,只是把HTTP调用包了一层,厂商特有的能力(比如function calling、JSON mode、流式回调)没法统一表达,业务代码还是得绕过抽象层直接调SDK。二是抽象层设计得太厚,试图把所有厂商的能力都抹平,结果就是只能用最基础的功能,高级特性全部丢失。三是抽象层没有跟上厂商迭代,新模型出了新参数,抽象层没更新,业务方只能自己想办法。
Eclipse想做的事情,本质上是要解决这个“抽象层悖论”。它不是简单地提供一个接口,而是试图定义一套厂商中立的AI能力契约,让上层应用依赖契约而不是依赖具体实现。这个思路和当年JDBC屏蔽数据库差异、JPA屏蔽ORM差异是一脉相承的。
2.3 从JDBC到AI:历史总是在重复
如果你做过Java开发,一定熟悉JDBC。JDBC定义了一套标准接口,DriverManager、Connection、Statement、ResultSet,各家数据库厂商提供驱动实现。应用代码只依赖JDBC接口,换数据库时只需要换驱动和连接串。这个模式拯救了无数项目。
AI领域现在缺的就是这样一个“JDBC”。每家厂商都有自己的SDK、自己的协议、自己的返回格式。应用代码直接依赖这些专有实现,换厂商就等于重写。Eclipse推动的方向,可以理解为在AI能力层做一次“标准化”。当然,AI比数据库复杂得多,因为模型的行为不是确定性的,prompt的细微变化可能导致输出完全不同。但正因为复杂,才更需要一个中立的契约层来隔离变化。
注意:抽象层不是银弹。它能解决接口层面的迁移成本,但解决不了prompt效果差异、模型能力差异这些本质问题。选型时仍然需要做充分的评测。
3. Eclipse的解题思路:把AI能力抽象成可替换的组件
3.1 核心不是“统一API”,而是“可替换契约”
很多人一听到“让企业自由更换AI提供商”,第一反应是“又搞一个统一API”。但Eclipse的思路不太一样。统一API的思路是:我定义一个标准接口,你们都来实现。可替换契约的思路是:我定义一套能力描述和交互协议,应用依赖这套协议,具体实现可以插拔。
这两者的区别很微妙但很关键。统一API往往追求“所有厂商都长一样”,结果就是只能覆盖最基础的能力。可替换契约承认厂商之间有差异,但通过能力声明、降级策略、适配层来管理这些差异。比如,某厂商支持function calling,某厂商不支持,契约层可以声明这个能力,应用可以选择用或不用,不用的时候走降级路径。
这就像USB接口。USB定义了物理形状和电气协议,但设备可以是键盘、鼠标、U盘、网卡,能力各不相同。操作系统通过设备描述符知道这个设备能干什么,然后加载对应驱动。AI契约层也应该这样:应用知道当前AI提供商支持哪些能力,然后决定怎么用。
3.2 能力声明与运行时适配
具体到技术实现,Eclipse相关的项目通常会涉及几个核心概念。第一个是能力声明。每个AI提供商适配器需要声明自己支持哪些能力,比如:文本生成、流式输出、function calling、JSON mode、向量化、多模态输入等。这些声明可以是静态的配置文件,也可以是运行时探测的。
第二个是运行时适配。应用发起一个AI请求时,契约层根据当前配置的提供商和能力声明,决定如何组装请求、如何解析响应、如何处理错误。如果某个能力不支持,契约层可以抛出明确的异常,或者走预设的降级逻辑。
第三个是配置驱动切换。理想情况下,更换AI提供商只需要改配置,不需要改代码。配置里指定提供商标识、认证信息、模型名称、能力开关等。应用启动时加载配置,契约层根据配置初始化对应的适配器。
这套机制在Java生态里其实有现成的参考。比如Spring AI项目就在做类似的事情,它提供了ChatClient、EmbeddingClient等抽象,支持多种AI提供商。Eclipse如果推动标准化,很可能会和这些现有项目形成互补或整合。
3.3 开源为什么是关键
Eclipse选择开源路线,这一点很重要。AI提供商切换这件事,如果由一个厂商主导,其他厂商必然不信任。只有中立的开源基金会来推动,才能让各家厂商愿意参与。开源还意味着企业可以自己审查适配层代码,确保没有隐藏的绑定逻辑。
另外,开源让适配器的开发可以社区化。一家厂商的适配器可以由厂商自己维护,也可以由社区维护。企业遇到问题可以自己修,不用等官方排期。这种模式在JDBC驱动、日志框架、HTTP客户端等领域已经被验证过无数次。
从热搜词也能看出,大家对“开源”和“AI”的结合非常关注。开源鸿蒙、开源项目、开源模型这些词频繁出现,说明开发者社区对开放生态有强烈需求。Eclipse在AI提供商切换上的努力,正好踩在这个趋势上。
4. 落地实操:从单厂商绑定到可切换架构的改造路径
4.1 第一步:盘点现有AI调用的耦合点
如果你现在手里有一个已经集成了AI功能的应用,想改造成可切换架构,第一步不是写代码,而是盘点耦合点。我通常会列一个表格,把所有和AI厂商相关的地方找出来。
| 耦合点类型 | 具体表现 | 改造难度 |
|---|---|---|
| SDK依赖 | pom.xml或build.gradle里直接依赖厂商SDK | 中 |
| 认证方式 | API key硬编码或放在特定配置项 | 低 |
| 请求组装 | prompt格式、参数名、角色定义 | 高 |
| 响应解析 | JSON结构、字段名、错误码 | 高 |
| 流式处理 | SSE或WebSocket的具体实现 | 中 |
| 监控告警 | 基于厂商错误码的告警规则 | 中 |
| 业务假设 | 业务层直接依赖厂商返回的特定字段 | 高 |
这个表格能帮你快速定位改造的重点。一般来说,请求组装和响应解析是最麻烦的,因为涉及业务逻辑。SDK依赖和认证方式相对容易替换。
4.2 第二步:定义你自己的AI能力接口
在引入Eclipse相关标准之前,你可以先定义一套自己的内部接口。不要一上来就追求大而全,从最核心的能力开始。比如:
public interface AiChatService { ChatResponse chat(ChatRequest request); Flux<ChatResponse> chatStream(ChatRequest request); } public class ChatRequest { private String systemPrompt; private List<Message> messages; private Map<String, Object> options; private List<ToolDefinition> tools; } public class ChatResponse { private String content; private List<ToolCall> toolCalls; private TokenUsage usage; private String finishReason; }这个接口的关键是请求和响应对象要厂商中立。不要在里面放任何特定厂商的字段。options用Map来承载厂商特有参数,但业务代码尽量不用。tools用统一的ToolDefinition描述,适配层负责转换成各厂商的格式。
定义好接口后,为每个厂商写一个实现类。实现类里做三件事:把统一请求转成厂商请求、调用厂商SDK或HTTP接口、把厂商响应转成统一响应。这样业务代码只依赖AiChatService接口,换厂商时只需要换实现类。
4.3 第三步:用配置驱动适配器选择
接口定义好之后,下一步是让适配器可配置。在Spring Boot里可以用条件装配:
@Configuration public class AiConfig { @Bean @ConditionalOnProperty(name = "ai.provider", havingValue = "providerA") public AiChatService providerAChatService(ProviderAProperties props) { return new ProviderAChatService(props); } @Bean @ConditionalOnProperty(name = "ai.provider", havingValue = "providerB") public AiChatService providerBChatService(ProviderBProperties props) { return new ProviderBChatService(props); } }配置文件里只需要写ai.provider=providerA或ai.provider=providerB。切换时改配置重启即可,代码零改动。如果要做灰度切换,还可以在适配器层加路由逻辑,按租户或按流量比例分发到不同提供商。
4.4 第四步:处理能力差异和降级
不同厂商的能力差异是绕不开的。比如A厂商支持function calling,B厂商不支持。你的应用如果依赖function calling,换到B厂商时就会出问题。这时候需要能力探测和降级策略。
能力探测可以在适配器初始化时做,通过一个supports(String capability)方法暴露给上层。业务代码在调用前先检查:
if (aiChatService.supports("function_calling")) { // 使用function calling } else { // 降级:用prompt模拟,或者走其他逻辑 }降级策略要提前设计好。比如function calling不支持时,可以把工具描述拼到prompt里,让模型输出特定格式的文本,然后自己解析。虽然效果可能差一些,但至少功能可用。
提示:能力探测不要只依赖厂商文档,最好在适配器里做实际探测。有些厂商文档说支持,实际有各种限制。
5. 多AI协作场景下的架构演进
5.1 为什么企业最终会用到多个AI提供商
一开始大家想的是“选一个最好的”。但实际用下来,往往会变成“不同场景用不同提供商”。原因很简单:没有哪个模型在所有任务上都最好。代码生成可能A厂商强,中文理解可能B厂商强,成本敏感的场景可能用开源模型更划算。
再加上合规要求,有些数据不能出境,有些数据不能给第三方。这时候就需要多AI协作:同一个应用里,不同功能模块用不同的AI提供商,或者同一个请求经过多个模型处理。
5.2 路由层与编排层
多AI协作需要两个额外的组件:路由层和编排层。路由层决定一个请求发给哪个提供商。决策依据可以是任务类型、成本预算、延迟要求、数据敏感级别等。编排层负责协调多个AI调用,比如先用模型A做意图识别,再用模型B生成回复,最后用模型C做安全检查。
这两个组件都应该建立在前面说的可替换契约之上。路由层依赖AiChatService接口,不依赖具体实现。编排层同理。这样新增一个提供商时,只需要注册一个新的适配器,路由和编排逻辑不用改。
5.3 成本与延迟的权衡
多AI协作带来灵活性的同时,也带来复杂性。最直接的问题就是成本和延迟。每次多调用一个模型,就多一份成本和延迟。所以路由策略要精细。
我一般会建议客户按这个优先级设计路由:数据敏感度 > 任务复杂度 > 成本 > 延迟。数据敏感的请求必须走合规的提供商,这个没得商量。任务复杂的走能力最强的模型。简单任务走便宜快速的模型。延迟敏感的走就近部署或低延迟提供商。
这套策略可以配置化,用规则引擎或简单的决策树实现。关键是可观测:每次路由决策都要记录原因,方便后续优化。
6. 实操中容易踩的坑与我的经验
6.1 不要过度抽象
我见过一些团队,一上来就想做一个“万能AI抽象层”,把所有厂商的所有能力都抽象进来。结果抽象层变得极其复杂,维护成本比直接调SDK还高。我的建议是:从最核心的两三个能力开始,按需扩展。先支持chat和embedding,跑通了再考虑function calling、多模态。抽象层要薄,要贴近实际使用场景。
6.2 prompt的厂商差异比想象中大
即使接口统一了,prompt的效果差异仍然存在。同一个prompt,在A厂商模型上效果很好,换到B厂商可能完全不行。这不是抽象层能解决的。我的做法是:为每个提供商维护独立的prompt模板,放在配置或数据库里,按提供商加载。这样切换时不用改代码,但需要重新调优prompt。调优工作要提前规划进迁移计划。
6.3 错误处理和重试策略要统一
不同厂商的错误码和限流策略不同。抽象层需要把这些统一成一套错误体系。比如定义RateLimitException、AuthenticationException、ContentFilterException等。适配器负责把厂商错误码映射成统一异常。重试策略也在抽象层统一实现,比如指数退避、最大重试次数。这样业务代码只需要处理统一异常,不用关心是哪家厂商。
6.4 监控和成本追踪不能忘
切换提供商后,监控指标会变。原来基于厂商返回的token数做成本追踪,新厂商可能返回格式不同。抽象层要统一token统计口径,把输入token、输出token、总token都标准化。监控指标也要统一,比如请求量、成功率、P95延迟、错误率。这些指标按提供商打标签,方便对比。
6.5 测试策略要覆盖多提供商
如果你的应用支持多个提供商,测试就不能只测一个。我一般会做三层测试:契约测试确保每个适配器都符合接口约定;集成测试用真实API跑核心场景;对比测试用同一批输入跑不同提供商,对比输出质量和成本。对比测试最有价值,能帮你发现能力差异和潜在问题。
7. 我对这件事的看法和后续可以关注的方向
Eclipse推动AI提供商可替换这件事,方向是对的,但路还很长。标准化最难的不是技术,而是厂商意愿。大厂商愿不愿意参与一个中立的契约标准,取决于这个标准能不能给它带来增量客户,而不是让它更容易被替换。这需要基金会、社区、企业用户共同推动。
从技术角度看,我觉得接下来值得关注几个方向。一是契约标准的成熟度,看Eclipse能不能联合多家厂商和开源项目,形成一套被广泛接受的接口规范。二是适配器生态,看社区能不能快速产出高质量的适配器,覆盖主流厂商和开源模型。三是与现有框架的整合,比如Spring AI、LangChain4j这些项目会不会采纳Eclipse的标准。
如果你现在正在做AI功能,我的建议是:不要等到标准成熟再行动。先在自己的项目里做一层薄薄的抽象,把厂商相关的代码隔离出来。这样等标准出来时,你只需要把内部接口对齐到标准接口,迁移成本会小很多。这件事的核心价值不是技术有多难,而是意识:从第一天起就意识到AI提供商是可替换的,架构上留好口子。
我在实际项目里踩过的最大的坑,就是一开始图快,业务代码直接调厂商SDK。等到要换的时候,发现到处都是耦合。后来花了大力气重构,才把抽象层补上。如果重来一次,我会在项目启动时就定义好接口,哪怕只有一个实现类。这个习惯,值得每一个做AI集成的团队养成。