AI Services

PHP-Service-Klassen für KI-Integration. Native HTTP-Clients für Ollama, Qdrant und Claude API.

Pfad/src/Infrastructure/AI/
NamespaceInfrastructure\AI
Contractlayered-architecture-pruefung_v1.0.yaml
StatusProduktiv (Migration von Python abgeschlossen)

AIConfig

Zentralisierte Konfiguration und Service-Factory für alle AI-Services.

DateiAIConfig.php
Typreadonly class
FunktionConfig-Management, Service-Factory, Credentials-Loading

Properties

final readonly class AIConfig
{
    public string $ollamaHost;           // http://localhost:11434
    public string $qdrantHost;           // http://localhost:6333
    public string $anthropicApiKey;      // sk-ant-... (aus credentials.md)
    public string $embeddingModel;       // mxbai-embed-large
    public string $claudeModel;          // claude-opus-4-5-20251101
    public string $defaultCollection;    // documents
}

Factory-Methoden

// Config erstellen (lädt API-Key aus credentials.md)
$config = AIConfig::fromCredentialsFile();

// Einzelne Services erstellen
$ollama = $config->createOllamaService();
$qdrant = $config->createQdrantService();
$claude = $config->createClaudeService();

// Vollständiger ChatService (alle Dependencies)
$chatService = $config->createChatService();

Verwendung

use Infrastructure\AI\AIConfig;

// Einfachster Weg: ChatService direkt nutzen
$config = AIConfig::fromCredentialsFile();
$chat = $config->createChatService();
$result = $chat->chat('Was ist systemisches Coaching?');

// Alternative: Einzelne Services für spezielle Aufgaben
$config = AIConfig::fromCredentialsFile();
$ollama = $config->createOllamaService();
$embedding = $ollama->getEmbedding('Text zum Einbetten');

OllamaService

Embedding-Generierung und LLM-Generierung via Ollama REST API.

DateiOllamaService.php
Hosthttp://localhost:11434
Embedding-Modelmxbai-embed-large
LLM-Modelgemma3:4b-it-qat
Dimension1024

Methoden

final readonly class OllamaService
{
    public function __construct(string $host = 'http://localhost:11434');

    /**
     * Generiert Embedding-Vektor für Text.
     * @return array<int, float> 1024-dimensionaler Vektor
     * @throws RuntimeException
     */
    public function getEmbedding(
        string $text,
        string $model = 'mxbai-embed-large'
    ): array;

    /**
     * Generiert Text-Antwort mit Ollama-LLM.
     * @throws RuntimeException
     */
    public function generate(
        string $prompt,
        string $model = 'gemma3:4b-it-qat'
    ): string;

    /**
     * Prüft ob Ollama API erreichbar ist.
     */
    public function isAvailable(): bool;
}

Code-Beispiel

use Infrastructure\AI\OllamaService;

$ollama = new OllamaService('http://localhost:11434');

// Embedding erstellen
$embedding = $ollama->getEmbedding('Hello World');
// Returns: [0.123, -0.456, 0.789, ...] (1024 floats)

// Text generieren
$response = $ollama->generate('Explain quantum computing.');
// Returns: "Quantum computing is a type of computing that..."

// Health Check
if ($ollama->isAvailable()) {
    echo "Ollama läuft";
}

API-Aufruf

POST http://localhost:11434/api/embeddings
Content-Type: application/json

{
    "model": "mxbai-embed-large",
    "prompt": "Text zum Einbetten"
}

Response:
{
    "embedding": [0.123, -0.456, ...]
}

QdrantService

Vektorsuche via Qdrant REST API.

DateiQdrantService.php
Hosthttp://localhost:6333
Collectiondocuments
DistanceCosine
Timeout30s

Methoden

final readonly class QdrantService
{
    public function __construct(string $host = 'http://localhost:6333');

    /**
     * Sucht ähnliche Dokumente per Vektor-Similarity.
     * @param array<int, float> $vector Embedding-Vektor
     * @return array<int, array{id: int|string, score: float, payload: array}>
     * @throws RuntimeException
     */
    public function search(
        array $vector,
        string $collection = 'documents',
        int $limit = 5
    ): array;

    /**
     * Prüft ob Collection existiert.
     */
    public function collectionExists(string $collection): bool;

    /**
     * Prüft ob Qdrant API erreichbar ist.
     */
    public function isAvailable(): bool;

    /**
     * Holt Collection-Informationen.
     * @return array<string, mixed>|null
     * @throws RuntimeException
     */
    public function getCollectionInfo(string $collection): ?array;
}

Code-Beispiel

use Infrastructure\AI\QdrantService;

$qdrant = new QdrantService('http://localhost:6333');

// Vektorsuche
$vector = [0.123, -0.456, 0.789, ...]; // 1024-dimensional
$results = $qdrant->search($vector, 'documents', 5);
// Returns: [
//   ['id' => 1, 'score' => 0.89, 'payload' => ['content' => '...', 'title' => '...']],
//   ['id' => 2, 'score' => 0.76, 'payload' => ['content' => '...', 'title' => '...']]
// ]

// Collection prüfen
if ($qdrant->collectionExists('documents')) {
    $info = $qdrant->getCollectionInfo('documents');
    echo "Vektoren: " . $info['vectors_count'];
}

API-Aufruf

POST http://localhost:6333/collections/documents/points/search
Content-Type: application/json

{
    "vector": [0.123, -0.456, ...],
    "limit": 5,
    "with_payload": true
}

Response:
{
    "result": [
        {
            "id": 1,
            "score": 0.89,
            "payload": {
                "content": "...",
                "document_title": "..."
            }
        }
    ]
}

ClaudeService

LLM-Anfragen via Anthropic API.

DateiClaudeService.php
APIhttps://api.anthropic.com/v1/messages
API Version2023-06-01
Modelclaude-opus-4-5-20251101
Max Tokens4000
Timeout120s

Methoden

final readonly class ClaudeService
{
    public function __construct(string $apiKey);

    /**
     * Sendet Prompt an Claude und gibt Antwort zurück.
     * @return array{text: string, usage: array{input_tokens: int, output_tokens: int}}
     * @throws RuntimeException
     */
    public function ask(
        string $prompt,
        ?string $systemPrompt = null,
        string $model = 'claude-opus-4-5-20251101',
        int $maxTokens = 4000
    ): array;

    /**
     * Erstellt RAG-Prompt mit Kontext.
     */
    public function buildRagPrompt(string $question, string $context): string;

    /**
     * Liefert Standard-System-Prompt für RAG.
     */
    public function getDefaultSystemPrompt(): string;

    /**
     * Prüft ob Claude API erreichbar ist.
     */
    public function isAvailable(): bool;
}

Code-Beispiel

use Infrastructure\AI\ClaudeService;

$claude = new ClaudeService('sk-ant-...');

// Einfache Anfrage
$result = $claude->ask('Explain quantum computing');
echo $result['text'];
echo "Tokens: " . $result['usage']['output_tokens'];

// Mit System-Prompt
$system = 'You are a physics teacher.';
$result = $claude->ask('Explain relativity', $system);

// RAG-Prompt erstellen
$question = 'Was ist systemisches Coaching?';
$context = '[Quelle 1: Coaching Grundlagen]\nSystemisches Coaching betrachtet...';
$prompt = $claude->buildRagPrompt($question, $context);
$systemPrompt = $claude->getDefaultSystemPrompt();
$result = $claude->ask($prompt, $systemPrompt);

API-Aufruf

POST https://api.anthropic.com/v1/messages
Content-Type: application/json
x-api-key: sk-ant-...
anthropic-version: 2023-06-01

