mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(ui): handle nullable JSON-Schema type arrays in tool call arguments
resolveSchemaProperty now collapses a type array (e.g. ["integer", "null"], the shape Pydantic's model_json_schema() emits for Optional[int]) to its non-null member before any caller branches on it - it previously only handled the anyOf/oneOf nullable encoding. buildDefaultValue no longer synthesizes 0/false for an untouched nullable numeric/boolean field, since that value looks user-provided and gets sent to the tool even when the field was never touched. initialArgumentValues now passes the raw (unresolved) prop into buildDefaultValue so it can still see the original array type and know the field is nullable. Found and fixed while rebasing this branch onto the current litellm_internal_staging, verified via the existing and newly-added tests in toolCallArguments/ToolTestPanel and MCPToolArgumentsForm.
This commit is contained in:
parent
5b46865e7b
commit
1a2e40111c
2 changed files with 23 additions and 5 deletions
|
|
@ -16,6 +16,12 @@ const isPlainObject = (value: unknown): value is Record<string, unknown> =>
|
|||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
export const resolveSchemaProperty = (prop: InputSchemaProperty): InputSchemaProperty => {
|
||||
// JSON Schema represents a nullable/optional field as a type array (e.g. ["integer", "null"]) -
|
||||
// the shape Pydantic's model_json_schema() emits for Optional[int]. Collapse to the single
|
||||
// non-null type before any caller branches on it.
|
||||
if (Array.isArray(prop.type)) {
|
||||
return { ...prop, type: prop.type.find((t) => t !== "null") ?? prop.type[0] };
|
||||
}
|
||||
if (prop.type !== undefined) return prop;
|
||||
const members = (prop.anyOf ?? prop.oneOf ?? []).filter((member) => member.type !== "null");
|
||||
if (members.length !== 1 || members[0].type === undefined) return prop;
|
||||
|
|
@ -185,12 +191,17 @@ function buildDefaultValue(declared: InputSchemaProperty | undefined, overrideDe
|
|||
if (prop.type === "array") return buildArrayDefault(prop, effectiveDefault);
|
||||
if (effectiveDefault !== undefined) return effectiveDefault;
|
||||
|
||||
// A nullable numeric/boolean field (an array "type" on the ORIGINAL declaration) with no
|
||||
// explicit default starts empty rather than a synthetic 0/false: unlike a string's natural ""
|
||||
// default, that value looks user-provided, passes the non-empty submission filter in
|
||||
// buildToolCallArguments, and gets sent to the tool even when the field was never touched.
|
||||
const isNullable = Array.isArray(declared.type);
|
||||
switch (prop.type) {
|
||||
case "integer":
|
||||
case "number":
|
||||
return 0;
|
||||
return isNullable ? undefined : 0;
|
||||
case "boolean":
|
||||
return false;
|
||||
return isNullable ? undefined : false;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
@ -199,7 +210,11 @@ function buildDefaultValue(declared: InputSchemaProperty | undefined, overrideDe
|
|||
export const initialArgumentValues = (fields: readonly ToolArgumentField[]): unknown[] =>
|
||||
fields.map(({ prop }) => {
|
||||
const resolved = resolveSchemaProperty(prop);
|
||||
const defaultValue = buildDefaultValue(resolved);
|
||||
// Pass the RAW prop, not `resolved` - buildDefaultValue resolves it again internally, and
|
||||
// needs the original `type` (possibly still an array) to know the field is nullable at all.
|
||||
// Passing the pre-resolved (already-scalar) type here would make Array.isArray(declared.type)
|
||||
// always false, silently defeating the nullable-omits-default behavior above.
|
||||
const defaultValue = buildDefaultValue(prop);
|
||||
if (isJsonField(resolved)) {
|
||||
return isBlank(defaultValue) ? "" : JSON.stringify(defaultValue, null, 2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,8 @@ function convertFormValues(
|
|||
}
|
||||
});
|
||||
|
||||
const isNestedParams = resolveSchemaType(schema.properties?.params?.type) === "object" && schema.properties?.params?.properties;
|
||||
const isNestedParams =
|
||||
resolveSchemaType(schema.properties?.params?.type) === "object" && schema.properties?.params?.properties;
|
||||
|
||||
return isNestedParams ? { params: convertedValues } : convertedValues;
|
||||
}
|
||||
|
|
@ -326,7 +327,9 @@ const MCPToolArgumentsForm = forwardRef<MCPToolArgumentsFormRef, MCPToolArgument
|
|||
className="font-mono"
|
||||
placeholder={
|
||||
prop.description ||
|
||||
(effectiveType === "object" ? `Enter JSON object for ${key}` : `Enter JSON array for ${key}`)
|
||||
(effectiveType === "object"
|
||||
? `Enter JSON object for ${key}`
|
||||
: `Enter JSON array for ${key}`)
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue