Log a warning for each AI provider registered without an API key so that misconfiguration is surfaced at boot time (not silently on the first API call). Each message names the environment variable to set: ANTHROPIC_API_KEY – Claude GOOGLE_AI_API_KEY – Gemini OPENAI_API_KEY – OpenAI Providers without a key remain registered but are marked unavailable via isAvailable(), preserving backward compatibility. - Add Log::warning() calls in registerProviders() for empty keys - Extend AgenticManagerTest with a dedicated 'API key validation warnings' describe block (7 new test cases) - Update DX-002 in TODO.md as resolved Closes #29 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
138 lines
3.7 KiB
PHP
138 lines
3.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Core\Mod\Agentic\Services;
|
||
|
||
use Illuminate\Support\Facades\Log;
|
||
use InvalidArgumentException;
|
||
|
||
class AgenticManager
|
||
{
|
||
/** @var array<string, AgenticProviderInterface> */
|
||
private array $providers = [];
|
||
|
||
private string $defaultProvider = 'claude';
|
||
|
||
public function __construct()
|
||
{
|
||
$this->registerProviders();
|
||
}
|
||
|
||
/**
|
||
* Get an AI provider by name.
|
||
*/
|
||
public function provider(?string $name = null): AgenticProviderInterface
|
||
{
|
||
$name = $name ?? $this->defaultProvider;
|
||
|
||
if (! isset($this->providers[$name])) {
|
||
throw new InvalidArgumentException("Unknown AI provider: {$name}");
|
||
}
|
||
|
||
return $this->providers[$name];
|
||
}
|
||
|
||
/**
|
||
* Get the Claude provider.
|
||
*/
|
||
public function claude(): ClaudeService
|
||
{
|
||
return $this->providers['claude'];
|
||
}
|
||
|
||
/**
|
||
* Get the Gemini provider.
|
||
*/
|
||
public function gemini(): GeminiService
|
||
{
|
||
return $this->providers['gemini'];
|
||
}
|
||
|
||
/**
|
||
* Get the OpenAI provider.
|
||
*/
|
||
public function openai(): OpenAIService
|
||
{
|
||
return $this->providers['openai'];
|
||
}
|
||
|
||
/**
|
||
* Get all available providers.
|
||
*
|
||
* @return array<string, AgenticProviderInterface>
|
||
*/
|
||
public function availableProviders(): array
|
||
{
|
||
return array_filter(
|
||
$this->providers,
|
||
fn (AgenticProviderInterface $provider) => $provider->isAvailable()
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Check if a provider is available.
|
||
*/
|
||
public function isAvailable(string $name): bool
|
||
{
|
||
return isset($this->providers[$name]) && $this->providers[$name]->isAvailable();
|
||
}
|
||
|
||
/**
|
||
* Set the default provider.
|
||
*/
|
||
public function setDefault(string $name): void
|
||
{
|
||
if (! isset($this->providers[$name])) {
|
||
throw new InvalidArgumentException("Unknown AI provider: {$name}");
|
||
}
|
||
|
||
$this->defaultProvider = $name;
|
||
}
|
||
|
||
/**
|
||
* Register all AI providers.
|
||
*
|
||
* Logs a warning for each provider whose API key is absent so that
|
||
* misconfiguration is surfaced at boot time rather than on the first
|
||
* API call. Set the corresponding environment variable to silence it:
|
||
*
|
||
* ANTHROPIC_API_KEY – Claude
|
||
* GOOGLE_AI_API_KEY – Gemini
|
||
* OPENAI_API_KEY – OpenAI
|
||
*/
|
||
private function registerProviders(): void
|
||
{
|
||
// Use null coalescing since config() returns null for missing env vars
|
||
$claudeKey = config('services.anthropic.api_key') ?? '';
|
||
$geminiKey = config('services.google.ai_api_key') ?? '';
|
||
$openaiKey = config('services.openai.api_key') ?? '';
|
||
|
||
if (empty($claudeKey)) {
|
||
Log::warning("Agentic: 'claude' provider has no API key configured. Set ANTHROPIC_API_KEY to enable it.");
|
||
}
|
||
|
||
if (empty($geminiKey)) {
|
||
Log::warning("Agentic: 'gemini' provider has no API key configured. Set GOOGLE_AI_API_KEY to enable it.");
|
||
}
|
||
|
||
if (empty($openaiKey)) {
|
||
Log::warning("Agentic: 'openai' provider has no API key configured. Set OPENAI_API_KEY to enable it.");
|
||
}
|
||
|
||
$this->providers['claude'] = new ClaudeService(
|
||
apiKey: $claudeKey,
|
||
model: config('services.anthropic.model') ?? 'claude-sonnet-4-20250514',
|
||
);
|
||
|
||
$this->providers['gemini'] = new GeminiService(
|
||
apiKey: $geminiKey,
|
||
model: config('services.google.ai_model') ?? 'gemini-2.0-flash',
|
||
);
|
||
|
||
$this->providers['openai'] = new OpenAIService(
|
||
apiKey: $openaiKey,
|
||
model: config('services.openai.model') ?? 'gpt-4o-mini',
|
||
);
|
||
}
|
||
}
|