Primera version de chunkeo completo crud

This commit is contained in:
Sebastian
2025-11-05 19:18:11 +00:00
parent df2c184814
commit 7c6e8c4858
36 changed files with 6242 additions and 5 deletions

View File

@@ -0,0 +1,442 @@
"""
Servicio de lógica de negocio para operaciones con bases de datos vectoriales.
Este módulo contiene toda la lógica de negocio relacionada con la gestión
de colecciones y chunks en bases de datos vectoriales.
"""
import logging
from typing import List, Dict, Any, Optional
from app.vector_db import get_vector_db
from app.models.vector_models import (
CollectionCreateRequest,
CollectionCreateResponse,
CollectionDeleteResponse,
CollectionExistsResponse,
CollectionInfoResponse,
FileExistsInCollectionResponse,
GetChunksByFileResponse,
DeleteFileFromCollectionResponse,
AddChunksResponse,
VectorDBHealthResponse,
VectorDBErrorResponse
)
logger = logging.getLogger(__name__)
class VectorService:
"""
Servicio para gestionar operaciones con bases de datos vectoriales.
Este servicio actúa como una capa intermedia entre los routers y
la implementación de la base de datos vectorial.
"""
def __init__(self):
"""Inicializa el servicio con la instancia de la base de datos vectorial."""
self.vector_db = get_vector_db()
async def check_collection_exists(self, collection_name: str) -> CollectionExistsResponse:
"""
Verifica si una colección existe.
Args:
collection_name: Nombre de la colección
Returns:
CollectionExistsResponse: Response con el resultado
"""
try:
exists = await self.vector_db.collection_exists(collection_name)
logger.info(f"Verificación de colección '{collection_name}': {exists}")
return CollectionExistsResponse(
exists=exists,
collection_name=collection_name
)
except Exception as e:
logger.error(f"Error al verificar colección '{collection_name}': {e}")
raise
async def create_collection(
self,
request: CollectionCreateRequest
) -> CollectionCreateResponse:
"""
Crea una nueva colección.
Args:
request: Request con los datos de la colección
Returns:
CollectionCreateResponse: Response con el resultado
Raises:
ValueError: Si la colección ya existe
"""
try:
# Verificar si ya existe
exists = await self.vector_db.collection_exists(request.collection_name)
if exists:
logger.warning(f"Intento de crear colección existente: '{request.collection_name}'")
raise ValueError(f"La colección '{request.collection_name}' ya existe")
# Crear la colección
success = await self.vector_db.create_collection(
collection_name=request.collection_name,
vector_size=request.vector_size,
distance=request.distance
)
if success:
logger.info(f"Colección '{request.collection_name}' creada exitosamente")
return CollectionCreateResponse(
success=True,
collection_name=request.collection_name,
message=f"Colección '{request.collection_name}' creada exitosamente"
)
else:
logger.error(f"Fallo al crear colección '{request.collection_name}'")
raise Exception(f"No se pudo crear la colección '{request.collection_name}'")
except ValueError:
raise
except Exception as e:
logger.error(f"Error al crear colección '{request.collection_name}': {e}")
raise
async def delete_collection(self, collection_name: str) -> CollectionDeleteResponse:
"""
Elimina una colección completa.
Args:
collection_name: Nombre de la colección
Returns:
CollectionDeleteResponse: Response con el resultado
Raises:
ValueError: Si la colección no existe
"""
try:
# Verificar que existe
exists = await self.vector_db.collection_exists(collection_name)
if not exists:
logger.warning(f"Intento de eliminar colección inexistente: '{collection_name}'")
raise ValueError(f"La colección '{collection_name}' no existe")
# Eliminar la colección
success = await self.vector_db.delete_collection(collection_name)
if success:
logger.info(f"Colección '{collection_name}' eliminada exitosamente")
return CollectionDeleteResponse(
success=True,
collection_name=collection_name,
message=f"Colección '{collection_name}' eliminada exitosamente"
)
else:
logger.error(f"Fallo al eliminar colección '{collection_name}'")
raise Exception(f"No se pudo eliminar la colección '{collection_name}'")
except ValueError:
raise
except Exception as e:
logger.error(f"Error al eliminar colección '{collection_name}': {e}")
raise
async def get_collection_info(self, collection_name: str) -> Optional[CollectionInfoResponse]:
"""
Obtiene información de una colección.
Args:
collection_name: Nombre de la colección
Returns:
Optional[CollectionInfoResponse]: Información de la colección o None
"""
try:
info = await self.vector_db.get_collection_info(collection_name)
if info is None:
logger.warning(f"Colección '{collection_name}' no encontrada")
return None
return CollectionInfoResponse(**info)
except Exception as e:
logger.error(f"Error al obtener info de colección '{collection_name}': {e}")
raise
async def check_file_exists_in_collection(
self,
collection_name: str,
file_name: str
) -> FileExistsInCollectionResponse:
"""
Verifica si un archivo existe en una colección.
Args:
collection_name: Nombre de la colección
file_name: Nombre del archivo
Returns:
FileExistsInCollectionResponse: Response con el resultado
"""
try:
# Primero verificar que la colección existe
collection_exists = await self.vector_db.collection_exists(collection_name)
if not collection_exists:
logger.warning(f"Colección '{collection_name}' no existe")
return FileExistsInCollectionResponse(
exists=False,
collection_name=collection_name,
file_name=file_name,
chunk_count=0
)
# Verificar si el archivo existe
file_exists = await self.vector_db.file_exists_in_collection(
collection_name,
file_name
)
chunk_count = None
if file_exists:
chunk_count = await self.vector_db.count_chunks_in_file(
collection_name,
file_name
)
logger.info(
f"Archivo '{file_name}' en colección '{collection_name}': "
f"existe={file_exists}, chunks={chunk_count}"
)
return FileExistsInCollectionResponse(
exists=file_exists,
collection_name=collection_name,
file_name=file_name,
chunk_count=chunk_count
)
except Exception as e:
logger.error(
f"Error al verificar archivo '{file_name}' "
f"en colección '{collection_name}': {e}"
)
raise
async def get_chunks_by_file(
self,
collection_name: str,
file_name: str,
limit: Optional[int] = None
) -> GetChunksByFileResponse:
"""
Obtiene todos los chunks de un archivo.
Args:
collection_name: Nombre de la colección
file_name: Nombre del archivo
limit: Límite opcional de chunks
Returns:
GetChunksByFileResponse: Response con los chunks
Raises:
ValueError: Si la colección no existe
"""
try:
# Verificar que la colección existe
exists = await self.vector_db.collection_exists(collection_name)
if not exists:
logger.warning(f"Colección '{collection_name}' no existe")
raise ValueError(f"La colección '{collection_name}' no existe")
# Obtener chunks
chunks = await self.vector_db.get_chunks_by_file(
collection_name,
file_name,
limit
)
logger.info(
f"Obtenidos {len(chunks)} chunks del archivo '{file_name}' "
f"de la colección '{collection_name}'"
)
return GetChunksByFileResponse(
collection_name=collection_name,
file_name=file_name,
chunks=chunks,
total_chunks=len(chunks)
)
except ValueError:
raise
except Exception as e:
logger.error(
f"Error al obtener chunks del archivo '{file_name}' "
f"de la colección '{collection_name}': {e}"
)
raise
async def delete_file_from_collection(
self,
collection_name: str,
file_name: str
) -> DeleteFileFromCollectionResponse:
"""
Elimina todos los chunks de un archivo de una colección.
Args:
collection_name: Nombre de la colección
file_name: Nombre del archivo
Returns:
DeleteFileFromCollectionResponse: Response con el resultado
Raises:
ValueError: Si la colección no existe o el archivo no está en la colección
"""
try:
# Verificar que la colección existe
collection_exists = await self.vector_db.collection_exists(collection_name)
if not collection_exists:
logger.warning(f"Colección '{collection_name}' no existe")
raise ValueError(f"La colección '{collection_name}' no existe")
# Verificar que el archivo existe en la colección
file_exists = await self.vector_db.file_exists_in_collection(
collection_name,
file_name
)
if not file_exists:
logger.warning(
f"Archivo '{file_name}' no existe en colección '{collection_name}'"
)
raise ValueError(
f"El archivo '{file_name}' no existe en la colección '{collection_name}'"
)
# Eliminar el archivo
chunks_deleted = await self.vector_db.delete_file_from_collection(
collection_name,
file_name
)
logger.info(
f"Eliminados {chunks_deleted} chunks del archivo '{file_name}' "
f"de la colección '{collection_name}'"
)
return DeleteFileFromCollectionResponse(
success=True,
collection_name=collection_name,
file_name=file_name,
chunks_deleted=chunks_deleted,
message=f"Archivo '{file_name}' eliminado exitosamente ({chunks_deleted} chunks)"
)
except ValueError:
raise
except Exception as e:
logger.error(
f"Error al eliminar archivo '{file_name}' "
f"de la colección '{collection_name}': {e}"
)
raise
async def add_chunks(
self,
collection_name: str,
chunks: List[Dict[str, Any]]
) -> AddChunksResponse:
"""
Agrega chunks a una colección.
Args:
collection_name: Nombre de la colección
chunks: Lista de chunks a agregar
Returns:
AddChunksResponse: Response con el resultado
Raises:
ValueError: Si la colección no existe
"""
try:
# Verificar que la colección existe
exists = await self.vector_db.collection_exists(collection_name)
if not exists:
logger.warning(f"Colección '{collection_name}' no existe")
raise ValueError(f"La colección '{collection_name}' no existe")
# Agregar chunks
success = await self.vector_db.add_chunks(collection_name, chunks)
if success:
logger.info(
f"Agregados {len(chunks)} chunks a la colección '{collection_name}'"
)
return AddChunksResponse(
success=True,
collection_name=collection_name,
chunks_added=len(chunks),
message=f"Se agregaron {len(chunks)} chunks exitosamente"
)
else:
logger.error(f"Fallo al agregar chunks a '{collection_name}'")
raise Exception(f"No se pudieron agregar los chunks a '{collection_name}'")
except ValueError:
raise
except Exception as e:
logger.error(f"Error al agregar chunks a '{collection_name}': {e}")
raise
async def health_check(self) -> VectorDBHealthResponse:
"""
Verifica el estado de la conexión con la base de datos vectorial.
Returns:
VectorDBHealthResponse: Response con el estado
"""
try:
is_healthy = await self.vector_db.health_check()
if is_healthy:
return VectorDBHealthResponse(
status="healthy",
db_type="qdrant",
message="Conexión exitosa con la base de datos vectorial"
)
else:
return VectorDBHealthResponse(
status="unhealthy",
db_type="qdrant",
message="No se pudo conectar con la base de datos vectorial"
)
except Exception as e:
logger.error(f"Error en health check: {e}")
return VectorDBHealthResponse(
status="error",
db_type="qdrant",
message=f"Error al verificar conexión: {str(e)}"
)
# Instancia global del servicio
vector_service = VectorService()