静态缓存页面 · 查看动态版本 · 登录
智柴网 登录 | 注册
← 返回话题
✨步子哥 @steper · 2025-11-08 22:46

JManus 存储层架构与设计思想深度解析

引言

JManus 作为基于 Spring AI Alibaba 构建的 AI Agent 管理系统,其存储层设计体现了现代分布式系统中数据持久化的最佳实践。本文将从架构和设计思想的角度,深入剖析 JManus 存储层的设计理念、技术选型、核心组件以及实现细节,为读者呈现一个完整的企业级存储解决方案。

一、整体架构概览

1.1 架构设计理念

JManus 存储层采用分层解耦、多数据库支持、领域驱动设计三大核心设计理念:

  • 分层解耦:通过 Repository 模式将数据访问逻辑与业务逻辑分离,实现关注点分离
  • 多数据库支持:支持 H2、MySQL、PostgreSQL 三种数据库,满足不同场景需求
  • 领域驱动设计:按照业务领域划分存储模块,每个领域拥有独立的实体和存储逻辑

1.2 技术栈选型

核心依赖:

  • Spring Data JPA 3.5.6:提供声明式数据访问
  • Hibernate:ORM 框架,支持自动 DDL 生成
  • HikariCP:高性能数据库连接池
  • Jackson:JSON 序列化与反序列化
  • Spring AI:AI 对话内存管理

二、多数据库支持架构

2.1 数据库配置策略

JManus 采用配置文件分离的策略,为每种数据库提供独立的配置:

#### H2 数据库配置(开发环境)

spring:
  datasource:
    url: jdbc:h2:file:./h2-data/openmanus_db;MODE=MYSQL;DATABASE_TO_LOWER=TRUE
    driver-class-name: org.h2.Driver
    username: sa
    password: $FSD#@!@#!#$!12341234
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: update

#### MySQL 配置(生产环境)

spring:
  datasource:
    url: jdbc:mysql://your-mysql-host:3306/openmanus_db?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: your_mysql_username
    password: your_mysql_password
  jpa:
    database-platform: org.hibernate.dialect.MySQLDialect
    hibernate:
      ddl-auto: update

#### PostgreSQL 配置(企业环境)

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/openmanus_db
    driver-class-name: org.postgresql.Driver
    username: postgres
    password: 123456
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: update

2.2 连接池优化配置

JManus 使用 HikariCP 作为连接池,提供了精细化的配置:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20          # 最大连接数
      minimum-idle: 5                # 最小空闲连接
      connection-timeout: 30000      # 连接超时时间
      idle-timeout: 600000           # 空闲连接超时
      max-lifetime: 1800000          # 连接最大生命周期
      pool-name: Spring-AI-Alibaba-JManus-${spring.profiles.active}-Pool
      connection-test-query: SELECT 1
      validation-timeout: 5000
      leak-detection-threshold: 60000

2.3 数据库特定内存支持

JManus 为每种数据库实现了特定的聊天内存存储:

#### 抽象基类设计

public abstract class JdbcChatMemoryRepository implements ChatMemoryRepository {
    protected abstract String hasTableSql(String tableName);
    protected abstract String createTableSql(String tableName);
    protected abstract String getAddSql();
    protected abstract String getGetSql();
}

#### H2 实现

public class H2ChatMemoryRepository extends JdbcChatMemoryRepository {
    @Override
    protected String createTableSql(String tableName) {
        return String.format(
            "CREATE TABLE %s (id BIGINT AUTO_INCREMENT PRIMARY KEY, "
            + "conversation_id VARCHAR(256) NOT NULL, content LONGTEXT NOT NULL, "
            + "type VARCHAR(100) NOT NULL, timestamp TIMESTAMP NOT NULL, "
            + "CONSTRAINT chk_message_type CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL')))",
            tableName);
    }
}

#### MySQL 实现

public class MysqlChatMemoryRepository extends JdbcChatMemoryRepository {
    @Override
    protected String hasTableSql(String tableName) {
        return String.format(
            "SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '%s'",
            tableName);
    }
}

#### PostgreSQL 实现

