LangChain4j的初步学习【逐步添加中】
LangChain4j 是一个专为 Java 开发者设计的、受 Python LangChain 启发而构建的框架。它的核心目标是简化大型语言模型(LLM)的集成过程,让开发者能够轻松地将强大的 AI 功能构建到自己的 Java 应用程序中。
1. 什么是 LangChain4j?
-
定义:一个用于 Java 应用程序的 AI 集成框架。
-
灵感来源:Python 的 LangChain 项目,但并非其直接端口,而是根据 Java 生态的特点(如强类型、丰富的企业级库)重新设计和实现。
-
核心价值:提供一套抽象、工具和预构建组件,将 LLM(如 OpenAI 的 GPT-4, Google 的 Gemini, 本地的 Mixtral)与外部数据和系统连接起来,从而创建功能强大、上下文感知的 AI 应用。
-
设计哲学:约定优于配置,旨在通过简洁、富有表现力且遵循 Java 习惯的 API 来降低开发复杂度。
LangChain4j 中文文档https://docs.langchain4j.info/
2. 核心概念与功能
LangChain4j 的核心是围绕一系列“概念”构建的,这些概念是构建复杂 LLM 应用的基础模块。
-
Model(模型)
-
抽象了与各种 LLM 提供商的交互。你可以通过更改配置轻松切换模型,而无需重写业务逻辑。
-
支持提供商:OpenAI, Azure OpenAI, Google Gemini, Anthropic Claude, Hugging Face, Ollama(本地模型), LocalAI 等。
-
-
ChatMemory(聊天记忆)
-
管理对话的上下文历史。这是实现多轮对话的关键。
-
类型:支持多种存储方式,如简单的内存存储、基于 Redis 或数据库的持久化存储,以确保应用重启后对话状态不丢失。
-
-
Tools(工具)
-
允许 LLM 按需执行外部动作或获取外部数据。这是将 LLM 能力与现有系统和 API 连接起来的超级能力。
-
示例:一个“获取天气”的工具,一个“执行数据库查询”的工具,一个“调用内部 HTTP API”的工具。
-
LLM 可以根据用户请求决定调用哪个工具,并将工具执行结果融入其回复中。
-
-
PromptTemplates(提示模板)
-
帮助动态生成高质量的提示(Prompt)。它将用户输入、上下文、对话历史等变量注入到预定义的模板中,形成最终发送给 LLM 的指令。
-
好处:提高提示词的一致性、可维护性和复用性。
-
-
OutputParsers(输出解析器)
-
LLM 的输出是非结构化的文本。OutputParsers 负责将这些文本解析成结构化、可编程的对象(如 Java 对象、JSON 等)。
-
示例:让 LLM 生成一个
Person
对象的 JSON 字符串,然后用JsonOutputParser
将其自动反序列化为 Java 的Person
类实例。
-
-
Embeddings(嵌入) & VectorStores(向量数据库)
-
Embeddings:将文本(如文档、问答)转换为数值向量( embeddings)。
-
VectorStores:存储和检索这些向量。通过计算向量之间的相似度,可以实现语义搜索。
-
应用场景:检索增强生成(RAG)。这是 LangChain4j 最强大的功能之一。它允许你将自己的数据(PDF、Word、数据库)转换为向量并存入向量数据库。当用户提问时,系统会先从你的数据中检索最相关的信息,然后将这些信息作为上下文提供给 LLM,从而让 LLM 生成基于你自身知识的、更准确的回答,避免“幻觉”。
-
-
Chains(链)
-
将上述多个组件按顺序链接起来,形成一个复杂的工作流。一个链的输出可以作为下一个链的输入。
-
示例:
RetrievalChain
就是一个预定义的链,它自动完成了“检索相关文档 -> 构建提示 -> 调用模型 -> 解析输出”的整个 RAG 流程。
-
-
AI Services(AI 服务)
-
这是 LangChain4j 中一个非常强大且具有创新性的抽象。它允许你通过定义一个 Java 接口 来创建一个 AI 应用。
-
你可以在接口方法上使用注解(如
@SystemMessage
,@UserMessage
,@MemoryId
,@V
)来定义行为、提示和记忆。 -
框架会在运行时自动生成这个接口的代理实现,处理所有与 LLM 的交互、工具调用、记忆管理等复杂细节。这让代码变得极其简洁和声明式。
-
3.代码初步
相关背景知识
开始之前需要准备一个空的SpringBoot项目。
相关依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-spring-boot-starter</artifactId><version>1.4.0-beta10</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai-spring-boot-starter</artifactId><version>1.3.0-beta9</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.5.18</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId><version>1.3.0</version></dependency><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId><version>1.0.1-beta6</version></dependency>
(1)入门程序
import dev.langchain4j.model.openai.OpenAiChatModel;public class App
{public static void main( String[] args ){OpenAiChatModel chatModel = OpenAiChatModel.builder().baseUrl("your_baseUrl")//这个需要去调用的模型的提供者的文档查看.modelName("your_modelName")//这是你调用的模型名字.apiKey("your_apiKey")//这是你的api秘钥.logRequests(true)//打印请求.logResponses(true)//打印回复.build();String result = chatModel.chat("你是谁?");//向大模型请求的问题System.out.println(result);}
}
(2)配置yml文件
langchain4j:open-ai:chat-model: #普通调用base-url: your_baseUrlapi-key: your_apiKeymodel-name: your_modelNamelog-requests: true #开启请求打印log-responses: true #开启回复打印streaming-chat-model: #流式调用base-url: your_baseUrlapi-key: your_apiKeymodel-name: your_modelNamelog-requests: true #开启请求打印log-responses: true #开启回复打印
logging:level:dev.langchain4j: debug
(3)入门接口
a.创建service相关类
import org.springframework.stereotype.Service;@Service
public interface ConsultantService {String chat(String message);
}
b.创建controller相关类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ChatController {@Autowiredprivate ConsultantService consultantService;@RequestMapping("/chat")public String chat (String message){return consultantService.chat(message);}
}
c.创建config相关类
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class CommonConfig {@Autowiredprivate OpenAiChatModel model;//通过你的yml文件自动创建的@Beanpublic ConsultantService consultantService(){return AiServices.builder(ConsultantService.class).chatModel(model).build();}
}
d.使用Apifox测试
e.声明式使用
不使用config类,直接在service相关方法上加上注解@AiService
。@AiService
是来自 LangChain4j 框架的一个注解,用于将一个接口自动装配为一个 AI 服务(即代理实现),使得你可以像调用普通方法一样调用 AI 模型。
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,//自动装配chatModel = "openAiChatModel"//模型Bean名,这里是LangChain4j通过yml文件自动创建的默认名字
)
public interface ConsultantService {String chat(String message);
}
wiringMode = AiServiceWiringMode.EXPLICIT
作用:
这个参数控制 Spring 如何为 @AiService
接口创建实现类(代理)。
可选值:
-
AiServiceWiringMode.EXPLICIT
(显式装配) -
AiServiceWiringMode.AUTO
(自动装配)
EXPLICIT
的含义:
-
表示你需要 手动明确地配置 所使用的
ChatModel
、Tokenizer
等组件。 -
Spring 不会自动从上下文中查找默认的
ChatModel
。 -
你必须通过
chatModel = "..."
显式指定 Spring 容器中某个 bean 的名称。
AUTO
的含义:
-
LangChain4j 会尝试自动从 Spring 容器中查找 唯一的
ChatModel
bean。 -
如果有多个
ChatModel
bean,会报错。
-
不需要写
chatModel = "..."
。
chatModel = "openAiChatModel"
作用:
指定该 AI 服务应使用 Spring 容器中名为 "openAiChatModel"
的 ChatModel
bean。
openAiChatModel
是什么?
它是 LangChain4j 为 OpenAI 类型模型 自动创建的默认 Bean 名称。
当你在 application.yml
中配置了相关参数(base-url、api-key、model-name),
LangChain4j 的自动配置(LangChain4jAutoConfiguration
)就会:
-
创建一个
OpenAiChatModel
实例(或兼容 OpenAI 的客户端)。 -
使用你提供的
base-url
、api-key
、model-name
等参数。 -
将这个实例注册为 Spring 容器中的一个 Bean,名字叫
openAiChatModel
。
如果想使用多个model,你可以不使用LangChain4j自动创建Bean,在config类中写类似如下代码:
@Bean("name")//自己取个名字
public ChatModel qwenChatModel() {return OpenAiChatModel.builder().baseUrl("your_base-url").apiKey(System.getenv("your_API_KEY")).modelName("your_model_name").build();
}
在注解处写成对应名字即可使用对应模型。
(4)流式调用
a.修改service相关类
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "openAiChatModel",streamingChatModel = "openAiStreamingChatModel"//h和上面类似,是Langchain4j自动注册的Bean
)
public interface ConsultantService {String chat(String message);Flux<String> fluxChat(String message);
}
b.修改新的接口
@RequestMapping("/fluxChat")public Flux<String> fluxChat(String message){return consultantService.fluxChat(message);}
(5)会话功能
a.消息来源和意图
@SystemMessage
、@UserMessage
、@AssistantMessage
是 LangChain4j 提供的用于在 @AiService
接口中定义对话角色(message roles)的注解。它们的作用是帮助你结构化地组织对话内容,让 AI 模型清楚每条消息的来源和意图。
什么是消息角色(Message Roles)?
在大模型(如 GPT、Qwen、Claude 等)中,对话通常由三种角色构成:
角色 | 说明 |
---|---|
system | 系统提示词,用于设定 AI 的行为、身份、语气等(例如:“你是一个专业的客服助手”) |
user | 用户输入的问题或请求 |
assistant | AI 的回复(历史对话中的) |
LangChain4j 使用这些注解让你在接口方法中清晰地标注每段文本属于哪个角色。
@SystemMessage
用于定义 系统提示词(System Prompt),通常只在对话开始时设置一次。
@SystemMessage("你是一个性格活泼有趣的代码助手,只能回答用户关于代码的相关问题,其他的都需要拒绝。")String chat(String message);
-
效果:每次调用
chat(...)
时,都会自动加上这条系统消息。 -
常用于:设定 AI 的角色、语气、输出格式等。
也可以通过参数从资源文件夹的文件读取消息
@SystemMessage(fromResource="promptWord/system.txt")String chat(String message);
@UserMessage
标注哪一部分是 用户输入的消息。
-
{{it}}
是模板变量,表示参数值(当只有一个参数时可用{{it}}
,只能用it!!!)。 -
如果有多个参数,可以用@V注解。
@UserMessage("我想说:{{it}}。")Flux<String> fluxChat(String message);@UserMessage("请你比较{{msg1}}和{{msg2}}的区别。")Flux<String> fluxChat(@V("msg1")String message1,@V("msg2") String message2);
@AssistantMessage
用于提供 AI 之前的回复,模拟历史对话,实现多轮对话上下文。
@SystemMessage("你是一个旅游顾问")
@UserMessage("推荐一些国内适合夏天旅游的地方")
@AssistantMessage("你可以考虑去云南大理、贵州贵阳或青海湖,气候凉爽。")
@UserMessage("有没有更适合带小孩去的?")
String recommendWithMemory();
-
这种写法相当于“硬编码”了一段对话历史。
-
适用于测试或固定流程的引导对话。
-
实际项目中更常用的是通过
Memory
(如ChatMemory
)来动态管理历史。
b.会话记忆入门
大模型是不具备记忆能力的,要想让大模型记住之前聊天的内容,唯一的办法就是把之前聊天的内容与新的提示词一起发给大模型。
修改config类
@Configuration
public class CommonConfig {/*@Autowiredprivate OpenAiChatModel model;@Beanpublic ConsultantService consultantService(){return AiServices.builder(ConsultantService.class).chatModel(model).build();}*/@Beanpublic ChatMemory chatMemory(){//这里的10是最大储存消息数return MessageWindowChatMemory.builder().maxMessages(10).build();}
}
修改service类
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
import reactor.core.publisher.Flux;@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "openAiChatModel",streamingChatModel = "openAiStreamingChatModel",chatMemory = "chatMemory"//自动找的chatMemory的bean
)
public interface ConsultantService {@SystemMessage("你是一个性格活泼有趣的代码助手,只能回答用户关于代码的相关问题,其他的都需要拒绝。")String chat(String message);@UserMessage("我想说:{{it}}。")Flux<String> fluxChat(String message);@UserMessage("请你比较{{msg1}}和{{msg2}}的区别。")Flux<String> fluxChat(@V("msg1")String message1,@V("msg2") String message2);
}
c.会话记忆隔离
刚才我们做的会话记忆,所有会话使用的是同一个记忆存储对象,因此不同会话之间的记忆并没有做到隔离。
在config中创建记忆对象提供者
@Beanpublic ChatMemoryProvider chatMemoryProvider(){return new ChatMemoryProvider() {@Overridepublic ChatMemory get(Object memoryId) {return MessageWindowChatMemory.builder().id(memoryId).maxMessages(10).build();}};}
修改service类的注解参数
@AiService(wiringMode = AiServiceWiringMode.EXPLICIT,chatModel = "openAiChatModel",streamingChatModel = "openAiStreamingChatModel",/*chatMemory = "chatMemory"*/chatMemoryProvider = "chatMemoryProvider"
)
修改service类相关方法
@UserMessage:显式标注哪个参数是用户输入
@MemoryId:区分不同用户的对话记忆(会话隔离)
如果不加 @UserMessage
会怎样?
LangChain4j 会尝试自动推断哪个参数是用户输入。
但如果参数多于一个(比如还有 @MemoryId
),它就无法确定,可能导致错误或忽略参数。
所以:当有多个参数时,必须用 @UserMessage
明确标注用户输入参数。
public interface ConsultantService {@SystemMessage("你是一个性格活泼有趣的代码助手,只能回答用户关于代码的相关问题,其他的都需要拒绝。")String chat(@MemoryId String memoryId, @UserMessage String message);@UserMessage("我想说:{{it}}。")Flux<String> fluxChat(@MemoryId String memoryId, @UserMessage String message);@UserMessage("请你比较{{msg1}}和{{msg2}}的区别。")Flux<String> fluxChat(@MemoryId String memoryId, @V("msg1") String message1, @V("msg2") String message2);
}
修改接口
import com.learning.learning.service.ConsultantService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;@RestController
public class ChatController {@Autowiredprivate ConsultantService consultantService;@RequestMapping("/chat")public String chat(String memoryId,String message) {return consultantService.chat(memoryId,message);}@RequestMapping("/fluxChat")public Flux<String> fluxChat(String memoryId, String message) {return consultantService.fluxChat(memoryId, message);}@RequestMapping("/fluxChat1")public Flux<String> fluxChat1(String memoryId, String message1, String message2) {return consultantService.fluxChat(memoryId, message1, message2);}
}