[GH-ISSUE #24237] issue: Internal tools are not injected when tools are set in Filter function #123547

Closed
opened 2026-05-21 02:51:51 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @moritzderallerechte on GitHub (Apr 29, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/24237

Check Existing Issues

  • I have searched for any existing and/or related issues.
  • I have searched for any existing and/or related discussions.
  • I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!).
  • I am using the latest version of Open WebUI.

Installation Method

Git Clone

Open WebUI Version

v0.9.2

Ollama Version (if applicable)

No response

Operating System

Ubuntu

Browser (if applicable)

No response

Confirmation

  • I have read and followed all instructions in README.md.
  • I am using the latest version of both Open WebUI and Ollama.
  • I have included the browser console logs.
  • I have included the Docker container logs.
  • I have provided every relevant configuration, setting, and environment variable used in my setup.
  • I have clearly listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc).
  • I have documented step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation. My steps:
  • Start with the initial platform/version/OS and dependencies used,
  • Specify exact install/launch/configure commands,
  • List URLs visited, user input (incl. example values/emails/passwords if needed),
  • Describe all options and toggles enabled or changed,
  • Include any files or environmental changes,
  • Identify the expected and actual result at each stage,
  • Ensure any reasonably skilled user can follow and hit the same issue.

Expected Behavior

When the inlet function of a filter writes to the tool field of the request body, OWUI should still inject the internal tools.

Actual Behavior

When the inlet sets the tools field, all other internal tools are omitted.

Steps to Reproduce

My filter inlet function does this:

async def inlet(self, body: dict, __metadata__: dict, __event_emitter__) -> dict:
        tools = body.get("tools", [])
        logger.info(f"current tools: {tools}")

        tools.append(
            {
                "type": "openrouter:web_search",
                "parameters": {
                    "max_results": self.valves.WEB_SEARCH_RESULTS,
                    "max_total_results": self.valves.WEB_SEARCH_MAX_RESULTS,
                },
            }
        )

        body["tools"] = tools
        logger.info(f"new tools: {tools}")
        return body

Logs & Screenshots

The provided code produces the follwing logs:

function_websearch:inlet:30 - current tools: []
function_websearch:inlet:43 - new tools: [{'type': 'openrouter:web_search', 'parameters': {'max_results': 4, 'max_total_results': 12}}]

When logging the tools afterwards in a pipe() function, it should for example look like this:

