Implementación del cliente Ollama y su credencial, integración de logging en base de datos, y mejoras en la gestión de herramientas MCP.
This commit is contained in:
+51
-30
@@ -11,29 +11,43 @@ from fastmcp.client.transports import StreamableHttpTransport
|
||||
from fastmcp.client import Client
|
||||
from src.Llms.MCPs.McpClient import MCPClient # ya tienes esta clase
|
||||
from src.Llms.MCPs.McpClient_Registry import ClientRegistry # o ajusta según tu estructura
|
||||
from src.Credenciales.ollama_credencial import OllamaCredencial
|
||||
from src.ConexionApis.Ollama_cliente import OllamaCliente
|
||||
from src.Llms.Modelos.Ollama_model import ModeloOllama
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
# Usar Credencial openai
|
||||
# # Usar Credencial openai
|
||||
|
||||
conexion_admin = PostgresConexion(db_credencial)
|
||||
repo = OpenAICredencialRepo(conexion_admin)
|
||||
credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||
cliente = OpenAICliente(credencial_openai)
|
||||
# conexion_admin = PostgresConexion(db_credencial)
|
||||
# repo = OpenAICredencialRepo(conexion_admin)
|
||||
# credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||
# if credencial_openai is None:
|
||||
# raise ValueError("No se encontró la credencial OpenAI con el ID proporcionado.")
|
||||
# cliente = OpenAICliente(credencial_openai)
|
||||
|
||||
|
||||
# Usar Credencial ollama
|
||||
credencial_ollama = OllamaCredencial(titulo="Ollama")
|
||||
|
||||
# crea el modelo (openai)
|
||||
cliente = OllamaCliente(credencial_ollama)
|
||||
|
||||
modelo = ModeloOpenAI(
|
||||
modelo = ModeloOllama(
|
||||
cliente=cliente,
|
||||
model="gpt-4o",
|
||||
temperature=1,
|
||||
top_p=1.0
|
||||
)
|
||||
model="llama3.1:8b")
|
||||
|
||||
|
||||
|
||||
# # crea el modelo (openai)
|
||||
|
||||
# modelo = ModeloOpenAI(
|
||||
# cliente=cliente,
|
||||
# model="gpt-4o",
|
||||
# temperature=1
|
||||
# )
|
||||
|
||||
# Le otorga memoria
|
||||
|
||||
@@ -70,20 +84,20 @@ async def main():
|
||||
agente2 = AgenteAI(
|
||||
modelo=modelo,
|
||||
nombre="Asistente Inteligente",
|
||||
descripcion="Un asistente conversacional versátil, capaz de resolver problemas, acceder a herramientas y proporcionar respuestas útiles.",
|
||||
system_prompt=(
|
||||
"Eres un asistente inteligente que ayuda al usuario a resolver tareas, responder preguntas y usar herramientas disponibles si es necesario. "
|
||||
"Debes razonar paso a paso, y si se detecta que una herramienta MCP es útil, actúa generando el bloque MCP apropiado sin dar más explicaciones. "
|
||||
"Siempre estructura tus respuestas con claridad, y termina con <END> cuando creas haber completado la tarea."
|
||||
),
|
||||
descripcion="",
|
||||
system_prompt="",
|
||||
# system_prompt = """
|
||||
# Eres un asistente general. No tienes acceso a herramientas externas ni herramientas MCP. No debes mencionar herramientas MCP, servidores ni bloques de código.
|
||||
# Responde de forma clara y amigable a cualquier pregunta general del usuario.
|
||||
# """.strip(),
|
||||
rol="asistente",
|
||||
objetivos=[
|
||||
"Resolver tareas del usuario",
|
||||
"Usar herramientas MCP si es útil",
|
||||
"Responder de forma clara y útil"
|
||||
# "Resolver tareas del usuario",
|
||||
# "Usar herramientas MCP si es útil",
|
||||
# "Responder de forma clara y útil"
|
||||
],
|
||||
|
||||
# max_iterations=3,
|
||||
max_iterations=0,
|
||||
# memoria=memoria,
|
||||
|
||||
mcp=registry # ← ✅ Integración del cliente MCP
|
||||
@@ -92,22 +106,29 @@ async def main():
|
||||
|
||||
# --- FUNCIÓN DE EJECUCIÓN ---
|
||||
async def probar_interaccion_stream():
|
||||
# # 🔌 Conectar a los servidores MCP registrados
|
||||
# await mcp_client.connect_all()
|
||||
print("🧠 Agente iniciado. Escribe 'salir' para terminar.\n")
|
||||
|
||||
print("Respuesta en streaming:\n")
|
||||
respuesta_gen = await agente2.interactuar_en_bucle(
|
||||
"¿Cuál es mi nombre de usuario en este sistema?",
|
||||
stream=True
|
||||
)
|
||||
while True:
|
||||
prompt = input("\n📝 Escribe tu pregunta: ")
|
||||
if prompt.strip().lower() in ("salir", "exit", "quit"):
|
||||
print("\n👋 Hasta pronto.")
|
||||
break
|
||||
|
||||
async for token in respuesta_gen:
|
||||
print(token, end="", flush=True)
|
||||
print("\n💬 Respuesta en streaming:\n")
|
||||
respuesta_gen = await agente2.interactuar_en_bucle(
|
||||
prompt=prompt,
|
||||
stream=True
|
||||
)
|
||||
|
||||
async for token in respuesta_gen:
|
||||
print(token, end="", flush=True)
|
||||
|
||||
|
||||
await probar_interaccion_stream()
|
||||
|
||||
|
||||
|
||||
|
||||
# Ejecutar
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from entrypoint.init_db import db_credencial
|
||||
from src.Logger.logger_db import LoggerDB, logger
|
||||
|
||||
|
||||
LoggerDB(db_credencial, "logger_eventos", created_by="sistema_agente")
|
||||
|
||||
logger.info("Esto solo se verá en la base de datos")
|
||||
logger.error("No aparecerá en la terminal")
|
||||
@@ -0,0 +1,62 @@
|
||||
import requests
|
||||
from src.Credenciales.ollama_credencial import OllamaCredencial
|
||||
|
||||
class OllamaCliente:
|
||||
def __init__(self, credencial: OllamaCredencial):
|
||||
"""
|
||||
Inicializa el cliente de Ollama con una instancia de OllamaCredencial.
|
||||
"""
|
||||
self.credencial = credencial
|
||||
self.base_url = self.credencial.base_url
|
||||
|
||||
# --- Chat Completions ---
|
||||
def chat_completion(self, model: str, messages: list, stream: bool = False, **kwargs):
|
||||
url = f"{self.base_url}/api/chat"
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"stream": stream,
|
||||
**kwargs
|
||||
}
|
||||
response = requests.post(url, json=payload, stream=stream)
|
||||
response.raise_for_status()
|
||||
|
||||
return self._handle_stream(response) if stream else response.json()
|
||||
|
||||
def _handle_stream(self, response):
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
try:
|
||||
parsed = line.decode("utf-8")
|
||||
# Extraer contenido si está en JSON como {"message":{"content":"..."},...}
|
||||
if parsed.startswith("{"):
|
||||
import json
|
||||
data = json.loads(parsed)
|
||||
if "message" in data and "content" in data["message"]:
|
||||
yield data["message"]["content"]
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# --- Text Completion (legacy) ---
|
||||
def completion(self, model: str, prompt: str, **kwargs):
|
||||
url = f"{self.base_url}/api/generate"
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
**kwargs
|
||||
}
|
||||
response = requests.post(url, json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
# --- Embeddings ---
|
||||
def embedding(self, model: str, input: str | list[str], **kwargs):
|
||||
url = f"{self.base_url}/api/embeddings"
|
||||
payload = {
|
||||
"model": model,
|
||||
"prompt": input,
|
||||
**kwargs
|
||||
}
|
||||
response = requests.post(url, json=payload)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
@@ -0,0 +1,20 @@
|
||||
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||
|
||||
class OllamaCredencial:
|
||||
def __init__(self, titulo: str, base_url: str = "http://localhost:11434", id: str = None):
|
||||
"""
|
||||
:param titulo: Nombre descriptivo para esta credencial de Ollama.
|
||||
:param base_url: URL base donde está corriendo el servidor de Ollama (por defecto: localhost).
|
||||
"""
|
||||
self.id = id if id is not None else GeneradorIDUnico("OLLA").generar()
|
||||
self.titulo = titulo
|
||||
self.base_url = base_url.rstrip("/")
|
||||
|
||||
def get_headers(self) -> dict:
|
||||
"""
|
||||
Retorna encabezados para autenticación si se requiere en el futuro.
|
||||
Por defecto, Ollama local no usa headers especiales.
|
||||
"""
|
||||
return {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
+258
-120
@@ -3,6 +3,14 @@ from src.Llms.Memory.Base_MemoryConv import MemoryConvABC
|
||||
from src.Llms.MCPs.McpClient_Registry import ClientRegistry
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Union, AsyncGenerator
|
||||
import re
|
||||
import json
|
||||
|
||||
from entrypoint.init_db import db_credencial
|
||||
from src.Logger.logger_db import LoggerDB, logger
|
||||
LoggerDB(db_credencial, "logger_agentes", created_by="sistema")
|
||||
|
||||
|
||||
|
||||
|
||||
class AgenteAI:
|
||||
@@ -17,7 +25,7 @@ class AgenteAI:
|
||||
max_iterations: int = 1,
|
||||
memoria: Optional[MemoryConvABC] = None,
|
||||
version: str = "1.0.0",
|
||||
mcp: ClientRegistry = None,
|
||||
mcp: Optional[ClientRegistry] = None,
|
||||
output_schema: Optional[dict] = None,
|
||||
):
|
||||
self.modelo = modelo
|
||||
@@ -48,219 +56,349 @@ class AgenteAI:
|
||||
|
||||
|
||||
|
||||
@property
|
||||
async def full_system_prompt(self) -> str:
|
||||
tools_str = await self._obtener_herramientas_disponibles_str()
|
||||
return f"""
|
||||
Eres un agente conversacional con acceso a herramientas MCP (Model Context Protocol).
|
||||
async def generar_system_prompt(self) -> str:
|
||||
|
||||
Tu comportamiento sigue este flujo:
|
||||
info = f"""Eres un agente de texto y te llamas {self.nombre}
|
||||
|
||||
1. **Piensa** para razonar tu decisión.
|
||||
2. **Decide** si:
|
||||
- puedes responder tú mismo,
|
||||
- necesitas más información del usuario,
|
||||
- o necesitas una herramienta MCP.
|
||||
3. **Actúa**:
|
||||
- Cuando uses MCP, termina **solo** con un bloque de código MCP y **nada más**.
|
||||
- Ten en cuenta EXACTAMENTE los parámetros especificados.
|
||||
- **No expliques, no hables después del bloque. Termina tu turno.**
|
||||
### Descripción:
|
||||
{self.descripcion}
|
||||
|
||||
---
|
||||
### Rol:
|
||||
{self.rol}
|
||||
|
||||
# Formato MCP
|
||||
### Objetivos:
|
||||
{chr(10).join(f"- {o}" for o in self.objetivos)}
|
||||
|
||||
```mcp
|
||||
{{
|
||||
"tool": "<nombre_de_la_herramienta>",
|
||||
"input": {{
|
||||
"clave": "valor"
|
||||
}}
|
||||
}}
|
||||
Reglas clave:
|
||||
### System Prompt:
|
||||
{self.system_prompt}
|
||||
|
||||
Razonas antes de actuar.
|
||||
Siempre estructura tus respuestas con claridad, y termina con <END> cuando hayas completado la tarea principal del usuario.
|
||||
""".strip()
|
||||
|
||||
Nunca hables después de un bloque MCP.
|
||||
|
||||
No combines respuestas y herramientas.
|
||||
|
||||
Piensa. Decide. Actúa.
|
||||
|
||||
Herramientas disponibles para usar con MCP:
|
||||
{tools_str}
|
||||
|
||||
""".strip()
|
||||
return info
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async def construir_prompt_usuario(self, prompt_usuario: str) -> str:
|
||||
bloques = []
|
||||
|
||||
# Conseguir las herramientas disponibles
|
||||
if self.mcp:
|
||||
tools_str = await self._obtener_herramientas_disponibles_str()
|
||||
bloques.append(f"### Herramientas disponibles (MCP):\n{tools_str}")
|
||||
bloques.append("""### Instrucciones para actuar con herramientas MCP:
|
||||
Eres un agente conversacional con acceso a herramientas MCP. Cuando el usuario te haga una solicitud, sigue este proceso paso a paso:
|
||||
---
|
||||
🧠 **Piensa**:
|
||||
Reflexiona en voz alta. Explica claramente qué crees que se necesita hacer y por qué.
|
||||
🎯 **Decide**:
|
||||
Elige si puedes resolverlo tú solo, si necesitas más información del usuario, o si una herramienta MCP sería útil.
|
||||
⚙️ **Actúa**:
|
||||
Si decides usar una herramienta, **escribe el bloque MCP justo después**, sin ningún texto extra después del bloque.
|
||||
---
|
||||
### Formato MCP:
|
||||
|
||||
```mcp
|
||||
{
|
||||
"server": "tools",
|
||||
"tool": "get_current_user",
|
||||
"input": {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❗ REGLAS IMPORTANTES:
|
||||
|
||||
- **Puedes pensar y decidir con texto normal**, pero:
|
||||
- El **bloque MCP debe ser lo último** que aparece en tu mensaje.
|
||||
- **NO escribas nada después del bloque MCP.**
|
||||
- **NO pongas `<END>` justo después de usar MCP.**
|
||||
- Solo usa `<END>` cuando:
|
||||
- hayas terminado completamente la tarea del usuario,
|
||||
- e interpretado la salida de las herramientas que usaste.
|
||||
- Puedes hacer múltiples pasos si es necesario: usar una herramienta, esperar su salida, analizarla, usar otra, etc.
|
||||
- Si decides no usar herramientas, simplemente responde como lo harías normalmente.
|
||||
- Si no estás seguro de algo, **pide aclaraciones al usuario** antes de actuar.
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ Ejemplos comunes de errores y cómo evitarlos:
|
||||
|
||||
❌ Incorrecto:
|
||||
```
|
||||
|
||||
{
|
||||
"server": "tools",
|
||||
"tool": "generate_uuid",
|
||||
"input": {}
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
🔴 Este bloque no funcionará. Le falta indicar que es un bloque MCP.
|
||||
|
||||
✅ Correcto:
|
||||
```mcp
|
||||
{
|
||||
"server": "tools",
|
||||
"tool": "generate_uuid",
|
||||
"input": {}
|
||||
}
|
||||
````
|
||||
🔵 Siempre usa ` ```mcp ` (con triple backtick y la palabra `mcp`) antes del JSON. No escribas nada después del bloque.
|
||||
````
|
||||
---
|
||||
|
||||
### ✅ Ejemplo correcto:
|
||||
|
||||
Necesito generar un identificador único para el usuario.
|
||||
Para eso usaré la herramienta `generate_uuid` disponible.
|
||||
|
||||
```mcp
|
||||
{
|
||||
"server": "tools",
|
||||
"tool": "generate_uuid",
|
||||
"input": {}
|
||||
}
|
||||
|
||||
""")
|
||||
|
||||
if self.memoria:
|
||||
historial = self.memoria.cargar_historial_chat()
|
||||
if historial:
|
||||
memoria_str = "\n".join(
|
||||
[f"{msg['role']}: {msg['content']}" for msg in historial]
|
||||
)
|
||||
bloques.append(f"### Memoria del chat:\n{memoria_str}")
|
||||
|
||||
if self.output_schema:
|
||||
schema_str = str(self.output_schema)
|
||||
bloques.append(f"### Salida esperada:\n{schema_str}")
|
||||
|
||||
bloques.append(f"### Prompt del usuario:\n{prompt_usuario}")
|
||||
|
||||
return "\n\n".join(bloques)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Conseguir las herramientas disponibles
|
||||
|
||||
async def _obtener_herramientas_disponibles_str(self) -> str:
|
||||
logger.info("Inicio de obtención de herramientas disponibles")
|
||||
|
||||
if not self.mcp:
|
||||
logger.warning("No se ha definido el cliente MCP.")
|
||||
return "No se han definido herramientas disponibles."
|
||||
|
||||
herramientas = []
|
||||
tools_por_cliente = await self.mcp.listar_tools_por_cliente()
|
||||
try:
|
||||
resultado = await self.mcp.listar_tools_por_cliente()
|
||||
tools_por_cliente = resultado.get("tools", {})
|
||||
errores = resultado.get("errores", {})
|
||||
|
||||
for name, tools in tools_por_cliente.items():
|
||||
if not tools:
|
||||
continue
|
||||
herramientas.append(f"\n🔌 Cliente: {name}")
|
||||
for tool in tools:
|
||||
props = tool.inputSchema.get("properties", {})
|
||||
parametros = "\n ".join(f"- {k} ({v.get('type', '?')})" for k, v in props.items())
|
||||
herramientas.append(f"""Nombre: {tool.name}
|
||||
Descripción: {tool.description}
|
||||
Parámetros:
|
||||
{parametros}
|
||||
""")
|
||||
return "\n".join(herramientas) or "No hay herramientas disponibles actualmente."
|
||||
logger.debug(f"Tools obtenidas: {list(tools_por_cliente.keys())}")
|
||||
logger.debug(f"Errores detectados: {list(errores.keys())}")
|
||||
|
||||
herramientas = []
|
||||
|
||||
for name, tools in tools_por_cliente.items():
|
||||
if not tools:
|
||||
logger.info(f"Servidor {name} no tiene herramientas disponibles.")
|
||||
continue
|
||||
|
||||
herramientas.append(f"\n🔌 Server: {name}")
|
||||
for tool in tools:
|
||||
props = tool.inputSchema.get("properties", {})
|
||||
parametros = "\n ".join(f"- {k} ({v.get('type', '?')})" for k, v in props.items())
|
||||
herramientas.append(f"""Nombre: {tool.name}
|
||||
Descripción: {tool.description}
|
||||
Parámetros:
|
||||
{parametros}
|
||||
""")
|
||||
logger.debug(f"Herramienta agregada: {tool.name} del servidor {name}")
|
||||
|
||||
if errores:
|
||||
herramientas.append("\n⚠️ Los siguientes servidores no están disponibles:")
|
||||
for name, error in errores.items():
|
||||
herramientas.append(f"- {name}: {error}")
|
||||
logger.warning(f"Servidor con error: {name} -> {error}")
|
||||
|
||||
logger.info("Finalización de obtención de herramientas exitosamente.")
|
||||
return "\n".join(herramientas) or "No hay herramientas disponibles actualmente."
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error inesperado al obtener herramientas: {str(e)}", exc_info=True)
|
||||
return "Se produjo un error al obtener las herramientas disponibles."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Formatear prompt para agentes
|
||||
### Formatear prompt para agentes
|
||||
|
||||
def _formatear_prompt(self, mensajes: List[dict]) -> str:
|
||||
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in mensajes])
|
||||
|
||||
|
||||
|
||||
### Ejecutar codigo MCP
|
||||
|
||||
async def ejecutar_bloque_mcp(self, respuesta: str) -> Optional[str]:
|
||||
logger.info("Iniciando ejecución de bloque MCP.")
|
||||
|
||||
patron = r"```mcp\s*(\{.*?\})\s*```"
|
||||
match = re.search(patron, respuesta, re.DOTALL)
|
||||
|
||||
if not match:
|
||||
patron_incorrecto = r"```[\s]*\{.*?\}[\s]*```"
|
||||
if re.search(patron_incorrecto, respuesta, re.DOTALL):
|
||||
logger.warning("Bloque detectado sin especificador `mcp`.")
|
||||
return "Advertencia: Usaste un bloque de herramienta MCP pero olvidaste indicar el lenguaje `mcp`. Corrige el bloque a: ```mcp { ... } ```"
|
||||
logger.info("No se encontró ningún bloque MCP en la respuesta.")
|
||||
return None
|
||||
|
||||
try:
|
||||
bloque_json_str = match.group(1)
|
||||
logger.debug(f"Bloque MCP detectado: {bloque_json_str}")
|
||||
|
||||
bloque = json.loads(bloque_json_str)
|
||||
|
||||
server_name = bloque["server"]
|
||||
tool_name = bloque["tool"]
|
||||
input_args = bloque.get("input", {})
|
||||
|
||||
logger.info(f"Bloque MCP válido. Servidor: {server_name}, Herramienta: {tool_name}")
|
||||
logger.debug(f"Parámetros de entrada: {input_args}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error al interpretar el bloque MCP: {e}", exc_info=True)
|
||||
return f"Error al interpretar el bloque MCP: {e}"
|
||||
|
||||
try:
|
||||
cliente_mcp = self.mcp.get(server_name)
|
||||
except KeyError:
|
||||
logger.warning(f"No se encontró el cliente MCP para el servidor '{server_name}'.")
|
||||
return f"No se encontró el cliente MCP para el servidor '{server_name}'"
|
||||
|
||||
try:
|
||||
logger.info(f"Ejecutando herramienta '{tool_name}' en servidor '{server_name}' con argumentos: {json.dumps(input_args, ensure_ascii=False)}")
|
||||
|
||||
async with cliente_mcp:
|
||||
resultado = await cliente_mcp.call_tool(tool_name, input_args)
|
||||
logger.info(f"Ejecución completada exitosamente. Resultado: {resultado}")
|
||||
return str(resultado)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error al ejecutar herramienta '{tool_name}' en servidor '{server_name}': {e}", exc_info=True)
|
||||
return f"Error al ejecutar herramienta '{tool_name}' en servidor '{server_name}': {e}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
###----------- Funcion para interactuar
|
||||
###----------- Funcion para interactuar
|
||||
|
||||
async def interactuar(self, prompt: str, stream: bool = False) -> Union[str, AsyncGenerator[str, None]]:
|
||||
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
||||
contexto = historial + [{"role": "user", "content": prompt}]
|
||||
mensaje_usuario = await self.construir_prompt_usuario(prompt)
|
||||
contexto = [{"role": "user", "content": mensaje_usuario}]
|
||||
prompt_final = self._formatear_prompt(contexto)
|
||||
|
||||
respuesta = await self.modelo.responder(
|
||||
prompt=prompt_final,
|
||||
system_prompt=await self.full_system_prompt, # ✅ correcto
|
||||
system_prompt=await self.generar_system_prompt(),
|
||||
stream=stream
|
||||
)
|
||||
|
||||
if stream:
|
||||
async def wrapper():
|
||||
buffer_respuesta = ""
|
||||
async for token in respuesta:
|
||||
buffer_respuesta += token
|
||||
yield token
|
||||
if self.memoria:
|
||||
self.memoria.guardar_turno("user", prompt)
|
||||
self.memoria.guardar_turno("assistant", buffer_respuesta)
|
||||
self.numero_interacciones += 1
|
||||
self.updated_at = datetime.now()
|
||||
return wrapper()
|
||||
else:
|
||||
if self.memoria:
|
||||
self.memoria.guardar_turno("user", prompt)
|
||||
self.memoria.guardar_turno("assistant", respuesta)
|
||||
self.numero_interacciones += 1
|
||||
self.updated_at = datetime.now()
|
||||
return respuesta
|
||||
return respuesta
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
###----------- Funcion para interactuar en bucle
|
||||
###----------- Funcion para interactuar en bucle
|
||||
|
||||
async def interactuar_en_bucle(self, prompt: str, stream: bool = False) -> Union[List[str], AsyncGenerator[str, None]]:
|
||||
print("🚀 [interactuar_en_bucle] Iniciando interacción")
|
||||
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
||||
print(f"📜 [interactuar_en_bucle] Historial cargado: {historial}")
|
||||
respuestas = [] if not stream else None
|
||||
respuesta_anterior = None
|
||||
respuesta_anterior = ""
|
||||
resultado_mcp_anterior = None # <-- Guarda último resultado del MCP
|
||||
iteration = 0
|
||||
prompt_original = prompt.strip()
|
||||
print(f"✏️ [interactuar_en_bucle] Prompt original: {prompt_original}")
|
||||
|
||||
async def generador():
|
||||
nonlocal iteration, respuesta_anterior
|
||||
prompt_actual = prompt_original
|
||||
nonlocal iteration, respuesta_anterior, resultado_mcp_anterior
|
||||
|
||||
while self.max_iterations == 0 or iteration < self.max_iterations:
|
||||
print(f"\n🔁 [generador] Iteración: {iteration}")
|
||||
instruccion_fin = (
|
||||
"\n\nIMPORTANTE: Cuando hayas respondido completamente a la pregunta original del usuario y no requieras más pasos, "
|
||||
"escribe <END> para indicar que has terminado."
|
||||
)
|
||||
|
||||
if iteration == 0:
|
||||
prompt_actual += (
|
||||
"\n\nIMPORTANTE:\n"
|
||||
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
||||
"di alguna de estas frases: <END>"
|
||||
)
|
||||
prompt_actual = prompt_original + instruccion_fin
|
||||
else:
|
||||
prompt_actual = (
|
||||
f"Esta es la pregunta original:\n{prompt_original}\n\n"
|
||||
f"Esto fue lo último que dijiste:\n{respuesta_anterior}\n"
|
||||
"\n\nIMPORTANTE:\n"
|
||||
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
||||
"di alguna de estas frases: <END>"
|
||||
"En caso contrario, responde a la pregunta original "
|
||||
"y añade información relevante que no hayas mencionado antes.\n\n"
|
||||
f"Esto fue lo último que dijiste:\n{respuesta_anterior}\n\n"
|
||||
f"{instruccion_fin}"
|
||||
)
|
||||
|
||||
contexto = historial + [{"role": "user", "content": prompt_actual}]
|
||||
if resultado_mcp_anterior:
|
||||
prompt_actual += (
|
||||
"\n\nEsta fue la salida de la herramienta que usaste:\n"
|
||||
f"{resultado_mcp_anterior}\n\n"
|
||||
"Úsala para seguir resolviendo el problema o tomar una nueva decisión."
|
||||
)
|
||||
|
||||
mensaje_usuario = await self.construir_prompt_usuario(prompt_actual)
|
||||
contexto = [{"role": "user", "content": mensaje_usuario}]
|
||||
prompt_final = self._formatear_prompt(contexto)
|
||||
|
||||
print(f"📨 [generador] Prompt final enviado al modelo:\n{prompt_final}")
|
||||
|
||||
print("🤖 [generador] Esperando respuesta del modelo...")
|
||||
respuesta = await self.modelo.responder(
|
||||
prompt=prompt_final,
|
||||
system_prompt=await self.full_system_prompt,
|
||||
system_prompt=await self.generar_system_prompt(),
|
||||
stream=stream
|
||||
)
|
||||
print("✅ [generador] Respuesta recibida")
|
||||
|
||||
if stream:
|
||||
buffer_respuesta = ""
|
||||
async for token in respuesta:
|
||||
buffer_respuesta += token
|
||||
# print(f"🔹 [stream] Token: {token}")
|
||||
yield token
|
||||
respuesta_anterior = buffer_respuesta
|
||||
# print(f"📦 [stream] Respuesta completa:\n{respuesta_anterior}")
|
||||
else:
|
||||
respuestas.append(respuesta)
|
||||
respuesta_anterior = respuesta
|
||||
# print(f"📦 [generador] Respuesta completa:\n{respuesta_anterior}")
|
||||
|
||||
# Revisar y ejecutar bloque MCP si existe
|
||||
resultado_mcp_anterior = None
|
||||
if "```mcp" in respuesta_anterior:
|
||||
resultado_mcp = await self.ejecutar_bloque_mcp(respuesta_anterior)
|
||||
if resultado_mcp:
|
||||
resultado_mcp_anterior = resultado_mcp
|
||||
|
||||
if stream:
|
||||
yield "\n" + resultado_mcp
|
||||
else:
|
||||
respuestas.append(resultado_mcp)
|
||||
|
||||
# Guardar historial si hay memoria
|
||||
if self.memoria:
|
||||
print("💾 [memoria] Guardando turno en la memoria...")
|
||||
self.memoria.guardar_turno("user", prompt_actual)
|
||||
self.memoria.guardar_turno("assistant", respuesta_anterior)
|
||||
|
||||
self.numero_interacciones += 1
|
||||
self.updated_at = datetime.now()
|
||||
print(f"📊 [generador] Interacción #{self.numero_interacciones} registrada")
|
||||
|
||||
if "<end>" in respuesta_anterior.lower():
|
||||
print("🛑 [generador] Detectado <end>. Terminando bucle.")
|
||||
if "<end>" in respuesta_anterior.lower() and "```mcp" not in respuesta_anterior.lower():
|
||||
break
|
||||
|
||||
iteration += 1
|
||||
prompt_actual = ""
|
||||
|
||||
return generador() if stream else await generador_to_list(generador)
|
||||
return generador() if stream else await generador_to_list(generador())
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,35 +22,35 @@ class ClientRegistry:
|
||||
def __contains__(self, name: str) -> bool:
|
||||
return name in self._clients
|
||||
|
||||
async def listar_tools_por_cliente(self) -> dict[str, list[Any]]:
|
||||
resultado = {}
|
||||
async def listar_tools_por_cliente(self) -> dict[str, Any]:
|
||||
resultado = {"tools": {}, "errores": {}}
|
||||
for name, wrapper in self._clients.items():
|
||||
try:
|
||||
async with wrapper:
|
||||
resultado[name] = await wrapper.list_tools()
|
||||
resultado["tools"][name] = await wrapper.list_tools()
|
||||
except Exception as e:
|
||||
print(f"[TOOLS] ❌ Error en '{name}': {e}")
|
||||
resultado[name] = []
|
||||
resultado["errores"][name] = str(e)
|
||||
resultado["tools"][name] = []
|
||||
return resultado
|
||||
|
||||
async def listar_prompts_por_cliente(self) -> dict[str, list[Any]]:
|
||||
resultado = {}
|
||||
async def listar_prompts_por_cliente(self) -> dict[str, Any]:
|
||||
resultado = {"prompts": {}, "errores": {}}
|
||||
for name, wrapper in self._clients.items():
|
||||
try:
|
||||
async with wrapper:
|
||||
resultado[name] = await wrapper.list_prompts()
|
||||
resultado["prompts"][name] = await wrapper.list_prompts()
|
||||
except Exception as e:
|
||||
print(f"[PROMPTS] ❌ Error en '{name}': {e}")
|
||||
resultado[name] = []
|
||||
resultado["errores"][name] = str(e)
|
||||
resultado["prompts"][name] = []
|
||||
return resultado
|
||||
|
||||
async def listar_resources_por_cliente(self) -> dict[str, list[Any]]:
|
||||
resultado = {}
|
||||
async def listar_resources_por_cliente(self) -> dict[str, Any]:
|
||||
resultado = {"resources": {}, "errores": {}}
|
||||
for name, wrapper in self._clients.items():
|
||||
try:
|
||||
async with wrapper:
|
||||
resultado[name] = await wrapper.list_resources()
|
||||
resultado["resources"][name] = await wrapper.list_resources()
|
||||
except Exception as e:
|
||||
print(f"[RESOURCES] ❌ Error en '{name}': {e}")
|
||||
resultado[name] = []
|
||||
resultado["errores"][name] = str(e)
|
||||
resultado["resources"][name] = []
|
||||
return resultado
|
||||
@@ -87,6 +87,6 @@ def is_prime(n: int) -> bool:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# mcp.run(transport="streamable-http", host="127.0.0.1", port=4200, path="/math")
|
||||
mcp.run(transport="streamable-http", host="127.0.0.1", port=4200, path="/math")
|
||||
|
||||
mcp.run(transport="stdio")
|
||||
# mcp.run(transport="stdio")
|
||||
@@ -66,3 +66,4 @@ def current_timestamp() -> float:
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http", host="127.0.0.1", port=4300, path="/tools")
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from src.Llms.Modelos.Base_model import ModeloABC
|
||||
from src.Security.GenerarIDs import GeneradorIDUnico
|
||||
from typing import AsyncGenerator, Union
|
||||
from src.ConexionApis.Ollama_cliente import OllamaCliente # Asegúrate de importar correctamente
|
||||
import asyncio
|
||||
|
||||
class ModeloOllama(ModeloABC):
|
||||
def __init__(
|
||||
self,
|
||||
cliente: OllamaCliente,
|
||||
model: str = "llama3",
|
||||
id: str = None,
|
||||
temperature: float = 0.7,
|
||||
top_p: float = 1.0,
|
||||
top_k: int = None,
|
||||
frecuencia_penalizacion: float = 0.0,
|
||||
num_tokens_maximos: int = 512
|
||||
):
|
||||
if not isinstance(cliente, OllamaCliente):
|
||||
raise TypeError("El parámetro 'cliente' debe ser una instancia de OllamaCliente")
|
||||
|
||||
|
||||
self.id = id if id else GeneradorIDUnico("MOOL").generar()
|
||||
super().__init__(
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
top_p=top_p,
|
||||
top_k=top_k,
|
||||
frecuencia_penalizacion=frecuencia_penalizacion,
|
||||
num_tokens_maximos=num_tokens_maximos
|
||||
)
|
||||
self.cliente = cliente
|
||||
|
||||
async def responder(
|
||||
self,
|
||||
prompt: str,
|
||||
system_prompt: str = "",
|
||||
stream: bool = False,
|
||||
**kwargs
|
||||
) -> Union[str, AsyncGenerator[str, None]]:
|
||||
messages = []
|
||||
if system_prompt:
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
def sync_call():
|
||||
return self.cliente.chat_completion(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
temperature=self.temperature,
|
||||
top_p=self.top_p,
|
||||
max_tokens=self.num_tokens_maximos,
|
||||
frequency_penalty=self.frecuencia_penalizacion,
|
||||
stream=stream,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
resultado = await loop.run_in_executor(None, sync_call)
|
||||
|
||||
if stream:
|
||||
async def generador():
|
||||
for token in resultado:
|
||||
yield token
|
||||
return generador()
|
||||
else:
|
||||
return resultado.choices[0].message.content
|
||||
@@ -0,0 +1,60 @@
|
||||
from loguru import logger
|
||||
from sqlalchemy import Column, Integer, String, Text, TIMESTAMP
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from src.ArquitectureLayer.Model import Model_base
|
||||
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
||||
from src.Credenciales.postgres_credencial import PostgresCredencial
|
||||
|
||||
class LoggerDB:
|
||||
_sink_removido = False # ← evita múltiples remove() si se crean varias instancias
|
||||
|
||||
def __init__(self, credencial: PostgresCredencial, nombre_tabla: str, created_by: str = None):
|
||||
if not LoggerDB._sink_removido:
|
||||
logger.remove() # 🧹 elimina impresión en terminal
|
||||
LoggerDB._sink_removido = True
|
||||
|
||||
self.conexion = PostgresConexion(credencial)
|
||||
self.engine = self.conexion.get_engine()
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
self.nombre_tabla = nombre_tabla
|
||||
self.created_by = created_by
|
||||
|
||||
self.modelo_logger = self._generar_modelo_logger()
|
||||
self._crear_tabla_si_no_existe()
|
||||
logger.add(self._sink, level="DEBUG")
|
||||
|
||||
def _generar_modelo_logger(self):
|
||||
class LoggerTable(Model_base):
|
||||
__tablename__ = self.nombre_tabla
|
||||
id = Column(Integer, primary_key=True)
|
||||
nivel = Column(String, nullable=False)
|
||||
mensaje = Column(Text, nullable=False)
|
||||
fecha = Column(TIMESTAMP(timezone=True), nullable=False)
|
||||
modulo = Column(String, nullable=True)
|
||||
funcion = Column(String, nullable=True)
|
||||
linea = Column(Integer, nullable=True)
|
||||
return LoggerTable
|
||||
|
||||
def _crear_tabla_si_no_existe(self):
|
||||
self.modelo_logger.__table__.create(self.engine, checkfirst=True)
|
||||
|
||||
def _sink(self, message):
|
||||
record = message.record
|
||||
try:
|
||||
session = self.Session()
|
||||
log_entry = self.modelo_logger(
|
||||
nivel=record["level"].name,
|
||||
mensaje=record["message"],
|
||||
fecha=record["time"],
|
||||
modulo=record["module"],
|
||||
funcion=record["function"],
|
||||
linea=record["line"],
|
||||
sys_created_by=self.created_by
|
||||
)
|
||||
session.add(log_entry)
|
||||
session.commit()
|
||||
session.close()
|
||||
except SQLAlchemyError as e:
|
||||
print(f"[LoggerDB] Error guardando log en BD: {e}")
|
||||
Reference in New Issue
Block a user