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 fastmcp.client import Client
|
||||||
from src.Llms.MCPs.McpClient import MCPClient # ya tienes esta clase
|
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.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
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|
||||||
# Usar Credencial openai
|
# # Usar Credencial openai
|
||||||
|
|
||||||
conexion_admin = PostgresConexion(db_credencial)
|
# conexion_admin = PostgresConexion(db_credencial)
|
||||||
repo = OpenAICredencialRepo(conexion_admin)
|
# repo = OpenAICredencialRepo(conexion_admin)
|
||||||
credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
# credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||||
cliente = OpenAICliente(credencial_openai)
|
# 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,
|
cliente=cliente,
|
||||||
model="gpt-4o",
|
model="llama3.1:8b")
|
||||||
temperature=1,
|
|
||||||
top_p=1.0
|
|
||||||
)
|
|
||||||
|
# # crea el modelo (openai)
|
||||||
|
|
||||||
|
# modelo = ModeloOpenAI(
|
||||||
|
# cliente=cliente,
|
||||||
|
# model="gpt-4o",
|
||||||
|
# temperature=1
|
||||||
|
# )
|
||||||
|
|
||||||
# Le otorga memoria
|
# Le otorga memoria
|
||||||
|
|
||||||
@@ -70,20 +84,20 @@ async def main():
|
|||||||
agente2 = AgenteAI(
|
agente2 = AgenteAI(
|
||||||
modelo=modelo,
|
modelo=modelo,
|
||||||
nombre="Asistente Inteligente",
|
nombre="Asistente Inteligente",
|
||||||
descripcion="Un asistente conversacional versátil, capaz de resolver problemas, acceder a herramientas y proporcionar respuestas útiles.",
|
descripcion="",
|
||||||
system_prompt=(
|
system_prompt="",
|
||||||
"Eres un asistente inteligente que ayuda al usuario a resolver tareas, responder preguntas y usar herramientas disponibles si es necesario. "
|
# system_prompt = """
|
||||||
"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. "
|
# Eres un asistente general. No tienes acceso a herramientas externas ni herramientas MCP. No debes mencionar herramientas MCP, servidores ni bloques de código.
|
||||||
"Siempre estructura tus respuestas con claridad, y termina con <END> cuando creas haber completado la tarea."
|
# Responde de forma clara y amigable a cualquier pregunta general del usuario.
|
||||||
),
|
# """.strip(),
|
||||||
rol="asistente",
|
rol="asistente",
|
||||||
objetivos=[
|
objetivos=[
|
||||||
"Resolver tareas del usuario",
|
# "Resolver tareas del usuario",
|
||||||
"Usar herramientas MCP si es útil",
|
# "Usar herramientas MCP si es útil",
|
||||||
"Responder de forma clara y útil"
|
# "Responder de forma clara y útil"
|
||||||
],
|
],
|
||||||
|
|
||||||
# max_iterations=3,
|
max_iterations=0,
|
||||||
# memoria=memoria,
|
# memoria=memoria,
|
||||||
|
|
||||||
mcp=registry # ← ✅ Integración del cliente MCP
|
mcp=registry # ← ✅ Integración del cliente MCP
|
||||||
@@ -92,22 +106,29 @@ async def main():
|
|||||||
|
|
||||||
# --- FUNCIÓN DE EJECUCIÓN ---
|
# --- FUNCIÓN DE EJECUCIÓN ---
|
||||||
async def probar_interaccion_stream():
|
async def probar_interaccion_stream():
|
||||||
# # 🔌 Conectar a los servidores MCP registrados
|
print("🧠 Agente iniciado. Escribe 'salir' para terminar.\n")
|
||||||
# await mcp_client.connect_all()
|
|
||||||
|
|
||||||
print("Respuesta en streaming:\n")
|
while True:
|
||||||
respuesta_gen = await agente2.interactuar_en_bucle(
|
prompt = input("\n📝 Escribe tu pregunta: ")
|
||||||
"¿Cuál es mi nombre de usuario en este sistema?",
|
if prompt.strip().lower() in ("salir", "exit", "quit"):
|
||||||
stream=True
|
print("\n👋 Hasta pronto.")
|
||||||
)
|
break
|
||||||
|
|
||||||
async for token in respuesta_gen:
|
print("\n💬 Respuesta en streaming:\n")
|
||||||
print(token, end="", flush=True)
|
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()
|
await probar_interaccion_stream()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Ejecutar
|
# Ejecutar
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(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 src.Llms.MCPs.McpClient_Registry import ClientRegistry
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, List, Union, AsyncGenerator
|
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:
|
class AgenteAI:
|
||||||
@@ -17,7 +25,7 @@ class AgenteAI:
|
|||||||
max_iterations: int = 1,
|
max_iterations: int = 1,
|
||||||
memoria: Optional[MemoryConvABC] = None,
|
memoria: Optional[MemoryConvABC] = None,
|
||||||
version: str = "1.0.0",
|
version: str = "1.0.0",
|
||||||
mcp: ClientRegistry = None,
|
mcp: Optional[ClientRegistry] = None,
|
||||||
output_schema: Optional[dict] = None,
|
output_schema: Optional[dict] = None,
|
||||||
):
|
):
|
||||||
self.modelo = modelo
|
self.modelo = modelo
|
||||||
@@ -48,219 +56,349 @@ class AgenteAI:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
async def generar_system_prompt(self) -> str:
|
||||||
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).
|
|
||||||
|
|
||||||
Tu comportamiento sigue este flujo:
|
info = f"""Eres un agente de texto y te llamas {self.nombre}
|
||||||
|
|
||||||
1. **Piensa** para razonar tu decisión.
|
### Descripción:
|
||||||
2. **Decide** si:
|
{self.descripcion}
|
||||||
- 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.**
|
|
||||||
|
|
||||||
---
|
### Rol:
|
||||||
|
{self.rol}
|
||||||
|
|
||||||
# Formato MCP
|
### Objetivos:
|
||||||
|
{chr(10).join(f"- {o}" for o in self.objetivos)}
|
||||||
|
|
||||||
```mcp
|
### System Prompt:
|
||||||
{{
|
{self.system_prompt}
|
||||||
"tool": "<nombre_de_la_herramienta>",
|
|
||||||
"input": {{
|
|
||||||
"clave": "valor"
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
Reglas clave:
|
|
||||||
|
|
||||||
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.
|
return info
|
||||||
|
|
||||||
No combines respuestas y herramientas.
|
|
||||||
|
|
||||||
Piensa. Decide. Actúa.
|
|
||||||
|
|
||||||
Herramientas disponibles para usar con MCP:
|
|
||||||
{tools_str}
|
|
||||||
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
async def _obtener_herramientas_disponibles_str(self) -> str:
|
||||||
|
logger.info("Inicio de obtención de herramientas disponibles")
|
||||||
|
|
||||||
if not self.mcp:
|
if not self.mcp:
|
||||||
|
logger.warning("No se ha definido el cliente MCP.")
|
||||||
return "No se han definido herramientas disponibles."
|
return "No se han definido herramientas disponibles."
|
||||||
|
|
||||||
herramientas = []
|
try:
|
||||||
tools_por_cliente = await self.mcp.listar_tools_por_cliente()
|
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():
|
logger.debug(f"Tools obtenidas: {list(tools_por_cliente.keys())}")
|
||||||
if not tools:
|
logger.debug(f"Errores detectados: {list(errores.keys())}")
|
||||||
continue
|
|
||||||
herramientas.append(f"\n🔌 Cliente: {name}")
|
herramientas = []
|
||||||
for tool in tools:
|
|
||||||
props = tool.inputSchema.get("properties", {})
|
for name, tools in tools_por_cliente.items():
|
||||||
parametros = "\n ".join(f"- {k} ({v.get('type', '?')})" for k, v in props.items())
|
if not tools:
|
||||||
herramientas.append(f"""Nombre: {tool.name}
|
logger.info(f"Servidor {name} no tiene herramientas disponibles.")
|
||||||
Descripción: {tool.description}
|
continue
|
||||||
Parámetros:
|
|
||||||
{parametros}
|
herramientas.append(f"\n🔌 Server: {name}")
|
||||||
""")
|
for tool in tools:
|
||||||
return "\n".join(herramientas) or "No hay herramientas disponibles actualmente."
|
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:
|
def _formatear_prompt(self, mensajes: List[dict]) -> str:
|
||||||
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in mensajes])
|
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]]:
|
async def interactuar(self, prompt: str, stream: bool = False) -> Union[str, AsyncGenerator[str, None]]:
|
||||||
historial = self.memoria.cargar_historial_chat() if self.memoria else []
|
mensaje_usuario = await self.construir_prompt_usuario(prompt)
|
||||||
contexto = historial + [{"role": "user", "content": prompt}]
|
contexto = [{"role": "user", "content": mensaje_usuario}]
|
||||||
prompt_final = self._formatear_prompt(contexto)
|
prompt_final = self._formatear_prompt(contexto)
|
||||||
|
|
||||||
respuesta = await self.modelo.responder(
|
respuesta = await self.modelo.responder(
|
||||||
prompt=prompt_final,
|
prompt=prompt_final,
|
||||||
system_prompt=await self.full_system_prompt, # ✅ correcto
|
system_prompt=await self.generar_system_prompt(),
|
||||||
stream=stream
|
stream=stream
|
||||||
)
|
)
|
||||||
|
|
||||||
if stream:
|
return respuesta
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###----------- 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]]:
|
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
|
respuestas = [] if not stream else None
|
||||||
respuesta_anterior = None
|
respuesta_anterior = ""
|
||||||
|
resultado_mcp_anterior = None # <-- Guarda último resultado del MCP
|
||||||
iteration = 0
|
iteration = 0
|
||||||
prompt_original = prompt.strip()
|
prompt_original = prompt.strip()
|
||||||
print(f"✏️ [interactuar_en_bucle] Prompt original: {prompt_original}")
|
|
||||||
|
|
||||||
async def generador():
|
async def generador():
|
||||||
nonlocal iteration, respuesta_anterior
|
nonlocal iteration, respuesta_anterior, resultado_mcp_anterior
|
||||||
prompt_actual = prompt_original
|
|
||||||
|
|
||||||
while self.max_iterations == 0 or iteration < self.max_iterations:
|
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:
|
if iteration == 0:
|
||||||
prompt_actual += (
|
prompt_actual = prompt_original + instruccion_fin
|
||||||
"\n\nIMPORTANTE:\n"
|
|
||||||
"Si al revisar tu última respuesta y mi pregunta inicial consideras que has terminado, "
|
|
||||||
"di alguna de estas frases: <END>"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
prompt_actual = (
|
prompt_actual = (
|
||||||
f"Esta es la pregunta original:\n{prompt_original}\n\n"
|
f"Esta es la pregunta original:\n{prompt_original}\n\n"
|
||||||
f"Esto fue lo último que dijiste:\n{respuesta_anterior}\n"
|
f"Esto fue lo último que dijiste:\n{respuesta_anterior}\n\n"
|
||||||
"\n\nIMPORTANTE:\n"
|
f"{instruccion_fin}"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
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(
|
respuesta = await self.modelo.responder(
|
||||||
prompt=prompt_final,
|
prompt=prompt_final,
|
||||||
system_prompt=await self.full_system_prompt,
|
system_prompt=await self.generar_system_prompt(),
|
||||||
stream=stream
|
stream=stream
|
||||||
)
|
)
|
||||||
print("✅ [generador] Respuesta recibida")
|
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
buffer_respuesta = ""
|
buffer_respuesta = ""
|
||||||
async for token in respuesta:
|
async for token in respuesta:
|
||||||
buffer_respuesta += token
|
buffer_respuesta += token
|
||||||
# print(f"🔹 [stream] Token: {token}")
|
|
||||||
yield token
|
yield token
|
||||||
respuesta_anterior = buffer_respuesta
|
respuesta_anterior = buffer_respuesta
|
||||||
# print(f"📦 [stream] Respuesta completa:\n{respuesta_anterior}")
|
|
||||||
else:
|
else:
|
||||||
respuestas.append(respuesta)
|
respuestas.append(respuesta)
|
||||||
respuesta_anterior = 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:
|
if self.memoria:
|
||||||
print("💾 [memoria] Guardando turno en la memoria...")
|
|
||||||
self.memoria.guardar_turno("user", prompt_actual)
|
self.memoria.guardar_turno("user", prompt_actual)
|
||||||
self.memoria.guardar_turno("assistant", respuesta_anterior)
|
self.memoria.guardar_turno("assistant", respuesta_anterior)
|
||||||
|
|
||||||
self.numero_interacciones += 1
|
self.numero_interacciones += 1
|
||||||
self.updated_at = datetime.now()
|
self.updated_at = datetime.now()
|
||||||
print(f"📊 [generador] Interacción #{self.numero_interacciones} registrada")
|
|
||||||
|
|
||||||
if "<end>" in respuesta_anterior.lower():
|
if "<end>" in respuesta_anterior.lower() and "```mcp" not in respuesta_anterior.lower():
|
||||||
print("🛑 [generador] Detectado <end>. Terminando bucle.")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
iteration += 1
|
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:
|
def __contains__(self, name: str) -> bool:
|
||||||
return name in self._clients
|
return name in self._clients
|
||||||
|
|
||||||
async def listar_tools_por_cliente(self) -> dict[str, list[Any]]:
|
async def listar_tools_por_cliente(self) -> dict[str, Any]:
|
||||||
resultado = {}
|
resultado = {"tools": {}, "errores": {}}
|
||||||
for name, wrapper in self._clients.items():
|
for name, wrapper in self._clients.items():
|
||||||
try:
|
try:
|
||||||
async with wrapper:
|
async with wrapper:
|
||||||
resultado[name] = await wrapper.list_tools()
|
resultado["tools"][name] = await wrapper.list_tools()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[TOOLS] ❌ Error en '{name}': {e}")
|
resultado["errores"][name] = str(e)
|
||||||
resultado[name] = []
|
resultado["tools"][name] = []
|
||||||
return resultado
|
return resultado
|
||||||
|
|
||||||
async def listar_prompts_por_cliente(self) -> dict[str, list[Any]]:
|
async def listar_prompts_por_cliente(self) -> dict[str, Any]:
|
||||||
resultado = {}
|
resultado = {"prompts": {}, "errores": {}}
|
||||||
for name, wrapper in self._clients.items():
|
for name, wrapper in self._clients.items():
|
||||||
try:
|
try:
|
||||||
async with wrapper:
|
async with wrapper:
|
||||||
resultado[name] = await wrapper.list_prompts()
|
resultado["prompts"][name] = await wrapper.list_prompts()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[PROMPTS] ❌ Error en '{name}': {e}")
|
resultado["errores"][name] = str(e)
|
||||||
resultado[name] = []
|
resultado["prompts"][name] = []
|
||||||
return resultado
|
return resultado
|
||||||
|
|
||||||
async def listar_resources_por_cliente(self) -> dict[str, list[Any]]:
|
async def listar_resources_por_cliente(self) -> dict[str, Any]:
|
||||||
resultado = {}
|
resultado = {"resources": {}, "errores": {}}
|
||||||
for name, wrapper in self._clients.items():
|
for name, wrapper in self._clients.items():
|
||||||
try:
|
try:
|
||||||
async with wrapper:
|
async with wrapper:
|
||||||
resultado[name] = await wrapper.list_resources()
|
resultado["resources"][name] = await wrapper.list_resources()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[RESOURCES] ❌ Error en '{name}': {e}")
|
resultado["errores"][name] = str(e)
|
||||||
resultado[name] = []
|
resultado["resources"][name] = []
|
||||||
return resultado
|
return resultado
|
||||||
@@ -87,6 +87,6 @@ def is_prime(n: int) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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__":
|
if __name__ == "__main__":
|
||||||
mcp.run(transport="streamable-http", host="127.0.0.1", port=4300, path="/tools")
|
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