文章

Java-langchain4j框架

Java-langchain4j框架

简介

LangChain4j 是一个专为 Java 生态设计的开源 LLM 应用开发框架,旨在简化将大语言模型(LLM)集成到 Java 应用程序中的过程。该项目始于 2023 年初 ChatGPT 热潮期间,填补了 Java 生态中缺少类似 Python LangChain 框架的空白。

虽然名字中有”LangChain”,但该项目融合了 LangChain、Haystack、LlamaIndex 和更广泛社区的想法与概念,并加入了自身的创新。目前最新版本为 1.0.0-beta3

核心设计理念

  • 统一 API:提供统一的 API 接口,支持 15+ 个 LLM 提供商和 20+ 个嵌入存储,无需为每个提供商学习专用 API,可轻松切换而无需重写代码
  • 全面工具箱:从低级提示模板、聊天记忆管理和函数调用,到高级模式如代理和 RAG,提供即用型组件
  • 两个抽象层次:低层次(最大自由度,完全控制组合方式)和高层次(AI 服务,声明式 API,隐藏复杂性)

核心功能一览

功能说明
LLM 提供商集成支持 15+ 个主流 LLM 提供商
嵌入存储集成支持 20+ 个向量数据库
嵌入模型集成支持 15+ 个嵌入模型
图像生成支持 5 个图像生成模型
多模态支持文本和图像作为输入
AI 服务高级声明式 LLM API
提示模板灵活的提示词模板系统
聊天记忆消息窗口和令牌窗口两种策略
流式响应逐 token 流式输出
结构化输出将 LLM 输出解析为 Java 对象
工具/函数调用LLM 可调用外部工具
RAG完整的检索增强生成管道
Agent智能代理和多代理编排
可观察性内置监控和日志支持

库结构

LangChain4j 采用模块化设计:

  • langchain4j-core:定义核心抽象(如 ChatLanguageModelEmbeddingStore)及其 API
  • langchain4j:主模块,包含文档加载器、聊天记忆实现以及 AI 服务等高级功能
  • langchain4j-{integration}:各种 LLM 提供商和嵌入存储的集成模块,可独立使用

国内常用模型提供商

LangChain4j 也对国内主流大模型提供了良好的支持:

提供商模块名说明
阿里云 DashScope(通义千问)langchain4j-dashscope支持千问系列模型
智谱 AI(ChatGLM)langchain4j-zhipu支持 GLM-4 等模型
百度千帆langchain4j-qianfan支持文心一言
MiniMaxlangchain4j-minimax支持 MiniMax 模型
讯飞星火langchain4j-spark支持星火大模型

快速开始

环境要求

  • JDK 17+
  • Maven 或 Gradle

添加依赖

Maven 方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- OpenAI 集成 -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>1.0.0-beta3</version>
</dependency>

<!-- 高级 AI 服务 API(推荐) -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>1.0.0-beta3</version>
</dependency>

Gradle 方式:

1
2
implementation 'dev.langchain4j:langchain4j-open-ai:1.0.0-beta3'
implementation 'dev.langchain4j:langchain4j:1.0.0-beta3'

使用 BOM 管理版本(推荐):

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.0.0-beta3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

第一个程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;

import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;

public class QuickStart {
    public static void main(String[] args) {
        // 1. 创建模型实例
        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName(GPT_4_O_MINI)
                .build();

        // 2. 发送消息并获取响应
        String answer = model.chat("你好,请介绍一下你自己");
        System.out.println(answer);
    }
}

使用国内模型(以通义千问为例)

1
2
3
4
5
6
7
8
9
10
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.dashscope.QwenChatModel;

ChatLanguageModel model = QwenChatModel.builder()
        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
        .modelName("qwen-plus")
        .build();

String answer = model.chat("你好,请用中文介绍一下LangChain4j");
System.out.println(answer);

使用本地模型(Ollama)

1
2
3
4
5
6
7
8
9
10
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.ollama.OllamaChatModel;

ChatLanguageModel model = OllamaChatModel.builder()
        .baseUrl("http://localhost:11434")
        .modelName("llama3.1")
        .build();

String answer = model.chat("Hello, how are you?");
System.out.println(answer);

实用技巧:开发阶段推荐使用 Ollama 运行本地模型,既省钱又无需担心 API 速率限制,适合快速迭代。生产环境再切换到云端 API。

核心概念:低级 API

在深入高级 API 之前,了解低级 API 有助于理解框架的底层机制。

ChatLanguageModel

ChatLanguageModel 是最核心的低级接口,代表一个可以接收聊天消息并返回响应的模型。

1
2
3
4
5
6
7
8
9
10
11
// 简单文本对话
String response = model.chat("What is Java?");

// 使用 ChatMessage 对象进行更精细的控制
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.SystemMessage;

UserMessage userMessage = UserMessage.from("解释一下什么是微服务");
AiMessage aiMessage = model.chat(userMessage).aiMessage();
System.out.println(aiMessage.text());

ChatMessage 体系

LangChain4j 定义了几种消息类型:

消息类型说明使用场景
UserMessage用户发送的消息用户输入
AiMessageAI 的回复模型输出
SystemMessage系统指令设定 AI 的行为和角色
ToolExecutionResultMessage工具执行结果函数调用返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.response.ChatResponse;

// 构建多轮对话
SystemMessage systemMessage = SystemMessage.from("你是一位资深的Java架构师,擅长解答技术问题");
UserMessage userMessage = UserMessage.from("请对比Spring Boot和Quarkus的优劣");

ChatRequest request = ChatRequest.builder()
        .messages(systemMessage, userMessage)
        .build();

ChatResponse response = model.chat(request);
System.out.println(response.aiMessage().text());

ChatResponse 和元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ChatResponse response = model.chat(ChatRequest.builder()
        .messages(UserMessage.from("Hello"))
        .build());

// 获取 AI 回复内容
AiMessage aiMessage = response.aiMessage();
String text = aiMessage.text();

// 获取 token 使用信息
TokenUsage tokenUsage = response.tokenUsage();
System.out.println("输入token: " + tokenUsage.inputTokenCount());
System.out.println("输出token: " + tokenUsage.outputTokenCount());

// 获取结束原因
FinishReason finishReason = response.finishReason();

AI 服务(高级 API)

AI 服务是 LangChain4j 最核心的高级抽象,类似于 Spring Data JPA 或 Retrofit 的设计理念——通过声明式接口定义 API,框架自动生成实现。

最简单的 AI 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 定义接口
interface Assistant {
    String chat(String userMessage);
}

// 2. 创建模型
ChatLanguageModel model = OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName(GPT_4_O_MINI)
        .build();

// 3. 创建 AI 服务实例
Assistant assistant = AiServices.create(Assistant.class, model);

// 4. 使用
String answer = assistant.chat("Hello");
System.out.println(answer); // Hello, how can I help you?

@SystemMessage — 设定 AI 角色

1
2
3
4
5
6
7
interface Friend {
    @SystemMessage("你是我的好朋友,用口语化的方式回答问题")
    String chat(String userMessage);
}

Friend friend = AiServices.create(Friend.class, model);
String answer = friend.chat("你好"); // 嘿!最近咋样?

@SystemMessage 也支持从资源文件加载提示模板:

1
2
@SystemMessage(fromResource = "my-prompt-template.txt")
String chat(String userMessage);

动态系统消息:

1
2
3
4
5
Friend friend = AiServices.builder(Friend.class)
        .chatLanguageModel(model)
        .systemMessageProvider(chatMemoryId -> 
            "你是用户" + chatMemoryId + "的专属助手")
        .build();

@UserMessage — 用户消息模板

1
2
3
4
5
6
7
interface Translator {
    @UserMessage("将以下文本翻译成{{language}}:{{text}}")
    String translate(@V("language") String language, @V("text") String text);
}

Translator translator = AiServices.create(Translator.class, model);
String result = translator.translate("日语", "今天天气真好");

提示:在 Quarkus 或 Spring Boot 应用中,不需要使用 @V 注解,参数名会自动识别。

返回类型

AI 服务方法支持多种返回类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Assistant {
    // 返回纯文本
    String chat(String message);
    
    // 返回结构化对象(自动解析)
    Person extractPerson(String text);
    
    // 返回枚举
    Sentiment analyzeSentiment(String text);
    
    // 返回列表
    List<String> generateTopics(String text);
    
    // 包装在 Result 中获取元数据
    Result<String> chatWithMetadata(String message);
}

使用 Result<T> 获取额外元数据:

1
2
3
4
5
6
7
8
9
interface Assistant {
    @UserMessage("生成关于{{topic}}的文章大纲")
    Result<List<String>> generateOutline(String topic);
}

Result<List<String>> result = assistant.generateOutline("Java");
List<String> outline = result.content();
TokenUsage tokenUsage = result.tokenUsage();
List<Content> sources = result.sources();

提示模板

提示模板是构建高质量 LLM 应用的基础,LangChain4j 提供了灵活的模板系统。

PromptTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import dev.langchain4j.model.input.PromptTemplate;

// 创建模板
PromptTemplate template = PromptTemplate.from(
    "作为一名{{role}},请解释{{concept}}的概念"
);

// 命名参数渲染
Prompt prompt = template.apply(Map.of(
    "role", "Java架构师",
    "concept", "微服务架构"
));

String response = model.chat(prompt.userMessageText());

ChatPromptTemplate

支持多消息类型的模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import dev.langchain4j.model.input.ChatPromptTemplate;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;

ChatPromptTemplate chatPrompt = ChatPromptTemplate.builder()
        .systemMessage("你是一名{{role}},请用{{style}}的风格回答问题")
        .userMessage("{{question}}")
        .build();

ChatPrompt prompt = chatPrompt.apply(Map.of(
    "role", "技术专家",
    "style", "通俗易懂",
    "question", "什么是Docker?"
));

ChatResponse response = model.chat(prompt.messages());

提示工程技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 1. 少量示例(Few-shot)
@SystemMessage("""
    你是一个文本情感分析器。请按照以下示例分析情感:
    
    示例1:
    输入:今天真开心!
    输出:POSITIVE
    
    示例2:
    输入:这部电影太令人失望了
    输出:NEGATIVE
    
    示例3:
    输入:天气还可以吧
    输出:NEUTRAL
    """)
Sentiment analyze(String text);

// 2. 思维链(Chain of Thought)
@SystemMessage("""
    请按步骤分析以下问题:
    1. 首先,理解问题的核心
    2. 然后,列出关键因素
    3. 接着,逐步推理
    4. 最后,给出结论
    
    请在回答中展示你的推理过程。
    """)
