LangChain4j实战:Java统一LLM应用开发框架与RAG实现
在实际 Java 项目中集成大语言模型(LLM)时,开发者面临的最大挑战不是模型本身的能力,而是如何将不同厂商的 API、向量数据库、工具调用和业务流程统一起来。每个 LLM 提供商都有自己的接口规范,每个向量存储也有独特的数据操作方式,直接对接意味着代码会充满厂商锁定的硬编码,测试和切换成本极高。LangChain4j 正是为了解决这一问题而设计的 Java 库,它提供了一套统一的 API,让开发者可以用同一套代码对接 OpenAI、Google Gemini、Anthropic 等 20 多家 LLM 服务,以及 Pinecone、Milvus、Chroma 等 30 多种向量数据库。
更重要的是,LangChain4j 不是简单地将 Python 版 LangChain 移植到 Java,而是完全基于 Java 生态的习惯重新设计。它强调类型安全、POJO、接口隔离、依赖注入和流畅式 API,并且与 Spring Boot、Quarkus、Helidon、Micronaut 等企业级框架无缝集成。这意味着你可以在熟悉的 Java 开发模式中快速构建 RAG(检索增强生成)、Agent(智能体)、工具调用等高级 LLM 应用,而不必担心底层实现差异。
本文将带你从零开始,理解 LangChain4j 的核心设计思路,完成环境准备和依赖配置,实现一个可运行的 RAG 示例,并深入讲解关键参数和常见问题排查方法。学完后,你将掌握如何在生产环境中安全、高效地使用 LangChain4j 构建 LLM 应用。
1. 理解 LangChain4j 的架构设计和工作原理
LangChain4j 的核心价值在于抽象和统一。它把 LLM 应用开发中常见的环节——如对话管理、文档加载、向量化、检索、工具调用——都定义成接口,并为每个接口提供多种实现。这种设计让代码不再依赖具体厂商,而是依赖抽象层,从而大幅提升可测试性和可维护性。
1.1 核心模块划分
LangChain4j 采用模块化设计,主要分为以下几个核心模块:
- langchain4j-core:提供基础抽象,如
ChatLanguageModel(聊天模型)、EmbeddingModel(嵌入模型)、ChatMemory(对话记忆)、Tool(工具调用)等接口。 - langchain4j-{provider}:针对特定 LLM 提供商的实现,例如
langchain4j-openai、langchain4j-google-ai-gemini。 - langchain4j-{embedding-store}:针对特定向量数据库的实现,例如
langchain4j-pinecone、langchain4j-milvus。 - langchain4j-{framework}:与 Java 框架的集成模块,例如
quarkus-langchain4j、与 Spring Boot 集成的自动配置。 - langchain4j-agentic:提供 Agent(智能体)相关功能,支持 ReAct、AutoGPT 等模式。
这种模块化意味着你可以按需引入依赖,避免引入不必要的代码和配置。
1.2 统一 API 的工作机制
以调用 LLM 为例,不同厂商的 API 参数命名、格式、认证方式各不相同。LangChain4j 通过ChatLanguageModel接口统一了调用方式:
// 这是 LangChain4j 的统一接口 public interface ChatLanguageModel { Response<String> generate(String userMessage); Response<AiMessage> generate(UserMessage userMessage); // 其他重载方法... }无论底层是 OpenAI 还是 Gemini,你都可以通过同一组方法调用。LangChain4j 在内部处理了请求转换、认证、重试、异常处理等细节。这种设计不仅简化了代码,还让单元测试更容易——你可以轻松注入一个模拟的ChatLanguageModel进行测试。
1.3 与 Spring Boot 的集成原理
LangChain4j 为 Spring Boot 提供了自动配置支持。当你在项目中引入langchain4j-spring-boot-starter时,Spring Boot 的自动配置机制会:
- 根据
application.properties或application.yml中的配置(如langchain4j.openai.api-key)创建对应的 Bean。 - 自动配置
ChatLanguageModel、EmbeddingModel等实例。 - 提供健康检查端点(如果配置了 Spring Boot Actuator)。
这种集成方式让 LangChain4j 的使用变得像使用其他 Spring Boot 组件一样自然,无需手动编写大量样板代码。
2. 环境准备与项目配置
开始编码前,需要确保本地环境符合要求,并正确配置项目依赖。LangChain4j 支持 Java 8 及以上版本,但推荐使用 Java 17 或更高版本以获得更好的性能和功能支持。
2.1 开发环境要求
| 组件 | 最低要求 | 推荐版本 | 备注 |
|---|---|---|---|
| JDK | 8 | 17 或 21 | Java 17 以上支持新特性 |
| Maven | 3.5 | 3.9+ | 或 Gradle 7.x+ |
| IDE | 任意 | IntelliJ IDEA | 需要 Lombok 插件 |
| 网络 | 可访问 LLM API | 稳定连接 | 测试需要实际调用 API |
确保 IDE 已安装 Lombok 插件,因为 LangChain4j 大量使用 Lombok 注解简化代码。
2.2 Maven 依赖配置
对于 Spring Boot 项目,最简单的入门方式是使用langchain4j-spring-boot-starter。在pom.xml中添加以下依赖:
<properties> <langchain4j.version>0.31.0</langchain4j.version> <spring-boot.version>3.2.0</spring-boot.version> </properties> <dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- LangChain4j Spring Boot Starter --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- OpenAI 集成(如使用其他提供商可替换) --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> </dependencies>如果你不使用 Spring Boot,可以只引入核心模块和具体的提供商模块:
<dependencies> <!-- LangChain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-core</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- OpenAI 集成 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> </dependencies>2.3 配置文件设置
在application.yml或application.properties中配置 LLM 提供商的相关参数:
# application.yml langchain4j: openai: api-key: ${OPENAI_API_KEY} # 从环境变量读取 model-name: gpt-3.5-turbo # 使用的模型 temperature: 0.7 # 创造性程度 max-tokens: 500 # 最大输出token数 timeout: 60s # 请求超时时间 # 日志配置,便于调试 logging: level: dev.langchain4j: DEBUG或者使用application.properties:
# application.properties langchain4j.openai.api-key=${OPENAI_API_KEY} langchain4j.openai.model-name=gpt-3.5-turbo langchain4j.openai.temperature=0.7 langchain4j.openai.max-tokens=500 langchain4j.openai.timeout=60s logging.level.dev.langchain4j=DEBUG注意:API Key 等敏感信息不要直接写在配置文件中,应该使用环境变量或专门的密钥管理服务。上面的
${OPENAI_API_KEY}表示从环境变量中读取值。
2.4 非 Spring 项目的配置方式
如果不使用 Spring Boot,需要手动创建 LLM 实例:
import dev.langchain4j.model.openai.OpenAiChatModel; public class ManualConfig { public static void main(String[] args) { OpenAiChatModel model = OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-3.5-turbo") .temperature(0.7) .maxTokens(500) .timeout(Duration.ofSeconds(60)) .build(); // 使用 model 进行对话... } }这种方式虽然灵活,但需要手动管理对象的生命周期和依赖关系,适合简单的命令行工具或非 Spring 项目。
3. 实现一个完整的 RAG 应用示例
RAG(Retrieval-Augmented Generation)是当前最实用的 LLM 应用模式之一,它通过检索相关知识来增强生成的准确性和相关性。下面我们实现一个简单的文档问答系统,演示 LangChain4j 的核心功能。
3.1 项目结构设计
src/main/java/ ├── com/example/rag/ │ ├── config/ │ │ └── EmbeddingConfig.java # 向量化配置 │ ├── service/ │ │ ├── DocumentService.java # 文档处理服务 │ │ └── QAService.java # 问答服务 │ ├── controller/ │ │ └── QAController.java # Web接口 │ └── Application.java # 启动类 resources/ ├── documents/ # 存放待处理的文档 │ └── example.txt └── application.yml3.2 核心代码实现
首先实现文档处理服务,负责加载文档、分割文本、生成向量并存储:
@Service public class DocumentService { private final EmbeddingModel embeddingModel; private final EmbeddingStore<TextSegment> embeddingStore; public DocumentService(EmbeddingModel embeddingModel, EmbeddingStore<TextSegment> embeddingStore) { this.embeddingModel = embeddingModel; this.embeddingStore = embeddingStore; } public void processDocument(Path documentPath) { try { // 1. 加载文档 Document document = DocumentLoader.load(documentPath); // 2. 分割文本(按段落或固定大小) DocumentSplitter splitter = new DocumentByParagraphSplitter(500, 50); List<TextSegment> segments = splitter.split(document); // 3. 生成向量 List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); // 4. 存储到向量数据库 embeddingStore.addAll(embeddings, segments); } catch (IOException e) { throw new RuntimeException("文档处理失败: " + documentPath, e); } } }接下来实现问答服务,负责检索相关文档并生成答案:
@Service public class QAService { private final ChatLanguageModel chatModel; private final EmbeddingModel embeddingModel; private final EmbeddingStore<TextSegment> embeddingStore; public QAService(ChatLanguageModel chatModel, EmbeddingModel embeddingModel, EmbeddingStore<TextSegment> embeddingStore) { this.chatModel = chatModel; this.embeddingModel = embeddingModel; this.embeddingStore = embeddingStore; } public String answerQuestion(String question) { // 1. 将问题转换为向量 Embedding questionEmbedding = embeddingModel.embed(question).content(); // 2. 检索最相关的文档片段 int maxResults = 3; double minScore = 0.7; List<EmbeddingMatch<TextSegment>> relevantMatches = embeddingStore.findRelevant(questionEmbedding, maxResults, minScore); // 3. 构建上下文 StringBuilder context = new StringBuilder(); for (EmbeddingMatch<TextSegment> match : relevantMatches) { context.append(match.embedded().text()).append("\n\n"); } // 4. 构建提示词 String prompt = String.format( "请基于以下上下文回答问题。如果上下文不足以回答问题,请说明。\n\n" + "上下文:\n%s\n\n问题:%s", context.toString(), question ); // 5. 调用 LLM 生成答案 return chatModel.generate(prompt); } }Web 控制器提供 REST API:
@RestController @RequestMapping("/api/qa") public class QAController { private final QAService qaService; private final DocumentService documentService; public QAController(QAService qaService, DocumentService documentService) { this.qaService = qaService; this.documentService = documentService; } @PostMapping("/ask") public ResponseEntity<String> askQuestion(@RequestBody QuestionRequest request) { try { String answer = qaService.answerQuestion(request.getQuestion()); return ResponseEntity.ok(answer); } catch (Exception e) { return ResponseEntity.status(500) .body("回答问题失败: " + e.getMessage()); } } @PostMapping("/upload") public ResponseEntity<String> uploadDocument(@RequestParam("file") MultipartFile file) { try { Path tempFile = Files.createTempFile("upload-", ".txt"); file.transferTo(tempFile); documentService.processDocument(tempFile); Files.delete(tempFile); // 清理临时文件 return ResponseEntity.ok("文档处理完成"); } catch (Exception e) { return ResponseEntity.status(500) .body("文档上传失败: " + e.getMessage()); } } public static class QuestionRequest { private String question; // getter 和 setter } }3.3 向量存储配置
对于简单的演示项目,可以使用内存中的向量存储。在生产环境中,需要配置持久化向量数据库:
@Configuration public class EmbeddingConfig { @Bean @ConditionalOnMissingBean public EmbeddingStore<TextSegment> embeddingStore() { // 内存存储,适合演示(重启后数据丢失) return new InMemoryEmbeddingStore<>(); } // 生产环境使用 PostgreSQL + pgvector 的配置示例 // @Bean // public EmbeddingStore<TextSegment> pgVectorEmbeddingStore(DataSource dataSource) { // return new PgVectorEmbeddingStore.Builder() // .dataSource(dataSource) // .tableName("document_embeddings") // .dimension(1536) // OpenAI 嵌入向量的维度 // .build(); // } }3.4 应用启动类
@SpringBootApplication public class Application { @Bean CommandLineRunner init(DocumentService documentService) { return args -> { // 启动时加载示例文档 Path exampleDoc = Paths.get(ClassLoader.getSystemResource("documents/example.txt").toURI()); documentService.processDocument(exampleDoc); System.out.println("示例文档加载完成"); }; } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }4. 关键参数详解与性能调优
LangChain4j 提供了丰富的配置参数,理解这些参数的含义和影响对于构建稳定的生产应用至关重要。
4.1 LLM 调用参数
| 参数 | 类型 | 默认值 | 说明 | 调优建议 |
|---|---|---|---|---|
| temperature | double | 0.7 | 控制输出的随机性 | 问答类应用用 0.1-0.3,创意类用 0.7-0.9 |
| maxTokens | int | 500 | 最大输出token数 | 根据需求调整,太长会增加成本和延迟 |
| topP | double | 1.0 | 核采样参数 | 与temperature二选一,通常用temperature |
| timeout | Duration | 60s | 请求超时时间 | 根据网络状况和模型复杂度调整 |
| maxRetries | int | 3 | 最大重试次数 | 对稳定性要求高的场景可增加到5 |
# 生产环境推荐的配置 langchain4j: openai: temperature: 0.1 # 问答应用需要确定性 max-tokens: 1000 # 预留足够空间 timeout: 120s # 复杂问题需要更长时间 max-retries: 5 # 提高容错性 log-requests: true # 开启请求日志 log-responses: true # 开启响应日志4.2 向量检索参数
向量检索的质量直接影响 RAG 的效果,关键参数包括:
// 检索相关文档时的参数配置 EmbeddingStore<TextSegment> store = // ... 获取存储实例 // 查找最相关的文档片段 List<EmbeddingMatch<TextSegment>> matches = store.findRelevant( questionEmbedding, 5, // maxResults: 返回最相似的5个结果 0.6 // minScore: 相似度阈值,0-1之间,越高要求越严格 );参数调优建议:
- maxResults:根据文档密度调整。文档内容密集时用较小的值(3-5),内容稀疏时用较大的值(5-10)。
- minScore:根据嵌入模型的质量调整。OpenAI text-embedding-ada-002 通常 0.7-0.8,本地模型可能需要 0.5-0.6。
4.3 对话记忆管理
对于多轮对话场景,需要管理对话历史:
// 创建对话记忆(基于内存) ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); // 在对话中使用 UserMessage userMessage = UserMessage.from("你好"); AiMessage aiMessage = chatModel.generate(userMessage).content(); chatMemory.add(userMessage); chatMemory.add(aiMessage); // 下一轮对话会自动包含历史 UserMessage nextMessage = UserMessage.from("我刚才问了什么?"); // 这次生成会基于完整的对话历史 AiMessage response = chatModel.generate(chatMemory.messages()).content();记忆管理策略:
- 消息窗口:保留最近 N 条消息,适合短对话。
- 令牌窗口:保留最近 N 个令牌,适合控制成本。
- 总结式记忆:将长历史总结为简短描述,适合长对话。
5. 常见问题排查与解决方案
在实际使用 LangChain4j 过程中,会遇到各种问题。下面列出典型问题及其解决方法。
5.1 依赖冲突问题
问题现象:
java.lang.NoSuchMethodError: okhttp3.RequestBody.create([BLokhttp3/MediaType;)原因分析:多个 HTTP 客户端库版本冲突,特别是 OkHttp 的不同版本。
解决方案:使用 Maven 的依赖树分析工具:
mvn dependency:tree -Dincludes=com.squareup.okhttp3:okhttp在pom.xml中统一版本:
<properties> <okhttp.version>4.12.0</okhttp.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp-bom</artifactId> <version>${okhttp.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>5.2 API 调用失败问题
问题现象:
dev.langchain4j.model.openai.OpenAiHttpException: HTTP 429 Too Many Requests原因分析:API 调用频率超过限制,或者配额用尽。
排查步骤:
- 检查 API Key 是否正确且有效
- 查看当前使用量和配额限制
- 检查是否有其他进程在使用同一密钥
解决方案:
langchain4j: openai: max-retries: 5 # 增加重试次数 retryer: # 自定义重试策略 max-attempts: 5 wait-duration: 2s multiplier: 2.05.3 向量检索效果差
问题现象:RAG 系统返回的答案与问题不相关,或者遗漏关键信息。
原因分析:
- 文档分割策略不合适
- 相似度阈值设置不合理
- 嵌入模型不适合当前领域
优化方案:
改进文档分割:
// 尝试不同的分割策略 DocumentSplitter splitter = new DocumentBySentenceSplitter(50, 10); // 按句子分割 // 或 DocumentSplitter splitter = new RecursiveDocumentSplitter(500, 50, 10); // 递归分割调整检索参数:
// 动态调整检索参数 int maxResults = question.length() > 50 ? 5 : 3; // 复杂问题返回更多结果 double minScore = isTechnicalQuestion(question) ? 0.75 : 0.65; // 技术问题提高阈值5.4 内存泄漏问题
问题现象:应用运行时间越长,内存占用越高,最终出现 OutOfMemoryError。
原因分析:
- 对话记忆未清理
- 大文档处理时内存未释放
- 向量存储缓存过大
解决方案:
定期清理对话记忆:
@Scheduled(fixedRate = 3600000) // 每小时清理一次 public void cleanupOldConversations() { conversationService.cleanupOlderThan(Duration.ofHours(24)); }优化文档处理内存使用:
public void processLargeDocument(Path documentPath) { try (Stream<String> lines = Files.lines(documentPath)) { // 流式处理大文件,避免全部加载到内存 lines.forEach(line -> { // 分批处理每一行 processTextSegment(line); }); } catch (IOException e) { // 异常处理 } }6. 生产环境最佳实践
将 LangChain4j 应用到生产环境时,需要考虑性能、安全、监控等多个方面。
6.1 安全配置建议
API 密钥管理:
# 不要硬编码密钥,使用环境变量或密钥管理服务 langchain4j: openai: api-key: ${OPENAI_API_KEY:} # 从环境变量读取,冒号后为默认值(空)输入输出过滤:
@Service public class SafeQAService { private final QAService qaService; public String safeAnswerQuestion(String question) { // 1. 输入验证 if (!isValidQuestion(question)) { return "问题格式不正确"; } // 2. 内容过滤 if (containsSensitiveContent(question)) { return "问题包含不合适内容"; } // 3. 调用原始服务 String answer = qaService.answerQuestion(question); // 4. 输出过滤 return filterSensitiveContent(answer); } private boolean isValidQuestion(String question) { return question != null && question.length() > 0 && question.length() <= 1000; } }6.2 性能优化策略
缓存常用查询:
@Service public class CachedQAService { private final QAService delegate; private final Cache<String, String> cache; public CachedQAService(QAService delegate) { this.delegate = delegate; this.cache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.HOURS) .build(); } public String answerQuestion(String question) { return cache.get(question, delegate::answerQuestion); } }异步处理耗时操作:
@Async public CompletableFuture<String> answerQuestionAsync(String question) { return CompletableFuture.completedFuture(answerQuestion(question)); }6.3 监控与日志
添加监控指标:
@Component public class QAServiceMetrics { private final MeterRegistry meterRegistry; private final Counter requestCounter; private final Timer responseTimer; public QAServiceMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.requestCounter = Counter.builder("qa.requests") .description("问答请求数量") .register(meterRegistry); this.responseTimer = Timer.builder("qa.response.time") .description("问答响应时间") .register(meterRegistry); } public String trackRequest(String question) { requestCounter.increment(); return responseTimer.record(() -> { // 实际处理逻辑 return processQuestion(question); }); } }结构化日志配置:
// logback-spring.json { "appenders": { "console": { "type": "console", "encoder": { "pattern": "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" } } }, "loggers": { "dev.langchain4j": "DEBUG", "com.example.rag": "INFO" } }6.4 部署检查清单
在部署到生产环境前,使用以下清单进行检查:
- [ ] API 密钥已通过安全方式配置,未硬编码在代码中
- [ ] 向量数据库连接参数正确,且有备份策略
- [ ] 所有外部服务(LLM API、向量DB)都有重试机制
- [ ] 设置了合理的超时时间,避免线程阻塞
- [ ] 内存使用有监控,大文档处理有流式方案
- [ ] 输入输出有内容过滤,防止注入攻击
- [ ] 关键操作有日志记录,便于问题排查
- [ ] 性能指标有收集,能够发现性能瓶颈
- [ ] 有降级方案,当 LLM 服务不可用时能优雅处理
LangChain4j 为 Java 开发者提供了构建 LLM 应用的完整工具箱,但真正发挥其价值需要在理解核心概念的基础上,结合具体业务场景进行恰当的配置和优化。从简单的问答系统到复杂的智能体应用,LangChain4j 都能提供良好的支持,关键是掌握其设计哲学并遵循企业级应用的最佳实践。