Compare commits
3
Commits
949802fb2b
...
f508f5670a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f508f5670a | ||
|
|
a3ecbc0e02 | ||
|
|
8c42f2518b |
@@ -19,6 +19,15 @@ OPENWEBUI_LITELLM_KEY=
|
|||||||
# Seconds of inactivity before llama-server is stopped. 900 = 15 min.
|
# Seconds of inactivity before llama-server is stopped. 900 = 15 min.
|
||||||
LAZYTAINER_INACTIVE_TIMEOUT=900
|
LAZYTAINER_INACTIVE_TIMEOUT=900
|
||||||
|
|
||||||
|
# --- Embedding model (knowledgebase, see docs/memory-knowledgebase.md) ---
|
||||||
|
EMBEDDING_MODEL_FILE=nomic-embed-text-v1.5.Q8_0.gguf
|
||||||
|
|
||||||
|
# --- SearXNG web search (see docs/research/litellm-searxng-search.md) ---
|
||||||
|
# Required — the litellm container can't resolve search.home's LAN mDNS
|
||||||
|
# name on its own; this becomes an extra_hosts entry. Use a static
|
||||||
|
# DHCP-reserved IP so it doesn't drift.
|
||||||
|
SEARXNG_LAN_IP=
|
||||||
|
|
||||||
# --- LiteLLM proxy (see docs/proxy-key-onboarding.md, docs/network-access.md) ---
|
# --- LiteLLM proxy (see docs/proxy-key-onboarding.md, docs/network-access.md) ---
|
||||||
LITELLM_PORT=4000
|
LITELLM_PORT=4000
|
||||||
# Required — generate real random values before first run, e.g. `openssl rand -hex 32`.
|
# Required — generate real random values before first run, e.g. `openssl rand -hex 32`.
|
||||||
@@ -33,3 +42,13 @@ LITELLM_DB_PASSWORD=
|
|||||||
# master key never has to be typed into the browser.
|
# master key never has to be typed into the browser.
|
||||||
UI_USERNAME=admin
|
UI_USERNAME=admin
|
||||||
UI_PASSWORD=
|
UI_PASSWORD=
|
||||||
|
|
||||||
|
# --- Knowledgebase (pgvector, see docs/memory-knowledgebase.md) ---
|
||||||
|
# Required — random values, e.g. `openssl rand -hex 32`.
|
||||||
|
PGVECTOR_DB_PASSWORD=
|
||||||
|
# Auth key litellm-pgvector requires on its own API (its SERVER_API_KEY).
|
||||||
|
LITELLM_PGVECTOR_API_KEY=
|
||||||
|
# A virtual key litellm-pgvector uses to call back into litellm for
|
||||||
|
# embeddings — create it in the Admin UI like any other workload key
|
||||||
|
# (see docs/proxy-key-onboarding.md), name it "litellm-pgvector".
|
||||||
|
LITELLM_PGVECTOR_EMBEDDING_KEY=
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
.claude/
|
.claude/
|
||||||
.env
|
.env
|
||||||
|
# Personal memory content ingested by scripts/ingest-memory.sh — not meant
|
||||||
|
# to be committed to this repo.
|
||||||
|
data/
|
||||||
|
.leankg/
|
||||||
|
|||||||
@@ -36,3 +36,7 @@ An [AI gateway/proxy](https://git.arthurerlich.de/haylan/LLM-Server/issues/9) fr
|
|||||||
- Request priority across workloads: [`docs/proxy-request-priority.md`](docs/proxy-request-priority.md).
|
- Request priority across workloads: [`docs/proxy-request-priority.md`](docs/proxy-request-priority.md).
|
||||||
|
|
||||||
Open WebUI and the coding CLIs (see [`docs/coding-cli-setup.md`](docs/coding-cli-setup.md)) route through the proxy now — llama-server has no published host port anymore. **Not yet verified**: none of this has been smoke-tested on real hardware (LiteLLM's priority scheduler in particular is beta — see `docs/proxy-request-priority.md`) — see [issue #17](https://git.arthurerlich.de/haylan/LLM-Server/issues/17).
|
Open WebUI and the coding CLIs (see [`docs/coding-cli-setup.md`](docs/coding-cli-setup.md)) route through the proxy now — llama-server has no published host port anymore. **Not yet verified**: none of this has been smoke-tested on real hardware (LiteLLM's priority scheduler in particular is beta — see `docs/proxy-request-priority.md`) — see [issue #17](https://git.arthurerlich.de/haylan/LLM-Server/issues/17).
|
||||||
|
|
||||||
|
### Web search, knowledgebase, and memory
|
||||||
|
|
||||||
|
The gateway also fronts SearXNG-backed web search and a pgvector-backed knowledgebase (loaded with `data/memory.md` / `data/claude-legacy-memory.md`), wired at the LiteLLM layer so every client gets them, not just Open WebUI — see [`docs/memory-knowledgebase.md`](docs/memory-knowledgebase.md). **Not yet verified on real hardware** — see [issue #24](https://git.arthurerlich.de/haylan/LLM-Server/issues/24).
|
||||||
|
|||||||
@@ -35,6 +35,36 @@ services:
|
|||||||
- "lazytainer.group.llamaserver.inactiveTimeout=${LAZYTAINER_INACTIVE_TIMEOUT:-900}"
|
- "lazytainer.group.llamaserver.inactiveTimeout=${LAZYTAINER_INACTIVE_TIMEOUT:-900}"
|
||||||
- "lazytainer.group.llamaserver.minPacketThreshold=2"
|
- "lazytainer.group.llamaserver.minPacketThreshold=2"
|
||||||
|
|
||||||
|
embedding-server:
|
||||||
|
image: ghcr.io/ggml-org/llama.cpp:server-rocm
|
||||||
|
container_name: embedding-server
|
||||||
|
devices:
|
||||||
|
- /dev/kfd
|
||||||
|
- /dev/dri
|
||||||
|
group_add:
|
||||||
|
- video
|
||||||
|
- render
|
||||||
|
security_opt:
|
||||||
|
- seccomp=unconfined
|
||||||
|
ipc: host
|
||||||
|
volumes:
|
||||||
|
- models:/models
|
||||||
|
command: >
|
||||||
|
-m /models/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf}
|
||||||
|
--host 0.0.0.0
|
||||||
|
--port 8080
|
||||||
|
--embeddings
|
||||||
|
--pooling mean
|
||||||
|
--n-gpu-layers 999
|
||||||
|
--ctx-size 8192
|
||||||
|
# A dedicated embedding model — the chat model isn't embedding-trained
|
||||||
|
# and llama.cpp serves one model per process, so this is a second small
|
||||||
|
# instance, not a mode switch on llama-server. See
|
||||||
|
# docs/research/litellm-knowledgebase.md. Small enough (~150MB Q8) to
|
||||||
|
# run alongside the chat model's ~19.6GB in the R9700's 32GB VRAM.
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [ai-stack]
|
||||||
|
|
||||||
# ponytail: one-off downloader, not a standing service — run via
|
# ponytail: one-off downloader, not a standing service — run via
|
||||||
# `docker compose --profile tools run --rm downloader` (see scripts/download-model.sh).
|
# `docker compose --profile tools run --rm downloader` (see scripts/download-model.sh).
|
||||||
# Keeps the model file inside the named `models` volume instead of a host bind-mount.
|
# Keeps the model file inside the named `models` volume instead of a host bind-mount.
|
||||||
@@ -52,6 +82,20 @@ services:
|
|||||||
curl -L --fail --create-dirs -o /models/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf}
|
curl -L --fail --create-dirs -o /models/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf}
|
||||||
https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/main/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf}
|
https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/main/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf}
|
||||||
|
|
||||||
|
# ponytail: same one-off pattern as `downloader`, for the embedding model —
|
||||||
|
# run via `docker compose --profile tools run --rm downloader-embedding`.
|
||||||
|
downloader-embedding:
|
||||||
|
image: curlimages/curl:latest
|
||||||
|
profiles: ["tools"]
|
||||||
|
user: root
|
||||||
|
volumes:
|
||||||
|
- models:/models
|
||||||
|
entrypoint: ["sh", "-c"]
|
||||||
|
command:
|
||||||
|
- >
|
||||||
|
curl -L --fail --create-dirs -o /models/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf}
|
||||||
|
https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf}
|
||||||
|
|
||||||
qdrant:
|
qdrant:
|
||||||
image: qdrant/qdrant:latest
|
image: qdrant/qdrant:latest
|
||||||
container_name: qdrant
|
container_name: qdrant
|
||||||
@@ -107,6 +151,12 @@ services:
|
|||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=postgresql://litellm:${LITELLM_DB_PASSWORD}@litellm-db:5432/litellm
|
- DATABASE_URL=postgresql://litellm:${LITELLM_DB_PASSWORD}@litellm-db:5432/litellm
|
||||||
|
# The litellm container only joins the ai-stack bridge network, which has
|
||||||
|
# no visibility into the LAN's mDNS/local-DNS names — search.home won't
|
||||||
|
# resolve without this. Set SEARXNG_LAN_IP in .env to its stable LAN IP
|
||||||
|
# (static DHCP reservation recommended). See docs/research/litellm-searxng-search.md.
|
||||||
|
extra_hosts:
|
||||||
|
- "search.home:${SEARXNG_LAN_IP}"
|
||||||
command: ["--config", "/app/config.yaml", "--port", "4000"]
|
command: ["--config", "/app/config.yaml", "--port", "4000"]
|
||||||
ports:
|
ports:
|
||||||
# published for LAN access (proxy.ai.home) and, via NPM, proxy.ai.haylan.ch —
|
# published for LAN access (proxy.ai.home) and, via NPM, proxy.ai.haylan.ch —
|
||||||
@@ -141,6 +191,59 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
# Separate Postgres instance (with the pgvector extension) for the
|
||||||
|
# knowledgebase — NOT the same database as litellm-db, which is plain
|
||||||
|
# postgres:16-alpine and has no vector extension installed. See
|
||||||
|
# docs/research/litellm-knowledgebase.md.
|
||||||
|
pgvector-db:
|
||||||
|
image: pgvector/pgvector:pg16
|
||||||
|
container_name: pgvector-db
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=litellm_pgvector
|
||||||
|
- POSTGRES_PASSWORD=${PGVECTOR_DB_PASSWORD}
|
||||||
|
- POSTGRES_DB=litellm_pgvector
|
||||||
|
volumes:
|
||||||
|
- pgvector-db-data:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [ai-stack]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -d litellm_pgvector -U litellm_pgvector"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
# LiteLLM's native knowledgebase/vector-store feature has no Qdrant backend
|
||||||
|
# (the qdrant service above only serves Open WebUI's own RAG/Memory) — this
|
||||||
|
# companion service (github.com/BerriAI/litellm-pgvector) is the only
|
||||||
|
# self-hosted path. No published image exists yet, so this builds straight
|
||||||
|
# from the upstream repo. See docs/research/litellm-knowledgebase.md.
|
||||||
|
# ponytail: unverified against real hardware — Prisma migration behavior on
|
||||||
|
# first boot and the exact vector_store_registry field names for the
|
||||||
|
# pg_vector provider need a live smoke test. See issue #24.
|
||||||
|
litellm-pgvector:
|
||||||
|
build:
|
||||||
|
context: https://github.com/BerriAI/litellm-pgvector.git
|
||||||
|
container_name: litellm-pgvector
|
||||||
|
depends_on:
|
||||||
|
pgvector-db:
|
||||||
|
condition: service_healthy
|
||||||
|
litellm:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://litellm_pgvector:${PGVECTOR_DB_PASSWORD}@pgvector-db:5432/litellm_pgvector
|
||||||
|
- SERVER_API_KEY=${LITELLM_PGVECTOR_API_KEY}
|
||||||
|
# Calls back into litellm for embeddings, same pattern as any other
|
||||||
|
# workload — see docs/proxy-key-onboarding.md for issuing this key.
|
||||||
|
- EMBEDDING__MODEL=local-embedding
|
||||||
|
- EMBEDDING__BASE_URL=http://litellm:4000
|
||||||
|
- EMBEDDING__API_KEY=${LITELLM_PGVECTOR_EMBEDDING_KEY}
|
||||||
|
- EMBEDDING__DIMENSIONS=768
|
||||||
|
expose:
|
||||||
|
- "8000"
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [ai-stack]
|
||||||
|
|
||||||
lazytainer:
|
lazytainer:
|
||||||
image: ghcr.io/vmorganp/lazytainer:master
|
image: ghcr.io/vmorganp/lazytainer:master
|
||||||
container_name: lazytainer
|
container_name: lazytainer
|
||||||
@@ -159,3 +262,4 @@ volumes:
|
|||||||
qdrant-data:
|
qdrant-data:
|
||||||
openwebui-data:
|
openwebui-data:
|
||||||
litellm-db-data:
|
litellm-db-data:
|
||||||
|
pgvector-db-data:
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Knowledgebase, memory, and web search
|
||||||
|
|
||||||
|
Three gateway-level capabilities added on top of the [AI gateway/proxy](https://git.arthurerlich.de/haylan/LLM-Server/issues/9), so every client behind LiteLLM gets them — not just Open WebUI. See [issue #21](https://git.arthurerlich.de/haylan/LLM-Server/issues/21) for the rationale.
|
||||||
|
|
||||||
|
**Not yet verified on real hardware** — see [issue #24](https://git.arthurerlich.de/haylan/LLM-Server/issues/24). In particular: `litellm-pgvector`'s Prisma migrations on first boot, and the exact `vector_store_registry` field names for the `pg_vector` provider.
|
||||||
|
|
||||||
|
## Web search (SearXNG)
|
||||||
|
|
||||||
|
`litellm-config.yaml`'s `search_tools` block wires the LAN's SearXNG instance in as a **standalone REST endpoint**, not a model-callable tool — call it directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://<proxy>:4000/v1/search/searxng-search \
|
||||||
|
-H "Authorization: Bearer <a virtual key>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"query": "...", "max_results": 5}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Because this doesn't ask the model to emit a tool call, it sidesteps Qwen3.8-27B's known-flaky tool-calling (`docs/research/qwen3.8-27b-tool-calling.md`) entirely. Open WebUI's own web-search setting can point at this endpoint the same way.
|
||||||
|
|
||||||
|
Requires `SEARXNG_LAN_IP` set in `.env` (SearXNG's stable LAN IP — use a static DHCP reservation) so the `litellm` container can resolve `search.home` via `extra_hosts`. Full research: `docs/research/litellm-searxng-search.md`.
|
||||||
|
|
||||||
|
## Knowledgebase (vector store / RAG)
|
||||||
|
|
||||||
|
LiteLLM's native knowledgebase feature has **no Qdrant backend** — the `qdrant` service in this stack only serves Open WebUI's own separate RAG/Memory feature and is unrelated to this. The only self-hosted path is [litellm-pgvector](https://github.com/BerriAI/litellm-pgvector), a companion service backed by its own Postgres+pgvector database (`pgvector-db`), which this stack now runs alongside `litellm`. Full research: `docs/research/litellm-knowledgebase.md`.
|
||||||
|
|
||||||
|
New pieces:
|
||||||
|
|
||||||
|
- **`embedding-server`** — a second llama.cpp instance (small footprint, `nomic-embed-text-v1.5`) serving `/v1/embeddings`. The chat model isn't embedding-trained and llama.cpp serves one model per process, so this can't just be a flag on `llama-server`.
|
||||||
|
- **`pgvector-db`** — Postgres with the pgvector extension, separate from `litellm-db`.
|
||||||
|
- **`litellm-pgvector`** — the connector service; built straight from its upstream repo (no published image exists).
|
||||||
|
- `litellm-config.yaml`'s `local-embedding` model entry and `vector_store_registry` block, tying it together.
|
||||||
|
|
||||||
|
### First-time setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --profile tools run --rm downloader-embedding # fetch the embedding model
|
||||||
|
docker compose up -d embedding-server pgvector-db litellm-pgvector
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a `litellm-pgvector` virtual key in LiteLLM's Admin UI (per `docs/proxy-key-onboarding.md`) and set it as `LITELLM_PGVECTOR_EMBEDDING_KEY` in `.env` — the connector calls back into `litellm` for embeddings, same as any other workload.
|
||||||
|
|
||||||
|
### Loading memory into it
|
||||||
|
|
||||||
|
`data/memory.md` and `data/claude-legacy-memory.md` — Claude-memory-style fact files — get loaded via:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/ingest-memory.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
One chunk per fact/paragraph line, tagged with `source`/`section` metadata. Re-run after editing either file (see the script's header comment for the no-dedup caveat).
|
||||||
|
|
||||||
|
### Querying it
|
||||||
|
|
||||||
|
Via the OpenAI Assistants-style `file_search` tool on a chat completion:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "qwen3.8-27b-local",
|
||||||
|
"messages": [...],
|
||||||
|
"tools": [{"type": "file_search", "vector_store_ids": ["memory-and-notes"]}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or directly: `POST /v1/vector_stores/memory-and-notes/search` with `{"query": "..."}`.
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
# Research: LiteLLM vector store / knowledgebase feature (for Gitea issue #23)
|
||||||
|
|
||||||
|
**Question:** Can LiteLLM's built-in "knowledgebase" / vector store feature be
|
||||||
|
used to load two Markdown fact files (`data/memory.md`,
|
||||||
|
`data/claude-legacy-memory.md`) into a queryable knowledge base, and if so,
|
||||||
|
what does that require from this stack (config shape, backend, embedding
|
||||||
|
model, ingestion mechanism)?
|
||||||
|
|
||||||
|
**Bottom line: no, not against this stack's existing Qdrant instance, and not
|
||||||
|
without adding a dedicated embedding model.** LiteLLM's vector store feature
|
||||||
|
is a *routing/registry* layer over a small set of natively-integrated
|
||||||
|
backends (Bedrock Knowledge Bases, OpenAI Vector Stores, Azure
|
||||||
|
Vector/AI-Search, Vertex AI RAG/Search, Gemini File Search, RAGFlow) plus one
|
||||||
|
self-hosted option — Postgres+pgvector, via a **separate companion service**
|
||||||
|
(`BerriAI/litellm-pgvector`), not Qdrant. There is no Qdrant provider at all.
|
||||||
|
Ingesting the two fact files would also require running a dedicated
|
||||||
|
embedding-capable model, which this stack doesn't currently have (the one
|
||||||
|
llama.cpp instance serves a chat model, not started with `--embeddings`).
|
||||||
|
|
||||||
|
## 1. Config shape in `config.yaml`
|
||||||
|
|
||||||
|
Top-level block is **`vector_store_registry`** (this is the current key name;
|
||||||
|
an earlier PR that introduced the feature used `vector_stores` — see
|
||||||
|
Provenance note below), a list of entries:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vector_store_registry:
|
||||||
|
- vector_store_name: "my-knowledgebase" # optional friendly name
|
||||||
|
litellm_params:
|
||||||
|
vector_store_id: "T37J8R4WTM" # required, provider's own ID
|
||||||
|
custom_llm_provider: "bedrock" # required — selects backend
|
||||||
|
vector_store_description: "..." # optional
|
||||||
|
vector_store_metadata: {} # optional
|
||||||
|
litellm_credential_name: "..." # optional, reuse a named credential
|
||||||
|
embedding_model: "..." # backend-dependent — see §3
|
||||||
|
```
|
||||||
|
|
||||||
|
Source: [docs.litellm.ai/docs/completion/knowledgebase](https://docs.litellm.ai/docs/completion/knowledgebase)
|
||||||
|
(fetched directly). Earlier shape (same idea, older field names) documented
|
||||||
|
in [BerriAI/litellm PR #10448](https://github.com/BerriAI/litellm/pull/10448)
|
||||||
|
("[Feat] Vector Stores/KnowledgeBases - Allow defining Vector Store Configs"):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vector_stores:
|
||||||
|
- vector_store_name: "bedrock-litellm-website-knowledgebase"
|
||||||
|
litellm_params:
|
||||||
|
custom_llm_provider: "bedrock"
|
||||||
|
id: "T37J8R4WTM"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a **registry of pointers to knowledge bases that already exist on
|
||||||
|
the backend provider** — it is not itself a vector database. `model_list`
|
||||||
|
entries are not where a store is attached; `vector_store_registry` is its
|
||||||
|
own top-level sibling of `model_list`.
|
||||||
|
|
||||||
|
### Referencing a vector store from a request
|
||||||
|
|
||||||
|
Not via a `model_list` entry — via the **`tools`** array of a
|
||||||
|
`/chat/completions` (or `/v1/responses`) request, OpenAI Assistants-style
|
||||||
|
`file_search` tool:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "gpt-4o",
|
||||||
|
"messages": [...],
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"type": "file_search",
|
||||||
|
"vector_store_ids": ["T37J8R4WTM"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
LiteLLM intercepts the tool call, looks up the referenced ID in
|
||||||
|
`vector_store_registry`, calls that backend's native search, and injects the
|
||||||
|
retrieved chunks into the prompt before calling the target model. Source:
|
||||||
|
[docs.litellm.ai/docs/completion/knowledgebase](https://docs.litellm.ai/docs/completion/knowledgebase).
|
||||||
|
|
||||||
|
There are also direct, OpenAI-compatible proxy endpoints for managing/using a
|
||||||
|
store outside of a chat completion — see §4.
|
||||||
|
|
||||||
|
## 2. Backend providers — Qdrant is NOT one of them
|
||||||
|
|
||||||
|
Per the official docs page and the PR that introduced the feature, the
|
||||||
|
natively-supported `custom_llm_provider` values are:
|
||||||
|
|
||||||
|
| Provider | `custom_llm_provider` value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| AWS Bedrock Knowledge Bases | `bedrock` | Original/reference implementation ([PR #10448](https://github.com/BerriAI/litellm/pull/10448)) |
|
||||||
|
| OpenAI Vector Stores | `openai` | Wraps OpenAI's own vector store API |
|
||||||
|
| Azure Vector Stores | `azure` | Assistants-API-only per docs |
|
||||||
|
| Azure AI Search | `azure_ai_search` (vector search capability) | |
|
||||||
|
| Vertex AI RAG Engine | `vertex_ai` | Added in [PR #15781](https://github.com/BerriAI/litellm/pull/15781) |
|
||||||
|
| Vertex AI Search API | `vertex_ai/search_api` | |
|
||||||
|
| Gemini File Search | — | |
|
||||||
|
| RAGFlow Datasets | — | Dataset mgmt only; search unsupported per docs |
|
||||||
|
| Postgres + pgvector | `pg_vector` (via a **separate** connector service) | See below — not built into the litellm proxy image |
|
||||||
|
|
||||||
|
Source: [docs.litellm.ai/docs/completion/knowledgebase](https://docs.litellm.ai/docs/completion/knowledgebase),
|
||||||
|
cross-checked against [PR #12595](https://github.com/BerriAI/litellm/pull/12595)
|
||||||
|
("[Feat] Vector Stores - Add Vertex RAG Engine API as a provider") and
|
||||||
|
[PR #15781](https://github.com/BerriAI/litellm/pull/15781) ("(feat) Vector
|
||||||
|
Stores: support Vertex AI Search API").
|
||||||
|
|
||||||
|
**Qdrant is not listed anywhere** in the knowledgebase docs, the vector
|
||||||
|
store provider PRs, or the pgvector connector's own README. LiteLLM does use
|
||||||
|
Qdrant in one unrelated feature — **semantic response caching**
|
||||||
|
(`docs.litellm.ai/docs/caching/all_caches`, `qdrant_api_base` /
|
||||||
|
`qdrant_api_key` / `qdrant_collection_name` config) — but that is a cache for
|
||||||
|
LLM *responses*, not the vector-store/knowledgebase (RAG) feature, and shares
|
||||||
|
no config or code path with `vector_store_registry`. It would not let
|
||||||
|
LiteLLM search Qdrant-held documents as a knowledge base.
|
||||||
|
|
||||||
|
### The pgvector option is a separate microservice, not a built-in backend
|
||||||
|
|
||||||
|
[`BerriAI/litellm-pgvector`](https://github.com/BerriAI/litellm-pgvector) is
|
||||||
|
its own repo/container: a FastAPI app that exposes OpenAI-compatible vector
|
||||||
|
store endpoints backed by Postgres with the `pgvector` extension, calling
|
||||||
|
back out to a LiteLLM proxy's `/embeddings` endpoint to generate embeddings.
|
||||||
|
It is registered into `vector_store_registry` like any other backend (with
|
||||||
|
`custom_llm_provider: pg_vector` pointed at this companion service's URL),
|
||||||
|
but it is **not Qdrant** and **not part of the main `litellm` proxy image**
|
||||||
|
already running in this stack — it would mean deploying and operating a
|
||||||
|
fourth service (on top of `litellm`, `litellm-db`, and `llama-server`), with
|
||||||
|
its own Postgres database using the pgvector extension (the existing
|
||||||
|
`litellm-db` Postgres image, `postgres:16-alpine`, does not have pgvector
|
||||||
|
installed).
|
||||||
|
|
||||||
|
### Verdict on the existing Qdrant instance
|
||||||
|
|
||||||
|
**LiteLLM's knowledgebase/vector_store feature cannot point at this repo's
|
||||||
|
existing standalone `qdrant` service.** There is no Qdrant provider type for
|
||||||
|
`vector_store_registry`. To use LiteLLM's native feature at all, this stack
|
||||||
|
would need to either integrate with a cloud-native backend (Bedrock/Vertex/
|
||||||
|
Azure/OpenAI — none of which apply, this stack is local-only) or stand up
|
||||||
|
the separate `litellm-pgvector` + pgvector-enabled Postgres stack — an
|
||||||
|
entirely different vector store technology from the Qdrant already running
|
||||||
|
for Open WebUI. The existing Qdrant collection Open WebUI uses for its own
|
||||||
|
RAG/Memory feature is unrelated to and unreachable from LiteLLM's
|
||||||
|
knowledgebase feature.
|
||||||
|
|
||||||
|
## 3. Embedding model requirement
|
||||||
|
|
||||||
|
LiteLLM's vector store feature **does call an embedding endpoint itself**
|
||||||
|
when a backend needs one (pgvector explicitly; the managed cloud backends
|
||||||
|
handle embedding server-side). The field is `embedding_model` inside a
|
||||||
|
`vector_store_registry` entry's `litellm_params`, confirmed in
|
||||||
|
[BerriAI/litellm issue #23980](https://github.com/BerriAI/litellm/issues/23980)
|
||||||
|
("[Bug]: Vector store creation fails when using model mapping public model
|
||||||
|
name for embedding_model"), which shows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"litellm_params": {
|
||||||
|
"vector_bucket_name": "my-embeddings",
|
||||||
|
"index_name": "test-index",
|
||||||
|
"aws_region_name": "us-east-1",
|
||||||
|
"embedding_model": "test-vector-store/bedrock/amazon.nova-2-multimodal-embeddings-v1:0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For the pgvector connector specifically, the embedding model is configured
|
||||||
|
via its own env vars (not `vector_store_registry`, since it's a separate
|
||||||
|
service): `EMBEDDING__MODEL`, `EMBEDDING__BASE_URL` (pointed at a LiteLLM
|
||||||
|
proxy), `EMBEDDING__API_KEY`, `EMBEDDING__DIMENSIONS` — e.g.
|
||||||
|
`EMBEDDING__MODEL=text-embedding-ada-002`,
|
||||||
|
`EMBEDDING__BASE_URL=http://litellm:4000`. Source:
|
||||||
|
[BerriAI/litellm-pgvector README](https://github.com/BerriAI/litellm-pgvector/blob/main/README.md).
|
||||||
|
This confirms: **yes, the embedding model must be reachable as a model
|
||||||
|
LiteLLM's proxy can call** — i.e. it needs its own `model_list` entry with
|
||||||
|
`mode: embedding` (LiteLLM's standard way of declaring an embedding-capable
|
||||||
|
model — see [docs.litellm.ai/docs/embedding/supported_embedding](https://github.com/BerriAI/litellm/blob/main/docs/my-website/docs/embedding/supported_embedding.md)),
|
||||||
|
so the pgvector service (or LiteLLM itself for backends that embed
|
||||||
|
internally) can call `POST /embeddings` against it through the proxy.
|
||||||
|
|
||||||
|
### Does llama.cpp (this repo's model server) support `/embeddings`?
|
||||||
|
|
||||||
|
Yes, but not enabled as currently configured, and not well-suited to the
|
||||||
|
model already loaded. llama.cpp's server (`tools/server`, the same
|
||||||
|
`ghcr.io/ggml-org/llama.cpp:server-rocm` image this repo uses per
|
||||||
|
`docker-compose.yml`) exposes an OpenAI-compatible `POST /v1/embeddings`
|
||||||
|
route (and a native `/embedding` route), **but only when started with the
|
||||||
|
`--embeddings` flag** — source:
|
||||||
|
[ggml-org/llama.cpp tools/server/README.md](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md).
|
||||||
|
This repo's `llama-server` command in `docker-compose.yml` (lines 16–22) does
|
||||||
|
not pass `--embeddings`, so the currently-running instance does not serve
|
||||||
|
embeddings at all.
|
||||||
|
|
||||||
|
Even if the flag were added, llama.cpp loads **one model per server
|
||||||
|
process** — the flag would make the already-loaded Qwen3.8-27B **chat**
|
||||||
|
model (per `litellm-config.yaml`, `qwen3.8-27b-local` /
|
||||||
|
`Qwen3.8-27B-UD-Q4_K_XL.gguf`) emit pooled hidden-state vectors as
|
||||||
|
"embeddings," but a generalist instruction-tuned chat model is not what
|
||||||
|
that's trained for — embedding quality from a non-embedding-trained model is
|
||||||
|
materially worse than a purpose-trained embedding model (e.g. BGE, Nomic
|
||||||
|
Embed, mxbai-embed, gte). **A separate, dedicated embedding model/server
|
||||||
|
would be needed** — either a second `llama-server` container loaded with a
|
||||||
|
small GGUF embedding model (`--embeddings` flag on), or a different
|
||||||
|
embedding-serving stack — and it would need its own `model_list` entry in
|
||||||
|
`litellm-config.yaml` with `mode: embedding` for LiteLLM/litellm-pgvector to
|
||||||
|
call it.
|
||||||
|
|
||||||
|
## 4. How documents actually get ingested
|
||||||
|
|
||||||
|
Two ingestion paths exist depending on backend, both are HTTP APIs — there
|
||||||
|
is no admin-UI "add document" flow beyond store *creation*, and no CLI:
|
||||||
|
|
||||||
|
**a) OpenAI-compatible vector-store file API** (used for the `openai`
|
||||||
|
backend, and shown as the general pattern in the docs):
|
||||||
|
|
||||||
|
- `POST /v1/vector_stores` — create a store. Body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "My Document Store",
|
||||||
|
"file_ids": ["file-abc123"],
|
||||||
|
"chunking_strategy": {
|
||||||
|
"type": "static",
|
||||||
|
"static": {"max_chunk_size_tokens": 800, "chunk_overlap_tokens": 400}
|
||||||
|
},
|
||||||
|
"metadata": {"key": "value"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Requires files already uploaded through a separate Files API to obtain
|
||||||
|
`file_id`s first — the docs page does not show a files-upload endpoint
|
||||||
|
under the vector-store docs directly (this is OpenAI's own two-step
|
||||||
|
upload-then-attach flow, proxied through). Source:
|
||||||
|
[docs.litellm.ai/docs/vector_stores/create](https://docs.litellm.ai/docs/vector_stores/create).
|
||||||
|
- `POST /v1/vector_stores/{vector_store_id}/search` — query it:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"query": "What is the capital of France?",
|
||||||
|
"filters": {"file_ids": ["file-abc123"]},
|
||||||
|
"max_num_results": 5,
|
||||||
|
"ranking_options": {"score_threshold": 0.7},
|
||||||
|
"rewrite_query": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Source: [docs.litellm.ai/docs/vector_stores/search](https://docs.litellm.ai/docs/vector_stores/search).
|
||||||
|
- `GET /vector_store/list` — list registered stores. Source:
|
||||||
|
[docs.litellm.ai/docs/completion/knowledgebase](https://docs.litellm.ai/docs/completion/knowledgebase).
|
||||||
|
- LiteLLM Admin UI: **Experimental → Vector Stores → Create Vector Store**
|
||||||
|
exists for registering/creating stores, and the **Logs** page shows
|
||||||
|
vector-store search queries/scores after use — but this is store
|
||||||
|
management and observability, not a bulk document-ingestion UI. Source:
|
||||||
|
[docs.litellm.ai/docs/completion/knowledgebase](https://docs.litellm.ai/docs/completion/knowledgebase).
|
||||||
|
|
||||||
|
**b) `litellm-pgvector` connector's own embeddings API** (only path relevant
|
||||||
|
if this repo went the self-hosted pgvector route, since Qdrant isn't
|
||||||
|
supported at all): direct chunk-level ingestion, no file upload step —
|
||||||
|
|
||||||
|
- `POST /v1/vector_stores/{id}/embeddings` — single chunk:
|
||||||
|
```json
|
||||||
|
{"content": "...", "embedding": [/* optional, else computed server-side */], "metadata": {}}
|
||||||
|
```
|
||||||
|
- `POST /v1/vector_stores/{id}/embeddings/batch` — array of the same shape,
|
||||||
|
for bulk loading.
|
||||||
|
|
||||||
|
Source: [BerriAI/litellm-pgvector README](https://github.com/BerriAI/litellm-pgvector/blob/main/README.md).
|
||||||
|
|
||||||
|
For a follow-up ticket that wants to programmatically load
|
||||||
|
`data/memory.md` / `data/claude-legacy-memory.md` (dated fact lists) into a
|
||||||
|
knowledge base, the realistic path through LiteLLM's native feature would be
|
||||||
|
the pgvector connector's batch-embeddings endpoint (chunk the Markdown into
|
||||||
|
facts/sections client-side, POST each as a batch) — **but that requires
|
||||||
|
first standing up**: (1) a pgvector-enabled Postgres, (2) the
|
||||||
|
`litellm-pgvector` service, and (3) a dedicated embedding model server
|
||||||
|
registered in `litellm-config.yaml`. None of that reuses the Qdrant instance
|
||||||
|
already running in this stack.
|
||||||
|
|
||||||
|
## 5. Repo context read for this research
|
||||||
|
|
||||||
|
- `litellm-config.yaml` — current config has one `model_list` entry
|
||||||
|
(`qwen3.8-27b-local`, chat-only, via llama.cpp), `router_settings`
|
||||||
|
(priority scheduling), `general_settings.master_key`. No
|
||||||
|
`vector_store_registry` block exists yet.
|
||||||
|
- `docker-compose.yml` — confirms `qdrant` service (image `qdrant/qdrant`,
|
||||||
|
network `ai-stack`, no host port, only `open-webui` currently consumes it
|
||||||
|
via `VECTOR_DB=qdrant` / `QDRANT_URI=http://qdrant:6333`); confirms
|
||||||
|
`llama-server` command has no `--embeddings` flag and loads a single GGUF
|
||||||
|
(`Qwen3.8-27B-UD-Q4_K_XL.gguf`); confirms `litellm-db` is plain
|
||||||
|
`postgres:16-alpine` (no pgvector extension installed).
|
||||||
|
- `CLAUDE.md` — points to `docs/agents/issue-tracker.md` (Gitea issues via
|
||||||
|
`tea`) and `docs/agents/domain.md` (domain docs convention).
|
||||||
|
- `docs/agents/domain.md` — says to check `CONTEXT.md` and `docs/adr/` at
|
||||||
|
repo root before exploring, but "if any of these files don't exist,
|
||||||
|
proceed silently." Neither `CONTEXT.md` nor `docs/adr/` exist in this repo
|
||||||
|
yet, so there's no glossary/ADR conflict to flag.
|
||||||
|
- `docs/research/` — repeated existing precedent (e.g.
|
||||||
|
`docs/research/proxy-shadow-pricing.md`, `docs/research/voidllm-evaluation.md`,
|
||||||
|
`docs/research/qwen3.8-27b-tool-calling.md`) confirms this is the
|
||||||
|
established location and Markdown format for this kind of investigation;
|
||||||
|
this file follows that convention.
|
||||||
|
|
||||||
|
## Provenance note on the top-level config key name
|
||||||
|
|
||||||
|
The docs page fetched live (`docs.litellm.ai/docs/completion/knowledgebase`)
|
||||||
|
shows `vector_store_registry` as the current top-level key. The original
|
||||||
|
feature PR ([#10448](https://github.com/BerriAI/litellm/pull/10448)) used
|
||||||
|
`vector_stores` in its example YAML. If implementing against a specific
|
||||||
|
pinned LiteLLM version, verify the exact key against that version's docs/
|
||||||
|
source rather than assuming either name — this repo's `docker-compose.yml`
|
||||||
|
pins `ghcr.io/berriai/litellm:main-stable`, a rolling tag, so the schema in
|
||||||
|
whatever image is actually pulled should be spot-checked (e.g. `GET /openapi.json`
|
||||||
|
against the running proxy, or grepping the image's installed
|
||||||
|
`litellm/types/router.py` / proxy schema) before writing config against it.
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# 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
|
||||||
|
94–124; `networks:` block, lines 154–156)
|
||||||
|
|
||||||
|
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 144–152
|
||||||
|
|
||||||
|
**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 94–124), `ai-stack` network block (lines 154–156),
|
||||||
|
`lazytainer`'s `network_mode: host` precedent (lines 144–152).
|
||||||
|
- `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.
|
||||||
@@ -22,6 +22,44 @@ model_list:
|
|||||||
input_cost_per_token: 0.000002 # $2 / MTok
|
input_cost_per_token: 0.000002 # $2 / MTok
|
||||||
output_cost_per_token: 0.00001 # $10 / MTok
|
output_cost_per_token: 0.00001 # $10 / MTok
|
||||||
|
|
||||||
|
- model_name: local-embedding
|
||||||
|
litellm_params:
|
||||||
|
# Served by the dedicated embedding-server (nomic-embed-text-v1.5), not
|
||||||
|
# the chat model — see docker-compose.yml. Called by litellm-pgvector
|
||||||
|
# to embed knowledgebase content, and available directly at
|
||||||
|
# /v1/embeddings for anything else that wants it.
|
||||||
|
model: openai/local-embedding
|
||||||
|
api_base: http://embedding-server:8080/v1
|
||||||
|
api_key: local
|
||||||
|
model_info:
|
||||||
|
mode: embedding
|
||||||
|
|
||||||
|
# SearXNG-backed web search — a standalone REST endpoint (/v1/search/searxng-search),
|
||||||
|
# NOT a model-callable tool and not auto-injected into chat completions. See
|
||||||
|
# docs/research/litellm-searxng-search.md. Requires the litellm container to
|
||||||
|
# resolve search.home — see the `extra_hosts` entry in docker-compose.yml.
|
||||||
|
search_tools:
|
||||||
|
- search_tool_name: searxng-search
|
||||||
|
litellm_params:
|
||||||
|
search_provider: searxng
|
||||||
|
api_base: http://search.home/
|
||||||
|
|
||||||
|
# Knowledgebase / RAG, backed by the litellm-pgvector companion service (NOT
|
||||||
|
# Qdrant — LiteLLM's native vector-store feature has no Qdrant provider, see
|
||||||
|
# docs/research/litellm-knowledgebase.md). vector_store_id is this proxy's
|
||||||
|
# own identifier for the store, not assigned by a backend.
|
||||||
|
# ponytail: field names here (custom_llm_provider: pg_vector, api_base
|
||||||
|
# pointed at litellm-pgvector) are the best fit from the litellm-pgvector
|
||||||
|
# README, not confirmed against a running deploy yet — smoke-test before
|
||||||
|
# relying on it. See issue #24.
|
||||||
|
vector_store_registry:
|
||||||
|
- vector_store_name: memory-and-notes
|
||||||
|
litellm_params:
|
||||||
|
vector_store_id: "memory-and-notes"
|
||||||
|
custom_llm_provider: pg_vector
|
||||||
|
api_base: http://litellm-pgvector:8000
|
||||||
|
embedding_model: local-embedding
|
||||||
|
|
||||||
router_settings:
|
router_settings:
|
||||||
# ponytail: LiteLLM's request-prioritization scheduler is beta (see
|
# ponytail: LiteLLM's request-prioritization scheduler is beta (see
|
||||||
# docs/proxy-request-priority.md) — exact settings key/shape must be
|
# docs/proxy-request-priority.md) — exact settings key/shape must be
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Generates random values for the secrets docker-compose.yml requires
|
# Generates random values for the secrets docker-compose.yml requires
|
||||||
# (LITELLM_MASTER_KEY, LITELLM_SALT_KEY, LITELLM_DB_PASSWORD, UI_PASSWORD)
|
# (LITELLM_MASTER_KEY, LITELLM_SALT_KEY, LITELLM_DB_PASSWORD, UI_PASSWORD,
|
||||||
# and writes them into .env — creating it from .env.example first if it
|
# PGVECTOR_DB_PASSWORD, LITELLM_PGVECTOR_API_KEY) and writes them into .env —
|
||||||
# doesn't exist.
|
# creating it from .env.example first if it doesn't exist.
|
||||||
#
|
#
|
||||||
# ponytail: only fills in blank values, never overwrites ones you've already
|
# ponytail: only fills in blank values, never overwrites ones you've already
|
||||||
# set — safe to re-run. Re-running won't touch LITELLM_SALT_KEY once it's
|
# set — safe to re-run. Re-running won't touch LITELLM_SALT_KEY once it's
|
||||||
@@ -26,5 +26,7 @@ set_if_blank LITELLM_MASTER_KEY "$(openssl rand -hex 32)"
|
|||||||
set_if_blank LITELLM_SALT_KEY "$(openssl rand -hex 32)"
|
set_if_blank LITELLM_SALT_KEY "$(openssl rand -hex 32)"
|
||||||
set_if_blank LITELLM_DB_PASSWORD "$(openssl rand -hex 32)"
|
set_if_blank LITELLM_DB_PASSWORD "$(openssl rand -hex 32)"
|
||||||
set_if_blank UI_PASSWORD "$(openssl rand -hex 16)"
|
set_if_blank UI_PASSWORD "$(openssl rand -hex 16)"
|
||||||
|
set_if_blank PGVECTOR_DB_PASSWORD "$(openssl rand -hex 32)"
|
||||||
|
set_if_blank LITELLM_PGVECTOR_API_KEY "$(openssl rand -hex 32)"
|
||||||
|
|
||||||
echo "Done. Review .env, then set OPENWEBUI_LITELLM_KEY per docs/proxy-key-onboarding.md."
|
echo "Done. Review .env, then set OPENWEBUI_LITELLM_KEY, LITELLM_PGVECTOR_EMBEDDING_KEY, and SEARXNG_LAN_IP per docs/proxy-key-onboarding.md and docs/memory-knowledgebase.md."
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Loads data/memory.md and data/claude-legacy-memory.md into the LiteLLM
|
||||||
|
# knowledgebase (the "memory-and-notes" vector store, see litellm-config.yaml)
|
||||||
|
# via litellm-pgvector's batch-embeddings endpoint.
|
||||||
|
#
|
||||||
|
# ponytail: one chunk per non-empty, non-heading line — both source files are
|
||||||
|
# already one fact/paragraph per line (no hard-wrapping), so this needs no
|
||||||
|
# real chunking logic. Re-run after editing either file; there's no dedup, so
|
||||||
|
# this appends duplicates on a second run against unchanged content — clear
|
||||||
|
# the store first (DELETE the vector_store_id's rows) if you need a clean
|
||||||
|
# reload.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
[ -f .env ] && set -a && . ./.env && set +a
|
||||||
|
|
||||||
|
: "${LITELLM_PGVECTOR_API_KEY:?Set LITELLM_PGVECTOR_API_KEY in .env first}"
|
||||||
|
LITELLM_PGVECTOR_URL="${LITELLM_PGVECTOR_URL:-http://localhost:8000}"
|
||||||
|
VECTOR_STORE_ID="memory-and-notes"
|
||||||
|
|
||||||
|
# Must match litellm-config.yaml's vector_store_registry entry — the
|
||||||
|
# registry just points at a store the backend must already know about.
|
||||||
|
# Ignores failure if it already exists (no documented idempotency check).
|
||||||
|
curl -sf -X POST "${LITELLM_PGVECTOR_URL}/v1/vector_stores" \
|
||||||
|
-H "Authorization: Bearer ${LITELLM_PGVECTOR_API_KEY}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"name\": \"${VECTOR_STORE_ID}\"}" > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
ingest_file() {
|
||||||
|
local file="$1" section=""
|
||||||
|
local batch="[]"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
case "$line" in
|
||||||
|
"#"*) section="${line#\# }"; section="${section#\#\# }"; continue ;;
|
||||||
|
""|"---") continue ;;
|
||||||
|
esac
|
||||||
|
batch=$(jq --arg content "$line" --arg source "$file" --arg section "$section" \
|
||||||
|
'. += [{"content": $content, "metadata": {"source": $source, "section": $section}}]' <<<"$batch")
|
||||||
|
done < "$file"
|
||||||
|
|
||||||
|
echo "Ingesting $(jq 'length' <<<"$batch") chunks from $file..."
|
||||||
|
curl -sf -X POST "${LITELLM_PGVECTOR_URL}/v1/vector_stores/${VECTOR_STORE_ID}/embeddings/batch" \
|
||||||
|
-H "Authorization: Bearer ${LITELLM_PGVECTOR_API_KEY}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$batch" > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
ingest_file data/memory.md
|
||||||
|
ingest_file data/claude-legacy-memory.md
|
||||||
|
echo "Done."
|
||||||
Reference in New Issue
Block a user