| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- import json
- from typing import List, Dict
- from fastapi import HTTPException
- from openai import OpenAI
- from logging import getLogger
- from rapidfuzz import process, fuzz # Recomendación: pip install rapidfuzz para búsquedas rápidas
- # Asumiendo que estas importaciones existen en tu proyecto
- from config.settings import OPENAI_API_KEY
- from models.user import User
- from services.data_service import data_bg_loaded
- from services.openai_service.openai_tools import tools_list, tools
- openai_client = OpenAI(api_key=OPENAI_API_KEY)
- logger = getLogger(__name__)
- # --- OPTIMIZACIÓN 1: MINI-RAG EN MEMORIA ---
- # En lugar de enviar TODO, buscamos lo más relevante a la última pregunta.
- def get_relevant_context(last_user_message: str, dataset: List[Dict], top_k: int = 5) -> str:
- if not last_user_message or not dataset:
- return ""
-
- # Extraemos solo las preguntas para buscar coincidencias
- questions = [item["q"] for item in dataset]
-
- # Buscamos las 'top_k' preguntas más similares a lo que dijo el usuario
- results = process.extract(last_user_message, questions, scorer=fuzz.WRatio, limit=top_k)
-
- # Construimos un contexto limpio en formato Markdown (consume menos tokens que JSON)
- context_str = "### INFORMACIÓN RELEVANTE DEL MENÚ:\n"
- for _, score, index in results:
- if score > 50: # Umbral de relevancia
- item = dataset[index]
- context_str += f"- P: {item['q']}\n R: {item['ans']}\n"
-
- return context_str
- async def generate_completion(messages_array: List[dict], user: User) -> str:
- if not OPENAI_API_KEY:
- raise HTTPException(status_code=500, detail="OpenAI API key missing.")
- # 1. Obtener el último mensaje del usuario para buscar contexto
- last_message_content = messages_array[-1].get("message", "") if messages_array else ""
-
- # 2. Filtrar la base de datos (Crucial para escalar)
- # Si la DB es gigante, aquí llamarías a tu Vector DB (pgvector).
- # Por ahora, usamos el filtro inteligente en memoria.
- dynamic_context = get_relevant_context(last_message_content, data_bg_loaded)
- # 3. Construir el System Prompt
- system_prompt = f"""
- ### ROL
- Eres IAKlein, el asistente virtual del Bar Klein 🍻. Amigo carismático, breve y experto.
- ### DIRECTRICES
- - **Estilo:** Chat rápido, emojis, tono relajado.
- - **Regla:** NO tomas pedidos. Diles: "Para pedir, Justo en el boton de abajo esta la tienda 😉".
- - **Datos:** Usa SOLO la información provista abajo. Si no sabes, dilo con gracia.
- {dynamic_context}
- """
- # 4. Formatear historial correctamente para la API de Chat
- # Convertimos tu array de dicts al formato nativo de OpenAI
- api_messages = [{"role": "system", "content": system_prompt}]
-
- # Tomamos los últimos 10 mensajes para mantener el contexto ligero
- for msg in messages_array[-10:]:
- role = "user" # Asumimos user por defecto, ajusta según tu lógica si tienes mensajes del bot guardados
- content = f"<{msg.get('username', 'User')}> {msg.get('message', '')}"
- api_messages.append({"role": role, "content": content})
- try:
- # Primera llamada al modelo
- completion = openai_client.chat.completions.create(
- model="gpt-4o-mini",
- messages=api_messages,
- temperature=0.7,
- tools=tools_list,
- tool_choice="auto",
- )
- message = completion.choices[0].message
- # --- OPTIMIZACIÓN 2: MANEJO DE TOOLS ROBUSTO ---
- if message.tool_calls:
- logger.info(f"Tool calls detected: {message.tool_calls}")
-
- # Agregamos la intención de llamada al historial
- api_messages.append(message)
- for tool_call in message.tool_calls:
- function_name = tool_call.function.name
- if function_name in tools:
- # Ejecutar herramienta
- func_args = json.loads(tool_call.function.arguments)
- tool_result = tools[function_name](name=user.name, email=user.email, **func_args)
-
- # Agregar el resultado de la herramienta al historial
- api_messages.append({
- "role": "tool",
- "tool_call_id": tool_call.id,
- "content": str(tool_result)
- })
- else:
- logger.warning(f"Tool {function_name} not found.")
- # SEGUNDA LLAMADA: Para que la IA interprete el resultado de la tool y responda al usuario
- final_completion = openai_client.chat.completions.create(
- model="gpt-4o-mini",
- messages=api_messages
- )
- return final_completion.choices[0].message.content
- return message.content
- except Exception as e:
- logger.error(f"OpenAI Error: {e}")
- return "¡Ups! Se me cayó la bandeja con las cervezas. Pregúntame de nuevo en un segundo. 🍻"
- def admin_completion(prompt: str, messages_array: List[dict]) -> str:
- """Generate OpenAI admin completion"""
- messages = list(map(lambda x: f'<{x.get("username", "unknown")}> {x.get("message", "")}', messages_array))
- if not OPENAI_API_KEY:
- logger.error("Error: OpenAI API key is not configured.")
- raise HTTPException(status_code=500, detail="OpenAI API key not configured on server.")
- try:
- completion = openai_client.chat.completions.create(
- model="gpt-4o-mini",
- messages=[
- {"role": "system", "content": f"""
- IAKlein es el asistente oficial del bar Klein 🍻 y siempre se comunica en un estilo de chat corto, tipo mensajería o IRC, con emojis y mucho carisma. Su única función es entregar avisos oficiales del Biergarten Klein y no debe inventar ni agregar información extra. Los mensajes siempre deben integrarse a la conversación en curso, usando únicamente el chat como contexto para sonar naturales y sorpresivos. Cada aviso debe anunciarse con frases coloquiales como: “desde arriba me cuentan que…”, “me dicen que les pase el dato…”, “me informan que…”, o “me están pidiendo que diga que…” 😉. El tono tiene que ser siempre claro, positivo y cercano, asegurándose de transmitir toda la información sin omitir nada.
- escribe el mensaje usando los ultimos mensajes de la siguiente lista por ejemplo "hablando de ...(conexion con el tema).. me dicen que...(mensaje oficial)..." o "me cuentan que...(mensaje oficial)..., hablando de ...(conexion con el tema)..."
- mensajes: {messages}"""},
- {"role": "user", "content": prompt}
- ],
- temperature=0.7,
- )
- response_content = completion.choices[0].message.content
- return response_content if response_content else "-1"
- except Exception as e:
- logger.error(f"Error calling OpenAI for admin completion: {e}")
- raise HTTPException(status_code=500, detail="Error al procesar tu solicitud con OpenAI.")
|