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:
2025-05-19 22:57:01 +02:00
parent 9db2f70009
commit 6b491a9a41
10 changed files with 544 additions and 167 deletions
+51 -30
View File
@@ -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())
+8
View File
@@ -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")
+62
View File
@@ -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()
+20
View File
@@ -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
View File
@@ -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())
+15 -15
View File
@@ -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
+2 -2
View File
@@ -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")
+1
View File
@@ -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")
+67
View File
@@ -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
+60
View File
@@ -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}")