Streaming With Task Events Ollama
Streaming with task events ollama example.
Streaming with Task Events - Showcase Flux’s Event-Based Execution.
Run it
python examples/ai/streaming_with_task_events_ollama.py
"""
Streaming with Task Events - Showcase Flux's Event-Based Execution.
This example demonstrates how Flux's automatic task event generation can be used
to stream LLM tokens through the event system. Each token (or token batch) is
processed by a task, which automatically creates TASK_STARTED and TASK_COMPLETED
events that can be consumed via Server-Sent Events (SSE).
Key Concepts Demonstrated:
- Automatic event generation from tasks
- Event-based execution architecture
- Real-time streaming via SSE endpoints
- Distributed execution with event tracking
How It Works:
1. LLM generates tokens in real-time (streaming)
2. Each token batch is passed to a Flux task
3. Flux automatically creates events for each task execution
4. Events are persisted via checkpoint mechanism
5. Clients consume events via SSE endpoint in real-time
Prerequisites:
1. Install Ollama: https://ollama.ai
2. Pull a model: ollama pull llama3.2
3. Start Ollama service: ollama serve
Usage (CLI):
flux workflow run streaming_with_task_events_ollama '{"prompt": "Explain quantum computing in 3 sentences."}'
Usage (HTTP with SSE):
curl -N http://localhost:8000/workflows/streaming_with_task_events_ollama/run/stream?detailed=true \\
-H "Content-Type: application/json" \\
-d '{"prompt": "Explain quantum computing in 3 sentences."}'
SSE Event Structure:
You'll see events like:
- TASK_STARTED: {"name": "process_token_batch", "value": {...}}
- TASK_COMPLETED: {"name": "process_token_batch", "value": {"tokens": "Hello", "position": 0}}
For a Python SSE client example, see: examples/ai/stream_event_client.py
"""
from __future__ import annotations
import time
from typing import Any
from ollama import AsyncClient
from flux import ExecutionContext, task, workflow
@task
async def process_token_batch(
tokens: str,
batch_number: int,
total_tokens: int,
) -> dict[str, Any]:
"""
Process a batch of streaming tokens.
This task automatically generates Flux events:
- TASK_STARTED: Signals batch processing has begun
- TASK_COMPLETED: Contains the processed batch data in event.value
These events can be consumed in real-time via the SSE endpoint,
allowing clients to reconstruct the streaming response.
Args:
tokens: The token string for this batch
batch_number: Sequential batch identifier
total_tokens: Total tokens processed so far
Returns:
Dictionary with batch metadata that will be stored in TASK_COMPLETED event
"""
return {
"tokens": tokens,
"batch_number": batch_number,
"total_tokens": total_tokens,
"timestamp": time.time(),
}
@task.with_options(retry_max_attempts=3, retry_delay=1, retry_backoff=2, timeout=120)
async def stream_llm_with_task_events(
prompt: str,
system_prompt: str,
model: str,
ollama_url: str,
batch_size: int,
) -> dict[str, Any]:
"""
Stream LLM response and emit events for each token batch.
This demonstrates Flux's event-based execution:
1. Streams tokens from LLM
2. Batches tokens for efficiency
3. Passes each batch to process_token_batch task
4. Each task call generates TASK_STARTED + TASK_COMPLETED events
5. Events are automatically persisted and available via SSE
Args:
prompt: User's prompt
system_prompt: System prompt for the LLM
model: Ollama model to use
ollama_url: Ollama server URL
batch_size: Number of tokens per batch (affects event frequency)
Returns:
Complete response with streaming metadata
"""
try:
client = AsyncClient(host=ollama_url)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]
# Track streaming metrics
start_time = time.time()
complete_response = ""
token_buffer = ""
total_tokens = 0
batch_count = 0
print("\n" + "=" * 60)
print("STREAMING WITH TASK EVENTS")
print("=" * 60)
print(
f"Each batch of {batch_size} tokens will generate Flux task events",
)
print("Monitor via: GET /workflows/<name>/executions/<id>?detailed=true")
print("Stream via: POST /workflows/<name>/run/stream?detailed=true")
print("=" * 60 + "\n")
# Stream tokens from LLM
async for chunk in await client.chat(model=model, messages=messages, stream=True):
token = chunk["message"]["content"]
complete_response += token
token_buffer += token
total_tokens += 1
# Print to stdout for CLI demonstration
print(token, end="", flush=True)
# Emit task event when batch is ready
if len(token_buffer) >= batch_size:
# This task call automatically creates TASK_STARTED + TASK_COMPLETED events
await process_token_batch(
tokens=token_buffer,
batch_number=batch_count,
total_tokens=total_tokens,
)
batch_count += 1
token_buffer = ""
# Process remaining tokens
if token_buffer:
await process_token_batch(
tokens=token_buffer,
batch_number=batch_count,
total_tokens=total_tokens,
)
batch_count += 1
print("\n" + "=" * 60)
generation_time = time.time() - start_time
tokens_per_second = total_tokens / generation_time if generation_time > 0 else 0
return {
"response": complete_response,
"streaming_metrics": {
"total_tokens": total_tokens,
"total_batches": batch_count,
"batch_size": batch_size,
"generation_time_seconds": round(generation_time, 2),
"tokens_per_second": round(tokens_per_second, 2),
},
"event_info": {
"task_events_generated": batch_count * 2, # STARTED + COMPLETED per batch
"events_per_batch": 2,
"message": "Each batch generated TASK_STARTED and TASK_COMPLETED events",
},
}
except Exception as e:
raise RuntimeError(
f"Failed to call Ollama API: {str(e)}. "
"Make sure Ollama is running (ollama serve) and the model is available.",
) from e
@workflow
async def streaming_with_task_events_ollama(ctx: ExecutionContext[dict[str, Any]]):
"""
Showcase Flux's event-based execution with LLM streaming.
This workflow demonstrates how Flux's automatic task event generation
can be used to stream data through the event system. Each token batch
becomes a task execution, creating traceable events that can be consumed
in real-time via SSE.
Input format:
{
"prompt": "Your prompt here", # Required
"system_prompt": "Optional system prompt",
"model": "llama3.2", # Optional: model name
"batch_size": 5, # Optional: tokens per batch (default: 5)
"ollama_url": "http://localhost:11434" # Optional
}
Returns:
{
"response": "Generated text...",
"streaming_metrics": {
"total_tokens": 150,
"total_batches": 30,
"batch_size": 5,
"generation_time_seconds": 8.5,
"tokens_per_second": 17.6
},
"event_info": {
"task_events_generated": 60, # STARTED + COMPLETED per batch
"message": "Each batch generated TASK_STARTED and TASK_COMPLETED events"
},
"execution_id": "..."
}
Event Consumption:
- HTTP GET: /workflows/{name}/executions/{id}?detailed=true
- SSE Stream: /workflows/{name}/run/stream?detailed=true
"""
# Get configuration from input
input_data = ctx.input or {}
prompt = input_data.get("prompt")
system_prompt = input_data.get(
"system_prompt",
"You are a helpful AI assistant. Provide clear, concise explanations.",
)
model = input_data.get("model", "llama3.2")
batch_size = input_data.get("batch_size", 5) # Balance latency vs event volume
ollama_url = input_data.get("ollama_url", "http://localhost:11434")
# Validate required inputs
if not prompt:
return {
"error": "No prompt provided in input",
"execution_id": ctx.execution_id,
}
# Stream LLM response with task-based event generation
result = await stream_llm_with_task_events(
prompt=prompt,
system_prompt=system_prompt,
model=model,
ollama_url=ollama_url,
batch_size=batch_size,
)
return {
**result,
"execution_id": ctx.execution_id,
"how_to_consume_events": {
"http_api": f"GET /workflows/streaming_with_task_events_ollama/executions/{ctx.execution_id}?detailed=true",
"sse_stream": "POST /workflows/streaming_with_task_events_ollama/run/stream?detailed=true",
"python_client": "See examples/ai/stream_event_client.py",
},
}
if __name__ == "__main__": # pragma: no cover
import asyncio
async def run_example():
"""Run example demonstrating task-based event streaming."""
prompt = "Explain how machine learning models learn from data in 3 paragraphs."
print("\n" + "=" * 60)
print("TASK-BASED EVENT STREAMING DEMO")
print("=" * 60)
print(f"\nPrompt: {prompt}\n")
print("This example demonstrates Flux's event-based execution:")
print(" - Each token batch is processed by a Flux task")
print(" - Tasks automatically generate TASK_STARTED + TASK_COMPLETED events")
print(" - Events can be consumed in real-time via SSE")
print("=" * 60)
try:
result = streaming_with_task_events_ollama.run(
{
"prompt": prompt,
"model": "llama3.2",
"batch_size": 10, # Larger batches = fewer events
},
)
if result.has_failed:
raise Exception(f"Workflow failed: {result.output}")
print("\n\n" + "=" * 60)
print("STREAMING COMPLETE")
print("=" * 60)
print("\nMetrics:")
print(f" Total tokens: {result.output['streaming_metrics']['total_tokens']}")
print(
f" Total batches: {result.output['streaming_metrics']['total_batches']}",
)
print(
f" Batch size: {result.output['streaming_metrics']['batch_size']} tokens",
)
print(
f" Generation time: {result.output['streaming_metrics']['generation_time_seconds']}s",
)
print(
f" Tokens/second: {result.output['streaming_metrics']['tokens_per_second']}",
)
print("\nEvent Info:")
print(
f" Task events generated: {result.output['event_info']['task_events_generated']}",
)
print(
f" Events per batch: {result.output['event_info']['events_per_batch']} (STARTED + COMPLETED)",
)
print("\nHow to consume events in real-time:")
print(f" HTTP API: {result.output['how_to_consume_events']['http_api']}")
print(f" SSE Stream: {result.output['how_to_consume_events']['sse_stream']}")
print(
f" Python Client: {result.output['how_to_consume_events']['python_client']}",
)
except Exception as e:
print(f"\nError: {e}")
print("\nMake sure:")
print(" 1. Ollama is running: ollama serve")
print(" 2. You have pulled a model: ollama pull llama3.2")
print(" 3. Flux server and worker are running")
asyncio.run(run_example())
Last verified against Flux 0.56.0.