public class PostgresChatMemoryRepository extends JdbcChatMemoryRepository {
    @Override
    protected String createTableSql(String tableName) {
        return String.format(
            "CREATE TABLE %s (id BIGSERIAL PRIMARY KEY, "
            + "conversation_id VARCHAR(256) NOT NULL, content TEXT NOT NULL, "
            + "type VARCHAR(100) NOT NULL, timestamp TIMESTAMP NOT NULL, "
            + "CONSTRAINT chk_message_type CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL')))",
            tableName);
    }
}

三、实体设计与 JPA 实现

3.1 实体设计原则

JManus 的实体设计遵循以下原则:

1. 领域边界清晰:每个业务领域拥有独立的实体 2. 关系映射合理:使用适当的 JPA 关系注解 3. 索引优化:为频繁查询的字段添加索引 4. 数据完整性:使用约束保证数据质量

3.2 核心实体架构

#### 计划执行记录实体

@Entity
@Table(name = "plan_execution_record")
public class PlanExecutionRecordEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "current_plan_id", nullable = false, unique = true)
    private String currentPlanId;
    
    @Column(name = "root_plan_id")
    private String rootPlanId;
    
    @Column(name = "parent_plan_id")
    private String parentPlanId;
    
    @Column(name = "user_request", columnDefinition = "LONGTEXT")
    private String userRequest;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "plan_execution_id")
    private List<AgentExecutionRecordEntity> agentExecutionSequence;
}

#### Agent 执行记录实体

@Entity
@Table(name = "agent_execution_record", indexes = { @Index(columnList = "step_id") })
public class AgentExecutionRecordEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "step_id", unique = true)
    private String stepId;
    
    @Column(name = "agent_request", columnDefinition = "LONGTEXT")
    private String agentRequest;
    
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "agent_execution_record_id")
    private List<ThinkActRecordEntity> thinkActSteps;
}

3.3 复杂类型转换

JManus 使用 JPA 属性转换器处理复杂数据类型:

@Converter
public class MapToStringConverter implements AttributeConverter<Map<String, String>, String> {
    private final ObjectMapper objectMapper;
    
    @Override
    public String convertToDatabaseColumn(Map<String, String> attribute) {
        try {
            return objectMapper.writeValueAsString(attribute);
        } catch (Exception e) {
            throw new IllegalArgumentException("Error converting map to string", e);
        }
    }
    
    @Override
    public Map<String, String> convertToEntityAttribute(String dbData) {
        if (dbData == null || dbData.isEmpty()) {
            return new HashMap<>();
        }
        try {
            return objectMapper.readValue(dbData, new TypeReference<>() {});
        } catch (Exception e) {
            throw new IllegalArgumentException("Error converting string to map", e);
        }
    }
}

四、执行记录与审计追踪

4.1 分层执行记录架构

JManus 采用三层执行记录架构

#### 计划执行记录层

  • 记录整个计划的执行生命周期
  • 维护计划层次结构(根计划、父计划、子计划)
  • 存储用户请求和执行结果摘要
#### Agent 执行记录层
  • 记录每个 Agent 的执行过程
  • 维护执行状态和时间戳
  • 关联具体的模型信息
#### 思考-行动记录层
  • 记录 Agent 的思考和行动过程
  • 存储工具调用信息和结果
  • 支持并行工具执行记录

4.2 执行记录服务实现

@Service
public class NewRepoPlanExecutionRecorder implements PlanExecutionRecorder {
    
    @Transactional
    public Long recordPlanExecutionStart(String currentPlanId, String title, String userRequest,
            List<ExecutionStep> executionSteps, String parentPlanId, String rootPlanId, String toolcallId) {
        // 检查计划是否已存在
        Optional<PlanExecutionRecordEntity> existingPlanOpt = 
            planExecutionRecordRepository.findByCurrentPlanId(currentPlanId);
        
        PlanExecutionRecordEntity planExecutionRecordEntity;
        if (existingPlanOpt.isPresent()) {
            // 更新现有计划
            planExecutionRecordEntity = existingPlanOpt.get();
        } else {
            // 创建新计划
            planExecutionRecordEntity = new PlanExecutionRecordEntity(currentPlanId);
        }
        
        // 设置层次关系
        if (rootPlanId != null && !rootPlanId.trim().isEmpty()) {
            createPlanRelationship(currentPlanId, parentPlanId, rootPlanId, toolcallId);
        }
        
        return planExecutionRecordRepository.save(planExecutionRecordEntity).getId();
    }
}

4.3 事务管理策略

JManus 在关键业务操作中使用声明式事务管理:

@Transactional
public void recordCompleteAgentExecution(ExecutionStep step) {
    // 查询 Agent 执行记录
    Optional<AgentExecutionRecordEntity> agentRecordOpt = 
        agentExecutionRecordRepository.findByStepId(step.getStepId());
    
    if (!agentRecordOpt.isPresent()) {
        throw new IllegalArgumentException("agent record is null");
    }
    
    AgentExecutionRecordEntity agentRecord = agentRecordOpt.get();
    
    // 更新执行状态
    agentRecord.setStatus(ExecutionStatusEntity.FINISHED);
    agentRecord.setEndTime(LocalDateTime.now());
    
    // 保存更新
    agentExecutionRecordRepository.save(agentRecord);
}

五、MCP 配置存储架构

5.1 MCP 配置模型设计

Model Context Protocol (MCP) 配置存储体现了 JManus 对 AI 工具生态的深度支持:

@Entity
@Table(name = "mcp_config")
public class McpConfigEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String mcpServerName;
    
    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private McpConfigType connectionType;
    
    @Column(nullable = false, length = 4000)
    private String connectionConfig;
    
    @Column(nullable = false, columnDefinition = "VARCHAR(10) DEFAULT 'ENABLE'")
    @Enumerated(EnumType.STRING)
    private McpConfigStatus status = McpConfigStatus.ENABLE;
}

5.2 MCP 状态管理

JManus 实现了完整的 MCP 服务状态管理:

public enum McpConfigStatus {
    ENABLE,    // 启用状态
    DISABLE,   // 禁用状态
    ERROR,     // 错误状态
    CONNECTING // 连接中状态
}

public enum McpConfigType {
    HTTP,      // HTTP 连接
    WEBSOCKET, // WebSocket 连接
    STDIO      // 标准输入输出
}

5.3 MCP 配置验证与连接管理

@Service
public class McpService {
    
    public McpConfigVO validateAndConnect(McpServerRequestVO request) {
        // 配置验证
        McpConfigEntity configEntity = mcpConfigRepository
            .findByMcpServerName(request.getServerName());
        
        if (configEntity == null) {
            throw new McpConfigNotFoundException(
                "MCP config not found: " + request.getServerName());
        }
        
        // 连接测试
        McpConnection connection = mcpConnectionFactory
            .createConnection(configEntity);
        
        // 状态更新
        configEntity.setStatus(McpConfigStatus.ENABLE);
        mcpConfigRepository.save(configEntity);
        
        return convertToVO(configEntity);
    }
}

六、计划模板与执行计划存储

6.1 计划模板存储架构

计划模板存储支持版本管理和参数化配置:

@Entity
@Table(name = "plan_template")
public class PlanTemplate {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "plan_template_id", length = 50, unique = true, nullable = false)
    private String planTemplateId;
    
    @Column(name = "title", length = 255)
    private String title;
    
    @Column(name = "user_request", length = 4000)
    private String userRequest;
    
    @Column(name = "is_internal_toolcall", nullable = false)
    private boolean isInternalToolcall = false;
}

6.2 计划版本管理

@Entity
@Table(name = "plan_template_version")
public class PlanTemplateVersion {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "plan_template_id", nullable = false)
    private PlanTemplate planTemplate;
    
    @Column(name = "version", nullable = false)
    private Integer version;
    
    @Column(name = "content", columnDefinition = "LONGTEXT")
    private String content;
    
    @Column(name = "is_current", nullable = false)
    private boolean isCurrent = false;
}

6.3 动态计划创建与存储

@Service
public class DynamicAgentPlanCreator {
    
