feat: multi-provider AI with per-company override and Claude support
- Global settings: custom AI panel with OpenAI/Gemini/Claude each with API key, model selector, and live test-connection button (no save needed) - Per-company IA tab: same three providers + fallback to global when empty - AiBot: reads per-company config_json first, falls back to env() global - Added callClaude() for Anthropic Messages API - More OpenAI models: gpt-4.1, gpt-4.1-mini - test-ai endpoint: accepts provider/api_key/model from POST for live test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b82fcf6aaa
commit
2b859e3dc0
+65
-7
@@ -79,12 +79,15 @@ class AiBot
|
||||
|
||||
private static function callLlm(string $systemPrompt, int $ctxId, array $company): ?array
|
||||
{
|
||||
$provider = env('AI_PROVIDER', 'openai');
|
||||
$cfg = self::getConfig($company);
|
||||
// Per-company provider overrides global; empty = use global
|
||||
$provider = $cfg['ai_provider'] ?? env('AI_PROVIDER', 'openai');
|
||||
$history = ConversationContext::getAiHistory($ctxId);
|
||||
|
||||
return match ($provider) {
|
||||
'openai' => self::callOpenAI($systemPrompt, $history, $company),
|
||||
'gemini' => self::callGemini($systemPrompt, $history),
|
||||
'gemini' => self::callGemini($systemPrompt, $history, $company),
|
||||
'claude' => self::callClaude($systemPrompt, $history, $company),
|
||||
'mock' => self::mockResponse($history),
|
||||
default => self::callOpenAI($systemPrompt, $history, $company),
|
||||
};
|
||||
@@ -92,12 +95,13 @@ class AiBot
|
||||
|
||||
private static function callOpenAI(string $systemPrompt, array $history, array $company): ?array
|
||||
{
|
||||
$apiKey = env('OPENAI_API_KEY', '');
|
||||
$cfg = self::getConfig($company);
|
||||
$apiKey = $cfg['openai_api_key'] ?? env('OPENAI_API_KEY', '');
|
||||
if ($apiKey === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$model = env('OPENAI_MODEL', 'gpt-4o-mini');
|
||||
$model = $cfg['ai_model'] ?? env('OPENAI_MODEL', 'gpt-4o-mini');
|
||||
|
||||
$messages = [
|
||||
['role' => 'system', 'content' => $systemPrompt],
|
||||
@@ -145,12 +149,13 @@ class AiBot
|
||||
return $text !== null ? ['content' => $text] : null;
|
||||
}
|
||||
|
||||
private static function callGemini(string $systemPrompt, array $history): ?array
|
||||
private static function callGemini(string $systemPrompt, array $history, array $company = []): ?array
|
||||
{
|
||||
$apiKey = env('GEMINI_API_KEY', '');
|
||||
$cfg = self::getConfig($company);
|
||||
$apiKey = $cfg['gemini_api_key'] ?? env('GEMINI_API_KEY', '');
|
||||
if ($apiKey === '') return null;
|
||||
|
||||
$model = env('GEMINI_MODEL', 'gemini-2.5-flash');
|
||||
$model = $cfg['gemini_model'] ?? env('GEMINI_MODEL', 'gemini-2.5-flash');
|
||||
$maxTokens = (int)env('AI_MAX_TOKENS', '500');
|
||||
|
||||
// Gemini usa "contents" con roles "user"/"model"
|
||||
@@ -196,6 +201,59 @@ class AiBot
|
||||
return $text !== null ? ['content' => $text] : null;
|
||||
}
|
||||
|
||||
private static function callClaude(string $systemPrompt, array $history, array $company = []): ?array
|
||||
{
|
||||
$cfg = self::getConfig($company);
|
||||
$apiKey = $cfg['claude_api_key'] ?? env('CLAUDE_API_KEY', '');
|
||||
if ($apiKey === '') return null;
|
||||
|
||||
$model = $cfg['claude_model'] ?? env('CLAUDE_MODEL', 'claude-haiku-4-5');
|
||||
$maxTokens = (int)env('AI_MAX_TOKENS', '500');
|
||||
|
||||
$messages = [];
|
||||
foreach ($history as $msg) {
|
||||
$messages[] = ['role' => $msg['role'] ?? 'user', 'content' => $msg['content'] ?? ''];
|
||||
}
|
||||
if (empty($messages)) {
|
||||
$messages[] = ['role' => 'user', 'content' => ''];
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'model' => $model,
|
||||
'max_tokens' => $maxTokens,
|
||||
'system' => $systemPrompt,
|
||||
'messages' => $messages,
|
||||
]);
|
||||
|
||||
$ch = curl_init('https://api.anthropic.com/v1/messages');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'x-api-key: ' . $apiKey,
|
||||
'anthropic-version: 2023-06-01',
|
||||
],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200 || $response === false) {
|
||||
WpWebhook::log('ERROR', "Claude API error: {$error} HTTP:{$httpCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
$text = $data['content'][0]['text'] ?? null;
|
||||
|
||||
return $text !== null ? ['content' => $text] : null;
|
||||
}
|
||||
|
||||
private static function mockResponse(array $history): array
|
||||
{
|
||||
$last = end($history);
|
||||
|
||||
Reference in New Issue
Block a user