mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(llm): classify DashScope 'url error' as client error + broaden dot-version purge
This commit is contained in:
parent
8a25d723f0
commit
adfe23cd2c
@ -152,6 +152,19 @@ public class NodeStreamingChatHelper {
|
||||
|| msg.contains("invalid_request_error") || msg.contains("unsupported")) {
|
||||
return ErrorType.CLIENT_ERROR;
|
||||
}
|
||||
// DashScope-specific "model name does not map to a valid endpoint" — reported as
|
||||
// "[InvalidParameter] url error, please check url" (see
|
||||
// https://help.aliyun.com/zh/model-studio/error-code#error-url). Despite the wording
|
||||
// it's not a URL issue — it's the provider rejecting an unknown/unsupported model id
|
||||
// on the native protocol. Treat as client error so we do NOT retry.
|
||||
if (msg.contains("[InvalidParameter]")
|
||||
|| msg.contains("InvalidParameter")
|
||||
|| msg.contains("url error")
|
||||
|| msg.contains("Model not exist")
|
||||
|| msg.contains("model_not_found")
|
||||
|| msg.contains("Model not found")) {
|
||||
return ErrorType.CLIENT_ERROR;
|
||||
}
|
||||
// Server errors
|
||||
if (msg.contains("500") || msg.contains("502") || msg.contains("503") || msg.contains("504")
|
||||
|| msg.contains("APITimeoutError") || msg.contains("APIConnectionError")
|
||||
@ -211,10 +224,15 @@ public class NodeStreamingChatHelper {
|
||||
if (lastResult.errorType() == ErrorType.THINKING_BLOCK_ERROR) {
|
||||
return lastResult; // 已经重试过了
|
||||
}
|
||||
// 成功或不可重试
|
||||
// 成功
|
||||
if (lastResult.errorMessage() == null || lastResult.errorType() == ErrorType.NONE) {
|
||||
return lastResult;
|
||||
}
|
||||
// Any other non-null errored result with a classified type that doStreamCall
|
||||
// chose NOT to retry (i.e. UNKNOWN, or RATE_LIMIT/SERVER_ERROR past MAX_RETRIES)
|
||||
// must exit — otherwise we silently spin through attempts and waste seconds
|
||||
// per turn on unrecoverable errors like DashScope's "url error" / unknown model.
|
||||
return lastResult;
|
||||
}
|
||||
// lastResult == null 表示需要重试
|
||||
}
|
||||
@ -640,6 +658,13 @@ public class NodeStreamingChatHelper {
|
||||
private static String extractUserFriendlyError(Throwable error) {
|
||||
String msg = error.getMessage();
|
||||
if (msg == null) return error.getClass().getSimpleName();
|
||||
// DashScope "url error" is really "model name not mapped to any valid endpoint".
|
||||
// Translate it so users see the real cause and the actionable next step.
|
||||
if (msg.contains("url error") || msg.contains("[InvalidParameter]")
|
||||
|| msg.contains("Model not exist") || msg.contains("model_not_found")
|
||||
|| msg.contains("Model not found")) {
|
||||
return "Model name not available on this provider — verify the model exists and is supported (Settings → Models)";
|
||||
}
|
||||
// 对 Jackson 反序列化错误,提取关键信息
|
||||
if (msg.contains("engine_overloaded")) return "Model service overloaded, please retry later";
|
||||
if (msg.contains("unsupported image format") || msg.contains("unsupported")) return "Unsupported file format (e.g. SVG), use PNG/JPG instead";
|
||||
|
||||
@ -51,6 +51,15 @@ public class ModelDiscoveryService {
|
||||
"qwen3.5-plus"
|
||||
);
|
||||
|
||||
/**
|
||||
* Pattern matching DashScope model ids that use a dot-versioned family (e.g.
|
||||
* "qwen3.5-max", "qwen3.6-plus"). These are only offered on compatible-mode
|
||||
* and consistently fail on the native endpoint with
|
||||
* "[InvalidParameter] url error". Block them regardless of exact name.
|
||||
*/
|
||||
private static final java.util.regex.Pattern DASHSCOPE_NATIVE_UNSUPPORTED_PATTERN =
|
||||
java.util.regex.Pattern.compile("^qwen\\d+\\.\\d+.*", java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/**
|
||||
* Allow-list prefixes for DashScope models that are known to work on the native
|
||||
* protocol. An empty set means "no prefix filter" (we still apply DENY).
|
||||
@ -120,15 +129,7 @@ public class ModelDiscoveryService {
|
||||
}
|
||||
int before = discovered.size();
|
||||
List<ModelInfoDTO> filtered = discovered.stream()
|
||||
.filter(m -> {
|
||||
String id = m.getId();
|
||||
if (id == null || id.isBlank()) return false;
|
||||
String lower = id.toLowerCase();
|
||||
if (DASHSCOPE_NATIVE_DENY.contains(lower)) return false;
|
||||
// Allow if any allowed prefix matches; if allow-list is empty, permit everything
|
||||
if (DASHSCOPE_NATIVE_ALLOW_PREFIXES.isEmpty()) return true;
|
||||
return DASHSCOPE_NATIVE_ALLOW_PREFIXES.stream().anyMatch(lower::startsWith);
|
||||
})
|
||||
.filter(m -> isDashScopeModelIdAcceptable(m.getId()))
|
||||
.toList();
|
||||
if (filtered.size() < before) {
|
||||
log.info("[ModelDiscovery] Filtered {} -> {} DashScope models for provider={} (allow/deny rules)",
|
||||
@ -137,6 +138,39 @@ public class ModelDiscoveryService {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a DashScope model id is allowed on the native protocol:
|
||||
* not in the explicit DENY set, doesn't match the dot-version unsupported
|
||||
* pattern, and matches at least one ALLOW prefix (or the allow list is empty).
|
||||
*/
|
||||
private static boolean isDashScopeModelIdAcceptable(String modelId) {
|
||||
if (modelId == null || modelId.isBlank()) return false;
|
||||
String lower = modelId.toLowerCase();
|
||||
if (DASHSCOPE_NATIVE_DENY.contains(lower)) return false;
|
||||
if (DASHSCOPE_NATIVE_UNSUPPORTED_PATTERN.matcher(lower).matches()) return false;
|
||||
if (DASHSCOPE_NATIVE_ALLOW_PREFIXES.isEmpty()) return true;
|
||||
return DASHSCOPE_NATIVE_ALLOW_PREFIXES.stream().anyMatch(lower::startsWith);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defensive guard for code paths that persist a model id without going through
|
||||
* discovery (e.g. the manual "Add model" form). Throws a MateClawException with
|
||||
* a user-friendly message if the id is known to be unusable under the provider's
|
||||
* runtime protocol.
|
||||
*/
|
||||
public static void assertModelIdAcceptable(String providerId, ModelProviderEntity provider, String modelId) {
|
||||
if (provider == null) return;
|
||||
ModelProtocol protocol = ModelProtocol.fromChatModel(provider.getChatModel());
|
||||
if (protocol == ModelProtocol.DASHSCOPE_NATIVE && !isDashScopeModelIdAcceptable(modelId)) {
|
||||
throw new MateClawException(
|
||||
"err.llm.model_not_supported",
|
||||
"Model id '" + modelId + "' is not supported on DashScope native protocol. " +
|
||||
"Dot-versioned families (e.g. qwen3.5-*, qwen3.6-*) are only available via compatible-mode. " +
|
||||
"Use an allowed id such as qwen-max / qwen-plus / qwen3-max."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe each discovered model in parallel (bounded concurrency) using the same
|
||||
* protocol the runtime will use. Populates {@code probeOk}/{@code probeError}
|
||||
@ -253,10 +287,9 @@ public class ModelDiscoveryService {
|
||||
for (String modelId : modelIds) {
|
||||
if (modelId == null || modelId.isBlank()) continue;
|
||||
if (existingIds.contains(modelId)) continue;
|
||||
// Defense-in-depth: never add a DashScope model that is on the native protocol deny list
|
||||
if (protocol == ModelProtocol.DASHSCOPE_NATIVE
|
||||
&& DASHSCOPE_NATIVE_DENY.contains(modelId.toLowerCase())) {
|
||||
log.warn("[ModelDiscovery] Refusing to add {} — on DashScope native deny list", modelId);
|
||||
// Defense-in-depth: never add a DashScope model that fails the protocol-aware check
|
||||
if (protocol == ModelProtocol.DASHSCOPE_NATIVE && !isDashScopeModelIdAcceptable(modelId)) {
|
||||
log.warn("[ModelDiscovery] Refusing to add {} — blocked by DashScope native protocol filter", modelId);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -121,7 +121,15 @@ public class ModelProviderService {
|
||||
|
||||
public ProviderInfoDTO addModel(String providerId, AddProviderModelRequest request) {
|
||||
getProvider(providerId);
|
||||
modelConfigService.addModelToProvider(providerId, request.getId(), request.getName(), false);
|
||||
// Defense-in-depth: the manual "Add model" form must apply the same
|
||||
// protocol-level safety as auto-discovery — otherwise users can freely
|
||||
// type an unknown model id (e.g. "qwen3.6-plus") that DashScope native
|
||||
// rejects at runtime with the opaque "[InvalidParameter] url error".
|
||||
String modelId = request.getId();
|
||||
if (modelId != null && !modelId.isBlank()) {
|
||||
ModelDiscoveryService.assertModelIdAcceptable(providerId, this.getProvider(providerId), modelId);
|
||||
}
|
||||
modelConfigService.addModelToProvider(providerId, modelId, request.getName(), false);
|
||||
return toProviderInfo(getProvider(providerId), modelConfigService.listModelsByProvider(providerId));
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
-- V16: Broaden the DashScope native-protocol purge beyond the two explicit ids in V15.
|
||||
-- Any model whose name matches "qwenN.M-*" (dot-versioned family like qwen3.5-*, qwen3.6-*)
|
||||
-- only exists on compatible-mode and fails on the native endpoint with
|
||||
-- "[InvalidParameter] url error". Users who manually added such ids through the
|
||||
-- "Add model" form before V16's runtime guard was in place end up with conversations
|
||||
-- that silently fail. Clear them here so those users get a fresh start.
|
||||
DELETE FROM mate_model_config
|
||||
WHERE provider = 'dashscope'
|
||||
AND (model_name LIKE 'qwen1.%'
|
||||
OR model_name LIKE 'qwen2.%'
|
||||
OR model_name LIKE 'qwen3.%'
|
||||
OR model_name LIKE 'qwen4.%'
|
||||
OR model_name LIKE 'qwen5.%');
|
||||
@ -0,0 +1,8 @@
|
||||
-- V16: Broaden the DashScope native-protocol purge (see V15).
|
||||
DELETE FROM mate_model_config
|
||||
WHERE provider = 'dashscope'
|
||||
AND (model_name LIKE 'qwen1.%'
|
||||
OR model_name LIKE 'qwen2.%'
|
||||
OR model_name LIKE 'qwen3.%'
|
||||
OR model_name LIKE 'qwen4.%'
|
||||
OR model_name LIKE 'qwen5.%');
|
||||
Loading…
Reference in New Issue
Block a user