企业级 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