Feat: Agregar agente Groq con integración RAG
- Nuevo módulo groq_agent.py para consultas a la API de Groq - Panel de administración en /groq para configurar API key, modelo y prompt - Comando /rag en Discord y Telegram para consultar el RAG - Sistema de prompt personalizable guardado en base de datos - Soporte para variables de entorno en Docker - Fix: starlette version para evitar bug con Jinja2
This commit is contained in:
158
panel/main.py
158
panel/main.py
@@ -14,7 +14,7 @@ from pydantic import BaseModel
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from passlib.hash import pbkdf2_sha256 as hasher
|
||||
from botdiscord.config import load_config, get_web_config, get_libretranslate_url, get_db_type
|
||||
from botdiscord.config import load_config, get_web_config, get_libretranslate_url, get_db_type, get_groq_config
|
||||
|
||||
# Asegurar que las variables de entorno se carguen correctamente
|
||||
load_dotenv()
|
||||
@@ -703,6 +703,162 @@ async def metrics_page(request: Request):
|
||||
"username": username
|
||||
})
|
||||
|
||||
@app.get("/groq")
|
||||
async def groq_page(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
from botdiscord.database import get_bot_config, set_bot_config
|
||||
groq_config = get_groq_config()
|
||||
|
||||
# Default prompt
|
||||
default_prompt = """Eres el General Reserves, comandante del ejército de Last War: Survival Game.
|
||||
|
||||
IDIOMA - IMPORTANTE:
|
||||
1. Detecta el idioma de la PREGUNTA del usuario
|
||||
2. Si NO es inglés, tradúcela al inglés ANTES de consultar el RAG
|
||||
3. Cuando recibas la respuesta del RAG, tradúcela al MISMO IDIOMA de la pregunta original
|
||||
4. RESPONDE SIEMPRE en el mismo idioma que te habló el usuario
|
||||
|
||||
SALUDOS: Saluda como "¡A la orden, recruit! 🎖️" o "¡Reporting for duty!"
|
||||
|
||||
FORMATO DE RESPUESTA:
|
||||
- Primero saluda al usuario
|
||||
- Da la información encontrada
|
||||
- NUNCA repitas información varias veces
|
||||
- Sé conciso
|
||||
|
||||
RESTRICCIONES:
|
||||
1. SOLO responde sobre Last War: Survival Game
|
||||
2. NUNCA inventes información
|
||||
3. Si no hay datos en el RAG, responde con humor gentil: "¡Mi radar no detectó eso, recruit! 🤔"
|
||||
|
||||
Usa el sistema RAG para buscar información."""
|
||||
|
||||
# Always use default prompt (user can edit and save custom one)
|
||||
groq_config["system_prompt"] = default_prompt
|
||||
|
||||
# Check if user has a custom prompt saved
|
||||
saved_prompt = get_bot_config("groq_system_prompt")
|
||||
if saved_prompt:
|
||||
groq_config["system_prompt"] = saved_prompt
|
||||
groq_config["has_custom_prompt"] = True
|
||||
else:
|
||||
groq_config["has_custom_prompt"] = False
|
||||
|
||||
groq_models = [
|
||||
{"id": "llama-3.3-70b-versatile", "name": "Llama 3.3 70B (Versatile)"},
|
||||
{"id": "llama-3.1-70b-versatile", "name": "Llama 3.1 70B (Versatile)"},
|
||||
{"id": "llama-3.1-8b-instant", "name": "Llama 3.1 8B (Instant)"},
|
||||
{"id": "mixtral-8x7b-32768", "name": "Mixtral 8x7B"},
|
||||
{"id": "gemma2-9b-it", "name": "Gemma 2 9B"},
|
||||
]
|
||||
|
||||
return templates.TemplateResponse("groq.html", {
|
||||
"request": request,
|
||||
"groq_config": groq_config,
|
||||
"groq_models": groq_models,
|
||||
"is_admin": request.cookies.get("username") == "nickpons666"
|
||||
})
|
||||
|
||||
@app.post("/groq/save")
|
||||
async def save_groq_config(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
form = await request.form()
|
||||
api_key = form.get("api_key", "")
|
||||
model = form.get("model", "llama-3.3-70b-versatile")
|
||||
rag_url = form.get("rag_url", "http://localhost:8004")
|
||||
system_prompt = form.get("system_prompt", "")
|
||||
|
||||
config = load_config()
|
||||
if "groq" not in config:
|
||||
config["groq"] = {}
|
||||
|
||||
config["groq"]["api_key"] = api_key
|
||||
config["groq"]["model"] = model
|
||||
config["groq"]["rag_url"] = rag_url
|
||||
|
||||
import yaml
|
||||
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.yaml")
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
|
||||
|
||||
if system_prompt:
|
||||
from botdiscord.database import set_bot_config
|
||||
set_bot_config("groq_system_prompt", system_prompt)
|
||||
|
||||
from botdiscord.groq_agent import reload_config
|
||||
reload_config()
|
||||
|
||||
return RedirectResponse(url="/groq?saved=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@app.post("/groq/reset-prompt")
|
||||
async def reset_groq_prompt(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
from botdiscord.database import set_bot_config
|
||||
set_bot_config("groq_system_prompt", "")
|
||||
|
||||
from botdiscord.groq_agent import reload_config
|
||||
reload_config()
|
||||
|
||||
return RedirectResponse(url="/groq?reset=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@app.post("/groq/test")
|
||||
async def test_groq_agent(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
form = await request.form()
|
||||
test_question = form.get("test_question", "")
|
||||
|
||||
if not test_question:
|
||||
return RedirectResponse(url="/groq?error=no_question", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
from botdiscord.groq_agent import chat_with_rag
|
||||
try:
|
||||
result = await chat_with_rag(test_question)
|
||||
response = result.get("response", "Sin respuesta")
|
||||
sources = result.get("sources", [])
|
||||
rag_result = result.get("rag_result", {})
|
||||
rag_answer = rag_result.get("answer", "") if rag_result else ""
|
||||
|
||||
return templates.TemplateResponse("groq.html", {
|
||||
"request": request,
|
||||
"groq_config": get_groq_config(),
|
||||
"groq_models": [
|
||||
{"id": "llama-3.3-70b-versatile", "name": "Llama 3.3 70B (Versatile)"},
|
||||
{"id": "llama-3.1-70b-versatile", "name": "Llama 3.1 70B (Versatile)"},
|
||||
{"id": "llama-3.1-8b-instant", "name": "Llama 3.1 8B (Instant)"},
|
||||
{"id": "mixtral-8x7b-32768", "name": "Mixtral 8x7B"},
|
||||
{"id": "gemma2-9b-it", "name": "Gemma 2 9B"},
|
||||
],
|
||||
"is_admin": request.cookies.get("username") == "nickpons666",
|
||||
"test_result": {
|
||||
"question": test_question,
|
||||
"response": response,
|
||||
"sources": sources,
|
||||
"rag_answer": rag_answer
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return templates.TemplateResponse("groq.html", {
|
||||
"request": request,
|
||||
"groq_config": get_groq_config(),
|
||||
"groq_models": [
|
||||
{"id": "llama-3.3-70b-versatile", "name": "Llama 3.3 70B (Versatile)"},
|
||||
{"id": "llama-3.1-70b-versatile", "name": "Llama 3.1 70B (Versatile)"},
|
||||
{"id": "llama-3.1-8b-instant", "name": "Llama 3.1 8B (Instant)"},
|
||||
{"id": "mixtral-8x7b-32768", "name": "Mixtral 8x7B"},
|
||||
{"id": "gemma2-9b-it", "name": "Gemma 2 9B"},
|
||||
],
|
||||
"is_admin": request.cookies.get("username") == "nickpons666",
|
||||
"test_error": str(e)
|
||||
})
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
web_config = get_web_config()
|
||||
|
||||
@@ -97,6 +97,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white mb-3" style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bi bi-robot"></i> {{ "Agente Groq" | translate(lang) }}</h5>
|
||||
<p class="card-text">{{ "Configurar agente RAG con Groq" | translate(lang) }}</p>
|
||||
<a href="/groq" class="btn btn-light btn-sm">{{ "Configurar" | translate(lang) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
|
||||
172
panel/templates/groq.html
Normal file
172
panel/templates/groq.html
Normal file
@@ -0,0 +1,172 @@
|
||||
{% set lang = request.cookies.get('panel_lang', 'es') %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ "Configuración de Groq - Agente RAG" | translate(lang) }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/dashboard">
|
||||
<i class="bi bi-translate"></i> {{ "Bots de Traducción" | translate(lang) }}
|
||||
</a>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="dropdown me-3">
|
||||
<button class="btn btn-outline-light btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-translate"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item {{ 'active' if lang == 'es' }}" href="/set-lang/es">Español</a></li>
|
||||
<li><a class="dropdown-item {{ 'active' if lang == 'en' }}" href="/set-lang/en">English</a></li>
|
||||
<li><a class="dropdown-item {{ 'active' if lang == 'pt' }}" href="/set-lang/pt">Português</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/dashboard" class="btn btn-outline-light btn-sm me-2">{{ "Dashboard" | translate(lang) }}</a>
|
||||
<a href="/logout" class="btn btn-outline-light btn-sm">{{ "Cerrar Sesión" | translate(lang) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">🤖 {{ "Configuración de Groq - Agente RAG" | translate(lang) }}</h2>
|
||||
|
||||
{% if request.query_params.get('saved') == '1' %}
|
||||
<div class="alert alert-success">
|
||||
<i class="bi bi-check-circle"></i> {{ "Configuración guardada correctamente" | translate(lang) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if test_error %}
|
||||
<div class="alert alert-danger">
|
||||
<i class="bi bi-exclamation-triangle"></i> {{ "Error al probar el agente:" | translate(lang) }} {{ test_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-key"></i> {{ "Configuración de API" | translate(lang) }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/groq/save">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ "API Key de Groq" | translate(lang) }}</label>
|
||||
<input type="password" class="form-control" name="api_key"
|
||||
value="{{ groq_config.api_key if is_admin else '********************************' }}"
|
||||
placeholder="sk-..." required>
|
||||
<div class="form-text">{{ "Obtén tu API key en" | translate(lang) }} <a href="https://console.groq.com" target="_blank">console.groq.com</a></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ "Modelo" | translate(lang) }}</label>
|
||||
<select class="form-select" name="model">
|
||||
{% for model in groq_models %}
|
||||
<option value="{{ model.id }}" {% if groq_config.model == model.id %}selected{% endif %}>
|
||||
{{ model.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ "URL del API RAG" | translate(lang) }}</label>
|
||||
<input type="text" class="form-control" name="rag_url"
|
||||
value="{{ groq_config.rag_url }}"
|
||||
placeholder="http://localhost:8004">
|
||||
<div class="form-text">{{ "URL donde corre el servidor RAG (puerto 8004)" | translate(lang) }}</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ "System Prompt" | translate(lang) }}</label>
|
||||
<textarea class="form-control" name="system_prompt" rows="6">{{ groq_config.system_prompt }}</textarea>
|
||||
<div class="form-text">{{ "Instrucciones del sistema para el agente Groq" | translate(lang) }}</div>
|
||||
{% if groq_config.has_custom_prompt %}
|
||||
<div class="mt-2">
|
||||
<form method="post" action="/groq/reset-prompt" style="display:inline;">
|
||||
<button type="submit" class="btn btn-warning btn-sm">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> {{ "Restablecer Prompt Default" | translate(lang) }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> {{ "Guardar Configuración" | translate(lang) }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-chat-dots"></i> {{ "Probar Agente" | translate(lang) }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/groq/test">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ "Pregunta de prueba" | translate(lang) }}</label>
|
||||
<textarea class="form-control" name="test_question" rows="3"
|
||||
placeholder="How to get heroes in Last War?">{{ test_result.question if test_result else '' }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="bi bi-send"></i> {{ "Enviar Pregunta" | translate(lang) }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if test_result %}
|
||||
<hr>
|
||||
<div class="mt-3">
|
||||
<h6>{{ "Respuesta del Agente:" | translate(lang) }}</h6>
|
||||
<div class="bg-light p-3 rounded" style="max-height: 300px; overflow-y: auto;">
|
||||
{{ test_result.response }}
|
||||
</div>
|
||||
|
||||
{% if test_result.rag_answer %}
|
||||
<h6 class="mt-3">{{ "Respuesta del RAG:" | translate(lang) }}</h6>
|
||||
<div class="bg-info bg-opacity-10 p-3 rounded" style="max-height: 150px; overflow-y: auto;">
|
||||
{{ test_result.rag_answer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if test_result.sources %}
|
||||
<h6 class="mt-3">{{ "Fuentes:" | translate(lang) }}</h6>
|
||||
<ul class="list-group">
|
||||
{% for source in test_result.sources[:5] %}
|
||||
<li class="list-group-item">
|
||||
<small>{{ source.title }}</small>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-info-circle"></i> {{ "Información" | translate(lang) }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>{{ "Modelos gratuitos de Groq:" | translate(lang) }}</strong></p>
|
||||
<ul>
|
||||
<li><code>llama-3.3-70b-versatile</code> - {{ "Recomendado" | translate(lang) }}</li>
|
||||
<li><code>llama-3.1-70b-versatile</code></li>
|
||||
<li><code>llama-3.1-8b-instant</code> - {{ "Más rápido" | translate(lang) }}</li>
|
||||
<li><code>mixtral-8x7b-32768</code></li>
|
||||
<li><code>gemma2-9b-it</code></li>
|
||||
</ul>
|
||||
<p class="text-muted"><small>{{ "Los modelos se seleccionan desde la API de Groq y pueden cambiar" | translate(lang) }}</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user