mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-11-03 14:35:32 +08:00
fix(llm): adapt reasoning model probes and parameter errors (#640)
This commit is contained in:
parent
0a7dbef619
commit
8337d13403
@ -2036,9 +2036,26 @@ public class NodeStreamingChatHelper {
|
||||
|| combined.contains("model not found") || combined.contains("not_found_error")) {
|
||||
return "Model name not available on this provider — verify the model exists and is supported (Settings → Models)";
|
||||
}
|
||||
// Do not mislabel unsupported model parameters as image errors (#640).
|
||||
// Only emit known parameter names and fixed guidance, never raw response
|
||||
// bodies, which may contain credentials or echoed conversation content.
|
||||
String lowerError = combined.toLowerCase(java.util.Locale.ROOT);
|
||||
if (lowerError.contains("unsupported parameter") || lowerError.contains("unsupported_parameter")
|
||||
|| lowerError.contains("unsupported value") || lowerError.contains("unsupported_value")) {
|
||||
if (lowerError.contains("max_tokens") && lowerError.contains("max_completion_tokens")) {
|
||||
return "模型不支持 max_tokens,请改用 max_completion_tokens。请更新 MateClaw,并检查模型及提供商生成参数配置。";
|
||||
}
|
||||
for (String parameter : java.util.List.of("max_completion_tokens", "max_tokens", "temperature", "top_p",
|
||||
"reasoning_effort", "stream_options", "parallel_tool_calls", "tool_choice", "response_format")) {
|
||||
if (lowerError.contains(parameter)) {
|
||||
return "模型不支持生成参数或参数值:" + parameter + "。请检查模型及提供商生成参数配置。";
|
||||
}
|
||||
}
|
||||
return "模型不支持当前生成参数或参数值,请检查模型及提供商生成参数配置。";
|
||||
}
|
||||
// 对 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";
|
||||
if (lowerError.contains("unsupported image format")) return "Unsupported file format (e.g. SVG), use PNG/JPG instead";
|
||||
if (msg.contains("invalid_request_error") || msg.contains("400 Bad Request")) return "Bad request, please check input";
|
||||
if (msg.contains("rate_limit") || msg.contains("429")) return "Rate limit exceeded, please retry later";
|
||||
if (msg.contains("timeout") || msg.contains("Timeout")) return "Request timeout, please retry";
|
||||
|
||||
@ -681,8 +681,9 @@ public class ModelDiscoveryService {
|
||||
|
||||
/**
|
||||
* Build the smoke-test request body for the OpenAI-compatible test-prompt path.
|
||||
* The core fields (model/messages/max_tokens/temperature) are fixed by design —
|
||||
* this is a minimal-token connectivity probe, not a real chat turn — but any
|
||||
* Probe limits follow the same ModelFamily constraints as runtime chat.
|
||||
* Reasoning models need a completion budget that includes reasoning tokens;
|
||||
* the standard ten-token probe is insufficient for those models. Any
|
||||
* unrecognized top-level {@code generateKwargs} key (e.g. vLLM's
|
||||
* {@code chat_template_kwargs} used to disable Qwen thinking mode) is forwarded
|
||||
* verbatim, same as the runtime chat path in
|
||||
@ -694,14 +695,21 @@ public class ModelDiscoveryService {
|
||||
Map<String, Object> requestBody = new LinkedHashMap<>(ProviderGenerateKwargs.collectPassthroughExtraBody(kwargs));
|
||||
requestBody.put("model", modelId);
|
||||
requestBody.put("messages", List.of(Map.of("role", "user", "content", "请回复:连接正常")));
|
||||
requestBody.put("max_tokens", 10);
|
||||
Object probeTemperature;
|
||||
if (ModelFamily.detect(modelId).fixedTemperatureOne()) {
|
||||
probeTemperature = 1.0d;
|
||||
ModelFamily family = ModelFamily.detect(modelId);
|
||||
if (family.useMaxCompletionTokens()) {
|
||||
requestBody.put("max_completion_tokens", 4096);
|
||||
// Let OpenAI/Azure use model defaults: reasoning deployments may
|
||||
// reject explicit sampling options even in a connectivity probe.
|
||||
} else {
|
||||
probeTemperature = 0;
|
||||
requestBody.put("max_tokens", 10);
|
||||
Object probeTemperature;
|
||||
if (family.fixedTemperatureOne()) {
|
||||
probeTemperature = 1.0d;
|
||||
} else {
|
||||
probeTemperature = 0;
|
||||
}
|
||||
requestBody.put("temperature", probeTemperature);
|
||||
}
|
||||
requestBody.put("temperature", probeTemperature);
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package vip.mate.agent.graph;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ModelParameterErrorHintTest {
|
||||
@Test
|
||||
void unsupportedTokenParameterHasActionableHintWithoutEchoingSecrets() {
|
||||
String body = "{\"error\":{\"message\":\"Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead. token=private-secret\"}}";
|
||||
var error = WebClientResponseException.create(400, "Bad Request", HttpHeaders.EMPTY,
|
||||
body.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
|
||||
String hint = ReflectionTestUtils.invokeMethod(NodeStreamingChatHelper.class,
|
||||
"extractUserFriendlyError", new IllegalStateException("400 Bad Request", error));
|
||||
assertNotNull(hint);
|
||||
assertTrue(hint.contains("max_completion_tokens"));
|
||||
assertFalse(hint.contains("private-secret"));
|
||||
assertFalse(hint.contains("file format"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsupportedSamplingParameterIsNotAnImageError() {
|
||||
String hint = ReflectionTestUtils.invokeMethod(NodeStreamingChatHelper.class,
|
||||
"extractUserFriendlyError", new IllegalStateException("400 Bad Request: unsupported parameter: 'temperature'"));
|
||||
assertNotNull(hint);
|
||||
assertTrue(hint.contains("temperature"));
|
||||
assertFalse(hint.contains("file format"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void actualUnsupportedImageFormatKeepsExistingHint() {
|
||||
String hint = ReflectionTestUtils.invokeMethod(NodeStreamingChatHelper.class,
|
||||
"extractUserFriendlyError", new IllegalStateException("unsupported image format"));
|
||||
assertNotNull(hint);
|
||||
assertTrue(hint.contains("PNG/JPG"));
|
||||
}
|
||||
}
|
||||
@ -109,4 +109,26 @@ class ModelDiscoveryServiceTestPromptTest {
|
||||
|
||||
assertEquals(1.0d, requestBody.get("temperature"));
|
||||
}
|
||||
@Test
|
||||
@DisplayName("OpenAI reasoning probes use completion-token budgets and omit unsupported sampling parameters (#640)")
|
||||
void reasoningProbeUsesCompletionTokens() {
|
||||
for (String model : java.util.List.of("gpt-5.5", "gpt-5-mini", "o1", "o3", "o4-mini")) {
|
||||
Map<String, Object> body = ModelDiscoveryService.buildTestPromptRequestBody(model,
|
||||
Map.of("max_tokens", 10, "temperature", 0.2, "top_p", 0.9));
|
||||
assertFalse(body.containsKey("max_tokens"), model);
|
||||
assertEquals(4096, body.get("max_completion_tokens"), model);
|
||||
assertFalse(body.containsKey("temperature"), model);
|
||||
assertFalse(body.containsKey("top_p"), model);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("GPT-4o probes retain their supported standard parameters")
|
||||
void gpt4oProbeRemainsStandard() {
|
||||
Map<String, Object> body = ModelDiscoveryService.buildTestPromptRequestBody("gpt-4o", Map.of());
|
||||
assertEquals(10, body.get("max_tokens"));
|
||||
assertEquals(0, body.get("temperature"));
|
||||
assertFalse(body.containsKey("max_completion_tokens"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user