LangChain4J
基本概念与依赖导入
模型开发和常规开发层级对比
Prompt:好比UI层,进行用户交互
Langchain4j, Spring AI:好比Controller,调用AI大模型
各类AI大模型:好比Service,提供人工智能服务
向量数据库:好比Mapper,进行数据存储

大模型调用三件套
API Key:sk-xxx自己的API key
模型名 :qwen-plus、deepseek-v4-pro
调用地址:使用sdk调用时需要配置的base_url
导入依赖
导入 Lanchain4j 依赖
导入 BOM 包管理版本
<properties>
<!-- langchain4j -->
<langchain4j.version>1.0.1</langchain4j.version>
</properties>
<dependencyManagement>
<dependencies>
<!--langchain4j的依赖清单,加载BOM后所有langchain4j版本号可以被统一管理起来
https://docs.langchain4j.dev/get-started -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependencies>
</dependencyManagement>
导入原生依赖包
- 导入 Low-Level API 和 High-Level API 的依赖包
<!--langchain4j-open-ai 基础-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<!--langchain4j 高阶-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
</dependency>
导入SpringBoot 集成依赖包
参考文档: SprintBoot 集成
- 导入和 SpringBoot 的整合包
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.0-beta3</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>1.0.0-beta3</version>
</dependency>
第三方集成 (通义千问为例)
参考文档:通义千问
导入 BOM 包管理版本
<properties>
<!--langchain4j-community 引入阿里云百炼平台依赖管理清单-->
<langchain4j-community.version>1.0.1-beta6</langchain4j-community.version>
</properties>
<dependencyManagement>
<dependencies>
<!--引入阿里云百炼平台依赖管理清单
https://docs.langchain4j.dev/integrations/language-models/dashscope
-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-bom</artifactId>
<version>${langchain4j-community.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
导入 SpringBoot 集成依赖包
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
</dependency>
创建 ChatModel
APIKey 要从环境变量中获取。
设置方法:百炼平台文档
@Bean
public ChatModel chatModelQwen()
{
System.out.println("ALIQWEN_API: "+System.getenv("ALIQWEN_API"));
return OpenAiChatModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
创建 AI Service
- 和 SpringBoot 整合:声明式创建 AI Service
// ChatAssiatant.java
@AiService
public interface ChatAssistant {
String chat(String prompt);
}
// Controller.java
@RestController
public class DeclarativeAIServiceController {
@Resource
private ChatAssistant chatAssistant;
@GetMapping(value = "/lc4j/boot/chatAssistant")
public String deepseekAI(@RequestParam(value = "prompt", defaultValue = "你是谁") String prompt)
{
return chatAssistant.chat(prompt);
}
}
@AIService(wiringMode = EXPLICIT, chatModel = "chatModelQwen") 可以指定改 AI Service 使用的是 chatModelQwen 模型。
- 原生整合:手动创建 AI Service
// ChatAssistant.java
public interface ChatAssistant {
String chat(String prompt);
}
// Config.java
@Bean(name = "qwen")
public ChatModel chatModelQwen()
{
return OpenAiChatModel.builder()
.apiKey(System.getenv("aliQwen-api"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean(name = "deepseek")
public ChatModel chatModelDeepSeek()
{
return
OpenAiChatModel.builder()
.apiKey(System.getenv("deepseek-api"))
.modelName("deepseek-chat")
//.modelName("deepseek-reasoner")
.baseUrl("https://api.deepseek.com/v1")
.build();
}
@Bean
public ChatAssistant chatAssistant(@Qualifier("qwen") ChatModel chatModelQwen) {
return AiServices.create(ChatAssistant.class, chatModelQwen);
}
// Controller.java
@RestController
@Slf4j
public class HighApiController {
@Resource
private ChatAssistant chatAssistant;
@GetMapping(value = "/highapi/highapi")
public String highApi(@RequestParam(value = "prompt", defaultValue = "你是谁") String prompt)
{
return chatAssistant.chat(prompt);
}
}
模型参数
// Config.java
@Configuration
public class LLMConfig
{
@Bean(name = "qwen")
public ChatModel chatModelQwen()
{
return OpenAiChatModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.logRequests(true) // 日志级别设置为debug才有效
.logResponses(true)// 日志级别设置为debug才有效
.listeners(List.of(new TestChatModelListener())) // 事件监听
.maxRetries(2) // 最大重试次数
.timeout(Duration.ofSeconds(5)) // 请求大模型的超时时间
.build();
}
}
// Linstener.java
@Slf4j
public class TestChatModelListener implements ChatModelListener
{
@Override
public void onRequest(ChatModelRequestContext requestContext)
{
// onRequest配置的k:v键值对,在onResponse阶段可以获得,上下文传递参数好用
String uuidValue = IdUtil.simpleUUID();
requestContext.attributes().put("TraceID",uuidValue);
log.info("请求参数requestContext:{}", requestContext+"\t"+uuidValue);
}
@Override
public void onResponse(ChatModelResponseContext responseContext)
{
Object object = responseContext.attributes().get("TraceID");
log.info("返回结果responseContext:{}", object);
}
@Override
public void onError(ChatModelErrorContext errorContext)
{
log.error("请求异常ChatModelErrorContext:{}", errorContext);
}
}
图片生产模型
使用 qwen-vl-max 模型
// Config.java
@Configuration
public class LLMConfig
{
@Bean
public ChatModel ImageModel() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
//qwen-vl-max 是一个多模态大模型,支持图片和文本的结合输入,适用于视觉-语言任务。
.modelName("qwen-vl-max") //deepseek-v4-flash-vision-exp
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
}
发送用户消息:
- 图片转 Base 64 编码
- 构造请求消息 UserMessage :包含文字和转码后的图片
- 调用 .chat 接口
- 打印请求结果
// ImageModeController.java
@RestController
@Slf4j
public class ImageModelController
{
@Autowired
private ChatModel chatModel;
@Value("classpath:static/images/mi.jpg")
private Resource resource;//import org.springframework.core.io.Resource;
/**
* @Description: 通过Base64编码将图片转化为字符串
* 结合ImageContent和TextContent形成UserMessage一起发送到模型进行处理。
* @Auther: zzyybs@126.com
* *测试地址:http://localhost:9006/image/call
*/ @GetMapping(value = "/image/call")
public String readImageContent() throws IOException
{
String result = null;
//第一步,图片转码:通过Base64编码将图片转化为字符串
byte[] byteArray = resource.getContentAsByteArray();
String base64Data = Base64.getEncoder().encodeToString(byteArray);
//第二步,提示词指定:结合ImageContent和TextContent一起发送到模型进行处理。
UserMessage userMessage = UserMessage.from(
TextContent.from("从下面图片种获取来源网站名称,股价走势和5月30号股价"),
ImageContent.from(base64Data, "image/jpg")
);
//第三步,API调用:使用OpenAiChatModel来构建请求,并通过chat()方法调用模型。
//请求内容包括文本提示和图片,模型会根据输入返回分析结果。
ChatResponse chatResponse = chatModel.chat(userMessage);
//第四步,解析与输出:从ChatResponse中获取AI大模型的回复,打印出处理后的结果。
result = chatResponse.aiMessage().text();
//后台打印
System.out.println(result);
//返回前台
return result;
}
}
图片生成
@RestController
public class WanxImageModelController {
@Resource
private WanxImageModel wanxImageModel;
@GetMapping(value = "/image/create2")
public String createImageContent2(){
Response<Image> imageResponse = wanxImageModel.generate("美女");
System.out.println(imageResponse.content().url());
return imageResponse.content().url().toString();
}
}
//config
@Bean
public WanxImageModel wanxImageModel(){
return WanxImageModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("wanx2.1-t2i-turbo")
.build();
}
流式输出
流式输出
参考文档:
前置准备
导入响应式编程依赖包:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-reactor</artifactId>
<version>1.0.0-beta3</version>
</dependency>
配置文件设置字符集:
# 设置响应的字符编码,避免流式返回输出乱码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
StreamingChatResponseHandler 处理流式事件:
public interface StreamingChatResponseHandler {
void onPartialResponse(String partialResponse);
void onCompleteResponse(ChatResponse completeResponse);
void onError(Throwable error);
}
通过实现 StreamingChatResponseHandler,可以为以下事件定义操作:
- 当生成下一个部分响应时:调用
onPartialResponse(String partialResponse)。可以在标记可用时立即将其发送到 UI。 - 当 LLM 完成生成时:调用
onCompleteResponse(ChatResponse completeResponse)。ChatResponse对象包含完整的响应(AiMessage)以及ChatResponseMetadata。 - 当发生错误时:调用
onError(Throwable error)。
原生使用
LanguageModel->StreamingLanguageModelChatLanguageModel->StreamingChatLanguageModel
// Config.java
@Configuration
public class LLMConfig
{
// 流式对话接口 StreamingChatModel
@Bean
public StreamingChatModel streamingChatModel(){
return OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
}
// Controller.java
@RestController
@Slf4j
public class StreamingChatModelController
{
@Resource //直接使用 low-level LLM API
private StreamingChatModel streamingChatLanguageModel;
// http://localhost:9007/chatstream/chat?prompt=杭州有什么好吃的
@GetMapping(value = "/chatstream/chat")
public Flux<String> chat(@RequestParam("prompt") String prompt)
{
System.out.println("---come in chat");
return Flux.create(emitter -> {
streamingChatLanguageModel.chat(prompt, new StreamingChatResponseHandler()
{
@Override
public void onPartialResponse(String partialResponse)
{
emitter.next(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse)
{
emitter.complete();
}
@Override
public void onError(Throwable throwable)
{
emitter.error(throwable);
}
});
});
}
//该方式只会在后端输出
@GetMapping(value = "/chatstream/chat2")
public void chat2(@RequestParam(value = "prompt", defaultValue = "北京有什么好吃") String prompt){
System.out.println("---come in chat2");
streamingChatModel.chat(prompt, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String s) {
System.out.println(s);
}
@Override
public void onCompleteResponse(ChatResponse chatResponse) {
System.out.println("---response over: "+chatResponse);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
});
}
AI Services 使用
- 接口签名为:
Flux<String> chatFlux(String prompt); - 传递 streamingChatModel 创建
// ChatAssistant.java
public interface ChatAssistant
{
String chat(String prompt);
Flux<String> chatFlux(String prompt);
}
// Config.java
@Configuration
public class LLMConfig {
@Bean
public ChatAssistant chatAssistant(StreamingChatModel streamingChatModel){
return AiServices.create(ChatAssistant.class, streamingChatModel);
}
}
@RestController
public class StreamingChatModelController {
@GetMapping(value = "/chatstream/chat3")
public Flux<String> chat3(@RequestParam(value = "prompt", defaultValue = "南京有什么好吃") String prompt){
System.out.println("---come in chat3");
return chatAssistant.chatFlux(prompt);
}
}
ChatMemory
参考文档:聊天记忆
聊天记忆含义
LangChain4j 提供的聊天记忆的抽象容器,用于简化手动维护和管理聊天消息的繁琐工作。
记忆 vs 历史的区别:
- 历史:保持用户和AI之间所有消息的完整记录,是用户在UI中看到的实际对话内容
- 记忆:保存部分信息呈现给LLM,使其表现得"记住"对话,可以通过淘汰、总结、修改消息等方式处理历史
主要功能特性
- 淘汰策略:自动管理消息数量以适应LLM上下文窗口限制
MessageWindowChatMemory(消息计数):保留最近的N条消息TokenWindowChatMemory(Tokenizer计数):保留最近的N个 Token
- 持久化支持:可将聊天消息存储到持久化存储(
ChatMemoryStore)中 - SystemMessage特殊处理:系统消息的专门管理机制
- 工具消息特殊处理:避免孤立工具执行结果消息导致的问题
代码示例
在创建 AiService 时,再添加一个 chatMemoryProvider 即可。
// LLMConfig.java
// MessageWindowChatMemory示例
@Bean(name = "chatMessageWindowChatMemory")
public ChatMemoryAssistant chatMessageWindowChatMemory(ChatModel chatModel)
{
return AiServices.builder(ChatMemoryAssistant.class)
.chatModel(chatModel)
//按照memoryId对应创建了一个chatMemory
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
.build();
}
// TokenWindowChatMemory示例(需要Tokenizer计数)
@Bean(name = "chatTokenWindowChatMemory")
public ChatMemoryAssistant chatTokenWindowChatMemory(ChatModel chatModel)
{
TokenCountEstimator openAiTokenCountEstimator = new OpenAiTokenCountEstimator("gpt-4");
return AiServices.builder(ChatMemoryAssistant.class)
.chatModel(chatModel)
.chatMemoryProvider(memoryId -> TokenWindowChatMemory.withMaxTokens(1000,openAiTokenCountEstimator))
.build();
}
聊天记忆持久化
用 redis 为例。
1.实现 ChatMemoryStore 接口。注意需要用 messagesToJson 和 messagesToJson 进行序列化和反序列化。
// RedisChatMemoryStore.java
@Component
public class RedisChatMemoryStore implements ChatMemoryStore
{
public static final String CHAT_MEMORY_PREFIX = "CHAT_MEMORY:";
@Resource
private RedisTemplate<String,String> redisTemplate;
@Override
public List<ChatMessage> getMessages(Object memoryId)
{
String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);
return ChatMessageDeserializer.messagesFromJson(retValue);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages)
{
redisTemplate.opsForValue()
.set(CHAT_MEMORY_PREFIX + memoryId, ChatMessageSerializer.messagesToJson(messages));
}
@Override
public void deleteMessages(Object memoryId)
{
redisTemplate.delete(CHAT_MEMORY_PREFIX + memoryId);
}
}
2.创建 ChatMemoryProvider,添加 ChatMemoryStore
// LLMConfig.java
// ...
@Bean
public ChatPersistenceAssistant chatMemoryAssistant(ChatModel chatModel)
{
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(1000)
.chatMemoryStore(redisChatMemoryStore)
.build();
// TokenCountEstimator openAiTokenCountEstimator = new OpenAiTokenCountEstimator("gpt-4");
// ChatMemoryProvider chatMemoryProvider1 = memoryId -> TokenWindowChatMemory.builder()
// .id(memoryId)
// .maxTokens(1000, openAiTokenCountEstimator)
// .chatMemoryStore(redisChatMemoryStore)
// .build();
return AiServices.builder(ChatPersistenceAssistant.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
}
// ...
提示词工程
消息类型
| 消息类型 | 说明 |
|---|---|
| SystemMessage【人设】 | 系统提示词,一次只能保留一条,添加后始终保留 |
| UserMessage【用户消息】 | 用户消息,代表用户输入 |
| AiMessage【大模型回复】 | AI 消息,代表模型的回应 |
| ToolExecutionResultMessage【工具调用】 | 工具执行结果消息,ToolExecutionRequestMessage的返回结果 |
三种提示词构建方式
@SystemMessage+@UserMessage+@V
单独设置系统提示词,用 @V 填充用户消息的占位符。
// LawAssistant.java
public interface LawAssistant
{
// @SystemMessage+@UserMessage+@V
@SystemMessage("你是一位专业的中国法律顾问,只回答与中国法律相关的问题。" +
"输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答中国法律相关的问题。'")
@UserMessage("请回答以下法律问题:{{question}},字数控制在{{length}}以内")
String chat(@V("question") String question, @V("length") int length);
}
带着@StructuredPrompt 的业务实体类
单独设置系统提示词,用业务类填充用户消息的占位符。
// LawPrompt.java
@Data
@StructuredPrompt("根据中国{{legal}}法律,解答以下问题:{{question}}")
public class LawPrompt
{
private String legal;
private String question;
}
// LawAssistant.java
public interface LawAssistant
{
// LawPrompt:@StructuredPrompt的业务实体类,比如
@SystemMessage("你是一位专业的中国法律顾问,只回答与中国法律相关的问题。" +
"输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答中国法律相关的问题。'")
String chat(LawPrompt lawPrompt);
}
PromptTemplate
设置一个提示词模板,通过填充占位符形成不同提示词。
//1 构造PromptTemplate模板
PromptTemplate template = PromptTemplate.from("你是一个{{it}}助手,{{question}}怎么办");
//2 由PromptTemplate生成Prompt
Prompt prompt = template.apply(Map.of("it",role,"question",question));
//3 Prompt提示词变成UserMessage
UserMessage userMessage = prompt.toUserMessage();
使用案例
@RestController
@Slf4j
public class ChatPromptController
{
@Resource
private LawAssistant lawAssistant;
@Resource
private ChatModel chatModel;
// http://localhost:9009/chatprompt/test1
@GetMapping(value = "/chatprompt/test1")
public String test1()
{
String chat = lawAssistant.chat("什么是知识产权?",2000);
System.out.println(chat);
String chat2 = lawAssistant.chat("什么是java?",2000);
System.out.println(chat2);
return "success : "+ DateUtil.now()+"<br> \n\n chat: "+chat+"<br> \n\n chat2: "+chat2;
}
@GetMapping(value = "/chatprompt/test2")
public String test2()
{
LawPrompt prompt = new LawPrompt();
prompt.setLegal("知识产权");
prompt.setQuestion("TRIPS协议?");
String chat = lawAssistant.chat(prompt);
System.out.println(chat);
return "success : "+ DateUtil.now()+"<br> \n\n chat: "+chat;
}
@GetMapping(value = "/chatprompt/test3")
public String test3()
{
// 看看源码,单个参数时 PromptTemplate 构造使用 it 属性作为默认占位符
String role = "财务会计";
String question = "人民币大写";
//1 构造PromptTemplate模板
PromptTemplate template = PromptTemplate.from("你是一个{{it}}助手,{{question}}怎么办");
//2 由PromptTemplate生成Prompt
Prompt prompt = template.apply(Map.of("it",role,"question",question));
//3 Prompt提示词变成UserMessage
UserMessage userMessage = prompt.toUserMessage();
//4 调用大模型
ChatResponse chatResponse = chatModel.chat(userMessage);
//4.1 后台打印
System.out.println(chatResponse.aiMessage().text());
//4.2 前台返回
return "success : "+ DateUtil.now()+"<br> \n\n chat: "+chatResponse.aiMessage().text();
}
}
持久化之 ChatMemoryStore
用redis来存储
# ==========config redis===============
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.connect-timeout=3s
spring.data.redis.timeout=2s
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
public static final String CHAT_MEMORY_PREFIX = "CHAT_MEMORY:";
@Resource
private RedisTemplate<String,String> redisTemplate;
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String retValue = redisTemplate.opsForValue().get(CHAT_MEMORY_PREFIX + memoryId);
return ChatMessageDeserializer.messagesFromJson(retValue);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> list) {
redisTemplate.opsForValue()
.set(CHAT_MEMORY_PREFIX + memoryId,ChatMessageSerializer.messagesToJson(list));
}
@Override
public void deleteMessages(Object memoryId) {
redisTemplate.delete(CHAT_MEMORY_PREFIX + memoryId);
}
}
@Configuration
public class LLMConfig {
@Resource
private RedisChatMemoryStore redisChatMemoryStore;
@Bean
public ChatModel chatModel(){
return OpenAiChatModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("qwen-long")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean
public ChatPersistenceAssistant chatPersistenceAssistant(ChatModel chatModel){
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(1000)
.chatMemoryStore(redisChatMemoryStore)
.build();
return AiServices.builder(ChatPersistenceAssistant.class)
.chatModel(chatModel)
.chatMemoryProvider(chatMemoryProvider)
.build();
}
}
public interface ChatPersistenceAssistant {
String chat(@MemoryId Long userId, @UserMessage String messgae);
}
@RestController
public class ChatPersistenceController {
@Resource
private ChatPersistenceAssistant chatPersistenceAssistant;
@GetMapping(value = "/chatpersistence/redis")
public String testChatPersistence(){
chatPersistenceAssistant.chat(1L, "你好!我的名字是redis");
chatPersistenceAssistant.chat(2L, "你好!我的名字是nacos");
String chat = chatPersistenceAssistant.chat(1L, "我的名字是什么");
System.out.println(chat);
chat = chatPersistenceAssistant.chat(2L, "我的名字是什么");
System.out.println(chat);
return "testChatPersistence success : "+ DateUtil.now();
}
}
Function Call
基本概念
大模型本身不调用工具,而是决策使用哪个工具。
工具调用一般会有两轮对话,以调用工具计算两数之和为例:
- 1.定义工具: 我们将手动创建一个
ToolSpecification来描述一个名为calculator的工具,它有一个add方法。 - 2.首次请求: 用户提问 “What is the sum of 5 and 7?”。我们将此问题连同工具规范一起发送给 LLM。
- 3.处理模型响应: LLM 不会直接回答,而是返回一个
ToolExecutionRequest,要求我们执行add方法,并提供参数{"a": 5, "b": 7}。 - 4.执行工具: 我们的 Java 代码将解析这个请求,调用本地的
add方法,得到结果12。 - 5.二次请求: 我们将工具的执行结果
12封装成ToolExecutionResultMessage,连同之前的对话历史一起再次发送给 LLM。 - 6.最终答案: LLM 收到工具的计算结果后,会用自然语言生成最终的答案,例如 “The sum of 5 and 7 is 12.”。
调用工具的两种方式
调用工具需要告知大模型三个参数:
1.工具名称
2.工具描述
3.工具参数
本地需要有模型的执行代码。
低阶 API:ChatModel
- ToolSpecification :描述工具,包含名称,描述,参数
- ToolExecutor :工具的业务逻辑
- AiServices.tools() 挂载工具
@Configuration
public class LLMConfig {
@Bean
public ChatModel chatModel(){
return OpenAiChatModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean
public FunctionAssistant functionAssistant(ChatModel chatModel){
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("开具发票助手")
.description("根据用户提交的开票信息,开具发票")
.parameters(JsonObjectSchema.builder()
.addStringProperty("companyName", "公司名称")
.addStringProperty("dutyNumber", "税号序列")
.addStringProperty("amount", "开票金额,保留两位有效数字")
.build())
.build();
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId)->{
System.out.println(toolExecutionRequest.id());
System.out.println(toolExecutionRequest.name());
String arguments1 = toolExecutionRequest.arguments();
System.out.println("arguments1****》 " + arguments1);
return "开具成功";
};
Map<ToolSpecification, ToolExecutor> tools =new HashMap<>();
tools.put(toolSpecification, toolExecutor);
return AiServices.builder(FunctionAssistant.class)
.chatModel(chatModel)
.tools(tools)// Tools (Function Calling)
.build();
}
}
public interface FunctionAssistant {
String chat(String message);
}
@RestController
public class ChatFunctionCallingController {
@Resource
private FunctionAssistant functionAssistant;
@GetMapping(value = "/chatfunction/test1")
public String test1()
{
String chat = functionAssistant.chat("开张发票,公司:尚硅谷教育科技有限公司 税号:atguigu533 金额:668.12");
System.out.println(chat);
return "success : "+ DateUtil.now() + "\t"+chat;
}
}
高阶 API:AIService
@Tool和@P注解:描述工具- 具体的类方法:工具的业务逻辑
AiServices.tools()挂载工具
// InvoiceHandler.java
@Slf4j
public class InvoiceHandler {
@Tool(name = "开票助手", value = "根据用户提交的开票信息进行开票")
public String handle(@P("公司名称") String companyName,
@P("税号") String dutyNumber,
@P("金额保留两位有效数字") String amount) throws Exception
{
log.info("companyName =>>>> {} dutyNumber =>>>> {} amount =>>>> {}", companyName, dutyNumber, amount);
//----------------------------------
// 这块写自己的业务逻辑,调用redis/rabbitmq/kafka/mybatis/顺丰单据/医疗化验报告/支付接口等第3方
//----------------------------------
return "开票成功";
}
}
// LLMConfig.java
@Bean
public FunctionAssistant FunctionAssistantfunctionAssistant(ChatModel chatModel)
{
return AiServices.builder(FunctionAssistant.class)
.chatModel(chatModel)
.tools(new InvoiceHandler())
.build();
}
向量数据库
基本概念

- 嵌入模型(向量模型):数据和用户请求通过嵌入模型转换为向量
- 向量数据库:数据向量存储在向量数据库中
- 相似度查询:用户请求也转换为向量,和向量数据库中的向量进行匹配,根据相似度高低返回结果
配置通义向量模型
获取模型

模型名称为:text-embedding-v3
配置模型
和配置聊天模型一致,只不过接口变为 EmbeddingModel
//LLMConfig.java
@Configuration
public class LLMConfig {
@Bean
public EmbeddingModel embeddingModel()
{
return OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("ALIQWEN_API"))
.modelName("text-embedding-v3")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
}
使用模型
文本向量化测试,看看形成向量后的文本。
- embeddingModel.embed() :把数据或用户请求转化为向量
// Controller.java
@GetMapping(value = "/embedding/embed")
public String embed()
{
String prompt = """
咏鸡
鸡鸣破晓光,红冠映朝阳金羽披霞彩,昂首步高岗。
""";
Response<Embedding> embeddingResponse = embeddingModel.embed(prompt);
System.out.println(embeddingResponse);
return embeddingResponse.content().toString();
}
配置 Qdrant 向量数据库
安装 Qdrant
添加依赖:项目 pom.xml 文件中添加:
<!--qdrant-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qdrant</artifactId>
</dependency>
Docker 部署 qdrant:docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
- 6333 端口:前端端口,访问 http://localhost:6333/dashboard#/welcome 即可看到前端界面
- 6334 端口:gRpc 调用端口
配置 Qdrant
// LLMConfig.java
// Qdrant客户端:创建Collection
@Bean
public QdrantClient qdrantClient() {
QdrantGrpcClient.Builder grpcClientBuilder =
QdrantGrpcClient.newBuilder("127.0.0.1", 6334, false);
return new QdrantClient(grpcClientBuilder.build());
}
// Qdrant存储器:连接到具体的Collction
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return QdrantEmbeddingStore.builder()
.host("127.0.0.1")
.port(6334)
.collectionName("test-qdrant")
.build();
}
使用 Qdrant
创建 Qdrant Collection(相当于在 Mysql 中创建一个数据库):qdrantClient
// Controller.java
@GetMapping(value = "/embedding/createCollection")
public void createCollection()
{
var vectorParams = Collections.VectorParams.newBuilder()
.setDistance(Collections.Distance.Cosine)
.setSize(1024)
.build();
qdrantClient.createCollectionAsync("test-qdrant", vectorParams);
}
往 Collection 中添加数据:embeddingStore.add
在 Collection 中查询数据:embeddingStore.search
// Controller.java
// 往向量数据库新增文本记录
@GetMapping(value = "/embedding/add")
public String add()
{
String prompt = """
咏鸡
鸡鸣破晓光,红冠映朝阳。金羽披霞彩,昂首步高岗。
""";
TextSegment segment1 = TextSegment.from(prompt);
segment1.metadata().put("author", "zzyy");
Embedding embedding1 = embeddingModel.embed(segment1).content();
String result = embeddingStore.add(embedding1, segment1);
System.out.println(result);
return result;
}
@GetMapping(value = "/embedding/query1")
public void query1(){
Embedding queryEmbedding = embeddingModel.embed("咏鸡说的是什么").content();
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(1)
.build();
EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);
System.out.println(searchResult.matches().get(0).embedded().text());
}
测试结果
// Controller.java
@Resource
InMemoryEmbeddingStore<TextSegment> embeddingStore;
@Resource
ChatAssistant chatAssistant;
// http://localhost:9013/rag/add
@GetMapping(value = "/rag/add")
public String testAdd() throws FileNotFoundException
{
//Document document = FileSystemDocumentLoader.loadDocument("/Users/iven/Documents/Research/03 Line/AI开发/Langchain4j-4-Function Call.md");
FileInputStream fileInputStream = new FileInputStream("/Users/iven/Documents/Research/03 Line/AI开发/Langchain4j-4-Function Call.md");
Document document = new ApacheTikaDocumentParser().parse(fileInputStream);
EmbeddingStoreIngestor.ingest(document, embeddingStore);
String result = chatAssistant.chat("文档内容是什么");
System.out.println(result);
return result;
}

RAG
基本概念
RAG(检索增强生成,Retrieval-Augmented Generation)是通过外挂知识库,让大模型具备垂类知识的一种技术,扩展知识边界,并避免“幻觉”问题。
主要步骤有:
- 预处理(蓝线部分):将数据通过分词器切分为片段,然后通过嵌入模型转换为向量,存储到向量数据库中
- 检索(红线部分):通过外部文档库搜索相关文本片段,或通过向量数据库进行相似度查询,先检索出与用户问题相关的内容。
- 生成(红线部分):将检索到的相关内容与用户原始问题一同输入到大语言模型中,由模型进行理解和综合,生成更准确、包含事实依据的回答。

简单示例
添加依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
</dependency>
添加向量数据库(这里方便起见使用内存向量数据库)
// LLMConfig.java
@Bean
public InMemoryEmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
构建 AIService
// ChatAssictant.java
public interface ChatAssistant {
/**
* 聊天
*
* @param message 消息
* @return {@link String }
*/
String chat(String message);
}
// LLMConfig.java
@Configuration
public class LLMConfig {
@Bean
public ChatModel chatModel() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("aliQwen-api"))
.modelName("qwen-plus")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
/**
* 需要预处理文档并将其存储在专门的嵌入存储(也称为矢量数据库)中。当用户提出问题时,这对于快速找到相关信息是必要的。
* 我们可以使用我们支持的 15 多个嵌入存储中的任何一个,但为了简单起见,我们将使用内存中的嵌入存储:
*
* https://docs.langchain4j.dev/integrations/embedding-stores/in-memory
*
* @return
*/
@Bean
public InMemoryEmbeddingStore<TextSegment> embeddingStore() {
return new InMemoryEmbeddingStore<>();
}
@Bean
public ChatAssistant assistant(ChatModel chatModel, EmbeddingStore<TextSegment> embeddingStore)
{
return AiServices.builder(ChatAssistant.class)
.chatModel(chatModel)
.chatMemory(MessageWindowChatMemory.withMaxMessages(50))
.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
.build();
}
}
@RestController
@Slf4j
public class RAGController{
@Resource
InMemoryEmbeddingStore<TextSegment> embeddingStore;
@Resource
ChatAssistant chatAssistant;
// http://localhost:9013/rag/add
@GetMapping(value = "/rag/add")
public String testAdd() throws FileNotFoundException{
//Document document = FileSystemDocumentLoader.loadDocument("D:\\44\\alibaba-java.docx");
FileInputStream fileInputStream = new FileInputStream("D:\\44\\alibaba-java.docx");
Document document = new ApacheTikaDocumentParser().parse(fileInputStream);
EmbeddingStoreIngestor.ingest(document, embeddingStore);
String result = chatAssistant.chat("错误码00000和A0001分别是什么");
System.out.println(result);
return result;
}
}
MCP
基本概念
MCP 让模型可以调用远程或本地的各种工具与资源,如果模型是大脑,MCP 则赋予了大脑手脚,使它可以与外界世界进行交互。大模型版的openFeign,用于大模型之间的通讯
MCP协议指定了两种传输类型:
- HTTP:客户端请求一个 SSE 通道来接收来自服务器的事件,然后通过 HTTP POST 请求发送命令。
- stdio:客户端可以将 MCP 服务器作为本地子进程运行,并通过标准输入/输出直接与其通信。
用户与 MCP 和大模型的交互方式如下图所示(出处):


使用示例
- 本地启动 mcp 服务端(stdio 方式)
- 构建 mcp 客户端
- 构建工具集
- 挂载到 AIService
- 发送请求返回结果(流式返回)
//Controller.java
@Autowired
private StreamingChatModel streamingChatModel;
@GetMapping("/mcp/chat")
public Flux<String> chat(@RequestParam("question") String question) throws Exception
{
// 1.本地启动mcp服务端(stdio)
String baiduApiKey = System.getenv("BAIDU_MAP_API_KEY");
if (baiduApiKey == null) {
throw new IllegalArgumentException("请设置环境变量 BAIDU_MAP_API_KEY");
}
System.out.println("使用的百度地图API Key: " + baiduApiKey);
McpTransport transport = new StdioMcpTransport.Builder()
.command(List.of("npx", "-y", "@baidumap/mcp-server-baidu-map"))
.environment(Map.of("BAIDU_MAP_API_KEY", baiduApiKey))
.build();
// 2.构建McpClient客户端
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
// 3.创建工具集和原生的FunctionCalling类似
ToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(mcpClient)
.build();
// 4.通过AiServivces给我们自定义接口McpService构建实现类并将工具集和大模型赋值给AiService
McpService mcpService = AiServices.builder(McpService.class)
.streamingChatModel(streamingChatModel)
.toolProvider(toolProvider)
.build();
// 5.调用我们定义的HighApi接口,通过大模型对百度mcpserver调用
Flux<String> result = mcpService.chat(question);
// 在流式响应完成后关闭 MCP 客户端
return result.doFinally(signalType -> {
try {
mcpClient.close();
} catch (Exception e) {
// 记录关闭异常但不影响响应
System.err.println("Error closing MCP client: " + e.getMessage());
}
});
}
返回结果
更多推荐

所有评论(0)