javaLangchain4j从官方文档入手,看他做了什么——AiService详解(三)

0 阅读7分钟

今天,我们来看看AiService服务类的整个调度过程,看看它做了些什么?

那么快点开始吧

Assistant assistant1 = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .chatMemoryProvider(memoryId -> chatMemory)
        .contentRetriever(xxxx)
    	...
    	...
        .build();

通常AiService服务类需要AiServices来进行builder

,那么首先到builder里面去打个断点

1、构建AIServiceContext上下文

在这里插入图片描述

进来以后看见这是个创建AI上下文的方法,如果你需要自己实现上下文管理就需要实现AiServiceContextFactory接口,没有的话就使用默认的AiServiceContext。那么这个上下文有哪些东西呢?其实就是

​ .chatModel(chatModel) ​ .chatMemoryProvider(memoryId -> chatMemory) ​ .contentRetriever(xxxx) ​ ... ​ ...

builder后面的这些所有的属性

在这里插入图片描述

chatmodel对象(必须)

自定义的Assistant接口(存入aiServiceClass ,必须)

returnType返回类型

toolService,工具服务类

retrievalAugmentor检索服务类

chatMemoryService store存储类

......

在这里插入图片描述

创建完AiServiceContext上下文之后开始构建AiService的实现类

2.构建AiService实现类

在这里插入图片描述

可以看到这里跟上面说的实现AiServiceContextFactory机制一样的需要你去实现AiServicesFactory接口。

但是需要注意他这里面两个接口都使用了spi(Service Provider Interface)机制去加载一个实现类的 在这里插入图片描述

SPI机制链接:Java SPI 机制详解 | JavaGuide

在这里插入图片描述

可以看到需要在META-INF/services去定义需要实现类的接口路径的文件,然后需要在这个文件里面写入实现类的包路径

总的来说,想要实现Spi机制需要干两件事:

服务实现:第三方实现接口

配置文件META-INF/services/[接口全类名],声明实现类

我们接着说回正文,如果没有自定的实现类就去实例化一个DefaultAiServices来作为AiService并把刚刚初始化的Context传给他。

3.代理实现类具体流程

在这里插入图片描述

接着去build里面使用java的动态代理去实现一个实现类,这里也是我认为,langchain4j里面最巧妙核心的地方了吧。通过自定义的Assistant接口,可以自行的去安排返回数据类型,以及方法名称,还有参数注解等等。langchain通过代理实现符合规定的接口,实现类对话接口定义与服务组装的解耦。为Assistant接口的方法都创建一个实现类

3.1实现默认Object方法

接着我们来看这个实现里面主要做了那些事情。

在这里插入图片描述

如果是默认的方法走默认处理,每个实现类都需要实现Object的三个基础方法

3.2验证method参数和组装上下文

在这里插入图片描述

如果这里的方法是来自ChatMemoryAccess的那么就走handleChatMemoryAccess方法,这个里面大概是实现了删除和获取聊天记忆的实现。

然后会去验证整个方法里面的参数是否合理

组装了invocationContext上下文来管理和存放所有需要使用到的工具,rag,store,chatmodel等等内容

这里进入了核心实现invoke(method, args, invocationContext);

3.3获取ChatMemory对象

在这里插入图片描述

首先拿到chatMemory为下文拿去历史消息做准备

3.4组装消息

在这里插入图片描述

组装系统消息和用户消息

3.5向订阅推送消息和上下文

在这里插入图片描述

向所有实现了AiServiceListener接口的对象推送DefaultAiServiceStartedEvent对象。

3.6RAG检索增强消息

在这里插入图片描述

下一步,如果存在RAG(检索模型)的话,就会调用模型检索向量数据,然后生成一个新的userMessage(包含了检索到的消息)

3.7经过安全护栏机制

在这里插入图片描述

RAG重新检索完消息它使用了“安全护栏“,护栏机制对输入的内容进行安全检索。那么这个guardrail是从哪里来的呢?关键就在于Context调用的guardrailService()这个方法

在这里插入图片描述

进入这个方法的核心里面,它会去找所有带有标注@InputGuardrails注解的对话方法,然后获取注解里面实现了InputGuardrail接口的实现类。也就是说这个安全护栏机制是需要自己去实现InputGuardrail接口来实现的,同样还有OutPutGuardrails;

