mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +08:00
fix(llm): skip chat-style probe for embedding-prefix models in DashScope discovery
This commit is contained in:
parent
58c53687e5
commit
3340885da3
@ -139,6 +139,17 @@ public class ModelDiscoveryService {
|
|||||||
"qwen3-livetranslate-"
|
"qwen3-livetranslate-"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model-id prefixes that mark an embedding model. These pass the chat-focused
|
||||||
|
* acceptable-id check (so the manual "Add model" form works) but must be
|
||||||
|
* excluded from chat-style runtime probes and from "new chat models" auto
|
||||||
|
* suggestions — they only speak the embeddings protocol, not chat completion.
|
||||||
|
*/
|
||||||
|
private static final Set<String> EMBEDDING_MODEL_PREFIXES = Set.of(
|
||||||
|
"text-embedding-",
|
||||||
|
"embedding-"
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow-list prefixes for DashScope models that are known to work on the native
|
* Allow-list prefixes for DashScope models that are known to work on the native
|
||||||
* protocol (chat or embedding). An empty set means "no prefix filter" (we still
|
* protocol (chat or embedding). An empty set means "no prefix filter" (we still
|
||||||
@ -183,10 +194,13 @@ public class ModelDiscoveryService {
|
|||||||
.map(ModelConfigEntity::getModelName)
|
.map(ModelConfigEntity::getModelName)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// Only propose models that passed the probe (or were not probed) as "new"
|
// Only propose models that passed the probe (or were not probed) as "new".
|
||||||
|
// Embedding models are catalog-visible but excluded from the chat-discovery
|
||||||
|
// suggestion bucket — adding them as chat models would 400 at runtime.
|
||||||
List<ModelInfoDTO> newModels = discovered.stream()
|
List<ModelInfoDTO> newModels = discovered.stream()
|
||||||
.filter(m -> !existingIds.contains(m.getId()))
|
.filter(m -> !existingIds.contains(m.getId()))
|
||||||
.filter(m -> !Boolean.FALSE.equals(m.getProbeOk()))
|
.filter(m -> !Boolean.FALSE.equals(m.getProbeOk()))
|
||||||
|
.filter(m -> !isEmbeddingModelId(m.getId()))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return new DiscoverResult(discovered, newModels, discovered.size(), newModels.size());
|
return new DiscoverResult(discovered, newModels, discovered.size(), newModels.size());
|
||||||
@ -235,6 +249,18 @@ public class ModelDiscoveryService {
|
|||||||
return DASHSCOPE_NATIVE_ALLOW_PREFIXES.stream().anyMatch(lower::startsWith);
|
return DASHSCOPE_NATIVE_ALLOW_PREFIXES.stream().anyMatch(lower::startsWith);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when a model id looks like an embedding model (e.g.
|
||||||
|
* {@code text-embedding-v3}). Used to skip chat-style probes and to keep
|
||||||
|
* embedding entries out of the "new chat models to add" auto-suggestion.
|
||||||
|
* Package-private for unit tests.
|
||||||
|
*/
|
||||||
|
static boolean isEmbeddingModelId(String modelId) {
|
||||||
|
if (modelId == null) return false;
|
||||||
|
String lower = modelId.toLowerCase();
|
||||||
|
return EMBEDDING_MODEL_PREFIXES.stream().anyMatch(lower::startsWith);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defensive guard for code paths that persist a model id without going through
|
* 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
|
* discovery (e.g. the manual "Add model" form). Throws a MateClawException with
|
||||||
@ -270,6 +296,13 @@ public class ModelDiscoveryService {
|
|||||||
Semaphore sem = new Semaphore(MAX_PROBE_CONCURRENCY);
|
Semaphore sem = new Semaphore(MAX_PROBE_CONCURRENCY);
|
||||||
List<CompletableFuture<Void>> futures = new ArrayList<>(discovered.size());
|
List<CompletableFuture<Void>> futures = new ArrayList<>(discovered.size());
|
||||||
for (ModelInfoDTO dto : discovered) {
|
for (ModelInfoDTO dto : discovered) {
|
||||||
|
if (isEmbeddingModelId(dto.getId())) {
|
||||||
|
// Embedding models don't speak chat completion — sendTestPrompt would
|
||||||
|
// 400 with InvalidParameter. Leave probeOk null (no warning badge)
|
||||||
|
// and surface a clear, non-error reason in probeError.
|
||||||
|
dto.setProbeError("Embedding model — not chat-probeable");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
futures.add(CompletableFuture.runAsync(() -> {
|
futures.add(CompletableFuture.runAsync(() -> {
|
||||||
try { sem.acquire(); }
|
try { sem.acquire(); }
|
||||||
catch (InterruptedException ie) { Thread.currentThread().interrupt(); return; }
|
catch (InterruptedException ie) { Thread.currentThread().interrupt(); return; }
|
||||||
|
|||||||
@ -0,0 +1,61 @@
|
|||||||
|
package vip.mate.llm.service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coverage for {@link ModelDiscoveryService#isEmbeddingModelId(String)} —
|
||||||
|
* the predicate that keeps embedding models out of the chat-style probe and
|
||||||
|
* out of the chat-discovery "new models" suggestion bucket.
|
||||||
|
*/
|
||||||
|
class ModelDiscoveryEmbeddingDetectionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void dashscopeEmbeddingVariants_recognised() {
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("text-embedding-v1"));
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("text-embedding-v3"));
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("text-embedding-v4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void openAiEmbeddingVariants_recognised() {
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("text-embedding-ada-002"));
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("text-embedding-3-small"));
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("text-embedding-3-large"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void genericEmbeddingPrefix_recognised() {
|
||||||
|
// Some providers ship just "embedding-..." without the "text-" prefix.
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("embedding-001"));
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("embedding-large"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void chatModels_notRecognisedAsEmbedding() {
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId("qwen-plus"));
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId("gpt-4o"));
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId("claude-sonnet-4-6"));
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId("deepseek-r1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectionIsCaseInsensitive() {
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("Text-Embedding-V4"));
|
||||||
|
assertTrue(ModelDiscoveryService.isEmbeddingModelId("TEXT-EMBEDDING-3-SMALL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nullAndBlank_notEmbedding() {
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId(null));
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stringContainingEmbeddingMidway_notDetected() {
|
||||||
|
// Only prefix-anchored matches count; arbitrary mentions don't.
|
||||||
|
assertFalse(ModelDiscoveryService.isEmbeddingModelId("qwen-embedding-experimental"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user