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

LangChain4j 框架探索

LangChain4j 框架探索

  • Get Started
  • 核心概念1 - Message
  • 核心概念2 - Model
  • 核心概念3 - Ai Services
  • 核心概念4 - Structured Output
  • 核心概念5 - Chat Memory
  • 核心概念6 - Tool (Function Calling)
  • 核心概念7 - RAG(Retrieval-Augmented Generation)
    • 核心 RAG APIs
    • 使用示例
  • 总结

Get Started

Python: LangChain 官方文档

在这里插入图片描述

Java: LangChain4j 官方文档

在这里插入图片描述

Langchain4j 是 Langchain 这一 LLM 实现的java版本,更广为人知的可能是 python 版本的Langchain 实现,因此我这里拿出 python 版 Langchain 的官方页面和 Langchain4j 的官方文档做对比。
可以从对比中看出,python 版本强调的几个关键特性 langchain4j 都复刻出来了,其中我比较关注的是 AI Services、Tools、RAG 这几个高级功能的实现,其次是 ChatModel、Structured Output、Chat Memory 这些基础功能的实现。下图是官方给出的 Langchain4j 框架结构图:

在这里插入图片描述

集成情况
这里也列一下官方给出的对于各类大模型提供方 (providers)、向量存储、嵌入模型 (embedding)、图片生成模型、评分模型等的集成情况:
15+ LLM providers
20+ embedding (vector) stores
15+ embedding models
5 image generation models
2 scoring (re-ranking) models
1 moderation model (OpenAI)

集成方法

# build.gradle
implementation 'dev.langchain4j:langchain4j'
implementation 'dev.langchain4j:langchain4j-open-ai' 
implementation 'dev.langchain4j:langchain4j-ollama' 
implementation 'dev.langchain4j:langchain4j-easy-rag'

其中 langchain4j 库是核心库;
langchain4j-open-ai 和 langchain4j-ollama 分别用于集成openai和ollama的官方接口;
langchain4j-easy-rag 用于实现 RAG 检索增强生成;

接下来,我会对 Langchain4j 中最值得关注的几大特性逐一谈谈我对特性的理解以及在实践中的收获。

核心概念1 - Message

在这里插入图片描述

Message 很好理解,就是各类消息实体,从 Langchain4j 的实现来看,分为了 AiMessage、SystemMessage、ToolExecutionResultMessage、UserMessage 这四类消息。
在实践中发现,SystemMessage、UserMessage 这两类是暴露给开发者使用的,可以用相关注解(@SystemMessage、@UserMessage)将对应类型的提示语注入到 Agent 上下文中,具体例子可以在下文 Ai Services 部分提到;
AiMessage、ToolExecutionResultMessage 则是给各类大模型的实现使用的,通常作为大模型 API 的返回,其中 ToolExecutionResultMessage 又和 Tool 特性相关;
除此以外,和 Message 紧密相关的 Chat Memory 中也提供了一些方法来操作这些 Message,这在 Chat Memory 部分也会提到。

核心概念2 - Model

在这里插入图片描述

Model 则是上面提到的大模型实现。可以从上图看出,ChatLanguageModel 接口的实现都是定义在单独的依赖包中的,这是因为每种大模型的使用方式均有区别,比如 ollama 是基于本地的,openai 是需要 apikey 的,其他区别还有很多。
除此以外,还有 ImageModel 接口用于实现各类图片生成模型,嵌入模型和评分模型等的接口这里先不做介绍了。

核心概念3 - Ai Services

Ai Services for Java 类似于 python LangChain 的 Chains 特性。
我的理解:Ai Services 作为 Agent 代理对象,可以作为 Tool、RAG等高级 API 功能的入口。

public interface Assistant {
   @SystemMessage({
           "你是一个AI助手, 默认使用中文对话, 所有系统消息中:\n" +
                   "IF-ELSE 语句用于设置提示语策略的生效场景, \n" +
                   "INPUT 用于描述输入, \n" +
                   "OUTPUT 用于描述返回要求, \n" +
                   "CONDITION 用于描述附加条件, \n" +
                   "AND/OR/NOT 用于表示与/或/非的逻辑判断",
           "IF USER INPUT `如何使用机器人` OR `如何使用AI Bot`等相关问题 ELSE YOU OUTPUT 如下帮助文档:\n" +
                   "```\n" +
                   "1) \\gpt start [-s|-g]: 开始会话, 可选选项 -s 用于启用严格模式(@机器人 触发), -g 用于启用群聊模式\n" +
                   "2) \\gpt end: 结束会话\n" +
                   "3) \\gpt clear: 清空会话记录\n" +
                   "4) #image + 文本: 图片生成请求,需要在会话中执行才可生效\n" +
                   "```",
           "现在的时间是:{{current_time}}"
   })
   String chat(@MemoryId @UserName String userName, @UserMessage String message, @V("current_time") LocalDateTime time);
}

