本篇目标 :从 Spring AI 切换到 LangChain4j,用"写 Java 接口"的方式驱动大模型------一个
@AiService注解,让框架替你生成整个 AI 调用层。
前置知识:上面的 Spring AI 系列(概念全部打通,本篇随时做映射)
01 先破一个流传很广的误会
很多人一听 "LangChain4j" 就以为它是 Python LangChain 的 Java 翻译版。错。
💡 事实
LangChain4j 是从零构建的地道 Java 库:类型安全、POJO、注解、接口、流式 API------只是设计思想上借鉴了 LangChain 的"链式编排"理念。你不会看到一行 Python 味道的代码。
它对 Java 开发者的最大友好,是两级抽象:
| 抽象层 | 是什么 | 适合谁 |
|---|---|---|
| 低层原语 | ChatModel、EmbeddingStore 等,自由度拉满 |
想精细控制每次调用的细节党 |
| 高层 AI Services | 定义一个 Java 接口 + 注解,框架动态生成实现 | 想把"调 AI"写成"调本地方法"的效率党 |
低层是给 Spring AI 老手的亲切感,高层才是 LangChain4j 的招牌绝活。 本篇从低层玩到高层,最后给一张 Spring AI 概念映射表,让你无缝切换。
02 五分钟:第一声 Hello
不碰 Spring,纯 Java 先跑通------理解了裸用法,Spring 集成只是加糖。
2.1 依赖
bash
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>1.19.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!-- OpenAI 兼容接入(DeepSeek 直接用) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
</dependencies>
2.2 三行代码
bash
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.baseUrl("https://api.deepseek.com/v1") // OpenAI 兼容,一换就切厂商
.modelName("deepseek-v4-flash")
.build();
String answer = model.chat("用一句话解释什么是向量检索");
System.out.println(answer);
对,就是 model.chat(...)。没有 Client、没有 Prompt 对象、没有 Advisor------最原始的对话,一句话进一句话出。
💡 与 Spring AI 对照
ChatModel.chat("...")≈ Spring AI 的ChatClient.prompt().user("...").call().content()。LangChain4j 把最常用路径压到了极限;需要复杂参数时,再用chat(ChatRequest)完整形态。
03 大杀器:AiServices------接口一写,AI 上岗
这是整篇文章的题眼。你只需要定义一个接口:

bash
interface PetAssistant {
@SystemMessage("""
你是宠物医院的导诊助手,回答简洁专业。
涉及用药时必须提醒:最终以兽医面诊为准。
""")
String chat(String userMessage);
}
然后让框架把它变成实现:
bash
PetAssistant assistant = AiServices.builder(PetAssistant.class)
.chatModel(model)
.build();
String reply = assistant.chat("猫咪吐了怎么办?"); // ← 像调本地方法一样调 AI
没有代理类、没有实现类,框架在运行时生成动态代理,你定义的每个方法 = 一种 AI 调用模板。整套原理一张图看懂:
3.1 参数模板:@UserMessage + @V
bash
interface Translator {
@SystemMessage("你是专业翻译")
@UserMessage("把下面的文本翻译成{{lang}},只输出译文:\n{{text}}")
String translate(@V("text") String text, @V("lang") String lang);
}
@V 把方法参数注入模板占位符------方法签名即 Prompt 契约,调用方完全无感。
3.2 结构化输出:返回值直接写 record
bash
record PetSymptom(String species, String mainIssue, String urgency) {}
interface TriageService {
@UserMessage("分析这段病情描述并输出结构化症状:{{text}}")
PetSymptom triage(@V("text") String text);
}
PetSymptom symptom = triageService.triage("我家橘猫吐了 3 天,没精神");
symptom.urgency(); // "high",直接可路由可入库
方法的返回类型就是契约 ------想返回 String、record、List<PetSymptom> 甚至枚举,框架自动处理 JSON Schema 生成与反序列化。还记得 Spring AI 里 .entity(PetSymptom.class) 那一行吗?这里它变成了方法签名本身。
💡 类比
AiServices 之于大模型调用,就像 MyBatis Mapper 之于 SQL:你写接口 + 注解,框架生成实现。
@SystemMessage是@Select,返回 record 是自动结果映射------AI 调用被"Mapper 化"了。
04 会话记忆:@MemoryId 一注解,多用户各聊各的
裸的 AI Service 是无状态的。加上记忆只需要两步:
bash
interface PetAssistant {
@SystemMessage("你是宠物医院导诊助手")
String chat(@MemoryId String sessionId, @UserMessage String message);
}
PetAssistant assistant = AiServices.builder(PetAssistant.class)
.chatModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(20)) // 窗口记忆,默认在内存
.build();
assistant.chat("session-A", "我家猫吐了");
assistant.chat("session-A", "需要禁食多久?"); // 记得上一句
assistant.chat("session-B", "有什么热门科室?"); // 与 A 完全隔离
核心就一个 @MemoryId:同一个 id 共享一份记忆,不同 id 互不可见 。生产环境要持久化记忆时,换 ChatMemoryProvider 接上你的存储(Redis / DB),下篇进阶篇展开。
💡 与 Spring AI 对照
@MemoryId≈ Spring AI 2.0 的ChatMemory.CONVERSATION_ID请求参数。LangChain4j 把它做成了方法参数,类型更硬、IDE 能跳转。
05 流式输出:TokenStream 三回调
把返回类型从 String 换成 TokenStream,就拿到打字机效果:
bash
interface PetAssistant {
@SystemMessage("你是宠物医生助手")
TokenStream chat(String userMessage);
}
assistant.chat("讲讲猫咪肠炎护理")
.onPartialResponse(token -> print(token)) // 每个 token 到达时
.onCompleteResponse(resp -> save(resp)) // 完整响应(含 token 用量)
.onError(err -> log.error("失败", err))
.start(); // 触发执行
Spring 项目里更舒服 :加 langchain4j-reactor 模块,返回类型直接写 Flux<String>,SSE 接口一行串起来:
bash
@AiService
interface PetAssistant {
@SystemMessage("你是宠物医生助手")
Flux<String> chat(String userMessage);
}
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam String message) {
return assistant.chat(message);
}
06 Spring Boot 集成:@AiService 注解版
回到你的主战场。LangChain4j 官方提供 Spring Boot starter
| 你的 Spring Boot | 选哪个 starter |
|---|---|
| Boot 4.0+ | langchain4j-open-ai-spring-boot4-starter |
| Boot 3.5+ | langchain4j-open-ai-spring-boot-starter |
| 声明式 AI Services | 另加 langchain4j-spring-boot4-starter(SB4)/ langchain4j-spring-boot-starter(SB3) |
两套 starter 并行发布、版本号一致,按 Boot 大版本选对后缀即可。
配置(键前缀是 langchain4j,不是 spring.ai):
bash
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY}
base-url: https://api.deepseek.com/v1
model-name: deepseek-v4-flash
log-requests: true # 调试期打开,看真实报文
log-responses: true
6.1 自动装配模式(默认)
bash
@AiService
public interface PetAssistant {
@SystemMessage("你是宠物医院的导诊助手")
String chat(String userMessage);
}
启动时 starter 扫描所有 @AiService 接口,自动生成实现并注册为 bean,直接 @Autowired 就能用。上下文里这些组件会被自动接上 :ChatModel、ChatMemory、ContentRetriever、RetrievalAugmentor、ToolProvider,以及所有 @Component 类里的 @Tool 方法------对,工具都自动挂上了。
⚠️ 唯一的坑 :同一类型组件存在多个 bean 时(比如两个 ChatModel),启动直接失败。这时切显式装配:
bash
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "openAiChatModel") // 按 bean 名指定
interface OpenAiAssistant { ... }
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,
chatModel = "ollamaChatModel")
interface OllamaAssistant { ... }
显式模式下所有组件都必须点名,适合多模型、多知识库的复杂项目。
07 一张表:从 Spring AI 迁移到 LangChain4j
给从阶段2 过来的你,概念一一对应:
| 概念 | Spring AI 2.0 | LangChain4j 1.19 |
|---|---|---|
| 底层模型接口 | ChatModel(经 ChatClient 使用) |
ChatModel.chat(...) |
| 门面/高层封装 | ChatClient(链式 API) |
AiServices / @AiService(动态代理) |
| 系统提示 | .system("...") |
@SystemMessage |
| 用户消息模板 | PromptTemplate + .param() |
@UserMessage + @V |
| 结构化输出 | .entity(Class) |
方法返回类型直接写 record |
| 会话记忆 | MessageChatMemoryAdvisor + CONVERSATION_ID |
chatMemory(...) + @MemoryId |
| 工具调用 | @Tool + .tools() |
@Tool(@Component 自动拾取) |
| RAG | QuestionAnswerAdvisor |
ContentRetriever / RetrievalAugmentor |
| 流式 | .stream() → Flux<String> |
TokenStream / Flux<String> |
| 配置前缀 | spring.ai.* |
langchain4j.* |
| 最低 JDK | 21 | 17(存量项目友好) |
一句话总结性格差异:Spring AI 是"Spring 全家桶原生的 AI 方言",LangChain4j 是"把 AI 调用 Mapper 化的独立军"。前者生态统一,后者抽象更激进、模型/向量库覆盖更广(还支持 Quarkus / Micronaut / 纯 Java)。
08 避坑清单
| # | 坑 | 现象 | 解法 |
|---|---|---|---|
| 1 | 以为要 Java 21 | 存量 Java 17 项目不敢上 | LangChain4j 最低 JDK 17;Spring AI 2.0 才要求 21 |
| 2 | starter 后缀选错 | Boot 4 项目引了 SB3 starter,自动装配失效 | SB4 用 -spring-boot4-starter 后缀 |
| 3 | 上下文有两个 ChatModel bean |
启动报错 | @AiService(wiringMode = EXPLICIT, chatModel = "bean名") |
| 4 | DeepSeek 走 OpenAI 兼容忘了 /v1 |
404 / 鉴权怪错 | baseUrl("https://api.deepseek.com/v1") |
| 5 | 想当然找 ChatLanguageModel |
类不存在 | 1.x 已统一为 ChatModel(generate() 也改 chat()) |
| 6 | @MemoryId 忘了加 |
所有人共享同一份记忆 | 多用户场景必须显式标注 |
| 7 | 集成模块版本混乱 | BOM 管不到 beta 行为差异 | 集成模块以 1.19.0-beta29 这类号并行迭代,升级看 changelog |
| 8 | 接口方法全是 String 返回 |
结构化能力没用上 | 返回值直接写 record / List / 枚举,框架自动转换 |
09 总结:你已经把 AI 调用"Mapper 化"了
本篇打穿的能力链:ChatModel 裸调用 → AiServices 动态代理 → @SystemMessage/@UserMessage 模板 → record 结构化返回 → @MemoryId 记忆 → TokenStream/Flux 流式 → @AiService Spring 自动装配。
下一篇进阶教学,把这个"Mapper"武装成全能选手 :@Tool 工具调用、RAG 三步走(Ingestor / ContentRetriever / RetrievalAugmentor),以及 LangChain4j 压轴的新东西------Agentic 模块(AgenticScope 共享状态、Supervisor 编排、Saga 式工具补偿),看看一个 Java 库怎么把 Agent 全家桶备齐。
若对你有帮助,点赞、推荐、分享
关注我,持续更新AI Agent学习内容,早日学会Agent开发!