Open WebUI + Paperless: AI chat nad tvojimi dokumentmi

Chceš sa pýtať AI na obsah svojich dokumentov? Python bridge prepojí Paperless-ngx s Open WebUI — rovnaký systém potom môžeš prepojiť aj s routerom, AdGuardom alebo inými homebabovými službami.

Paperless-AI sa postará o automatické tagovanie — ale čo ak chceš aktívne komunikovať so svojím archívom? Kedy vyprší záruka na práčku? Koľko som zaplatil za elektrinu minulý rok? Na tieto otázky ti odpovie Open WebUI prepojený cez Python bridge priamo s Paperless API.

A to nie je všetko — rovnaký Open WebUI inštanciu môžeš napojiť na router, AdGuard Home, Grafanu alebo akúkoľvek inú službu v homelabe, ktorá má API. Jeden chat interface, celý homelab pod kontrolou.

Open WebUI
AI chat
Claude, Ollama alebo iný LLM
Bridge
Python Flask
Prekladač medzi LLM a API
Paperless
Paperless API
Dokumenty, tagy, korešpondenti

Čo všetko môžeš integrovať

Open WebUI podporuje viacero nástrojov naraz. Ja mám napojené:

Paperless-ngx
Vyhľadávanie, tagovanie, kategorizácia, odpovede na otázky o dokumentoch.
Router / DNS
Stav siete, pripojené zariadenia, pravidlá cez API routera.
AdGuard Home
Štatistiky blokovania, pravidlá, whitelist/blacklist cez AdGuard API.

Princíp je rovnaký pre všetky integrácie — jednoduchý Flask server, ktorý prekladá požiadavky od LLM na API volania príslušnej služby.

Krok 1 — Paperless API token

Prihlás sa do Paperless-ngx a klikni vpravo hore na svoje meno → My Profile → Auth Token. Token skopíruj — budeš ho potrebovať pri konfigurácii bridge.

Krok 2 — Štruktúra priečinkov

mkdir -p /volume1/docker/openwebui/bridges/paperless

Krok 3 — Paperless Bridge (Python Flask)

Bridge je Flask server — prijíma požiadavky od Open WebUI a preposiela ich do Paperless API. Vytvor súbor paperless_bridge.py v priečinku /volume1/docker/openwebui/bridges/paperless/:

import os, requests
from flask import Flask, jsonify, request

app = Flask(__name__)
PL_URL = os.environ.get("PAPERLESS_URL", "https://paperless.tvoja-domena.xyz")
PL_TOKEN = os.environ.get("PAPERLESS_TOKEN", "")

SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Token {PL_TOKEN}", "Content-Type": "application/json"})

def pl_get(path, params=None):
    try:
        r = SESSION.get(f"{PL_URL}/api/{path}", params=params, timeout=15)
        r.raise_for_status()
        return r.json(), 200
    except Exception as e:
        return {"error": str(e)}, 500

def pl_patch(path, data):
    try:
        r = SESSION.patch(f"{PL_URL}/api/{path}", json=data, timeout=15)
        r.raise_for_status()
        return r.json(), 200
    except Exception as e:
        return {"error": str(e)}, 500

def pl_post(path, json_data=None):
    try:
        r = SESSION.post(f"{PL_URL}/api/{path}", json=json_data, timeout=15)
        r.raise_for_status()
        try: return r.json(), 200
        except: return {"success": True}, 200
    except Exception as e:
        return {"error": str(e)}, 500

@app.route("/health")
def health():
    data, code = pl_get("documents/?page_size=1")
    if code != 200: return jsonify({"status": "error", "detail": data}), 500
    return jsonify({"status": "ok", "service": "paperless-bridge"})

@app.route("/stats")
def stats():
    docs, _ = pl_get("documents/?page_size=1")
    tags, _ = pl_get("tags/?page_size=1")
    corr, _ = pl_get("correspondents/?page_size=1")
    types, _ = pl_get("document_types/?page_size=1")
    return jsonify({"documents": docs.get("count",0), "tags": tags.get("count",0),
                    "correspondents": corr.get("count",0), "document_types": types.get("count",0)})

