LangChain4j AiServices 源码分析文档

分类:人工智能
000

一、概述

1.1 类定位与核心价值

AiServices 是 LangChain4j 框架的高层核心API,用于封装底层 LLM 交互的复杂逻辑,彻底规避底层 ChatModel、ChatMessage、ChatMemory 等组件的样板代码开发。

其核心设计理念借鉴 Spring Data JPA、Retrofit 声明式编程思想:开发者只需定义Java接口,框架自动生成代理实现类,专注业务逻辑而非底层组件编排。

相较于框架早期的 Chains(遗留链路),AiServices 具备更高的灵活性、可定制性,是官方主推的 LLM 应用开发方案,全面支持对话记忆、工具调用、RAG、结构化输出、流式响应等核心能力。

1.2 核心特性总览

AiServices 一站式支持以下 LLM 应用核心能力:

  • 声明式消息模板(静态/动态系统提示、用户提示)
  • 会话记忆管理(全局共享记忆、多用户独立记忆)
  • 工具调用(静态工具、动态工具、并发工具执行、异常容错)
  • RAG 检索增强(基础检索、高阶检索增强器)
  • 流式响应、结构化输出(POJO/枚举/布尔值自动解析)
  • 内容自动审核、输入/输出防护栅格
  • 事件监听、链路可观测、工具补偿机制

二、核心设计架构

2.1 类结构与核心成员

AiServices 为抽象构建器类,采用 Builder 设计模式,所有配置方法支持链式调用,最终通过build() 生成代理实例。

核心成员变量

  • AiServiceContext context:核心上下文,存储所有配置信息(模型、记忆、工具、RAG、防护规则等)
  • contentRetrieverSet/retrievalAugmentorSet:RAG 配置互斥标记,保证同一实例仅配置一种RAG模式

工厂机制

通过静态内部类 FactoryHolder 实现 SPI 工厂加载,默认使用 DefaultAiServices 实现,支持自定义工厂扩展:

  • 优先加载 SPI 配置的 AiServicesFactory
  • 无自定义工厂时,使用框架默认实现 DefaultAiServices

2.2 核心工作原理

  1. 接口定义:开发者定义纯Java接口,通过注解/方法参数声明AI能力
  2. 上下文初始化:通过 builder() 初始化上下文,承载所有组件配置
  3. 代理生成:框架通过反射生成接口代理类,封装底层调用逻辑
  4. 请求编排:自动完成提示词渲染、记忆拼接、RAG检索、工具调用、参数转换
  5. 结果解析:自动将LLM原始响应解析为声明的返回类型(字符串、POJO、枚举等)

三、快速创建AI服务实例

AiServices 提供两种实例创建方式,适配简单/复杂场景

3.1 极简创建方式(静态方法)

适用于无复杂配置的基础对话场景,支持普通模型和流式模型

// 1. 普通对话模型
interface Assistant {
    String chat(String userMessage);
}
// 快速创建实例
Assistant assistant = AiServices.create(Assistant.class, chatModel);

// 2. 流式对话模型(返回值必须为TokenStream)
interface StreamAssistant {
    TokenStream chat(String userMessage);
}
StreamAssistant streamAssistant = AiServices.create(StreamAssistant.class, streamingChatModel);

3.2 构建器创建方式(推荐)

适用于需要配置记忆、工具、RAG、防护规则的复杂场景,支持全量自定义配置

Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel) // 绑定模型
        .chatMemory(memory) // 绑定会话记忆
        .tools(new UserTools()) // 绑定工具
        .contentRetriever(retriever) // 绑定RAG检索器
        .moderationModel(moderationModel) // 绑定内容审核
        .build();

四、核心配置能力详解

4.1 模型配置

AiServices 严格限制同一实例只能配置一种模型(普通对话模型/流式对话模型二选一)

  • chatModel(ChatModel):配置普通LLM模型,用于同步对话
  • streamingChatModel(StreamingChatModel):配置流式LLM模型,接口返回值必须为 TokenStream

基础校验规则:构建时会校验模型非空,未配置模型将抛出 illegalConfiguration 异常

4.2 提示词配置体系

支持静态注解、动态函数、消息转换器三级提示词配置,优先级:注解 > 动态提供者 > 转换器

4.2.1 系统提示配置

  • 静态系统提示:systemMessage(String) 固定系统提示词
  • 动态系统提示(按记忆ID):systemMessageProvider(Function) 根据会话ID动态生成提示
  • 动态系统提示(全上下文):systemMessageProviderWithContext(Function) 可感知模型、请求参数上下文
  • 系统提示转换器:systemMessageTransformer 全局后置修改系统提示,支持追加/覆盖内容

4.2.2 用户提示配置

支持动态用户提示模板,可通过 {{it}} 或 @V 注解绑定方法参数,实现动态Prompt渲染

4.3 会话记忆配置

解决LLM无状态问题,支持两种记忆模式,互斥配置

  • 全局共享记忆:chatMemory(ChatMemory) 所有请求共用一个记忆实例,适用于单会话场景
  • 多用户独立记忆:chatMemoryProvider(ChatMemoryProvider)配合 @MemoryId 注解,按会话ID隔离记忆,适用于多用户场景

重要注意事项:同一 @MemoryId 不支持并发调用,会导致记忆数据错乱,框架无并发防护机制,需业务层控制

4.4 请求转换器配置

chatRequestTransformer 支持在请求发送至LLM前,动态修改完整 ChatRequest,适用场景:追加全局上下文、动态修改请求参数、统一消息预处理

  • 单参数转换器:仅感知请求对象
  • 双参数转换器:同时感知请求对象和会话记忆ID

4.5 内容审核配置

通过 moderationModel(ModerationModel) 配置审核模型,配合 @Moderate 注解实现方法级内容审核,检测到违规内容抛出 ModerationException

五、工具调用核心机制

AiServices 提供业界完善的工具调用能力,支持静态注册、动态工具、并发执行、异常容错、工具搜索优化

5.1 工具注册方式

  • 注解式静态工具:注册带 @Tool 注解的Java对象,自动解析工具方法
  • 编程式工具:手动绑定 ToolSpecification 和 ToolExecutor
  • 动态工具提供者:ToolProvider 每次请求动态返回工具列表,适配动态场景

5.2 工具执行优化

  • 并发执行:executeToolsConcurrently() 开启多工具并发执行,支持自定义线程池
  • 最大轮次限制:maxToolCallingRoundTrips() 限制LLM工具调用轮次,防止死循环(默认100轮)
  • 工具搜索策略:toolSearchStrategy 延迟加载工具,减少每次请求的Token消耗

5.3 工具异常容错机制

针对工具调用全流程异常提供自定义处理器,解决LLM幻觉、参数错误、执行失败问题

  • 幻觉工具处理:hallucinatedToolNameStrategy 处理LLM调用不存在工具的场景
  • 参数异常处理:toolArgumentsErrorHandler 处理参数解析失败、缺失参数等问题,支持LLM自动重试
  • 执行异常处理:toolExecutionErrorHandler 处理工具业务执行异常,支持脱敏错误信息,避免敏感数据泄露

5.4 工具补偿机制

compensateOnToolErrors(boolean) 开启工具故障补偿,工具执行失败时,反向执行已成功工具的补偿方法(@CompensateFor),实现事务回滚能力

5.5 工具执行回调

  • beforeToolExecution:工具执行前回调,可做参数校验、日志记录
  • afterToolExecution:工具执行后回调,可做结果统计、异常监控

六、RAG检索增强能力

AiServices 原生集成RAG能力,提供两种配置模式,互斥生效,满足简单/复杂检索场景

6.1 基础检索模式

contentRetriever(ContentRetriever):快速配置检索器,框架自动封装为 DefaultRetrievalAugmentor,适用于简单的向量检索场景

6.2 高阶检索模式

retrievalAugmentor(RetrievalAugmentor):自定义检索增强器,支持查询改写、路由、重排序、内容聚合、注入等全流程自定义,适配复杂企业级RAG场景

6.3 记忆存储优化

storeRetrievedContentInChatMemory(boolean) 配置是否将RAG检索后的增强消息存入会话记忆:

  • true(默认):存储增强后消息,上下文连贯,但记忆体积更大
  • false:仅存储原始用户消息,减小内存占用,避免检索内容冗余堆积

七、防护栅格机制(Guardrail)

框架提供输入/输出双层防护栅格,用于风险校验、prompt注入防御、输出合规校验,优先级低于注解配置

7.1 输入防护栅格

作用于用户原始输入,校验失败直接抛出 GuardrailException,不支持重试,适用于prompt注入、非法内容拦截

支持三种配置方式:批量类配置、实例配置、数组/列表批量注入

7.2 输出防护栅格

作用于LLM最终输出,支持重试、重prompt机制,校验失败可自动重试生成合规结果,适用于输出脱敏、格式校验、内容合规检测

多防护栅格叠加时,任意一个触发重试/重prompt,所有栅格将重新校验新响应结果

八、可观测与事件监听

支持AI服务全链路事件监听,实现日志统计、链路追踪、自定义监控能力

  • registerListener/registerListeners:注册单/多个事件监听器
  • unregisterListener/unregisterListeners:注销监听器,支持动态启停

可监听事件包含:服务调用开始/结束、工具执行、RAG检索、异常报错等全生命周期事件

九、工具类静态方法

9.1 消息过滤

removeToolMessages():过滤对话消息列表中的工具执行消息、工具调用请求消息,仅保留纯对话消息,用于上下文清理

9.2 审核结果校验

verifyModerationIfNeeded():异步审核结果校验,捕获审核异常,统一抛出内容违规异常,适配异步审核场景

十、核心约束与最佳实践

10.1 配置互斥约束

  • 普通模型与流式模型二选一,不可同时配置
  • 基础检索器(contentRetriever)与高阶检索增强器(retrievalAugmentor)二选一
  • 全局记忆与多用户记忆提供者二选一

10.2 并发约束

同一 @MemoryId 禁止并发调用,框架无并发锁控制,需业务层加锁或限流,避免记忆数据错乱

10.3 版本兼容说明

  • maxSequentialToolsInvocations 已废弃,推荐使用maxToolCallingRoundTrips
  • 旧版工具映射方法废弃,推荐使用 tools(List<AiServiceTool>) 支持返回行为配置
  • 2.0版本将调整工具异常默认处理策略,由「抛出异常」改为「返回错误信息允许LLM重试」

十一、核心能力落地示例

11.1 多用户记忆示例

// 定义带会话ID的AI接口
interface UserAssistant {
    String chat(@MemoryId Long userId, @UserMessage String message);
}

// 构建多用户记忆AI服务
UserAssistant assistant = AiServices.builder(UserAssistant.class)
        .chatModel(model)
        .chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(10))
        .build();

// 不同用户独立会话
assistant.chat(1L, "我叫张三");
assistant.chat(2L, "我叫李四");

11.2 工具调用异常容错示例

AiServices.builder(Assistant.class)
        .chatModel(model)
        .tools(new MathTools())
        // 工具参数异常返回友好提示,允许LLM重试
        .toolArgumentsErrorHandler((error, ctx) -> ToolErrorHandlerResult.text("参数格式错误,请重新输入"))
        // 执行异常脱敏,避免泄露内部信息
        .toolExecutionErrorHandler((error, ctx) -> ToolErrorHandlerResult.text("工具执行失败,请稍后重试"))
        .build();