feat: 完善模型能力识别与验证
- 自动识别模型类型、视觉、推理和工具能力并保留手动覆盖 - 使用 AgentScope 工具与视觉探测并统一管理端配置反馈
This commit is contained in:
@@ -80,8 +80,8 @@ public class Model extends ModelBase {
|
||||
deepseekConfig.setThinkingProtocol("deepseek");
|
||||
deepseekConfig.setNeedReasoningContentForToolMessage(Boolean.TRUE);
|
||||
deepseekConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
||||
if (getSupportToolMessage() != null) {
|
||||
deepseekConfig.setSupportToolMessage(getSupportToolMessage());
|
||||
if (getSupportTool() != null) {
|
||||
deepseekConfig.setSupportToolMessage(getSupportTool());
|
||||
}
|
||||
return new DeepseekChatModel(deepseekConfig);
|
||||
default:
|
||||
@@ -92,8 +92,8 @@ public class Model extends ModelBase {
|
||||
openAIChatConfig.setModel(checkAndGetModelName());
|
||||
openAIChatConfig.setRequestPath(checkAndGetRequestPath());
|
||||
openAIChatConfig.setSupportImageBase64Only(getSupportImageB64Only());
|
||||
if (getSupportToolMessage() != null) {
|
||||
openAIChatConfig.setSupportToolMessage(getSupportToolMessage());
|
||||
if (getSupportTool() != null) {
|
||||
openAIChatConfig.setSupportToolMessage(getSupportTool());
|
||||
}
|
||||
return new OpenAIChatModel(openAIChatConfig);
|
||||
}
|
||||
|
||||
@@ -77,10 +77,12 @@ public class UnifiedModelInvokeServiceImpl implements UnifiedModelInvokeService
|
||||
throw ModelInvokeException.badRequest("当前模型仅支持 base64 图片输入", "messages", "image_base64_only");
|
||||
}
|
||||
}
|
||||
if (request.getTools() != null && !request.getTools().isEmpty() && !Boolean.TRUE.equals(model.getSupportTool())) {
|
||||
if (request.getTools() != null
|
||||
&& !request.getTools().isEmpty()
|
||||
&& Boolean.FALSE.equals(model.getSupportTool())) {
|
||||
throw ModelInvokeException.badRequest("当前模型不支持 tools 参数", "tools", "tool_not_supported");
|
||||
}
|
||||
if (hasToolMessage(messages) && !Boolean.TRUE.equals(model.getSupportToolMessage())) {
|
||||
if (hasToolMessage(messages) && Boolean.FALSE.equals(model.getSupportTool())) {
|
||||
throw ModelInvokeException.badRequest("当前模型不支持 tool 消息透传", "messages", "tool_message_not_supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package tech.easyflow.ai.service;
|
||||
|
||||
import com.mybatisflex.core.service.IService;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.service.capability.ModelCapabilityResolution;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
@@ -19,6 +20,15 @@ public interface ModelService extends IService<Model> {
|
||||
|
||||
Map<String, Object> verifyModelConfig(Model llm);
|
||||
|
||||
/**
|
||||
* 根据供应商和模型 ID 自动解析模型能力。
|
||||
*
|
||||
* @param providerId 供应商 ID
|
||||
* @param modelName 模型 ID
|
||||
* @return 模型能力识别结果
|
||||
*/
|
||||
ModelCapabilityResolution resolveModelCapabilities(BigInteger providerId, String modelName);
|
||||
|
||||
Map<String, Map<String, List<Model>>> getList(Model entity);
|
||||
|
||||
void removeByEntity(Model entity);
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.easyagents.core.model.embedding.EmbeddingModel;
|
||||
import com.easyagents.core.model.rerank.RerankModel;
|
||||
import com.easyagents.core.store.VectorData;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.update.UpdateChain;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import org.slf4j.Logger;
|
||||
@@ -21,7 +22,11 @@ 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.capability.ModelCapabilityResolution;
|
||||
import tech.easyflow.ai.service.capability.ModelCapabilityResolver;
|
||||
import tech.easyflow.ai.service.capability.ModelCapabilitySource;
|
||||
import tech.easyflow.ai.service.verification.ChatModelConnectivityVerifier;
|
||||
import tech.easyflow.ai.service.verification.ChatModelVerificationResult;
|
||||
import tech.easyflow.common.tree.Tree;
|
||||
import tech.easyflow.common.util.SqlOperatorsUtil;
|
||||
import tech.easyflow.common.util.SqlUtil;
|
||||
@@ -47,6 +52,10 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
|
||||
@Autowired
|
||||
ModelProviderService modelProviderService;
|
||||
|
||||
/** 统一模型能力解析器。 */
|
||||
@Autowired
|
||||
private ModelCapabilityResolver modelCapabilityResolver;
|
||||
|
||||
@Resource
|
||||
private Cache<String, Object> cache;
|
||||
|
||||
@@ -92,6 +101,18 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据供应商和模型 ID 自动解析模型能力。
|
||||
*
|
||||
* @param providerId 供应商 ID
|
||||
* @param modelName 模型 ID
|
||||
* @return 模型能力识别结果
|
||||
*/
|
||||
@Override
|
||||
public ModelCapabilityResolution resolveModelCapabilities(BigInteger providerId, String modelName) {
|
||||
return modelCapabilityResolver.resolve(resolveProviderType(providerId, null), modelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, List<Model>>> getList(Model entity) {
|
||||
Map<String, Map<String, List<Model>>> result = new HashMap<>();
|
||||
@@ -166,7 +187,15 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
|
||||
if (chatModelConnectivityVerifier == null) {
|
||||
throw new BusinessException("Agent 模型连通性验证组件未加载");
|
||||
}
|
||||
return chatModelConnectivityVerifier.verify(model).toMap();
|
||||
ChatModelVerificationResult result = chatModelConnectivityVerifier.verify(model);
|
||||
if (result.getSupportTool() != null && model.getId() != null) {
|
||||
UpdateChain<Model> updateChain = updateChain();
|
||||
updateChain.set(Model::getSupportTool, result.getSupportTool());
|
||||
updateChain.set(Model::getSupportToolMessage, result.getSupportTool());
|
||||
updateChain.eq(Model::getId, model.getId());
|
||||
updateChain.update();
|
||||
}
|
||||
return result.toMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -199,6 +228,7 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
|
||||
if (entity == null) {
|
||||
throw new BusinessException("模型配置不能为空");
|
||||
}
|
||||
applyAutoCapabilities(entity);
|
||||
if (entity.getPublishEnabled() == null) {
|
||||
entity.setPublishEnabled(Boolean.FALSE);
|
||||
}
|
||||
@@ -239,6 +269,101 @@ public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements
|
||||
entity.setInvokeCode(invokeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自动识别结果写入待保存模型,并清理已下线的展示能力字段。
|
||||
*
|
||||
* @param entity 待保存模型
|
||||
*/
|
||||
private void applyAutoCapabilities(Model entity) {
|
||||
String providerType = resolveProviderType(entity.getProviderId(), entity.getModelProvider());
|
||||
ModelCapabilityResolution resolution = modelCapabilityResolver.resolve(
|
||||
providerType, entity.getModelName());
|
||||
boolean modelIdentityChanged = hasModelIdentityChanged(entity);
|
||||
|
||||
if (resolution.getSource() != ModelCapabilitySource.DEFAULT) {
|
||||
entity.setModelType(resolution.getModelType());
|
||||
applyResolvedChatCapabilities(entity, resolution);
|
||||
} else if (StrUtil.isBlank(entity.getModelType())) {
|
||||
entity.setModelType(Model.MODEL_TYPES[0]);
|
||||
}
|
||||
|
||||
if (!Model.MODEL_TYPES[0].equals(entity.getModelType())) {
|
||||
entity.setSupportImage(Boolean.FALSE);
|
||||
entity.setSupportThinking(Boolean.FALSE);
|
||||
entity.setSupportTool(Boolean.FALSE);
|
||||
} else if (modelIdentityChanged) {
|
||||
// 切换模型后,仅将仍无法识别且未被用户设置的能力归零。
|
||||
entity.setSupportImage(Boolean.TRUE.equals(entity.getSupportImage()));
|
||||
entity.setSupportThinking(Boolean.TRUE.equals(entity.getSupportThinking()));
|
||||
entity.setSupportTool(Boolean.TRUE.equals(entity.getSupportTool()));
|
||||
}
|
||||
|
||||
// 视频、音频尚未接入模型调用链,保存时保持关闭。
|
||||
entity.setSupportVideo(Boolean.FALSE);
|
||||
entity.setSupportAudio(Boolean.FALSE);
|
||||
// tool 消息能力跟随工具调用能力,不再由前端单独配置。
|
||||
entity.setSupportToolMessage(entity.getSupportTool());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断更新请求是否切换了实际模型或供应商。
|
||||
*
|
||||
* @param entity 待更新模型
|
||||
* @return 模型标识发生变化返回 true
|
||||
*/
|
||||
private boolean hasModelIdentityChanged(Model entity) {
|
||||
if (entity.getId() == null) {
|
||||
return false;
|
||||
}
|
||||
Model stored = modelMapper.selectOneById(entity.getId());
|
||||
if (stored == null) {
|
||||
return false;
|
||||
}
|
||||
boolean modelChanged = StrUtil.isNotBlank(entity.getModelName())
|
||||
&& !StrUtil.equalsIgnoreCase(
|
||||
StrUtil.trim(entity.getModelName()),
|
||||
StrUtil.trim(stored.getModelName()));
|
||||
boolean providerChanged = entity.getProviderId() != null
|
||||
&& !Objects.equals(entity.getProviderId(), stored.getProviderId());
|
||||
return modelChanged || providerChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用识别结果补齐尚未明确配置的对话能力,保留用户手动设置。
|
||||
*
|
||||
* @param entity 待保存模型
|
||||
* @param resolution 模型能力识别结果
|
||||
*/
|
||||
private void applyResolvedChatCapabilities(Model entity, ModelCapabilityResolution resolution) {
|
||||
if (entity.getSupportImage() == null && resolution.getSupportImage() != null) {
|
||||
entity.setSupportImage(resolution.getSupportImage());
|
||||
}
|
||||
if (entity.getSupportThinking() == null && resolution.getSupportThinking() != null) {
|
||||
entity.setSupportThinking(resolution.getSupportThinking());
|
||||
}
|
||||
if (entity.getSupportTool() == null && resolution.getSupportTool() != null) {
|
||||
entity.setSupportTool(resolution.getSupportTool());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型配置对应的供应商类型。
|
||||
*
|
||||
* @param providerId 供应商 ID
|
||||
* @param provider 已加载的供应商对象
|
||||
* @return 供应商类型,供应商不存在时返回 null
|
||||
*/
|
||||
private String resolveProviderType(BigInteger providerId, ModelProvider provider) {
|
||||
if (provider != null && StrUtil.isNotBlank(provider.getProviderType())) {
|
||||
return provider.getProviderType();
|
||||
}
|
||||
if (providerId == null) {
|
||||
return null;
|
||||
}
|
||||
ModelProvider storedProvider = modelProviderService.getById(providerId);
|
||||
return storedProvider == null ? null : storedProvider.getProviderType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Model> listInvokeModels() {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(Model::getModelType, Model.MODEL_TYPES[0]);
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Chat Model 与 VLM 的双阶段连通性验证结果。
|
||||
* Chat Model 与 VLM 的连接和工具能力验证结果。
|
||||
*/
|
||||
public final class ChatModelVerificationResult {
|
||||
|
||||
@@ -16,6 +16,8 @@ public final class ChatModelVerificationResult {
|
||||
private final ModelVerificationStatus streaming;
|
||||
/** 实际生效的 HTTP 版本策略。 */
|
||||
private final String effectiveHttpVersion;
|
||||
/** 当前端点是否通过工具调用探测。 */
|
||||
private final Boolean supportTool;
|
||||
/** 用户可见的简洁结果说明。 */
|
||||
private final String message;
|
||||
|
||||
@@ -26,48 +28,39 @@ public final class ChatModelVerificationResult {
|
||||
* @param nonStreaming 非流式验证状态
|
||||
* @param streaming 流式验证状态
|
||||
* @param effectiveHttpVersion 实际生效的 HTTP 版本策略
|
||||
* @param supportTool 当前端点是否通过工具调用探测
|
||||
* @param message 用户可见结果说明
|
||||
*/
|
||||
private ChatModelVerificationResult(ModelVerificationStatus status,
|
||||
ModelVerificationStatus nonStreaming,
|
||||
ModelVerificationStatus streaming,
|
||||
String effectiveHttpVersion,
|
||||
Boolean supportTool,
|
||||
String message) {
|
||||
this.status = status;
|
||||
this.nonStreaming = nonStreaming;
|
||||
this.streaming = streaming;
|
||||
this.effectiveHttpVersion = effectiveHttpVersion;
|
||||
this.supportTool = supportTool;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建双阶段全部通过的结果。
|
||||
* 创建一次连接与工具探测通过的结果。
|
||||
*
|
||||
* @param effectiveHttpVersion 实际生效的 HTTP 版本策略
|
||||
* @return 全部通过结果
|
||||
* @param supportTool 当前端点是否通过工具调用探测
|
||||
* @return 验证通过结果
|
||||
*/
|
||||
public static ChatModelVerificationResult passed(String effectiveHttpVersion) {
|
||||
public static ChatModelVerificationResult passed(String effectiveHttpVersion,
|
||||
boolean supportTool) {
|
||||
return new ChatModelVerificationResult(
|
||||
ModelVerificationStatus.PASSED,
|
||||
ModelVerificationStatus.PASSED,
|
||||
ModelVerificationStatus.PASSED,
|
||||
ModelVerificationStatus.SKIPPED,
|
||||
effectiveHttpVersion,
|
||||
"验证成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基础连接通过但流式阶段失败的结果。
|
||||
*
|
||||
* @param effectiveHttpVersion 实际生效的 HTTP 版本策略
|
||||
* @return 部分通过结果
|
||||
*/
|
||||
public static ChatModelVerificationResult streamingUnavailable(String effectiveHttpVersion) {
|
||||
return new ChatModelVerificationResult(
|
||||
ModelVerificationStatus.PARTIAL,
|
||||
ModelVerificationStatus.PASSED,
|
||||
ModelVerificationStatus.FAILED,
|
||||
effectiveHttpVersion,
|
||||
"连接成功,流式响应不可用,可关闭智能体的模型流式响应。");
|
||||
supportTool,
|
||||
"验证通过");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,6 +99,15 @@ public final class ChatModelVerificationResult {
|
||||
return effectiveHttpVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用探测结果。
|
||||
*
|
||||
* @return 是否通过工具调用探测
|
||||
*/
|
||||
public Boolean getSupportTool() {
|
||||
return supportTool;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可见结果说明。
|
||||
*
|
||||
@@ -126,6 +128,9 @@ public final class ChatModelVerificationResult {
|
||||
result.put("nonStreaming", nonStreaming.name());
|
||||
result.put("streaming", streaming.name());
|
||||
result.put("effectiveHttpVersion", effectiveHttpVersion);
|
||||
if (supportTool != null) {
|
||||
result.put("supportTool", supportTool);
|
||||
}
|
||||
result.put("message", message);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.service.capability.ModelCapabilityResolution;
|
||||
import tech.easyflow.ai.service.capability.ModelCapabilitySource;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 模型能力自动识别与手动覆盖合并规则测试。
|
||||
*/
|
||||
public class ModelServiceImplCapabilityOverrideTest {
|
||||
|
||||
/**
|
||||
* 验证用户明确关闭的能力不会被模型库重新打开。
|
||||
*
|
||||
* @throws ReflectiveOperationException 无法调用待测试方法时抛出
|
||||
*/
|
||||
@Test
|
||||
public void shouldPreserveExplicitCapabilityOverrides() throws ReflectiveOperationException {
|
||||
Model model = new Model();
|
||||
model.setSupportImage(Boolean.FALSE);
|
||||
model.setSupportThinking(Boolean.FALSE);
|
||||
model.setSupportTool(Boolean.FALSE);
|
||||
|
||||
applyResolvedCapabilities(model, detectedCapabilities());
|
||||
|
||||
Assert.assertEquals(Boolean.FALSE, model.getSupportImage());
|
||||
Assert.assertEquals(Boolean.FALSE, model.getSupportThinking());
|
||||
Assert.assertEquals(Boolean.FALSE, model.getSupportTool());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证空能力值会由模型库自动补齐。
|
||||
*
|
||||
* @throws ReflectiveOperationException 无法调用待测试方法时抛出
|
||||
*/
|
||||
@Test
|
||||
public void shouldFillCapabilitiesWhenNotConfigured() throws ReflectiveOperationException {
|
||||
Model model = new Model();
|
||||
|
||||
applyResolvedCapabilities(model, detectedCapabilities());
|
||||
|
||||
Assert.assertEquals(Boolean.TRUE, model.getSupportImage());
|
||||
Assert.assertEquals(Boolean.TRUE, model.getSupportThinking());
|
||||
Assert.assertEquals(Boolean.TRUE, model.getSupportTool());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模型库已确认的对话能力。
|
||||
*
|
||||
* @return 全部开启的模型能力
|
||||
*/
|
||||
private ModelCapabilityResolution detectedCapabilities() {
|
||||
return new ModelCapabilityResolution(
|
||||
Model.MODEL_TYPES[0],
|
||||
Boolean.TRUE,
|
||||
Boolean.TRUE,
|
||||
Boolean.TRUE,
|
||||
ModelCapabilitySource.CATALOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用服务内部的能力合并逻辑。
|
||||
*
|
||||
* @param model 待合并模型
|
||||
* @param resolution 自动识别结果
|
||||
* @throws ReflectiveOperationException 无法调用待测试方法时抛出
|
||||
*/
|
||||
private void applyResolvedCapabilities(Model model, ModelCapabilityResolution resolution)
|
||||
throws ReflectiveOperationException {
|
||||
ModelServiceImpl service = new ModelServiceImpl();
|
||||
Method method = ModelServiceImpl.class.getDeclaredMethod(
|
||||
"applyResolvedChatCapabilities", Model.class, ModelCapabilityResolution.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(service, model, resolution);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user