String analyzeProblem(String problem);

// 3. 输出格式约束
@UserMessage("""
    分析以下文本,并以JSON格式返回结果:
    {{text}}
    
    要求JSON格式:
    {
        "summary": "内容摘要",
        "keywords": ["关键词1", "关键词2"],
        "sentiment": "POSITIVE/NEGATIVE/NEUTRAL"
    }
    只返回JSON,不要其他内容。
    """)
String analyzeAsJson(@V("text") String text);

聊天记忆

LLM 本身是无状态的,每次请求都是独立的。聊天记忆使得 AI 可以”记住”之前的对话内容。

记忆 vs 历史

  • 历史:保持所有消息完整无缺,是用户在 UI 中看到的内容
  • 记忆:呈现给 LLM 的信息,可能经过淘汰、总结等处理

LangChain4j 目前只提供”记忆”,如果需要完整历史需自行保存。

MessageWindowChatMemory(消息窗口)

保留最近 N 条消息,淘汰最旧的消息:

1
2
3
4
5
import dev.langchain4j.memory.chat.MessageWindowChatMemory;

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(10)  // 保留最近10条消息
        .build();

TokenWindowChatMemory(令牌窗口)

保留最近 N 个令牌,淘汰超出的消息(消息不可分割,整条淘汰):

1
2
3
4
5
6
import dev.langchain4j.memory.chat.TokenWindowChatMemory;

ChatMemory chatMemory = TokenWindowChatMemory.builder()
        .maxTokens(1000)
        .tokenizer(new OpenAiTokenizer("gpt-4o-mini"))
        .build();

实用技巧TokenWindowChatMemory 更精确,适合生产环境;MessageWindowChatMemory 适合快速原型开发。

持久化聊天记忆

默认聊天记忆存储在内存中,通过实现 ChatMemoryStore 接口可以持久化到数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import dev.langchain4j.store.memory.chat.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMessageDeserializer;

class PersistentChatMemoryStore implements ChatMemoryStore {
    
    private final ChatMessageRepository repository; // 自定义的数据访问层
    
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String json = repository.findByMemoryId(memoryId);
        return ChatMessageDeserializer.messagesFromJson(json);
    }
    
    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        String json = ChatMessageSerializer.messagesToJson(messages);
        repository.save(memoryId, json);
    }
    
    @Override
    public void deleteMessages(Object memoryId) {
        repository.deleteByMemoryId(memoryId);
    }
}

为每个用户提供独立的聊天记忆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;

// 使用 ChatMemoryProvider 为不同用户创建独立的 ChatMemory
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
        .id(memoryId)
        .maxMessages(20)
        .chatMemoryStore(new PersistentChatMemoryStore())
        .build();

// 在 AI 服务中使用
Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(model)
        .chatMemoryProvider(chatMemoryProvider)
        .build();

// 不同用户有独立的记忆
String answer1 = assistant.chat("user-001", "我叫张三");
String answer2 = assistant.chat("user-002", "我叫李四");
String answer3 = assistant.chat("user-001", "我叫什么名字?"); // 张三

在 AI 服务中使用聊天记忆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Assistant {
    String chat(String message);
    String chat(@MemoryId String memoryId, @UserMessage String message);
}

// 方式1:共享单个 ChatMemory 实例
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(model)
        .chatMemory(chatMemory)
        .build();

// 方式2:使用 ChatMemoryProvider 为每个 memoryId 提供独立记忆
Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(model)
        .chatMemoryProvider(chatMemoryProvider)
        .build();

企业级技巧:在生产环境中,推荐使用 Redis 或数据库实现 ChatMemoryStore 的持久化,避免服务重启导致用户对话丢失。同时设置合理的记忆窗口大小,平衡上下文质量和成本。

实战:基于 Redis 的多用户聊天记忆持久化

以下是一个生产级的 Redis 聊天记忆持久化方案,支持多用户会话隔离:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
@Slf4j
public class RedisChatMemoryStore implements ChatMemoryStore {

    private static final String KEY_PREFIX = "chat:memory:";
    private static final Duration TTL = Duration.ofHours(24);  // 24小时过期

    private final RedisTemplate<String, Object> redisTemplate;

    public RedisChatMemoryStore(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String key = KEY_PREFIX + memoryId;
        Object raw = redisTemplate.opsForValue().get(key);
        if (raw == null) {
            log.debug("未找到用户{}的聊天记忆,可能是首次对话", memoryId);
            return List.of();
        }
        return ChatMessageDeserializer.messagesFromJson(raw.toString());
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        String key = KEY_PREFIX + memoryId;
        String json = ChatMessageSerializer.messagesToJson(messages);
        redisTemplate.opsForValue().set(key, json, TTL);
        log.debug("已更新用户{}的聊天记忆,共{}条消息", memoryId, messages.size());
    }

    @Override
    public void deleteMessages(Object memoryId) {
        String key = KEY_PREFIX + memoryId;
        redisTemplate.delete(key);
        log.info("已清除用户{}的聊天记忆", memoryId);
    }
}

在 Spring Boot 控制器中集成多用户记忆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
@RequestMapping("/api/chat")
public class ChatController {

    private final RedisChatMemoryStore chatMemoryStore;
    private final StreamingChatLanguageModel streamingModel;

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(
            @RequestParam String message,
            @RequestParam String userId) {  // 以userId作为memoryId实现多用户隔离

        ChatMemoryProvider chatMemoryProvider = memoryId ->
                MessageWindowChatMemory.builder()
                        .id(memoryId)
                        .maxMessages(20)
                        .chatMemoryStore(chatMemoryStore)
                        .build();

        StreamingAssistant assistant = AiServices.builder(StreamingAssistant.class)
                .streamingChatLanguageModel(streamingModel)
                .chatMemoryProvider(chatMemoryProvider)
                .build();

        return assistant.chat(message, userId);
    }
}

企业级要点:

  • 使用 KEY_PREFIX 避免键名冲突
  • 设置 TTL 自动过期,防止 Redis 内存无限增长
  • userId 作为 memoryId 实现多用户会话隔离
  • maxMessages 设置为 20 条,平衡上下文质量和 API 成本
  • 服务重启后用户对话不丢失(Redis 持久化)

流式响应

LLM 逐 token 生成响应,流式传输可以让用户几乎立即开始阅读响应,极大改善用户体验。

低级流式 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;

// 1. 创建流式模型
StreamingChatLanguageModel streamingModel = OpenAiStreamingChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName(GPT_4_O_MINI)
        .build();

// 2. 流式调用
streamingModel.chat("讲一个笑话", new StreamingChatResponseHandler() {
    @Override
    public void onPartialResponse(String partialResponse) {
        System.out.print(partialResponse); // 逐token输出
    }
    
    @Override
    public void onCompleteResponse(ChatResponse completeResponse) {
        System.out.println("\n--- 响应完成 ---");
    }
    
    @Override
    public void onError(Throwable error) {
        error.printStackTrace();
    }
});

使用 Lambda 简化

1
2
3
4
5
6
7
8
9
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onPartialResponse;
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onPartialResponseAndError;

// 仅处理部分响应
streamingModel.chat("讲一个笑话", onPartialResponse(System.out::print));

// 同时处理响应和错误
streamingModel.chat("讲一个笑话", 
    onPartialResponseAndError(System.out::print, Throwable::printStackTrace));

AI 服务中的流式响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Assistant {
    @SystemMessage("你是一个友好的助手")
    TokenStream chat(String message);
}

StreamingChatLanguageModel streamingModel = OpenAiStreamingChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName(GPT_4_O_MINI)
        .build();

Assistant assistant = AiServices.builder(Assistant.class)
        .streamingChatLanguageModel(streamingModel)
        .build();

// 使用 TokenStream
TokenStream tokenStream = assistant.chat("介绍一下Java");
tokenStream
    .onPartialResponse(System.out::print)
    .onCompleteResponse(response -> System.out.println("\n完成"))
    .onError(Throwable::printStackTrace)
    .start();

Spring Boot 中使用 Flux 流式响应

1
2
3
4
5
@AiService
interface Assistant {
    @SystemMessage("你是一个友好的助手")
    Flux<String> chat(String userMessage);
}

需要额外导入 langchain4j-reactor 模块,配合 WebFlux 实现真正的 SSE(Server-Sent Events)流式传输。

企业级技巧:在 Web 应用中,流式响应通常结合 SSE 或 WebSocket 使用。Spring Boot 中可利用 Flux<String> 配合 SseEmitter 实现前端实时显示 AI 回复的效果。

实战:ChatGPT 风格的 SSE 流式聊天接口

以下是一个生产级的 SSE 流式响应方案,实现类似 ChatGPT 的逐字输出效果:

后端(Spring Boot + WebFlux):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@RestController
@RequestMapping("/api/chat")
public class StreamingChatController {

    private final StreamingChatLanguageModel streamingModel;
    private final ChatMemoryProvider chatMemoryProvider;

    @GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> streamChat(
            @RequestParam String message,
            @RequestParam String userId) {

        interface ChatAssistant {
            @SystemMessage("你是一个专业的AI助手,请用中文回答问题")
            TokenStream chat(@MemoryId String userId, @UserMessage String message);
        }

        ChatAssistant assistant = AiServices.builder(ChatAssistant.class)
                .streamingChatLanguageModel(streamingModel)
                .chatMemoryProvider(chatMemoryProvider)
                .build();

        return Flux.create(sink -> {
            assistant.chat(userId, message)
                    .onPartialResponse(token -> {
                        // 逐token推送给前端
                        sink.next(ServerSentEvent.<String>builder()
                                .event("message")
                                .data(token)
                                .build());
                    })
                    .onCompleteResponse(response -> {
                        // 发送结束标记
                        sink.next(ServerSentEvent.<String>builder()
                                .event("done")
                                .data("[DONE]")
                                .build());
                        sink.complete();
                    })
                    .onError(error -> {
                        sink.next(ServerSentEvent.<String>builder()
                                .event("error")
                                .data(error.getMessage())
                                .build());
                        sink.complete();
                    })
                    .start();
        });
    }
}

前端(JavaScript EventSource):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const eventSource = new EventSource(
    `/api/chat/sse?message=${encodeURIComponent(userInput)}&userId=${userId}`
);

let fullResponse = '';

eventSource.addEventListener('message', function(event) {
    // 逐字追加显示,实现打字机效果
    fullResponse += event.data;
    document.getElementById('ai-response').innerHTML = 
        marked.parse(fullResponse);  // 支持Markdown渲染
    scrollToBottom();  // 自动滚动到底部
});

