LangChain4j 框架使用指南
本文面向 Java 后端工程师,系统讲解 LangChain4j —— Java 版 LangChain 的核心能力、Spring Boot 集成方式与生产实践。所有示例基于 Java 17 + Spring Boot 3.x + LangChain4j 0.34.0。
一、为什么需要 LangChain4j
如果你直接用 OkHttp 调 OpenAI 接口写一个 ChatGPT,会发现要解决一堆「围绕 LLM 的工程问题」:
- 多个模型供应商(OpenAI / Azure / Ollama / 通义 / 文心)切换;
- Prompt 模板拼接、变量替换;
- 多轮对话的 Chat Memory 维护与裁剪;
- Function Calling / Tools 调用本地服务;
- RAG:文档切分 → embedding → 向量库 → 检索 → 拼接到 prompt;
- 流式输出(SSE);
- Token 用量统计、限流、重试;
- 单元测试与可观测性。
LangChain4j 把上述能力做成可组合的 Java 抽象,类似于 Python 生态的 LangChain,但更Java 风格:基于接口、Builder、Spring Boot Starter,无 Python 那一堆动态魔法。
官方文档:https://docs.langchain4j.dev
二、核心概念地图
┌────────────────────────────────────────────────────────────┐
│ AI Service │
│ 声明式接口(@SystemMessage、@UserMessage) │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ChatModel│ │ Memory │ │ Tools │ │ RAG │ │
│ └─────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ OpenAI/Ollama 消息裁剪 本地方法/HTTP Embedding+VectorStore
└────────────────────────────────────────────────────────────┘
记住四个一等公民:
| 抽象 | 角色 |
|---|---|
ChatLanguageModel / StreamingChatLanguageModel | 与 LLM 通信 |
ChatMemory | 维护多轮对话上下文 |
EmbeddingModel + EmbeddingStore | 向量化 + 向量存储(RAG 基础) |
AiServices | 把上面三者粘合到一个 Java 接口 |
三、环境准备
3.1 Maven 依赖
<properties>
<java.version>17</java.version>
<langchain4j.version>0.34.0</langchain4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
<!-- OpenAI 兼容(DeepSeek / 通义百炼 / Moonshot 都用这个) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!-- Ollama 本地模型 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
</dependency>
<!-- Spring Boot Starter(自动装配 ChatModel) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>
<!-- RAG 文档解析 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
</dependency>
<!-- 向量库:内存版,演示用 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
</dependency>
</dependencies>
3.2 application.yml
langchain4j:
open-ai:
chat-model:
base-url: https://api.openai.com/v1 # 也可填 DeepSeek / 阿里百炼地址
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o-mini
temperature: 0.7
timeout: PT60S
log-requests: true
log-responses: true
streaming-chat-model:
base-url: https://api.openai.com/v1
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o-mini
本地零成本玩:装个 Ollama,把
langchain4j-ollama-spring-boot-starter换上即可,一行 API Key 都不用。
四、第一个例子:直接调用 ChatModel
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
public class HelloLangChain4j {
public static void main(String[] args) {
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.temperature(0.7)
.build();
String answer = model.generate("用一句话解释什么是 BM25 算法");
System.out.println(answer);
}
}
带消息历史:
import dev.langchain4j.data.message.*;
List<ChatMessage> history = List.of(
SystemMessage.from("你是一名严谨的 Java 后端架构师。"),
UserMessage.from("Spring 的循环依赖是怎么解决的?只讲三级缓存。")
);
String resp = model.generate(history).content().text();
五、Prompt 模板:变量替换 + 结构化输出
5.1 PromptTemplate
import dev.langchain4j.model.input.PromptTemplate;
import java.util.Map;
PromptTemplate tpl = PromptTemplate.from(
"请把下面这段 {{lang}} 代码翻译成 {{target}}:\n```\n{{code}}\n```");
String prompt = tpl.apply(Map.of(
"lang", "Java",
"target", "Kotlin",
"code", "public int add(int a, int b){return a+b;}"
)).text();
System.out.println(model.generate(prompt));
5.2 结构化输出(直接拿对象)
LangChain4j 自动用 JSON Schema + 反射把 LLM 输出反序列化成 POJO:
record Recipe(String name, List<String> ingredients, String steps) {}
interface ChefAssistant {
@UserMessage("给我一道{{dish}}的菜谱,输出 JSON。")
Recipe recipeOf(@V("dish") String dish);
}
ChefAssistant chef = AiServices.create(ChefAssistant.class, model);
Recipe r = chef.recipeOf("番茄炒蛋");
System.out.println(r.name() + " 食材:" + r.ingredients());
底层会自动追加 output format instruction,把响应解析为 Recipe。
六、AI Services:声明式 LLM 接口(最常用)
AiServices 是 LangChain4j 的「招牌特性」。把 LLM 当成一个 Spring Bean,开箱即用:
public interface SupportAgent {
@SystemMessage("你是一个客服机器人,回答必须中文,礼貌且简短。")
String chat(@UserMessage String userMessage);
@SystemMessage("把下面的文本翻译成英文,仅输出译文。")
String translate(String chinese);
@UserMessage("分析下面这段评论的情感,返回 POSITIVE / NEGATIVE / NEUTRAL:\n{{it}}")
Sentiment analyze(String comment);
enum Sentiment { POSITIVE, NEGATIVE, NEUTRAL }
}
注册:
@Configuration
public class AiServiceConfig {
@Bean
SupportAgent supportAgent(ChatLanguageModel model) {
return AiServices.builder(SupportAgent.class)
.chatLanguageModel(model)
.build();
}
}
调用就是普通 Spring Bean:
@RestController
@RequiredArgsConstructor
public class ChatController {
private final SupportAgent agent;
@PostMapping("/chat")
public String chat(@RequestBody String msg) {
return agent.chat(msg);
}
}
七、Chat Memory:让对话有"记忆"
7.1 几种内置策略
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(20);
// 或按 token 裁剪(更精细)
ChatMemory memory2 = TokenWindowChatMemory.withMaxTokens(2000, new OpenAiTokenizer("gpt-4o-mini"));
7.2 多用户隔离(按 conversationId)
public interface ChatBot {
String chat(@MemoryId String userId, @UserMessage String message);
}
ChatBot bot = AiServices.builder(ChatBot.class)
.chatLanguageModel(model)
.chatMemoryProvider(uid -> MessageWindowChatMemory.withMaxMessages(30))
.build();
bot.chat("user-A", "我叫小王");
bot.chat("user-A", "我刚才说我叫什么?"); // -> 小王
bot.chat("user-B", "你好"); // 完全独立的会话
7.3 持久化到 Redis
把 ChatMemoryStore 接口实现一下即可:
@Component
@RequiredArgsConstructor
public class RedisChatMemoryStore implements ChatMemoryStore {
private final StringRedisTemplate redis;
private static final String KEY = "chat:memory:";
private static final Duration TTL = Duration.ofDays(7);
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String json = redis.opsForValue().get(KEY + memoryId);
return json == null ? new ArrayList<>() : ChatMessageDeserializer.messagesFromJson(json);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
redis.opsForValue().set(KEY + memoryId,
ChatMessageSerializer.messagesToJson(messages), TTL);
}
@Override
public void deleteMessages(Object memoryId) {
redis.delete(KEY + memoryId);
}
}
然后注入:
.chatMemoryProvider(uid -> MessageWindowChatMemory.builder()
.id(uid)
.maxMessages(30)
.chatMemoryStore(redisChatMemoryStore)
.build())
八、Tools / Function Calling:让 LLM 调用你的 Java 方法
public class WeatherTools {
@Tool("查询指定城市的天气,返回温度(摄氏度)和天气状况")
public String getWeather(@P("城市名,如:杭州") String city) {
// 真实实现:调和风/墨迹 API
return "{\"city\":\"" + city + "\",\"temp\":24,\"desc\":\"多云\"}";
}
@Tool("查询当前北京时间,返回 ISO-8601 字符串")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
interface SmartAssistant {
String chat(@MemoryId String uid, @UserMessage String msg);
}
SmartAssistant assistant = AiServices.builder(SmartAssistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(20))
.tools(new WeatherTools()) // 关键:注入工具
.build();
System.out.println(assistant.chat("u1", "现在杭州天气怎么样?要带伞吗?"));
执行流程:
User: 现在杭州天气怎么样?
↓ LLM 决定调用 getWeather("杭州")
Tool: {"city":"杭州","temp":24,"desc":"多云"}
↓ LLM 把工具结果用自然语言总结
Bot: 杭州目前 24°C,多云,不需要带伞。
多个工具时,LLM 会自动选择合适的方法;支持嵌套 / 并行调用。
九、RAG:检索增强生成
RAG 是企业落地最重要的一块,分为两个阶段:离线建库 与 在线检索 + 生成。
9.1 离线建库
// 1. 加载文档(支持 .txt / .md / .pdf / .docx ...)
List<Document> docs = FileSystemDocumentLoader.loadDocuments(
Path.of("./knowledge"),
new ApachePdfBoxDocumentParser()
);
// 2. 切分(按段落 / 按 token)
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50); // chunkSize=500, overlap=50
// 3. 选择 embedding 模型(OpenAI / Ollama / 本地 BGE)
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("text-embedding-3-small")
.build();
// 4. 选向量库(演示用内存版,生产换成 Milvus / Elasticsearch / PgVector / Chroma)
EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
// 5. 灌库
EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(store)
.build()
.ingest(docs);
9.2 在线检索 + 生成
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.6)
.build();
interface KnowledgeAssistant {
@SystemMessage("你是一个内部知识库助手,只能根据提供的资料回答;资料没有就说不知道。")
String ask(@UserMessage String question);
}
KnowledgeAssistant ka = AiServices.builder(KnowledgeAssistant.class)
.chatLanguageModel(model)
.contentRetriever(retriever) // 自动检索 + 拼 prompt
.chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(10))
.build();
System.out.println(ka.ask("请问公司的报销流程是什么?"));
9.3 切换到 Elasticsearch 向量库
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-elasticsearch</artifactId>
</dependency>
RestClient rc = RestClient.builder(HttpHost.create("http://localhost:9200")).build();
EmbeddingStore<TextSegment> esStore = ElasticsearchEmbeddingStore.builder()
.restClient(rc)
.indexName("kb_docs")
.dimension(1536)
.build();
切换零成本,业务代码不用改。
9.4 高级 RAG:多路召回 + 重排
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.query.router.DefaultQueryRouter;
import dev.langchain4j.rag.content.aggregator.ReRankingContentAggregator;
ContentRetriever vector = EmbeddingStoreContentRetriever.builder()...build();
ContentRetriever bm25 = new BM25ContentRetriever(...); // 自己实现,调 ES BM25
RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(new DefaultQueryRouter(vector, bm25)) // 双路并发召回
.contentAggregator(new ReRankingContentAggregator(rerankModel)) // 交叉编码器重排
.build();
KnowledgeAssistant ka = AiServices.builder(KnowledgeAssistant.class)
.chatLanguageModel(model)
.retrievalAugmentor(augmentor)
.build();
十、流式输出(SSE)
后端:
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.service.TokenStream;
interface StreamingAssistant {
TokenStream chat(@UserMessage String msg);
}
StreamingAssistant agent = AiServices.builder(StreamingAssistant.class)
.streamingChatLanguageModel(streamingModel)
.build();
@GetMapping(value = "/sse/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chat(@RequestParam String q) {
SseEmitter emitter = new SseEmitter(0L);
agent.chat(q)
.onNext(token -> safeSend(emitter, token))
.onComplete(resp -> emitter.complete())
.onError(emitter::completeWithError)
.start();
return emitter;
}
private void safeSend(SseEmitter e, String data) {
try { e.send(SseEmitter.event().data(data)); } catch (IOException ex) { e.completeWithError(ex); }
}
前端就是普通 EventSource,与你写过的 ChatGPT 流式 Demo 完全一样。
十一、可观测性:日志、Token、重试
11.1 请求/响应日志
OpenAiChatModel.builder()
.apiKey(...)
.logRequests(true)
.logResponses(true)
.build();
或者 application.yml 中:
langchain4j:
open-ai:
chat-model:
log-requests: true
log-responses: true
11.2 Token 用量
Response<AiMessage> resp = model.generate(messages);
TokenUsage usage = resp.tokenUsage();
log.info("input={}, output={}, total={}", usage.inputTokenCount(),
usage.outputTokenCount(), usage.totalTokenCount());
11.3 重试
OpenAiChatModel 自带 maxRetries(int) 配置;底层基于指数退避。
.maxRetries(3)
.timeout(Duration.ofSeconds(60))
更复杂的可以包装 Resilience4j:
Retry retry = Retry.of("llm", RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofSeconds(2))
.retryExceptions(IOException.class)
.build());
十二、实战示例:企业知识库问答系统
把前面所有能力串起来做一个最小可用版:
@Configuration
@RequiredArgsConstructor
public class KbConfig {
private final ChatLanguageModel chatModel;
private final EmbeddingModel embeddingModel;
private final RedisChatMemoryStore redisStore;
@Bean
public EmbeddingStore<TextSegment> store() {
// 生产换成 ES / Milvus
return new InMemoryEmbeddingStore<>();
}
@Bean
public ContentRetriever retriever(EmbeddingStore<TextSegment> store) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.6)
.build();
}
@Bean
public KbAssistant kbAssistant(ContentRetriever retriever) {
return AiServices.builder(KbAssistant.class)
.chatLanguageModel(chatModel)
.contentRetriever(retriever)
.tools(new TicketTools()) // 提交工单
.chatMemoryProvider(uid -> MessageWindowChatMemory.builder()
.id(uid)
.maxMessages(30)
.chatMemoryStore(redisStore)
.build())
.build();
}
}
public interface KbAssistant {
@SystemMessage("""
你是「公司内部知识库助手」。
规则:
1. 优先用提供的检索资料回答;
2. 资料不足以回答时,调用 createTicket 创建工单;
3. 回答必须中文、引用资料编号,例如 [来源 1]。
""")
String ask(@MemoryId String userId, @UserMessage String question);
}
public class TicketTools {
@Tool("当知识库找不到答案时,创建一个客服工单,返回工单号")
public String createTicket(@P("用户ID") String userId,
@P("问题描述") String question) {
// 真正落库
return "TKT-" + System.currentTimeMillis();
}
}
Controller:
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/kb")
public class KbController {
private final KbAssistant assistant;
private final EmbeddingStoreIngestor ingestor;
@PostMapping("/upload")
public String upload(@RequestPart("file") MultipartFile file) throws IOException {
Path tmp = Files.createTempFile("kb-", "-" + file.getOriginalFilename());
file.transferTo(tmp);
Document doc = FileSystemDocumentLoader.loadDocument(tmp);
ingestor.ingest(doc);
return "ok";
}
@PostMapping("/ask")
public Map<String, String> ask(@RequestParam String uid, @RequestBody String q) {
return Map.of("answer", assistant.ask(uid, q));
}
}
5 分钟你就有了一个支持上传 PDF、多用户记忆、可创建工单的内部 ChatGPT。
十三、生产化清单(强烈建议照着做)
| 维度 | 推荐做法 |
|---|---|
| 模型 | 至少配置主备两家供应商(如 OpenAI + 通义),通过 @Primary + 故障切换 |
| Embedding | 中文优先 BGE / m3e,维度与向量库 mapping 必须一致 |
| 向量库 | <1M 条用 PgVector;中等规模用 Elasticsearch;高 QPS 用 Milvus |
| Memory | 单测/演示用 InMemory;生产用 Redis,注意按 token 裁剪 |
| Prompt | 抽到独立 .st / .txt 文件,配合版本管理 |
| Token 控制 | 用 TokenWindowChatMemory + 系统消息精简 |
| 限流 | 基于用户 ID 维度做漏桶(Redis Lua),保护账单 |
| 审计 | 每次请求落库:用户、prompt、response、token、latency |
| 安全 | 用户输入做 prompt-injection 过滤;工具调用做白名单 |
| 测试 | LangChain4j 提供 OpenAiChatModelMock;评估用 Ragas / TruLens |
十四、常见坑
- 维度不一致:换 embedding 模型必须重建索引;常见错误
dimensions does not match。 - JSON 解析失败:模型偶尔不按格式返回;建议加
responseFormat = JSON或JsonSchema严格模式。 - 工具描述模糊:
@Tool描述写得越精确,LLM 越容易选对工具,参数解析也越准。 - Memory 无限增长:一定要设上限(
maxMessages或maxTokens),否则费用爆炸。 - 流式异常没关闭 SSE:必须在
onError里completeWithError,否则连接泄漏。 - 大文件 RAG 切片过粗:默认
recursive(500, 50)适合大多数场景;代码 / 表格类文档需要自定义 splitter。 - 国内模型适配:通义、DeepSeek、Moonshot 都兼容 OpenAI 协议,把
base-url换掉即可。
十五、参考资源
- 官方文档:https://docs.langchain4j.dev
- GitHub:https://github.com/langchain4j/langchain4j
- 示例工程:https://github.com/langchain4j/langchain4j-examples
- Spring AI 对比:Spring AI 抽象更轻,LangChain4j 功能更全(RAG、Agent、Memory 都内置),生产推荐 LangChain4j。
总结
LangChain4j 把「跟 LLM 打交道的工程脚手架」做完了:
ChatLanguageModel屏蔽厂商差异;AiServices让你像写普通 Java 接口一样写 LLM 应用;ChatMemory+Tools+RAG三大件覆盖 90% 业务场景;- Spring Boot Starter 让接入成本接近零。
给 Java 后端开发的建议路径:先用 AiServices + Tools 做一个客服机器人 → 加上 RAG 接入企业文档 → 引入 Streaming + Memory → 做多路召回与重排 → 接入可观测性,就是一个能上线的生产级 LLM 应用。
祝你写出第一个让产品同学惊呼"这居然是 Java 写的?"的 AI 应用 🚀。
1万+

被折叠的 条评论
为什么被折叠?



