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.
21 KiB
| layout | title | parent |
|---|---|---|
| default | Especificación Técnica de Respuestas de LLM en Streaming | 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:
- Interfaces de Usuario en Tiempo Real: Transmite tokens a la interfaz de usuario a medida que se generan, proporcionando retroalimentación visual inmediata.
- 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.
- 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.
- 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:
- El cliente envía
TextCompletionRequestcon los campossystemyprompt. - El servicio de LLM procesa la solicitud y espera a que se complete la generación.
- Se devuelve un único
TextCompletionResponsecon la cadenaresponsecompleta.
Esquema actual (trustgraph-base/trustgraph/schema/services/llm.py):
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:
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:
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:
class PromptRequest(Record):
id = String()
terms = Map(String())
streaming = Boolean() # NEW: Default false
Cambios en la respuesta:
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:
{
"response": "partial text...",
"end_of_stream": false,
"model": "model-name"
}
Fragmento final:
{
"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):
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):
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:
- Sobreescribir
supports_streaming()para devolverTrue - Implementar generador asíncrono
generate_content_stream() - Manejar tanto los modelos Gemini como Claude (a través de la API de VertexAI Anthropic)
Streaming de Gemini:
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:
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:
- Soporte completo de streaming: Implementar
generate_content_stream() - Modo de compatibilidad: Manejar la bandera
end_of_streamcorrectamente (devolver una única respuesta conend_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):
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
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:
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:
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:
- Transmisión de pensamientos:
Múltiples fragmentos con
chunk_type="thought",end_of_message=falseEl fragmento final del pensamiento tieneend_of_message=true - Notificación de acción:
Un solo fragmento con
chunk_type="action",end_of_message=true - Observación:
Fragmento(s) con
chunk_type="observation", el final tieneend_of_message=true - Repetir los pasos 1-3 mientras el agente razona
- Respuesta final:
chunk_type="answer"con la respuesta final encontentEl último fragmento tieneend_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:
-
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.
-
Manejo de errores en la transmisión: Si ocurre un error, el campo
errorse 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"conend_of_dialog=true. -
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.
-
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 conend_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/