@app.route("/documents")
def documents():
    params = {"page": request.args.get("page",1), "page_size": request.args.get("page_size",25)}
    query = request.args.get("query","")
    if query: params["query"] = query
    if request.args.get("tag_id"): params["tags__id__all"] = request.args.get("tag_id")
    params["ordering"] = request.args.get("ordering","-created")
    data, code = pl_get("documents/", params)
    return jsonify(data), code

@app.route("/documents/untagged")
def untagged():
    return jsonify(*pl_get("documents/", {"page_size": 50, "is_tagged": 0}))

@app.route("/documents/recent")
def recent():
    return jsonify(*pl_get("documents/", {"page_size": request.args.get("limit",20), "ordering": "-added"}))

@app.route("/documents/<int:doc_id>")
def document_detail(doc_id):
    return jsonify(*pl_get(f"documents/{doc_id}/"))

@app.route("/documents/<int:doc_id>/suggest")
def suggest(doc_id):
    doc, code = pl_get(f"documents/{doc_id}/")
    if code != 200: return jsonify(doc), code
    tags_data, _ = pl_get("tags/?page_size=200")
    corr_data, _ = pl_get("correspondents/?page_size=200")
    types_data, _ = pl_get("document_types/?page_size=100")
    return jsonify({"document": {"id": doc.get("id"), "title": doc.get("title"),
        "content": doc.get("content","")[:3000], "current_tags": doc.get("tags",[]),
        "current_correspondent": doc.get("correspondent"), "created": doc.get("created")},
        "available_tags": tags_data.get("results",[]),
        "available_correspondents": corr_data.get("results",[]),
        "available_document_types": types_data.get("results",[])})

@app.route("/documents/<int:doc_id>/update", methods=["POST"])
def document_update(doc_id):
    body = request.get_json(force=True, silent=True) or {}
    return jsonify(*pl_patch(f"documents/{doc_id}/", body))

@app.route("/documents/bulk", methods=["POST"])
def documents_bulk():
    body = request.get_json(force=True, silent=True) or {}
    documents = body.get("documents",[])
    method = body.get("method","")
    parameters = body.get("parameters",{})
    if not documents or not method:
        return jsonify({"error": "Chýba documents alebo method"}), 400
    return jsonify(*pl_post("documents/bulk_edit/", json_data={"documents":documents,"method":method,**parameters}))

@app.route("/tags")
def tags():
    return jsonify(*pl_get("tags/?page_size=200"))

@app.route("/tags/create", methods=["POST"])
def tags_create():
    body = request.get_json(force=True, silent=True) or {}
    if not body.get("name"): return jsonify({"error": "Chýba name"}), 400
    return jsonify(*pl_post("tags/", json_data={"name": body["name"]}))

@app.route("/correspondents")
def correspondents():
    return jsonify(*pl_get("correspondents/?page_size=200"))

@app.route("/document_types")
def document_types():
    return jsonify(*pl_get("document_types/?page_size=100"))

if __name__ == "__main__":
    print(f"[Paperless Bridge] Start - {PL_URL}")
    app.run(host="0.0.0.0", port=5003, debug=False)

Krok 4 — Docker Compose

Vytvor docker-compose.yml v /volume1/docker/openwebui/:

version: "3.8"

services:

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    restart: unless-stopped
    network_mode: host
    volumes:
      - openwebui_data:/app/backend/data
    environment:
      - ANTHROPIC_API_KEY=<tvoj_claude_api_kluc>
      - WEBUI_SECRET_KEY=<nahodny_retazec_min_32_znakov>
      - WEBUI_AUTH=false

  paperless-bridge:
    image: python:3.11-slim
    container_name: paperless-bridge
    restart: unless-stopped
    network_mode: host
    environment:
      - PAPERLESS_URL=https://paperless.tvoja-domena.xyz
      - PAPERLESS_TOKEN=<tvoj_paperless_api_token>
    volumes:
      - /volume1/docker/openwebui/bridges/paperless:/app
    working_dir: /app
    command: >
      sh -c "pip install flask requests --quiet &&
             python paperless_bridge.py"

volumes:
  openwebui_data:
    driver: local
Prečo network_mode: host

Oba kontajnery bežia priamo na sieťovom rozhraní NASu. Bridge tak vie dosiahnuť Paperless cez jeho FQDN alebo lokálnu IP bez problémov. Ak máš Paperless za Cloudflare Tunnelom, FQDN je najelegantnejšie riešenie.