    public PlanExecutionRecordEntity createDynamicPlan(
            String planTemplateId, 
            Map<String, Object> parameters,
            String parentPlanId,
            String rootPlanId) {
        
        // 获取计划模板
        PlanTemplate template = planTemplateRepository
            .findByPlanTemplateId(planTemplateId)
            .orElseThrow(() -> new PlanTemplateNotFoundException(planTemplateId));
        
        // 参数映射
        Map<String, Object> mappedParameters = planParameterMappingService
            .mapParameters(template, parameters);
        
        // 创建执行计划
        PlanExecutionRecordEntity executionPlan = new PlanExecutionRecordEntity();
        executionPlan.setCurrentPlanId(generatePlanId());
        executionPlan.setTitle(template.getTitle());
        executionPlan.setUserRequest(processTemplate(template.getUserRequest(), mappedParameters));
        executionPlan.setParentPlanId(parentPlanId);
        executionPlan.setRootPlanId(rootPlanId);
        
        return planExecutionRecordRepository.save(executionPlan);
    }
}

七、用户管理与命名空间隔离

7.1 用户实体设计

@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username", unique = true, nullable = false, length = 50)
    private String username;
    
    @Column(name = "email", unique = true, nullable = false, length = 100)
    private String email;
    
    @Column(name = "display_name", length = 100)
    private String displayName;
    
    @Column(name = "status", length = 20)
    private String status;
    
    @ElementCollection
    @CollectionTable(name = "user_preferences", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "preference")
    private List<String> preferences;
}

7.2 命名空间隔离机制

@Entity
@Table(name = "namespace")
public class NamespaceEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "name", nullable = false, unique = true, length = 100)
    private String name;
    
    @Column(name = "code", nullable = false, unique = true, length = 50)
    private String code;
    
    @Column(name = "description", length = 500)
    private String description;
}

7.3 数据初始化策略

JManus 使用 Spring Boot 的 CommandLineRunner 实现数据初始化:

@Component
public class UserDataInitializer implements CommandLineRunner {
    
    @Override
    public void run(String... args) throws Exception {
        initializeDefaultUsers();
    }
    
    private void initializeDefaultUsers() {
        if (!userRepository.existsByUsername("jmanus_user")) {
            UserEntity defaultUser = new UserEntity(
                "jmanus_user", 
                "user@jmanus.ai", 
                "JManus User");
            defaultUser.setStatus("active");
            defaultUser.setCreatedAt(LocalDateTime.now().minusDays(30));
            defaultUser.setPreferences(Arrays.asList("dark_mode", "notifications_enabled"));
            
            userRepository.save(defaultUser);
        }
    }
}

八、内存管理与聊天历史存储

8.1 聊天内存架构设计

JManus 实现了多数据库支持的聊天内存存储:

8.2 内存配置管理

@Configuration
public class MemoryConfig {
    
    @Bean
    public ChatMemoryRepository chatMemoryRepository(JdbcTemplate jdbcTemplate) {
        ChatMemoryRepository chatMemoryRepository = null;
        
        if (mysqlEnabled) {
            chatMemoryRepository = MysqlChatMemoryRepository
                .mysqlBuilder()
                .jdbcTemplate(jdbcTemplate)
                .build();
        } else if (postgresEnabled) {
            chatMemoryRepository = PostgresChatMemoryRepository
                .postgresBuilder()
                .jdbcTemplate(jdbcTemplate)
                .build();
        } else if (h2Enabled) {
            chatMemoryRepository = H2ChatMemoryRepository
                .h2Builder()
                .jdbcTemplate(jdbcTemplate)
                .build();
        }
        
        return chatMemoryRepository;
    }
}

8.3 消息窗口管理

@Bean
public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
    return MessageWindowChatMemory.builder()
        .chatMemoryRepository(chatMemoryRepository)
        .build();
}

九、文件上传与静态资源管理

9.1 文件上传配置

manus:
  file-upload:
    max-file-size: 1073741824          # 1GB
    max-files-per-upload: 10
    upload-directory: uploaded_files
    validation-strategy: code          # code 或 config

