1. 项目概述
在Java生态中开发AI应用一直是个痛点,传统方式需要手动处理HTTP请求、管理对话历史、实现知识库检索等功能,代码冗长且难以维护。LangChain4j作为Java版的LangChain框架,彻底改变了这一局面。它让开发者能够像编写普通业务代码一样轻松集成AI能力,大幅提升了开发效率。
我在最近的一个企业级项目中,成功将LangChain4j集成到若依(RuoYi)单体应用中,实现了包括多轮对话、流式输出、RAG知识库等在内的五大核心AI功能。整个过程让我深刻体会到LangChain4j的强大之处——它通过声明式接口和自动化组件管理,将复杂的AI功能简化为几行代码就能实现的服务。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么选择LangChain4j
2.1 传统方式的痛点
在没有框架支持的情况下,Java开发者通常需要手动编写HTTP客户端代码来调用AI服务。这不仅代码量大,而且难以维护。以调用OpenAI为例,传统方式需要:
java复制public String chat(String message) {
// 手动构建请求体
String requestBody = """
{
"model": "gpt-4",
"messages": [
{"role": "user", "content": "%s"}
]
}
""".formatted(message);
// 手动发HTTP请求
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.openai.com/v1/chat/completions"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
// 手动解析响应
HttpResponse<String> response = client.send(request, ...);
JSONObject json = JSON.parseObject(response.body());
return json.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message")
.getString("content");
}
这种方式存在诸多问题:
- 每次调用都要重复编写HTTP请求代码
- 没有内置的对话历史管理
- 缺乏知识库检索能力
- 切换AI模型需要重构大量代码
- 难以实现流式输出等高级功能
2.2 LangChain4j的优势
LangChain4j通过声明式接口和自动化组件管理,完美解决了上述问题。使用LangChain4j,同样的功能可以简化为:
java复制@AiService
public interface CustomerAssistant {
@SystemMessage("你是若依系统的智能客服,请用中文回答用户问题")
String chat(@MemoryId String userId, @UserMessage String message);
}
// 使用
@Autowired
CustomerAssistant assistant;
String reply = assistant.chat("user123", "如何重置密码?");
LangChain4j会自动处理:
- HTTP请求的发送和响应解析
- 对话历史的管理
- 模型切换的适配
- 高级功能的实现
3. 环境准备与集成
3.1 版本要求
在开始集成前,需要确保开发环境满足以下要求:
| 环境 | 要求 |
|---|---|
| JDK | 17+ (LangChain4j 1.x最低要求) |
| Spring Boot | 2.7.x或3.x |
| LangChain4j | 1.12.2 |
| 若依单体 | 3.8.x |
注意:若依默认使用JDK 8,而LangChain4j 1.x需要JDK 17+。如果无法升级JDK,可以使用LangChain4j 0.36.x版本(支持JDK 8)。
3.2 依赖配置
在若依单体的pom.xml中添加以下依赖:
xml复制<!-- LangChain4j BOM(统一版本管理) -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>1.12.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- LangChain4j核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.12.2-beta22</version>
</dependency>
<!-- 使用OpenAI -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.12.2-beta22</version>
</dependency>
<!-- 或使用通义千问(二选一) -->
<!--
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
<version>1.12.2-beta22</version>
</dependency>
-->
<!-- 向量数据库(RAG用,本文使用内存版) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
</dependency>
</dependencies>
3.3 配置文件
在application.yml中添加LangChain4j配置:
yaml复制# LangChain4j配置
langchain4j:
# OpenAI配置
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY:your-api-key-here}
model-name: gpt-4
temperature: 0.7
max-tokens: 2048
log-requests: true
log-responses: true
# 流式输出
streaming-chat-model:
api-key: ${OPENAI_API_KEY:your-api-key-here}
model-name: gpt-4
temperature: 0.7
# 通义千问配置(二选一)
# community:
# dashscope:
# chat-model:
# api-key: ${DASHSCOPE_API_KEY:your-api-key-here}
# model-name: qwen-plus
# temperature: 0.7
提示:API Key建议通过环境变量或配置中心管理,不要直接写在配置文件中。
4. 核心功能实现
4.1 基础对话服务
基础对话是最简单的AI功能,LangChain4j通过@AiService注解可以轻松实现:
java复制@AiService
public interface ChatAssistant {
@SystemMessage("你是若依管理系统的智能助手,请用简洁专业的中文回答问题。")
String chat(@UserMessage String message);
}
对应的Controller:
java复制@RestController
@RequestMapping("/ai")
public class AiChatController extends BaseController {
@Autowired
private ChatAssistant chatAssistant;
@PostMapping("/chat")
public AjaxResult chat(@RequestParam String message) {
String reply = chatAssistant.chat(message);
return AjaxResult.success(reply);
}
}
测试:
bash复制curl -X POST "http://localhost:8080/ai/chat" -d "message=你好,请介绍一下若依框架"
4.2 多轮对话实现
多轮对话需要管理对话历史,LangChain4j通过ChatMemory组件实现:
java复制@Configuration
public class LangChain4jConfig {
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20) // 最多保留最近20条消息
.build();
}
}
@AiService
public interface MemoryChatAssistant {
@SystemMessage("""
你是若依管理系统的智能客服助手。
你可以帮助用户解答系统使用问题、功能介绍等。
请用友好、专业的中文回答。
""")
String chat(
@MemoryId String userId, // 用户ID,用于区分不同用户的对话历史
@UserMessage String message // 用户消息
);
}
Controller集成:
java复制@PostMapping("/chat/memory")
public AjaxResult chatWithMemory(@RequestParam String message) {
Long userId = SecurityUtils.getUserId();
String reply = memoryChatAssistant.chat(String.valueOf(userId), message);
return AjaxResult.success(reply);
}
4.3 流式输出实现
流式输出可以提升用户体验,实现打字机效果:
java复制@AiService
public interface StreamingChatAssistant {
@SystemMessage("你是若依系统的智能助手,请用中文回答。")
Flux<String> chat(
@MemoryId String userId,
@UserMessage String message
);
}
SSE接口实现:
java复制@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamChat(@RequestParam String message,
@RequestParam(defaultValue = "default") String userId) {
SseEmitter emitter = new SseEmitter(60_000L); // 60秒超时
streamingChatAssistant.chat(userId, message)
.subscribe(
token -> {
try {
emitter.send(SseEmitter.event()
.data(token)
.name("message"));
} catch (Exception e) {
emitter.completeWithError(e);
}
},
emitter::completeWithError,
() -> {
try {
emitter.send(SseEmitter.event()
.data("[DONE]")
.name("done"));
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
}
);
return emitter;
}
前端接收:
javascript复制function streamChat(message) {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '';
const url = `/ai/chat/stream?message=${encodeURIComponent(message)}&userId=${userId}`;
const eventSource = new EventSource(url);
eventSource.addEventListener('message', (event) => {
resultDiv.innerHTML += event.data;
});
eventSource.addEventListener('done', (event) => {
eventSource.close();
console.log('对话完成');
});
eventSource.onerror = (error) => {
console.error('SSE错误:', error);
eventSource.close();
};
}
4.4 RAG知识库问答
RAG(检索增强生成)可以让AI基于知识库内容回答问题:
java复制@Configuration
public class RagConfig {
@Bean
public EmbeddingModel embeddingModel() {
return new AllMiniLmL6V2EmbeddingModel();
}
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
@Bean
public EmbeddingStoreIngestor embeddingStoreIngestor(
EmbeddingModel embeddingModel,
EmbeddingStore<TextSegment> embeddingStore) {
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
return EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
}
}
@Component
public class KnowledgeBaseLoader implements ApplicationRunner {
@Autowired
private EmbeddingStoreIngestor ingestor;
@Override
public void run(ApplicationArguments args) {
List<Document> documents = List.of(
Document.from("若依系统用户管理:\n1. 进入系统管理 → 用户管理\n2. 点击新增按钮..."),
Document.from("若依系统菜单管理:\n1. 进入系统管理 → 菜单管理\n2. 菜单类型分为...")
);
ingestor.ingest(documents);
System.out.println("✅ 若依知识库加载完成");
}
}
@AiService
public interface RagAssistant {
@SystemMessage("""
你是若依管理系统的专业客服助手。
请根据提供的知识库内容回答用户问题。
如果知识库中没有相关信息,请如实告知用户,不要编造答案。
回答要简洁、准确、友好。
""")
String chat(
@MemoryId String userId,
@UserMessage String question
);
}
4.5 Function Calling工具调用
Function Calling允许AI调用Java方法获取实时数据:
java复制@Component
public class RuoYiTools {
@Autowired
private ISysUserService userService;
@Tool("获取系统当前用户总数")
public String getUserCount() {
int count = userService.selectUserList(new SysUser()).size();
return "系统当前共有 " + count + " 个用户";
}
}
@AiService
public interface SmartAssistant {
@SystemMessage("""
你是若依管理系统的智能助手,你可以:
1. 查询系统实时数据(用户数、菜单数等)
2. 查询用户信息
3. 获取当前时间
当用户询问系统数据时,请主动调用相关工具获取最新数据。
""")
String chat(
@MemoryId String userId,
@UserMessage String message
);
}
5. 项目结构与完整配置
5.1 项目目录结构
建议的AI模块目录结构:
code复制ruoyi-admin/
└── src/main/java/com/ruoyi/
└── ai/
├── config/
│ └── LangChain4jConfig.java # AI配置类
├── service/
│ ├── ChatAssistant.java # 基础对话AI Service
│ ├── CustomerAssistant.java # 客服AI Service
│ └── RagAssistant.java # RAG知识库AI Service
├── tools/
│ └── RuoYiTools.java # 若依系统工具(供AI调用)
├── controller/
│ └── AiChatController.java # 对话接口
└── domain/
└── ChatRequest.java # 请求DTO
5.2 完整LangChain4j配置
java复制@Configuration
public class LangChain4jConfig {
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.build();
}
@Bean
public EmbeddingModel embeddingModel() {
return new AllMiniLmL6V2EmbeddingModel();
}
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
@Bean
public ContentRetriever contentRetriever(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.6)
.build();
}
}
5.3 完整Controller
java复制@RestController
@RequestMapping("/ai")
public class AiChatController extends BaseController {
@Autowired
private ChatAssistant chatAssistant;
@Autowired
private MemoryChatAssistant memoryChatAssistant;
@Autowired
private StreamingChatAssistant streamingChatAssistant;
@Autowired
private RagAssistant ragAssistant;
@Autowired
private SmartAssistant smartAssistant;
@PostMapping("/chat")
public AjaxResult chat(@RequestParam String message) {
return AjaxResult.success(chatAssistant.chat(message));
}
@PostMapping("/chat/memory")
public AjaxResult chatWithMemory(@RequestParam String message) {
String userId = String.valueOf(SecurityUtils.getUserId());
return AjaxResult.success(memoryChatAssistant.chat(userId, message));
}
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamChat(@RequestParam String message) {
String userId = String.valueOf(SecurityUtils.getUserId());
SseEmitter emitter = new SseEmitter(60_000L);
streamingChatAssistant.chat(userId, message)
.subscribe(
token -> {
try {
emitter.send(SseEmitter.event().data(token).name("message"));
} catch (Exception e) {
emitter.completeWithError(e);
}
},
emitter::completeWithError,
() -> {
try {
emitter.send(SseEmitter.event().data("[DONE]").name("done"));
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
}
);
return emitter;
}
@PostMapping("/chat/rag")
public AjaxResult ragChat(@RequestParam String question) {
String userId = String.valueOf(SecurityUtils.getUserId());
return AjaxResult.success(ragAssistant.chat(userId, question));
}
@PostMapping("/chat/smart")
public AjaxResult smartChat(@RequestParam String message) {
String userId = String.valueOf(SecurityUtils.getUserId());
return AjaxResult.success(smartAssistant.chat(userId, message));
}
}
6. 常见问题与解决方案
6.1 JDK版本问题
问题:若依默认使用JDK 8,而LangChain4j 1.x需要JDK 17+
解决方案:
- 升级项目JDK到17+(推荐)
- 或使用LangChain4j 0.36.x版本(支持JDK 8)
6.2 流式输出中断
问题:流式输出时连接经常中断
解决方案:
- 增加SSE超时时间(默认30秒可能不够)
- 检查网络稳定性,特别是使用海外AI服务时
- 前端添加重连机制
6.3 RAG检索效果不佳
问题:知识库问答返回不相关结果
解决方案:
- 优化文档分块策略(调整块大小和重叠)
- 提高相似度阈值(minScore)
- 优化知识库文档质量
- 考虑使用更强大的Embedding模型
6.4 性能优化建议
- 对于生产环境,建议:
- 使用持久化向量数据库(如Milvus、Qdrant)
- 实现缓存机制,减少重复计算
- 对高频问题预生成答案
- 监控AI服务调用情况,及时发现异常
- 根据业务需求调整模型参数(temperature等)
7. 项目总结与展望
通过本次LangChain4j与若依单体的集成实践,我深刻体会到现代AI开发框架的强大之处。LangChain4j通过声明式接口和自动化组件管理,将复杂的AI功能简化为几行代码就能实现的服务,极大提升了开发效率。
在实际项目中,这种集成方式特别适合需要快速为现有系统添加AI能力的情况。开发者无需深入了解AI底层实现,就能构建出功能完善的智能应用。
未来,我计划进一步探索:
- 更复杂的工具调用场景
- 多模态AI能力集成
- 本地大模型的应用
- 更智能的对话管理策略
对于Java开发者来说,LangChain4j无疑是一个值得深入学习和应用的工具,它让AI集成变得前所未有的简单和高效。
