用 LangChain4j 开发 AI 应用并集成若依单体 | 从 0 到 1 实战指南
还在手写 HTTP 调用 OpenAI?LangChain4j 让 Java 开发 AI 应用像写普通业务代码一样简单。本文从 LangChain4j 核心概念出发,手把手带你完成若依单体应用集成,实现智能对话、RAG 知识库问答、流式输出、Function Calling 等核心功能,附完整代码
一、为什么需要 LangChain4j?
1.1 没有框架时的痛点
java
/**
* 传统方式:手写 HTTP 调用 OpenAI
* 问题:代码繁琐、难以维护、缺少上下文管理
*/
public String chat(String message) {
// 手动构建请求体
String requestBody = """
{
"model": "gpt-4o",
"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");
// 还没有:对话历史、RAG、工具调用、流式输出...
}
问题一大堆:
- 每次调用都要手写 HTTP 请求
- 没有对话历史管理(多轮对话)
- 没有 RAG(知识库问答)
- 没有 Function Calling(工具调用)
- 切换模型(OpenAI → 通义千问)需要大改代码
1.2 LangChain4j 解决了什么?
java
/**
* 使用 LangChain4j:声明式 AI 服务
* 一个接口,搞定一切
*/
@AiService
public interface CustomerAssistant {
@SystemMessage("你是若依系统的智能客服,请用中文回答用户问题")
String chat(@MemoryId String userId, @UserMessage String message);
}
// 使用
@Autowired
CustomerAssistant assistant;
String reply = assistant.chat("user123", "如何重置密码?");
// 自动管理对话历史、自动调用 LLM、自动返回结果
1.3 LangChain4j 是什么?
LangChain4j 是 Java 版的 LangChain,是目前 Java 生态中最成熟的 AI 应用开发框架。

核心能力:
| 能力 | 说明 |
|---|---|
| 多模型支持 | OpenAI、通义千问、文心一言、Ollama 本地模型等 30+ |
| AI Services | 声明式接口,像写 Service 一样开发 AI 功能 |
| 对话记忆 | 自动管理多轮对话历史 |
| RAG | 检索增强生成,知识库问答 |
| Function Calling | 让 AI 调用你的 Java 方法 |
| 流式输出 | 逐字输出,提升用户体验 |
| Embedding | 文本向量化,支持语义搜索 |
二、LangChain4j 核心概念
2.1 整体架构
┌─────────────────────────────────────────────────────────────────────┐
│ LangChain4j 核心架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ AI Services(高层 API) │ │
│ │ @AiService 声明式接口,自动组装所有组件 │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┼──────────────────────────────────┐ │
│ │ │ │ │
│ │ ┌────────────┐ ┌───────┴──────┐ ┌────────────────────┐ │ │
│ │ │ ChatModel │ │ ChatMemory │ │ Retriever(RAG) │ │ │
│ │ │ (LLM 调用) │ │ (对话历史) │ │ (知识库检索) │ │ │
│ │ └────────────┘ └──────────────┘ └────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────┐ ┌──────────────┐ ┌────────────────────┐ │ │
│ │ │ Tools │ │ Embedding │ │ OutputParser │ │ │
│ │ │ (工具调用) │ │ (向量化) │ │ (结构化输出) │ │ │
│ │ └────────────┘ └──────────────┘ └────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 底层集成(30+ 模型) │ │
│ │ OpenAI | 通义千问 | 文心一言 | Ollama | Azure OpenAI | ... │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 核心组件说明
| 组件 | 作用 |
|---|---|
| ChatModel | 与 LLM 交互的核心接口 |
| ChatMemory | 管理对话历史(多轮对话) |
| EmbeddingModel | 将文本转为向量 |
| EmbeddingStore | 向量数据库(存储和检索向量) |
| ContentRetriever | RAG 检索器,从知识库检索相关内容 |
| Tools | 工具定义,让 AI 调用 Java 方法 |
| AI Services | 高层声明式 API,自动组装上述组件 |
三、环境准备
3.1 版本要求
| 环境 | 要求 |
|---|---|
| JDK | 17+(LangChain4j 1.x 最低要求) |
| Spring Boot | 2.7.x 或 3.x |
| LangChain4j | 1.12.2(本文使用) |
| 若依单体 | 3.8.x |
⚠️ 注意:LangChain4j 1.x 要求 JDK 17+,若依默认是 JDK 8,需要升级或使用 LangChain4j 0.36.x(支持 JDK 8)
3.2 API Key 准备
本文支持两种模型,任选其一:
方案一:OpenAI(需要科学上网)
- 注册地址:https://platform.openai.com
- 获取 API Key:https://platform.openai.com/api-keys
方案二:通义千问(国内推荐,免费额度)
- 注册地址:https://dashscope.aliyun.com
- 获取 API Key:控制台 → API-KEY 管理
四、若依单体集成 LangChain4j
4.1 添加依赖
在若依单体的 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>
4.2 配置文件
在 application.yml 中添加:
yaml
# ===== LangChain4j 配置 =====
langchain4j:
# 方案一:OpenAI 配置
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY:your-api-key-here}
model-name: gpt-4o-mini
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-4o-mini
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 不要写死在配置文件里,使用环境变量或 Nacos 配置中心管理
4.3 创建 AI 模块目录结构
在若依单体中创建 AI 相关目录:
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.1 定义 AI Service
java
package com.ruoyi.ai.service;
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
/**
* 基础对话 AI Service
* 声明式接口,LangChain4j 自动实现
*/
@AiService
public interface ChatAssistant {
/**
* 简单对话(无记忆)
*/
@SystemMessage("你是若依管理系统的智能助手,请用简洁专业的中文回答问题。")
String chat(@UserMessage String message);
}
5.2 Controller 层
java
package com.ruoyi.ai.controller;
import com.ruoyi.ai.service.ChatAssistant;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* AI 对话接口
*/
@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);
}
}
5.3 测试
bash
curl -X POST "http://localhost:8080/ai/chat" \
-d "message=你好,请介绍一下若依框架"
响应:
json
{
"code": 200,
"msg": "操作成功",
"data": "若依(RuoYi)是一款基于 Spring Boot 的权限管理系统框架,提供了用户管理、角色管理、菜单管理等基础功能..."
}
六、功能二:多轮对话(带记忆)
6.1 配置对话记忆
java
package com.ruoyi.ai.config;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* LangChain4j 配置类
*/
@Configuration
public class LangChain4jConfig {
/**
* 对话记忆提供者
* 每个用户(memoryId)独立维护一份对话历史
*/
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20) // 最多保留最近 20 条消息
.build();
}
}
6.2 带记忆的 AI Service
java
package com.ruoyi.ai.service;
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
/**
* 带记忆的对话 AI Service
* 支持多轮对话,每个用户独立维护对话历史
*/
@AiService
public interface MemoryChatAssistant {
@SystemMessage("""
你是若依管理系统的智能客服助手。
你可以帮助用户解答系统使用问题、功能介绍等。
请用友好、专业的中文回答。
""")
String chat(
@MemoryId String userId, // 用户ID,用于区分不同用户的对话历史
@UserMessage String message // 用户消息
);
}
6.3 Controller 集成
java
/**
* 多轮对话接口
*/
@PostMapping("/chat/memory")
public AjaxResult chatWithMemory(@RequestParam String message) {
// 获取当前登录用户ID,作为记忆隔离的 key
Long userId = SecurityUtils.getUserId();
String reply = memoryChatAssistant.chat(String.valueOf(userId), message);
return AjaxResult.success(reply);
}
效果演示:
用户:我叫张三
AI:你好,张三!有什么我可以帮助你的吗?
用户:我叫什么名字?
AI:你叫张三!(AI 记住了上下文)
用户:若依系统如何添加菜单?
AI:在若依系统中,添加菜单的步骤如下...
七、功能三:流式输出(打字机效果)
7.1 定义流式 AI Service
java
package com.ruoyi.ai.service;
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import reactor.core.publisher.Flux;
/**
* 流式输出 AI Service
* 返回 Flux<String>,逐字输出
*/
@AiService
public interface StreamingChatAssistant {
@SystemMessage("你是若依系统的智能助手,请用中文回答。")
Flux<String> chat(
@MemoryId String userId,
@UserMessage String message
);
}
7.2 SSE 流式接口
java
package com.ruoyi.ai.controller;
import com.ruoyi.ai.service.StreamingChatAssistant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
/**
* 流式对话接口(SSE)
*/
@RestController
@RequestMapping("/ai")
public class AiStreamController {
@Autowired
private StreamingChatAssistant streamingChatAssistant;
/**
* 流式对话(Server-Sent Events)
* 前端使用 EventSource 接收
*/
@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 发送一次
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;
}
}
7.3 前端接收流式输出
javascript
/**
* 前端:使用 EventSource 接收流式输出
* 实现打字机效果
*/
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);
// 接收每个 token
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();
};
}
八、功能四:RAG 知识库问答
8.1 什么是 RAG?
┌─────────────────────────────────────────────────────────────────────┐
│ RAG(检索增强生成)原理 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 传统 LLM 问答: │
│ 用户问题 → LLM → 回答(只有训练数据,不了解你的业务) │
│ │
│ RAG 问答: │
│ 用户问题 → 向量检索(从知识库找相关内容)→ 拼接到 Prompt → LLM → 回答│
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 知识库构建(离线): │ │
│ │ 文档 → 分块 → 向量化 → 存入向量数据库 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 问答流程(在线): │ │
│ │ 用户问题 → 向量化 → 相似度检索 → 取 Top-K 文档 │ │
│ │ → 拼接到 Prompt → LLM 生成回答 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
8.2 构建知识库
java
package com.ruoyi.ai.config;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* RAG 知识库配置
*/
@Configuration
public class RagConfig {
/**
* 向量化模型(本地,无需 API Key)
* 使用 all-MiniLM-L6-v2,轻量高效
*/
@Bean
public EmbeddingModel embeddingModel() {
return new AllMiniLmL6V2EmbeddingModel();
}
/**
* 向量数据库(内存版,生产环境建议使用 Milvus/Qdrant/PgVector)
*/
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
/**
* 初始化知识库(应用启动时加载文档)
*/
@Bean
public EmbeddingStoreIngestor embeddingStoreIngestor(
EmbeddingModel embeddingModel,
EmbeddingStore<TextSegment> embeddingStore) {
// 文档分块器:每块 500 字符,重叠 50 字符
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
return EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
}
}
8.3 加载若依系统文档
java
package com.ruoyi.ai.service;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 应用启动时加载知识库文档
*/
@Component
public class KnowledgeBaseLoader implements ApplicationRunner {
@Autowired
private EmbeddingStoreIngestor ingestor;
@Override
public void run(ApplicationArguments args) {
// 加载若依系统使用手册(可以从数据库、文件、URL 加载)
List<Document> documents = List.of(
Document.from("""
若依系统用户管理:
1. 进入系统管理 → 用户管理
2. 点击新增按钮,填写用户信息
3. 用户名、昵称为必填项
4. 密码默认为 admin123
5. 可以为用户分配角色
"""),
Document.from("""
若依系统菜单管理:
1. 进入系统管理 → 菜单管理
2. 菜单类型分为:目录、菜单、按钮
3. 目录:一级菜单,不对应具体页面
4. 菜单:对应具体的页面路由
5. 按钮:页面内的操作权限控制
6. 路由地址填写前端路由路径
7. 组件路径填写 Vue 组件相对路径
"""),
Document.from("""
若依系统角色管理:
1. 进入系统管理 → 角色管理
2. 新增角色,填写角色名称和权限字符
3. 为角色分配菜单权限
4. 在用户管理中为用户分配角色
5. 数据权限:全部、本部门、本部门及以下、仅本人
"""),
Document.from("""
若依系统常见问题:
Q: 如何重置用户密码?
A: 在用户管理列表,点击操作列的重置密码按钮,输入新密码即可。
Q: 如何修改系统 Logo?
A: 在系统配置中修改 Logo 路径,或直接替换 /profile/upload 目录下的图片。
Q: 如何开启验证码?
A: 在 application.yml 中设置 ruoyi.captchaEnabled=true
""")
);
// 将文档向量化并存入向量数据库
ingestor.ingest(documents);
System.out.println("✅ 若依知识库加载完成,共 " + documents.size() + " 个文档");
}
}
8.4 定义 RAG AI Service
java
package com.ruoyi.ai.service;
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
/**
* RAG 知识库问答 AI Service
* 自动从知识库检索相关内容,增强回答质量
*/
@AiService
public interface RagAssistant {
@SystemMessage("""
你是若依管理系统的专业客服助手。
请根据提供的知识库内容回答用户问题。
如果知识库中没有相关信息,请如实告知用户,不要编造答案。
回答要简洁、准确、友好。
""")
String chat(
@MemoryId String userId,
@UserMessage String question
);
}
8.5 注册 RAG 检索器
java
package com.ruoyi.ai.config;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RagConfig {
/**
* RAG 内容检索器
* 自动注入到 @AiService 中
*/
@Bean
public ContentRetriever contentRetriever(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3) // 最多检索 3 个相关片段
.minScore(0.6) // 相似度阈值(0-1)
.build();
}
}
8.6 RAG 接口
java
/**
* RAG 知识库问答接口
*/
@PostMapping("/chat/rag")
public AjaxResult ragChat(@RequestParam String question) {
Long userId = SecurityUtils.getUserId();
String answer = ragAssistant.chat(String.valueOf(userId), question);
return AjaxResult.success(answer);
}
效果演示:
用户:如何重置密码?
AI:在若依系统中,重置用户密码的步骤如下:
1. 进入系统管理 → 用户管理
2. 在用户列表中找到需要重置密码的用户
3. 点击操作列的"重置密码"按钮
4. 输入新密码并确认即可
(AI 基于知识库内容回答,而非凭空生成)
九、功能五:Function Calling(工具调用)
9.1 什么是 Function Calling?
传统 AI:只能回答文字问题
Function Calling:AI 可以调用你的 Java 方法,获取实时数据
例如:
用户:现在系统有多少个用户?
AI:(调用 getUserCount() 方法)→ 系统目前共有 1234 个用户
9.2 定义工具类
java
package com.ruoyi.ai.tools;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.ISysMenuService;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.P;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 若依系统工具集
* AI 可以调用这些方法获取实时数据
*/
@Component
public class RuoYiTools {
@Autowired
private ISysUserService userService;
@Autowired
private ISysMenuService menuService;
/**
* 获取系统用户数量
*/
@Tool("获取系统当前用户总数")
public String getUserCount() {
int count = userService.selectUserList(new SysUser()).size();
return "系统当前共有 " + count + " 个用户";
}
/**
* 根据用户名查询用户信息
*/
@Tool("根据用户名查询用户信息")
public String getUserByName(@P("用户名") String userName) {
SysUser user = userService.selectUserByUserName(userName);
if (user == null) {
return "未找到用户名为 " + userName + " 的用户";
}
return String.format("用户信息:用户名=%s,昵称=%s,邮箱=%s,状态=%s",
user.getUserName(),
user.getNickName(),
user.getEmail(),
"0".equals(user.getStatus()) ? "正常" : "停用");
}
/**
* 获取系统菜单数量
*/
@Tool("获取系统菜单总数")
public String getMenuCount() {
int count = menuService.selectMenuList(new SysMenu(), 1L).size();
return "系统当前共有 " + count + " 个菜单";
}
/**
* 获取当前时间
*/
@Tool("获取当前系统时间")
public String getCurrentTime() {
return "当前时间:" + LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
}
9.3 带工具的 AI Service
java
package com.ruoyi.ai.service;
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
/**
* 带工具调用的 AI Service
* 可以调用 RuoYiTools 中的方法
*/
@AiService
public interface SmartAssistant {
@SystemMessage("""
你是若依管理系统的智能助手,你可以:
1. 查询系统实时数据(用户数、菜单数等)
2. 查询用户信息
3. 获取当前时间
当用户询问系统数据时,请主动调用相关工具获取最新数据。
""")
String chat(
@MemoryId String userId,
@UserMessage String message
);
}
效果演示:
用户:系统现在有多少用户?
AI:(自动调用 getUserCount())
系统当前共有 1234 个用户。
用户:帮我查一下 admin 用户的信息
AI:(自动调用 getUserByName("admin"))
用户信息:用户名=admin,昵称=管理员,邮箱=admin@163.com,状态=正常
十、完整配置汇总
10.1 完整 LangChain4jConfig
java
package com.ruoyi.ai.config;
import com.ruoyi.ai.tools.RuoYiTools;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* LangChain4j 完整配置
*/
@Configuration
public class LangChain4jConfig {
/**
* 对话记忆提供者(每个用户独立)
*/
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.build();
}
/**
* 本地向量化模型(无需 API Key)
*/
@Bean
public EmbeddingModel embeddingModel() {
return new AllMiniLmL6V2EmbeddingModel();
}
/**
* 内存向量数据库(生产环境替换为 Milvus/Qdrant)
*/
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
/**
* RAG 内容检索器
*/
@Bean
public ContentRetriever contentRetriever(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.6)
.build();
}
}
10.2 完整 Controller
java
package com.ruoyi.ai.controller;
import com.ruoyi.ai.service.*;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* AI 对话完整 Controller
*/
@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));
}
/**
* 流式对话(SSE)
*/
@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;
}
/**
* RAG 知识库问答
*/
@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));
}
}
十一、常见问题
Q1:JDK 版本不够怎么办?
xml
<!-- 若依默认 JDK 8,LangChain4j 1.x 需要 JDK 17 -->
<!-- 方案一:升级 JDK 到 17(推荐) -->
<!-- 方案二:使用 LangChain4j 0.36.x(支持 JDK 8) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>0.36.2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>0.36.2</version>
</dependency>
Q2:国内无法访问 OpenAI 怎么办?
yaml
# 方案一:使用通义千问(阿里云,国内可用)
langchain4j:
community:
dashscope:
chat-model:
api-key: your-dashscope-api-key
model-name: qwen-plus
# 方案二:使用 Ollama 本地模型(完全离线)
langchain4j:
ollama:
chat-model:
base-url: http://localhost:11434
model-name: qwen2.5:7b
temperature: 0.7
Q3:如何切换不同的 LLM?
java
// LangChain4j 的优势:切换模型只需改配置,代码不变
// 只需修改 application.yml 中的模型配置即可
// AI Service 接口代码完全不需要改动
Q4:生产环境向量数据库推荐?
| 数据库 | 特点 | 推荐场景 |
|---|---|---|
| InMemory | 内存存储,重启丢失 | 开发测试 |
| PgVector | PostgreSQL 扩展,稳定 | 中小规模 |
| Milvus | 专业向量数据库,高性能 | 大规模生产 |
| Qdrant | 轻量,易部署 | 中等规模 |
十二、总结
| 功能 | 实现方式 | 难度 |
|---|---|---|
| 基础对话 | @AiService + ChatModel |
⭐ |
| 多轮对话 | @MemoryId + ChatMemoryProvider |
⭐⭐ |
| 流式输出 | Flux<String> + SSE |
⭐⭐ |
| RAG 知识库 | EmbeddingStore + ContentRetriever |
⭐⭐⭐ |
| 工具调用 | @Tool 注解 |
⭐⭐ |
LangChain4j 的核心优势:
- ✅ 声明式 API:像写 Service 一样开发 AI 功能
- ✅ 模型无关:切换 LLM 只改配置,代码不变
- ✅ Spring Boot 原生集成:自动装配,开箱即用
- ✅ 功能完整:对话记忆、RAG、工具调用、流式输出一应俱全
原创不易,如果对你有帮助,点个赞再走~
关注我,持续分享 Java AI 应用开发实战!