建造者模式进阶:复杂AI服务的优雅构建
建造者模式进阶:复杂 AI 服务的优雅构建
AI 服务配置的多样性
在我们的系统中,一个完整的 AI 服务需要配置众多组件:
基础模型配置:
- 聊天模型(ChatModel)
- 流式聊天模型(StreamingChatModel)
- 模型参数(API Key、Base URL、温度等)
功能组件配置:
- 对话记忆(ChatMemory)
- 工具集合(Tools)
- 安全护轨(Guardrails)
- 异常处理策略(Error Handling)
业务逻辑配置:
- 记忆提供者(Memory Provider)
- 工具执行策略(Tool Execution Strategy)
- 输入输出处理器(Processors)
如果用传统方式构建这样复杂的对象,代码会变成这样:
// 传统方式 - 混乱且难以维护
public AiCodeGeneratorService createService(long appId, CodeGenTypeEnum type) {
AiCodeGeneratorService service = new AiCodeGeneratorService();
service.setChatModel(getChatModel(type));
service.setStreamingChatModel(getStreamingChatModel(type));
service.setChatMemory(buildChatMemory(appId));
service.setTools(getTools(type));
service.setInputGuardrails(getInputGuardrails());
service.setOutputGuardrails(getOutputGuardrails(type));
service.setErrorHandlingStrategy(getErrorStrategy());
// ... 还有更多配置
return service;
}
这种方式的问题显而易见:
- 配置分散:配置逻辑散落在各个方法中
- 顺序依赖:某些配置可能依赖其他配置的结果
- 可读性差:无法直观看出对象的完整配置
- 扩展困难:新增配置项需要修改多处代码
建造者模式的优雅解决方案
LangChain4j 的 AiServices Builder
LangChain4j 框架提供了一个优雅的建造者模式实现:
// 来源:src/main/java/com/ustinian/cheeseaicode/ai/AiCodeGeneratorServiceFactory.java (第125-134行)
yield AiServices.builder(AiCodeGeneratorService.class)
.streamingChatModel(reasoningStreamingChatModel)
.chatMemoryProvider(memoryId -> chatMemory)
.tools(toolManager.getAllTools())
.hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
toolExecutionRequest, "Error: there is no tool called " + toolExecutionRequest.name()
))
.inputGuardrails(List.of(new PromptSafetyInputGuardrail()))
.build();
建造者模式的优势一目了然:
- 链式调用:方法链式调用,代码流畅易读
- 配置集中:所有配置在一个地方完成
- 类型安全:编译时就能发现配置错误
- 可选配置:支持可选参数,有合理的默认值
- 不可变对象:构建完成后对象不可修改,保证线程安全
不同业务场景的差异化构建
在我们的项目中,不同的代码生成类型需要不同的 AI 服务配置:
// 来源:src/main/java/com/ustinian/cheeseaicode/ai/AiCodeGeneratorServiceFactory.java (第121-148行)
return switch (codeGenType) {
case VUE_PROJECT -> {
// 使用多例模式的 StreamingChatModel 解决并发问题
StreamingChatModel reasoningStreamingChatModel = applicationContext.getBean("reasoningStreamingChatModelPrototype", StreamingChatModel.class);
yield AiServices.builder(AiCodeGeneratorService.class)
.streamingChatModel(reasoningStreamingChatModel)
.chatMemoryProvider(memoryId -> chatMemory)
.tools(toolManager.getAllTools())
.hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
toolExecutionRequest, "Error: there is no tool called " + toolExecutionRequest.name()
))
.inputGuardrails(List.of(new PromptSafetyInputGuardrail()))
.build();
}
case HTML, MULTI_FILE -> {
// 使用多例模式的 ChatModel 和 StreamingChatModel 解决并发问题
ChatModel chatModel = applicationContext.getBean("routingChatModelPrototype", ChatModel.class);
StreamingChatModel openAiStreamingChatModel = applicationContext.getBean("streamingChatModelPrototype", StreamingChatModel.class);
yield AiServices.builder(AiCodeGeneratorService.class)
.chatModel(chatModel)
.streamingChatModel(openAiStreamingChatModel)
.chatMemory(chatMemory)
.inputGuardrails(new PromptSafetyInputGuardrail())
.build();
}
default -> throw new BusinessException(ErrorCode.SYSTEM_ERROR,
"不支持的代码生成类型: " + codeGenType.getValue());
};
差异化配置的精妙设计:
-
VUE_PROJECT 配置:
- 使用推理模型(reasoningStreamingChatModel)
- 集成完整工具链(getAllTools)
- 配置工具幻觉处理策略
- 添加输入安全护轨
-
HTML/MULTI_FILE 配置:
- 同时配置聊天模型和流式模型
- 使用简化的记忆管理
- 只添加基础的输入护轨
这种差异化配置体现了建造者模式的灵活性,同一个构建器可以根据不同参数构建出功能各异的对象。
记忆管理的建造者实现
MessageWindowChatMemory 的构建
AI 服务的记忆管理也是一个复杂的配置过程:
// 来源:src/main/java/com/ustinian/cheeseaicode/ai/AiCodeGeneratorServiceFactory.java (第112-117行)
MessageWindowChatMemory chatMemory = MessageWindowChatMemory
.builder()
.id(appId)
.chatMemoryStore(redisChatMemoryStore)
.maxMessages(100)
.build();
记忆构建的配置要点:
- 唯一标识:每个应用有独立的记忆 ID
- 持久化存储:使用 Redis 作为记忆存储后端
- 容量限制:最多保留 100 条历史消息
- 滑动窗口:自动清理超出限制的旧消息
RedisChatMemoryStore 的建造者配置
连 Redis 存储的配置也使用了建造者模式:
// 来源:src/main/java/com/ustinian/cheeseaicode/config/RedisChatMemoryStoreConfig.java (第28-36行)
@Bean
public RedisChatMemoryStore redisChatMemoryStore() {
RedisChatMemoryStore.Builder builder = RedisChatMemoryStore.builder()
.host(host)
.port(port)
.password(password)
.ttl(ttl);
if (StrUtil.isNotBlank(password)) {
builder.user("default");
}
return builder.build();
}
Redis 存储配置的细节处理:
- 条件配置:只有在密码非空时才设置用户名
- TTL 管理:设置记忆数据的过期时间
- 连接参数:从配置文件读取连接信息
- 容错处理:支持无密码的 Redis 实例
路由服务的简化构建
对于相对简单的路由服务,建造者模式同样展现了其简洁性:
// 来源:src/main/java/com/ustinian/cheeseaicode/ai/AiCodeGenTypeRoutingServiceFactory.java (第32-37行)
public AiCodeGenTypeRoutingService createAiCodeGenTypeRoutingService(){
// 使用多例Bean避免依赖自动配置
ChatModel routingChatModel = applicationContext.getBean("routingChatModelPrototype", ChatModel.class);
return AiServices.builder(AiCodeGenTypeRoutingService.class)
.chatModel(routingChatModel)
.build();
}
简化构建的设计思路:
- 最小配置:只配置必需的聊天模型
- 默认行为:其他配置使用框架默认值
- 专用目标:专门用于代码生成类型路由判断
- 轻量实现:避免不必要的功能组件
模型配置的建造者应用
OpenAI 模型的统一构建
在模型配置层面,建造者模式也发挥了重要作用:
// 来源:src/main/java/com/ustinian/cheeseaicode/config/RoutingAiModelConfig.java (第39-47行)
@Bean
@Scope("prototype")
public ChatModel routingChatModelPrototype() {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName(modelName)
.baseUrl(baseUrl)
.maxTokens(maxTokens)
.temperature(temperature)
.logRequests(logRequests)
.logResponses(logResponses)
.build();
}
// 来源:src/main/java/com/ustinian/cheeseaicode/config/StreamingChatModelConfig.java (第32-41行)
@Bean
@Scope("prototype")
public StreamingChatModel streamingChatModelPrototype() {
return OpenAiStreamingChatModel.builder()
.apiKey(apiKey)
.baseUrl(baseUrl)
.modelName(modelName)
.maxTokens(maxTokens)
.temperature(temperature)
.logRequests(logRequests)
.logResponses(logResponses)
.build();
}
模型构建的统一性:
- 配置一致性:所有模型使用相同的配置参数结构
- 类型区分:ChatModel 和 StreamingChatModel 的差异化构建
- 参数化配置:通过 Spring 配置文件管理所有参数
- 调试支持:可配置的请求和响应日志记录
建造者模式的技术优势
可读性与可维护性
对比传统构造方法:
// 传统方式 - 参数过多且顺序固定
AiService service = new AiService(
chatModel,
streamingModel,
memory,
tools,
guardrails,
strategy,
true,
false,
100
); // 这些布尔值和数字代表什么?
// 建造者方式 - 自文档化且灵活
AiService service = AiServices.builder(AiService.class)
.chatModel(chatModel)
.streamingChatModel(streamingModel)
.chatMemory(memory)
.tools(tools)
.inputGuardrails(guardrails)
.errorHandlingStrategy(strategy)
.logRequests(true)
.logResponses(false)
.maxRetries(100)
.build();
可读性提升显著:
- 参数语义化:每个配置都有明确的方法名
- 可选参数:不需要的配置可以省略
- 顺序无关:配置顺序可以任意调整
- IDE 友好:代码提示和自动补全支持更好
扩展性与灵活性
新增配置项的便利性:
// 只需要在 Builder 中添加新方法,不影响现有代码
AiServices.builder(AiCodeGeneratorService.class)
.streamingChatModel(model)
.chatMemory(memory)
.tools(tools)
.inputGuardrails(guardrails)
// 新增配置项
.customProcessor(processor)
.retryPolicy(policy)
.timeoutSettings(timeout)
.build();
类型安全与编译检查
建造者模式提供了强类型检查:
// 编译时就能发现类型错误
AiServices.builder(AiCodeGeneratorService.class)
.streamingChatModel(chatModel) // 如果类型不匹配,编译失败
.maxTokens("invalid") // 如果类型不匹配,编译失败
.build();
类型安全的优势:
- 编译时检查:类型错误在编译阶段就被发现
- 重构友好:IDE 可以安全地进行重构操作
- 文档价值:类型本身就是最好的文档
关键技术收获
- 复杂度管理:学会了如何优雅地处理复杂对象的构建
- API 设计:理解了好的 API 应该是自文档化和类型安全的
- 可扩展性思维:考虑了如何设计易于扩展的架构
- 用户体验:从开发者体验的角度思考 API 设计
设计模式的实际价值
- 不是为了模式而模式:建造者模式解决了实际的复杂性问题
- 框架设计的智慧:LangChain4j 的 API 设计值得学习和借鉴
- 渐进式应用:可以从简单场景开始,逐步应用到复杂场景
- 团队效率提升:好的设计模式能显著提升团队协作效率

浙公网安备 33010602011771号