跟踪一个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容器中识别和管理。在这里,它导入了AiServicesAutoConfig
和RagAutoConfig
两个配置类,这意味着这两个配置类中的所有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执行流程
- 获取特定类型的 Bean 名称:
- 查找所有
ChatLanguageModel
、StreamingChatLanguageModel
、ChatMemory
、ChatMemoryProvider
、ContentRetriever
和RetrievalAugmentor
类型的 Bean 名称。
- 查找带有 @Tool 注解的 Bean:
- 遍历所有 Bean 定义,查找带有
@Tool
注解的 Bean,并将它们的名称添加到tools
集合中。
- 处理 @AiService 注解的类:
- 查找所有带有
@AiService
注解的类。 - 对于每个这样的类,如果它已经被定义为 Bean(即用户可能已经手动配置了它),则不执行任何操作(或可以考虑抛出异常)。
- 否则,创建一个新的
GenericBeanDefinition
,其类为AiServiceFactory
,并为其构造函数提供一个参数(即当前的@AiService
注解的类)。 - 接着,根据
@AiService
注解中的属性,查找和添加与这些属性相对应的 Bean 引用到AiServiceFactory
Bean 的属性中。 - 根据
@AiService
注解的wiringMode
属性,将相应的工具(从@Tool
注解中获取的或手动指定的)添加到AiServiceFactory
Bean 的属性中。 - 最后,将新创建的
GenericBeanDefinition
注册到 Spring 的BeanDefinitionRegistry
中。 - 总结: 就是根据
@AiService
去构建一个可以由AiServiceFactory
创建的BeanDefinition。
@Tool工具的执行流程
核心调用方法
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返回最终的执行结果给用户
核心源码调用流程: