diff --git a/yudao-module-ai1-springai/pom.xml b/yudao-module-ai1-springai/pom.xml
index d0a3916..f29374d 100644
--- a/yudao-module-ai1-springai/pom.xml
+++ b/yudao-module-ai1-springai/pom.xml
@@ -22,7 +22,7 @@
2.0.1
- 0.12.0
+ 0.13.0
3.0.10
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderRespVO.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderRespVO.java
index 42aa913..cd14196 100644
--- a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderRespVO.java
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderRespVO.java
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.ai1.controller.admin.model.vo.provider;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
+import java.util.Map;
+
import java.time.LocalDateTime;
@Schema(description = "管理后台 - AI1 供应商 Response VO")
@@ -21,8 +23,8 @@ public class Ai1ProviderRespVO {
@Schema(description = "API 密钥,脱敏展示", example = "sk-****abcd")
private String apiKey;
- @Schema(description = "请求附属 Header,JSON 数组", example = "[{\"key\":\"X-Session\",\"value\":\"{session}\"}]")
- private String headers;
+ @Schema(description = "请求 Header,JSON 对象", example = "{\"X-Session\":\"{session}\"}")
+ private Map headers;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderSaveReqVO.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderSaveReqVO.java
index b07bdbc..cb41dca 100644
--- a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderSaveReqVO.java
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/controller/admin/model/vo/provider/Ai1ProviderSaveReqVO.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.ai1.controller.admin.model.vo.provider;
-import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.ai1.util.Ai1Utils;
@@ -12,6 +11,8 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
+import java.util.Map;
+
@Schema(description = "管理后台 - AI1 供应商新增/修改 Request VO")
@Data
public class Ai1ProviderSaveReqVO {
@@ -33,9 +34,8 @@ public class Ai1ProviderSaveReqVO {
@Size(max = 200, message = "API 密钥长度不能超过 200 个字符")
private String apiKey;
- @Schema(description = "请求附属 Header,JSON 数组", example = "[{\"key\":\"X-Session\",\"value\":\"{session}\"}]")
- @Size(max = 2000, message = "请求附属 Header 长度不能超过 2000 个字符")
- private String headers;
+ @Schema(description = "请求 Header,JSON 对象", example = "{\"X-Session\":\"{session}\"}")
+ private Map headers;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "状态不能为空")
@@ -46,10 +46,10 @@ public class Ai1ProviderSaveReqVO {
@Size(max = 255, message = "备注长度不能超过 255 个字符")
private String remark;
- @AssertTrue(message = "请求附属 Header 格式不正确,应为 JSON 数组:[{\"key\":\"...\",\"value\":\"...\"}]")
+ @AssertTrue(message = "请求 Header 名称不能为空或重复,值必须为字符串,JSON 长度不能超过 2000 个字符")
@JsonIgnore
public boolean isHeadersValid() {
- return StrUtil.isBlank(headers) || Ai1Utils.parseHeaders(headers) != null;
+ return Ai1Utils.isHeadersValid(headers);
}
}
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/dataobject/model/Ai1ProviderDO.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/dataobject/model/Ai1ProviderDO.java
index fa8bec1..a08da32 100644
--- a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/dataobject/model/Ai1ProviderDO.java
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/dataobject/model/Ai1ProviderDO.java
@@ -4,9 +4,13 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
+import java.util.Map;
+
/**
* AI1 供应商 DO
*
@@ -14,7 +18,7 @@ import lombok.*;
*
* @author 芋道源码
*/
-@TableName("ai1_provider")
+@TableName(value = "ai1_provider", autoResultMap = true)
@KeySequence("ai1_provider_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@@ -42,11 +46,12 @@ public class Ai1ProviderDO extends TenantBaseDO {
*/
private String apiKey;
/**
- * 请求附属 Header
+ * 请求 Header
*
- * JSON 数组,格式为 [{"key":"...","value":"..."}];value 可包含 {session} 占位符,请求时替换为会话编号
+ * JSON 对象,格式为 {"X-Session":"{session}"};value 可包含 {session} 占位符,请求时替换为会话编号
*/
- private String headers;
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private Map headers;
/**
* 状态
*
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
index f73328f..1bb1b99 100644
--- 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
@@ -16,6 +16,7 @@ public interface Ai1ErrorCodeConstants {
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, "配置({})无法解析,请检查环境变量或配置项");
+ ErrorCode PROVIDER_HEADERS_INVALID = new ErrorCode(1_041_000_007, "请求 Header 格式不正确或名称重复,请检查供应商配置");
// ========== 模型 1-041-001-000 ==========
ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_041_001_000, "模型不存在");
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
index bea83f5..ef1dace 100644
--- 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
@@ -51,7 +51,7 @@ public class Ai1LlmChatTool {
* @param content 当前用户消息
* @param tools 工具(ToolCallback 或 @Tool 对象),可为空
* @param advisors 会话 Advisor,可为空
- * @param sessionId 会话编号,用于替换附属 Header 中的 {session} 占位符
+ * @param sessionId 会话编号,用于替换请求 Header 中的 {session} 占位符
* @param onThinking 思考过程增量回调,可为空
* @param onContent 回复内容增量回调,可为空
* @return 完整回复(内容 + 思考过程)
diff --git a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmModelFactory.java b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmModelFactory.java
index 674dfc3..6e0645c 100644
--- a/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmModelFactory.java
+++ b/yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/harness/llm/Ai1LlmModelFactory.java
@@ -1,6 +1,5 @@
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.service.model.bo.Ai1ModelRespBO;
import com.google.common.cache.Cache;
@@ -21,7 +20,6 @@ import org.springframework.util.MultiValueMap;
import java.net.URI;
import java.time.Duration;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
@@ -43,7 +41,7 @@ import java.util.function.Predicate;
public class Ai1LlmModelFactory {
/**
- * 附属 Header 会话占位符:构建模型时替换为当前会话编号
+ * 请求 Header 会话占位符:构建模型时替换为当前会话编号
*/
public static final String SESSION_PLACEHOLDER = "{session}";
/**
@@ -76,7 +74,7 @@ public class Ai1LlmModelFactory {
* 获取(或构建)对话模型,并包装为 ChatClient
*
* @param model 模型运行时快照
- * @param sessionId 会话编号,用于替换附属 Header 中的 {session} 占位符,可为空
+ * @param sessionId 会话编号,用于替换请求 Header 中的 {session} 占位符,可为空
* @return ChatClient
*/
public ChatClient buildChatClient(Ai1ModelRespBO model, Long sessionId) {
@@ -132,7 +130,7 @@ public class Ai1LlmModelFactory {
/**
* 获取(或构建)对话模型
*
- * 仅当附属 Header 使用 {session} 占位符(需按会话隔离)时,才把会话编号纳入缓存 key;
+ * 仅当请求 Header 使用 {session} 占位符(需按会话隔离)时,才把会话编号纳入缓存 key;
* 否则同一模型跨会话复用,避免每个会话都构建一个模型实例
*/
private OpenAiChatModel getOrCreateChatModel(Ai1ModelRespBO model, Long sessionId) {
@@ -205,23 +203,23 @@ public class Ai1LlmModelFactory {
}
/**
- * 附属 Header 是否使用 {session} 占位符
+ * 请求 Header 是否使用 {session} 占位符
*/
- private static boolean usesSessionHeader(List