3:I[4707,[],""]
6:I[6423,[],""]
8:I[2972,["972","static/chunks/972-5c59758923e28d42.js","202","static/chunks/app/%5Blocale%5D/guides/%5Bslug%5D/page-b120b7c0b4d1d29b.js"],""]
4:["locale","de","d"]
5:["slug","rag-python-gdpr-document-search","d"]
0:["AMmUzAC5M1wE8hNEcLLHY",[[["",{"children":[["locale","de","d"],{"children":["guides",{"children":[["slug","rag-python-gdpr-document-search","d"],{"children":["__PAGE__?{\"locale\":\"de\",\"slug\":\"rag-python-gdpr-document-search\"}",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":[["locale","de","d"],{"children":["guides",{"children":[["slug","rag-python-gdpr-document-search","d"],{"children":["__PAGE__",{},[["$L1","$L2",null],null],null]},[null,["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","$4","children","guides","children","$5","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","$4","children","guides","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7a83e08d32162c8d.css","precedence":"next","crossOrigin":"$undefined"}]],"$L7"],null],null]},[[null,["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":["$","html",null,{"lang":"en","children":["$","body",null,{"className":"min-h-screen bg-background text-foreground flex items-center justify-center","children":["$","div",null,{"className":"text-center space-y-6 px-4","children":[["$","h1",null,{"className":"text-6xl font-bold text-primary","children":"404"}],["$","p",null,{"className":"text-xl text-muted-foreground","children":"Page not found"}],["$","$L8",null,{"href":"/en","className":"inline-block px-6 py-3 bg-primary text-primary-foreground rounded-lg font-semibold hover:bg-primary/90 transition-colors","children":"Go to Homepage"}]]}]}]}],"notFoundStyles":[]}]],null],null],["$L9",null]]]]
e:I[346,["972","static/chunks/972-5c59758923e28d42.js","203","static/chunks/app/%5Blocale%5D/layout-eaa8629840a87bdd.js"],"ThemeProvider"]
f:I[6932,["972","static/chunks/972-5c59758923e28d42.js","203","static/chunks/app/%5Blocale%5D/layout-eaa8629840a87bdd.js"],"LocaleSwitcher"]
10:I[9783,["972","static/chunks/972-5c59758923e28d42.js","203","static/chunks/app/%5Blocale%5D/layout-eaa8629840a87bdd.js"],"MobileMenu"]
11:I[8003,["972","static/chunks/972-5c59758923e28d42.js","203","static/chunks/app/%5Blocale%5D/layout-eaa8629840a87bdd.js"],""]
a:T44a,{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Can I use Qdrant with EU-hosted inference?","acceptedAnswer":{"@type":"Answer","text":"Yes. Qdrant runs on your own infrastructure (or Qdrant Cloud in EU regions). The inference call to JuiceFactory only receives the retrieved context — the vector database itself never leaves your environment."}},{"@type":"Question","name":"How does this differ from using OpenAI directly?","acceptedAnswer":{"@type":"Answer","text":"Two lines of code change: base_url and api_key. The SDK, request format, and response format are identical. The difference is where the data goes — EU infrastructure with zero retention instead of US servers with 30-day retention."}},{"@type":"Question","name":"What embedding model should I use for EU-compliant RAG?","acceptedAnswer":{"@type":"Answer","text":"JuiceFactory offers Qwen3-Embed (2560 dimensions) hosted in Stockholm. It outperforms most 1024-dim models on retrieval benchmarks and processes your documents statelessly — no training on your data, no retention."}}]}b:Tdaf,# ingest.py
import fitz  # PyMuPDF
from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import uuid
import config


def get_openai_client() -> OpenAI:
    """Create OpenAI client pointing to Juice Factory EU API."""
    return OpenAI(
        api_key=config.API_KEY,
        base_url=config.API_BASE_URL,
    )


def get_qdrant_client() -> QdrantClient:
    """Create Qdrant client."""
    return QdrantClient(host=config.QDRANT_HOST, port=config.QDRANT_PORT)


def extract_text_from_pdf(pdf_bytes: bytes) -> list[dict]:
    """Extract text from PDF, page by page."""
    doc = fitz.open(stream=pdf_bytes, filetype="pdf")
    pages = []
    for page_num, page in enumerate(doc):
        text = page.get_text("text").strip()
        if text:
            pages.append({
                "page": page_num + 1,
                "text": text,
            })
    doc.close()
    return pages


def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
    """Split text into overlapping chunks by word count."""
    words = text.split()
    chunks = []
    start = 0
    while start < len(words):
        end = start + chunk_size
        chunk = " ".join(words[start:end])
        chunks.append(chunk)
        start = end - overlap
    return chunks


def generate_embeddings(texts: list[str], client: OpenAI) -> list[list[float]]:
    """Generate embeddings using Juice Factory EU API."""
    response = client.embeddings.create(
        model=config.EMBEDDING_MODEL,
        input=texts,
    )
    return [item.embedding for item in response.data]


def ensure_collection(qdrant: QdrantClient):
    """Create Qdrant collection if it doesn't exist."""
    collections = [c.name for c in qdrant.get_collections().collections]
    if config.COLLECTION_NAME not in collections:
        qdrant.create_collection(
            collection_name=config.COLLECTION_NAME,
            vectors_config=VectorParams(
                size=config.EMBEDDING_DIMENSIONS,
                distance=Distance.COSINE,
            ),
        )


def ingest_pdf(pdf_bytes: bytes, filename: str) -> int:
    """Full ingestion pipeline: PDF → chunks → embeddings → Qdrant."""
    openai_client = get_openai_client()
    qdrant = get_qdrant_client()
    ensure_collection(qdrant)

    # Extract text from PDF
    pages = extract_text_from_pdf(pdf_bytes)

    # Chunk all pages
    all_chunks = []
    for page_data in pages:
        chunks = chunk_text(
            page_data["text"],
            chunk_size=config.CHUNK_SIZE,
            overlap=config.CHUNK_OVERLAP,
        )
        for chunk in chunks:
            all_chunks.append({
                "text": chunk,
                "page": page_data["page"],
                "filename": filename,
            })

    if not all_chunks:
        return 0

    # Generate embeddings (batch)
    texts = [c["text"] for c in all_chunks]
    embeddings = generate_embeddings(texts, openai_client)

    # Store in Qdrant
    points = [
        PointStruct(
            id=str(uuid.uuid4()),
            vector=embedding,
            payload={
                "text": chunk["text"],
                "page": chunk["page"],
                "filename": chunk["filename"],
            },
        )
        for chunk, embedding in zip(all_chunks, embeddings)
    ]

    qdrant.upsert(
        collection_name=config.COLLECTION_NAME,
        points=points,
    )

    return len(points)
c:Tb5a,# search.py
from openai import OpenAI
from qdrant_client import QdrantClient
import config
from ingest import get_openai_client, get_qdrant_client, generate_embeddings


def search_documents(query: str, top_k: int = None) -> list[dict]:
    """Search for relevant document chunks."""
    if top_k is None:
        top_k = config.TOP_K

    openai_client = get_openai_client()
    qdrant = get_qdrant_client()

    # Embed the query
    query_embedding = generate_embeddings([query], openai_client)[0]

    # Search Qdrant
    results = qdrant.search(
        collection_name=config.COLLECTION_NAME,
        query_vector=query_embedding,
        limit=top_k,
    )

    return [
        {
            "text": hit.payload["text"],
            "page": hit.payload["page"],
            "filename": hit.payload["filename"],
            "score": hit.score,
        }
        for hit in results
    ]


def rag_query(question: str) -> dict:
    """Full RAG pipeline: embed query → retrieve context → generate answer."""
    # Retrieve relevant chunks
    chunks = search_documents(question)

    if not chunks:
        return {
            "answer": "No relevant documents found. Please upload documents first.",
            "sources": [],
        }

    # Build context from retrieved chunks
    context_parts = []
    for i, chunk in enumerate(chunks, 1):
        context_parts.append(
            f"[Source {i}: {chunk['filename']}, page {chunk['page']}]\n{chunk['text']}"
        )
    context = "\n\n".join(context_parts)

    # Generate answer using EU-hosted LLM
    openai_client = get_openai_client()
    response = openai_client.chat.completions.create(
        model=config.CHAT_MODEL,
        messages=[
            {
                "role": "system",
                "content": (
                    "You are a document assistant. Answer questions based on the "
                    "provided context. Always cite which source and page number your "
                    "answer comes from. If the context doesn't contain enough "
                    "information to answer, say so clearly."
                ),
            },
            {
                "role": "user",
                "content": f"Context:\n{context}\n\nQuestion: {question}",
            },
        ],
        temperature=0.1,
        max_tokens=1000,
    )

    return {
        "answer": response.choices[0].message.content,
        "sources": [
            {
                "filename": c["filename"],
                "page": c["page"],
                "score": round(c["score"], 4),
                "excerpt": c["text"][:200] + "..." if len(c["text"]) > 200 else c["text"],
            }
            for c in chunks
        ],
        "model": response.model,
        "usage": {
            "prompt_tokens": response.usage.prompt_tokens,
            "completion_tokens": response.usage.completion_tokens,
        },
    }
d:T742,# main.py
from fastapi import FastAPI, UploadFile, File, HTTPException
from pydantic import BaseModel
from ingest import ingest_pdf
from search import rag_query, search_documents

app = FastAPI(
    title="GDPR-Safe Document Search API",
    description="RAG-powered document search with EU-hosted inference",
    version="1.0.0",
)


class QueryRequest(BaseModel):
    question: str
    top_k: int = 5


class QueryResponse(BaseModel):
    answer: str
    sources: list[dict]
    model: str | None = None
    usage: dict | None = None


@app.post("/upload")
async def upload_document(file: UploadFile = File(...)):
    """Upload a PDF document for indexing."""
    if not file.filename.lower().endswith(".pdf"):
        raise HTTPException(status_code=400, detail="Only PDF files are supported")

    pdf_bytes = await file.read()
    if len(pdf_bytes) > 50 * 1024 * 1024:  # 50MB limit
        raise HTTPException(status_code=400, detail="File too large (max 50MB)")

    num_chunks = ingest_pdf(pdf_bytes, file.filename)

    return {
        "filename": file.filename,
        "chunks_indexed": num_chunks,
        "status": "indexed",
    }


@app.post("/query", response_model=QueryResponse)
async def query_documents(request: QueryRequest):
    """Ask a question about uploaded documents."""
    if not request.question.strip():
        raise HTTPException(status_code=400, detail="Question cannot be empty")

    result = rag_query(request.question)
    return QueryResponse(**result)


@app.post("/search")
async def search_only(request: QueryRequest):
    """Search for relevant chunks without generating an answer."""
    results = search_documents(request.question, top_k=request.top_k)
    return {"results": results}


@app.get("/health")
async def health():
    """Health check endpoint."""
    return {"status": "ok", "data_residency": "EU"}
2:[["$","script",null,{"type":"application/ld+json","dangerouslySetInnerHTML":{"__html":"{\"@context\":\"https://schema.org\",\"@type\":\"BreadcrumbList\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https://juicefactory.ai/de/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Guides\",\"item\":\"https://juicefactory.ai/de/guides\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"RAG in Python: GDPR-Safe Document Search\",\"item\":\"https://juicefactory.ai/de/guides/rag-python-gdpr-document-search\"}]}"}}],["$","script",null,{"type":"application/ld+json","dangerouslySetInnerHTML":{"__html":"$a"}}],null,["$","script",null,{"type":"application/ld+json","dangerouslySetInnerHTML":{"__html":"{\"@context\":\"https://schema.org\",\"@type\":\"TechArticle\",\"headline\":\"RAG in Python: DSGVO-konforme Dokumentensuche-API erstellen (2026)\",\"description\":\"Erstellen Sie ein produktionsreifes RAG-System in Python mit FastAPI, Qdrant und EU-gehosteter Inferenz. DSGVO-konforme Dokumentensuche mit PyMuPDF und privatem LLM.\",\"author\":{\"@type\":\"Organization\",\"name\":\"Juice Factory\",\"url\":\"https://juicefactory.ai\"},\"publisher\":{\"@type\":\"Organization\",\"name\":\"Juice Factory\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://juicefactory.ai/logo-opengraph.png\"}},\"url\":\"https://juicefactory.ai/de/guides/rag-python-gdpr-document-search\",\"datePublished\":\"2025-01-15\",\"dateModified\":\"2026-05-15\",\"inLanguage\":\"de\",\"mainEntityOfPage\":{\"@type\":\"WebPage\",\"@id\":\"https://juicefactory.ai/de/guides/rag-python-gdpr-document-search\"}}"}}],["$","section",null,{"className":"pt-32 pb-20 px-4","children":["$","div",null,{"className":"container mx-auto max-w-4xl","children":["$","article",null,{"className":"prose prose-neutral dark:prose-invert max-w-none","children":[["$","h1","h1-0",{"children":"RAG in Python: Eine DSGVO-konforme Dokumentensuche-API mit EU-gehostetem Inference bauen"}],"\n",["$","p","p-0",{"children":"Erstellen Sie ein produktionsreifes Retrieval-Augmented-Generation-System (RAG) in Python, das sämtliche Daten innerhalb der EU hält. Dieser Leitfaden behandelt die Dokumentenverarbeitung mit PyMuPDF, Vektorspeicherung mit Qdrant und LLM-Inference über die private EU-API von Juice Factory — alles eingebettet in einen FastAPI-Service."}],"\n",["$","p","p-1",{"children":"Am Ende dieses Guides haben Sie eine funktionierende Dokumentensuche-API, die:"}],"\n",["$","ul","ul-0",{"children":["\n",["$","li","li-0",{"children":"Text aus PDFs mittels PyMuPDF extrahiert"}],"\n",["$","li","li-1",{"children":"Embeddings erzeugt und in Qdrant speichert"}],"\n",["$","li","li-2",{"children":"Fragen mithilfe von abgerufenem Kontext und EU-gehostetem LLM-Inference beantwortet"}],"\n",["$","li","li-3",{"children":"Niemals Nutzerdaten aus der EU heraussendet"}],"\n"]}],"\n",["$","hr","hr-0",{}],"\n",["$","h2","h2-0",{"children":"Voraussetzungen"}],"\n",["$","ul","ul-1",{"children":["\n",["$","li","li-0",{"children":"Python 3.10+"}],"\n",["$","li","li-1",{"children":"Docker (für Qdrant)"}],"\n",["$","li","li-2",{"children":["Ein Juice Factory API-Key (",["$","a","a-0",{"href":"https://portal.juicefactory.ai","children":"hier beantragen"}],")"]}],"\n"]}],"\n",["$","hr","hr-1",{}],"\n",["$","h2","h2-1",{"children":"Architekturüberblick"}],"\n",["$","pre","pre-0",{"children":["$","code","code-0",{"children":"┌──────────────┐     ┌───────────────┐     ┌──────────────────┐\n│  PDF Upload  │────▶│  PyMuPDF      │────▶│  Qdrant          │\n│  (FastAPI)   │     │  Text Extract │     │  Vector Store    │\n└──────────────┘     └───────────────┘     └──────────────────┘\n                                                    │\n┌──────────────┐     ┌───────────────┐              │\n│  User Query  │────▶│  Embedding    │──── search ──┘\n│  (FastAPI)   │     │  (EU API)     │\n└──────────────┘     └───────┬───────┘\n                             │\n                     ┌───────▼───────┐     ┌──────────────────┐\n                     │  Context +    │────▶│  LLM Inference   │\n                     │  Query        │     │  (EU-hosted)     │\n                     └───────────────┘     └──────────────────┘\n"}]}],"\n",["$","p","p-2",{"children":"Das System folgt einer Standard-RAG-Pipeline, allerdings läuft jede Komponente, die mit Nutzerdaten in Berührung kommt, innerhalb der EU-Infrastruktur. Qdrant wird selbst gehostet, und sowohl Embeddings als auch LLM-Inference werden über die EU-Endpunkte von Juice Factory geroutet."}],"\n",["$","hr","hr-2",{}],"\n",["$","h2","h2-2",{"children":"Schritt 1: Projektaufbau"}],"\n",["$","p","p-3",{"children":"Erstellen Sie das Projektverzeichnis und installieren Sie die Abhängigkeiten:"}],"\n",["$","pre","pre-1",{"children":["$","code","code-0",{"className":"language-bash","children":"mkdir rag-document-search && cd rag-document-search\npython -m venv .venv\nsource .venv/bin/activate\n"}]}],"\n",["$","p","p-4",{"children":"Installieren Sie die benötigten Pakete:"}],"\n",["$","pre","pre-2",{"children":["$","code","code-0",{"className":"language-bash","children":"pip install fastapi uvicorn pymupdf qdrant-client openai python-multipart\n"}]}],"\n",["$","p","p-5",{"children":"Erstellen Sie die Projektstruktur:"}],"\n",["$","pre","pre-3",{"children":["$","code","code-0",{"children":"rag-document-search/\n├── main.py              # FastAPI application\n├── ingest.py            # Document ingestion pipeline\n├── search.py            # Query and retrieval logic\n├── config.py            # Configuration\n└── requirements.txt\n"}]}],"\n",["$","p","p-6",{"children":[["$","strong","strong-0",{"children":"requirements.txt"}],":"]}],"\n",["$","pre","pre-4",{"children":["$","code","code-0",{"children":"fastapi==0.115.0\nuvicorn==0.30.0\npymupdf==1.24.0\nqdrant-client==1.11.0\nopenai==1.50.0\npython-multipart==0.0.9\n"}]}],"\n",["$","hr","hr-3",{}],"\n",["$","h2","h2-3",{"children":"Schritt 2: Konfiguration"}],"\n",["$","p","p-7",{"children":"Richten Sie die Konfiguration mit Ihren Juice-Factory-API-Zugangsdaten ein:"}],"\n",["$","pre","pre-5",{"children":["$","code","code-0",{"className":"language-python","children":"# config.py\nimport os\n\n# Juice Factory EU API (OpenAI-compatible)\nAPI_BASE_URL = \"https://api.juicefactory.ai/v1\"\nAPI_KEY = os.environ.get(\"JUICEFACTORY_API_KEY\", \"your-api-key\")\n\n# Embedding model\nEMBEDDING_MODEL = \"text-embedding-3-small\"\nEMBEDDING_DIMENSIONS = 1536\n\n# Chat model for RAG responses\nCHAT_MODEL = \"gpt-4\"\n\n# Qdrant configuration (self-hosted in EU)\nQDRANT_HOST = os.environ.get(\"QDRANT_HOST\", \"localhost\")\nQDRANT_PORT = int(os.environ.get(\"QDRANT_PORT\", \"6333\"))\nCOLLECTION_NAME = \"documents\"\n\n# Chunk settings\nCHUNK_SIZE = 500       # tokens per chunk (approximate)\nCHUNK_OVERLAP = 50     # overlap between chunks\nTOP_K = 5              # number of chunks to retrieve\n"}]}],"\n",["$","hr","hr-4",{}],"\n",["$","h2","h2-4",{"children":"Schritt 3: Qdrant mit Docker starten"}],"\n",["$","p","p-8",{"children":"Starten Sie Qdrant lokal (oder auf Ihrem EU-Server):"}],"\n",["$","pre","pre-6",{"children":["$","code","code-0",{"className":"language-bash","children":"docker run -d \\\n  --name qdrant \\\n  -p 6333:6333 \\\n  -p 6334:6334 \\\n  -v qdrant_storage:/qdrant/storage \\\n  qdrant/qdrant:latest\n"}]}],"\n",["$","p","p-9",{"children":"Qdrant speichert alle Daten lokal — keine externen Aufrufe, keine Telemetrie, volle Kontrolle über den Speicherort der Daten."}],"\n",["$","hr","hr-5",{}],"\n",["$","h2","h2-5",{"children":"Schritt 4: Dokumentenverarbeitung mit PyMuPDF"}],"\n",["$","p","p-10",{"children":"Die Verarbeitungspipeline extrahiert Text aus PDFs, teilt ihn in Chunks auf, erzeugt Embeddings über die EU-API und speichert alles in Qdrant."}],"\n",["$","pre","pre-7",{"children":["$","code","code-0",{"className":"language-python","children":"$b"}]}],"\n",["$","p","p-11",{"children":"Wichtige Punkte:"}],"\n",["$","ul","ul-2",{"children":["\n",["$","li","li-0",{"children":[["$","strong","strong-0",{"children":"PyMuPDF"}]," (",["$","code","code-0",{"children":"fitz"}],") extrahiert Text ohne externe Abhängigkeiten oder Cloud-Aufrufe"]}],"\n",["$","li","li-1",{"children":[["$","strong","strong-0",{"children":"Embeddings"}]," werden über die EU-API von Juice Factory erzeugt — dasselbe OpenAI SDK, EU-Endpunkt"]}],"\n",["$","li","li-2",{"children":[["$","strong","strong-0",{"children":"Qdrant"}]," speichert Vektoren lokal und ohne Telemetrie"]}],"\n"]}],"\n",["$","hr","hr-6",{}],"\n",["$","h2","h2-6",{"children":"Schritt 5: Suche und RAG-Abfrage"}],"\n",["$","p","p-12",{"children":"Das Suchmodul bettet die Benutzeranfrage als Embedding ein, ruft relevante Chunks ab und sendet diese zusammen mit der Frage an das LLM."}],"\n",["$","pre","pre-8",{"children":["$","code","code-0",{"className":"language-python","children":"$c"}]}],"\n",["$","p","p-13",{"children":["Die Funktion ",["$","code","code-0",{"children":"rag_query"}]," bildet das Herzstück des Systems:"]}],"\n",["$","ol","ol-0",{"children":["\n",["$","li","li-0",{"children":"Wandelt die Benutzerfrage über die EU-API in ein Embedding um"}],"\n",["$","li","li-1",{"children":"Ruft die Top-K relevantesten Chunks aus Qdrant ab"}],"\n",["$","li","li-2",{"children":"Sendet Kontext und Frage an das EU-gehostete LLM"}],"\n",["$","li","li-3",{"children":"Gibt die Antwort mit Quellenangaben zurück"}],"\n"]}],"\n",["$","hr","hr-7",{}],"\n",["$","h2","h2-7",{"children":"Schritt 6: FastAPI-Anwendung"}],"\n",["$","p","p-14",{"children":"Verbinden Sie alles zu einem FastAPI-Service:"}],"\n",["$","pre","pre-9",{"children":["$","code","code-0",{"className":"language-python","children":"$d"}]}],"\n",["$","hr","hr-8",{}],"\n",["$","h2","h2-8",{"children":"Schritt 7: Starten und Testen"}],"\n",["$","p","p-15",{"children":"Starten Sie den API-Server:"}],"\n",["$","pre","pre-10",{"children":["$","code","code-0",{"className":"language-bash","children":"export JUICEFACTORY_API_KEY=\"your-api-key\"\nuvicorn main:app --host 0.0.0.0 --port 8000 --reload\n"}]}],"\n",["$","h3","h3-0",{"children":"Dokument hochladen"}],"\n",["$","pre","pre-11",{"children":["$","code","code-0",{"className":"language-bash","children":"curl -X POST http://localhost:8000/upload \\\n  -F \"file=@contract.pdf\"\n"}]}],"\n",["$","p","p-16",{"children":"Antwort:"}],"\n",["$","pre","pre-12",{"children":["$","code","code-0",{"className":"language-json","children":"{\n  \"filename\": \"contract.pdf\",\n  \"chunks_indexed\": 47,\n  \"status\": \"indexed\"\n}\n"}]}],"\n",["$","h3","h3-1",{"children":"Eine Frage stellen"}],"\n",["$","pre","pre-13",{"children":["$","code","code-0",{"className":"language-bash","children":"curl -X POST http://localhost:8000/query \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"question\": \"What are the payment terms in the contract?\"}'\n"}]}],"\n",["$","p","p-17",{"children":"Antwort:"}],"\n",["$","pre","pre-14",{"children":["$","code","code-0",{"className":"language-json","children":"{\n  \"answer\": \"According to the contract (Source 1, page 4), payment terms are Net 30 from the date of invoice. Late payments accrue interest at 1.5% per month as specified in Section 5.2.\",\n  \"sources\": [\n    {\n      \"filename\": \"contract.pdf\",\n      \"page\": 4,\n      \"score\": 0.9234,\n      \"excerpt\": \"Payment Terms. The Client shall pay all invoices within thirty (30) days...\"\n    }\n  ],\n  \"model\": \"gpt-4-0125-preview\",\n  \"usage\": {\n    \"prompt_tokens\": 847,\n    \"completion_tokens\": 89\n  }\n}\n"}]}],"\n",["$","hr","hr-9",{}],"\n",["$","h2","h2-9",{"children":"Checkliste zur DSGVO-Konformität"}],"\n",["$","p","p-18",{"children":"Diese Architektur erfüllt die DSGVO-Anforderungen auf jeder Ebene:"}],"\n",["$","table","table-0",{"children":[["$","thead","thead-0",{"children":["$","tr","tr-0",{"children":[["$","th","th-0",{"children":"Komponente"}],["$","th","th-1",{"children":"Datenverarbeitung"}],["$","th","th-2",{"children":"DSGVO-Konformität"}]]}]}],["$","tbody","tbody-0",{"children":[["$","tr","tr-0",{"children":[["$","td","td-0",{"children":["$","strong","strong-0",{"children":"PDF-Upload"}]}],["$","td","td-1",{"children":"Dateien werden im Arbeitsspeicher verarbeitet, Text wird lokal extrahiert"}],["$","td","td-2",{"children":"Kein externer Datentransfer"}]]}],["$","tr","tr-1",{"children":[["$","td","td-0",{"children":["$","strong","strong-0",{"children":"Embeddings"}]}],["$","td","td-1",{"children":"Erzeugt über die EU-API von Juice Factory"}],["$","td","td-2",{"children":"EU-Datenresidenz, keine Datenspeicherung"}]]}],["$","tr","tr-2",{"children":[["$","td","td-0",{"children":["$","strong","strong-0",{"children":"Vektorspeicher"}]}],["$","td","td-1",{"children":"Selbst gehostetes Qdrant, EU-Infrastruktur"}],["$","td","td-2",{"children":"Volle Kontrolle über den Speicherort"}]]}],["$","tr","tr-3",{"children":[["$","td","td-0",{"children":["$","strong","strong-0",{"children":"LLM-Inference"}]}],["$","td","td-1",{"children":"Juice Factory EU-API, zustandslose Verarbeitung"}],["$","td","td-2",{"children":"Keine Speicherung von Anfragen, kein Training"}]]}],["$","tr","tr-4",{"children":[["$","td","td-0",{"children":["$","strong","strong-0",{"children":"API-Server"}]}],["$","td","td-1",{"children":"Ihre Infrastruktur, Ihre Logging-Richtlinie"}],["$","td","td-2",{"children":"Kontrolle auf Anwendungsebene"}]]}]]}]]}],"\n",["$","p","p-19",{"children":["$","strong","strong-0",{"children":"Wesentliche Garantien:"}]}],"\n",["$","ul","ul-3",{"children":["\n",["$","li","li-0",{"children":"Benutzeranfragen verlassen niemals die EU"}],"\n",["$","li","li-1",{"children":"Keine Daten werden für Modelltraining verwendet"}],"\n",["$","li","li-2",{"children":"Qdrant speichert ausschließlich Embeddings (keine Rohanfragen)"}],"\n",["$","li","li-3",{"children":"LLM-Inference ist zustandslos — Anfragen werden nicht aufbewahrt"}],"\n",["$","li","li-4",{"children":"Sie kontrollieren sämtliche Logging- und Datenaufbewahrungsrichtlinien"}],"\n"]}],"\n",["$","hr","hr-10",{}],"\n",["$","h2","h2-10",{"children":"Hinweise für den Produktivbetrieb"}],"\n",["$","h3","h3-2",{"children":"Qdrant skalieren"}],"\n",["$","p","p-20",{"children":"Für Produktivumgebungen mit großen Dokumentensammlungen:"}],"\n",["$","pre","pre-15",{"children":["$","code","code-0",{"className":"language-bash","children":"# Run Qdrant with persistent storage and resource limits\ndocker run -d \\\n  --name qdrant \\\n  -p 6333:6333 \\\n  --memory=4g \\\n  -v /data/qdrant:/qdrant/storage \\\n  qdrant/qdrant:latest\n"}]}],"\n",["$","p","p-21",{"children":"Bei Sammlungen mit mehr als 10 Millionen Vektoren empfiehlt sich der verteilte Modus von Qdrant mit Sharding über mehrere EU-gehostete Knoten."}],"\n",["$","h3","h3-3",{"children":"Chunking-Strategie"}],"\n",["$","p","p-22",{"children":"Das einfache wortbasierte Chunking in diesem Leitfaden funktioniert für die meisten Dokumente. Für bessere Ergebnisse bei strukturierten Dokumenten gibt es folgende Ansätze:"}],"\n",["$","ul","ul-4",{"children":["\n",["$","li","li-0",{"children":[["$","strong","strong-0",{"children":"Semantisches Chunking"}],": Aufteilung an Absatz- oder Abschnittsgrenzen"]}],"\n",["$","li","li-1",{"children":[["$","strong","strong-0",{"children":"Sliding Window"}],": Überlappende Chunks verwenden, um Kontextbrüche zu vermeiden"]}],"\n",["$","li","li-2",{"children":[["$","strong","strong-0",{"children":"Metadaten-Anreicherung"}],": Abschnittsüberschriften, Dokumenttitel und Datumsangaben in die Chunk-Metadaten aufnehmen"]}],"\n"]}],"\n",["$","h3","h3-4",{"children":"Fehlerbehandlung"}],"\n",["$","p","p-23",{"children":"Implementieren Sie Retry-Logik für API-Aufrufe und behandeln Sie Qdrant-Verbindungsfehler:"}],"\n",["$","pre","pre-16",{"children":["$","code","code-0",{"className":"language-python","children":"from tenacity import retry, stop_after_attempt, wait_exponential\n\n@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10))\ndef generate_embeddings_with_retry(texts, client):\n    return generate_embeddings(texts, client)\n"}]}],"\n",["$","h3","h3-5",{"children":"Authentifizierung"}],"\n",["$","p","p-24",{"children":"Fügen Sie für den Produktivbetrieb eine API-Key-Authentifizierung zu Ihren FastAPI-Endpunkten hinzu:"}],"\n",["$","pre","pre-17",{"children":["$","code","code-0",{"className":"language-python","children":"from fastapi import Depends, Security\nfrom fastapi.security import APIKeyHeader\n\napi_key_header = APIKeyHeader(name=\"X-API-Key\")\n\nasync def verify_api_key(api_key: str = Security(api_key_header)):\n    if api_key != os.environ.get(\"APP_API_KEY\"):\n        raise HTTPException(status_code=403, detail=\"Invalid API key\")\n    return api_key\n\n@app.post(\"/query\", dependencies=[Depends(verify_api_key)])\nasync def query_documents(request: QueryRequest):\n    ...\n"}]}],"\n",["$","hr","hr-11",{}],"\n",["$","h2","h2-11",{"children":"Zusammenfassung"}],"\n",["$","p","p-25",{"children":"Dieser Leitfaden zeigt eine vollständige RAG-Pipeline, die durchgängig DSGVO-konform arbeitet:"}],"\n",["$","ol","ol-1",{"children":["\n",["$","li","li-0",{"children":[["$","strong","strong-0",{"children":"Dokumentenverarbeitung"}],": PyMuPDF extrahiert Text lokal, ohne Cloud-Abhängigkeiten"]}],"\n",["$","li","li-1",{"children":[["$","strong","strong-0",{"children":"Embeddings"}],": Werden über die EU-API von Juice Factory erzeugt, ohne Datenspeicherung"]}],"\n",["$","li","li-2",{"children":[["$","strong","strong-0",{"children":"Vektorspeicher"}],": Selbst gehostetes Qdrant hält alle indexierten Daten unter Ihrer Kontrolle"]}],"\n",["$","li","li-3",{"children":[["$","strong","strong-0",{"children":"LLM-Inference"}],": EU-gehostet, zustandslose Verarbeitung ohne Speicherung von Anfragen"]}],"\n",["$","li","li-4",{"children":[["$","strong","strong-0",{"children":"API-Schicht"}],": FastAPI gibt Ihnen volle Kontrolle über Zugriff, Logging und Datenverarbeitung"]}],"\n"]}],"\n",["$","p","p-26",{"children":"Das gesamte System lässt sich auf EU-Infrastruktur betreiben, ohne dass Daten die Region verlassen. Die Umstellung von einem nicht-konformen Setup ist unkompliziert — ersetzen Sie die API-Base-URL, leiten Sie Embeddings über den EU-Endpunkt und hosten Sie Ihren Vektorspeicher selbst."}],"\n",["$","hr","hr-12",{}],"\n",["$","h2","h2-12",{"children":"Verwandte Guides"}],"\n",["$","ul","ul-5",{"children":["\n",["$","li","li-0",{"children":[["$","a","a-0",{"href":"/en/guides/gdpr-safe-ai-inference","children":"GDPR-Safe AI Inference"}]," — Architekturleitfaden für konforme KI-Anwendungen mit RAG"]}],"\n",["$","li","li-1",{"children":[["$","a","a-0",{"href":"/en/replace-openai","children":"Replacing OpenAI with EU Infrastructure"}]," — Migrationsleitfaden für den Wechsel des API-Anbieters"]}],"\n",["$","li","li-2",{"children":[["$","a","a-0",{"href":"/en/automation/n8n-private-ai","children":"n8n + Private AI Automation"}]," — Workflow-Automatisierung mit EU-gehostetem Inference"]}],"\n",["$","li","li-3",{"children":[["$","a","a-0",{"href":"/en/guides/cursor-byok-setup","children":"Cursor AI BYOK Setup"}]," — Juice Factory als BYOK-Anbieter in Cursor verwenden"]}],"\n"]}]]}]}]}],["$","section",null,{"className":"py-12 px-4","children":["$","div",null,{"className":"container mx-auto max-w-4xl","children":[["$","h2",null,{"className":"text-2xl font-bold mb-6 text-foreground","children":"Related Guides"}],["$","div",null,{"className":"grid gap-4 sm:grid-cols-2 lg:grid-cols-3","children":[["$","$L8","gdpr-safe-ai-inference",{"href":"/de/guides/gdpr-safe-ai-inference","className":"block rounded-lg border border-border bg-card p-5 transition-colors hover:bg-accent hover:text-accent-foreground","children":["$","h3",null,{"className":"text-sm font-semibold leading-snug text-foreground","children":"GDPR-Safe AI Inference"}]}],["$","$L8","gdpr-compliant-enterprise-rag",{"href":"/de/guides/gdpr-compliant-enterprise-rag","className":"block rounded-lg border border-border bg-card p-5 transition-colors hover:bg-accent hover:text-accent-foreground","children":["$","h3",null,{"className":"text-sm font-semibold leading-snug text-foreground","children":"GDPR-Compliant Enterprise RAG"}]}],["$","$L8","implementing-gdpr-compliant-ai",{"href":"/de/guides/implementing-gdpr-compliant-ai","className":"block rounded-lg border border-border bg-card p-5 transition-colors hover:bg-accent hover:text-accent-foreground","children":["$","h3",null,{"className":"text-sm font-semibold leading-snug text-foreground","children":"GDPR-Compliant AI Infrastructure"}]}]]}]]}]}],["$","section",null,{"className":"py-16 px-4","children":["$","div",null,{"className":"container mx-auto max-w-4xl","children":["$","div",null,{"className":"bg-gradient-to-br from-primary/5 to-secondary/5 backdrop-blur-sm border border-primary/30 rounded-lg p-12 space-y-6 text-center","children":[["$","h2",null,{"className":"text-3xl md:text-4xl font-bold","children":"Ship GDPR-Compliant AI Today"}],["$","p",null,{"className":"text-xl text-muted-foreground max-w-2xl mx-auto","children":"Zero-retention inference in Stockholm. DPA included. Same OpenAI SDK, two lines change."}],["$","div",null,{"className":"flex flex-col sm:flex-row gap-4 justify-center pt-4","children":[["$","a",null,{"href":"https://portal.juicefactory.ai/auth/signup","className":"px-8 py-4 bg-primary text-primary-foreground rounded-lg text-lg font-semibold hover:bg-primary/90 transition-colors inline-block","children":"Get a free API key"}],["$","$L8",null,{"href":"/de/guides/implementing-gdpr-compliant-ai","className":"px-8 py-4 border border-border rounded-lg text-lg font-semibold hover:bg-accent transition-colors inline-block","children":"Read the GDPR implementation guide"}]]}]]}]}]}]]
7:["$","html",null,{"lang":"de","suppressHydrationWarning":true,"children":[["$","head",null,{}],["$","body",null,{"className":"min-h-screen bg-background text-foreground antialiased","children":[["$","$Le",null,{"attribute":"class","defaultTheme":"dark","enableSystem":true,"disableTransitionOnChange":true,"children":[["$","nav",null,{"className":"fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-md border-b border-border","children":["$","div",null,{"className":"container mx-auto px-4 h-16 flex items-center justify-between","children":[["$","$L8",null,{"href":"/de","className":"flex items-center gap-2","children":[["$","svg",null,{"className":"w-8 h-8 text-primary animate-pulse-glow","fill":"none","viewBox":"0 0 24 24","stroke":"currentColor","children":["$","path",null,{"strokeLinecap":"round","strokeLinejoin":"round","strokeWidth":2,"d":"M13 10V3L4 14h7v7l9-11h-7z"}]}],["$","span",null,{"className":"text-2xl font-bold bg-gradient-primary bg-clip-text text-transparent","children":"Juice Factory"}]]}],["$","div",null,{"className":"hidden md:flex items-center gap-6","children":[["$","$L8",null,{"href":"/de/defense","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Verteidigung"}],["$","$L8",null,{"href":"/de/tech","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Technologie"}],["$","$L8",null,{"href":"/de/byok","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"BYOK"}],["$","$L8",null,{"href":"/de/private-ai-for-business","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Private KI"}],["$","$L8",null,{"href":"/de/guides","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Anleitungen"}],["$","$L8",null,{"href":"/de/trust","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Trust Center"}],["$","$Lf",null,{"currentLocale":"de"}],["$","a",null,{"href":"https://portal.juicefactory.ai/auth/login","className":"px-4 py-2 bg-primary text-primary-foreground rounded-md text-sm font-medium hover:bg-primary/90 transition-colors","children":"Anmelden"}]]}],["$","$L10",null,{"locale":"de","nav":{"home":"Startseite","defense":"Verteidigung","tech":"Technologie","byok":"BYOK","privateAi":"Private KI","guides":"Anleitungen","trust":"Trust Center","login":"Anmelden"}}]]}]}],["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children","$4","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}],["$","footer",null,{"className":"py-12 px-4 border-t border-border","children":["$","div",null,{"className":"container mx-auto flex flex-col md:flex-row justify-between items-center gap-4","children":[["$","p",null,{"className":"text-sm text-muted-foreground","children":["© ",2026," ","Juice Factory. Alle Rechte vorbehalten."]}],["$","div",null,{"className":"flex gap-6","children":[["$","$L8",null,{"href":"/en/trust","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Trust Center"}],["$","$L8",null,{"href":"/en/security","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Security"}],["$","$L8",null,{"href":"/en/data-processing","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Data Processing"}],["$","$L8",null,{"href":"/de/privacy","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Datenschutz"}],["$","$L8",null,{"href":"/de/terms","className":"text-sm text-muted-foreground hover:text-foreground transition-colors","children":"Nutzungsbedingungen"}]]}]]}]}]]}],["$","$L11",null,{"src":"https://www.googletagmanager.com/gtag/js?id=G-HGZMPNZK5F","strategy":"afterInteractive"}],["$","$L11",null,{"id":"ga4-init","strategy":"afterInteractive","children":"\n            window.dataLayer = window.dataLayer || [];\n            function gtag(){dataLayer.push(arguments);}\n            gtag('js', new Date());\n            gtag('config', 'G-HGZMPNZK5F');\n          "}],["$","$L11",null,{"id":"matomo-init","strategy":"afterInteractive","children":"\n            var _paq = window._paq = window._paq || [];\n            _paq.push(['trackPageView']);\n            _paq.push(['enableLinkTracking']);\n            (function() {\n              var u=\"https://matomo.manprogroup.com/\";\n              _paq.push(['setTrackerUrl', u+'matomo.php']);\n              _paq.push(['setSiteId', '14']);\n              var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];\n              g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);\n            })();\n          "}]]}]]}]
9:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"RAG in Python: DSGVO-konforme Dokumentensuche-API erstellen (2026)"}],["$","meta","3",{"name":"description","content":"Erstellen Sie ein produktionsreifes RAG-System in Python mit FastAPI, Qdrant und EU-gehosteter Inferenz. DSGVO-konforme Dokumentensuche mit PyMuPDF und privatem LLM."}],["$","link","4",{"rel":"canonical","href":"https://juicefactory.ai/de/guides/rag-python-gdpr-document-search"}],["$","link","5",{"rel":"alternate","hrefLang":"en","href":"https://juicefactory.ai/en/guides/rag-python-gdpr-document-search"}],["$","link","6",{"rel":"alternate","hrefLang":"sv","href":"https://juicefactory.ai/sv/guides/rag-python-gdpr-document-search"}],["$","link","7",{"rel":"alternate","hrefLang":"de","href":"https://juicefactory.ai/de/guides/rag-python-gdpr-document-search"}],["$","link","8",{"rel":"alternate","hrefLang":"fr","href":"https://juicefactory.ai/fr/guides/rag-python-gdpr-document-search"}],["$","link","9",{"rel":"alternate","hrefLang":"x-default","href":"https://juicefactory.ai/en/guides/rag-python-gdpr-document-search"}],["$","meta","10",{"property":"og:title","content":"RAG in Python: DSGVO-konforme Dokumentensuche-API erstellen (2026)"}],["$","meta","11",{"property":"og:description","content":"Erstellen Sie ein produktionsreifes RAG-System in Python mit FastAPI, Qdrant und EU-gehosteter Inferenz. DSGVO-konforme Dokumentensuche mit PyMuPDF und privatem LLM."}],["$","meta","12",{"property":"og:url","content":"https://juicefactory.ai/de/guides/rag-python-gdpr-document-search"}],["$","meta","13",{"property":"og:site_name","content":"Juice Factory"}],["$","meta","14",{"property":"og:locale","content":"de"}],["$","meta","15",{"property":"og:image:alt","content":"Juice Factory AI Guide"}],["$","meta","16",{"property":"og:image:type","content":"image/png"}],["$","meta","17",{"property":"og:image","content":"http://localhost:3000/de/guides/rag-python-gdpr-document-search/opengraph-image?b184df05bd4ad81a"}],["$","meta","18",{"property":"og:image:width","content":"1200"}],["$","meta","19",{"property":"og:image:height","content":"630"}],["$","meta","20",{"property":"og:type","content":"article"}],["$","meta","21",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","22",{"name":"twitter:title","content":"RAG in Python: DSGVO-konforme Dokumentensuche-API erstellen (2026)"}],["$","meta","23",{"name":"twitter:description","content":"Erstellen Sie ein produktionsreifes RAG-System in Python mit FastAPI, Qdrant und EU-gehosteter Inferenz. DSGVO-konforme Dokumentensuche mit PyMuPDF und privatem LLM."}],["$","meta","24",{"name":"twitter:image","content":"https://juicefactory.ai/logo-opengraph.png"}],["$","link","25",{"rel":"icon","href":"/favicon.png"}]]
1:null
