feat: 增强智能体模型调用兼容能力

- 增加模型流式开关和 HTTP 传输策略配置

- 使用 AgentScope 执行基础连接、流式与 VLM 双阶段验证

- 固定多模态校验图片并统一验证状态展示
This commit is contained in:
2026-07-17 19:57:06 +08:00
parent ba21f861f4
commit 791649c7d5
22 changed files with 1492 additions and 107 deletions

View File

@@ -4,8 +4,6 @@ package tech.easyflow.ai.service.impl;
import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.Cache;
import com.easyagents.core.document.Document;
import com.easyagents.core.model.chat.ChatModel;
import com.easyagents.core.model.chat.ChatOptions;
import com.easyagents.core.model.embedding.EmbeddingModel;
import com.easyagents.core.model.rerank.RerankModel;
import com.easyagents.core.store.VectorData;
@@ -23,6 +21,7 @@ import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.ai.mapper.ModelMapper;
import tech.easyflow.ai.service.ModelProviderService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.verification.ChatModelConnectivityVerifier;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.util.SqlOperatorsUtil;
import tech.easyflow.common.util.SqlUtil;
@@ -51,6 +50,10 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
@Resource
private Cache<String, Object> cache;
/** 与智能体运行时同链路的 Chat Model 连通性验证器。 */
@Autowired(required = false)
private ChatModelConnectivityVerifier chatModelConnectivityVerifier;
@Override
public boolean addAiLlm(Model entity) {
@@ -69,8 +72,7 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
Map<String, Object> resMap = new HashMap<>();
// 走聊天验证逻辑
if (Model.MODEL_TYPES[0].equals(modelType)) {
verifyChatLlm(model);
return null;
return verifyChatLlm(model);
}
// 走向量化验证逻辑
if (Model.MODEL_TYPES[1].equals(modelType)) {
@@ -154,25 +156,17 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
}
}
private void verifyChatLlm(Model llm) {
ChatModel chatModel = llm.toChatModel();
if (chatModel == null) {
throw new BusinessException("chatModel为空");
/**
* 使用智能体同链路验证 Chat Model 或 VLM。
*
* @param model 已补齐供应商默认配置的模型
* @return 结构化双阶段验证结果
*/
private Map<String, Object> verifyChatLlm(Model model) {
if (chatModelConnectivityVerifier == null) {
throw new BusinessException("Agent 模型连通性验证组件未加载");
}
try {
ChatOptions options=new ChatOptions();
options.setThinkingEnabled(false);
String response = chatModel.chat("我在对模型配置进行校验,你收到这条消息无需做任何思考,直接回复一个“你好”即可!",options);
if (response == null) {
throw new BusinessException("校验未通过,请前往后端日志查看详情!");
}
log.info("校验结果:{}", response);
} catch (Exception e) {
log.error("校验失败:{}", e.getMessage());
throw new BusinessException(e.getMessage());
}
return chatModelConnectivityVerifier.verify(model).toMap();
}
@Override

View File

@@ -0,0 +1,90 @@
package tech.easyflow.ai.service.support;
import tech.easyflow.common.web.exceptions.BusinessException;
import java.io.IOException;
import java.io.InputStream;
/**
* 提供模型连接验证专用的固定 PNG 图片。
*/
public final class VlmVerificationImage {
/** 模型需要识别并返回的固定数字。 */
public static final String VERIFICATION_CODE = "5839";
/** 固定验证图片的 classpath 路径。 */
private static final String RESOURCE_PATH = "/images/vlm-verification.png";
/** PNG 文件签名字节。 */
private static final byte[] PNG_SIGNATURE = {
(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
};
/** 启动后复用的固定 PNG 字节。 */
private static final byte[] PNG_BYTES = loadPng();
/**
* 禁止实例化工具类。
*/
private VlmVerificationImage() {
}
/**
* 返回固定连接验证 PNG 的字节副本。
*
* @return PNG 字节副本
*/
public static byte[] pngBytes() {
return PNG_BYTES.clone();
}
/**
* 从 classpath 加载固定验证图片。
*
* @return PNG 文件字节
* @throws BusinessException 资源缺失、读取失败或文件格式非法时抛出
*/
private static byte[] loadPng() {
try (InputStream input = VlmVerificationImage.class.getResourceAsStream(RESOURCE_PATH)) {
if (input == null) {
throw new BusinessException("VLM 校验图片资源不存在:" + RESOURCE_PATH);
}
byte[] bytes = input.readAllBytes();
if (!hasPngSignature(bytes)) {
throw new BusinessException("VLM 校验图片资源不是有效的 PNG 文件");
}
return bytes;
} catch (IOException error) {
throw new BusinessException("VLM 校验图片读取失败:" + safeMessage(error));
}
}
/**
* 检查文件是否包含标准 PNG 签名。
*
* @param bytes 待检查文件字节
* @return 包含完整 PNG 签名时返回 true
*/
private static boolean hasPngSignature(byte[] bytes) {
if (bytes == null || bytes.length < PNG_SIGNATURE.length) {
return false;
}
for (int index = 0; index < PNG_SIGNATURE.length; index++) {
if (bytes[index] != PNG_SIGNATURE[index]) {
return false;
}
}
return true;
}
/**
* 获取非空异常描述。
*
* @param error 原始异常
* @return 可展示的异常描述
*/
private static String safeMessage(Exception error) {
return error.getMessage() == null || error.getMessage().isBlank()
? "未知错误"
: error.getMessage();
}
}

View File

@@ -0,0 +1,17 @@
package tech.easyflow.ai.service.verification;
import tech.easyflow.ai.entity.Model;
/**
* Chat Model 与 VLM 的运行时同链路连通性验证器。
*/
public interface ChatModelConnectivityVerifier {
/**
* 验证模型的非流式基础连接与流式响应能力。
*
* @param model 已补齐供应商默认配置的模型
* @return 双阶段验证结果
*/
ChatModelVerificationResult verify(Model model);
}

View File

@@ -0,0 +1,132 @@
package tech.easyflow.ai.service.verification;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Chat Model 与 VLM 的双阶段连通性验证结果。
*/
public final class ChatModelVerificationResult {
/** 整体验证状态。 */
private final ModelVerificationStatus status;
/** 非流式基础连接验证状态。 */
private final ModelVerificationStatus nonStreaming;
/** 流式响应验证状态。 */
private final ModelVerificationStatus streaming;
/** 实际生效的 HTTP 版本策略。 */
private final String effectiveHttpVersion;
/** 用户可见的简洁结果说明。 */
private final String message;
/**
* 创建验证结果。
*
* @param status 整体验证状态
* @param nonStreaming 非流式验证状态
* @param streaming 流式验证状态
* @param effectiveHttpVersion 实际生效的 HTTP 版本策略
* @param message 用户可见结果说明
*/
private ChatModelVerificationResult(ModelVerificationStatus status,
ModelVerificationStatus nonStreaming,
ModelVerificationStatus streaming,
String effectiveHttpVersion,
String message) {
this.status = status;
this.nonStreaming = nonStreaming;
this.streaming = streaming;
this.effectiveHttpVersion = effectiveHttpVersion;
this.message = message;
}
/**
* 创建双阶段全部通过的结果。
*
* @param effectiveHttpVersion 实际生效的 HTTP 版本策略
* @return 全部通过结果
*/
public static ChatModelVerificationResult passed(String effectiveHttpVersion) {
return new ChatModelVerificationResult(
ModelVerificationStatus.PASSED,
ModelVerificationStatus.PASSED,
ModelVerificationStatus.PASSED,
effectiveHttpVersion,
"验证成功");
}
/**
* 创建基础连接通过但流式阶段失败的结果。
*
* @param effectiveHttpVersion 实际生效的 HTTP 版本策略
* @return 部分通过结果
*/
public static ChatModelVerificationResult streamingUnavailable(String effectiveHttpVersion) {
return new ChatModelVerificationResult(
ModelVerificationStatus.PARTIAL,
ModelVerificationStatus.PASSED,
ModelVerificationStatus.FAILED,
effectiveHttpVersion,
"连接成功,流式响应不可用,可关闭智能体的模型流式响应。");
}
/**
* 获取整体验证状态。
*
* @return 整体验证状态
*/
public ModelVerificationStatus getStatus() {
return status;
}
/**
* 获取非流式基础连接验证状态。
*
* @return 非流式验证状态
*/
public ModelVerificationStatus getNonStreaming() {
return nonStreaming;
}
/**
* 获取流式响应验证状态。
*
* @return 流式验证状态
*/
public ModelVerificationStatus getStreaming() {
return streaming;
}
/**
* 获取实际生效的 HTTP 版本策略。
*
* @return HTTP 版本策略名称
*/
public String getEffectiveHttpVersion() {
return effectiveHttpVersion;
}
/**
* 获取用户可见结果说明。
*
* @return 结果说明
*/
public String getMessage() {
return message;
}
/**
* 转换为现有模型验证接口使用的响应结构。
*
* @return 有序响应字段
*/
public Map<String, Object> toMap() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("status", status.name());
result.put("nonStreaming", nonStreaming.name());
result.put("streaming", streaming.name());
result.put("effectiveHttpVersion", effectiveHttpVersion);
result.put("message", message);
return result;
}
}

View File

@@ -0,0 +1,15 @@
package tech.easyflow.ai.service.verification;
/**
* 模型连通性验证状态。
*/
public enum ModelVerificationStatus {
/** 所有要求的验证阶段均通过。 */
PASSED,
/** 基础连接通过,但增强能力验证未通过。 */
PARTIAL,
/** 验证失败。 */
FAILED,
/** 前置阶段失败,当前阶段未执行。 */
SKIPPED
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -0,0 +1,32 @@
package tech.easyflow.ai.service.support;
import org.junit.Assert;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
/**
* VLM 连接验证专用 PNG 测试。
*/
public class VlmVerificationImageTest {
/**
* 验证固定资源图片可重复读取并能被标准 PNG 解码器解析。
*
* @throws Exception 图片解码失败时抛出
*/
@Test
public void pngBytesShouldReturnDeterministicReadableImage() throws Exception {
byte[] first = VlmVerificationImage.pngBytes();
byte[] second = VlmVerificationImage.pngBytes();
BufferedImage image = ImageIO.read(new ByteArrayInputStream(first));
Assert.assertArrayEquals(first, second);
Assert.assertNotNull(image);
Assert.assertEquals(480, image.getWidth());
Assert.assertEquals(180, image.getHeight());
Assert.assertEquals("5839", VlmVerificationImage.VERIFICATION_CODE);
}
}