企业级Multi-Agent协同框架-完整设计文档

企业级 Multi-Agent 协同任务处理框架 ------ 完整设计文档

版本 :v1.1

技术栈 :Spring Boot 3.2.x / Spring AI 1.0.0-M6 / JDK 17 / MySQL 8.0 / MyBatis / Redis 7.x / Qdrant

定位 :开箱即用的企业级多智能体协同框架,新增业务仅需实现 BaseAgent 接口即可接入协作流程


目录


一、架构定位与设计目标

1.1 定位

适用于:企业业务AI中台、自动化办公Agent、数据查询与分析Agent、RAG知识库Agent、代码生成与审核Agent、合同审核与风控Agent。

1.2 核心特点

特性 说明
开箱即用 内置 TaskParseAgent、RetrievalAgent、VerifyAgent、SummaryAgent
动态配置 Agent 元信息存 MySQL,支持后台增删改,无需重启
双模式协作 串行流水线 / 并行执行,由 Orchestrator 统一调度
全生命周期 任务状态机、子任务日志、入参出参快照、耗时追踪
三层记忆 Redis 短期上下文 + MySQL 持久归档 + Qdrant 向量检索
RBAC 权限 Spring Security 6 + 用户-角色-权限模型

1.3 模块分层结构

复制代码
com.enterprise.agent
├── config/              ← SpringAI / Qdrant / Redis / Security / 线程池配置
├── core/                ← Multi-Agent 框架核心引擎(无需业务修改)
│   ├── orchestrator/    ← 任务编排调度器、Agent选择策略
│   ├── agent/           ← BaseAgent 抽象、内置通用Agent实现
│   ├── memory/          ← 记忆管理器(Redis + DB + Qdrant)
│   ├── context/         ← TaskContext 任务上下文
│   ├── exception/       ← 框架统一异常、全局错误码
│   └── strategy/        ← Agent路由策略
├── entity/              ← MySQL 数据库实体
├── mapper/              ← MyBatis Mapper 接口 + XML 映射文件
├── dto/                 ← 请求 DTO、响应 VO
├── service/             ← 业务服务接口 + impl 实现
├── controller/          ← HTTP 接口(用户端 + 管理端)
├── util/                ← 通用工具类
└── prompt/              ← Prompt 模板管理,支持数据库动态加载

1.4 任务状态机

复制代码
PENDING(待分配) → RUNNING(执行中) → SUCCESS(成功) / FAILED(失败)
                       ↓
                  PARTIAL(部分完成) → 继续执行 → SUCCESS
                                         ↓
                                      FAILED → 重试 → CANCELLED(取消)

二、整体架构设计

2.1 核心架构图

复制代码
用户请求 POST /api/agent/task/submit
        │
        ▼
┌─ Spring Security 权限校验 ──────────────────────────┐
│  Token校验 → 角色权限 → 资源授权                      │
└──────────────────────┬──────────────────────────────┘
                       │ 通过
                       ▼
┌─ Orchestrator(编排调度器)─────────────────────────┐
│                                                     │
│  ① 创建 TaskContext(贯穿全生命周期)                 │
│  ② TaskParseAgent → 解析需求,拆解子任务列表          │
│  ③ AgentRouter → 根据子任务类型匹配 Agent             │
│  ④ 判断协作模式:SERIAL(串行) / PARALLEL(并行)        │
│  ⑤ 调度子Agent执行,监控超时,异常重试                │
│  ⑥ 收集所有 AgentResult → SummaryAgent 汇总          │
│  ⑦ 三层记忆持久化(Redis + MySQL + Qdrant)          │
│  ⑧ 返回最终结果                                      │
└─────────────────────────────────────────────────────┘
         │              │              │
         ▼              ▼              ▼
┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ 内置Agent   │ │  业务Agent    │ │  记忆管理器   │
│            │ │              │ │              │
│ TaskParse  │ │ 运营分析Agent │ │ RedisMemory  │
│ Retrieval  │ │ 合同审核Agent │ │ MySQL Audit  │
│ Verify     │ │ 代码生成Agent │ │ Qdrant RAG   │
│ Summary    │ │ (自定义扩展)  │ │              │
└────────────┘ └──────────────┘ └──────────────┘

2.2 Agent 分层抽象

复制代码
BaseAgent(顶层接口)
├── getAgentCode()        ← 与 ai_agent_info.agent_code 匹配
├── execute(TaskContext)  ← 统一执行入口
└── isAsync()             ← 是否支持异步执行

内置通用 Agent:
├── TaskParseAgent     → 任务需求拆解,生成子任务列表
├── RetrievalAgent     → 对接 Qdrant 向量库,RAG 检索
├── VerifyAgent        → 内容校验 & 合规审查
└── SummaryAgent       → 结果汇总摘要

用户自定义 Agent(仅需实现 BaseAgent):
└── 任意业务 Agent

三、数据库完整设计

3.1 表结构清单

表名 说明 所属模块
sys_user 用户表 RBAC权限
sys_role 角色表 RBAC权限
sys_user_role 用户角色关联 RBAC权限
sys_permission 权限表 RBAC权限
sys_role_permission 角色权限关联 RBAC权限
ai_agent_info Agent基础信息 Agent管理
ai_task 主任务表 任务管理
ai_task_sub 子任务表 任务管理
ai_prompt_template 提示词模板 Prompt管理
ai_knowledge_doc 知识库文档 知识库
ai_knowledge_vector 向量映射表 知识库

3.2 完整建表 SQL

sql 复制代码
-- ============================================================
-- 企业级 Multi-Agent 协同任务处理框架 ------ 完整建表 SQL
-- 数据库:MySQL 8.0+
-- ============================================================

CREATE DATABASE IF NOT EXISTS enterprise_agent
    DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;

USE enterprise_agent;

-- ============================================================
-- 一、RBAC 权限基础表(5张)
-- ============================================================

