JavaAI:LangChain4j学习(三)AI Service及与SpringBoot结合使用

LangChain4j学习(三)

  • 概念
    • Chains (legacy)
    • AI Services
    • 举例
  • AI Service 连接 SpringBoot
    • Explicit Component Wiring 显示组件
      • 举例
    • 监听AI Service注册
    • Flux
    • 可观察性
    • Tools (Function Calling)
    • RAG
      • 举例

前提:使用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 目前仅实现了两种链式流程(ChainConversationalChainConversationalRetrievalChain),且暂时不计划添加更多。

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)完成更复杂的任务。

未来可能可以分别评估并为每个子任务找到最佳参数, 或者从长远看,甚至可以为每个特定的 子任务微调小型的专用模型。

你可能感兴趣的:(Java,学习,人工智能,spring,boot,java,后端,ai,langchain)