当前位置: 首页 > news >正文

使用langchain4j遇到的难题(暂记)

目录

一、在 Spring Boot 项目中集成 LangChain4j 框架,使用 Redis 持久化聊天历史

问题

根本原因分析

解决方案:

二、Jackson 无法序列化 ToolExecutionRequest 对象(循环调用Tools)

问题:

根本原因分析:

解决方案:

注意:langchain4j对于序列化有专门的工具处理,如下:

同时对于版本较高的可以使用官方的redis模块:

三、处理消息role时遇到的问题

问题:

解决方案:


一、在 Spring Boot 项目中集成 LangChain4j 框架,使用 Redis 持久化聊天历史

问题

{"error":{"code":"1214","message":"输入不能为空"}}
以及 Jackson 序列化异常No serializer found for class dev.langchain4j.data.message.UserMessage
and no properties discovered to create BeanSerializer

根本原因分析


1. 首次对话 Redis 空数据问题
新会话(sessionId)在 Redis 中无历史记录
RedisChatMemoryStore.getMessages() 返回空列表
某些 LLM API(如智谱 AI)要求消息列表不能为空
2. Jackson 无法直接序列化 ChatMessage
LangChain4j 的消息类(UserMessage、AiMessage、SystemMessage)没有标准的 getter 方法,导致 Jackson 序列化失败。
3. 消息类混淆陷阱
容易错误导入 dev.ai4j.openai4j.chat.AssistantMessage,而实际应使用 dev.langchain4j.data.message.AiMessage。

解决方案:

核心思路:创建中间包装类
通过自定义的 MessageWrapper 类作为桥梁,实现 ChatMessage 与 JSON 的双向转换。

package com.demo.javaaitest.utile; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @Component public class RedisChatMemoryStore implements ChatMemoryStore { private static final String KEY_PREFIX = "chat:memory:"; private static final long TTL_HOURS = 24; @Autowired private StringRedisTemplate redisTemplate; private final ObjectMapper objectMapper = new ObjectMapper(); @Override public List<ChatMessage> getMessages(Object memoryId) { String key = KEY_PREFIX + memoryId.toString(); String json = redisTemplate.opsForValue().get(key); if (json == null || json.trim().isEmpty()) { System.out.println("[RedisChatMemoryStore] 会话 " + memoryId + " 无历史记录(新会话)"); return new ArrayList<>(); } try { List<MessageWrapper> wrappers = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, MessageWrapper.class)); List<ChatMessage> messages = new ArrayList<>(); for (MessageWrapper wrapper : wrappers) { ChatMessage message = deserializeMessage(wrapper); if (message != null) { messages.add(message); } } System.out.println("[RedisChatMemoryStore] 会话 " + memoryId + " 加载了 " + messages.size() + " 条历史消息"); return messages; } catch (JsonProcessingException e) { System.err.println("[RedisChatMemoryStore] 反序列化失败, memoryId: " + memoryId + ", 错误: " + e.getMessage()); return new ArrayList<>(); } } @Override public void updateMessages(Object memoryId, List<ChatMessage> messages) { if (messages == null || messages.isEmpty()) { System.out.println("[RedisChatMemoryStore] 会话 " + memoryId + " 尝试保存空消息列表,跳过"); return; } String key = KEY_PREFIX + memoryId.toString(); try { List<MessageWrapper> wrappers = new ArrayList<>(); for (ChatMessage message : messages) { wrappers.add(serializeMessage(message)); } String json = objectMapper.writeValueAsString(wrappers); redisTemplate.opsForValue().set(key, json, TTL_HOURS, TimeUnit.HOURS); System.out.println("[RedisChatMemoryStore] 会话 " + memoryId + " 已保存 " + messages.size() + " 条消息到 Redis"); } catch (JsonProcessingException e) { System.err.println("[RedisChatMemoryStore] 序列化失败, memoryId: " + memoryId + ", 错误: " + e.getMessage()); } } @Override public void deleteMessages(Object memoryId) { String key = KEY_PREFIX + memoryId.toString(); redisTemplate.delete(key); System.out.println("[RedisChatMemoryStore] 会话 " + memoryId + " 已删除"); } /** * 将 ChatMessage 转换为可序列化的 Wrapper 对象 */ private MessageWrapper serializeMessage(ChatMessage message) { MessageWrapper wrapper = new MessageWrapper(); if (message instanceof UserMessage) { wrapper.setType("USER"); wrapper.setContent(((UserMessage) message).text()); } else if (message instanceof AiMessage) { wrapper.setType("ASSISTANT"); wrapper.setContent(((AiMessage) message).text()); } else if (message instanceof SystemMessage) { wrapper.setType("SYSTEM"); wrapper.setContent(((SystemMessage) message).text()); } else { wrapper.setType("UNKNOWN"); wrapper.setContent(""); } return wrapper; } /** * 从 Wrapper 对象还原为 ChatMessage */ private ChatMessage deserializeMessage(MessageWrapper wrapper) { if (wrapper.getContent() == null) { return null; } switch (wrapper.getType()) { case "USER": return UserMessage.from(wrapper.getContent()); case "ASSISTANT": return AiMessage.from(wrapper.getContent()); case "SYSTEM": return SystemMessage.from(wrapper.getContent()); default: System.err.println("[RedisChatMemoryStore] 未知的消息类型: " + wrapper.getType()); return null; } } /** * 消息包装类,用于 JSON 序列化 */ private static class MessageWrapper { private String type; private String content; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } }

二、Jackson 无法序列化 ToolExecutionRequest 对象(循环调用Tools)

问题:

使用@Tool注解,工具调用必须保存toolExecutionRequests列表时进行序列化失败

根本原因分析:

因为它的字段是private且没有标准的 getter 方法(或者 Jackson 找不到可序列化的属性)。我们需要将toolExecutionRequests转换为 Jackson 能理解的格式(比如Map)来存储,读取时再反向构造。

解决方案:

updateMessages中,不要直接存储ToolExecutionRequest对象,而是将其转换为Map<String, Object>

if (msg instanceof AiMessage) {
AiMessage aiMsg = (AiMessage) msg;
if (aiMsg.hasToolExecutionRequests()) {
List<Map<String, Object>> requestMaps = aiMsg.toolExecutionRequests().stream()
.map(req -> {
Map<String, Object> reqMap = new HashMap<>();
reqMap.put("id", req.id());
reqMap.put("name", req.name());
reqMap.put("arguments", req.arguments());
return reqMap;
})
.collect(Collectors.toList());
map.put("toolExecutionRequests", requestMaps);
} else {
map.put("text", aiMsg.text());
}
}

getMessages中,读取时反向转换

case "AI":
Object requests = map.get("toolExecutionRequests");
if (requests != null) {
// requests 是一个 List<Map<String, Object>>
List<Map<String, Object>> requestMaps = (List<Map<String, Object>>) requests;
List<ToolExecutionRequest> toolRequests = requestMaps.stream()
.map(reqMap -> ToolExecutionRequest.builder()
.id((String) reqMap.get("id"))
.name((String) reqMap.get("name"))
.arguments((String) reqMap.get("arguments"))
.build())
.collect(Collectors.toList());
return new AiMessage(toolRequests);
} else {
String text = (String) map.get("text");
return text != null ? new AiMessage(text) : null;
}

