mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix: align generateKwargs reserved key readers
This commit is contained in:
parent
265728bab7
commit
0e534476a3
@ -38,6 +38,7 @@ import vip.mate.config.ReasoningRetentionProperties;
|
||||
import vip.mate.exception.MateClawException;
|
||||
import vip.mate.llm.chatmodel.HttpTimeouts;
|
||||
import vip.mate.llm.chatmodel.OpenAiCompatibleChatModelBuilder;
|
||||
import vip.mate.llm.chatmodel.ProviderGenerateKwargs;
|
||||
import vip.mate.llm.chatmodel.ReasoningEffortResolver;
|
||||
import vip.mate.llm.model.ModelConfigEntity;
|
||||
import vip.mate.llm.model.ModelFamily;
|
||||
@ -403,7 +404,7 @@ public class AgentGraphBuilder {
|
||||
if (protocol == ModelProtocol.DASHSCOPE_NATIVE) {
|
||||
builtinSearchEnabled = dashScopeBuilder.isBuiltinSearchEnabled(runtimeModel, provider);
|
||||
} else if (OpenAiCompatibleChatModelBuilder.isKimiProvider(provider)
|
||||
&& Boolean.TRUE.equals(providerKwargs.get("enableSearch"))) {
|
||||
&& Boolean.TRUE.equals(ProviderGenerateKwargs.findOptionValue(providerKwargs, "enableSearch"))) {
|
||||
builtinSearchEnabled = true;
|
||||
}
|
||||
if (builtinSearchEnabled) {
|
||||
|
||||
@ -92,7 +92,7 @@ public class DashScopeChatModelBuilder implements ChatModelBuilder {
|
||||
return Boolean.TRUE.equals(runtimeModel.getEnableSearch());
|
||||
}
|
||||
Map<String, Object> kwargs = modelProviderService.readProviderGenerateKwargs(provider);
|
||||
Object kwargsSearch = kwargs.get("enableSearch");
|
||||
Object kwargsSearch = ProviderGenerateKwargs.findOptionValue(kwargs, "enableSearch");
|
||||
if (kwargsSearch != null) {
|
||||
return Boolean.TRUE.equals(kwargsSearch);
|
||||
}
|
||||
@ -145,7 +145,7 @@ public class DashScopeChatModelBuilder implements ChatModelBuilder {
|
||||
builder.withEnableSearch(true);
|
||||
String strategy = runtimeModel.getSearchStrategy();
|
||||
if (!StringUtils.hasText(strategy)) {
|
||||
strategy = (String) kwargs.get("searchStrategy");
|
||||
strategy = (String) ProviderGenerateKwargs.findOptionValue(kwargs, "searchStrategy");
|
||||
}
|
||||
if (StringUtils.hasText(strategy)) {
|
||||
builder.withSearchOptions(DashScopeApiSpec.SearchOptions.builder()
|
||||
|
||||
@ -160,11 +160,11 @@ public class OpenAiCompatibleChatModelBuilder implements ChatModelBuilder {
|
||||
|
||||
// built-in search: model-level field wins, provider generateKwargs as fallback
|
||||
boolean searchEnabled = Boolean.TRUE.equals(runtimeModel.getEnableSearch())
|
||||
|| Boolean.TRUE.equals(kwargs.get("enableSearch"));
|
||||
|| Boolean.TRUE.equals(ProviderGenerateKwargs.findOptionValue(kwargs, "enableSearch"));
|
||||
if (searchEnabled) {
|
||||
String strategy = runtimeModel.getSearchStrategy();
|
||||
if (!StringUtils.hasText(strategy)) {
|
||||
strategy = (String) kwargs.get("searchStrategy");
|
||||
strategy = (String) ProviderGenerateKwargs.findOptionValue(kwargs, "searchStrategy");
|
||||
}
|
||||
OpenAiApi.ChatCompletionRequest.WebSearchOptions.SearchContextSize contextSize;
|
||||
try {
|
||||
@ -277,7 +277,7 @@ public class OpenAiCompatibleChatModelBuilder implements ChatModelBuilder {
|
||||
}
|
||||
|
||||
boolean kimiSearchEnabled = isKimiProvider(provider)
|
||||
&& Boolean.TRUE.equals(kwargs.get("enableSearch"));
|
||||
&& Boolean.TRUE.equals(ProviderGenerateKwargs.findOptionValue(kwargs, "enableSearch"));
|
||||
|
||||
ApiKey apiKeyImpl = (keyRequired && StringUtils.hasText(apiKey))
|
||||
? new SimpleApiKey(apiKey.trim())
|
||||
@ -410,7 +410,7 @@ public class OpenAiCompatibleChatModelBuilder implements ChatModelBuilder {
|
||||
private static final Pattern OPENAI_BASE_URL_VERSION_SUFFIX = Pattern.compile(".*/v\\d+$");
|
||||
|
||||
private String resolveOpenAiCompletionsPath(String baseUrl, Map<String, Object> kwargs) {
|
||||
Object raw = kwargs.get("completionsPath");
|
||||
Object raw = ProviderGenerateKwargs.findOptionValue(kwargs, "completionsPath");
|
||||
boolean explicit = raw instanceof String value && StringUtils.hasText(value);
|
||||
String path = explicit ? ((String) raw).trim() : "/v1/chat/completions";
|
||||
if (!path.startsWith("/")) {
|
||||
|
||||
@ -11,8 +11,8 @@ import java.util.Set;
|
||||
* Reads typed values out of a provider's {@code generateKwargs} map.
|
||||
*
|
||||
* <p>A lookup tries the camelCase key first, then a snake_case fallback, and also
|
||||
* descends into a nested {@code chatOptions} map — so an admin may specify an
|
||||
* option under any of those shapes. Shared by the OpenAI-compatible chat model
|
||||
* descends into a nested {@code chatOptions} / {@code chat_options} map — so an
|
||||
* admin may specify an option under any of those shapes. Shared by the OpenAI-compatible chat model
|
||||
* builder, the reasoning-effort resolver, and the provider test-prompt path
|
||||
* ({@code ModelDiscoveryService}) so every outbound request built from
|
||||
* {@code generateKwargs} treats unrecognized keys the same way.
|
||||
@ -25,7 +25,7 @@ public final class ProviderGenerateKwargs {
|
||||
/**
|
||||
* Top-level {@code generateKwargs} keys with dedicated typed handling elsewhere
|
||||
* (both camelCase and snake_case spellings), plus the {@code chatOptions} nesting
|
||||
* wrapper itself (its contents are already consumed via {@link #findOptionValue}).
|
||||
* wrappers themselves (their contents are already consumed via {@link #findOptionValue}).
|
||||
* Centralized here so passthrough logic and known-key extraction across callers
|
||||
* can't drift out of sync. Anything else at the top level of generateKwargs is
|
||||
* forwarded verbatim — see {@link #collectPassthroughExtraBody}.
|
||||
@ -95,6 +95,9 @@ public final class ProviderGenerateKwargs {
|
||||
return kwargs.get(key);
|
||||
}
|
||||
Object chatOptions = kwargs.get("chatOptions");
|
||||
if (!(chatOptions instanceof Map<?, ?>)) {
|
||||
chatOptions = kwargs.get("chat_options");
|
||||
}
|
||||
if (chatOptions instanceof Map<?, ?> optionsMap) {
|
||||
return ((Map<String, Object>) optionsMap).get(key);
|
||||
}
|
||||
|
||||
@ -929,7 +929,7 @@ public class ModelDiscoveryService {
|
||||
*/
|
||||
private String resolveCompletionsPath(String baseUrl, Map<String, Object> kwargs) {
|
||||
if (kwargs != null) {
|
||||
Object raw = kwargs.get("completionsPath");
|
||||
Object raw = ProviderGenerateKwargs.findOptionValue(kwargs, "completionsPath");
|
||||
if (raw instanceof String value && StringUtils.hasText(value)) {
|
||||
String path = value.trim();
|
||||
if (!path.startsWith("/")) {
|
||||
@ -980,7 +980,7 @@ public class ModelDiscoveryService {
|
||||
if (kwargs == null) {
|
||||
return;
|
||||
}
|
||||
Object customHeaders = kwargs.get("customHeaders");
|
||||
Object customHeaders = ProviderGenerateKwargs.findOptionValue(kwargs, "customHeaders");
|
||||
if (customHeaders instanceof Map) {
|
||||
((Map<String, Object>) customHeaders).forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
|
||||
@ -127,6 +127,20 @@ class OpenAiCompatibleChatModelBuilderTest {
|
||||
"unrecognized passthrough keys must still be forwarded");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Snake_case built-in search kwargs enable web search options")
|
||||
void snakeCaseBuiltinSearchKwargs_enableWebSearchOptions() {
|
||||
Map<String, Object> kwargs = new LinkedHashMap<>();
|
||||
kwargs.put("enable_search", true);
|
||||
kwargs.put("search_strategy", "high");
|
||||
when(modelProviderService.readProviderGenerateKwargs(provider)).thenReturn(kwargs);
|
||||
|
||||
OpenAiChatOptions options = builder.buildOpenAiOptions(model("gpt-4o-search-preview"), provider);
|
||||
|
||||
assertNotNull(options.getWebSearchOptions(),
|
||||
"enable_search should be treated the same as enableSearch");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Empty generateKwargs: no exception, extraBody stays empty/null (pre-existing behavior preserved)")
|
||||
void emptyGenerateKwargs_noExceptionNoExtraBody() {
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package vip.mate.llm.chatmodel;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ProviderGenerateKwargsTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("findOptionValue reads snake_case keys nested under chat_options")
|
||||
void findOptionValue_readsSnakeCaseNestedUnderChatOptionsSnakeCaseWrapper() {
|
||||
Map<String, Object> kwargs = Map.of(
|
||||
"chat_options", Map.of(
|
||||
"enable_search", true,
|
||||
"search_strategy", "pro"
|
||||
)
|
||||
);
|
||||
|
||||
assertEquals(true, ProviderGenerateKwargs.findOptionValue(kwargs, "enableSearch"));
|
||||
assertEquals("pro", ProviderGenerateKwargs.findOptionValue(kwargs, "searchStrategy"));
|
||||
}
|
||||
}
|
||||
@ -1099,7 +1099,7 @@ export default {
|
||||
protocolAnthropic: 'Anthropic (Messages API)',
|
||||
protocolGemini: 'Gemini Native',
|
||||
protocolDashScope: 'DashScope Native',
|
||||
advancedHint: 'Use this for generation options such as temperature, max_tokens, top_p, reasoning effort, enable_search/search_strategy, headers, and completions_path. Any other top-level key you add here is forwarded as-is into the outbound request body — for example, { "chat_template_kwargs": { "enable_thinking": false } } disables thinking mode on vLLM-served Qwen models. Keys nested inside a "chatOptions" object are not forwarded this way.',
|
||||
advancedHint: "Use this for generation options such as temperature, max_tokens, top_p, reasoning effort, enable_search/search_strategy, headers, and completions_path. Any other top-level key you add here is forwarded as-is into the outbound request body — for example, {'{'} \"chat_template_kwargs\": {'{'} \"enable_thinking\": false {'}'} {'}'} disables thinking mode on vLLM-served Qwen models. Keys nested inside a \"chatOptions\" object are not forwarded this way.",
|
||||
requireApiKeyHint: 'Turn this off for internal or local OpenAI-compatible services that do not require auth. Connection tests will omit the Authorization header.',
|
||||
fallbackPriorityHint: 'Pool try-order (lower wins): 0 = excluded, 1 = first in line, 2 = second, and so on. Providers sharing the same value are tried in alphabetical order of their ID.',
|
||||
fallbackBadge: 'Preferred #{priority}',
|
||||
|
||||
@ -944,7 +944,7 @@ export default {
|
||||
protocolAnthropic: 'Anthropic(Messages API)',
|
||||
protocolGemini: 'Gemini 原生',
|
||||
protocolDashScope: 'DashScope 原生',
|
||||
advancedHint: '用于补充 temperature、max_tokens、top_p、reasoning effort、enable_search/search_strategy、headers、completions_path 等生成参数。除此之外的顶层 key 会原样透传到发往模型服务商的请求体中,例如 { "chat_template_kwargs": { "enable_thinking": false } } 可用于关闭 vLLM 部署的 Qwen 模型的思考模式。注意:嵌套在 "chatOptions" 对象内部的 key 不会被这样透传。',
|
||||
advancedHint: "用于补充 temperature、max_tokens、top_p、reasoning effort、enable_search/search_strategy、headers、completions_path 等生成参数。除此之外的顶层 key 会原样透传到发往模型服务商的请求体中,例如 {'{'} \"chat_template_kwargs\": {'{'} \"enable_thinking\": false {'}'} {'}'} 可用于关闭 vLLM 部署的 Qwen 模型的思考模式。注意:嵌套在 \"chatOptions\" 对象内部的 key 不会被这样透传。",
|
||||
requireApiKeyHint: '公司内部或本地 OpenAI 兼容服务如果不需要鉴权,可以关闭此项;测试连接时将不会发送 Authorization 头。',
|
||||
fallbackPriorityHint: '池内尝试顺序(数字越小越先):0 = 不参与;1 = 第一顺位;2 = 第二顺位,依此类推。多个提供商共用同一数字时按 ID 字典序。',
|
||||
fallbackBadge: '偏好 #{priority}',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user