Notas y bibliotecas funcionando

This commit is contained in:
2025-05-14 02:06:33 +02:00
parent bf1814bb8e
commit c13240b481
18 changed files with 266 additions and 168 deletions
+8 -2
View File
@@ -48,10 +48,16 @@ class Model_base:
def __json__(self) -> dict:
"""Devuelve una representación JSON serializable (dict plano)."""
out = {}
# Prevención de error: solo ejecuta si __table__ existe
if not hasattr(self, "__table__"):
return out
for attr in self.__table__.columns:
val = getattr(self, attr.name)
val = getattr(self, attr.name, None)
if isinstance(val, datetime):
out[attr.name] = val.isoformat()
else:
out[attr.name] = val
return out
return out
-65
View File
@@ -1,65 +0,0 @@
import asyncio
import os
from typing import Optional, List, Dict
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.types import Tool
class MCPStdioServer:
def __init__(
self,
name: str,
command: str,
args: List[str],
env: Optional[Dict[str, str]] = None
):
self.name = name
self.command = command
self.args = args
self.env = env or os.environ.copy()
self.exit_stack = AsyncExitStack()
self.session: Optional[ClientSession] = None
self.tools: List[Tool] = []
async def start(self):
# Configurar el bucle de eventos Proactor en Windows si es necesario
if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
server_params = StdioServerParameters(
command=self.command,
args=self.args,
env=self.env
)
# Iniciar el transporte y establecer la sesión
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
read, write = stdio_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
await self.session.initialize()
response = await self.session.list_tools()
self.tools = response.tools
if self.tools:
print(f"[{self.name}] Servidor iniciado con herramientas:")
for tool in self.tools:
nombre = getattr(tool, "name", "[sin nombre]")
descripcion = getattr(tool, "description", "[sin descripción]")
print(f" - {nombre} - {descripcion}")
else:
print(f"[{self.name}] Servidor iniciado, pero no se detectaron herramientas.")
async def call_tool(self, tool_name: str, arguments: Dict):
if not self.session:
raise RuntimeError("La sesión no está inicializada.")
result = await self.session.call_tool(tool_name, arguments)
return result.content
def get_tool_names(self) -> List[str]:
return [tool.name for tool in self.tools]
async def stop(self):
await self.exit_stack.aclose()
print(f"[{self.name}] Servidor detenido.")
@@ -0,0 +1,15 @@
from fastmcp import Client
import asyncio
async def main():
async with Client("http://127.0.0.1:8080/mcp") as client:
tools = await client.list_tools()
for tool in tools:
print(f"🔧 {tool.name} - {tool.description or 'sin descripción'}")
client.call_tool_mcp()
if __name__ == "__main__":
asyncio.run(main())
+30
View File
@@ -0,0 +1,30 @@
# archivo: sse_server.py
from fastmcp.server import FastMCP
import asyncio
from fastmcp import Client
# Crear la instancia del servidor
server = FastMCP(
name="ServidorSSE",
instructions="Este servidor expone herramientas de prueba.",
)
# Herramienta 1: saludar
@server.tool(name="saludar", description="Saluda a una persona por su nombre.")
def saludar(nombre: str) -> str:
return f"¡Hola, {nombre}!"
# Herramienta 2: espera asíncrona
@server.tool(name="esperar", description="Espera N segundos y responde.")
async def esperar(segundos: int) -> str:
await asyncio.sleep(segundos)
return f"Esperé {segundos} segundos como me pediste."
# Punto de entrada para ejecutarlo por SSE
if __name__ == "__main__":
server.run(
transport="streamable-http", # <-- cambio aquí
host="0.0.0.0",
port=8080,
)
+1 -1
View File
@@ -27,7 +27,7 @@ class MemoryConvPostgres(MemoryConvABC):
)
# Crea la tabla si no existe
self.metadata.create_all(self.conexion.engine)
self.metadata.create_all(self.conexion._engine)
def guardar_turno(self, rol: Literal["user", "assistant"], contenido: str) -> None:
stmt = insert(self.tabla).values(rol=rol, contenido=contenido)
+8 -4
View File
@@ -17,9 +17,11 @@ class ModeloOpenAI(ModeloABC):
num_tokens_maximos: int = 512,
use_legacy: bool = False
):
id = id if id is not None else GeneradorIDUnico("MOPA").generar()
# Generar ID con prefijo MOPA si no fue proporcionado
self.id = id if id is not None else GeneradorIDUnico("MOPA").generar()
# Inicializar resto del modelo base
super().__init__(
id=id,
model=model,
temperature=temperature,
top_p=top_p,
@@ -27,6 +29,8 @@ class ModeloOpenAI(ModeloABC):
frecuencia_penalizacion=frecuencia_penalizacion,
num_tokens_maximos=num_tokens_maximos
)
# Asignar cliente e indicadores adicionales
self.cliente = cliente
self.use_legacy = use_legacy
@@ -79,8 +83,8 @@ class ModeloOpenAI(ModeloABC):
if stream:
async def generador():
for token in resultado: # ya es un generador del cliente
for token in resultado:
yield token
return generador()
else:
return resultado.choices[0].message.content
return resultado.choices[0].message.content
+12 -8
View File
@@ -5,6 +5,7 @@ from src.ConexionSql.Base_conexion import ConexionBase
from sqlalchemy import MetaData # Asegúrate de importar esto
from src.TextManager.notas_mmr import generar_tabla_nota_para_biblioteca # Ajusta si es necesario
from sqlalchemy import inspect
from src.base import Base
class Biblioteca:
@@ -38,18 +39,21 @@ class Biblioteca:
raise ValueError("Debes proporcionar un 'embedder' o un 'vector_dim' explícito.")
def generar_modelo_notas(self, conexion: ConexionBase):
"""
Genera dinámicamente un modelo de notas asociado a esta biblioteca y lo crea en la base de datos.
Previene la creación si la tabla ya existe.
"""
nombre_tabla = f"{self.nombre}"
print(f"[Notas] Generando tabla: {nombre_tabla}")
engine = conexion.get_engine()
inspector = inspect(engine)
if inspector.has_table(nombre_tabla):
print(f"[Notas] ❌ Ya existe la tabla {nombre_tabla}")
raise ValueError(f"Ya existe una tabla con el nombre '{nombre_tabla}' en la base de datos.")
metadata = MetaData()
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, metadata)
metadata.create_all(engine)
print("[Notas] Generando definición SQL...")
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, Base.metadata)
print("[Notas] Creando tabla en base de datos...")
Base.metadata.create_all(engine)
print("[Notas] ✔️ Tabla creada")
return NotaModel
+32 -34
View File
@@ -1,6 +1,6 @@
import os
from dotenv import load_dotenv
from sqlalchemy import Column, String, Table, MetaData
from sqlalchemy import Table, Column, String, Text, MetaData
from pgvector.sqlalchemy import Vector
from sqlalchemy.orm import registry, Session
from src.TextManager.nota import Nota
@@ -13,6 +13,9 @@ from src.ArquitectureLayer.Mapper import Mapper_base
from src.ArquitectureLayer.Model import Model_base
from src.ArquitectureLayer.Repo import Repo_base
from src.base import Base # Este es tu declarative_base()
# ----------------------
# Cargar .env
# ----------------------
@@ -28,47 +31,42 @@ mapper_registry = registry()
# FUNCIONES AUXILIARES
# ----------------------
def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int, metadata: MetaData) -> Tuple[Table, type]:
"""
Genera una tabla dinámica y modelo ORM para una biblioteca dada, con campos vectoriales.
Genera una tabla dinámica y modelo ORM para una biblioteca dada, con campos vectoriales y campos del sistema.
"""
# Normalización robusta del nombre de la tabla
nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower())
try:
print(f"[INFO] Generando tabla para biblioteca: '{biblioteca_nombre}' con dimensión de vector: {vector_dim}")
tabla = Table(
nombre_tabla,
metadata,
Column("id", String, primary_key=True),
Column("titulo", String, nullable=False),
Column("tags", String),
Column("conexiones", String),
Column("texto", String),
Column("resumen", String),
Column("vector", Vector(vector_dim), nullable=True),
Column("vector_resumen", Vector(vector_dim), nullable=True),
*Model_base.__table__.columns # Añade columnas del sistema si Model_base es declarativo
)
# Nombre SQL-safe
nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower())
print(f"[DEBUG] Nombre de tabla SQL-safe: '{nombre_tabla}'")
class NotaModel(Model_base):
"""
Modelo ORM generado dinámicamente para notas de una biblioteca.
"""
def __init__(self, nota):
self.id = nota.id
self.titulo = nota.titulo
self.tags = ",".join(nota.tags)
self.conexiones = ",".join(nota.conexiones)
self.texto = nota.texto
self.resumen = nota.resumen
self.vector = nota.vector
self.vector_resumen = getattr(nota, "vector_resumen", None)
# Modelo ORM dinámico
class NotaModel(Base, Model_base):
__tablename__ = nombre_tabla
__table_args__ = {"extend_existing": True}
# Registrar solo si aún no se ha hecho
if NotaModel.__name__ not in mapper_registry._class_registry:
mapper_registry.map_imperatively(NotaModel, tabla)
id = Column(String, primary_key=True)
titulo = Column(String, nullable=False)
tags = Column(String)
conexiones = Column(String)
texto = Column(Text)
resumen = Column(Text)
vector = Column(Vector(vector_dim), nullable=True)
vector_resumen = Column(Vector(vector_dim), nullable=True)
return tabla, NotaModel
print(f"[INFO] Modelo ORM 'NotaModel' creado para la tabla '{nombre_tabla}'")
print(f"[DEBUG] Columnas del modelo: {[c.name for c in NotaModel.__table__.columns]}")
print(f"[DEBUG] Tipos de columnas: {[str(c.type) for c in NotaModel.__table__.columns]}")
print(f"[DEBUG] Claves primarias: {[c.name for c in NotaModel.__table__.primary_key]}")
return NotaModel.__table__, NotaModel
except Exception as e:
print(f"[ERROR] Error al generar la tabla y modelo ORM para '{biblioteca_nombre}': {e}")
raise
# ----------------------
# MAPPER