AI Services 是 LangChain4j 最核心的创新:声明一个 Java 接口,框架自动生成代理实现。本章深入解析其原理、完整方法签名、Result 包装类、以及高级配置。
3.1 设计哲学:接口即契约
AI Services 的设计灵感来自 Spring Data JPA 和 Retrofit:
kotlin
Spring Data JPA: interface UserRepo extends JpaRepository<User, Long>
→ Spring 自动实现 findByName(String name)
Retrofit: interface GitHubService
→ Retrofit 自动实现 @GET("users/{id}")
LangChain4j: interface Assistant
→ AiServices.create() 自动编排 LLM 调用全链路
代理替你做了哪些事?

3.2 最小可用示例到完整配置
三步启动
java
// Step 1: 定义接口
interface Assistant {
String chat(String userMessage);
}
// Step 2: 创建 ChatModel
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(GPT_4_O_MINI)
.build();
// Step 3: 创建代理
Assistant assistant = AiServices.create(Assistant.class, model);
// 使用
String answer = assistant.chat("用一句话解释 JVM 的 G1 垃圾回收器");
AiServices Builder 的全部配置项
java
Assistant assistant = AiServices.builder(Assistant.class)
// === 核心模型 ===
.chatModel(model) // ChatModel 实例
.streamingChatModel(streamingModel) // StreamingChatModel(流式场景)
// === 提示词 ===
.systemMessageProvider(memoryId -> "...") // 动态 SystemMessage
.systemMessageTransformer((msg, ctx) -> msg) // SystemMessage 后处理
// === 记忆 ===
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.chatMemoryProvider(memoryId -> createMemory())
// === 工具 ===
.tools(new Calculator()) // 单个工具对象
.tools(List.of(calculator, translator)) // 多个工具
.toolProvider(toolProvider) // 动态工具提供者
.toolSearchStrategy(new SimpleToolSearchStrategy()) // 工具搜索策略
// === RAG ===
.contentRetriever(contentRetriever) // Naive RAG
.retrievalAugmentor(retrievalAugmentor) // Advanced RAG
.storeRetrievedContentInChatMemory(false) // 是否将检索内容存入记忆
// === 安全 ===
.moderationModel(moderationModel) // 自动内容审核
.inputGuardrails(guardrail1, guardrail2) // 输入护栏
.outputGuardrails(guardrail1, guardrail2) // 输出护栏
// === 其他 ===
.chatRequestTransformer(req -> req) // 每次请求前改写 ChatRequest
.build();
3.3 @SystemMessage 全解
静态系统消息
java
interface FriendlyAssistant {
// 方式1:直接写死在注解中
@SystemMessage("你是一个幽默风趣的助手,回答要使用口语化表达")
String chat(String userMessage);
// 方式2:从 classpath 资源文件加载(支持模板变量)
@SystemMessage(fromResource = "prompts/friendly-system.txt")
String chat2(String userMessage);
}
动态系统消息(基于 Memory ID)
java
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.systemMessageProvider(chatMemoryId -> {
// chatMemoryId 即 @MemoryId 的值
if ("vip_user".equals(chatMemoryId)) {
return "你是一个尊贵的白金会员专属客服,使用敬语";
}
return "你是一个普通的客服助手";
})
.build();
SystemMessageTransformer(后处理)
java
// 在每个 SystemMessage 后追加当前日期
.systemMessageTransformer((systemMessage, context) -> {
if (systemMessage == null) return null;
return systemMessage + "\n今天的日期是:" + LocalDate.now();
})
3.4 @UserMessage:模板与变量绑定
基础模板
java
interface Translator {
@UserMessage("将以下文本翻译成{{language}}:\n\n{{it}}")
String translate(@V("language") String language, String text);
}
模板中 {{it}} 引用第一个/唯一的无注解参数,{{variableName}} 引用 @V("variableName") 标注的参数。
@V 注解的必要性
java
// 如果编译时加了 -parameters 标志,可以不写 @V
@UserMessage("分析{{country}}的经济状况")
String analyze(String country); // 参数名可从字节码获取
// 如果没有 -parameters(默认),必须用 @V 显式指定
@UserMessage("分析{{country}}的经济状况")
String analyze(@V("country") String country);
Spring Boot 和 Quarkus 默认启用
-parameters,通常不需要@V。
无参方法
java
// 完全不需要用户输入的场景
@SystemMessage("你是一个知识渊博的助手")
@UserMessage("德国首都是哪里?直接回答城市名")
String germanCapital();
3.5 完整方法签名参考
UserMessage 的形式
java
// 最简单的
String chat(String userMessage);
// 显式标注
String chat(@UserMessage String userMessage);
// 带上下文参数
String chat(@UserMessage String userMessage, @V("country") String country);
// 多模态
String chat(@UserMessage String userMessage, @UserMessage ImageContent image);
String chat(@UserMessage String userMessage, @UserMessage List<Content> contents);
String chat(List<AudioContent> contents);
// 完全由注解定义
@UserMessage("What is the capital of Germany?")
String chat();
// 模板
@UserMessage("What is the capital of {{it}}?")
String chat(String country);
@UserMessage("What is the {{what}} of {{country}}?")
String chat(@V("what") String what, @V("country") String country);
与 @SystemMessage 的组合
java
// 固定 SystemMessage + 可变 UserMessage
@SystemMessage("你是一个翻译专家")
String chat(@UserMessage String userMessage);
// SystemMessage 也可以使用模板变量
@SystemMessage("Given a name of a country, {{answerInstructions}}")
String chat(@V("answerInstructions") String instructions, @UserMessage String userMessage);
// 完全固定的问答
@SystemMessage("你是一个地理专家")
@UserMessage("中国最大的淡水湖是哪个?")
String chat();
3.6 返回类型的完整能力
String 返回
java
String chat(String message); // 原始 LLM 输出,不做解析
结构化输出(后续专门章节分析讲解)
java
// boolean
@UserMessage("Does '{{it}}' have a positive sentiment?")
boolean isPositive(String text);
// Enum
enum Priority { CRITICAL, HIGH, LOW }
@UserMessage("Analyze the priority: {{it}}")
Priority analyzePriority(String issue);
// POJO
@UserMessage("Extract person info from: {{it}}")
Person extractPerson(String text);
Result 元数据包装
java
interface Assistant {
@UserMessage("Generate an outline for: {{it}}")
Result<List<String>> generateOutline(String topic);
}
Result<List<String>> result = assistant.generateOutline("Java Virtual Threads");
// 数据
List<String> outline = result.content();
// 元数据
TokenUsage usage = result.tokenUsage(); // Token 用量
List<Content> sources = result.sources(); // RAG 来源文档
List<ToolExecution> toolExecutions = result.toolExecutions(); // 工具调用全记录
FinishReason finishReason = result.finishReason(); // 最终停止原因
Result 的价值 :当 AI Service 背后涉及多次 LLM 调用(工具调用循环),tokenUsage() 返回的是所有调用的 token 总和,这是裸返回类型无法获取的关键信息。
TokenStream(流式)
java
interface Assistant {
TokenStream chat(String message);
}
后续专门章节分析讲解。
3.7 ChatMemory 集成
单用户场景(共享记忆)
java
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
assistant.chat("我叫张三"); // LLM 记住"张三"
assistant.chat("我叫什么?"); // LLM 回答"张三"
多用户场景(隔离记忆)
java
interface Assistant {
String chat(@MemoryId int memoryId, @UserMessage String message);
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.chatMemoryProvider(memoryId ->
MessageWindowChatMemory.withMaxMessages(10))
.build();
assistant.chat(1, "我叫张三"); // 用户1的记忆
assistant.chat(2, "我叫李四"); // 用户2的记忆,完全隔离
运行时记忆访问
java
interface Assistant extends ChatMemoryAccess {
String chat(@MemoryId int memoryId, @UserMessage String message);
}
// 查询某个用户的对话历史
List<ChatMessage> history = assistant.getChatMemory(1).messages();
// 清除某个用户的记忆
assistant.evictChatMemory(2);
并发警告 :同一
@MemoryId不要并发调用,ChatMemory 没有内置并发保护(ArrayList实现)。
3.8 Service 编排:小而精的组合
官方强烈建议将复杂任务分解为多个职责单一的 AI Service,而不是一个超级 Service。
示例:客服系统
java
// ─── 独立的小服务,各司其职 ───
interface GreetingExpert {
@UserMessage("Is the following text a greeting? Text: {{it}}")
boolean isGreeting(String text);
}
interface SentimentAnalyzer {
@UserMessage("Does '{{it}}' have a positive sentiment?")
boolean isPositive(String text);
}
interface PriorityAnalyzer {
@UserMessage("Analyze priority of: {{it}}")
Priority analyzePriority(String issue);
}
interface ChatBot {
@SystemMessage("You are a polite customer support agent of Miles of Smiles.")
String reply(String userMessage);
}
// ─── 编排类(纯 Java 代码,可测试) ───
class CustomerSupportOrchestrator {
private final GreetingExpert greetingExpert;
private final SentimentAnalyzer sentimentAnalyzer;
private final PriorityAnalyzer priorityAnalyzer;
private final ChatBot chatBot;
public String handle(String userMessage) {
// LLM 驱动的 if/else
if (greetingExpert.isGreeting(userMessage)) {
return "Greetings from Miles of Smiles! How can I help?";
}
// LLM 驱动的 switch
Priority priority = priorityAnalyzer.analyzePriority(userMessage);
return switch (priority) {
case CRITICAL -> escalate(userMessage);
case HIGH, LOW -> chatBot.reply(userMessage);
};
}
private String escalate(String msg) {
// 危急问题走人工通道
return "Your issue has been escalated. Agent will contact you within 1 hour.";
}
}
这种设计的好处
| 优势 | 说明 |
|---|---|
| 成本控制 | isGreeting() 可用便宜模型(如本地 Llama),复杂对话用 GPT-4 |
| 独立测试 | 每个 Service 可独立做集成测试和评估 |
| 独立演进 | 换模型、调 prompt、加护栏,不影响其他 Service |
| 确定性 + LLM 混合 | 业务逻辑用 Java 代码,语言理解用 LLM,取长补短 |
3.9 设计原理:反射代理的工作方式
当前版本,AiServices.create() 内部使用 Java 动态代理(java.lang.reflect.Proxy):
java
// 概念上等价于:
Object proxy = Proxy.newProxyInstance(
classLoader,
new Class[]{Assistant.class},
(proxy, method, args) -> {
// 1. 读取 @SystemMessage、@UserMessage 注解
// 2. 用实际参数替换模板变量
// 3. 从 ChatMemoryProvider 获取历史消息
// 4. 组装成 ChatRequest
// 5. 调用 ChatModel.chat()
// 6. 根据返回类型解析(String/POJO/Enum/Result/TokenStream)
// 7. 触发 guardrails
// 8. 返回
}
);
官方表示正在考虑其他替代实现方式(如编译期代码生成),但目前反射代理是唯一实现。