Web Search
llm-manager can automatically search the web when your chat messages contain research-oriented keywords. Results are fetched via SearXNG and injected into the prompt before your message, allowing the LLM to cite sources and provide up-to-date information.
Server-Side Flow
Web search runs entirely on the llm-manager server. External clients (chat frontends, curl, etc.) connect to llm-manager’s API proxy (default port 49222) just like any other chat request — no special headers or endpoints needed. The server intercepts chat completions requests, checks for search keywords, performs the SearXNG search, injects the results into the prompt, and forwards the enriched request to llama-server.
┌──────────┐ /v1/chat/completions ┌──────────────────┐
│ Client │ ──────────────────────────────►│ llm-manager API │
│ (curl, │ ◄──────────────────────────────│ proxy (port 49222)│
│ UI, etc)│ SSE streaming response └────────┬─────────┘
└──────────┘ │
│ triggers SearXNG
▼
┌──────────────────┐
│ SearXNG │
│ instance │
└──────────────────┘
The web_search_engine_url config points to the SearXNG instance, not the client. Clients never need direct access to SearXNG — they only talk to llm-manager’s API proxy.
Trigger
Web search triggers when your message contains $web:
$web best model for coding 2026
$web compare qwen 3 and llama 4
$web recommend vision model
Configuration
Via Server Settings Panel
- Open the Server Settings panel (press
F2orlwhen focused) - Navigate to the Web Search field using arrow keys
- Press
↵(Enter) to open the Web Search Picker dialog
The dialog (65 columns wide, 15 rows tall) shows:
| Field | Type | Description |
|---|---|---|
| Enabled | Toggle | Shows “On” (green) or “Off” (gray) — press ↵ to toggle |
| Engine | Dropdown | Search engine: searxng |
| Engine URL | Text input | URL of your SearXNG instance (e.g., https://search.example.com) |
| API Key | Text input | Bearer token for authentication (optional, masked as **** when set) |
Navigation: ↑/↓ (or j/k) to move between fields, ↵ to toggle/edit, ⎋ (Esc) to close.
Via config.yaml
Add these fields to your ~/.config/llm-manager/config.yaml:
default:
web_search_enabled: true
web_search_engine: searxng
web_search_engine_url: "https://search.example.com"
web_search_api_key: null # optional, omit or set to null if not needed
Per-Model Override
Web search settings can also be configured per-model in ~/.config/llm-manager/models/<model_name>.yaml:
web_search_enabled: true
web_search_engine: searxng
web_search_engine_url: "https://search.example.com"
web_search_api_key: null
Model-level settings override the global defaults.
How It Works
When a message matches a trigger keyword:
- Query extraction — the full user message is used as the search query
- SearXNG search — HTTP GET request to
{engine_url}/search?q={query}&format=json(only when the message contains$web) - URL extraction — any URLs found in the message are collected for page fetching (independent of
$web) - Concurrent gathering — the SearXNG search and the URL page-fetching run as separate tasks in parallel, both bounded by the 15-second timeout
- Result parsing — expects JSON with a
resultsarray; each result needstitle,url, andcontent/snippetfields - Page fetching — Wikipedia results and up to 5 other URLs have their page content fetched in parallel
- Context injection — results are prepended to the message as a
[WEB CONTEXT]...[END WEB CONTEXT]block
Concurrent Fetching & SSRF Protection
Fetching page content from arbitrary URLs in a message is powerful but risky: a malicious link could point at an internal service (e.g. a cloud metadata endpoint or an intranet server). To prevent this, every URL is validated before fetching:
- Scheme check — only
httpandhttpsare allowed - DNS resolution + IP block list — the hostname is resolved and every resolved IP is checked. Addresses in blocked ranges are rejected:
- loopback (
127.0.0.0/8,::1) - private (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7) - link-local / cloud metadata (
169.254.0.0/16) - unspecified (
0.0.0.0,::) - IPv4-mapped addresses are decoded and checked as v4
- loopback (
If any resolved IP is blocked, the URL is skipped with a logged warning rather than fetched. Pages that come back too short (likely blocked or empty) or are blocked by Cloudflare / a security filter are also skipped and counted. The SearXNG search and the URL fetch run concurrently, so adding URLs does not wait for (or block) a $web search and vice versa.
Request Details
- Endpoint:
{engine_url}/search?q={url_encoded_query}&format=json - User-Agent:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 - Authentication:
Authorization: Bearer {api_key}header (only ifapi_keyis configured) - Timeout: 15 seconds
- Max results: 10
Injected Prompt Format
The web context is prepended to the user message like this:
[WEB CONTEXT]
INSTRUCTION: Cite sources using inline markdown links in your answer.
## Search Results
1. **Title** - URL
snippet text
## Web Context
## [Title](URL)
...fetched page content...
[END WEB CONTEXT]
[Original user message]
Engine Support
| Engine | Status | Notes |
|---|---|---|
| SearXNG | ✅ Fully functional | Requires a configured engine_url pointing to a SearXNG instance |
SearXNG Setup
Minimal settings.yml
SearXNG requires a settings.yml configuration file. Create one before deploying:
use_default_settings: true
server:
secret_key: "change-this-to-a-random-secret" # generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
port: 8081
bind_address: "0.0.0.0"
# Base URL — required to avoid 303 redirects
# Set to the public URL where SearXNG is accessible
base_url: "http://localhost:8081" # or "https://search.example.com"
search:
default_lang: en
# Enable JSON format for API access (required for llm-manager web search)
formats:
- html
- json
Note: The
server.portinsettings.ymlis for SearXNG’s WSGI metadata. The actual listening port is controlled by theGRANIAN_PORTenvironment variable (default8080). You must set-e GRANIAN_PORT=8081to match your desired port.
Podman (standalone)
Run SearXNG as a standalone Podman container:
# Create config directory and settings file
mkdir -p ~/.searxng
cat > ~/.searxng/settings.yml << 'EOF'
use_default_settings: true
server:
secret_key: "change-this-to-a-random-secret"
port: 8081
bind_address: "0.0.0.0"
base_url: "http://localhost:8081" # uncomment if behind reverse proxy
search:
default_lang: en
formats:
- html
- json
EOF
# Run the container
podman run -d \
--name searxng \
-p 8081:8081 \
-e GRANIAN_PORT=8081 \
-v ~/.searxng/settings.yml:/etc/searxng/settings.yml:Z \
--restart unless-stopped \
searxng/searxng:latest
Important: Do not use
-v ~/.searxng:/etc/searxng/lib/searx:Z— it replaces the entire Python package directory with an empty directory, causing the container to crash. Only mount thesettings.ymlfile.
After deployment, use http://localhost:8081 (or your public URL) as the Engine URL in llm-manager.
Docker Compose
For Docker Compose users, create docker-compose.yml:
services:
searxng:
image: searxng/searxng:latest
ports:
- "8081:8081"
environment:
- GRANIAN_PORT=8081
volumes:
- ~/.searxng/settings.yml:/etc/searxng/settings.yml:Z
restart: unless-stopped
Run with:
docker compose up -d
podman-compose
For podman-compose users:
services:
searxng:
image: searxng/searxng:latest
ports:
- "8081:8081"
environment:
- GRANIAN_PORT=8081
volumes:
- ~/.searxng/settings.yml:/etc/searxng/settings.yml:Z
restart: unless-stopped
Run with:
podman-compose up -d
Settings Panel Display
The LLM Settings panel shows the current web search status:
Web Search (Enabled: searxng)
or
Web Search (Disabled: searxng)
Troubleshooting
- 303 redirect — set
server.base_urlinsettings.yamlto the public URL (e.g.,http://localhost:8081orhttps://search.example.com) - Search returns no results — verify the Engine URL is accessible and points to a running SearXNG instance
- Timeout errors — web search has a 15-second timeout; slow SearXNG instances may need tuning
- Authentication failures — if
web_search_api_keyis set, ensure the SearXNG instance accepts the Bearer token - Results not appearing in chat — check that trigger keywords are present in the message
- HTTPS certificate errors — ensure the SearXNG instance has valid TLS certificates if using
https://