Streaming Agent Ollama
Streaming agent ollama example.
Streaming Response Agent using Ollama (Local LLM).
Run it
python examples/ai/streaming_agent_ollama.py
"""
Streaming Response Agent using Ollama (Local LLM).
This example demonstrates real-time token streaming for better user experience.
Instead of waiting for the entire response, tokens are streamed as they're generated,
providing immediate feedback and a more interactive feel.
Use cases:
- Long-form content generation (essays, reports, stories)
- Real-time translations
- Interactive chatbots with immediate feedback
- Code generation with live preview
- Any scenario where perceived latency matters
Prerequisites:
1. Install Ollama: https://ollama.ai
2. Pull a model: ollama pull llama3.2
3. Start Ollama service: ollama serve
Usage:
# Generate streaming content
flux workflow run streaming_agent_ollama '{"prompt": "Write a detailed explanation of how neural networks work", "stream": true}'
# Compare with non-streaming (default)
flux workflow run streaming_agent_ollama '{"prompt": "Write a detailed explanation of how neural networks work"}'
# Use different model
flux workflow run streaming_agent_ollama '{"prompt": "Explain quantum computing", "model": "qwen2.5:3b", "stream": true}'
"""
from __future__ import annotations
import time
from typing import Any
from ollama import AsyncClient
from flux import ExecutionContext, task, workflow
@task.with_options(retry_max_attempts=3, retry_delay=1, retry_backoff=2, timeout=120)
async def generate_streaming_response(
prompt: str,
system_prompt: str,
model: str,
ollama_url: str,
) -> tuple[str, float, int]:
"""
Generate a streaming response from Ollama.
This task streams tokens as they're generated, providing real-time feedback.
In a production environment, you would typically yield/stream these tokens
to the client for display.
Args:
prompt: User's prompt
system_prompt: System prompt for the LLM
model: Ollama model to use
ollama_url: Ollama server URL
Returns:
Tuple of (complete_response, generation_time, token_count)
"""
try:
client = AsyncClient(host=ollama_url)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]
# Track metrics
start_time = time.time()
complete_response = ""
token_count = 0
# Stream the response
print("\n" + "=" * 60)
print("STREAMING RESPONSE:")
print("=" * 60)
async for chunk in await client.chat(model=model, messages=messages, stream=True):
# Extract the token from the chunk
token = chunk["message"]["content"]
complete_response += token
token_count += 1
# Print token in real-time (in production, you'd send this to client)
print(token, end="", flush=True)
print("\n" + "=" * 60)
generation_time = time.time() - start_time
return complete_response, generation_time, token_count
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
@task.with_options(retry_max_attempts=3, retry_delay=1, retry_backoff=2, timeout=120)
async def generate_non_streaming_response(
prompt: str,
system_prompt: str,
model: str,
ollama_url: str,
) -> tuple[str, float]:
"""
Generate a non-streaming response from Ollama for comparison.
Args:
prompt: User's prompt
system_prompt: System prompt for the LLM
model: Ollama model to use
ollama_url: Ollama server URL
Returns:
Tuple of (complete_response, generation_time)
"""
try:
client = AsyncClient(host=ollama_url)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
]
# Track metrics
start_time = time.time()
# Get complete response at once
response = await client.chat(model=model, messages=messages)
complete_response = response["message"]["content"]
generation_time = time.time() - start_time
print("\n" + "=" * 60)
print("NON-STREAMING RESPONSE:")
print("=" * 60)
print(complete_response)
print("=" * 60)
return complete_response, generation_time
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_agent_ollama(ctx: ExecutionContext[dict[str, Any]]):
"""
An AI agent that demonstrates streaming vs non-streaming responses.
This workflow shows the performance and UX benefits of streaming tokens
in real-time rather than waiting for the complete response.
Input format:
{
"prompt": "Your prompt here", # Required
"stream": true, # Optional: enable streaming (default: true)
"system_prompt": "Optional system prompt",
"model": "llama3.2", # Optional: model name
"ollama_url": "http://localhost:11434" # Optional: Ollama URL
}
Returns:
{
"response": "Generated text...",
"mode": "streaming" | "non-streaming",
"generation_time_seconds": 12.34,
"tokens_generated": 256, # Only for streaming mode
"tokens_per_second": 20.8, # Only for streaming mode
"model": "llama3.2"
}
"""
# Get configuration from input
input_data = ctx.input or {}
prompt = input_data.get("prompt")
stream = input_data.get("stream", True)
system_prompt = input_data.get(
"system_prompt",
"You are a helpful AI assistant. Provide clear, detailed explanations.",
)
model = input_data.get("model", "qwen3")
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,
}
# Generate response based on streaming preference
if stream:
response, generation_time, token_count = await generate_streaming_response(
prompt=prompt,
system_prompt=system_prompt,
model=model,
ollama_url=ollama_url,
)
tokens_per_second = token_count / generation_time if generation_time > 0 else 0
return {
"response": response,
"mode": "streaming",
"generation_time_seconds": round(generation_time, 2),
"tokens_generated": token_count,
"tokens_per_second": round(tokens_per_second, 2),
"model": model,
"execution_id": ctx.execution_id,
}
else:
response, generation_time = await generate_non_streaming_response(
prompt=prompt,
system_prompt=system_prompt,
model=model,
ollama_url=ollama_url,
)
return {
"response": response,
"mode": "non-streaming",
"generation_time_seconds": round(generation_time, 2),
"model": model,
"execution_id": ctx.execution_id,
}
if __name__ == "__main__": # pragma: no cover
import asyncio
async def run_example():
"""Run example demonstrating both streaming and non-streaming modes."""
prompt = "Explain how neural networks learn from data in 3 paragraphs."
print("\n" + "=" * 60)
print("STREAMING AGENT DEMO")
print("=" * 60)
print(f"\nPrompt: {prompt}\n")
try:
# Test streaming mode
print("\n" + "=" * 60)
print("MODE 1: STREAMING (Real-time token generation)")
print("=" * 60)
result_streaming = streaming_agent_ollama.run(
{
"prompt": prompt,
"stream": True,
"model": "llama3.2",
},
)
if result_streaming.has_failed:
raise Exception(f"Streaming workflow failed: {result_streaming.output}")
print("\n\nStreaming Metrics:")
print(f" - Generation time: {result_streaming.output['generation_time_seconds']}s")
print(f" - Tokens generated: {result_streaming.output['tokens_generated']}")
print(f" - Tokens per second: {result_streaming.output['tokens_per_second']}")
# Test non-streaming mode for comparison
print("\n\n" + "=" * 60)
print("MODE 2: NON-STREAMING (Wait for complete response)")
print("=" * 60)
result_non_streaming = streaming_agent_ollama.run(
{
"prompt": prompt,
"stream": False,
"model": "llama3.2",
},
)
if result_non_streaming.has_failed:
raise Exception(f"Non-streaming workflow failed: {result_non_streaming.output}")
print("\n\nNon-Streaming Metrics:")
print(f" - Generation time: {result_non_streaming.output['generation_time_seconds']}s")
# Compare
print("\n" + "=" * 60)
print("COMPARISON")
print("=" * 60)
print(
f"Streaming: {result_streaming.output['generation_time_seconds']}s "
f"({result_streaming.output['tokens_per_second']} tokens/s)",
)
print(
f"Non-streaming: {result_non_streaming.output['generation_time_seconds']}s "
f"(0 tokens/s until complete)",
)
print("\nKey benefits of streaming:")
print(" - Immediate feedback for users")
print(" - Better perceived performance")
print(" - Can start processing partial responses")
print(" - Improved interactivity for long responses")
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")
asyncio.run(run_example())
Last verified against Flux 0.56.0.