ModelRegistry.php
- Pfad:
src/Infrastructure/AI/ModelRegistry.php - Namespace: Infrastructure\AI
- Zeilen: 313 | Größe: 8,431 Bytes
- Geändert: 2025-12-25 18:16:02 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 78
- Dependencies: 100 (25%)
- LOC: 62 (20%)
- Methods: 30 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 4
- implements Domain\Service\ModelRegistryInterface
- constructor PDO
- use Domain\Service\ModelRegistryInterface
- use PDO
Klassen 1
-
ModelRegistryclass Zeile 12
Funktionen 17
-
__construct()public Zeile 17 -
getInstance()public Zeile 28 -
setInstance()public Zeile 42 -
clearCache()public Zeile 50 -
getChatModels()public Zeile 60 -
getVisionModels()public Zeile 70 -
getEmbeddingModels()public Zeile 80 -
getModels()public Zeile 90 -
getModel()public Zeile 125 -
getLabel()public Zeile 141 -
isValid()public Zeile 151 -
isLocal()public Zeile 161 -
getDefaultChatModel()public Zeile 169 -
getDefaultVisionModel()public Zeile 179 -
loadAllModels()private Zeile 195 -
syncFromOllama()public Zeile 219 -
generateDisplayName()private Zeile 297
Verwendet von 9
- ChatServiceProvider.php use
- ContentController.php constructor
- ContentController.php use
- ContentPipelineController.php use
- ContentPipelineController.php constructor
- ManageChatSessionsUseCase.php use
- ManageChatSessionsUseCase.php constructor
- PipelineStepService.php constructor
- PipelineStepService.php use
Versionen 5
-
v5
2025-12-25 18:16 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v4
2025-12-25 10:40 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v3
2025-12-23 08:50 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v2
2025-12-23 08:47 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation -
v1
2025-12-23 08:05 | claude-code-hook | modified
Claude Code Pre-Hook Backup vor Edit-Operation
Code
<?php
declare(strict_types=1);
namespace Infrastructure\AI;
// @responsibility: Zentrale Modell-Registry aus ki_dev.ai_models
use Domain\Service\ModelRegistryInterface;
use PDO;
final class ModelRegistry implements ModelRegistryInterface
{
private static ?array $cache = null;
private static ?self $instance = null;
public function __construct(
private PDO $pdo
) {
}
/**
* Get singleton instance (for static facade compatibility).
* Must be initialized via setInstance() from DI container first.
*
* @throws \RuntimeException if not initialized
*/
public static function getInstance(): self
{
if (self::$instance === null) {
throw new \RuntimeException(
'ModelRegistry not initialized. Use DI container or call setInstance() first.'
);
}
return self::$instance;
}
/**
* Set the singleton instance (called by DI container).
*/
public static function setInstance(self $instance): void
{
self::$instance = $instance;
}
/**
* Clear cache (e.g., after Ollama sync).
*/
public static function clearCache(): void
{
self::$cache = null;
}
/**
* Get all available chat models.
*
* @return array<string, string> [full_key => display_name]
*/
public function getChatModels(): array
{
return $this->getModels(chat: true);
}
/**
* Get all vision-capable models.
*
* @return array<string, string> [full_key => display_name]
*/
public function getVisionModels(): array
{
return $this->getModels(vision: true);
}
/**
* Get all embedding models.
*
* @return array<string, string> [full_key => display_name]
*/
public function getEmbeddingModels(): array
{
return $this->getModels(embedding: true);
}
/**
* Get models with optional filters.
*
* @return array<string, string> [full_key => display_name]
*/
public function getModels(
?bool $chat = null,
?bool $vision = null,
?bool $embedding = null,
?string $provider = null
): array {
$allModels = $this->loadAllModels();
$result = [];
foreach ($allModels as $model) {
if (!$model['is_available']) {
continue;
}
if ($chat !== null && (bool) $model['is_chat'] !== $chat) {
continue;
}
if ($vision !== null && (bool) $model['is_vision'] !== $vision) {
continue;
}
if ($embedding !== null && (bool) $model['is_embedding'] !== $embedding) {
continue;
}
if ($provider !== null && $model['provider'] !== $provider) {
continue;
}
$result[$model['full_key']] = $model['display_name'];
}
return $result;
}
/**
* Get a single model by full_key.
*/
public function getModel(string $fullKey): ?array
{
$allModels = $this->loadAllModels();
foreach ($allModels as $model) {
if ($model['full_key'] === $fullKey) {
return $model;
}
}
return null;
}
/**
* Get display label for a model.
*/
public function getLabel(string $fullKey): string
{
$model = $this->getModel($fullKey);
return $model['display_name'] ?? $fullKey;
}
/**
* Check if model exists and is available.
*/
public function isValid(string $fullKey): bool
{
$model = $this->getModel($fullKey);
return $model !== null && $model['is_available'];
}
/**
* Check if model is local (Ollama).
*/
public function isLocal(string $fullKey): bool
{
return str_starts_with($fullKey, 'ollama:');
}
/**
* Get default chat model (first available by priority).
*/
public function getDefaultChatModel(): string
{
$chatModels = $this->getChatModels();
return array_key_first($chatModels) ?? 'ollama:mistral:latest';
}
/**
* Get default vision model.
*/
public function getDefaultVisionModel(): string
{
$visionModels = $this->getVisionModels();
// Prefer local vision model
foreach (array_keys($visionModels) as $key) {
if (str_starts_with($key, 'ollama:')) {
return $key;
}
}
return array_key_first($visionModels) ?? 'ollama:minicpm-v:latest';
}
/**
* Load all models from database (with caching).
*/
private function loadAllModels(): array
{
if (self::$cache !== null) {
return self::$cache;
}
$stmt = $this->pdo->query(
'SELECT id, provider, model_id, display_name, full_key,
is_available, is_chat, is_embedding, is_vision,
context_length, parameters, priority
FROM ai_models
WHERE is_available = 1
ORDER BY priority ASC'
);
self::$cache = $stmt->fetchAll(PDO::FETCH_ASSOC);
return self::$cache;
}
/**
* Sync models from Ollama (call after `ollama pull`).
* Updates is_available and last_seen_at for Ollama models.
*/
public function syncFromOllama(): array
{
$output = [];
exec('ollama list 2>/dev/null', $output, $returnCode);
if ($returnCode !== 0) {
return ['error' => 'Could not run ollama list'];
}
$ollamaModels = [];
foreach ($output as $line) {
if (preg_match('/^(\S+)\s+/', $line, $matches)) {
$modelName = $matches[1];
if ($modelName !== 'NAME') {
$ollamaModels[] = $modelName;
}
}
}
// Mark all Ollama models as unavailable first
$this->pdo->exec(
"UPDATE ai_models SET is_available = 0 WHERE provider = 'ollama'"
);
$added = [];
$updated = [];
foreach ($ollamaModels as $modelId) {
// Check if model exists
$stmt = $this->pdo->prepare(
'SELECT id FROM ai_models WHERE provider = ? AND model_id = ?'
);
$stmt->execute(['ollama', $modelId]);
$existing = $stmt->fetch();
if ($existing) {
// Update existing
$stmt = $this->pdo->prepare(
'UPDATE ai_models SET is_available = 1, last_seen_at = NOW()
WHERE provider = ? AND model_id = ?'
);
$stmt->execute(['ollama', $modelId]);
$updated[] = $modelId;
} else {
// Insert new
$displayName = $this->generateDisplayName($modelId);
$isEmbedding = str_contains($modelId, 'embed');
$isVision = str_contains($modelId, 'vision') || str_contains($modelId, 'minicpm-v');
$stmt = $this->pdo->prepare(
'INSERT INTO ai_models
(provider, model_id, display_name, is_available, is_chat, is_embedding, is_vision, last_seen_at, priority)
VALUES (?, ?, ?, 1, ?, ?, ?, NOW(), 90)'
);
$stmt->execute([
'ollama',
$modelId,
$displayName,
$isEmbedding ? 0 : 1,
$isEmbedding ? 1 : 0,
$isVision ? 1 : 0,
]);
$added[] = $modelId;
}
}
self::clearCache();
return [
'ollama_models' => $ollamaModels,
'added' => $added,
'updated' => $updated,
];
}
/**
* Generate display name from model ID.
*/
private function generateDisplayName(string $modelId): string
{
// Remove version tags
$name = preg_replace('/:latest$/', '', $modelId);
// Capitalize and format
$parts = explode(':', $name);
$baseName = ucfirst($parts[0]);
if (isset($parts[1])) {
$baseName .= ' ' . strtoupper($parts[1]);
}
return $baseName . ' (lokal)';
}
}