diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 6518d2607f..20e8ce365c 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -802,6 +802,13 @@ def resolve_schema(schema, components, resolved_schemas=None): if 'items' in resolved_schema: resolved_schema['items'] = resolve_schema(resolved_schema['items'], components) + # Resolve composition keywords (oneOf, anyOf, allOf) which may contain $ref + for keyword in ('oneOf', 'anyOf', 'allOf'): + if keyword in resolved_schema and isinstance(resolved_schema[keyword], list): + resolved_schema[keyword] = [ + resolve_schema(inner, components, resolved_schemas) for inner in resolved_schema[keyword] + ] + return resolved_schema diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 81a10cebfd..93c94fefc9 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1377,9 +1377,35 @@ function resolveSchema(schemaRef, components, resolvedSchemas = new Set()) { // for primitive types (string, integer, etc.), just use as is break; } + + // Resolve composition keywords (oneOf, anyOf, allOf) which may contain $ref + for (const keyword of ['oneOf', 'anyOf', 'allOf']) { + if (Array.isArray(schemaRef[keyword])) { + schemaObj[keyword] = schemaRef[keyword].map((inner) => + resolveSchema(inner, components, resolvedSchemas) + ); + } + } + return schemaObj; } + // Handle schemas that only have composition keywords without an explicit type + const compositionObj: Record = {}; + let hasComposition = false; + for (const keyword of ['oneOf', 'anyOf', 'allOf']) { + if (Array.isArray(schemaRef[keyword])) { + compositionObj[keyword] = schemaRef[keyword].map((inner) => + resolveSchema(inner, components, resolvedSchemas) + ); + hasComposition = true; + } + } + if (hasComposition) { + if (schemaRef.description) compositionObj.description = schemaRef.description; + return compositionObj; + } + // fallback for schemas without explicit type return {}; }