写接口,不写实现:LangChain4j 的 @AiService 到底有多优雅?

发布时间:2026/7/3 13:48:03

写接口,不写实现:LangChain4j 的 @AiService 到底有多优雅? 本文将介绍LangChain4j 最有特色的设计AiService。如果你是一个 Spring 老手第一次看到AiService很可能会脱口而出“这不就是 Spring Data Repository 的 AI 版本嘛”没错就是这个感觉。一、跑通第一个 AiService1.1定义接口package com.stduying.service; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.spring.AiService; AiService // 标记为 AI 服务接口 public interface SimpleAssistant { SystemMessage(你是一个友好的 AI 助手用简洁的语言回答问题) String chat(String userMessage); }1.2 注入并使用package com.stduying.controller.aiservice; import com.stduying.service.SimpleAssistant; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController RequestMapping(/assistant) public class AssistantController { private final SimpleAssistant assistant; public AssistantController(SimpleAssistant assistant) { this.assistant assistant; } GetMapping public String ask(RequestParam String question) { return assistant.chat(question); } }启动项目后执行curl http://localhost:8080/assistant?question什么是Spring AOP模型就会根据SystemMessage的角色设定返回一个简洁的回答。不需要写任何实现类不需要手动构建ChatClient不需要拼接SystemMessage和UserMessage——AiService全帮你干了。二、AiService 的工作原理当 Spring Boot 启动时LangChain4j 会扫描所有标注了AiService的接口为每个接口动态生成一个代理实现并注册为 Spring Bean。这个代理实现大致做了这些事情方法调用如 assistant.chat(userMessage) ↓ 读取方法上的 SystemMessage角色设定 ↓ 把方法参数包装成 UserMessage ↓ 构建完整的 messages 列表[SystemMessage, UserMessage] ↓ 调用底层 ChatModel 的 generate() 方法 ↓ 将模型的输出String/对象等返回给调用方理解这个流程后后面遇到其他注解如UserMessage、MemoryId时你就知道它们是在哪个环节起作用了。三、一个接口多个方法多种 AI 能力同一个接口可以定义多个方法每个方法可以有自己的SystemMessage代表不同的 AI 能力AiService public interface MultiCapabilityAssistant { SystemMessage(你是一个 Java 技术助手专注于代码质量和性能优化) String reviewCode(String code); SystemMessage( 你是一个技术文档写作专家。 把技术内容转化为清晰易懂的文档有条理有示例。 ) String writeDoc(String techContent); SystemMessage(你是一个 SQL 专家帮助优化数据库查询) String optimizeSql(String sql); }使用起来非常直观RestController RequestMapping(/dev) public class DevAssistantController { private final MultiCapabilityAssistant assistant; public DevAssistantController(MultiCapabilityAssistant assistant) { this.assistant assistant; } PostMapping(/review) public String reviewCode(RequestBody String code) { return assistant.reviewCode(code); } PostMapping(/doc) public String writeDoc(RequestBody String techContent) { return assistant.writeDoc(techContent); } PostMapping(/sql) public String optimizeSql(RequestBody String sql) { return assistant.optimizeSql(sql); } }测试curl -X POST http://localhost:8080/dev/review -H Content-Type: text/plain -d public void foo() { System.out.println(\hello\); } curl -X POST http://localhost:8080/dev/doc -H Content-Type: text/plain -d Spring AOP 的工作原理 curl -X POST http://localhost:8080/dev/sql -H Content-Type: text/plain -d SELECT * FROM orders WHERE user_id ?四、指定使用哪个模型多模型共存有时我们会在同一个项目中配置多个模型例如一个便宜的模型处理简单问答一个更强也更贵的模型处理复杂任务。配置第二个模型 Bean第一个模型由 Starter 自动配置Configuration public class ModelConfig { // 主模型 qwen-max 已由 Starter 自动配置Bean 名为 openAiChatModel Bean(cheapModel) public ChatModel qwenTurboModel() { return OpenAiChatModel.builder() .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .apiKey(System.getenv(DASHSCOPE_API_KEY)) .modelName(qwen-turbo) // 成本更低 .build(); } }然后在AiService中显式声明要用哪个模型 Bean// 便宜模型 AiService(wiringMode AiServiceWiringMode.EXPLICIT, chatModel cheapModel) public interface EconomyAssistant { SystemMessage(你是一个简单的问答助手) String chat(String message); } // 主力模型 AiService(wiringMode AiServiceWiringMode.EXPLICIT, chatModel openAiChatModel) public interface PremiumAssistant { SystemMessage(你是一个高质量的技术顾问回答要深入专业) String chat(String message); }AiService(wiringMode AiServiceWiringMode.EXPLICIT, chatModel cheapModel)核心作用就是精确地告诉框架你的 AI 服务要用哪个大模型。⚙️ wiringMode AiServiceWiringMode.EXPLICIT掌控依赖装配wiringMode控制了 Spring 如何为你的 AI 服务Autowired所需的组件。它主要有两种模式你可以把它们理解成选择依赖注入的方式模式含义优点缺点自动装配 (AUTOMATIC)这是默认模式。框架会自动在 Spring 容器里寻找所需类型如ChatModel的 Bean 进行注入。简单省事依赖少时配置极快。容易冲突当某个类型存在多个 Bean例如你配置了两个大模型时实例会启动失败。显式装配 (EXPLICIT)开发者需要明确指定要用哪个 Bean 的名字精细控制装配过程。安全精确可完全避免 Bean 冲突尤其适合多模型等复杂场景。相对需要多写一点配置指定 Bean 的名称。你的代码选择了EXPLICIT模式这是一种追求精确控制、避免潜在冲突的良好实践。 chatModel cheapModel指定大语言模型当使用EXPLICIT装配模式时chatModel参数就变得至关重要。它的值cheapModel是一个字符串指向你在 Spring 配置中定义的一个具体的ChatModel类型的 Bean 名字。cheapModel这个命名通常是为了区分不同型号或服务商的模型例如 OpenAI 的gpt-3.5-turbo成本低对比gpt-4成本高。五、手动构建AiServices.builder()除了注解方式LangChain4j 也支持编程式构建 AI 服务这在需要动态配置时非常有用。Configuration public class AssistantConfig { Bean public SimpleAssistant simpleAssistant(ChatModel chatModel) { return AiServices.builder(SimpleAssistant.class) .chatModel(chatModel) // 后面还可以加 memory、tools 等 .build(); } }六、对比 Spring AI 的写法为了让大家直观感受AiService的优雅我们把前面那个多能力助手的例子用 Spring AI 实现一遍。Spring AI 写法Service public class TechAssistantService { private final ChatClient chatClient; public TechAssistantService(ChatClient.Builder builder) { this.chatClient builder .defaultSystem(你是一个 Java 技术助手) .build(); } public String reviewCode(String code) { return chatClient.prompt() .system(你是一个代码审查专家) .user(code) .call() .content(); } public String writeDoc(String content) { return chatClient.prompt() .system(你是一个技术文档写作专家) .user(content) .call() .content(); } public String optimizeSql(String sql) { return chatClient.prompt() .system(你是一个 SQL 专家) .user(sql) .call() .content(); } }LangChain4j AiService 写法AiService public interface TechAssistant { SystemMessage(你是一个代码审查专家) String reviewCode(String code); SystemMessage(你是一个技术文档写作专家) String writeDoc(String content); SystemMessage(你是一个 SQL 专家) String optimizeSql(String sql); }同样的功能LangChain4j 的接口版本代码量减少一大半更直观看一眼接口就知道有哪些 AI 能力不需要实现类框架自动生成与 Spring 生态无缝集成直接Autowired使用七、小结AiService是 LangChain4j 最值得学习的设计。它借鉴了 Spring Data 的 Repository 模式把AI 能力的声明与实现彻底分离。用AiService标记接口用SystemMessage等注解描述能力框架自动生成代理注入即用这种声明式编程风格让 Java 开发者几乎零学习成本就能写出优雅的 AI 服务代码。下一节我们将进入工具体系看看如何给AiService配上“手和脚”——让模型不仅能说还能调用外部能力。

相关新闻