你不知道的LangChain4j 那些事
这是一篇风格化很重的LangChain4j实践教程。我不想给你看一个标准的技术文档模板,我想给你看一个真实的、会踩坑、会骂街、最终又能解决问题的Java开发者的实战记录。
LangChain4j,顾名思义,是LangChain的Java移植版。它的目标很明确:让Java开发者也能像Python开发者一样丝滑地构建大模型应用。目前它已经支持接入OpenAI、Azure、Ollama等超过20家模型提供商,还有内存管理、RAG、函数调用等一整套工具链。
我会从搭建环境开始,中间穿插我踩过的坑和学到的教训,然后重点讲RAG和Tool Calling这两个核心功能,最后聊几句LangChain4j和Spring AI到底怎么选。
1. 环境准备:先别急着写代码,选对版本很重要
LangChain4j的版本迭代挺快的,写这篇文章时最新稳定版是1.0.0-beta1。注意这个beta后缀——很多功能还在打磨,你用的版本可能和我用的不太一样。
如果你是Spring Boot项目,Maven里加上这些:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0.0-beta1</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.0.0-beta1</version>
</dependency>
如果你是纯Java项目不用Spring,也可以,只是多写几行配置代码的事。
API Key别硬编码到代码里,这是血的教训。用环境变量或者配置文件管理。
2. 别用Low-Level API,AI Services才是正解
刚上手时我犯过一个错误:直接用ChatLanguageModel来调大模型。代码大概长这样:
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-3.5-turbo")
.build();
String response = model.generate("Hello, world!");
能跑,但问题很快就来了:每次都要手动处理对话历史,想加个RAG还得自己拼Prompt,代码越写越乱。后来才发现LangChain4j的设计精髓根本不在这里,而是在AI Services——你只需要定义一个接口,框架帮你生成实现。
public interface Assistant {
String chat(String message);
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
这叫什么?这叫“声明式AI开发”。你声明你想要什么,框架帮你搞定怎么做到。
3. 第一个坑:对话记忆存MySQL,我踩过的雷
说实话,LangChain4j的官方文档现在确实还不够完善。有个开发者朋友在掘金上吐槽说,他想把对话记录存到MySQL,结果从下午一直折腾到晚上11点半。
问题出在哪?他把模型从阿里百炼换成OpenAI后,所有问题竟然都消失了。这说明什么?LangChain4j对不同模型提供商的适配程度是不一样的,有些SDK还藏着bug。如果你也遇到莫名其妙的持久化问题,不妨先换个模型试试,先确认是框架问题还是模型适配问题。
如果要用ChatMemory持久化到数据库,建议用框架官方支持的ChatMemoryStore实现,别自己瞎折腾。目前比较靠谱的方案是用MongoDB或Redis存对话记录。
4. RAG:从“极简版”到“实战版”的进化
RAG(检索增强生成)是LangChain4j最亮眼的功能之一。说白了就是给大模型配一个“小抄本”,让它回答前先查一下你的私有知识库。
4.1 极简版:三分钟跑通RAG
先跑通再说,别一上来就追求完美。引入langchain4j-easy-rag依赖:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.0.0-beta1</version>
</dependency>
然后几行代码就能跑起来:
// 1. 加载文档
List<Document> documents = FileSystemDocumentLoader
.loadDocuments("src/main/resources/docs");
// 2. 转换成向量并存到内存
EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
// 3. 构建AI Service并绑定检索器
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
.build();
极简版的特点是“一切皆默认”——内置Embedding模型、内存向量存储、默认分块策略。很适合快速验证想法。
4.2 实战版:换掉默认,优化效果
极简版跑通后,你会发现几个问题:内存存储重启就没了,默认的Embedding模型效果一般,文档分块策略太粗暴。
向量数据库的选择
生产环境别用内存存储。我推荐两个方向:
- PgVector:如果你的项目已经在用PostgreSQL,这是最省事的选择,直接用现有数据库存向量。
- MongoDB Atlas:如果你需要的是开箱即用的云服务,MongoDB的Vector Search功能很成熟,而且和LangChain4j有官方集成模块。
以PgVector为例,配置大概这样:
EmbeddingStore<TextSegment> embeddingStore = PgVectorEmbeddingStore.builder()
.host("localhost")
.port(5432)
.database("rag_db")
.user("postgres")
.password("password")
.table("embeddings")
.dimension(1536) // OpenAI embedding的维度
.build();
Embedding模型的选择
默认的Embedding模型效果一般。生产环境建议换成OpenAI的text-embedding-3-small或Cohere的embed模型,中文场景可以试试阿里的通义千问Embedding。
5. Tool Calling:让AI学会调用你的Java方法
如果说RAG是给AI配了本参考书,那Tool Calling就是给了AI一双能干活的手。当用户问“今天天气怎么样”时,AI知道自己答不上来,但可以调用你写的getWeather工具来获取实时数据。
5.1 基础用法
用@Tool注解标记你想让AI调用的方法:
public class WeatherTools {
@Tool("获取指定城市的当前天气")
public String getWeather(@P("城市名称") String city) {
// 这里调用真实的天气API
return city + "今天晴朗,温度22°C";
}
@Tool("计算两个数的和")
public double sum(@P("第一个数") double a, @P("第二个数") double b) {
return a + b;
}
}
然后在构建AI Service时注册这些工具:
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.tools(new WeatherTools())
.build();
现在你可以问AI“北京天气怎么样”或者“帮我算一下156加243”,它会自动判断需要调用哪个工具。
5.2 第二个坑:工具调用无限循环
我遇到过一个问题:AI在一个对话里反复调用同一个工具,token烧得飞起。有开发者说他看着AI对话转了半个小时,花了将近100万token。
原因是什么?记忆窗口太小了。LangChain4j默认只保留最近几条对话记录,当工具调用次数多了之后,AI会“忘记”自己刚才已经调用过这个工具了,于是重复调用。
解决方案也很简单:调大MessageWindowChatMemory的容量:
MessageWindowChatMemory chatMemory = MessageWindowChatMemory
.withMaxMessages(35); // 默认是10,调到35
这个值需要根据你的实际场景调整,太小容易循环,太大token消耗多。
5.3 多个工具怎么让AI选对?
如果你的工具有很多,关键是把@Tool注解里的描述写清楚。AI是根据你的描述来判断该用哪个工具的。描述要写清楚“这个工具是干什么的”、“什么时候应该用”。写得好不好,直接影响AI选工具的准确率。
6. 流式输出:别把用户晾在那干等
LangChain4j的流式输出确实比Spring AI麻烦一些。Spring AI里call是全量,stream是流式,很直觉。但LangChain4j需要单独用StreamingChatLanguageModel,还要加langchain4j-reactor依赖。
如果用AI Services方式实现流式,代码是这样的:
public interface StreamingAssistant {
TokenStream chat(String message);
}
StreamingAssistant assistant = AiServices.builder(StreamingAssistant.class)
.streamingChatLanguageModel(streamingModel)
.chatMemory(chatMemory)
.build();
TokenStream tokenStream = assistant.chat("讲个笑话");
tokenStream.onNext(token -> System.out.print(token))
.onComplete(response -> System.out.println("\n完成"))
.onError(Throwable::printStackTrace)
.start();
返回类型用TokenStream,然后通过onNext逐个处理token。
如果你用Spring Boot + WebFlux,可以直接把TokenStream转成Flux<String>,然后通过Server-Sent Events推给前端,实现ChatGPT那种逐字输出的效果。
7. 本地模型部署:Ollama + DeepSeek
不想每次都调OpenAI的付费API?Ollama是你最好的朋友。
Ollama是一个本地大模型运行平台,支持CPU和GPU,能跑DeepSeek、Llama、Mistral等各种开源模型。
安装和运行很简单:
# 安装Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 拉取DeepSeek-R1模型(14b参数,本地可跑)
ollama pull deepseek-r1:14b
# 启动服务
ollama serve
然后在LangChain4j里这样配置:
ChatLanguageModel model = OllamaChatModel.builder()
.baseUrl("http://localhost:11434")
.modelName("deepseek-r1:14b")
.build();
这样就完全本地化了,不用API Key,不用联网,数据安全也更有保障。
8. LangChain4j vs Spring AI,我到底该怎么选?
这个问题我被问过很多次。简单说:
LangChain4j适合你,如果:
- 你需要丰富的功能(RAG、Agent、工具调用都很成熟)
- 你不一定要用Spring生态(纯Java、Quarkus都可以)
- 你需要兼容多种模型提供商
Spring AI适合你,如果:
- 你已经在用Spring Boot/Cloud全家桶
- 你追求和Spring生态的无缝集成(依赖注入、配置管理、可观测性)
- 你能接受功能相对较少但更稳定的状态
我的个人选择:如果是Spring项目,我会用LangChain4j做核心AI逻辑,因为它的功能确实更全;配置文件管理、依赖注入这些基础设施就交给Spring。两者不冲突,可以一起用。
写在最后
LangChain4j现在确实还有很多不完美的地方:官方文档不够详细,某些功能还带着beta标签,不同模型提供商的适配程度参差不齐。但它在快速迭代,而且社区活跃度很高。
如果你是一个想用Java做大模型应用的开发者,LangChain4j是目前最好的选择之一。不要被它的不完美吓到——Python的LangChain一开始也是这样的。技术都是在踩坑中成长的,LangChain4j在成长,我们也是。
希望这篇文章能帮你少踩几个坑。如果你在实践中遇到什么问题,欢迎留言交流。
更多推荐


所有评论(0)