3.8判断模型输出格式

在这里插入图片描述

这里根据 Assistant(用户自定义接口)判断返回类型是否是流式消息,以及模型是否支持Json格式输出

3.9内容审核

在这里插入图片描述

如果有在Assistant(用户自定义接口)的对话方法上使用Moderate注解以及添加了moderationModel的话就会进入到这里面,去请求审核模型并把它返回的规范内容作为输入内容。

3.10组装工具上下文

在这里插入图片描述

这里面进行了工具组装,包括了

在这里插入图片描述

请求时发送给大模型的ToolSpecification对象还有执行工具时的ToolExecutor,这样一般情况下langchain就会在一次对话后,如果有工具返回,就从toolExecutors这个map里面根据工具的名称拿到执行对象。

3.11流式消息

在这里插入图片描述

如果刚刚判断返回类型为TokenStream或者是Flux就会走这个路径

3.12构建对话请求体

在这里插入图片描述

如果模型支持 JSON 格式 + 已经生成了 JSON Schema → 就给 AI 开启 “强制 JSON 返回模式”,然后把这个格式设置塞进请求参数里。

接着构建chatRequest对象

3.13发送请求到对话

在这里插入图片描述

这里构造了chatExecutor,然后执行execute()方法时最终将整理的请求发送给了大模型,前面一大堆的铺垫总算是为了这一刻。

然后把请求体和请求结果推送给注册事件

3.14工具执行

在这里插入图片描述

AI 回答-------->判断是否需要调用工具(查天气、查数据库、查接口)-------->如果需要 → 自动调用工具------->把结果再丢给 AI-------->AI 整理后再回答-------->直到不需要调用工具为止

最后如果如果ai调用了立即返回工具且对话方法使用Result来包装的话就会立即返回,不再进行后续逻辑,这种一般是同步执行某个接口要么只需要返回执行状态。

3.15再进安全护栏

在这里插入图片描述

跟输入护栏一样方法里面如果有OutputGuardrails注解和自己实现了OutputGuardrail接口就可以对Ai输出的文本进行处理,整合成最终的输出结果。

3.16组装返回类型并返回

在这里插入图片描述

最后根据对话方法的returnTyoe并将response解析为对应的对象,然后判断是否需要包装为Result,否则直接返回。

4.AiService 调用完整流程

用户调用
    ↓
参数验证
    ↓
组装上下文
    ↓
获取 ChatMemory
    ↓
组装消息
    ↓
RAG 检索增强
    ↓
输入护栏检查
    ↓
发送请求到 LLM
    ↓
工具执行循环
    ↓
输出护栏检查
    ↓
解析返回结果
    ↓
返回给用户

总结

本文详细分析了 LangChain4j AiService 的完整调度流程,主要包括:

  1. 上下文构建:通过 Builder 模式初始化 AiServiceContext
  2. 动态代理:使用 Java 代理机制实现接口解耦
  3. 消息处理:组装系统消息、用户消息和历史记忆
  4. RAG 增强:检索向量数据增强用户输入
  5. 护栏机制:输入/输出双向安全检查
  6. 工具调用:自动识别并执行工具
  7. 结果解析:根据返回类型自动转换结果

下篇预告

在下一篇文章中,我们将继续探讨:

  • 流式消息的实现原理
  • ChatExecutor 的包装机制
  • 不同返回类型的解析策略
  • 自定义 TypeFactory 的实现

敬请期待!

## 参考资料

- LangChain4j 官方文档

- Java SPI 机制详解

忆 4. RAG 增强:检索向量数据增强用户输入

  1. 护栏机制:输入/输出双向安全检查
  2. 工具调用:自动识别并执行工具
  3. 结果解析:根据返回类型自动转换结果

下篇预告

在下一篇文章中,我们将继续探讨:

  • 流式消息的实现原理
  • ChatExecutor 的包装机制
  • 不同返回类型的解析策略
  • 自定义 TypeFactory 的实现

敬请期待!

## 参考资料

- LangChain4j 官方文档

- Java SPI 机制详解

- Model Context Protocol