eventSource.addEventListener('done', function(event) {
    eventSource.close();  // 关闭连接
    hideLoadingSpinner();  // 隐藏加载动画
});

eventSource.addEventListener('error', function(event) {
    console.error('流式响应错误:', event.data);
    eventSource.close();
    showError('AI回复出现问题,请重试');
});

三种流式方式对比:

方式适用场景特点
StreamingChatResponseHandler快速原型、控制台测试最简单,回调式
TokenStream后端逐token处理、缓存拉式,控制灵活
Flux<String>Web应用、SSE/WebSocket非阻塞,响应式,生产首选

结构化输出

将 LLM 的非结构化文本输出转换为结构化的 Java 对象,是 LLM 应用落地的关键能力。

三种实现方式

方式可靠性说明
JSON Schema最高LLM 原生支持,由 API 层面保证输出格式
提示 + JSON 模式中等提示中要求 JSON 格式 + 开启 JSON 模式
仅提示最低仅通过提示词要求输出格式

方式一:JSON Schema(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
record Person(String name, int age, double height, boolean married) {}

interface PersonExtractor {
    @UserMessage("从以下文本中提取人物信息:{{text}}")
    Person extractPerson(@V("text") String text);
}

PersonExtractor extractor = AiServices.create(PersonExtractor.class, model);

Person person = extractor.extractPerson(
    "John is 42 years old and lives an independent life. " +
    "He stands 1.75 meters tall. Currently unmarried."
);

System.out.println(person); // Person[name=John, age=42, height=1.75, married=false]

方式二:提取枚举值

1
2
3
4
5
6
7
8
9
10
11
12
enum Sentiment {
    POSITIVE, NEUTRAL, NEGATIVE
}

interface SentimentAnalyzer {
    @UserMessage("分析以下文本的情感倾向:{{text}}")
    Sentiment analyzeSentimentOf(@V("text") String text);
}

SentimentAnalyzer analyzer = AiServices.create(SentimentAnalyzer.class, model);
Sentiment sentiment = analyzer.analyzeSentimentOf("今天真是美好的一天!");
// sentiment = POSITIVE

方式三:提取复杂对象

1
2
3
4
5
6
7
8
9
10
11
12
13
record Address(String street, String city) {}
record Employee(String name, int age, Address address, List<String> skills) {}

interface EmployeeExtractor {
    @UserMessage("从以下文本中提取员工信息:{{text}}")
    Employee extractEmployee(@V("text") String text);
}

EmployeeExtractor extractor = AiServices.create(EmployeeExtractor.class, model);
Employee emp = extractor.extractEmployee(
    "张三,28岁,住在北京海淀区中关村大街1号,擅长Java、Python和Go"
);
// Employee[name=张三, age=28, address=Address[street=中关村大街1号, city=北京], skills=[Java, Python, Go]]

低级 API 手动指定 JSON Schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import dev.langchain4j.model.chat.request.ResponseFormat;
import dev.langchain4j.model.chat.request.json.JsonSchema;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;

ResponseFormat responseFormat = ResponseFormat.builder()
        .type(ResponseFormat.Type.JSON)
        .jsonSchema(JsonSchema.builder()
                .name("Person")
                .rootElement(JsonObjectSchema.builder()
                        .addStringProperty("name")
                        .addIntegerProperty("age")
                        .addNumberProperty("height")
                        .addBooleanProperty("married")
                        .required("name", "age", "height", "married")
                        .build())
                .build())
        .build();

ChatRequest chatRequest = ChatRequest.builder()
        .responseFormat(responseFormat)
        .messages(UserMessage.from("John is 42 years old..."))
        .build();

ChatResponse chatResponse = model.chat(chatRequest);
// 输出: {"name":"John","age":42,"height":1.75,"married":false}

JSON Schema 支持的类型

Schema 类型Java 类型
JsonObjectSchema对象/POJO
JsonStringSchemaString, char
JsonIntegerSchemaint, long, BigInteger
JsonNumberSchemafloat, double, BigDecimal
JsonBooleanSchemaboolean
JsonEnumSchemaenum
JsonArraySchemaList, Set
JsonReferenceSchema递归引用(如树结构)
JsonAnyOfSchema多态类型

企业级技巧:结构化输出在数据抽取、信息归类等场景中非常实用。建议始终使用 JSON Schema 方式,以确保输出格式稳定可靠。对于复杂对象,可先定义好 Java record,让框架自动推断 Schema。

实战一:智能简历信息抽取系统

从非结构化的简历文本中自动提取结构化信息,用于 HR 系统的简历筛选:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 定义简历数据结构
record EducationRecord(String school, String major, String degree, String startDate, String endDate) {}
record WorkExperience(String company, String position, String startDate, String endDate, List<String> responsibilities) {}
record ResumeInfo(
    String name,
    String phone,
    String email,
    Integer age,
    List<String> skills,
    List<EducationRecord> education,
    List<WorkExperience> workExperience,
    String summary
) {}

// 定义抽取接口
interface ResumeExtractor {
    @SystemMessage("""
        你是一个专业的HR助手,负责从简历文本中提取结构化信息。
        请仔细阅读简历内容,准确提取所有字段。
        如果某些信息无法从文本中找到,对应字段返回null。
        技能列表请提取技术相关的关键词。
        """)
    @UserMessage("请从以下简历中提取信息:\n\n{{resumeText}}")
    ResumeInfo extractResume(@V("resumeText") String resumeText);
}

// 使用示例
ResumeExtractor extractor = AiServices.create(ResumeExtractor.class, model);

String resumeText = """
    张三,手机13800138000,邮箱zhangsan@gmail.com
    男,1995年出生
    
    教育经历:
    2013-2017 清华大学 计算机科学与技术 本科
    2017-2020 北京大学 软件工程 硕士
    
    工作经历:
    2020-2022 阿里巴巴 Java开发工程师
    - 负责交易系统微服务开发
    - 参与双11性能优化
    2022-至今 字节跳动 高级Java工程师
    - 主导推荐系统架构设计
    - 带领5人团队完成核心模块重构
    
    技能:Java, Spring Boot, MySQL, Redis, Kafka, Docker
    """;

ResumeInfo info = extractor.extractResume(resumeText);
// 自动解析为结构化对象,可直接存入数据库

实战二:合同关键条款自动审核

从合同文本中提取关键条款,辅助法务审核:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
record ContractClause(String clauseType, String content, String riskLevel, String suggestion) {}
record ContractReview(
    String contractName,
    String partyA,
    String partyB,
    Double totalAmount,
    String startDate,
    String endDate,
    List<ContractClause> keyClauses,
    List<String> riskPoints,
    String overallAssessment
) {}

interface ContractReviewer {
    @SystemMessage("""
        你是一个专业的法务审核助手。请从合同文本中提取以下信息:
        1. 合同双方、金额、期限等基本信息
        2. 关键条款(付款条件、违约责任、保密条款、争议解决等)
        3. 风险点(对己方不利的条款、模糊表述、缺失条款)
        4. 总体评估和建议
        riskLevel 取值为:HIGH/MEDIUM/LOW
        """)
    @UserMessage("请审核以下合同:\n\n{{contractText}}")
    ContractReview reviewContract(@V("contractText") String contractText);
}

// 使用
ContractReviewer reviewer = AiServices.create(ContractReviewer.class, model);
ContractReview review = reviewer.reviewContract(contractText);
// review.riskPoints() 可直接展示给法务人员重点关注

实战三:客户评论情感与标签分析

批量分析客户评论,为产品运营提供数据支撑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
record ReviewAnalysis(
    Sentiment sentiment,
    List<String> tags,           // 如:["物流", "质量", "客服"]
    String summary,
    Integer rating,              // 1-5星
    boolean needsAttention       // 是否需要人工跟进
) {}

enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }

interface ReviewAnalyzer {
    @SystemMessage("""
        分析客户评论,提取情感倾向、标签、摘要和评分。
        如果是差评(1-2星)或涉及投诉/退款,needsAttention设为true。
        tags从以下选项中选择:[质量, 物流, 客服, 价格, 包装, 售后, 退款]
        """)
    ReviewAnalysis analyzeReview(@V("review") String reviewText);
}

// 批量处理
ReviewAnalyzer analyzer = AiServices.create(ReviewAnalyzer.class, model);
List<ReviewAnalysis> results = reviews.stream()
        .map(analyzer::analyzeReview)
        .toList();

// 统计需要跟进的评论
long attentionCount = results.stream().filter(ReviewAnalysis::needsAttention).count();

工具(函数调用)

工具/函数调用是 LLM 应用最强大的功能之一,允许 LLM 在需要时调用外部工具来完成特定任务。

核心概念

LLM 本身不能直接调用工具,而是表达调用意图。开发者执行工具后将结果反馈给 LLM,LLM 再基于结果生成最终回答。

1
用户提问 → LLM 判断是否需要工具 → 返回工具调用请求 → 开发者执行工具 → 返回结果 → LLM 生成最终回答

高级 API:@Tool 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class WeatherTools {
    @Tool("返回给定城市的天气预报")
    String getWeather(@P("城市名称") String city) {
        // 调用实际的天气API
        return weatherService.getWeather(city);
    }
    
    @Tool("返回给定城市的空气质量指数")
    String getAirQuality(@P("城市名称") String city) {
        return airQualityService.getAQI(city);
    }
}

interface WeatherAssistant {
    @SystemMessage("你是一个天气助手,可以帮助用户查询天气信息")
    String chat(String message);
}

WeatherAssistant assistant = AiServices.builder(WeatherAssistant.class)
        .chatLanguageModel(model)
        .tools(new WeatherTools())
        .build();

String answer = assistant.chat("北京今天天气怎么样?空气好不好?");
// LLM 会自动调用 getWeather("北京") 和 getAirQuality("北京")

低级 API:ToolSpecification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;

ToolSpecification weatherTool = ToolSpecification.builder()
        .name("getWeather")
        .description("返回给定城市的天气预报")
        .parameters(JsonObjectSchema.builder()
                .addStringProperty("city", "城市名称")
                .required("city")
                .build())
        .build();

ChatRequest request = ChatRequest.builder()
        .messages(UserMessage.from("北京天气如何?"))
        .toolSpecifications(List.of(weatherTool))
        .build();

ChatResponse response = model.chat(request);

