diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 0f18d2b753..ebf744d7d7 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -1668,6 +1668,7 @@ async def execute_tool_server( path_params = {} query_params = {} body_params = {} + declared_param_names = set() # Merge path-level and operation-level parameters for execution. path_level_params = methods.get('parameters', []) @@ -1688,6 +1689,7 @@ async def execute_tool_server( param_name = param.get('name') if not param_name: continue + declared_param_names.add(param_name) param_in = param.get('in') if param_name in params: if param_in == 'path': @@ -1707,8 +1709,16 @@ async def execute_tool_server( if query_params: final_url = f'{final_url}?{urlencode(query_params)}' - if operation.get('requestBody', {}).get('content'): - if params: + request_body_content = operation.get('requestBody', {}).get('content') + if request_body_content and params: + json_schema = request_body_content.get('application/json', {}).get('schema') + resolved_body_schema = resolve_schema(json_schema, openapi.get('components', {})) + is_composed_schema = any(keyword in resolved_body_schema for keyword in ('allOf', 'anyOf', 'oneOf')) + body_properties = {} if is_composed_schema else (resolved_body_schema.get('properties') or {}) + # Strict servers reject declared parameters in the body, unless the body schema declares them too. + if body_properties: + body_params = {k: v for k, v in params.items() if k in body_properties or k not in declared_param_names} + else: body_params = params async with aiohttp.ClientSession( diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index a6ece21d55..e5060fa888 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1,5 +1,5 @@ import { WEBUI_BASE_URL } from '$lib/constants'; -import { convertOpenApiToToolPayload } from '$lib/utils'; +import { convertOpenApiToToolPayload, resolveSchema } from '$lib/utils'; import { normalizeTags } from '$lib/utils/tags'; import { getOpenAIModelsDirect } from './openai'; @@ -598,10 +598,12 @@ export const executeToolServer = async ( const pathParams: Record = {}; const queryParams: Record = {}; let bodyParams: any = {}; + const declaredParamNames = new Set(); for (const param of mergedParams.values()) { const paramName = param?.name; if (!paramName) continue; + declaredParamNames.add(paramName); const paramIn = param?.in; if (params.hasOwnProperty(paramName)) { if (paramIn === 'path') { @@ -631,7 +633,24 @@ export const executeToolServer = async ( if (operation.requestBody && operation.requestBody.content) { const contentType = Object.keys(operation.requestBody.content)[0]; if (params !== undefined) { - bodyParams = params; + const jsonSchema = operation.requestBody.content['application/json']?.schema; + const resolvedBodySchema = resolveSchema(jsonSchema, serverData.openapi.components); + const isComposedSchema = ['allOf', 'anyOf', 'oneOf'].some( + (keyword) => keyword in resolvedBodySchema + ); + const bodyProperties = isComposedSchema ? {} : (resolvedBodySchema.properties ?? {}); + // Strict servers reject declared parameters in the body, unless the body schema declares them too. + if (Object.keys(bodyProperties).length > 0) { + bodyParams = Object.fromEntries( + Object.entries(params).filter( + ([key]) => + Object.prototype.hasOwnProperty.call(bodyProperties, key) || + !declaredParamNames.has(key) + ) + ); + } else { + bodyParams = params; + } } else { // Optional: Fallback or explicit error if body is expected but not provided throw new Error(`Request body expected for operation '${name}' but none found.`); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 96b243c01a..91b66f8524 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1464,7 +1464,11 @@ export const getLineCount = (text) => { }; // Helper function to recursively resolve OpenAPI schema into JSON schema format -function resolveSchema(schemaRef, components, resolvedSchemas = new Set()) { +export function resolveSchema( + schemaRef, + components, + resolvedSchemas = new Set() +): Record { if (!schemaRef) return {}; if (schemaRef['$ref']) {