这是我 DEMO 中一个通用 AiService 的定义,它的一个基本功能就是可以用方法注解定义一些提示语,我这里规定了一些提示词规范和机器人指令;
Langchain4j 定义了一些注解支持方法将对象参数序列化到 prompt 中,比如上面的 Assistant 中:
@MemoryId - 配合 ChatMemory 使用,标记该参数作为 ChatMemory 聊天上下文缓存中区分用户的唯一ID;
@Username - 标记该参数为 用户名,帮助 AiService 识别不用用户,但本身不具备多轮记忆,用这个注解可以简单理解为告诉AI你是谁;
@UserMessage - 标记该参数是用户当前要发的消息,除了作为参数注解,该注解也可以作为方法注解适用于更加灵活的场景中;

更通用的,还有:
@V(“xxx”) - 很好理解的,这是一个别名注解,配合它可以在方法注解 @SystemMessage 中使用{{}} 双括号作为变量拼接到系统消息提示语中,此外也可以@UserMessage等其他方法注解中,通过这种方式可以灵活的给AI输入各种结构化信息。

Assistant 的使用方法如下:

Assistant assistant = AiServices.builder(Assistant.class)
       .chatLanguageModel(chatModel)
       .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(100))
       .build();
assistant.chat("test", "hello", LocalDateTime.now());

它使用 builder 模式初始化必要的属性,chatModel 属性通常是必须的,如果想要启用聊天上下文(即 Chat Memory),则还需要配置 chatMemoryProvider,其他与关键特性相关的属性还有 tools、retrievalAugmentor(RAG 相关)等,这里不做赘述。

核心概念4 - Structured Output

结构化输出对于LLM框架来说意义非凡,这个特性赋予了AI模型与代码逻辑层准确交互的能力,将AI模型输出的人类语言处理成面向对象程序可以理解的信息,在NLP领域的分支——信息抽取任务中 LLM 框架的结构化转换能力可谓遥遥领先;

在 Langchain4j 框架中,结合Java面向对象的特性,框架实现了 AiService 根据返回的消息自动解析为 AiService 中的方法入口的返回参数类型的能力,举例如下:

// Ai Service 定义 route 方法并返回 RouterType 枚举类型
public interface Router {
    @UserMessage("判断 `{{message}}` 与哪种类型相关, 必须返回其中一种:{{types}}")
    RouterType route(@UserName String userName, @V("message") String message, @V("types") Map<String, String> types);
}
// RouterType 枚举类定义
public enum RouterType {
    @Description("天气、新闻、微博热搜等实时信息")
    TIME,
    @Description("以 #image 开头、绘画 或 生成图片")
    IMAGE,
    @Description("和以上类型都无关")
    OTHER;
    @Getter
    private static final Map<String, String> descriptions = Arrays.stream(values())
                .collect(Collectors.toMap(RouterType::name, RouterType::getDescription));

    private static String getDescription(RouterType routerType) {
        try {
            java.lang.Class<RouterType> clazz = RouterType.class;
            java.lang.reflect.Field field = clazz.getField(routerType.name());
            Description description = field.getAnnotation(Description.class);
            return description != null ? description.value()[0] : "";
        } catch (NoSuchFieldException | SecurityException e) {
            return "";
        }
    }
}
// Ai Service 方法使用并返回枚举类对象
ChatLanguageModel chatModel = ModelFactory.buildchatModel(config);
Router tester = Aiservices.builder(Router.class).chatLanguageModel(chatModel).build();
tester.route("test", "现在是几点", RouterType.getDescriptions());// TIME
tester.route("test", "画一杯咖啡", RouterType.getDescriptions()) //IMAGE

该例子中我使用枚举类型 RouterType 作为 Router::route 方法的返回参数,LLM 会根据我提供的message判断属于 RouterType 中的哪一种类型。值得一提的是,@Description注解虽然可以帮助AI理解当前枚举值的含义,但并非必须的,框架默认会根据枚举值的字符串分析其含义;
除此以外,在实战中有一点发现,虽然我给 RouterType 提供了 OTHER 值作为异常情况的返回,但这并不总是成功的,比如我输入一段随机乱写的字符,它可能会报 “不存在符合 XXX值 的 RouterType 枚举值” 的类似错误,所以其解析逻辑依旧存在无法处理的边界情况,针对这一问题,我将 RouterType.getAll() 作为输入,提高了其识别枚举类型的能力,除此以外,也可以用捕获异常并返回 OTHER 的方式增强其处理能力,可视具体情况而定。

当然,枚举类型是Java对象中比较特殊的一类,结合枚举类型的返回值,可以实现类似于NLP领域中的多标签分类任务的功能,这可以帮助我们实现路由、映射等功能。

对于Java中的一般POJO对象,框架也可以实现类似功能,更多场景和尝试大家可以尽情探索。。。

核心概念5 - Chat Memory

顾名思义,Chat Memory 这一特性实现了对消息上下文的管理。
在这里插入图片描述
在这里插入图片描述

上面两个接口是 Chat Memory 相关的关键接口,ChatMemory 用于实现对 ChatMessage 列表的上下文管理,即用于实现不同用户拥有不同的 ChatMemory,可以看到它有一个 id 属性就是这个用途。有一点值得注意,为了实现这个功能,需要在 AiService 的方法参数中通过 @MemoryId 注解来关联,具体示例可以参考上文 Ai Services 部分给出的 Assistant 的 chat 方法定义。
而 ChatMessageStore 用于实现 ChatMessage 列表的存储管理,它在核心库的默认实现是 InMemoryChatMemoryStore,即存储在内存当中,当然也有其他存储方案的实现。

核心概念6 - Tool (Function Calling)

// Tool 定义
public class WebPageTool {
    @Tool("IF USER INPUT `热搜、头条` 等新闻话题 ELSE YOU OUTPUT `序号) 热搜标题` CONDITION 每条热搜需要换行")
    public String getNews(@ToolMemoryId String userName,
                             @P("关键字:比如 微博热搜、今日头条、每日新闻") String keyword) {
        return WeiBoCrawler.crawlWeiboTops();
    }

    @Tool("IF USER INPUT `time-sensitive (recent or newest) question`, ELSE YOU search from web")
    public String getWebPage(@ToolMemoryId String userName,
                             @P("The origin query") String query,
                             @P("Name of the search engine: [baidu, bing, 360, sogou, quark]") String engine) {
        return SearchCrawler.crawlFromEngine(engine, query);
    }
}
// Tool 使用
Assistant assistant = AiServices.builder(Assistant.class)
                .chatLanguageModel(chatModel)
                .tools(new WebPageTool())
                .chatMemoryProvider(memoryId -> messageService.buildChatMemory(userName))
                .build();

Tool 特性允许 LLM 在必要时调用一个或多个可用的自定义工具方法。Tool 的使用场景通常有:网络搜索、对外部 API 的调用或特定代码段的执行等。
需要注意的是,LLM 框架本身是不具备判断 Tool 是否需要执行或者需要执行哪些 Tool 的逻辑的,这块功能由 大模型提供方 实现,也就是说如果一个 模型(比如gpt-3.5-turbo)不具备 Tool 能力,那我们提供的 Tool 就不会生效;相反,只有选择支持 Tool 的模型(比如 gpt-4o-mini)才能从接口返回中解析得到 ToolExecutionResultMessage,告诉我们需要执行哪些 Tool。

在实践中也发现,由于 Tool 功能依赖于具体模型是否支持,我们可以采取降级方案,比如使用 RAG router 或者 使用一个 返回类型为枚举类型的 Ai Service 帮助我们 route 到对应方法上,在本文 Ai Service 部分和 RAG 部分的例子中均有对应例子。

核心概念7 - RAG(Retrieval-Augmented Generation)

检索增强生成 是一种结合了信息检索和语言生成技术的方法。LLM的知识仅限于其所训练的数据,而 RAG 可以让LLM了解特定领域的知识或专有数据。
简而言之,RAG 是一种在问题发送到 LLM 之前从数据中查找相关信息并将其注入到提示语中的方法。这样, LLM 将获得相关信息,并能够使用这些信息进行回复,这应该会减少产生幻觉的可能性。
目前主流的检索方案有 Full-text(全文)检索(或关键字检索)、Vector(向量)检索(或语义检索)以及 组合多种方法的方案。

RAG 过程分为 2 个不同的阶段:索引和检索。 LangChain4j 为这两个阶段提供了工具。

索引阶段简要流程:
在这里插入图片描述

索引阶段简要流程:
在这里插入图片描述

核心 RAG APIs

Document: 表示一个文档,例如单个 PDF 文件或网页,目前只支持表示文本信息;
Metadata:描述 Document 元信息的类,,例如其名称、来源、更新日期等;
Document Loader:文档加载器,针对不同文档类型和来源有很多实现;
Document Parser:文档解析器,用于解析 PDF、DOC、TXT 等多种格式的文档,通常配合 Document Loader 使用;
Document Transformer:可以执行各种文档转换,例如 清理、过滤、丰富、摘要等等;
Text Segment:文本段,由 Document 分割出的更小的段,只包含文本信息;
Document Splitter: 文件分割器,用于 Document 分割成 Text Segment;
Text Segment Transformer:文本段转换器,类似于DocumentTransformer,没有具体实现;
Embedding:封装了一个向量,表示已嵌入内容的“语义含义”;
Embedding Model:嵌入模型,用于将文本转换为 Embedding;
Embedding Store:嵌入存储,也称为向量数据库;
Embedding Store Ingestor:嵌入存储摄取器,代表一个摄取管道,负责将 Document 摄取到 EmbeddingStore 中。

