Java开发者必知:AIService让LLM调用更简单 - 教程
目录
1. 背景:为什么需要 AIService
在 LLM 进入工程化元年后,Java 开发者最常遇到的三类需求是:
快速验证“ Prompt 是否可行”,不想写冗余的 HTTP 调用与 JSON 解析;
让 LLM 记住多轮对话,并在下一次请求中保持上下文;
把 LLM 输出自动映射到业务对象,而不是手写正则去“扒”字段。
如果直接基于最底层的 ChatLanguageModel 编程,开发者需要重复完成“参数拼装—网络 IO—异常处理—结果反序列化—业务后处理”整条链路。LangChain4j 团队将这一整套样板逻辑下沉到框架内部,并暴露出声明式接口,即 AIService。它的定位等同于 Spring Data JPA:把“如何存取”做成动态代理,把“存取什么”留给业务注解。
2. 概念:AIService 是什么、不是什么
2.1 定义
AIService 是 LangChain4j 提供的高级抽象,它通过 JDK 动态代理机制,在运行时为开发者定义的接口生成实现类,将“对 LLM 的一次调用”包装成普通 Java 方法。
2.2 核心职责
输入格式化:把 Java 形参转换成 LLM 可消化的 SystemMessage + UserMessage;
输出解析:把 LLM 返回的 AiMessage 反序列化成 Java 返回值;
中间层扩展:在两次网络 IO 之间插入记忆、工具、 moderation 等切面逻辑。
2.3 与 Chain 的区别
LangChain4j 早期曾提供 ConversationalChain 等“链”式组件,但链对自定义节点顺序高度敏感,扩展点固化。AIService 采用“接口即服务”思路,把编排决策权交还给开发者,因而被官方定位为未来主推模式。
3. 架构:三层模型与关键组件
3.1 接口层
开发者只需定义一个普通 Java 接口,并通过注解声明系统提示、用户消息、记忆隔离 ID 等元数据。
3.2 代理层
DefaultAiServices 在启动阶段通过 Proxy.newProxyInstance 生成实现类;方法被调用时,由 InvocationHandler 把“Java 反射元数据”翻译成“ChatMessage 列表”,再交给下层 ChatLanguageModel。
3.3 组件层
包含 ChatLanguageModel、ChatMemory、ContentModerator、Tool 等 SPI。代理层以组合模式引用它们,实现“调用-记忆-审查-工具”一条闭环。
4. 源码:一次代理调用的完整生命周期
以下流程基于 0.35 版本 DefaultAiServices#invoke 阅读得出:
拦截接口方法,提取 @SystemMessage、@UserMessage、@MemoryId、@V 等注解;
将模板变量替换为实际参数,生成 SystemMessage 和 UserMessage;
如果配置了 ChatMemory,则按 memoryId 取出历史消息,与新消息合并;
调用 ChatLanguageModel.generate(List<ChatMessage>
若返回类型为 String,直接取 content;若为 POJO,则通过 JsonSchema 解析;
将本轮对话写回 ChatMemory,供下一次请求使用;
如果方法标注 @Moderate,则额外调用 ModerationModel 进行内容合规校验。
5. 实战:10 行代码跑通第一个 AI 接口
5.1 依赖引入
dev.langchain4j
langchain4j-open-ai
0.35
5.2 定义接口
public interface Assistant {
@SystemMessage("你是一名资深技术博主,用中文回答。")
String chat(@UserMessage String question);
}
5.3 启动与调用
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
Assistant assistant = AiServices.create(Assistant.class, model);
String answer = assistant.chat("AIService 的核心价值是什么?");
System.out.println(answer);
运行结果示例:
AIService 的核心价值在于“隐藏交互复杂度,让开发者专注业务语义”……
至此,第一行 AI 代码完成,总代码量(含 main)不超过 30 行。
6. 进阶:聊天记忆、工具调用与 RAG 编排
6.1 聊天记忆
ChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemory(memory)
.build();
相同 memory 实例在多轮调用中自动维护上下文;若需用户级隔离,可配置 ChatMemoryProvider 并结合 @MemoryId。
6.2 工具调用
class WeatherService {
@Tool("获取城市气温")
double getTemperature(@P("城市") String city) { … }
}
AiServices.builder(Assistant.class)
.tools(new WeatherService())
.build();
当用户提问“今天北京多少度”时,LLM 会生成工具调用指令,框架负责反射执行并把结果回传给模型,完成闭环决策。
6.3 RAG 编排
通过集成 EmbeddingStore 与 ContentRetriever,AIService 可在生成阶段自动检索私有知识库,把相关段落插入 SystemMessage,实现“检索增强生成”。得益于代理层的设计,业务接口无需任何改动,仅替换底层组件即可。
7. 工程化:SpringBoot 集成与多环境配置
7.1 自动配置
LangChain4j 提供 langchain4j-spring-boot-starter,在 application.yml 中声明模型参数后,可直接注入 Assistant Bean,省去 AiServices.create() 样板。
7.2 多模型切换
利用 Spring Profile 定义不同环境的 modelName、baseUrl、timeout;运行时通过 @ConditionalOnProperty 动态装配 ChatLanguageModel,实现“同一套代码,开发环境走 4o-mini,生产环境走 4o”。
7.3 可观测性
框架内置 Metrics 接口,可扩展 Micrometer;在 InvocationHandler 中埋点记录“输入 token 数/输出 token 数/方法耗时”,对接 Prometheus 后,可在 Grafana 面板实时观察 LLM 调用成本。
8. 性能与可靠性:线程池、重试与限流
线程池:ChatLanguageModel 默认同步阻塞,建议用 Supplier<CompletableFuture>
重试:OpenAiChatModel 支持 backoff 策略,可配置 maxRetries、retryDelay;
限流:LLM 按 token 计费,需在网关层或 SDK 层做 QPS + token 双维度限流,防止提示注入攻击导致账单爆炸。
9. 常见错误与排查思路
| 现象 | 根因 | 排查要点 |
|---|---|---|
| java.lang.IllegalConfiguration: moderationModel is null | 接口方法加 @Moderate 却未注入 ModerationModel | 检查 AiServices.builder().moderationModel() 是否缺失 |
| 返回内容被截断 | 超过模型 maxTokens | 显式设置 OpenAiChatModel#maxTokens,或改用流式 StreamingChatModel |
| 多轮对话“失忆” | 忘记配置 ChatMemory | 确认 AiServices.builder().chatMemory() 已绑定,且 Controller 层未每次 new 新实例 |
| 工具调用报 NoSuchMethodException | 工具方法参数未加 @P 注解 |
AIService 通过“声明式接口 + 动态代理”把 LLM 的交互复杂度下沉到框架层,让 Java 工程师能以最小成本享受生成式 AI 的能力。

浙公网安备 33010602011771号