<?php
declare(strict_types=1);
namespace Infrastructure\AI;
use RuntimeException;
/**
* Zentralisierte AI-Service-Konfiguration.
*
* Verwaltet alle Konfigurationswerte für AI-Services (Ollama, Qdrant, Claude)
* und stellt Factory-Methoden zur Service-Erstellung bereit.
*
* Diese Klasse:
* - Lädt Credentials sicher aus der credentials.md Datei
* - Definiert Default-Werte für alle Service-URLs und Modelle
* - Erstellt konfigurierte Service-Instanzen
* - Verhindert doppeltes Laden von API-Keys
*
* @package Infrastructure\AI
* @author System Generated
* @version 1.0.0
*/
final readonly class AIConfig
{
/**
* Konstruiert eine neue AIConfig-Instanz.
*
* @param string $ollamaHost Ollama API Host-URL
* @param string $qdrantHost Qdrant API Host-URL
* @param string $anthropicApiKey Anthropic API Key
* @param string $embeddingModel Embedding-Modell für Ollama
* @param string $claudeModel Claude-Modell für Anthropic
* @param string $defaultCollection Standard-Collection für Qdrant
*/
public function __construct(
public string $ollamaHost,
public string $qdrantHost,
public string $anthropicApiKey,
public string $embeddingModel,
public string $claudeModel,
public string $defaultCollection
) {
}
/**
* Erstellt AIConfig aus Credentials-Datei mit Default-Werten.
*
* Lädt den Anthropic API Key aus der credentials.md Datei und
* verwendet Default-Werte für alle anderen Konfigurationsparameter.
*
* @param string $credentialsPath Pfad zur credentials.md Datei (default: /var/www/docs/credentials/credentials.md)
*
* @return self Konfigurierte AIConfig-Instanz
*
* @throws RuntimeException Wenn Credentials-Datei nicht existiert
* @throws RuntimeException Wenn Credentials-Datei nicht gelesen werden kann
* @throws RuntimeException Wenn Anthropic API Key nicht gefunden wird
*
* @example
* $config = AIConfig::fromCredentialsFile();
* $chatService = $config->createChatService();
*/
public static function fromCredentialsFile(
string $credentialsPath = '/var/www/docs/credentials/credentials.md'
): self {
$anthropicApiKey = self::loadAnthropicApiKey($credentialsPath);
return new self(
ollamaHost: 'http://localhost:11434',
qdrantHost: 'http://localhost:6333',
anthropicApiKey: $anthropicApiKey,
embeddingModel: 'mxbai-embed-large',
claudeModel: 'claude-opus-4-5-20251101',
defaultCollection: 'documents'
);
}
/**
* Erstellt einen konfigurierten ChatService.
*
* Erzeugt alle benötigten Dependencies (OllamaService, QdrantService, ClaudeService)
* und liefert einen vollständig konfigurierten ChatService zurück.
*
* @return ChatService Konfigurierter ChatService
*
* @example
* $config = AIConfig::fromCredentialsFile();
* $chatService = $config->createChatService();
* $result = $chatService->chat('Was ist systemisches Coaching?');
*/
public function createChatService(): ChatService
{
return new ChatService(
$this->createOllamaService(),
$this->createQdrantService(),
$this->createClaudeService()
);
}
/**
* Erstellt einen konfigurierten OllamaService.
*
* @return OllamaService Konfigurierter OllamaService
*
* @example
* $config = AIConfig::fromCredentialsFile();
* $ollama = $config->createOllamaService();
* $embedding = $ollama->getEmbedding('Hello World');
*/
public function createOllamaService(): OllamaService
{
return new OllamaService($this->ollamaHost);
}
/**
* Erstellt einen konfigurierten QdrantService.
*
* @return QdrantService Konfigurierter QdrantService
*
* @example
* $config = AIConfig::fromCredentialsFile();
* $qdrant = $config->createQdrantService();
* $results = $qdrant->search($vector, 'documents');
*/
public function createQdrantService(): QdrantService
{
return new QdrantService($this->qdrantHost);
}
/**
* Erstellt einen konfigurierten ClaudeService.
*
* @return ClaudeService Konfigurierter ClaudeService
*
* @example
* $config = AIConfig::fromCredentialsFile();
* $claude = $config->createClaudeService();
* $result = $claude->ask('Explain quantum computing');
*/
public function createClaudeService(): ClaudeService
{
return new ClaudeService($this->anthropicApiKey);
}
/**
* Lädt den Anthropic API Key aus der Credentials-Datei.
*
* Sucht nach einer Zeile, die mit 'sk-ant-' beginnt und gibt diese zurück.
* API Key wird niemals in Exception-Messages geloggt.
*
* @param string $credentialsPath Pfad zur credentials.md Datei
*
* @return string Der gefundene API Key
*
* @throws RuntimeException Wenn Credentials-Datei nicht existiert
* @throws RuntimeException Wenn Credentials-Datei nicht gelesen werden kann
* @throws RuntimeException Wenn API Key nicht gefunden wird
*/
private static function loadAnthropicApiKey(string $credentialsPath): string
{
if (!file_exists($credentialsPath)) {
throw new RuntimeException('Credentials file not found');
}
$content = file_get_contents($credentialsPath);
if ($content === false) {
throw new RuntimeException('Could not read credentials file');
}
foreach (explode("\n", $content) as $line) {
$trimmedLine = trim($line);
if (str_starts_with($trimmedLine, 'sk-ant-')) {
return $trimmedLine;
}
}
throw new RuntimeException('Anthropic API key not found in credentials file');
}
}