{
    "model": "claude-opus-4-5-20251101",
    "max_tokens": 4000,
    "system": "Du bist ein hilfreicher Assistent...",
    "messages": [
        {
            "role": "user",
            "content": "..."
        }
    ]
}

Response:
{
    "content": [
        {
            "type": "text",
            "text": "..."
        }
    ],
    "usage": {
        "input_tokens": 100,
        "output_tokens": 200
    }
}

ChatService

Orchestriert RAG-Pipeline mit allen Services. Vollständiger Ablauf: Embedding → Vektorsuche → Kontext → LLM-Antwort.

DateiChatService.php
AbhängigkeitenOllamaService, QdrantService, ClaudeService
Pipeline-Schritte6 (Embedding, Search, Context, LLM, Sources, Response)

Methoden

final readonly class ChatService
{
    public function __construct(
        OllamaService $ollama,
        QdrantService $qdrant,
        ClaudeService $claude
    );

    /**
     * Führt vollständige RAG-Chat-Pipeline aus.
     * @return array{
     *   question: string,
     *   answer: string,
     *   sources: array,
     *   model: string,
     *   usage?: array,
     *   chunks_used: int
     * }
     * @throws RuntimeException
     */
    public function chat(
        string $question,
        string $model = 'anthropic',
        string $collection = 'documents',
        int $limit = 5
    ): array;
}

Code-Beispiel

use Infrastructure\AI\AIConfig;

// Mit Factory (empfohlen)
$config = AIConfig::fromCredentialsFile();
$chat = $config->createChatService();

$result = $chat->chat(
    question: 'Was ist systemisches Coaching?',
    model: 'anthropic',
    collection: 'documents',
    limit: 5
);

echo $result['answer'];
echo "Chunks: " . $result['chunks_used'];
foreach ($result['sources'] as $source) {
    echo $source['title'] . " (Score: " . $source['score'] . ")";
}

Response-Struktur

{
    "question": "Was ist systemisches Coaching?",
    "answer": "Systemisches Coaching ist ein Ansatz...",
    "sources": [
        {
            "title": "Coaching Grundlagen",
            "score": 0.89,
            "content": "..."
        }
    ],
    "model": "anthropic",
    "usage": {
        "input_tokens": 234,
        "output_tokens": 567
    },
    "chunks_used": 5
}

Fehlerbehandlung

Alle Services werfen RuntimeException bei Fehlern:

ServiceFehlerfall
OllamaServiceAPI nicht erreichbar, ungültiges Embedding, Timeout
QdrantServiceAPI nicht erreichbar, Collection fehlt, ungültige Antwort
ClaudeServiceAPI nicht erreichbar, ungültiger API-Key, Rate-Limit, Timeout
ChatServiceEmbedding-Fehler, Search-Fehler, keine Dokumente, LLM-Fehler
try {
    $result = $chat->chat('Was ist systemisches Coaching?');
} catch (RuntimeException $e) {
    error_log('Chat failed: ' . $e->getMessage());
    // Fehlerbehandlung
}

Konfiguration

Alle Konfigurationswerte werden von AIConfig verwaltet:

ParameterDefault-WertQuelle
ollamaHosthttp://localhost:11434AIConfig
qdrantHosthttp://localhost:6333AIConfig
anthropicApiKeysk-ant-.../var/www/docs/credentials/credentials.md
embeddingModelmxbai-embed-largeAIConfig
claudeModelclaude-opus-4-5-20251101AIConfig
defaultCollectiondocumentsAIConfig

Custom-Konfiguration

use Infrastructure\AI\AIConfig;

// Vollständig custom
$config = new AIConfig(
    ollamaHost: 'http://192.168.1.100:11434',
    qdrantHost: 'http://192.168.1.101:6333',
    anthropicApiKey: 'sk-ant-...',
    embeddingModel: 'mxbai-embed-large',
    claudeModel: 'claude-opus-4-5-20251101',
    defaultCollection: 'my_docs'
);

$chat = $config->createChatService();