Files
LLM-Server/docs/research/litellm-searxng-search.md
haylanandClaude-Bot 8c42f2518b research: LiteLLM web-search + SearXNG wiring (issue #22)
Answers whether SearXNG is natively supported by LiteLLM's /v1/search
feature, whether it's a model-tool or automatic retrieval, what
docker-compose.yml networking change is needed for the litellm container
to reach the LAN's search.home host, and how it interacts with this
project's known-flaky Qwen3.8-27B tool-calling.

Research only — litellm-config.yaml and docker-compose.yml are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 21:25:30 +02:00

261 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Research: Wiring the local SearXNG instance into LiteLLM's web-search feature
**Question:** How does LiteLLM's web-search integration
(https://docs.litellm.ai/docs/search) actually work, and what does wiring the
local SearXNG instance (`http://search.home/`) into this repo's
`litellm-config.yaml` require?
**Answer, short version:** SearXNG **is** a natively supported provider for
LiteLLM's `/v1/search` feature — no custom-endpoint workaround needed. But the
feature is **not** a model-callable tool and **not** automatic
context-injection into chat completions either — it's a **separate REST API**
(`/v1/search/{search_tool_name}`) that a caller (Open WebUI, a script, a
future MCP wrapper) must invoke directly, independent of any LLM call. That
sidesteps this project's known-flaky Qwen3.8-27B tool-calling entirely, as
long as nothing wraps the endpoint back into model-driven tool-calling.
Reachability is the real blocker: `search.home` is a LAN mDNS/local-DNS name
that the `litellm` container cannot resolve by default — needs an
`extra_hosts` entry in `docker-compose.yml`.
## 1. What LiteLLM's search feature actually is
LiteLLM ships a **unified search API** (`/v1/search` and
`/v1/search/{search_tool_name}`) that wraps multiple search-provider backends
behind one Perplexity-compatible request/response shape.
Source: https://docs.litellm.ai/docs/search
Config shape in `config.yaml` (LiteLLM's own documented example, Perplexity
shown, same shape for every provider):
```yaml
search_tools:
- search_tool_name: perplexity-search
litellm_params:
search_provider: perplexity
api_key: os.environ/PERPLEXITYAI_API_KEY
```
Call shape:
```bash
curl http://0.0.0.0:4000/v1/search/searxng-search \
-H "Authorization: Bearer sk-1234" \
-H "Content-Type: application/json" \
-d '{"query": "latest AI developments", "max_results": 5}'
```
Source: https://docs.litellm.ai/docs/search
**18 providers are listed as supported**, including Perplexity, Tavily, Exa
AI, Brave, Parallel AI, Google PSE, DataForSEO, Firecrawl, **SearXNG**,
Linkup, Serper, DuckDuckGo, SearchAPI.io, You.com, APISerpent, Bedrock
AgentCore, Nimble, and Bing Grounding.
Source: https://docs.litellm.ai/docs/search
## 2. Is SearXNG natively supported? — Yes
SearXNG is one of the 18 built-in `search_provider` values, added by
BerriAI/litellm PR #16259 ("[Feat] add serxng search API provider").
Source: https://github.com/BerriAI/litellm/pull/16259
Config shape for SearXNG specifically:
```yaml
search_tools:
- search_tool_name: searxng-search
litellm_params:
search_provider: searxng
api_base: https://your-searxng-instance.com
```
Equivalently, the base URL can be supplied via the `SEARXNG_API_BASE`
environment variable instead of an inline `api_base` key — SearXNG has no API
key of its own (it's an unauthenticated local meta-search engine), so this is
the one provider in the list where `litellm_params` doesn't need a secret.
Sources: https://github.com/BerriAI/litellm/pull/16259,
https://docs.litellm.ai/docs/search
**For this repo**, the addition to `litellm-config.yaml` (research only — not
applied here) would look like:
```yaml
search_tools:
- search_tool_name: searxng-search
litellm_params:
search_provider: searxng
api_base: http://search.home/
```
No custom-endpoint or "generic OpenAI-compatible /v1/web_search" fallback is
needed — the concern in the ticket that LiteLLM's docs "may assume a hosted
provider like Tavily/Serper" turned out not to apply; SearXNG is a first-class
`search_provider` value, same shape as every hosted one.
## 3. Tool-call vs. automatic injection vs. a third thing
The ticket asked to determine whether this is (a) a tool the model must
explicitly call, or (b) automatic pre-retrieval/context-injection like
Perplexity's own search-augmented answers. **It's neither** — it's a
**standalone REST endpoint** that sits alongside `/v1/chat/completions`, not
wired into it:
> The documentation indicates this is a separate REST endpoint the
> application calls directly. The page presents `/search` as a standalone API
> endpoint alongside chat completions, not as an automatic injection feature.
> Users explicitly invoke the search endpoint; LiteLLM does not automatically
> inject search results into completions.
Source: https://docs.litellm.ai/docs/search (fetched content, describing the
`/v1/search` and `/v1/search/{search_tool_name}` endpoints as siblings of
`/v1/chat/completions`, not a chat-completion parameter or automatic
retrieval step)
Practical effect: whatever calls this endpoint — Open WebUI's own web-search
feature, a shell script, a future MCP server — does so with a plain HTTP call.
**LiteLLM's model routing and Qwen3.8-27B's tool-calling reliability are not
in that path at all**, unless something downstream chooses to expose this
endpoint back to the model *as* a function-calling tool (e.g. an MCP wrapper
that hands the model a `web_search` tool definition backed by this endpoint —
that would reintroduce the model-must-emit-a-correct-tool-call problem, but
that's a choice made one layer up, not something LiteLLM's `/v1/search`
feature forces).
## 4. Network reachability: `search.home` from inside the `litellm` container
`docker-compose.yml`'s `litellm` service joins only the `ai-stack` bridge
network (`networks: [ai-stack]`, line 116) and gets DNS resolution from
Docker's embedded DNS server for that network — which resolves other
containers by service/container name (`llama-server`, `qdrant`, etc., as
already used at `api_base: http://llama-server:8080/v1` in
`litellm-config.yaml` line 8) but has **no visibility into the LAN's mDNS/
local-DNS namespace** that resolves `search.home` on the host machine or on
LAN clients. So `http://search.home/` will not resolve from inside the
`litellm` container as configured today — this matches the ticket's
suspicion, and is standard Docker bridge-networking behavior, not specific to
this repo.
Source: `g:\_DEV\repos\LLM-Server\docker-compose.yml` (litellm service, lines
94124; `networks:` block, lines 154156)
No `extra_hosts`, `host.docker.internal`, or `network_mode: host` pattern
exists yet anywhere in this compose file to crib from — this would be the
first. (One service, `lazytainer`, already uses `network_mode: host`, but for
an unrelated reason — Docker-socket/host-port introspection — and switching
`litellm` to host networking would be a much bigger blast-radius change than
this ticket needs, dropping the `ai-stack` network isolation for every other
port `litellm` exposes.)
Source: `g:\_DEV\repos\LLM-Server\docker-compose.yml` lines 144152
**Recommendation: `extra_hosts` on the `litellm` service**, mapping
`search.home` to its LAN IP, e.g.:
```yaml
litellm:
...
extra_hosts:
- "search.home:192.0.2.10" # replace with SearXNG's actual LAN IP
```
This is the smallest, most local fix: one line, scoped to the one service
that needs it, no change to network topology or isolation, and it keeps
`litellm-config.yaml`'s `api_base: http://search.home/` value human-readable
(matching this repo's existing preference for symbolic hostnames like
`ai.home` / `proxy.ai.home` documented in `docs/network-access.md`) rather
than hardcoding the LAN IP directly into the YAML config. The IP needs to stay
in sync if SearXNG's host ever gets a new DHCP lease — same caveat that would
apply to any hardcoded-IP alternative, just isolated to one `extra_hosts`
line instead of buried in the search config.
`host.docker.internal` is not applicable here: that special hostname
resolves to the Docker **host's** own IP (useful for reaching a service
running directly on the host machine's loopback), not to arbitrary LAN mDNS
names — it wouldn't help resolve `search.home` unless SearXNG happens to run
on the same physical host as this compose stack.
## 5. Interaction with this project's known-flaky Qwen3.8-27B tool-calling
`docs/research/qwen3.8-27b-tool-calling.md` (2026-08-24) found, with medium-
to-high confidence, that Qwen3.8-27B's tool-calling through llama.cpp
inherits open/partially-fixed upstream parser bugs from the Qwen3.5 lineage
(issues #21158, #20837 in `ggml-org/llama.cpp`) — tool calls can be emitted
but not recognized, or land as inert XML inside a reasoning block, especially
with thinking enabled.
Source: `g:\_DEV\repos\LLM-Server\docs\research\qwen3.8-27b-tool-calling.md`
(section 3, "Bottom line" section)
Given section 3 above (`/v1/search` is a standalone endpoint, not a
model-tool), **that flakiness has no bearing on the recommended integration
path**: nothing about calling `POST /v1/search/searxng-search` from Open
WebUI or a script asks Qwen3.8-27B to emit a tool call at all. The search
happens (or doesn't) independent of the model's tool-calling grammar/parser
entirely.
The risk **would** resurface only if a *different* design choice is made
later — e.g. wrapping this same SearXNG-backed endpoint as an MCP tool or a
`tools=[...]` function definition handed to Qwen3.8-27B in a chat-completion
request, so the model itself decides when to search. That path would inherit
every bug documented in `qwen3.8-27b-tool-calling.md` (calls silently dropped,
calls trapped inside `<think>` blocks, etc.) and would need the live smoke
test that doc recommends before being trusted unattended. **That is not what
LiteLLM's `/v1/search` feature itself requires** — it's a choice a caller
could additionally make on top of it.
## Recommendation
1. Add a `search_tools` block to `litellm-config.yaml` using
`search_provider: searxng` and `api_base: http://search.home/` (see
section 2) — no custom/generic-endpoint workaround needed, this is a
first-class supported provider.
2. Add `extra_hosts: ["search.home:<LAN IP>"]` to the `litellm` service in
`docker-compose.yml` (see section 4) so the container can resolve the
hostname; confirm the IP is stable (static DHCP reservation) since
`extra_hosts` is a static mapping baked in at container start.
3. Treat `/v1/search` as a plain HTTP integration point, not a model tool —
whatever calls it (Open WebUI, a script) should call the REST endpoint
directly rather than exposing it to Qwen3.8-27B as a function-calling
tool, to avoid inheriting this project's documented tool-calling
flakiness (section 5). If model-driven search-tool-calling is wanted
later, that's a separate decision that should be smoke-tested against the
caveats in `qwen3.8-27b-tool-calling.md` first.
This is research only — `litellm-config.yaml` and `docker-compose.yml` are
not modified by this doc.
## Sources
- https://docs.litellm.ai/docs/search — LiteLLM search feature docs:
endpoints, `search_tools` config shape, provider list, standalone-endpoint
behavior.
- https://github.com/BerriAI/litellm/pull/16259 — SearXNG provider
implementation: `search_provider: searxng`, `api_base` /
`SEARXNG_API_BASE` config.
- `g:\_DEV\repos\LLM-Server\docker-compose.yml``litellm` service
definition (lines 94124), `ai-stack` network block (lines 154156),
`lazytainer`'s `network_mode: host` precedent (lines 144152).
- `g:\_DEV\repos\LLM-Server\litellm-config.yaml` — current proxy config
conventions (`model_list`, `litellm_params`, `api_base` usage at line 8).
- `g:\_DEV\repos\LLM-Server\docs\network-access.md` — this repo's existing
`*.home` / NPM hostname conventions.
- `g:\_DEV\repos\LLM-Server\docs\research\qwen3.8-27b-tool-calling.md`
prior findings on Qwen3.8-27B tool-calling reliability via llama.cpp.
## Confidence/uncertainty summary
- **High confidence:** SearXNG is a native, first-class `search_provider` in
LiteLLM's `/v1/search` feature (directly documented and confirmed via the
implementing PR); the feature is a standalone REST endpoint separate from
chat completions, not automatic context-injection and not itself a
model-callable tool.
- **Medium confidence:** the exact field name (`api_base` vs. relying solely
on `SEARXNG_API_BASE`) — two independent fetch passes against
docs.litellm.ai returned slightly different renderings of the same example
(one showed `api_key: os.environ/SEARXNG_API_BASE`, the other and the PR
fetch showed `api_base: <url>`); the PR-sourced `api_base` form is treated
as authoritative here since it comes from the implementing code change, but
this should be smoke-tested against the actual deployed LiteLLM image
(`ghcr.io/berriai/litellm:main-stable`) before being relied on verbatim.
- **Not independently verified:** SearXNG's actual LAN IP/hostname stability
on this network, and whether the deployed LiteLLM version
(`main-stable`, per `docker-compose.yml` line 95) already includes PR
#16259 — worth a quick `docker exec litellm pip show litellm` / changelog
check before wiring this in for real.