LangChain4j 整合 Spring Boot 完整指南
目录
- 基础整合(纯Java)
- [Spring Boot Starter 整合](#Spring Boot Starter 整合)
- [高阶用法 - AiServices](#高阶用法 - AiServices)
- 流式响应
- 系统提示词
- 用户提示词
- 记忆功能
- [RAG 检索增强生成](#RAG 检索增强生成)
- [Tool 工具调用](#Tool 工具调用)
- [SSE 服务器推送](#SSE 服务器推送)
1. 基础整合(纯Java)
1.1 环境要求
- JDK 17 及以上
- Maven 3.6+
- Spring Boot 3.x
1.2 添加依赖
xml
<!-- langchain4j OpenAI 模块,需要 JDK 17+ -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.0.1</version>
</dependency>
<!-- 日志依赖(可选) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.18</version>
</dependency>
1.3 添加配置文件 application.yml
yaml
langchain4j:
open-ai:
chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${DASHSCOPE_API_KEY:your-api-key-here} # 建议使用环境变量
model-name: qwen-plus
1.4 创建配置类
java
@Configuration
public class ChatModelConfig {
@Value("${langchain4j.open-ai.chat-model.base-url}")
private String baseUrl;
@Value("${langchain4j.open-ai.chat-model.api-key}")
private String apiKey;
@Value("${langchain4j.open-ai.chat-model.model-name}")
private String modelName;
@Bean
public OpenAiChatModel openAiChatModel() {
return OpenAiChatModel.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.modelName(modelName)
.timeout(Duration.ofSeconds(60))
.maxRetries(3)
.logRequests(true) // 记录请求日志,需要添加日志依赖
.logResponses(true) // 记录响应日志,需要添加日志依赖
.build();
}
}
1.5 调用API获取对话内容
java
@RestController
@RequestMapping("/chatModel")
public class ChatModelController {
@Resource
private OpenAiChatModel openAiChatModel;
/**
* 基本用法 - 获取模型对话内容
*
* @param prompt 用户输入的提示词
* @return AI返回的内容
*/
@GetMapping("/content")
public String content(@RequestParam String prompt) {
return openAiChatModel.chat(prompt);
}
}
1.6 日志输出示例
启用日志后,控制台会输出完整的HTTP请求和响应信息:
HTTP request:
- method: POST
- url: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
- headers: [Authorization: Bearer ***], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
"model": "qwen-plus",
"messages": [{
"role": "user",
"content": "hello"
}],
"stream": false
}
HTTP response:
- status code: 200
- body: {
"choices": [{
"message": {
"content": "Hello! How can I assist you today?",
"role": "assistant"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 11,
"completion_tokens": 22,
"total_tokens": 33
}
}
注意 : 日志中API Key会自动脱敏显示为
Bearer ***
2. Spring Boot Starter 整合
使用 Spring Boot Starter 可以大大简化配置,无需手动创建配置类。
2.1 添加依赖
xml
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LangChain4j Spring Boot Starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.1-beta6</version>
</dependency>
</dependencies>
2.2 添加配置文件
yaml
langchain4j:
open-ai:
chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${DASHSCOPE_API_KEY:your-api-key-here}
model-name: qwen-plus
# 可选:开启日志
log-requests: true
log-responses: true
logging:
level:
dev.langchain4j: DEBUG
2.3 创建Controller
java
@RestController
@RequestMapping("/hello")
public class HelloController {
@Resource
private OpenAiChatModel openAiChatModel;
@GetMapping("/test")
public String helloWorld(@RequestParam String prompt) {
return openAiChatModel.chat(prompt);
}
}
3. 高阶用法 - AiServices
AiServices 提供了更强大的功能,支持声明式接口定义。
3.1 添加依赖
xml
<!-- AiServices 所需依赖 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.1-beta6</version>
</dependency>
3.2 基于配置类的方式
3.2.1 定义Service接口
java
public interface ConsultantService {
String chat(String message);
}
3.2.2 创建配置类
java
@Configuration
public class CommonConfig {
@Resource
private OpenAiChatModel openAiChatModel;
@Bean
public ConsultantService consultantService() {
return AiServices.builder(ConsultantService.class)
.chatModel(openAiChatModel)
.build();
}
}
3.2.3 创建Controller
java
@RestController
@RequestMapping("/consultant")
public class ConsultantController {
@Resource
private ConsultantService consultantService;
@GetMapping("/chat")
public String chat(@RequestParam String prompt) {
return consultantService.chat(prompt);
}
}
3.3 声明式方式(推荐)
直接在接口上使用 @AiService 注解,无需配置类:
java
@AiService
public interface ConsultantService {
String chat(String message);
}
如果需要手动指定模型:
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT, // 手动装配模式
chatModel = "openAiChatModel" // 指定ChatModel Bean名称
)
public interface ConsultantService {
String chat(String message);
}
说明:
wiringMode = EXPLICIT:手动指定依赖,需明确指定 chatModelwiringMode = IMPLICIT(默认):自动装配,框架会自动寻找对应的模型
4. 流式响应
流式响应可以实现类似"打字机"效果,逐步返回AI生成的内容。
4.1 添加依赖
xml
<!-- WebFlux 用于支持响应式流 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- LangChain4j Reactor 支持 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.1-beta6</version>
</dependency>
4.2 添加配置
yaml
langchain4j:
open-ai:
# 流式模型配置
streaming-chat-model:
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
api-key: ${DASHSCOPE_API_KEY:your-api-key-here}
model-name: qwen-plus
log-requests: true
log-responses: true
logging:
level:
dev.langchain4j: DEBUG
4.3 定义流式接口
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel" // 指定流式模型
)
public interface ConsultantStreamService {
/**
* 流式对话接口
* 返回 Flux<String> 实现流式输出
*/
Flux<String> chat(String message);
}
4.4 创建Controller
java
@RestController
@RequestMapping("/stream")
public class StreamController {
@Resource
private ConsultantStreamService consultantStreamService;
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam String prompt) {
return consultantStreamService.chat(prompt);
}
}
5. 系统提示词
系统提示词用于设定AI的角色和行为规则。
5.1 使用 @SystemMessage 注解(内联方式)
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel"
)
public interface ConsultantStreamService {
@SystemMessage("你是辉哥的助手小月月,人美心善又多金。")
Flux<String> chat(String message);
}
5.2 从文件加载系统提示词
当系统提示词较长时,建议将其放在单独的文件中:
创建 resources/system.txt 文件:
你是专业的AI志愿填报顾问,可以给用户提供如下功能:
1. 查询目标院校的院校简介
2. 查询目标院校的录取规则
3. 查询目标院校的奖学金设置状况
4. 查询目标院校的食宿条件
5. 查询目标院校招生联系方式
6. 查询目标院校2024年不同专业录取情况
7. 查询热门专业
8. 查询天坑专业
9. 根据学生提供的分数和不同学校以及学校历年录取分数,推荐合适的学校和专业
10. 高考志愿填报一对一沟通预约服务
说明:
- 每次回答完用户问题,最后都加上一句话:如需专业志愿填报指导,建议预约一对一服务。
- 不要提及"根据您提供的信息"这样的话术
- 只回答有关高考志愿填报的问题,其它问题不予回答
在代码中引用:
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel"
)
public interface ConsultantStreamService {
@SystemMessage(fromResource = "system.txt")
Flux<String> chat(String message);
}
6. 用户提示词
用户提示词用于格式化用户输入的消息。
6.1 使用默认变量 it
java
@AiService
public interface ConsultantStreamService {
@UserMessage("请帮我分析以下问题:{{it}}")
Flux<String> chat(String message);
}
6.2 使用自定义变量名 @V
java
@AiService
public interface ConsultantStreamService {
@UserMessage("请帮我分析以下问题:{{question}}")
Flux<String> chat(@V("question") String message);
}
6.3 多个变量的情况
java
@AiService
public interface AnalysisService {
@UserMessage("请分析{{city}}的{{topic}}情况")
String analyze(@V("city") String city, @V("topic") String topic);
}
7. 记忆功能
记忆功能让AI能够记住对话历史,实现上下文关联的对话。
7.1 内存记忆配置
7.1.1 创建 ChatMemory Bean
java
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(20) // 最多保留20条消息
.build();
}
7.1.2 在接口中配置记忆
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel",
chatMemory = "chatMemory" // 指定记忆Bean
)
public interface ConsultantStreamService {
Flux<String> chat(String message);
}
7.2 会话记忆隔离
上面的配置是所有用户共享同一个记忆,实际应用中需要按用户隔离。
7.2.1 创建 ChatMemoryProvider
java
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId) // 每个用户独立的记忆ID
.maxMessages(20)
.build();
}
7.2.2 在接口中使用
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel",
chatMemoryProvider = "chatMemoryProvider" // 使用Provider实现隔离
)
public interface ConsultantStreamService {
@SystemMessage(fromResource = "system.txt")
Flux<String> chat(@MemoryId String chatMemoryId, @UserMessage String message);
}
7.2.3 Controller中使用用户ID
java
@RestController
@RequestMapping("/stream")
public class StreamController {
@Resource
private ConsultantStreamService consultantStreamService;
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(
@RequestParam String userId, // 用户ID用于记忆隔离
@RequestParam String message
) {
return consultantStreamService.chat(userId, message);
}
}
7.3 记忆持久化(Redis)
内存记忆在服务重启后会丢失,可以使用Redis进行持久化。
7.3.1 添加Redis依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
7.3.2 Redis配置
yaml
spring:
data:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
7.3.3 实现 ChatMemoryStore
java
@Service
public class RedisChatMemoryStore implements ChatMemoryStore {
@Autowired
private StringRedisTemplate redisTemplate;
private static final Duration EXPIRE_TIME = Duration.ofDays(1);
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String json = redisTemplate.opsForValue().get(memoryId.toString());
return ChatMessageDeserializer.messagesFromJson(json);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String json = ChatMessageSerializer.messagesToJson(messages);
redisTemplate.opsForValue().set(memoryId.toString(), json, EXPIRE_TIME);
}
@Override
public void deleteMessages(Object memoryId) {
redisTemplate.delete(memoryId.toString());
}
}
7.3.4 配置使用Redis存储
java
@Configuration
public class ChatMemoryConfig {
@Resource
private ChatMemoryStore redisChatMemoryStore;
@Bean
public ChatMemoryProvider chatMemoryProvider() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20)
.chatMemoryStore(redisChatMemoryStore) // 使用Redis存储
.build();
}
}
8. RAG 检索增强生成
RAG(Retrieval-Augmented Generation)可以让AI基于私有知识库回答问题。
8.1 添加依赖
xml
<!-- Easy RAG 依赖,包含文档加载、分割、向量化等功能 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.0.1-beta6</version>
</dependency>
8.2 基础用法 - 加载文档到向量数据库
java
@Configuration
public class RagConfig {
/**
* 构建向量数据库
*/
@Bean
public EmbeddingStore embeddingStore() {
// 加载 classpath:contents 目录下的文档
List<Document> documents = ClassPathDocumentLoader.loadDocuments("contents");
// 创建内存向量存储
InMemoryEmbeddingStore store = new InMemoryEmbeddingStore();
// 将文档向量化并存入
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(store)
.build();
ingestor.ingest(documents);
return store;
}
/**
* 构建内容检索器
*/
@Bean
public ContentRetriever contentRetriever(EmbeddingStore embeddingStore) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.minScore(0.5) // 最小相似度分数
.maxResults(5) // 最多返回5条结果
.build();
}
}
8.3 进阶用法 - 文档分割
直接将整个文档进行embedding效果不佳,需要对文本进行切分:
java
@Bean
public ContentRetriever contentRetriever(EmbeddingStore embeddingStore) {
// 1. 加载PDF文档(使用Apache PDFBox解析器)
List<Document> documents = ClassPathDocumentLoader.loadDocuments(
"contents",
new ApachePdfBoxDocumentParser()
);
// 2. 创建文本分割器
// 参数:chunkSize=500字符,chunkOverlap=100字符
DocumentSplitter documentSplitter = DocumentSplitters.recursive(500, 100);
// 3. 配置Ingestor
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(embeddingStore)
.documentSplitter(documentSplitter) // 使用文本分割器
.build();
ingestor.ingest(documents);
// 4. 返回检索器
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.minScore(0.5)
.maxResults(5)
.build();
}
8.4 在AiService中使用RAG
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel",
chatMemoryProvider = "chatMemoryProvider",
contentRetriever = "contentRetriever" // 配置RAG检索器
)
public interface ConsultantStreamService {
@SystemMessage(fromResource = "system.txt")
Flux<String> chat(@MemoryId String chatMemoryId, @UserMessage String message);
}
9. Tool 工具调用
Tool功能让AI能够调用外部工具(如查询天气、计算等)。
9.1 创建工具类
java
@Component
public class WeatherTool {
/**
* 查询天气工具
* 方法描述非常重要,AI会根据描述判断是否调用此方法
*/
@Tool("查询指定城市的当前天气情况,返回温度和天气状况")
public String getWeather(String city) {
System.out.println("正在查询天气: " + city);
// 实际项目中可以调用第三方天气API
if ("北京".equalsIgnoreCase(city)) {
return "北京今天晴朗,气温 25°C";
} else if ("上海".equalsIgnoreCase(city)) {
return "上海今天多云,气温 22°C";
}
return "暂时无法查询 " + city + " 的天气";
}
/**
* 计算工具
*/
@Tool("计算两个数字的乘积")
public double multiply(double a, double b) {
return a * b;
}
}
9.2 在AiService中配置工具
java
@AiService(
wiringMode = AiServiceWiringMode.EXPLICIT,
streamingChatModel = "openAiStreamingChatModel",
chatMemoryProvider = "chatMemoryProvider",
contentRetriever = "contentRetriever",
tools = "weatherTool" // 指定工具Bean名称
)
public interface ConsultantStreamService {
Flux<String> chat(@MemoryId String chatMemoryId, @UserMessage String message);
}
9.3 动态工具提供(高级)
根据上下文动态决定是否提供某个工具:
java
@Component
public class ContextAwareToolProvider implements ToolProvider {
private final WeatherTool weatherTool;
public ContextAwareToolProvider(WeatherTool weatherTool) {
this.weatherTool = weatherTool;
}
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
String userMessage = extractUserMessage(request);
ToolProviderResult.Builder builder = ToolProviderResult.builder();
// 根据用户消息动态决定是否提供天气工具
if (needsWeatherTool(userMessage)) {
try {
Method method = WeatherTool.class.getMethod("getWeather", String.class);
builder.add(
ToolSpecifications.toolSpecificationFrom(method),
new DefaultToolExecutor(weatherTool, method)
);
System.out.println("✅ 检测到天气相关查询,启用 WeatherTool");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
System.out.println("❌ 未检测到天气相关查询,不启用 WeatherTool");
}
return builder.build();
}
private String extractUserMessage(ToolProviderRequest request) {
if (request == null || request.userMessage() == null) {
return "";
}
return request.userMessage().singleText().toLowerCase();
}
private boolean needsWeatherTool(String message) {
if (message == null || message.isEmpty()) {
return false;
}
String[] weatherKeywords = {
"天气", "weather", "气温", "温度",
"下雨", "晴天", "阴天", "多云",
"冷", "热", "凉爽", "温暖"
};
for (String keyword : weatherKeywords) {
if (message.contains(keyword)) {
return true;
}
}
return false;
}
}
9.4 配置动态工具
java
@Bean
public ConsultantService consultantService(
ChatModel openAiChatModel,
StreamingChatModel openAiStreamingChatModel,
ChatMemoryProvider chatMemoryProvider,
ContentRetriever contentRetriever,
ToolProvider toolProvider) {
return AiServices.builder(ConsultantService.class)
.chatModel(openAiChatModel)
.streamingChatModel(openAiStreamingChatModel)
.chatMemoryProvider(chatMemoryProvider)
.contentRetriever(contentRetriever)
.toolProvider(toolProvider) // 使用动态工具提供者
.build();
}
10. SSE 服务器推送
10.1 添加sse客户端依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mcp</artifactId>
<version>1.0.1-beta6</version>
</dependency>
10.2 创建配置类
@Configuration
public class McpClientConfig {
@Bean(destroyMethod = "close")
public McpClient userScoreMcpClient(@Value("${mcp.sse-server.url}") String sseUrl,
@Value("${mcp.sse-server.log-requests:true}") boolean logRequests,
@Value("${mcp.sse-server.log-responses:true}") boolean logResponses) {
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl(sseUrl)
.timeout(Duration.ofSeconds(30))
.logRequests(logRequests)
.logResponses(logResponses)
.build();
return new DefaultMcpClient.Builder()
.clientName("langchain4j-demo")
.clientVersion("1.0.0")
.transport(transport)
.toolExecutionTimeout(Duration.ofSeconds(30))
.build();
}
@Bean
public McpToolProvider mcpToolProvider(McpClient userScoreMcpClient) {
return McpToolProvider.builder()
.mcpClients(userScoreMcpClient)
.filterToolNames("getScore")
.build();
}
}
@Component
public class ContextAwareToolProvider implements ToolProvider {
private final WeatherTool weatherTool;
private final McpToolProvider mcpToolProvider;
public ContextAwareToolProvider(WeatherTool weatherTool, McpToolProvider mcpToolProvider) {
this.weatherTool = weatherTool;
this.mcpToolProvider = mcpToolProvider;
}
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
// 获取用户消息
String userMessage = extractUserMessage(request);
ToolProviderResult.Builder builder = ToolProviderResult.builder();
ToolProviderResult mcpTools = mcpToolProvider.provideTools(request);
builder.addAll(mcpTools.tools());
// 根据消息内容判断是否需要天气工具
if (needsWeatherTool(userMessage)) {
Class<WeatherTool> weatherToolClass = WeatherTool.class;
try {
Method method = weatherToolClass.getMethod("getWeather", String.class);
builder.add(ToolSpecifications.toolSpecificationFrom(method), new DefaultToolExecutor(weatherTool, method));
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
System.out.println("✅ 检测到天气相关查询,启用 WeatherTool");
} else {
System.out.println("❌ 未检测到天气相关查询,不启用 WeatherTool");
}
// 返回工具结果
return builder.build();
}
/**
* 从请求中提取用户消息
*/
private String extractUserMessage(ToolProviderRequest request) {
if (request == null || request.userMessage() == null) {
return "";
}
return request.userMessage().singleText().toLowerCase();
}
/**
* 判断是否需要天气工具
* 通过关键词匹配来判断用户是否在询问天气
*/
private boolean needsWeatherTool(String message) {
if (message == null || message.isEmpty()) {
return false;
}
// 定义天气相关的关键词
String[] weatherKeywords = {
"天气", "weather", "气温", "温度",
"下雨", "晴天", "阴天", "多云",
"冷", "热", "凉爽", "温暖",
"摄氏度", "°c", "度"
};
// 检查消息中是否包含任何天气关键词
for (String keyword : weatherKeywords) {
if (message.contains(keyword.toLowerCase())) {
return true;
}
}
return false;
}
}