feat: soporte Google Gemini como proveedor de IA
- Settings: agrega Gemini al selector de proveedor, campos gemini_api_key y gemini_model - Modelos: gemini-2.0-flash, gemini-1.5-flash, gemini-1.5-pro - AiBot::callGemini(): llama generateContent API con system_instruction y historial - migrate.php: seed defaults gemini_api_key y gemini_model Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ef799a4a0a
commit
5277d47c14
@@ -1675,9 +1675,11 @@ HTML;
|
|||||||
['key' => 'verify_hmac_signature', 'label' => 'Validar firma HMAC de Meta', 'type' => 'checkbox', 'placeholder' => ''],
|
['key' => 'verify_hmac_signature', 'label' => 'Validar firma HMAC de Meta', 'type' => 'checkbox', 'placeholder' => ''],
|
||||||
],
|
],
|
||||||
'Inteligencia Artificial' => [
|
'Inteligencia Artificial' => [
|
||||||
['key' => 'ai_provider', 'label' => 'Proveedor', 'type' => 'select', 'options' => ['mock' => 'Mock (simulado)', 'openai' => 'OpenAI']],
|
['key' => 'ai_provider', 'label' => 'Proveedor', 'type' => 'select', 'options' => ['mock' => 'Mock (simulado)', 'openai' => 'OpenAI', 'gemini' => 'Google Gemini']],
|
||||||
['key' => 'openai_api_key', 'label' => 'OpenAI API Key', 'type' => 'password', 'placeholder' => 'sk-...'],
|
['key' => 'openai_api_key', 'label' => 'OpenAI API Key', 'type' => 'password', 'placeholder' => 'sk-...'],
|
||||||
['key' => 'openai_model', 'label' => 'Modelo', 'type' => 'select', 'options' => ['gpt-4o-mini' => 'GPT-4o Mini', 'gpt-4o' => 'GPT-4o', 'gpt-3.5-turbo' => 'GPT-3.5 Turbo']],
|
['key' => 'openai_model', 'label' => 'Modelo OpenAI', 'type' => 'select', 'options' => ['gpt-4o-mini' => 'GPT-4o Mini', 'gpt-4o' => 'GPT-4o', 'gpt-3.5-turbo' => 'GPT-3.5 Turbo']],
|
||||||
|
['key' => 'gemini_api_key', 'label' => 'Google Gemini API Key', 'type' => 'password', 'placeholder' => 'AIza...'],
|
||||||
|
['key' => 'gemini_model', 'label' => 'Modelo Gemini', 'type' => 'select', 'options' => ['gemini-2.0-flash' => 'Gemini 2.0 Flash', 'gemini-1.5-flash' => 'Gemini 1.5 Flash', 'gemini-1.5-pro' => 'Gemini 1.5 Pro']],
|
||||||
['key' => 'ai_max_tokens', 'label' => 'Máximo de tokens', 'type' => 'number', 'placeholder' => '500'],
|
['key' => 'ai_max_tokens', 'label' => 'Máximo de tokens', 'type' => 'number', 'placeholder' => '500'],
|
||||||
['key' => 'ai_default_prompt', 'label' => 'System Prompt por defecto', 'type' => 'textarea', 'placeholder' => 'Eres un asistente...'],
|
['key' => 'ai_default_prompt', 'label' => 'System Prompt por defecto', 'type' => 'textarea', 'placeholder' => 'Eres un asistente...'],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class AiBot
|
|||||||
|
|
||||||
return match ($provider) {
|
return match ($provider) {
|
||||||
'openai' => self::callOpenAI($systemPrompt, $history, $company),
|
'openai' => self::callOpenAI($systemPrompt, $history, $company),
|
||||||
|
'gemini' => self::callGemini($systemPrompt, $history),
|
||||||
'mock' => self::mockResponse($history),
|
'mock' => self::mockResponse($history),
|
||||||
default => self::callOpenAI($systemPrompt, $history, $company),
|
default => self::callOpenAI($systemPrompt, $history, $company),
|
||||||
};
|
};
|
||||||
@@ -104,6 +105,56 @@ class AiBot
|
|||||||
return ['content' => $text];
|
return ['content' => $text];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function callGemini(string $systemPrompt, array $history): ?array
|
||||||
|
{
|
||||||
|
$apiKey = env('GEMINI_API_KEY', '');
|
||||||
|
if ($apiKey === '') return null;
|
||||||
|
|
||||||
|
$model = env('GEMINI_MODEL', 'gemini-2.0-flash');
|
||||||
|
$maxTokens = (int)env('AI_MAX_TOKENS', '500');
|
||||||
|
|
||||||
|
// Gemini usa "contents" con roles "user"/"model"
|
||||||
|
$contents = [];
|
||||||
|
foreach ($history as $msg) {
|
||||||
|
$role = ($msg['role'] ?? 'user') === 'assistant' ? 'model' : 'user';
|
||||||
|
$contents[] = ['role' => $role, 'parts' => [['text' => $msg['content'] ?? '']]];
|
||||||
|
}
|
||||||
|
// Si el historial está vacío o el último es del model, Gemini requiere que el último sea user
|
||||||
|
if (empty($contents)) {
|
||||||
|
$contents[] = ['role' => 'user', 'parts' => [['text' => '']]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = json_encode([
|
||||||
|
'system_instruction' => ['parts' => [['text' => $systemPrompt]]],
|
||||||
|
'contents' => $contents,
|
||||||
|
'generationConfig' => ['maxOutputTokens' => $maxTokens, 'temperature' => 0.7],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$apiKey}";
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => $payload,
|
||||||
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||||
|
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) {
|
||||||
|
return ['content' => 'Lo siento, tengo problemas para procesar tu mensaje. Por favor intenta de nuevo.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
$text = $data['candidates'][0]['content']['parts'][0]['text'] ?? null;
|
||||||
|
|
||||||
|
return $text !== null ? ['content' => $text] : ['content' => 'No pude generar una respuesta. ¿Puedes reformular tu pregunta?'];
|
||||||
|
}
|
||||||
|
|
||||||
private static function mockResponse(array $history): array
|
private static function mockResponse(array $history): array
|
||||||
{
|
{
|
||||||
$last = end($history);
|
$last = end($history);
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ $seedSettings = [
|
|||||||
'ai_provider' => 'mock',
|
'ai_provider' => 'mock',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
'openai_model' => 'gpt-4o-mini',
|
'openai_model' => 'gpt-4o-mini',
|
||||||
|
'gemini_api_key' => '',
|
||||||
|
'gemini_model' => 'gemini-2.0-flash',
|
||||||
'ai_max_tokens' => '500',
|
'ai_max_tokens' => '500',
|
||||||
'ai_default_prompt' => 'Eres un asistente virtual de Palmas360. Responde de forma amable y profesional.',
|
'ai_default_prompt' => 'Eres un asistente virtual de Palmas360. Responde de forma amable y profesional.',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user