107 خطوط
4.3 KiB
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,
|
|
]);
|
|
}
|
|
}
|