九、内容安全与合规:审核组件
前面已经掌握了构建智能知识库的核心技能。但是,当你的 AI 应用走向生产环境时,有一个重要问题必须考虑:内容安全。
AI 模型可能生成不合适的内容(例如暴力、色情、仇恨言论),也可能被用户恶意利用来诱导模型输出有害信息。因此,为你的 AI 应用加上“护栏”——内容审核机制,是至关重要的。
LangChain4j 提供了专门的审核组件,帮助你自动检测和过滤不安全的内容。本章将带你学习如何使用这些组件,确保你的 AI 应用安全合规。
9.1 ModerationModel 的作用
ModerationModel(审核模型)是一种特殊的模型,专门用于判断一段文本是否包含不安全内容。它通常返回一个分类结果,例如文本是否涉及仇恨言论、自残、暴力等。
LangChain4j 内置了对 OpenAI 审核模型的支持(也可以扩展其他服务商)。OpenAI 的审核模型可以识别以下类别:
hate:仇恨言论hate/threatening:仇恨言论且带有威胁self-harm:自残相关内容sexual:色情内容sexual/minors:涉及未成年人的色情内容violence:暴力内容violence/graphic:血腥暴力内容
你可以用审核模型在用户输入和AI输出两个阶段进行检查:
- 预审核:在用户消息发送给 AI 之前,先检查是否违规。如果违规,可以拒绝回答或给出警告。
- 后审核:在 AI 生成回答之后,检查回答内容是否安全。如果不安全,可以屏蔽回答或返回默认消息。
9.2 使用 OpenAiModerationModel 进行内容审核
LangChain4j 提供了 OpenAiModerationModel 类,用于调用 OpenAI 的审核接口。它需要 API 密钥(可以使用与聊天模型相同的密钥)。
9.2.1 引入依赖
如果你还没有添加 OpenAI 的依赖,请确保项目中有:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
9.2.2 创建审核模型实例
import dev.langchain4j.model.moderation.ModerationModel;
import dev.langchain4j.model.openai.OpenAiModerationModel;
ModerationModel moderationModel = OpenAiModerationModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1") // 使用演示端点
.apiKey("demo")
.build();
注意:OpenAI 的审核接口通常不需要单独计费,但使用演示端点可能有限制。生产环境建议使用真实 OpenAI API 密钥。
9.2.3 审核一段文本
审核模型返回一个 Moderation 对象,包含是否违规以及详细的分类结果。
import dev.langchain4j.model.moderation.Moderation;
String userInput = "我想自杀,活着没意思。";
Moderation moderation = moderationModel.moderate(userInput).content();
if (moderation.flagged()) {
System.out.println("⚠️ 检测到违规内容!");
System.out.println("违规类别:" + moderation.categories());
System.out.println("分数:" + moderation.categoryScores());
} else {
System.out.println("✅ 内容安全,可继续处理。");
}
运行后,如果输入包含自残倾向,flagged() 会返回 true,我们可以拒绝该请求。
9.2.4 对用户输入进行预审核
在实际应用中,我们可以在调用 AI 之前先审核用户消息:
String userMessage = "我要骂人,你是笨蛋!";
Moderation moderation = moderationModel.moderate(userMessage).content();
if (moderation.flagged()) {
System.out.println("对不起,您的消息包含不当内容,请文明交流。");
} else {
// 安全,继续调用 AI
String answer = assistant.chat(userMessage);
System.out.println("AI: " + answer);
}
9.2.5 对 AI 输出进行后审核
同样,我们可以在 AI 返回回答后,再审核一次:
String answer = assistant.chat(userMessage);
Moderation outputModeration = moderationModel.moderate(answer).content();
if (outputModeration.flagged()) {
System.out.println("AI 生成的内容可能存在风险,已被拦截。");
} else {
System.out.println("AI: " + answer);
}
9.3 声明式审核:@Moderate 注解
手动调用审核模型虽然灵活,但略显繁琐。LangChain4j 提供了更优雅的方式:@Moderate 注解。你可以在 AI Service 的方法上添加此注解,框架会自动对用户输入和 AI 输出进行审核。
9.3.1 基本用法
import dev.langchain4j.service.Moderate;
interface SafeAssistant {
@Moderate
String chat(String userMessage);
}
然后构建 AI Service 时,需要传入审核模型:
SafeAssistant assistant = AiServices.builder(SafeAssistant.class)
.chatLanguageModel(chatModel)
.moderationModel(moderationModel) // 注入审核模型
.build();
String response = assistant.chat("你好"); // 自动审核输入和输出
当用户输入或 AI 输出触发审核时,框架会抛出 ModerationException 异常。你可以捕获该异常并处理。
9.3.2 处理审核异常
try {
String response = assistant.chat("我想自杀");
System.out.println(response);
} catch (dev.langchain4j.model.moderation.ModerationException e) {
System.out.println("内容不合规:" + e.getMessage());
}
9.3.3 自定义审核行为
默认情况下,@Moderate 会同时审核用户输入和 AI 输出。如果需要只审核输入或只审核输出,可以配置 ModerationModel 的行为,但目前注解本身不支持细分。如果你需要精细控制,可以结合手动审核。
9.4 实践:给聊天助手加上审核功能
现在我们把审核功能集成到之前的多用户聊天助手中,实现一个安全合规的客服系统。
9.4.1 定义带审核的接口
interface SafeCustomerService {
@SystemMessage("你是一个友好的客服助手。")
@Moderate
String chat(@MemoryId String userId, String userMessage);
}
9.4.2 构建 AI Service 并处理审核异常
import dev.langchain4j.model.moderation.ModerationException;
public class SafeChatDemo {
public static void main(String[] args) {
// 创建模型
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.build();
// 创建审核模型
OpenAiModerationModel moderationModel = OpenAiModerationModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.build();
// 创建记忆提供者
var memories = new ConcurrentHashMap<String, ChatMemory>();
ChatMemoryProvider memoryProvider = memoryId ->
memories.computeIfAbsent((String) memoryId, id ->
MessageWindowChatMemory.builder().id(id).maxMessages(10).build());
// 构建 AI Service
SafeCustomerService assistant = AiServices.builder(SafeCustomerService.class)
.chatLanguageModel(chatModel)
.moderationModel(moderationModel)
.chatMemoryProvider(memoryProvider)
.build();
// 模拟用户对话
Scanner scanner = new Scanner(System.in);
System.out.println("安全客服助手启动(输入 'exit' 退出)");
while (true) {
System.out.print("用户ID: ");
String userId = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(userId)) break;
System.out.print("消息: ");
String message = scanner.nextLine();
if ("exit".equalsIgnoreCase(message)) break;
try {
String response = assistant.chat(userId, message);
System.out.println("AI: " + response);
} catch (ModerationException e) {
System.out.println("⚠️ 内容不合规,已被拦截。原因:" + e.getMessage());
}
System.out.println();
}
scanner.close();
}
}
9.4.3 测试
尝试输入正常内容,如“你好”,AI 会正常回复。 尝试输入敏感内容,如“我要自杀”,程序会捕获异常并输出提示信息。
流程图:带审核的对话流程
sequenceDiagram
participant 用户
participant 审核组件
participant AI服务
participant 大模型
用户->>审核组件: 发送消息
审核组件->>审核组件: 检查消息是否安全
alt 消息不安全
审核组件-->>用户: 返回拒绝信息(抛异常)
else 消息安全
审核组件->>AI服务: 传递安全消息
AI服务->>大模型: 调用模型生成回答
大模型-->>AI服务: 返回回答
AI服务->>审核组件: 检查回答是否安全
alt 回答不安全
审核组件-->>用户: 拦截回答,返回警告
else 回答安全
审核组件-->>用户: 返回回答
end
end
9.5 本章小结
- 审核模型的作用:识别不当内容,保护应用安全。
- OpenAiModerationModel 的使用:手动审核文本,对输入输出分别检查。
- @Moderate 注解:声明式审核,自动拦截不安全内容。
- 实践:为聊天助手添加审核功能,捕获异常并友好提示。
十、多模态探索:处理图片与文件
到目前为止,我们所有的交互都是基于文本的。但现实世界的信息是多样的——图片、PDF、音频、视频……大语言模型正在向多模态进化,能够理解和生成非文本内容。LangChain4j 也紧跟潮流,提供了初步的多模态支持。
本章将带你探索如何让 AI “看懂”图片,以及如何处理 PDF 等文件。虽然目前多模态功能还在快速发展中,但掌握基础用法可以为你打开更多应用场景的大门。
10.1 LangChain4j 对多模态的支持现状
目前,LangChain4j 对多模态的支持主要体现在以下几个方面:
- 图像输入:可以通过
ImageContent将图片作为消息的一部分发送给支持多模态的模型(如 OpenAI 的 GPT-4 Vision、Google 的 Gemini Pro Vision 等)。 - 文件输入:某些模型支持接收 PDF、音频、视频等文件,LangChain4j 提供了对应的
PdfFileContent、AudioFileContent、VideoFileContent等类。 - 多模态输出:部分模型可以生成图像,但 LangChain4j 尚未直接封装,仍需要通过原始 API 调用。
需要注意的是,多模态功能需要模型本身支持。本章示例将使用 OpenAI 的 GPT-4 Vision 模型(即 gpt-4-vision-preview 或更新版本)。由于演示端点可能不支持多模态,你需要使用真实的 OpenAI API 密钥。
10.2 发送包含图片的消息(ImageContent)
要让 AI 描述一张图片,我们需要将图片以 ImageContent 的形式嵌入到用户消息中。图片可以来自 URL 或本地文件。
10.2.1 准备依赖
确保你的项目中已经包含了 OpenAI 集成(用于支持 GPT-4 Vision)和必要的图片处理库(如果需要从本地文件加载)。如果你使用 Maven:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
10.2.2 创建支持多模态的模型
我们需要创建一个 OpenAiChatModel 实例,并指定支持视觉的模型名称(如 gpt-4-turbo 或 gpt-4-vision-preview)。同时,需要提供真实的 API 密钥(演示端点不支持多模态)。
OpenAiChatModel visionModel = OpenAiChatModel.builder()
.apiKey("你的真实OpenAI密钥") // 需要替换为真实密钥
.modelName("gpt-4-turbo") // 或 "gpt-4-vision-preview"
.build();
注意:
gpt-4-vision-preview是早期的视觉模型版本,现在通常使用gpt-4-turbo(它内置了视觉能力)。请参考 OpenAI 官方文档确认当前可用的视觉模型名称。
10.2.3 构造包含图片的用户消息
我们使用 UserMessage.from(ImageContent) 或 UserMessage.from(TextContent, ImageContent) 来创建包含图片的消息。
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
// 方式1:只有图片,没有文字
UserMessage messageWithImage = UserMessage.from(ImageContent.from("https://example.com/cat.jpg"));
// 方式2:文字 + 图片
UserMessage messageWithTextAndImage = UserMessage.from(
TextContent.from("请描述这张图片:"),
ImageContent.from("https://example.com/cat.jpg")
);
ImageContent.from() 可以接受:
- 图片的 URL(字符串)
- 本地图片文件的路径(会读取并 Base64 编码)
- 直接传入
Image对象(需要手动加载)
对于本地图片,可以这样:
import java.nio.file.Paths;
ImageContent localImage = ImageContent.from(Paths.get("/path/to/local/image.jpg"));
10.2.4 调用模型获取描述
将构造好的消息传入模型,获取回复。
UserMessage userMessage = UserMessage.from(
TextContent.from("这张图片里有什么?"),
ImageContent.from("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg")
);
List<ChatMessage> messages = List.of(userMessage);
String response = visionModel.chat(messages);
System.out.println("AI 描述:\n" + response);
10.2.5 完整示例:图片描述助手
创建一个类 ImageDescriptionDemo.java:
package com.example;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.util.List;
public class ImageDescriptionDemo {
public static void main(String[] args) {
// 使用真实的 OpenAI 密钥(演示端点不支持视觉)
OpenAiChatModel visionModel = OpenAiChatModel.builder()
.apiKey("sk-...") // 替换为你的密钥
.modelName("gpt-4-turbo")
.build();
// 构造带图片的消息
UserMessage userMessage = UserMessage.from(
TextContent.from("请详细描述这张图片的内容,包括动物、环境、颜色等。"),
ImageContent.from("https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg")
);
// 调用模型
String response = visionModel.chat(List.of(userMessage));
System.out.println("AI 描述:\n" + response);
}
}
运行后,AI 会返回对猫图片的描述。如果图片 URL 不可用,可以替换为其他公开图片。
10.3 处理 PDF 等文件
除了图片,LangChain4j 还支持将 PDF 文件作为消息发送给某些模型(例如 GPT-4 的 file-search 功能)。但需要注意的是,模型本身通常不直接“看懂” PDF,而是将其内容(文本)提取后作为上下文。因此,更常见的做法是先用文档加载器提取 PDF 文本,然后作为普通文本发送。
不过,LangChain4j 提供了 PdfFileContent 类,用于表示 PDF 文件内容。如果你的模型支持直接处理 PDF(如 OpenAI 的 Assistants API),可以这样使用。
10.3.1 使用 PdfFileContent
import dev.langchain4j.data.message.PdfFileContent;
import dev.langchain4j.data.message.UserMessage;
import java.nio.file.Paths;
PdfFileContent pdfContent = PdfFileContent.from(Paths.get("/path/to/document.pdf"));
UserMessage userMessage = UserMessage.from(
TextContent.from("请总结这个 PDF 文件的内容:"),
pdfContent
);
然而,目前标准的 OpenAiChatModel 并不直接支持 PDF 文件输入(除非通过 Assistants API)。因此,这段代码可能无法直接工作,需要配合特定的模型实现。对于初学者,我们推荐使用传统方式:先用文档加载器提取 PDF 文本,再作为文本消息发送。
10.3.2 提取 PDF 文本后发送(推荐方式)
你需要添加 PDF 解析库,如 Apache PDFBox 或 LangChain4j 内置的 ApachePdfDocumentParser(需要额外依赖)。
首先,添加依赖:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
<version>${langchain4j.version}</version>
</dependency>
然后使用 ApachePdfDocumentParser 提取文本:
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import java.nio.file.Paths;
ApachePdfBoxDocumentParser parser = new ApachePdfBoxDocumentParser();
Document pdfDocument = parser.parse(Paths.get("/path/to/document.pdf"));
String pdfText = pdfDocument.text();
// 然后将 pdfText 作为普通文本发送
UserMessage userMessage = UserMessage.from("请总结以下内容:" + pdfText);
String summary = visionModel.chat(List.of(userMessage));
System.out.println(summary);
这种方式虽然简单,但受限于模型上下文窗口,不适合非常长的 PDF。对于长文档,应该采用 RAG 方式(分割、检索、生成),这在前面章节已经介绍过。
10.4 实践:让 AI 描述一张图片的内容
为了巩固所学,我们做一个完整的多模态实践:编写一个程序,让用户输入图片路径或 URL,AI 返回对图片的描述。
10.4.1 代码实现
创建 InteractiveImageDescriber.java:
package com.example;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Scanner;
public class InteractiveImageDescriber {
public static void main(String[] args) {
// 配置模型(使用真实密钥)
OpenAiChatModel visionModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY")) // 从环境变量读取密钥
.modelName("gpt-4-turbo")
.build();
Scanner scanner = new Scanner(System.in);
System.out.println("图片描述助手启动。请输入图片路径或URL(输入 'exit' 退出):");
while (true) {
System.out.print("> ");
String input = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(input)) {
break;
}
try {
// 判断是 URL 还是本地文件
ImageContent imageContent;
if (input.startsWith("http://") || input.startsWith("https://")) {
imageContent = ImageContent.from(input);
} else {
// 假设是本地文件路径
Path path = Paths.get(input);
imageContent = ImageContent.from(path);
}
UserMessage userMessage = UserMessage.from(
TextContent.from("请用中文详细描述这张图片的内容,包括主体、颜色、动作、背景等。"),
imageContent
);
System.out.println("正在分析图片,请稍候...");
String description = visionModel.chat(List.of(userMessage));
System.out.println("AI描述:\n" + description);
} catch (Exception e) {
System.err.println("处理图片时出错:" + e.getMessage());
}
System.out.println();
}
scanner.close();
}
}
10.4.2 运行说明
- 设置环境变量
OPENAI_API_KEY为你的真实 OpenAI 密钥。 - 运行程序。
- 输入一张图片的 URL(例如
https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg)或本地图片的绝对路径。 - 等待 AI 返回描述。
示例输出:
> https://upload.wikimedia.org/wikipedia/commons/3/3a/Cat03.jpg
正在分析图片,请稍候...
AI描述:
这张图片中是一只可爱的虎斑猫。它有着黄绿色的眼睛和粉色的鼻子,毛色以棕色、黑色和白色相间,形成典型的虎斑纹。猫咪正坐在一个浅色的木质地板上,背景是模糊的室内环境,可以看到一些家具和植物。它的姿态放松,耳朵竖起,目光直视镜头,显得非常好奇和警觉。整体画面色调温暖,突出了猫咪的可爱和灵动。
10.4.3 注意事项
- 模型支持:确保你使用的 OpenAI 账户有权限访问 GPT-4 Vision 模型。某些旧账户可能需要单独开通。
- 图片大小:图片过大会消耗更多 Token,甚至超出限制。LangChain4j 会自动压缩图片吗?不会。你需要自行控制图片大小,或使用 URL 形式(模型通常会下载并处理)。
- 成本:视觉模型调用成本高于纯文本模型,请留意 Token 消耗。
10.5 本章小结
- 图像输入:使用
ImageContent将图片发送给支持视觉的模型,让 AI 描述图片内容。 - 文件处理:了解了
PdfFileContent的存在,并掌握了用传统方式提取 PDF 文本后发送。 - 实践:编写了交互式图片描述程序,体验了多模态交互的魅力。
多模态是 AI 发展的重要方向,LangChain4j 正在不断丰富这方面的支持。未来,你可能可以用它构建看图说话、图像问答、文档分析等更强大的应用。
十一、监控与扩展:事件监听
随着 AI 应用逐渐复杂,你可能会想知道:
- 每次请求花了多长时间?
- 消耗了多少 Token?(关系到成本)
- 如果出错了,错误信息是什么?
- 能不能记录所有请求和响应,用于调试或分析?
这些需求都可以通过 事件监听器(Listener) 来实现。LangChain4j 提供了 AiServiceListener 接口,允许你在 AI Service 调用的各个阶段插入自定义逻辑,比如日志记录、指标收集、耗时统计等。
本章将带你掌握监听器的使用,让你的应用具备可观测性。
11.1 为什么需要监听?—— 日志、监控、追踪
想象一下,你的 AI 助手已经上线,每天有成千上万用户使用。这时,你可能面临以下问题:
- 性能问题:某个用户的请求特别慢,是网络问题还是模型问题?
- 成本失控:Token 消耗突然飙升,是哪个用户、哪个问题导致的?
- 调试困难:用户反馈 AI 回答错误,但无法复现当时的上下文。
- 安全审计:需要记录所有用户输入和 AI 输出,以备合规检查。
监听器正是为了解决这些问题而生。它像“摄像头”一样,记录下每次调用的详细信息。
监听器工作示意图:
graph LR
subgraph AI Service 调用生命周期
A[开始调用] --> B[请求构建]
B --> C[发送给模型]
C --> D[收到响应]
D --> E[结束调用]
end
subgraph 监听器回调
L1[onRequest<br>请求开始]
L2[onResponse<br>成功响应]
L3[onError<br>出错]
end
A -.-> L1
C -.-> L2
C -.-> L3
D -.-> L2
11.2 实现 AiServiceListener 接口
AiServiceListener 接口定义在 dev.langchain4j.service 包中,包含以下主要方法:
public interface AiServiceListener {
default void onRequest(AiServiceRequest request) { }
default void onResponse(AiServiceResponse response) { }
default void onError(AiServiceError error) { }
}
onRequest:在请求发送给模型之前调用。你可以在这里获取用户消息、系统消息、工具定义等信息。onResponse:在成功收到模型响应后调用。你可以获取模型回复、Token 用量、检索到的文档等。onError:在调用过程中发生异常时调用(包括网络错误、模型返回错误、审核拦截等)。
每个回调方法都提供了对应的上下文对象,包含丰富的信息。
11.2.1 自定义监听器示例
创建一个简单的监听器,打印日志:
import dev.langchain4j.service.AiServiceListener;
import dev.langchain4j.service.AiServiceRequest;
import dev.langchain4j.service.AiServiceResponse;
import dev.langchain4j.service.AiServiceError;
public class LoggingListener implements AiServiceListener {
@Override
public void onRequest(AiServiceRequest request) {
System.out.println(">>> 请求开始");
System.out.println("用户消息: " + request.userMessage());
System.out.println("系统消息: " + request.systemMessage());
System.out.println("记忆中的消息数: " + request.memory().messages().size());
}
@Override
public void onResponse(AiServiceResponse response) {
System.out.println("<<< 请求成功");
System.out.println("AI回复: " + response.aiMessage().text());
System.out.println("Token用量: " + response.tokenUsage());
System.out.println("检索到的文档: " + response.sources());
}
@Override
public void onError(AiServiceError error) {
System.err.println("!!! 请求出错");
System.err.println("错误信息: " + error.message());
System.err.println("异常: " + error.error());
}
}
11.3 注册监听器到 AI Service
监听器需要通过 AiServices 的 withListeners() 方法注册。可以注册一个或多个监听器。
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.withListeners(new LoggingListener()) // 注册监听器
.build();
如果要注册多个监听器,可以传入多个:
.withListeners(listener1, listener2, listener3)
监听器会按照传入顺序依次调用。
11.4 实践:记录每次请求的耗时和 Token 用量
让我们构建一个实用的监听器,它可以:
- 记录每次请求的耗时(从
onRequest到onResponse/onError) - 记录 Token 用量(输入、输出、总计)
- 将日志写入文件或控制台,便于后续分析。
11.4.1 定义带记忆的 AI Service 接口
为了演示更真实的场景,我们使用一个带记忆的助手接口。
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
interface MonitoredAssistant {
@SystemMessage("你是一个乐于助人的助手。")
String chat(@MemoryId int userId, String message);
}
11.4.2 实现监控监听器
我们将实现一个监听器,它使用 ThreadLocal 或实例变量来记录开始时间,但要注意监听器实例是单例的,多个请求会并发执行,因此需要确保线程安全。这里我们使用 ThreadLocal 存储每个请求的开始时间。
import dev.langchain4j.service.*;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;
public class MonitoringListener implements AiServiceListener {
// 线程安全的 Map,存储每个请求的开始时间
private final ThreadLocal<Instant> startTime = new ThreadLocal<>();
@Override
public void onRequest(AiServiceRequest request) {
startTime.set(Instant.now());
System.out.println("[监控] 请求开始于: " + startTime.get());
System.out.println("[监控] 用户ID: " + request.memoryId());
System.out.println("[监控] 用户消息: " + request.userMessage().singleText());
}
@Override
public void onResponse(AiServiceResponse response) {
Instant end = Instant.now();
Instant start = startTime.get();
Duration duration = Duration.between(start, end);
startTime.remove(); // 清理
System.out.println("[监控] 请求成功,耗时: " + duration.toMillis() + " ms");
if (response.tokenUsage() != null) {
System.out.println("[监控] Token用量 - 输入: " + response.tokenUsage().inputTokenCount()
+ ", 输出: " + response.tokenUsage().outputTokenCount()
+ ", 总计: " + response.tokenUsage().totalTokenCount());
}
if (response.sources() != null && !response.sources().isEmpty()) {
System.out.println("[监控] 引用文档数: " + response.sources().size());
}
}
@Override
public void onError(AiServiceError error) {
Instant end = Instant.now();
Instant start = startTime.get();
Duration duration = Duration.between(start, end);
startTime.remove();
System.err.println("[监控] 请求失败,耗时: " + duration.toMillis() + " ms");
System.err.println("[监控] 错误: " + error.message());
}
}
11.4.3 构建并测试
创建主类 MonitoringDemo.java:
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
public class MonitoringDemo {
public static void main(String[] args) {
// 模型
OpenAiChatModel model = OpenAiChatModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.build();
// 记忆提供者
var memories = new ConcurrentHashMap<Object, dev.langchain4j.memory.ChatMemory>();
var memoryProvider = (dev.langchain4j.memory.ChatMemoryProvider) memoryId ->
memories.computeIfAbsent(memoryId, id ->
MessageWindowChatMemory.builder().id(id).maxMessages(10).build());
// 创建监听器
MonitoringListener listener = new MonitoringListener();
// 构建 AI Service
MonitoredAssistant assistant = AiServices.builder(MonitoredAssistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryProvider)
.withListeners(listener)
.build();
// 模拟用户对话
Scanner scanner = new Scanner(System.in);
System.out.println("监控助手启动(输入用户ID和消息,如 '1001 你好')");
System.out.println("输入 'exit' 退出");
while (true) {
System.out.print("> ");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) break;
String[] parts = input.split(" ", 2);
if (parts.length < 2) {
System.out.println("格式错误,请使用 '用户ID 消息'");
continue;
}
int userId = Integer.parseInt(parts[0]);
String message = parts[1];
try {
String response = assistant.chat(userId, message);
System.out.println("AI: " + response);
} catch (Exception e) {
System.err.println("发生异常: " + e.getMessage());
}
System.out.println();
}
scanner.close();
}
}
11.4.4 运行观察
运行程序,输入类似 1 你好,你会看到类似以下的监控输出:
[监控] 请求开始于: 2025-04-07T10:15:30.123Z
[监控] 用户ID: 1
[监控] 用户消息: 你好
[监控] 请求成功,耗时: 2345 ms
[监控] Token用量 - 输入: 50, 输出: 120, 总计: 170
AI: 你好!有什么可以帮你的吗?
11.5 监听器的高级应用
11.5.1 记录完整对话日志
你可以将每次请求的输入、输出、Token 等信息存储到数据库或日志文件,用于后续分析。例如,在 onResponse 中插入一条数据库记录。
11.5.2 动态调整行为
监听器可以修改请求吗?不可以。监听器是只读的,主要用于观察和记录。如果你需要修改请求(例如动态添加系统消息),应该在构建 AI Service 时通过其他方式实现(如 SystemMessageProvider)。
11.5.3 多监听器的执行顺序
如果注册了多个监听器,它们会按注册顺序执行。例如,先执行日志监听器,再执行监控监听器。
11.5.4 与 Micrometer 集成
你可以将监听器与 Micrometer(Spring Boot 的监控库)集成,将 Token 用量、耗时等指标导出到 Prometheus 等监控系统。
示例(伪代码):
@Override
public void onResponse(AiServiceResponse response) {
if (response.tokenUsage() != null) {
Counter.builder("ai.token.usage")
.tag("type", "input")
.register(meterRegistry)
.increment(response.tokenUsage().inputTokenCount());
// ... 类似处理输出 token
}
}
11.6 本章小结
- 事件监听器的作用:监控、日志、度量。
- AiServiceListener 接口:三个主要回调方法。
- 实现自定义监听器:记录耗时和 Token 用量。
- 注册监听器:通过
withListeners()方法。 - 实践:构建了一个带监控的助手,并观察输出。
十二、与 Spring Boot 生态集成
在前面的章节中,我们一直在 main 方法里运行示例,这在学习和测试阶段非常方便。但在实际企业开发中,我们通常使用 Spring Boot 这样的框架来构建可维护、可扩展的 Web 应用。将 LangChain4j 与 Spring Boot 集成,可以让你享受 Spring 生态的便利:依赖注入、自动配置、配置文件管理、监控等。
本章将带你一步步将 LangChain4j 融入 Spring Boot 项目,最终构建一个提供 REST API 的知识库问答服务。
12.1 为什么要在 Spring Boot 中使用 LangChain4j?
- 配置集中管理:API 密钥、模型名称、超时等配置可以写在
application.yml中,而不是散落在代码里。 - 依赖注入:AI Service、工具类、检索器等都可以定义为 Spring Bean,通过
@Autowired或构造函数注入,便于单元测试和替换。 - 自动配置:LangChain4j 提供了 Spring Boot Starter,可以自动创建常用的 Bean(如
ChatLanguageModel),减少样板代码。 - 与 Web 层无缝整合:通过 Controller 暴露 REST API,让前端或其他服务调用你的 AI 能力。
- 利用 Spring 生态:可以结合 Spring Security 做权限控制,结合 Spring Cache 做结果缓存,结合 Spring Actuator 做健康检查。
12.2 引入 LangChain4j Spring Boot Starter
首先,创建一个 Spring Boot 项目(可以使用 Spring Initializr 快速生成),并添加以下依赖:
Maven (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version> <!-- 使用较新版本,Java 17+ -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>langchain4j-springboot-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<langchain4j.version>1.0.0-beta3</langchain4j.version>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- LangChain4j Spring Boot Starter -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- 如果你需要使用 OpenAI 模型,引入对应集成(starter 会自动引入核心,但模型集成需单独加) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- 如果需要本地嵌入模型,添加(可选) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- 为了简化代码,引入 Lombok(可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Gradle (build.gradle)
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'dev.langchain4j:langchain4j-spring-boot-starter:1.0.0-beta3'
implementation 'dev.langchain4j:langchain4j-open-ai-spring-boot-starter:1.0.0-beta3'
implementation 'dev.langchain4j:langchain4j-embeddings-all-minilm-l6-v2:1.0.0-beta3'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
test {
useJUnitPlatform()
}
注意:langchain4j-spring-boot-starter 是核心自动配置模块,它本身不包含具体模型集成。你需要根据使用的模型引入对应的 starter,例如 langchain4j-open-ai-spring-boot-starter 或 langchain4j-ollama-spring-boot-starter 等。
12.3 在 application.yml 中配置模型和 API 密钥
在 src/main/resources/application.yml 中配置 OpenAI 模型参数。如果你使用演示端点,可以配置如下:
langchain4j:
open-ai:
chat-model:
base-url: http://langchain4j.dev/demo/openai/v1
api-key: demo
model-name: gpt-4o-mini
temperature: 0.7
log-requests: true # 可选,打印请求日志
log-responses: true # 可选,打印响应日志
如果你使用真实 OpenAI API,替换为:
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY} # 从环境变量读取
model-name: gpt-4o-mini
temperature: 0.7
提示:更多配置项可以参考 LangChain4j Spring Boot Starter 文档。
12.4 将 AI Service 声明为 Bean
在 Spring Boot 中,我们可以将 AI Service 接口定义为 Bean,然后在需要的地方注入。有两种方式:
- 使用
@Bean方法手动创建(更灵活) - 使用
@AiService注解自动创建(更简洁,需要引入额外注解)
方式一:使用 @Bean 手动创建
创建一个配置类 AiConfig.java:
package com.example.config;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.service.AiServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
public Assistant assistant(ChatLanguageModel chatLanguageModel) {
// ChatLanguageModel 已经由 Spring Boot Starter 自动创建并注入
return AiServices.create(Assistant.class, chatLanguageModel);
}
}
这里的 Assistant 接口是我们定义的 AI Service 接口,可以放在 com.example.service 包中:
package com.example.service;
import dev.langchain4j.service.SystemMessage;
public interface Assistant {
@SystemMessage("你是一个乐于助人的助手。")
String chat(String userMessage);
}
方式二:使用 @AiService 注解自动创建(推荐)
LangChain4j 提供了 @AiService 注解,你只需在接口上添加该注解,框架就会自动创建该接口的实现并注册为 Spring Bean。
首先,确保你的主类或配置类启用了 @EnableAiServices(如果 starter 版本支持,可能自动启用,但为了保险可以加上):
package com.example;
import dev.langchain4j.service.spring.EnableAiServices;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableAiServices // 启用 AI Service 自动扫描
public class LangChain4jApplication {
public static void main(String[] args) {
SpringApplication.run(LangChain4jApplication.class, args);
}
}
然后,在 AI Service 接口上添加 @AiService 注解:
package com.example.service;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;
@AiService
public interface Assistant {
@SystemMessage("你是一个乐于助人的助手。")
String chat(String userMessage);
}
无需手动创建 @Bean,Spring Boot 启动后会自动扫描带有 @AiService 的接口,并创建实现类 Bean。你可以在任何地方通过 @Autowired 注入该接口。
12.5 在 Controller 中注入 AI Service,构建 REST API
现在创建一个简单的 REST Controller,提供聊天接口。
package com.example.controller;
import com.example.service.Assistant;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/chat")
@RequiredArgsConstructor
public class ChatController {
private final Assistant assistant;
@PostMapping
public String chat(@RequestBody ChatRequest request) {
return assistant.chat(request.message());
}
// 简单的请求体封装
public record ChatRequest(String message) {}
}
也可以提供一个 GET 接口便于测试:
@GetMapping
public String chatGet(@RequestParam String message) {
return assistant.chat(message);
}
12.6 实践:快速搭建一个包含 RAG 功能的 Web 服务
接下来我们整合之前学过的 RAG 功能,构建一个知识库问答的 Web 服务。我们将:
- 在应用启动时加载知识文档并摄入到向量存储中(内存存储)
- 创建带有 RAG 检索器的 AI Service
- 通过 REST API 提供问答
12.6.1 准备知识文档
在 src/main/resources 目录下创建 knowledge.txt 文件,内容如下(你可以替换为自己的知识):
LangChain4j 是一个为 Java 开发者设计的 LLM 集成框架。
它提供了统一 API,支持多种模型提供商。
RAG 是 Retrieval-Augmented Generation 的缩写,意为检索增强生成。
LangChain4j 内置了完整的 RAG 工具链,包括文档加载器、分割器、嵌入模型和向量存储。
使用 LangChain4j,你可以轻松构建基于私有知识库的问答系统。
Spring Boot 集成让这一切更加简单。
12.6.2 定义 RAG 相关的 Bean
我们需要定义:
- 嵌入模型(
EmbeddingModel) - 向量存储(
EmbeddingStore) - 文档加载和摄入逻辑(在应用启动时执行)
- 检索器(
ContentRetriever) - 带有 RAG 的 AI Service
修改配置类 AiConfig.java:
package com.example.config;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.InMemoryEmbeddingStore;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.nio.file.Path;
@Slf4j
@Configuration
public class RagConfig {
// 定义嵌入模型 Bean
@Bean
public EmbeddingModel embeddingModel() {
return new AllMiniLmL6V2EmbeddingModel();
}
// 定义向量存储 Bean(使用内存存储,重启后丢失)
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
// 定义检索器 Bean,使用向量存储和嵌入模型
@Bean
public ContentRetriever contentRetriever(
EmbeddingStore<TextSegment> embeddingStore,
EmbeddingModel embeddingModel) {
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.5)
.build();
}
// 定义带有 RAG 的 AI Service Bean
@Bean
public RagAssistant ragAssistant(
dev.langchain4j.model.chat.ChatLanguageModel chatLanguageModel,
ContentRetriever contentRetriever) {
return AiServices.builder(RagAssistant.class)
.chatLanguageModel(chatLanguageModel)
.contentRetriever(contentRetriever)
.build();
}
// 应用启动时摄入知识文档
@Bean
public IngestionInitializer ingestionInitializer(
EmbeddingModel embeddingModel,
EmbeddingStore<TextSegment> embeddingStore) {
return new IngestionInitializer(embeddingModel, embeddingStore);
}
// 内部类,用于初始化摄入
@Slf4j
public static class IngestionInitializer {
private final EmbeddingModel embeddingModel;
private final EmbeddingStore<TextSegment> embeddingStore;
public IngestionInitializer(EmbeddingModel embeddingModel,
EmbeddingStore<TextSegment> embeddingStore) {
this.embeddingModel = embeddingModel;
this.embeddingStore = embeddingStore;
}
@PostConstruct
public void ingest() {
try {
// 从 classpath 加载文档
Path path = new ClassPathResource("knowledge.txt").getFile().toPath();
Document document = FileSystemDocumentLoader.loadDocument(path);
// 构建摄入器
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(dev.langchain4j.data.document.splitter.DocumentSplitters.recursive(300, 30))
.embeddingModel(embeddingModel)
.embeddingStore(embeddingStore)
.build();
ingestor.ingest(document);
log.info("知识文档已成功摄入,向量存储条目数:{}",
((InMemoryEmbeddingStore<?>) embeddingStore).entries().size());
} catch (IOException e) {
log.error("摄入知识文档失败", e);
}
}
}
}
12.6.3 定义 RAG Assistant 接口
package com.example.service;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;
@AiService
public interface RagAssistant {
@SystemMessage("你是一个知识库助手,请基于提供的上下文回答问题。如果上下文不足以回答,请说明你不知道。")
String chat(String userMessage);
}
注意这里我们没有使用 @MemoryId,因为简单示例中不需要记忆。如果需要,可以按照前面章节的方法添加。
12.6.4 创建 REST Controller
package com.example.controller;
import com.example.service.RagAssistant;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/rag")
@RequiredArgsConstructor
public class RagController {
private final RagAssistant ragAssistant;
@PostMapping
public String ask(@RequestBody QueryRequest request) {
return ragAssistant.chat(request.question());
}
@GetMapping
public String askGet(@RequestParam String question) {
return ragAssistant.chat(question);
}
public record QueryRequest(String question) {}
}
12.6.5 启动应用并测试
运行 Spring Boot 主类。控制台会输出日志,显示文档摄入成功。
然后使用 curl 或浏览器测试:
# GET 请求
curl "http://localhost:8080/api/rag?question=什么是RAG?"
# POST 请求
curl -X POST http://localhost:8080/api/rag \
-H "Content-Type: application/json" \
-d '{"question": "LangChain4j 支持哪些模型?"}'
你应该会看到基于 knowledge.txt 内容的回答。如果问题不在文档中,AI 会表示不知道。
12.7 进一步扩展
- 添加记忆:如果需要多轮对话,可以注入
ChatMemory和@MemoryId。定义带有记忆的 AI Service,并在 Controller 中接收用户 ID。 - 添加工具:将工具类也定义为 Spring Bean,并通过
AiServices.tools()传入。 - 配置多个模型:可以在
application.yml中配置多个模型 Bean,然后通过@Qualifier选择使用哪个。 - 使用缓存:对频繁相同的问题,可以用 Spring Cache 缓存结果。
- 集成 Spring Security:为 API 添加认证和授权。
12.8 本章小结
通过本章的学习,你成功将 LangChain4j 与 Spring Boot 集成,并构建了一个提供 REST API 的知识库问答服务。你掌握了:
- 引入 LangChain4j Spring Boot Starter 及相关依赖。
- 在
application.yml中配置模型参数。 - 使用
@AiService注解或手动@Bean方式将 AI Service 声明为 Spring Bean。 - 在 Controller 中注入并使用 AI Service。
- 整合 RAG 组件,在应用启动时加载知识文档,并通过 REST API 提供问答。
至此,已经完成了从零到一构建完整 AI 应用的学习旅程。现在,你可以将所学知识应用到实际项目中,构建各种智能应用,如客服机器人、文档问答系统、智能助手等。
十三、总结与最佳实践
完成了整个学习旅程!从第一章的环境搭建到第十二章的 Spring Boot 集成,你已经系统地掌握了 LangChain4j 的所有核心组件,并亲手实践了从简单聊天到复杂 RAG 知识库的构建。现在,让我们一起来回顾一下所学内容,并探讨在生产环境中如何做出明智的技术选型,最后为你指明继续深入的方向。
13.1 回顾 LangChain4j 所有组件及适用场景
在整个教程中,我们逐步探索了以下组件,每个组件都有其独特的用途。下面的表格可以帮助你快速回顾:
| 组件类别 | 核心组件/概念 | 适用场景 | 你学到的实践 |
|---|---|---|---|
| 基础模型 | ChatLanguageModel | 任何需要与大模型对话的地方 | 用 OpenAiChatModel 发送消息,获取回复 |
| 消息类型 | SystemMessage, UserMessage, AiMessage | 设定角色、构建多轮对话 | 使用系统消息定义 AI 行为,构造多消息请求 |
| 提示词管理 | PromptTemplate | 动态构造用户消息,避免字符串拼接 | 用 {{变量}} 占位符,生成标准化的提示词 |
| 记忆管理 | ChatMemory, MessageWindowChatMemory, @MemoryId, ChatMemoryProvider | 多轮对话、多用户隔离 | 实现能记住上下文的聊天机器人,为每个用户分配独立记忆 |
| 声明式 AI | AiServices, @SystemMessage, @UserMessage, @V, Result<T> | 简化代码,以接口方式定义 AI 行为 | 定义 Assistant 接口,通过注解配置提示词,获取结构化输出和元数据 |
| 工具调用 | @Tool, ToolProvider | 让 AI 获取实时信息或执行操作(如查天气、查订单) | 编写工具方法,注入 AI Service,AI 自动调用 |
| RAG 基础 | DocumentLoader, DocumentSplitter, EmbeddingModel, EmbeddingStore, EmbeddingStoreIngestor | 构建私有知识库,让 AI 基于文档回答 | 加载文档、分割、向量化、存储,完成知识摄入 |
| RAG 检索 | ContentRetriever, EmbeddingStoreContentRetriever | 在问答时检索相关知识 | 创建检索器并注入 AI Service,实现检索增强生成 |
| 高级 RAG | RetrievalAugmentor, QueryTransformer, QueryRouter, ContentAggregator, ContentInjector | 需要复杂检索策略(查询改写、多源检索、结果重排) | 定制检索流程,实现多文档源检索、查询压缩等 |
| 内容审核 | ModerationModel, OpenAiModerationModel, @Moderate | 确保输入输出内容安全合规 | 对用户消息和 AI 回复进行审核,拦截不当内容 |
| 多模态 | ImageContent, PdfFileContent | 让 AI 理解图片、处理 PDF 等文件 | 发送图片让 AI 描述,提取 PDF 文本后分析 |
| 事件监听 | AiServiceListener | 监控、日志、统计 Token 用量 | 实现监听器记录请求耗时、Token 消耗 |
| Spring Boot 集成 | langchain4j-spring-boot-starter, @AiService | 将 AI 能力融入企业级 Web 应用 | 配置 application.yml,将 AI Service 声明为 Bean,通过 REST API 暴露服务 |
一句话总结:LangChain4j 通过组件化设计,让你像搭积木一样组合这些模块,快速构建从简单到复杂的 AI 应用。
13.2 生产环境选型建议
当你准备将应用部署到生产环境时,需要根据实际场景做出更细致的选择。以下是一些关键决策点的建议。
13.2.1 向量数据库选型
在 RAG 应用中,向量存储是核心组件。我们之前使用了 InMemoryEmbeddingStore,但它无法持久化,且受限于单机内存。生产环境通常需要专业的向量数据库。
| 向量数据库 | 优点 | 适用场景 |
|---|---|---|
| PGvector (PostgreSQL 插件) | - 如果你已经在使用 PostgreSQL,可以复用现有数据库 - 支持 SQL 查询,与关系数据无缝结合 - 开源免费 | 中小型项目,数据量在百万级以下,希望简化架构 |
| Elasticsearch | - 强大的全文检索 + 向量检索混合能力 - 分布式、高可用 - 适合日志、文档类数据 | 需要同时支持关键词搜索和语义搜索,数据量大 |
| Pinecone | - 全托管、免运维 - 高性能、低延迟 - 支持元数据过滤 | 快速启动,不想自己维护基础设施,预算充足 |
| Chroma | - 开源、轻量、易用 - 专为 RAG 设计 - 支持嵌入式运行 | 开发测试,或小规模生产 |
| Milvus / Zilliz Cloud | - 功能强大,支持十亿级向量 - 丰富的索引类型 - 云服务或自托管 | 大规模生产环境,对性能和扩展性要求高 |
建议:对于大多数 Java 后端团队,如果已有 PostgreSQL,PGvector 是最平滑的选择;如果团队熟悉 Elasticsearch,它也是一个强大的多面手。初创项目可以先从 PGvector 开始,后期如有需要再迁移。
13.2.2 模型选型(云端 vs 本地)
大模型的选择直接影响成本、响应速度和数据隐私。
| 模型类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 云端模型 (OpenAI, Azure, 通义千问等) | - 能力强大,持续更新 - 无需自己部署硬件 - 开箱即用 | - 调用付费,长期成本高 - 数据需发送到第三方(有隐私风险) - 依赖网络 | 通用对话、需要强大推理能力的场景,对数据隐私要求不高的项目 |
| 本地模型 (Ollama, llama.cpp, Hugging Face) | - 数据完全私有 - 一次部署,免费调用(除硬件成本) - 延迟低(无网络开销) | - 需要 GPU 资源,硬件成本高 - 模型能力相对较弱(尤其小参数模型) - 部署运维复杂 | 数据隐私要求极高(如金融、医疗),或需要离线运行 |
建议:
- 初期可以先用云端模型快速验证,如 OpenAI 的
gpt-4o-mini成本较低。 - 当应用成熟且数据敏感时,可考虑用本地模型替代,例如通过 Ollama 运行
llama3或qwen。 - LangChain4j 支持通过
OllamaChatModel轻松切换,只需修改几行配置。
13.2.3 记忆持久化方案
MessageWindowChatMemory 是内存存储,应用重启后对话历史会丢失。生产环境通常需要持久化记忆。
- 简单方案:将对话历史存入关系数据库,每次启动时加载。可以实现
ChatMemoryStore接口,用 JPA 或 MyBatis 存储消息。 - 缓存方案:使用 Redis 作为快速存储,设置过期时间,既持久化又保证性能。LangChain4j 社区有
RedisChatMemory的实现示例。 - 云服务:如果使用向量数据库如 Pinecone,也可以同时存储对话向量,但通常记忆是独立的。
建议:对于大多数 Web 应用,用 Redis 存储对话历史是最佳选择:速度快,支持过期,数据结构简单。如果你的团队对 Redis 不熟,可以用数据库 + 缓存双写。
13.2.4 性能与成本优化
- 缓存:对于重复的问题(如常见 FAQ),可以用 Spring Cache 或 Caffeine 缓存 AI 的回答,减少模型调用。
- 批处理:如果业务允许,可以将多个请求合并成一个发送给模型(例如批量翻译),降低 Token 消耗。
- 流式输出:对于长回答,使用流式接口(
StreamingChatLanguageModel)可以提升用户体验,减少首字延迟。 - Token 监控:通过监听器记录 Token 用量,设置每日/每月上限,避免成本失控。
- 模型降级:对于简单问题,可以使用小参数模型(如
gpt-3.5-turbo),复杂问题才调用大模型。LangChain4j 支持动态选择模型。
13.3 后续学习路径推荐
你已经掌握了 LangChain4j 的绝大部分功能,接下来可以朝以下方向深入:
-
深入 RAG 高级技术:
- 学习混合检索(Hybrid Search):结合关键词检索和向量检索,提升准确率。
- 学习重排序(Reranking):用专门的模型对检索结果重新排序,提高相关性。
- 学习查询规划(Query Planning):对于复杂问题,拆分成多个子查询再聚合答案。
-
探索更多模型集成:
- 尝试集成 Anthropic Claude、Google Gemini、Azure OpenAI 等模型。
- 探索本地模型部署工具,如 Ollama、vLLM,并与 LangChain4j 结合。
-
构建多 Agent 系统:
- LangChain4j 正在发展 Agent 功能(如
ToolExecutor、PlanAndExecute),你可以尝试构建能自主调用多工具的 Agent。
- LangChain4j 正在发展 Agent 功能(如
-
关注 LangChain4j 官方动态:
- GitHub 仓库:langchain4j
- 官方文档:docs.langchain4j.dev
- 加入社区讨论,获取最新特性和最佳实践。
-
结合实际业务落地:
- 尝试将 AI 能力集成到现有系统中,如智能客服、内部知识库、代码生成助手等。
- 关注成本和效果,不断迭代优化。
附录
A. 常见问题解答(FAQ)
Q1: 为什么我用 demo 密钥调用时,有时会失败?
A: 演示端点仅供学习和测试,有速率限制且不稳定。如果遇到错误,可以稍后重试,或换成真实 API 密钥。
Q2: 本地嵌入模型 AllMiniLmL6V2EmbeddingModel 第一次运行很慢?
A: 第一次运行时需要从 Hugging Face 下载模型文件(约 80MB),请耐心等待。下载完成后会缓存在本地,后续启动很快。
Q3: 我的 PDF 文档很大,如何用 RAG 处理?
A: 使用 DocumentSplitters 将 PDF 分割成适当大小的块(如每块 500 字符),然后摄入。分割时可以设置重叠,避免丢失上下文。
Q4: 如何估算 Token 消耗?
A: 可以通过监听器获取 TokenUsage,也可以使用在线工具(如 OpenAI Tokenizer)估算。注意不同模型的计费规则可能不同。
Q5: LangChain4j 支持流式输出吗?
A: 支持。可以使用 StreamingChatLanguageModel 和 TokenStream 接口,在 AI Service 中返回 Flux<String> 或类似响应式类型。Spring Boot 中可以结合 WebFlux 实现 SSE(Server-Sent Events)。
Q6: 我想在 Android 上使用 LangChain4j 可以吗?
A: 可以,但需要注意依赖大小和模型选择。Android 项目可以使用 langchain4j 核心,但本地嵌入模型可能较大,建议使用云端模型。
B. 完整代码示例仓库地址
为了方便你查阅和运行,本教程的所有代码示例已整理到一个 GitHub 仓库中:
仓库结构按照章节组织,每个示例都是独立的可运行类,并包含必要的说明。
C. 参考资源链接
- LangChain4j 官方文档:docs.langchain4j.dev
- LangChain4j GitHub:github.com/langchain4j…
- Spring Boot Starter 文档:github.com/langchain4j…
- OpenAI API 文档:platform.openai.com/docs
- PGvector 项目:github.com/pgvector/pg…
- Ollama 官网:ollama.ai
希望这份指南能帮助你顺利进入大模型应用开发的世界。记住,实践是最好的老师,多写代码、多试错、多总结,你会越来越熟练。