--- layout: default title: "Especificación Técnica del Procesamiento por Lotes de Embeddings" parent: "Spanish (Beta)" --- # Especificación Técnica del Procesamiento por Lotes de Embeddings > **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta. ## Resumen Esta especificación describe optimizaciones para el servicio de embeddings para soportar el procesamiento por lotes de múltiples textos en una sola solicitud. La implementación actual procesa un texto a la vez, perdiendo las significativas ventajas de rendimiento que los modelos de embeddings proporcionan al procesar lotes. 1. **Ineficiencia en el Procesamiento de un Solo Texto**: La implementación actual envuelve textos individuales en una lista, lo que no aprovecha las capacidades de procesamiento por lotes de FastEmbed. 2. **Sobrecarga de una Solicitud por Texto**: Cada texto requiere un viaje de ida y vuelta separado de Pulsar. 3. **Ineficiencia en la Inferencia del Modelo**: Los modelos de embeddings tienen una sobrecarga fija por lote; los lotes pequeños desperdician recursos de GPU/CPU. 4. **Procesamiento Serial en los Llamadores**: Los servicios clave iteran sobre los elementos y llaman a los embeddings uno a la vez. ## Objetivos **Soporte para la API de Lotes**: Permitir el procesamiento de múltiples textos en una sola solicitud. **Compatibilidad con Versiones Anteriores**: Mantener el soporte para solicitudes de un solo texto. **Mejora Significativa del Rendimiento**: Apuntar a una mejora de rendimiento de 5 a 10 veces para operaciones por lotes. **Latencia Reducida por Texto**: Disminuir la latencia amortizada al incrustar múltiples textos. **Eficiencia de Memoria**: Procesar lotes sin un consumo excesivo de memoria. **Independencia del Proveedor**: Soporte para el procesamiento por lotes en FastEmbed, Ollama y otros proveedores. **Migración de Llamadores**: Actualizar todos los llamadores de embeddings para que utilicen la API de lotes cuando sea beneficioso. ## Antecedentes ### Implementación Actual - Servicio de Embeddings La implementación de embeddings en `trustgraph-flow/trustgraph/embeddings/fastembed/processor.py` presenta una ineficiencia de rendimiento significativa: ```python # fastembed/processor.py line 56 async def on_embeddings(self, text, model=None): use_model = model or self.default_model self._load_model(use_model) vecs = self.embeddings.embed([text]) # Single text wrapped in list return [v.tolist() for v in vecs] ``` **Problemas:** 1. **Tamaño de lote 1**: El método `embed()` de FastEmbed está optimizado para el procesamiento por lotes, pero siempre lo llamamos con `[text]`, un lote de tamaño 1. 2. **Sobrecarga por solicitud**: Cada solicitud de incrustación incurre en: Serialización/deserialización de mensajes de Pulsar Latencia de ida y vuelta de la red Sobrecarga de inicio de inferencia del modelo Sobrecarga de programación asíncrona de Python 3. **Limitación del esquema**: El esquema `EmbeddingsRequest` solo admite un texto: ```python @dataclass class EmbeddingsRequest: text: str = "" # Single text only ``` ### Llamadores actuales - Procesamiento serial #### 1. API Gateway **Archivo:** `trustgraph-flow/trustgraph/gateway/dispatch/embeddings.py` La puerta de enlace acepta solicitudes de incrustación de texto único a través de HTTP/WebSocket y las reenvía al servicio de incrustaciones. Actualmente no existe un punto final por lotes. ```python class EmbeddingsRequestor(ServiceRequestor): # Handles single EmbeddingsRequest -> EmbeddingsResponse request_schema=EmbeddingsRequest, # Single text only response_schema=EmbeddingsResponse, ``` **Impacto:** Los clientes externos (aplicaciones web, scripts) deben realizar N solicitudes HTTP para incrustar N textos. #### 2. Servicio de Incorporación de Documentos **Archivo:** `trustgraph-flow/trustgraph/embeddings/document_embeddings/embeddings.py` Procesa fragmentos de documentos uno a la vez: ```python async def on_message(self, msg, consumer, flow): v = msg.value() # Single chunk per request resp = await flow("embeddings-request").request( EmbeddingsRequest(text=v.chunk) ) vectors = resp.vectors ``` **Impacto:** Cada fragmento de documento requiere una llamada de incrustación separada. Un documento con 100 fragmentos = 100 solicitudes de incrustación. #### 3. Servicio de Incrustaciones de Grafos **Archivo:** `trustgraph-flow/trustgraph/embeddings/graph_embeddings/embeddings.py` Recorre las entidades e incrusta cada una de forma secuencial: ```python async def on_message(self, msg, consumer, flow): for entity in v.entities: # Serial embedding - one entity at a time vectors = await flow("embeddings-request").embed( text=entity.context ) entities.append(EntityEmbeddings( entity=entity.entity, vectors=vectors, chunk_id=entity.chunk_id, )) ``` **Impacto:** Un mensaje con 50 entidades = 50 solicitudes de incrustación serial. Esto es un cuello de botella importante durante la construcción del grafo de conocimiento. #### 4. Servicio de Incrustaciones de Filas **Archivo:** `trustgraph-flow/trustgraph/embeddings/row_embeddings/embeddings.py` Itera sobre textos únicos e incrusta cada uno de forma serial: ```python async def on_message(self, msg, consumer, flow): for text, (index_name, index_value) in texts_to_embed.items(): # Serial embedding - one text at a time vectors = await flow("embeddings-request").embed(text=text) embeddings_list.append(RowIndexEmbedding( index_name=index_name, index_value=index_value, text=text, vectors=vectors )) ``` **Impacto:** Procesar una tabla con 100 valores indexados únicos = 100 solicitudes de incrustación seriales. #### 5. EmbeddingsClient (Cliente Base) **Archivo:** `trustgraph-base/trustgraph/base/embeddings_client.py` El cliente utilizado por todos los procesadores de flujo solo admite la incrustación de texto único: ```python class EmbeddingsClient(RequestResponse): async def embed(self, text, timeout=30): resp = await self.request( EmbeddingsRequest(text=text), # Single text timeout=timeout ) return resp.vectors ``` **Impacto:** Todos los clientes que utilizan esta herramienta están limitados a operaciones de texto único. #### 6. Herramientas de Línea de Comandos **Archivo:** `trustgraph-cli/trustgraph/cli/invoke_embeddings.py` La herramienta de la línea de comandos acepta un único argumento de texto: ```python def query(url, flow_id, text, token=None): result = flow.embeddings(text=text) # Single text vectors = result.get("vectors", []) ``` **Impacto:** Los usuarios no pueden realizar incrustaciones por lotes desde la línea de comandos. El procesamiento de un archivo de textos requiere N invocaciones. #### 7. SDK de Python El SDK de Python proporciona dos clases de cliente para interactuar con los servicios de TrustGraph. Ambas solo admiten la incrustación de un solo texto. **Archivo:** `trustgraph-base/trustgraph/api/flow.py` ```python class FlowInstance: def embeddings(self, text): """Get embeddings for a single text""" input = {"text": text} return self.request("service/embeddings", input)["vectors"] ``` **Archivo:** `trustgraph-base/trustgraph/api/socket_client.py` ```python class SocketFlowInstance: def embeddings(self, text: str, **kwargs: Any) -> Dict[str, Any]: """Get embeddings for a single text via WebSocket""" request = {"text": text} return self.client._send_request_sync( "embeddings", self.flow_id, request, False ) ``` **Impacto:** Los desarrolladores de Python que utilizan el SDK deben iterar sobre los textos y realizar N llamadas de API separadas. No existe soporte para la inserción por lotes para los usuarios del SDK. ### Impacto en el rendimiento Para la ingestión típica de documentos (1000 fragmentos de texto): **Actual:** 1000 solicitudes separadas, 1000 llamadas de inferencia de modelos. **Por lotes (batch_size=32):** 32 solicitudes, 32 llamadas de inferencia de modelos (reducción del 96.8%). Para la inserción de gráficos (mensaje con 50 entidades): **Actual:** 50 llamadas `await` secuenciales, ~5-10 segundos. **Por lotes:** 1-2 llamadas por lotes, ~0.5-1 segundo (mejora de 5-10 veces). FastEmbed y bibliotecas similares logran una escalabilidad de rendimiento cercana a la lineal con el tamaño del lote hasta los límites del hardware (típicamente 32-128 textos por lote). ## Diseño técnico ### Arquitectura La optimización del procesamiento por lotes de inserciones requiere cambios en los siguientes componentes: #### 1. **Mejora del esquema** Extender `EmbeddingsRequest` para admitir múltiples textos. Extender `EmbeddingsResponse` para devolver múltiples conjuntos de vectores. Mantener la compatibilidad con versiones anteriores con solicitudes de un solo texto. Módulo: `trustgraph-base/trustgraph/schema/services/llm.py` #### 2. **Mejora del servicio base** Actualizar `EmbeddingsService` para manejar solicitudes por lotes. Agregar configuración del tamaño del lote. Implementar el manejo de solicitudes con conocimiento del lote. Módulo: `trustgraph-base/trustgraph/base/embeddings_service.py` #### 3. **Actualizaciones del procesador del proveedor** Actualizar el procesador FastEmbed para pasar el lote completo a `embed()`. Actualizar el procesador de Ollama para manejar lotes (si es compatible). Agregar procesamiento secuencial de respaldo para los proveedores que no admiten lotes. Módulos: `trustgraph-flow/trustgraph/embeddings/fastembed/processor.py` `trustgraph-flow/trustgraph/embeddings/ollama/processor.py` #### 4. **Mejora del cliente** Agregar un método de inserción por lotes a `EmbeddingsClient`. Admitir tanto API individuales como por lotes. Agregar inserción automática por lotes para grandes entradas. Módulo: `trustgraph-base/trustgraph/base/embeddings_client.py` #### 5. **Actualizaciones del llamador: Procesadores de flujo** Actualizar `graph_embeddings` para agrupar los contextos de las entidades. Actualizar `row_embeddings` para agrupar los textos del índice. Actualizar `document_embeddings` si el agrupamiento de mensajes es factible. Módulos: `trustgraph-flow/trustgraph/embeddings/graph_embeddings/embeddings.py` `trustgraph-flow/trustgraph/embeddings/row_embeddings/embeddings.py` `trustgraph-flow/trustgraph/embeddings/document_embeddings/embeddings.py` #### 6. **Mejora de la puerta de enlace de API** Agregar un punto final de inserción por lotes. Admitir una matriz de textos en el cuerpo de la solicitud. Módulo: `trustgraph-flow/trustgraph/gateway/dispatch/embeddings.py` #### 7. **Mejora de la herramienta de la línea de comandos** Agregar soporte para múltiples textos o entrada de archivos. Agregar un parámetro de tamaño de lote. Módulo: `trustgraph-cli/trustgraph/cli/invoke_embeddings.py` #### 8. **Mejora del SDK de Python** Agregar el método `embeddings_batch()` a `FlowInstance`. Agregar el método `embeddings_batch()` a `SocketFlowInstance`. Admitir tanto API individuales como por lotes para los usuarios del SDK. Módulos: `trustgraph-base/trustgraph/api/flow.py` `trustgraph-base/trustgraph/api/socket_client.py` ### Modelos de datos #### EmbeddingsRequest ```python @dataclass class EmbeddingsRequest: texts: list[str] = field(default_factory=list) ``` Uso: Texto único: `EmbeddingsRequest(texts=["hello world"])` Lote: `EmbeddingsRequest(texts=["text1", "text2", "text3"])` #### EmbeddingsResponse ```python @dataclass class EmbeddingsResponse: error: Error | None = None vectors: list[list[list[float]]] = field(default_factory=list) ``` Estructura de la respuesta: `vectors[i]` contiene el conjunto de vectores para `texts[i]` Cada conjunto de vectores es `list[list[float]]` (los modelos pueden devolver múltiples vectores por texto) Ejemplo: 3 textos → `vectors` tiene 3 entradas, cada una conteniendo los incrustados de ese texto ### APIs #### EmbeddingsClient ```python class EmbeddingsClient(RequestResponse): async def embed( self, texts: list[str], timeout: float = 300, ) -> list[list[list[float]]]: """ Embed one or more texts in a single request. Args: texts: List of texts to embed timeout: Timeout for the operation Returns: List of vector sets, one per input text """ resp = await self.request( EmbeddingsRequest(texts=texts), timeout=timeout ) if resp.error: raise RuntimeError(resp.error.message) return resp.vectors ``` #### Punto final de incrustaciones de la API Gateway Punto final actualizado que admite incrustaciones individuales o por lotes: ``` POST /api/v1/embeddings Content-Type: application/json { "texts": ["text1", "text2", "text3"], "flow_id": "default" } Response: { "vectors": [ [[0.1, 0.2, ...]], [[0.3, 0.4, ...]], [[0.5, 0.6, ...]] ] } ``` ### Detalles de implementación #### Fase 1: Cambios en el esquema **EmbeddingsRequest:** ```python @dataclass class EmbeddingsRequest: texts: list[str] = field(default_factory=list) ``` **Respuesta de Inserción:** ```python @dataclass class EmbeddingsResponse: error: Error | None = None vectors: list[list[list[float]]] = field(default_factory=list) ``` **Actualizaciones en EmbeddingsService.on_request:** ```python async def on_request(self, msg, consumer, flow): request = msg.value() id = msg.properties()["id"] model = flow("model") vectors = await self.on_embeddings(request.texts, model=model) response = EmbeddingsResponse(error=None, vectors=vectors) await flow("response").send(response, properties={"id": id}) ``` #### Fase 2: Actualización del Procesador FastEmbed **Actual (Ineficiente):** ```python async def on_embeddings(self, text, model=None): use_model = model or self.default_model self._load_model(use_model) vecs = self.embeddings.embed([text]) # Batch of 1 return [v.tolist() for v in vecs] ``` **Actualizado:** ```python async def on_embeddings(self, texts: list[str], model=None): """Embed texts - processes all texts in single model call""" if not texts: return [] use_model = model or self.default_model self._load_model(use_model) # FastEmbed handles the full batch efficiently all_vecs = list(self.embeddings.embed(texts)) # Return list of vector sets, one per input text return [[v.tolist()] for v in all_vecs] ``` #### Fase 3: Actualización del Servicio de Inserción de Gráficos **Actual (Serial):** ```python async def on_message(self, msg, consumer, flow): entities = [] for entity in v.entities: vectors = await flow("embeddings-request").embed(text=entity.context) entities.append(EntityEmbeddings(...)) ``` **Actualizado (Lote):** ```python async def on_message(self, msg, consumer, flow): # Collect all contexts contexts = [entity.context for entity in v.entities] # Single batch embedding call all_vectors = await flow("embeddings-request").embed(texts=contexts) # Pair results with entities entities = [ EntityEmbeddings( entity=entity.entity, vectors=vectors[0], # First vector from the set chunk_id=entity.chunk_id, ) for entity, vectors in zip(v.entities, all_vectors) ] ``` #### Fase 4: Actualización del Servicio de Incrustación de Filas **Actual (Serial):** ```python for text, (index_name, index_value) in texts_to_embed.items(): vectors = await flow("embeddings-request").embed(text=text) embeddings_list.append(RowIndexEmbedding(...)) ``` **Actualizado (Lote):** ```python # Collect texts and metadata texts = list(texts_to_embed.keys()) metadata = list(texts_to_embed.values()) # Single batch embedding call all_vectors = await flow("embeddings-request").embed(texts=texts) # Pair results embeddings_list = [ RowIndexEmbedding( index_name=meta[0], index_value=meta[1], text=text, vectors=vectors[0] # First vector from the set ) for text, meta, vectors in zip(texts, metadata, all_vectors) ] ``` #### Fase 5: Mejora de la Herramienta de Línea de Comandos (CLI) **CLI Actualizada:** ```python def main(): parser = argparse.ArgumentParser(...) parser.add_argument( 'text', nargs='*', # Zero or more texts help='Text(s) to convert to embedding vectors', ) parser.add_argument( '-f', '--file', help='File containing texts (one per line)', ) parser.add_argument( '--batch-size', type=int, default=32, help='Batch size for processing (default: 32)', ) ``` Uso: ```bash # Single text (existing) tg-invoke-embeddings "hello world" # Multiple texts tg-invoke-embeddings "text one" "text two" "text three" # From file tg-invoke-embeddings -f texts.txt --batch-size 64 ``` #### Fase 6: Mejora del SDK de Python **FlowInstance (cliente HTTP):** ```python class FlowInstance: def embeddings(self, texts: list[str]) -> list[list[list[float]]]: """ Get embeddings for one or more texts. Args: texts: List of texts to embed Returns: List of vector sets, one per input text """ input = {"texts": texts} return self.request("service/embeddings", input)["vectors"] ``` **SocketFlowInstance (cliente WebSocket):** ```python class SocketFlowInstance: def embeddings(self, texts: list[str], **kwargs: Any) -> list[list[list[float]]]: """ Get embeddings for one or more texts via WebSocket. Args: texts: List of texts to embed Returns: List of vector sets, one per input text """ request = {"texts": texts} response = self.client._send_request_sync( "embeddings", self.flow_id, request, False ) return response["vectors"] ``` **Ejemplos de uso del SDK:** ```python # Single text vectors = flow.embeddings(["hello world"]) print(f"Dimensions: {len(vectors[0][0])}") # Batch embedding texts = ["text one", "text two", "text three"] all_vectors = flow.embeddings(texts) # Process results for text, vecs in zip(texts, all_vectors): print(f"{text}: {len(vecs[0])} dimensions") ``` ## Consideraciones de seguridad **Límites de tamaño de la solicitud**: Aplicar un tamaño máximo de lote para evitar el agotamiento de recursos. **Manejo de tiempos de espera**: Ajustar los tiempos de espera de manera adecuada para el tamaño del lote. **Límites de memoria**: Monitorear el uso de memoria para lotes grandes. **Validación de entrada**: Validar todos los textos en el lote antes de procesarlos. ## Consideraciones de rendimiento ### Mejoras esperadas **Rendimiento:** Texto único: ~10-50 textos/segundo (dependiendo del modelo) Lote (tamaño 32): ~200-500 textos/segundo (mejora de 5 a 10 veces) **Latencia por texto:** Texto único: 50-200 ms por texto Lote (tamaño 32): 5-20 ms por texto (promedio) **Mejoras específicas del servicio:** | Servicio | Actual | En lote | Mejora | |---------|---------|---------|-------------| | Incrustaciones de grafos (50 entidades) | 5-10 s | 0.5-1 s | 5-10 veces | | Incrustaciones de filas (100 textos) | 10-20 s | 1-2 s | 5-10 veces | | Ingestión de documentos (1000 fragmentos) | 100-200 s | 10-30 s | 5-10 veces | ### Parámetros de configuración ```python # Recommended defaults DEFAULT_BATCH_SIZE = 32 MAX_BATCH_SIZE = 128 BATCH_TIMEOUT_MULTIPLIER = 2.0 ``` ## Estrategia de Pruebas ### Pruebas Unitarias Inserción de texto única (compatibilidad con versiones anteriores) Manejo de lotes vacíos Aplicación del tamaño máximo del lote Manejo de errores para fallas parciales del lote ### Pruebas de Integración Inserción de lote de extremo a extremo a través de Pulsar Procesamiento por lotes del servicio de inserción de gráficos Procesamiento por lotes del servicio de inserción de filas Punto final de lote de la puerta de enlace de la API ### Pruebas de Rendimiento Comparación de rendimiento de inserción única frente a inserción por lotes Uso de memoria con varios tamaños de lote Análisis de la distribución de la latencia ## Plan de Migración Esta es una versión que introduce cambios importantes. Todas las fases se implementan juntas. ### Fase 1: Cambios en el Esquema Reemplazar `text: str` con `texts: list[str]` en EmbeddingsRequest Cambiar el tipo de `vectors` a `list[list[list[float]]]` en EmbeddingsResponse ### Fase 2: Actualizaciones del Procesador Actualizar la firma de `on_embeddings` en los procesadores FastEmbed y Ollama Procesar el lote completo en una única llamada al modelo ### Fase 3: Actualizaciones del Cliente Actualizar `EmbeddingsClient.embed()` para que acepte `texts: list[str]` ### Fase 4: Actualizaciones del Llamador Actualizar graph_embeddings para procesar contextos de entidades por lotes Actualizar row_embeddings para procesar textos de índice por lotes Actualizar document_embeddings para usar el nuevo esquema Actualizar la herramienta de línea de comandos ### Fase 5: Puerta de Enlace de la API Actualizar el punto final de inserción para el nuevo esquema ### Fase 6: SDK de Python Actualizar la firma de `FlowInstance.embeddings()` Actualizar la firma de `SocketFlowInstance.embeddings()` ## Preguntas Abiertas **Inserción de Lotes Grandes en Streaming**: ¿Debemos admitir la transmisión de resultados para lotes muy grandes (>100 textos)? **Límites Específicos del Proveedor**: ¿Cómo debemos manejar los proveedores con tamaños máximos de lote diferentes? **Manejo de Fallas Parciales**: Si un texto en un lote falla, ¿deberíamos fallar todo el lote o devolver resultados parciales? **Inserción por Lotes de Documentos**: ¿Debemos realizar la inserción por lotes en varios mensajes de Chunk o mantener el procesamiento por mensaje? ## Referencias [Documentación de FastEmbed](https://github.com/qdrant/fastembed) [API de Inserción de Ollama](https://github.com/ollama/ollama) [Implementación del Servicio de Inserción](trustgraph-base/trustgraph/base/embeddings_service.py) [Optimización del Rendimiento de GraphRAG](graphrag-performance-optimization.md)