LangChain4j AI Services 深度解析:声明式 API 与接口驱动开发
前言
LangChain4j 的 AI Services 是框架的核心特性之一,它采用声明式 API 设计,让开发者只需定义接口,无需编写实现类即可调用 LLM。这种设计类似于 Spring Data JPA 或 Retrofit,极大地简化了 AI 应用的开发复杂度。
本文将深入剖析 AI Services 的核心注解、模板语法、结构化输出、流式响应,以及多模型路由等高级特性。
一、核心注解详解
1.1 @AiService 注解
@AiService 是 AI Services 的核心注解,用于标记 AI Service 接口。
java
import dev.langchain4j.service.AiService;
@AiService
public interface Assistant {
String chat(String message);
}
使用示例:
java
// 创建 AI Service 实例
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.build();
// 调用
String response = assistant.chat("你好");
1.2 @SystemMessage 注解
定义系统提示词,设定 AI 的角色和行为。
java
import dev.langchain4j.service.SystemMessage;
@AiService
public interface CodingAssistant {
@SystemMessage("你是一个专业的Java开发工程师,擅长Spring Boot和微服务架构")
String codeReview(String code);
@SystemMessage("""
你是一个技术文档撰写专家,要求:
1. 结构清晰,使用 Markdown 格式
2. 包含代码示例和最佳实践
3. 适合中级开发者阅读
""")
String writeDoc(String topic);
}
1.3 @UserMessage 注解
定义用户消息模板,支持变量插值。
java
import dev.langchain4j.service.UserMessage;
@AiService
public interface QuestionAssistant {
// 使用 {{it}} 表示唯一参数
@UserMessage("请解释:{{it}}")
String explain(String concept);
// 使用 {{var}} 表示命名变量
@UserMessage("请比较{{lang1}}和{{lang2}}的区别")
String compare(
@V("lang1") String language1,
@V("lang2") String language2
);
// 复杂模板
@UserMessage("""
请帮我分析以下情况:
用户:{{username}}
问题类型:{{issueType}}
问题描述:{{description}}
请提供3个解决方案,每个方案包含:
1. 方案描述
2. 实施步骤
3. 预期效果
""")
String analyzeIssue(
@V("username") String username,
@V("issueType") String issueType,
@V("description") String description
);
}
1.4 @AssistantMessage 注解
定义 AI 的预设响应,用于少样本学习(Few-shot Learning)。
java
import dev.langchain4j.service.AssistantMessage;
@AiService
public interface FewShotAssistant {
@UserMessage("1 + 1 = ?")
@AssistantMessage("1 + 1 = 2")
@UserMessage("2 + 2 = ?")
@AssistantMessage("2 + 2 = 4")
@UserMessage("3 + 3 = ?")
String solve(String expression);
}
// 调用
String answer = assistant.solve("5 + 5 = ?");
// 预期输出: "5 + 5 = 10"
二、模板语法详解
2.1 Mustache 风格模板
LangChain4j 支持 Mustache 风格的模板语法:
| 语法 | 说明 | 示例 |
|---|---|---|
{``{it}} |
表示方法唯一参数 | @UserMessage("请解释:{``{it}}") |
{``{var}} |
表示命名变量 | @UserMessage("Hello, {``{name}}") + @V("name") |
{``{#condition}}...{``{/condition}} |
条件渲染 | {``{#hasDetails}}...{``{/hasDetails}} |
{``{#array}}...{``{/array}} |
循环渲染 | {``{#items}}- {``{.}}{``{/items}} |
2.2 基础模板示例
java
@AiService
public interface TemplateAssistant {
// 单参数模板
@UserMessage("请翻译成英文:{{it}}")
String translate(String text);
// 多参数模板
@UserMessage("请写一封{{tone}}的邮件给{{recipient}},内容是:{{content}}")
String writeEmail(
@V("tone") String tone,
@V("recipient") String recipient,
@V("content") String content
);
// 条件渲染
@UserMessage("""
请生成{{#isProfessional}}专业{{/isProfessional}}{{^isProfessional}}通俗{{/isProfessional}}版本的解释:
{{topic}}
""")
String explain(
@V("topic") String topic,
@V("isProfessional") boolean isProfessional
);
}
// 调用示例
String email = assistant.writeEmail("礼貌", "张经理", "明天会议延期");
// 输出: "张经理,您好!关于明天会议...(礼貌语气)"
String explanation = assistant.explain("微服务架构", true);
// 输出: "专业版本的解释..."
2.3 循环渲染
java
@AiService
public interface LoopAssistant {
@UserMessage("""
请评价以下技术栈:
{{#techs}}
- {{name}}:{{description}}
{{/techs}}
给出评分(1-5分)和建议。
""")
String evaluateTechStack(
@V("techs") List<Technology> techs
);
}
record Technology(String name, String description) {}
// 调用
List<Technology> techs = List.of(
new Technology("Spring Boot", "快速开发框架"),
new Technology("React", "前端 UI 框架"),
new Technology("PostgreSQL", "关系型数据库")
);
String evaluation = assistant.evaluateTechStack(techs);
2.4 从资源文件加载模板
java
// prompts/code-review.txt
请你作为代码审查专家,审查以下代码:
```java
{{code}}
审查要点:
- 代码风格
- 潜在 Bug
- 性能优化
- 安全问题
请以 Markdown 格式输出审查结果。
```java
@AiService
public interface CodeReviewAssistant {
@UserMessage(fromResource = "prompts/code-review.txt")
String review(@V("code") String code);
}
三、结构化输出
3.1 POJO 映射
LangChain4j 支持将 LLM 返回的 JSON 自动映射为 POJO。
java
@AiService
public interface StructuredOutputAssistant {
// 返回 POJO
User analyzeUser(String text);
}
record User(
String name,
int age,
String email,
List<String> interests
) {}
// 调用
User user = assistant.analyzeUser("我叫张三,25岁,邮箱是zhangsan@example.com,喜欢编程和阅读");
// user.name() == "张三"
// user.age() == 25
3.2 枚举类型
java
@AiService
public interface EnumAssistant {
Sentiment analyzeSentiment(String text);
}
enum Sentiment {
POSITIVE, NEGATIVE, NEUTRAL
}
// 调用
Sentiment sentiment = assistant.analyzeSentiment("今天天气真不错!");
// sentiment == Sentiment.POSITIVE
3.3 Boolean 类型
java
@AiService
public interface BooleanAssistant {
boolean isSpam(String message);
boolean containsCode(String text);
}
// 调用
boolean spam = assistant.isSpam("恭喜您中奖100万!");
boolean code = assistant.containsCode("public static void main(String[] args) {}");
3.4 @StructuredPrompt 注解
用于更复杂的结构化输出场景。
java
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.StructuredPrompt;
@StructuredPrompt("""
请分析以下招聘需求:
职位:{{jobTitle}}
公司:{{company}}
要求:{{requirements}}
""")
record JobAnalysis(
String jobTitle,
String company,
String requirements
) {}
@AiService
public interface JobAssistant {
JobAnalysisResponse analyze(JobAnalysis job);
}
record JobAnalysisResponse(
String summary,
List<String> keySkills,
List<String> missingSkills,
int experienceLevel // 1-5
) {}
3.5 结构化输出流程图
┌─────────────────────────────────────────────────────────────┐
│ 结构化输出处理流程 │
└─────────────────────────────────────────────────────────────┘
┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────┐
│ 用户调用 │ -> │ @AiService │ -> │ ChatLanguage │ -> │ LLM │
│ 返回POJO │ │ 接口代理 │ -> │ Model │ -> │ API │
└─────────┘ └──────────────┘ └──────────────┘ └───────┘
│ │ │ │
│ 1. 定义返回类型 │ 2. 构建Prompt │ 3. 发送请求 │ 4. 返回JSON
│ POJO │ 包含类型提示 │ │
└────────────────┴──────────────────┴───────────────┘
│
5. JSON解析
6. 映射到POJO
7. 返回给用户
详细步骤:
1. 用户调用 assistant.analyzeUser(...)
2. @AiService 代理根据返回类型 User 生成类型提示
3. 构建完整 Prompt(系统提示 + 类型说明 + 用户输入)
4. 发送到 LLM
5. LLM 返回 JSON 格式:{"name": "张三", "age": 25, ...}
6. LangChain4j 使用 Jackson 解析 JSON
7. 映射到 User POJO 并返回
四、流式响应
4.1 TokenStream 接口
java
import dev.langchain4j.model.StreamingResponseHandler;
import dev.langchain4j.model.output.TokenStream;
import dev.langchain4j.model.output.Response;
@AiService
public interface StreamingAssistant {
TokenStream chat(@UserMessage String message);
}
// 使用示例
StreamingAssistant assistant = AiServices.builder(StreamingAssistant.class)
.streamingChatLanguageModel(streamingModel)
.build();
assistant.chat("请写一篇关于Java的文章")
.onNext(token -> {
System.out.print(token); // 逐词输出
})
.onComplete(response -> {
System.out.println("\n完成!");
System.out.println("Total tokens: " + response.tokenUsage().totalTokenCount());
})
.onError(error -> {
error.printStackTrace();
});
4.2 Flux 集成(Reactive Streams)
java
import reactor.core.publisher.Flux;
@AiService
public interface ReactiveAssistant {
Flux<String> chat(String message);
}
// 使用示例
Flux<String> responseFlux = assistant.chat("写一首诗");
responseFlux
.buffer(100) // 每 100 个字符一批
.subscribe(batch -> {
System.out.println(batch);
});
4.3 流式响应流程图
┌─────────────────────────────────────────────────────────────┐
│ 流式响应处理流程 │
└─────────────────────────────────────────────────────────────┘
用户 LLM @AiService
│ │ │
│ 1. 发起请求 │ │
├─────────────────────>│ │
│ "请写一篇文章" │ │
│ │ │
│ │ 2. 生成并流式返回Token │
│ ├────────────────────>│
│ │ "LangChain4j..." │
│ │ "是..." │
│ │ "一个..." │
│ │ │
│ 3. onNext(TOKEN) │ │
│<─────────────────────┤ │
│ 输出 "LangChain4j" │ │
│ │ │
│ 4. onNext(TOKEN) │ │
│<─────────────────────┤ │
│ 输出 "是" │ │
│ │ │
│ ... │ ... │
│ │ │
│ 5. onComplete │ │
│<─────────────────────┤ │
│ 完成,返回 TokenUsage│ │
│ │ │
└──────────────────────┴───────────────────────┘
优势:
- 降低延迟:首字输出时间短
- 实时反馈:用户可即时看到输出
- 更好的用户体验:类似 ChatGPT 的打字效果
五、多模型路由
5.1 基于方法的模型路由
java
@AiService
public interface MultiModelAssistant {
// 轻量查询使用 Ollama
@ModelNameProvider("ollama")
String simpleQuestion(@UserMessage String question);
// 复杂任务使用 GPT-4
@ModelNameProvider("openai-gpt4")
String complexTask(@UserMessage String task);
}
5.2 动态模型选择
java
public class ModelRouter {
private final ChatLanguageModel ollamaModel;
private final ChatLanguageModel gpt4Model;
public ModelRouter() {
this.ollamaModel = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("llama3.1")
.build();
this.gpt4Model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4")
.build();
}
public Assistant createAssistant(String modelType) {
ChatLanguageModel model = switch (modelType) {
case "lightweight" -> ollamaModel;
case "heavyweight" -> gpt4Model;
default -> throw new IllegalArgumentException("Unknown model type");
};
return AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.build();
}
}
// 使用
ModelRouter router = new ModelRouter();
// 轻量任务
Assistant lightweightAssistant = router.createAssistant("lightweight");
String simpleAnswer = lightweightAssistant.chat("你好");
// 复杂任务
Assistant heavyweightAssistant = router.createAssistant("heavyweight");
String complexAnswer = heavyweightAssistant.chat("请分析以下代码并给出优化建议:...");
5.3 基于成本的模型选择
java
public class CostAwareRouter {
private final ChatLanguageModel cheapModel;
private final ChatLanguageModel expensiveModel;
// 成本阈值(字符数)
private static final int COST_THRESHOLD = 1000;
public String chat(String message) {
// 短文本使用廉价模型
if (message.length() < COST_THRESHOLD) {
return AiServices.builder(Assistant.class)
.chatLanguageModel(cheapModel)
.build()
.chat(message);
}
// 长文本使用高质量模型
else {
return AiServices.builder(Assistant.class)
.chatLanguageModel(expensiveModel)
.build()
.chat(message);
}
}
}
六、高级特性
6.1 超时控制
java
@AiService
public interface TimeoutAssistant {
@Timeout(Duration.ofSeconds(30))
String quickAnswer(String question);
@Timeout(Duration.ofMinutes(5))
String longTask(String task);
}
6.2 温度控制
java
@AiService
public interface TemperatureAssistant {
@Temperature(0.2) // 低温度,确定性输出
String codeGeneration(String description);
@Temperature(0.8) // 高温度,创造性输出
String creativeWriting(String topic);
}
6.3 最大 Token 控制
java
@AiService
public interface MaxTokensAssistant {
@MaxTokens(100) // 短响应
String summary(String text);
@MaxTokens(2000) // 长响应
String detailedExplanation(String topic);
}
6.4 聊天记忆
java
@AiService
public interface MemoryAssistant {
@MemoryId // 指定记忆 ID
String chat(@MemoryId String userId, String message);
}
// 使用
String userId = "user123";
String response1 = assistant.chat(userId, "你好,我叫张三");
String response2 = assistant.chat(userId, "我的名字是什么?");
// AI 会记住之前的对话:"你的名字是张三"
七、常见问题
Q1: 如何处理 LLM 返回的无效 JSON?
A: LangChain4j 会自动重试和修复:
java
// 配置 JSON 修复策略
AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.jsonRepairStrategy(JsonRepairStrategy.AUTO_FIX)
.build();
Q2: 流式响应可以中途取消吗?
A: 可以,使用 CancellationToken:
java
CancellationToken token = new SimpleCancellationToken();
assistant.chat("写一篇长文章")
.onNext(token -> {
if (shouldCancel(token)) {
token.cancel();
}
});
Q3: 如何实现多个 AI Service 共享状态?
A: 使用共享的 ChatMemoryProvider:
java
ChatMemoryProvider memoryProvider = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
Assistant assistant1 = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryProvider)
.build();
Assistant assistant2 = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryProvider)
.build();
// assistant1 和 assistant2 共享记忆
八、小结
本文深入剖析了 LangChain4j AI Services 的核心特性,包括:
- 核心注解:@AiService、@SystemMessage、@UserMessage、@AssistantMessage
- 模板语法:Mustache 风格、条件渲染、循环渲染、资源文件加载
- 结构化输出:POJO 映射、枚举类型、Boolean 类型、@StructuredPrompt
- 流式响应:TokenStream 接口、Flux 集成、实时输出
- 多模型路由:基于方法的路由、动态选择、成本感知路由
- 高级特性:超时控制、温度控制、最大 Token 控制、聊天记忆
核心思想: 声明式 API 设计,让开发者只需定义接口,无需编写实现类即可调用 LLM。
下一步学习:
- 文章 5:《LangChain4j 与 OpenAI 深度集成:Function Calling、JSON Mode 与结构化输出》
参考文献:
- LangChain4j 官方文档:https://docs.langchain4j.dev/tutorials/ai-services/
- Mustache 模板引擎:https://mustache.github.io/
- Spring Data JPA 官方文档:https://spring.io/projects/spring-data-jpa