AI Agent 深潜:六大核心模块的设计本质与 Java 实现

简介: AI Agent不是大模型API的简单封装,而是具备自主决策、闭环执行与迭代优化能力的完整智能系统。其核心由六大协同模块构成:规划(任务调度中枢)、记忆(经验沉淀载体)、工具使用(外部交互触手)、行动(落地执行手脚)、反思(自我迭代大脑)和多智能体协作(团队协同体系),共同支撑复杂任务的端到端可靠执行。

很多开发者对接了大模型API,封装了几轮对话,就宣称自己做了一个AI Agent。但上线后很快就会发现:复杂任务做一半就跑偏,调用工具频繁出错,相同的问题一犯再犯,根本无法实现自主闭环的任务执行。 本质原因,是没有摸到AI Agent的核心架构逻辑。AI Agent不是大模型API的简单封装,而是一套具备自主决策、闭环执行、迭代优化能力的完整智能系统。这套系统的核心,就是六大相互协同、环环相扣的核心模块:规划、记忆、工具使用、行动、反思、多智能体协作。

规划模块:Agent的任务调度中枢

规划模块是AI Agent的任务调度中枢,决定了Agent能否完成长程、复杂的目标。其核心价值,是破解大语言模型原生的单步推理容错率低、长上下文信息衰减、复杂任务拆解能力不足的核心痛点。

设计本质与底层逻辑

规划模块的本质,是模拟人类解决复杂问题的思考模式:先把大目标拆解成小步骤,再按优先级和依赖关系排序,形成可落地的执行路径。 其底层逻辑围绕三个核心维度展开:

  1. 任务拆解的粒度控制:拆解过粗会导致子任务无法直接执行,拆解过细会导致执行链路过长、容错率下降。合理的粒度,是每个子任务都有明确的输入、输出和成功判断标准,且无需二次拆解即可直接执行。
  2. 依赖关系的拓扑排序:复杂任务的子任务之间往往存在前置依赖,规划模块必须识别这些依赖关系,生成符合执行逻辑的线性序列,避免出现循环依赖或顺序错乱。
  3. 动态调整的容错机制:执行过程中出现异常或结果不符合预期时,规划模块需要能够快速调整执行计划,要么重试失败的子任务,要么重新拆解剩余任务,而不是直接全盘崩溃。

核心规划模式与适用场景

链式规划(Chain of Thought)

链式规划是最基础的规划模式,基于思维链思想,将复杂目标拆解为线性执行的子任务序列,前一个子任务的输出作为后一个子任务的输入。 适用场景:逻辑清晰、流程固定的线性任务,比如文档生成、数据处理、简单代码开发等。优势是实现简单、执行链路清晰,劣势是应对分支逻辑和异常场景的能力较弱。

树状规划(Tree of Thoughts)

树状规划基于思维树思想,将目标拆解为多个分支路径,每个路径生成多个可能的执行方案,通过评估筛选出最优路径继续推进。 适用场景:需要多方案对比、创意类、决策类任务,比如方案设计、问题排查、策略优化等。优势是容错率高、能探索最优解,劣势是计算成本高、执行周期长。

动态规划(Plan and Execute)

动态规划采用“先规划、后执行、再迭代”的模式,先生成整体执行计划,再分步执行,每执行完一个阶段就根据执行结果更新后续计划,实现规划与执行的动态协同。 适用场景:长周期、高复杂度、需求可能动态变化的任务,比如大型项目开发、完整业务流程处理、多角色协同任务等。优势是灵活性强、能应对动态变化,劣势是实现复杂度高,对模块间的协同能力要求高。

易混淆概念边界澄清

很多开发者会把思维链(CoT)和规划模块混为一谈,二者的核心边界非常清晰:

  • 思维链是单步推理的逻辑展开,是大模型在单次调用内完成的推理过程,解决的是“这一步怎么想”的问题。
  • 规划模块是多步任务的全局调度,是跨多次大模型调用、跨多个模块的完整任务管理体系,解决的是“整个任务怎么做”的问题。 简单来说,思维链是规划模块的底层能力之一,但绝不等同于规划模块本身。

Java工程化实现

核心实体定义

package com.jam.demo.agent.plan.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 任务实体类
* @author ken
*/

@Data
@TableName("agent_task")
@Schema(description = "任务实体")
public class AgentTask {

   @Schema(description = "主键ID")
   @TableId(type = IdType.AUTO)
   private Long id;

   @Schema(description = "任务唯一ID")
   @TableField("task_id")
   private String taskId;

   @Schema(description = "会话ID")
   @TableField("session_id")
   private String sessionId;

   @Schema(description = "父任务ID")
   @TableField("parent_task_id")
   private String parentTaskId;

   @Schema(description = "任务名称")
   @TableField("task_name")
   private String taskName;

   @Schema(description = "任务描述")
   @TableField("task_desc")
   private String taskDesc;

   @Schema(description = "任务状态")
   @TableField("task_status")
   private String taskStatus;

   @Schema(description = "优先级1-10,数字越大优先级越高")
   @TableField("priority")
   private Integer priority;

   @Schema(description = "执行者")
   @TableField("executor")
   private String executor;

   @Schema(description = "规划内容")
   @TableField("plan_content")
   private String planContent;

   @Schema(description = "执行结果")
   @TableField("result_content")
   private String resultContent;

   @Schema(description = "错误信息")
   @TableField("error_msg")
   private String errorMsg;

   @Schema(description = "重试次数")
   @TableField("retry_count")
   private Integer retryCount;

   @Schema(description = "最大重试次数")
   @TableField("max_retry")
   private Integer maxRetry;

   @Schema(description = "创建时间")
   @TableField(value = "create_time", fill = FieldFill.INSERT)
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
   private LocalDateTime updateTime;

   @Schema(description = "是否删除0-否1-是")
   @TableLogic
   @TableField("is_deleted")
   private Integer isDeleted;
}

规划器核心接口

package com.jam.demo.agent.plan;

import com.jam.demo.agent.plan.entity.AgentTask;
import java.util.List;

/**
* 规划器接口
* @author ken
*/

public interface Planner {

   /**
    * 生成任务执行计划
    * @param rootTask 根任务
    * @return 拆解后的子任务列表
    */

   List<AgentTask> generatePlan(AgentTask rootTask);

   /**
    * 优化现有计划
    * @param existTasks 现有任务列表
    * @param optimizeSuggestion 优化建议
    * @return 优化后的任务列表
    */

   List<AgentTask> optimizePlan(List<AgentTask> existTasks, String optimizeSuggestion);
}

链式规划器实现

package com.jam.demo.agent.plan.impl;

import com.alibaba.fastjson2.JSON;
import com.jam.demo.agent.plan.Planner;
import com.jam.demo.agent.plan.entity.AgentTask;
import com.jam.demo.agent.llm.LLMClient;
import com.jam.demo.agent.memory.MemoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.UUID;

/**
* 链式规划器实现
* 基于Chain of Thought思想,将复杂任务拆解为线性执行的子任务序列
* @author ken
*/

@Slf4j
@Component
public class ChainPlanner implements Planner {

   private final LLMClient llmClient;
   private final MemoryService memoryService;

   private static final String PLAN_SYSTEM_PROMPT = """
           你是一个专业的任务规划专家,需要将用户的复杂目标拆解为可执行、可验证的子任务序列。
           拆解规则:
           1. 子任务必须是线性执行的,前一个子任务的输出是后一个子任务的输入
           2. 每个子任务必须有明确的执行目标、输入、输出和成功判断标准
           3. 子任务数量控制在3-10个之间,避免过度拆解或拆解不足
           4. 子任务必须覆盖用户目标的所有需求,不能有遗漏
           5. 必须严格按照JSON数组格式返回,每个元素包含taskName、taskDesc、priority三个字段
           6. 禁止返回任何除JSON数组之外的内容
           "
"";

   public ChainPlanner(LLMClient llmClient, MemoryService memoryService) {
       this.llmClient = llmClient;
       this.memoryService = memoryService;
   }

   @Override
   public List<AgentTask> generatePlan(AgentTask rootTask) {
       if (ObjectUtils.isEmpty(rootTask) || !StringUtils.hasText(rootTask.getTaskDesc())) {
           throw new IllegalArgumentException("根任务信息不能为空");
       }
       String sessionId = rootTask.getSessionId();
       String relatedMemory = memoryService.getRelatedMemory(sessionId, rootTask.getTaskDesc(), 5);
       String userPrompt = buildUserPrompt(rootTask, relatedMemory);
       String llmResult = llmClient.chat(PLAN_SYSTEM_PROMPT, userPrompt);
       List<AgentTask> subTasks = parseTaskResult(llmResult, rootTask);
       log.info("链式规划器生成任务计划完成,根任务ID:{},子任务数量:{}", rootTask.getTaskId(), subTasks.size());
       return subTasks;
   }

   @Override
   public List<AgentTask> optimizePlan(List<AgentTask> existTasks, String optimizeSuggestion) {
       if (ObjectUtils.isEmpty(existTasks)) {
           throw new IllegalArgumentException("现有任务列表不能为空");
       }
       if (!StringUtils.hasText(optimizeSuggestion)) {
           return existTasks;
       }
       String systemPrompt = """
               你是一个专业的任务规划优化专家,需要根据优化建议,对现有的任务列表进行优化调整。
               优化规则:
               1. 必须严格按照优化建议进行调整,不能偏离优化方向
               2. 调整后的任务必须保持线性执行的逻辑,不能出现循环或依赖混乱
               3. 每个任务必须有明确的执行目标、输入、输出和成功判断标准
               4. 必须严格按照JSON数组格式返回,每个元素包含taskName、taskDesc、priority三个字段
               5. 禁止返回任何除JSON数组之外的内容
               "
"";
       String userPrompt = String.format("现有任务列表:%s\n优化建议:%s", JSON.toJSONString(existTasks), optimizeSuggestion);
       String llmResult = llmClient.chat(systemPrompt, userPrompt);
       AgentTask rootTask = existTasks.get(0);
       List<AgentTask> optimizedTasks = parseTaskResult(llmResult, rootTask);
       log.info("链式规划器优化任务计划完成,根任务ID:{},优化后子任务数量:{}", rootTask.getTaskId(), optimizedTasks.size());
       return optimizedTasks;
   }

   /**
    * 构建用户提示词
    * @param rootTask 根任务
    * @param relatedMemory 相关记忆
    * @return 提示词内容
    */

   private String buildUserPrompt(AgentTask rootTask, String relatedMemory) {
       StringBuilder promptBuilder = new StringBuilder();
       promptBuilder.append("用户的核心目标:").append(rootTask.getTaskDesc()).append("\n");
       if (StringUtils.hasText(relatedMemory)) {
           promptBuilder.append("相关历史经验:").append(relatedMemory).append("\n");
       }
       promptBuilder.append("请按照规则拆解为子任务序列。");
       return promptBuilder.toString();
   }

   /**
    * 解析LLM返回的任务结果
    * @param llmResult LLM返回的JSON内容
    * @param rootTask 根任务
    * @return 子任务列表
    */

   private List<AgentTask> parseTaskResult(String llmResult, AgentTask rootTask) {
       if (!StringUtils.hasText(llmResult)) {
           throw new RuntimeException("LLM返回的规划结果为空");
       }
       List<AgentTask> subTasks;
       try {
           subTasks = JSON.parseArray(llmResult, AgentTask.class);
       } catch (Exception e) {
           log.error("解析规划结果失败,LLM返回内容:{}", llmResult, e);
           throw new RuntimeException("解析规划结果失败", e);
       }
       if (ObjectUtils.isEmpty(subTasks)) {
           throw new RuntimeException("解析后的子任务列表为空");
       }
       String sessionId = rootTask.getSessionId();
       String rootTaskId = rootTask.getTaskId();
       for (AgentTask task : subTasks) {
           task.setTaskId(UUID.randomUUID().toString().replace("-", ""));
           task.setSessionId(sessionId);
           task.setParentTaskId(rootTaskId);
           task.setTaskStatus("PENDING");
           task.setRetryCount(0);
           task.setMaxRetry(3);
           if (ObjectUtils.isEmpty(task.getPriority())) {
               task.setPriority(5);
           }
       }
       return subTasks;
   }
}

落地避坑经验

  1. 不要完全依赖大模型生成规划,必须在工程层面加入校验规则。比如子任务数量限制、优先级校验、依赖关系校验、必填字段校验等,避免大模型生成的规划不符合执行要求。
  2. 规划的粒度要和任务复杂度匹配。简单任务过度拆解会导致执行效率低下,复杂任务拆解不足会导致大模型无法完成。可以通过任务复杂度评分,动态调整拆解粒度。
  3. 必须加入规划的降级机制。当大模型生成规划失败时,要有预设的通用规划模板兜底,而不是直接让整个任务失败。比如固定的“需求分析-方案设计-执行落地-结果验证”四步模板。

记忆模块:Agent的经验沉淀载体

记忆模块是AI Agent的经验沉淀载体,决定了Agent能否具备上下文连贯性、长期学习能力和个性化交互能力。其核心价值,是破解大语言模型原生的上下文窗口限制、信息遗忘、无法复用历史经验的核心痛点。

设计本质与底层逻辑

记忆模块的本质,是模拟人类的记忆机制,将Agent的交互历史、执行经验、决策逻辑进行分层存储和精准检索,让Agent在执行任务时能够调用相关的历史信息,做出更合理的决策。 其底层逻辑围绕三个核心维度展开:

  1. 记忆的分层存储:不同类型、不同重要程度的记忆,需要存储在不同的介质中,兼顾检索效率和存储成本。比如高频使用的工作记忆放在内存中,长期经验放在数据库和向量库中。
  2. 记忆的精准检索:记忆的核心价值不在于“存了多少”,而在于“能不能在需要的时候,找到最相关的内容”。必须通过相似度匹配、重要性排序、标签过滤等方式,实现高精准的记忆检索,避免检索结果出现大量噪音。
  3. 记忆的生命周期管理:记忆不是永久存储的,需要有完整的生命周期管理机制。比如过期记忆的清理、低价值记忆的归档、高价值记忆的强化,避免记忆无限膨胀导致检索效率下降。

记忆的分层架构与实现逻辑

参考认知科学的记忆分层理论,Agent的记忆体系分为四层,每层都有明确的职责和实现方式:

  1. 感官记忆:对应Agent实时接收的输入信息,比如用户的最新提问、工具返回的实时结果、执行过程中的临时数据。存储在内存缓存中,生命周期极短,仅在当前执行步骤中有效,用于支撑当前步骤的实时决策。
  2. 工作记忆:对应当前任务的执行上下文,比如任务的整体目标、已完成的子任务结果、当前执行进度、临时生成的规划内容。存储在会话级缓存中,生命周期和当前任务绑定,任务结束后可选择性归档到长期记忆中,用于支撑当前任务的连贯执行。
  3. 情景记忆:对应Agent的历史执行案例,比如某个任务的完整执行流程、成功的经验、失败的教训、用户的反馈信息。存储在关系型数据库中,生命周期为长期,用于同类任务的经验复用和反思优化。
  4. 语义记忆:对应Agent沉淀的通用知识和规则,比如工具的使用方法、任务的执行规范、用户的个性化偏好、通用的行业知识。存储在向量数据库中,生命周期为永久,用于全场景的知识支撑和决策参考。

易混淆概念边界澄清

很多开发者会把RAG(检索增强生成)和记忆模块混为一谈,二者的核心边界非常清晰:

  • RAG的核心是外部知识的检索与注入,处理的是“Agent不知道的通用知识”,比如行业文档、产品手册、第三方数据,这些知识不是Agent自身生成的,也和Agent的历史行为无关。
  • 记忆模块的核心是自身经验的存储与复用,处理的是“Agent自身的行为、决策、经验”,比如历史执行的任务、和用户的交互记录、成功和失败的教训,这些都是Agent自身生成的,和Agent的历史行为强相关。 简单来说,RAG是Agent的“外部图书馆”,记忆模块是Agent的“个人日记本和经验手册”。

Java工程化实现

核心实体定义

package com.jam.demo.agent.memory.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 记忆实体类
* @author ken
*/

@Data
@TableName("agent_memory")
@Schema(description = "记忆实体")
public class AgentMemory {

   @Schema(description = "主键ID")
   @TableId(type = IdType.AUTO)
   private Long id;

   @Schema(description = "会话ID")
   @TableField("session_id")
   private String sessionId;

   @Schema(description = "记忆类型:WORKING-工作记忆,LONG_TERM-长期记忆,EPISODIC-情景记忆")
   @TableField("memory_type")
   private String memoryType;

   @Schema(description = "记忆内容")
   @TableField("content")
   private String content;

   @Schema(description = "重要性评分1-10")
   @TableField("importance_score")
   private Integer importanceScore;

   @Schema(description = "记忆标签,逗号分隔")
   @TableField("tags")
   private String tags;

   @Schema(description = "向量库ID")
   @TableField("vector_id")
   private String vectorId;

   @Schema(description = "创建时间")
   @TableField(value = "create_time", fill = FieldFill.INSERT)
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
   private LocalDateTime updateTime;

   @Schema(description = "是否删除0-否1-是")
   @TableLogic
   @TableField("is_deleted")
   private Integer isDeleted;
}

记忆服务核心接口

package com.jam.demo.agent.memory;

import com.jam.demo.agent.memory.entity.AgentMemory;
import java.util.List;

/**
* 记忆服务接口
* @author ken
*/

public interface MemoryService {

   /**
    * 新增记忆
    * @param memory 记忆实体
    * @return 新增结果
    */

   boolean addMemory(AgentMemory memory);

   /**
    * 批量新增记忆
    * @param memoryList 记忆实体列表
    * @return 新增结果
    */

   boolean batchAddMemory(List<AgentMemory> memoryList);

   /**
    * 获取会话相关的工作记忆
    * @param sessionId 会话ID
    * @return 记忆列表
    */

   List<AgentMemory> getWorkingMemory(String sessionId);

   /**
    * 获取与目标内容相关的记忆
    * @param sessionId 会话ID
    * @param targetContent 目标内容
    * @param topN 返回数量
    * @return 相关记忆内容拼接字符串
    */

   String getRelatedMemory(String sessionId, String targetContent, int topN);

   /**
    * 清理会话的工作记忆
    * @param sessionId 会话ID
    * @return 清理结果
    */

   boolean clearWorkingMemory(String sessionId);

   /**
    * 归档工作记忆到长期记忆
    * @param sessionId 会话ID
    * @return 归档结果
    */

   boolean archiveWorkingMemory(String sessionId);
}

记忆服务实现

package com.jam.demo.agent.memory.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.jam.demo.agent.llm.LLMClient;
import com.jam.demo.agent.memory.MemoryService;
import com.jam.demo.agent.memory.entity.AgentMemory;
import com.jam.demo.agent.memory.mapper.AgentMemoryMapper;
import com.jam.demo.agent.vector.VectorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
* 记忆服务实现类
* @author ken
*/

@Slf4j
@Service
public class MemoryServiceImpl implements MemoryService {

   private final AgentMemoryMapper memoryMapper;
   private final LLMClient llmClient;
   private final VectorService vectorService;

   private static final String IMPORTANCE_SCORE_PROMPT = """
           你是一个记忆重要性评分专家,需要对给定的记忆内容进行1-10分的重要性评分。
           评分规则:
           1. 1-3分:临时无关内容,对后续任务没有参考价值
           2. 4-6分:普通内容,对同类任务有一定参考价值
           3. 7-10分:核心经验、用户偏好、关键教训,对长期任务有重要参考价值
           只需要返回数字分数,禁止返回任何其他内容
           "
"";

   public MemoryServiceImpl(AgentMemoryMapper memoryMapper, LLMClient llmClient, VectorService vectorService) {
       this.memoryMapper = memoryMapper;
       this.llmClient = llmClient;
       this.vectorService = vectorService;
   }

   @Override
   public boolean addMemory(AgentMemory memory) {
       if (ObjectUtils.isEmpty(memory) || !StringUtils.hasText(memory.getContent())) {
           throw new IllegalArgumentException("记忆内容不能为空");
       }
       if (!StringUtils.hasText(memory.getSessionId())) {
           throw new IllegalArgumentException("会话ID不能为空");
       }
       if (ObjectUtils.isEmpty(memory.getImportanceScore())) {
           String scoreStr = llmClient.chat(IMPORTANCE_SCORE_PROMPT, memory.getContent());
           try {
               memory.setImportanceScore(Integer.parseInt(scoreStr.trim()));
           } catch (Exception e) {
               log.warn("记忆重要性评分失败,使用默认值1,内容:{}", memory.getContent(), e);
               memory.setImportanceScore(1);
           }
       }
       float[] embedding = llmClient.embedding(memory.getContent());
       String vectorId = UUID.randomUUID().toString().replace("-", "");
       vectorService.insertVector(vectorId, embedding, JSON.toJSONString(memory));
       memory.setVectorId(vectorId);
       int result = memoryMapper.insert(memory);
       log.info("新增记忆完成,会话ID:{},记忆ID:{}", memory.getSessionId(), memory.getId());
       return result > 0;
   }

   @Override
   public boolean batchAddMemory(List<AgentMemory> memoryList) {
       if (CollectionUtils.isEmpty(memoryList)) {
           return true;
       }
       for (AgentMemory memory : memoryList) {
           addMemory(memory);
       }
       return true;
   }

   @Override
   public List<AgentMemory> getWorkingMemory(String sessionId) {
       if (!StringUtils.hasText(sessionId)) {
           throw new IllegalArgumentException("会话ID不能为空");
       }
       LambdaQueryWrapper<AgentMemory> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(AgentMemory::getSessionId, sessionId)
               .eq(AgentMemory::getMemoryType, "WORKING")
               .orderByDesc(AgentMemory::getCreateTime);
       return memoryMapper.selectList(queryWrapper);
   }

   @Override
   public String getRelatedMemory(String sessionId, String targetContent, int topN) {
       if (!StringUtils.hasText(targetContent)) {
           return "";
       }
       if (topN <= 0) {
           topN = 5;
       }
       float[] queryEmbedding = llmClient.embedding(targetContent);
       List<String> vectorIdList = vectorService.searchSimilarVector(queryEmbedding, topN);
       if (CollectionUtils.isEmpty(vectorIdList)) {
           return "";
       }
       LambdaQueryWrapper<AgentMemory> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.in(AgentMemory::getVectorId, vectorIdList)
               .eq(!ObjectUtils.isEmpty(sessionId), AgentMemory::getSessionId, sessionId)
               .orderByDesc(AgentMemory::getImportanceScore);
       List<AgentMemory> memoryList = memoryMapper.selectList(queryWrapper);
       if (CollectionUtils.isEmpty(memoryList)) {
           return "";
       }
       return memoryList.stream()
               .map(AgentMemory::getContent)
               .collect(Collectors.joining("\n"));
   }

   @Override
   public boolean clearWorkingMemory(String sessionId) {
       if (!StringUtils.hasText(sessionId)) {
           throw new IllegalArgumentException("会话ID不能为空");
       }
       LambdaQueryWrapper<AgentMemory> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(AgentMemory::getSessionId, sessionId)
               .eq(AgentMemory::getMemoryType, "WORKING");
       List<AgentMemory> memoryList = memoryMapper.selectList(queryWrapper);
       if (CollectionUtils.isEmpty(memoryList)) {
           return true;
       }
       List<String> vectorIdList = memoryList.stream()
               .map(AgentMemory::getVectorId)
               .filter(StringUtils::hasText)
               .collect(Collectors.toList());
       if (!CollectionUtils.isEmpty(vectorIdList)) {
           vectorService.deleteVector(vectorIdList);
       }
       memoryMapper.delete(queryWrapper);
       log.info("清理工作记忆完成,会话ID:{},清理数量:{}", sessionId, memoryList.size());
       return true;
   }

   @Override
   public boolean archiveWorkingMemory(String sessionId) {
       if (!StringUtils.hasText(sessionId)) {
           throw new IllegalArgumentException("会话ID不能为空");
       }
       LambdaQueryWrapper<AgentMemory> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(AgentMemory::getSessionId, sessionId)
               .eq(AgentMemory::getMemoryType, "WORKING")
               .ge(AgentMemory::getImportanceScore, 5);
       List<AgentMemory> memoryList = memoryMapper.selectList(queryWrapper);
       if (CollectionUtils.isEmpty(memoryList)) {
           clearWorkingMemory(sessionId);
           return true;
       }
       for (AgentMemory memory : memoryList) {
           memory.setId(null);
           memory.setMemoryType("LONG_TERM");
       }
       memoryMapper.batchInsert(memoryList);
       clearWorkingMemory(sessionId);
       log.info("归档工作记忆完成,会话ID:{},归档数量:{}", sessionId, memoryList.size());
       return true;
   }
}

MySQL表结构

CREATE TABLE `agent_memory` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `session_id` varchar(64) NOT NULL COMMENT '会话ID',
 `memory_type` varchar(32) NOT NULL COMMENT '记忆类型:WORKING-工作记忆,LONG_TERM-长期记忆,EPISODIC-情景记忆',
 `content` text NOT NULL COMMENT '记忆内容',
 `importance_score` int NOT NULL DEFAULT '1' COMMENT '重要性评分1-10',
 `tags` varchar(256) DEFAULT NULL COMMENT '记忆标签,逗号分隔',
 `vector_id` varchar(64) DEFAULT NULL COMMENT '向量库ID',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除0-否1-是',
 PRIMARY KEY (`id`),
 KEY `idx_session_id` (`session_id`),
 KEY `idx_memory_type` (`memory_type`),
 KEY `idx_importance_score` (`importance_score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Agent记忆表';

落地避坑经验

  1. 不要把所有对话内容都存入记忆,必须做重要性筛选。大量低价值的对话内容会导致检索结果噪音过多,反而影响Agent的决策效果。通过重要性评分机制,只存储评分高于阈值的高价值记忆。
  2. 记忆检索必须做多层过滤。先通过向量相似度匹配找到相关记忆,再通过重要性排序、标签过滤、时间范围过滤,筛选出最相关、最有价值的内容,避免一次性给大模型注入过多无关信息。
  3. 必须做记忆的过期清理机制。对于超过一定时间的低价值记忆,要自动归档或清理,避免记忆库无限膨胀。可以设置分层的过期策略,比如工作记忆任务结束后清理,长期记忆1年过期,核心经验永久保留。

工具使用模块:Agent的外部交互触手

工具使用模块是AI Agent的外部交互触手,决定了Agent能否突破大模型的固有局限,和真实世界进行交互。其核心价值,是破解大语言模型原生的知识截止日期限制、无法获取实时信息、无法执行物理/数字操作、无法处理复杂计算的核心痛点。

设计本质与底层逻辑

工具使用模块的本质,是给Agent提供一套可自主调用的能力接口,让Agent在决策过程中,能够根据任务需求,自主选择合适的工具,填写正确的参数,执行对应的操作,并获取执行结果,支撑后续的决策。 其底层逻辑围绕四个核心维度展开:

  1. 工具的标准化定义:所有工具必须遵循统一的定义规范,包括工具名称、功能描述、入参定义、出参定义、异常处理规则,让大模型能够清晰理解工具的用途和使用方式,避免调用错误。
  2. 工具的全生命周期管理:工具的注册、发现、匹配、调用、结果返回、异常处理,必须有完整的管理机制,实现工具的动态插拔,无需修改核心代码即可新增工具。
  3. 工具调用的安全管控:工具调用会和外部系统进行交互,可能会产生副作用,必须有严格的安全管控机制,比如权限控制、参数校验、沙箱执行、操作审计,避免危险操作和滥用。
  4. 工具结果的结构化处理:工具返回的原始结果往往格式多样、内容冗余,必须进行结构化处理和信息提炼,提取出和当前任务相关的核心信息,再交给大模型处理,避免信息过载。

工具全生命周期管理逻辑

  1. 工具注册:开发者通过注解或配置的方式,将工具定义注册到工具注册中心,工具注册中心会解析工具的元数据,生成标准化的工具描述,供大模型理解和调用。
  2. 工具匹配:Agent在执行任务时,根据当前的子任务目标,从工具注册中心匹配出适合的工具列表,交给大模型选择是否调用、调用哪个工具。
  3. 参数解析与校验:大模型生成工具调用的参数后,工具调用模块会对参数进行解析和校验,确保参数的类型、格式、取值范围符合工具的入参要求,校验不通过则直接返回错误,避免无效调用。
  4. 工具执行:参数校验通过后,工具调用模块会通过反射或动态代理的方式,执行对应的工具方法,同时记录执行日志、耗时、异常信息,用于后续的审计和反思。
  5. 结果处理:工具执行完成后,对返回结果进行结构化处理,提取核心信息,过滤冗余内容,同时处理执行过程中的异常,生成标准化的结果返回给Agent。

易混淆概念边界澄清

很多开发者会把工具调用和普通API调用混为一谈,二者的核心边界非常清晰:

  • 普通API调用是开发者硬编码的固定流程,什么时候调用、调用哪个API、传入什么参数,都是开发者提前写死在代码里的,大模型没有任何决策权。
  • 工具调用是Agent自主决策的动态行为,要不要调用、调用哪个工具、传入什么参数,都是大模型根据当前任务的执行情况自主决定的,开发者只需要定义工具,不需要干预具体的调用过程。 简单来说,普通API调用是开发者让程序做什么,程序就做什么;工具调用是Agent自己决定要做什么,然后调用对应的工具去完成。

Java工程化实现

工具注解定义

package com.jam.demo.agent.tool.annotation;

import java.lang.annotation.*;

/**
* 工具注解,用于标记工具类
* @author ken
*/

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AgentTool {

   /**
    * 工具名称
    * @return 工具名称
    */

   String name();

   /**
    * 工具描述
    * @return 工具描述
    */

   String description();
}

package com.jam.demo.agent.tool.annotation;

import java.lang.annotation.*;

/**
* 工具方法注解,用于标记工具类中的可调用方法
* @author ken
*/

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToolMethod {

   /**
    * 方法名称
    * @return 方法名称
    */

   String name();

   /**
    * 方法描述
    * @return 方法描述
    */

   String description();
}

package com.jam.demo.agent.tool.annotation;

import java.lang.annotation.*;

/**
* 工具参数注解,用于标记工具方法的入参
* @author ken
*/

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToolParam {

   /**
    * 参数名称
    * @return 参数名称
    */

   String name();

   /**
    * 参数描述
    * @return 参数描述
    */

   String description();

   /**
    * 是否必填
    * @return 是否必填
    */

   boolean required() default true;
}

工具元数据实体

package com.jam.demo.agent.tool.entity;

import lombok.Data;
import java.util.List;
import java.util.Map;

/**
* 工具元数据实体
* @author ken
*/

@Data
public class ToolMeta {

   private String toolName;

   private String toolDescription;

   private List<ToolMethodMeta> methodList;
}

package com.jam.demo.agent.tool.entity;

import lombok.Data;
import java.util.List;

/**
* 工具方法元数据实体
* @author ken
*/

@Data
public class ToolMethodMeta {

   private String methodName;

   private String methodDescription;

   private List<ToolParamMeta> paramList;

   private String returnType;

   private String returnDescription;
}

package com.jam.demo.agent.tool.entity;

import lombok.Data;

/**
* 工具参数元数据实体
* @author ken
*/

@Data
public class ToolParamMeta {

   private String paramName;

   private String paramDescription;

   private boolean required;

   private String paramType;
}

工具调用请求实体

package com.jam.demo.agent.tool.entity;

import lombok.Data;
import java.util.Map;

/**
* 工具调用请求实体
* @author ken
*/

@Data
public class ToolCallRequest {

   private String toolName;

   private String methodName;

   private Map<String, Object> params;
}

工具注册中心

package com.jam.demo.agent.tool;

import com.jam.demo.agent.tool.entity.ToolMeta;
import com.jam.demo.agent.tool.entity.ToolCallRequest;

import java.util.List;

/**
* 工具注册中心接口
* @author ken
*/

public interface ToolRegistry {

   /**
    * 注册工具
    * @param toolInstance 工具实例
    */

   void registerTool(Object toolInstance);

   /**
    * 获取所有工具的元数据
    * @return 工具元数据列表
    */

   List<ToolMeta> getAllToolMeta();

   /**
    * 生成工具描述文本,用于大模型提示词
    * @return 工具描述文本
    */

   String generateToolDescription();

   /**
    * 调用工具
    * @param request 工具调用请求
    * @return 工具执行结果
    */

   Object callTool(ToolCallRequest request);
}

工具注册中心实现

package com.jam.demo.agent.tool.impl;

import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.jam.demo.agent.tool.ToolRegistry;
import com.jam.demo.agent.tool.annotation.AgentTool;
import com.jam.demo.agent.tool.annotation.ToolMethod;
import com.jam.demo.agent.tool.annotation.ToolParam;
import com.jam.demo.agent.tool.entity.ToolMeta;
import com.jam.demo.agent.tool.entity.ToolMethodMeta;
import com.jam.demo.agent.tool.entity.ToolParamMeta;
import com.jam.demo.agent.tool.entity.ToolCallRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;

/**
* 工具注册中心实现类
* @author ken
*/

@Slf4j
@Component
public class ToolRegistryImpl implements ToolRegistry {

   private final Map<String, Object> toolInstanceMap = Maps.newConcurrentMap();
   private final Map<String, ToolMeta> toolMetaMap = Maps.newConcurrentMap();

   @Override
   public void registerTool(Object toolInstance) {
       if (ObjectUtils.isEmpty(toolInstance)) {
           throw new IllegalArgumentException("工具实例不能为空");
       }
       Class<?> toolClass = toolInstance.getClass();
       if (!toolClass.isAnnotationPresent(AgentTool.class)) {
           throw new IllegalArgumentException("工具类必须添加@AgentTool注解");
       }
       AgentTool agentTool = toolClass.getAnnotation(AgentTool.class);
       String toolName = agentTool.name();
       if (!StringUtils.hasText(toolName)) {
           throw new IllegalArgumentException("工具名称不能为空");
       }
       if (toolInstanceMap.containsKey(toolName)) {
           throw new IllegalArgumentException("工具名称已存在:" + toolName);
       }
       ToolMeta toolMeta = parseToolMeta(toolClass, agentTool);
       toolInstanceMap.put(toolName, toolInstance);
       toolMetaMap.put(toolName, toolMeta);
       log.info("注册工具完成,工具名称:{}", toolName);
   }

   @Override
   public List<ToolMeta> getAllToolMeta() {
       return Lists.newArrayList(toolMetaMap.values());
   }

   @Override
   public String generateToolDescription() {
       List<ToolMeta> toolMetaList = getAllToolMeta();
       if (ObjectUtils.isEmpty(toolMetaList)) {
           return "无可用工具";
       }
       StringBuilder descriptionBuilder = new StringBuilder();
       descriptionBuilder.append("可用工具列表:\n");
       for (int i = 0; i < toolMetaList.size(); i++) {
           ToolMeta toolMeta = toolMetaList.get(i);
           descriptionBuilder.append(i + 1).append(". 工具名称:").append(toolMeta.getToolName()).append("\n");
           descriptionBuilder.append("   工具描述:").append(toolMeta.getToolDescription()).append("\n");
           descriptionBuilder.append("   可用方法:\n");
           List<ToolMethodMeta> methodList = toolMeta.getMethodList();
           for (ToolMethodMeta methodMeta : methodList) {
               descriptionBuilder.append("   - 方法名称:").append(methodMeta.getMethodName()).append("\n");
               descriptionBuilder.append("     方法描述:").append(methodMeta.getMethodDescription()).append("\n");
               descriptionBuilder.append("     入参说明:\n");
               List<ToolParamMeta> paramList = methodMeta.getParamList();
               for (ToolParamMeta paramMeta : paramList) {
                   descriptionBuilder.append("       * 参数名称:").append(paramMeta.getParamName())
                           .append(",类型:").append(paramMeta.getParamType())
                           .append(",是否必填:").append(paramMeta.isRequired() ? "是" : "否")
                           .append(",描述:").append(paramMeta.getParamDescription()).append("\n");
               }
               descriptionBuilder.append("     返回值类型:").append(methodMeta.getReturnType())
                       .append(",描述:").append(methodMeta.getReturnDescription()).append("\n");
           }
       }
       descriptionBuilder.append("\n工具调用规则:\n");
       descriptionBuilder.append("1. 必须严格按照JSON格式返回工具调用内容,格式为:{\"toolName\":\"工具名称\",\"methodName\":\"方法名称\",\"params\":{\"参数名\":\"参数值\"}}\n");
       descriptionBuilder.append("2. 只能调用上述列表中的工具和方法,禁止调用不存在的工具和方法\n");
       descriptionBuilder.append("3. 必须严格按照入参说明填写参数,必填参数不能为空,参数类型必须匹配\n");
       descriptionBuilder.append("4. 如果不需要调用工具,直接返回空字符串即可\n");
       return descriptionBuilder.toString();
   }

   @Override
   public Object callTool(ToolCallRequest request) {
       if (ObjectUtils.isEmpty(request)) {
           throw new IllegalArgumentException("工具调用请求不能为空");
       }
       String toolName = request.getToolName();
       String methodName = request.getMethodName();
       if (!StringUtils.hasText(toolName) || !StringUtils.hasText(methodName)) {
           throw new IllegalArgumentException("工具名称和方法名称不能为空");
       }
       Object toolInstance = toolInstanceMap.get(toolName);
       if (ObjectUtils.isEmpty(toolInstance)) {
           throw new IllegalArgumentException("工具不存在:" + toolName);
       }
       ToolMeta toolMeta = toolMetaMap.get(toolName);
       List<ToolMethodMeta> methodMetaList = toolMeta.getMethodList();
       ToolMethodMeta targetMethodMeta = methodMetaList.stream()
               .filter(m -> m.getMethodName().equals(methodName))
               .findFirst()
               .orElseThrow(() -> new IllegalArgumentException("方法不存在:" + methodName));
       Method targetMethod;
       try {
           targetMethod = findTargetMethod(toolInstance.getClass(), methodName, targetMethodMeta);
       } catch (Exception e) {
           log.error("查找目标方法失败,工具名称:{},方法名称:{}", toolName, methodName, e);
           throw new RuntimeException("查找目标方法失败", e);
       }
       Map<String, Object> params = request.getParams();
       List<ToolParamMeta> paramMetaList = targetMethodMeta.getParamList();
       Object[] args = new Object[paramMetaList.size()];
       for (int i = 0; i < paramMetaList.size(); i++) {
           ToolParamMeta paramMeta = paramMetaList.get(i);
           String paramName = paramMeta.getParamName();
           Object paramValue = params.get(paramName);
           if (paramMeta.isRequired() && ObjectUtils.isEmpty(paramValue)) {
               throw new IllegalArgumentException("必填参数不能为空:" + paramName);
           }
           args[i] = paramValue;
       }
       try {
           Object result = targetMethod.invoke(toolInstance, args);
           log.info("调用工具成功,工具名称:{},方法名称:{}", toolName, methodName);
           return result;
       } catch (Exception e) {
           log.error("调用工具失败,工具名称:{},方法名称:{}", toolName, methodName, e);
           throw new RuntimeException("调用工具失败", e);
       }
   }

   /**
    * 解析工具元数据
    * @param toolClass 工具类
    * @param agentTool 工具注解
    * @return 工具元数据
    */

   private ToolMeta parseToolMeta(Class<?> toolClass, AgentTool agentTool) {
       ToolMeta toolMeta = new ToolMeta();
       toolMeta.setToolName(agentTool.name());
       toolMeta.setToolDescription(agentTool.description());
       Method[] methods = toolClass.getDeclaredMethods();
       List<ToolMethodMeta> methodMetaList = Lists.newArrayList();
       for (Method method : methods) {
           if (!method.isAnnotationPresent(ToolMethod.class)) {
               continue;
           }
           ToolMethod toolMethod = method.getAnnotation(ToolMethod.class);
           ToolMethodMeta methodMeta = new ToolMethodMeta();
           methodMeta.setMethodName(toolMethod.name());
           methodMeta.setMethodDescription(toolMethod.description());
           methodMeta.setReturnType(method.getReturnType().getSimpleName());
           Parameter[] parameters = method.getParameters();
           List<ToolParamMeta> paramMetaList = Lists.newArrayList();
           for (Parameter parameter : parameters) {
               if (!parameter.isAnnotationPresent(ToolParam.class)) {
                   continue;
               }
               ToolParam toolParam = parameter.getAnnotation(ToolParam.class);
               ToolParamMeta paramMeta = new ToolParamMeta();
               paramMeta.setParamName(toolParam.name());
               paramMeta.setParamDescription(toolParam.description());
               paramMeta.setRequired(toolParam.required());
               paramMeta.setParamType(parameter.getType().getSimpleName());
               paramMetaList.add(paramMeta);
           }
           methodMeta.setParamList(paramMetaList);
           methodMetaList.add(methodMeta);
       }
       toolMeta.setMethodList(methodMetaList);
       return toolMeta;
   }

   /**
    * 查找目标方法
    * @param toolClass 工具类
    * @param methodName 方法名称
    * @param methodMeta 方法元数据
    * @return 目标方法
    */

   private Method findTargetMethod(Class<?> toolClass, String methodName, ToolMethodMeta methodMeta) {
       Method[] methods = toolClass.getDeclaredMethods();
       for (Method method : methods) {
           if (!method.isAnnotationPresent(ToolMethod.class)) {
               continue;
           }
           ToolMethod toolMethod = method.getAnnotation(ToolMethod.class);
           if (toolMethod.name().equals(methodName)) {
               return method;
           }
       }
       throw new NoSuchMethodException("方法不存在:" + methodName);
   }
}

示例工具实现

package com.jam.demo.agent.tool.impl;

import com.jam.demo.agent.tool.annotation.AgentTool;
import com.jam.demo.agent.tool.annotation.ToolMethod;
import com.jam.demo.agent.tool.annotation.ToolParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
* 示例工具:字符串处理工具
* @author ken
*/

@Slf4j
@Component
@AgentTool(name = "StringProcessTool", description = "提供字符串处理相关的能力,包括长度计算、大小写转换、拼接、替换等操作")
public class StringProcessTool {

   /**
    * 计算字符串长度
    * @param content 待计算的字符串
    * @return 字符串长度
    */

   @ToolMethod(name = "getStringLength", description = "计算输入字符串的长度")
   public Integer getStringLength(@ToolParam(name = "content", description = "待计算的字符串", required = true) String content) {
       return content.length();
   }

   /**
    * 转换为大写
    * @param content 待转换的字符串
    * @return 转换后的大写字符串
    */

   @ToolMethod(name = "toUpperCase", description = "将输入字符串转换为全大写格式")
   public String toUpperCase(@ToolParam(name = "content", description = "待转换的字符串", required = true) String content) {
       return content.toUpperCase();
   }

   /**
    * 字符串替换
    * @param content 原始字符串
    * @param oldStr 要替换的旧字符串
    * @param newStr 替换后的新字符串
    * @return 替换后的字符串
    */

   @ToolMethod(name = "replaceString", description = "将原始字符串中的指定旧字符串替换为新字符串")
   public String replaceString(
           @ToolParam(name = "content", description = "原始字符串", required = true)
String content,
           @ToolParam(name = "oldStr", description = "要替换的旧字符串", required = true) String oldStr,
           @ToolParam(name = "newStr", description = "替换后的新字符串", required = true) String newStr) {
       return content.replace(oldStr, newStr);
   }
}

落地避坑经验

  1. 不要给Agent开放过多的工具,必须做场景化的工具隔离。给Agent开放几十上百个工具,会导致大模型无法准确选择合适的工具,出现大量的误调用。应该根据当前任务的场景,只给Agent开放该场景下需要的工具列表,减少大模型的选择成本。
  2. 工具的描述必须精准、清晰,不能有歧义。工具和方法的描述,必须明确说明工具的用途、适用场景、入参要求、出参格式,让大模型能够准确理解工具的能力,避免调用错误。尤其是入参的描述,必须明确说明参数的格式、取值范围、示例。
  3. 必须做严格的参数校验和安全管控。大模型生成的参数可能会有格式错误、类型不匹配、注入攻击等问题,必须在工具执行前做严格的参数校验,校验不通过直接返回错误。对于有副作用的工具,比如文件写入、数据库修改、接口调用,必须做权限控制、操作审计、沙箱执行,避免危险操作。

行动执行模块:Agent的落地执行手脚

行动执行模块是AI Agent的落地执行手脚,决定了Agent的决策能否真正转化为实际的执行结果,形成完整的闭环。其核心价值,是破解Agent决策与执行脱节、执行过程不可控、异常无法处理、结果无法追溯的核心痛点。

设计本质与底层逻辑

行动执行模块的本质,是Agent的执行管控中枢,负责将规划模块生成的子任务、工具使用模块生成的调用请求,转化为原子化的执行动作,管控整个执行过程的状态、事务、异常、重试,确保每一步操作都有结果、有反馈、可追溯。 其底层逻辑围绕四个核心维度展开:

  1. 动作的原子化设计:每个执行动作必须是原子化的,要么全部执行成功,要么全部执行失败,不能出现部分执行成功的中间状态。原子化的动作是实现事务管控、异常重试、状态追溯的基础。
  2. 执行过程的状态机管理:每个动作都有明确的状态流转规则,从待执行、执行中、成功、失败,有固定的流转路径,不能出现状态错乱。通过状态机管理,可以清晰地掌握每个动作的执行进度,实现执行过程的可视化和可控化。
  3. 执行的事务性与幂等性:对于有副作用的执行动作,必须保证事务性,执行失败时能够完整回滚,避免数据不一致;同时必须保证幂等性,同一个动作重复执行,不会产生额外的副作用,避免重试导致的重复操作。
  4. 异常处理与重试机制:执行过程中出现异常时,必须有完整的异常捕获、处理、重试机制,根据异常的类型和严重程度,选择直接重试、降级处理、终止执行等不同的策略,而不是直接让整个任务崩溃。

执行状态机与事务管控逻辑

动作状态流转规则

动作的核心状态分为四种,流转规则如下:

  1. PENDING(待执行):动作已创建,等待执行,是动作的初始状态。只能流转到EXECUTING(执行中)或CANCELLED(已取消)状态。
  2. EXECUTING(执行中):动作正在执行,只能流转到SUCCESS(成功)或FAILED(失败)状态。
  3. SUCCESS(成功):动作执行成功,是动作的终态,不能再流转到其他状态。
  4. FAILED(失败):动作执行失败,是动作的终态,也可以根据重试规则,流转回PENDING(待执行)状态,重新执行。

事务管控逻辑

对于涉及数据修改、外部系统调用的有副作用的动作,采用编程式事务进行管控,核心流程如下:

  1. 开启事务,更新动作状态为EXECUTING,锁定动作记录,避免并发执行。
  2. 执行动作的核心逻辑,包括工具调用、数据修改、外部系统交互等。
  3. 执行成功,提交事务,更新动作状态为SUCCESS,记录执行结果和耗时。
  4. 执行失败,回滚事务,更新动作状态为FAILED,记录错误信息,根据重试规则判断是否需要重新执行。

易混淆概念边界澄清

很多开发者会把行动执行模块和工具使用模块混为一谈,二者的核心边界非常清晰:

  • 工具使用模块的核心是能力的定义与封装,负责管理Agent可以调用的所有能力,解决的是“Agent能做什么”的问题。
  • 行动执行模块的核心是执行的管控与落地,负责管理Agent每一个动作的执行全流程,解决的是“Agent怎么把事做成”的问题。 简单来说,工具使用模块是Agent的“能力库”,行动执行模块是Agent的“执行指挥官”,负责调用能力库中的能力,管控整个执行过程,确保执行结果符合预期。

Java工程化实现

核心实体定义

package com.jam.demo.agent.action.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 动作实体类
* @author ken
*/

@Data
@TableName("agent_action")
@Schema(description = "动作实体")
public class AgentAction {

   @Schema(description = "主键ID")
   @TableId(type = IdType.AUTO)
   private Long id;

   @Schema(description = "动作唯一ID")
   @TableField("action_id")
   private String actionId;

   @Schema(description = "所属任务ID")
   @TableField("task_id")
   private String taskId;

   @Schema(description = "会话ID")
   @TableField("session_id")
   private String sessionId;

   @Schema(description = "动作类型:TOOL_CALL-工具调用,LLM_INFERENCE-LLM推理,USER_INTERACT-用户交互")
   @TableField("action_type")
   private String actionType;

   @Schema(description = "动作名称")
   @TableField("action_name")
   private String actionName;

   @Schema(description = "动作内容")
   @TableField("action_content")
   private String actionContent;

   @Schema(description = "动作状态:PENDING-待执行,EXECUTING-执行中,SUCCESS-成功,FAILED-失败")
   @TableField("action_status")
   private String actionStatus;

   @Schema(description = "输入参数")
   @TableField("input_params")
   private String inputParams;

   @Schema(description = "输出结果")
   @TableField("output_result")
   private String outputResult;

   @Schema(description = "错误信息")
   @TableField("error_msg")
   private String errorMsg;

   @Schema(description = "链路追踪ID")
   @TableField("trace_id")
   private String traceId;

   @Schema(description = "执行时间")
   @TableField("execute_time")
   private LocalDateTime executeTime;

   @Schema(description = "执行耗时,毫秒")
   @TableField("duration")
   private Long duration;

   @Schema(description = "创建时间")
   @TableField(value = "create_time", fill = FieldFill.INSERT)
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
   private LocalDateTime updateTime;

   @Schema(description = "是否删除0-否1-是")
   @TableLogic
   @TableField("is_deleted")
   private Integer isDeleted;
}

动作执行器核心接口

package com.jam.demo.agent.action;

import com.jam.demo.agent.action.entity.AgentAction;

/**
* 动作执行器接口
* @author ken
*/

public interface ActionExecutor {

   /**
    * 执行动作
    * @param action 动作实体
    */

   void executeAction(AgentAction action);

   /**
    * 重试执行动作
    * @param actionId 动作ID
    */

   void retryAction(String actionId);

   /**
    * 取消动作
    * @param actionId 动作ID
    */

   void cancelAction(String actionId);
}

动作执行器实现

package com.jam.demo.agent.action.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.agent.action.ActionExecutor;
import com.jam.demo.agent.action.entity.AgentAction;
import com.jam.demo.agent.action.mapper.AgentActionMapper;
import com.jam.demo.agent.llm.LLMClient;
import com.jam.demo.agent.tool.ToolRegistry;
import com.jam.demo.agent.tool.entity.ToolCallRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
* 动作执行器实现类
* @author ken
*/

@Slf4j
@Component
public class ActionExecutorImpl implements ActionExecutor {

   private final AgentActionMapper actionMapper;
   private final PlatformTransactionManager transactionManager;
   private final ToolRegistry toolRegistry;
   private final LLMClient llmClient;

   private static final int MAX_RETRY_COUNT = 3;

   public ActionExecutorImpl(AgentActionMapper actionMapper, PlatformTransactionManager transactionManager,
                             ToolRegistry toolRegistry, LLMClient llmClient)
{
       this.actionMapper = actionMapper;
       this.transactionManager = transactionManager;
       this.toolRegistry = toolRegistry;
       this.llmClient = llmClient;
   }

   @Override
   public void executeAction(AgentAction action) {
       if (ObjectUtils.isEmpty(action)) {
           throw new IllegalArgumentException("动作实体不能为空");
       }
       if (!"PENDING".equals(action.getActionStatus())) {
           throw new IllegalArgumentException("只有待执行状态的动作才能执行");
       }
       DefaultTransactionDefinition def = new DefaultTransactionDefinition();
       def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
       TransactionStatus status = transactionManager.getTransaction(def);
       LocalDateTime startTime = LocalDateTime.now();
       try {
           action.setActionStatus("EXECUTING");
           action.setExecuteTime(startTime);
           actionMapper.updateById(action);
           transactionManager.commit(status);
           Object result = doExecute(action);
           long duration = ChronoUnit.MILLIS.between(startTime, LocalDateTime.now());
           TransactionStatus successStatus = transactionManager.getTransaction(def);
           action.setActionStatus("SUCCESS");
           action.setOutputResult(ObjectUtils.isEmpty(result) ? "" : JSON.toJSONString(result));
           action.setDuration(duration);
           actionMapper.updateById(action);
           transactionManager.commit(successStatus);
           log.info("动作执行成功,动作ID:{},耗时:{}ms", action.getActionId(), duration);
       } catch (Exception e) {
           transactionManager.rollback(status);
           long duration = ChronoUnit.MILLIS.between(startTime, LocalDateTime.now());
           TransactionStatus failStatus = transactionManager.getTransaction(def);
           action.setActionStatus("FAILED");
           action.setErrorMsg(e.getMessage());
           action.setDuration(duration);
           actionMapper.updateById(action);
           transactionManager.rollback(failStatus);
           log.error("动作执行失败,动作ID:{},耗时:{}ms", action.getActionId(), duration, e);
           throw new RuntimeException("动作执行失败", e);
       }
   }

   @Override
   public void retryAction(String actionId) {
       if (!StringUtils.hasText(actionId)) {
           throw new IllegalArgumentException("动作ID不能为空");
       }
       AgentAction action = actionMapper.selectOne(new LambdaQueryWrapper<AgentAction>()
               .eq(AgentAction::getActionId, actionId));
       if (ObjectUtils.isEmpty(action)) {
           throw new IllegalArgumentException("动作不存在:" + actionId);
       }
       if (!"FAILED".equals(action.getActionStatus())) {
           throw new IllegalArgumentException("只有失败状态的动作才能重试");
       }
       int retryCount = action.getRetryCount() == null ? 0 : action.getRetryCount();
       if (retryCount >= MAX_RETRY_COUNT) {
           throw new IllegalArgumentException("动作重试次数已达上限,最大重试次数:" + MAX_RETRY_COUNT);
       }
       action.setRetryCount(retryCount + 1);
       action.setActionStatus("PENDING");
       action.setErrorMsg(null);
       action.setOutputResult(null);
       action.setExecuteTime(null);
       action.setDuration(null);
       actionMapper.updateById(action);
       log.info("动作重试准备完成,动作ID:{},当前重试次数:{}", actionId, action.getRetryCount());
       executeAction(action);
   }

   @Override
   public void cancelAction(String actionId) {
       if (!StringUtils.hasText(actionId)) {
           throw new IllegalArgumentException("动作ID不能为空");
       }
       AgentAction action = actionMapper.selectOne(new LambdaQueryWrapper<AgentAction>()
               .eq(AgentAction::getActionId, actionId));
       if (ObjectUtils.isEmpty(action)) {
           throw new IllegalArgumentException("动作不存在:" + actionId);
       }
       if (!"PENDING".equals(action.getActionStatus()) && !"EXECUTING".equals(action.getActionStatus())) {
           throw new IllegalArgumentException("只有待执行或执行中状态的动作才能取消");
       }
       DefaultTransactionDefinition def = new DefaultTransactionDefinition();
       def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
       TransactionStatus status = transactionManager.getTransaction(def);
       try {
           action.setActionStatus("CANCELLED");
           actionMapper.updateById(action);
           transactionManager.commit(status);
           log.info("动作取消成功,动作ID:{}", actionId);
       } catch (Exception e) {
           transactionManager.rollback(status);
           log.error("动作取消失败,动作ID:{}", actionId, e);
           throw new RuntimeException("动作取消失败", e);
       }
   }

   /**
    * 执行动作核心逻辑
    * @param action 动作实体
    * @return 执行结果
    */

   private Object doExecute(AgentAction action) {
       String actionType = action.getActionType();
       return switch (actionType) {
           case "TOOL_CALL" -> executeToolCall(action);
           case "LLM_INFERENCE" -> executeLlmInference(action);
           case "USER_INTERACT" -> executeUserInteract(action);
           default -> throw new IllegalArgumentException("不支持的动作类型:" + actionType);
       };
   }

   /**
    * 执行工具调用动作
    * @param action 动作实体
    * @return 工具执行结果
    */

   private Object executeToolCall(AgentAction action) {
       String inputParams = action.getInputParams();
       if (!StringUtils.hasText(inputParams)) {
           throw new IllegalArgumentException("工具调用参数不能为空");
       }
       ToolCallRequest request;
       try {
           request = JSON.parseObject(inputParams, ToolCallRequest.class);
       } catch (Exception e) {
           log.error("工具调用参数解析失败,参数内容:{}", inputParams, e);
           throw new RuntimeException("工具调用参数解析失败", e);
       }
       return toolRegistry.callTool(request);
   }

   /**
    * 执行LLM推理动作
    * @param action 动作实体
    * @return LLM推理结果
    */

   private Object executeLlmInference(AgentAction action) {
       String inputParams = action.getInputParams();
       if (!StringUtils.hasText(inputParams)) {
           throw new IllegalArgumentException("LLM推理参数不能为空");
       }
       try {
           String systemPrompt = JSON.parseObject(inputParams).getString("systemPrompt");
           String userPrompt = JSON.parseObject(inputParams).getString("userPrompt");
           if (!StringUtils.hasText(userPrompt)) {
               throw new IllegalArgumentException("用户提示词不能为空");
           }
           return llmClient.chat(systemPrompt, userPrompt);
       } catch (Exception e) {
           log.error("LLM推理参数解析失败,参数内容:{}", inputParams, e);
           throw new RuntimeException("LLM推理参数解析失败", e);
       }
   }

   /**
    * 执行用户交互动作
    * @param action 动作实体
    * @return 交互结果
    */

   private Object executeUserInteract(AgentAction action) {
       String actionContent = action.getActionContent();
       if (!StringUtils.hasText(actionContent)) {
           throw new IllegalArgumentException("用户交互内容不能为空");
       }
       log.info("等待用户交互,动作ID:{},交互内容:{}", action.getActionId(), actionContent);
       return "用户交互已发起,等待用户回复";
   }
}

MySQL表结构

CREATE TABLE `agent_action` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `action_id` varchar(64) NOT NULL COMMENT '动作唯一ID',
 `task_id` varchar(64) NOT NULL COMMENT '所属任务ID',
 `session_id` varchar(64) NOT NULL COMMENT '会话ID',
 `action_type` varchar(32) NOT NULL COMMENT '动作类型:TOOL_CALL-工具调用,LLM_INFERENCE-LLM推理,USER_INTERACT-用户交互',
 `action_name` varchar(128) NOT NULL COMMENT '动作名称',
 `action_content` text COMMENT '动作内容',
 `action_status` varchar(32) NOT NULL DEFAULT 'PENDING' COMMENT '动作状态:PENDING-待执行,EXECUTING-执行中,SUCCESS-成功,FAILED-失败,CANCELLED-已取消',
 `input_params` text COMMENT '输入参数',
 `output_result` text COMMENT '输出结果',
 `error_msg` text COMMENT '错误信息',
 `trace_id` varchar(64) DEFAULT NULL COMMENT '链路追踪ID',
 `execute_time` datetime DEFAULT NULL COMMENT '执行时间',
 `duration` bigint DEFAULT NULL COMMENT '执行耗时,毫秒',
 `retry_count` int NOT NULL DEFAULT '0' COMMENT '重试次数',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除0-否1-是',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_action_id` (`action_id`),
 KEY `idx_task_id` (`task_id`),
 KEY `idx_session_id` (`session_id`),
 KEY `idx_action_status` (`action_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Agent动作执行表';

落地避坑经验

  1. 必须保证动作的原子性和幂等性。每个动作只能对应一个原子操作,禁止在一个动作里封装多个有副作用的操作,避免执行失败时出现部分成功的脏数据。所有有副作用的动作必须实现幂等性,通过action_id做唯一约束,避免重试导致的重复操作。
  2. 动作执行必须做超时控制。对于工具调用、外部系统交互类的动作,必须设置合理的超时时间,避免动作长时间处于执行中状态,阻塞整个任务的执行。超时的动作需要主动终止,更新状态为失败,再根据重试规则判断是否重新执行。
  3. 必须做完整的执行链路追踪。每个动作都要绑定trace_id,关联所属的任务和会话,完整记录动作的输入、输出、耗时、错误信息,实现执行全链路的可追溯。当任务执行出现异常时,可以快速定位到具体的动作和错误原因,便于排查和修复。

反思模块:Agent的自我迭代大脑

反思模块是AI Agent的自我迭代大脑,决定了Agent能否从成功和失败的经验中学习,持续优化自身的决策和执行能力。其核心价值,是破解Agent重复犯错、无法优化执行策略、无法沉淀通用经验的核心痛点。

设计本质与底层逻辑

反思模块的本质,是模拟人类的复盘反思能力,对Agent的执行过程、决策逻辑、执行结果进行全面的复盘分析,找出成功的关键因素和失败的根本原因,形成可复用的优化建议和经验沉淀,反哺到规划、记忆、工具使用等其他模块,实现Agent能力的持续迭代。 其底层逻辑围绕四个核心维度展开:

  1. 反思的触发机制:明确什么时候需要触发反思,不是每一步执行都要反思,而是在关键节点触发,比如任务执行完成、任务执行失败、子任务执行结果不符合预期、连续多次执行同一个动作等,避免无效反思增加计算成本。
  2. 反思的分析维度:明确反思需要分析哪些内容,核心包括目标对齐度分析、决策逻辑分析、执行过程分析、工具使用分析、结果质量分析五个维度,全面覆盖Agent的整个执行链路,找出问题的根本原因,而不是只看表面现象。
  3. 反思的成果转化:反思的核心价值不在于分析问题,而在于把分析结果转化为可落地的优化动作。比如优化后续的任务规划、更新记忆中的经验沉淀、调整工具调用的策略、修正决策的逻辑,实现反思成果的闭环落地。
  4. 反思的迭代闭环:反思不是一次性的动作,而是持续的迭代过程。通过“执行-反思-优化-再执行-再反思”的闭环,让Agent的能力持续提升,越用越智能,越用越贴合用户的需求。

核心反思模式与适用场景

单步反思(Step Reflection)

单步反思是最基础的反思模式,针对单个子任务或单个动作的执行结果进行复盘分析,找出当前步骤的问题和优化点,立即优化后续的执行动作。 适用场景:子任务执行失败、执行结果不符合预期、工具调用出错等场景,优势是响应及时,能快速修正当前的执行偏差,避免错误持续放大,劣势是只能看到单步的问题,无法发现全局的系统性问题。

全局反思(Global Reflection)

全局反思是针对整个任务的完整执行链路进行全面的复盘分析,从目标拆解、规划设计、执行过程、结果交付全流程进行复盘,找出全局的优化点和可沉淀的通用经验。 适用场景:整个任务执行完成、任务整体失败、长周期任务的阶段性复盘等场景,优势是能发现系统性的问题,沉淀通用的经验,劣势是计算成本高,实时性较弱。

经验沉淀反思(Experience Reflection)

经验沉淀反思是针对多个同类任务的执行历史进行聚合分析,找出同类任务的通用执行规律、常见问题、最优执行策略,形成标准化的经验模板,沉淀到长期记忆中,用于后续同类任务的执行。 适用场景:批量执行同类任务、固定场景的常态化任务等场景,优势是能实现Agent能力的持续迭代,越用越智能,劣势是需要大量的历史执行数据,分析周期长。

易混淆概念边界澄清

很多开发者会把反思模块和普通的异常处理混为一谈,二者的核心边界非常清晰:

  • 异常处理的核心是解决当前的错误,针对执行过程中出现的异常,进行捕获、重试、降级处理,确保当前任务能继续执行,解决的是“当下的问题怎么处理”的问题。
  • 反思模块的核心是避免未来再犯同样的错误,针对已经发生的错误和成功的经验,进行深度分析,找出根本原因,形成优化方案,沉淀通用经验,解决的是“未来怎么做得更好”的问题。 简单来说,异常处理是“救火”,解决当下的紧急问题;反思模块是“防火”,从根源上避免问题再次发生,同时优化整体的执行效率和结果质量。

Java工程化实现

核心实体定义

package com.jam.demo.agent.reflection.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 反思记录实体类
* @author ken
*/

@Data
@TableName("agent_reflection")
@Schema(description = "反思记录实体")
public class AgentReflection {

   @Schema(description = "主键ID")
   @TableId(type = IdType.AUTO)
   private Long id;

   @Schema(description = "反思唯一ID")
   @TableField("reflection_id")
   private String reflectionId;

   @Schema(description = "会话ID")
   @TableField("session_id")
   private String sessionId;

   @Schema(description = "关联任务ID")
   @TableField("task_id")
   private String taskId;

   @Schema(description = "反思类型:STEP-单步反思,GLOBAL-全局反思,EXPERIENCE-经验沉淀反思")
   @TableField("reflection_type")
   private String reflectionType;

   @Schema(description = "反思触发原因")
   @TableField("trigger_reason")
   private String triggerReason;

   @Schema(description = "原始执行数据")
   @TableField("execute_data")
   private String executeData;

   @Schema(description = "分析结果")
   @TableField("analysis_result")
   private String analysisResult;

   @Schema(description = "问题根因")
   @TableField("root_cause")
   private String rootCause;

   @Schema(description = "优化方案")
   @TableField("optimize_plan")
   private String optimizePlan;

   @Schema(description = "优化落地状态:PENDING-待落地,COMPLETED-已落地,FAILED-落地失败")
   @TableField("落地状态")
   private String implementStatus;

   @Schema(description = "经验沉淀内容")
   @TableField("experience_content")
   private String experienceContent;

   @Schema(description = "创建时间")
   @TableField(value = "create_time", fill = FieldFill.INSERT)
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
   private LocalDateTime updateTime;

   @Schema(description = "是否删除0-否1-是")
   @TableLogic
   @TableField("is_deleted")
   private Integer isDeleted;
}

反思服务核心接口

package com.jam.demo.agent.reflection;

import com.jam.demo.agent.reflection.entity.AgentReflection;

/**
* 反思服务接口
* @author ken
*/

public interface ReflectionService {

   /**
    * 触发单步反思
    * @param taskId 任务ID
    * @param sessionId 会话ID
    * @param triggerReason 触发原因
    * @param executeData 执行数据
    * @return 反思记录
    */

   AgentReflection triggerStepReflection(String taskId, String sessionId, String triggerReason, String executeData);

   /**
    * 触发全局反思
    * @param taskId 任务ID
    * @param sessionId 会话ID
    * @param triggerReason 触发原因
    * @param executeData 执行数据
    * @return 反思记录
    */

   AgentReflection triggerGlobalReflection(String taskId, String sessionId, String triggerReason, String executeData);

   /**
    * 触发经验沉淀反思
    * @param sessionId 会话ID
    * @param taskType 任务类型
    * @return 反思记录
    */

   AgentReflection triggerExperienceReflection(String sessionId, String taskType);

   /**
    * 落地优化方案
    * @param reflectionId 反思ID
    * @return 落地结果
    */

   boolean implementOptimizePlan(String reflectionId);
}

反思服务实现

package com.jam.demo.agent.reflection.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.jam.demo.agent.llm.LLMClient;
import com.jam.demo.agent.memory.MemoryService;
import com.jam.demo.agent.memory.entity.AgentMemory;
import com.jam.demo.agent.plan.Planner;
import com.jam.demo.agent.reflection.ReflectionService;
import com.jam.demo.agent.reflection.entity.AgentReflection;
import com.jam.demo.agent.reflection.mapper.AgentReflectionMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;

/**
* 反思服务实现类
* @author ken
*/

@Slf4j
@Service
public class ReflectionServiceImpl implements ReflectionService {

   private final AgentReflectionMapper reflectionMapper;
   private final LLMClient llmClient;
   private final MemoryService memoryService;
   private final Planner planner;

   private static final String STEP_REFLECTION_SYSTEM_PROMPT = """
           你是一个专业的AI Agent执行复盘专家,需要对单步任务的执行结果进行深度反思分析。
           分析规则:
           1. 先明确当前步骤的执行目标与实际结果的偏差,判断是否符合预期
           2. 分析执行过程中的决策逻辑、工具使用、参数填写是否合理
           3. 定位问题的根本原因,不能只描述表面现象
           4. 生成可落地的优化方案,优化方案必须具体、可执行,不能是空话
           5. 提炼可复用的经验总结,用于后续同类步骤的执行
           6. 必须严格按照JSON格式返回,包含analysisResult、rootCause、optimizePlan、experienceContent四个字段
           7. 禁止返回任何除JSON之外的内容
           "
"";

   private static final String GLOBAL_REFLECTION_SYSTEM_PROMPT = """
           你是一个专业的AI Agent全局复盘专家,需要对整个任务的完整执行链路进行深度反思分析。
           分析规则:
           1. 先明确任务的整体目标与最终交付结果的对齐度,判断是否完成核心目标
           2. 从目标拆解、规划设计、执行过程、工具使用、结果交付全流程进行全面分析
           3. 定位全局的系统性问题和关键优化点,而不是只关注单个步骤的问题
           4. 生成全局的优化方案,优化方案必须覆盖规划、执行、工具、记忆全链路
           5. 提炼可复用的通用执行方法论,用于后续同类任务的执行
           6. 必须严格按照JSON格式返回,包含analysisResult、rootCause、optimizePlan、experienceContent四个字段
           7. 禁止返回任何除JSON之外的内容
           "
"";

   private static final String EXPERIENCE_REFLECTION_SYSTEM_PROMPT = """
           你是一个专业的AI Agent经验沉淀专家,需要对同类任务的历史执行数据进行聚合分析,沉淀通用经验。
           分析规则:
           1. 分析同类任务的通用执行规律、最优执行路径、常见问题与解决方案
           2. 提炼标准化的任务执行模板,覆盖目标拆解、规划设计、工具使用全流程
           3. 总结同类任务的避坑指南和最佳实践
           4. 生成的经验必须通用、可复用、可落地,不能只针对单个任务
           5. 必须严格按照JSON格式返回,包含analysisResult、optimizePlan、experienceContent三个字段
           6. 禁止返回任何除JSON之外的内容
           "
"";

   public ReflectionServiceImpl(AgentReflectionMapper reflectionMapper, LLMClient llmClient,
                                MemoryService memoryService, Planner planner)
{
       this.reflectionMapper = reflectionMapper;
       this.llmClient = llmClient;
       this.memoryService = memoryService;
       this.planner = planner;
   }

   @Override
   public AgentReflection triggerStepReflection(String taskId, String sessionId, String triggerReason, String executeData) {
       if (!StringUtils.hasText(taskId) || !StringUtils.hasText(sessionId)) {
           throw new IllegalArgumentException("任务ID和会话ID不能为空");
       }
       if (!StringUtils.hasText(executeData)) {
           throw new IllegalArgumentException("执行数据不能为空");
       }
       AgentReflection reflection = new AgentReflection();
       reflection.setReflectionId(UUID.randomUUID().toString().replace("-", ""));
       reflection.setTaskId(taskId);
       reflection.setSessionId(sessionId);
       reflection.setReflectionType("STEP");
       reflection.setTriggerReason(triggerReason);
       reflection.setExecuteData(executeData);
       reflection.setImplementStatus("PENDING");
       String userPrompt = String.format("单步执行数据:%s\n触发原因:%s", executeData, triggerReason);
       String llmResult = llmClient.chat(STEP_REFLECTION_SYSTEM_PROMPT, userPrompt);
       parseReflectionResult(reflection, llmResult);
       reflectionMapper.insert(reflection);
       log.info("单步反思触发完成,反思ID:{},任务ID:{}", reflection.getReflectionId(), taskId);
       implementOptimizePlan(reflection.getReflectionId());
       return reflection;
   }

   @Override
   public AgentReflection triggerGlobalReflection(String taskId, String sessionId, String triggerReason, String executeData) {
       if (!StringUtils.hasText(taskId) || !StringUtils.hasText(sessionId)) {
           throw new IllegalArgumentException("任务ID和会话ID不能为空");
       }
       if (!StringUtils.hasText(executeData)) {
           throw new IllegalArgumentException("执行数据不能为空");
       }
       AgentReflection reflection = new AgentReflection();
       reflection.setReflectionId(UUID.randomUUID().toString().replace("-", ""));
       reflection.setTaskId(taskId);
       reflection.setSessionId(sessionId);
       reflection.setReflectionType("GLOBAL");
       reflection.setTriggerReason(triggerReason);
       reflection.setExecuteData(executeData);
       reflection.setImplementStatus("PENDING");
       String userPrompt = String.format("任务完整执行数据:%s\n触发原因:%s", executeData, triggerReason);
       String llmResult = llmClient.chat(GLOBAL_REFLECTION_SYSTEM_PROMPT, userPrompt);
       parseReflectionResult(reflection, llmResult);
       reflectionMapper.insert(reflection);
       log.info("全局反思触发完成,反思ID:{},任务ID:{}", reflection.getReflectionId(), taskId);
       implementOptimizePlan(reflection.getReflectionId());
       return reflection;
   }

   @Override
   public AgentReflection triggerExperienceReflection(String sessionId, String taskType) {
       if (!StringUtils.hasText(sessionId) || !StringUtils.hasText(taskType)) {
           throw new IllegalArgumentException("会话ID和任务类型不能为空");
       }
       LambdaQueryWrapper<AgentReflection> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(AgentReflection::getSessionId, sessionId)
               .like(AgentReflection::getTriggerReason, taskType)
               .orderByDesc(AgentReflection::getCreateTime)
               .last("limit 20");
       List<AgentReflection> historyReflectionList = reflectionMapper.selectList(queryWrapper);
       if (ObjectUtils.isEmpty(historyReflectionList)) {
           throw new IllegalArgumentException("没有找到同类任务的历史反思数据");
       }
       AgentReflection reflection = new AgentReflection();
       reflection.setReflectionId(UUID.randomUUID().toString().replace("-", ""));
       reflection.setSessionId(sessionId);
       reflection.setReflectionType("EXPERIENCE");
       reflection.setTriggerReason("同类任务经验沉淀,任务类型:" + taskType);
       reflection.setExecuteData(JSON.toJSONString(historyReflectionList));
       reflection.setImplementStatus("PENDING");
       String userPrompt = String.format("同类任务历史反思数据:%s\n任务类型:%s", JSON.toJSONString(historyReflectionList), taskType);
       String llmResult = llmClient.chat(EXPERIENCE_REFLECTION_SYSTEM_PROMPT, userPrompt);
       parseReflectionResult(reflection, llmResult);
       reflectionMapper.insert(reflection);
       log.info("经验沉淀反思触发完成,反思ID:{},任务类型:{}", reflection.getReflectionId(), taskType);
       implementOptimizePlan(reflection.getReflectionId());
       return reflection;
   }

   @Override
   public boolean implementOptimizePlan(String reflectionId) {
       if (!StringUtils.hasText(reflectionId)) {
           throw new IllegalArgumentException("反思ID不能为空");
       }
       AgentReflection reflection = reflectionMapper.selectOne(new LambdaQueryWrapper<AgentReflection>()
               .eq(AgentReflection::getReflectionId, reflectionId));
       if (ObjectUtils.isEmpty(reflection)) {
           throw new IllegalArgumentException("反思记录不存在:" + reflectionId);
       }
       try {
           String experienceContent = reflection.getExperienceContent();
           if (StringUtils.hasText(experienceContent)) {
               AgentMemory memory = new AgentMemory();
               memory.setSessionId(reflection.getSessionId());
               memory.setMemoryType("LONG_TERM");
               memory.setContent(experienceContent);
               memory.setImportanceScore(8);
               memory.setTags(reflection.getReflectionType() + "," + reflection.getTaskId());
               memoryService.addMemory(memory);
           }
           reflection.setImplementStatus("COMPLETED");
           reflectionMapper.updateById(reflection);
           log.info("优化方案落地完成,反思ID:{}", reflectionId);
           return true;
       } catch (Exception e) {
           reflection.set落地Status("FAILED");
           reflectionMapper.updateById(reflection);
           log.error("优化方案落地失败,反思ID:{}", reflectionId, e);
           return false;
       }
   }

   /**
    * 解析反思结果
    * @param reflection 反思实体
    * @param llmResult LLM返回的JSON内容
    */

   private void parseReflectionResult(AgentReflection reflection, String llmResult) {
       if (!StringUtils.hasText(llmResult)) {
           throw new RuntimeException("LLM返回的反思结果为空");
       }
       try {
           AgentReflection result = JSON.parseObject(llmResult, AgentReflection.class);
           reflection.setAnalysisResult(result.getAnalysisResult());
           reflection.setRootCause(result.getRootCause());
           reflection.setOptimizePlan(result.getOptimizePlan());
           reflection.setExperienceContent(result.getExperienceContent());
       } catch (Exception e) {
           log.error("解析反思结果失败,LLM返回内容:{}", llmResult, e);
           throw new RuntimeException("解析反思结果失败", e);
       }
   }
}

MySQL表结构

