()
+ .eq(Ai1SkillDO::getStatus, status)
+ .orderByAsc(Ai1SkillDO::getId));
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/redis/Ai1RedisKeyConstants.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/redis/Ai1RedisKeyConstants.java
new file mode 100644
index 0000000..8a1c01d
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/redis/Ai1RedisKeyConstants.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.ai1.dal.redis;
+
+/**
+ * AI1 Redis Key 枚举类
+ *
+ * @author 芋道源码
+ */
+public interface Ai1RedisKeyConstants {
+
+ /**
+ * 会话生成任务队列
+ *
+ * KEY 格式:ai1:session:tasks
+ * VALUE 数据类型:Stream 生成任务,字段为 tenantId、messageId、agentId、sessionId、content
+ *
+ * 所有租户共用一个队列,KEY 不带 tenantId;worker 从任务的 tenantId 字段还原租户上下文
+ */
+ String SESSION_TASK_STREAM = "ai1:session:tasks";
+
+ /**
+ * 会话生成任务队列的消费组
+ */
+ String SESSION_TASK_GROUP = "ai1-session-workers";
+
+ /**
+ * 会话生成任务执行锁,按助手消息编号隔离;使用 Redisson 看门狗续期
+ *
+ * KEY 格式:ai1:session:task-lock:{messageId}
+ */
+ String SESSION_TASK_LOCK = "ai1:session:task-lock:%d";
+
+ /**
+ * 会话结果流,按助手消息编号隔离
+ *
+ * KEY 格式:ai1:session:result:{messageId}
+ * VALUE 数据类型:Stream 生成事件,字段为 type、data
+ *
+ * messageId 全局唯一,KEY 不带 tenantId;续传前已按租户、用户校验消息归属
+ */
+ String SESSION_RESULT_STREAM = "ai1:session:result:%d";
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/Ai1ErrorCodeConstants.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/Ai1ErrorCodeConstants.java
new file mode 100644
index 0000000..f73328f
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/Ai1ErrorCodeConstants.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.ai1.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * AI1 错误码枚举类
+ *
+ * ai1 系统,使用 1-041-000-000 段
+ */
+public interface Ai1ErrorCodeConstants {
+
+ // ========== 供应商 1-041-000-000 ==========
+ ErrorCode PROVIDER_NOT_EXISTS = new ErrorCode(1_041_000_000, "供应商不存在");
+ ErrorCode PROVIDER_DISABLE = new ErrorCode(1_041_000_001, "供应商({}) 已禁用");
+ ErrorCode PROVIDER_HAS_MODEL = new ErrorCode(1_041_000_003, "供应商下存在模型,禁止删除");
+ ErrorCode PROVIDER_REMOTE_MODEL_LOAD_FAIL = new ErrorCode(1_041_000_004, "模型拉取失败:请检查供应商地址与 API 密钥");
+ ErrorCode PROVIDER_REMOTE_MODEL_EMPTY = new ErrorCode(1_041_000_005, "远程未返回可用模型");
+ ErrorCode CONFIG_PLACEHOLDER_NOT_RESOLVED = new ErrorCode(1_041_000_006, "配置({})无法解析,请检查环境变量或配置项");
+
+ // ========== 模型 1-041-001-000 ==========
+ ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_041_001_000, "模型不存在");
+ ErrorCode MODEL_DISABLE = new ErrorCode(1_041_001_001, "模型({}) 已禁用");
+ ErrorCode MODEL_DUPLICATE = new ErrorCode(1_041_001_002, "模型标识({}) 已存在");
+ ErrorCode MODEL_USED_BY_AGENT = new ErrorCode(1_041_001_003, "模型已被 Agent 使用,禁止删除");
+ ErrorCode MODEL_NOT_BELONG_PROVIDER = new ErrorCode(1_041_001_004, "模型不属于该供应商");
+ ErrorCode MODEL_TYPE_NOT_CHAT = new ErrorCode(1_041_001_005, "所选模型不是对话模型");
+ ErrorCode MODEL_TYPE_NOT_EMBEDDING = new ErrorCode(1_041_001_006, "所选模型不是嵌入模型");
+ ErrorCode MODEL_IMPORT_ALL_EXISTS = new ErrorCode(1_041_001_007, "所选模型均已导入,无需重复导入");
+ ErrorCode MODEL_USED_BY_KNOWLEDGE_BASE = new ErrorCode(1_041_001_008, "模型已被知识库使用,禁止删除");
+ ErrorCode MODEL_TYPE_USED_BY_AGENT = new ErrorCode(1_041_001_009, "模型已被 Agent 使用,禁止修改类型");
+ ErrorCode MODEL_TYPE_USED_BY_KNOWLEDGE_BASE = new ErrorCode(1_041_001_010, "模型已被知识库使用,禁止修改类型");
+
+ // ========== 知识库 1-041-002-000 ==========
+ ErrorCode KNOWLEDGE_BASE_NOT_EXISTS = new ErrorCode(1_041_002_000, "知识库不存在");
+ ErrorCode KNOWLEDGE_BASE_DOCUMENT_EMPTY = new ErrorCode(1_041_002_001, "知识库下暂无文档");
+ ErrorCode KNOWLEDGE_BASE_SEARCH_FAIL = new ErrorCode(1_041_002_002, "向量检索失败:{}");
+ ErrorCode KNOWLEDGE_BASE_USED_BY_AGENT = new ErrorCode(1_041_002_003, "知识库已被 Agent 使用,禁止删除");
+
+ // ========== 知识文档 1-041-003-000 ==========
+ ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_041_003_000, "文档不存在");
+ ErrorCode KNOWLEDGE_DOCUMENT_CONTENT_EMPTY = new ErrorCode(1_041_003_001, "文档内容为空,无法向量化");
+ ErrorCode KNOWLEDGE_DOCUMENT_FILE_TYPE_INVALID = new ErrorCode(1_041_003_002, "仅支持 .txt / .md 文本文件");
+ ErrorCode KNOWLEDGE_DOCUMENT_FILE_EMPTY = new ErrorCode(1_041_003_003, "文件内容为空");
+ ErrorCode KNOWLEDGE_DOCUMENT_FILE_READ_FAIL = new ErrorCode(1_041_003_004, "文件读取失败:{}");
+ ErrorCode KNOWLEDGE_DOCUMENT_VECTORIZE_FAIL = new ErrorCode(1_041_003_005, "向量化失败:{}");
+
+ // ========== MCP 1-041-004-000 ==========
+ ErrorCode MCP_NOT_EXISTS = new ErrorCode(1_041_004_000, "MCP 不存在");
+ ErrorCode MCP_CONFIG_INVALID = new ErrorCode(1_041_004_001, "config 配置需为合法的 JSON 对象");
+ ErrorCode MCP_COMMAND_REQUIRED = new ErrorCode(1_041_004_002, "本地(stdio)类型必须在 config 中配置 command 命令");
+ ErrorCode MCP_URL_REQUIRED = new ErrorCode(1_041_004_003, "远程(http)类型必须配置服务地址");
+
+ // ========== SKILL 1-041-005-000 ==========
+ ErrorCode SKILL_NOT_EXISTS = new ErrorCode(1_041_005_000, "SKILL 不存在");
+ ErrorCode SKILL_USED_BY_AGENT = new ErrorCode(1_041_005_001, "SKILL 已被 Agent 使用,禁止删除");
+ ErrorCode SKILL_NAME_DUPLICATE = new ErrorCode(1_041_005_002, "SKILL 名称({}) 已存在");
+
+ // ========== SKILL 内容文件 1-041-006-000 ==========
+ ErrorCode SKILL_FILE_NOT_EXISTS = new ErrorCode(1_041_006_000, "文件或目录不存在");
+ ErrorCode SKILL_FILE_NAME_INVALID = new ErrorCode(1_041_006_001, "名称格式不合法(不能包含路径分隔符、控制字符,且不能为 . 或 ..)");
+ ErrorCode SKILL_FILE_NAME_DUPLICATE = new ErrorCode(1_041_006_002, "同级下已存在同名节点({})");
+ ErrorCode SKILL_FILE_PARENT_INVALID = new ErrorCode(1_041_006_003, "父节点必须是同一 SKILL 下的目录");
+ ErrorCode SKILL_FILE_LOCKED = new ErrorCode(1_041_006_004, "固定文件或目录禁止{}");
+ ErrorCode SKILL_FILE_MOVE_TO_SELF = new ErrorCode(1_041_006_005, "不能移入自身");
+ ErrorCode SKILL_FILE_NOT_FILE = new ErrorCode(1_041_006_006, "目录不支持保存内容");
+ ErrorCode SKILL_FILE_MOVE_TO_DESCENDANT = new ErrorCode(1_041_006_007, "不能移入自身的子目录");
+ ErrorCode SKILL_FILE_PARENT_NOT_EXISTS = new ErrorCode(1_041_006_008, "父目录不存在");
+
+ // ========== Agent 1-041-007-000 ==========
+ ErrorCode AGENT_NOT_EXISTS = new ErrorCode(1_041_007_000, "Agent 不存在");
+ ErrorCode AGENT_DISABLE = new ErrorCode(1_041_007_001, "Agent({}) 已关闭");
+
+ // ========== 会话 1-041-008-000 ==========
+ ErrorCode SESSION_NOT_EXISTS = new ErrorCode(1_041_008_000, "会话不存在");
+
+ // ========== 会话消息 1-041-009-000 ==========
+ ErrorCode SESSION_MESSAGE_NOT_EXISTS = new ErrorCode(1_041_009_000, "会话消息不存在");
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/knowledge/Ai1KnowledgeDocumentStatusEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/knowledge/Ai1KnowledgeDocumentStatusEnum.java
new file mode 100644
index 0000000..b8fda48
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/knowledge/Ai1KnowledgeDocumentStatusEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.ai1.enums.knowledge;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * AI1 知识文档向量化状态的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1KnowledgeDocumentStatusEnum implements ArrayValuable {
+
+ UNPROCESSED(0, "未处理"),
+ VECTORIZED(1, "已向量化"),
+ FAILED(2, "失败");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(Ai1KnowledgeDocumentStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/mcp/Ai1McpTransportEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/mcp/Ai1McpTransportEnum.java
new file mode 100644
index 0000000..f9da8e7
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/mcp/Ai1McpTransportEnum.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.ai1.enums.mcp;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * AI1 MCP 传输方式的枚举
+ *
+ * 取值与 MCP 配置 JSON 中的 transport 字段一致
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1McpTransportEnum implements ArrayValuable {
+
+ HTTP("http", "远程(Streamable HTTP)"),
+ STDIO("stdio", "本地进程(stdio)");
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(Ai1McpTransportEnum::getTransport).toArray(String[]::new);
+
+ /**
+ * 传输方式
+ */
+ private final String transport;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isStdio(String transport) {
+ return ObjUtil.equal(STDIO.transport, transport);
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/model/Ai1ModelTypeEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/model/Ai1ModelTypeEnum.java
new file mode 100644
index 0000000..d26c5a6
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/model/Ai1ModelTypeEnum.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.ai1.enums.model;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * AI1 模型类型的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1ModelTypeEnum implements ArrayValuable {
+
+ CHAT(0, "对话"),
+ EMBEDDING(1, "嵌入");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(Ai1ModelTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isChat(Integer type) {
+ return ObjUtil.equal(CHAT.type, type);
+ }
+
+ public static boolean isEmbedding(Integer type) {
+ return ObjUtil.equal(EMBEDDING.type, type);
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionMessageRoleEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionMessageRoleEnum.java
new file mode 100644
index 0000000..2597969
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionMessageRoleEnum.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.ai1.enums.session;
+
+import cn.hutool.core.util.ObjUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * AI1 会话消息角色的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1SessionMessageRoleEnum {
+
+ USER("user", "用户"),
+ ASSISTANT("assistant", "助手");
+
+ /**
+ * 角色
+ */
+ private final String role;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static boolean isUser(String role) {
+ return ObjUtil.equal(USER.role, role);
+ }
+
+ public static boolean isAssistant(String role) {
+ return ObjUtil.equal(ASSISTANT.role, role);
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionMessageStatusEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionMessageStatusEnum.java
new file mode 100644
index 0000000..e545c98
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionMessageStatusEnum.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.ai1.enums.session;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * AI1 会话消息生成状态的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1SessionMessageStatusEnum implements ArrayValuable {
+
+ GENERATING(0, "生成中"), // 助手消息占位,可按消息编号续传
+ SUCCESS(1, "完成"),
+ FAILED(2, "失败");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(Ai1SessionMessageStatusEnum::getStatus).toArray(Integer[]::new);
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isGenerating(Integer status) {
+ return ObjUtil.equal(GENERATING.status, status);
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionStreamEventEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionStreamEventEnum.java
new file mode 100644
index 0000000..b11cd60
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/session/Ai1SessionStreamEventEnum.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.ai1.enums.session;
+
+import cn.hutool.core.util.ObjUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * AI1 会话流事件的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1SessionStreamEventEnum {
+
+ STREAM("stream", "结果流标识"), // 连接建立时下发,data 为助手消息编号
+ THINKING("thinking", "思考过程增量"),
+ MESSAGE("message", "回复内容增量"),
+ PING("ping", "空闲心跳"),
+ DONE("done", "生成结束"),
+ ERROR("error", "生成失败"); // data 为错误提示
+
+ /**
+ * 事件
+ */
+ private final String event;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ public static boolean isTerminal(String event) {
+ return ObjUtil.equal(DONE.event, event) || ObjUtil.equal(ERROR.event, event);
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/skill/Ai1SkillFileTypeEnum.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/skill/Ai1SkillFileTypeEnum.java
new file mode 100644
index 0000000..73fa785
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/enums/skill/Ai1SkillFileTypeEnum.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.ai1.enums.skill;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * AI1 SKILL 内容节点类型的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum Ai1SkillFileTypeEnum implements ArrayValuable {
+
+ DIRECTORY(0, "目录"),
+ FILE(1, "文件");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(Ai1SkillFileTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 名称
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isDirectory(Integer type) {
+ return ObjUtil.equal(DIRECTORY.type, type);
+ }
+
+ public static boolean isFile(Integer type) {
+ return ObjUtil.equal(FILE.type, type);
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/config/Ai1AutoConfiguration.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/config/Ai1AutoConfiguration.java
new file mode 100644
index 0000000..af986cd
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/config/Ai1AutoConfiguration.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.ai1.framework.ai.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 芋道 AI1 自动配置
+ *
+ * 模型、向量存储、MCP 客户端均由 framework/ai/core 与 tool 层按数据库配置程序化构建,这里只负责注册 {@link YudaoAi1Properties}
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(YudaoAi1Properties.class)
+public class Ai1AutoConfiguration {
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/config/YudaoAi1Properties.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/config/YudaoAi1Properties.java
new file mode 100644
index 0000000..d36d982
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/config/YudaoAi1Properties.java
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.module.ai1.framework.ai.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 芋道 AI1 配置类
+ *
+ * @author 芋道源码
+ */
+@ConfigurationProperties(prefix = "yudao.ai1")
+@Data
+public class YudaoAi1Properties {
+
+ /**
+ * Milvus 向量数据库
+ */
+ private Milvus milvus = new Milvus();
+
+ /**
+ * 会话流
+ */
+ private Session session = new Session();
+
+ /**
+ * SKILL 技能
+ */
+ private Skill skill = new Skill();
+
+ @Data
+ public static class Milvus {
+
+ /**
+ * 连接地址
+ */
+ private String uri = "http://127.0.0.1:19530";
+ /**
+ * 用户名
+ */
+ private String username;
+ /**
+ * 密码
+ */
+ private String password;
+ /**
+ * 数据库
+ */
+ private String database = "default";
+ /**
+ * 知识库集合名前缀,实际集合名为 {collectionPrefix}{知识库编号}
+ */
+ private String collectionPrefix = "knowledge_base_";
+ /**
+ * 默认检索数量,知识库未配置检索数量时使用
+ */
+ private Integer defaultTopK = 5;
+
+ }
+
+ @Data
+ public static class Session {
+
+ /**
+ * 默认系统指令中的 Agent 名称占位符
+ */
+ public static final String AGENT_NAME_PLACEHOLDER = "{agentName}";
+
+ /**
+ * 默认系统指令:Agent 未配置系统指令时使用,{agentName} 占位符替换为 Agent 名称
+ */
+ private String defaultSystemPrompt = "你是 " + AGENT_NAME_PLACEHOLDER + " 的智能助手。";
+ /**
+ * 结果流
+ */
+ private Stream stream = new Stream();
+ /**
+ * 生成 worker
+ */
+ private Worker worker = new Worker();
+ /**
+ * SSE 连接
+ */
+ private Sse sse = new Sse();
+ /**
+ * 上下文历史
+ */
+ private History history = new History();
+
+ }
+
+ @Data
+ public static class Stream {
+
+ /**
+ * 单次生成 / 单 SSE 连接最长时长,单位:毫秒
+ */
+ private Long timeoutMs = 180000L;
+ /**
+ * 结果流保留时长,单位:秒;决定断线、刷新后可续传的时间窗
+ */
+ private Long ttlSeconds = 3600L;
+
+ }
+
+ @Data
+ public static class Worker {
+
+ /**
+ * 单节点生成 worker 线程数(多实例部署时为每个实例的数量)
+ */
+ private Integer count = 4;
+
+ }
+
+ @Data
+ public static class Sse {
+
+ /**
+ * 单节点 SSE 最大并发连接数,超出时拒绝新连接
+ */
+ private Integer maxConnections = 64;
+
+ }
+
+ @Data
+ public static class History {
+
+ /**
+ * 附加给模型的最近历史消息条数上限
+ */
+ private Integer limit = 20;
+
+ }
+
+ @Data
+ public static class Skill {
+
+ /**
+ * 技能物化根目录,实际目录为 {root}/agent_{agentId}(Agent 编号全局唯一,无需按租户分层);
+ * 默认放在用户目录下,集群部署建议挂载共享持久卷
+ */
+ private String root = System.getProperty("user.home") + "/yudao-ai1/skills";
+
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/package-info.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/package-info.java
new file mode 100644
index 0000000..44f4a1f
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/ai/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * ai1 模块的 ai 配置
+ */
+package cn.iocoder.yudao.module.ai1.framework.ai;
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/package-info.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/package-info.java
new file mode 100644
index 0000000..04ba873
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 ai1 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.ai1.framework;
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/web/config/Ai1WebConfiguration.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/web/config/Ai1WebConfiguration.java
new file mode 100644
index 0000000..fad9d74
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/web/config/Ai1WebConfiguration.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.ai1.framework.web.config;
+
+import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * ai1 模块的 web 组件的 Configuration
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+public class Ai1WebConfiguration {
+
+ /**
+ * ai1 模块的 API 分组
+ */
+ @Bean
+ public GroupedOpenApi ai1GroupedOpenApi() {
+ return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("ai1");
+ }
+
+}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/web/package-info.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/web/package-info.java
new file mode 100644
index 0000000..955c3f1
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/framework/web/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * ai1 模块的 web 配置
+ */
+package cn.iocoder.yudao.module.ai1.framework.web;
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmChatTool.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmChatTool.java
new file mode 100644
index 0000000..bea83f5
--- /dev/null
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmChatTool.java
@@ -0,0 +1,151 @@
+package cn.iocoder.yudao.module.ai1.harness.llm;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.ai1.enums.session.Ai1SessionMessageRoleEnum;
+import cn.iocoder.yudao.module.ai1.service.model.bo.Ai1ModelRespBO;
+import jakarta.annotation.Resource;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.client.advisor.api.Advisor;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.model.Generation;
+import org.springframework.ai.support.ToolCallbacks;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * AI1 LLM 会话工具
+ *
+ * 只负责「按给定模型与消息执行一次流式会话」:接收已装配好的系统指令、历史消息、工具与 Advisor,
+ * 经 ChatClient 流式会话,增量通过回调输出;传输方式(SSE、Redis Stream)由调用方决定
+ *
+ * @author 芋道源码
+ */
+@Component
+public class Ai1LlmChatTool {
+
+ /**
+ * 思考过程的元数据键(Spring AI OpenAI 推理模型的 reasoning_content)
+ */
+ private static final String METADATA_REASONING = "reasoningContent";
+
+ @Resource
+ private Ai1LlmModelFactory llmModelFactory;
+
+ /**
+ * 流式会话:装配消息 → 附加工具、Advisor → 增量输出
+ *
+ * @param model 模型运行时快照(需为对话模型)
+ * @param systemPrompt 系统指令,可为空
+ * @param histories 历史消息,按时间升序;每项为 [role, content]
+ * @param content 当前用户消息
+ * @param tools 工具(ToolCallback 或 @Tool 对象),可为空
+ * @param advisors 会话 Advisor,可为空
+ * @param sessionId 会话编号,用于替换附属 Header 中的 {session} 占位符
+ * @param onThinking 思考过程增量回调,可为空
+ * @param onContent 回复内容增量回调,可为空
+ * @return 完整回复(内容 + 思考过程)
+ */
+ public ChatText chat(Ai1ModelRespBO model, String systemPrompt, List histories, String content,
+ List