Notas y bibliotecas funcionando
This commit is contained in:
@@ -3,6 +3,7 @@ from fastapi import Path
|
|||||||
|
|
||||||
from backend.schemas.text_manager_schema import BibliotecaInput, NotaInput
|
from backend.schemas.text_manager_schema import BibliotecaInput, NotaInput
|
||||||
|
|
||||||
|
from fastapi.concurrency import run_in_threadpool
|
||||||
from backend.db.conexion import get_conexion
|
from backend.db.conexion import get_conexion
|
||||||
from backend.services.text_manager_srvc import *
|
from backend.services.text_manager_srvc import *
|
||||||
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
from src.ConexionSql.Postgres_conexion import PostgresConexion
|
||||||
@@ -10,21 +11,24 @@ from src.ConexionSql.Postgres_conexion import PostgresConexion
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", summary="Crear una nueva biblioteca")
|
@router.post("/biblioteca", summary="Crear una nueva biblioteca")
|
||||||
def crear_biblioteca_endpoint(
|
async def crear_biblioteca_endpoint(
|
||||||
data: BibliotecaInput,
|
data: BibliotecaInput,
|
||||||
conexion: PostgresConexion = Depends(get_conexion)
|
conexion: PostgresConexion = Depends(get_conexion)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return crear_biblioteca(
|
return await run_in_threadpool(
|
||||||
nombre_biblioteca=data.nombre_biblioteca,
|
crear_biblioteca,
|
||||||
descripcion=data.descripcion,
|
data.nombre_biblioteca,
|
||||||
conexion=conexion
|
conexion,
|
||||||
|
data.descripcion,
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail="Error interno al crear la biblioteca")
|
raise HTTPException(status_code=500, detail=f"Error interno al crear la biblioteca: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/list", summary="Listar todas las bibliotecas")
|
@router.get("/list", summary="Listar todas las bibliotecas")
|
||||||
def listar_todas_bibliotecas(
|
def listar_todas_bibliotecas(
|
||||||
|
|||||||
@@ -10,24 +10,45 @@ from backend.schemas.text_manager_schema import NotaInput
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str):
|
def crear_biblioteca(nombre_biblioteca: str, conexion: PostgresConexion, descripcion: str = None):
|
||||||
cred_repo = OpenAICredencialRepo(conexion)
|
print("[INICIO] Creando biblioteca...")
|
||||||
credencial = cred_repo.get_by_id("OPAK20250510-ac2cea8af3110632314")
|
|
||||||
|
|
||||||
embedder = OpenAIEmbedder(credencial, model="text-embedding-3-large")
|
try:
|
||||||
biblioteca = Biblioteca(nombre=nombre_biblioteca,
|
print("[Paso 1] Obteniendo credencial...")
|
||||||
embedder=embedder,
|
cred_repo = OpenAICredencialRepo(conexion)
|
||||||
descripcion=descripcion)
|
credencial = cred_repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||||
|
print("[OK] Credencial obtenida:", credencial.titulo if credencial else "❌ None")
|
||||||
|
|
||||||
repo = BibliotecaRepo(conexion)
|
print("[Paso 2] Instanciando embedder...")
|
||||||
repo.add(biblioteca=biblioteca)
|
embedder = OpenAIEmbedder(credencial, model="text-embedding-3-large")
|
||||||
|
print("[OK] Embedder instanciado")
|
||||||
|
|
||||||
biblioteca.generar_modelo_notas(conexion)
|
print("[Paso 3] Instanciando biblioteca...")
|
||||||
|
biblioteca = Biblioteca(
|
||||||
|
nombre=nombre_biblioteca,
|
||||||
|
embedder=embedder,
|
||||||
|
descripcion=descripcion
|
||||||
|
)
|
||||||
|
print(f"[OK] Biblioteca instanciada con ID: {biblioteca.id}")
|
||||||
|
|
||||||
return {
|
print("[Paso 4] Guardando en base de datos...")
|
||||||
"mensaje": f"Biblioteca '{nombre_biblioteca}' creada con éxito.",
|
repo = BibliotecaRepo(conexion)
|
||||||
"id": biblioteca.id
|
repo.add(biblioteca=biblioteca)
|
||||||
}
|
print("[OK] Biblioteca guardada")
|
||||||
|
|
||||||
|
print("[Paso 5] Generando modelo de notas...")
|
||||||
|
biblioteca.generar_modelo_notas(conexion)
|
||||||
|
print("[OK] Modelo de notas generado")
|
||||||
|
|
||||||
|
print("[FIN] Biblioteca creada correctamente")
|
||||||
|
return {
|
||||||
|
"mensaje": f"Biblioteca '{nombre_biblioteca}' creada con éxito.",
|
||||||
|
"id": biblioteca.id
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("[ERROR] Ocurrió una excepción:", str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def listar_bibliotecas(conexion: PostgresConexion) -> list[dict]:
|
def listar_bibliotecas(conexion: PostgresConexion) -> list[dict]:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Box,
|
Box,
|
||||||
Loader,
|
Loader,
|
||||||
|
Textarea
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -38,6 +39,10 @@ export function Biblioteca() {
|
|||||||
const [loadingNotas, setLoadingNotas] = useState(false);
|
const [loadingNotas, setLoadingNotas] = useState(false);
|
||||||
const [notaEnEdicion, setNotaEnEdicion] = useState<Nota | null>(null);
|
const [notaEnEdicion, setNotaEnEdicion] = useState<Nota | null>(null);
|
||||||
const [modalEditarAbierto, setModalEditarAbierto] = useState(false);
|
const [modalEditarAbierto, setModalEditarAbierto] = useState(false);
|
||||||
|
const [modalNuevaBiblio, setModalNuevaBiblio] = useState(false);
|
||||||
|
const [nombreBiblio, setNombreBiblio] = useState('');
|
||||||
|
const [descripcionBiblio, setDescripcionBiblio] = useState('');
|
||||||
|
const [loadingNuevaBiblio, setLoadingNuevaBiblio] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const fetchBibliotecas = async () => {
|
const fetchBibliotecas = async () => {
|
||||||
@@ -63,6 +68,31 @@ export function Biblioteca() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const crearBiblioteca = async () => {
|
||||||
|
setLoadingNuevaBiblio(true); // 🔄 Activa el loader en el botón
|
||||||
|
try {
|
||||||
|
// Llamada a backend
|
||||||
|
await axios.post('/api/v1/text_manager/biblioteca', {
|
||||||
|
nombre_biblioteca: nombreBiblio,
|
||||||
|
descripcion: descripcionBiblio,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🧼 Limpia formularios
|
||||||
|
setNombreBiblio('');
|
||||||
|
setDescripcionBiblio('');
|
||||||
|
|
||||||
|
// 🔒 Cierra el modal
|
||||||
|
setModalNuevaBiblio(false);
|
||||||
|
|
||||||
|
// 🔄 Refresca la lista de bibliotecas
|
||||||
|
await fetchBibliotecas();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error al crear biblioteca:', error);
|
||||||
|
} finally {
|
||||||
|
setLoadingNuevaBiblio(false); // ✅ Apaga el loader
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const agregarNota = async () => {
|
const agregarNota = async () => {
|
||||||
if (!bibliotecaSeleccionada) return;
|
if (!bibliotecaSeleccionada) return;
|
||||||
|
|
||||||
@@ -101,7 +131,14 @@ export function Biblioteca() {
|
|||||||
if (!bibliotecaSeleccionada) return;
|
if (!bibliotecaSeleccionada) return;
|
||||||
try {
|
try {
|
||||||
await axios.delete(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}/${notaId}`);
|
await axios.delete(`/api/v1/text_manager/nota/${bibliotecaSeleccionada.id}/${notaId}`);
|
||||||
await fetchBibliotecas();
|
|
||||||
|
// Solo actualiza la biblioteca actual
|
||||||
|
const nuevasNotas = await axios.get(`/api/v1/text_manager/nota/list/${bibliotecaSeleccionada.id}`);
|
||||||
|
const nuevasBibliotecas = bibliotecas.map((b) =>
|
||||||
|
b.id === bibliotecaSeleccionada.id ? { ...b, notas: nuevasNotas.data as Nota[] } : b
|
||||||
|
);
|
||||||
|
setBibliotecas(nuevasBibliotecas);
|
||||||
|
setBibliotecaSeleccionada(nuevasBibliotecas.find(b => b.id === bibliotecaSeleccionada.id) || null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error al eliminar nota:", error);
|
console.error("Error al eliminar nota:", error);
|
||||||
}
|
}
|
||||||
@@ -140,9 +177,9 @@ export function Biblioteca() {
|
|||||||
<Box w={240} p="md">
|
<Box w={240} p="md">
|
||||||
<ScrollArea h="100%">
|
<ScrollArea h="100%">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Button color="teal" onClick={fetchBibliotecas}>
|
<Button color="teal" onClick={fetchBibliotecas}>🔄 Recuperar bibliotecas</Button>
|
||||||
🔄 Recuperar bibliotecas
|
<Button color="grape" variant="outline" onClick={() => setModalNuevaBiblio(true)}>➕ Nueva biblioteca</Button>
|
||||||
</Button>
|
|
||||||
|
|
||||||
{bibliotecas.map((biblio) => (
|
{bibliotecas.map((biblio) => (
|
||||||
<Button
|
<Button
|
||||||
@@ -181,32 +218,29 @@ export function Biblioteca() {
|
|||||||
padding="lg"
|
padding="lg"
|
||||||
radius="md"
|
radius="md"
|
||||||
withBorder
|
withBorder
|
||||||
style={{ width: 300, position: 'relative' }}
|
style={{
|
||||||
|
width: 300,
|
||||||
|
height: 250, // Altura fija para asegurar la separación
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Botones en esquina superior derecha */}
|
<div>
|
||||||
<Box style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4, zIndex: 1 }}>
|
<Title order={4} style={{ marginBottom: 10 }}>{nota.titulo}</Title>
|
||||||
|
<Text>{nota.texto}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box mt="md" style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
color="blue"
|
|
||||||
variant="light"
|
variant="light"
|
||||||
p={4}
|
color="blue"
|
||||||
onClick={() => abrirModalEditar(nota)}
|
onClick={() => abrirModalEditar(nota)}
|
||||||
>
|
>
|
||||||
✏️
|
Editar
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
color="red"
|
|
||||||
variant="light"
|
|
||||||
p={4}
|
|
||||||
onClick={() => eliminarNota(nota.id)}
|
|
||||||
>
|
|
||||||
🗑️
|
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Title order={4} style={{ marginBottom: 10 }}>{nota.titulo}</Title>
|
|
||||||
<Text>{nota.texto}</Text>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
// Fin de notas en cards
|
// Fin de notas en cards
|
||||||
@@ -218,9 +252,7 @@ export function Biblioteca() {
|
|||||||
) : (
|
) : (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>Selecciona una biblioteca</Text>
|
<Text>Selecciona una biblioteca</Text>
|
||||||
<Button color="teal" onClick={fetchBibliotecas}>
|
|
||||||
🔄 Recuperar bibliotecas
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -234,8 +266,10 @@ export function Biblioteca() {
|
|||||||
value={tituloNota}
|
value={tituloNota}
|
||||||
onChange={(event) => setTituloNota(event.currentTarget.value)}
|
onChange={(event) => setTituloNota(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<Textarea
|
||||||
label="Contenido"
|
label="Contenido"
|
||||||
|
minRows={6}
|
||||||
|
autosize
|
||||||
value={contenidoNota}
|
value={contenidoNota}
|
||||||
onChange={(event) => setContenidoNota(event.currentTarget.value)}
|
onChange={(event) => setContenidoNota(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
@@ -253,16 +287,60 @@ export function Biblioteca() {
|
|||||||
setNotaEnEdicion((prev) => (prev ? { ...prev, titulo: e.currentTarget.value } : null))
|
setNotaEnEdicion((prev) => (prev ? { ...prev, titulo: e.currentTarget.value } : null))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<Textarea
|
||||||
label="Contenido"
|
label="Contenido"
|
||||||
|
minRows={6}
|
||||||
|
autosize
|
||||||
value={notaEnEdicion?.texto || ""}
|
value={notaEnEdicion?.texto || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setNotaEnEdicion((prev) => (prev ? { ...prev, texto: e.currentTarget.value } : null))
|
setNotaEnEdicion((prev) => (prev ? { ...prev, texto: e.currentTarget.value } : null))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button onClick={guardarEdicionNota}>Guardar cambios</Button>
|
<Group grow>
|
||||||
|
<Button color="blue" onClick={guardarEdicionNota}>
|
||||||
|
💾 Guardar cambios
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!notaEnEdicion || !bibliotecaSeleccionada) return;
|
||||||
|
await eliminarNota(notaEnEdicion.id);
|
||||||
|
setModalEditarAbierto(false);
|
||||||
|
setNotaEnEdicion(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🗑️ Eliminar nota
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* Modal para crear una biblioteca */}
|
||||||
|
<Modal
|
||||||
|
opened={modalNuevaBiblio}
|
||||||
|
onClose={() => setModalNuevaBiblio(false)}
|
||||||
|
title="Crear nueva biblioteca"
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label="Nombre"
|
||||||
|
value={nombreBiblio}
|
||||||
|
onChange={(e) => setNombreBiblio(e.currentTarget.value)}
|
||||||
|
disabled={loadingNuevaBiblio}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Descripción"
|
||||||
|
value={descripcionBiblio}
|
||||||
|
onChange={(e) => setDescripcionBiblio(e.currentTarget.value)}
|
||||||
|
disabled={loadingNuevaBiblio}
|
||||||
|
/>
|
||||||
|
<Button onClick={crearBiblioteca} loading={loadingNuevaBiblio}>
|
||||||
|
Crear
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
|
||||||
</AppShellWithMenu>
|
</AppShellWithMenu>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"'OPAK20250510-ac2cea8af3110632314'"
|
"'OPAK20250513-61b29978b7604031014'"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 2,
|
"execution_count": 2,
|
||||||
@@ -37,8 +37,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"# apikey_gpt = OpenAICredencial(titulo=\"Credencial_enmanuel_gpt\")\n",
|
"apikey_gpt = OpenAICredencial(titulo=\"Credencial_enmanuel_gpt\",\n",
|
||||||
"# repo.add(apikey_gpt)"
|
" api_key=\"\")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"repo.add(apikey_gpt)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"credencial_openai = repo.get_by_id('OPAK20250510-ac2cea8af3110632314')\n",
|
"credencial_openai = repo.get_by_id('OPAK20250513-61b29978b7604031014')\n",
|
||||||
"print(f\"✅ Credencial: {credencial_openai.titulo}\")"
|
"print(f\"✅ Credencial: {credencial_openai.titulo}\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -13,7 +13,7 @@ from src.Llms.Memory.postgres_MemoryConv import MemoryConvPostgres
|
|||||||
conexion_admin = PostgresConexion(db_credencial)
|
conexion_admin = PostgresConexion(db_credencial)
|
||||||
|
|
||||||
repo = OpenAICredencialRepo(conexion_admin)
|
repo = OpenAICredencialRepo(conexion_admin)
|
||||||
credencial_openai = repo.get_by_id(1)
|
credencial_openai = repo.get_by_id("OPAK20250513-61b29978b7604031014")
|
||||||
|
|
||||||
cliente = OpenAICliente(credencial_openai)
|
cliente = OpenAICliente(credencial_openai)
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,16 @@ class Model_base:
|
|||||||
def __json__(self) -> dict:
|
def __json__(self) -> dict:
|
||||||
"""Devuelve una representación JSON serializable (dict plano)."""
|
"""Devuelve una representación JSON serializable (dict plano)."""
|
||||||
out = {}
|
out = {}
|
||||||
|
|
||||||
|
# Prevención de error: solo ejecuta si __table__ existe
|
||||||
|
if not hasattr(self, "__table__"):
|
||||||
|
return out
|
||||||
|
|
||||||
for attr in self.__table__.columns:
|
for attr in self.__table__.columns:
|
||||||
val = getattr(self, attr.name)
|
val = getattr(self, attr.name, None)
|
||||||
if isinstance(val, datetime):
|
if isinstance(val, datetime):
|
||||||
out[attr.name] = val.isoformat()
|
out[attr.name] = val.isoformat()
|
||||||
else:
|
else:
|
||||||
out[attr.name] = val
|
out[attr.name] = val
|
||||||
return out
|
|
||||||
|
return out
|
||||||
@@ -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())
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
@@ -27,7 +27,7 @@ class MemoryConvPostgres(MemoryConvABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Crea la tabla si no existe
|
# 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:
|
def guardar_turno(self, rol: Literal["user", "assistant"], contenido: str) -> None:
|
||||||
stmt = insert(self.tabla).values(rol=rol, contenido=contenido)
|
stmt = insert(self.tabla).values(rol=rol, contenido=contenido)
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
num_tokens_maximos: int = 512,
|
num_tokens_maximos: int = 512,
|
||||||
use_legacy: bool = False
|
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__(
|
super().__init__(
|
||||||
id=id,
|
|
||||||
model=model,
|
model=model,
|
||||||
temperature=temperature,
|
temperature=temperature,
|
||||||
top_p=top_p,
|
top_p=top_p,
|
||||||
@@ -27,6 +29,8 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
frecuencia_penalizacion=frecuencia_penalizacion,
|
frecuencia_penalizacion=frecuencia_penalizacion,
|
||||||
num_tokens_maximos=num_tokens_maximos
|
num_tokens_maximos=num_tokens_maximos
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Asignar cliente e indicadores adicionales
|
||||||
self.cliente = cliente
|
self.cliente = cliente
|
||||||
self.use_legacy = use_legacy
|
self.use_legacy = use_legacy
|
||||||
|
|
||||||
@@ -79,8 +83,8 @@ class ModeloOpenAI(ModeloABC):
|
|||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
async def generador():
|
async def generador():
|
||||||
for token in resultado: # ya es un generador del cliente
|
for token in resultado:
|
||||||
yield token
|
yield token
|
||||||
return generador()
|
return generador()
|
||||||
else:
|
else:
|
||||||
return resultado.choices[0].message.content
|
return resultado.choices[0].message.content
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from src.ConexionSql.Base_conexion import ConexionBase
|
|||||||
from sqlalchemy import MetaData # Asegúrate de importar esto
|
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 src.TextManager.notas_mmr import generar_tabla_nota_para_biblioteca # Ajusta si es necesario
|
||||||
from sqlalchemy import inspect
|
from sqlalchemy import inspect
|
||||||
|
from src.base import Base
|
||||||
|
|
||||||
|
|
||||||
class Biblioteca:
|
class Biblioteca:
|
||||||
@@ -38,18 +39,21 @@ class Biblioteca:
|
|||||||
raise ValueError("Debes proporcionar un 'embedder' o un 'vector_dim' explícito.")
|
raise ValueError("Debes proporcionar un 'embedder' o un 'vector_dim' explícito.")
|
||||||
|
|
||||||
def generar_modelo_notas(self, conexion: ConexionBase):
|
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}"
|
nombre_tabla = f"{self.nombre}"
|
||||||
|
print(f"[Notas] Generando tabla: {nombre_tabla}")
|
||||||
|
|
||||||
engine = conexion.get_engine()
|
engine = conexion.get_engine()
|
||||||
|
|
||||||
inspector = inspect(engine)
|
inspector = inspect(engine)
|
||||||
|
|
||||||
if inspector.has_table(nombre_tabla):
|
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.")
|
raise ValueError(f"Ya existe una tabla con el nombre '{nombre_tabla}' en la base de datos.")
|
||||||
|
|
||||||
metadata = MetaData()
|
print("[Notas] Generando definición SQL...")
|
||||||
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, metadata)
|
tabla, NotaModel = generar_tabla_nota_para_biblioteca(nombre_tabla, self.vector_dim, Base.metadata)
|
||||||
metadata.create_all(engine)
|
|
||||||
|
print("[Notas] Creando tabla en base de datos...")
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
print("[Notas] ✔️ Tabla creada")
|
||||||
|
|
||||||
return NotaModel
|
return NotaModel
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
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 pgvector.sqlalchemy import Vector
|
||||||
from sqlalchemy.orm import registry, Session
|
from sqlalchemy.orm import registry, Session
|
||||||
from src.TextManager.nota import Nota
|
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.Model import Model_base
|
||||||
from src.ArquitectureLayer.Repo import Repo_base
|
from src.ArquitectureLayer.Repo import Repo_base
|
||||||
|
|
||||||
|
|
||||||
|
from src.base import Base # Este es tu declarative_base()
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
# Cargar .env
|
# Cargar .env
|
||||||
# ----------------------
|
# ----------------------
|
||||||
@@ -28,47 +31,42 @@ mapper_registry = registry()
|
|||||||
# FUNCIONES AUXILIARES
|
# FUNCIONES AUXILIARES
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
|
|
||||||
def generar_tabla_nota_para_biblioteca(biblioteca_nombre: str, vector_dim: int, metadata: MetaData) -> Tuple[Table, type]:
|
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
|
try:
|
||||||
nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower())
|
print(f"[INFO] Generando tabla para biblioteca: '{biblioteca_nombre}' con dimensión de vector: {vector_dim}")
|
||||||
|
|
||||||
tabla = Table(
|
# Nombre SQL-safe
|
||||||
nombre_tabla,
|
nombre_tabla = re.sub(r"[^a-zA-Z0-9_]", "_", biblioteca_nombre.strip().lower())
|
||||||
metadata,
|
print(f"[DEBUG] Nombre de tabla SQL-safe: '{nombre_tabla}'")
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
class NotaModel(Model_base):
|
# Modelo ORM dinámico
|
||||||
"""
|
class NotaModel(Base, Model_base):
|
||||||
Modelo ORM generado dinámicamente para notas de una biblioteca.
|
__tablename__ = nombre_tabla
|
||||||
"""
|
__table_args__ = {"extend_existing": True}
|
||||||
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)
|
|
||||||
|
|
||||||
# Registrar solo si aún no se ha hecho
|
id = Column(String, primary_key=True)
|
||||||
if NotaModel.__name__ not in mapper_registry._class_registry:
|
titulo = Column(String, nullable=False)
|
||||||
mapper_registry.map_imperatively(NotaModel, tabla)
|
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
|
# MAPPER
|
||||||
|
|||||||
Reference in New Issue
Block a user