if (response.aiMessage().hasToolExecutionRequests()) {
    // LLM 请求调用工具
    ToolExecutionRequest toolRequest = response.aiMessage().toolExecutionRequests().get(0);
    System.out.println("工具名: " + toolRequest.name());       // getWeather
    System.out.println("参数: " + toolRequest.arguments());    // {"city":"北京"}
    
    // 手动执行工具
    String result = weatherService.getWeather("北京");
    
    // 将结果返回给 LLM
    ToolExecutionResultMessage toolResult = ToolExecutionResultMessage.from(toolRequest, result);
    ChatRequest request2 = ChatRequest.builder()
            .messages(UserMessage.from("北京天气如何?"), 
                      response.aiMessage(), 
                      toolResult)
            .toolSpecifications(List.of(weatherTool))
            .build();
    ChatResponse response2 = model.chat(request2);
    System.out.println(response2.aiMessage().text());
}

工具方法参数和返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class OrderTools {
    // 支持基本类型、对象类型、枚举、List、自定义 POJO
    @Tool("查询订单详情")
    Order getOrder(@P("订单号") String orderNo, @P("客户姓名") String customerName) {
        return orderService.findByOrderNo(orderNo, customerName);
    }
    
    // 可选参数
    @Tool("搜索商品")
    List<Product> searchProducts(
            @P("搜索关键词") String keyword,
            @P(required = false, value = "价格排序方式") SortOrder priceOrder) {
        return productService.search(keyword, priceOrder);
    }
    
    // void 返回类型:成功时返回 "Success"
    @Tool("取消订单")
    void cancelOrder(@P("订单号") String orderNo) {
        orderService.cancel(orderNo);
    }
}

企业级工具示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Component
public class DatabaseTools {
    
    private final JdbcTemplate jdbcTemplate;
    private final RedisTemplate<String, String> redisTemplate;
    
    public DatabaseTools(JdbcTemplate jdbcTemplate, RedisTemplate<String, String> redisTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.redisTemplate = redisTemplate;
    }
    
    @Tool("查询数据库中的用户数量")
    long countUsers() {
        return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Long.class);
    }
    
    @Tool("根据用户ID查询用户信息")
    String getUserById(@P("用户ID") Long userId) {
        // 先查缓存
        String cached = redisTemplate.opsForValue().get("user:" + userId);
        if (cached != null) {
            return cached;
        }
        // 查数据库
        Map<String, Object> user = jdbcTemplate.queryForMap("SELECT * FROM users WHERE id = ?", userId);
        String result = user.toString();
        redisTemplate.opsForValue().set("user:" + userId, result, Duration.ofMinutes(30));
        return result;
    }
    
    @Tool("执行只读SQL查询")
    String executeQuery(@P("SQL查询语句(仅支持SELECT)") String sql) {
        // 安全校验:只允许SELECT语句
        if (!sql.trim().toUpperCase().startsWith("SELECT")) {
            return "错误:仅允许执行SELECT查询";
        }
        List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
        return results.toString();
    }
}

安全提示:在工具方法中,务必做好输入校验和权限控制,防止 LLM 被诱导执行危险操作(如 SQL 注入、删除数据等)。关键操作建议加入人工确认环节。

实战一:电商订单查询与操作系统

完整的订单管理工具集,支持查询、取消、退款等操作,展示了工具调用的典型企业级应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Component
@Slf4j
public class OrderTools {

    private final OrderService orderService;
    private final PaymentService paymentService;

    public OrderTools(OrderService orderService, PaymentService paymentService) {
        this.orderService = orderService;
        this.paymentService = paymentService;
    }

    @Tool("查询订单详情,包括商品、金额、状态、物流等信息")
    OrderDetail getOrderDetail(@P("订单号") String orderNo) {
        log.info("工具调用:查询订单 {}", orderNo);
        return orderService.getDetail(orderNo);
    }

    @Tool("查询订单的物流状态")
    LogisticsInfo getLogistics(@P("订单号") String orderNo) {
        return orderService.getLogistics(orderNo);
    }

    @Tool("取消未发货的订单")
    String cancelOrder(@P("订单号") String orderNo, @P("取消原因") String reason) {
        Order order = orderService.getByOrderNo(orderNo);
        if (order.getStatus() == OrderStatus.SHIPPED) {
            return "该订单已发货,无法取消。建议您收到货后申请退货。";
        }
        orderService.cancel(orderNo, reason);
        return "订单" + orderNo + "已成功取消,退款将在1-3个工作日内原路返回。";
    }

    @Tool("申请退款,仅限已签收的订单")
    String requestRefund(
            @P("订单号") String orderNo,
            @P("退款原因") String reason,
            @P("退款金额") @P(required = false) Double amount) {
        Order order = orderService.getByOrderNo(orderNo);
        if (order.getStatus() != OrderStatus.DELIVERED) {
            return "仅已签收的订单可申请退款,当前订单状态:" + order.getStatus();
        }
        double refundAmount = (amount != null) ? amount : order.getTotalAmount();
        paymentService.refund(orderNo, refundAmount, reason);
        return "退款申请已提交,订单" + orderNo + "退款" + refundAmount + "元,预计3-5个工作日到账。";
    }

    @Tool("查询用户的全部订单列表")
    List<OrderSummary> listOrders(
            @P("用户手机号") String phone,
            @P("订单状态筛选") @P(required = false) String status) {
        return orderService.listByPhone(phone, status);
    }
}

在 AI 服务中集成:

1
2
3
4
5
6
7
8
9
10
11
12
@AiService
interface CustomerServiceAssistant {
    @SystemMessage("""
        你是一个电商客服助手。请根据用户的问题调用相应的工具。
        注意:
        - 取消订单前务必先查询订单状态,确认是否可取消
        - 退款操作要确认订单已签收
        - 涉及金额的操作要特别谨慎
        - 语气要友好专业
        """)
    String chat(@MemoryId String userId, @UserMessage String message);
}

对话示例:

1
2
3
4
5
6
7
8
9
10
11
用户: 我的订单202501010001怎么还没到?
AI: [调用 getOrderDetail("202501010001")]
    [调用 getLogistics("202501010001")]
    您的订单202501010001目前正在运输中,预计明天送达。
    物流信息:2025-01-05 快递已到达北京转运中心。

用户: 太慢了,我想取消这个订单
AI: [调用 getOrderDetail("202501010001") 确认状态]
    很抱歉您的订单已经发货,无法直接取消。
    建议您收到货后联系我申请退货退款,届时会全额退款。
    是否需要我帮您标记一下?

实战二:智能数据库查询助手

让非技术人员通过自然语言查询数据库,工具方法自动将自然语言转为 SQL 并执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Component
public class DatabaseQueryTools {

    private final JdbcTemplate jdbcTemplate;

    @Tool("查询用户总数")
    long countUsers() {
        return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Long.class);
    }

    @Tool("按日期范围查询订单金额统计")
    String getOrderStats(
            @P("开始日期,格式yyyy-MM-dd") String startDate,
            @P("结束日期,格式yyyy-MM-dd") String endDate) {
        Map<String, Object> result = jdbcTemplate.queryForMap(
            "SELECT COUNT(*) as order_count, COALESCE(SUM(amount),0) as total_amount " +
            "FROM orders WHERE created_at BETWEEN ? AND ?",
            startDate, endDate);
        return String.format("%s至%s期间,共%s笔订单,总金额%s元",
                startDate, endDate, result.get("order_count"), result.get("total_amount"));
    }

    @Tool("查询指定商品的销售排名")
    String getProductRanking(
            @P("排名前N名") int topN,
            @P("时间范围,如:本月/本季度/本年") String period) {
        String dateCondition = switch (period) {
            case "本月" -> "AND created_at >= DATE_TRUNC('month', CURRENT_DATE)";
            case "本季度" -> "AND created_at >= DATE_TRUNC('quarter', CURRENT_DATE)";
            case "本年" -> "AND created_at >= DATE_TRUNC('year', CURRENT_DATE)";
            default -> "";
        };
        List<Map<String, Object>> results = jdbcTemplate.queryForList(
            "SELECT p.name, SUM(oi.quantity) as total_qty " +
            "FROM order_items oi JOIN products p ON oi.product_id = p.id " +
            "JOIN orders o ON oi.order_id = o.id " +
            "WHERE 1=1 " + dateCondition + " " +
            "GROUP BY p.name ORDER BY total_qty DESC LIMIT ?",
            topN);
        return results.toString();
    }
}

安全提醒:数据库工具中应严格限制为只读查询,避免 LLM 被诱导执行 DELETE/UPDATE 等危险操作。对于写操作,建议单独设置需要人工确认的流程。

RAG(检索增强生成)

LLM 的知识仅限于训练数据。RAG 是一种在发送给 LLM 之前,从外部数据中找到并注入相关信息片段到提示中的方法,可以显著降低幻觉概率。

RAG 两个阶段

  1. 索引阶段(离线):文档预处理 → 分割 → 嵌入 → 存储到向量数据库
  2. 检索阶段(在线):用户提问 → 嵌入查询 → 向量搜索 → 注入提示 → LLM 回答

Easy RAG(最简方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

// 1. 加载文档(支持 TXT、PDF、DOC、PPT、XLS 等)
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/path/to/documents");

// 2. 创建内存向量存储并导入
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, embeddingStore);

// 3. 创建 AI 服务并使用
interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(model)
        .contentRetriever(ContentRetriever.from(embeddingStore, embeddingModel))
        .build();

String answer = assistant.chat("公司的请假制度是什么?");

文档加载

1
2
3
4
5
6
7
8
9
10
11
12
// 加载指定目录的所有文件
List<Document> docs = FileSystemDocumentLoader.loadDocuments("/path/to/docs");

// 递归加载子目录
List<Document> docs = FileSystemDocumentLoader.loadDocumentsRecursively("/path/to/docs");

// 使用 glob 过滤
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.pdf");
List<Document> docs = FileSystemDocumentLoader.loadDocuments("/path/to/docs", pathMatcher);

// 从 URL 加载
Document document = UrlDocumentLoader.load("https://example.com/page.html");

文档分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
import dev.langchain4j.data.document.splitter.DocumentBySentenceSplitter;
import dev.langchain4j.data.document.splitter.DocumentByRegexSplitter;

// 按段落分割,每个片段最多300个token,重叠30个token
DocumentByParagraphSplitter splitter = DocumentByParagraphSplitter.builder()
        .maxParagraphs(1)
        .maxSegmentSize(300, new OpenAiTokenizer())
        .overlapSize(30)
        .build();

// 按句子分割
DocumentBySentenceSplitter sentenceSplitter = new DocumentBySentenceSplitter(
        300,    // 每段最大token数
        30      // 重叠token数
);

// 使用 EmbeddingStoreIngestor 配置分割器
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
        .documentSplitter(splitter)
        .embeddingModel(embeddingModel)
        .embeddingStore(embeddingStore)
        .build();