Krok 5 — Spustenie a overenie

cd /volume1/docker/openwebui
sudo docker compose up -d

# Over ze bridge bezi
curl http://localhost:5003/health
# {"service":"paperless-bridge","status":"ok"}

curl http://localhost:5003/stats
# {"documents":501,"tags":94,...}
⚠️ Bridge súbor musí byť na disku pred štartom

Kontajner paperless-bridge potrebuje paperless_bridge.py v namountovanom priečinku. Súbor vytvor pred spustením docker compose up, inak kontajner zlyhá.

Krok 6 — Open WebUI — pripojenie Claude API

Otvor http://<NAS_IP>:8080 v prehliadači.

  1. Vpravo hore → Settings → Admin → Connections
  2. Klikni + a pridaj: URL https://api.anthropic.com/v1, API Key, typ autentifikácie Bearer
  3. Ulož a v chate vyber model claude-haiku-4-5-20251001 alebo claude-sonnet-4-6
Ollama ako alternatíva

Nechceš cloudové API? Open WebUI funguje aj s lokálnym Ollama — stačí pridať Ollama URL v nastaveniach. Claude je však výrazne lepší pri používaní nástrojov ako väčšina lokálnych modelov.

Krok 7 — Vytvorenie Paperless nástroja v Open WebUI

  1. Workspace → Tools → + (New Tool)
  2. Vlož kód nástroja (nižšie)
  3. Ulož a v chate aktivuj cez ikonu + vedľa vstupného poľa
"""
title: Paperless Control
description: Správa dokumentov v Paperless-ngx
version: 1.0.0
"""

import requests

PL = "http://localhost:5003"

class Tools:
    def get_paperless_stats(self) -> str:
        """Vráti štatistiky Paperless — počet dokumentov, tagov, korešpondentov"""
        r = requests.get(f"{PL}/stats", timeout=15)
        return str(r.json())

    def get_paperless_documents(self, query: str = "", page: int = 1) -> str:
        """Vráti zoznam dokumentov, voliteľne filtrovaných podľa textu"""
        params = f"?page={page}&page_size=25"
        if query: params += f"&query={query}"
        r = requests.get(f"{PL}/documents{params}", timeout=15)
        return str(r.json())

    def get_paperless_untagged(self) -> str:
        """Vráti dokumenty bez tagov čakajúce na kategorizáciu"""
        r = requests.get(f"{PL}/documents/untagged", timeout=15)
        return str(r.json())

    def get_document_for_categorization(self, doc_id: int) -> str:
        """Načíta dokument s obsahom a dostupnými tagmi pre AI kategorizáciu"""
        r = requests.get(f"{PL}/documents/{doc_id}/suggest", timeout=15)
        return str(r.json())

    def update_paperless_document(self, doc_id: int, tags: list = None,
                                   correspondent: int = None,
                                   document_type: int = None,
                                   title: str = None) -> str:
        """Aktualizuje dokument — tagy, korešpondent, typ alebo názov"""
        body = {}
        if tags is not None: body["tags"] = tags
        if correspondent is not None: body["correspondent"] = correspondent
        if document_type is not None: body["document_type"] = document_type
        if title is not None: body["title"] = title
        r = requests.post(f"{PL}/documents/{doc_id}/update", json=body, timeout=15)
        return str(r.json())

    def get_paperless_tags(self) -> str:
        """Vráti všetky existujúce tagy"""
        r = requests.get(f"{PL}/tags", timeout=15)
        return str(r.json())

    def create_paperless_tag(self, name: str) -> str:
        """Vytvorí nový tag"""
        r = requests.post(f"{PL}/tags/create", json={"name": name}, timeout=15)
        return str(r.json())

    def bulk_edit_documents(self, document_ids: list, method: str,
                             parameters: dict = None) -> str:
        """Hromadná editácia — set_tags, modify_tags, set_correspondent, delete"""
        body = {"documents": document_ids, "method": method, "parameters": parameters or {}}
        r = requests.post(f"{PL}/documents/bulk", json=body, timeout=30)
        return str(r.json())

Test

Po aktivácii nástroja v chate vyskúšaj:

Ukáž štatistiky Paperless

