LangChain4j学习(三)
前提:使用LangChain4j + SpringBoot + DashScope通义千问
JavaAI:LangChain4j学习(一) 集成SpringBoot和阿里通义千问DashScope
JavaAI:LangChain4j学习(二)聊天,记忆存储,流式输出
概念
低级组件(例如Chat、LanguageModel、ChatMessage、ChatMemory等)工作非常灵活,能赋予完全的自由度,但同时也迫使使用者编写大量样板代码。由于基于大型语言模型(LLM)的应用程序通常需要多个组件协同工作(例如提示模板、聊天记忆、LLM、输出解析器、检索增强生成(RAG)组件:嵌入模型和存储库),并且往往涉及多次交互,因此协调所有这些组件会变得愈发繁琐。
LangChain4j开发者希望能专注于业务逻辑,而非低层级的实现细节。因此,LangChain4j 目前提供了两个高级概念来帮助实现这一目标:AI 服务和链式流程。
Chains (legacy)
链(Chains)的概念源于 Python 的 LangChain(引入 LCEL 之前)。其核心思想是为每个常见用例(如聊天机器人、检索增强生成(RAG)等)提供一个链式流程。链式流程将多个底层组件组合起来,并协调它们之间的交互。但它们的主要问题是,如果需要进行定制,会显得过于僵化。
LangChain4j 目前仅实现了两种链式流程(ChainConversational
和 ChainConversationalRetrievalChain
),且暂时不计划添加更多。
AI Services
LangChain4j开发者们提出了另一个专为Java定制的解决方案,称为AI Services。其核心思想是通过简单的API,将与大语言模型(LLM)及其他组件交互的复杂性隐藏起来。
这种方法与Spring Data JPA或Retrofit非常相似:只需声明式地定义包含所需API的接口,LangChain4j便会提供一个实现该接口的代理对象。可以将AI Service视为应用程序服务层中的一个组件,它提供AI服务。
AI Services处理最常见的操作:
- 为LLM格式化输入
- 解析LLM的输出
它们还支持更多高级功能:
- 聊天记忆(Chat memory)
- 工具调用(Tools)
- 检索增强生成(RAG, Retrieval Augmented Generation)
AI Services可用于构建支持多轮交互的有状态聊天机器人,也可用于自动化流程,其中每次调用LLM都是独立的。
举例
1.定义一个具有单个方法 的接口,该方法将 a 作为输入并返回一个 .chatStringString
interface Assistant {
String chat(String userMessage);
}
2.创建低级组件ChatLanguageModel,组件将在 AI Service 的后台使用
ChatLanguageModel model = QwenChatModel.builder()
.apiKey("xxxxxxxxxxxxxxxxxxx")
.modelName("qwen-plus")
.build();
3.使用该类来创建 AI Service 的实例
Assistant assistant = AiServices.create(Assistant.class, model);
4.更简洁的方式
在 Quarkus 和 Spring Boot 应用程序中, autoconfiguration 处理创建 bean。所以可以通过注入/自动装配到任何需要的地方。 AiServices.create(…)创建一次后,任何地方使用assistant
直接使用 assistant
String answer = assistant.chat("Hello");
System.out.println(answer);
当应用程序启动时,LangChain4j starter 将扫描 Classpath 并找到所有带有@AiService的接口,并创建接口的实现,同时扫描应用程序上下文中可用的所有 LangChain4j 组件 并 注册为 bean, 因此,您可以在需要的地方自动连接它:
@Autowired
Assistant assistant;
AI Service 连接 SpringBoot
如果应用程序上下文中存在以下组件,它们将自动装配到AI Service中:
- ChatLanguageModel
- StreamingChatLanguageModel
- ChatMemory
- ChatMemoryProvider
- ContentRetriever
- RetrievalAugmentor
使用@Component、@Service、@Tool等注解的类的方法会被自动检测和处理
@Component
public class BookingTools {
private final BookingService bookingService;
public BookingTools(BookingService bookingService) {
this.bookingService = bookingService;
}
@Tool
public Booking getBookingDetails(String bookingNumber, String customerName, String customerSurname) {
return bookingService.getBookingDetails(bookingNumber, customerName, customerSurname);
}
@Tool
public void cancelBooking(String bookingNumber, String customerName, String customerSurname) {
bookingService.cancelBooking(bookingNumber, customerName, customerSurname);
}
}
注意: 如果应用程序上下文中存在多个相同类型的组件,则应用程序将无法启动。 在这种情况下,请使用 explicit wiring 模式(如下所述)。
Explicit Component Wiring 显示组件
如果有多个 AI 服务,每个服务连接不同的 LangChain4j 组件,必须显式指定所有组件, 设置wiringMode = EXPLICIT,且指定使用的组件名。@AiService(wiringMode = EXPLICIT)
举例
假设我们配置了两个 :ChatLanguageModel
# OpenAI
langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.chat-model.model-name=gpt-4o-mini
# Ollama
langchain4j.ollama.chat-model.base-url=http://localhost:11434
langchain4j.ollama.chat-model.model-name=llama3.1
@AiService(wiringMode = EXPLICIT, chatModel = "openAiChatModel")
interface OpenAiAssistant {
@SystemMessage("你是一名aaaaaaaaaa的AI助手")
String chat(String userMessage);
}
@AiService(wiringMode = EXPLICIT, chatModel = "ollamaChatModel")
interface OllamaAssistant {
@SystemMessage("你是一名bbbbbbbb的AI助手")
String chat(String userMessage);
}
监听AI Service注册
侦听 AI 服务注册事件
可以通过实现AiServiceRegisteredEvent接口来监听 AI Service 声明的Bean 。 AI Service 在 Spring 中注册时将触发此事件,显示运行时获取有关所有已注册的 AI 服务及其工具的信息。 下面是一个示例:
ApplicationListener< AiServiceRegisteredEvent >
@Component
class AiServiceRegisteredEventListener implements ApplicationListener<AiServiceRegisteredEvent> {
@Override
public void onApplicationEvent(AiServiceRegisteredEvent event) {
Class<?> aiServiceClass = event.aiServiceClass();
List<ToolSpecification> toolSpecifications = event.toolSpecifications();
for (int i = 0; i < toolSpecifications.size(); i++) {
System.out.printf("[%s]: [Tool-%s]: %s%n", aiServiceClass.getSimpleName(), i + 1, toolSpecifications.get(i));
}
}
}
Flux
流式传输使用
@AiService
interface Assistant {
@SystemMessage("You are a polite assistant")
Flux<String> chat(String userMessage);
}
可观察性
要启用可观察性,需要声明一个或多个 ChatLanguageModelStreaming,ChatLanguageModel,ChatModelListener,每个 bean 都将自动 注入到 Spring Boot 中
@Configuration
class MyConfiguration {
@Bean
ChatModelListener chatModelListener() {
return new ChatModelListener() {
private static final Logger log = LoggerFactory.getLogger(ChatModelListener.class);
@Override
public void onRequest(ChatModelRequestContext requestContext) {
log.info("onRequest(): {}", requestContext.chatRequest());
}
@Override
public void onResponse(ChatModelResponseContext responseContext) {
log.info("onResponse(): {}", responseContext.chatResponse());
}
@Override
public void onError(ChatModelErrorContext errorContext) {
log.info("onError(): {}", errorContext.error().getMessage());
}
};
}
}
Tools (Function Calling)
工具使用
LangChain4j 将自动执行,add(1, 2),multiply(3, 4)
class Tools {
@Tool
int add(int a, int b) {
return a + b;
}
@Tool
int multiply(int a, int b) {
return a * b;
}
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.tools(new Tools())
.build();
String answer = assistant.chat(" 1+2 和 3*4 的 结果是?");
RAG
AI Service可以通过配置内容检索器(ContentRetriever)来启用简单的检索增强生成(naive RAG)功能。
EmbeddingStore embeddingStore = ...
EmbeddingModel embeddingModel = ...
ContentRetriever contentRetriever = new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.contentRetriever(contentRetriever)
.build();
配置检索增强器(RetrievalAugmentor)能够提供更大的灵活性,启用高级的检索增强生成(RAG)功能,例如查询转换、重新排序等。
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(...)
.queryRouter(...)
.contentAggregator(...)
.contentInjector(...)
.executor(...)
.build();
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.retrievalAugmentor(retrievalAugmentor)
.build();
举例
公司构建一个聊天机器人, 如果用户向聊天机器人打招呼, 我希望它使用预定义的问候语进行响应,而不依赖 LLM 生成问候语。 如果用户提出问题,我希望 LLM 使用公司的内部知识库RAG生成响应。
将此任务分解为 2 个单独的 AI Service
interface GreetingExpert {
@UserMessage("以下文本是问候语吗? Text: {{it}}")
boolean isGreeting(String text);
}
interface ChatBot {
@SystemMessage("你是公司的AI智能机器人,请根据xxxxxxx进行回复")
String reply(String userMessage);
}
class MilesOfSmiles {
private final GreetingExpert greetingExpert;
private final ChatBot chatBot;
...
public String handle(String userMessage) {
if (greetingExpert.isGreeting(userMessage)) {
return "欢迎访问,我是AI机器人,有什么需要帮助的吗?";
} else {
return chatBot.reply(userMessage);
}
}
}
GreetingExpert greetingExpert = AiServices.create(GreetingExpert.class, llama2);
ChatBot chatBot = AiServices.builder(ChatBot.class)
.chatLanguageModel(gpt4)
.contentRetriever(milesOfSmilesContentRetriever)
.build();
MilesOfSmiles milesOfSmiles = new MilesOfSmiles(greetingExpert, chatBot);
String greeting = milesOfSmiles.handle("你好");
System.out.println(greeting);
String answer = milesOfSmiles.handle("你提供什么服务?");
System.out.println(answer);
注意:使用便宜的 Llama2 完成识别文本是否为问候语的任务, 使用昂贵的 GPT-4 + 内容检索器 (RAG)完成更复杂的任务。
未来可能可以分别评估并为每个子任务找到最佳参数, 或者从长远看,甚至可以为每个特定的 子任务微调小型的专用模型。