注意:langchain4j对于序列化有专门的工具处理,如下:
package com.demo.javaaitest.redis; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.ChatMessageDeserializer; import dev.langchain4j.data.message.ChatMessageSerializer; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @Component public class RedisChatMemoryStore implements ChatMemoryStore { @Autowired private StringRedisTemplate redisTemplate; private static final String KEY_PREFIX = "chat:"; @Override public List<ChatMessage> getMessages(Object memoryId) { String key = KEY_PREFIX + memoryId.toString(); String json = redisTemplate.opsForValue().get(key); if (json == null || json.isEmpty()) { return new ArrayList<>(); } try { // 使用官方反序列化工具,一行代码搞定 return ChatMessageDeserializer.messagesFromJson(json); } catch (Exception e) { e.printStackTrace(); return new ArrayList<>(); } } @Override public void updateMessages(Object memoryId, List<ChatMessage> messages) { String key = KEY_PREFIX + memoryId.toString(); try { // 使用官方序列化工具,一行代码搞定 String json = ChatMessageSerializer.messagesToJson(messages); redisTemplate.opsForValue().set(key, json, 24, TimeUnit.HOURS); } catch (Exception e) { e.printStackTrace(); } } @Override public void deleteMessages(Object memoryId) { String key = KEY_PREFIX + memoryId.toString(); redisTemplate.delete(key); } }
同时对于版本较高的可以使用官方的redis模块:

import dev.langchain4j.store.memory.chat.RedisChatMemoryStore;

// 在你的配置类中
@Bean
public ChatMemoryStore chatMemoryStore(RedisClient redisClient) {
// RedisChatMemoryStore 的构造方法可能因版本而异,请参考官方文档
return new RedisChatMemoryStore(redisClient);
}

三、处理消息role时遇到的问题

问题:

不同的 LLM 提供商对角色名的格式要求可能不同

解决方案:

在 LangChain4j 中,Role主要用于标识对话中不同消息的发送者。框架本身和不同的模型提供商都定义了各自的角色枚举,但核心概念是相通的。

1、核心角色 (OpenAI 风格)

在 LangChain4j 的核心抽象中,最常使用的Role枚举(通常与 OpenAI 模型对应)包含以下几种:

  • SYSTEM: 用于设定AI助手的背景、行为或人格。这条消息通常位于对话的最开始,用来指导模型后续的所有回复。

  • USER: 代表最终用户或应用程序发出的消息,即用户提出的问题或指令。

  • ASSISTANT: 代表AI模型生成回复的消息。在多轮对话中,之前的AI回复会以这个角色继续参与上下文。

  • TOOL/FUNCTION: 用于表示工具调用或函数执行的结果。当AI决定调用一个工具(如查询数据库)时,工具的执行结果会以这个角色返回给模型。其中FUNCTION角色已被标记为@Deprecated(弃用),推荐使用TOOL

2、特定模型提供商 (Provider-specific) 的角色

除了上述通用角色,LangChain4j 在为不同模型提供商(如 Anthropic, Mistral, WorkersAI)做适配时,也定义了各自的角色枚举。虽然名称可能略有不同,但语义是基本一致的:

模型提供商对应角色枚举 (Enum)包含的角色
Anthropic(Claude)AnthropicRole与核心角色类似,包含SYSTEM,USER,ASSISTANT等。
Mistral AIMistralAiRole包含SYSTEM,USER,ASSISTANT,TOOL
Workers AIMessageRole包含system,ai(相当于 ASSISTANT),user

3、使用注意事项

  • ChatMessage接口:在 LangChain4j 中,所有角色的消息都实现了ChatMessage接口,这为处理不同类型的消息提供了统一的类型安全方式。

  • 大小写问题:不同的 LLM 提供商对角色名的格式要求可能不同(例如,有的要求全部小写)。在使用时,需要注意框架的序列化逻辑是否会自动处理,否则可能会遇到类似的问题。

http://www.gsyq.cn/news/1581488.html

相关文章:

  • C.3 DRM/TTM 灵魂拷问 100 问: 解释下 AMDGPU_GEM_CREATE_VRAM_CLEARED 标志的作用和实现原理
  • 无人机电力营销落地瓶颈深度解析|四大核心壁垒、运维营销业务差异化、实景落地案例、全套YOLOv8电力AI视觉工程实现
  • 从零剖析十路充电桩嵌入式源码----软件开发环境搭建【3.1】
  • ivs-nat与nginx四层代理区别
  • deepspeed,vllm,llamafactory的使用
  • 云耀计算AI-Claura,在树莓派运行的AI
  • IntelliGit 项目个人工作总结
  • 金融事件序列建模:PRAGMA Transformer模型解析与应用
  • 复杂流体系统实时控制:模型降阶与滚动时域优化实践
  • 当AI Agent开始写AI Agent:自进化系统在企业管理中的伦理与安全红线
  • 广告物料行业实践指南:从制作到落地的全流程解析与未来趋势展望
  • 自适应信息流:让视觉语言模型学会动态聚焦的关键技术
  • 专利代理师:2025年实务真题回忆版
  • Windows Codex + CC Switch+deepseek 完整闭坑配置指南
  • 博弈论与机制设计:构建AI系统评估的20条核心原则与实践指南
  • AestheticNet:融合视觉认知与语义感知的图像美学质量评估新范式
  • Mind‘s Eye视觉认知基准:从抽象推理到动态预测的AI能力评估
  • 云计算虚拟网络:VXLAN覆盖网络与SDN控制器架构
  • 从脆弱数据主体到脆弱化数据实践:AI伦理的工程化视角与加固方法
  • React Fiber 的优先级调度原理
  • FreqFlow:基于频率感知的流匹配模型提升图像生成细节质量
  • Wasserstein几何与随机测地投影:优化神经网络训练的新视角
  • NestPipe框架:优化大规模推荐系统训练效率的创新方案
  • 安全技术Web应用防火墙规则配置与攻击防护的效果验证
  • 大语言模型在POI预测中的上下文学习应用
  • 委托代理关系中的中途支付与终止合同机制:提升项目效率的契约设计
  • Mind‘s Eye基准:评估多模态大模型的视觉认知与空间推理能力
  • Ubuntu 16.04 安装 devtools:旧系统对接 R 最新生态的实战指南
  • 机器学习融合手机信令与收费数据实现交通流精准实时估计
  • 自动驾驶博弈论MPC实时求解:牛顿类方法的工程实践与优化