接入6家大模型API后的适配器设计模式总结
我总结的6家大模型API接入实战经验与踩坑实录前不久接了一个企业内部智能助手的项目客户是一家拥有数千名员工的传统制造企业。他们希望把内部知识库和几个主流的大模型能力打通做一个能问业务数据、也能写代码辅助的聊天机器人。说实话一开始我觉得这很简单不就是调个API吗结果真正落地时发现各家厂商的鉴权方式、参数结构、流式响应格式简直是一团乱麻。最后我们接入了6家大模型厂商为了统一对外接口我花了一周时间重构了适配层。这个过程坑不少但也让我对设计模式在工程化中的应用有了全新的理解。统一抽象与适配器模式的抉择在项目初期产品需求很明确前端只需要一个chat接口传进去用户的问题返回答案。但后端对接的时候噩梦开始了。A厂商用API Key放在Header里B厂商要求Bearer TokenC厂商的参数叫promptD厂商叫inputE厂商甚至还要传一个复杂的JSON结构定义流式字段。如果我在Controller层直接写6个if-else判断那代码量至少膨胀三倍而且以后每加一家模型都要改核心逻辑维护成本太高。当时我有两个方案。方案A是简单粗暴地用策略模式工厂模式每个模型实现一个独立的Service类。方案B是引入适配器模式在所有底层API之上再包一层统一的ModelAdapter接口屏蔽具体厂商差异。试了一圈发现方案A虽然直观但每个Service里都重复处理了鉴权重试、错误码转换这些通用逻辑。而我选方案B是因为我想把“如何调用”和“怎么调用”彻底分离。于是我定义了一个核心接口LargeLanguageModelClient里面只保留三个方法authenticate()、generateResponse()和streamGenerate()。javapublic interface LargeLanguageModelClient {// 统一认证接口void authenticate() throws AuthException;// 同步生成String generateRequest(GenAIRequest request) throws GenAIOperationException;// 流式生成Flux streamGenerateRequest(GenAIRequest request);}有意思的是对于每家厂商我并不是直接实现这个接口而是先写一个AbstractModelClient基类。基类里封装了通用的HTTP客户端初始化、超时设置和基础的日志记录。这样具体到某家厂商比如通义千问或文心一言的实现类只需要关注参数映射和响应解析。说实话刚开始我觉得这样分层有点过度设计毕竟只有6家模型。但当我遇到第三个厂商的签名算法完全不一致时我庆幸自己做了抽象。不然每次新增模型我都要去复制粘贴那些繁琐的HTTP Header构建代码。流式响应与异常处理的深坑接入过程中最让我头疼的不是参数对齐而是流式响应Streaming的处理。大部分大模型都支持SSEServer-Sent Events但各家返回的数据格式千差万别。有的直接在body里返回data: {content: hello}有的要把整个JSON包在event:标签里还有的甚至会在非数据帧之间插入空行或者特殊字符。我当时觉得这样就行直接把底层流读出来转发给前端WebSocket。结果测试的时候发现偶尔会出现数据截断或者乱码。排查了半天才发现是底层的HttpComponents客户端在处理chunked transfer编码时缓冲区的默认大小太小导致长文本被切分后重新组装时丢失了边界信息。解决这个问题的过程挺折腾。我首先换用了WebClient它的反应式流处理更优雅。但即使换了客户端解析逻辑还是得写。我设计了一个StreamParser接口针对每家厂商写不同的解析器。javapublic interface StreamParser {boolean isDataFrame(String rawLine);String extractContent(String rawLine);boolean isEndOfStream(String rawLine);}这里有个大坑。有些厂商在流结束时不会发送明确的[DONE]标记而是直接关闭连接。而有些厂商会在中间发送心跳包。如果不仔细处理前端要么一直转圈等待结束要么收到一堆空数据。我最后的解决方案是在适配器层增加一个StreamBuffer。它不直接转发给前端而是先缓存数据检测是否是一个完整的语义单元比如以句号或换行结尾然后再推送。这样做虽然增加了一点延迟大概200ms但用户体验好太多了。另一个踩坑点是错误处理。各家模型的错误码完全不通约。有的返回HTTP 400报错信息在JSON的error.message字段有的直接返回HTTP 500但实际是限流。我原本想统一捕获所有异常然后抛出自定义的BusinessException。结果有一次线上故障因为某家厂商返回的错误信息包含中文乱码导致我的JSON序列化器崩溃整个服务挂了。后来我学乖了在适配器层增加了严格的异常隔离。不管底层抛什么怪异的异常上层统一转换为标准的GenAIOperationException并且只记录脱敏后的错误码绝不把原始响应体直接透传给前端。这让我意识到防御性编程不是废话是保命符。性能权衡与最终总结接入6家模型后我还做了一个性能压测。发现适配器层的引入确实带来了一定的开销主要是对象序列化和反序列化的次数增加了。为了优化这点我没有每次都新建请求对象而是引入了Builder模式来复用配置。此外我还加了熔断机制。当某家模型连续失败超过阈值时自动切换到备用模型。这个功能虽然是后来加的但得益于最初的适配器架构扩展起来非常轻松。只需要新增一个BackupModelClient实现并注册到熔断管理器中即可完全不需要改动业务逻辑代码。回顾这个项目从最初的手忙脚乱到后来的从容应对最大的收获就是理解了“稳定”二字的重量。API对接看似只是简单的CRUD操作实则是对稳定性、兼容性和可维护性的极致考验。适配器模式在这里不仅仅是一个设计模式更是一种工程治理手段。它让我们在面对多变的外部依赖时能够保持内部核心的纯净与稳定。本文基于实际项目经验整理欢迎在评论区交流技术问题。