-- 1. 用户表
CREATE TABLE sys_user (
    id              BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    username        VARCHAR(64)  NOT NULL              COMMENT '用户名',
    password        VARCHAR(256) NOT NULL              COMMENT 'BCrypt加密密码',
    real_name       VARCHAR(64)  DEFAULT ''            COMMENT '真实姓名',
    email           VARCHAR(128) DEFAULT ''            COMMENT '邮箱',
    phone           VARCHAR(20)  DEFAULT ''            COMMENT '手机号',
    dept_id         BIGINT       DEFAULT 0             COMMENT '所属部门ID',
    status          TINYINT      DEFAULT 1             COMMENT '1-正常 0-禁用',
    last_login_time DATETIME     DEFAULT NULL          COMMENT '最后登录时间',
    is_deleted      TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by       VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time     DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by       VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time     DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_username (username),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 2. 角色表
CREATE TABLE sys_role (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    role_code   VARCHAR(64)  NOT NULL              COMMENT '角色编码 ROLE_ADMIN/ROLE_USER等',
    role_name   VARCHAR(64)  NOT NULL              COMMENT '角色名称',
    description VARCHAR(256) DEFAULT ''            COMMENT '角色描述',
    status      TINYINT      DEFAULT 1             COMMENT '1-正常 0-禁用',
    is_deleted  TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by   VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by   VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_role_code (role_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

-- 3. 用户角色关联表
CREATE TABLE sys_user_role (
    id      BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
    user_id BIGINT NOT NULL                    COMMENT '用户ID',
    role_id BIGINT NOT NULL                    COMMENT '角色ID',
    UNIQUE INDEX uk_user_role (user_id, role_id),
    INDEX idx_user_id (user_id),
    INDEX idx_role_id (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

-- 4. 权限表
CREATE TABLE sys_permission (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    perm_code   VARCHAR(128) NOT NULL              COMMENT '权限编码',
    perm_name   VARCHAR(64)  NOT NULL              COMMENT '权限名称',
    perm_type   VARCHAR(32)  DEFAULT 'API'         COMMENT 'MENU/BUTTON/API',
    parent_id   BIGINT       DEFAULT 0             COMMENT '父级ID',
    path        VARCHAR(256) DEFAULT ''            COMMENT '路由/接口路径',
    sort_order  INT          DEFAULT 0             COMMENT '排序',
    is_deleted  TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by   VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by   VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_perm_code (perm_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

-- 5. 角色权限关联表
CREATE TABLE sys_role_permission (
    id      BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
    role_id BIGINT NOT NULL                    COMMENT '角色ID',
    perm_id BIGINT NOT NULL                    COMMENT '权限ID',
    UNIQUE INDEX uk_role_perm (role_id, perm_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
sql 复制代码
-- ============================================================
-- 二、Agent 核心业务表(6张)
-- ============================================================

-- 6. Agent基础信息表
CREATE TABLE ai_agent_info (
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    agent_code          VARCHAR(64)  NOT NULL              COMMENT 'Agent唯一编码',
    agent_name          VARCHAR(128) NOT NULL              COMMENT 'Agent名称',
    agent_type          VARCHAR(32)  DEFAULT 'CUSTOM'      COMMENT 'BUILTIN-内置 CUSTOM-自定义',
    agent_category      VARCHAR(32)  DEFAULT 'BUSINESS'    COMMENT 'PARSE/RETRIEVAL/VERIFY/SUMMARY/BUSINESS',
    description         VARCHAR(512) DEFAULT ''            COMMENT 'Agent功能描述',
    execute_mode        VARCHAR(16)  DEFAULT 'SYNC'        COMMENT 'SYNC-同步 ASYNC-异步',
    prompt_template_id  BIGINT       DEFAULT NULL          COMMENT '关联提示词模板ID',
    sort_order          INT          DEFAULT 0             COMMENT '排序',
    status              TINYINT      DEFAULT 1             COMMENT '1-启用 0-停用',
    is_deleted          TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by           VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time         DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by           VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_agent_code (agent_code),
    INDEX idx_agent_type (agent_type),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Agent基础信息表';

-- 7. 主任务表
CREATE TABLE ai_task (
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    task_no             VARCHAR(64)  NOT NULL              COMMENT '任务编号 TASK-YYYYMMDD-序号',
    user_id             BIGINT       NOT NULL              COMMENT '发起用户ID',
    task_title          VARCHAR(256) DEFAULT ''            COMMENT '任务标题',
    task_content        TEXT                               COMMENT '任务原始内容(用户输入)',
    task_type           VARCHAR(32)  DEFAULT 'GENERAL'     COMMENT '任务类型',
    cooperate_mode      VARCHAR(16)  DEFAULT 'SERIAL'      COMMENT 'SERIAL-串行 PARALLEL-并行',
    task_status         VARCHAR(16)  DEFAULT 'PENDING'     COMMENT 'PENDING/RUNNING/PARTIAL/SUCCESS/FAILED/CANCELLED',
    priority            INT          DEFAULT 2             COMMENT '1-低 2-中 3-高 4-紧急',
    timeout_seconds     INT          DEFAULT 300           COMMENT '超时时间(秒)',
    retry_times         INT          DEFAULT 0             COMMENT '已重试次数',
    max_retry           INT          DEFAULT 3             COMMENT '最大重试次数',
    start_time          DATETIME     DEFAULT NULL          COMMENT '开始时间',
    end_time            DATETIME     DEFAULT NULL          COMMENT '结束时间',
    total_duration_ms   BIGINT       DEFAULT 0             COMMENT '总耗时(毫秒)',
    result_summary      TEXT                               COMMENT '结果摘要',
    error_msg           TEXT                               COMMENT '错误信息',
    is_deleted          TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by           VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time         DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by           VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_task_no (task_no),
    INDEX idx_user_id (user_id),
    INDEX idx_task_status (task_status),
    INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主任务表';

-- 8. 子任务表(每个Agent单次执行记录)
CREATE TABLE ai_task_sub (
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    task_id             BIGINT       NOT NULL              COMMENT '主任务ID',
    task_no             VARCHAR(64)  NOT NULL              COMMENT '主任务编号',
    sub_task_no         VARCHAR(64)  NOT NULL              COMMENT '子任务编号 TASK-xxx-SUB-序号',
    agent_code          VARCHAR(64)  NOT NULL              COMMENT '执行的Agent编码',
    agent_name          VARCHAR(128) DEFAULT ''            COMMENT 'Agent名称(快照)',
    parent_sub_id       BIGINT       DEFAULT NULL          COMMENT '上级子任务ID(串行依赖)',
    execute_order       INT          DEFAULT 1             COMMENT '执行顺序',
    sub_task_content    TEXT                               COMMENT '子任务内容',
    sub_task_status     VARCHAR(16)  DEFAULT 'PENDING'     COMMENT 'PENDING/RUNNING/SUCCESS/FAILED/SKIPPED',
    input_snapshot      JSON         DEFAULT NULL          COMMENT '入参快照(JSON)',
    output_snapshot     JSON         DEFAULT NULL          COMMENT '出参快照(JSON)',
    start_time          DATETIME     DEFAULT NULL          COMMENT '开始时间',
    end_time            DATETIME     DEFAULT NULL          COMMENT '结束时间',
    duration_ms         BIGINT       DEFAULT 0             COMMENT '执行耗时(毫秒)',
    error_msg           TEXT                               COMMENT '错误信息',
    retry_count         INT          DEFAULT 0             COMMENT '当前重试次数',
    is_deleted          TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by           VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time         DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by           VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_sub_task_no (sub_task_no),
    INDEX idx_task_id (task_id),
    INDEX idx_task_no (task_no),
    INDEX idx_agent_code (agent_code),
    INDEX idx_status (sub_task_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='子任务表';

-- 9. 提示词模板表
CREATE TABLE ai_prompt_template (
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    template_code       VARCHAR(64)  NOT NULL              COMMENT '模板编码',
    template_name       VARCHAR(128) NOT NULL              COMMENT '模板名称',
    template_type       VARCHAR(32)  DEFAULT 'SYSTEM'      COMMENT 'SYSTEM/USER/TOOL',
    agent_code          VARCHAR(64)  DEFAULT ''            COMMENT '关联Agent编码',
    template_content    TEXT         NOT NULL              COMMENT '模板内容(支持{{variable}}占位符)',
    variables           VARCHAR(512) DEFAULT ''            COMMENT '模板变量列表(逗号分隔)',
    version             INT          DEFAULT 1             COMMENT '版本号',
    status              TINYINT      DEFAULT 1             COMMENT '1-启用 0-停用',
    is_deleted          TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by           VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time         DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by           VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    UNIQUE INDEX uk_template_code (template_code),
    INDEX idx_agent_code (agent_code),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='提示词模板表';

-- 10. 知识库原始文档表
CREATE TABLE ai_knowledge_doc (
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    doc_title           VARCHAR(256) NOT NULL              COMMENT '文档标题',
    doc_content         LONGTEXT                           COMMENT '文档原始内容',
    doc_type            VARCHAR(32)  DEFAULT 'TXT'         COMMENT 'TXT/PDF/MD/HTML',
    doc_size            BIGINT       DEFAULT 0             COMMENT '文档大小(字节)',
    chunk_count         INT          DEFAULT 0             COMMENT '分块数量',
    qdrant_collection   VARCHAR(64)  DEFAULT 'enterprise_knowledge' COMMENT 'Qdrant集合名称',
    status              TINYINT      DEFAULT 0             COMMENT '1-已向量化 0-未向量化',
    is_deleted          TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by           VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time         DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by           VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    INDEX idx_doc_type (doc_type),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库原始文档表';

-- 11. 向量关联映射表
CREATE TABLE ai_knowledge_vector (
    id                  BIGINT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
    doc_id              BIGINT       NOT NULL              COMMENT '关联文档ID',
    qdrant_point_id     VARCHAR(128) NOT NULL              COMMENT 'Qdrant向量Point ID',
    chunk_index         INT          DEFAULT 0             COMMENT '分块序号',
    chunk_content       TEXT                               COMMENT '分块文本内容',
    embedding_model     VARCHAR(64)  DEFAULT 'text-embedding-v3' COMMENT '向量化模型',
    token_count         INT          DEFAULT 0             COMMENT 'Token数量',
    is_deleted          TINYINT      DEFAULT 0             COMMENT '逻辑删除',
    create_by           VARCHAR(64)  DEFAULT ''            COMMENT '创建人',
    create_time         DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_by           VARCHAR(64)  DEFAULT ''            COMMENT '更新人',
    update_time         DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    INDEX idx_doc_id (doc_id),
    UNIQUE INDEX uk_qdrant_point_id (qdrant_point_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='向量关联映射表';

3.3 模拟数据 SQL

sql 复制代码
-- ============================================================
-- 模拟数据
-- ============================================================

USE enterprise_agent;

-- -------------------- 用户(密码均为BCrypt加密的"123456") --------------------
INSERT INTO sys_user (id, username, password, real_name, email, phone, dept_id, status, create_by) VALUES
(1, 'admin',    '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', '系统管理员', 'admin@company.com',    '13800000001', 1, 1, 'system'),
(2, 'zhangsan', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', '张三',       'zhangsan@company.com', '13800000002', 2, 1, 'admin'),
(3, 'lisi',     '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', '李四',       'lisi@company.com',     '13800000003', 2, 1, 'admin'),
(4, 'wangwu',   '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', '王五',       'wangwu@company.com',   '13800000004', 3, 1, 'admin');

-- -------------------- 角色 --------------------
INSERT INTO sys_role (id, role_code, role_name, description, create_by) VALUES
(1, 'ROLE_ADMIN',   '超级管理员', '拥有所有权限',         'system'),
(2, 'ROLE_MANAGER', '部门经理',   '管理部门业务与数据',   'admin'),
(3, 'ROLE_ANALYST', '数据分析师', '可进行数据查询与分析', 'admin'),
(4, 'ROLE_USER',    '普通用户',   '基础功能使用',         'admin');

-- -------------------- 用户角色关联 --------------------
INSERT INTO sys_user_role (user_id, role_id) VALUES
(1, 1), (2, 2), (2, 3), (3, 3), (4, 4);

-- -------------------- 权限 --------------------
INSERT INTO sys_permission (id, perm_code, perm_name, perm_type, path, sort_order, create_by) VALUES
(1, 'agent:task:submit',    '提交任务',   'API', '/api/agent/task/submit',   1, 'system'),
(2, 'agent:task:query',     '查询任务',   'API', '/api/agent/task/query/**', 2, 'system'),
(3, 'agent:task:cancel',    '取消任务',   'API', '/api/agent/task/cancel',   3, 'system'),
(4, 'agent:admin:agent',    'Agent管理',  'API', '/api/admin/agent/**',      4, 'system'),
(5, 'agent:admin:prompt',   '提示词管理', 'API', '/api/admin/prompt/**',     5, 'system'),
(6, 'agent:knowledge:upload','知识库上传', 'API', '/api/knowledge/upload',    6, 'system'),
(7, 'agent:knowledge:query', '知识库查询', 'API', '/api/knowledge/search',    7, 'system');

-- -------------------- 角色权限关联(admin拥有全部权限) --------------------
INSERT INTO sys_role_permission (role_id, perm_id) VALUES
(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7);

-- -------------------- Agent基础信息(4个内置Agent) --------------------
INSERT INTO ai_agent_info (id, agent_code, agent_name, agent_type, agent_category, description, execute_mode, sort_order, status, create_by) VALUES
(1, 'TASK_PARSE',  '任务解析Agent',  'BUILTIN', 'PARSE',     '解析用户需求,拆解为原子子任务列表',                  'SYNC',  1, 1, 'system'),
(2, 'RETRIEVAL',   '知识检索Agent',  'BUILTIN', 'RETRIEVAL', '对接Qdrant向量库,执行RAG检索,返回相关知识片段',     'SYNC',  2, 1, 'system'),
(3, 'VERIFY',      '内容校验Agent',  'BUILTIN', 'VERIFY',    '校验Agent输出内容的合规性、准确性、完整性',           'SYNC',  3, 1, 'system'),
(4, 'SUMMARY',     '结果汇总Agent',  'BUILTIN', 'SUMMARY',   '汇总所有子Agent输出结果,生成统一格式的最终响应',     'SYNC',  4, 1, 'system');

-- -------------------- 提示词模板 --------------------
INSERT INTO ai_prompt_template (id, template_code, template_name, template_type, agent_code, template_content, variables, create_by) VALUES
(1, 'PROMPT_TASK_PARSE', '任务解析提示词', 'SYSTEM', 'TASK_PARSE',
'你是一个专业的任务解析器。请将用户的原始需求拆解为可独立执行的原子子任务。
每个子任务需包含:任务类型、任务内容、所需Agent类型、依赖关系。
以JSON数组格式返回:[{"taskType":"...","content":"...","agentCategory":"...","dependsOn":null}]',
'userInput', 'system'),

(2, 'PROMPT_RETRIEVAL', '知识检索提示词', 'SYSTEM', 'RETRIEVAL',
'你是一个知识检索专家。根据用户问题,从知识库中检索最相关的文档片段。
请基于检索到的知识内容回答用户问题,如果知识库中没有相关信息,请如实告知。
检索到的知识内容:{{retrievedContent}}
用户问题:{{userQuestion}}',
'retrievedContent,userQuestion', 'system'),

(3, 'PROMPT_VERIFY', '内容校验提示词', 'SYSTEM', 'VERIFY',
'你是一个内容审核专家。请校验以下Agent输出内容的合规性、准确性和完整性。
校验维度:事实准确性、逻辑一致性、合规风险、敏感信息泄露。
输出JSON格式:{"pass":true/false,"issues":["问题1","问题2"],"score":0-100}',
'contentToVerify', 'system'),

(4, 'PROMPT_SUMMARY', '结果汇总提示词', 'SYSTEM', 'SUMMARY',
'你是一个结果汇总专家。请将多个子Agent的输出结果整合为一份完整、清晰、专业的最终报告。
用户原始需求:{{originalRequest}}
子任务执行结果:{{subTaskResults}}
请按逻辑结构组织内容,包含:执行摘要、详细分析、结论建议。',
'originalRequest,subTaskResults', 'system');

四、核心代码实现

4.1 BaseAgent 顶层接口

java 复制代码
package com.enterprise.agent.core.agent;

import com.enterprise.agent.core.context.TaskContext;

/**
 * Multi-Agent 框架顶层抽象接口
 * 所有业务Agent必须实现此接口,统一标准入参、出参、执行方法
 *
 * 使用方式:新增业务Agent只需实现此接口,并在 ai_agent_info 表中注册即可
 */
public interface BaseAgent {

    /**
     * 获取Agent唯一编码,与 ai_agent_info.agent_code 匹配
     * Orchestrator 通过此编码从数据库中查找Agent配置并路由任务
     *
     * @return Agent编码,如 "TASK_PARSE"、"RETRIEVAL"、"DATA_ANALYSIS"
     */
    String getAgentCode();

    /**
     * Agent执行入口
     * 接收任务上下文,执行业务逻辑,返回执行结果
     *
     * @param context 任务上下文(贯穿整个任务生命周期,包含任务信息、用户信息、前置结果等)
     * @return AgentResult 执行结果(包含成功/失败状态、输出内容、耗时等)
     */
    AgentResult execute(TaskContext context);

    /**
     * 是否允许异步执行
     * 默认同步执行。如果Agent逻辑不依赖前序结果且无副作用,可返回 true
     *
     * @return true-异步 false-同步(默认)
     */
    default boolean isAsync() {
        return false;
    }
}

4.2 TaskContext 任务上下文

java 复制代码
package com.enterprise.agent.core.context;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 任务上下文 ------ 贯穿整个Multi-Agent任务生命周期
 * 承载:任务元信息、用户信息、子任务列表、各Agent执行结果、全局共享数据
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TaskContext {

    // ===== 任务元信息 =====
    private Long taskId;
    private String taskNo;
    private String taskTitle;
    private String userInput;
    private String cooperateMode;
    private Integer priority;

    // ===== 用户信息 =====
    private Long userId;
    private String username;
    private List<String> roles;

    // ===== 子任务与结果 =====
    private List<SubTask> subTasks;
    private Map<String, AgentResult> agentResults;

    // ===== 全局共享数据(线程安全) =====
    private ConcurrentHashMap<String, Object> sharedData;

    // ===== 时间追踪 =====
    private LocalDateTime startTime;
    private LocalDateTime endTime;

    /**
     * 子任务定义
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class SubTask {
        private int order;
        private String content;
        private String agentCategory;
        private Integer dependsOn;
        private String matchedAgentCode;
    }

    public void putSharedData(String key, Object value) {
        if (this.sharedData == null) {
            this.sharedData = new ConcurrentHashMap<>();
        }
        this.sharedData.put(key, value);
    }

    @SuppressWarnings("unchecked")
    public <T> T getSharedData(String key) {
        if (this.sharedData == null) return null;
        return (T) this.sharedData.get(key);
    }

    public void recordAgentResult(String agentCode, AgentResult result) {
        if (this.agentResults == null) {
            this.agentResults = new ConcurrentHashMap<>();
        }
        this.agentResults.put(agentCode, result);
    }
}

4.3 AgentResult 执行结果

java 复制代码
package com.enterprise.agent.core.agent;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Agent 执行结果
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AgentResult {

    private boolean success;
    private String agentCode;
    private String agentName;
    private String output;
    private String errorMsg;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private long durationMs;

    public static AgentResult success(String agentCode, String agentName, String output) {
        return AgentResult.builder()
                .success(true)
                .agentCode(agentCode)
                .agentName(agentName)
                .output(output)
                .startTime(LocalDateTime.now())
                .endTime(LocalDateTime.now())
                .build();
    }

    public static AgentResult fail(String agentCode, String agentName, String errorMsg) {
        return AgentResult.builder()
                .success(false)
                .agentCode(agentCode)
                .agentName(agentName)
                .errorMsg(errorMsg)
                .startTime(LocalDateTime.now())
                .endTime(LocalDateTime.now())
                .build();
    }
}

4.4 Orchestrator 编排调度器(核心引擎)

java 复制代码
package com.enterprise.agent.core.orchestrator;

import com.enterprise.agent.core.agent.AgentResult;
import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.core.exception.AgentException;
import com.enterprise.agent.core.memory.MemoryManager;
import com.enterprise.agent.core.strategy.AgentRouter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 编排调度器(中央大脑)
 *
 * 完整执行流程:
 *   ① 接收用户原始任务请求,创建 TaskContext
 *   ② 调用 TaskParseAgent 拆解任务为子任务列表
 *   ③ 通过 AgentRouter 为每个子任务匹配合适的Agent
 *   ④ 按协作模式调度执行(SERIAL串行 / PARALLEL并行)
 *   ⑤ 收集所有 AgentResult → SummaryAgent 汇总
 *   ⑥ 三层记忆持久化(Redis + MySQL + Qdrant)
 *   ⑦ 返回最终结果
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class Orchestrator {

    private final AgentRouter agentRouter;
    private final MemoryManager memoryManager;

    @org.springframework.beans.factory.annotation.Qualifier("agentExecutor")
    private final Executor agentExecutor;

    /**
     * 编排执行主入口
     *
     * @param context 已初始化的任务上下文(含用户输入、用户信息等)
     * @return 最终汇总结果
     */
    public String orchestrate(TaskContext context) {
        log.info("[Orchestrator] 开始编排任务, taskNo={}, mode={}",
                context.getTaskNo(), context.getCooperateMode());

        context.setStartTime(LocalDateTime.now());

        try {
            // ===== 步骤1: 任务拆解 =====
            List<TaskContext.SubTask> subTasks = parseTask(context);
            context.setSubTasks(subTasks);
            log.info("[Orchestrator] 任务拆解完成, subTaskCount={}", subTasks.size());

            // ===== 步骤2: Agent路由匹配 =====
            for (TaskContext.SubTask subTask : subTasks) {
                String agentCode = agentRouter.route(subTask);
                subTask.setMatchedAgentCode(agentCode);
                log.info("[Orchestrator] 子任务[{}] 路由到 Agent: {}", subTask.getOrder(), agentCode);
            }

            // ===== 步骤3: 按协作模式执行 =====
            if ("PARALLEL".equalsIgnoreCase(context.getCooperateMode())) {
                executeParallel(context, subTasks);
            } else {
                executeSerial(context, subTasks);
            }

            // ===== 步骤4: 结果汇总 =====
            String finalResult = summarize(context);
            context.setEndTime(LocalDateTime.now());
            long duration = java.time.Duration.between(
                    context.getStartTime(), context.getEndTime()).toMillis();
            log.info("[Orchestrator] 任务执行完成, taskNo={}, duration={}ms",
                    context.getTaskNo(), duration);

            // ===== 步骤5: 三层记忆持久化 =====
            memoryManager.persistAll(context);

            return finalResult;

        } catch (Exception e) {
            log.error("[Orchestrator] 任务执行异常, taskNo={}", context.getTaskNo(), e);
            context.setEndTime(LocalDateTime.now());
            throw new AgentException("TASK_EXEC_FAILED", "任务执行失败: " + e.getMessage(), e);
        }
    }

    /**
     * 串行执行(流水线模式)
     * 子任务按顺序逐个执行,前一个的输出通过 sharedData 传递给后一个
     */
    private void executeSerial(TaskContext context, List<TaskContext.SubTask> subTasks) {
        log.info("[Orchestrator] 串行执行模式, subTaskCount={}", subTasks.size());

        for (TaskContext.SubTask subTask : subTasks) {
            String agentCode = subTask.getMatchedAgentCode();
            BaseAgent agent = agentRouter.getAgent(agentCode);
            if (agent == null) {
                log.warn("[Orchestrator] Agent未找到, agentCode={}, 跳过子任务[{}]",
                        agentCode, subTask.getOrder());
                continue;
            }

            // 将前置子任务输出作为当前子任务输入
            if (subTask.getDependsOn() != null) {
                String prevOutput = context.getSharedData(
                        "subTask_" + subTask.getDependsOn() + "_output");
                context.putSharedData("previous_output", prevOutput);
            }

            AgentResult result = executeWithTimeout(agent, context, context.getPriority());
            context.recordAgentResult(agentCode, result);
            context.putSharedData("subTask_" + subTask.getOrder() + "_output",
                    result.getOutput());

            // 失败处理
            if (!result.isSuccess() && subTask.getOrder() < subTasks.size()) {
                log.warn("[Orchestrator] 子任务[{}]失败,中断串行流水线", subTask.getOrder());
                break;
            }
        }
    }

    /**
     * 并行执行
     * 无依赖的子任务同时提交到线程池
     */
    private void executeParallel(TaskContext context, List<TaskContext.SubTask> subTasks) {
        log.info("[Orchestrator] 并行执行模式, subTaskCount={}", subTasks.size());

        List<CompletableFuture<Void>> futures = new ArrayList<>();
        for (TaskContext.SubTask subTask : subTasks) {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                String agentCode = subTask.getMatchedAgentCode();
                BaseAgent agent = agentRouter.getAgent(agentCode);
                if (agent == null) return;
                AgentResult result = executeWithTimeout(agent, context, context.getPriority());
                context.recordAgentResult(agentCode, result);
            }, agentExecutor);
            futures.add(future);
        }

        try {
            // 使用任务配置的超时时间,兜底300秒
            long parallelTimeout = context.getPriority() >= 3 ? 120 : 300;
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                    .get(parallelTimeout, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            log.warn("[Orchestrator] 并行执行超时");
        } catch (Exception e) {
            log.error("[Orchestrator] 并行执行异常", e);
        }
    }

    /**
     * 带超时控制的Agent执行
     */
    private AgentResult executeWithTimeout(BaseAgent agent, TaskContext context, Integer priority) {
        long timeout = priority >= 3 ? 120 : 300;
        try {
            if (agent.isAsync()) {
                return agent.execute(context);
            }
            Future<AgentResult> future = agentExecutor.submit(() -> agent.execute(context));
            return future.get(timeout, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            log.error("[Orchestrator] Agent[{}] 执行超时", agent.getAgentCode());
            return AgentResult.fail(agent.getAgentCode(), agent.getAgentCode(),
                    "执行超时(" + timeout + "秒)");
        } catch (Exception e) {
            log.error("[Orchestrator] Agent[{}] 执行异常", agent.getAgentCode(), e);
            return AgentResult.fail(agent.getAgentCode(), agent.getAgentCode(), e.getMessage());
        }
    }

    /**
     * 调用 TaskParseAgent 拆解任务
     */
    private List<TaskContext.SubTask> parseTask(TaskContext context) {
        BaseAgent parseAgent = agentRouter.getAgent("TASK_PARSE");
        if (parseAgent == null) {
            return List.of(TaskContext.SubTask.builder()
                    .order(1).content(context.getUserInput())
                    .agentCategory("BUSINESS").dependsOn(null).build());
        }
        AgentResult result = parseAgent.execute(context);
        return parseSubTaskList(result.getOutput());
    }

    /**
     * 调用 SummaryAgent 汇总结果
     */
    private String summarize(TaskContext context) {
        BaseAgent summaryAgent = agentRouter.getAgent("SUMMARY");
        if (summaryAgent == null) {
            StringBuilder sb = new StringBuilder();
            Map<String, AgentResult> results = context.getAgentResults();
            if (results != null) {
                results.forEach((code, r) ->
                        sb.append("[").append(code).append("]: ")
                          .append(r.getOutput()).append("\n"));
            }
            return sb.toString();
        }
        return summaryAgent.execute(context).getOutput();
    }

    private static final com.fasterxml.jackson.databind.ObjectMapper OBJECT_MAPPER =
            new com.fasterxml.jackson.databind.ObjectMapper();

    /**
     * 解析子任务JSON列表
     */
    @SuppressWarnings("unchecked")
    private List<TaskContext.SubTask> parseSubTaskList(String json) {
        try {
            List<Map<String, Object>> rawList = OBJECT_MAPPER.readValue(json, List.class);
            List<TaskContext.SubTask> result = new ArrayList<>();
            for (int i = 0; i < rawList.size(); i++) {
                Map<String, Object> item = rawList.get(i);
                result.add(TaskContext.SubTask.builder()
                        .order(i + 1)
                        .content((String) item.getOrDefault("content", ""))
                        .agentCategory((String) item.getOrDefault("agentCategory", "BUSINESS"))
                        .dependsOn(item.get("dependsOn") != null
                                ? Integer.valueOf(item.get("dependsOn").toString()) : null)
                        .build());
            }
            return result;
        } catch (Exception e) {
            log.warn("[Orchestrator] 子任务JSON解析失败,使用默认拆分", e);
            return List.of(TaskContext.SubTask.builder()
                    .order(1).content(json).agentCategory("BUSINESS").build());
        }
    }
}

4.5 内置通用 Agent 实现

4.5.1 TaskParseAgent(任务解析)
java 复制代码
package com.enterprise.agent.core.agent.impl;

import com.enterprise.agent.core.agent.AgentResult;
import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.prompt.PromptTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

/**
 * 任务解析Agent
 * 职责:将用户原始需求拆解为可独立执行的原子子任务列表
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TaskParseAgent implements BaseAgent {

    private final ChatClient chatClient;
    private final PromptTemplateService promptTemplateService;

    @Override
    public String getAgentCode() {
        return "TASK_PARSE";
    }

    @Override
    public AgentResult execute(TaskContext context) {
        long start = System.currentTimeMillis();
        log.info("[TaskParseAgent] 开始解析任务, taskNo={}", context.getTaskNo());

        try {
            String prompt = promptTemplateService.loadTemplate("PROMPT_TASK_PARSE");
            if (prompt == null) {
                prompt = getDefaultPrompt();
            }

            String result = chatClient.prompt()
                    .system(prompt)
                    .user(context.getUserInput())
                    .call()
                    .content();

            long duration = System.currentTimeMillis() - start;
            log.info("[TaskParseAgent] 解析完成, duration={}ms", duration);

            return AgentResult.builder()
                    .success(true)
                    .agentCode(getAgentCode())
                    .agentName("任务解析Agent")
                    .output(result)
                    .durationMs(duration)
                    .build();

        } catch (Exception e) {
            log.error("[TaskParseAgent] 解析失败", e);
            long duration = System.currentTimeMillis() - start;
            return AgentResult.builder()
                    .success(false)
                    .agentCode(getAgentCode())
                    .agentName("任务解析Agent")
                    .errorMsg(e.getMessage())
                    .durationMs(duration)
                    .build();
        }
    }

    private String getDefaultPrompt() {
        return """
            你是一个专业的任务解析器。将用户需求拆解为可独立执行的原子子任务。
            以JSON数组返回:[{"taskType":"...","content":"...","agentCategory":"...","dependsOn":null}]
            agentCategory可选值:RETRIEVAL(知识检索)、VERIFY(内容校验)、SUMMARY(结果汇总)、BUSINESS(业务处理)
            """;
    }
}
4.5.2 RetrievalAgent(知识库RAG检索)
java 复制代码
package com.enterprise.agent.core.agent.impl;

import com.enterprise.agent.core.agent.AgentResult;
import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.prompt.PromptTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;

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

/**
 * 知识库RAG检索Agent
 * 职责:对接Qdrant向量库,执行语义检索,返回相关知识片段
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RetrievalAgent implements BaseAgent {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;
    private final PromptTemplateService promptTemplateService;

    @Override
    public String getAgentCode() {
        return "RETRIEVAL";
    }

    @Override
    public AgentResult execute(TaskContext context) {
        long start = System.currentTimeMillis();
        log.info("[RetrievalAgent] 开始RAG检索, taskNo={}", context.getTaskNo());

        try {
            String query = context.getUserInput();
            List<Document> docs = vectorStore.similaritySearch(
                    SearchRequest.builder()
                            .query(query)
                            .topK(5)
                            .similarityThreshold(0.7)
                            .build()
            );

            if (docs.isEmpty()) {
                return AgentResult.success(getAgentCode(), "知识检索Agent",
                        "未检索到相关知识内容。");
            }

            String retrievedContent = docs.stream()
                    .map(Document::getText)
                    .collect(Collectors.joining("\n---\n"));

            String prompt = promptTemplateService.loadTemplate("PROMPT_RETRIEVAL");
            if (prompt == null) {
                prompt = "基于以下知识回答问题:\n{{retrievedContent}}\n\n问题:{{userQuestion}}";
            }
            prompt = prompt.replace("{{retrievedContent}}", retrievedContent)
                    .replace("{{userQuestion}}", query);

            String result = chatClient.prompt()
                    .system(prompt)
                    .user(query)
                    .call()
                    .content();

            long duration = System.currentTimeMillis() - start;
            log.info("[RetrievalAgent] RAG检索完成, docCount={}, duration={}ms", docs.size(), duration);

            return AgentResult.builder()
                    .success(true)
                    .agentCode(getAgentCode())
                    .agentName("知识检索Agent")
                    .output(result)
                    .durationMs(duration)
                    .build();

        } catch (Exception e) {
            log.error("[RetrievalAgent] 检索失败", e);
            long duration = System.currentTimeMillis() - start;
            return AgentResult.builder()
                    .success(false)
                    .agentCode(getAgentCode())
                    .agentName("知识检索Agent")
                    .errorMsg(e.getMessage())
                    .durationMs(duration)
                    .build();
        }
    }
}
4.5.3 VerifyAgent(内容校验)
java 复制代码
package com.enterprise.agent.core.agent.impl;

import com.enterprise.agent.core.agent.AgentResult;
import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.prompt.PromptTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

/**
 * 内容校验Agent
 * 职责:校验Agent输出内容的合规性、准确性、完整性
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class VerifyAgent implements BaseAgent {

    private final ChatClient chatClient;
    private final PromptTemplateService promptTemplateService;

    @Override
    public String getAgentCode() {
        return "VERIFY";
    }

    @Override
    public AgentResult execute(TaskContext context) {
        long start = System.currentTimeMillis();
        log.info("[VerifyAgent] 开始内容校验, taskNo={}", context.getTaskNo());

        try {
            String contentToVerify = context.getSharedData("content_to_verify");
            if (contentToVerify == null) {
                StringBuilder sb = new StringBuilder();
                if (context.getAgentResults() != null) {
                    context.getAgentResults().forEach((code, r) -> {
                        if (r.isSuccess() && !"VERIFY".equals(code) && !"SUMMARY".equals(code)) {
                            sb.append("[").append(code).append("]: ").append(r.getOutput()).append("\n");
                        }
                    });
                }
                contentToVerify = sb.toString();
            }

            if (contentToVerify == null || contentToVerify.isBlank()) {
                return AgentResult.success(getAgentCode(), "内容校验Agent",
                        "{\"pass\":true,\"issues\":[],\"score\":100}");
            }

            String prompt = promptTemplateService.loadTemplate("PROMPT_VERIFY");
            if (prompt == null) {
                prompt = "校验内容:\n{{contentToVerify}}\n输出JSON: {\"pass\":bool,\"issues\":[],\"score\":0-100}";
            }
            prompt = prompt.replace("{{contentToVerify}}", contentToVerify);

            String result = chatClient.prompt()
                    .system(prompt)
                    .user(contentToVerify)
                    .call()
                    .content();

            long duration = System.currentTimeMillis() - start;
            log.info("[VerifyAgent] 校验完成, duration={}ms", duration);

            return AgentResult.builder()
                    .success(true)
                    .agentCode(getAgentCode())
                    .agentName("内容校验Agent")
                    .output(result)
                    .durationMs(duration)
                    .build();

        } catch (Exception e) {
            log.error("[VerifyAgent] 校验失败", e);
            long duration = System.currentTimeMillis() - start;
            return AgentResult.builder()
                    .success(false)
                    .agentCode(getAgentCode())
                    .agentName("内容校验Agent")
                    .errorMsg(e.getMessage())
                    .durationMs(duration)
                    .build();
        }
    }
}
4.5.4 SummaryAgent(结果汇总)
java 复制代码
package com.enterprise.agent.core.agent.impl;

import com.enterprise.agent.core.agent.AgentResult;
import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.prompt.PromptTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 结果汇总Agent
 * 职责:汇总所有子Agent输出结果,生成统一格式的最终响应
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SummaryAgent implements BaseAgent {

    private final ChatClient chatClient;
    private final PromptTemplateService promptTemplateService;

    @Override
    public String getAgentCode() {
        return "SUMMARY";
    }

    @Override
    public AgentResult execute(TaskContext context) {
        long start = System.currentTimeMillis();
        log.info("[SummaryAgent] 开始结果汇总, taskNo={}", context.getTaskNo());

        try {
            Map<String, AgentResult> results = context.getAgentResults();
            if (results == null || results.isEmpty()) {
                return AgentResult.success(getAgentCode(), "结果汇总Agent", "无子Agent执行结果。");
            }

            StringBuilder subTaskResults = new StringBuilder();
            results.forEach((code, r) -> {
                if (!"SUMMARY".equals(code)) {
                    subTaskResults.append("【").append(r.getAgentName())
                            .append("(").append(code).append(")】\n")
                            .append(r.isSuccess() ? r.getOutput()
                                    : "执行失败: " + r.getErrorMsg())
                            .append("\n\n");
                }
            });

            String prompt = promptTemplateService.loadTemplate("PROMPT_SUMMARY");
            if (prompt == null) {
                prompt = "汇总子任务结果。原始需求:{{originalRequest}}\n子任务结果:{{subTaskResults}}\n生成包含执行摘要、详细分析、结论建议的完整报告。";
            }
            prompt = prompt.replace("{{originalRequest}}", context.getUserInput())
                    .replace("{{subTaskResults}}", subTaskResults.toString());

            String result = chatClient.prompt()
                    .system(prompt)
                    .user(subTaskResults.toString())
                    .call()
                    .content();

            long duration = System.currentTimeMillis() - start;
            log.info("[SummaryAgent] 汇总完成, duration={}ms", duration);

            return AgentResult.builder()
                    .success(true)
                    .agentCode(getAgentCode())
                    .agentName("结果汇总Agent")
                    .output(result)
                    .durationMs(duration)
                    .build();

        } catch (Exception e) {
            log.error("[SummaryAgent] 汇总失败", e);
            long duration = System.currentTimeMillis() - start;
            return AgentResult.builder()
                    .success(false)
                    .agentCode(getAgentCode())
                    .agentName("结果汇总Agent")
                    .errorMsg(e.getMessage())
                    .durationMs(duration)
                    .build();
        }
    }
}

4.6 记忆管理器

java 复制代码
package com.enterprise.agent.core.memory;

import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.entity.AiTask;
import com.enterprise.agent.entity.AiTaskSub;
import com.enterprise.agent.mapper.AiTaskMapper;
import com.enterprise.agent.mapper.AiTaskSubMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * 三层记忆管理器
 *   L1 - Redis:短期会话上下文(30分钟TTL)
 *   L2 - MySQL:任务与子任务持久归档
 *   L3 - Qdrant:知识库向量检索(由 RetrievalAgent 独立处理)
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MemoryManager {

    private static final String TASK_MEMORY_KEY = "agent:task:memory:";
    private static final int TTL_MINUTES = 30;

    private final RedisTemplate<String, Object> redisTemplate;
    private final AiTaskMapper aiTaskMapper;
    private final AiTaskSubMapper aiTaskSubMapper;

    private final TaskPersistenceService taskPersistenceService;

    /**
     * 三层记忆统一持久化入口
     */
    public void persistAll(TaskContext context) {
        saveShortTermMemory(context);
        // 异步持久化通过独立 Service 调用,确保 @Async 生效
        taskPersistenceService.persistTaskToDb(context);
    }

    /**
     * L1: 保存短期会话上下文到 Redis
     */
    public void saveShortTermMemory(TaskContext context) {
        String key = TASK_MEMORY_KEY + context.getTaskNo();
        redisTemplate.opsForValue().set(key, context, TTL_MINUTES, TimeUnit.MINUTES);
        log.debug("[L1-Memory] Redis保存会话上下文, taskNo={}", context.getTaskNo());
    }

    /**
     * L1: 从Redis读取会话上下文
     */
    public TaskContext loadShortTermMemory(String taskNo) {
        String key = TASK_MEMORY_KEY + taskNo;
        Object obj = redisTemplate.opsForValue().get(key);
        if (obj instanceof TaskContext ctx) {
            redisTemplate.expire(key, TTL_MINUTES, TimeUnit.MINUTES);
            return ctx;
        }
        return null;
    }
}

4.6.1 TaskPersistenceService(异步持久化服务)

说明 :将 @Async 持久化逻辑抽取为独立 Service,避免同类调用导致 AOP 代理失效。

java 复制代码
package com.enterprise.agent.core.memory;

import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.entity.AiTask;
import com.enterprise.agent.entity.AiTaskSub;
import com.enterprise.agent.mapper.AiTaskMapper;
import com.enterprise.agent.mapper.AiTaskSubMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * 任务异步持久化服务
 * 独立 Service 确保 @Async 通过 Spring AOP 代理生效
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TaskPersistenceService {

    private final AiTaskMapper aiTaskMapper;
    private final AiTaskSubMapper aiTaskSubMapper;

    /**
     * L2: 异步持久化任务与子任务到 MySQL
     */
    @Async
    public void persistTaskToDb(TaskContext context) {
        try {
            AiTask task = new AiTask();
            task.setTaskNo(context.getTaskNo());
            task.setTaskStatus("SUCCESS");
            task.setResultSummary(context.getAgentResults() != null
                    ? context.getAgentResults().toString() : "");
            task.setEndTime(LocalDateTime.now());
            task.setUpdateBy(context.getUsername());
            aiTaskMapper.updateByTaskNo(task);

            if (context.getAgentResults() != null) {
                context.getAgentResults().forEach((code, r) -> {
                    AiTaskSub sub = new AiTaskSub();
                    sub.setTaskNo(context.getTaskNo());
                    sub.setAgentCode(code);
                    sub.setSubTaskStatus(r.isSuccess() ? "SUCCESS" : "FAILED");
                    sub.setOutputSnapshot(r.getOutput());
                    sub.setErrorMsg(r.getErrorMsg());
                    sub.setEndTime(LocalDateTime.now());
                    sub.setDurationMs(r.getDurationMs());
                    sub.setUpdateBy(context.getUsername());
                    aiTaskSubMapper.updateByTaskNoAndAgentCode(sub);
                });
            }
            log.info("[L2-Memory] MySQL异步归档完成, taskNo={}", context.getTaskNo());
        } catch (Exception e) {
            log.error("[L2-Memory] MySQL归档失败, taskNo={}", context.getTaskNo(), e);
        }
    }
}

4.7 AgentRouter 路由策略

java 复制代码
package com.enterprise.agent.core.strategy;

import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.entity.AiAgentInfo;
import com.enterprise.agent.mapper.AiAgentInfoMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Agent 路由策略
 * 根据子任务的 agentCategory 匹配数据库中已注册的启用Agent
 * 支持两种模式:
 *   ① 固定路由:按 agentCategory 精确匹配
 *   ② 降级路由:无匹配时返回第一个启用的同类型Agent
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AgentRouter {

    private final AiAgentInfoMapper agentInfoMapper;
    private final Map<String, BaseAgent> agentRegistry;

    /**
     * 为子任务路由到合适的Agent
     *
     * @param subTask 子任务定义
     * @return 匹配的 Agent 编码
     */
    public String route(TaskContext.SubTask subTask) {
        String category = subTask.getAgentCategory();

        // 从数据库查询该分类下所有启用的Agent
        List<AiAgentInfo> agents = agentInfoMapper.selectByCategory(category);

        if (agents == null || agents.isEmpty()) {
            log.warn("[AgentRouter] 无匹配Agent, category={}, 使用默认BUSINESS路由", category);
            agents = agentInfoMapper.selectByCategory("BUSINESS");
        }

        if (agents == null || agents.isEmpty()) {
            log.warn("[AgentRouter] 无任何可用Agent");
            return "TASK_PARSE"; // 最终降级
        }

        // 优先选择在Spring容器中已注册的Agent
        for (AiAgentInfo agent : agents) {
            if (agentRegistry.containsKey(agent.getAgentCode())) {
                return agent.getAgentCode();
            }
        }

        // 降级:返回第一个启用的Agent
        return agents.get(0).getAgentCode();
    }

    /**
     * 获取Agent实例
     */
    public BaseAgent getAgent(String agentCode) {
        BaseAgent agent = agentRegistry.get(agentCode);
        if (agent == null) {
            log.warn("[AgentRouter] Agent实例未找到, agentCode={}", agentCode);
        }
        return agent;
    }

    /**
     * 获取所有已注册的Agent编码列表
     */
    public List<String> getAllAgentCodes() {
        return agentRegistry.keySet().stream().sorted().collect(Collectors.toList());
    }
}

4.8 Agent 注册器(自动发现所有 BaseAgent 实现类)

java 复制代码
package com.enterprise.agent.core.strategy;

import com.enterprise.agent.core.agent.BaseAgent;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Agent 注册器
 * 应用启动时自动扫描所有 BaseAgent 实现类,注册到 agentRegistry
 * 新增业务Agent只需实现 BaseAgent 并标注 @Component,无需修改此处
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AgentRegistryInitializer {

    private final ApplicationContext applicationContext;
    private final Map<String, BaseAgent> agentRegistry;

    @PostConstruct
    public void init() {
        Map<String, BaseAgent> agents = applicationContext.getBeansOfType(BaseAgent.class);
        for (BaseAgent agent : agents.values()) {
            String code = agent.getAgentCode();
            agentRegistry.put(code, agent);
            log.info("[AgentRegistry] 注册Agent: code={}, class={}",
                    code, agent.getClass().getSimpleName());
        }
        log.info("[AgentRegistry] Agent注册完成, 共注册 {} 个Agent", agentRegistry.size());
    }
}

4.9 异常定义

java 复制代码
package com.enterprise.agent.core.exception;

/**
 * Multi-Agent 框架统一异常
 */
public class AgentException extends RuntimeException {

    private final String errorCode;

    public AgentException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public AgentException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

4.10 Prompt 模板服务

java 复制代码
package com.enterprise.agent.prompt;

import com.enterprise.agent.entity.AiPromptTemplate;
import com.enterprise.agent.mapper.AiPromptTemplateMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 提示词模板服务
 * 支持从数据库动态加载Prompt,结合本地缓存减少DB查询
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PromptTemplateService {

    private final AiPromptTemplateMapper promptTemplateMapper;
    private final Map<String, String> localCache = new ConcurrentHashMap<>();

    /**
     * 加载提示词模板
     * 优先本地缓存 → 数据库 → 返回 null
     *
     * @param templateCode 模板编码,如 "PROMPT_TASK_PARSE"
     * @return 模板内容,未找到返回 null
     */
    public String loadTemplate(String templateCode) {
        // 优先本地缓存
        if (localCache.containsKey(templateCode)) {
            return localCache.get(templateCode);
        }

        // 数据库查询
        AiPromptTemplate template = promptTemplateMapper.selectByCode(templateCode);
        if (template != null && template.getStatus() == 1) {
            localCache.put(templateCode, template.getTemplateContent());
            return template.getTemplateContent();
        }

        log.warn("[Prompt] 模板未找到或已停用, templateCode={}", templateCode);
        return null;
    }

    /**
     * 刷新缓存(提示词更新后调用)
     */
    public void refreshCache(String templateCode) {
        localCache.remove(templateCode);
        AiPromptTemplate template = promptTemplateMapper.selectByCode(templateCode);
        if (template != null && template.getStatus() == 1) {
            localCache.put(templateCode, template.getTemplateContent());
        }
        log.info("[Prompt] 缓存已刷新, templateCode={}", templateCode);
    }

    /**
     * 刷新所有缓存
     */
    public void refreshAll() {
        localCache.clear();
        log.info("[Prompt] 所有缓存已清空");
    }
}

4.11 MyBatis Mapper 接口与 XML

AiAgentInfoMapper.java
java 复制代码
package com.enterprise.agent.mapper;

import com.enterprise.agent.entity.AiAgentInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface AiAgentInfoMapper {

    AiAgentInfo selectByCode(@Param("agentCode") String agentCode);

    List<AiAgentInfo> selectByCategory(@Param("category") String category);

    List<AiAgentInfo> selectAllEnabled();

    int insert(AiAgentInfo agentInfo);

    int updateById(AiAgentInfo agentInfo);

    int deleteById(@Param("id") Long id);
}
AiAgentInfoMapper.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.enterprise.agent.mapper.AiAgentInfoMapper">

    <resultMap id="BaseResultMap" type="com.enterprise.agent.entity.AiAgentInfo">
        <id column="id" property="id"/>
        <result column="agent_code" property="agentCode"/>
        <result column="agent_name" property="agentName"/>
        <result column="agent_type" property="agentType"/>
        <result column="agent_category" property="agentCategory"/>
        <result column="description" property="description"/>
        <result column="execute_mode" property="executeMode"/>
        <result column="prompt_template_id" property="promptTemplateId"/>
        <result column="sort_order" property="sortOrder"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="create_by" property="createBy"/>
        <result column="create_time" property="createTime"/>
        <result column="update_by" property="updateBy"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <select id="selectByCode" resultMap="BaseResultMap">
        SELECT * FROM ai_agent_info
        WHERE agent_code = #{agentCode} AND status = 1 AND is_deleted = 0
    </select>

    <select id="selectByCategory" resultMap="BaseResultMap">
        SELECT * FROM ai_agent_info
        WHERE agent_category = #{category} AND status = 1 AND is_deleted = 0
        ORDER BY sort_order ASC
    </select>

    <select id="selectAllEnabled" resultMap="BaseResultMap">
        SELECT * FROM ai_agent_info
        WHERE status = 1 AND is_deleted = 0
        ORDER BY sort_order ASC
    </select>

    <insert id="insert" parameterType="com.enterprise.agent.entity.AiAgentInfo"
            useGeneratedKeys="true" keyProperty="id">
        INSERT INTO ai_agent_info (agent_code, agent_name, agent_type, agent_category,
            description, execute_mode, prompt_template_id, sort_order, status, create_by, create_time)
        VALUES (#{agentCode}, #{agentName}, #{agentType}, #{agentCategory},
            #{description}, #{executeMode}, #{promptTemplateId}, #{sortOrder}, #{status},
            #{createBy}, NOW())
    </insert>

    <update id="updateById" parameterType="com.enterprise.agent.entity.AiAgentInfo">
        UPDATE ai_agent_info
        SET agent_name = #{agentName}, description = #{description},
            execute_mode = #{executeMode}, prompt_template_id = #{promptTemplateId},
            sort_order = #{sortOrder}, status = #{status},
            update_by = #{updateBy}, update_time = NOW()
        WHERE id = #{id} AND is_deleted = 0
    </update>

    <update id="deleteById">
        UPDATE ai_agent_info SET is_deleted = 1, update_time = NOW()
        WHERE id = #{id}
    </update>
</mapper>
AiTaskMapper.java
java 复制代码
package com.enterprise.agent.mapper;

import com.enterprise.agent.entity.AiTask;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface AiTaskMapper {

    AiTask selectByTaskNo(@Param("taskNo") String taskNo);

    List<AiTask> selectByUserId(@Param("userId") Long userId);

    int insert(AiTask task);

    int updateByTaskNo(AiTask task);

    int updateStatus(@Param("taskNo") String taskNo, @Param("taskStatus") String taskStatus,
                     @Param("updateBy") String updateBy);
}
AiTaskMapper.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.enterprise.agent.mapper.AiTaskMapper">

    <resultMap id="BaseResultMap" type="com.enterprise.agent.entity.AiTask">
        <id column="id" property="id"/>
        <result column="task_no" property="taskNo"/>
        <result column="user_id" property="userId"/>
        <result column="task_title" property="taskTitle"/>
        <result column="task_content" property="taskContent"/>
        <result column="task_type" property="taskType"/>
        <result column="cooperate_mode" property="cooperateMode"/>
        <result column="task_status" property="taskStatus"/>
        <result column="priority" property="priority"/>
        <result column="timeout_seconds" property="timeoutSeconds"/>
        <result column="retry_times" property="retryTimes"/>
        <result column="max_retry" property="maxRetry"/>
        <result column="start_time" property="startTime"/>
        <result column="end_time" property="endTime"/>
        <result column="total_duration_ms" property="totalDurationMs"/>
        <result column="result_summary" property="resultSummary"/>
        <result column="error_msg" property="errorMsg"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="create_by" property="createBy"/>
        <result column="create_time" property="createTime"/>
        <result column="update_by" property="updateBy"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <select id="selectByTaskNo" resultMap="BaseResultMap">
        SELECT * FROM ai_task WHERE task_no = #{taskNo} AND is_deleted = 0
    </select>

    <select id="selectByUserId" resultMap="BaseResultMap">
        SELECT * FROM ai_task
        WHERE user_id = #{userId} AND is_deleted = 0
        ORDER BY create_time DESC
    </select>

    <insert id="insert" parameterType="com.enterprise.agent.entity.AiTask"
            useGeneratedKeys="true" keyProperty="id">
        INSERT INTO ai_task (task_no, user_id, task_title, task_content, task_type,
            cooperate_mode, task_status, priority, timeout_seconds, max_retry,
            start_time, create_by, create_time)
        VALUES (#{taskNo}, #{userId}, #{taskTitle}, #{taskContent}, #{taskType},
            #{cooperateMode}, #{taskStatus}, #{priority}, #{timeoutSeconds}, #{maxRetry},
            #{startTime}, #{createBy}, NOW())
    </insert>

    <update id="updateByTaskNo" parameterType="com.enterprise.agent.entity.AiTask">
        UPDATE ai_task
        SET task_status = #{taskStatus},
            result_summary = #{resultSummary},
            end_time = #{endTime},
            total_duration_ms = #{totalDurationMs},
            error_msg = #{errorMsg},
            retry_times = #{retryTimes},
            update_by = #{updateBy},
            update_time = NOW()
        WHERE task_no = #{taskNo} AND is_deleted = 0
    </update>

    <update id="updateStatus">
        UPDATE ai_task
        SET task_status = #{taskStatus}, update_by = #{updateBy}, update_time = NOW()
        WHERE task_no = #{taskNo} AND is_deleted = 0
    </update>
</mapper>
AiTaskSubMapper.java
java 复制代码
package com.enterprise.agent.mapper;

import com.enterprise.agent.entity.AiTaskSub;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface AiTaskSubMapper {

    List<AiTaskSub> selectByTaskNo(@Param("taskNo") String taskNo);

    int batchInsert(@Param("list") List<AiTaskSub> subTasks);

    int updateByTaskNoAndAgentCode(AiTaskSub subTask);
}
AiTaskSubMapper.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.enterprise.agent.mapper.AiTaskSubMapper">

    <resultMap id="BaseResultMap" type="com.enterprise.agent.entity.AiTaskSub">
        <id column="id" property="id"/>
        <result column="task_id" property="taskId"/>
        <result column="task_no" property="taskNo"/>
        <result column="sub_task_no" property="subTaskNo"/>
        <result column="agent_code" property="agentCode"/>
        <result column="agent_name" property="agentName"/>
        <result column="parent_sub_id" property="parentSubId"/>
        <result column="execute_order" property="executeOrder"/>
        <result column="sub_task_content" property="subTaskContent"/>
        <result column="sub_task_status" property="subTaskStatus"/>
        <result column="input_snapshot" property="inputSnapshot"/>
        <result column="output_snapshot" property="outputSnapshot"/>
        <result column="start_time" property="startTime"/>
        <result column="end_time" property="endTime"/>
        <result column="duration_ms" property="durationMs"/>
        <result column="error_msg" property="errorMsg"/>
        <result column="retry_count" property="retryCount"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="create_by" property="createBy"/>
        <result column="create_time" property="createTime"/>
        <result column="update_by" property="updateBy"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <select id="selectByTaskNo" resultMap="BaseResultMap">
        SELECT * FROM ai_task_sub
        WHERE task_no = #{taskNo} AND is_deleted = 0
        ORDER BY execute_order ASC
    </select>

    <insert id="batchInsert">
        INSERT INTO ai_task_sub (task_id, task_no, sub_task_no, agent_code, agent_name,
            parent_sub_id, execute_order, sub_task_content, sub_task_status,
            start_time, create_by, create_time)
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.taskId}, #{item.taskNo}, #{item.subTaskNo}, #{item.agentCode},
             #{item.agentName}, #{item.parentSubId}, #{item.executeOrder},
             #{item.subTaskContent}, #{item.subTaskStatus},
             #{item.startTime}, #{item.createBy}, NOW())
        </foreach>
    </insert>

    <update id="updateByTaskNoAndAgentCode" parameterType="com.enterprise.agent.entity.AiTaskSub">
        UPDATE ai_task_sub
        SET sub_task_status = #{subTaskStatus},
            output_snapshot = #{outputSnapshot},
            error_msg = #{errorMsg},
            end_time = #{endTime},
            duration_ms = #{durationMs},
            retry_count = #{retryCount},
            update_by = #{updateBy},
            update_time = NOW()
        WHERE task_no = #{taskNo} AND agent_code = #{agentCode} AND is_deleted = 0
    </update>
</mapper>
AiPromptTemplateMapper.java
java 复制代码
package com.enterprise.agent.mapper;

import com.enterprise.agent.entity.AiPromptTemplate;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface AiPromptTemplateMapper {

    AiPromptTemplate selectByCode(@Param("templateCode") String templateCode);

    int insert(AiPromptTemplate template);

    int updateById(AiPromptTemplate template);
}
AiPromptTemplateMapper.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.enterprise.agent.mapper.AiPromptTemplateMapper">

    <resultMap id="BaseResultMap" type="com.enterprise.agent.entity.AiPromptTemplate">
        <id column="id" property="id"/>
        <result column="template_code" property="templateCode"/>
        <result column="template_name" property="templateName"/>
        <result column="template_type" property="templateType"/>
        <result column="agent_code" property="agentCode"/>
        <result column="template_content" property="templateContent"/>
        <result column="variables" property="variables"/>
        <result column="version" property="version"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="create_by" property="createBy"/>
        <result column="create_time" property="createTime"/>
        <result column="update_by" property="updateBy"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <select id="selectByCode" resultMap="BaseResultMap">
        SELECT * FROM ai_prompt_template
        WHERE template_code = #{templateCode} AND status = 1 AND is_deleted = 0
    </select>

    <insert id="insert" parameterType="com.enterprise.agent.entity.AiPromptTemplate"
            useGeneratedKeys="true" keyProperty="id">
        INSERT INTO ai_prompt_template (template_code, template_name, template_type,
            agent_code, template_content, variables, version, status, create_by, create_time)
        VALUES (#{templateCode}, #{templateName}, #{templateType},
            #{agentCode}, #{templateContent}, #{variables}, #{version}, #{status},
            #{createBy}, NOW())
    </insert>

    <update id="updateById" parameterType="com.enterprise.agent.entity.AiPromptTemplate">
        UPDATE ai_prompt_template
        SET template_name = #{templateName}, template_content = #{templateContent},
            variables = #{variables}, version = version + 1, status = #{status},
            update_by = #{updateBy}, update_time = NOW()
        WHERE id = #{id} AND is_deleted = 0
    </update>
</mapper>

4.12 Entity 实体类

AiAgentInfo.java
java 复制代码
package com.enterprise.agent.entity;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class AiAgentInfo {
    private Long id;
    private String agentCode;
    private String agentName;
    private String agentType;
    private String agentCategory;
    private String description;
    private String executeMode;
    private Long promptTemplateId;
    private Integer sortOrder;
    private Integer status;
    private Integer isDeleted;
    private String createBy;
    private LocalDateTime createTime;
    private String updateBy;
    private LocalDateTime updateTime;
}
AiTask.java
java 复制代码
package com.enterprise.agent.entity;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class AiTask {
    private Long id;
    private String taskNo;
    private Long userId;
    private String taskTitle;
    private String taskContent;
    private String taskType;
    private String cooperateMode;
    private String taskStatus;
    private Integer priority;
    private Integer timeoutSeconds;
    private Integer retryTimes;
    private Integer maxRetry;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private Long totalDurationMs;
    private String resultSummary;
    private String errorMsg;
    private Integer isDeleted;
    private String createBy;
    private LocalDateTime createTime;
    private String updateBy;
    private LocalDateTime updateTime;
}
AiTaskSub.java
java 复制代码
package com.enterprise.agent.entity;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class AiTaskSub {
    private Long id;
    private Long taskId;
    private String taskNo;
    private String subTaskNo;
    private String agentCode;
    private String agentName;
    private Long parentSubId;
    private Integer executeOrder;
    private String subTaskContent;
    private String subTaskStatus;
    private String inputSnapshot;
    private String outputSnapshot;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private Long durationMs;
    private String errorMsg;
    private Integer retryCount;
    private Integer isDeleted;
    private String createBy;
    private LocalDateTime createTime;
    private String updateBy;
    private LocalDateTime updateTime;
}
AiPromptTemplate.java
java 复制代码
package com.enterprise.agent.entity;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class AiPromptTemplate {
    private Long id;
    private String templateCode;
    private String templateName;
    private String templateType;
    private String agentCode;
    private String templateContent;
    private String variables;
    private Integer version;
    private Integer status;
    private Integer isDeleted;
    private String createBy;
    private LocalDateTime createTime;
    private String updateBy;
    private LocalDateTime updateTime;
}

4.13 DTO 请求/响应对象

TaskSubmitRequest.java
java 复制代码
package com.enterprise.agent.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class TaskSubmitRequest {

    /** 任务标题 */
    @NotBlank(message = "任务标题不能为空")
    private String taskTitle;

    /** 任务内容(用户原始输入) */
    @NotBlank(message = "任务内容不能为空")
    private String taskContent;

    /** 任务类型 */
    private String taskType = "GENERAL";

    /** 协作模式:SERIAL-串行 / PARALLEL-并行 */
    private String cooperateMode = "SERIAL";

    /** 优先级:1-低 2-中 3-高 4-紧急 */
    private Integer priority = 2;

    /** 超时时间(秒),默认300秒 */
    private Integer timeoutSeconds = 300;

    /** 最大重试次数 */
    private Integer maxRetry = 3;
}
TaskResponse.java
java 复制代码
package com.enterprise.agent.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TaskResponse {

    /** 任务编号 */
    private String taskNo;

    /** 任务状态 */
    private String taskStatus;

    /** 最终结果 */
    private String result;

    /** 子任务数量 */
    private Integer subTaskCount;

    /** 成功子任务数 */
    private Integer successCount;

    /** 失败子任务数 */
    private Integer failCount;

    /** 总耗时(毫秒) */
    private Long durationMs;

    /** 创建时间 */
    private LocalDateTime createTime;
}

4.14 Service 层

AgentTaskService.java
java 复制代码
package com.enterprise.agent.service;

import com.enterprise.agent.core.context.TaskContext;
import com.enterprise.agent.core.orchestrator.Orchestrator;
import com.enterprise.agent.dto.TaskResponse;
import com.enterprise.agent.dto.TaskSubmitRequest;
import com.enterprise.agent.entity.AiTask;
import com.enterprise.agent.entity.AiTaskSub;
import com.enterprise.agent.mapper.AiTaskMapper;
import com.enterprise.agent.mapper.AiTaskSubMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Agent任务服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AgentTaskService {

    private final Orchestrator orchestrator;
    private final AiTaskMapper aiTaskMapper;
    private final AiTaskSubMapper aiTaskSubMapper;

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");

    /**
     * 提交并执行任务
     */
    @Transactional(rollbackFor = Exception.class)
    public TaskResponse submitAndExecute(TaskSubmitRequest request, Long userId, String username) {
        // ① 生成任务编号
        String taskNo = generateTaskNo();

        // ② 构建 TaskContext
        TaskContext context = TaskContext.builder()
                .taskNo(taskNo)
                .taskTitle(request.getTaskTitle())
                .userInput(request.getTaskContent())
                .cooperateMode(request.getCooperateMode())
                .priority(request.getPriority())
                .userId(userId)
                .username(username)
                .build();

        // ③ 持久化主任务
        AiTask task = new AiTask();
        task.setTaskNo(taskNo);
        task.setUserId(userId);
        task.setTaskTitle(request.getTaskTitle());
        task.setTaskContent(request.getTaskContent());
        task.setTaskType(request.getTaskType());
        task.setCooperateMode(request.getCooperateMode());
        task.setTaskStatus("RUNNING");
        task.setPriority(request.getPriority());
        task.setTimeoutSeconds(request.getTimeoutSeconds());
        task.setMaxRetry(request.getMaxRetry());
        task.setStartTime(LocalDateTime.now());
        task.setCreateBy(username);
        aiTaskMapper.insert(task);

        context.setTaskId(task.getId());

        // ④ 编排执行(内部会拆解子任务并填充 context.subTasks)
        String result = orchestrator.orchestrate(context);

        // ④.1 批量初始化子任务记录到数据库(INSERT,后续 MemoryManager 做 UPDATE)
        if (context.getSubTasks() != null && !context.getSubTasks().isEmpty()) {
            List<AiTaskSub> subTaskRecords = new ArrayList<>();
            for (TaskContext.SubTask st : context.getSubTasks()) {
                AiTaskSub sub = new AiTaskSub();
                sub.setTaskId(task.getId());
                sub.setTaskNo(taskNo);
                sub.setSubTaskNo(taskNo + "-SUB-" + st.getOrder());
                sub.setAgentCode(st.getMatchedAgentCode());
                sub.setExecuteOrder(st.getOrder());
                sub.setSubTaskContent(st.getContent());
                sub.setSubTaskStatus("RUNNING");
                sub.setStartTime(LocalDateTime.now());
                sub.setCreateBy(username);
                subTaskRecords.add(sub);
            }
            aiTaskSubMapper.batchInsert(subTaskRecords);
        }

        // ⑤ 统计子任务结果
        int successCount = 0;
        int failCount = 0;
        if (context.getSubTasks() != null) {
            for (TaskContext.SubTask st : context.getSubTasks()) {
                if (context.getAgentResults() != null
                        && context.getAgentResults().containsKey(st.getMatchedAgentCode())) {
                    if (context.getAgentResults().get(st.getMatchedAgentCode()).isSuccess()) {
                        successCount++;
                    } else {
                        failCount++;
                    }
                }
            }
        }

        // ⑥ 更新任务状态
        task.setTaskStatus(failCount == 0 ? "SUCCESS" : "PARTIAL");
        task.setResultSummary(result);
        task.setEndTime(LocalDateTime.now());
        task.setTotalDurationMs(java.time.Duration.between(
                task.getStartTime(), task.getEndTime()).toMillis());
        aiTaskMapper.updateByTaskNo(task);

        // ⑦ 构建响应
        return TaskResponse.builder()
                .taskNo(taskNo)
                .taskStatus(task.getTaskStatus())
                .result(result)
                .subTaskCount(context.getSubTasks() != null ? context.getSubTasks().size() : 0)
                .successCount(successCount)
                .failCount(failCount)
                .durationMs(task.getTotalDurationMs())
                .createTime(task.getCreateTime())
                .build();
    }

    /**
     * 查询任务详情
     */
    public AiTask queryTask(String taskNo) {
        return aiTaskMapper.selectByTaskNo(taskNo);
    }

    /**
     * 查询用户任务列表
     */
    public List<AiTask> queryUserTasks(Long userId) {
        return aiTaskMapper.selectByUserId(userId);
    }

    /**
     * 查询子任务列表
     */
    public List<AiTaskSub> querySubTasks(String taskNo) {
        return aiTaskSubMapper.selectByTaskNo(taskNo);
    }

    /**
     * 生成任务编号:TASK-yyyyMMdd-UUID前8位
     */
    private String generateTaskNo() {
        String date = LocalDateTime.now().format(DATE_FMT);
        String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
        return "TASK-" + date + "-" + uuid;
    }
}

4.15 Controller 层

AgentTaskController.java(用户端)
java 复制代码
package com.enterprise.agent.controller;

import com.enterprise.agent.dto.TaskResponse;
import com.enterprise.agent.dto.TaskSubmitRequest;
import com.enterprise.agent.entity.AiTask;
import com.enterprise.agent.entity.AiTaskSub;
import com.enterprise.agent.service.AgentTaskService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Agent任务控制器(用户端接口)
 */
@Slf4j
@RestController
@RequestMapping("/api/agent/task")
@RequiredArgsConstructor
public class AgentTaskController {

    private final AgentTaskService agentTaskService;

    /**
     * 提交任务
     *
     * POST /api/agent/task/submit
     * {
     *   "taskTitle": "帮我分析本月销售数据",
     *   "taskContent": "查询本月各产品线的销售额、同比环比数据,生成分析报告",
     *   "cooperateMode": "SERIAL",
     *   "priority": 2
     * }
     */
    @PostMapping("/submit")
    public ResponseEntity<Map<String, Object>> submitTask(
            @Valid @RequestBody TaskSubmitRequest request,
            Authentication authentication) {

        Long userId = getUserId(authentication);
        String username = authentication.getName();

        log.info("[API] 收到任务提交, userId={}, taskTitle={}", userId, request.getTaskTitle());

        TaskResponse response = agentTaskService.submitAndExecute(request, userId, username);

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "任务执行完成");
        result.put("data", response);
        return ResponseEntity.ok(result);
    }

    /**
     * 查询任务详情
     * GET /api/agent/task/query/{taskNo}
     */
    @GetMapping("/query/{taskNo}")
    public ResponseEntity<Map<String, Object>> queryTask(@PathVariable String taskNo) {
        AiTask task = agentTaskService.queryTask(taskNo);

        Map<String, Object> result = new HashMap<>();
        if (task == null) {
            result.put("code", 404);
            result.put("message", "任务不存在");
            return ResponseEntity.ok(result);
        }
        result.put("code", 200);
        result.put("message", "查询成功");
        result.put("data", task);
        return ResponseEntity.ok(result);
    }

    /**
     * 查询子任务列表
     * GET /api/agent/task/sub/{taskNo}
     */
    @GetMapping("/sub/{taskNo}")
    public ResponseEntity<Map<String, Object>> querySubTasks(@PathVariable String taskNo) {
        List<AiTaskSub> subTasks = agentTaskService.querySubTasks(taskNo);

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "查询成功");
        result.put("data", subTasks);
        return ResponseEntity.ok(result);
    }

    /**
     * 查询用户任务列表
     * GET /api/agent/task/list
     */
    @GetMapping("/list")
    public ResponseEntity<Map<String, Object>> queryUserTasks(Authentication authentication) {
        Long userId = getUserId(authentication);
        List<AiTask> tasks = agentTaskService.queryUserTasks(userId);

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "查询成功");
        result.put("data", tasks);
        return ResponseEntity.ok(result);
    }

    private Long getUserId(Authentication authentication) {
        // 从认证信息中获取用户ID(实际项目需根据Security实现调整)
        if (authentication.getPrincipal() instanceof com.enterprise.agent.config.SecurityUser user) {
            return user.getUserId();
        }
        return 1L; // 默认
    }
}
AgentAdminController.java(管理端)
java 复制代码
package com.enterprise.agent.controller;

import com.enterprise.agent.core.strategy.AgentRouter;
import com.enterprise.agent.entity.AiAgentInfo;
import com.enterprise.agent.mapper.AiAgentInfoMapper;
import com.enterprise.agent.prompt.PromptTemplateService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Agent管理控制器(后台管理接口)
 */
@RestController
@RequestMapping("/api/admin/agent")
@RequiredArgsConstructor
public class AgentAdminController {

    private final AiAgentInfoMapper agentInfoMapper;
    private final AgentRouter agentRouter;
    private final PromptTemplateService promptTemplateService;

    /**
     * 查询所有已启用的Agent
     */
    @GetMapping("/list")
    public ResponseEntity<Map<String, Object>> listAgents() {
        List<AiAgentInfo> agents = agentInfoMapper.selectAllEnabled();
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", agents);
        return ResponseEntity.ok(result);
    }

    /**
     * 新增Agent
     */
    @PostMapping("/add")
    public ResponseEntity<Map<String, Object>> addAgent(@RequestBody AiAgentInfo agentInfo) {
        agentInfo.setCreateBy("admin");
        agentInfoMapper.insert(agentInfo);

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "Agent新增成功");
        return ResponseEntity.ok(result);
    }

    /**
     * 停用Agent
     */
    @PostMapping("/disable/{id}")
    public ResponseEntity<Map<String, Object>> disableAgent(@PathVariable Long id) {
        AiAgentInfo agent = new AiAgentInfo();
        agent.setId(id);
        agent.setStatus(0);
        agent.setUpdateBy("admin");
        agentInfoMapper.updateById(agent);

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "Agent已停用");
        return ResponseEntity.ok(result);
    }

    /**
     * 获取所有已注册Agent编码列表
     */
    @GetMapping("/registered")
    public ResponseEntity<Map<String, Object>> registeredAgents() {
        List<String> codes = agentRouter.getAllAgentCodes();
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", codes);
        return ResponseEntity.ok(result);
    }

    /**
     * 刷新提示词缓存
     */
    @PostMapping("/prompt/refresh/{templateCode}")
    public ResponseEntity<Map<String, Object>> refreshPrompt(
            @PathVariable String templateCode) {
        promptTemplateService.refreshCache(templateCode);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "提示词缓存已刷新");
        return ResponseEntity.ok(result);
    }
}

4.16 核心配置类

AgentCoreConfig.java(框架核心Bean配置)
java 复制代码
package com.enterprise.agent.config;

import com.enterprise.agent.core.agent.BaseAgent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;
import java.util.concurrent.*;

/**
 * Multi-Agent 框架核心配置
 */
@Configuration
public class AgentCoreConfig {

    /**
     * Agent注册表(线程安全)
     * 存储 agentCode → Agent实例 的映射
     */
    @Bean
    public Map<String, BaseAgent> agentRegistry() {
        return new ConcurrentHashMap<>();
    }

    /**
     * Agent执行线程池
     * 用于并行执行多个Agent
     */
    @Bean("agentExecutor")
    public Executor agentExecutor() {
        return new ThreadPoolExecutor(
                8,                          // 核心线程数
                32,                         // 最大线程数
                60L, TimeUnit.SECONDS,      // 空闲线程存活时间
                new LinkedBlockingQueue<>(200), // 任务队列
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );
    }
}
AsyncConfig.java(异步配置)
java 复制代码
package com.enterprise.agent.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * 异步任务配置
 * 用于 L2 MySQL归档、L3 向量库写入等非阻塞操作
 */
@Configuration
@EnableAsync
public class AsyncConfig {
}
SecurityUser.java(Security用户封装)
java 复制代码
package com.enterprise.agent.config;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Getter
@AllArgsConstructor
public class SecurityUser implements UserDetails {

    private final Long userId;
    private final String username;
    private final String password;
    private final Collection<? extends GrantedAuthority> authorities;

    @Override
    public boolean isAccountNonExpired() { return true; }

    @Override
    public boolean isAccountNonLocked() { return true; }

    @Override
    public boolean isCredentialsNonExpired() { return true; }

    @Override
    public boolean isEnabled() { return true; }
}

五、完整任务执行流程图

5.1 串行执行模式(SERIAL)

复制代码
用户 POST /api/agent/task/submit
        │
        ▼
┌─ Controller 参数校验 ──────────────────────┐
│  校验 taskTitle、taskContent 非空           │
└────────────────┬───────────────────────────┘
                 │
                 ▼
┌─ Security 权限校验 ─────────────────────────┐
│  Token校验 → 角色权限 → 资源授权             │
└────────────────┬───────────────────────────┘
                 │ 通过
                 ▼
┌─ AgentTaskService.submitAndExecute() ───────┐
│                                              │
│  ① 生成任务编号 TASK-20260709-a1b2c3d4      │
│  ② 构建 TaskContext(用户输入+协作模式+权限) │
│  ③ 持久化主任务到 ai_task(状态: RUNNING)   │
└────────────────┬───────────────────────────┘
                 │
                 ▼
┌─ Orchestrator.orchestrate() ────────────────┐
│                                              │
│  ┌─────────────────────────────────────┐    │
│  │ 步骤1: TaskParseAgent 任务拆解      │    │
│  │  输入: "分析本月销售数据并生成报告"  │    │
│  │  输出: [                           │    │
│  │    {order:1, content:"查询销售数据",│    │
│  │     agentCategory:"RETRIEVAL"},    │    │
│  │    {order:2, content:"校验数据",    │    │
│  │     agentCategory:"VERIFY"},       │    │
│  │    {order:3, content:"汇总报告",    │    │
│  │     agentCategory:"SUMMARY"}       │    │
│  │  ]                                 │    │
│  └─────────────┬───────────────────────┘    │
│                │                              │
│  ┌─────────────▼───────────────────────┐    │
│  │ 步骤2: AgentRouter 路由匹配         │    │
│  │  子任务1 → RETRIEVAL (RetrievalAgent)│    │
│  │  子任务2 → VERIFY (VerifyAgent)     │    │
│  │  子任务3 → SUMMARY (SummaryAgent)   │    │
│  └─────────────┬───────────────────────┘    │
│                │                              │
│  ┌─────────────▼───────────────────────┐    │
│  │ 步骤3: 串行执行(SERIAL模式)       │    │
│  │                                     │    │
│  │  RetrievalAgent.execute()           │    │
│  │    ├── Qdrant 检索Top-5相关文档     │    │
│  │    ├── LLM 基于文档生成回答         │    │
│  │    └── 输出 → sharedData            │    │
│  │         │                           │    │
│  │         ▼                           │    │
│  │  VerifyAgent.execute()              │    │
│  │    ├── 读取前置输出                 │    │
│  │    ├── LLM 校验合规性/准确性        │    │
│  │    └── 输出 → sharedData            │    │
│  │         │                           │    │
│  │         ▼                           │    │
│  │  SummaryAgent.execute()             │    │
│  │    ├── 收集所有Agent结果            │    │
│  │    ├── LLM 汇总生成最终报告         │    │
│  │    └── 输出 → 最终结果              │    │
│  └─────────────────────────────────────┘    │
│                                              │
│  ┌─────────────────────────────────────┐    │
│  │ 步骤4: 三层记忆持久化                │    │
│  │   L1 Redis: 会话上下文(TTL 30min)    │    │
│  │   L2 MySQL: 任务/子任务异步归档     │    │
│  └─────────────────────────────────────┘    │
└────────────────┬───────────────────────────┘
                 │
                 ▼
         返回 TaskResponse
         {
           taskNo: "TASK-20260709-a1b2c3d4",
           taskStatus: "SUCCESS",
           result: "【销售数据分析报告】...",
           subTaskCount: 3,
           successCount: 3,
           failCount: 0,
           durationMs: 4521
         }

5.2 并行执行模式(PARALLEL)

复制代码
Orchestrator.orchestrate() --- PARALLEL 模式
        │
        ▼
步骤1: TaskParseAgent 拆解任务(同串行)
        │
        ▼
步骤2: AgentRouter 路由匹配(同串行)
        │
        ▼
步骤3: 并行执行 ──────────────────────────────────┐
                                                  │
  ┌──────────────────┬──────────────────┬─────────┐
  │                  │                  │         │
  ▼                  ▼                  ▼         │
RetrievalAgent    VerifyAgent       SummaryAgent   │
(线程池-1)        (线程池-2)        (线程池-3)     │
  │                  │                  │         │
  │ Qdrant检索       │ 等待其他Agent    │ 等待     │
  │ LLM生成          │ 全部完成         │ 其他     │
  │                  │ 再校验           │ Agent    │
  │                  │                  │ 完成     │
  ▼                  ▼                  ▼         │
AgentResult       AgentResult       AgentResult    │
                                                  │
  └──────────────────┴──────────────────┴─────────┘
        │
        ▼
CompletableFuture.allOf() 等待全部完成(超时300秒)
        │
        ▼
步骤4: 三层记忆持久化 + 返回结果

5.3 任务状态流转图

复制代码
                 用户提交
                    │
                    ▼
              ┌──────────┐
              │  PENDING  │ (初始状态)
              └────┬─────┘
                   │ Orchestrator接收
                   ▼
              ┌──────────┐
              │ RUNNING   │ (Agent集群工作中)
              └────┬─────┘
                   │
         ┌─────────┼─────────┐
         ▼         ▼         ▼
    ┌────────┐ ┌───────┐ ┌────────┐
    │PARTIAL │ │SUCCESS│ │ FAILED │
    └───┬────┘ └───────┘ └───┬────┘
        │                    │
        │ (剩余继续)          │ (重试)
        ▼                    ▼
    ┌───────┐          ┌──────────┐
    │SUCCESS│          │CANCELLED │ (超时或手动取消)
    └───────┘          └──────────┘

六、详细执行流程与案例说明

6.1 端到端完整执行流程详解

本节从微观层面,逐步骤剖析一个任务从 HTTP 请求到最终返回的完整生命周期。

6.1.1 第一层:Controller 接入层
复制代码
用户发送 POST /api/agent/task/submit
        │
        ▼
┌─ 1. Spring 前置处理 ──────────────────────────┐
│  - DispatcherServlet 路由分发                   │
│  - Filter 链(CORS、编码、日志拦截器)            │
│  - @Valid 触发 JSR-303 参数校验                 │
│    · taskTitle 非空校验 → 不通过返回 400        │
│    · taskContent 非空校验 → 不通过返回 400       │
└──────────────────┬──────────────────────────────┘
                   │ 校验通过
                   ▼
┌─ 2. Spring Security 权限校验 ──────────────────┐
│  - JWT/Token 解析 → 提取用户身份                 │
│  - SecurityContextHolder 设置认证信息            │
│  - @PreAuthorize("hasAuthority('agent:task:submit')")│
│  - 权限不足 → 返回 403 Forbidden                │
│  - 审计日志记录:用户 zhangsan 提交任务           │
└──────────────────┬──────────────────────────────┘
                   │ 授权通过
                   ▼
┌─ 3. Controller 方法体执行 ─────────────────────┐
│  - getUserId(authentication) → 获取 userId=2     │
│  - username = authentication.getName() → zhangsan│
│  - log.info("[API] 收到任务提交...")             │
│  - 调用 agentTaskService.submitAndExecute(...)   │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
              Service 层
6.1.2 第二层:Service 服务层
复制代码
AgentTaskService.submitAndExecute(request, userId, username)
        │
        ▼
┌─ 步骤① 生成任务编号 ────────────────────────────┐
│  - 日期格式化:LocalDateTime.now() → "20260709"   │
│  - UUID生成:UUID.randomUUID() → "a1b2c3d4..."   │
│  - 拼接:TASK-20260709-a1b2c3d4                   │
│  - 存入 ai_task.task_no                           │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 步骤② 构建 TaskContext ────────────────────────┐
│  TaskContext.builder()                            │
│    .taskNo("TASK-20260709-a1b2c3d4")              │
│    .taskTitle("帮我分析本月销售数据")               │
│    .userInput("查询本月各产品线的销售额、同比...")  │
│    .cooperateMode("SERIAL")                        │
│    .priority(2)                                    │
│    .userId(2L)                                     │
│    .username("zhangsan")                           │
│    .build()                                        │
│                                                    │
│  此时 context 内部状态:                            │
│  - subTasks: null(待 TaskParseAgent 填充)         │
│  - agentResults: null(待各Agent执行后填充)        │
│  - sharedData: null(待执行过程中动态填充)         │
│  - startTime: null(待 Orchestrator 设置)          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 步骤③ 持久化主任务 ────────────────────────────┐
│  INSERT INTO ai_task (                             │
│    task_no, user_id, task_title, task_content,      │
│    task_type, cooperate_mode, task_status,          │
│    priority, timeout_seconds, max_retry,            │
│    start_time, create_by, create_time               │
│  ) VALUES (                                         │
│    'TASK-20260709-a1b2c3d4', 2,                     │
│    '帮我分析本月销售数据',                           │
│    '查询本月各产品线的销售额...',                    │
│    'GENERAL', 'SERIAL', 'RUNNING',                  │
│    2, 300, 3,                                       │
│    '2026-07-09 14:30:00', 'zhangsan', NOW()         │
│  )                                                  │
│                                                     │
│  数据库状态:                                        │
│  ┌──────────────────────────────────────┐          │
│  │ ai_task (id=1001)                     │          │
│  │ ├─ task_no: TASK-20260709-a1b2c3d4    │          │
│  │ ├─ task_status: RUNNING               │          │
│  │ └─ create_time: 2026-07-09 14:30:00   │          │
│  └──────────────────────────────────────┘          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 步骤④ 编排执行 ────────────────────────────────┐
│  String result = orchestrator.orchestrate(context); │
│  → 进入核心编排引擎(详见 6.1.3)                   │
│  返回值:完整的汇总报告文本                          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 步骤⑤ 统计子任务结果 ──────────────────────────┐
│  遍历 context.getSubTasks():                      │
│    子任务1(TASK_PARSE) → 成功                      │
│    子任务2(RETRIEVAL)  → 成功                      │
│    子任务3(VERIFY)     → 成功                      │
│    子任务4(SUMMARY)    → 成功                      │
│                                                    │
│  successCount = 4, failCount = 0                   │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 步骤⑥ 更新任务最终状态 ────────────────────────┐
│  UPDATE ai_task SET                                │
│    task_status = 'SUCCESS',                         │
│    result_summary = '【销售数据分析报告】...',       │
│    end_time = '2026-07-09 14:30:05',                │
│    total_duration_ms = 4521,                        │
│    update_by = 'zhangsan',                          │
│    update_time = NOW()                              │
│  WHERE task_no = 'TASK-20260709-a1b2c3d4'           │
│                                                     │
│  数据库最终状态:                                    │
│  ┌──────────────────────────────────────┐          │
│  │ ai_task (id=1001)                     │          │
│  │ ├─ task_status: SUCCESS               │          │
│  │ ├─ total_duration_ms: 4521            │          │
│  │ └─ result_summary: TEXT(完整报告)      │          │
│  └──────────────────────────────────────┘          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 步骤⑦ 构建响应 ───────────────────────────────┐
│  TaskResponse.builder()                            │
│    .taskNo("TASK-20260709-a1b2c3d4")               │
│    .taskStatus("SUCCESS")                           │
│    .result("【销售数据分析报告】...")                │
│    .subTaskCount(4)                                 │
│    .successCount(4)                                 │
│    .failCount(0)                                    │
│    .durationMs(4521)                                │
│    .createTime(2026-07-09 14:30:00)                 │
│    .build()                                         │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
          返回 ResponseEntity.ok(result)
6.1.3 第三层:Orchestrator 核心编排引擎
复制代码
Orchestrator.orchestrate(context) --- 详细执行流程
        │
        ▼
┌─ 初始化 ──────────────────────────────────────────┐
│  context.setStartTime(LocalDateTime.now())          │
│  → 2026-07-09 14:30:00.123                         │
│  log.info("[Orchestrator] 开始编排任务...")         │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
╔═════════════════════════════════════════════════════╗
║ 阶段1:任务拆解 (parseTask)                         ║
╚═════════════════════════════════════════════════════╝
        │
        ▼
┌─ 1.1 查找 TaskParseAgent ─────────────────────────┐
│  BaseAgent parseAgent = agentRouter.getAgent("TASK_PARSE") │
│  从 agentRegistry Map 中查找:                       │
│    - 命中 → TaskParseAgent 实例                     │
│    - 未命中 → 降级:直接将 userInput 作为一个子任务  │
│    - Map 查找 O(1),极快                            │
└──────────────────┬──────────────────────────────────┘
                   │ 命中
                   ▼
┌─ 1.2 TaskParseAgent.execute(context) ──────────────┐
│  内部执行步骤:                                      │
│                                                     │
│  ① 加载提示词模板                                    │
│    PromptTemplateService.loadTemplate("PROMPT_TASK_PARSE")│
│    查询链路:本地缓存 → DB → 返回默认模板             │
│    模板内容:                                        │
│    "你是一个专业的任务解析器。请将用户的原始需求       │
│     拆解为可独立执行的原子子任务。每个子任务需包含:   │
│     任务类型、任务内容、所需Agent类型、依赖关系。      │
│     以JSON数组格式返回:                              │
│     [{"taskType":"...","content":"...",              │
│       "agentCategory":"...","dependsOn":null}]"      │
│                                                     │
│  ② 调用 LLM(ChatClient)                            │
│    chatClient.prompt()                              │
│      .system(prompt)                                │
│      .user(context.getUserInput())                  │
│      .call().content()                              │
│                                                     │
│    LLM 输入(完整消息):                             │
│    ┌────────────────────────────────────────────┐   │
│    │ System: 你是一个专业的任务解析器...         │   │
│    │ User: 查询本月各产品线的销售额、同比环比     │   │
│    │       数据,生成分析报告                     │   │
│    └────────────────────────────────────────────┘   │
│                                                     │
│    LLM 输出:                                        │
│    [                                                │
│      {                                              │
│        "taskType": "DATA_QUERY",                    │
│        "content": "查询本月各产品线销售数据",        │
│        "agentCategory": "RETRIEVAL",                │
│        "dependsOn": null                            │
│      },                                             │
│      {                                              │
│        "taskType": "DATA_VERIFY",                   │
│        "content": "校验销售数据的准确性和完整性",    │
│        "agentCategory": "VERIFY",                   │
│        "dependsOn": 1                               │
│      },                                             │
│      {                                              │
│        "taskType": "REPORT",                        │
│        "content": "基于校验后的数据生成分析报告",    │
│        "agentCategory": "SUMMARY",                  │
│        "dependsOn": 2                               │
│      }                                              │
│    ]                                                │
│                                                     │
│  ③ 返回 AgentResult                                  │
│    AgentResult.success("TASK_PARSE", "任务解析Agent", jsonStr)│
│    durationMs ≈ 1200ms(LLM调用耗时)               │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
┌─ 1.3 解析子任务 JSON ─────────────────────────────┐
│  parseSubTaskList(result.getOutput())               │
│                                                     │
│  ObjectMapper.readValue(json, List.class)           │
│  遍历每个元素构建 SubTask 对象:                      │
│                                                     │
│  子任务列表:                                        │
│  ┌───────┬──────────────────┬──────────────┬──────────┐│
│  │ order │ content          │ agentCategory│ dependsOn││
│  ├───────┼──────────────────┼──────────────┼──────────┤│
│  │   1   │ 查询销售数据      │ RETRIEVAL    │   null   ││
│  │   2   │ 校验数据准确性    │ VERIFY       │    1     ││
│  │   3   │ 生成分析报告      │ SUMMARY      │    2     ││
│  └───────┴──────────────────┴──────────────┴──────────┘│
│                                                     │
│  JSON解析失败时的降级策略:                            │
│  → 将整个原始内容作为一个 BUSINESS 子任务             │
│                                                     │
│  context.setSubTasks(subTasks)  // 存入上下文         │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
╔═════════════════════════════════════════════════════╗
║ 阶段2:Agent 路由匹配 (AgentRouter.route)           ║
╚═════════════════════════════════════════════════════╝
        │
        ▼
┌─ 2.1 遍历子任务进行路由 ──────────────────────────┐
│  for (SubTask subTask : subTasks) {                 │
│    String agentCode = agentRouter.route(subTask);   │
│    subTask.setMatchedAgentCode(agentCode);           │
│  }                                                   │
│                                                     │
│  子任务1:category=RETRIEVAL                        │
│    → DB 查询:SELECT * FROM ai_agent_info           │
│               WHERE agent_category='RETRIEVAL'       │
│               AND status=1 AND is_deleted=0          │
│    → 返回:agent_code='RETRIEVAL'                    │
│    → agentRegistry.containsKey('RETRIEVAL') → true   │
│    → 路由结果:RETRIEVAL                             │
│                                                     │
│  子任务2:category=VERIFY                            │
│    → DB 查询:agent_category='VERIFY'                │
│    → 返回:agent_code='VERIFY'                       │
│    → agentRegistry.containsKey('VERIFY') → true      │
│    → 路由结果:VERIFY                                │
│                                                     │
│  子任务3:category=SUMMARY                           │
│    → DB 查询:agent_category='SUMMARY'               │
│    → 返回:agent_code='SUMMARY'                      │
│    → agentRegistry.containsKey('SUMMARY') → true     │
│    → 路由结果:SUMMARY                               │
│                                                     │
│  路由匹配的降级策略:                                  │
│  ① 精确匹配 category → 未命中                        │
│  ② 降级查 BUSINESS 类型 → 未命中                     │
│  ③ 最终降级返回 "TASK_PARSE"                         │
│                                                     │
│  路由后子任务状态:                                    │
│  ┌───────┬──────────────┬───────────────────┐       │
│  │ order │ agentCategory│ matchedAgentCode   │       │
│  ├───────┼──────────────┼───────────────────┤       │
│  │   1   │ RETRIEVAL    │ RETRIEVAL          │       │
│  │   2   │ VERIFY       │ VERIFY             │       │
│  │   3   │ SUMMARY      │ SUMMARY            │       │
│  └───────┴──────────────┴───────────────────┘       │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
╔═════════════════════════════════════════════════════╗
║ 阶段3:按协作模式执行                                ║
╚═════════════════════════════════════════════════════╝
        │
        ├─── cooperateMode=SERIAL → executeSerial()
        │
        ├─── cooperateMode=PARALLEL → executeParallel()
        │
        ▼
  【以下以 SERIAL 模式为例展开】
6.1.4 串行执行详细流程 (executeSerial)
复制代码
executeSerial(context, subTasks)
        │
        ▼
┌─ 子任务1:RETRIEVAL Agent 执行 ──────────────────┐
│                                                     │
│  ① 获取Agent实例                                    │
│    BaseAgent agent = agentRouter.getAgent("RETRIEVAL")│
│    → 返回 RetrievalAgent 实例                        │
│                                                     │
│  ② 检查依赖(dependsOn=null,跳过)                  │
│                                                     │
│  ③ 带超时执行                                        │
│    executeWithTimeout(agent, context, priority=2)    │
│    timeout = 300秒                                   │
│                                                     │
│  ④ RetrievalAgent.execute(context) 内部:            │
│    ┌────────────────────────────────────────────┐   │
│    │ a. 向量检索:                               │   │
│    │   vectorStore.similaritySearch(              │   │
│    │     SearchRequest.builder()                 │   │
│    │       .query("本月销售数据")                 │   │
│    │       .topK(5)                              │   │
│    │       .similarityThreshold(0.7)             │   │
│    │       .build()                              │   │
│    │   )                                         │   │
│    │   → 返回 3 条相关文档片段                    │   │
│    │                                             │   │
│    │ b. 拼接检索结果:                            │   │
│    │   doc1: "产品线A本月销售额500万..."          │   │
│    │   doc2: "产品线B环比增长12%..."              │   │
│    │   doc3: "整体毛利率提升至35%..."             │   │
│    │                                             │   │
│    │ c. 加载提示词模板:                          │   │
│    │   loadTemplate("PROMPT_RETRIEVAL")           │   │
│    │   → 替换 {{retrievedContent}}               │   │
│    │   → 替换 {{userQuestion}}                   │   │
│    │                                             │   │
│    │ d. LLM 生成回答:                            │   │
│    │   chatClient.prompt()                       │   │
│    │     .system(prompt)                         │   │
│    │     .user(query)                            │   │
│    │     .call().content()                       │   │
│    │                                             │   │
│    │   → "根据知识库数据,本月销售情况如下:      │   │
│    │      产品线A销售额500万(环比+8%),           │   │
│    │      产品线B销售额320万(环比+12%)..."        │   │
│    └────────────────────────────────────────────┘   │
│                                                     │
│  ⑤ 记录结果到 context                               │
│    context.recordAgentResult("RETRIEVAL", result)    │
│    context.putSharedData("subTask_1_output", output) │
│                                                     │
│  ⑥ 持久化子任务到数据库(异步)                       │
│    INSERT INTO ai_task_sub (                         │
│      task_id, task_no, sub_task_no, agent_code,       │
│      agent_name, execute_order, sub_task_content,     │
│      sub_task_status, output_snapshot,                │
│      start_time, end_time, duration_ms                │
│    ) VALUES (...)                                    │
│                                                     │
│  结果状态:✓ SUCCESS | 耗时:1800ms                  │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
┌─ 子任务2:VERIFY Agent 执行 ──────────────────────┐
│                                                     │
│  ① dependsOn=1 → 读取前置输出                        │
│    String prevOutput = context.getSharedData(         │
│        "subTask_1_output");                          │
│    context.putSharedData("previous_output", prevOutput)│
│                                                     │
│  ② VerifyAgent.execute(context) 内部:                │
│    ┌────────────────────────────────────────────┐   │
│    │ a. 提取待校验内容:                          │   │
│    │   从 sharedData 获取 "content_to_verify"    │   │
│    │   → 未设置,则从 agentResults 中收集        │   │
│    │   → 收集到 RETRIEVAL 的输出                 │   │
│    │                                             │   │
│    │ b. 加载提示词模板 PROMPT_VERIFY:            │   │
│    │   "校验以下内容的合规性、准确性、完整性...    │   │
│    │    输出JSON: {pass, issues[], score}"        │   │
│    │                                             │   │
│    │ c. LLM 校验:                                │   │
│    │   → 输入:RETRIEVAL 的输出内容               │   │
│    │   → 输出:                                   │   │
│    │   {                                         │   │
│    │     "pass": true,                           │   │
│    │     "issues": ["毛利率数据建议补充同比对比"], │   │
│    │     "score": 92                             │   │
│    │   }                                         │   │
│    └────────────────────────────────────────────┘   │
│                                                     │
│  ③ 记录结果                                         │
│    context.recordAgentResult("VERIFY", result)       │
│    context.putSharedData("subTask_2_output", output) │
│                                                     │
│  结果状态:✓ SUCCESS | 耗时:900ms                   │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
┌─ 子任务3:SUMMARY Agent 执行 ─────────────────────┐
│                                                     │
│  ① dependsOn=2 → 读取前置输出                        │
│                                                     │
│  ② SummaryAgent.execute(context) 内部:              │
│    ┌────────────────────────────────────────────┐   │
│    │ a. 收集所有 Agent 结果:                     │   │
│    │   results = context.getAgentResults()       │   │
│    │   → RETRIEVAL: "根据知识库数据..."           │   │
│    │   → VERIFY: "{pass:true, score:92}"         │   │
│    │                                             │   │
│    │ b. 拼接子任务结果文本:                       │   │
│    │   【知识检索Agent(RETRIEVAL)】               │   │
│    │   根据知识库数据,本月销售情况如下...          │   │
│    │                                             │   │
│    │   【内容校验Agent(VERIFY)】                  │   │
│    │   {"pass":true,"issues":[...],"score":92}   │   │
│    │                                             │   │
│    │ c. 加载提示词模板 PROMPT_SUMMARY:            │   │
│    │   → 替换 {{originalRequest}}                │   │
│    │   → 替换 {{subTaskResults}}                 │   │
│    │                                             │   │
│    │ d. LLM 汇总生成最终报告:                     │   │
│    │   "【销售数据分析报告】                       │   │
│    │    === 执行摘要 ===                          │   │
│    │    本月整体销售表现良好...                    │   │
│    │    === 详细分析 ===                          │   │
│    │    1. 产品线A销售额500万...                  │   │
│    │    === 结论建议 ===                          │   │
│    │    建议加强产品线B的推广力度..."              │   │
│    └────────────────────────────────────────────┘   │
│                                                     │
│  结果状态:✓ SUCCESS | 耗时:1500ms                  │
└──────────────────┬──────────────────────────────────┘
                   │
                   ▼
          所有子任务执行完毕
          context 最终状态:
          ┌───────────────────────────────────────┐
          │ agentResults:                          │
          │   RETRIEVAL → success, 1800ms           │
          │   VERIFY    → success, 900ms            │
          │   SUMMARY   → success, 1500ms           │
          │                                        │
          │ sharedData:                             │
          │   subTask_1_output → "根据知识库..."     │
          │   subTask_2_output → "{pass:true...}"   │
          │   subTask_3_output → "【销售数据...】"   │
          └───────────────────────────────────────┘
6.1.5 第四层:记忆持久化
复制代码
Orchestrator → memoryManager.persistAll(context)
        │
        ├─── saveShortTermMemory(context)  → 同步
        │
        └─── persistTaskToDb(context)      → 异步
                   │
                   ▼
┌─ L1:Redis 短期记忆 ──────────────────────────────┐
│  key: agent:task:memory:TASK-20260709-a1b2c3d4     │
│  value: TaskContext 序列化对象                      │
│  TTL: 30分钟                                        │
│                                                     │
│  Redis 存储内容:                                    │
│  ┌────────────────────────────────────────────┐   │
│  │ {                                           │   │
│  │   "taskNo": "TASK-20260709-a1b2c3d4",       │   │
│  │   "taskTitle": "帮我分析本月销售数据",        │   │
│  │   "userInput": "查询本月各产品线...",         │   │
│  │   "cooperateMode": "SERIAL",                │   │
│  │   "userId": 2,                              │   │
│  │   "username": "zhangsan",                   │   │
│  │   "agentResults": {                         │   │
│  │     "RETRIEVAL": {success:true,...},        │   │
│  │     "VERIFY": {success:true,...},           │   │
│  │     "SUMMARY": {success:true,...}           │   │
│  │   }                                         │   │
│  │ }                                           │   │
│  └────────────────────────────────────────────┘   │
│                                                     │
│  用途:同一会话30分钟内再次提问时可复用上下文        │
│  读取:memoryManager.loadShortTermMemory(taskNo)    │
└─────────────────────────────────────────────────────┘
                   │
                   ▼
┌─ L2:MySQL 持久归档(@Async 异步执行) ────────────┐
│                                                     │
│  ① 更新主任务表                                      │
│    UPDATE ai_task SET                               │
│      task_status = 'SUCCESS',                        │
│      result_summary = '【销售数据分析报告】...',      │
│      end_time = '2026-07-09 14:30:05',               │
│      total_duration_ms = 4521                        │
│    WHERE task_no = 'TASK-20260709-a1b2c3d4'          │
│                                                     │
│  ② 更新子任务表(逐个更新)                           │
│    UPDATE ai_task_sub SET                           │
│      sub_task_status = 'SUCCESS',                    │
│      output_snapshot = '根据知识库数据...',           │
│      end_time = '2026-07-09 14:30:02',               │
│      duration_ms = 1800                              │
│    WHERE task_no = 'TASK-...' AND agent_code = 'RETRIEVAL'│
│                                                     │
│    UPDATE ai_task_sub SET ...                       │
│    WHERE ... AND agent_code = 'VERIFY'               │
│                                                     │
│    UPDATE ai_task_sub SET ...                       │
│    WHERE ... AND agent_code = 'SUMMARY'              │
│                                                     │
│  归档完成后的数据库状态:                              │
│  ┌────────────────────────────────────────────┐    │
│  │ ai_task (id=1001)                           │    │
│  │ ├─ task_status: SUCCESS                    │    │
│  │ ├─ total_duration_ms: 4521                 │    │
│  │ └─ result_summary: "【销售数据分析报告】..." │    │
│  │                                             │    │
│  │ ai_task_sub (3条记录)                       │    │
│  │ ├─ sub_1: RETRIEVAL | SUCCESS | 1800ms     │    │
│  │ ├─ sub_2: VERIFY    | SUCCESS | 900ms      │    │
│  │ └─ sub_3: SUMMARY   | SUCCESS | 1500ms     │    │
│  └────────────────────────────────────────────┘    │
│                                                     │
│  异步执行失败处理:                                   │
│  → 日志记录 error 级别                               │
│  → 不影响主流程返回(用户已收到结果)                  │
│  → 可后续通过补偿任务重试                             │
└─────────────────────────────────────────────────────┘
6.1.6 完整数据流转时序图

AI大模型 Qdrant Redis SummaryAgent VerifyAgent RetrievalAgent AgentRouter TaskParseAgent Orchestrator MySQL AgentTaskService Spring Security AgentTaskController AI大模型 Qdrant Redis SummaryAgent VerifyAgent RetrievalAgent AgentRouter TaskParseAgent Orchestrator MySQL AgentTaskService Spring Security AgentTaskController #mermaid-svg-yWXab1n12zFZ765z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yWXab1n12zFZ765z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yWXab1n12zFZ765z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yWXab1n12zFZ765z .error-icon{fill:#552222;}#mermaid-svg-yWXab1n12zFZ765z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yWXab1n12zFZ765z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yWXab1n12zFZ765z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yWXab1n12zFZ765z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yWXab1n12zFZ765z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yWXab1n12zFZ765z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yWXab1n12zFZ765z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yWXab1n12zFZ765z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yWXab1n12zFZ765z .marker.cross{stroke:#333333;}#mermaid-svg-yWXab1n12zFZ765z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yWXab1n12zFZ765z p{margin:0;}#mermaid-svg-yWXab1n12zFZ765z .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yWXab1n12zFZ765z text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-yWXab1n12zFZ765z .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yWXab1n12zFZ765z .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-yWXab1n12zFZ765z .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-yWXab1n12zFZ765z .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-yWXab1n12zFZ765z #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-yWXab1n12zFZ765z .sequenceNumber{fill:white;}#mermaid-svg-yWXab1n12zFZ765z #sequencenumber{fill:#333;}#mermaid-svg-yWXab1n12zFZ765z #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-yWXab1n12zFZ765z .messageText{fill:#333;stroke:none;}#mermaid-svg-yWXab1n12zFZ765z .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yWXab1n12zFZ765z .labelText,#mermaid-svg-yWXab1n12zFZ765z .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-yWXab1n12zFZ765z .loopText,#mermaid-svg-yWXab1n12zFZ765z .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-yWXab1n12zFZ765z .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yWXab1n12zFZ765z .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-yWXab1n12zFZ765z .noteText,#mermaid-svg-yWXab1n12zFZ765z .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-yWXab1n12zFZ765z .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yWXab1n12zFZ765z .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yWXab1n12zFZ765z .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yWXab1n12zFZ765z .actorPopupMenu{position:absolute;}#mermaid-svg-yWXab1n12zFZ765z .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-yWXab1n12zFZ765z .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yWXab1n12zFZ765z .actor-man circle,#mermaid-svg-yWXab1n12zFZ765z line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-yWXab1n12zFZ765z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段1:任务拆解 阶段2:路由匹配 loop 遍历每个子任务 阶段3:串行执行 阶段4:记忆持久化 用户 POST /api/agent/task/submit 校验Token & 权限 ✓ 授权通过 submitAndExecute(request, userId, username) 生成 taskNo: TASK-20260709-xxx 构建 TaskContext INSERT ai_task (status=RUNNING) ✓ 写入成功 orchestrate(context) getAgent("TASK_PARSE") TaskParseAgent实例 execute(context) 加载提示词模板 PROMPT_TASK_PARSE 发送解析请求 JSON子任务列表 AgentResult 3个子任务 route(subTask) 查询匹配的Agent agent_code 匹配结果 execute(context) 向量相似度检索 Top-5 3条相关文档 基于文档生成回答 检索结果 AgentResult ✓ execute(context) 校验合规性/准确性 {pass:true, score:92} AgentResult ✓ execute(context) 汇总生成报告 完整分析报告 AgentResult ✓ 保存会话上下文 (TTL 30min) 异步归档任务&子任务结果 最终汇总结果 UPDATE ai_task (status=SUCCESS) TaskResponse 200 OK + 完整报告 用户


6.2 业务案例详解

案例一:销售数据分析(串行模式完整演示)

【业务背景】

某公司销售总监张三,需要快速了解本月各产品线的销售表现。他打开企业内部AI助手,输入分析需求。

【用户输入】

复制代码
POST /api/agent/task/submit
{
  "taskTitle": "本月销售数据分析",
  "taskContent": "请帮我分析本月(2026年7月)各产品线的销售情况,
                 包括销售额、环比增长率、同比增长率、毛利率,
                 并与上个月做对比,最后给出改进建议。",
  "taskType": "DATA_ANALYSIS",
  "cooperateMode": "SERIAL",
  "priority": 2
}

【完整执行过程】

步骤 组件 操作 输入 输出 耗时
1 Controller 参数校验、权限验证 请求体 userId=2, username=zhangsan ~10ms
2 Service 生成taskNo、构建Context userId, request TaskContext对象 ~5ms
3 Service 持久化主任务 AiTask对象 INSERT成功,taskId=1001 ~30ms
4 TaskParseAgent LLM解析需求 用户原始输入 3个子任务JSON ~1200ms
5 AgentRouter 路由匹配 subTask.category agent_code列表 ~15ms
6 RetrievalAgent Qdrant检索+LLM生成 查询文本 销售数据详情 ~1800ms
7 VerifyAgent LLM校验 检索结果 校验报告(score=92) ~900ms
8 SummaryAgent LLM汇总 所有结果 最终分析报告 ~1500ms
9 MemoryManager L1 Redis + L2 MySQL TaskContext 持久化完成 ~100ms
10 Service 更新任务状态 taskNo SUCCESS ~20ms
11 Controller 返回响应 TaskResponse HTTP 200 ~5ms

总耗时:约 5.6 秒

【TaskParseAgent 输出------子任务拆解】

json 复制代码
[
  {
    "taskType": "DATA_RETRIEVAL",
    "content": "从知识库中检索本月各产品线的销售数据,
               包括销售额、环比增长率、同比增长率、毛利率等指标",
    "agentCategory": "RETRIEVAL",
    "dependsOn": null
  },
  {
    "taskType": "DATA_VERIFY",
    "content": "校验检索到的销售数据是否完整准确,
               检查数据一致性,识别异常数据点",
    "agentCategory": "VERIFY",
    "dependsOn": 1
  },
  {
    "taskType": "REPORT_GENERATION",
    "content": "基于校验后的销售数据生成综合分析报告,
               包含数据概览、趋势分析、对比分析和改进建议",
    "agentCategory": "SUMMARY",
    "dependsOn": 2
  }
]

【各Agent详细输出】

RetrievalAgent 输出:

复制代码
根据知识库检索结果,本月销售数据如下:

1. 产品线A(企业SaaS):
   - 本月销售额:5,200万元
   - 环比增长:+8.3%
   - 同比增长:+15.2%
   - 毛利率:72%

2. 产品线B(数据中台):
   - 本月销售额:3,800万元
   - 环比增长:+12.1%
   - 同比增长:+28.5%
   - 毛利率:65%

3. 产品线C(AI平台):
   - 本月销售额:2,100万元
   - 环比增长:+25.6%
   - 同比增长:+42.3%
   - 毛利率:58%

VerifyAgent 输出:

json 复制代码
{
  "pass": true,
  "issues": [
    "产品线C的环比增长率25.6%偏高,建议确认数据源准确性",
    "毛利率数据建议补充成本构成明细",
    "建议增加各产品线客户数量变化数据"
  ],
  "score": 92,
  "summary": "数据基本完整准确,产品线C数据需二次确认"
}

SummaryAgent 输出(最终报告):

markdown 复制代码
# 2026年7月销售数据分析报告

## 一、执行摘要
本月三大产品线总销售额达1.11亿元,环比增长12.8%,同比增长22.6%。
其中AI平台产品线表现最为突出,环比增长25.6%。

## 二、详细分析

### 2.1 产品线A - 企业SaaS
- 销售额5,200万元,占总收入46.8%
- 环比+8.3%,同比+15.2%,保持稳健增长
- 毛利率72%,盈利能力最强
- 建议:维持现有策略,关注客户续费率

### 2.2 产品线B - 数据中台
- 销售额3,800万元,占总收入34.2%
- 环比+12.1%,同比+28.5%,增速加快
- 毛利率65%,中等水平
- 建议:加大市场推广力度,扩大客户基数

### 2.3 产品线C - AI平台
- 销售额2,100万元,占总收入19.0%
- 环比+25.6%,同比+42.3%,高速增长
- 毛利率58%,需关注成本控制
- 建议:抓住AI风口,加速产品迭代

## 三、改进建议
1. 产品线A:推出增值服务包,提升客单价
2. 产品线B:拓展政企客户,增加渠道合作
3. 产品线C:优化云成本,目标毛利率提升至65%

> ⚠️ 注意:产品线C数据需业务部门二次确认
> 校验评分:92/100

【数据库最终状态】

ai_task 表:

id task_no task_status total_duration_ms result_summary
1001 TASK-20260709-a1b2c3d4 SUCCESS 5580 (完整报告TEXT)

ai_task_sub 表:

id task_no agent_code execute_order sub_task_status duration_ms
2001 TASK-... TASK_PARSE 1 SUCCESS 1200
2002 TASK-... RETRIEVAL 2 SUCCESS 1800
2003 TASK-... VERIFY 3 SUCCESS 900
2004 TASK-... SUMMARY 4 SUCCESS 1500

案例二:合同风险审核(并行模式+异常处理)

【业务背景】

法务部门需要对一份供应商合同进行多维度风险审核。由于审核维度之间相互独立,采用并行模式可大幅提升效率。

【用户输入】

复制代码
POST /api/agent/task/submit
{
  "taskTitle": "供应商合同风险审核",
  "taskContent": "请审核以下合同条款的风险点:
  
  第5条:付款条款------甲方应于合同签订后3个工作日内支付合同总额的80%作为预付款。
  第8条:违约责任------如乙方延期交付,每延期一日支付合同总额0.01%的违约金,
         但违约金总额不超过合同总额的5%。
  第12条:保密条款------双方应对合同内容及履行过程中知悉的对方商业秘密予以保密,
         保密期限为合同终止后1年。
  第15条:争议解决------因本合同引起的争议,由乙方所在地人民法院管辖。",
  "taskType": "CONTRACT_REVIEW",
  "cooperateMode": "PARALLEL",
  "priority": 3
}

【TaskParseAgent 拆解------4个独立子任务】

json 复制代码
[
  {
    "taskType": "PAYMENT_REVIEW",
    "content": "审核付款条款:分析80%预付款比例的风险,
               对比行业惯例(通常30%-50%)",
    "agentCategory": "VERIFY",
    "dependsOn": null
  },
  {
    "taskType": "LIABILITY_REVIEW",
    "content": "审核违约责任条款:分析违约金上限5%是否合理,
               延期交付的违约金计算方式",
    "agentCategory": "VERIFY",
    "dependsOn": null
  },
  {
    "taskType": "CONFIDENTIALITY_REVIEW",
    "content": "审核保密条款:保密期限1年是否足够,
               是否覆盖合同终止后的保密需求",
    "agentCategory": "VERIFY",
    "dependsOn": null
  },
  {
    "taskType": "JURISDICTION_REVIEW",
    "content": "审核争议解决条款:乙方所在地管辖是否对甲方不利,
               建议修改为甲方所在地或仲裁",
    "agentCategory": "VERIFY",
    "dependsOn": null
  }
]

【并行执行时间线】

复制代码
时间轴:
00:00 ─┬─ 付款审核Agent 开始
       │
00:00 ─┼─ 违约责任审核Agent 开始
       │
00:00 ─┼─ 保密条款审核Agent 开始
       │
00:00 ─┼─ 争议解决审核Agent 开始
       │
01.80 ─┼─ 付款审核Agent 完成 ✓  (1800ms)
       │
02.10 ─┼─ 违约责任审核Agent 完成 ✓  (2100ms)
       │
01.95 ─┼─ 保密条款审核Agent 完成 ✓  (1950ms)
       │
02.50 ─┴─ 争议解决审核Agent 完成 ✓  (2500ms)

总耗时 = max(1800, 2100, 1950, 2500) = 2500ms
vs 串行耗时 = 1800+2100+1950+2500 = 8350ms
效率提升:3.34倍

【各Agent输出】

付款审核Agent:

json 复制代码
{
  "pass": false,
  "issues": [
    "⚠️ 高风险:预付款比例80%远超行业惯例(30%-50%)",
    "预付款未设置保函或担保机制,资金安全无保障",
    "建议修改为:合同签订后支付30%,验收后支付60%,质保期满支付10%"
  ],
  "score": 35,
  "riskLevel": "HIGH"
}

违约责任审核Agent:

json 复制代码
{
  "pass": true,
  "issues": [
    "⚠️ 中风险:违约金上限5%偏低,不足以覆盖甲方实际损失",
    "建议增加:因乙方原因导致甲方商誉损失的,不受上限限制",
    "违约金起算时点建议明确为'自约定交付日的次日起'"
  ],
  "score": 65,
  "riskLevel": "MEDIUM"
}

保密条款审核Agent:

json 复制代码
{
  "pass": false,
  "issues": [
    "⚠️ 高风险:保密期限仅1年,核心商业秘密需永久保密",
    "建议分级:一般信息保密1-3年,核心技术/客户信息永久保密",
    "缺少违反保密义务的具体赔偿标准"
  ],
  "score": 40,
  "riskLevel": "HIGH"
}

争议解决审核Agent:

json 复制代码
{
  "pass": false,
  "issues": [
    "⚠️ 高风险:约定乙方所在地管辖,甲方维权成本高",
    "建议修改为'甲方所在地人民法院管辖'或约定仲裁",
    "可折中方案:约定中国国际经济贸易仲裁委员会(CIETAC)仲裁"
  ],
  "score": 30,
  "riskLevel": "HIGH"
}

【SummaryAgent 汇总------最终审核报告】

markdown 复制代码
# 供应商合同风险审核报告

## 审核概览
| 审核维度 | 评分 | 风险等级 | 状态 |
|----------|------|----------|------|
| 付款条款 | 35/100 | 🔴 高风险 | 不通过 |
| 违约责任 | 65/100 | 🟡 中风险 | 有条件通过 |
| 保密条款 | 40/100 | 🔴 高风险 | 不通过 |
| 争议解决 | 30/100 | 🔴 高风险 | 不通过 |

## 综合评分:42/100 --- 不建议签署当前版本

## 核心风险摘要

### 🔴 必须修改项(3项)
1. **付款条款**:预付款80%过高,建议降至30%
2. **保密条款**:保密期限1年不足,核心信息需永久保密
3. **争议解决**:管辖地应改为甲方所在地或约定仲裁

### 🟡 建议修改项(1项)
4. **违约责任**:违约金上限5%偏低,建议增加例外情形

## 谈判策略建议
- 第一优先级:争议解决条款(乙方所在地→甲方所在地)
- 第二优先级:付款比例(80%→30%+分期支付)
- 第三优先级:保密期限(1年→分级管理)
- 可让步项:违约金上限(5%→8%,增加例外情形)

> 📋 审核时间:2026-07-09 15:30:00
> ⏱️ 总耗时:2500ms(并行模式)

案例三:异常场景处理------Agent执行失败与降级

【业务背景】

用户请求知识检索,但Qdrant向量库暂时不可用。系统需要优雅降级,保证不中断服务。

【用户输入】

复制代码
POST /api/agent/task/submit
{
  "taskTitle": "查询公司考勤制度",
  "taskContent": "公司年假怎么算?入职不满一年有年假吗?",
  "taskType": "KNOWLEDGE_QA",
  "cooperateMode": "SERIAL",
  "priority": 2
}

【异常场景1:Qdrant 连接超时】

复制代码
RetrievalAgent.execute(context) 执行过程:
        │
        ▼
┌─ 尝试 Qdrant 检索 ─────────────────────────────┐
│  vectorStore.similaritySearch(...)               │
│    │                                              │
│    ├─ 连接 Qdrant: http://localhost:6333          │
│    │                                              │
│    ├─ 超时 5 秒后抛出异常:                        │
│    │   java.net.ConnectException:                  │
│    │   Connection refused: localhost/127.0.0.1:6333│
│    │                                              │
│    └─ 被 try-catch 捕获                            │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ RetrievalAgent 异常处理 ────────────────────────┐
│  catch (Exception e) {                            │
│    log.error("[RetrievalAgent] 检索失败", e);     │
│    return AgentResult.fail(                       │
│        "RETRIEVAL",                                │
│        "知识检索Agent",                            │
│        "Connection refused: ..."                   │
│    );                                              │
│  }                                                 │
│                                                     │
│  返回结果:                                          │
│  {                                                  │
│    success: false,                                  │
│    agentCode: "RETRIEVAL",                          │
│    errorMsg: "Connection refused: localhost:6333"   │
│  }                                                  │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ Orchestrator 失败处理 ──────────────────────────┐
│  executeSerial() 中:                               │
│                                                     │
│  AgentResult result = executeWithTimeout(...)       │
│  context.recordAgentResult("RETRIEVAL", result)     │
│                                                     │
│  if (!result.isSuccess()) {                         │
│    log.warn("子任务失败,中断串行流水线")            │
│    break;  // ← 中断后续子任务执行                   │
│  }                                                  │
│                                                     │
│  后续 VERIFY、SUMMARY 不会执行                       │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ SummaryAgent 降级处理 ──────────────────────────┐
│  由于 VERIFY 和 SUMMARY 均未执行                    │
│  context.getAgentResults() 只有一条失败记录         │
│                                                     │
│  SummaryAgent 输出:                                 │
│  "【知识检索Agent(RETRIEVAL)】                      │
│   执行失败: Connection refused: localhost:6333      │
│                                                     │
│   抱歉,知识库检索服务暂时不可用,                   │
│   请稍后重试或联系管理员。"                          │
└──────────────────┬──────────────────────────────┘
                   │
                   ▼
┌─ 最终响应 ───────────────────────────────────────┐
│  {                                                  │
│    "code": 200,                                     │
│    "data": {                                        │
│      "taskNo": "TASK-20260709-xxx",                 │
│      "taskStatus": "PARTIAL",                       │
│      "result": "抱歉,知识库检索服务暂时不可用...",  │
│      "subTaskCount": 3,                             │
│      "successCount": 0,                             │
│      "failCount": 1,                                │
│      "durationMs": 5200                             │
│    }                                                │
│  }                                                  │
│                                                     │
│  用户收到友好提示,而非 500 错误页面                  │
└─────────────────────────────────────────────────────┘

【异常场景2:LLM 返回格式错误】

复制代码
TaskParseAgent 解析时,LLM 返回了非标准 JSON 格式:

LLM 错误输出:
"好的,我为您拆解为以下子任务:
1. 查询年假政策 - RETRIEVAL
2. 校验政策准确性 - VERIFY
3. 生成回答 - SUMMARY"

↓ parseSubTaskList() 解析失败

┌─ 降级处理 ──────────────────────────────────────┐
│  try {                                            │
│    ObjectMapper.readValue(json, List.class)       │
│    → 抛出 JsonParseException                      │
│  } catch (Exception e) {                          │
│    log.warn("子任务JSON解析失败,使用默认拆分")    │
│    return List.of(                                │
│      SubTask.builder()                            │
│        .order(1)                                  │
│        .content(json)  // 原始输出作为内容         │
│        .agentCategory("BUSINESS")                 │
│        .build()                                   │
│    );                                              │
│  }                                                 │
│                                                     │
│  降级结果:将整个LLM输出作为一个BUSINESS子任务       │
│  由通用Agent处理,保证流程不中断                     │
└─────────────────────────────────────────────────────┘

【异常场景3:并行模式下单个Agent超时】

复制代码
PARALLEL 模式,4个Agent同时执行:
        │
        ├─ Agent-A: 正常执行,50ms完成 ✓
        ├─ Agent-B: 正常执行,80ms完成 ✓
        ├─ Agent-C: 正常执行,65ms完成 ✓
        └─ Agent-D: 陷入死循环或LLM超时...
              │
              ▼
        ┌─ 超时处理 ──────────────────────────────┐
        │  executeWithTimeout(agent, context, priority)│
        │  timeout = 300秒                            │
        │                                              │
        │  Future<AgentResult> future =                │
        │    executor.submit(() -> agent.execute(...)) │
        │                                              │
        │  future.get(300, TimeUnit.SECONDS)            │
        │    │                                          │
        │    └─ 300秒后抛出 TimeoutException            │
        │                                              │
        │  catch (TimeoutException e) {                 │
        │    return AgentResult.fail(                   │
        │      agentCode,                              │
        │      "执行超时(300秒)"                      │
        │    );                                         │
        │  }                                            │
        └────────────────────────────────────────────┘

最终结果:
- Agent-A: ✓ success
- Agent-B: ✓ success
- Agent-C: ✓ success
- Agent-D: ✗ fail (超时)

CompletableFuture.allOf() 等待全部完成(或300秒超时)
SummaryAgent 汇总时正确处理混合状态

案例四:多轮对话------Redis上下文复用

【业务背景】

用户在30分钟内连续提问,系统通过Redis短期记忆实现上下文连贯。

【对话序列】

复制代码
第1轮(14:00:00):
用户:公司年假怎么算?
      ↓
系统创建 TaskContext → 存入 Redis(TTL 30min)
     key: agent:task:memory:TASK-20260709-round1
     返回:年假计算规则...

第2轮(14:05:00):
用户:那我入职不满一年有年假吗?
      ↓
系统检测到 Redis 中存在上一轮上下文
      ↓
┌─ 上下文复用流程 ─────────────────────────────────┐
│                                                     │
│  ① 从请求中提取上次 taskNo(Cookie/Header携带)      │
│     taskNo = "TASK-20260709-round1"                 │
│                                                     │
│  ② 尝试加载短期记忆                                   │
│     TaskContext prevCtx =                             │
│       memoryManager.loadShortTermMemory(taskNo)      │
│                                                     │
│     Redis GET agent:task:memory:TASK-20260709-round1│
│     → 命中!返回 TaskContext 对象                    │
│                                                     │
│  ③ 合并上下文                                        │
│     prevCtx.putSharedData("history_round_1",         │
│       "用户询问年假计算规则,已告知:...")            │
│                                                     │
│     context = prevCtx.toBuilder()                    │
│       .taskNo(newTaskNo)                            │
│       .userInput("入职不满一年有年假吗")             │
│       .build()                                       │
│                                                     │
│  ④ 执行时,Agent 可获取历史上下文                     │
│     RetrievalAgent 检索时会结合历史对话               │
│     LLM 回答:                                       │
│     "根据公司制度,入职不满一年的员工,                 │
│      年假按实际工作月数折算。您入职至今               │
│      共计X个月,可享受Y天年假。"                      │
│                                                     │
│  ⑤ 刷新 Redis TTL                                    │
│     redisTemplate.expire(key, 30, MINUTES)           │
│                                                     │
└─────────────────────────────────────────────────────┘

第3轮(14:35:00):
用户:那年假没休完能累积到明年吗?
      ↓
Redis GET → key 已过期(超过30分钟)
      ↓
返回 null → 作为新会话处理
系统:抱歉,您的会话已过期,请重新描述您的问题。

【Redis 内存结构】

复制代码
Key: agent:task:memory:TASK-20260709-round1
Value: {
  "taskNo": "TASK-20260709-round1",
  "userId": 2,
  "username": "zhangsan",
  "userInput": "公司年假怎么算?",
  "agentResults": {
    "RETRIEVAL": {
      "success": true,
      "output": "根据公司《考勤管理制度》第3章..."
    },
    "SUMMARY": {
      "success": true,
      "output": "年假计算规则:1-10年工龄享5天..."
    }
  },
  "sharedData": {
    "history_round_1": "用户询问年假计算规则..."
  }
}
TTL: 30 minutes

6.3 执行流程关键决策点总结

决策点 触发条件 处理方式 影响范围
权限不足 用户无 agent:task:submit 权限 返回 403,拒绝执行 请求终止
参数校验失败 taskTitle/taskContent 为空 返回 400,提示必填项 请求终止
TaskParseAgent 不存在 agentRegistry 中无 TASK_PARSE 降级:userInput 作为一个子任务 功能降级但可用
子任务JSON解析失败 LLM返回非标准JSON 降级:整个内容作为一个BUSINESS子任务 粒度变粗但可用
Agent路由未命中 数据库中无匹配category的Agent ①降级查BUSINESS ②最终降级TASK_PARSE 可能执行非最优Agent
串行子任务失败 某个Agent返回 success=false 中断后续子任务,记录PARTIAL状态 部分结果可用
并行子任务超时 300秒内未完成 标记为FAILED,其他继续 部分结果可用
Qdrant不可用 连接超时/拒绝 RetrievalAgent返回fail,不阻塞流程 检索功能不可用
Redis不可用 连接失败 短期记忆跳过,不影响主流程 多轮对话失效
MySQL归档失败 @Async异常 记录日志,不影响用户响应 审计记录缺失(可补偿)

七、协作模式详解

7.1 串行模式(SERIAL)

适用场景:任务之间有严格的前后依赖关系

复制代码
典型流程:查询数据 → 校验数据 → 汇总报告

优势:
  - 数据流清晰,可追溯
  - 前一步的输出是后一步的输入
  - 单步失败可立即中断

劣势:
  - 总耗时 = 各步骤耗时之和
  - 某一步慢会导致整体变慢

7.2 并行模式(PARALLEL)

适用场景:子任务相互独立,无数据依赖

复制代码
典型流程:同时查询多个数据源 → 汇总

优势:
  - 总耗时 ≈ 最慢子任务的耗时
  - 适合多数据源并行查询

劣势:
  - 无法利用前序结果
  - 需注意线程安全

八、提示词模板管理

8.1 模板变量替换规则

复制代码
模板中的占位符格式:{{variableName}}

示例模板:
"基于以下知识内容回答问题:\n{{retrievedContent}}\n\n问题:{{userQuestion}}"

运行时替换:
prompt.replace("{{retrievedContent}}", actualContent)
      .replace("{{userQuestion}}", actualQuestion)

8.2 提示词更新流程

复制代码
后台管理界面
    │ POST /api/admin/prompt/update
    ▼
更新 ai_prompt_template 表
    │
    ▼
调用 PromptTemplateService.refreshCache(templateCode)
    │
    ▼
清空对应模板的本地缓存
    │
    ▼
下次 Agent 执行时自动从 DB 加载最新版本

九、扩展指南

9.1 新增业务Agent(3步完成)

步骤1:创建 Agent 实现类

java 复制代码
package com.enterprise.agent.core.agent.impl;

import com.enterprise.agent.core.agent.AgentResult;
import com.enterprise.agent.core.agent.BaseAgent;
import com.enterprise.agent.core.context.TaskContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class DataAnalysisAgent implements BaseAgent {

    private final ChatClient chatClient;

    @Override
    public String getAgentCode() {
        return "DATA_ANALYSIS";  // ← 与数据库 agent_code 一致
    }

    @Override
    public AgentResult execute(TaskContext context) {
        long start = System.currentTimeMillis();

        try {
            String userInput = context.getUserInput();
            // 也可从 sharedData 获取前置Agent的输出
            String prevOutput = context.getSharedData("previous_output");

            String result = chatClient.prompt()
                    .system("你是一个数据分析专家...")
                    .user(userInput)
                    .call()
                    .content();

            long duration = System.currentTimeMillis() - start;
            return AgentResult.builder()
                    .success(true)
                    .agentCode(getAgentCode())
                    .agentName("数据分析Agent")
                    .output(result)
                    .durationMs(duration)
                    .build();

        } catch (Exception e) {
            log.error("[DataAnalysisAgent] 执行失败", e);
            long duration = System.currentTimeMillis() - start;
            return AgentResult.builder()
                    .success(false)
                    .agentCode(getAgentCode())
                    .agentName("数据分析Agent")
                    .errorMsg(e.getMessage())
                    .durationMs(duration)
                    .build();
        }
    }
}

步骤2:在数据库中注册Agent

sql 复制代码
INSERT INTO ai_agent_info (agent_code, agent_name, agent_type, agent_category,
    description, execute_mode, sort_order, status, create_by)
VALUES ('DATA_ANALYSIS', '数据分析Agent', 'CUSTOM', 'BUSINESS',
    '执行数据分析任务,生成数据报告', 'SYNC', 10, 1, 'admin');

步骤3:配置提示词模板(可选)

sql 复制代码
INSERT INTO ai_prompt_template (template_code, template_name, template_type, agent_code,
    template_content, variables, create_by)
VALUES ('PROMPT_DATA_ANALYSIS', '数据分析提示词', 'SYSTEM', 'DATA_ANALYSIS',
    '你是一个数据分析专家。请基于提供的数据进行分析...', 'userInput', 'admin');

完成! 重启应用后,框架会自动扫描并注册 DataAnalysisAgent,用户提交的任务如果被 TaskParseAgent 拆解出 agentCategory: "BUSINESS" 类型的子任务,就会被路由到该 Agent。

9.2 扩展点清单

扩展点 方式 说明
新增Agent 实现 BaseAgent + 数据库注册 最常用的扩展方式
新增路由策略 实现自定义 Router 类 替代默认的 category 匹配
新增协作模式 扩展 Orchestrator 的执行方法 如 DAG 依赖图执行
自定义提示词 修改 ai_prompt_template 无需改代码,即时生效
接入新的向量库 替换 VectorStore 实现 Spring AI 统一抽象

文档版本 :v1.1

最后更新 :2026-07-09

更新说明 :新增第六章「详细执行流程与案例说明」,包含端到端执行流程详解、4个业务案例(销售数据分析、合同风险审核、异常场景处理、多轮对话上下文复用)、关键决策点总结表

适用版本:Spring Boot 3.2.x / Spring AI 1.0.0-M6 / JDK 17 / MySQL 8.0 / Redis 7.x / Qdrant

相关推荐
星辰AI1 小时前
ESLint 自定义规则实战:用 AST 分析封堵团队特有的代码坏味道
人工智能·ai·语言模型
AI小码2 小时前
WAIC 2026前瞻:AI产业进入拼落地的下半场
人工智能·算法·ai·程序员·大模型·编程·智能体
小企鹅么么2 小时前
【AI应用开发工程师】第六章:Context Engineering
人工智能·ai
霸道流氓气质2 小时前
Java 工程师 AI 智能体学习路线 · 阶段 3:编程调用 LLM 详解
ai
ASKCOS3 小时前
深度解析 Kimi Code Swarm 模式:多 Agent 并行编程的架构设计与工程权衡
ai
曦尧5 小时前
Argo CD:面向 Kubernetes 的声明式 GitOps 持续交付工具
ai·自动化
ofoxcoding5 小时前
Codex Computer Use 完整指南:AI 自动操控 Mac 与 Windows 桌面实战详解
人工智能·windows·macos·ai
奇牙coding1235 小时前
OpenAI Realtime API WebSocket 断连 4008/1006 怎么解决?不是 Key 失效,是实时多模态独有的会话超时规则
python·websocket·网络协议·ai
结城明日奈是我老婆6 小时前
comfyui 控制图像Controlnet
ai·ai作画