9.2 文件验证策略

JManus 提供两种文件验证策略:

1. 代码验证策略:通过代码逻辑验证文件类型 2. 配置验证策略:通过配置文件定义允许的文件类型

9.3 文件存储服务

@Service
public class FileUploadService {
    
    public FileUploadResult uploadFiles(MultipartFile[] files, String uploadId) {
        List<FileInfo> uploadedFiles = new ArrayList<>();
        
        for (MultipartFile file : files) {
            // 文件验证
            if (!fileValidationService.validateFile(file)) {
                throw new FileValidationException("Invalid file type: " + file.getOriginalFilename());
            }
            
            // 文件存储
            String filePath = storeFile(file, uploadId);
            FileInfo fileInfo = createFileInfo(file, filePath);
            uploadedFiles.add(fileInfo);
        }
        
        return new FileUploadResult(uploadId, uploadedFiles);
    }
}

十、定时任务与调度存储

10.1 Cron 任务实体设计

@Entity
@Table(name = "cron_tasks")
public class CronEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "cron_name", nullable = false, length = 100)
    private String cronName;
    
    @Column(name = "cron_expression", nullable = false, length = 100)
    private String cronExpression;
    
    @Column(name = "plan_desc", columnDefinition = "TEXT")
    private String planDescription;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false)
    private TaskStatus status;
}

10.2 动态任务调度

@Service
public class DynamicCronTaskScheduler {
    
    public void scheduleCronTask(CronEntity cronTask) {
        // 解析 Cron 表达式
        CronExpression cronExpression = CronExpression.parse(cronTask.getCronExpression());
        
        // 创建调度任务
        ScheduledFuture<?> scheduledTask = taskScheduler.schedule(
            () -> executeCronTask(cronTask),
            cronExpression
        );
        
        // 存储任务引用
        scheduledTasks.put(cronTask.getId(), scheduledTask);
    }
}

十一、模型配置与动态模型管理

11.1 动态模型实体设计

@Entity
@Table(name = "dynamic_models")
public class DynamicModelEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "model_name", nullable = false, unique = true, length = 100)
    private String modelName;
    
    @Column(name = "base_url", nullable = false, length = 500)
    private String baseUrl;
    
    @Column(name = "api_key", length = 500)
    private String apiKey;
    
    @Convert(converter = MapToStringConverter.class)
    @Column(name = "headers", columnDefinition = "TEXT")
    private Map<String, String> headers;
    
    @Column(name = "is_default")
    private Boolean isDefault = false;
    
    @Column(name = "temperature")
    private Double temperature = 0.7;
    
    @Column(name = "top_p")
    private Double topP = 1.0;
}

11.2 模型缓存策略

@Service
public class ModelServiceImpl implements ModelService {
    
    // 第三方 API 调用缓存,2秒过期
    private final Map<String, CacheEntry<List<AvailableModel>>> apiCache = 
        new ConcurrentHashMap<>();
    
    private static final long CACHE_EXPIRY_MS = 2000; // 2秒
    
    public List<AvailableModel> getAvailableModels(String baseUrl, String apiKey) {
        String cacheKey = baseUrl + ":" + apiKey;
        
        // 检查缓存
        CacheEntry<List<AvailableModel>> cachedEntry = apiCache.get(cacheKey);
        if (cachedEntry != null && !cachedEntry.isExpired()) {
            return cachedEntry.getData();
        }
        
        // 调用 API
        List<AvailableModel> models = callThirdPartyApiInternal(baseUrl, apiKey);
        
        // 缓存结果
        apiCache.put(cacheKey, new CacheEntry<>(models));
        
        return models;
    }
}

11.3 模型验证与连接测试

public ValidationResult validateConfig(String baseUrl, String apiKey) {
    try {
        // 1. 验证 Base URL 格式
        if (!isValidBaseUrl(baseUrl)) {
            return ValidationResult.invalid("Base URL format is incorrect");
        }
        
        // 2. 验证 API Key 格式
        if (!isValidApiKey(apiKey)) {
            return ValidationResult.invalid("API Key format is incorrect");
        }
        
        // 3. 调用第三方 API 验证
        List<AvailableModel> models = callThirdPartyApiInternal(baseUrl, apiKey);
        
        return ValidationResult.valid(models);
        
    } catch (AuthenticationException e) {
        return ValidationResult.invalid("API Key is invalid or expired");
    } catch (NetworkException e) {
        return ValidationResult.invalid("Network connection failed");
    }
}

十二、数据初始化与迁移策略

12.1 数据初始化架构

JManus 采用模块化初始化策略,每个业务领域拥有独立的数据初始化器:

12.2 初始化实现模式

所有初始化器遵循统一的模式:

@Component
public class DataInitializer implements CommandLineRunner {
    
    private static final Logger logger = LoggerFactory.getLogger(DataInitializer.class);
    
    @Override
    public void run(String... args) throws Exception {
        logger.info("Starting {} data initialization", getInitializerName());
        
        try {
            if (shouldInitialize()) {
                initializeData();
                logger.info("{} data initialization completed successfully", getInitializerName());
            } else {
                logger.info("{} data already exists, skipping initialization", getInitializerName());
            }
        } catch (Exception e) {
            logger.error("Error during {} data initialization: {}", getInitializerName(), e.getMessage(), e);
            // 初始化失败不应阻止应用启动
        }
    }
    
    protected abstract String getInitializerName();
    protected abstract boolean shouldInitialize();
    protected abstract void initializeData();
}

12.3 幂等性保证

所有初始化操作都具有幂等性:

private void initializeDefaultUsers() {
    // 检查是否已存在,避免重复创建
    if (!userRepository.existsByUsername("jmanus_user")) {
        UserEntity defaultUser = new UserEntity(
            "jmanus_user", 
            "user@jmanus.ai", 
            "JManus User");
        defaultUser.setStatus("active");
        defaultUser.setCreatedAt(LocalDateTime.now());
        
        userRepository.save(defaultUser);
        logger.info("Created default user: {}", defaultUser.getUsername());
    }
}

十三、事务管理与数据一致性

13.1 事务管理策略

JManus 采用声明式事务管理,在关键业务操作中使用 @Transactional 注解:

#### 计划执行记录事务

@Transactional
public Long recordPlanExecutionStart(String currentPlanId, String title, String userRequest,
        List<ExecutionStep> executionSteps, String parentPlanId, String rootPlanId, String toolcallId) {
    // 创建或更新计划执行记录
    PlanExecutionRecordEntity planRecord = createOrUpdatePlan(currentPlanId, title, userRequest);
    
    // 创建 Agent 执行记录
    List<AgentExecutionRecordEntity> agentRecords = createAgentExecutionRecords(executionSteps);
    
    // 建立关联关系
    planRecord.setAgentExecutionSequence(agentRecords);
    
    // 创建层次关系
    if (rootPlanId != null) {
        createPlanRelationship(currentPlanId, parentPlanId, rootPlanId, toolcallId);
    }
    
    // 保存所有实体
    return planExecutionRecordRepository.save(planRecord).getId();
}

#### Agent 执行完成事务

@Transactional
public void recordCompleteAgentExecution(ExecutionStep step) {
    // 查询 Agent 执行记录
    AgentExecutionRecordEntity agentRecord = agentExecutionRecordRepository
        .findByStepId(step.getStepId())
        .orElseThrow(() -> new IllegalArgumentException("Agent record not found"));
    
    // 更新执行状态
    agentRecord.setStatus(convertAgentStateToExecutionStatus(step.getStatus()));
    agentRecord.setEndTime(LocalDateTime.now());
    agentRecord.setResult(step.getResult());
    
    // 保存更新
    agentExecutionRecordRepository.save(agentRecord);
}

13.2 数据一致性保证

#### 乐观锁机制 对于可能并发更新的实体,使用版本字段实现乐观锁:

@Entity
public class PlanExecutionRecordEntity {
    @Version
    private Long version;
    
    // 其他字段...
}

#### 唯一约束保证 通过数据库唯一约束防止重复数据:

@Entity
@Table(name = "agent_execution_record", 
       uniqueConstraints = @UniqueConstraint(columnNames = "step_id"))
public class AgentExecutionRecordEntity {
    @Column(name = "step_id", unique = true)
    private String stepId;
}

#### 业务层一致性检查 在业务逻辑层进行一致性验证:

private void createPlanRelationship(String currentPlanId, String parentPlanId, 
                                   String rootPlanId, String toolcallId) {
    // 验证必需参数
    if (currentPlanId == null || currentPlanId.trim().isEmpty()) {
        throw new IllegalArgumentException("currentPlanId is required");
    }
    
    if (rootPlanId == null || rootPlanId.trim().isEmpty()) {
        throw new IllegalArgumentException("rootPlanId is required");
    }
    
    // 验证业务规则
    if (parentPlanId != null && !parentPlanId.trim().isEmpty()) {
        if (toolcallId == null || toolcallId.trim().isEmpty()) {
            throw new IllegalArgumentException(
                "toolcallId is required when parentPlanId is provided");
        }
        
        if (rootPlanId.equals(currentPlanId)) {
            throw new IllegalArgumentException(
                "rootPlanId cannot equal currentPlanId when parentPlanId is provided");
        }
    }
}

十四、性能优化与索引策略

14.1 数据库索引设计

JManus 针对高频查询场景设计了优化的索引策略:

#### 执行记录索引

@Entity
@Table(name = "agent_execution_record", 
       indexes = { @Index(columnList = "step_id") })
public class AgentExecutionRecordEntity {
    @Column(name = "step_id", unique = true)
    private String stepId;
}

#### 计划执行记录复合索引

@Entity
@Table(name = "plan_execution_record", 
       indexes = {
           @Index(columnList = "current_plan_id"),
           @Index(columnList = "root_plan_id"),
           @Index(columnList = "parent_plan_id")
       })
public class PlanExecutionRecordEntity {
    // 实体字段...
}

#### MCP 配置索引

@Entity
@Table(name = "mcp_config", 
       indexes = { @Index(columnList = "mcp_server_name") })
public class McpConfigEntity {
    @Column(nullable = false, unique = true)
    private String mcpServerName;
}

14.2 查询优化策略

#### 延迟加载策略

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "plan_execution_id")
private List<AgentExecutionRecordEntity> agentExecutionSequence;

#### 批量操作优化

@Override
public void saveAll(String conversationId, List<Message> messages) {
    // 先删除旧数据
    this.deleteByConversationId(conversationId);
    
    // 批量插入新数据
    this.jdbcTemplate.batchUpdate(getAddSql(), 
        new AddBatchPreparedStatement(conversationId, messages));
}

14.3 缓存策略

#### API 调用缓存

// 第三方 API 调用缓存,2秒过期
private final Map<String, CacheEntry<List<AvailableModel>>> apiCache = 
    new ConcurrentHashMap<>();

private static final long CACHE_EXPIRY_MS = 2000; // 2秒

public List<AvailableModel> getAvailableModels(String baseUrl, String apiKey) {
    String cacheKey = baseUrl + ":" + apiKey;
    
    // 检查缓存
    CacheEntry<List<AvailableModel>> cachedEntry = apiCache.get(cacheKey);
    if (cachedEntry != null && !cachedEntry.isExpired()) {
        return cachedEntry.getData();
    }
    
    // 调用 API 并缓存结果
    List<AvailableModel> models = callThirdPartyApiInternal(baseUrl, apiKey);
    apiCache.put(cacheKey, new CacheEntry<>(models));
    
    return models;
}

#### 默认模型缓存

@Service
public class LlmService {
    private volatile ChatModel defaultChatModel;
    
    public void refreshDefaultModelCache() {
        // 刷新默认模型缓存
        DynamicModelEntity defaultModel = dynamicModelRepository
            .findByIsDefaultTrue();
        
        if (defaultModel != null) {
            this.defaultChatModel = createChatModel(defaultModel);
        }
    }
}

14.4 数据库连接池优化

#### HikariCP 配置优化