CREATE TABLE `agent_reflection` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `reflection_id` varchar(64) NOT NULL COMMENT '反思唯一ID',
 `session_id` varchar(64) NOT NULL COMMENT '会话ID',
 `task_id` varchar(64) DEFAULT NULL COMMENT '关联任务ID',
 `reflection_type` varchar(32) NOT NULL COMMENT '反思类型:STEP-单步反思,GLOBAL-全局反思,EXPERIENCE-经验沉淀反思',
 `trigger_reason` varchar(256) NOT NULL COMMENT '反思触发原因',
 `execute_data` longtext COMMENT '原始执行数据',
 `analysis_result` text COMMENT '分析结果',
 `root_cause` text COMMENT '问题根因',
 `optimize_plan` text COMMENT '优化方案',
 `implement_status` varchar(32) NOT NULL DEFAULT 'PENDING' COMMENT '优化落地状态:PENDING-待落地,COMPLETED-已落地,FAILED-落地失败',
 `experience_content` text COMMENT '经验沉淀内容',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除0-否1-是',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_reflection_id` (`reflection_id`),
 KEY `idx_task_id` (`task_id`),
 KEY `idx_session_id` (`session_id`),
 KEY `idx_reflection_type` (`reflection_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Agent反思记录表';

落地避坑经验

  1. 反思不能只停留在分析层面,必须实现闭环落地。很多Agent的反思模块只是生成了分析报告,没有把优化方案落地到其他模块,导致反思没有任何实际价值。必须把反思生成的经验沉淀到记忆模块,把优化方案反馈到规划模块、工具使用模块,实现反思成果的真正落地。
  2. 必须控制反思的触发频率,避免过度反思。每一步执行都触发反思,会导致计算成本大幅上升,执行效率大幅下降。必须设置合理的触发机制,只在关键节点触发反思,比如任务失败、结果不符合预期、任务完成后,平衡反思的价值和计算成本。
  3. 反思必须聚焦根因,不能只看表面现象。很多Agent的反思只是描述了“工具调用失败”“结果不符合预期”等表面现象,没有找到根本原因,比如“工具描述不清晰导致大模型参数填写错误”“任务拆解粒度过粗导致无法执行”,这样的反思无法从根源上解决问题,后续还会重复犯错。

多智能体协作模块:Agent的团队协同体系

多智能体协作模块是AI Agent的团队协同体系,决定了Agent能否突破单智能体的能力边界,通过多个不同角色、不同能力的智能体协同配合,完成复杂的、多角色的、跨领域的大型任务。其核心价值,是破解单智能体能力单一、专业度不足、无法处理多角色复杂任务的核心痛点。

设计本质与底层逻辑

多智能体协作的本质,是模拟人类的团队协作模式,将复杂的大型任务拆解为不同角色负责的子任务,每个智能体都有明确的角色定位、职责边界、专业能力,通过标准化的沟通机制、协同流程、决策机制,实现多个智能体的高效协同,共同完成整体目标。 其底层逻辑围绕四个核心维度展开:

  1. 角色的专业化分工:每个智能体都有明确的角色定位和专业领域,比如产品经理Agent、开发工程师Agent、测试工程师Agent、项目经理Agent,每个Agent只负责自己专业领域内的任务,提升任务执行的专业度和质量,避免单智能体什么都做但什么都做不精的问题。
  2. 标准化的沟通协议:多个智能体之间的沟通必须遵循标准化的协议,包括消息格式、沟通流程、响应规则、权限控制,确保智能体之间的沟通高效、准确、无歧义,避免出现沟通错位、信息丢失、理解偏差的问题。
  3. 清晰的协同流程:多个智能体的协作必须有清晰的流程和规则,包括任务分配机制、依赖关系管理、进度同步机制、冲突解决机制、成果验收机制,确保整个协作过程有序推进,不会出现职责不清、互相推诿、进度混乱的问题。
  4. 统一的决策中枢:多个智能体的协作必须有一个统一的决策中枢,也就是协调者Agent,负责整体目标的把控、任务的分配、冲突的解决、进度的管控、成果的整合,确保所有智能体的工作都围绕整体目标推进,不会出现方向跑偏的问题。

核心协作模式与适用场景

串行协作模式

串行协作模式是最基础的协作模式,多个智能体按照固定的流程顺序执行,前一个智能体的输出作为后一个智能体的输入,依次完成整个任务的各个环节。 适用场景:流程固定、环节清晰的线性任务,比如软件开发的瀑布模型、内容创作的全流程、数据处理的流水线等场景,优势是实现简单、流程清晰、易于管控,劣势是灵活性差,无法应对需要并行处理的任务。

并行协作模式

并行协作模式是将整体任务拆解为多个相互独立的子任务,分配给多个不同的智能体并行执行,所有子任务都完成后,再由协调者Agent进行成果整合,交付最终结果。 适用场景:可以拆解为多个独立子任务的并行处理场景,比如多维度的数据分析、多模块的并行开发、多渠道的信息收集等场景,优势是执行效率高,能大幅缩短任务的整体执行周期,劣势是对任务拆解的要求高,需要子任务之间没有强依赖关系。

分层协作模式

分层协作模式是按照决策层、协调层、执行层三个层级进行分工,决策层Agent负责整体目标和方向的把控,协调层Agent负责任务的拆解、分配、协调和管控,执行层Agent负责具体的任务执行,形成金字塔式的协作架构。 适用场景:大型、复杂、长周期的项目型任务,比如大型软件项目开发、完整的营销活动策划与执行、企业级的业务流程处理等场景,优势是权责清晰、管控力强、能应对超复杂的大型任务,劣势是实现复杂度高,对各层级Agent的能力要求高。

易混淆概念边界澄清

很多开发者会把多智能体协作和单智能体的多角色Prompt混为一谈,二者的核心边界非常清晰:

  • 单智能体的多角色Prompt,只是在提示词里给大模型设定了不同的角色,让大模型模拟不同角色的语气和思考方式,本质上还是同一个大模型、同一个智能体在执行所有的任务,所有的决策和执行都在同一个上下文里,没有真正的分工和协同。
  • 多智能体协作,是多个完全独立的智能体,每个智能体都有自己独立的规划、记忆、工具、执行、反思模块,有自己独立的上下文和能力边界,通过标准化的协议进行沟通和协同,每个智能体只负责自己职责范围内的任务,是真正的团队协作。 简单来说,单智能体的多角色Prompt,是一个人扮演多个角色;多智能体协作,是多个专业的人组成一个团队,各司其职,协同完成任务。

Java工程化实现

核心实体定义

package com.jam.demo.agent.multiagent.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 智能体实体类
* @author ken
*/

@Data
@TableName("agent_instance")
@Schema(description = "智能体实体")
public class AgentInstance {

   @Schema(description = "主键ID")
   @TableId(type = IdType.AUTO)
   private Long id;

   @Schema(description = "智能体唯一ID")
   @TableField("agent_id")
   private String agentId;

   @Schema(description = "智能体角色名称")
   @TableField("agent_role")
   private String agentRole;

   @Schema(description = "智能体名称")
   @TableField("agent_name")
   private String agentName;

   @Schema(description = "智能体描述")
   @TableField("agent_desc")
   private String agentDesc;

   @Schema(description = "系统提示词")
   @TableField("system_prompt")
   private String systemPrompt;

   @Schema(description = "可用工具列表,逗号分隔")
   @TableField("available_tools")
   private String availableTools;

   @Schema(description = "智能体类型:COORDINATOR-协调者,EXECUTOR-执行者")
   @TableField("agent_type")
   private String agentType;

   @Schema(description = "状态:ENABLED-启用,DISABLED-禁用")
   @TableField("status")
   private String status;

   @Schema(description = "创建时间")
   @TableField(value = "create_time", fill = FieldFill.INSERT)
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
   private LocalDateTime updateTime;

   @Schema(description = "是否删除0-否1-是")
   @TableLogic
   @TableField("is_deleted")
   private Integer isDeleted;
}

package com.jam.demo.agent.multiagent.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 智能体消息实体类
* @author ken
*/

@Data
@TableName("agent_message")
@Schema(description = "智能体消息实体")
public class AgentMessage {

   @Schema(description = "主键ID")
   @TableId(type = IdType.AUTO)
   private Long id;

   @Schema(description = "消息唯一ID")
   @TableField("message_id")
   private String messageId;

   @Schema(description = "会话ID")
   @TableField("session_id")
   private String sessionId;

   @Schema(description = "发送方智能体ID")
   @TableField("sender_agent_id")
   private String senderAgentId;

   @Schema(description = "接收方智能体ID")
   @TableField("receiver_agent_id")
   private String receiverAgentId;

   @Schema(description = "消息类型:TASK_ASSIGN-任务分配,RESULT_REPORT-结果汇报,QUESTION-提问,ANSWER-回答,NOTICE-通知")
   @TableField("message_type")
   private String messageType;

   @Schema(description = "消息内容")
   @TableField("content")
   private String content;

   @Schema(description = "关联任务ID")
   @TableField("task_id")
   private String taskId;

   @Schema(description = "消息状态:UNREAD-未读,READ-已读,PROCESSED-已处理")
   @TableField("message_status")
   private String messageStatus;

   @Schema(description = "创建时间")
   @TableField(value = "create_time", fill = FieldFill.INSERT)
   private LocalDateTime createTime;

   @Schema(description = "更新时间")
   @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
   private LocalDateTime updateTime;

   @Schema(description = "是否删除0-否1-是")
   @TableLogic
   @TableField("is_deleted")
   private Integer isDeleted;
}

多智能体协作服务核心接口

package com.jam.demo.agent.multiagent;

import com.jam.demo.agent.multiagent.entity.AgentInstance;
import com.jam.demo.agent.multiagent.entity.AgentMessage;

import java.util.List;

/**
* 多智能体协作服务接口
* @author ken
*/

public interface MultiAgentService {

   /**
    * 注册智能体
    * @param agentInstance 智能体实例
    * @return 注册结果
    */

   boolean registerAgent(AgentInstance agentInstance);

   /**
    * 获取可用的智能体列表
    * @return 智能体列表
    */

   List<AgentInstance> getEnabledAgentList();

   /**
    * 发送智能体消息
    * @param message 消息实体
    * @return 发送结果
    */

   boolean sendMessage(AgentMessage message);

   /**
    * 获取智能体的未处理消息
    * @param agentId 智能体ID
    * @return 消息列表
    */

   List<AgentMessage> getUnprocessedMessage(String agentId);

   /**
    * 处理消息
    * @param messageId 消息ID
    * @param processResult 处理结果
    * @return 处理结果
    */

   boolean processMessage(String messageId, String processResult);

   /**
    * 启动多智能体协作任务
    * @param sessionId 会话ID
    * @param target 整体目标
    * @return 任务启动结果
    */

   boolean startMultiAgentTask(String sessionId, String target);
}

多智能体协作服务实现

package com.jam.demo.agent.multiagent.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.jam.demo.agent.llm.LLMClient;
import com.jam.demo.agent.multiagent.MultiAgentService;
import com.jam.demo.agent.multiagent.entity.AgentInstance;
import com.jam.demo.agent.multiagent.entity.AgentMessage;
import com.jam.demo.agent.multiagent.mapper.AgentInstanceMapper;
import com.jam.demo.agent.multiagent.mapper.AgentMessageMapper;
import com.jam.demo.agent.plan.Planner;
import com.jam.demo.agent.plan.entity.AgentTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
* 多智能体协作服务实现类
* @author ken
*/

@Slf4j
@Service
public class MultiAgentServiceImpl implements MultiAgentService {

   private final AgentInstanceMapper agentInstanceMapper;
   private final AgentMessageMapper agentMessageMapper;
   private final LLMClient llmClient;
   private final Planner planner;

   private static final String TASK_SPLIT_SYSTEM_PROMPT = """
           你是一个专业的多智能体任务分配专家,需要将用户的整体目标拆解为对应角色智能体负责的子任务。
           拆解规则:
           1. 必须严格根据每个智能体的角色和职责,分配对应的子任务,不能超出智能体的职责范围
           2. 每个子任务必须有明确的交付标准和完成时限
           3. 必须明确子任务之间的依赖关系和执行顺序
           4. 必须严格按照JSON数组格式返回,每个元素包含agentRole、taskName、taskDesc、deliveryStandard、priority、dependOnAgentRole字段
           5. 禁止返回任何除JSON数组之外的内容
           "
"";

   public MultiAgentServiceImpl(AgentInstanceMapper agentInstanceMapper, AgentMessageMapper agentMessageMapper,
                                 LLMClient llmClient, Planner planner)
{
       this.agentInstanceMapper = agentInstanceMapper;
       this.agentMessageMapper = agentMessageMapper;
       this.llmClient = llmClient;
       this.planner = planner;
   }

   @Override
   public boolean registerAgent(AgentInstance agentInstance) {
       if (ObjectUtils.isEmpty(agentInstance)) {
           throw new IllegalArgumentException("智能体实例不能为空");
       }
       if (!StringUtils.hasText(agentInstance.getAgentId())) {
           agentInstance.setAgentId(UUID.randomUUID().toString().replace("-", ""));
       }
       if (!StringUtils.hasText(agentInstance.getAgentRole()) || !StringUtils.hasText(agentInstance.getSystemPrompt())) {
           throw new IllegalArgumentException("智能体角色和系统提示词不能为空");
       }
       agentInstance.setStatus("ENABLED");
       int result = agentInstanceMapper.insert(agentInstance);
       log.info("智能体注册完成,智能体ID:{},角色:{}", agentInstance.getAgentId(), agentInstance.getAgentRole());
       return result > 0;
   }

   @Override
   public List<AgentInstance> getEnabledAgentList() {
       LambdaQueryWrapper<AgentInstance> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(AgentInstance::getStatus, "ENABLED");
       return agentInstanceMapper.selectList(queryWrapper);
   }

   @Override
   public boolean sendMessage(AgentMessage message) {
       if (ObjectUtils.isEmpty(message)) {
           throw new IllegalArgumentException("消息实体不能为空");
       }
       if (!StringUtils.hasText(message.getSenderAgentId()) || !StringUtils.hasText(message.getReceiverAgentId())) {
           throw new IllegalArgumentException("发送方和接收方智能体ID不能为空");
       }
       message.setMessageId(UUID.randomUUID().toString().replace("-", ""));
       message.setMessageStatus("UNREAD");
       int result = agentMessageMapper.insert(message);
       log.info("智能体消息发送完成,消息ID:{},发送方:{},接收方:{}",
               message.getMessageId(), message.getSenderAgentId(), message.getReceiverAgentId());
       return result > 0;
   }

   @Override
   public List<AgentMessage> getUnprocessedMessage(String agentId) {
       if (!StringUtils.hasText(agentId)) {
           throw new IllegalArgumentException("智能体ID不能为空");
       }
       LambdaQueryWrapper<AgentMessage> queryWrapper = new LambdaQueryWrapper<>();
       queryWrapper.eq(AgentMessage::getReceiverAgentId, agentId)
               .in(AgentMessage::getMessageStatus, "UNREAD", "READ")
               .orderByAsc(AgentMessage::getCreateTime);
       return agentMessageMapper.selectList(queryWrapper);
   }

   @Override
   public boolean processMessage(String messageId, String processResult) {
       if (!StringUtils.hasText(messageId)) {
           throw new IllegalArgumentException("消息ID不能为空");
       }
       AgentMessage message = agentMessageMapper.selectOne(new LambdaQueryWrapper<AgentMessage>()
               .eq(AgentMessage::getMessageId, messageId));
       if (ObjectUtils.isEmpty(message)) {
           throw new IllegalArgumentException("消息不存在:" + messageId);
       }
       message.setMessageStatus("PROCESSED");
       message.setContent(message.getContent() + "\n处理结果:" + processResult);
       int result = agentMessageMapper.updateById(message);
       log.info("消息处理完成,消息ID:{}", messageId);
       return result > 0;
   }

   @Override
   public boolean startMultiAgentTask(String sessionId, String target) {
       if (!StringUtils.hasText(sessionId) || !StringUtils.hasText(target)) {
           throw new IllegalArgumentException("会话ID和整体目标不能为空");
       }
       List<AgentInstance> agentList = getEnabledAgentList();
       if (CollectionUtils.isEmpty(agentList)) {
           throw new IllegalArgumentException("没有可用的智能体");
       }
       AgentInstance coordinatorAgent = agentList.stream()
               .filter(agent -> "COORDINATOR".equals(agent.getAgentType()))
               .findFirst()
               .orElseThrow(() -> new IllegalArgumentException("没有找到协调者智能体"));
       String agentDesc = agentList.stream()
               .map(agent -> "角色:" + agent.getAgentRole() + ",职责:" + agent.getAgentDesc())
               .collect(Collectors.joining("\n"));
       String userPrompt = String.format("整体目标:%s\n可用智能体列表:%s", target, agentDesc);
       String llmResult = llmClient.chat(TASK_SPLIT_SYSTEM_PROMPT, userPrompt);
       List<Map<String, Object>> taskList;
       try {
           taskList = JSON.parseArray(llmResult, Map.class);
       } catch (Exception e) {
           log.error("任务拆解结果解析失败,LLM返回内容:{}", llmResult, e);
           throw new RuntimeException("任务拆解结果解析失败", e);
       }
       if (CollectionUtils.isEmpty(taskList)) {
           throw new RuntimeException("拆解后的任务列表为空");
       }
       Map<String, AgentInstance> agentRoleMap = agentList.stream()
               .collect(Collectors.toMap(AgentInstance::getAgentRole, agent -> agent));
       for (Map<String, Object> taskMap : taskList) {
           String agentRole = (String) taskMap.get("agentRole");
           AgentInstance targetAgent = agentRoleMap.get(agentRole);
           if (ObjectUtils.isEmpty(targetAgent)) {
               log.warn("没有找到对应角色的智能体,角色:{}", agentRole);
               continue;
           }
           AgentMessage message = new AgentMessage();
           message.setSessionId(sessionId);
           message.setSenderAgentId(coordinatorAgent.getAgentId());
           message.setReceiverAgentId(targetAgent.getAgentId());
           message.setMessageType("TASK_ASSIGN");
           message.setTaskId(UUID.randomUUID().toString().replace("-", ""));
           message.setContent(JSON.toJSONString(taskMap));
           sendMessage(message);
       }
       log.info("多智能体协作任务启动完成,会话ID:{},拆解任务数量:{}", sessionId, taskList.size());
       return true;
   }
}

MySQL表结构

CREATE TABLE `agent_instance` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `agent_id` varchar(64) NOT NULL COMMENT '智能体唯一ID',
 `agent_role` varchar(64) NOT NULL COMMENT '智能体角色名称',
 `agent_name` varchar(128) NOT NULL COMMENT '智能体名称',
 `agent_desc` text NOT NULL COMMENT '智能体描述',
 `system_prompt` longtext NOT NULL COMMENT '系统提示词',
 `available_tools` varchar(512) DEFAULT NULL COMMENT '可用工具列表,逗号分隔',
 `agent_type` varchar(32) NOT NULL DEFAULT 'EXECUTOR' COMMENT '智能体类型:COORDINATOR-协调者,EXECUTOR-执行者',
 `status` varchar(32) NOT NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED-启用,DISABLED-禁用',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除0-否1-是',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_agent_id` (`agent_id`),
 UNIQUE KEY `uk_agent_role` (`agent_role`),
 KEY `idx_agent_type` (`agent_type`),
 KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='智能体实例表';

CREATE TABLE `agent_message` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `message_id` varchar(64) NOT NULL COMMENT '消息唯一ID',
 `session_id` varchar(64) NOT NULL COMMENT '会话ID',
 `sender_agent_id` varchar(64) NOT NULL COMMENT '发送方智能体ID',
 `receiver_agent_id` varchar(64) NOT NULL COMMENT '接收方智能体ID',
 `message_type` varchar(32) NOT NULL COMMENT '消息类型:TASK_ASSIGN-任务分配,RESULT_REPORT-结果汇报,QUESTION-提问,ANSWER-回答,NOTICE-通知',
 `content` longtext NOT NULL COMMENT '消息内容',
 `task_id` varchar(64) DEFAULT NULL COMMENT '关联任务ID',
 `message_status` varchar(32) NOT NULL DEFAULT 'UNREAD' COMMENT '消息状态:UNREAD-未读,READ-已读,PROCESSED-已处理',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除0-否1-是',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_message_id` (`message_id`),
 KEY `idx_session_id` (`session_id`),
 KEY `idx_sender_agent_id` (`sender_agent_id`),
 KEY `idx_receiver_agent_id` (`receiver_agent_id`),
 KEY `idx_message_status` (`message_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='智能体消息表';

落地避坑经验

  1. 必须明确每个智能体的职责边界,避免职责重叠和模糊。多个智能体之间的职责必须清晰、无重叠、无遗漏,每个智能体只负责自己专业领域内的事情,避免出现多个智能体都能做同一件事,或者某个事情没有智能体负责的情况,否则会出现互相推诿、重复工作、任务遗漏的问题。
  2. 必须建立标准化的沟通协议,避免沟通错位。多个智能体之间的沟通必须有固定的消息格式、字段定义、响应规则,不能用自由文本随意沟通,否则会出现大模型理解偏差、信息传递错误、任务执行跑偏的问题。所有的消息都必须有明确的类型、发送方、接收方、关联任务、核心内容,确保沟通无歧义。
  3. 必须有统一的协调者和决策中枢,避免群龙无首。多智能体协作必须有一个明确的协调者Agent,负责整体目标的把控、任务的分配、冲突的解决、进度的管控,不能让多个执行者Agent直接互相沟通、自行决策,否则会出现方向跑偏、目标失控、流程混乱的问题,最终无法完成整体目标。

六大模块的协同闭环与整体架构

AI Agent的六大核心模块不是相互独立的,而是环环相扣、相互协同的完整闭环系统。每个模块都有自己的核心职责,同时又为其他模块提供支撑,共同构成了Agent的完整智能体系。

六大模块的协同闭环流程如下:

  1. 用户输入目标后,规划模块首先从记忆模块中获取相关的历史经验和同类任务的执行模板,将整体目标拆解为可执行的子任务序列,生成完整的执行计划。
  2. 行动执行模块根据规划模块生成的执行计划,将子任务转化为原子化的执行动作,按照顺序依次执行。
  3. 执行过程中需要调用外部能力时,行动执行模块通过工具使用模块,选择合适的工具,校验参数,执行工具调用,获取执行结果,继续推进任务。
  4. 每一步动作执行完成后,反思模块会根据执行结果,判断是否需要触发单步反思,分析执行过程中的问题,生成优化方案,沉淀经验到记忆模块,同时反馈给规划模块,优化后续的执行计划。
  5. 整个任务执行完成后,反思模块触发全局反思,对整个任务的执行全流程进行复盘,沉淀通用经验到长期记忆中,实现Agent能力的持续迭代。
  6. 对于复杂的多角色任务,多智能体协作模块会将整体目标拆解为不同角色智能体的子任务,通过协调者Agent管控整个协作流程,多个智能体并行或串行执行,共享记忆中心的内容,协同完成整体目标。

核心pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>3.2.5</version>
       <relativePath/>
   </parent>
   <groupId>com.jam.demo</groupId>
   <artifactId>agent-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>agent-demo</name>
   <description>AI Agent 核心架构实现</description>
   <properties>
       <java.version>17</java.version>
       <mybatis-plus.version>3.5.7</mybatis-plus.version>
       <fastjson2.version>2.0.52</fastjson2.version>
       <guava.version>33.2.0-jre</guava.version>
       <lombok.version>1.18.30</lombok.version>
       <springdoc.version>2.5.0</springdoc.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-transaction</artifactId>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <scope>runtime</scope>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.springdoc</groupId>
           <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
           <version>${springdoc.version}</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
               <configuration>
                   <excludes>
                       <exclude>
                           <groupId>org.projectlombok</groupId>
                           <artifactId>lombok</artifactId>
                       </exclude>
                   </excludes>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

最终总结

AI Agent的核心,不是大模型本身,而是围绕大模型构建的这套完整的、闭环的、可迭代的智能系统。六大核心模块,分别对应了人类智能的六个核心能力:规划对应目标拆解与路径设计能力,记忆对应经验存储与复用能力,工具使用对应外部世界交互能力,行动对应落地执行能力,反思对应自我迭代能力,多智能体协作对应团队协同能力。

很多开发者在做AI Agent的时候,过度关注大模型的选型和Prompt的优化,却忽略了这套核心架构的搭建。但实际上,决定Agent上限的,从来都不是大模型的能力,而是这套架构的完整性和协同性。一个架构完整、模块协同顺畅的Agent,即使基于普通的开源大模型,也能完成复杂的长程任务;而一个架构残缺、模块割裂的Agent,即使基于最先进的大模型,也只能完成简单的对话,无法形成真正的智能闭环。真正的AI Agent,应该像一个专业的人一样,有目标、有计划、有记忆、会用工具、能执行、会反思、能和他人协作。而六大核心模块,就是构建这个“数字人”的核心骨架。只有把每个模块的设计原理摸透,把每个模块的能力做扎实,把模块之间的协同闭环跑通,才能真正做出能落地、能解决实际问题、能持续迭代的AI Agent。

目录
相关文章
|
4天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
|
22天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
34916 57
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
16天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
15078 44
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
11天前
|
人工智能 JavaScript Ubuntu
低成本搭建AIP自动化写作系统:Hermes保姆级使用教程,长文和逐步实操贴图
我带着怀疑的态度,深度使用了几天,聚焦微信公众号AIP自动化写作场景,写出来的几篇文章,几乎没有什么修改,至少合乎我本人的意愿,而且排版风格,也越来越完善,同样是起码过得了我自己这一关。 这个其实OpenClaw早可以实现了,但是目前我觉得最大的区别是,Hermes会自主总结提炼,并更新你的写作技能。 相信就冲这一点,就值得一试。 这篇帖子主要就Hermes部署使用,作一个非常详细的介绍,几乎一步一贴图。 关于Hermes,无论你赞成哪种声音,我希望都是你自己动手行动过,发自内心的选择!
2945 28
|
1天前
|
云安全 人工智能 安全
|
1月前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
45866 160
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
7天前
|
弹性计算 人工智能 自然语言处理
阿里云Qwen3.6全新开源,三步完成专有版部署!
Qwen3.6是阿里云全新MoE架构大模型系列,稀疏激活显著降低推理成本,兼顾顶尖性能与高性价比;支持多规格、FP8量化、原生Agent及100+语言,开箱即用。

热门文章

最新文章

下一篇
开通oss服务