Files
skills/mis-agent-spec/SKILL.md
T
V-LiuShuang 58e9193267 add
2026-08-19 13:39:09 +08:00

482 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: mis-agent-spec
description: 在mis-agent模块编写代码必备技能。
---
# Intro
请结合[目录结构](#目录结构)与[核心技术栈](#核心技术栈),并严格遵守[代码编写规范](#代码编写规范)生成代码,被要求编写HTTP接口文档时,请参考[HTTP接口文档示例](./examples/api-doc.md)。
## 核心技术栈
- JavaOpenJDK21
- spring-boot
- 版本:3.2.9
- 文档:https://docs.spring.io/spring-boot/docs/3.2.9/reference/html
- 源码:https://github.com/spring-projects/spring-boot/tree/v3.2.9
- spring-framework
- 版本:6.1.12
- 文档:https://docs.spring.io/spring-framework/reference/6.2/index.html
- 源码:https://github.com/spring-projects/spring-framework/tree/v6.1.12
- mybatis-plus
- 版本:3.5.7
- 文档:https://github.com/baomidou/mybatis-plus-doc
- 源码:https://github.com/baomidou/mybatis-plus/tree/v3.5.7
- MySQL
- 版本:8.0.25
- 文档:https://dev.mysql.com/doc/refman/8.0/en/
- 源码:https://github.com/mysql/mysql-server/tree/mysql-8.0.25
- Redis8.0+
- redisson-spring-boot-starter
- 版本:3.34.1
- 文档:https://redisson.pro/docs/integration-with-spring/#usage
- 源码:https://github.com/redisson/redisson/tree/redisson-3.34.1/redisson-spring-boot-starter/src/main
- RocketMQ
- 文档:https://rocketmq.apache.org/zh/docs/4.x
- rocketmq-client
- 版本:4.9.8
- 源码:https://github.com/apache/rocketmq/tree/rocketmq-all-4.9.8
- rocketmq-spring-boot-starter
- 版本:2.2.3
- 源码:https://github.com/apache/rocketmq-spring/tree/rocketmq-spring-all-2.2.3
- agentscope-java
- 版本:1.0.11
- 文档:https://java.agentscope.io/v1/zh/docs/task/agent-as-tool.html
- 源码:https://github.com/agentscope-ai/agentscope-java/tree/v1.0.11
- spring-ai
- 版本:1.1.2
- 文档:https://docs.spring.io/spring-ai/reference/1.1/index.html
- 源码:https://github.com/spring-projects/spring-ai/tree/v1.1.2
- spring-ai-alibaba
- 版本:1.1.2.2
- 文档:https://java2ai.com/docs/overview
- 源码:https://github.com/alibaba/spring-ai-alibaba/tree/v1.1.2.2
- hutool-core
- 版本:5.8.42
- 文档:https://github.com/chinabugotech/hutool-site/tree/master/docs/core
- 源码:https://github.com/chinabugotech/hutool/tree/5.8.42/hutool-core
- hutool-json
- 版本:5.8.42
- 文档:https://github.com/chinabugotech/hutool-site/tree/master/docs/json
- 源码:https://github.com/chinabugotech/hutool/tree/5.8.42/hutool-json
- spring-cloud-alibaba
- 版本:2023.0.1.2
- 文档:https://sca.aliyun.com/docs/2023/overview/version-explain
- 源码:https://github.com/alibaba/spring-cloud-alibaba/tree/2023.0.1.2
## 目录结构
### 项目根目录
```text
<root>\
├── mis-api\ # 微服务之间暴露的dubbo接口
├── mis-auth\ # API网关鉴权服务
├── mis-common\ # 存放多个模块的公共依赖
├── mis-gateway\ # API网关
├── mis-modules\ # 业务模块代码根目录
├── mis-visual\ # 与业务无关的子模块
└── pom.xml # 在<profiles>中定义了Nacos地址及账号密码、NewAPI的地址和密钥
```
### 模块根目录
```text
mis-modules\mis-agent\src\main\
├── java
│ └── com\lcfc\agent\
│ ├── agents\ # 智能体核心
│ │ ├── a2a\ # A2A Agent
│ │ ├── config\ # Spring配置类
│ │ ├── constants\ # 常量类
│ │ ├── context\ # 自定义上下文载体类
│ │ ├── controller\ # SpringWeb控制器
│ │ ├── dubbo\ # Dubbo接口实现类
│ │ ├── enums\ # 枚举类
│ │ ├── events\ # Spring事件消息
│ │ ├── mapper\ # Mybatis Mapper接口
│ │ ├── multiagent\ # 多智能体
│ │ ├── saa\ # 单智能体
│ │ ├── service\ # Service接口
│ │ │ └── impl\ # Service接口实现类
│ │ ├── util\ # 工具类
│ │ ├── validator\ # 验证器
│ │ └── domain\ # 数据库实体类
│ │ ├── DTO\ # 数据传输对象
│ │ └── VO\ # 视图对象
│ ├── eventhub\ # 事件发布订阅(非SpringBean可用)
│ │ ├── MisAgentEventHub.java # 注册监听器,broadcast推送事件
│ │ └── MisAgentEventListener.java # 所有监听器须实现此接口
│ ├── memory\ # 长期记忆
│ │ ├── UserLongTermChannel.java # 记忆通道(如hindsight
│ │ ├── UserLongTermMemoryClient.java # 用户级长期记忆客户端接口
│ │ ├── UserLongTermMemoryConfig.java # 用户级长期记忆配置
│ │ └── UserLongTermMemoryCtl.java # 用户级长期记忆控制类
│ ├── monitor\ # 智能体监控(收集tool/skill/workflow/知识库调用及token消耗)
│ │ ├── AgentChatMetricsCtx.java # 监控数据上下文
│ │ ├── AgentChatMetricsFinishEvent.java# 监控数据收集完成事件
│ │ ├── AgentChatMetricsFinishListener.java # 监控数据完成监听器
│ │ ├── IAgentChatMetricsConsumer.java # 消费监控数据接口
│ │ ├── MisAgentChatMetricsCtl.java # 监控上下文控制类
│ │ └── MisMcpToolMetadataCtl.java # MCP元数据控制类
│ ├── skills\ # 智能体Skill
│ │ ├── BaseSkillToolsExecutor.java # Skill脚本执行工具父类
│ │ ├── AgentScopeSkillTools.java # 基于agentscope-java执行Skill脚本
│ │ ├── SpringAiSkillTools.java # 基于spring-ai执行Skill脚本
│ │ ├── AgentSkillMetadataHandler.java # Skill元数据(磁盘记录数量)
│ │ ├── AgentSkillResourceHandler.java # Skill静态资源处理(下载/删除)
│ │ ├── AgentSkillRuntimeHandler.java # Skill运行时临时目录管理(创建/删除)
│ │ ├── AgentSkillRuntimeConfigHandler.java # Skill运行时自定义配置(读取/删除)
│ │ ├── AgentSkillSessionCache.java # Skill临时目录缓存(自动续期/删除)
│ │ ├── RemoveSkillConsumer.java # 智能体解绑Skill事件 / 服务端Skill删除事件
│ │ ├── SkillRemoveConsumer.java # (待确认用途)
│ │ ├── SkillResourceChangeConsumer.java # Skill静态资源更新事件处理
│ │ └── SkillStateChangeConsumer.java # Skill状态变更事件处理(禁用/启用)
│ ├── utils\ # 公共工具包
│ │ ├── AsyncTaskTemplate.java # 多线程异步任务模板
│ │ ├── RLockTemplate.java # 基于Redis的分布式锁
│ │ └── TransactionTaskTemplate.java # 编程式Spring事务模板
│ └── stats\ # 项目数据统计
│ ├── api\ # Controller
│ ├── service\ # Service
│ ├── conf\ # 配置类
│ ├── domain\
│ │ ├── dto\ # DTO
│ │ ├── vo\ # VO
│ │ ├── enums\ # Enum
│ │ ├── entity\ # Entity
│ │ └── mapper\ # Mapper Java源文件
│ └── mq\
│ ├── event\ # 消息载体
│ └── consumer\ # 监听器
└── resources
├── mapper\ # Mybatis Mapper XML
└── application.yml # 配置文件
```
## 代码编写规范
| 类目 | 使用原则 | 描述 |
| --- | --- | --- |
| JDK11~21的新特性 | 积极使用 | 局部变量类型推断、Switch表达式、模式匹配、文本块、记录类、密封类、虚拟线程,虚拟线程应当用于IO密集型的任务,而不是CPU密集型任务。|
| 设计模式 | 非常看重 | 为了降低代码耦合度,提高可维护性,请积极的采用工厂、模板、策略、发布订阅等常用的设计模式。一个Java源文件的代码行数不得超过300行,超出则拆成多个文件,一个方法体内的行数不得超过20行,超出则拆成多个方法。对于发布订阅模式,请根据以下情况判断:跨服务通信、集群实例广播消息,优先使用RocketMQ消息队列。进程内通信则使用ApplicationEventPublisher和@EventListener即可。|
| Lombok注解 | 积极使用 | 无需担心编译速度被拖慢。|
| Null值处理 | 适当应用 | 优先考虑`Optional.ofNullable(obj)`而不是`if (obj == null)` |
| Stream | 积极使用 | 优先使用Stream而不是循环,若map代码块超过5行就单独写一个方法,如果需要中间对象则优先使用记录类,记录类和DTO存放在一个目录。|
| 工具类 | 积极使用 | 优先采纳hutool,除非hutool没有提供才新建一个工具类。|
| 构建对象 | 非常看重 | 构建JavaBean对象,优先使用构造函数而不是单独写工具类或方法,只有3个以内的属性,应提供一个全部参数的构造函数。|
| List | 适当应用 | 在非多线程场景,默认用ArrayList就够了。只有需要按照插入顺序遍历元素的场景,才考虑使用LinkedList。需考虑线程安全用CopyOnWriteArrayList。|
| Set | 适当应用 | 在非多线程场景,默认用HashSet。对排序要求考虑用LinkedHashSet或TreeSet。需考虑线程安全用`cn.hutool.core.collection.ConcurrentHashSet`、ConcurrentSkipListSet、CopyOnWriteArraySet等。|
| Map | 适当应用 | 在非线程场景,默认使用HashMap就够了。对排序要求考虑用LinkedHashMap或TreeMap。如果存在并发场景,需考虑线程安全用ConcurrentHashMap或ConcurrentSkipListMap等JUC工具包。|
| Queue | 适当应用 | 要控制内存,用ArrayBlockingQueue。经典生产者消费者,用LinkedBlockingQueue。要窃取任务,用LinkedBlockingDeque。追求极致吞吐且不怕队列暴涨,用ConcurrentLinkedQueue。必须双端且高吞吐,用ConcurrentLinkedDeque。|
| Lock | 适当应用 | 分布式锁使用Redisson相关API,单机使用ReentrantLock、StampedLock、ReentrantReadWriteLock即可。|
## 实体类与MySQL字段类型映射规则
|实体类字段类型|MySQL字段类型|
| --- | --- |
| String | VARCHAT(255) |
| Long | BIGINT |
| Integer | INT |
| LocalDate | DATE |
| LocalDateTime | DATETIME |
| BigDecimal | DECIMAL(14, 2) |
- 数据库表结构字段名称、实体类字段名称,采用驼峰与下划线映射的方式。
- `@TableField`注解中`typeHandler`等于`JacksonTypeHandler.class`时,表结构字段默认JSON类型。
- 实体类字段包含`@NotNull`注解,需要结合`@Schema`注解中的`defaultValue`属性判断是否需要设置默认值。
- 主键列是否采用MySQL自增序列,取决于实体类被`@TableId`注解修饰的字段,要看其`type`属性,如果是`IdType.AUTO`则使用自增,否则不需要。
- 如果`@Schema`注解指定了`maxLength`属性,按如下原则设置字段类型:
- 数值小于或等于2000,使用VARCHAR
- 数值大于2000且小于10000,使用TEXT
- 数值大于10000,使用LONGTEXT
## MySQL表结构示例
```sql
CREATE TABLE `example_table` (
`id` BIGINT NOT NULL COMMENT 'ID',
`str_field` VARCHAR(32) NOT NULL COMMENT '字符串`,
`int_field` INT DEFAULT NULL COMMENT '`,
`long_field` BIGINT DEFAULT NULL COMMENT '长整数`,
`decimal_field` DECIMAL(14, 2) DEFAULT NULL COMMENT '`,
`date_field` DATE NOT NULL COMMENT '日期类型`,
`time_field` DATETIME NOT NULL COMMENT '`,
`state` VARCHAR(32) NOT NULL COMMENT '状态:only_read、read_write',
`json_field` JSON DATETIME NULL COMMENT 'JSON类型'
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='示例表';
```
## 实体类示例
```java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.math.BigDecimal;
@Data
@TableName(value = "example_table", autoResultMap = true)
@Schema(title = "示例表")
public class ExampleEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
@TableField(value = "id")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@NotBlank
@Schema(description = "字符串")
@TableField(value = "str_field")
private String strField;
@Schema(description = "整数", defaultValue = "0")
@TableField(value = "int_field")
private Integer intField;
@Schema(description = "长整数")
@TableField(value = "long_field")
@JsonSerialize(using = ToStringSerializer.class)
private Long longField;
@Schema(description = "高精度浮点数")
@TableField(value = "decimal_field")
private BigDecimal decimalField;
@NotNull
@Schema(description = "日期类型", pattern = "yyyy-MM-dd")
@TableField(value = "date_field")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate dateField;
@NotNull
@Schema(description = "时间类型", pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(value = "time_field")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime timeField;
@NotNull
@Schema(
title = "状态",
description = "参考示例值",
example = StateEnum.DESC
)
@TableField(value = "state")
private StateEnum state;
@Schema(description = "JSON类型")
@TableField(value = "json_field", typeHandler = JacksonTypeHandler.class)
private JsonField jsonField;
@Data
public static class JsonField implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "示例字段1")
private String field1;
@Schema(description = "示例字段2")
private Integer field2;
}
}
```
## 枚举类示例
```java
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
@Schema(description = "某某状态")
public enum StateEnum {
ONLY_READ("only_read"),
READ_WRITE("read_write");
public static final String DESC = "only_read=只读、read_write=读写";
@JsonValue
@EnumValue
private final String value;
@JsonCreator
public static StateEnum deserialize(String input) {
return Arrays.stream(values()).filter(e -> e.getValue().equals(input)).findFirst().orElse(null);
}
}
```
## DTO示例
```java
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.math.BigDecimal;
@Data
@Schema(title = "DTO示例")
public class ExampleDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank
@Schema(description = "字符串")
private String strField;
@Schema(description = "长整数")
@JsonSerialize(using = ToStringSerializer.class)
private Long longField;
@NotNull
@Schema(description = "日期类型", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private LocalDate dateField;
@NotNull
@Schema(description = "时间类型", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime timeField;
@NotNull
@Schema(
title = "状态",
description = "参考示例值",
example = StateEnum.DESC
)
private StateEnum state;
}
```
## FAQ
### 怎么确认用哪个profile
若当前Git分支出于dev则使用dev,否则使用qas。
### 怎么读profile的配置?
从[项目根目录](#项目根目录)的`pom.xml`文件中获取不同`profiles.active`的配置,示例:
```xml
<profile>
<id>dev</id>
<properties>
<!-- 环境标识,可用的数值有:dev、qas -->
<profiles.active>dev</profiles.active>
<!-- 当前环境的Nacos服务地址 -->
<nacos.server>127.0.0.1:8868</nacos.server>
<!-- 当前环境的Nacos注册中心分组名称 -->
<nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
<!-- 当前环境的Nacos配置中心分组名称 -->
<nacos.config.group>DEFAULT_GROUP</nacos.config.group>
<!-- 当前环境的Nacos登录账号 -->
<nacos.username>nacos</nacos.username>
<!-- 当前环境的Nacos登录密码 -->
<nacos.password>1234567890</nacos.password>
<!-- 当前环境的NewAPI服务地址 -->
<newAPI.url>http://127.0.0.1:3000</newAPI.url>
<!-- 当前环境的NewAPI模型ApiKey -->
<newAPI.key>sk-xxx</newAPI.key>
</properties>
</profile>
```
### 怎么打包?
系统已安装`mvnd`用来替换`mvn`命令,先确认该用哪个profile,获取`profiles.active`后在[项目根目录](#项目根目录)执行命令`mvnd clean package -DskipTests -P{profiles.active}`**耗时约80秒**。
### 怎么读取nacos配置文件?
按步骤一步步执行:
1. 先确认该用哪个profile,从**项目根目录**的`pom.xml`获取以下几个属性:
- `profiles.active`
- `nacos.server`
- `nacos.config.group`
- `nacos.username`
- `nacos.password`
2. 获取accessToken。
请求示例:
```bash
curl -X POST 'http://{nacos.server}/nacos/v1/auth/login' -d 'username={nacos.username}&password={nacos.password}'
```
输出示例:
```json
{"accessToken":"xxx"}
```
如果输出不包含accessToken字段视为失败,**最多重试2次,仍然失败则直接中断整个流程。**
3. 获取配置文件信息。
- `dataId`:如果要请求MySQL执行SQL使用`datasource.yml`,否则使用`mis-agent.yml`
- `accessToken`:上一步得到的`accessToken`
请求示例:
```bash
curl -X GET "http://{nacos.server}/nacos/v3/console/cs/config?dataId={dataId}&groupName={groupName}&namespaceId={profiles.active}" -H "Authorization: Bearer {accessToken}"
```
输出示例:
```json
{
"code": 0,
"message": "success",
"data": {"content": "..."}
}
```
`data.content`就是配置内容,通常是yaml格式。