Chat API

RAG-basierter Chat mit Vektorsuche und LLM-Integration. Verwendet lokale Embeddings (Ollama), Vektorsuche (Qdrant) und Claude (Anthropic) oder Ollama als LLM.

Endpunkt (HTML)POST /chat/message
Endpunkt (JSON)POST /api/v1/chat
Controller/src/Controller/ChatController.php
Service/src/Infrastructure/AI/ChatService.php

Request

POST /api/v1/chat
Content-Type: application/json

{
    "question": "Was ist systemische Therapie?",
    "model": "anthropic",
    "collection": "documents",
    "limit": 5
}
ParameterTypDefaultBeschreibung
questionstringrequiredBenutzer-Frage
modelstring"anthropic""anthropic" (claude-opus-4-5-20251101) oder "ollama" (gemma3:4b-it-qat)
collectionstring"documents"Qdrant Collection
limitint5Max. Kontext-Chunks

Response

{
    "answer": "Systemische Therapie ist ein psychotherapeutischer Ansatz...",
    "sources": [
        {
            "title": "Einführung in die systemische Therapie",
            "content": "...",
            "score": 0.89,
            "path": "/Documents/therapie/einfuehrung.pdf"
        }
    ],
    "model": "claude-opus-4-5-20251101",
    "tokens": 1234
}

RAG-Pipeline

Die Chat-Funktionalität nutzt native PHP-Services (keine Python-Aufrufe mehr):

1. Embedding erstellen (OllamaService)
   question → mxbai-embed-large → [1024-dim vector]

2. Vektorsuche (QdrantService)
   vector → Qdrant :6333 → top-k similar chunks

3. Kontext aufbauen (ChatService)
   chunks → formatierter Kontext mit Quellenangaben
   max. 3000 tokens (~12000 chars)

4. LLM-Anfrage (ClaudeService oder OllamaService)
   system_prompt + rag_prompt + context → answer

5. Quellen extrahieren (ChatService)
   search_results → deduplizierte Quellen-Liste

6. Response zusammenstellen (ChatService)
   answer + sources + metadata + usage

Architektur

Browser/Client
    ↓
ChatController (/src/Controller/)
    ↓
ChatService (/src/Infrastructure/AI/)
    ↓
┌──────────────────┬──────────────────┬──────────────────┐
│ OllamaService    │ QdrantService    │ ClaudeService    │
│ (Embeddings)     │ (Vektorsuche)    │ (LLM)            │
└──────────────────┴──────────────────┴──────────────────┘
    ↓                   ↓                   ↓
┌──────────────────┬──────────────────┬──────────────────┐
│ Ollama API       │ Qdrant API       │ Anthropic API    │
│ :11434           │ :6333            │ api.anthropic... │
└──────────────────┴──────────────────┴──────────────────┘

Migration: Python shell_exec → Native PHP (2025-12-20)
Status: Produktiv

Fehlerbehandlung

HTTP CodeBedeutung
400Fehlende oder ungültige Parameter
500Service-Fehler (Ollama, Qdrant, Claude)
503Service nicht verfügbar

Exception-Handling

Alle Services werfen RuntimeException bei Fehlern:

try {
    $config = AIConfig::fromCredentialsFile();
    $chat = $config->createChatService();
    $result = $chat->chat($question);
} catch (RuntimeException $e) {
    // Fehler-Logging
    error_log('Chat failed: ' . $e->getMessage());

    // JSON Response
    http_response_code(500);
    echo json_encode([
        'error' => 'Chat request failed',
        'message' => $e->getMessage()
    ]);
}

Typische Fehler

FehlerUrsacheLösung
Embedding generation failedOllama nicht erreichbarsystemctl status ollama prüfen
Vector search failedQdrant nicht erreichbarsystemctl status qdrant prüfen
No relevant documents foundKeine Treffer in CollectionCollection prüfen, limit erhöhen
Claude API request failedAPI-Key ungültig, Rate-Limitcredentials.md prüfen, warten

HTMX-Integration

<form hx-post="/chat/message" hx-target="#chat-messages" hx-swap="beforeend">
    <select name="model" id="chat-model">
        <option value="anthropic">claude-opus-4-5-20251101</option>
        <option value="ollama">gemma3:4b-it-qat (lokal)</option>
    </select>
    <input type="text" name="message" placeholder="Frage stellen...">
    <button type="submit">Senden</button>
</form>

<div id="chat-messages"></div>

Model-Persistenz

Die Modell-Auswahl wird im localStorage gespeichert und beim Seitenaufruf wiederhergestellt:

// Speichern
localStorage.setItem('chat-model-preference', model);

// Laden
const saved = localStorage.getItem('chat-model-preference');