DeepSeek RAG&MCP + Agent智能体项目 —— 动态决策策略的接口对接

一、前言

至此,对于动态决策的基本逻辑我们都写完了,接下来就是完善和写对接前端的接口了。

这里我们要加上流式响应,所以稍微还要修改一下前面的代码。

二、自动装配

首先,我们前面的装配是要自己写代码实现的,这个很麻烦,因为我们知道,既然都要去使用Agent了,那我肯定要装配一个客户端啊,所以这个应该是一个标准的前置动作,即在使用Agent前必须要做的,因此我们将这个写到配置类中,启动服务时自动装配,当然,也不能完全写死,我们允许在配置文件中修改对应的配置(包括但不限于是否自动装配装配客户端的ids)。

java 复制代码
@Data
@ConfigurationProperties(prefix = "spring.ai.agent.auto-config")
public class AiAgentAutoConfigProperties {

    /**
     * 是否启用AI Agent自动装配
     */
    private boolean enabled = false;

    /**
     * 需要自动装配的客户端ID列表
     */
    private List<String> clientIds;

}
java 复制代码
@Slf4j
@Configuration
@EnableConfigurationProperties(AiAgentAutoConfigProperties.class)
@ConditionalOnProperty(prefix = "spring.ai.agent.auto-config", name = "enabled", havingValue = "true")
public class AiAgentAutoConfiguration implements ApplicationListener<ApplicationReadyEvent> {
    @Resource
    private AiAgentAutoConfigProperties aiAgentAutoConfigProperties;

    @Resource
    private DefaultArmoryStrategyFactory defaultArmoryStrategyFactory;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        try {
            log.info("AI Agent 自动装配开始,配置: {}", aiAgentAutoConfigProperties);

            // 检查配置是否有效
            if (!aiAgentAutoConfigProperties.isEnabled()) {
                log.info("AI Agent 自动装配未启用");
                return;
            }

            List<String> clientIds = aiAgentAutoConfigProperties.getClientIds();
            if (CollectionUtils.isEmpty(clientIds)) {
                log.warn("AI Agent 自动装配配置的客户端ID列表为空");
                return;
            }

            // 解析客户端ID列表(支持逗号分隔的字符串)
            List<String> commandIdList;
            if (clientIds.size() == 1 && clientIds.get(0).contains(Constants.SPLIT)) {
                // 处理逗号分隔的字符串
                commandIdList = Arrays.stream(clientIds.get(0).split(Constants.SPLIT))
                        .map(String::trim)
                        .filter(id -> !id.isEmpty())
                        .collect(Collectors.toList());
            } else {
                commandIdList = clientIds;
            }

            log.info("开始自动装配AI客户端,客户端ID列表: {}", commandIdList);

            // 执行自动装配
            StrategyHandler<ArmoryCommandEntity, DefaultArmoryStrategyFactory.DynamicContext, String> armoryStrategyHandler =
                    defaultArmoryStrategyFactory.armoryStrategyHandler();

            String result = armoryStrategyHandler.apply(
                    ArmoryCommandEntity.builder()
                            .commandType(AiAgentEnumVO.AI_CLIENT.getCode())
                            .commandIdList(commandIdList)
                            .build(),
                    new DefaultArmoryStrategyFactory.DynamicContext());

            log.info("AI Agent 自动装配完成,结果: {}", result);

        } catch (Exception e) {
            log.error("AI Agent 自动装配失败", e);
        }
    }
}
bash 复制代码
  ai:
    openai:
      base-url: https://apis.itedus.cn
      api-key: sk-xxxxxxxxxxx
    agent:
      auto-config:
        enabled: true
        client-ids: 3101,3102,3103,3104

三、接口对接

1.服务层

请求体:

java 复制代码
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AutoAgentRequestDTO {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * Ai智能提ID
     */
    private String aiAgentId;
    /**
     * 用户消息
     */
    private String message;
    /**
     * 会话ID
     */
    private String sessionId;
    /**
     * 最大执行步数
     */
    private Integer maxStep;

}

服务接口:在这里去选择策略

java 复制代码
public interface IAiAgentService {

    ResponseBodyEmitter autoAgent(AutoAgentRequestDTO requestDTO, HttpServletResponse response);

}

Controller:

java 复制代码
@Slf4j
@RestController
@RequestMapping("/api/v1/agent")
@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
public class AiAgentController implements IAiAgentService {