ingestor.ingest(documents);

实用技巧:分割粒度是影响 RAG 效果的关键因素。片段太大会包含无关信息,太小会丢失上下文。建议根据实际文档特点调整分割策略,一般 300-500 token 为宜,配合适当的重叠。

Naive RAG(基础 RAG)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;

// 创建内容检索器
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
        .embeddingStore(embeddingStore)
        .embeddingModel(embeddingModel)
        .maxResults(5)                    // 最多返回5个相关片段
        .minScore(0.5)                    // 最低相似度阈值
        .build();

// 在 AI 服务中使用
interface Assistant {
    @SystemMessage("基于提供的上下文信息回答问题,如果上下文中没有相关信息,请说明")
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(model)
        .contentRetriever(contentRetriever)
        .build();

Advanced RAG(高级 RAG)

高级 RAG 提供模块化框架,支持查询转换、多源检索、重排序等高级功能。

查询转换

1
2
3
4
5
6
7
8
9
10
11
12
import dev.langchain4j.rag.query.transformer.ExpandingQueryTransformer;
import dev.langchain4j.rag.query.transformer.CompressingQueryTransformer;

// 查询扩展:将一个查询扩展为多个变体
QueryTransformer expandingTransformer = ExpandingQueryTransformer.builder()
        .chatLanguageModel(model)
        .build();

// 查询压缩:将多轮对话压缩为独立查询
QueryTransformer compressingTransformer = CompressingQueryTransformer.builder()
        .chatLanguageModel(model)
        .build();

查询路由

1
2
3
4
5
6
7
8
9
10
11
12
import dev.langchain4j.rag.query.router.QueryRouter;

// 根据查询内容路由到不同的检索器
QueryRouter queryRouter = query -> {
    if (query.text().contains("代码")) {
        return List.of(codeRetriever);
    } else if (query.text().contains("文档")) {
        return List.of(docRetriever);
    } else {
        return List.of(generalRetriever);
    }
};

重排序

1
2
3
4
5
6
7
import dev.langchain4j.rag.content.aggregator.ContentAggregator;
import dev.langchain4j.rag.content.aggregator.ReRankingContentAggregator;

// 使用重排序模型对检索结果重新排序
ContentAggregator aggregator = ReRankingContentAggregator.builder()
        .scoringModel(scoringModel)
        .build();

完整的高级 RAG 流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.RetrievalAugmentor;

RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
        .queryTransformer(expandingTransformer)         // 查询扩展
        .queryRouter(queryRouter)                        // 查询路由
        .contentRetriever(contentRetriever)              // 内容检索
        .contentAggregator(aggregator)                   // 内容聚合/重排序
        .build();

Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(model)
        .retrievalAugmentor(retrievalAugmentor)
        .build();

Metadata 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建带元数据的文档
Metadata metadata = new Metadata();
metadata.put("source", "company-wiki");
metadata.put("department", "HR");
metadata.put("lastUpdated", "2024-01-15");

Document document = Document.from("公司的年假制度...", metadata);

// 搜索时根据元数据过滤
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
        .embeddingStore(embeddingStore)
        .embeddingModel(embeddingModel)
        .filter(metadataKey("department").isEqualTo("HR"))  // 只搜索HR部门的文档
        .build();

向量数据库集成

LangChain4j 支持 20+ 种向量数据库,以下是常见的几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Milvus
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;

MilvusEmbeddingStore store = MilvusEmbeddingStore.builder()
        .host("localhost")
        .port(19530)
        .collectionName("documents")
        .dimension(1536)
        .build();

// Redis
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;

RedisEmbeddingStore store = RedisEmbeddingStore.builder()
        .host("localhost")
        .port(6379)
        .dimension(1536)
        .build();

// Pinecone
import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;

PineconeEmbeddingStore store = PineconeEmbeddingStore.builder()
        .apiKey(System.getenv("PINECONE_API_KEY"))
        .index("documents")
        .build();

// PGVector
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;

PgVectorEmbeddingStore store = PgVectorEmbeddingStore.builder()
        .host("localhost")
        .port(5432)
        .database("vectordb")
        .user("postgres")
        .password("password")
        .table("embeddings")
        .dimension(1536)
        .build();

企业级技巧:生产环境中,推荐使用 Milvus、PGVector 或 Redis 作为向量存储。Milvus 性能强劲适合大规模场景;PGVector 与现有 PostgreSQL 基础设施集成方便;Redis 适合需要低延迟的场景。

实战:企业级知识库问答系统

以下是一个完整的企业级知识库问答系统架构与实现,涵盖从文档摄入到智能问答的全流程:

架构设计(按团队规模):

架构组件小型团队(<10人)中型企业(10-100人)大型组织(>100人)
向量数据库InMemoryEmbeddingStoreRedis Stack / ChromaMilvus / Weaviate
文档处理单机同步处理分布式队列异步处理微服务化文档处理流水线
检索策略简单向量检索混合检索(向量+关键词)多级检索+重排序
部署方式单体应用容器化部署Kubernetes 集群
监控告警基础日志指标监控+告警全链路追踪+智能告警

1. 文档摄入服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Service
@Slf4j
public class DocumentIngestionService {

    private final EmbeddingStoreIngestor ingestor;

    public DocumentIngestionService(EmbeddingModel embeddingModel,
                                     EmbeddingStore<TextSegment> embeddingStore) {
        this.ingestor = EmbeddingStoreIngestor.builder()
                .documentSplitter(DocumentSplitters.recursive(
                        300, 30, new OpenAiTokenizer()))
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .documentTransformer(doc -> {
                    // 添加元数据,便于后续过滤和溯源
                    doc.metadata().put("ingestedAt", Instant.now().toString());
                    return doc;
                })
                .build();
    }

    /**
     * 批量导入目录下的文档
     */
    @Async
    public CompletableFuture<Void> ingestDirectory(String directoryPath) {
        PathMatcher pdfMatcher = FileSystems.getDefault().getPathMatcher("glob:**.pdf");
        PathMatcher docMatcher = FileSystems.getDefault().getPathMatcher("glob:**.{doc,docx}");
        PathMatcher allMatcher = FileSystems.getDefault().getPathMatcher("glob:**.{pdf,doc,docx,txt,md,html}");

        List<Document> documents = FileSystemDocumentLoader.loadDocuments(
                Path.of(directoryPath), allMatcher);

        log.info("开始摄入{}个文档...", documents.size());
        ingestor.ingest(documents);
        log.info("文档摄入完成");

        return CompletableFuture.completedFuture(null);
    }

    /**
     * 单文档实时导入(用户上传场景)
     */
    public void ingestSingleDocument(MultipartFile file, String category) throws IOException {
        // 解析文档
        DocumentParser parser = new ApacheTikaDocumentParser();
        Document document = parser.parse(file.getInputStream());

        // 增强元数据
        document.metadata().put("fileName", file.getOriginalFilename());
        document.metadata().put("category", category);
        document.metadata().put("fileSize", file.getSize());

        ingestor.ingest(document);
        log.info("文档 {} 摄入成功", file.getOriginalFilename());
    }
}

2. 智能问答服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Service
public class KnowledgeBaseQAService {

    private final ChatLanguageModel chatModel;
    private final ContentRetriever contentRetriever;

    public KnowledgeBaseQAService(ChatLanguageModel chatModel,
                                   EmbeddingStore<TextSegment> embeddingStore,
                                   EmbeddingModel embeddingModel) {
        this.chatModel = chatModel;
        this.contentRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(embeddingModel)
                .maxResults(5)           // 返回最相关的5个片段
                .minScore(0.6)           // 最低相似度阈值
                .build();
    }

    public String ask(String question, String category) {
        // 如果需要按分类过滤
        ContentRetriever filteredRetriever = EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(embeddingModel)
                .filter(metadataKey("category").isEqualTo(category))
                .maxResults(5)
                .minScore(0.6)
                .build();

        interface KBAssistant {
            @SystemMessage("""
                你是一个企业知识库助手。请基于提供的上下文信息回答用户问题。
                规则:
                1. 只基于提供的上下文信息回答
                2. 如果上下文中没有相关信息,明确说“我在知识库中未找到相关信息”
                3. 不要编造或推测任何信息
                4. 引用来源时,说明出自哪个文档
                """)
            Result<String> answer(String question);
        }

        KBAssistant assistant = AiServices.builder(KBAssistant.class)
                .chatLanguageModel(chatModel)
                .contentRetriever(filteredRetriever)
                .build();

        Result<String> result = assistant.answer(question);
        // result.sources() 包含了检索到的原文片段,可用于溯源
        return result.content();
    }
}

3. 实际效果案例(某电商客服系统):

场景:用户咨询”我买的洗衣机坏了,怎么维修?”

1
2
3
4
5
6
7
8
9
10
处理流程:
1. 用户输入通过意图识别为“售后维修”
2. RAG引擎检索知识库,命中文档《家电维修政策》
3. 生成回答:“根据政策,您可联系400-xxx-xxxx预约上门维修,或携带发票至线下门店。”
4. 若用户追问“需要准备什么?”,系统检索《维修准备清单》并补充回答

实测效果:
- 回答准确率从72%提升至91%
- 平均响应时间从15秒降至3秒
- 人工客服工作量减少60%

Agent(智能代理)

Agent 是 LLM 应用的最高级形态,能够自主规划和使用工具来完成复杂任务。

基础 Agent

使用 AI 服务 + 工具即可构建基础 Agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Component
public class CustomerSupportTools {
    
    private final BookingService bookingService;
    
    public CustomerSupportTools(BookingService bookingService) {
        this.bookingService = bookingService;
    }
    
    @Tool("查询预订详情")
    Booking getBookingDetails(
            @P("预订号") String bookingNumber, 
            @P("客户名") String customerName, 
            @P("客户姓") String customerSurname) {
        return bookingService.getBookingDetails(bookingNumber, customerName, customerSurname);
    }
    
    @Tool("取消预订")
    void cancelBooking(
            @P("预订号") String bookingNumber, 
            @P("客户名") String customerName, 
            @P("客户姓") String customerSurname) {
        bookingService.cancelBooking(bookingNumber, customerName, customerSurname);
    }
}

@AiService
interface CustomerSupportAgent {
    @SystemMessage("""
        你是一个客户支持代理。
        在取消预订之前,请务必确认客户的身份信息和预订详情。
        语气要礼貌专业。
        """)
    String chat(@MemoryId String userId, @UserMessage String message);
}

Agentic 模块(新特性)

LangChain4j 新增了 langchain4j-agentic 模块,支持构建更复杂的 Agent 编排工作流:

顺序工作流(Sequential)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import dev.langchain4j.agentic.workflow.SequentialAgent;

// 多个 Agent 按顺序执行,前一个的输出作为后一个的输入
Agent researchAgent = Agent.builder()
        .name("researcher")
        .systemMessage("你负责搜索和收集信息")
        .chatModel(model)
        .build();

Agent writerAgent = Agent.builder()
        .name("writer")
        .systemMessage("你负责根据研究结果撰写文章")
        .chatModel(model)
        .build();

Agent editorAgent = Agent.builder()
        .name("editor")
        .systemMessage("你负责审核和润色文章")
        .chatModel(model)
        .build();

SequentialAgent pipeline = SequentialAgent.builder()
        .agents(researchAgent, writerAgent, editorAgent)
        .build();

String result = pipeline.execute("写一篇关于AI发展趋势的文章");

条件工作流(Conditional)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 根据条件选择不同的 Agent 处理
Agent techSupport = Agent.builder()
        .name("tech-support")
        .systemMessage("你负责技术支持")
        .chatModel(model)
        .build();

Agent billingSupport = Agent.builder()
        .name("billing-support")
        .systemMessage("你负责账单问题")
        .chatModel(model)
        .build();

ConditionalAgent router = ConditionalAgent.builder()
        .condition(query -> query.contains("账单") ? "billing" : "tech")
        .branch("billing", billingSupport)
        .branch("tech", techSupport)
        .build();

循环工作流(Loop)

1
2
3
4
5
6
// Agent 反复执行直到满足条件
LoopAgent reviewLoop = LoopAgent.builder()
        .agent(reviewer)
        .maxIterations(5)            // 最多循环5次
        .terminationCondition(result -> result.contains("APPROVED"))
        .build();

并行工作流(Parallel)

1
2
3
4
5
6
// 多个 Agent 同时执行
ParallelAgent parallelAgent = ParallelAgent.builder()
        .agents(marketAnalyzer, techAnalyzer, riskAnalyzer)
        .build();

String combinedResult = parallelAgent.execute("分析这个投资标的");

主管模式(Supervisor)

1
2
3
4
5
6
7
// 主管 Agent 自主决定调用哪个 Agent
Agent supervisor = Agent.builder()
        .name("supervisor")
        .systemMessage("你是一个主管,根据用户需求决定调用哪个专业Agent")
        .chatModel(model)
        .tools(researchTool, analysisTool, writingTool)
        .build();

多 Agent 系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用 A2A(Agent-to-Agent)协议实现 Agent 间通信
// 通过 langchain4j-agentic-a2a 模块

// 主管 Agent + 专业 Agent 架构
Agent coordinator = Agent.builder()
        .name("coordinator")
        .systemMessage("你是协调者,将任务分配给合适的专业Agent")
        .chatModel(model)
        .build();

Agent coder = Agent.builder()
        .name("coder")
        .systemMessage("你是编程专家,负责编写代码")
        .chatModel(model)
        .tool(coderTool)
        .build();

Agent tester = Agent.builder()
        .name("tester")
        .systemMessage("你是测试专家,负责编写测试用例")
        .chatModel(model)
        .tool(testTool)
        .build();

企业级技巧:Agent 系统中,务必设置合理的终止条件和最大迭代次数,防止 Agent 陷入无限循环。对于关键操作(如删除数据、发送邮件),建议加入人工审核(human-in-the-loop)机制。

实战一:招聘流程监督者 Agent

以下是基于 LangChain4j Agentic 模块实现的企业级招聘流程监督者系统,监督者 Agent 自主决定调用哪些子 Agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 1. 定义子智能体
public interface HrCvReviewer {
    @Agent("HR评审员,从人力资源角度审查候选人简历")
    ResultWithAgenticScope<String> reviewCv(@V("cv") String cv, @V("jobDescription") String jobDescription);
}

public interface ManagerCvReviewer {
    @Agent("经理评审员,从技术和管理角度审查候选人简历")
    ResultWithAgenticScope<String> reviewCv(@V("cv") String cv, @V("jobDescription") String jobDescription);
}

public interface InterviewOrganizer {
    @Agent("面试安排员,为通过筛选的候选人安排面试")
    ResultWithAgenticScope<String> organize(@V("candidateName") String name, @V("position") String position);
}

public interface EmailAssistant {
    @Agent("邮件助手,向未通过筛选的候选人发送拒绝邮件")
    ResultWithAgenticScope<String> sendRejection(@V("candidateName") String name, @V("position") String position);
}

// 2. 构建子智能体实例
HrCvReviewer hrReviewer = AgenticServices.agentBuilder(HrCvReviewer.class)
        .chatModel(model)
        .outputKey("hrReview")
        .build();

ManagerCvReviewer managerReviewer = AgenticServices.agentBuilder(ManagerCvReviewer.class)
        .chatModel(model)
        .outputKey("managerReview")
        .build();

InterviewOrganizer interviewOrganizer = AgenticServices.agentBuilder(InterviewOrganizer.class)
        .chatModel(model)
        .tools(new OrganizingTools())  // 包含日历、邮件等工具
        .build();

EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.class)
        .chatModel(model)
        .tools(new OrganizingTools())
        .build();

// 3. 构建监督者智能体
SupervisorAgent hiringSupervisor = AgenticServices.supervisorBuilder()
        .chatModel(model)
        .subAgents(hrReviewer, managerReviewer, interviewOrganizer, emailAssistant)
        .contextGenerationStrategy(SupervisorContextStrategy.CHAT_MEMORY_AND_SUMMARIZATION)
        .responseStrategy(SupervisorResponseStrategy.SUMMARY)
        .supervisorContext("始终使用所有可用的评审者。始终用中文回答。调用智能体时使用纯JSON格式。")
        .build();

// 4. 执行招聘流程
String result = hiringSupervisor.invoke(
    "请审查以下候选人简历:" + candidateCv + ",职位要求:" + jobDescription);

// 监督者会自动:
// 1. 调用HR评审 → 获得HR视角的评价
// 2. 调用经理评审 → 获得技术视角的评价  
// 3. 根据评审结果决定下一步:
//    - 通过 → 调用面试安排员
//    - 不通过 → 调用邮件助手发送拒绝邮件

实战二:AI 辅助代码审查工作流

构建一个自动化的代码审查流水线,多个 Agent 协作完成代码审查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 定义代码审查相关的工具
@Component
public class CodeReviewTools {

    @Tool("获取指定文件最新的代码变更")
    String getFileDiff(@P("文件路径") String filePath, @P("分支名") String branch) {
        return gitService.getFileDiff(filePath, branch);
    }

    @Tool("获取代码仓库中指定文件的完整内容")
    String getFileContent(@P("文件路径") String filePath, @P("分支名") String branch) {
        return gitService.getFileContent(filePath, branch);
    }

    @Tool("在代码审查系统中添加评论")
    void addReviewComment(@P("文件路径") String filePath, @P("行号") int line, @P("评论内容") String comment) {
        codeReviewService.addComment(filePath, line, comment);
    }

    @Tool("批准代码合并请求")
    void approveMergeRequest(@P("合并请求ID") String mrId) {
        codeReviewService.approve(mrId);
    }
}

// 定义安全审查工具
@Component
public class SecurityTools {

    @Tool("检查代码是否包含常见安全漏洞模式")
    List<String> scanSecurityIssues(@P("代码内容") String code) {
        return securityScanner.scan(code);
    }

    @Tool("检查依赖包是否存在已知安全漏洞")
    List<String> checkDependencyVulnerabilities(@P("项目路径") String projectPath) {
        return dependencyChecker.check(projectPath);
    }
}

// 定义审查 Agent
@AiService
interface CodeReviewerAgent {
    @SystemMessage("""
        你是一个高级代码审查员。请检查代码的:
        1. 代码质量和可读性
        2. 潜在的 Bug 和逻辑错误
        3. 性能问题
        4. 设计模式的使用
        给出具体的改进建议和代码示例。
        """)
    String reviewCode(@UserMessage String codeChange);
}

@AiService
interface SecurityReviewerAgent {
    @SystemMessage("""
        你是一个安全审查专家。请检查代码中是否存在:
        1. SQL注入、XSS等安全漏洞
        2. 敏感信息泄露
        3. 不安全的加密使用
        4. 权限控制缺失
        严重问题请标记为 [CRITICAL],一般问题标记为 [WARNING]。
        """)
    String reviewSecurity(@UserMessage String codeChange);
}

// 顺序工作流:代码审查 → 安全审查 → 汇总
// 代码审查Agent先检查代码质量,安全审查Agent再检查安全漏洞
// 最后汇总所有审查意见,决定是否批准合并

实战三:邮件自动分类与回复 Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component
public class EmailTools {

    @Tool("获取未读邮件列表")
    List<EmailInfo> getUnreadEmails() {
        return emailService.getUnreadEmails();
    }

    @Tool("发送邮件回复")
    void sendReply(@P("邮件ID") String emailId, 
                   @P("回复内容") String content,
                   @P("是否需要人工审核") boolean needApproval) {
        if (needApproval) {
            // 需要人工审核的邮件先放入待审核队列
            approvalQueue.add(new PendingReply(emailId, content));
        } else {
            emailService.sendReply(emailId, content);
        }
    }

    @Tool("转发邮件给指定部门")
    void forwardEmail(@P("邮件ID") String emailId, @P("目标部门") String department) {
        emailService.forward(emailId, department);
    }
}

@AiService
interface EmailAssistantAgent {
    @SystemMessage("""
        你是一个邮件处理助手。对于收到的邮件:
        1. 判断邮件类型:咨询/投诉/合作/其他
        2. 咨询类:根据知识库生成回复草稿
        3. 投诉类:标记为紧急,转交客服部门
        4. 合作类:转交商务部门
        5. 所有对外回复必须经过人工审核后再发送
        """)
    String processEmails(@UserMessage String instruction);
}

Spring Boot 集成

LangChain4j 提供了完善的 Spring Boot 自动配置支持,极大简化了开发流程。

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- OpenAI Spring Boot Starter -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    <version>1.0.0-beta3</version>
</dependency>

<!-- 声明式 AI 服务 Starter -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-spring-boot-starter</artifactId>
    <version>1.0.0-beta3</version>
</dependency>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
# application.yml
langchain4j:
  open-ai:
    chat-model:
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4o
      temperature: 0.7
      max-tokens: 4096
      log-requests: true
      log-responses: true
    streaming-chat-model:
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4o

声明式 AI 服务

1
2
3
4
5
@AiService
interface Assistant {
    @SystemMessage("你是一个智能客服助手,请用专业且友好的语气回答问题")
    String chat(@MemoryId String userId, @UserMessage String message);
}

无需手动调用 AiServices.create(),Spring Boot 启动时自动扫描 @AiService 注解的接口并注册为 Bean。

自动装配工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class OrderTools {
    
    private final OrderService orderService;
    
    public OrderTools(OrderService orderService) {
        this.orderService = orderService;
    }
    
    @Tool("查询订单状态")
    String getOrderStatus(@P("订单号") String orderNo) {
        return orderService.getStatus(orderNo);
    }
    
    @Tool("申请退款")
    String requestRefund(@P("订单号") String orderNo, @P("退款原因") String reason) {
        return orderService.refund(orderNo, reason);
    }
}

所有标注了 @Tool 的方法会自动装配到 AI 服务中。

显式组件装配

当存在多个同类型组件时,使用显式装配:

1
2
3
4
5
6
7
8
9
10
# 配置多个模型
langchain4j:
  open-ai:
    chat-model:
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4o-mini
  ollama:
    chat-model:
      base-url: http://localhost:11434
      model-name: llama3.1
1
2
3
4
5
6
7
8
9
10
11
@AiService(wiringMode = WiringMode.EXPLICIT, chatModel = "openAiChatModel")
interface OpenAiAssistant {
    @SystemMessage("你是OpenAI助手")
    String chat(String message);
}

@AiService(wiringMode = WiringMode.EXPLICIT, chatModel = "ollamaChatModel")
interface OllamaAssistant {
    @SystemMessage("你是Ollama助手")
    String chat(String message);
}

完整的 Spring Boot 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Controller
@RestController
@RequestMapping("/api/chat")
public class ChatController {
    
    private final Assistant assistant;
    
    public ChatController(Assistant assistant) {
        this.assistant = assistant;
    }
    
    @PostMapping
    public ChatResponse chat(@RequestBody ChatRequest request) {
        String answer = assistant.chat(request.getUserId(), request.getMessage());
        return new ChatResponse(answer);
    }
}

// DTO
record ChatRequest(String userId, String message) {}
record ChatResponse(String answer) {}

// 配置持久化聊天记忆
@Configuration
public class ChatMemoryConfig {
    
    @Bean
    ChatMemoryProvider chatMemoryProvider(ChatMemoryStore chatMemoryStore) {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(20)
                .chatMemoryStore(chatMemoryStore)
                .build();
    }
}

可观察性(Observability)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
public class ObservabilityConfig {
    
    @Bean
    ChatModelListener chatModelListener() {
        return new ChatModelListener() {
            private static final Logger log = LoggerFactory.getLogger(ChatModelListener.class);
            
            @Override
            public void onRequest(ChatModelRequestContext requestContext) {
                log.info("LLM请求: {}", requestContext.chatRequest().messages().size() + " 条消息");
            }
            
            @Override
            public void onResponse(ChatModelResponseContext responseContext) {
                TokenUsage usage = responseContext.chatResponse().tokenUsage();
                log.info("LLM响应: 输入{}token, 输出{}token", 
                        usage.inputTokenCount(), usage.outputTokenCount());
            }
            
            @Override
            public void onError(ChatModelErrorContext errorContext) {
                log.error("LLM错误: {}", errorContext.error().getMessage());
            }
        };
    }
}

实战:生产级 Spring Boot AI 对话系统

以下是一个完整的生产级 AI 对话系统配置,整合了 Redis 持久化记忆、流式响应、工具调用和可观察性:

