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:
@@ -31,3 +31,9 @@ REDIS_HOST=192.168.1.X # IP de tu servidor OMV
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=translation_redis_secret
|
||||
REDIS_DB=0
|
||||
|
||||
# Configuración de Groq (Agente RAG)
|
||||
GROQ_API_KEY=
|
||||
GROQ_MODEL=llama-3.3-70b-versatile
|
||||
RAG_API_URL=http://localhost:8004
|
||||
GROQ_SYSTEM_PROMPT=You are a helpful assistant with access to a RAG system that contains knowledge about the game Last War.
|
||||
|
||||
@@ -4,9 +4,12 @@ WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
nano \
|
||||
vim \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir starlette>=0.40.0,\<1.0.0
|
||||
RUN pip install --no-cache-dir -r requirements.txt python-dotenv
|
||||
|
||||
COPY . .
|
||||
|
||||
75
action_plan_groq_ui.md
Normal file
75
action_plan_groq_ui.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Plan de Acción: Panel de Administración de Groq
|
||||
|
||||
## Objetivo
|
||||
Agregar al panel de administración una nueva sección para configurar el agente Groq con integración RAG.
|
||||
|
||||
## 1. Modificaciones en `config.yaml`
|
||||
|
||||
Agregar nueva sección `groq` con los campos:
|
||||
- `api_key`: API key de Groq
|
||||
- `model`: Modelo a usar (default: llama-3.3-70b-versatile)
|
||||
- `rag_url`: URL de la API RAG (default: http://localhost:8004)
|
||||
- `system_prompt`: Prompt del sistema para el agente
|
||||
|
||||
## 2. Modificaciones en `botdiscord/config.py`
|
||||
|
||||
- Agregar funciones para obtener la configuración de Groq:
|
||||
- `get_groq_config()` - Retorna dict con api_key, model, rag_url, system_prompt
|
||||
- `get_groq_api_key()` - Retorna solo la API key
|
||||
- `get_groq_model()` - Retorna el modelo
|
||||
- `get_groq_rag_url()` - Retorna la URL del RAG
|
||||
- `get_groq_system_prompt()` - Retorna el prompt
|
||||
|
||||
## 3. Modificaciones en `.env.example`
|
||||
|
||||
Agregar variables de entorno:
|
||||
```bash
|
||||
GROQ_API_KEY=
|
||||
GROQ_MODEL=llama-3.3-70b-versatile
|
||||
RAG_API_URL=http://localhost:8004
|
||||
GROQ_SYSTEM_PROMPT=You are a helpful assistant...
|
||||
```
|
||||
|
||||
## 4. Modificaciones en `panel/main.py`
|
||||
|
||||
- Agregar endpoint `/groq` (GET) - Página de configuración
|
||||
- Agregar endpoint `/groq/save` (POST) - Guardar configuración
|
||||
- Modificar `get_config()` para incluir la config de Groq
|
||||
|
||||
## 5. Crear plantilla `panel/templates/groq.html`
|
||||
|
||||
- Formulario con campos:
|
||||
- API Key (campo de texto)
|
||||
- Modelo (dropdown con modelos gratuitos de Groq)
|
||||
- RAG URL (campo de texto)
|
||||
- System Prompt (textarea)
|
||||
- Botón para guardar
|
||||
- Información sobre modelos gratuitos
|
||||
|
||||
## 6. Modelos Gratuitos de Groq a incluir en el dropdown
|
||||
|
||||
- `llama-3.3-70b-versatile` (recomendado)
|
||||
- `llama-3.1-70b-versatile`
|
||||
- `llama-3.1-8b-instant`
|
||||
- `mixtral-8x7b-32768`
|
||||
- `gemma2-9b-it`
|
||||
|
||||
## 7. Agregar enlace en dashboard
|
||||
|
||||
Agregar link/nav al panel de Groq en las plantillas existentes.
|
||||
|
||||
## Variables de Entorno para Docker Compose
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- GROQ_API_KEY=${GROQ_API_KEY}
|
||||
- GROQ_MODEL=${GROQ_MODEL}
|
||||
- RAG_API_URL=${RAG_API_URL}
|
||||
- GROQ_SYSTEM_PROMPT=${GROQ_SYSTEM_PROMPT}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Tiempo estimado**: 30-45 minutos
|
||||
**Archivos a modificar**: 6
|
||||
**Archivos a crear**: 1
|
||||
@@ -195,6 +195,24 @@ async def configurar(interaction: discord.Interaction):
|
||||
view = ConfigView(interaction.guild_id, "discord")
|
||||
await interaction.response.send_message("Selecciona idiomas habilitados:", view=view, ephemeral=True)
|
||||
|
||||
@bot.tree.command(name="rag", description="Busca información sobre Last War en la base de conocimientos")
|
||||
async def rag_command(interaction: discord.Interaction, *, pregunta: str):
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
from botdiscord.groq_agent import chat_with_rag
|
||||
result = await chat_with_rag(pregunta)
|
||||
|
||||
response = result.get("response", "Sin respuesta")
|
||||
sources = result.get("sources", [])
|
||||
|
||||
embed = discord.Embed(title="🔍 Last War Knowledge", description=response, color=discord.Color.blue())
|
||||
|
||||
if sources:
|
||||
source_text = "\n".join([f"• {s.get('title', 'N/A')}" for s in sources[:3]])
|
||||
embed.add_field(name="Sources", value=source_text, inline=False)
|
||||
|
||||
await interaction.followup.send(embed=embed, ephemeral=True)
|
||||
|
||||
def run_discord_bot():
|
||||
token = get_discord_token()
|
||||
bot.run(token)
|
||||
|
||||
@@ -45,6 +45,11 @@ def load_config(config_path: str = None) -> dict:
|
||||
{"code": "it", "name": "Italiano"},
|
||||
{"code": "pt", "name": "Português"}
|
||||
]
|
||||
},
|
||||
"groq": {
|
||||
"api_key": "",
|
||||
"model": "llama-3.3-70b-versatile",
|
||||
"rag_url": "http://localhost:8004"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +79,9 @@ def load_config(config_path: str = None) -> dict:
|
||||
"DB_USER": ("database", "user"),
|
||||
"DB_PASSWORD": ("database", "password"),
|
||||
"DB_NAME": ("database", "name"),
|
||||
"GROQ_API_KEY": ("groq", "api_key"),
|
||||
"GROQ_MODEL": ("groq", "model"),
|
||||
"RAG_API_URL": ("groq", "rag_url"),
|
||||
}
|
||||
|
||||
for env_key, (section, key, *transform) in env_mappings.items():
|
||||
@@ -122,3 +130,15 @@ def get_db_type() -> str:
|
||||
|
||||
def get_web_config() -> dict:
|
||||
return get_config().get("web", {})
|
||||
|
||||
def get_groq_config() -> dict:
|
||||
return get_config().get("groq", {})
|
||||
|
||||
def get_groq_api_key() -> str:
|
||||
return get_config().get("groq", {}).get("api_key", "")
|
||||
|
||||
def get_groq_model() -> str:
|
||||
return get_config().get("groq", {}).get("model", "llama-3.3-70b-versatile")
|
||||
|
||||
def get_groq_rag_url() -> str:
|
||||
return get_config().get("groq", {}).get("rag_url", "http://localhost:8004")
|
||||
|
||||
@@ -394,6 +394,12 @@ def set_active_languages(guild_id: int, lang_codes: list):
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_bot_config(key: str) -> str:
|
||||
return get_config_value(key)
|
||||
|
||||
def set_bot_config(key: str, value: str):
|
||||
return set_config_value(key, value)
|
||||
|
||||
def get_config_value(key: str) -> str:
|
||||
db_type = get_db_type()
|
||||
|
||||
|
||||
209
botdiscord/groq_agent.py
Normal file
209
botdiscord/groq_agent.py
Normal file
@@ -0,0 +1,209 @@
|
||||
import os
|
||||
import aiohttp
|
||||
from typing import Optional, List, Dict, Any
|
||||
from utils.logger import discord_logger as log
|
||||
|
||||
def _ensure_env_loaded():
|
||||
"""Asegura que las variables de entorno estén cargadas."""
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
def _get_groq_config():
|
||||
_ensure_env_loaded()
|
||||
try:
|
||||
from botdiscord.config import get_groq_config
|
||||
return get_groq_config()
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _load_config():
|
||||
_ensure_env_loaded()
|
||||
# Primero tomar de variables de entorno (Docker), luego de config.yaml como backup
|
||||
env_key = os.getenv("GROQ_API_KEY", "")
|
||||
env_model = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
|
||||
env_rag_url = os.getenv("RAG_API_URL", "http://localhost:8004")
|
||||
|
||||
cfg = _get_groq_config()
|
||||
|
||||
return {
|
||||
"api_key": env_key or cfg.get("api_key", ""),
|
||||
"model": env_model or cfg.get("model", "llama-3.3-70b-versatile"),
|
||||
"rag_url": env_rag_url or cfg.get("rag_url", "http://localhost:8004")
|
||||
}
|
||||
|
||||
def _get_system_prompt():
|
||||
try:
|
||||
from botdiscord.database import get_bot_config
|
||||
prompt = get_bot_config("groq_system_prompt")
|
||||
if prompt:
|
||||
return prompt
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
prompt_file = os.path.join(os.path.dirname(__file__), "..", "prompt_general.md")
|
||||
if os.path.exists(prompt_file):
|
||||
with open(prompt_file, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return """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."""
|
||||
|
||||
_config_cache = None
|
||||
|
||||
def get_config():
|
||||
global _config_cache
|
||||
if _config_cache is None:
|
||||
_config_cache = _load_config()
|
||||
|
||||
log.info(f"Groq Config loaded - API Key: {'set' if _config_cache.get('api_key') else 'NOT SET'}, Model: {_config_cache.get('model')}, RAG URL: {_config_cache.get('rag_url')}")
|
||||
return _config_cache
|
||||
|
||||
def reload_config():
|
||||
global _config_cache
|
||||
_config_cache = _load_config()
|
||||
|
||||
async def query_rag(question: str, top_k: int = 3) -> Dict[str, Any]:
|
||||
"""Consulta la API RAG y retorna la respuesta."""
|
||||
config = get_config()
|
||||
rag_url = config.get("rag_url", "http://localhost:8004")
|
||||
|
||||
log.info(f"Querying RAG at {rag_url} with question: {question[:50]}...")
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{rag_url}/query",
|
||||
json={"question": question, "top_k": top_k},
|
||||
timeout=30
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
return await resp.json()
|
||||
else:
|
||||
log.error(f"RAG API error: {resp.status}")
|
||||
return {"answer": "Error querying knowledge base", "sources": [], "cached": False}
|
||||
except Exception as e:
|
||||
log.error(f"RAG query failed: {e}")
|
||||
return {"answer": "Error connecting to knowledge base", "sources": [], "cached": False}
|
||||
|
||||
def build_messages(question: str, rag_context: Optional[Dict[str, Any]] = None) -> List[Dict[str, str]]:
|
||||
"""Construye los mensajes para la API de Groq."""
|
||||
system_prompt = _get_system_prompt()
|
||||
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
|
||||
if rag_context:
|
||||
context_text = f"Context from knowledge base:\n{rag_context.get('answer', '')}\n\nSources: {rag_context.get('sources', [])}"
|
||||
messages.append({"role": "system", "content": context_text})
|
||||
|
||||
messages.append({"role": "user", "content": question})
|
||||
return messages
|
||||
|
||||
async def chat_with_rag(question: str, use_rag: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
Procesa una pregunta usando Groq con RAG opcional.
|
||||
|
||||
Args:
|
||||
question: La pregunta del usuario
|
||||
use_rag: Si True, consulta la base de conocimientos primero
|
||||
|
||||
Returns:
|
||||
Dict con 'response' (respuesta final), 'rag_result' (resultado RAG), 'sources' (fuentes)
|
||||
"""
|
||||
config = get_config()
|
||||
api_key = config.get("api_key")
|
||||
model = config.get("model", "llama-3.3-70b-versatile")
|
||||
|
||||
if not api_key:
|
||||
return {
|
||||
"response": "Error: GROQ_API_KEY not configured",
|
||||
"rag_result": None,
|
||||
"sources": []
|
||||
}
|
||||
|
||||
rag_result = None
|
||||
if use_rag:
|
||||
rag_result = await query_rag(question, top_k=2)
|
||||
log.info(f"RAG result length: {len(str(rag_result.get('answer', '')))} chars")
|
||||
|
||||
if not rag_result or not rag_result.get('answer'):
|
||||
# No hay contexto del RAG, responder directamente
|
||||
return {
|
||||
"response": "¡Mi radar no detectó información relevante en la base de datos, recruit! 🤔 No encontré información sobre eso.",
|
||||
"rag_result": rag_result,
|
||||
"sources": []
|
||||
}
|
||||
|
||||
messages = build_messages(question, rag_result)
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 1024
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
"https://api.groq.com/openai/v1/chat/completions",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=30
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
choice = data.get("choices", [{}])[0]
|
||||
response = choice.get("message", {}).get("content", "")
|
||||
|
||||
sources = []
|
||||
if rag_result and rag_result.get("sources"):
|
||||
sources = rag_result["sources"]
|
||||
|
||||
return {
|
||||
"response": response,
|
||||
"rag_result": rag_result,
|
||||
"sources": sources
|
||||
}
|
||||
else:
|
||||
error = await resp.text()
|
||||
log.error(f"Groq API error: {resp.status} - {error}")
|
||||
return {
|
||||
"response": "Error processing request",
|
||||
"rag_result": rag_result,
|
||||
"sources": []
|
||||
}
|
||||
except Exception as e:
|
||||
log.error(f"Groq request failed: {e}")
|
||||
return {
|
||||
"response": "Error connecting to AI service",
|
||||
"rag_result": rag_result,
|
||||
"sources": []
|
||||
}
|
||||
@@ -291,6 +291,33 @@ async def handle_sticker(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
async def handle_video_note(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
return
|
||||
|
||||
async def rag_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if not context.args:
|
||||
await update.message.reply_text(
|
||||
"Uso: /rag <pregunta>\n\n"
|
||||
"Ejemplo: /rag How to get heroes?"
|
||||
)
|
||||
return
|
||||
|
||||
pregunta = " ".join(context.args)
|
||||
await update.message.reply_text("🔍 Buscando en la base de conocimientos...")
|
||||
|
||||
from botdiscord.groq_agent import chat_with_rag
|
||||
result = await chat_with_rag(pregunta)
|
||||
|
||||
response = result.get("response", "Sin respuesta")
|
||||
sources = result.get("sources", [])
|
||||
|
||||
text = f"🔍 *Last War Knowledge*\n\n{response}"
|
||||
|
||||
if sources:
|
||||
text += "\n\n*Fuentes:*\n"
|
||||
for s in sources[:3]:
|
||||
title = s.get("title", "N/A")
|
||||
text += f"• {title}\n"
|
||||
|
||||
await update.message.reply_text(text, parse_mode="Markdown")
|
||||
|
||||
def run_telegram_bot():
|
||||
try:
|
||||
from botdiscord.database import init_db
|
||||
@@ -323,6 +350,7 @@ def run_telegram_bot():
|
||||
|
||||
application.add_handler(CommandHandler("start", start))
|
||||
application.add_handler(CommandHandler("idiomas", languages_command))
|
||||
application.add_handler(CommandHandler("rag", rag_command))
|
||||
application.add_handler(CallbackQueryHandler(translation_callback, pattern="^trans_"))
|
||||
application.add_handler(MessageHandler(filters.PHOTO, handle_photo))
|
||||
application.add_handler(MessageHandler(filters.Document.ALL, handle_document))
|
||||
|
||||
@@ -37,3 +37,7 @@ languages:
|
||||
- code: pt
|
||||
name: Português
|
||||
flag: "🇵🇹"
|
||||
groq:
|
||||
api_key:
|
||||
model: llama-3.3-70b-versatile
|
||||
rag_url: http://localhost:8004
|
||||
|
||||
@@ -1,14 +1,148 @@
|
||||
version: '3.8'
|
||||
|
||||
name: bots-translation
|
||||
services:
|
||||
redis-cache:
|
||||
image: redis:alpine
|
||||
container_name: redis-translation-cache
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-translation_redis_secret}
|
||||
bots-translation:
|
||||
cpu_shares: 90
|
||||
command: []
|
||||
container_name: bots-translation
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
reservations:
|
||||
devices: []
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
environment:
|
||||
- ADMIN_PASSWORD=MiPo6425@@
|
||||
- ADMIN_USERNAME=nickpons666
|
||||
- DATABASE_PATH=/app/data/bots_config.db
|
||||
- DB_HOST=10.10.4.17
|
||||
- DB_NAME=traductor_bots
|
||||
- DB_PASSWORD=MiPo6425@@
|
||||
- DB_PORT=3390
|
||||
- DB_TYPE=mysql
|
||||
- DB_USER=nickpons666
|
||||
- DISCORD_TOKEN=MTM4NTc5MDM0NDU5NDk4NTA2MQ.GvobiS.TRQM9dX7vDjmuGVa3Ckp6YRtGEWxdW0gBDbvCI
|
||||
- LIBRETRANSLATE_URL=https://translate-pons.duckdns.org/translate
|
||||
- TELEGRAM_TOKEN=8469229183:AAEVIV5e7rjDXKNgFTX0dnCW6JWB88X4p2I
|
||||
- WEB_HOST=0.0.0.0
|
||||
- WEB_PORT=8000
|
||||
- PYTHONDONTWRITEBYTECODE=1
|
||||
- PYTHONOPTIMIZE=1
|
||||
- TZ=America/Mexico_City
|
||||
- REDIS_HOST=10.10.4.17
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=translation_redis_secret
|
||||
- REDIS_DB=0
|
||||
- GROQ_API_KEY=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
|
||||
- GROQ_MODEL=llama-3.3-70b-versatile
|
||||
- RAG_API_URL=http://10.10.4.17:8004
|
||||
hostname: bots-translation
|
||||
image: registry-pons.duckdns.org/bots-translation:redis
|
||||
labels:
|
||||
icon: https://www.ruthlessreviews.com/wp-content/uploads/2025/12/last-war-image.jpg
|
||||
ports:
|
||||
- "6379:6379"
|
||||
- target: 8000
|
||||
published: "8091"
|
||||
protocol: tcp
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /media/DATOS/AppData/redis:/data
|
||||
mem_limit: 256m
|
||||
mem_reservation: 128m
|
||||
- type: bind
|
||||
source: /media/DATOS/AppData/bots-translation/data
|
||||
target: /app/data
|
||||
- type: bind
|
||||
source: /media/DATOS/AppData/bots-translation/data/logs
|
||||
target: /app/data/logs
|
||||
x-casaos:
|
||||
envs:
|
||||
- container: DISCORD_TOKEN
|
||||
description:
|
||||
en_us: Token del bot de Discord
|
||||
- container: TELEGRAM_TOKEN
|
||||
description:
|
||||
en_us: Token del bot de Telegram
|
||||
- container: LIBRETRANSLATE_URL
|
||||
description:
|
||||
en_us: URL de LibreTranslate
|
||||
- container: ADMIN_USERNAME
|
||||
description:
|
||||
en_us: Usuario admin del panel
|
||||
- container: ADMIN_PASSWORD
|
||||
description:
|
||||
en_us: Contraseña admin del panel
|
||||
- container: DB_TYPE
|
||||
description:
|
||||
en_us: Tipo de base de datos (sqlite/mysql)
|
||||
- container: DB_HOST
|
||||
description:
|
||||
en_us: Host de MySQL
|
||||
- container: DB_PORT
|
||||
description:
|
||||
en_us: Puerto de MySQL
|
||||
- container: DB_USER
|
||||
description:
|
||||
en_us: Usuario de MySQL
|
||||
- container: DB_PASSWORD
|
||||
description:
|
||||
en_us: Contraseña de MySQL
|
||||
- container: DB_NAME
|
||||
description:
|
||||
en_us: Nombre de la base de datos MySQL
|
||||
- container: DATABASE_PATH
|
||||
description:
|
||||
en_us: Ruta de la base de datos SQLite (si DB_TYPE=sqlite)
|
||||
- container: REDIS_HOST
|
||||
description:
|
||||
en_us: IP del servidor Redis (OMV)
|
||||
- container: REDIS_PORT
|
||||
description:
|
||||
en_us: Puerto de Redis
|
||||
- container: REDIS_PASSWORD
|
||||
description:
|
||||
en_us: Contraseña de Redis
|
||||
- container: REDIS_DB
|
||||
description:
|
||||
en_us: Número de base de datos Redis
|
||||
- container: GROQ_API_KEY
|
||||
description:
|
||||
en_us: API Key de Groq para el agente RAG
|
||||
- container: GROQ_MODEL
|
||||
description:
|
||||
en_us: Modelo de Groq a usar
|
||||
- container: RAG_API_URL
|
||||
description:
|
||||
en_us: URL del servidor RAG
|
||||
ports:
|
||||
- container: "8000"
|
||||
description:
|
||||
en_us: Puerto del panel web
|
||||
volumes:
|
||||
- container: /app/data
|
||||
description:
|
||||
en_us: Datos de los bots y base de datos
|
||||
devices: []
|
||||
cap_add: []
|
||||
network_mode: bridge
|
||||
privileged: false
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
author: nickpons666
|
||||
category: Utility
|
||||
description:
|
||||
en_us: Bots de traducción para Discord y Telegram con panel web
|
||||
developer: nickpons666
|
||||
hostname: ""
|
||||
icon: https://www.ruthlessreviews.com/wp-content/uploads/2025/12/last-war-image.jpg
|
||||
index: /
|
||||
is_uncontrolled: false
|
||||
main: bots-translation
|
||||
port_map: "8091"
|
||||
scheme: http
|
||||
store_app_id: bots-translation
|
||||
tagline:
|
||||
en_us: Bots de Traducción
|
||||
title:
|
||||
custom: ""
|
||||
en_us: Bots de Traducción
|
||||
|
||||
@@ -1,37 +1,139 @@
|
||||
version: '3.8'
|
||||
|
||||
name: bots-translation
|
||||
services:
|
||||
bots-translation:
|
||||
build: .
|
||||
cpu_shares: 90
|
||||
command: []
|
||||
container_name: bots-translation
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8091:8000"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
reservations:
|
||||
devices: []
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
environment:
|
||||
- DISCORD_TOKEN=${DISCORD_TOKEN}
|
||||
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
|
||||
- LIBRETRANSLATE_URL=${LIBRETRANSLATE_URL}
|
||||
- ADMIN_PASSWORD=MiPo6425@@
|
||||
- ADMIN_USERNAME=nickpons666
|
||||
- DATABASE_PATH=/app/data/bots_config.db
|
||||
- DB_HOST=10.10.4.17
|
||||
- DB_NAME=traductor_bots
|
||||
- DB_PASSWORD=MiPo6425@@
|
||||
- DB_PORT=3390
|
||||
- DB_TYPE=mysql
|
||||
- DB_USER=nickpons666
|
||||
- DISCORD_TOKEN=MTM4NTc5MDM0NDU5NDk4NTA2MQ.GvobiS.TRQM9dX7vDjmuGVa3Ckp6YRtGEWxdW0gBDbvCI
|
||||
- LIBRETRANSLATE_URL=https://translate-pons.duckdns.org/translate
|
||||
- TELEGRAM_TOKEN=8469229183:AAEVIV5e7rjDXKNgFTX0dnCW6JWB88X4p2I
|
||||
- WEB_HOST=0.0.0.0
|
||||
- WEB_PORT=8000
|
||||
- ADMIN_USERNAME=${ADMIN_USERNAME}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
- DB_TYPE=mysql
|
||||
- DB_HOST=${DB_HOST}
|
||||
- DB_PORT=${DB_PORT}
|
||||
- DB_USER=${DB_USER}
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- DB_NAME=${DB_NAME}
|
||||
- PYTHONDONTWRITEBYTECODE=1
|
||||
- PYTHONOPTIMIZE=1
|
||||
# Redis caché compartida
|
||||
- REDIS_HOST=${REDIS_HOST}
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
env_file:
|
||||
- .env
|
||||
mem_limit: 512m
|
||||
mem_reservation: 256m
|
||||
pids_limit: 50
|
||||
- TZ=America/Mexico_City
|
||||
- REDIS_HOST=10.10.4.17
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=translation_redis_secret
|
||||
- REDIS_DB=0
|
||||
- GROQ_API_KEY=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
|
||||
- GROQ_MODEL=llama-3.3-70b-versatile
|
||||
- RAG_API_URL=http://10.10.4.17:8004
|
||||
hostname: bots-translation
|
||||
image: registry-pons.duckdns.org/bots-translation:latest
|
||||
labels:
|
||||
icon: https://www.ruthlessreviews.com/wp-content/uploads/2025/12/last-war-image.jpg
|
||||
ports:
|
||||
- target: 8000
|
||||
published: "8091"
|
||||
protocol: tcp
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /DATA/AppData/bots-translation/data
|
||||
target: /app/data
|
||||
- type: bind
|
||||
source: /DATA/AppData/bots-translation/data/logs
|
||||
target: /app/data/logs
|
||||
x-casaos:
|
||||
envs:
|
||||
- container: DISCORD_TOKEN
|
||||
description:
|
||||
en_us: Token del bot de Discord
|
||||
- container: TELEGRAM_TOKEN
|
||||
description:
|
||||
en_us: Token del bot de Telegram
|
||||
- container: LIBRETRANSLATE_URL
|
||||
description:
|
||||
en_us: URL de LibreTranslate
|
||||
- container: ADMIN_USERNAME
|
||||
description:
|
||||
en_us: Usuario admin del panel
|
||||
- container: ADMIN_PASSWORD
|
||||
description:
|
||||
en_us: Contraseña admin del panel
|
||||
- container: DB_TYPE
|
||||
description:
|
||||
en_us: Tipo de base de datos (sqlite/mysql)
|
||||
- container: DB_HOST
|
||||
description:
|
||||
en_us: Host de MySQL
|
||||
- container: DB_PORT
|
||||
description:
|
||||
en_us: Puerto de MySQL
|
||||
- container: DB_USER
|
||||
description:
|
||||
en_us: Usuario de MySQL
|
||||
- container: DB_PASSWORD
|
||||
description:
|
||||
en_us: Contraseña de MySQL
|
||||
- container: DB_NAME
|
||||
description:
|
||||
en_us: Nombre de la base de datos MySQL
|
||||
- container: DATABASE_PATH
|
||||
description:
|
||||
en_us: Ruta de la base de datos SQLite (si DB_TYPE=sqlite)
|
||||
- container: REDIS_HOST
|
||||
description:
|
||||
en_us: IP del servidor Redis (OMV)
|
||||
- container: REDIS_PORT
|
||||
description:
|
||||
en_us: Puerto de Redis
|
||||
- container: REDIS_PASSWORD
|
||||
description:
|
||||
en_us: Contraseña de Redis
|
||||
- container: REDIS_DB
|
||||
description:
|
||||
en_us: Número de base de datos Redis
|
||||
ports:
|
||||
- container: "8000"
|
||||
description:
|
||||
en_us: Puerto del panel web
|
||||
volumes:
|
||||
- container: /app/data
|
||||
description:
|
||||
en_us: Datos de los bots y base de datos
|
||||
devices: []
|
||||
cap_add: []
|
||||
network_mode: bridge
|
||||
privileged: false
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
author: nickpons666
|
||||
category: Utility
|
||||
description:
|
||||
en_us: Bots de traducción para Discord y Telegram con panel web
|
||||
developer: nickpons666
|
||||
hostname: ""
|
||||
icon: https://www.ruthlessreviews.com/wp-content/uploads/2025/12/last-war-image.jpg
|
||||
index: /
|
||||
is_uncontrolled: false
|
||||
main: bots-translation
|
||||
port_map: "8091"
|
||||
scheme: http
|
||||
store_app_id: bots-translation
|
||||
tagline:
|
||||
en_us: Bots de Traducción
|
||||
title:
|
||||
custom: ""
|
||||
en_us: Bots de Traducción
|
||||
|
||||
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>
|
||||
88
prompt_general.md
Normal file
88
prompt_general.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Prompt para Agente Groq - General Chistoso
|
||||
|
||||
## Instrucciones del Sistema
|
||||
|
||||
Eres el **General Reserves**, el comandante en jefe del ejército deLast War: Survival Game. Hablas con tus recruits (jugadores) de manera amigable, respetuosa y con humor militar. Tu objetivo es ayudar a los jugadores con información sobre el juego usando el sistema RAG.
|
||||
|
||||
## Identificación de Idioma
|
||||
|
||||
- Al recibir una pregunta, **detecta automáticamente el idioma** en que está escrita
|
||||
- Si el idioma no es inglés, **tradúcela al inglés** antes de consultar el RAG
|
||||
- Al recibir la respuesta del RAG, **tradúcela al idioma original** de la pregunta
|
||||
- Usa el mismo idioma que el jugador para responder
|
||||
|
||||
## Comportamiento
|
||||
|
||||
### Saludo Inicial
|
||||
Siempre saluda de manera respetuosa y militar:
|
||||
- "¡A la orden, recruit! 🎖️"
|
||||
- "¡Bienvenido a la base, soldado! 🫡"
|
||||
- "¡Reporting for duty! ¿En qué puedo ayudarte?"
|
||||
|
||||
### Tono
|
||||
- Amigable pero profesional
|
||||
- Humor militar sutil (no ofensivo)
|
||||
- Respetuoso siempre
|
||||
- Entusiasta sobre el juego
|
||||
|
||||
### Manejo de Información Desconocida
|
||||
Si el sistema RAG no tiene información sobre lo asked:
|
||||
|
||||
NO INVENTES NUNCA INFORMACIÓN. En su lugar, di algo gracioso pero gentil como:
|
||||
|
||||
- "¡Vaya! Eso parece ser información classificada que aún no hemos descifrado en la base de datos, recruit. 📁"
|
||||
- "Hmm, eso no está en mi manual de campo. ¡Pero buena pregunta! 🫡"
|
||||
- "¡Interrupt! Mi radar de información no detectó eso. Quizás sea un secreto de Estado... o solo no lo sé. 😅"
|
||||
- "¡Recruit! Eso parece terreno inexplorado. Mi base de datos dice: 'Sin información disponible'. 🙈"
|
||||
|
||||
## Restricciones Importantes
|
||||
|
||||
1. **SOLO responde sobre Last War: Survival Game** - El juego del sistema RAG
|
||||
2. **NUNCA inventes información** - Si no hay datos,dil o que no tienes esa información
|
||||
3. **Usa el RAG siempre** - Consulta la base de conocimientos antes de responder
|
||||
4. **Sé honesto** - Si la búsqueda no arroja resultados, dilo
|
||||
|
||||
## Ejemplos de Interacción
|
||||
|
||||
### Ejemplo 1 - англи́йский
|
||||
**Usuario:** Как получить героев?
|
||||
**Respuesta:** "¡Reporting for duty! 🎖️ Buscando en los archivos...
|
||||
|
||||
¡Encontré la información! Los héroes se obtienen principalmente através de:
|
||||
|
||||
1. **Recompensas diarias** - Completa misiones diarias para obtener fragmentos
|
||||
2. **Eventos especiales** - Participa en eventos limitados
|
||||
3. **Gashapón** - Usa núcleos para obtener héroes
|
||||
4. **Tienda del clan** - Compra con monedas del clan
|
||||
|
||||
¡Que tus tropas tengan éxito, recruit! 🛡️"
|
||||
|
||||
### Ejemplo 2 - Español
|
||||
**Usuario:** ¿Cuál es el mejor héroe?
|
||||
**Respuesta:** "¡A la orden, soldado! 🫡 Permíteme revisar los archivos...
|
||||
|
||||
Según la inteligencia de la base de datos, los mejores héroes dependen de tu estilo de juego:
|
||||
|
||||
- **DPS:** Murphy, Governor
|
||||
- **Tanque:** dummy, Brawler
|
||||
- **Soporte:** Priest, Medic
|
||||
|
||||
Pero recuerda: ¡el mejor héroe es el que mejor se adapta a tu estrategia! 📊"
|
||||
|
||||
### Ejemplo 3 - Información no disponible
|
||||
**Usuario:** ¿Cómo hacer trampa en el juego?
|
||||
**Respuesta:** "¡Alto ahí, recruit! 🚫
|
||||
|
||||
¡Mi radar detectó una pregunta classificada! No puedo ayudarte con eso. Además, jugar limpio es lo que nos hace verdaderos soldados. 🫡
|
||||
|
||||
Pero si tienes otras preguntas sobre el juego, ¡aquí estoy para servirte! ¿Qué más quieres saber?"
|
||||
|
||||
---
|
||||
|
||||
## Formato de Respuesta
|
||||
|
||||
1. Saludo militar amigable
|
||||
2. Información encontrada o explicación de que no se encontró
|
||||
3. Cierre friendly
|
||||
|
||||
¡Reporting for duty! 🎖️
|
||||
@@ -5,6 +5,7 @@ pyyaml>=6.0
|
||||
fastapi>=0.100.0
|
||||
uvicorn>=0.23.0
|
||||
jinja2>=3.1.0
|
||||
starlette>=0.40.0,<1.0.0
|
||||
pydantic>=2.0.0
|
||||
python-dotenv>=1.0.0
|
||||
python-multipart>=0.0.9
|
||||
|
||||
Reference in New Issue
Block a user