improve special tool config (#6)

This commit is contained in:
davi0015 2026-04-21 18:31:25 +08:00 committed by GitHub
parent dbe29d0f41
commit f4e5b9e91a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 13 deletions

View file

@ -227,9 +227,17 @@ const SimpleModelSettingsDialog = ({
const currentOverrides = settingsState.overridesOfModel?.[providerName]?.[modelName] ?? undefined;
const { recognizedModelName, isUnrecognizedModel } = defaultModelCapabilities
// Create the placeholder with the default values for allowed keys
// Create the placeholder with the default values for allowed keys.
// `specialToolFormat` is intentionally excluded: it's now auto-applied per
// provider (see `defaultSpecialToolFormat` in modelCapabilities.ts), the
// status line above already tells the user which format will be used, and
// the preset buttons below offer one-click switching. Including it here
// would just suggest the user has to type it themselves.
const partialDefaults: Partial<ModelOverrides> = {};
for (const k of modelOverrideKeys) { if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any; }
for (const k of modelOverrideKeys) {
if (k === 'specialToolFormat') continue;
if (defaultModelCapabilities[k]) partialDefaults[k] = defaultModelCapabilities[k] as any;
}
const placeholder = JSON.stringify(partialDefaults, null, 2);
const [overrideEnabled, setOverrideEnabled] = useState<boolean>(() => !!currentOverrides);
@ -319,11 +327,23 @@ const SimpleModelSettingsDialog = ({
</button>
</div>
{/* Display model recognition status */}
{/* Display model recognition status. For unrecognized models we surface the
tool-calling format Void auto-picked based on the provider's wire protocol,
so the user knows what to expect (and what to override if their model needs
a different format). */}
<div className="text-sm text-void-fg-3 mb-4">
{type === 'default' ? `${modelName} comes packaged with Void, so you shouldn't need to change these settings.`
{type === 'default'
? `${modelName} comes packaged with Void, so you shouldn't need to change these settings.`
: isUnrecognizedModel
? `Model not recognized by Void.`
? (() => {
const fmt = defaultModelCapabilities.specialToolFormat
const fmtLabel = fmt === 'openai-style' ? 'OpenAI-style'
: fmt === 'anthropic-style' ? 'Anthropic-style'
: fmt === 'gemini-style' ? 'Gemini-style'
: 'XML-in-prompt (fallback)'
const providerLabel = displayInfoOfProviderName(providerName).title
return `Model not recognized by Void. Defaulting to ${fmtLabel} tool calling based on the ${providerLabel} provider — override below if your model needs a different format.`
})()
: `Void recognizes ${modelName} ("${recognizedModelName}").`}
</div>
@ -334,10 +354,13 @@ const SimpleModelSettingsDialog = ({
<span className="text-void-fg-3 text-sm">Override model defaults</span>
</div>
{/* Informational link */}
{overrideEnabled && <div className="text-sm text-void-fg-3 mb-4">
<ChatMarkdownRender string={`See the [sourcecode](${sourcecodeOverridesLink}) for a reference on how to set this JSON (advanced).`} chatMessageLocation={undefined} />
</div>}
{/* Informational link. Shown unconditionally so users can discover the
full set of overridable fields (contextWindow, reasoningCapabilities,
supportsSystemMessage, etc.) before deciding whether to enable
overrides. */}
<div className="text-sm text-void-fg-3 mb-4">
<ChatMarkdownRender string={`Advanced — see the [sourcecode](${sourcecodeOverridesLink}) for the full list of fields you can override (e.g. \`contextWindow\`, \`reasoningCapabilities\`, \`supportsSystemMessage\`).`} chatMessageLocation={undefined} />
</div>
<textarea
key={overrideEnabled + ''}

View file

@ -237,6 +237,11 @@ type VoidStaticProviderInfo = { // doesn't change (not stateful)
providerReasoningIOSettings?: ProviderReasoningIOSettings; // input/output settings around thinking (allowed to be empty) - only applied if the model supports reasoning output
modelOptions: { [key: string]: VoidStaticModelInfo };
modelOptionsFallback: (modelName: string, fallbackKnownValues?: Partial<VoidStaticModelInfo>) => (VoidStaticModelInfo & { modelName: string, recognizedModelName: string }) | null;
// Tool format used when neither the per-model entry nor the fallback specifies one.
// If unset, `getModelCapabilities` defaults to 'openai-style' since the vast majority
// of provider endpoints speak the OpenAI tools API. Only providers whose endpoint
// uses a different native format (e.g. anthropic, gemini) need to override this.
defaultSpecialToolFormat?: VoidStaticModelInfo['specialToolFormat'];
}
@ -570,6 +575,7 @@ const anthropicModelOptions = {
} as const satisfies { [s: string]: VoidStaticModelInfo }
const anthropicSettings: VoidStaticProviderInfo = {
defaultSpecialToolFormat: 'anthropic-style',
providerReasoningIOSettings: {
input: {
includeInPayload: (reasoningInfo) => {
@ -918,6 +924,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
} as const satisfies { [s: string]: VoidStaticModelInfo }
const geminiSettings: VoidStaticProviderInfo = {
defaultSpecialToolFormat: 'gemini-style',
modelOptions: geminiModelOptions,
modelOptionsFallback: (modelName) => { return null },
}
@ -1491,25 +1498,36 @@ export const getModelCapabilities = (
const lowercaseModelName = modelName.toLowerCase()
const { modelOptions, modelOptionsFallback } = modelSettingsOfProvider[providerName]
const { modelOptions, modelOptionsFallback, defaultSpecialToolFormat } = modelSettingsOfProvider[providerName]
// Get any override settings for this model
const overrides = overridesOfModel?.[providerName]?.[modelName];
// Fill in `specialToolFormat` when neither the per-model entry, the fallback, nor
// the user override specified one. Most provider endpoints speak the OpenAI tools
// API, so 'openai-style' is the global default; providers like anthropic/gemini
// opt out via `defaultSpecialToolFormat` on their settings. Without this, an
// unrecognized model would silently fall back to brittle XML-in-prompt tools.
const providerToolFormatDefault = defaultSpecialToolFormat ?? 'openai-style'
const applyProviderToolFormatDefault = <T extends { specialToolFormat?: VoidStaticModelInfo['specialToolFormat'] }>(obj: T): T => {
if (!obj.specialToolFormat) obj.specialToolFormat = providerToolFormatDefault
return obj
}
// search model options object directly first
for (const modelName_ in modelOptions) {
const lowercaseModelName_ = modelName_.toLowerCase()
if (lowercaseModelName === lowercaseModelName_) {
return { ...modelOptions[modelName], ...overrides, modelName, recognizedModelName: modelName, isUnrecognizedModel: false };
return applyProviderToolFormatDefault({ ...modelOptions[modelName], ...overrides, modelName, recognizedModelName: modelName, isUnrecognizedModel: false });
}
}
const result = modelOptionsFallback(modelName)
if (result) {
return { ...result, ...overrides, modelName: result.modelName, isUnrecognizedModel: false };
return applyProviderToolFormatDefault({ ...result, ...overrides, modelName: result.modelName, isUnrecognizedModel: false });
}
return { modelName, ...defaultModelOptions, ...overrides, isUnrecognizedModel: true };
return applyProviderToolFormatDefault({ modelName, ...defaultModelOptions, ...overrides, isUnrecognizedModel: true });
}
// non-model settings