使用示例

public String searchDialog(GptSession session, String userMsg) {
   EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
   InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
   ContentRetriever embeddingStoreContentRetriever = EmbeddingStoreContentRetriever.builder()
           .embeddingStore(embeddingStore)
           .embeddingModel(embeddingModel)
           .maxResults(2)
           .minScore(0.6)
           .build();
   WebSearchEngine webSearchEngine = TavilyWebSearchEngine.builder()
           .apiKey(ConfigUtil.getConfig("tavily.key")) // get a free key: https://app.tavily.com/sign-in
           .build();
   ContentRetriever webSearchContentRetriever = WebSearchContentRetriever.builder()
           .webSearchEngine(webSearchEngine)
           .maxResults(3)
           .build();
   QueryRouter queryRouter = new DefaultQueryRouter(embeddingStoreContentRetriever, webSearchContentRetriever);
   RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
           .queryRouter(queryRouter)
           .build();

   ChatLanguageModel chatModel = session.getChatModel();
   String userName = session.getUserName();
   Assistant assistant = AiServices.builder(Assistant.class)
           .chatLanguageModel(chatModel)
           .retrievalAugmentor(retrievalAugmentor)
           .chatMemoryProvider(memoryId -> messageService.buildChatMemory(userName))
           .build();
   return assistant.chat(session.getShortName(), userMsg);
}

以上这段代码中就实现了一个比较经典的RAG高级用法,即通过 QueryRouter 组合了一个 EmbeddingStoreContentRetriever 和一个 WebSearchContentRetriever,QueryRouter 决定是走向量检索还是网络检索,流程图如下:
在这里插入图片描述

总结

Langchain for java 的框架探索中,Langchain 的结构化特性 将 AI 的灵活开放特性 和 Java 开发友好的面向对象特性 结合起来,这点引起极大舒适;
从和 python 版本的对比看来,Langchain4j 在实现时加入了自己的思考,并不是照抄照搬,值得肯定;
在具体的开发实践中,我从最初接触时的 0.32 过渡到了 最新的 0.36 版本,能感受到框架实现是越来越成熟的,对各类模型的集成和更新也是能跟上最新技术变革和变动的;
开发细节上,框架通过 Ai Services 集中整合各类特性,降低了上手难度,但相应的可能会放弃一些特性模块之间的独立性,相信框架维护者是有自己的考量的。
总之,难以否认的是,目前 AI 生态还是 python 这边优势更大,但是 Langchain4j 框架也算是为Java 开发者们的 AI 探索之路点燃了一把火炬。
最后附上我的实战 DEMO: https://github.com/PengfeiMiao/ichat, 感兴趣的小伙伴可以来看看。


http://www.kler.cn/a/467827.html

相关文章:

  • 欧科云链研究院:ChatGPT 眼中的 Web3
  • IDE和IDEA详解和具体差异
  • 计算机网络 (28)虚拟专用网VPN
  • 鸿蒙HarmonyOS开发:基于Swiper组件和自定义指示器实现多图片进度条轮播功能
  • 欧几里得距离在权重矩阵中的物理意义
  • 计算机的错误计算(二百零二)
  • 前端-计算机网络篇
  • 【Unity功能集】TextureShop纹理工坊(八)修剪工具
  • 基于Spring Boot的前后端分离的外卖点餐系统
  • 前端异常处理合集
  • python pandas 对mysql 一些常见操作
  • Vulnhub靶场(Earth)
  • 【机器学习篇】解密算法魔方之魅之机器学习的多维应用盛宴
  • C 实现植物大战僵尸(四)
  • 太速科技-633-4通道2Gsps 14bit AD采集PCie卡
  • Azkaban其二,具体使用以及告警设置
  • win10 npm login 登陆失败
  • ARM CCA机密计算安全模型之CCA认证
  • 大数据技术(六)—— Hbase集群安装
  • Oracle ADG备机报错ORA-00328 ORA-00334
  • 人工智能:是助力还是取代?
  • CSP知识点整理大全
  • arm64函数源码和汇编解析(objdump)
  • Java【线程与并发】
  • 项目配置设置二 (芒果头条项目进度3)
  • 大型 UniApp 应用的架构设计