mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 08:26:21 +02:00
1517 lines
54 KiB
Markdown
1517 lines
54 KiB
Markdown
|
|
---
|
||
|
|
layout: default
|
||
|
|
title: "Especificación Técnica de Refactorización de la API de Python"
|
||
|
|
parent: "Spanish (Beta)"
|
||
|
|
---
|
||
|
|
|
||
|
|
# Especificación Técnica de Refactorización de la API de Python
|
||
|
|
|
||
|
|
> **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 una refactorización integral de la biblioteca de cliente de la API de Python de TrustGraph para lograr la paridad de funciones con la API Gateway y agregar soporte para patrones de comunicación en tiempo real modernos.
|
||
|
|
|
||
|
|
La refactorización aborda cuatro casos de uso principales:
|
||
|
|
|
||
|
|
1. **Interacciones de LLM en Streaming**: Habilitar el streaming en tiempo real de las respuestas de LLM (agente, RAG de grafos, RAG de documentos, finalización de texto, prompts) con una latencia significativamente menor (~60 veces menor, 500 ms frente a 30 s para el primer token).
|
||
|
|
2. **Operaciones de Datos Masivos**: Soporte para la importación/exportación eficiente de triples, incrustaciones de grafos e incrustaciones de documentos para la gestión de grafos de conocimiento a gran escala.
|
||
|
|
3. **Paridad de Funciones**: Asegurar que cada punto final de la API Gateway tenga un método de API de Python correspondiente, incluidas las consultas de incrustaciones de grafos.
|
||
|
|
4. **Conexiones Persistentes**: Habilitar la comunicación basada en WebSocket para solicitudes multiplexadas y una menor sobrecarga de conexión.
|
||
|
|
|
||
|
|
## Objetivos
|
||
|
|
|
||
|
|
**Paridad de Funciones**: Cada servicio de la API Gateway tiene un método de API de Python correspondiente.
|
||
|
|
**Soporte de Streaming**: Todos los servicios capaces de streaming (agente, RAG, finalización de texto, prompt) admiten el streaming en la API de Python.
|
||
|
|
**Transporte WebSocket**: Agregar una capa de transporte WebSocket opcional para conexiones persistentes y multiplexación.
|
||
|
|
**Operaciones Masivas**: Agregar importación/exportación masiva eficiente para triples, incrustaciones de grafos e incrustaciones de documentos.
|
||
|
|
**Soporte Completo Async**: Implementación completa de async/await para todas las interfaces (REST, WebSocket, operaciones masivas, métricas).
|
||
|
|
**Compatibilidad con Versiones Anteriores**: El código existente continúa funcionando sin modificaciones.
|
||
|
|
**Seguridad de Tipos**: Mantener interfaces con seguridad de tipos utilizando dataclasses y sugerencias de tipo.
|
||
|
|
**Mejora Progresiva**: El streaming y el async son opcionales a través de la selección explícita de la interfaz.
|
||
|
|
**Rendimiento**: Lograr una mejora de latencia de 60 veces para las operaciones de streaming.
|
||
|
|
**Python Moderno**: Soporte para paradigmas tanto síncronos como asíncronos para una máxima flexibilidad.
|
||
|
|
|
||
|
|
## Antecedentes
|
||
|
|
|
||
|
|
### Estado Actual
|
||
|
|
|
||
|
|
La API de Python (`trustgraph-base/trustgraph/api/`) es una biblioteca de cliente REST con los siguientes módulos:
|
||
|
|
|
||
|
|
`flow.py`: Gestión de flujos y servicios con ámbito de flujo (50 métodos).
|
||
|
|
`library.py`: Operaciones de la biblioteca de documentos (9 métodos).
|
||
|
|
`knowledge.py`: Gestión central de grafos (4 métodos).
|
||
|
|
`collection.py`: Metadatos de colecciones (3 métodos).
|
||
|
|
`config.py`: Gestión de configuración (6 métodos).
|
||
|
|
`types.py`: Definiciones de tipos de datos (5 dataclasses).
|
||
|
|
|
||
|
|
**Operaciones Totales**: 50/59 (cobertura del 85%).
|
||
|
|
|
||
|
|
### Limitaciones Actuales
|
||
|
|
|
||
|
|
**Operaciones Faltantes**:
|
||
|
|
Consulta de incrustaciones de grafos (búsqueda semántica sobre entidades de grafos).
|
||
|
|
Importación/exportación masiva para triples, incrustaciones de grafos, incrustaciones de documentos, contextos de entidades, objetos.
|
||
|
|
Punto final de métricas.
|
||
|
|
|
||
|
|
**Capacidades Faltantes**:
|
||
|
|
Soporte de streaming para servicios de LLM.
|
||
|
|
Transporte WebSocket.
|
||
|
|
Solicitudes concurrentes multiplexadas.
|
||
|
|
Conexiones persistentes.
|
||
|
|
|
||
|
|
**Problemas de Rendimiento**:
|
||
|
|
Alta latencia para las interacciones de LLM (~30 s para el primer token).
|
||
|
|
Transferencia de datos masiva ineficiente (solicitud REST por elemento).
|
||
|
|
Sobrecarga de conexión para múltiples operaciones secuenciales.
|
||
|
|
|
||
|
|
**Problemas de Experiencia de Usuario**:
|
||
|
|
Sin retroalimentación en tiempo real durante la generación de LLM.
|
||
|
|
No se pueden cancelar las operaciones de LLM de larga duración.
|
||
|
|
Mala escalabilidad para las operaciones masivas.
|
||
|
|
|
||
|
|
### Impacto
|
||
|
|
|
||
|
|
La mejora de streaming de noviembre de 2024 en la API Gateway proporcionó una mejora de latencia de 60 veces (500 ms frente a 30 s para el primer token) para las interacciones de LLM, pero los usuarios de la API de Python no pueden aprovechar esta capacidad. Esto crea una brecha significativa de experiencia entre los usuarios de Python y los que no lo utilizan.
|
||
|
|
|
||
|
|
## Diseño Técnico
|
||
|
|
|
||
|
|
### Arquitectura
|
||
|
|
|
||
|
|
La API de Python refactorizada utiliza un **enfoque de interfaz modular** con objetos separados para diferentes patrones de comunicación. Todas las interfaces están disponibles tanto en variantes **síncronas como asíncronas**:
|
||
|
|
|
||
|
|
1. **Interfaz REST** (existente, mejorada).
|
||
|
|
**Sync**: `api.flow()`, `api.library()`, `api.knowledge()`, `api.collection()`, `api.config()`.
|
||
|
|
**Async**: `api.async_flow()`.
|
||
|
|
Solicitud/respuesta síncrona/asíncrona.
|
||
|
|
Modelo de conexión simple.
|
||
|
|
Predeterminado para la compatibilidad con versiones anteriores.
|
||
|
|
|
||
|
|
2. **Interfaz WebSocket** (nueva).
|
||
|
|
**Sync**: `api.socket()`.
|
||
|
|
**Async**: `api.async_socket()`.
|
||
|
|
Conexión persistente.
|
||
|
|
Solicitudes multiplexadas.
|
||
|
|
Soporte de streaming.
|
||
|
|
Mismas firmas de método que REST donde la funcionalidad se superpone.
|
||
|
|
|
||
|
|
3. **Interfaz de Operaciones Masivas** (nueva).
|
||
|
|
**Sync**: `api.bulk()`.
|
||
|
|
**Async**: `api.async_bulk()`.
|
||
|
|
Basado en WebSocket para la eficiencia.
|
||
|
|
Importación/exportación basada en iterador/AsyncIterator.
|
||
|
|
Maneja conjuntos de datos grandes.
|
||
|
|
|
||
|
|
4. **Interfaz de Métricas** (nueva).
|
||
|
|
**Sync**: `api.metrics()`.
|
||
|
|
**Async**: `api.async_metrics()`.
|
||
|
|
Acceso a métricas de Prometheus.
|
||
|
|
|
||
|
|
```python
|
||
|
|
import asyncio
|
||
|
|
|
||
|
|
# Synchronous interfaces
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# REST (existing, unchanged)
|
||
|
|
flow = api.flow().id("default")
|
||
|
|
response = flow.agent(question="...", user="...")
|
||
|
|
|
||
|
|
# WebSocket (new)
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
response = socket_flow.agent(question="...", user="...")
|
||
|
|
for chunk in socket_flow.agent(question="...", user="...", streaming=True):
|
||
|
|
print(chunk)
|
||
|
|
|
||
|
|
# Bulk operations (new)
|
||
|
|
bulk = api.bulk()
|
||
|
|
bulk.import_triples(flow="default", triples=triple_generator())
|
||
|
|
|
||
|
|
# Asynchronous interfaces
|
||
|
|
async def main():
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# Async REST (new)
|
||
|
|
flow = api.async_flow().id("default")
|
||
|
|
response = await flow.agent(question="...", user="...")
|
||
|
|
|
||
|
|
# Async WebSocket (new)
|
||
|
|
socket_flow = api.async_socket().flow("default")
|
||
|
|
async for chunk in socket_flow.agent(question="...", streaming=True):
|
||
|
|
print(chunk)
|
||
|
|
|
||
|
|
# Async bulk operations (new)
|
||
|
|
bulk = api.async_bulk()
|
||
|
|
await bulk.import_triples(flow="default", triples=async_triple_generator())
|
||
|
|
|
||
|
|
asyncio.run(main())
|
||
|
|
```
|
||
|
|
|
||
|
|
**Principios clave de diseño:**
|
||
|
|
**Misma URL para todas las interfaces:** `Api(url="http://localhost:8088/")` funciona para todas.
|
||
|
|
**Simetría sincrónica/asincrónica:** Cada interfaz tiene variantes tanto sincrónicas como asincrónicas con firmas de método idénticas.
|
||
|
|
**Firmas idénticas:** Donde la funcionalidad se superpone, las firmas de los métodos son idénticas entre REST y WebSocket, sincrónicas y asincrónicas.
|
||
|
|
**Mejora progresiva:** Elija la interfaz según las necesidades (REST para tareas simples, WebSocket para transmisión, Bulk para conjuntos de datos grandes, asíncrono para marcos modernos).
|
||
|
|
**Intención explícita:** `api.socket()` indica WebSocket, `api.async_socket()` indica WebSocket asíncrono.
|
||
|
|
**Compatible con versiones anteriores:** El código existente no se modifica.
|
||
|
|
|
||
|
|
### Componentes
|
||
|
|
|
||
|
|
#### 1. Clase API principal (modificada)
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/api.py`
|
||
|
|
|
||
|
|
**Clase API mejorada:**
|
||
|
|
|
||
|
|
```python
|
||
|
|
class Api:
|
||
|
|
def __init__(self, url: str, timeout: int = 60, token: Optional[str] = None):
|
||
|
|
self.url = url
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token # Optional bearer token for REST, query param for WebSocket
|
||
|
|
self._socket_client = None
|
||
|
|
self._bulk_client = None
|
||
|
|
self._async_flow = None
|
||
|
|
self._async_socket_client = None
|
||
|
|
self._async_bulk_client = None
|
||
|
|
|
||
|
|
# Existing synchronous methods (unchanged)
|
||
|
|
def flow(self) -> Flow:
|
||
|
|
"""Synchronous REST-based flow interface"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def library(self) -> Library:
|
||
|
|
"""Synchronous REST-based library interface"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def knowledge(self) -> Knowledge:
|
||
|
|
"""Synchronous REST-based knowledge interface"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def collection(self) -> Collection:
|
||
|
|
"""Synchronous REST-based collection interface"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def config(self) -> Config:
|
||
|
|
"""Synchronous REST-based config interface"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
# New synchronous methods
|
||
|
|
def socket(self) -> SocketClient:
|
||
|
|
"""Synchronous WebSocket-based interface for streaming operations"""
|
||
|
|
if self._socket_client is None:
|
||
|
|
self._socket_client = SocketClient(self.url, self.timeout, self.token)
|
||
|
|
return self._socket_client
|
||
|
|
|
||
|
|
def bulk(self) -> BulkClient:
|
||
|
|
"""Synchronous bulk operations interface for import/export"""
|
||
|
|
if self._bulk_client is None:
|
||
|
|
self._bulk_client = BulkClient(self.url, self.timeout, self.token)
|
||
|
|
return self._bulk_client
|
||
|
|
|
||
|
|
def metrics(self) -> Metrics:
|
||
|
|
"""Synchronous metrics interface"""
|
||
|
|
return Metrics(self.url, self.timeout, self.token)
|
||
|
|
|
||
|
|
# New asynchronous methods
|
||
|
|
def async_flow(self) -> AsyncFlow:
|
||
|
|
"""Asynchronous REST-based flow interface"""
|
||
|
|
if self._async_flow is None:
|
||
|
|
self._async_flow = AsyncFlow(self.url, self.timeout, self.token)
|
||
|
|
return self._async_flow
|
||
|
|
|
||
|
|
def async_socket(self) -> AsyncSocketClient:
|
||
|
|
"""Asynchronous WebSocket-based interface for streaming operations"""
|
||
|
|
if self._async_socket_client is None:
|
||
|
|
self._async_socket_client = AsyncSocketClient(self.url, self.timeout, self.token)
|
||
|
|
return self._async_socket_client
|
||
|
|
|
||
|
|
def async_bulk(self) -> AsyncBulkClient:
|
||
|
|
"""Asynchronous bulk operations interface for import/export"""
|
||
|
|
if self._async_bulk_client is None:
|
||
|
|
self._async_bulk_client = AsyncBulkClient(self.url, self.timeout, self.token)
|
||
|
|
return self._async_bulk_client
|
||
|
|
|
||
|
|
def async_metrics(self) -> AsyncMetrics:
|
||
|
|
"""Asynchronous metrics interface"""
|
||
|
|
return AsyncMetrics(self.url, self.timeout, self.token)
|
||
|
|
|
||
|
|
# Resource management
|
||
|
|
def close(self) -> None:
|
||
|
|
"""Close all synchronous connections"""
|
||
|
|
if self._socket_client:
|
||
|
|
self._socket_client.close()
|
||
|
|
if self._bulk_client:
|
||
|
|
self._bulk_client.close()
|
||
|
|
|
||
|
|
async def aclose(self) -> None:
|
||
|
|
"""Close all asynchronous connections"""
|
||
|
|
if self._async_socket_client:
|
||
|
|
await self._async_socket_client.aclose()
|
||
|
|
if self._async_bulk_client:
|
||
|
|
await self._async_bulk_client.aclose()
|
||
|
|
if self._async_flow:
|
||
|
|
await self._async_flow.aclose()
|
||
|
|
|
||
|
|
def __enter__(self):
|
||
|
|
return self
|
||
|
|
|
||
|
|
def __exit__(self, *args):
|
||
|
|
self.close()
|
||
|
|
|
||
|
|
async def __aenter__(self):
|
||
|
|
return self
|
||
|
|
|
||
|
|
async def __aexit__(self, *args):
|
||
|
|
await self.aclose()
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. Cliente WebSocket Síncrono
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/socket_client.py` (nuevo)
|
||
|
|
|
||
|
|
**Clase SocketClient**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class SocketClient:
|
||
|
|
"""Synchronous WebSocket client"""
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = self._convert_to_ws_url(url)
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
self._connection = None
|
||
|
|
self._request_counter = 0
|
||
|
|
|
||
|
|
def flow(self, flow_id: str) -> SocketFlowInstance:
|
||
|
|
"""Get flow instance for WebSocket operations"""
|
||
|
|
return SocketFlowInstance(self, flow_id)
|
||
|
|
|
||
|
|
def _connect(self) -> WebSocket:
|
||
|
|
"""Establish WebSocket connection (lazy)"""
|
||
|
|
# Uses asyncio.run() internally to wrap async websockets library
|
||
|
|
pass
|
||
|
|
|
||
|
|
def _send_request(
|
||
|
|
self,
|
||
|
|
service: str,
|
||
|
|
flow: Optional[str],
|
||
|
|
request: Dict[str, Any],
|
||
|
|
streaming: bool = False
|
||
|
|
) -> Union[Dict[str, Any], Iterator[Dict[str, Any]]]:
|
||
|
|
"""Send request and handle response/streaming"""
|
||
|
|
# Synchronous wrapper around async WebSocket calls
|
||
|
|
pass
|
||
|
|
|
||
|
|
def close(self) -> None:
|
||
|
|
"""Close WebSocket connection"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
class SocketFlowInstance:
|
||
|
|
"""Synchronous WebSocket flow instance with same interface as REST FlowInstance"""
|
||
|
|
def __init__(self, client: SocketClient, flow_id: str):
|
||
|
|
self.client = client
|
||
|
|
self.flow_id = flow_id
|
||
|
|
|
||
|
|
# Same method signatures as FlowInstance
|
||
|
|
def agent(
|
||
|
|
self,
|
||
|
|
question: str,
|
||
|
|
user: str,
|
||
|
|
state: Optional[Dict[str, Any]] = None,
|
||
|
|
group: Optional[str] = None,
|
||
|
|
history: Optional[List[Dict[str, Any]]] = None,
|
||
|
|
streaming: bool = False,
|
||
|
|
**kwargs
|
||
|
|
) -> Union[Dict[str, Any], Iterator[Dict[str, Any]]]:
|
||
|
|
"""Agent with optional streaming"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def text_completion(
|
||
|
|
self,
|
||
|
|
system: str,
|
||
|
|
prompt: str,
|
||
|
|
streaming: bool = False,
|
||
|
|
**kwargs
|
||
|
|
) -> Union[str, Iterator[str]]:
|
||
|
|
"""Text completion with optional streaming"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
# ... similar for graph_rag, document_rag, prompt, etc.
|
||
|
|
```
|
||
|
|
|
||
|
|
**Características principales:**
|
||
|
|
Conexión perezosa (solo se conecta cuando se envía la primera solicitud)
|
||
|
|
Multiplexación de solicitudes (hasta 15 concurrentes)
|
||
|
|
Reconexión automática en caso de desconexión
|
||
|
|
Análisis de respuesta en streaming
|
||
|
|
Operación segura para subprocesos
|
||
|
|
Envoltorio sincrónico alrededor de la biblioteca de websockets asíncrona
|
||
|
|
|
||
|
|
#### 3. Cliente WebSocket asíncrono
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/async_socket_client.py` (nuevo)
|
||
|
|
|
||
|
|
**Clase AsyncSocketClient:**
|
||
|
|
|
||
|
|
```python
|
||
|
|
class AsyncSocketClient:
|
||
|
|
"""Asynchronous WebSocket client"""
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = self._convert_to_ws_url(url)
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
self._connection = None
|
||
|
|
self._request_counter = 0
|
||
|
|
|
||
|
|
def flow(self, flow_id: str) -> AsyncSocketFlowInstance:
|
||
|
|
"""Get async flow instance for WebSocket operations"""
|
||
|
|
return AsyncSocketFlowInstance(self, flow_id)
|
||
|
|
|
||
|
|
async def _connect(self) -> WebSocket:
|
||
|
|
"""Establish WebSocket connection (lazy)"""
|
||
|
|
# Native async websockets library
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def _send_request(
|
||
|
|
self,
|
||
|
|
service: str,
|
||
|
|
flow: Optional[str],
|
||
|
|
request: Dict[str, Any],
|
||
|
|
streaming: bool = False
|
||
|
|
) -> Union[Dict[str, Any], AsyncIterator[Dict[str, Any]]]:
|
||
|
|
"""Send request and handle response/streaming"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def aclose(self) -> None:
|
||
|
|
"""Close WebSocket connection"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
class AsyncSocketFlowInstance:
|
||
|
|
"""Asynchronous WebSocket flow instance"""
|
||
|
|
def __init__(self, client: AsyncSocketClient, flow_id: str):
|
||
|
|
self.client = client
|
||
|
|
self.flow_id = flow_id
|
||
|
|
|
||
|
|
# Same method signatures as FlowInstance (but async)
|
||
|
|
async def agent(
|
||
|
|
self,
|
||
|
|
question: str,
|
||
|
|
user: str,
|
||
|
|
state: Optional[Dict[str, Any]] = None,
|
||
|
|
group: Optional[str] = None,
|
||
|
|
history: Optional[List[Dict[str, Any]]] = None,
|
||
|
|
streaming: bool = False,
|
||
|
|
**kwargs
|
||
|
|
) -> Union[Dict[str, Any], AsyncIterator[Dict[str, Any]]]:
|
||
|
|
"""Agent with optional streaming"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def text_completion(
|
||
|
|
self,
|
||
|
|
system: str,
|
||
|
|
prompt: str,
|
||
|
|
streaming: bool = False,
|
||
|
|
**kwargs
|
||
|
|
) -> Union[str, AsyncIterator[str]]:
|
||
|
|
"""Text completion with optional streaming"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
# ... similar for graph_rag, document_rag, prompt, etc.
|
||
|
|
```
|
||
|
|
|
||
|
|
**Características principales**:
|
||
|
|
Soporte nativo para async/await
|
||
|
|
Eficiente para aplicaciones asíncronas (FastAPI, aiohttp)
|
||
|
|
Sin bloqueo de hilos
|
||
|
|
Misma interfaz que la versión síncrona
|
||
|
|
AsyncIterator para streaming
|
||
|
|
|
||
|
|
#### 4. Cliente de Operaciones Masivas Síncronas
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/bulk_client.py` (nuevo)
|
||
|
|
|
||
|
|
**Clase BulkClient**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class BulkClient:
|
||
|
|
"""Synchronous bulk operations client"""
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = self._convert_to_ws_url(url)
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
|
||
|
|
def import_triples(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
triples: Iterator[Triple],
|
||
|
|
**kwargs
|
||
|
|
) -> None:
|
||
|
|
"""Bulk import triples via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def export_triples(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
**kwargs
|
||
|
|
) -> Iterator[Triple]:
|
||
|
|
"""Bulk export triples via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def import_graph_embeddings(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
embeddings: Iterator[Dict[str, Any]],
|
||
|
|
**kwargs
|
||
|
|
) -> None:
|
||
|
|
"""Bulk import graph embeddings via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def export_graph_embeddings(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
**kwargs
|
||
|
|
) -> Iterator[Dict[str, Any]]:
|
||
|
|
"""Bulk export graph embeddings via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
# ... similar for document embeddings, entity contexts, objects
|
||
|
|
|
||
|
|
def close(self) -> None:
|
||
|
|
"""Close connections"""
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
**Características principales:**
|
||
|
|
Basado en iteradores para un uso constante de memoria.
|
||
|
|
Conexiones WebSocket dedicadas por operación.
|
||
|
|
Seguimiento del progreso (callback opcional).
|
||
|
|
Manejo de errores con informes de éxito parcial.
|
||
|
|
|
||
|
|
#### 5. Cliente de operaciones masivas asíncronas
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/async_bulk_client.py` (nuevo)
|
||
|
|
|
||
|
|
**Clase AsyncBulkClient:**
|
||
|
|
|
||
|
|
```python
|
||
|
|
class AsyncBulkClient:
|
||
|
|
"""Asynchronous bulk operations client"""
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = self._convert_to_ws_url(url)
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
|
||
|
|
async def import_triples(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
triples: AsyncIterator[Triple],
|
||
|
|
**kwargs
|
||
|
|
) -> None:
|
||
|
|
"""Bulk import triples via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def export_triples(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
**kwargs
|
||
|
|
) -> AsyncIterator[Triple]:
|
||
|
|
"""Bulk export triples via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def import_graph_embeddings(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
embeddings: AsyncIterator[Dict[str, Any]],
|
||
|
|
**kwargs
|
||
|
|
) -> None:
|
||
|
|
"""Bulk import graph embeddings via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def export_graph_embeddings(
|
||
|
|
self,
|
||
|
|
flow: str,
|
||
|
|
**kwargs
|
||
|
|
) -> AsyncIterator[Dict[str, Any]]:
|
||
|
|
"""Bulk export graph embeddings via WebSocket"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
# ... similar for document embeddings, entity contexts, objects
|
||
|
|
|
||
|
|
async def aclose(self) -> None:
|
||
|
|
"""Close connections"""
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
**Características principales:**
|
||
|
|
Basado en AsyncIterator para un uso constante de memoria.
|
||
|
|
Eficiente para aplicaciones asíncronas.
|
||
|
|
Soporte nativo para async/await.
|
||
|
|
Misma interfaz que la versión síncrona.
|
||
|
|
|
||
|
|
#### 6. API de flujo REST (Síncrono - Sin cambios)
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/flow.py`
|
||
|
|
|
||
|
|
La API de flujo REST permanece **completamente sin cambios** para la compatibilidad con versiones anteriores. Todos los métodos existentes siguen funcionando:
|
||
|
|
|
||
|
|
`Flow.list()`, `Flow.start()`, `Flow.stop()`, etc.
|
||
|
|
`FlowInstance.agent()`, `FlowInstance.text_completion()`, `FlowInstance.graph_rag()`, etc.
|
||
|
|
Todas las firmas y tipos de retorno existentes se conservan.
|
||
|
|
|
||
|
|
**Nuevo:** Agregar `graph_embeddings_query()` a REST FlowInstance para la paridad de funciones:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class FlowInstance:
|
||
|
|
# All existing methods unchanged...
|
||
|
|
|
||
|
|
# New: Graph embeddings query (REST)
|
||
|
|
def graph_embeddings_query(
|
||
|
|
self,
|
||
|
|
text: str,
|
||
|
|
user: str,
|
||
|
|
collection: str,
|
||
|
|
limit: int = 10,
|
||
|
|
**kwargs
|
||
|
|
) -> List[Dict[str, Any]]:
|
||
|
|
"""Query graph embeddings for semantic search"""
|
||
|
|
# Calls POST /api/v1/flow/{flow}/service/graph-embeddings
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 7. API de flujo REST asíncrono
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/async_flow.py` (nuevo)
|
||
|
|
|
||
|
|
**Clases AsyncFlow y AsyncFlowInstance:**
|
||
|
|
|
||
|
|
```python
|
||
|
|
class AsyncFlow:
|
||
|
|
"""Asynchronous REST-based flow interface"""
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = url
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
|
||
|
|
async def list(self) -> List[Dict[str, Any]]:
|
||
|
|
"""List all flows"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def get(self, id: str) -> Dict[str, Any]:
|
||
|
|
"""Get flow definition"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def start(self, class_name: str, id: str, description: str, parameters: Dict) -> None:
|
||
|
|
"""Start a flow"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def stop(self, id: str) -> None:
|
||
|
|
"""Stop a flow"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def id(self, flow_id: str) -> AsyncFlowInstance:
|
||
|
|
"""Get async flow instance"""
|
||
|
|
return AsyncFlowInstance(self.url, self.timeout, self.token, flow_id)
|
||
|
|
|
||
|
|
async def aclose(self) -> None:
|
||
|
|
"""Close connection"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
class AsyncFlowInstance:
|
||
|
|
"""Asynchronous REST flow instance"""
|
||
|
|
|
||
|
|
async def agent(
|
||
|
|
self,
|
||
|
|
question: str,
|
||
|
|
user: str,
|
||
|
|
state: Optional[Dict[str, Any]] = None,
|
||
|
|
group: Optional[str] = None,
|
||
|
|
history: Optional[List[Dict[str, Any]]] = None,
|
||
|
|
**kwargs
|
||
|
|
) -> Dict[str, Any]:
|
||
|
|
"""Async agent execution"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def text_completion(
|
||
|
|
self,
|
||
|
|
system: str,
|
||
|
|
prompt: str,
|
||
|
|
**kwargs
|
||
|
|
) -> str:
|
||
|
|
"""Async text completion"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
async def graph_rag(
|
||
|
|
self,
|
||
|
|
question: str,
|
||
|
|
user: str,
|
||
|
|
collection: str,
|
||
|
|
**kwargs
|
||
|
|
) -> str:
|
||
|
|
"""Async graph RAG"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
# ... all other FlowInstance methods as async versions
|
||
|
|
```
|
||
|
|
|
||
|
|
**Características principales:**
|
||
|
|
HTTP asíncrono nativo utilizando `aiohttp` o `httpx`
|
||
|
|
Mismas firmas de método que la API REST sincrónica
|
||
|
|
Sin transmisión (utilice `async_socket()` para la transmisión)
|
||
|
|
Eficiente para aplicaciones asíncronas
|
||
|
|
|
||
|
|
#### 8. API de métricas
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/metrics.py` (nuevo)
|
||
|
|
|
||
|
|
**Métricas sincrónicas:**
|
||
|
|
|
||
|
|
```python
|
||
|
|
class Metrics:
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = url
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
|
||
|
|
def get(self) -> str:
|
||
|
|
"""Get Prometheus metrics as text"""
|
||
|
|
# Call GET /api/metrics
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
**Métricas Asíncronas**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
class AsyncMetrics:
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = url
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
|
||
|
|
async def get(self) -> str:
|
||
|
|
"""Get Prometheus metrics as text"""
|
||
|
|
# Call GET /api/metrics
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 9. Tipos Mejorados
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/types.py` (modificado)
|
||
|
|
|
||
|
|
**Nuevos Tipos**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from typing import Iterator, Union, Dict, Any
|
||
|
|
import dataclasses
|
||
|
|
|
||
|
|
@dataclasses.dataclass
|
||
|
|
class StreamingChunk:
|
||
|
|
"""Base class for streaming chunks"""
|
||
|
|
content: str
|
||
|
|
end_of_message: bool = False
|
||
|
|
|
||
|
|
@dataclasses.dataclass
|
||
|
|
class AgentThought(StreamingChunk):
|
||
|
|
"""Agent reasoning chunk"""
|
||
|
|
chunk_type: str = "thought"
|
||
|
|
|
||
|
|
@dataclasses.dataclass
|
||
|
|
class AgentObservation(StreamingChunk):
|
||
|
|
"""Agent tool observation chunk"""
|
||
|
|
chunk_type: str = "observation"
|
||
|
|
|
||
|
|
@dataclasses.dataclass
|
||
|
|
class AgentAnswer(StreamingChunk):
|
||
|
|
"""Agent final answer chunk"""
|
||
|
|
chunk_type: str = "final-answer"
|
||
|
|
end_of_dialog: bool = False
|
||
|
|
|
||
|
|
@dataclasses.dataclass
|
||
|
|
class RAGChunk(StreamingChunk):
|
||
|
|
"""RAG streaming chunk"""
|
||
|
|
end_of_stream: bool = False
|
||
|
|
error: Optional[Dict[str, str]] = None
|
||
|
|
|
||
|
|
# Type aliases for clarity
|
||
|
|
AgentStream = Iterator[Union[AgentThought, AgentObservation, AgentAnswer]]
|
||
|
|
RAGStream = Iterator[RAGChunk]
|
||
|
|
CompletionStream = Iterator[str]
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 6. API de métricas
|
||
|
|
|
||
|
|
Módulo: `trustgraph-base/trustgraph/api/metrics.py` (nuevo)
|
||
|
|
|
||
|
|
```python
|
||
|
|
class Metrics:
|
||
|
|
def __init__(self, url: str, timeout: int, token: Optional[str]):
|
||
|
|
self.url = url
|
||
|
|
self.timeout = timeout
|
||
|
|
self.token = token
|
||
|
|
|
||
|
|
def get(self) -> str:
|
||
|
|
"""Get Prometheus metrics as text"""
|
||
|
|
# Call GET /api/metrics
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
### Enfoque de Implementación
|
||
|
|
|
||
|
|
#### Fase 1: Mejora del API Central (Semana 1)
|
||
|
|
|
||
|
|
1. Agregar métodos `socket()`, `bulk()` y `metrics()` a la clase `Api`
|
||
|
|
2. Implementar inicialización perezosa para WebSocket y clientes masivos
|
||
|
|
3. Agregar soporte para administrador de contexto (`__enter__`, `__exit__`)
|
||
|
|
4. Agregar método `close()` para limpieza
|
||
|
|
5. Agregar pruebas unitarias para las mejoras del API
|
||
|
|
6. Verificar compatibilidad con versiones anteriores
|
||
|
|
|
||
|
|
**Compatibilidad con versiones anteriores**: No hay cambios que rompan la compatibilidad. Solo se agregan nuevos métodos.
|
||
|
|
|
||
|
|
#### Fase 2: Cliente WebSocket (Semanas 2-3)
|
||
|
|
|
||
|
|
1. Implementar clase `SocketClient` con gestión de conexión
|
||
|
|
2. Implementar `SocketFlowInstance` con las mismas firmas de método que `FlowInstance`
|
||
|
|
3. Agregar soporte para multiplexación de solicitudes (hasta 15 concurrentes)
|
||
|
|
4. Agregar análisis de respuesta en streaming para diferentes tipos de fragmentos
|
||
|
|
5. Agregar lógica de reconexión automática
|
||
|
|
6. Agregar pruebas unitarias e integrales
|
||
|
|
7. Documentar patrones de uso de WebSocket
|
||
|
|
|
||
|
|
**Compatibilidad con versiones anteriores**: Nueva interfaz solamente. No tiene impacto en el código existente.
|
||
|
|
|
||
|
|
#### Fase 3: Soporte de Streaming (Semanas 3-4)
|
||
|
|
|
||
|
|
1. Agregar clases de tipo de fragmento de streaming (`AgentThought`, `AgentObservation`, `AgentAnswer`, `RAGChunk`)
|
||
|
|
2. Implementar análisis de respuesta en streaming en `SocketClient`
|
||
|
|
3. Agregar parámetro de streaming a todos los métodos LLM en `SocketFlowInstance`
|
||
|
|
4. Manejar casos de error durante el streaming
|
||
|
|
5. Agregar pruebas unitarias e integrales para el streaming
|
||
|
|
6. Agregar ejemplos de streaming a la documentación
|
||
|
|
|
||
|
|
**Compatibilidad con versiones anteriores**: Nueva interfaz solamente. El API REST existente no se modifica.
|
||
|
|
|
||
|
|
#### Fase 4: Operaciones Masivas (Semanas 4-5)
|
||
|
|
|
||
|
|
1. Implementar clase `BulkClient`
|
||
|
|
2. Agregar métodos de importación/exportación masiva para triples, incrustaciones, contextos, objetos
|
||
|
|
3. Implementar procesamiento basado en iterador para un uso constante de memoria
|
||
|
|
4. Agregar seguimiento de progreso (callback opcional)
|
||
|
|
5. Agregar manejo de errores con informes de éxito parcial
|
||
|
|
6. Agregar pruebas unitarias e integrales
|
||
|
|
7. Agregar ejemplos de operaciones masivas
|
||
|
|
|
||
|
|
**Compatibilidad con versiones anteriores**: Nueva interfaz solamente. No tiene impacto en el código existente.
|
||
|
|
|
||
|
|
#### Fase 5: Paridad de Funciones y Pulido (Semana 5)
|
||
|
|
|
||
|
|
1. Agregar `graph_embeddings_query()` a REST `FlowInstance`
|
||
|
|
2. Implementar clase `Metrics`
|
||
|
|
3. Agregar pruebas de integración exhaustivas
|
||
|
|
4. Pruebas de rendimiento
|
||
|
|
5. Actualizar toda la documentación
|
||
|
|
6. Crear una guía de migración
|
||
|
|
|
||
|
|
**Compatibilidad con versiones anteriores**: Nuevos métodos solamente. No tiene impacto en el código existente.
|
||
|
|
|
||
|
|
### Modelos de Datos
|
||
|
|
|
||
|
|
#### Selección de Interfaz
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Single API instance, same URL for all interfaces
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# Synchronous interfaces
|
||
|
|
rest_flow = api.flow().id("default") # Sync REST
|
||
|
|
socket_flow = api.socket().flow("default") # Sync WebSocket
|
||
|
|
bulk = api.bulk() # Sync bulk operations
|
||
|
|
metrics = api.metrics() # Sync metrics
|
||
|
|
|
||
|
|
# Asynchronous interfaces
|
||
|
|
async_rest_flow = api.async_flow().id("default") # Async REST
|
||
|
|
async_socket_flow = api.async_socket().flow("default") # Async WebSocket
|
||
|
|
async_bulk = api.async_bulk() # Async bulk operations
|
||
|
|
async_metrics = api.async_metrics() # Async metrics
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Tipos de respuesta de transmisión
|
||
|
|
|
||
|
|
**Transmisión del agente**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# REST interface - non-streaming (existing)
|
||
|
|
rest_flow = api.flow().id("default")
|
||
|
|
response = rest_flow.agent(question="What is ML?", user="user123")
|
||
|
|
print(response["response"])
|
||
|
|
|
||
|
|
# WebSocket interface - non-streaming (same signature)
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
response = socket_flow.agent(question="What is ML?", user="user123")
|
||
|
|
print(response["response"])
|
||
|
|
|
||
|
|
# WebSocket interface - streaming (new)
|
||
|
|
for chunk in socket_flow.agent(question="What is ML?", user="user123", streaming=True):
|
||
|
|
if isinstance(chunk, AgentThought):
|
||
|
|
print(f"Thinking: {chunk.content}")
|
||
|
|
elif isinstance(chunk, AgentObservation):
|
||
|
|
print(f"Observed: {chunk.content}")
|
||
|
|
elif isinstance(chunk, AgentAnswer):
|
||
|
|
print(f"Answer: {chunk.content}")
|
||
|
|
if chunk.end_of_dialog:
|
||
|
|
break
|
||
|
|
```
|
||
|
|
|
||
|
|
**Transmisión RAG**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# REST interface - non-streaming (existing)
|
||
|
|
rest_flow = api.flow().id("default")
|
||
|
|
response = rest_flow.graph_rag(question="What is Python?", user="user123", collection="default")
|
||
|
|
print(response)
|
||
|
|
|
||
|
|
# WebSocket interface - streaming (new)
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
for chunk in socket_flow.graph_rag(
|
||
|
|
question="What is Python?",
|
||
|
|
user="user123",
|
||
|
|
collection="default",
|
||
|
|
streaming=True
|
||
|
|
):
|
||
|
|
print(chunk.content, end="", flush=True)
|
||
|
|
if chunk.end_of_stream:
|
||
|
|
break
|
||
|
|
```
|
||
|
|
|
||
|
|
**Operaciones masivas (síncronas):**
|
||
|
|
|
||
|
|
```python
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# Bulk import triples
|
||
|
|
def triple_generator():
|
||
|
|
yield Triple(s="http://ex.com/alice", p="http://ex.com/type", o="Person")
|
||
|
|
yield Triple(s="http://ex.com/alice", p="http://ex.com/name", o="Alice")
|
||
|
|
yield Triple(s="http://ex.com/bob", p="http://ex.com/type", o="Person")
|
||
|
|
|
||
|
|
bulk = api.bulk()
|
||
|
|
bulk.import_triples(flow="default", triples=triple_generator())
|
||
|
|
|
||
|
|
# Bulk export triples
|
||
|
|
for triple in bulk.export_triples(flow="default"):
|
||
|
|
print(f"{triple.s} -> {triple.p} -> {triple.o}")
|
||
|
|
```
|
||
|
|
|
||
|
|
**Operaciones masivas (asíncronas):**
|
||
|
|
|
||
|
|
```python
|
||
|
|
import asyncio
|
||
|
|
|
||
|
|
async def main():
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# Async bulk import triples
|
||
|
|
async def async_triple_generator():
|
||
|
|
yield Triple(s="http://ex.com/alice", p="http://ex.com/type", o="Person")
|
||
|
|
yield Triple(s="http://ex.com/alice", p="http://ex.com/name", o="Alice")
|
||
|
|
yield Triple(s="http://ex.com/bob", p="http://ex.com/type", o="Person")
|
||
|
|
|
||
|
|
bulk = api.async_bulk()
|
||
|
|
await bulk.import_triples(flow="default", triples=async_triple_generator())
|
||
|
|
|
||
|
|
# Async bulk export triples
|
||
|
|
async for triple in bulk.export_triples(flow="default"):
|
||
|
|
print(f"{triple.s} -> {triple.p} -> {triple.o}")
|
||
|
|
|
||
|
|
asyncio.run(main())
|
||
|
|
```
|
||
|
|
|
||
|
|
**Ejemplo de REST asíncrono**:
|
||
|
|
|
||
|
|
```python
|
||
|
|
import asyncio
|
||
|
|
|
||
|
|
async def main():
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# Async REST flow operations
|
||
|
|
flow = api.async_flow().id("default")
|
||
|
|
response = await flow.agent(question="What is ML?", user="user123")
|
||
|
|
print(response["response"])
|
||
|
|
|
||
|
|
asyncio.run(main())
|
||
|
|
```
|
||
|
|
|
||
|
|
**Ejemplo de transmisión WebSocket asíncrona:**
|
||
|
|
|
||
|
|
```python
|
||
|
|
import asyncio
|
||
|
|
|
||
|
|
async def main():
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# Async WebSocket streaming
|
||
|
|
socket = api.async_socket()
|
||
|
|
flow = socket.flow("default")
|
||
|
|
|
||
|
|
async for chunk in flow.agent(question="What is ML?", user="user123", streaming=True):
|
||
|
|
if isinstance(chunk, AgentAnswer):
|
||
|
|
print(chunk.content, end="", flush=True)
|
||
|
|
if chunk.end_of_dialog:
|
||
|
|
break
|
||
|
|
|
||
|
|
asyncio.run(main())
|
||
|
|
```
|
||
|
|
|
||
|
|
### APIs
|
||
|
|
|
||
|
|
#### New APIs
|
||
|
|
|
||
|
|
1. **Clase API Core**:
|
||
|
|
**Síncrono**:
|
||
|
|
`Api.socket()` - Obtener cliente WebSocket síncrono
|
||
|
|
`Api.bulk()` - Obtener cliente de operaciones masivas síncrono
|
||
|
|
`Api.metrics()` - Obtener cliente de métricas síncrono
|
||
|
|
`Api.close()` - Cerrar todas las conexiones síncronas
|
||
|
|
Soporte de administrador de contexto (`__enter__`, `__exit__`)
|
||
|
|
**Asíncrono**:
|
||
|
|
`Api.async_flow()` - Obtener cliente de flujo REST asíncrono
|
||
|
|
`Api.async_socket()` - Obtener cliente WebSocket asíncrono
|
||
|
|
`Api.async_bulk()` - Obtener cliente de operaciones masivas asíncrono
|
||
|
|
`Api.async_metrics()` - Obtener cliente de métricas asíncrono
|
||
|
|
`Api.aclose()` - Cerrar todas las conexiones asíncronas
|
||
|
|
Soporte de administrador de contexto asíncrono (`__aenter__`, `__aexit__`)
|
||
|
|
|
||
|
|
2. **Cliente WebSocket Síncrono**:
|
||
|
|
`SocketClient.flow(flow_id)` - Obtener instancia de flujo WebSocket
|
||
|
|
`SocketFlowInstance.agent(..., streaming: bool = False)` - Agente con transmisión opcional
|
||
|
|
`SocketFlowInstance.text_completion(..., streaming: bool = False)` - Completar texto con transmisión opcional
|
||
|
|
`SocketFlowInstance.graph_rag(..., streaming: bool = False)` - RAG de gráfico con transmisión opcional
|
||
|
|
`SocketFlowInstance.document_rag(..., streaming: bool = False)` - RAG de documento con transmisión opcional
|
||
|
|
`SocketFlowInstance.prompt(..., streaming: bool = False)` - Prompt con transmisión opcional
|
||
|
|
`SocketFlowInstance.graph_embeddings_query()` - Consulta de incrustaciones de gráfico
|
||
|
|
Todos los demás métodos de FlowInstance con firmas idénticas
|
||
|
|
|
||
|
|
3. **Cliente WebSocket Asíncrono**:
|
||
|
|
`AsyncSocketClient.flow(flow_id)` - Obtener instancia de flujo WebSocket asíncrono
|
||
|
|
`AsyncSocketFlowInstance.agent(..., streaming: bool = False)` - Agente asíncrono con transmisión opcional
|
||
|
|
`AsyncSocketFlowInstance.text_completion(..., streaming: bool = False)` - Completar texto asíncrono con transmisión opcional
|
||
|
|
`AsyncSocketFlowInstance.graph_rag(..., streaming: bool = False)` - RAG de gráfico asíncrono con transmisión opcional
|
||
|
|
`AsyncSocketFlowInstance.document_rag(..., streaming: bool = False)` - RAG de documento asíncrono con transmisión opcional
|
||
|
|
`AsyncSocketFlowInstance.prompt(..., streaming: bool = False)` - Prompt asíncrono con transmisión opcional
|
||
|
|
`AsyncSocketFlowInstance.graph_embeddings_query()` - Consulta de incrustaciones de gráfico asíncrona
|
||
|
|
Todos los demás métodos de FlowInstance como versiones asíncronas
|
||
|
|
|
||
|
|
4. **Cliente de Operaciones Masivas Síncrono**:
|
||
|
|
`BulkClient.import_triples(flow, triples)` - Importación masiva de triples
|
||
|
|
`BulkClient.export_triples(flow)` - Exportación masiva de triples
|
||
|
|
`BulkClient.import_graph_embeddings(flow, embeddings)` - Importación masiva de incrustaciones de gráfico
|
||
|
|
`BulkClient.export_graph_embeddings(flow)` - Exportación masiva de incrustaciones de gráfico
|
||
|
|
`BulkClient.import_document_embeddings(flow, embeddings)` - Importación masiva de incrustaciones de documento
|
||
|
|
`BulkClient.export_document_embeddings(flow)` - Exportación masiva de incrustaciones de documento
|
||
|
|
`BulkClient.import_entity_contexts(flow, contexts)` - Importación masiva de contextos de entidades
|
||
|
|
`BulkClient.export_entity_contexts(flow)` - Exportación masiva de contextos de entidades
|
||
|
|
`BulkClient.import_objects(flow, objects)` - Importación masiva de objetos
|
||
|
|
|
||
|
|
5. **Cliente de Operaciones Masivas Asíncrono**:
|
||
|
|
`AsyncBulkClient.import_triples(flow, triples)` - Importación asíncrona masiva de triples
|
||
|
|
`AsyncBulkClient.export_triples(flow)` - Exportación asíncrona masiva de triples
|
||
|
|
`AsyncBulkClient.import_graph_embeddings(flow, embeddings)` - Importación asíncrona masiva de incrustaciones de gráfico
|
||
|
|
`AsyncBulkClient.export_graph_embeddings(flow)` - Exportación asíncrona masiva de incrustaciones de gráfico
|
||
|
|
`AsyncBulkClient.import_document_embeddings(flow, embeddings)` - Importación asíncrona masiva de incrustaciones de documento
|
||
|
|
`AsyncBulkClient.export_document_embeddings(flow)` - Exportación asíncrona masiva de incrustaciones de documento
|
||
|
|
`AsyncBulkClient.import_entity_contexts(flow, contexts)` - Importación asíncrona masiva de contextos de entidades
|
||
|
|
`AsyncBulkClient.export_entity_contexts(flow)` - Exportación asíncrona masiva de contextos de entidades
|
||
|
|
`AsyncBulkClient.import_objects(flow, objects)` - Importación asíncrona masiva de objetos
|
||
|
|
|
||
|
|
6. **Cliente de Flujo REST Asíncrono**:
|
||
|
|
`AsyncFlow.list()` - Listar todos los flujos de forma asíncrona
|
||
|
|
`AsyncFlow.get(id)` - Obtener definición de flujo de forma asíncrona
|
||
|
|
`AsyncFlow.start(...)` - Iniciar flujo de forma asíncrona
|
||
|
|
`AsyncFlow.stop(id)` - Detener flujo de forma asíncrona
|
||
|
|
`AsyncFlow.id(flow_id)` - Obtener instancia de flujo de forma asíncrona
|
||
|
|
`AsyncFlowInstance.agent(...)` - Ejecución de agente asíncrona
|
||
|
|
`AsyncFlowInstance.text_completion(...)` - Completar texto de forma asíncrona
|
||
|
|
`AsyncFlowInstance.graph_rag(...)` - RAG de gráfico asíncrono
|
||
|
|
Todos los demás métodos de FlowInstance como versiones asíncronas
|
||
|
|
|
||
|
|
7. **Clientes de Métricas**:
|
||
|
|
`Metrics.get()` - Métricas de Prometheus síncronas
|
||
|
|
`AsyncMetrics.get()` - Métricas de Prometheus asíncronas
|
||
|
|
|
||
|
|
8. **Mejora de la API REST de Flujo**:
|
||
|
|
`FlowInstance.graph_embeddings_query()` - Consulta de incrustaciones de gráfico (paridad de funciones síncronas)
|
||
|
|
`AsyncFlowInstance.graph_embeddings_query()` - Consulta de incrustaciones de gráfico (paridad de funciones asíncronas)
|
||
|
|
|
||
|
|
#### APIs Modificadas
|
||
|
|
|
||
|
|
1. **Constructor** (pequeña mejora):
|
||
|
|
```python
|
||
|
|
Api(url: str, timeout: int = 60, token: Optional[str] = None)
|
||
|
|
```
|
||
|
|
Se agregó el parámetro `token` (opcional, para autenticación).
|
||
|
|
Si `None` no se especifica (por defecto): No se utiliza autenticación.
|
||
|
|
Si se especifica: Se utiliza como token de tipo "bearer" para REST (`Authorization: Bearer <token>`), parámetro de consulta para WebSocket (`?token=<token>`).
|
||
|
|
No se realizaron otros cambios; es totalmente compatible con versiones anteriores.
|
||
|
|
|
||
|
|
2. **Sin cambios que rompan la compatibilidad**:
|
||
|
|
Todos los métodos de la API REST existentes no se han modificado.
|
||
|
|
Todas las firmas existentes se han conservado.
|
||
|
|
Todos los tipos de retorno existentes se han conservado.
|
||
|
|
|
||
|
|
### Detalles de implementación
|
||
|
|
|
||
|
|
#### Manejo de errores
|
||
|
|
|
||
|
|
**Errores de conexión WebSocket**:
|
||
|
|
```python
|
||
|
|
try:
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
socket = api.socket()
|
||
|
|
socket_flow = socket.flow("default")
|
||
|
|
response = socket_flow.agent(question="...", user="user123")
|
||
|
|
except ConnectionError as e:
|
||
|
|
print(f"WebSocket connection failed: {e}")
|
||
|
|
print("Hint: Ensure Gateway is running and WebSocket endpoint is accessible")
|
||
|
|
```
|
||
|
|
|
||
|
|
**Respaldo Elegante**:
|
||
|
|
```python
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Try WebSocket streaming first
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
for chunk in socket_flow.agent(question="...", user="...", streaming=True):
|
||
|
|
print(chunk.content)
|
||
|
|
except ConnectionError:
|
||
|
|
# Fall back to REST non-streaming
|
||
|
|
print("WebSocket unavailable, falling back to REST")
|
||
|
|
rest_flow = api.flow().id("default")
|
||
|
|
response = rest_flow.agent(question="...", user="...")
|
||
|
|
print(response["response"])
|
||
|
|
```
|
||
|
|
|
||
|
|
**Errores de transmisión parcial**:
|
||
|
|
```python
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
|
||
|
|
accumulated = []
|
||
|
|
try:
|
||
|
|
for chunk in socket_flow.graph_rag(question="...", streaming=True):
|
||
|
|
accumulated.append(chunk.content)
|
||
|
|
if chunk.error:
|
||
|
|
print(f"Error occurred: {chunk.error}")
|
||
|
|
print(f"Partial response: {''.join(accumulated)}")
|
||
|
|
break
|
||
|
|
except Exception as e:
|
||
|
|
print(f"Streaming error: {e}")
|
||
|
|
print(f"Partial response: {''.join(accumulated)}")
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Gestión de Recursos
|
||
|
|
|
||
|
|
**Soporte para Administradores de Contexto**:
|
||
|
|
```python
|
||
|
|
# Automatic cleanup
|
||
|
|
with Api(url="http://localhost:8088/") as api:
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
response = socket_flow.agent(question="...", user="user123")
|
||
|
|
# All connections automatically closed
|
||
|
|
|
||
|
|
# Manual cleanup
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
try:
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
response = socket_flow.agent(question="...", user="user123")
|
||
|
|
finally:
|
||
|
|
api.close() # Explicitly close all connections (WebSocket, bulk, etc.)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Concurrencia y Subprocesamiento
|
||
|
|
|
||
|
|
**Seguridad de Subprocesos**:
|
||
|
|
Cada instancia de `Api` mantiene su propia conexión.
|
||
|
|
El transporte WebSocket utiliza bloqueos para el multiplexado de solicitudes, que es seguro para subprocesos.
|
||
|
|
Múltiples subprocesos pueden compartir una instancia de `Api` de forma segura.
|
||
|
|
Los iteradores de transmisión no son seguros para subprocesos (se deben consumir desde un solo subproceso).
|
||
|
|
|
||
|
|
**Soporte Asíncrono** (consideración futura):
|
||
|
|
```python
|
||
|
|
# Phase 2 enhancement (not in initial scope)
|
||
|
|
import asyncio
|
||
|
|
|
||
|
|
async def main():
|
||
|
|
api = await AsyncApi(url="ws://localhost:8088/")
|
||
|
|
flow = api.flow().id("default")
|
||
|
|
|
||
|
|
async for chunk in flow.agent(question="...", streaming=True):
|
||
|
|
print(chunk.content)
|
||
|
|
|
||
|
|
await api.close()
|
||
|
|
|
||
|
|
asyncio.run(main())
|
||
|
|
```
|
||
|
|
|
||
|
|
## Consideraciones de seguridad
|
||
|
|
|
||
|
|
### Autenticación
|
||
|
|
|
||
|
|
**Parámetro de token**:
|
||
|
|
```python
|
||
|
|
# No authentication (default)
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
|
||
|
|
# With authentication
|
||
|
|
api = Api(url="http://localhost:8088/", token="mytoken")
|
||
|
|
```
|
||
|
|
|
||
|
|
**Transporte REST**:
|
||
|
|
Token del portador a través del encabezado `Authorization`
|
||
|
|
Se aplica automáticamente a todas las solicitudes REST
|
||
|
|
Formato: `Authorization: Bearer <token>`
|
||
|
|
|
||
|
|
**Transporte WebSocket**:
|
||
|
|
Token a través de un parámetro de consulta adjunto a la URL de WebSocket
|
||
|
|
Se aplica automáticamente durante el establecimiento de la conexión
|
||
|
|
Formato: `ws://localhost:8088/api/v1/socket?token=<token>`
|
||
|
|
|
||
|
|
**Implementación**:
|
||
|
|
```python
|
||
|
|
class SocketClient:
|
||
|
|
def _connect(self) -> WebSocket:
|
||
|
|
# Construct WebSocket URL with optional token
|
||
|
|
ws_url = f"{self.url}/api/v1/socket"
|
||
|
|
if self.token:
|
||
|
|
ws_url = f"{ws_url}?token={self.token}"
|
||
|
|
# Connect to WebSocket
|
||
|
|
return websocket.connect(ws_url)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Ejemplo:**
|
||
|
|
```python
|
||
|
|
# REST with auth
|
||
|
|
api = Api(url="http://localhost:8088/", token="mytoken")
|
||
|
|
flow = api.flow().id("default")
|
||
|
|
# All REST calls include: Authorization: Bearer mytoken
|
||
|
|
|
||
|
|
# WebSocket with auth
|
||
|
|
socket = api.socket()
|
||
|
|
# Connects to: ws://localhost:8088/api/v1/socket?token=mytoken
|
||
|
|
```
|
||
|
|
|
||
|
|
### Comunicación Segura
|
||
|
|
|
||
|
|
Soporta tanto los esquemas WS (WebSocket) como los de WSS (WebSocket Secure).
|
||
|
|
Validación de certificados TLS para conexiones WSS.
|
||
|
|
Verificación de certificados opcional para desarrollo (con advertencia).
|
||
|
|
|
||
|
|
### Validación de Datos de Entrada
|
||
|
|
|
||
|
|
Validar esquemas de URL (http, https, ws, wss).
|
||
|
|
Validar valores de parámetros de transporte.
|
||
|
|
Validar combinaciones de parámetros de transmisión.
|
||
|
|
Validar tipos de datos para la importación masiva.
|
||
|
|
|
||
|
|
## Consideraciones de Rendimiento
|
||
|
|
|
||
|
|
### Mejoras en la Latencia
|
||
|
|
|
||
|
|
**Operaciones de LLM en Transmisión**:
|
||
|
|
**Tiempo hasta el primer token**: ~500ms (vs ~30s sin transmisión)
|
||
|
|
**Mejora**: 60 veces más rápido en términos de rendimiento percibido.
|
||
|
|
**Aplicable a**: Agente, Graph RAG, Document RAG, Completar Texto, Prompt.
|
||
|
|
|
||
|
|
**Conexiones Persistentes**:
|
||
|
|
**Sobrecarga de la conexión**: Eliminada para solicitudes posteriores.
|
||
|
|
**Handshake de WebSocket**: Costo único (~100ms).
|
||
|
|
**Aplicable a**: Todas las operaciones al usar el transporte WebSocket.
|
||
|
|
|
||
|
|
### Mejoras en el Rendimiento (Throughput)
|
||
|
|
|
||
|
|
**Operaciones Masivas**:
|
||
|
|
**Importación de triples**: ~10,000 triples/segundo (vs ~100/segundo con REST por elemento).
|
||
|
|
**Importación de embeddings**: ~5,000 embeddings/segundo (vs ~50/segundo con REST por elemento).
|
||
|
|
**Mejora**: 100 veces más de rendimiento para operaciones masivas.
|
||
|
|
|
||
|
|
**Multiplexación de Solicitudes**:
|
||
|
|
**Solicitudes concurrentes**: Hasta 15 solicitudes simultáneas sobre una sola conexión.
|
||
|
|
**Reutilización de la conexión**: Sin sobrecarga de conexión para operaciones concurrentes.
|
||
|
|
|
||
|
|
### Consideraciones de Memoria
|
||
|
|
|
||
|
|
**Respuestas en Transmisión**:
|
||
|
|
Uso constante de memoria (procesa los fragmentos a medida que llegan).
|
||
|
|
No se almacena en búfer la respuesta completa.
|
||
|
|
Adecuado para salidas muy largas (>1MB).
|
||
|
|
|
||
|
|
**Operaciones Masivas**:
|
||
|
|
Procesamiento basado en iteradores (uso constante de memoria).
|
||
|
|
No se carga todo el conjunto de datos en la memoria.
|
||
|
|
Adecuado para conjuntos de datos con millones de elementos.
|
||
|
|
|
||
|
|
### Pruebas de Rendimiento (Esperadas)
|
||
|
|
|
||
|
|
| Operación | REST (existente) | WebSocket (en transmisión) | Mejora |
|
||
|
|
|-----------|----------------|----------------------|-------------|
|
||
|
|
| Agente (tiempo hasta el primer token) | 30s | 0.5s | 60x |
|
||
|
|
| Graph RAG (tiempo hasta el primer token) | 25s | 0.5s | 50x |
|
||
|
|
| Importar 10K triples | 100s | 1s | 100x |
|
||
|
|
| Importar 1M triples | 10,000s (2.7h) | 100s (1.6m) | 100x |
|
||
|
|
| 10 solicitudes pequeñas concurrentes | 5s (secuencial) | 0.5s (paralelo) | 10x |
|
||
|
|
|
||
|
|
## Estrategia de Pruebas
|
||
|
|
|
||
|
|
### Pruebas Unitarias
|
||
|
|
|
||
|
|
**Capa de Transporte** (`test_transport.py`):
|
||
|
|
Probar la solicitud/respuesta de transporte REST
|
||
|
|
Probar la conexión de transporte WebSocket
|
||
|
|
Probar la reconexión de transporte WebSocket
|
||
|
|
Probar el multiplexado de solicitudes
|
||
|
|
Probar el análisis de la respuesta de transmisión
|
||
|
|
Simular un servidor WebSocket para pruebas deterministas
|
||
|
|
|
||
|
|
**Métodos de la API** (`test_flow.py`, `test_library.py`, etc.):
|
||
|
|
Probar nuevos métodos con transporte simulado
|
||
|
|
Probar el manejo de parámetros de transmisión
|
||
|
|
Probar iteradores de operaciones masivas
|
||
|
|
Probar el manejo de errores
|
||
|
|
|
||
|
|
**Tipos** (`test_types.py`):
|
||
|
|
Probar nuevos tipos de fragmentos de transmisión
|
||
|
|
Probar la serialización/deserialización de tipos
|
||
|
|
|
||
|
|
### Pruebas de Integración
|
||
|
|
|
||
|
|
**REST de Extremo a Extremo** (`test_integration_rest.py`):
|
||
|
|
Probar todas las operaciones contra la puerta de enlace real (modo REST)
|
||
|
|
Verificar la compatibilidad con versiones anteriores
|
||
|
|
Probar condiciones de error
|
||
|
|
|
||
|
|
**WebSocket de Extremo a Extremo** (`test_integration_websocket.py`):
|
||
|
|
Probar todas las operaciones contra la puerta de enlace real (modo WebSocket)
|
||
|
|
Probar operaciones de transmisión
|
||
|
|
Probar operaciones masivas
|
||
|
|
Probar solicitudes concurrentes
|
||
|
|
Probar la recuperación de la conexión
|
||
|
|
|
||
|
|
**Servicios de Transmisión** (`test_streaming_integration.py`):
|
||
|
|
Probar la transmisión de agentes (pensamientos, observaciones, respuestas)
|
||
|
|
Probar la transmisión de RAG (fragmentos incrementales)
|
||
|
|
Probar la transmisión de finalización de texto (token por token)
|
||
|
|
Probar la transmisión de indicaciones
|
||
|
|
Probar el manejo de errores durante la transmisión
|
||
|
|
|
||
|
|
**Operaciones Masivas** (`test_bulk_integration.py`):
|
||
|
|
Probar la importación/exportación masiva de triples (1K, 10K, 100K elementos)
|
||
|
|
Probar la importación/exportación masiva de incrustaciones
|
||
|
|
Probar el uso de memoria durante las operaciones masivas
|
||
|
|
Probar el seguimiento del progreso
|
||
|
|
|
||
|
|
### Pruebas de Rendimiento
|
||
|
|
|
||
|
|
**Mediciones de Latencia** (`test_performance_latency.py`):
|
||
|
|
Medir el tiempo hasta el primer token (transmisión vs. no transmisión)
|
||
|
|
Medir la sobrecarga de la conexión (REST vs WebSocket)
|
||
|
|
Comparar con puntos de referencia esperados
|
||
|
|
|
||
|
|
**Mediciones de Rendimiento** (`test_performance_throughput.py`):
|
||
|
|
Medir el rendimiento de la importación masiva
|
||
|
|
Medir la eficiencia del multiplexado de solicitudes
|
||
|
|
Comparar con puntos de referencia esperados
|
||
|
|
|
||
|
|
### Pruebas de Compatibilidad
|
||
|
|
|
||
|
|
**Compatibilidad con Versiones Anteriores** (`test_backward_compatibility.py`):
|
||
|
|
Ejecutar el conjunto de pruebas existente contra la API refactorizada
|
||
|
|
Verificar la ausencia de cambios que rompan la compatibilidad
|
||
|
|
Probar la ruta de migración para patrones comunes
|
||
|
|
|
||
|
|
## Plan de Migración
|
||
|
|
|
||
|
|
### Fase 1: Migración Transparente (Predeterminada)
|
||
|
|
|
||
|
|
**No se requieren cambios en el código**. El código existente continúa funcionando:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Existing code works unchanged
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
flow = api.flow().id("default")
|
||
|
|
response = flow.agent(question="What is ML?", user="user123")
|
||
|
|
```
|
||
|
|
|
||
|
|
### Fase 2: Transmisión por demanda (Simple)
|
||
|
|
|
||
|
|
**Utilice la interfaz `api.socket()`** para habilitar la transmisión:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Before: Non-streaming REST
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
rest_flow = api.flow().id("default")
|
||
|
|
response = rest_flow.agent(question="What is ML?", user="user123")
|
||
|
|
print(response["response"])
|
||
|
|
|
||
|
|
# After: Streaming WebSocket (same parameters!)
|
||
|
|
api = Api(url="http://localhost:8088/") # Same URL
|
||
|
|
socket_flow = api.socket().flow("default")
|
||
|
|
|
||
|
|
for chunk in socket_flow.agent(question="What is ML?", user="user123", streaming=True):
|
||
|
|
if isinstance(chunk, AgentAnswer):
|
||
|
|
print(chunk.content, end="", flush=True)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Puntos clave:**
|
||
|
|
La misma URL para REST y WebSocket.
|
||
|
|
Las mismas firmas de métodos (migración sencilla).
|
||
|
|
Simplemente agregue `.socket()` y `streaming=True`.
|
||
|
|
|
||
|
|
### Fase 3: Operaciones masivas (Nueva funcionalidad)
|
||
|
|
|
||
|
|
**Utilice la interfaz `api.bulk()`** para conjuntos de datos grandes:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Before: Inefficient per-item operations
|
||
|
|
api = Api(url="http://localhost:8088/")
|
||
|
|
flow = api.flow().id("default")
|
||
|
|
|
||
|
|
for triple in my_large_triple_list:
|
||
|
|
# Slow per-item operations
|
||
|
|
# (no direct bulk insert in REST API)
|
||
|
|
pass
|
||
|
|
|
||
|
|
# After: Efficient bulk loading
|
||
|
|
api = Api(url="http://localhost:8088/") # Same URL
|
||
|
|
bulk = api.bulk()
|
||
|
|
|
||
|
|
# This is fast (10,000 triples/second)
|
||
|
|
bulk.import_triples(flow="default", triples=iter(my_large_triple_list))
|
||
|
|
```
|
||
|
|
|
||
|
|
### Actualizaciones de la documentación
|
||
|
|
|
||
|
|
1. **README.md**: Agregar ejemplos de transmisión y WebSocket
|
||
|
|
2. **Referencia de la API**: Documentar todos los nuevos métodos y parámetros
|
||
|
|
3. **Guía de migración**: Guía paso a paso para habilitar la transmisión
|
||
|
|
4. **Ejemplos**: Agregar scripts de ejemplo para patrones comunes
|
||
|
|
5. **Guía de rendimiento**: Documentar las mejoras de rendimiento esperadas
|
||
|
|
|
||
|
|
### Política de obsolescencia
|
||
|
|
|
||
|
|
**No hay obsolescencias**. Todas las API existentes siguen siendo compatibles. Esto es una mejora pura.
|
||
|
|
|
||
|
|
## Cronograma
|
||
|
|
|
||
|
|
### Semana 1: Base
|
||
|
|
Capa de abstracción de transporte
|
||
|
|
Refactorizar código REST existente
|
||
|
|
Pruebas unitarias para la capa de transporte
|
||
|
|
Verificación de compatibilidad con versiones anteriores
|
||
|
|
|
||
|
|
### Semana 2: Transporte WebSocket
|
||
|
|
Implementación del transporte WebSocket
|
||
|
|
Gestión de conexiones y reconexión
|
||
|
|
Multiplexación de solicitudes
|
||
|
|
Pruebas unitarias e de integración
|
||
|
|
|
||
|
|
### Semana 3: Soporte de transmisión
|
||
|
|
Agregar parámetro de transmisión a los métodos de LLM
|
||
|
|
Implementar el análisis de respuestas de transmisión
|
||
|
|
Agregar tipos de fragmentos de transmisión
|
||
|
|
Pruebas de integración de transmisión
|
||
|
|
|
||
|
|
### Semana 4: Operaciones masivas
|
||
|
|
Agregar métodos de importación/exportación masivos
|
||
|
|
Implementar operaciones basadas en iteradores
|
||
|
|
Pruebas de rendimiento
|
||
|
|
Pruebas de integración de operaciones masivas
|
||
|
|
|
||
|
|
### Semana 5: Paridad de funciones y documentación
|
||
|
|
Agregar consulta de incrustaciones de grafos
|
||
|
|
Agregar API de métricas
|
||
|
|
Documentación completa
|
||
|
|
Guía de migración
|
||
|
|
Versión candidata
|
||
|
|
|
||
|
|
### Semana 6: Lanzamiento
|
||
|
|
Pruebas de integración finales
|
||
|
|
Pruebas de referencia de rendimiento
|
||
|
|
Documentación de lanzamiento
|
||
|
|
Anuncio a la comunidad
|
||
|
|
|
||
|
|
**Duración total**: 6 semanas
|
||
|
|
|
||
|
|
## Preguntas abiertas
|
||
|
|
|
||
|
|
### Preguntas de diseño de la API
|
||
|
|
|
||
|
|
1. **Soporte asíncrono**: ✅ **RESUELTO** - Soporte asíncrono completo incluido en el lanzamiento inicial
|
||
|
|
Todas las interfaces tienen variantes asíncronas: `async_flow()`, `async_socket()`, `async_bulk()`, `async_metrics()`
|
||
|
|
Proporciona una simetría completa entre las API sincrónicas y asíncronas
|
||
|
|
Esencial para marcos de trabajo asíncronos modernos (FastAPI, aiohttp)
|
||
|
|
|
||
|
|
2. **Seguimiento del progreso**: ¿Deben las operaciones masivas admitir devoluciones de llamada de progreso?
|
||
|
|
```python
|
||
|
|
def progress_callback(processed: int, total: Optional[int]):
|
||
|
|
print(f"Processed {processed} items")
|
||
|
|
|
||
|
|
bulk.import_triples(flow="default", triples=triples, on_progress=progress_callback)
|
||
|
|
```
|
||
|
|
**Recomendación**: Agregar en la Fase 2. No es crítico para la versión inicial.
|
||
|
|
|
||
|
|
3. **Tiempo de espera de la transmisión**: ¿Cómo debemos manejar los tiempos de espera para las operaciones de transmisión?
|
||
|
|
**Recomendación**: Utilizar el mismo tiempo de espera que para las operaciones no de transmisión, pero restablecerlo cada vez que se recibe un fragmento.
|
||
|
|
|
||
|
|
4. **Almacenamiento en búfer de fragmentos**: ¿Debemos almacenar los fragmentos en búfer o devolverlos inmediatamente?
|
||
|
|
**Recomendación**: Devolverlos inmediatamente para lograr la menor latencia.
|
||
|
|
|
||
|
|
5. **Servicios globales a través de WebSocket**: ¿Debería `api.socket()` admitir servicios globales (biblioteca, conocimiento, colección, configuración) o solo servicios específicos del flujo?
|
||
|
|
**Recomendación**: Comenzar solo con servicios específicos del flujo (donde la transmisión es importante). Agregar servicios globales si es necesario en la Fase 2.
|
||
|
|
|
||
|
|
### Preguntas de implementación
|
||
|
|
|
||
|
|
1. **Biblioteca de WebSocket**: ¿Debemos usar `websockets`, `websocket-client` o `aiohttp`?
|
||
|
|
**Recomendación**: `websockets` (asíncrono, maduro, bien mantenido). Envolverlo en una interfaz síncrona utilizando `asyncio.run()`.
|
||
|
|
|
||
|
|
2. **Grupo de conexiones**: ¿Debemos admitir múltiples instancias concurrentes de `Api` que compartan un grupo de conexiones?
|
||
|
|
**Recomendación**: Dejar para la Fase 2. Cada instancia de `Api` tendrá sus propias conexiones inicialmente.
|
||
|
|
|
||
|
|
3. **Reutilización de conexiones**: ¿Deben `SocketClient` y `BulkClient` compartir la misma conexión de WebSocket, o usar conexiones separadas?
|
||
|
|
**Recomendación**: Conexiones separadas. Implementación más sencilla, separación de responsabilidades más clara.
|
||
|
|
|
||
|
|
4. **Conexión perezosa vs. activa**: ¿Se debe establecer la conexión de WebSocket en `api.socket()` o en la primera solicitud?
|
||
|
|
**Recomendación**: Perezosa (en la primera solicitud). Evita la sobrecarga de la conexión si el usuario solo utiliza métodos REST.
|
||
|
|
|
||
|
|
### Preguntas de prueba
|
||
|
|
|
||
|
|
1. **Pasarela de simulación**: ¿Debemos crear una pasarela de simulación ligera para las pruebas, o probar contra la pasarela real?
|
||
|
|
**Recomendación**: Ambos. Utilizar simulaciones para pruebas unitarias, y la pasarela real para pruebas de integración.
|
||
|
|
|
||
|
|
2. **Pruebas de regresión de rendimiento**: ¿Debemos agregar pruebas de regresión de rendimiento automatizadas a CI?
|
||
|
|
**Recomendación**: Sí, pero con umbrales amplios para tener en cuenta la variabilidad del entorno de CI.
|
||
|
|
|
||
|
|
## Referencias
|
||
|
|
|
||
|
|
### Especificaciones técnicas relacionadas
|
||
|
|
`docs/tech-specs/streaming-llm-responses.md` - Implementación de transmisión en la pasarela
|
||
|
|
`docs/tech-specs/rag-streaming-support.md` - Soporte de transmisión RAG
|
||
|
|
|
||
|
|
### Archivos de implementación
|
||
|
|
`trustgraph-base/trustgraph/api/` - Código fuente de la API de Python
|
||
|
|
`trustgraph-flow/trustgraph/gateway/` - Código fuente de la pasarela
|
||
|
|
`trustgraph-flow/trustgraph/gateway/dispatch/mux.py` - Implementación de referencia de multiplexor de WebSocket
|
||
|
|
|
||
|
|
### Documentación
|
||
|
|
`docs/apiSpecification.md` - Referencia completa de la API
|
||
|
|
`docs/api-status-summary.md` - Resumen del estado de la API
|
||
|
|
`README.websocket` - Documentación del protocolo WebSocket
|
||
|
|
`STREAMING-IMPLEMENTATION-NOTES.txt` - Notas de implementación de transmisión
|
||
|
|
|
||
|
|
### Bibliotecas externas
|
||
|
|
`websockets` - Biblioteca de WebSocket de Python (https://websockets.readthedocs.io/)
|
||
|
|
`requests` - Biblioteca HTTP de Python (existente)
|