trustgraph/docs/tech-specs/streaming-llm-responses.es.md
Alex Jenkins 8954fa3ad7 Feat: TrustGraph i18n & Documentation Translation Updates (#781)
Native CLI i18n: The TrustGraph CLI has built-in translation support
that dynamically loads language strings. You can test and use
different languages by simply passing the --lang flag (e.g., --lang
es for Spanish, --lang ru for Russian) or by configuring your
environment's LANG variable.

Automated Docs Translations: This PR introduces autonomously
translated Markdown documentation into several target languages,
including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew,
Arabic, Simplified Chinese, and Russian.
2026-04-14 12:08:32 +01:00

578 lines
21 KiB
Markdown

---
layout: default
title: "Especificación Técnica de Respuestas de LLM en Streaming"
parent: "Spanish (Beta)"
---
# Especificación Técnica de Respuestas de LLM en Streaming
> **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 la implementación del soporte de streaming para respuestas de LLM
en TrustGraph. El streaming permite la entrega en tiempo real de tokens generados
a medida que son producidos por el LLM, en lugar de esperar a que se complete
la generación de la respuesta.
Esta implementación admite los siguientes casos de uso:
1. **Interfaces de Usuario en Tiempo Real**: Transmite tokens a la interfaz de usuario a medida que se generan,
proporcionando retroalimentación visual inmediata.
2. **Reducción del Tiempo Hasta el Primer Token**: Los usuarios ven la salida inmediatamente
en lugar de esperar a que se complete la generación.
3. **Manejo de Respuestas Largas**: Maneja salidas muy largas que de otro modo
podrían provocar un tiempo de espera o exceder los límites de memoria.
4. **Aplicaciones Interactivas**: Permite interfaces de chat y agentes receptivas.
## Objetivos
**Compatibilidad con Versiones Anteriores**: Los clientes existentes que no utilizan streaming continúan funcionando
sin modificaciones.
**Diseño de API Consistente**: El streaming y el no streaming utilizan los mismos patrones de esquema
con una divergencia mínima.
**Flexibilidad del Proveedor**: Soporte de streaming cuando esté disponible, con una
alternativa gradual cuando no esté disponible.
**Implementación por Fases**: Implementación incremental para reducir el riesgo.
**Soporte de Extremo a Extremo**: Streaming desde el proveedor de LLM hasta las aplicaciones cliente
a través de Pulsar, la API de Gateway y la API de Python.
## Antecedentes
### Arquitectura Actual
El flujo actual de finalización de texto de LLM funciona de la siguiente manera:
1. El cliente envía `TextCompletionRequest` con los campos `system` y `prompt`.
2. El servicio de LLM procesa la solicitud y espera a que se complete la generación.
3. Se devuelve un único `TextCompletionResponse` con la cadena `response` completa.
Esquema actual (`trustgraph-base/trustgraph/schema/services/llm.py`):
```python
class TextCompletionRequest(Record):
system = String()
prompt = String()
class TextCompletionResponse(Record):
error = Error()
response = String()
in_token = Integer()
out_token = Integer()
model = String()
```
### Limitaciones actuales
**Latencia**: Los usuarios deben esperar a que se complete la generación antes de ver cualquier resultado.
**Riesgo de tiempo de espera**: Las generaciones largas pueden exceder los umbrales de tiempo de espera del cliente.
**Mala experiencia de usuario**: La falta de retroalimentación durante la generación crea la percepción de lentitud.
**Uso de recursos**: Las respuestas completas deben almacenarse en búfer en la memoria.
Esta especificación aborda estas limitaciones al permitir la entrega incremental de respuestas, manteniendo la compatibilidad total con versiones anteriores.
## Diseño técnico
### Fase 1: Infraestructura
La Fase 1 establece la base para la transmisión mediante la modificación de esquemas, API y herramientas de línea de comandos.
#### Cambios en el esquema
##### Esquema de LLM (`trustgraph-base/trustgraph/schema/services/llm.py`)
**Cambios en la solicitud:**
```python
class TextCompletionRequest(Record):
system = String()
prompt = String()
streaming = Boolean() # NEW: Default false for backward compatibility
```
`streaming`: Cuando `true`, solicita la entrega de la respuesta en modo de transmisión.
Predeterminado: `false` (el comportamiento existente se mantiene).
**Cambios en la respuesta:**
```python
class TextCompletionResponse(Record):
error = Error()
response = String()
in_token = Integer()
out_token = Integer()
model = String()
end_of_stream = Boolean() # NEW: Indicates final message
```
`end_of_stream`: Cuando `true`, indica que esta es la respuesta final (o única).
Para solicitudes no de transmisión: Respuesta única con `end_of_stream=true`.
Para solicitudes de transmisión: Múltiples respuestas, todas con `end_of_stream=false`
excepto la última.
##### Esquema de la solicitud (`trustgraph-base/trustgraph/schema/services/prompt.py`)
El servicio de solicitud envuelve la finalización de texto, por lo que refleja el mismo patrón:
**Cambios en la solicitud:**
```python
class PromptRequest(Record):
id = String()
terms = Map(String())
streaming = Boolean() # NEW: Default false
```
**Cambios en la respuesta:**
```python
class PromptResponse(Record):
error = Error()
text = String()
object = String()
end_of_stream = Boolean() # NEW: Indicates final message
```
#### Cambios en la API de Gateway
La API de Gateway debe exponer capacidades de transmisión a clientes HTTP/WebSocket.
**Actualizaciones de la API REST:**
`POST /api/v1/text-completion`: Aceptar el parámetro `streaming` en el cuerpo de la solicitud.
El comportamiento de la respuesta depende de la bandera de transmisión:
`streaming=false`: Respuesta JSON única (comportamiento actual).
`streaming=true`: Flujo de eventos enviados por el servidor (SSE) o mensajes WebSocket.
**Formato de respuesta (transmisión):**
Cada fragmento transmitido sigue la misma estructura de esquema:
```json
{
"response": "partial text...",
"end_of_stream": false,
"model": "model-name"
}
```
Fragmento final:
```json
{
"response": "final text chunk",
"end_of_stream": true,
"in_token": 150,
"out_token": 500,
"model": "model-name"
}
```
#### Cambios en la API de Python
La API del cliente de Python debe soportar tanto modos de transmisión como no de transmisión
manteniendo la compatibilidad con versiones anteriores.
**Actualizaciones de LlmClient** (`trustgraph-base/trustgraph/clients/llm_client.py`):
```python
class LlmClient(BaseClient):
def request(self, system, prompt, timeout=300, streaming=False):
"""
Non-streaming request (backward compatible).
Returns complete response string.
"""
# Existing behavior when streaming=False
async def request_stream(self, system, prompt, timeout=300):
"""
Streaming request.
Yields response chunks as they arrive.
"""
# New async generator method
```
**Actualizaciones de PromptClient** (`trustgraph-base/trustgraph/base/prompt_client.py`):
Patrón similar con el parámetro `streaming` y la variante de generador asíncrono.
#### Cambios en la herramienta de línea de comandos
**tg-invoke-llm** (`trustgraph-cli/trustgraph/cli/invoke_llm.py`):
```
tg-invoke-llm [system] [prompt] [--no-streaming] [-u URL] [-f flow-id]
```
La transmisión está habilitada de forma predeterminada para una mejor experiencia de usuario interactiva.
La opción `--no-streaming` desactiva la transmisión.
Cuando la transmisión está habilitada: envía los tokens a la salida estándar a medida que llegan.
Cuando la transmisión no está habilitada: espera la respuesta completa y luego la muestra.
**tg-invoke-prompt** (`trustgraph-cli/trustgraph/cli/invoke_prompt.py`):
```
tg-invoke-prompt [template-id] [var=value...] [--no-streaming] [-u URL] [-f flow-id]
```
El mismo patrón que `tg-invoke-llm`.
#### Cambios en la Clase Base del Servicio LLM
**LlmService** (`trustgraph-base/trustgraph/base/llm_service.py`):
```python
class LlmService(FlowProcessor):
async def on_request(self, msg, consumer, flow):
request = msg.value()
streaming = getattr(request, 'streaming', False)
if streaming and self.supports_streaming():
async for chunk in self.generate_content_stream(...):
await self.send_response(chunk, end_of_stream=False)
await self.send_response(final_chunk, end_of_stream=True)
else:
response = await self.generate_content(...)
await self.send_response(response, end_of_stream=True)
def supports_streaming(self):
"""Override in subclass to indicate streaming support."""
return False
async def generate_content_stream(self, system, prompt, model, temperature):
"""Override in subclass to implement streaming."""
raise NotImplementedError()
```
--
### Fase 2: Prueba de concepto de VertexAI
La Fase 2 implementa el streaming en un único proveedor (VertexAI) para validar la
infraestructura y habilitar pruebas de extremo a extremo.
#### Implementación de VertexAI
**Módulo:** `trustgraph-vertexai/trustgraph/model/text_completion/vertexai/llm.py`
**Cambios:**
1. Sobreescribir `supports_streaming()` para devolver `True`
2. Implementar generador asíncrono `generate_content_stream()`
3. Manejar tanto los modelos Gemini como Claude (a través de la API de VertexAI Anthropic)
**Streaming de Gemini:**
```python
async def generate_content_stream(self, system, prompt, model, temperature):
model_instance = self.get_model(model, temperature)
response = model_instance.generate_content(
[system, prompt],
stream=True # Enable streaming
)
for chunk in response:
yield LlmChunk(
text=chunk.text,
in_token=None, # Available only in final chunk
out_token=None,
)
# Final chunk includes token counts from response.usage_metadata
```
**Claude (a través de VertexAI Anthropic) en modo de transmisión:**
```python
async def generate_content_stream(self, system, prompt, model, temperature):
with self.anthropic_client.messages.stream(...) as stream:
for text in stream.text_stream:
yield LlmChunk(text=text)
# Token counts from stream.get_final_message()
```
#### Pruebas
Pruebas unitarias para el ensamblaje de respuestas en streaming.
Pruebas de integración con VertexAI (Gemini y Claude).
Pruebas de extremo a extremo: CLI -> Gateway -> Pulsar -> VertexAI -> back.
Pruebas de compatibilidad con versiones anteriores: Las solicitudes no en streaming aún funcionan.
--
### Fase 3: Todos los proveedores de LLM
La fase 3 extiende el soporte de streaming a todos los proveedores de LLM en el sistema.
#### Estado de implementación del proveedor
Cada proveedor debe:
1. **Soporte completo de streaming**: Implementar `generate_content_stream()`
2. **Modo de compatibilidad**: Manejar la bandera `end_of_stream` correctamente
(devolver una única respuesta con `end_of_stream=true`)
| Proveedor | Paquete | Soporte de streaming |
|----------|---------|-------------------|
| OpenAI | trustgraph-flow | Completo (API de streaming nativo) |
| Claude/Anthropic | trustgraph-flow | Completo (API de streaming nativo) |
| Ollama | trustgraph-flow | Completo (API de streaming nativo) |
| Cohere | trustgraph-flow | Completo (API de streaming nativo) |
| Mistral | trustgraph-flow | Completo (API de streaming nativo) |
| Azure OpenAI | trustgraph-flow | Completo (API de streaming nativo) |
| Google AI Studio | trustgraph-flow | Completo (API de streaming nativo) |
| VertexAI | trustgraph-vertexai | Completo (Fase 2) |
| Bedrock | trustgraph-bedrock | Completo (API de streaming nativo) |
| LM Studio | trustgraph-flow | Completo (compatible con OpenAI) |
| LlamaFile | trustgraph-flow | Completo (compatible con OpenAI) |
| vLLM | trustgraph-flow | Completo (compatible con OpenAI) |
| TGI | trustgraph-flow | Por determinar |
| Azure | trustgraph-flow | Por determinar |
#### Patrón de implementación
Para proveedores compatibles con OpenAI (OpenAI, LM Studio, LlamaFile, vLLM):
```python
async def generate_content_stream(self, system, prompt, model, temperature):
response = await self.client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": prompt}
],
temperature=temperature,
stream=True
)
async for chunk in response:
if chunk.choices[0].delta.content:
yield LlmChunk(text=chunk.choices[0].delta.content)
```
--
### Fase 4: API del Agente
La Fase 4 extiende la transmisión a la API del Agente. Esto es más complejo porque
la API del Agente ya es inherentemente multi-mensaje (pensamiento → acción → observación
→ repetir → respuesta final).
#### Esquema actual del Agente
```python
class AgentStep(Record):
thought = String()
action = String()
arguments = Map(String())
observation = String()
user = String()
class AgentRequest(Record):
question = String()
state = String()
group = Array(String())
history = Array(AgentStep())
user = String()
class AgentResponse(Record):
answer = String()
error = Error()
thought = String()
observation = String()
```
#### Cambios Propuestos al Esquema del Agente
**Solicitar Cambios:**
```python
class AgentRequest(Record):
question = String()
state = String()
group = Array(String())
history = Array(AgentStep())
user = String()
streaming = Boolean() # NEW: Default false
```
**Cambios en la respuesta:**
El agente produce múltiples tipos de salida durante su ciclo de razonamiento:
Pensamientos (razonamiento)
Acciones (llamadas a herramientas)
Observaciones (resultados de las herramientas)
Respuesta (respuesta final)
Errores
Dado que `chunk_type` identifica qué tipo de contenido se está enviando, los campos separados
`answer`, `error`, `thought` y `observation` se pueden combinar en
un único campo `content`:
```python
class AgentResponse(Record):
chunk_type = String() # "thought", "action", "observation", "answer", "error"
content = String() # The actual content (interpretation depends on chunk_type)
end_of_message = Boolean() # Current thought/action/observation/answer is complete
end_of_dialog = Boolean() # Entire agent dialog is complete
```
**Semántica de los campos:**
`chunk_type`: Indica qué tipo de contenido se encuentra en el campo `content`
`"thought"`: Razonamiento/pensamiento del agente
`"action"`: Herramienta/acción que se está invocando
`"observation"`: Resultado de la ejecución de la herramienta
`"answer"`: Respuesta final a la pregunta del usuario
`"error"`: Mensaje de error
`content`: El contenido transmitido real, interpretado según `chunk_type`
`end_of_message`: Cuando `true`, el tipo de fragmento actual está completo
Ejemplo: Todos los tokens para el pensamiento actual han sido enviados
Permite a los clientes saber cuándo pasar a la siguiente etapa
`end_of_dialog`: Cuando `true`, la interacción completa del agente ha finalizado
Este es el mensaje final en la transmisión
#### Comportamiento de la transmisión del agente
Cuando `streaming=true`:
1. **Transmisión de pensamientos:**
Múltiples fragmentos con `chunk_type="thought"`, `end_of_message=false`
El fragmento final del pensamiento tiene `end_of_message=true`
2. **Notificación de acción:**
Un solo fragmento con `chunk_type="action"`, `end_of_message=true`
3. **Observación:**
Fragmento(s) con `chunk_type="observation"`, el final tiene `end_of_message=true`
4. **Repetir** los pasos 1-3 mientras el agente razona
5. **Respuesta final:**
`chunk_type="answer"` con la respuesta final en `content`
El último fragmento tiene `end_of_message=true`, `end_of_dialog=true`
**Secuencia de ejemplo de la transmisión:**
```
{chunk_type: "thought", content: "I need to", end_of_message: false, end_of_dialog: false}
{chunk_type: "thought", content: " search for...", end_of_message: true, end_of_dialog: false}
{chunk_type: "action", content: "search", end_of_message: true, end_of_dialog: false}
{chunk_type: "observation", content: "Found: ...", end_of_message: true, end_of_dialog: false}
{chunk_type: "thought", content: "Based on this", end_of_message: false, end_of_dialog: false}
{chunk_type: "thought", content: " I can answer...", end_of_message: true, end_of_dialog: false}
{chunk_type: "answer", content: "The answer is...", end_of_message: true, end_of_dialog: true}
```
Cuando `streaming=false`:
Comportamiento actual preservado
Respuesta única con respuesta completa
`end_of_message=true`, `end_of_dialog=true`
#### Gateway y API de Python
Gateway: Nuevo punto final SSE/WebSocket para la transmisión de agentes
API de Python: Nuevo método de generador asíncrono `agent_stream()`
--
## Consideraciones de seguridad
**Sin nueva superficie de ataque**: La transmisión utiliza la misma autenticación/autorización
**Limitación de velocidad**: Aplicar límites de velocidad por token o por fragmento si es necesario
**Manejo de conexiones**: Terminar correctamente las transmisiones en caso de desconexión del cliente
**Gestión de tiempos de espera**: Las solicitudes de transmisión requieren un manejo adecuado de los tiempos de espera
## Consideraciones de rendimiento
**Memoria**: La transmisión reduce el uso máximo de memoria (sin almacenamiento en búfer de la respuesta completa)
**Latencia**: El tiempo hasta el primer token se reduce significativamente
**Sobrecarga de conexión**: Las conexiones SSE/WebSocket tienen una sobrecarga de mantenimiento activo
**Rendimiento de Pulsar**: Múltiples mensajes pequeños frente a un mensaje grande único
tradeoff
## Estrategia de pruebas
### Pruebas unitarias
Serialización/deserialización de esquema con nuevos campos
Compatibilidad con versiones anteriores (los campos faltantes utilizan los valores predeterminados)
Lógica de ensamblaje de fragmentos
### Pruebas de integración
Implementación de transmisión de cada proveedor de LLM
Puntos finales de transmisión de la API de Gateway
Métodos de transmisión del cliente de Python
### Pruebas de extremo a extremo
Salida de transmisión de la herramienta de línea de comandos
Flujo completo: Cliente → Gateway → Pulsar → LLM → de vuelta
Cargas de trabajo mixtas de transmisión/no transmisión
### Pruebas de compatibilidad con versiones anteriores
Los clientes existentes funcionan sin modificaciones
Las solicitudes no de transmisión se comportan de la misma manera
## Plan de migración
### Fase 1: Infraestructura
Implementar cambios de esquema (compatible con versiones anteriores)
Implementar actualizaciones de la API de Gateway
Implementar actualizaciones de la API de Python
Lanzar actualizaciones de la herramienta de línea de comandos
### Fase 2: VertexAI
Implementar la implementación de transmisión de VertexAI
Validar con cargas de trabajo de prueba
### Fase 3: Todos los proveedores
Implementar las actualizaciones de los proveedores de forma incremental
Monitorear problemas
### Fase 4: API de agente
Implementar cambios de esquema de agente
Implementar la implementación de transmisión de agente
Actualizar la documentación
## Cronograma
| Fase | Descripción | Dependencias |
|-------|-------------|--------------|
| Fase 1 | Infraestructura | Ninguna |
| Fase 2 | PoC de VertexAI | Fase 1 |
| Fase 3 | Todos los proveedores | Fase 2 |
| Fase 4 | API de agente | Fase 3 |
## Decisiones de diseño
Las siguientes preguntas se resolvieron durante la especificación:
1. **Conteo de tokens en la transmisión**: Los conteos de tokens son deltas, no totales acumulados.
Los consumidores pueden sumarlos si es necesario. Esto coincide con la forma en que la mayoría de los proveedores informan
el uso y simplifica la implementación.
2. **Manejo de errores en la transmisión**: Si ocurre un error, el campo `error` se
completa y no se necesitan otros campos. Un error siempre es la comunicación final
no se permiten ni se esperan mensajes posteriores después de
un error. Para las transmisiones de LLM/Prompt, `end_of_stream=true`. Para las transmisiones de agente,
`chunk_type="error"` con `end_of_dialog=true`.
3. **Recuperación de respuesta parcial**: El protocolo de mensajería (Pulsar) es resistente,
por lo que no se necesita reintento a nivel de mensaje. Si un cliente pierde el seguimiento de la transmisión
o se desconecta, debe reintentar la solicitud completa desde cero.
4. **Transmisión del servicio de prompt**: La transmisión solo es compatible con texto (`text`)
respuestas, no respuestas estructuradas (`object`). El servicio de prompt lo sabe desde
el principio si la salida será JSON o texto según la plantilla del prompt. Si se realiza una
solicitud de transmisión para un prompt de salida JSON, el
servicio debe:
Devolver el JSON completo en una sola respuesta con `end_of_stream=true`, o
Rechazar la solicitud de transmisión con un error
## Preguntas abiertas
Ninguna en este momento.
## Referencias
Esquema de LLM actual: `trustgraph-base/trustgraph/schema/services/llm.py`
Esquema de prompt actual: `trustgraph-base/trustgraph/schema/services/prompt.py`
Esquema de agente actual: `trustgraph-base/trustgraph/schema/services/agent.py`
Base del servicio de LLM: `trustgraph-base/trustgraph/base/llm_service.py`
Proveedor de VertexAI: `trustgraph-vertexai/trustgraph/model/text_completion/vertexai/llm.py`
API de Gateway: `trustgraph-base/trustgraph/api/`
Herramientas de CLI: `trustgraph-cli/trustgraph/cli/`