New_Micro_Learning/backend/app/Modules/AI/Application/AiProviderResolver.php

107 خطوط
4.3 KiB
PHP

<?php
namespace App\Modules\AI\Application;
use App\Modules\AI\Domain\AiProvider;
use App\Modules\AI\Infrastructure\AiEndpointPolicy;
use App\Modules\AI\Infrastructure\LocalStructuringProvider;
use App\Modules\AI\Infrastructure\OpenAiCompatibleProvider;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use RuntimeException;
final class AiProviderResolver
{
public function __construct(
private readonly LocalStructuringProvider $local,
private readonly OpenAiCompatibleProvider $openAi,
private readonly AiEndpointPolicy $endpointPolicy,
) {}
public function forOrganization(string $organizationId): AiProvider
{
$connection = DB::table('ai_provider_connections')
->where('organization_id', $organizationId)
->where('enabled', true)
->orderByDesc('is_default')
->orderBy('created_at')
->orderBy('id')
->first();
if ($connection) {
return $this->fromConnection($connection);
}
$row = DB::table('organization_profiles')->where('organization_id', $organizationId)->first();
$settings = json_decode($row?->settings ?: '{}', true);
return ($settings['ai']['provider'] ?? 'local') === 'openai' ? $this->openAi : $this->local;
}
public function byId(string $id, string $organizationId): AiProvider
{
if (str_starts_with($id, 'connection:')) {
$connection = DB::table('ai_provider_connections')
->where('organization_id', $organizationId)
->where('id', substr($id, 11))
->where('enabled', true)
->first();
if ($connection) {
return $this->fromConnection($connection);
}
throw new RuntimeException('The selected AI provider connection is unavailable.');
}
return match ($id) {
'openai' => $this->openAi,
'local', 'local-structuring-v1' => $this->local,
default => throw new RuntimeException('The selected AI provider is unknown.'),
};
}
public function fallbackForOrganization(string $organizationId, string $failedProviderId): ?AiProvider
{
$settings = $this->settings($organizationId);
$enabled = (bool) ($settings['reliability']['fallbackEnabled'] ?? true);
$fallback = $settings['fallbackProvider'] ?? 'local';
$fallbackConnectionId = $settings['fallbackConnectionId'] ?? null;
if ($enabled && $fallbackConnectionId && ! str_contains($failedProviderId, $fallbackConnectionId)) {
$connection = DB::table('ai_provider_connections')->where('organization_id', $organizationId)->where('id', $fallbackConnectionId)->where('enabled', true)->first();
if ($connection) {
return $this->fromConnection($connection);
}
}
return $enabled && $failedProviderId !== 'local' && $fallback === 'local' ? $this->local : null;
}
/** @return array<string, mixed> */
public function status(string $organizationId): array
{
$provider = $this->forOrganization($organizationId);
if ($provider instanceof OpenAiCompatibleProvider) {
return ['id' => $provider->id(), 'external' => $provider->external(), 'configured' => $provider->configured(), ...$provider->health()];
}
return ['id' => $provider->id(), 'external' => false, 'configured' => true, 'connected' => true, 'latencyMs' => 0, 'message' => 'پردازش محلی آماده است.'];
}
/** @return array<string, mixed> */
private function settings(string $organizationId): array
{
$row = DB::table('organization_profiles')->where('organization_id', $organizationId)->first();
$settings = json_decode($row?->settings ?: '{}', true);
return $settings['ai'] ?? [];
}
private function fromConnection(object $row): OpenAiCompatibleProvider
{
return new OpenAiCompatibleProvider($this->endpointPolicy, [
'id' => $row->id, 'provider' => $row->provider, 'mode' => $row->mode, 'base_url' => $row->base_url,
'api_key' => filled($row->encrypted_api_key) ? Crypt::decryptString($row->encrypted_api_key) : '',
'default_model' => $row->default_model, 'timeout_seconds' => $row->timeout_seconds,
]);
}
}