    @Resource(name = "autoAgentExecuteStrategy")
    private IExecuteStrategy autoAgentExecuteStrategy;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @RequestMapping(value = "auto_agent", method = RequestMethod.POST)
    public ResponseBodyEmitter autoAgent(@RequestBody AutoAgentRequestDTO request, HttpServletResponse response) {
        log.info("AutoAgent流式执行请求开始,请求信息:{}", JSON.toJSONString(request));

        try {
            // 设置SSE响应头
            response.setContentType("text/event-stream");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Connection", "keep-alive");

            // 1. 创建流式输出对象
            ResponseBodyEmitter emitter = new ResponseBodyEmitter(Long.MAX_VALUE);

            // 2. 构建执行命令实体
            ExecuteCommandEntity executeCommandEntity = ExecuteCommandEntity.builder()
                    .aiAgentId(request.getAiAgentId())
                    .message(request.getMessage())
                    .sessionId(request.getSessionId())
                    .maxStep(request.getMaxStep())
                    .build();

            // 3. 异步执行AutoAgent
            threadPoolExecutor.execute(() -> {
                try {
                    autoAgentExecuteStrategy.execute(executeCommandEntity, emitter);
                } catch (Exception e) {
                    log.error("AutoAgent执行异常:{}", e.getMessage(), e);
                    try {
                        emitter.send("执行异常:" + e.getMessage());
                    } catch (Exception ex) {
                        log.error("发送异常信息失败:{}", ex.getMessage(), ex);
                    }
                } finally {
                    try {
                        emitter.complete();
                    } catch (Exception e) {
                        log.error("完成流式输出失败:{}", e.getMessage(), e);
                    }
                }
            });

            return emitter;

        } catch (Exception e) {
            log.error("AutoAgent请求处理异常:{}", e.getMessage(), e);
            ResponseBodyEmitter errorEmitter = new ResponseBodyEmitter();
            try {
                errorEmitter.send("请求处理异常:" + e.getMessage());
                errorEmitter.complete();
            } catch (Exception ex) {
                log.error("发送错误信息失败:{}", ex.getMessage(), ex);
            }
            return errorEmitter;
        }
    }

}

这里单独把这一步拿出来说一下,ResponseBodyEmitter 相当于是一个后端往前端持续写数据的通道。是SpringMVC里面提供的一个响应对象,可以允许响应长连接,进而实现流式响应的能力,说白了就是保持连接,后端传一段消息过来就在前端展示一段,类似TCP但是单向。

java 复制代码
// 1. 创建流式输出对象
ResponseBodyEmitter emitter = new ResponseBodyEmitter(Long.MAX_VALUE);

在finally模块中关闭连接:

java 复制代码
 emitter.complete();

同时要配置SSE响应头:是为了让前端和中间网络层知道:这个接口要保持连接,并实时接收服务端一段一段推送的数据。

就相当于:

以前的流程:请求 -> 处理 -> 响应

现在的流程:请求 -> 处理一部分 -> 响应一部分 -> 处理一部分 -> 响应一部分 ... -> 结束连接

2.节点层

首先要引入流式响应,因此要在AbstractExecuteSupport中新增一个方法,用于获取在自动配置阶段装填的流式响应发射器对象(因为我们希望每一个分析的步骤都流式响应回前端)。

java 复制代码
/**
     * 通用的SSE结果发送方法
     * @param dynamicContext 动态上下文
     * @param result 要发送的结果实体
     */
    protected void sendSseResult(DefaultAutoAgentExecuteStrategyFactory.DynamicContext dynamicContext,
                                 AutoAgentExecuteResultEntity result) {
        try {
            ResponseBodyEmitter emitter = dynamicContext.getValue("emitter");
            if (emitter != null) {
                // 发送SSE格式的数据
                String sseData = "data: " + JSON.toJSONString(result) + "\n\n";
                emitter.send(sseData);
            }
        } catch (IOException e) {
            log.error("发送SSE结果失败:{}", e.getMessage(), e);
        }
    }

接下来就是在每个节点的每个步骤都添加上发射器,所有都要添加,这里只展示一部分:

java 复制代码
 if (line.contains("任务状态分析:")) {
                sendAnalysisSubResult(dynamicContext, currentSection, sectionContext.toString(), sessionId);
                currentSection = "analysis_status";
                log.info("\n🎯 任务状态分析:");
                continue;
            } else if (line.contains("执行历史评估:")) {
                sendAnalysisSubResult(dynamicContext, currentSection, sectionContext.toString(), sessionId);
                currentSection = "analysis_history";
                log.info("\n📈 执行历史评估:");
                continue;
            }
......

四、测试

相关推荐
米小虾10 分钟前
SKILLHARNESS:让AI Agent学会"安全地做事"
人工智能·agent
唐青枫1 小时前
Java Spring WebFlux 实战指南:用 Mono、Flux 和 WebClient 写响应式接口
java·spring
葫芦和十三1 小时前
图解 MongoDB 12|索引与查询优化地图:一条主线,三个判断轴
后端·mongodb·agent
doiito1 小时前
【Agent Harness】Gliding Horse 记忆系统深度剖析:像 CPU 一样思考的 AI 记忆架构
ai·rust·架构设计·系统设计·ai agent
葫芦和十三7 小时前
图解 MongoDB 11|慢查询排查闭环:从 Profile 到 explain 的分层路径
后端·mongodb·agent
葫芦和十三10 小时前
图解 MongoDB 09|explain 再读:从 queryPlanner 到 executionStats
后端·mongodb·agent
葫芦和十三11 小时前
图解 MongoDB 10|覆盖查询:让索引把活干完,根本不用回表
后端·mongodb·agent
冬奇Lab13 小时前
每日一个开源项目(第140篇):AgentScope 2.0 - 阿里开源的生产级 Agent 框架
人工智能·开源·agent
mobility13 小时前
免费AI视频生成器:我如何用零成本做出带旁白字幕的多场景AI视频
ai·vibe coding