写在前面你好我是 Evan。一名正在摸爬滚打的 Java 后端开发者也是这个专栏的作者。在智答 Agent 项目中我踩过最大的坑不是模型选错、不是 Prompt 调不好而是架构没有为 AI 预留扩展点。最初我们直接硬编码调用 OpenAI 的 API后来要切换成通义千问、DeepSeek甚至要同时支持流式和非流式输出还要统计每个用户的 Token 消费、缓存常见问题、支持异步回调……每一次变更都像在重构。痛定思痛我设计了一套“AI 能力底座”——将大模型相关的能力抽象成可插拔的扩展点。今天我就把这套设计思路分享出来从缓存策略、流式响应、Token 计费到回调机制手把手教你如何让后端优雅地拥抱 AI而不是被 AI 拖着走。一、为什么你的后端需要“AI 扩展点”传统后端调用 AI 模型通常就是一个 HTTP 请求 JSON 解析。但当你真正把 AI 能力落地到生产时会发现远远不够模型随时可能切换从 OpenAI 到国产模型甚至自建私有模型。输出方式多样流式响应 vs 阻塞式响应前端体验天差地别。成本需要控制Token 计费、缓存高频问题、限制恶意刷接口。异步场景复杂AI 生成长文可能耗时几十秒不能让 HTTP 连接一直等着。如果你的代码把模型调用、缓存、计费等逻辑全部耦合在一起任何一处变动都会牵一发而动全身。“AI 优先的架构”核心思想就是把 AI 能力抽象成一个个可插拔的组件通过配置或依赖注入灵活组合。下面这张图展示了整体架构接下来我们逐个拆解这些扩展点。二、扩展点一统一 AI 客户端 —— 让模型切换无痛最基础也最重要的扩展点是抽象出统一的 AI 客户端接口。无论底层是 OpenAI、通义千问、DeepSeek 还是本地部署的 Llama上层业务代码只依赖接口。public interface AiClient { // 阻塞式调用 String chat(String prompt, ListMessage history); // 流式调用 void chatStream(String prompt, ListMessage history, StreamCallback callback); // 获取模型信息用于计费 ModelInfo getModelInfo(); }通过 Spring 的ConditionalOnProperty可以根据配置文件动态注入不同的实现ai: provider: deepseek # 或 openai, qwen, mock这样做的好处是切换模型只需要改配置文件无需修改业务代码。新模型接入只需新增一个实现类。单元测试时可以用 MockClient 完全脱离真实 API。三、扩展点二缓存策略 —— 省钱又提速大模型调用昂贵且缓慢对于重复或相似的问题缓存能极大降低成本。但传统精确匹配key 完整 prompt太死板我们需要语义缓存意思相近的问题命中同一缓存。在 Java 中可以用 Redis 配合 RediSearch 模块或 pgvector 来实现。一个简单但有效的缓存键设计public class CacheKey { private String prompt; private String modelName; // 不同模型答案不同 private Double temperature; // 参数影响输出 // 甚至可以包含历史对话的哈希 }扩展点设计定义CacheStrategy接口允许切换不同实现无缓存、精确缓存、语义缓存。public interface CacheStrategy { OptionalString get(String prompt, AiContext ctx); void put(String prompt, String answer, AiContext ctx); }四、扩展点三流式响应 —— 让用户不再干等AI 生成内容通常需要几秒甚至几十秒。阻塞式接口会让用户体验极差而流式响应SSE 或 WebSocket能逐字输出大幅提升感知速度。技术选型推荐使用SSEServer-Sent Events它基于 HTTP实现简单自动重连非常适合单向流式输出。GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(String prompt) { SseEmitter emitter new SseEmitter(30000L); aiClient.chatStream(prompt, history, chunk - { emitter.send(chunk); }); emitter.onCompletion(() - log.info(Stream finished)); return emitter; }扩展点设计AiClient的chatStream方法就是一个天然的扩展点。不同的模型底层实现流式的方式不同OpenAI 用 Server-Sent Events通义用 WebSocket但上层业务只看到回调。五、扩展点四Token 计费 —— 成本可观测、可限制Token 是调用大模型的计价单位。没有计费模块你既不知道每个用户花了多少钱也无法防止恶意刷接口。核心设计在调用 AI 之前和之后分别统计输入 Token 和输出 Token可以从 API 响应中获取或本地估算。然后将消费记录写入数据库并实时检查用户配额。public class TokenCountingProxy implements AiClient { private final AiClient delegate; private final TokenUsageRepository repository; public String chat(String prompt, ListMessage history) { var start System.nanoTime(); String answer delegate.chat(prompt, history); var tokens estimateTokens(prompt, answer); repository.save(new TokenUsage(userId, tokens.input, tokens.output)); checkQuota(userId); return answer; } }扩展点设计TokenCounter接口可以有不同的实现精确从 API 获取、近似估算、甚至使用第三方计费服务。还可以与 Sentinel 结合实现令牌桶限流按 Token 消耗速率限制。六、扩展点五回调机制 —— 让异步成为一等公民有些 AI 任务耗时很长如生成万字报告、分析百页 PDF不适合同步等待。这时需要回调机制用户发起任务后立即返回任务 IDAI 处理完成后主动通知用户。架构设计客户端发起请求后端返回taskId。后台将任务丢入消息队列由专门的 Worker 消费并调用 AI。AI 完成后的结果通过回调 URL由客户端注册或 WebSocket 推送给用户。扩展点设计定义AsyncTaskHandler接口支持不同的任务存储Redis、数据库、不同的回调方式HTTP、WebSocket、消息队列。七、将这些扩展点组合成一个“底座”有了以上扩展点我们可以组装一个完整的 AI 能力底座。下面是一个 Spring Boot 自动配置的示意Configuration ConditionalOnProperty(ai.enabled) public class AiAutoConfiguration { Bean ConditionalOnMissingBean public AiClient aiClient(AiProperties props, ListCacheStrategy caches, ListTokenCounter counters) { AiClient client createClientByProvider(props.getProvider()); // 装饰器模式叠加能力 client new CachingAiClient(client, caches); client new TokenCountingAiClient(client, counters); return client; } }这样业务代码只需要注入AiClient底层自动拥有了缓存、计费等能力且各个策略都可以通过配置灵活开关。八、总结AI 底座不是过度设计而是长期主义很多开发者会问“我的项目一开始只用了一个模型有必要这么复杂吗”我的答案是这取决于你对未来的预期。如果你的 AI 功能只是一次性的 Demo当然可以硬编码。但只要你的业务会持续迭代、模型会升级、成本需要控制那么花一点时间设计扩展点长远看绝对值得。核心扩展点回顾✅统一 AI 客户端让模型切换成为配置项。✅缓存策略降低延迟和成本支持语义复用。✅流式响应提升用户体验消除白屏等待。✅Token 计费成本可视化防止滥用。✅回调机制支持长任务异步化释放 HTTP 线程。最后送你一句话不要让你的后端代码被某一个 AI 模型“绑架”。为未来预留插座才能在技术浪潮中从容转身。
为 LLM 预留“插座”:设计可插拔的 AI 能力底座
写在前面你好我是 Evan。一名正在摸爬滚打的 Java 后端开发者也是这个专栏的作者。在智答 Agent 项目中我踩过最大的坑不是模型选错、不是 Prompt 调不好而是架构没有为 AI 预留扩展点。最初我们直接硬编码调用 OpenAI 的 API后来要切换成通义千问、DeepSeek甚至要同时支持流式和非流式输出还要统计每个用户的 Token 消费、缓存常见问题、支持异步回调……每一次变更都像在重构。痛定思痛我设计了一套“AI 能力底座”——将大模型相关的能力抽象成可插拔的扩展点。今天我就把这套设计思路分享出来从缓存策略、流式响应、Token 计费到回调机制手把手教你如何让后端优雅地拥抱 AI而不是被 AI 拖着走。一、为什么你的后端需要“AI 扩展点”传统后端调用 AI 模型通常就是一个 HTTP 请求 JSON 解析。但当你真正把 AI 能力落地到生产时会发现远远不够模型随时可能切换从 OpenAI 到国产模型甚至自建私有模型。输出方式多样流式响应 vs 阻塞式响应前端体验天差地别。成本需要控制Token 计费、缓存高频问题、限制恶意刷接口。异步场景复杂AI 生成长文可能耗时几十秒不能让 HTTP 连接一直等着。如果你的代码把模型调用、缓存、计费等逻辑全部耦合在一起任何一处变动都会牵一发而动全身。“AI 优先的架构”核心思想就是把 AI 能力抽象成一个个可插拔的组件通过配置或依赖注入灵活组合。下面这张图展示了整体架构接下来我们逐个拆解这些扩展点。二、扩展点一统一 AI 客户端 —— 让模型切换无痛最基础也最重要的扩展点是抽象出统一的 AI 客户端接口。无论底层是 OpenAI、通义千问、DeepSeek 还是本地部署的 Llama上层业务代码只依赖接口。public interface AiClient { // 阻塞式调用 String chat(String prompt, ListMessage history); // 流式调用 void chatStream(String prompt, ListMessage history, StreamCallback callback); // 获取模型信息用于计费 ModelInfo getModelInfo(); }通过 Spring 的ConditionalOnProperty可以根据配置文件动态注入不同的实现ai: provider: deepseek # 或 openai, qwen, mock这样做的好处是切换模型只需要改配置文件无需修改业务代码。新模型接入只需新增一个实现类。单元测试时可以用 MockClient 完全脱离真实 API。三、扩展点二缓存策略 —— 省钱又提速大模型调用昂贵且缓慢对于重复或相似的问题缓存能极大降低成本。但传统精确匹配key 完整 prompt太死板我们需要语义缓存意思相近的问题命中同一缓存。在 Java 中可以用 Redis 配合 RediSearch 模块或 pgvector 来实现。一个简单但有效的缓存键设计public class CacheKey { private String prompt; private String modelName; // 不同模型答案不同 private Double temperature; // 参数影响输出 // 甚至可以包含历史对话的哈希 }扩展点设计定义CacheStrategy接口允许切换不同实现无缓存、精确缓存、语义缓存。public interface CacheStrategy { OptionalString get(String prompt, AiContext ctx); void put(String prompt, String answer, AiContext ctx); }四、扩展点三流式响应 —— 让用户不再干等AI 生成内容通常需要几秒甚至几十秒。阻塞式接口会让用户体验极差而流式响应SSE 或 WebSocket能逐字输出大幅提升感知速度。技术选型推荐使用SSEServer-Sent Events它基于 HTTP实现简单自动重连非常适合单向流式输出。GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(String prompt) { SseEmitter emitter new SseEmitter(30000L); aiClient.chatStream(prompt, history, chunk - { emitter.send(chunk); }); emitter.onCompletion(() - log.info(Stream finished)); return emitter; }扩展点设计AiClient的chatStream方法就是一个天然的扩展点。不同的模型底层实现流式的方式不同OpenAI 用 Server-Sent Events通义用 WebSocket但上层业务只看到回调。五、扩展点四Token 计费 —— 成本可观测、可限制Token 是调用大模型的计价单位。没有计费模块你既不知道每个用户花了多少钱也无法防止恶意刷接口。核心设计在调用 AI 之前和之后分别统计输入 Token 和输出 Token可以从 API 响应中获取或本地估算。然后将消费记录写入数据库并实时检查用户配额。public class TokenCountingProxy implements AiClient { private final AiClient delegate; private final TokenUsageRepository repository; public String chat(String prompt, ListMessage history) { var start System.nanoTime(); String answer delegate.chat(prompt, history); var tokens estimateTokens(prompt, answer); repository.save(new TokenUsage(userId, tokens.input, tokens.output)); checkQuota(userId); return answer; } }扩展点设计TokenCounter接口可以有不同的实现精确从 API 获取、近似估算、甚至使用第三方计费服务。还可以与 Sentinel 结合实现令牌桶限流按 Token 消耗速率限制。六、扩展点五回调机制 —— 让异步成为一等公民有些 AI 任务耗时很长如生成万字报告、分析百页 PDF不适合同步等待。这时需要回调机制用户发起任务后立即返回任务 IDAI 处理完成后主动通知用户。架构设计客户端发起请求后端返回taskId。后台将任务丢入消息队列由专门的 Worker 消费并调用 AI。AI 完成后的结果通过回调 URL由客户端注册或 WebSocket 推送给用户。扩展点设计定义AsyncTaskHandler接口支持不同的任务存储Redis、数据库、不同的回调方式HTTP、WebSocket、消息队列。七、将这些扩展点组合成一个“底座”有了以上扩展点我们可以组装一个完整的 AI 能力底座。下面是一个 Spring Boot 自动配置的示意Configuration ConditionalOnProperty(ai.enabled) public class AiAutoConfiguration { Bean ConditionalOnMissingBean public AiClient aiClient(AiProperties props, ListCacheStrategy caches, ListTokenCounter counters) { AiClient client createClientByProvider(props.getProvider()); // 装饰器模式叠加能力 client new CachingAiClient(client, caches); client new TokenCountingAiClient(client, counters); return client; } }这样业务代码只需要注入AiClient底层自动拥有了缓存、计费等能力且各个策略都可以通过配置灵活开关。八、总结AI 底座不是过度设计而是长期主义很多开发者会问“我的项目一开始只用了一个模型有必要这么复杂吗”我的答案是这取决于你对未来的预期。如果你的 AI 功能只是一次性的 Demo当然可以硬编码。但只要你的业务会持续迭代、模型会升级、成本需要控制那么花一点时间设计扩展点长远看绝对值得。核心扩展点回顾✅统一 AI 客户端让模型切换成为配置项。✅缓存策略降低延迟和成本支持语义复用。✅流式响应提升用户体验消除白屏等待。✅Token 计费成本可视化防止滥用。✅回调机制支持长任务异步化释放 HTTP 线程。最后送你一句话不要让你的后端代码被某一个 AI 模型“绑架”。为未来预留插座才能在技术浪潮中从容转身。