# [[How to set up local LLMs]]
![[How to set up local LLMs.svg]]
This guide sets up the following to run on your local machine:
- [[Ollama]] (runs local LLMs)
- 3 models:
- [[Qwen|qwen3:8b]] (general)
- [[Qwen|qwen2.5-coder:7b]] (coding)
- [[Llama|llama3.2:3b]] (fast + classifier)
- A router API (Python + FastAPI)
- [[Open WebUI]] (chat UI)
---
## Prerequisites
- Install [[Docker Desktop]] and make sure it is running
---
## Set up environment
### Create project folder
From your terminal
```bash
mkdir -p ~/local-ai-compose
cd ~/local-ai-compose
```
### Create requirements.txt
```bash
cat > requirements.txt <<'EOF'
fastapi==0.115.12
uvicorn[standard]==0.34.0
requests==2.32.3
EOF
```
### Create Dockerfile.router
```bash
cat > Dockerfile.router <<'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY router_server.py .
EXPOSE 8000
CMD ["uvicorn", "router_server:app", "--host", "0.0.0.0", "--port", "8000"]
EOF
```
### Create `router_server.py`
```bash
cat > router_server.py <<'EOF'
import json
import os
import requests
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://ollama:11434/api/chat")
MODEL_MAP = {
"general": "qwen3:8b",
"code": "qwen2.5-coder:7b",
"fast": "llama3.2:3b",
}
SYSTEM_PROMPTS = {
"general": "You are a helpful assistant.",
"code": "You are a senior software engineer. Write correct, runnable code and explain briefly.",
"fast": "Summarize or extract information concisely. Be brief and structured.",
}
CLASSIFIER_MODEL = "llama3.2:3b"
app = FastAPI()
class Message(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
model: Optional[str] = None
messages: List[Message]
temperature: Optional[float] = None
stream: Optional[bool] = False
def ollama_chat(model: str, messages: list[dict], format_schema=None) -> str:
payload = {
"model": model,
"messages": messages,
"stream": False,
}
if format_schema is not None:
payload["format"] = format_schema
response = requests.post(OLLAMA_URL, json=payload, timeout=300)
response.raise_for_status()
data = response.json()
return data["message"]["content"]
def classify(prompt: str) -> str:
lowered = prompt.lower()
obvious_code_hints = [
"python", "javascript", "typescript", "bash", "shell",
"stack trace", "traceback", "refactor", "debug",
"write code", "fix this code", "sql", "api",
"function", "class", "script", "regex"
]
if "```" in prompt or any(hint in lowered for hint in obvious_code_hints):
return "code"
schema = {
"type": "object",
"properties": {
"route": {
"type": "string",
"enum": ["general", "code", "fast"]
},
"reason": {"type": "string"}
},
"required": ["route", "reason"]
}
system = """Classify the user's request into exactly one of:
- general
- code
- fast
Rules:
- code: programming, debugging, scripts, APIs, config files
- fast: summarization, extraction, classification, short rewriting
- general: everything else
Return only valid JSON."""
try:
raw = ollama_chat(
CLASSIFIER_MODEL,
[
{"role": "system", "content": system},
{"role": "user", "content": prompt},
],
format_schema=schema,
)
parsed = json.loads(raw)
route = parsed.get("route", "general")
if route not in MODEL_MAP:
return "general"
return route
except Exception:
return "general"
def ask(prompt: str) -> dict:
route = classify(prompt)
model = MODEL_MAP[route]
content = ollama_chat(
model,
[
{"role": "system", "content": SYSTEM_PROMPTS[route]},
{"role": "user", "content": prompt},
],
)
return {
"route": route,
"model": model,
"content": content,
}
@app.get("/v1/models")
def list_models():
return {
"object": "list",
"data": [
{
"id": "auto-router",
"object": "model",
"owned_by": "local",
}
],
}
@app.post("/v1/chat/completions")
def chat(req: ChatRequest):
user_messages = [m for m in req.messages if m.role == "user"]
prompt = user_messages[-1].content if user_messages else req.messages[-1].content
result = ask(prompt)
return {
"id": "chatcmpl-local-router",
"object": "chat.completion",
"created": 0,
"model": "auto-router",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": f"[{result['route']} -> {result['model']}]\n\n{result['content']}"
},
"finish_reason": "stop",
}
],
}
EOF
```
### Create `docker-compose.yml`
```bash
cat > docker-compose.yml <<'EOF'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama-data:/root/.ollama
router:
build:
context: .
dockerfile: Dockerfile.router
container_name: local-router
depends_on:
- ollama
environment:
OLLAMA_URL: http://ollama:11434/api/chat
ports:
- "8000:8000"
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
depends_on:
- router
ports:
- "3001:8080"
volumes:
- open-webui-data:/app/backend/data
environment:
OPENAI_API_BASE_URL: http://router:8000/v1
OPENAI_API_KEY: anything
volumes:
ollama-data:
open-webui-data:
EOF
```
## Verify setup
### Start everything
```bash
docker compose up --build -d
```
### Check containers
```bash
docker compose ps
```
You should see:
- ollama
- local-router
- open-webui
### Download models
```bash
docker exec -it ollama ollama pull qwen3:8b
```
```bash
docker exec -it ollama ollama pull qwen2.5-coder:7b
```
```bash
docker exec -it ollama ollama pull llama3.2:3b
```
### Test router
```bash
curl http://localhost:8000/v1/models
```
## Setting up Open WebUI
### Logging into Open WebUI
Go to:
```
http://localhost:3001
```
Create any account.
### Configure Open WebUI
From the UI:
1. Click profile (top right).
2. Go to **Connections**.
3. Click **Add Connection**.
4. Choose **OpenAI-compatible**.
Fill in:
- Name: `Local Router`
- Base URL: `http://host.docker.internal:8000/v1`
- API key: `anything`
Then hit Save.
### Using Open WebUI
Click on **New Chat** on the top left.
If it's not already selected for you, select `auto-router` as a model.
Try a prompt like:
```
Write a Python script to read a CSV file
```
If you're curious which model that prompt was routed to, check the output of your router server. You should see something like this:
```bash
INFO: 127.0.0.1:55689 - "POST /v1/chat/completions HTTP/1.1" 200 OK
[classifier] route=general reason=not categorized under specific area
[router] route=general model=qwen3:8b
INFO: 127.0.0.1:55718 - "POST /v1/chat/completions HTTP/1.1" 200 OK
[router] route=code model=qwen2.5-coder:7b
INFO: 127.0.0.1:55774 - "POST /v1/chat/completions HTTP/1.1" 200 OK
[router] route=code model=qwen2.5-coder:7b
INFO: 127.0.0.1:55814 - "POST /v1/chat/completions HTTP/1.1" 200 OK
[router] route=code model=qwen2.5-coder:7b
INFO: 127.0.0.1:55828 - "POST /v1/chat/completions HTTP/1.1" 200 OK
```
## Next time
### Start it
```bash
cd ~/local-ai-compose
docker compose up -d
```
### Stop it
```bash
docker compose down
```
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'
# Excalidraw Data
## Text Elements
Open WebUI ^jUmTMGvg
router.py ^PnGTfVEQ
Ollama ^uBchXPNa
qwen 3:8B ^psF2bqjv
qwen 2.5-coder:7b ^MCST8eop
llama 3.2:3b ^zTd8KrUT
localhost:3001 ^YYW8Htx2
localhost:8000/v1 ^S0OUWo6U
localhost:11434 ^qiN8jUB3
%%
## Drawing
```compressed-json
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=
```
%%