AI Services
PHP-Service-Klassen für KI-Integration. Native HTTP-Clients für Ollama, Qdrant und Claude API.
| Pfad | /src/Infrastructure/AI/ |
|---|---|
| Namespace | Infrastructure\AI |
| Contract | layered-architecture-pruefung_v1.0.yaml |
| Status | Produktiv (Migration von Python abgeschlossen) |
AIConfig
Zentralisierte Konfiguration und Service-Factory für alle AI-Services.
| Datei | AIConfig.php |
|---|---|
| Typ | readonly class |
| Funktion | Config-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.
| Datei | OllamaService.php |
|---|---|
| Host | http://localhost:11434 |
| Embedding-Model | mxbai-embed-large |
| LLM-Model | gemma3:4b-it-qat |
| Dimension | 1024 |
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.
| Datei | QdrantService.php |
|---|---|
| Host | http://localhost:6333 |
| Collection | documents |
| Distance | Cosine |
| Timeout | 30s |
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.
| Datei | ClaudeService.php |
|---|---|
| API | https://api.anthropic.com/v1/messages |
| API Version | 2023-06-01 |
| Model | claude-opus-4-5-20251101 |
| Max Tokens | 4000 |
| Timeout | 120s |
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.
| Datei | ChatService.php |
|---|---|
| Abhängigkeiten | OllamaService, QdrantService, ClaudeService |
| Pipeline-Schritte | 6 (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:
| Service | Fehlerfall |
|---|---|
| OllamaService | API nicht erreichbar, ungültiges Embedding, Timeout |
| QdrantService | API nicht erreichbar, Collection fehlt, ungültige Antwort |
| ClaudeService | API nicht erreichbar, ungültiger API-Key, Rate-Limit, Timeout |
| ChatService | Embedding-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:
| Parameter | Default-Wert | Quelle |
|---|---|---|
| ollamaHost | http://localhost:11434 | AIConfig |
| qdrantHost | http://localhost:6333 | AIConfig |
| anthropicApiKey | sk-ant-... | /var/www/docs/credentials/credentials.md |
| embeddingModel | mxbai-embed-large | AIConfig |
| claudeModel | claude-opus-4-5-20251101 | AIConfig |
| defaultCollection | documents | AIConfig |
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();