'tools': [{
'type': 'function',
'function': {
'name': 'get_current_timestamp',
'description': 'Get the current Unix timestamp in seconds.',
'parameters': {
'properties': {},
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'calculate_timestamp',
'description': 'Get the current Unix timestamp, optionally adjusted by days, weeks, months, or years.\nUse this to calculate timestamps for date filtering in search functions.\nExamples: "last week" = weeks_ago=1, "3 days ago" = days_ago=3, "a year ago" = years_ago=1',
'parameters': {
'properties': {
'days_ago': {
'default': 0,
'description': 'Number of days to subtract from current time (default: 0)',
'type': 'integer'
},
'weeks_ago': {
'default': 0,
'description': 'Number of weeks to subtract from current time (default: 0)',
'type': 'integer'
},
'months_ago': {
'default': 0,
'description': 'Number of months to subtract from current time (default: 0)',
'type': 'integer'
},
'years_ago': {
'default': 0,
'description': 'Number of years to subtract from current time (default: 0)',
'type': 'integer'
}
},
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'list_knowledge_bases',
'description': "List the user's accessible knowledge bases.",
'parameters': {
'properties': {
'count': {
'default': 10,
'description': 'Maximum number of KBs to return (default: 10)',
'type': 'integer'
},
'skip': {
'default': 0,
'description': 'Number of results to skip for pagination (default: 0)',
'type': 'integer'
}
},
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'search_knowledge_bases',
'description': "Search the user's accessible knowledge bases by name and description.",
'parameters': {
'properties': {
'query': {
'description': 'The search query to find matching knowledge bases',
'type': 'string'
},
'count': {
'default': 5,
'description': 'Maximum number of results to return (default: 5)',
'type': 'integer'
},
'skip': {
'default': 0,
'description': 'Number of results to skip for pagination (default: 0)',
'type': 'integer'
}
},
'required': ['query'],
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'query_knowledge_bases',
'description': 'Search knowledge bases by semantic similarity to query.\nFinds KBs whose name/description match the meaning of your query.\nUse this to discover relevant knowledge bases before querying their files.',
'parameters': {
'properties': {
'query': {
'description': "Natural language query describing what you're looking for",
'type': 'string'
},
'count': {
'default': 5,
'description': 'Maximum results (default: 5)',
'type': 'integer'
}
},
'required': ['query'],
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'search_knowledge_files',
'description': 'Search files by filename across knowledge bases the user has access to.\nWhen the model has attached knowledge, searches only within attached KBs and files.',
'parameters': {
'properties': {
'query': {
'description': 'The search query to find matching files by filename',
'type': 'string'
},
'knowledge_id': {
'description': 'Optional KB id to limit search to a specific knowledge base',
'type': 'string'
},
'count': {
'default': 5,
'description': 'Maximum number of results to return (default: 5)',
'type': 'integer'
},
'skip': {
'default': 0,
'description': 'Number of results to skip for pagination (default: 0)',
'type': 'integer'
}
},
'required': ['query'],
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'query_knowledge_files',
'description': 'Search knowledge base files using semantic/vector search. Searches across collections (KBs),\nindividual files, and notes that the user has access to.',
'parameters': {
'properties': {
'query': {
'description': 'The search query to find semantically relevant content',
'type': 'string'
},
'knowledge_ids': {
'description': 'Optional list of KB ids to limit search to specific knowledge bases',
'items': {
'type': 'string'
},
'type': 'array'
},
'count': {
'default': 5,
'description': 'Maximum number of results to return (default: 5)',
'type': 'integer'
}
},
'required': ['query'],
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'view_knowledge_file',
'description': 'Get the content of a file from a knowledge base. Supports pagination for large files.',
'parameters': {
'properties': {
'file_id': {
'description': 'The ID of the file to retrieve',
'type': 'string'
},
'offset': {
'default': 0,
'description': 'Character offset to start reading from (default: 0)',
'type': 'integer'
},
'max_chars': {
'default': 10000,
'description': 'Maximum characters to return (default: 10000, hard cap: 100000)',
'type': 'integer'
}
},
'required': ['file_id'],
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'create_tasks',
'description': 'Create a task checklist to track progress on multi-step work.\nCall this once at the start to define all steps, then use\nupdate_task to mark each task as you complete it.',
'parameters': {
'properties': {
'tasks': {
'description': 'List of task items. Each item: content (string, required), status (pending|in_progress|completed|cancelled, default pending), id (optional, auto-generated).',
'items': {
'properties': {
'id': {
'description': 'Unique identifier for the task. Auto-generated if omitted.',
'type': 'string'
},
'content': {
'description': 'Task description.',
'type': 'string'
},
'status': {
'default': 'pending',
'description': 'Task status.',
'enum': ['pending', 'in_progress', 'completed', 'cancelled'],
'type': 'string'
}
},
'required': ['content'],
'type': 'object'
},
'type': 'array'
}
},
'required': ['tasks'],
'type': 'object'
}
}
}, {
'type': 'function',
'function': {
'name': 'update_task',
'description': 'Mark a single task as completed, in_progress, pending, or cancelled.\nCall this after finishing each step. You MUST call this for every\ntask, including the very last one.',
'parameters': {
'properties': {
'id': {
'description': 'The task ID to update',
'type': 'string'
},
'status': {
'default': 'completed',
'description': 'New status: completed, in_progress, pending, or cancelled (default: completed)',
'type': 'string'
}
},
'required': ['id'],
'type': 'object'
}
}
}, {
"type": "openrouter:web_search",
"parameters": {
"max_results": self.valves.WEB_SEARCH_RESULTS,
"max_total_results": self.valves.WEB_SEARCH_MAX_RESULTS,
},
}]

but it actually looks like this:

'tools': [{
"type": "openrouter:web_search",
"parameters": {
"max_results": self.valves.WEB_SEARCH_RESULTS,
"max_total_results": self.valves.WEB_SEARCH_MAX_RESULTS,
},
}]

Additional Information

To add a little context to this:
I am doing the described stuff in the filter function, since it allows me to use the nice UI integration buttons to enable and disable web search.

Originally created by @moritzderallerechte on GitHub (Apr 29, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/24237 ### Check Existing Issues - [x] I have searched for any existing and/or related issues. - [x] I have searched for any existing and/or related discussions. - [x] I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!). - [x] I am using the latest version of Open WebUI. ### Installation Method Git Clone ### Open WebUI Version v0.9.2 ### Ollama Version (if applicable) _No response_ ### Operating System Ubuntu ### Browser (if applicable) _No response_ ### Confirmation - [x] I have read and followed all instructions in `README.md`. - [x] I am using the latest version of **both** Open WebUI and Ollama. - [x] I have included the browser console logs. - [x] I have included the Docker container logs. - [x] I have **provided every relevant configuration, setting, and environment variable used in my setup.** - [x] I have clearly **listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup** (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc). - [x] I have documented **step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation**. My steps: - Start with the initial platform/version/OS and dependencies used, - Specify exact install/launch/configure commands, - List URLs visited, user input (incl. example values/emails/passwords if needed), - Describe all options and toggles enabled or changed, - Include any files or environmental changes, - Identify the expected and actual result at each stage, - Ensure any reasonably skilled user can follow and hit the same issue. ### Expected Behavior When the inlet function of a filter writes to the _tool_ field of the request body, OWUI should still inject the internal tools. ### Actual Behavior When the inlet sets the tools field, all other internal tools are omitted. ### Steps to Reproduce My filter inlet function does this: ``` async def inlet(self, body: dict, __metadata__: dict, __event_emitter__) -> dict: tools = body.get("tools", []) logger.info(f"current tools: {tools}") tools.append( { "type": "openrouter:web_search", "parameters": { "max_results": self.valves.WEB_SEARCH_RESULTS, "max_total_results": self.valves.WEB_SEARCH_MAX_RESULTS, }, } ) body["tools"] = tools logger.info(f"new tools: {tools}") return body ``` ### Logs & Screenshots The provided code produces the follwing logs: > function_websearch:inlet:30 - current tools: [] > function_websearch:inlet:43 - new tools: [{'type': 'openrouter:web_search', 'parameters': {'max_results': 4, 'max_total_results': 12}}] When logging the tools afterwards in a pipe() function, it should for example look like this: > 'tools': [{ > 'type': 'function', > 'function': { > 'name': 'get_current_timestamp', > 'description': 'Get the current Unix timestamp in seconds.', > 'parameters': { > 'properties': {}, > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'calculate_timestamp', > 'description': 'Get the current Unix timestamp, optionally adjusted by days, weeks, months, or years.\nUse this to calculate timestamps for date filtering in search functions.\nExamples: "last week" = weeks_ago=1, "3 days ago" = days_ago=3, "a year ago" = years_ago=1', > 'parameters': { > 'properties': { > 'days_ago': { > 'default': 0, > 'description': 'Number of days to subtract from current time (default: 0)', > 'type': 'integer' > }, > 'weeks_ago': { > 'default': 0, > 'description': 'Number of weeks to subtract from current time (default: 0)', > 'type': 'integer' > }, > 'months_ago': { > 'default': 0, > 'description': 'Number of months to subtract from current time (default: 0)', > 'type': 'integer' > }, > 'years_ago': { > 'default': 0, > 'description': 'Number of years to subtract from current time (default: 0)', > 'type': 'integer' > } > }, > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'list_knowledge_bases', > 'description': "List the user's accessible knowledge bases.", > 'parameters': { > 'properties': { > 'count': { > 'default': 10, > 'description': 'Maximum number of KBs to return (default: 10)', > 'type': 'integer' > }, > 'skip': { > 'default': 0, > 'description': 'Number of results to skip for pagination (default: 0)', > 'type': 'integer' > } > }, > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'search_knowledge_bases', > 'description': "Search the user's accessible knowledge bases by name and description.", > 'parameters': { > 'properties': { > 'query': { > 'description': 'The search query to find matching knowledge bases', > 'type': 'string' > }, > 'count': { > 'default': 5, > 'description': 'Maximum number of results to return (default: 5)', > 'type': 'integer' > }, > 'skip': { > 'default': 0, > 'description': 'Number of results to skip for pagination (default: 0)', > 'type': 'integer' > } > }, > 'required': ['query'], > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'query_knowledge_bases', > 'description': 'Search knowledge bases by semantic similarity to query.\nFinds KBs whose name/description match the meaning of your query.\nUse this to discover relevant knowledge bases before querying their files.', > 'parameters': { > 'properties': { > 'query': { > 'description': "Natural language query describing what you're looking for", > 'type': 'string' > }, > 'count': { > 'default': 5, > 'description': 'Maximum results (default: 5)', > 'type': 'integer' > } > }, > 'required': ['query'], > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'search_knowledge_files', > 'description': 'Search files by filename across knowledge bases the user has access to.\nWhen the model has attached knowledge, searches only within attached KBs and files.', > 'parameters': { > 'properties': { > 'query': { > 'description': 'The search query to find matching files by filename', > 'type': 'string' > }, > 'knowledge_id': { > 'description': 'Optional KB id to limit search to a specific knowledge base', > 'type': 'string' > }, > 'count': { > 'default': 5, > 'description': 'Maximum number of results to return (default: 5)', > 'type': 'integer' > }, > 'skip': { > 'default': 0, > 'description': 'Number of results to skip for pagination (default: 0)', > 'type': 'integer' > } > }, > 'required': ['query'], > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'query_knowledge_files', > 'description': 'Search knowledge base files using semantic/vector search. Searches across collections (KBs),\nindividual files, and notes that the user has access to.', > 'parameters': { > 'properties': { > 'query': { > 'description': 'The search query to find semantically relevant content', > 'type': 'string' > }, > 'knowledge_ids': { > 'description': 'Optional list of KB ids to limit search to specific knowledge bases', > 'items': { > 'type': 'string' > }, > 'type': 'array' > }, > 'count': { > 'default': 5, > 'description': 'Maximum number of results to return (default: 5)', > 'type': 'integer' > } > }, > 'required': ['query'], > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'view_knowledge_file', > 'description': 'Get the content of a file from a knowledge base. Supports pagination for large files.', > 'parameters': { > 'properties': { > 'file_id': { > 'description': 'The ID of the file to retrieve', > 'type': 'string' > }, > 'offset': { > 'default': 0, > 'description': 'Character offset to start reading from (default: 0)', > 'type': 'integer' > }, > 'max_chars': { > 'default': 10000, > 'description': 'Maximum characters to return (default: 10000, hard cap: 100000)', > 'type': 'integer' > } > }, > 'required': ['file_id'], > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'create_tasks', > 'description': 'Create a task checklist to track progress on multi-step work.\nCall this once at the start to define all steps, then use\nupdate_task to mark each task as you complete it.', > 'parameters': { > 'properties': { > 'tasks': { > 'description': 'List of task items. Each item: content (string, required), status (pending|in_progress|completed|cancelled, default pending), id (optional, auto-generated).', > 'items': { > 'properties': { > 'id': { > 'description': 'Unique identifier for the task. Auto-generated if omitted.', > 'type': 'string' > }, > 'content': { > 'description': 'Task description.', > 'type': 'string' > }, > 'status': { > 'default': 'pending', > 'description': 'Task status.', > 'enum': ['pending', 'in_progress', 'completed', 'cancelled'], > 'type': 'string' > } > }, > 'required': ['content'], > 'type': 'object' > }, > 'type': 'array' > } > }, > 'required': ['tasks'], > 'type': 'object' > } > } > }, { > 'type': 'function', > 'function': { > 'name': 'update_task', > 'description': 'Mark a single task as completed, in_progress, pending, or cancelled.\nCall this after finishing each step. You MUST call this for every\ntask, including the very last one.', > 'parameters': { > 'properties': { > 'id': { > 'description': 'The task ID to update', > 'type': 'string' > }, > 'status': { > 'default': 'completed', > 'description': 'New status: completed, in_progress, pending, or cancelled (default: completed)', > 'type': 'string' > } > }, > 'required': ['id'], > 'type': 'object' > } > } > }, { > "type": "openrouter:web_search", > "parameters": { > "max_results": self.valves.WEB_SEARCH_RESULTS, > "max_total_results": self.valves.WEB_SEARCH_MAX_RESULTS, > }, > }] but it actually looks like this: > 'tools': [{ > "type": "openrouter:web_search", > "parameters": { > "max_results": self.valves.WEB_SEARCH_RESULTS, > "max_total_results": self.valves.WEB_SEARCH_MAX_RESULTS, > }, > }] ### Additional Information To add a little context to this: I am doing the described stuff in the filter function, since it allows me to use the nice UI integration buttons to enable and disable web search.
GiteaMirror added the bug label 2026-05-21 02:51:52 -05:00
Author
Owner

@ER-EPR commented on GitHub (May 5, 2026):

Also the case when I add {"google_search":{}} into tool list. Did your filter works well with openrouter.

<!-- gh-comment-id:4376516478 --> @ER-EPR commented on GitHub (May 5, 2026): Also the case when I add {"google_search":{}} into tool list. Did your filter works well with openrouter.
Author
Owner

@tjbck commented on GitHub (May 8, 2026):

Addressed in dev.

<!-- gh-comment-id:4410110736 --> @tjbck commented on GitHub (May 8, 2026): Addressed in dev.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#123547