1. 依赖配置(pom.xml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<dependencies>
    <!-- Spring Boot WebFlux(支持流式响应) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- LangChain4j 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>

    <!-- OpenAI 集成 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>

    <!-- 响应式支持(Flux) -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-reactor</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>
</dependencies>

2. 应用配置(application.yml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
langchain4j:
  open-ai:
    streaming-chat-model:
      base-url: https://api.openai.com/v1
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4o-mini
      temperature: 0.7
      max-tokens: 4096
      log-requests: true
      log-responses: true

spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      database: 1

3. 流式助手接口:

1
2
3
4
5
@AiService
public interface StreamingAssistant {
    @SystemMessage("你是一个专业的AI助手,请用中文回答问题,语气友好专业")
    Flux<String> chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

4. 自定义工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class AssistantTools {

    @Tool("获取当前日期和时间")
    public String currentTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    @Tool("计算数学表达式")
    public String calculate(@P("数学表达式") String expression) {
        try {
            // 使用安全的表达式计算器
            return String.valueOf(mathEvaluator.evaluate(expression));
        } catch (Exception e) {
            return "计算错误:" + e.getMessage();
        }
    }
}

5. 控制器(支持 SSE 流式输出):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
@RequestMapping("/api/v1/chat")
public class ChatController {

    private final OpenAiStreamingChatModel streamingModel;
    private final RedisChatMemoryStore chatMemoryStore;
    private final AssistantTools assistantTools;

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(
            @RequestParam String message,
            @RequestParam(defaultValue = "1") int memoryId) {

        ChatMemoryProvider chatMemoryProvider = memoryId2 ->
                MessageWindowChatMemory.builder()
                        .id(memoryId2)
                        .maxMessages(20)
                        .chatMemoryStore(chatMemoryStore)
                        .build();

        StreamingAssistant assistant = AiServices.builder(StreamingAssistant.class)
                .streamingChatLanguageModel(streamingModel)
                .chatMemoryProvider(chatMemoryProvider)
                .tools(assistantTools)
                .build();

        return assistant.chat(memoryId, message);
    }
}

6. 前端调用示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 EventSource 接收流式响应
const eventSource = new EventSource(
    `/api/v1/chat/stream?message=${encodeURIComponent(input)}&memoryId=${userId}`
);

eventSource.onmessage = function(event) {
    document.getElementById('response').textContent += event.data;
};

eventSource.onerror = function() {
    eventSource.close();
};

项目结构一览:

1
2
3
4
5
6
7
8
9
10
11
12
src/main/java/com/example/ai/
├── config/
│   ├── RedisChatMemoryStore.java     # Redis持久化记忆
│   └── RedisTemplateConfig.java      # Redis配置
├── controller/
│   └── ChatController.java           # 聊天接口
├── service/
│   └── StreamingAssistant.java       # AI助手接口
├── tool/
│   └── AssistantTools.java           # 自定义工具
└── listener/
    └── ChatModelListener.java        # 监听器(可观察性)

LangChain4j 支持 15+ 个主流 LLM 提供商,以下是部分列表:

提供商模块名聊天流式工具JSON Schema
OpenAIlangchain4j-open-ai
Azure OpenAIlangchain4j-azure-open-ai
Google Geminilangchain4j-google-ai-gemini
Anthropiclangchain4j-anthropic
Ollamalangchain4j-ollama
Mistral AIlangchain4j-mistral-ai
DashScope(千问)langchain4j-dashscope
智谱 AIlangchain4j-zhipu
AWS Bedrocklangchain4j-bedrock
百度千帆langchain4j-qianfan
MiniMaxlangchain4j-minimax
讯飞星火langchain4j-spark

嵌入模型

嵌入模型将文本转换为向量表示,是 RAG 的核心组件。

进程内嵌入模型

LangChain4j 提供了 5 种可在 JVM 内直接运行的嵌入模型(无需外部 API):

1
2
3
4
5
6
7
8
import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;

// 使用 ONNX Runtime 在进程内运行
AllMiniLmL6V2EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

// 生成嵌入向量
Response<Embedding> response = embeddingModel.embed("Hello World");
float[] vector = response.content().vector();

云端嵌入模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// OpenAI 嵌入
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;

OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("text-embedding-3-small")
        .build();

// DashScope 嵌入
import dev.langchain4j.model.dashscope.QwenEmbeddingModel;

QwenEmbeddingModel embeddingModel = QwenEmbeddingModel.builder()
        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
        .modelName("text-embedding-v2")
        .build();

嵌入相似度计算

1
2
3
4
5
6
7
8
9
10
11
12
13
import dev.langchain4j.store.embedding.CosineSimilarity;
import dev.langchain4j.store.embedding.RelevanceScore;

Embedding embedding1 = embeddingModel.embed("Java编程").content();
Embedding embedding2 = embeddingModel.embed("Python编程").content();
Embedding embedding3 = embeddingModel.embed("美食烹饪").content();

double sim12 = CosineSimilarity.between(embedding1, embedding2);
double sim13 = CosineSimilarity.between(embedding1, embedding3);
// sim12 > sim13(编程相关度更高)

// 转换为0-1之间的相关度分数
double relevance = RelevanceScore.fromCosineSimilarity(sim12);

文档处理管道

EmbeddingStoreIngestor

EmbeddingStoreIngestor 是一个完整的文档摄入管道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;

// 构建摄入管道
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
        // 1. 文档分割器
        .documentSplitter(DocumentSplitters.recursive(300, 30, new OpenAiTokenizer()))
        // 2. 文档后处理器(可选)
        .documentTransformer(document -> {
            // 自定义文档预处理,如清洗、去噪
            return document;
        })
        // 3. 嵌入模型
        .embeddingModel(embeddingModel)
        // 4. 向量存储
        .embeddingStore(embeddingStore)
        // 5. 元数据增强(可选)
        .documentTransformer(document -> {
            document.metadata().put("ingestedAt", Instant.now().toString());
            return document;
        })
        .build();

// 加载并摄入文档
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/path/to/docs");
ingestor.ingest(documents);

自定义文档解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import dev.langchain4j.data.document.DocumentParser;

// 自定义解析器
public class CsvDocumentParser implements DocumentParser {
    @Override
    public List<Document> parse(InputStream inputStream) {
        // 解析 CSV 文件的逻辑
        // ...
        return documents;
    }
}

// 使用自定义解析器加载文档
DocumentParser parser = new CsvDocumentParser();
List<Document> documents = FileSystemDocumentLoader.loadDocuments(
        Path.of("/path/to/csv"),
        pathMatcher,
        parser
);

企业级最佳实践

1. API 密钥管理

1
2
3
4
5
6
7
8
9
// 错误:硬编码API密钥
// String apiKey = "sk-xxxxxx";  // 绝对不要这样做!

// 正确:使用环境变量
String apiKey = System.getenv("OPENAI_API_KEY");

// 更好:Spring Boot 配置文件 + 加密
// application.yml 中使用 ${OPENAI_API_KEY} 引用环境变量
// 或使用 Spring Cloud Config Server / Vault 管理密钥

2. 请求重试和超时

1
2
3
4
5
6
7
8
9
ChatLanguageModel model = OpenAiChatModel.builder()
        .apiKey(apiKey)
        .modelName("gpt-4o-mini")
        .timeout(Duration.ofSeconds(60))            // 请求超时
        .maxRetries(3)                               // 最大重试次数
        .temperature(0.7)                             // 控制创造性
        .topP(1.0)                                    // 核采样
        .maxTokens(4096)                              // 最大输出token数
        .build();

3. 成本控制

1
2
3
4
5
6
7
8
9
10
11
// 监控每次请求的 token 使用量
ChatResponse response = model.chat(request);
TokenUsage usage = response.tokenUsage();
log.info("本次请求消耗: 输入{} tokens, 输出{} tokens, 总计{} tokens",
        usage.inputTokenCount(), 
        usage.outputTokenCount(), 
        usage.totalTokenCount());

// 设置合理的 maxTokens 限制输出长度
// 使用合适的模型:简单任务用 gpt-4o-mini,复杂任务用 gpt-4o
// 优化提示词:简洁明确的提示词可以减少 token 消耗

4. 幻觉控制

1
2
3
4
5
6
7
8
@SystemMessage("""
    你是一个专业的知识助手。请遵循以下规则:
    1. 只基于提供的上下文信息回答问题
    2. 如果上下文中没有相关信息,请明确回答"我没有找到相关信息"
    3. 不要编造或推测任何信息
    4. 如果不确定,请说明不确定的部分
    """)
String chat(String message);

5. 输入输出护栏(Guardrails)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 输入护栏:过滤敏感信息
public class InputGuardrail {
    
    private static final List<String> SENSITIVE_PATTERNS = List.of(
            "密码", "身份证号", "银行卡号"
    );
    
    public String validate(String input) {
        for (String pattern : SENSITIVE_PATTERNS) {
            if (input.contains(pattern)) {
                return "您的输入包含敏感信息,请移除后重试";
            }
        }
        return input;
    }
}

// 输出护栏:验证输出格式和内容
public class OutputGuardrail {
    
    public String validate(String output) {
        // 检查是否包含不当内容
        // 检查输出格式是否合法
        // 检查输出长度是否合理
        return output;
    }
}

6. 异步处理

1
2
3
4
5
6
7
8
9
10
@Service
public class AiService {
    
    private final Assistant assistant;
    private final ExecutorService executor;
    
    public CompletableFuture<String> chatAsync(String message) {
        return CompletableFuture.supplyAsync(() -> assistant.chat(message), executor);
    }
}

7. 缓存策略

1
2
3
4
5
6
7
8
9
10
11
12
// 对相同问题的回答进行缓存
@Configuration
public class AiCacheConfig {
    
    @Bean
    public Cache<String, String> aiResponseCache() {
        return Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(Duration.ofHours(1))
                .build();
    }
}

8. 生产环境检查清单

检查项说明
API 密钥安全使用环境变量或密钥管理服务,禁止硬编码
请求超时设置合理的超时时间,避免长时间阻塞
重试策略配置合理的重试次数和间隔
Token 监控监控每次请求的 token 消耗,控制成本
错误处理捕获并优雅处理 LLM API 错误
内容安全过滤敏感输入,验证输出内容
聊天记忆持久化避免服务重启导致对话丢失
速率限制控制请求频率,避免超出 API 配额
日志记录记录请求和响应用于调试和审计
降级方案LLM 不可用时的备选方案

9. 企业级应用场景汇总

以下是 LangChain4j 在企业中的典型应用场景及实现要点:

应用场景核心技术实现复杂度业务价值
智能客服AI Services + Tools + Memory降低60%人工客服工作量
知识库问答RAG + EmbeddingStore信息检索效率提升10倍
文档智能审核结构化输出 + Agent中高审核效率提升5倍
简历筛选结构化输出 + Agent招聘效率提升3倍
代码审查Tools + Agent工作流代码质量提升30%
邮件分类回复Tools + Agent邮件处理效率提升5倍
数据报表查询Tools + 结构化输出非技术人员自助查询
合同分析结构化输出 + RAG法务审核效率提升4倍
舆情监控RAG + 结构化输出实时掌握品牌动态
智能排班Agent + Tools人力成本降低15%

10. 降级与容错策略

生产环境中 LLM API 可能不可用,需要设计容错机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Service
@Slf4j
public class ResilientAiService {

    private final ChatLanguageModel primaryModel;   // 主模型(如OpenAI)
    private final ChatLanguageModel fallbackModel;   // 备用模型(如Ollama本地)
    private final Cache<String, String> responseCache;

    public String chat(String message) {
        try {
            // 1. 先查缓存
            String cached = responseCache.getIfPresent(message);
            if (cached != null) {
                log.debug("命中缓存");
                return cached;
            }

            // 2. 尝试主模型
            String response = primaryModel.chat(message);
            responseCache.put(message, response);
            return response;

        } catch (Exception e) {
            log.warn("主模型调用失败,尝试备用模型: {}", e.getMessage());

            try {
                // 3. 降级到备用模型
                return fallbackModel.chat(message);

            } catch (Exception e2) {
                log.error("备用模型也失败: {}", e2.getMessage());

                // 4. 最终降级:返回预设回复
                return "抱歉,AI服务暂时不可用,请稍后重试或联系人工客服。";
            }
        }
    }
}

11. 多模型路由策略

根据任务类型自动选择最合适的模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class ModelRouter {

    private final ChatLanguageModel gpt4o;          // 复杂推理
    private final ChatLanguageModel gpt4oMini;      // 日常对话
    private final ChatLanguageModel localModel;    // 隐私敏感任务

    public ChatLanguageModel selectModel(String prompt, TaskType type) {
        return switch (type) {
            case COMPLEX_REASONING -> gpt4o;        // 复杂推理用最强模型
            case SIMPLE_CHAT -> gpt4oMini;           // 日常对话用轻量模型
            case PRIVACY_SENSITIVE -> localModel;    // 隐私数据用本地模型
        };
    }

    // 根据token估算自动选择
    public ChatLanguageModel autoSelect(String prompt) {
        int estimatedTokens = prompt.length() / 2;  // 粗略估算
        if (estimatedTokens > 8000) {
            return gpt4o;      // 长文本需要大上下文窗口
        } else if (containsSensitiveData(prompt)) {
            return localModel;  // 敏感数据走本地
        } else {
            return gpt4oMini;   // 普通场景用经济型
        }
    }
}

常见问题与踩坑

1. 模型选择问题

1
2
3
4
5
6
Q: 该选择哪个模型?
A: 根据场景选择:
   - 简单对话/文本生成:gpt-4o-mini 或通义千问-turbo(低成本)
   - 复杂推理/代码生成:gpt-4o 或 Claude-3.5-Sonnet(高质量)
   - 本地部署/隐私敏感:Ollama + llama3.1(免费+离线)
   - 国内合规要求:通义千问/智谱GLM/百度千帆

2. Token 限制问题

1
2
3
4
5
6
Q: 对话太长导致超出 token 限制怎么办?
A: 
   - 使用 TokenWindowChatMemory 自动管理上下文窗口
   - 设置合理的 maxTokens 和 maxMessages
   - 对长文档使用 RAG 而非将全文放入上下文
   - 对历史消息进行摘要压缩

3. 工具调用失败

1
2
3
4
5
6
7
Q: LLM 没有调用预期的工具怎么办?
A:
   - 检查工具描述是否清晰明确
   - 确认使用的模型支持工具调用
   - 在系统提示中明确指示何时使用工具
   - 简化工具参数,避免过于复杂
   - 添加更多示例引导 LLM 正确使用工具

4. 中文支持问题

1
2
3
4
5
6
Q: 中文场景下效果不好怎么办?
A:
   - 选择对中文支持好的模型(千问、GLM、GPT-4o)
   - 在系统提示中明确要求使用中文回答
   - 优化中文提示词,确保意图清晰
   - 使用中文友好的嵌入模型

5. 流式响应中断

1
2
3
4
5
6
Q: 流式响应中途出错怎么办?
A:
   - 实现 onError 回调,记录错误日志
   - 设置重试机制
   - 在前端实现断点续传
   - 检查网络连接稳定性

总结

LangChain4j 为 Java 开发者提供了一个全面、灵活的 LLM 应用开发框架。从简单的聊天到复杂的 RAG 系统,从单一 Agent 到多 Agent 编排,它都提供了完善的解决方案。

学习路径建议

  1. 入门阶段:掌握 ChatLanguageModel + AI 服务 + 聊天记忆
    • 实战目标:构建一个简单的多轮对话聊天机器人
    • 推荐练习:使用 Ollama 本地模型跑通第一个对话
  2. 进阶阶段:学习结构化输出 + 工具调用 + 流式响应
    • 实战目标:构建一个带工具调用的智能客服助手
    • 推荐练习:实现订单查询 + 流式响应 + Redis 记忆持久化
  3. 高级阶段:深入 RAG 管道 + Agent 编排 + Spring Boot 集成
    • 实战目标:构建一个企业级知识库问答系统
    • 推荐练习:实现 PDF 文档摄入 + 向量检索 + 问答闭环
  4. 生产阶段:关注可观察性 + 安全护栏 + 成本控制 + 高可用架构
    • 实战目标:将 AI 功能集成到现有业务系统
    • 推荐练习:实现多模型路由 + 降级策略 + 监控告警

实战项目推荐

项目难度涉及知识点学习收获
个人AI助手AI Services + Memory + Streaming掌握基础对话能力
智能客服系统⭐⭐Tools + Memory + SSE掌握工具调用和流式输出
知识库问答⭐⭐⭐RAG + Embedding + 分割策略掌握 RAG 全流程
简历筛选系统⭐⭐⭐结构化输出 + Agent掌握信息抽取和 Agent
代码审查助手⭐⭐⭐⭐Agent工作流 + Tools掌握多 Agent 协作
企业级智能客服⭐⭐⭐⭐⭐全部知识点掌握生产级架构设计

相关资源

  • 官方文档:https://docs.langchain4j.info/
  • GitHub 仓库:https://github.com/langchain4j/langchain4j
  • 中文社区文档:https://langchain4j.cn/
  • 示例代码:https://github.com/langchain4j/langchain4j-examples
  • Spring Boot 集成:https://docs.langchain4j.info/tutorials/spring-boot-integration
  • Agent 编排示例:https://github.com/yjmyzz/agentic_tutorial_with_langchain4j
  • Microsoft LangChain4j 入门课程:https://github.com/microsoft/LangChain4j-for-Beginners
本文由作者按照 CC BY 4.0 进行授权