这是我第一次接触ai的项目,所以可能会有很多理解错误的点,大部分只是自己对ai的认识,所以如果有错误的地方,可以直接指出。

根据我对xfg这个项目的学习,首先是先通过API功能测试,使用 Spring AILangChain4JGoogle ADK 框架对接 AI 服务,完成功能验证。先感受一下不同框架下的ai测试效果。

这里分了不同的包,进行不同场景下的测试。

agent包中,我们进行了Agent智能体编排,主要进行agent流程,执行逻辑的测试。

model包中,我们使用AI模型底层API层,通过调用大模型接口,是SDK原生能力的测试。

tool包下的mcp包中,进行能力的扩展,是对Agent调用工具,函数调用能力的测试。

感受完在不同条件下的ai测试后,我们正式开始搭建一个可以动态配置的Agent智能体。

我们定义使用工程 YML 文件方式,配置的通用的智能体配置表。允许用户在使用脚手架创建完成智能体后工程后,通过 YML 配置出自己需要的智能体,在结合业务场景做对应的衔接开发。

AiAgentAutoConfig类来加载YML配置。

public class AiAgentConfigTableVO {
    /**
     * 应用名称
     */
    private String appName;
    /**
     * 智能体配置
     */
    private Agent agent;
    /**
     * 智能体模块
     */
    private Module module;

    @Data
    public static class Agent {
        /**
         * 智能体ID
         */
        private String agentId;
        /**
         * 智能体名称
         */
        private String agentName;
        /**
         * 智能体描述
         */
        private String agentDesc;
    }

    @Data
    public static class Module {
        private AiApi aiApi;
        private ChatModel chatModel;
        private List<Agent> agents;
        private List<AgentWorkflow> agentWorkflows;
        private Runner runner;
        @Data
        public static class AiApi {
            private String baseUrl;
            private String apiKey;
            private String completionsPath = "/v1/chat/completions";
            private String embeddingsPath = "/v1/embeddings";
        }
        @Data
        public static class ChatModel {
            private String model;
            private List<ToolMcp> toolMcpList;
            private List<ToolSkills> toolSkillsList;
            @Data
            public static class ToolMcp {
                private SSEServerParameters sse;
                private StdioServerParameters stdio;
                private LocalParameters local;
                @Data
                public static class SSEServerParameters {
                    private String name;
                    private String baseUri;
                    private String sseEndpoint;
                    private Integer requestTimeout = 3000;

                }

                @Data
                public static class StdioServerParameters {
                    private String name;
                    private Integer requestTimeout = 3000;
                    private ServerParameters serverParameters;

                    @Data
                    public static class ServerParameters {
                        private String command;
                        private List<String> args;
                        private Map<String, String> env;

                    }
                }
                @Data
                public static class LocalParameters{
                    private String name;
                }
            }
            @Data
            public static class ToolSkills{
                /**
                 * 类型;directory(用户配置的,映射进来的)、resource(放到工程下的)
                 */
                private String type="directory";
                /**
                 * 路径;
                 */
                private String path;
            }
        }
        @Data
        public static class Agent {
            private String name;
            private String instruction;
            private String description;
            private String outputKey;
        }
        @Data
        public static class AgentWorkflow{
            /**
             * 类型;loop、parallel、sequential
             */
            private String type;
            private String name;
            private List<String> subAgents;
            private String description;
            private Integer maxIterations = 3;
        }
        @Data
        public static class Runner{
            private String agentName;
            private List<String> pluginNameList;
        }
    }
}

这两个类为配置表对象类,AiAgentConfigTableVO用来接收,存储,传递你在配置文件里所写的所有AI智能体配置。AiAgentAutoConfigProperties把 application.yml 配置文件里 ai.agent.config 开头的所有配置,自动映射成 Java 对象,是整个 AI Agent 自动装配体系的配置入口顶层类

我们来梳理一下流程:

1. Spring Boot 启动初始化阶段

  1. 扫描并加载配置类

    • AiAgentAutoConfig@Configuration 标记,Spring 会在启动时扫描到它。
    • 同时 @EnableConfigurationProperties(AiAgentAutoConfigProperties.class) 生效,告诉 Spring:把 AiAgentAutoConfigProperties 当成一个配置属性类,进行绑定。
  2. 绑定配置属性

    • Spring 会读取 application.yml/application.properties 中所有 ai.agent.config.* 开头的配置。
    • 把配置值绑定到 AiAgentAutoConfigProperties 的字段上:
      • ai.agent.config.enabledprivate boolean enabled(默认值 false)
      • ai.agent.config.tablesprivate Map<String, AiAgentConfigTableVO> tables
    • 绑定完成后,AiAgentAutoConfigProperties 会被注册为一个 Bean。
  3. 依赖注入

    • Spring 会把 AiAgentAutoConfigPropertiesIArmoryService 这两个 Bean,注入到 AiAgentAutoConfig 中被 @Resource 标记的字段里。
    • 此时 AiAgentAutoConfig 实例已经创建完成,但它的业务方法还没执行。

2. 应用就绪事件触发阶段(ApplicationReadyEvent

Spring Boot 启动完成后,会发布 ApplicationReadyEvent 事件。因为 AiAgentAutoConfig 实现了 ApplicationListener<ApplicationReadyEvent>,所以它的 onApplicationEvent 方法会被触发。

事件触发后,开始装配agent节点,在装配agent所有节点中,通过引用xfg的扳手工程中的规则树(组合模式)来进行流转实现。

之后,定义了单一职责的 IArmoryService 装配服务接口,并通过工厂管理节点衔接服务,以及定义上下文对象。这个上下文对象,会在各个节点间记录数据并流转使用。

public void acceptArmoryAgents(List<AiAgentConfigTableVO> tables) throws Exception {
        for (AiAgentConfigTableVO table : tables) {
            StrategyHandler<ArmoryCommandEntity, DefaultArmoryFactory.DynamicContext, AiAgentRegisterVO> handler = defaultArmoryFactory.armoryStrategyHandler();
            handler.apply(ArmoryCommandEntity.builder()
                    .aiAgentConfigTableVO(table).build(),
                    new DefaultArmoryFactory.DynamicContext());
        }
        
    }

接下来,我们介绍一下xfg扳手工程项目中的一个通用组件--规则树。

我们在做产品项目时,肯定会遇到某些场景,需要经过很多流程,这些流程牵扯着不同的业务属性,如:授信,账户,风控,配置,营销,推荐,之后又要到,不同的产品线上,如A产品功能,B产品功能。

那么,规则树的编排就很适合这样的场景使用。它的设计可以像组装乐高积木,改一个大楼一样,有非常多的延展,来满足我们的场景诉求。

  • 左侧,是复杂的业务流程节点编排,可以通过【路由】,拿到 get 方法中的节点判断和处理,走到下一个节点。与责任链相比,单链路执行适合固定的流程,如一些规则过滤。而规则树则适合编排复杂业务流程。

  • 右侧,是流程过滤提供的方法,包括了;多线程异步数据加载、业务受理、get负责路由执行时做的一些判断。而受理前、受理后、受理后异常,则在流程中进行穿插处理。

    1.StrategyHandler 策略处理接口

    StrategyHandler 是一个泛型接口,定义了策略处理的核心方法。它包含一个默认实现,当没有匹配的策略时使用默认处理。该接口的泛型参数包括请求参数类型(T)、动态上下文类型(D)和返回结果类型(R)。

    /**
     * @description 受理策略处理
     * T 入参类型
     * D 上下文参数
     * R 返参类型
     */
    public interface StrategyHandler<T, D, R> {
     
        StrategyHandler DEFAULT = (T, D) -> null;
     
        R apply(T requestParameter, D dynamicContext) throws Exception;
     
    }

    2.StrategyMapper 策略映射器

    StrategyMapper 是一个泛型接口,用于根据请求参数和动态上下文获取待执行的策略处理器。它定义了一个方法,根据传入的参数返回对应的策略处理器。

    /**
     * @description 策略映射器
     * T 入参类型
     * D 上下文参数
     * R 返参类型
     */
    public interface StrategyMapper<T, D extends DynamicContext, R> {
    
        /**
         * 获取待执行策略
         *
         * @param requestParameter 入参
         * @param dynamicContext   上下文
         * @return 返参
         * @throws Exception 异常
         */
        StrategyHandler<T, D, R> get(T requestParameter, D dynamicContext) throws Exception;
    
    }

    3.AbstractStrategyRouter 策略路由抽象类

    AbstractStrategyRouter 是一个抽象类,结合了策略处理器和策略映射器的功能,提供了灵活的策略路由机制。它实现了 StrategyMapper 和 StrategyHandler 接口,兼具映射和执行能力。该类还提供了一个默认策略处理器,当没有匹配的策略时使用默认处理。

    /**
     * @author Fuzhengwei bugstack.cn @小傅哥
     * @description 策略路由抽象类
     * @create 2024-12-14 13:25
     */
    public abstract class AbstractStrategyRouter<T, D extends DynamicContext, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {
    
        @Getter
        @Setter
        protected StrategyHandler<T, D, R> defaultStrategyHandler = StrategyHandler.DEFAULT;
    
        public R router(T requestParameter, D dynamicContext) throws Exception {
            StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);
            if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);
            return defaultStrategyHandler.apply(requestParameter, dynamicContext);
        }
    
    }

    4 AbstractMultiThreadStrategyRouter 异步资源加载策略抽象类
    AbstractMultiThreadStrategyRouter 扩展了基础策略路由,支持多线程异步数据加载和业务处理分离。它提供了异步数据加载框架,在执行业务逻辑前预先加载所需资源,从而提高系统性能和响应速度。

    /**
     * @author Fuzhengwei bugstack.cn @小傅哥
     * @description 异步资源加载策略
     * @create 2024-12-21 08:48
     */
    public abstract class AbstractMultiThreadStrategyRouter<T, D extends DynamicContext, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {
    
        @Getter
        @Setter
        protected StrategyHandler<T, D, R> defaultStrategyHandler = StrategyHandler.DEFAULT;
    
        public R router(T requestParameter, D dynamicContext) throws Exception {
            StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);
            if (null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);
            return defaultStrategyHandler.apply(requestParameter, dynamicContext);
        }
    
        @Override
        public R apply(T requestParameter, D dynamicContext) throws Exception {
            try {
                // 前置处理
                R applyBefore = applyBefore(requestParameter, dynamicContext);
                if (null != applyBefore) return applyBefore;
    
                // 异步加载数据
                multiThread(requestParameter, dynamicContext);
    
                // 业务流程受理
                R apply = doApply(requestParameter, dynamicContext);
    
                // 后置处理
                applyAfter(requestParameter, dynamicContext, apply);
    
                return apply;
            } catch (Exception e) {
                // 后置处理(异常)
                applyAfterException(requestParameter, dynamicContext, e);
                throw e;
            }
    
        }
    
        /**
         * 异步加载数据
         */
        protected abstract void multiThread(T requestParameter, D dynamicContext) throws ExecutionException, InterruptedException, TimeoutException;
    
        /**
         * 业务流程受理
         */
        protected abstract R doApply(T requestParameter, D dynamicContext) throws Exception;
    
        protected R applyBefore(T requestParameter, D dynamicContext) {
            return proceed(requestParameter, dynamicContext);
        }
    
        protected void applyAfter(T requestParameter, D dynamicContext, R r) {
    
        }
    
        protected void applyAfterException(T requestParameter, D dynamicContext, Exception e) {
    
        }
    
    }

    在项目中我们使用在策略路由抽象类上进行优化后的AbstractMultiThreadStrategyRouter。

    xfg的规则树模型设计的核心组件就是这些,我们只需要在项目中再加入一个默认工厂类(上文已提及),用它来负责创建和管理策略处理器,提供策略模式的统一入口。它维护策略执行的上下文信息,支持数据在不同策略节点间传递。

    public class DefaultArmoryFactory {
        @Resource
        private RootNode rootNode;
        @Resource
        private ApplicationContext applicationContext;
        public StrategyHandler<ArmoryCommandEntity,DynamicContext, AiAgentRegisterVO> armoryStrategyHandler(){
            return rootNode;
        }
        public AiAgentRegisterVO getAiAgentRegisterVO(String agentId) {
            return applicationContext.getBean(agentId, AiAgentRegisterVO.class);
        }
    
        /**
         * 定义一个上下文对象,用于各个节点串联的时候,写入数据和使用数据
         */
        @Data
        @Builder
        @AllArgsConstructor
        @NoArgsConstructor
        public static class DynamicContext {
            /**
             * LLM API
             */
            private OpenAiApi openAiApi;
            /**
             * LLM ChatModel
             */
            private ChatModel chatModel;
            /**
             * 原子安全的递进步骤
             */
            private Integer currentStepIndex=0;
            /**
             * 当前的智能体
             */
            private AiAgentConfigTableVO.Module.AgentWorkflow currentAgentWorkflow;
            /**
             * 智能体配置组
             */
            private Map<String, BaseAgent> agentGroup=new HashMap<>();
    
            private Map<String, Object> dataObjects = new HashMap<>();
    
            public <T> void setValue(String key, T value) {
                dataObjects.put(key, value);
            }
    
            public <T> T getValue(String key) {
                return (T) dataObjects.get(key);
            }
            public List<BaseAgent> queryAgentList(List<String> agentNames){
                if(agentNames == null || agentNames.isEmpty() || agentGroup == null){
                    return Collections.emptyList();
                }
                List<BaseAgent> agents=new ArrayList<>();
                for (String agentName : agentNames) {
                    BaseAgent agent = agentGroup.get(agentName);
                    if(agent != null){
                        agents.add(agent);
                    }
                }
                return agents;
            }
        }
    }

    然后在提供一个规则树抽象支持类,它继承了AbstractMultiThreadStrategyRouter,为具体业务实现提供了支持。

    public abstract class AbstractArmorySupport extends AbstractMultiThreadStrategyRouter<ArmoryCommandEntity, DefaultArmoryFactory.DynamicContext, AiAgentRegisterVO> {
        @Resource
        protected ApplicationContext applicationContext;
        @Override
        protected void multiThread(ArmoryCommandEntity requestParameter, DefaultArmoryFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {
    
        }
        /**
         * 通用的Bean注册方法
         *
         * @param beanName  Bean名称
         * @param beanClass Bean类型
         * @param <T>       Bean类型
         */
        protected synchronized <T> void registerBean(String beanName, Class<T> beanClass, T beanInstance) {
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
    
            // 注册Bean
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass, () -> beanInstance);
            BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
            beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
    
            // 如果Bean已存在,先移除
            if (beanFactory.containsBeanDefinition(beanName)) {
                beanFactory.removeBeanDefinition(beanName);
            }
    
            // 注册新的Bean
            beanFactory.registerBeanDefinition(beanName, beanDefinition);
    
            log.info("成功注册Bean: {}", beanName);
        }
    
        protected <T> T getBean(String beanName){
            return (T) applicationContext.getBean(beanName);
        }
    
    }

    通过规则树模型的设计,为之后的各个节点装配,流转,节点之间的通信和Bean对象的注册,获取Bean对象等等,提供了极大的便利。

    Logo

    AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

    更多推荐