Načítaj dokumenty bez tagov

Skategorizuj dokument č. 42 — načítaj obsah, navrhni tagy a počkaj na potvrdenie

Koľko dokumentov mám od roku 2024?
1+
Open WebUI inštancia, neobmedzene napojených služieb. Router, AdGuard, Paperless, Grafana — všetko v jednom chat rozhraní. Každá služba = jeden Python bridge súbor a jeden nástroj v Open WebUI.
⚠️ localhost vs LAN IP

Ak localhost:5003 v Tools kóde nefunguje, použi LAN IP NASu (napr. 192.168.1.101:5003). Závisí od toho, ako Docker mapuje porty v host mode na tvojom systéme.

Open WebUI + Paperless: KI-Chat über deinen Dokumenten

Willst du die KI nach dem Inhalt deiner Dokumente fragen? Python Bridge verbindet Paperless-ngx mit Open WebUI — dasselbe System kannst du auch mit Router, AdGuard oder anderen Homelab-Diensten verbinden.

Paperless-AI übernimmt das automatische Tagging — aber was, wenn du aktiv kommunizieren möchtest? Wann läuft die Garantie der Waschmaschine ab? Wie viel habe ich letztes Jahr für Strom bezahlt? Diese Fragen beantwortet Open WebUI, der über Python Bridge direkt mit der Paperless API verbunden ist.

Und das ist noch nicht alles — dieselbe Open WebUI-Instanz kann an Router, AdGuard Home, Grafana oder jeden anderen Homelab-Dienst mit API angebunden werden. Eine Chat-Oberfläche, der gesamte Homelab unter Kontrolle.

Open WebUI
KI-Chat
Claude, Ollama oder anderes LLM
Bridge
Python Flask
Übersetzer zwischen LLM und API
Paperless
Paperless API
Dokumente, Tags, Korrespondenten

Was du alles integrieren kannst

Open WebUI unterstützt mehrere Tools gleichzeitig. Ich habe folgendes angebunden:

Paperless-ngx
Suche, Tagging, Kategorisierung, Antworten auf Dokumentenfragen.
Router / DNS
Netzwerkstatus, verbundene Geräte, Regeln über Router-API.
AdGuard Home
Blockierstatistiken, Regeln, Whitelist/Blacklist über AdGuard API.

Das Prinzip ist bei allen Integrationen gleich — ein einfacher Flask-Server, der Anfragen vom LLM in API-Aufrufe des jeweiligen Dienstes übersetzt.

Schritt 1 — Paperless API-Token

Melde dich in Paperless-ngx an und klicke oben rechts auf deinen Namen → My Profile → Auth Token. Kopiere den Token — du brauchst ihn für die Bridge-Konfiguration.

Schritt 2 — Verzeichnisstruktur

mkdir -p /volume1/docker/openwebui/bridges/paperless

Schritt 3 — Paperless Bridge & Docker Compose

Erstelle paperless_bridge.py im Verzeichnis und starte den Stack. Der vollständige Code und die Compose-Datei sind identisch mit dem SK-Teil — sieh dir die SK-Version für den vollständigen Code an oder wechsle zur EN-Version.

Warum network_mode: host

Beide Container laufen direkt auf der Netzwerkschnittstelle des NAS. Die Bridge kann Paperless so über FQDN oder lokale IP ohne Probleme erreichen. Wenn Paperless hinter einem Cloudflare Tunnel liegt, ist FQDN die eleganteste Lösung.

Schritt 4 — Open WebUI einrichten

Öffne http://<NAS_IP>:8080 im Browser.

  1. Oben rechts → Settings → Admin → Connections
  2. Klicke + und füge hinzu: URL https://api.anthropic.com/v1, API-Schlüssel, Authentifizierungstyp Bearer
  3. Speichern und im Chat Modell claude-haiku-4-5-20251001 wählen

Schritt 5 — Tool in Open WebUI erstellen

Workspace → Tools → + (New Tool) — füge den Python-Code ein (siehe SK-Version für vollständigen Tool-Code), speichere und aktiviere ihn im Chat über das +-Symbol.

Test

Zeige Paperless-Statistiken

Lade Dokumente ohne Tags

Kategorisiere Dokument Nr. 42 — lade den Inhalt, schlage Tags vor und warte auf Bestätigung
⚠️ localhost vs LAN-IP

Wenn localhost:5003 im Tools-Code nicht funktioniert, verwende die LAN-IP des NAS (z.B. 192.168.1.101:5003). Dies hängt davon ab, wie Docker Ports im host-Modus auf deinem System mappt.

Open WebUI + Paperless: AI chat over your documents

Want to ask AI about your document contents? A Python bridge connects Paperless-ngx with Open WebUI — the same setup can be extended to your router, AdGuard or any other homelab service with an API.

Paperless-AI handles automatic tagging — but what if you want to actively communicate with your archive? When does my washing machine warranty expire? How much did I spend on electricity last year? Open WebUI connected via Python bridge to the Paperless API answers these questions directly.

And that's not all — the same Open WebUI instance can be connected to your router, AdGuard Home, Grafana or any other homelab service with an API. One chat interface, your entire homelab under control.

Open WebUI
AI chat
Claude, Ollama or other LLM
Bridge
Python Flask
Translator between LLM and API
Paperless
Paperless API
Documents, tags, correspondents

What you can integrate

Open WebUI supports multiple tools simultaneously. My current setup includes:

Paperless-ngx
Search, tagging, categorisation, answers about document contents.
Router / DNS
Network status, connected devices, rules via router API.
AdGuard Home
Blocking stats, rules, whitelist/blacklist via AdGuard API.

The principle is the same for all integrations — a simple Flask server that translates LLM requests into API calls for the respective service.

Step 1 — Paperless API token

Log into Paperless-ngx and click your name top right → My Profile → Auth Token. Copy the token — you'll need it for the bridge configuration.

Step 2 — Directory structure

mkdir -p /volume1/docker/openwebui/bridges/paperless

Step 3 — Paperless Bridge (Python Flask)

Create paperless_bridge.py in the bridges directory. The full code is identical to the SK section above — see the SK version for the complete bridge script.

Step 4 — Docker Compose

Create docker-compose.yml in /volume1/docker/openwebui/:

version: "3.8"

services:

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    restart: unless-stopped
    network_mode: host
    volumes:
      - openwebui_data:/app/backend/data
    environment:
      - ANTHROPIC_API_KEY=<your_claude_api_key>
      - WEBUI_SECRET_KEY=<random_string_min_32_chars>
      - WEBUI_AUTH=false

  paperless-bridge:
    image: python:3.11-slim
    container_name: paperless-bridge
    restart: unless-stopped
    network_mode: host
    environment:
      - PAPERLESS_URL=https://paperless.your-domain.xyz
      - PAPERLESS_TOKEN=<your_paperless_api_token>
    volumes:
      - /volume1/docker/openwebui/bridges/paperless:/app
    working_dir: /app
    command: >
      sh -c "pip install flask requests --quiet &&
             python paperless_bridge.py"

volumes:
  openwebui_data:
    driver: local

Step 5 — Start and verify

cd /volume1/docker/openwebui
sudo docker compose up -d

# Verify bridge is running
curl http://localhost:5003/health
# {"service":"paperless-bridge","status":"ok"}

curl http://localhost:5003/stats
# {"documents":501,"tags":94,...}

Step 6 — Connect Claude API in Open WebUI

Open http://<NAS_IP>:8080 in your browser.

  1. Top right → Settings → Admin → Connections
  2. Click + and add: URL https://api.anthropic.com/v1, API Key, auth type Bearer
  3. Save and select model claude-haiku-4-5-20251001 or claude-sonnet-4-6 in chat
Ollama as alternative

No cloud API? Open WebUI works with local Ollama too — just add the Ollama URL in settings. Claude is significantly better at tool use than most local models however.

Step 7 — Create Paperless tool in Open WebUI

Workspace → Tools → + (New Tool) — paste the tool code from the SK section, save and activate in chat via the + icon next to the input field.

Test

Show Paperless statistics

Load documents without tags

Categorise document #42 — load the content, suggest tags and wait for confirmation

How many documents do I have from 2024?
⚠️ localhost vs LAN IP

If localhost:5003 in the Tools code doesn't work, use the NAS LAN IP (e.g. 192.168.1.101:5003). This depends on how Docker maps ports in host mode on your system.