mirror of
https://github.com/anthropics/claude-agent-sdk-python.git
synced 2025-10-06 01:00:03 +03:00
512 lines
17 KiB
Python
Executable File
512 lines
17 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Comprehensive examples of using ClaudeSDKClient for streaming mode.
|
|
|
|
This file demonstrates various patterns for building applications with
|
|
the ClaudeSDKClient streaming interface.
|
|
|
|
The queries are intentionally simplistic. In reality, a query can be a more
|
|
complex task that Claude SDK uses its agentic capabilities and tools (e.g. run
|
|
bash commands, edit files, search the web, fetch web content) to accomplish.
|
|
|
|
Usage:
|
|
./examples/streaming_mode.py - List the examples
|
|
./examples/streaming_mode.py all - Run all examples
|
|
./examples/streaming_mode.py basic_streaming - Run a specific example
|
|
"""
|
|
|
|
import asyncio
|
|
import contextlib
|
|
import sys
|
|
|
|
from claude_agent_sdk import (
|
|
AssistantMessage,
|
|
ClaudeAgentOptions,
|
|
ClaudeSDKClient,
|
|
CLIConnectionError,
|
|
ResultMessage,
|
|
SystemMessage,
|
|
TextBlock,
|
|
ToolResultBlock,
|
|
ToolUseBlock,
|
|
UserMessage,
|
|
)
|
|
|
|
|
|
def display_message(msg):
|
|
"""Standardized message display function.
|
|
|
|
- UserMessage: "User: <content>"
|
|
- AssistantMessage: "Claude: <content>"
|
|
- SystemMessage: ignored
|
|
- ResultMessage: "Result ended" + cost if available
|
|
"""
|
|
if isinstance(msg, UserMessage):
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
print(f"User: {block.text}")
|
|
elif isinstance(msg, AssistantMessage):
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
print(f"Claude: {block.text}")
|
|
elif isinstance(msg, SystemMessage):
|
|
# Ignore system messages
|
|
pass
|
|
elif isinstance(msg, ResultMessage):
|
|
print("Result ended")
|
|
|
|
|
|
async def example_basic_streaming():
|
|
"""Basic streaming with context manager."""
|
|
print("=== Basic Streaming Example ===")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
print("User: What is 2+2?")
|
|
await client.query("What is 2+2?")
|
|
|
|
# Receive complete response using the helper method
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_multi_turn_conversation():
|
|
"""Multi-turn conversation using receive_response helper."""
|
|
print("=== Multi-Turn Conversation Example ===")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
# First turn
|
|
print("User: What's the capital of France?")
|
|
await client.query("What's the capital of France?")
|
|
|
|
# Extract and print response
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
|
|
# Second turn - follow-up
|
|
print("\nUser: What's the population of that city?")
|
|
await client.query("What's the population of that city?")
|
|
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_concurrent_responses():
|
|
"""Handle responses while sending new messages."""
|
|
print("=== Concurrent Send/Receive Example ===")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
# Background task to continuously receive messages
|
|
async def receive_messages():
|
|
async for message in client.receive_messages():
|
|
display_message(message)
|
|
|
|
# Start receiving in background
|
|
receive_task = asyncio.create_task(receive_messages())
|
|
|
|
# Send multiple messages with delays
|
|
questions = [
|
|
"What is 2 + 2?",
|
|
"What is the square root of 144?",
|
|
"What is 10% of 80?",
|
|
]
|
|
|
|
for question in questions:
|
|
print(f"\nUser: {question}")
|
|
await client.query(question)
|
|
await asyncio.sleep(3) # Wait between messages
|
|
|
|
# Give time for final responses
|
|
await asyncio.sleep(2)
|
|
|
|
# Clean up
|
|
receive_task.cancel()
|
|
with contextlib.suppress(asyncio.CancelledError):
|
|
await receive_task
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_with_interrupt():
|
|
"""Demonstrate interrupt capability."""
|
|
print("=== Interrupt Example ===")
|
|
print("IMPORTANT: Interrupts require active message consumption.")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
# Start a long-running task
|
|
print("\nUser: Count from 1 to 100 slowly")
|
|
await client.query(
|
|
"Count from 1 to 100 slowly, with a brief pause between each number"
|
|
)
|
|
|
|
# Create a background task to consume messages
|
|
messages_received = []
|
|
|
|
async def consume_messages():
|
|
"""Consume messages in the background to enable interrupt processing."""
|
|
async for message in client.receive_response():
|
|
messages_received.append(message)
|
|
display_message(message)
|
|
|
|
# Start consuming messages in the background
|
|
consume_task = asyncio.create_task(consume_messages())
|
|
|
|
# Wait 2 seconds then send interrupt
|
|
await asyncio.sleep(2)
|
|
print("\n[After 2 seconds, sending interrupt...]")
|
|
await client.interrupt()
|
|
|
|
# Wait for the consume task to finish processing the interrupt
|
|
await consume_task
|
|
|
|
# Send new instruction after interrupt
|
|
print("\nUser: Never mind, just tell me a quick joke")
|
|
await client.query("Never mind, just tell me a quick joke")
|
|
|
|
# Get the joke
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_manual_message_handling():
|
|
"""Manually handle message stream for custom logic."""
|
|
print("=== Manual Message Handling Example ===")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
await client.query("List 5 programming languages and their main use cases")
|
|
|
|
# Manually process messages with custom logic
|
|
languages_found = []
|
|
|
|
async for message in client.receive_messages():
|
|
if isinstance(message, AssistantMessage):
|
|
for block in message.content:
|
|
if isinstance(block, TextBlock):
|
|
text = block.text
|
|
print(f"Claude: {text}")
|
|
# Custom logic: extract language names
|
|
for lang in [
|
|
"Python",
|
|
"JavaScript",
|
|
"Java",
|
|
"C++",
|
|
"Go",
|
|
"Rust",
|
|
"Ruby",
|
|
]:
|
|
if lang in text and lang not in languages_found:
|
|
languages_found.append(lang)
|
|
print(f"Found language: {lang}")
|
|
elif isinstance(message, ResultMessage):
|
|
display_message(message)
|
|
print(f"Total languages mentioned: {len(languages_found)}")
|
|
break
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_with_options():
|
|
"""Use ClaudeAgentOptions to configure the client."""
|
|
print("=== Custom Options Example ===")
|
|
|
|
# Configure options
|
|
options = ClaudeAgentOptions(
|
|
allowed_tools=["Read", "Write"], # Allow file operations
|
|
system_prompt="You are a helpful coding assistant.",
|
|
env={
|
|
"ANTHROPIC_MODEL": "claude-sonnet-4-20250514",
|
|
},
|
|
)
|
|
|
|
async with ClaudeSDKClient(options=options) as client:
|
|
print("User: Create a simple hello.txt file with a greeting message")
|
|
await client.query("Create a simple hello.txt file with a greeting message")
|
|
|
|
tool_uses = []
|
|
async for msg in client.receive_response():
|
|
if isinstance(msg, AssistantMessage):
|
|
display_message(msg)
|
|
for block in msg.content:
|
|
if hasattr(block, "name") and not isinstance(
|
|
block, TextBlock
|
|
): # ToolUseBlock
|
|
tool_uses.append(getattr(block, "name", ""))
|
|
else:
|
|
display_message(msg)
|
|
|
|
if tool_uses:
|
|
print(f"Tools used: {', '.join(tool_uses)}")
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_async_iterable_prompt():
|
|
"""Demonstrate send_message with async iterable."""
|
|
print("=== Async Iterable Prompt Example ===")
|
|
|
|
async def create_message_stream():
|
|
"""Generate a stream of messages."""
|
|
print("User: Hello! I have multiple questions.")
|
|
yield {
|
|
"type": "user",
|
|
"message": {"role": "user", "content": "Hello! I have multiple questions."},
|
|
"parent_tool_use_id": None,
|
|
"session_id": "qa-session",
|
|
}
|
|
|
|
print("User: First, what's the capital of Japan?")
|
|
yield {
|
|
"type": "user",
|
|
"message": {
|
|
"role": "user",
|
|
"content": "First, what's the capital of Japan?",
|
|
},
|
|
"parent_tool_use_id": None,
|
|
"session_id": "qa-session",
|
|
}
|
|
|
|
print("User: Second, what's 15% of 200?")
|
|
yield {
|
|
"type": "user",
|
|
"message": {"role": "user", "content": "Second, what's 15% of 200?"},
|
|
"parent_tool_use_id": None,
|
|
"session_id": "qa-session",
|
|
}
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
# Send async iterable of messages
|
|
await client.query(create_message_stream())
|
|
|
|
# Receive the three responses
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
async for msg in client.receive_response():
|
|
display_message(msg)
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_bash_command():
|
|
"""Example showing tool use blocks when running bash commands."""
|
|
print("=== Bash Command Example ===")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
print("User: Run a bash echo command")
|
|
await client.query("Run a bash echo command that says 'Hello from bash!'")
|
|
|
|
# Track all message types received
|
|
message_types = []
|
|
|
|
async for msg in client.receive_messages():
|
|
message_types.append(type(msg).__name__)
|
|
|
|
if isinstance(msg, UserMessage):
|
|
# User messages can contain tool results
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
print(f"User: {block.text}")
|
|
elif isinstance(block, ToolResultBlock):
|
|
print(
|
|
f"Tool Result (id: {block.tool_use_id}): {block.content[:100] if block.content else 'None'}..."
|
|
)
|
|
|
|
elif isinstance(msg, AssistantMessage):
|
|
# Assistant messages can contain tool use blocks
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
print(f"Claude: {block.text}")
|
|
elif isinstance(block, ToolUseBlock):
|
|
print(f"Tool Use: {block.name} (id: {block.id})")
|
|
if block.name == "Bash":
|
|
command = block.input.get("command", "")
|
|
print(f" Command: {command}")
|
|
|
|
elif isinstance(msg, ResultMessage):
|
|
print("Result ended")
|
|
if msg.total_cost_usd:
|
|
print(f"Cost: ${msg.total_cost_usd:.4f}")
|
|
break
|
|
|
|
print(f"\nMessage types received: {', '.join(set(message_types))}")
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_control_protocol():
|
|
"""Demonstrate server info and interrupt capabilities."""
|
|
print("=== Control Protocol Example ===")
|
|
print("Shows server info retrieval and interrupt capability\n")
|
|
|
|
async with ClaudeSDKClient() as client:
|
|
# 1. Get server initialization info
|
|
print("1. Getting server info...")
|
|
server_info = await client.get_server_info()
|
|
|
|
if server_info:
|
|
print("✓ Server info retrieved successfully!")
|
|
print(f" - Available commands: {len(server_info.get('commands', []))}")
|
|
print(f" - Output style: {server_info.get('output_style', 'unknown')}")
|
|
|
|
# Show available output styles if present
|
|
styles = server_info.get('available_output_styles', [])
|
|
if styles:
|
|
print(f" - Available output styles: {', '.join(styles)}")
|
|
|
|
# Show a few example commands
|
|
commands = server_info.get('commands', [])[:5]
|
|
if commands:
|
|
print(" - Example commands:")
|
|
for cmd in commands:
|
|
if isinstance(cmd, dict):
|
|
print(f" • {cmd.get('name', 'unknown')}")
|
|
else:
|
|
print("✗ No server info available (may not be in streaming mode)")
|
|
|
|
print("\n2. Testing interrupt capability...")
|
|
|
|
# Start a long-running task
|
|
print("User: Count from 1 to 20 slowly")
|
|
await client.query("Count from 1 to 20 slowly, pausing between each number")
|
|
|
|
# Start consuming messages in background to enable interrupt
|
|
messages = []
|
|
async def consume():
|
|
async for msg in client.receive_response():
|
|
messages.append(msg)
|
|
if isinstance(msg, AssistantMessage):
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
# Print first 50 chars to show progress
|
|
print(f"Claude: {block.text[:50]}...")
|
|
break
|
|
if isinstance(msg, ResultMessage):
|
|
break
|
|
|
|
consume_task = asyncio.create_task(consume())
|
|
|
|
# Wait a moment then interrupt
|
|
await asyncio.sleep(2)
|
|
print("\n[Sending interrupt after 2 seconds...]")
|
|
|
|
try:
|
|
await client.interrupt()
|
|
print("✓ Interrupt sent successfully")
|
|
except Exception as e:
|
|
print(f"✗ Interrupt failed: {e}")
|
|
|
|
# Wait for task to complete
|
|
with contextlib.suppress(asyncio.CancelledError):
|
|
await consume_task
|
|
|
|
# Send new query after interrupt
|
|
print("\nUser: Just say 'Hello!'")
|
|
await client.query("Just say 'Hello!'")
|
|
|
|
async for msg in client.receive_response():
|
|
if isinstance(msg, AssistantMessage):
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
print(f"Claude: {block.text}")
|
|
|
|
print("\n")
|
|
|
|
|
|
async def example_error_handling():
|
|
"""Demonstrate proper error handling."""
|
|
print("=== Error Handling Example ===")
|
|
|
|
client = ClaudeSDKClient()
|
|
|
|
try:
|
|
await client.connect()
|
|
|
|
# Send a message that will take time to process
|
|
print("User: Run a bash sleep command for 60 seconds not in the background")
|
|
await client.query("Run a bash sleep command for 60 seconds not in the background")
|
|
|
|
# Try to receive response with a short timeout
|
|
try:
|
|
messages = []
|
|
async with asyncio.timeout(10.0):
|
|
async for msg in client.receive_response():
|
|
messages.append(msg)
|
|
if isinstance(msg, AssistantMessage):
|
|
for block in msg.content:
|
|
if isinstance(block, TextBlock):
|
|
print(f"Claude: {block.text[:50]}...")
|
|
elif isinstance(msg, ResultMessage):
|
|
display_message(msg)
|
|
break
|
|
|
|
except asyncio.TimeoutError:
|
|
print(
|
|
"\nResponse timeout after 10 seconds - demonstrating graceful handling"
|
|
)
|
|
print(f"Received {len(messages)} messages before timeout")
|
|
|
|
except CLIConnectionError as e:
|
|
print(f"Connection error: {e}")
|
|
|
|
except Exception as e:
|
|
print(f"Unexpected error: {e}")
|
|
|
|
finally:
|
|
# Always disconnect
|
|
await client.disconnect()
|
|
|
|
print("\n")
|
|
|
|
|
|
async def main():
|
|
"""Run all examples or a specific example based on command line argument."""
|
|
examples = {
|
|
"basic_streaming": example_basic_streaming,
|
|
"multi_turn_conversation": example_multi_turn_conversation,
|
|
"concurrent_responses": example_concurrent_responses,
|
|
"with_interrupt": example_with_interrupt,
|
|
"manual_message_handling": example_manual_message_handling,
|
|
"with_options": example_with_options,
|
|
"async_iterable_prompt": example_async_iterable_prompt,
|
|
"bash_command": example_bash_command,
|
|
"control_protocol": example_control_protocol,
|
|
"error_handling": example_error_handling,
|
|
}
|
|
|
|
if len(sys.argv) < 2:
|
|
# List available examples
|
|
print("Usage: python streaming_mode.py <example_name>")
|
|
print("\nAvailable examples:")
|
|
print(" all - Run all examples")
|
|
for name in examples:
|
|
print(f" {name}")
|
|
sys.exit(0)
|
|
|
|
example_name = sys.argv[1]
|
|
|
|
if example_name == "all":
|
|
# Run all examples
|
|
for example in examples.values():
|
|
await example()
|
|
print("-" * 50 + "\n")
|
|
elif example_name in examples:
|
|
# Run specific example
|
|
await examples[example_name]()
|
|
else:
|
|
print(f"Error: Unknown example '{example_name}'")
|
|
print("\nAvailable examples:")
|
|
print(" all - Run all examples")
|
|
for name in examples:
|
|
print(f" {name}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|