SpringBoot-LangChain4j核心源码分析

跟踪一个aiService的调用流程

我们构建一个aiService的bean, 触发请求:

@CrossOrigin
@Slf4j
@RestController
class ChatController {
    @Resource
    private AssistantService assistantService;
    
    @GetMapping("/ai/assistant")
    public String assistant(@RequestParam(defaultValue = "What can you do for me?") String message) {
        String response = assistantService.chat(message);
        log.info("Received message: {}, generated response: {}", message, response);
        return response;
    }
}

定义AiService:

@AiService
public interface AssistantService {

    @SystemMessage(value = {
            "you are system assistant, you can help me to do some works in this system.",
            "if user want to generate table data, must input the table name and the number of rows."
    })
    String chat(String message);
}

这里debug调用chat方法的时候实际上debug会走到dev.langchain4j.service.DefaultAiServices#build中的匿名InvocationHandler.invoke()方法

核心业务逻辑就是根据根据构建的aiService信息调用大模型的api:

如果有对当前的aiService定义添加tool工具, 那么会tool工具调用对应的大模型function calling的api, 这样模型会知道何时才会去调用系统内部的工具类.

@AiService如何构建的

实际上就是 langchain4j-spring-boot-starter 的自动装配流程

自动装配

starter主要作用就是确保所有必要的库和配置都被自动包含,从而减少了手动添加依赖和配置的工作量。

主要对 RAG的配置和AiService的配置

启动自动装配: 类 dev.langchain4j.spring.LangChain4jAutoConfig

LangChain4jAutoConfig

@Import注解用于导入其他的配置类,使得这些配置类中的bean定义可以被当前配置类所在的Spring容器中识别和管理。在这里,它导入了AiServicesAutoConfigRagAutoConfig两个配置类,这意味着这两个配置类中的所有bean定义都将被包含在当前Spring容器中。

AiServicesAutoConfig的核心功能: 定义自定义Bean处理器

@Bean
BeanFactoryPostProcessor aiServicesRegisteringBeanFactoryPostProcessor() {
    return beanFactory -> {

        // all components available in the application context
        String[] chatLanguageModels = beanFactory.getBeanNamesForType(ChatLanguageModel.class);
        String[] streamingChatLanguageModels = beanFactory.getBeanNamesForType(StreamingChatLanguageModel.class);
        String[] chatMemories = beanFactory.getBeanNamesForType(ChatMemory.class);
        String[] chatMemoryProviders = beanFactory.getBeanNamesForType(ChatMemoryProvider.class);
        String[] contentRetrievers = beanFactory.getBeanNamesForType(ContentRetriever.class);
        String[] retrievalAugmentors = beanFactory.getBeanNamesForType(RetrievalAugmentor.class);

        Set<String> tools = new HashSet<>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            try {
                Class<?> beanClass = Class.forName(beanFactory.getBeanDefinition(beanName).getBeanClassName());
                for (Method beanMethod : beanClass.getDeclaredMethods()) {
                    if (beanMethod.isAnnotationPresent(Tool.class)) {
                        tools.add(beanName);
                    }
                }
            } catch (Exception e) {
                // TODO
            }
        }

        findAiServices(beanFactory).forEach(aiServiceClass -> {

            if (beanFactory.getBeanNamesForType(aiServiceClass).length > 0) {
                // User probably wants to configure AI Service bean manually
                // TODO or better fail because user should not annotate it with @AiService then?
                return;
            }

            GenericBeanDefinition aiServiceBeanDefinition = new GenericBeanDefinition();
            aiServiceBeanDefinition.setBeanClass(AiServiceFactory.class);
            aiServiceBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aiServiceClass);
            MutablePropertyValues propertyValues = aiServiceBeanDefinition.getPropertyValues();

            AiService aiServiceAnnotation = aiServiceClass.getAnnotation(AiService.class);

            addBeanReference(
                    ChatLanguageModel.class,
                    aiServiceAnnotation,
                    aiServiceAnnotation.chatModel(),
                    chatLanguageModels,
                    "chatModel",
                    "chatLanguageModel",
                    propertyValues
            );

            addBeanReference(
                    StreamingChatLanguageModel.class,
                    aiServiceAnnotation,
                    aiServiceAnnotation.streamingChatModel(),
                    streamingChatLanguageModels,
                    "streamingChatModel",
                    "streamingChatLanguageModel",
                    propertyValues
            );

            addBeanReference(
                    ChatMemory.class,
                    aiServiceAnnotation,
                    aiServiceAnnotation.chatMemory(),
                    chatMemories,
                    "chatMemory",
                    "chatMemory",
                    propertyValues
            );

            addBeanReference(
                    ChatMemoryProvider.class,
                    aiServiceAnnotation,
                    aiServiceAnnotation.chatMemoryProvider(),
                    chatMemoryProviders,
                    "chatMemoryProvider",
                    "chatMemoryProvider",
                    propertyValues
            );

            addBeanReference(
                    ContentRetriever.class,
                    aiServiceAnnotation,
                    aiServiceAnnotation.contentRetriever(),
                    contentRetrievers,
                    "contentRetriever",
                    "contentRetriever",
                    propertyValues
            );

            addBeanReference(
                    RetrievalAugmentor.class,
                    aiServiceAnnotation,
                    aiServiceAnnotation.retrievalAugmentor(),
                    retrievalAugmentors,
                    "retrievalAugmentor",
                    "retrievalAugmentor",
                    propertyValues
            );

            if (aiServiceAnnotation.wiringMode() == EXPLICIT) {
                propertyValues.add("tools", toManagedList(asList(aiServiceAnnotation.tools())));
            } else if (aiServiceAnnotation.wiringMode() == AUTOMATIC) {
                propertyValues.add("tools", toManagedList(tools));
            } else {
                throw illegalArgument("Unknown wiring mode: " + aiServiceAnnotation.wiringMode());
            }

            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            registry.registerBeanDefinition(lowercaseFirstLetter(aiServiceClass.getSimpleName()), aiServiceBeanDefinition);
        });
    };
}