spring:
  datasource:
    hikari:
      maximum-pool-size: 20          # 根据服务器配置调整
      minimum-idle: 5                # 保持最小连接数
      connection-timeout: 30000      # 连接超时时间
      idle-timeout: 600000           # 空闲连接超时
      max-lifetime: 1800000          # 连接最大生命周期
      leak-detection-threshold: 60000 # 连接泄露检测

#### 连接池监控

@Component
public class DatabaseConnectionPoolMonitor {
    
    @Autowired
    private DataSource dataSource;
    
    @Scheduled(fixedDelay = 60000) // 每分钟监控一次
    public void monitorConnectionPool() {
        if (dataSource instanceof HikariDataSource) {
            HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
            
            logger.info("Connection Pool Stats - Active: {}, Idle: {}, Total: {}, Waiting: {}", 
                hikariDataSource.getHikariPoolMXBean().getActiveConnections(),
                hikariDataSource.getHikariPoolMXBean().getIdleConnections(),
                hikariDataSource.getHikariPoolMXBean().getTotalConnections(),
                hikariDataSource.getHikariPoolMXBean().getThreadsAwaitingConnection());
        }
    }
}

十五、设计思想总结

15.1 架构设计原则

JManus 存储层的设计体现了以下核心原则:

#### 1. 领域驱动设计 (DDD)

  • 按照业务领域划分存储模块
  • 每个领域拥有独立的实体、仓库和服务
  • 清晰的领域边界和职责分离
#### 2. 分层架构思想
  • 表现层 → 业务层 → 数据访问层 → 数据存储层
  • 每层职责单一,依赖关系清晰
  • 通过接口实现层间解耦
#### 3. 配置驱动开发
  • 通过配置控制不同数据库的支持
  • 灵活的配置文件分离策略
  • 环境特定的配置优化
#### 4. 企业级特性支持
  • 完整的事务管理机制
  • 数据一致性保证
  • 性能优化和监控
  • 安全性和权限控制

15.2 技术创新点

#### 1. 多数据库统一抽象 通过抽象基类和模板方法模式,实现了对 H2、MySQL、PostgreSQL 的统一支持,同时保留了各数据库的特性优化。

#### 2. 分层执行记录架构 创新的三层执行记录架构(计划→Agent→思考行动),为 AI 执行过程提供了完整的审计追踪能力。

#### 3. 动态模型管理 支持运行时动态配置和切换 AI 模型,通过缓存策略优化性能,为 AI 应用提供了灵活的模型管理能力。

#### 4. MCP 生态集成 深度集成 Model Context Protocol,为 AI 工具生态提供了标准化的配置和管理机制。

15.3 最佳实践总结

#### 1. 数据访问最佳实践

  • 使用 Spring Data JPA 简化数据访问
  • 通过 Repository 模式实现数据访问抽象
  • 合理使用索引优化查询性能
  • 采用延迟加载避免 N+1 查询问题
#### 2. 事务管理最佳实践
  • 在业务服务层使用声明式事务
  • 合理控制事务边界和粒度
  • 使用只读事务优化查询性能
  • 实现幂等性保证重复操作安全
#### 3. 性能优化最佳实践
  • 数据库连接池的合理配置
  • 缓存策略的灵活运用
  • 批量操作减少数据库交互
  • 索引设计的针对性优化
#### 4. 可维护性最佳实践
  • 统一的编码规范和命名约定
  • 完善的日志记录和监控
  • 模块化的代码组织结构
  • 详尽的文档和注释

结语

JManus 的存储层架构设计充分体现了现代分布式系统中数据持久化的先进理念和最佳实践。通过多数据库支持、领域驱动设计、分层架构、事务管理、性能优化等多个维度的深入设计,构建了一个高可用、高性能、可扩展、易维护的企业级存储解决方案。

该架构不仅满足了当前 AI Agent 管理系统的需求,也为未来的功能扩展和性能优化奠定了坚实的基础。其设计思想和实现模式对于构建类似的企业级应用具有重要的参考价值。

随着 AI 技术的不断发展,JManus 的存储架构也将持续演进,为 AI 应用提供更加强大和灵活的数据支撑能力。

👍 1