在实际 Java 项目中,想要接入大模型能力,很多人第一反应是直接调 HTTP API,但请求格式、结果解析、上下文管理、向量检索这些逻辑一旦多起来,代码会迅速变得混乱。LangChain4j 的价值不是简单封装一次 OpenAI 或通义千问的接口,而是把大模型应用里常见的 RAG、工具调用、多轮记忆和智能体流程,抽象成一组可以和 Spring Boot 一起工作的组件。这篇文章会从零搭一个最小项目,先跑通 RAG 知识库问答,再扩展出带工具调用的智能体,最后把依赖版本、运行时参数和排错路径一起梳理清楚。
整篇文章适合已经会用 Spring Boot 写 Web 接口,但还没有系统接触过大模型应用开发的读者。读完以后,你能理解 LangChain4j 的核心抽象,能在一个 Spring Boot 项目里完成文档加载、向量化、检索、问答和工具调用,也能在面对“检索不到”“答非所问”“依赖冲突”这类问题时,知道从哪一层开始排查。
1. 先理解 LangChain4j 把大模型应用拆成了哪些部件
LangChain4j 是面向 Java 生态的大模型应用开发框架。它参考了 Python 生态里 LangChain 的组件思想,但实现上更贴近 Java 开发者的习惯。它解决的并不是“调用大模型”这一个动作,而是把完整应用拆成模型、提示词模板、记忆、向量存储、检索器、工具和执行流程几层。
1.1 从一次普通 API 调用到组件化开发
直接用 HTTP 调用大模型时,代码通常是这样:
java复制String apiKey = "sk-xxx";
String prompt = "请解释什么是 RAG";
// 拼接请求体、设置超时、解析 JSON 响应
这种写法在单个调用里没问题,一旦你要做知识库问答、多轮对话、联网检索、调用内部接口,就会遇到几个重复问题:上下文要手动拼接、记忆要自己存、每个模型厂商的请求格式和响应结构都不一样、工具调用协议更是各有差异。LangChain4j 把这些重复劳动收敛到统一接口后面。
框架主要提供了四类能力:
- 模型接入统一化:不同厂商的大模型通过
ChatLanguageModel、EmbeddingModel等接口暴露给上层业务。 - 提示词工程化:
PromptTemplate负责把变量填充到模板里,而不是手工拼字符串。 - 记忆管理:
ChatMemory保存历史消息,MessageWindowChatMemory控制保留窗口。 - 知识和工具编排:
ContentRetriever负责检索知识库,Tool注解把 Java 方法暴露给模型调用。
1.2 核心抽象与一次请求的执行链路
在 LangChain4j 里,一次带 RAG 的问答请求会经过这样一条链路:
- 用户输入进入
AiServices。 - 框架调用
ContentRetriever从向量库检索相关片段。 - 检索结果通过提示词模板注入系统消息或用户消息。
- 组装完整消息列表后交给
ChatLanguageModel。 - 模型返回文本,或者请求调用某个
@Tool方法。 - 如果需要工具调用,框架把工具结果拼回消息,继续让模型生成下一步回复。
text复制用户输入 -> 检索器(知识库) -> 消息组装 -> 大模型 -> 文本回复
-> 工具调用 -> 工具结果 -> 大模型
这个链路就是 LangChain4j 应用的基本执行框架。理解它之后,所有配置和代码都只是在替换链路中的某一环。
1.3 与 Python 生态和平台类产品的关系
热词里经常出现“rag框架”“智能体框架”“dify智能体平台”“coze智能体”,还有“平台搭建的智能体与用 python 搭建的智能体有什么不同”。简单区分一下:Dify、Coze 这类平台提供可视化编排和托管环境,你不需要写太多代码;LangChain4j、LangChain、LlamaIndex 这类框架则强调在自有代码里控制每一步逻辑。平台适合快速验证和运营侧搭建,框架适合需要深度定制、和内部系统紧密集成的工程化项目。
如果团队本来就是 Java 技术栈,接入 LangChain4j 可以避免引入 Python 服务导致的技术栈割裂。但也要知道,LangChain4j 的生态成熟度仍在快速变化中,版本升级时接口变动比较频繁,落地前要锁定版本并仔细阅读升级说明。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目骨架搭建
这一节以 Spring Boot 3 配合 Maven 为例。实际项目里版本组合很多,下面给出的是一套可以跑通的组合,落地时请以官方仓库和 Maven 中央仓库的最新稳定版为准。
2.1 基础环境要求
| 组件 | 要求 | 说明 |
|---|---|---|
| JDK | 17 及以上 | Spring Boot 3 强制要求 |
| Maven | 3.8 及以上 | 管理依赖 |
| Spring Boot | 3.2.x 或 3.3.x 示例 | 与 LangChain4j Spring Boot Starter 配合 |
| 大模型 API | 兼容 OpenAI 协议的模型或通义千问 | 需要在环境变量中配置密钥 |
| 向量库 | 开发环境可用内存版,生产选 PostgreSQL、Milvus 等 | 示例使用内存 EmbeddingStore |
2.2 Maven 依赖和版本对齐
核心依赖是 langchain4j-spring-boot-starter,它会把 langchain4j-core、模型适配器、Spring Boot 自动配置一起带进来。同时需要按模型厂商引入对应依赖。
xml复制<properties>
<java.version>17</java.version>
<langchain4j.version>1.1.0</langchain4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>${langchain4j.version}</version>
</dependency>
</dependencies>
这里要特别提醒版本问题。LangChain4j 的版本迭代里,AiServices 的构造方式、ContentRetriever 的返回类型、Spring Boot Starter 的自动配置项都可能变化。网上很多文章用的是 0.x 版本,代码和 1.x 完全不兼容。如果你遇到接口找不到、方法签名对不上,先检查依赖版本,不要急着怀疑业务代码。
2.3 application.yml 的基础配置
把 API 密钥放进环境变量,不要在配置文件里明文提交到仓库。
yaml复制langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4o-mini
temperature: 0.2
embedding-model:
api-key: ${OPENAI_API_KEY}
model-name: text-embedding-3-small
spring:
application:
name: langchain4j-demo
如果使用的是兼容 OpenAI 协议的其他模型服务,可以调整 base-url 配置指向自己的网关地址。密钥配置错误时,最常见的现象是启动时没有报错,但第一个请求就出现 401 或 403。
2.4 项目目录结构
text复制src/main/java/com/example/langchain4jdemo/
├── Langchain4jDemoApplication.java
├── config/
│ └── RagConfig.java
├── controller/
│ ├── ChatController.java
│ └── KnowledgeController.java
├── service/
│ ├── RagChatService.java
│ └── AgentChatService.java
├── tool/
│ └── OrderQueryTool.java
└── vector/
└── InMemoryVectorStore.java
src/main/resources/
├── application.yml
└── docs/
└── product-manual.md
学习阶段保持这个结构就够用。生产环境通常要把向量库、模型配置、文档处理逻辑进一步拆分。
3. 用最小 RAG 流程跑通知识库问答
RAG 的中文叫检索增强生成。它的核心思路是不把全部知识塞进模型参数,而是在每次回答前先从外部知识库检索相关内容,再把检索到的片段作为上下文交给模型生成答案。这样做的好处是知识更新成本低、来源可追溯,也能减少模型编造不存在的细节。
3.1 RAG 的四个步骤
一个最小 RAG 流程包括:
- 加载文档:读取 PDF、TXT、Markdown 等格式的文本。
- 分块:把长文档切成适合向量化的片段。
- 向量化存储:用 Embedding 模型把文本转成向量,存进向量库。
- 检索增强问答:用户提问时,把问题向量化,检索最相似的片段,拼进提示词后请求模型。
java复制@Configuration
public class RagConfig {
@Bean
public EmbeddingStore<TextSegment> embeddingStore(
EmbeddingModel embeddingModel,
DocumentSplitter splitter) {
// 从 resources/docs 加载文档
List<Document> documents = loadDocuments();
List<TextSegment> segments = splitter.splitAll(documents);
EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
List<Embedding> embeddings = embeddingModel.embedAll(segments)
.content();
store.addAll(embeddings, segments);
return store;
}
}
InMemoryEmbeddingStore 只在进程内存里保存向量,应用重启后知识库就会清空。学习阶段这完全够用,生产环境必须换成持久化向量库。
3.2 用 Easy RAG 快速构建检索器
langchain4j-easy-rag 提供了一个非常省事的组件 EasyRag。它把文档加载、分块、向量化、存储和检索都封装起来了,适合先用最小代码验证链路。
java复制@Service
public class RagChatService {
private final ChatLanguageModel chatLanguageModel;
private final EasyRag easyRag;
public RagChatService(ChatLanguageModel chatLanguageModel, EasyRag easyRag) {
this.chatLanguageModel = chatLanguageModel;
this.easyRag = easyRag;
}
public String chat(String userMessage) {
return easyRag.chat(chatLanguageModel, userMessage, "请基于给定资料回答");
}
}
调用 easyRag.chat 时,框架会自动完成检索和上下文注入。第一次调用时如果知识库里还没有内容,返回结果可能完全来自模型自身知识,这就是后面要重点排查的“RAG 没生效”问题。
3.3 使用 AiServices 组合自定义 RAG
EasyRag 适合验证,真正到业务里通常需要自己控制检索器和提示词。推荐用 AiServices:
java复制@Configuration
public class RagConfig {
@Bean
public ContentRetriever contentRetriever(EmbeddingStore<TextSegment> store,
EmbeddingModel embeddingModel) {
return new EmbeddingStoreContentRetriever(store, embeddingModel, 3);
}
@Bean
public RagAssistant ragAssistant(ChatLanguageModel chatLanguageModel,
ContentRetriever contentRetriever) {
return AiServices.builder(RagAssistant.class)
.chatLanguageModel(chatLanguageModel)
.contentRetriever(contentRetriever)
.build();
}
}
定义接口:
java复制public interface RagAssistant {
String chat(String userMessage);
}
使用时注入 RagAssistant,直接调用 chat 方法。这里接口就是服务的门面,LangChain4j 会为接口生成代理对象。
3.4 从文件加载知识库并分块
加载本地文件时,要注意编码和分隔符。Markdown 文件最好按标题切,普通文本按固定窗口长度切:
java复制private List<Document> loadDocuments() {
Document document = FileSystemDocumentLoader
.loadDocument("src/main/resources/docs/product-manual.md");
return List.of(document);
}
private DocumentSplitter splitter() {
return DocumentSplitters.recursive(500, 50);
}
分块参数直接影响检索效果。块太大,向量里包含太多无关信息,检索精度下降;块太小,语义不完整,模型拿到的上下文零碎。比较常用的起始值是 500 个 token、重叠 50 个 token,再根据实际文档情况调整。
3.5 完整问答接口
java复制@RestController
@RequestMapping("/api/rag")
public class RagController {
private final RagChatService ragChatService;
public RagController(RagChatService ragChatService) {
this.ragChatService = ragChatService;
}
@PostMapping("/chat")
public ChatResponse chat(@RequestBody ChatRequest request) {
String answer = ragChatService.chat(request.message());
return new ChatResponse(answer);
}
}
请求体:
json复制{
"message": "产品支持哪些支付方式?"
}
响应体:
json复制{
"answer": "根据产品手册,当前版本支持微信支付、支付宝和银联在线支付。"
}
这里能明显看到 RAG 的效果:回答是基于外部文档内容生成的,而不是模型背诵的通用知识。
4. 从 RAG 扩展到智能体:添加记忆和工具调用
RAG 解决的是“模型没有的知识从哪来”的问题,智能体解决的是“模型只能生成文本,不能真正操作业务系统”的问题。两者的结合非常自然:知识库负责提供背景信息,工具负责执行动作。
4.1 智能体在 Java 里的本质
LangChain4j 的智能体并没有那么神秘。它由三个机制组成:
- 模型能够决定是否需要调用工具。
- 框架在模型和 Java 方法之间做协议转换。
- 系统维护多轮消息,保证工具调用过程中上下文不丢。
4.2 用 @Tool 把业务方法暴露给模型
定义一个订单查询工具。真实项目中这个方法一定连数据库或内部接口,这里用 Map 模拟:
java复制@Component
public class OrderQueryTool {
@Tool("根据订单号查询订单当前状态")
public String getOrderStatus(@ToolParam("订单号") String orderNo) {
Map<String, String> orders = Map.of(
"A1001", "已发货",
"A1002", "待支付",
"A1003", "已取消"
);
return orders.getOrDefault(orderNo, "未查询到该订单");
}
}
@Tool 注解有两个作用:一是把方法名和描述交给模型,让模型知道什么场景下该调用它;二是 @ToolParam 告诉模型调用时需要传什么参数。方法描述写清楚很重要,描述越模糊,模型误调用的概率越高。
4.3 通过 AiServices 构建带工具和记忆的智能体
java复制public interface Assistant {
String chat(String userMessage);
}
@Configuration
public class AgentConfig {
@Bean
public Assistant assistant(ChatLanguageModel chatLanguageModel,
OrderQueryTool orderQueryTool) {
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(20)
.build();
return AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.chatMemory(chatMemory)
.tools(orderQueryTool)
.build();
}
}
使用多轮对话时,用户说“帮我查一下 A1001”,模型会调用工具,结构化返回“已发货”。用户接着说“这个订单什么时候能送到”,模型能根据记忆里的订单号判断,不需要用户重复提供。
这种记忆能力由 MessageWindowChatMemory 提供。它只保留最近 20 条消息,一方面节省 token,另一方面避免上下文过长后模型注意力分散。生产系统如果对话非常多、需要跨会话保存记忆,就要把消息持久化到 Redis 或数据库,并用 ChatMemoryProvider 按会话 ID 管理。
4.4 同时使用 RAG 和工具的完整智能体
把检索器和工具放到同一个 AiServices:
java复制return AiServices.builder(Assistant.class)
.chatLanguageModel(chatLanguageModel)
.chatMemory(chatMemory)
.contentRetriever(contentRetriever)
.tools(orderQueryTool, refundTool)
.build();
这样一个 Assistant 就同时具备三种能力:常识问答靠模型参数,专业知识靠知识库检索,业务操作靠工具调用。用户问“根据产品手册,退款需要什么条件”,框架会先检索知识库再回答;用户问“A1002 可以退款吗”,框架会先查询订单状态,再结合知识库规则回答。
4.5 智能体的自主容错控制
热词里有一句“识的llm智能体自主容错控制:构建可靠ai系统的工程实践”,这说明在实际工程中,智能体不能只关注“能不能调用工具”,还要关注“调用错了怎么办”。
在 LangChain4j 里落地容错控制时,需要处理几个问题:
- 工具调用失败时,不能直接让整个对话中断,要捕获异常并生成一条工具结果消息返回给模型,让模型重新决策。
- 工具参数缺失或格式错误时,要返回明确的错误信息,而不是抛异常。
- 增加“确认再执行”机制。对退款、删除、修改类操作,让模型先输出待执行动作,由人工或业务规则确认后真正执行。
工具方法内部做防御式逻辑:
java复制@Tool("根据订单号执行退款,返回退款结果")
public String refund(@ToolParam("订单号") String orderNo) {
try {
// 调用退款服务
return "订单 " + orderNo + " 退款已受理";
} catch (Exception e) {
// 返回给模型,而不是抛出给调用方
return "退款失败,原因:" + e.getMessage();
}
}
这里的关键判断是:工具异常返回给模型,业务异常直接抛出给上层。模型可以根据失败原因调整策略,比如告诉用户“当前余额不足,暂时无法退款”,而系统级异常则要进入日志和监控。
5. RAG 的瓶颈、知识库选型与向量库参数
热词里大量出现“rag瓶颈”“kg知识库、rag知识库和结构知识库区分以及应用场景”“ontology rag”。这些词集中反映了一个问题:RAG 并不是简单接一个向量库就能解决所有问答场景,它在现实项目中会遇到很多边界问题。
5.1 RAG 常见的四类瓶颈
第一类是检索质量瓶颈。向量检索是相似度匹配,不是语义理解。两句话意思相近但表述差异大时,可能检索不到;关键词重合但语义无关时,可能检索出噪声片段。提高检索质量不能只靠换一个 Embedding 模型,还要调整分块策略、重排序、混合检索。
第二类是知识组织瓶颈。一份几百页的说明文档,如果直接全部切块入库,每个片段上下文不完整,检索结果经常是零散信息。更好的做法是按章节、表格、段落结构切分,甚至用标题层级给片段补充上下文信息。
第三类是更新一致性瓶颈。文档更新后,向量库里的旧向量不会自动删除,需要建立文档与向量的映射关系,实现增量更新或重建索引。
第四类是“模型只看片段不读全文”的瓶颈。RAG 默认只把 top-k 片段交给模型,如果答案分散在多个章节里,就回答不全。这时需要考虑递归检索、多轮检索或引入知识图谱。
5.2 向量库与 GraphRAG 的选型场景
| 方案 | 原理 | 适合场景 | 不适合场景 |
|---|---|---|---|
| 纯向量 RAG | 文本片段向量化,语义相似度检索 | 说明书、FAQ、政策条文等自由文本 | 需要精确关系推理、多跳查询的场景 |
| 结构化知识库(SQL/图数据库) | 预先建模实体和关系,精确查询 | 订单、商品属性、权限、设备关系 | 非结构化文本、语义模糊问题 |
| GraphRAG | 先抽取实体关系,再配合向量和图谱检索 | 多跳问答、实体关联分析 | 图谱构建成本高,更新复杂 |
| KG 与 RAG 混合 | 向量召回候选片段,图谱做关系扩展和验证 | 企业内部复杂知识库 | 中小规模文本库中收益不明显 |
热词里“rag知识库和结构知识库区分以及应用场景”对应的就是这个选型问题。在企业落地时,不要迷信某一种方案。常见做法是:把结构化数据放进数据库,把非结构化文档放进向量库,再在服务层做路由——用户问“订单金额多少”走数据库,问“退款政策是什么”走向量库。
5.3 向量库关键参数
以 EmbeddingStoreContentRetriever 为例,核心参数是检索数量 maxResults。
| 参数 | 含义 | 常见值 | 调大效果 | 调小效果 |
|---|---|---|---|---|
| maxResults | 返回给模型的片段数 | 3 到 5 | 信息更全,token 消耗增加,噪声可能增加 | 回答更聚焦,可能漏掉答案 |
| minScore | 相似度阈值 | 0.5 到 0.7 | 过滤低相关结果 | 可能过滤掉有效结果 |
| 分块大小 | 每个片段的 token 上限 | 300 到 800 | 上下文更完整,检索粒度变粗 | 片段零散,检索精度下降 |
错误配置的典型表现:maxResults=1 时答案经常不完整;minScore=0.9 时大量正常提问检索不到内容;分块 2000 token 时,模型经常被无关信息干扰。调试时建议先固定检索数量,再调整相似度阈值,最后再调分块策略。
5.4 在生产环境中如何选择向量库
学习环境用 InMemoryEmbeddingStore,重启即清空。生产环境至少需要持久化、备份、权限控制和可扩展性。
| 向量库 | 部署方式 | 适用规模 | 说明 |
|---|---|---|---|
| PostgreSQL pgvector | 自建或云数据库 | 中小规模 | 和业务数据放同一套数据库,运维简单 |
| Milvus | 独立集群 | 大规模 | 功能全面,需要独立运维 |
| Redis 向量检索 | 已有 Redis 集群 | 高吞吐、低延迟 | 适合已经大量使用 Redis 的团队 |
| Elasticsearch | 已用 ES 的团队 | 中等规模 | 可以同时做关键词搜索和向量检索 |
选型不由框架决定,而由团队现有基础设施和业务规模决定。不要在项目第一天就上分布式向量库,先用最简单的方案验证 RAG 效果,数据量上来之后再迁移。
6. 常见问题排查:按链路分层定位
LangChain4j 应用出问题时,最大的困难是不知道问题出在哪一层。下面这套排查链路按输入、检索、组装、模型、输出五层展开,适合大多数场景。
6.1 五层排查总览
text复制第一层:用户的输入有没有正确传递?
第二层:知识库检索有没有返回内容?
第三层:检索结果有没有进入模型上下文?
第四层:模型有没有按预期生成?
第五层:工具调用和输出序列化有没有异常?
6.2 检索不到内容的排查
现象:回答完全没有使用知识库内容,或者模型直接说“没有相关资料”。
按顺序检查:
- 检查知识库是否真的写入成功。查看日志或者写一个接口统计向量库中向量数量。
- 检查检索结果。打印检索器返回的片段数量和最高相似度分数。
- 检查问题描述与文档内容是否跨语言、跨表述。中文问题在中文文档里检索通常没问题,但英文模型和中文 Embedding 混用会降低效果。
java复制ContentRetriever retriever = new EmbeddingStoreContentRetriever(store, embeddingModel, 5);
List<Content> contents = retriever.retrieve("产品支持哪些支付方式");
System.out.println(contents.size());
contents.forEach(c -> System.out.println(c.text()));
如果这段代码能够检索出内容,但最终回答仍没用上,问题就出在消息组装层。
6.3 依赖版本冲突的排查
现象:启动报 NoClassDefFoundError、NoSuchMethodError,或者编译时找不到 AiServices。
检查方式:
bash复制mvn dependency:tree -Dincludes=dev.langchain4j
重点看两点:是否存在多个 LangChain4j 版本;Spring Boot 的版本是否和 langchain4j-spring-boot-starter 要求的版本一致。LangChain4j 的 1.x 版本对 Spring Boot 3 支持较好,0.x 时代的配置类名和现在完全不同。解决方式是直接统一版本号,不要手动逐个覆盖传递依赖。
6.4 工具调用不生效的排查
现象:模型明明有工具,但回答时完全忽略工具;或者工具调用了,但结果是错误的。
先确认工具方法是否被 @Tool 注解。再确认 AiServices 构建时是否调用了 .tools(instance),而不是只把工具类放在 Spring 容器里。
重点检查工具描述。如果描述写的是“查询订单状态”,模型不知道什么时候该用;如果写成“当用户询问订单状态、物流进度、发货时间时使用”,模型会更准确触发。另外,模型对工具的识别依赖工具描述和参数描述,这部分元数据质量直接影响调用率。
6.5 常见问题速查表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 启动报 401/403 | API 密钥未配置或配置了错误环境变量 | 查看环境变量、日志中的请求 URL | 统一用环境变量管理密钥 |
| 回答完全来自模型自身知识 | 知识库为空或检索未命中 | 打印 ContentRetriever.retrieve 结果 |
检查文档分块和向量写入 |
| 多个答案互相矛盾 | 检索片段包含过时或冲突信息 | 查看注入模型的上下文全文 | 增加知识版本管理、过滤过期文档 |
| 工具从未被调用 | 工具描述不清或遗漏注解 | 观察模型返回的 tool call 日志 | 优化 @Tool 描述,增加示例触发语 |
| CPU/内存占用过高 | 一次向量化大批量文档 | 检查启动日志和 GC 日志 | 分批处理,使用异步任务 |
| 长文本答案截断 | maxTokens 太小 | 查看响应 truncation 标记 | 调大 maxTokens 或要求分步输出 |
6.6 排查时最实用的调试手段
不要只看最终答案。打开 DEBUG 日志,观察框架每一轮向模型发送的消息内容。LangChain4j 的日志里会包含检索片段、工具调用参数和模型原始返回,这是定位问题的第一手资料。
yaml复制logging:
level:
dev.langchain4j: DEBUG
同时,把用户输入、检索片段、模型答案、工具结果都写入应用日志。这样出了问题不需要猜测。
7. 学习环境与生产环境的差异
学习阶段的代码目标明确:最快跑通链路。生产环境需要在此基础上补足稳定性、安全性和可观测性。
7.1 配置外置化与密钥安全
学习阶段把 API Key 放配置文件或环境变量都可以。生产环境必须做到:
- 密钥只在启动环境变量或密钥管理平台中存在。
- 配置中心统一管理模型名称、温度、超时等参数。
- 不同环境使用不同配置,禁止测试环境的密钥进入生产。
7.2 记忆和知识库的持久化
MessageWindowChatMemory 适合单机小规模使用。生产环境如果存在多实例部署,每个实例的内存记忆是独立的,必须改为 Redis 或数据库存储。同样的道理适用于 InMemoryEmbeddingStore,重启丢失知识库在生产环境是不可接受的。
7.3 全链路可观测性
大模型应用比传统接口更难排查,必须记录以下内容:
- 请求输入,包括用户问题和会话 ID。
- 检索器返回的片段数量、来源文档、相似度。
- 发送给模型的 token 数量和最终消息长度。
- 工具调用名称、参数、返回结果。
- 模型响应耗时、首 token 延迟、总耗时。
- 限流、超时、API 错误码。
7.4 生产环境的额外保障
| 关注点 | 学习环境 | 生产环境 |
|---|---|---|
| 知识库存储 | 内存 | pgvector / Milvus / Elasticsearch |
| 记忆存储 | 内存 | Redis / 数据库 |
| 密钥管理 | 环境变量 | 密钥管理平台 |
| 日志 | INFO 少量日志 | 结构化日志 + 全链路追踪 |
| 限流 | 无 | 按用户或按接口限流 |
| 监控告警 | 无 | API 错误率、延迟、成本 |
| 多实例 | 单机 | 无状态服务 + 外部化记忆 |
8. 可复用的项目落地清单
最后给出一份可以直接拿去用的检查清单。做 LangChain4j 项目时,按这个顺序逐项确认,能减少大部分返工。
8.1 项目启动前检查清单
- 确认 JDK 版本大于等于 17。
- 确认 Spring Boot 版本与 LangChain4j Starter 兼容。
- 使用 Maven 统一管理 LangChain4j 版本号。
- 确认大模型 API 的 base-url、api-key、模型名称配置正确。
- 确认 Embedding 模型与检索场景匹配。
- 学习环境用内存向量库,生产环境提前确认持久化向量库。
8.2 RAG 效果调优清单
- 文档是否按结构切分,而不是无脑按固定长度切。
- 分块大小是否在 300 到 800 token 之间。
- 检索数量是否在 3 到 5 个片段之间。
- 是否打印过检索片段来验证命中质量。
- 相似度阈值是否设置得过高或过低。
- 文档更新后,旧向量是否清理。
- 答案是否追溯到具体文档片段。
8.3 智能体可靠性清单
@Tool方法是否写清楚触发场景。- 工具参数是否需要
@ToolParam描述。 - 工具异常是否返回给模型重新决策,而不是直接爆出。
- 高风险操作是否有人工确认环节。
- 多轮对话记忆是否持久化。
- 是否记录工具调用日志。
- 是否对工具数量做了限制,避免模型在无关工具间徘徊。
8.4 发布前检查清单
- 密钥是否已经移出配置文件。
- 埋点日志是否覆盖输入、检索、模型、工具全链路。
- 是否配置了 API 超时和重试策略。
- 是否评估了单用户多轮对话的 token 成本。
- 是否在正式环境用小流量灰度验证知识库效果。
- 是否准备了一键回滚方案,出现严重回答质量问题时能快速切回原逻辑。
结束语
LangChain4j 的价值在于把大模型应用的常用模式变成可维护的 Java 组件。RAG 解决知识来源问题,工具调用解决模型与业务系统交互的问题,记忆解决上下文连续问题,这三者组合起来就是一个具备工程形态的智能体。实际落地时,不要把注意力全放在“模型多聪明”上,分块策略、检索质量、工具描述、记忆持久化和日志观测才是决定项目能不能上生产的关键。建议从本文的最小案例开始,先跑通 RAG,再加一个工具,最后逐步把内存组件替换为生产级组件。每替换一层,你的系统就更接近一个可靠的大型语言模型应用架构。
