深入浅出LangChain4J
1.走进LangChain
1.1.什么是LangChain4J?
The goal of LangChain4J is to simplify integrating LLMs into Java applications.
-- 《LangChain4J Doc》
LangChain4J就像Java的Spring框架一样,为LLM的接入提供了一套标准的接入能力。LangChain4J是LangChain在Java生态下的具体实现,用于构建基于LLM的应用和Agent系统。
LangChain4J为不同的LLM提供了一个统一的API,屏蔽了底层API供应商的实现和调用差异,开发者可以使用LangChain无缝切换不同的LLM。
2.Architecture of LangChain4J
LangChain4J的架构可以分为两层,分别为高层和底层。
高层AI服务屏蔽了底层实现的细节,开发者可以实现一些业务服务的API和LLM进行交互。
低层具有更高的自由度,你可以任意定义组件的具体实现,控制组件的组合方式等等。

下面,就LangChain4J的核心组件,一一详细介绍。
2.1.Chat and Language Model
一般地,LLMs的API可以分为两类,
LanguageModel:输入输出都为String类型,现在使用的越来越少了ChatModel:应用广泛的一类API,接收多个ChatMessage作为输入,输出一个AiMessage,除了文本类型之外,还支持图片、音频、视频等其他模式。
ChatModel是LangChain4J的低层API,提供强大的灵活性和可扩展性。
根据消息来源进行分类,LangChain4J支持以下五种消息类型:
| 消息类型 | 描述 | 主要方法 |
|---|---|---|
| UserMessage | 用户输入的消息数据 | content(), name(), attributes() |
| AiMessage | AI根据输入数据生成的消息 | text(), thinking(), toolExceptionRequests(), attributes() |
| ToolExecutionResultMessage | 工具执行结果消息 | |
| SystemMessage | 系统发送的消息,可以由开发者自定义 | |
| CustomMessage | 自定义消息 |
2.2.Chat Memory
我们知道,大模型是无状态的,模型本身并不会记录对话上下文。因此,如果你想要在对话中使用上下文的内容,就必须主动维护管理ChatMessage。但是手动维护比较麻烦,而LangChain4J提供了ChatMemory用来维护管理chatMessage.
2.2.1.History VS Memory
历史记录和记忆听起来似乎是同一种东西,但是对于LLM Agent来说,history和memory具有两种截然不同的语义:
history保留了用户和AI的所有会话记录,用户可以在界面中看到memory记录的是专门呈现给大模型使用的信息,使其表现得好像是"记住了"对话的内容,实际底层实现是截然不同的算法。
LangChain4J目前只提供Memroy ,而不提供History。
下面介绍关于Memory的几点高级特性。
2.2.2.Eviction Policy 淘汰策略
为什么要使用淘汰策略?
- 存储空间受限:Memory组件可以理解为缓存,当存放的内容超过容量大小的时候,需要使用一定的策略淘汰一些记忆;
- Token成本昂贵:更多的上下文记忆就代表使用更多的Token,而token的增加会增加每次调用LLM的成本;
- 控制延迟:发送的LLM的token越多,处理他们所需要的时长越长,则用户等待输出的时间越长
LangChain4J目前支持两种开箱即用的记忆淘汰策略:
- 基于消息滑动窗口:使用
MessageWindowChatMemory作为滑动窗口,仅保留最近的N条信息,淘汰窗口之外的旧消息; - 基于token的滑动窗口:
TokenWindowChatMemory只保留最近的N个token。但是消息是不可再分的,因此如果消息不合适的话,整条消息会直接被丢弃。
2.2.3.Persistence 持久化
默认情况下Memory是存放在内存中的,如果需要保留消息,可以自定义实现MessageStore,将chatMessage存储在数据库中。
java
class PersistentChatMemroyStore implements ChatMemoryStore {
@Override
public List<ChatMessage> getMessages(Object memoryId) {
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
}
@Override
public void deleteMessages(Object memoryId) {
// TODO: Implement deleting all messages in the persistent store by memory ID.
}
}
2.2.4.SystemMessage
SystemMessage是一种特殊的消息,具有如下的特性:
- 一旦添加,就会一直被保留,不会被淘汰策略淘汰
- 一次只能持有一个
SystemMessage SystemMessage具有唯一性,重复的SystemMessage会被忽略,如果不一致,则会覆盖掉之前的SystemMessage
2.2.5.Tool 消息配对
AIMessage 中的 ToolExecutionRequest 和 ToolExecutionResponse 总是成对出现的。如果 ToolExecutionRequest 被淘汰了,则其对应的 ToolExecutionResponse 消息也会被自动淘汰。
2.3.Tools(Function Calling)
工具(Tools)允许LLM在必要的时候调用开发者定义的一个或者多个可用工具,工具是一个泛称,可以表示任何东西,例如网页搜索、外部API、或者执行特定的代码等等。
实际上LLM并不会去主动调用工具,而是通过开发人员在业务层接收LLM的回复,根据LLM的意愿进行工具的调用并将执行结果反馈给LLM。
一个良好的工具应当具有以下几个属性:
- 清晰明确的工具名称
- 工具的具体描述,以及应该何时使用工具;
- 每个工具的具体参数;
如果这个工具对于人来说一眼就能理解应该如何使用,那么这个工具对于LLM也是易于使用的。
Tools的定义和使用可以参考文档:LangChain4J Documents
2.4.Agent
3.实战
3.1.ChatModel
3.1.1.基本聊天
java
public static void main(String[] args) {
// 1.创建 chatModel
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.baseUrl(System.getenv("OPENAI_BASE_URL"))
.modelName(System.getenv("OPENAI_MODEL"))
.build();
// 2.使用 chat 方法发起对话
ChatResponse response = chatModel.chat(ChatRequest.builder()
.messages(UserMessage.from("Hello, could you introduce yourself ?"))
.build());
// 3.控制台打印
System.out.println(response.aiMessage().text());
}
3.1.2.多轮对话
多轮对话和基本对话的实现方式相同,只不过构建对话请求的时候,需要将前文传入到messages中,LLM会参考message得出合理的响应。
java
private static void chatWithMultipleMessages() {
// 1.构建 User 和AI 的多轮对话
UserMessage firstUserMessage = UserMessage.from("Hello, My name is shepi, could you introduce yourself ?");
AiMessage firstAiMessage = CHAT_MODEL.chat(
ChatRequest.builder()
.messages(firstUserMessage)
.build())
.aiMessage();
UserMessage secondUserMessage = UserMessage.from("Please tell me my name");
AiMessage secondAiMessage = CHAT_MODEL.chat(
ChatRequest.builder()
.messages(firstUserMessage, firstAiMessage, secondUserMessage)
.build()).aiMessage();
// 2.控制台打印
System.out.println(secondAiMessage.text());
}
3.2.ChatMemory
ChatMemory可以帮助我们维护上下文,LangChain4J提供了两种开箱即用的窗口聊天记忆
MessageWindowChatMemoryTokenWindowChatMemory
下面是一个基于消息数量的窗口对话记忆:
java
public static void main(String[] args) {
// 1.新建 messageWindow,滑动窗口最大长度设置为 3
MessageWindowChatMemory chatWindow = MessageWindowChatMemory.builder()
.maxMessages(4)
.build();
// tryChatWithCommonMessages(chatWindow);
// 测试特殊的系统消息 systemMessage
tryChatWithSystemMessage(chatWindow);
}
/**
* 系统消息永远不会被驱逐
* @param chatWindow
*/
private static void tryChatWithSystemMessage(MessageWindowChatMemory chatWindow) {
// 创建系统消息
SystemMessage systemMessage = SystemMessage.from("The user talking with you is shepi");
chatWindow.add(systemMessage);
System.out.println(systemMessage.text());
for (int i = 0; i < 4; i++) {
UserMessage userMessage = null;
if (i == 3) {
userMessage = UserMessage.from("Please tell me my name");
} else {
userMessage = UserMessage.from("let's talk about something interesting");
}
System.out.println(userMessage.contents());
chatWindow.add(userMessage);
// 使用 chatMemory 对话
AiMessage aiMessage = CHAT_MODEL.chat(ChatRequest.builder().messages(chatWindow.messages()).build()).aiMessage();
System.out.println(aiMessage.text());
chatWindow.add(aiMessage);
}
}
private static void tryChatWithCommonMessages(MessageWindowChatMemory chatWindow) {
// 2.模拟进行多轮对话
// 第一轮给出自己的名字,第四轮询问AI我的名字,其他两轮随便聊点什么
for (int i = 0; i < 4; i++) {
UserMessage userMessage = null;
if (i == 0) {
userMessage = UserMessage.from("Hello, My name is shepi, could you introduce yourself ?");
} else if (i == 3) {
userMessage = UserMessage.from("Please tell me my name");
} else {
userMessage = UserMessage.from("let's talk about something interesting");
}
System.out.println(userMessage.contents());
chatWindow.add(userMessage);
// 使用 chatMemory 对话
AiMessage aiMessage = CHAT_MODEL.chat(ChatRequest.builder().messages(chatWindow.messages()).build()).aiMessage();
System.out.println(aiMessage.text());
chatWindow.add(aiMessage);
}
}
3.3.Tools
LangChain中定义Tool非常简单,只需要在工具接口方法上使用注解 @Tool("Tool description")即可,下面是一个简答的例子。
java
public class WeatherService {
@Tool("获取指定城市的天气情况")
public String getWeather(String city) {
// 模拟天气数据
return MessageFormat.
format("Today's weather in {0} is sunny with a temperature of 22 degrees.", city);
}
}
3.4.AiService (Agent 封装)
AiService 是什么?
AiService 是 LangChain4J 提供的高层 API,用于快速构建 Agent 应用。它屏蔽了工具调用的底层细节(如检测 LLM 返回的函数调用请求、执行工具、将结果反馈给 LLM),让开发者可以专注于业务逻辑。
AiService 与 Agent 的关系:
- AiService 是 LangChain4J 对 Agent 概念的具体实现
- 它自动完成了 Agent Loop 中的:接收输入 → 判断是否需要 Tool → 调用 Tool → 观察结果 → 生成响应
- 开发者只需定义接口和 Tool,AiService 会自动完成工具调用的编排
如果想要使用上面创建的 Tool,我们需要在定义 AiService 时,传入 Tool 对象。
java
private static final OpenAiChatModel CHAT_MODEL = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.baseUrl(System.getenv("OPENAI_BASE_URL"))
.modelName(System.getenv("OPENAI_MODEL"))
.build();
public static void main(String[] args) {
// 根据CHAT_MODEL创建一个 chatService
ChatAssistant chatService = AiServices.builder(ChatAssistant.class)
.chatLanguageModel(CHAT_MODEL)
.tools(new WeatherService())
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.build();
// 问题
String question = "今天北京什么天气?";
String answer = chatService.chat(question);
System.out.println(answer);
}
4.流式响应 (Streaming)
LangChain4J 支持流式响应,即 LLM 在生成内容时实时返回每个 token,而不是等待完整响应后再返回。
使用场景
- 需要实时显示生成过程的场景(如 ChatGPT 的打字机效果)
- 生成较长内容时提升用户体验
- 需要提前终止不满意的输出
实现方式
使用 StreamingAiServices 代替 AiServices:
java
interface StreamingChatAssistant {
void chat(String message, StreamingResponseHandler handler);
}
StreamingChatAssistant agent = StreamingAiServices.builder(StreamingChatAssistant.class)
.chatLanguageModel(model)
.build();
agent.chat("介绍一下 LangChain4J", new StreamingResponseHandler() {
@Override
public void onToken(String token) {
// 每收到一个 token 就调用
System.out.print(token);
}
@Override
public void onComplete(String fullResponse) {
System.out.println("\n完成!");
}
@Override
public void onError(Throwable error) {
System.err.println("错误: " + error.getMessage());
}
});
流式 vs 非流式
| 特性 | 非流式 (AiServices) | 流式 (StreamingAiServices) |
|---|---|---|
| 响应方式 | 等待完整响应后返回 | 实时返回每个 token |
| 用户体验 | 有等待感 | 实时反馈,体验更好 |
| 实现复杂度 | 简单 | 稍复杂(需要处理回调) |
| 适用场景 | 短文本、后台任务 | 长文本、交互式对话 |
完整示例代码见:StreamingAgent.java