Search API as OpenWebUI Native Tool
Add Scavio as a native OpenWebUI tool function. Structured JSON results, multi-platform search, and no SearXNG maintenance. Drop-in Python function.
You can configure a structured search API as an OpenWebUI native tool instead of relying on the default SearXNG web search integration. Native tool calling mode lets the LLM decide when to search and how to use results, which produces better answers than the automatic web search injection that prepends raw results to every prompt.
Why Native Tool Calling Beats Default Web Search
OpenWebUI's default web search mode (SearXNG, Brave, etc.) injects search results into the system prompt before the LLM sees it. This has problems: the LLM cannot control what gets searched, results bloat the context window on every turn, and the search query is just the raw user message rather than a refined query. Native tool calling mode lets the model decide if a search is needed, craft a targeted query, and process results selectively.
The Tool Function Schema
OpenWebUI native tools are Python functions with a specific signature. The function docstring becomes the tool description the LLM sees.
import requests
from typing import Optional
class Tools:
def __init__(self):
self.api_key = "" # Set in OpenWebUI tool settings
def web_search(
self,
query: str,
num_results: Optional[int] = 5,
__user__: dict = {},
) -> str:
"""
Search the web for current information. Use this when the user
asks about recent events, current data, or anything that requires
up-to-date information beyond your training data.
:param query: The search query string.
:param num_results: Number of results to return (1-10).
:return: Search results as formatted text.
"""
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": self.api_key},
json={"query": query, "num_results": num_results or 5},
timeout=15,
)
if resp.status_code != 200:
return f"Search failed: {resp.status_code}"
results = resp.json().get("results", [])
if not results:
return "No results found."
output = []
for i, r in enumerate(results, 1):
output.append(
f"{i}. {r.get('title', 'No title')}\n"
f" URL: {r.get('url', '')}\n"
f" {r.get('snippet', 'No snippet')}"
)
return "\n\n".join(output)Setting It Up in OpenWebUI
- Go to Workspace, then Tools, then click Create Tool
- Paste the function code above
- In the tool settings, set the
api_keyvalves field to your Scavio API key - Enable the tool in your model configuration under Tool settings
- In the chat, make sure native tool calling is enabled (the wrench icon, not the globe icon)
Adding a Reddit Search Tool
You can add a second tool function to the same class for Reddit-specific searches.
def reddit_search(
self,
query: str,
subreddit: Optional[str] = None,
__user__: dict = {},
) -> str:
"""
Search Reddit for discussions and opinions. Use this when the user
wants community perspectives, reviews, or recommendations.
:param query: The search query.
:param subreddit: Optional subreddit to search within.
:return: Reddit discussion results.
"""
search_query = query
if subreddit:
search_query = f"site:reddit.com/r/{subreddit} {query}"
else:
search_query = f"site:reddit.com {query}"
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": self.api_key},
json={"query": search_query, "num_results": 8},
timeout=15,
)
results = resp.json().get("results", [])
output = []
for i, r in enumerate(results, 1):
output.append(
f"{i}. {r.get('title', '')}\n"
f" {r.get('url', '')}\n"
f" {r.get('snippet', '')}"
)
return "\n\n".join(output) if output else "No Reddit results found."SearXNG vs Structured API Comparison
- SearXNG: free, self-hosted, but returns HTML-scraped results that break when upstream engines change their markup
- SearXNG requires Docker setup and ongoing maintenance for rate limit bans from Google/Bing
- Structured search API: $0.005/query, no infrastructure, stable JSON schema, no scraping risk
- At 50 searches/day: SearXNG = $0 + server cost + your time. Scavio = $7.50/mo. The question is whether your time maintaining SearXNG is worth $7.50.
Valves Configuration
OpenWebUI valves let users configure tool settings without editing code. Add a Pydantic model to expose the API key as a configurable field.
from pydantic import BaseModel, Field
class Tools:
class Valves(BaseModel):
api_key: str = Field(default="", description="Scavio API key")
default_results: int = Field(default=5, description="Default result count")
def __init__(self):
self.valves = self.Valves()
def web_search(self, query: str, __user__: dict = {}) -> str:
"""Search the web for current information."""
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": self.valves.api_key},
json={"query": query, "num_results": self.valves.default_results},
timeout=15,
)
# ... same result formatting as aboveWhen to Use Which Mode
Use native tool calling when you want the LLM to decide when searching is relevant. Use the default web search mode when every single user message should be grounded in fresh web data (like a customer support bot answering about current policies). For most general-purpose chat setups, native tool calling gives better results because it avoids polluting the context with irrelevant search results on conversational turns.