核心功能

  • 这个 BeanFactoryPostProcessor 在 Spring 容器启动时执行,用于修改或注册新的 Bean 定义。
  • 它的主要任务是查找具有 @AiService 注解的类,并为这些类动态创建和注册 AiServiceFactory 的 Bean

AiServiceFactory

构建对应的实例:

实际调用的dev.langchain4j.service.DefaultAiServices#build, 根据实际的配置信息构建对应的aiService的bean对象

AiServicesAutoConfig执行流程

  1. 获取特定类型的 Bean 名称:
  • 查找所有ChatLanguageModelStreamingChatLanguageModelChatMemoryChatMemoryProviderContentRetrieverRetrievalAugmentor类型的 Bean 名称。
  1. 查找带有 @Tool 注解的 Bean:
  • 遍历所有 Bean 定义,查找带有@Tool注解的 Bean,并将它们的名称添加到tools集合中。
  1. 处理 @AiService 注解的类:
  • 查找所有带有@AiService注解的类。
  • 对于每个这样的类,如果它已经被定义为 Bean(即用户可能已经手动配置了它),则不执行任何操作(或可以考虑抛出异常)。
  • 否则,创建一个新的GenericBeanDefinition,其类为AiServiceFactory,并为其构造函数提供一个参数(即当前的@AiService注解的类)。
  • 接着,根据@AiService注解中的属性,查找和添加与这些属性相对应的 Bean 引用到AiServiceFactoryBean 的属性中。
  • 根据@AiService注解的wiringMode属性,将相应的工具(从@Tool注解中获取的或手动指定的)添加到AiServiceFactoryBean 的属性中。
  • 最后,将新创建的GenericBeanDefinition注册到 Spring 的BeanDefinitionRegistry中。
  • 总结: 就是根据@AiService去构建一个可以由AiServiceFactory创建的BeanDefinition。

@Tool工具的执行流程

测试用例: langchain4j-examples/spring-boot-example at 4d0bc3139d892afbdffaafe51200bb91f37af1cd · langchain4j/langchain4j-examples · GitHub

核心调用方法

dev.langchain4j.model.openai.OpenAiChatModel#generate(java.util.List<dev.langchain4j.data.message.ChatMessage>, java.util.List<dev.langchain4j.agent.tool.ToolSpecification>, dev.langchain4j.agent.tool.ToolSpecification)

构建请求

设置前置message:

  • UserMessage: 用户提问的Message

  • SystemMessage: 限定当前模型功能的Message

设置工具: 在前面讲到过, 在构建aiService的时候会将@Tool注释的工具封装成ToolSpecification工具添加到 AiServiceContext的属性toolSpecifications中, 当工具如果不为空就会告诉大模型可以用工具有哪些; 最后根据大模型的反馈调用具体的工具类.

封装请求:

dev.langchain4j.model.openai.OpenAiChatModel#generate(java.util.List<dev.langchain4j.data.message.ChatMessage>, java.util.List<dev.langchain4j.agent.tool.ToolSpecification>, dev.langchain4j.agent.tool.ToolSpecification)

实现AI-Agent的核心原理

处理返回的response: 这时候知道需要调用的工具有哪些

循环调用ai模型, 根据模型反馈需要调用的工具链并将结果告诉模型, 最后根据模型的response返回最终的执行结果给用户

核心源码调用流程:

  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值