Python SDK for Claude Code enabling developers to build AI-powered applications and agents with support for custom tools, hooks, and bidirectional interactive conversations
The query() function provides a straightforward async interface for one-shot, stateless interactions with Claude Code. It's ideal for simple questions, batch processing, automation scripts, and scenarios where all inputs are known upfront.
Execute one-shot or unidirectional streaming interactions with Claude Code. Returns an async iterator of messages representing the conversation.
async def query(
*,
prompt: str | AsyncIterable[dict[str, Any]],
options: ClaudeAgentOptions | None = None,
transport: Transport | None = None,
) -> AsyncIterator[Message]Parameters:
prompt (str | AsyncIterable[dict[str, Any]]): The prompt to send. String for single-shot queries, AsyncIterable for streaming mode with multiple messagesoptions (ClaudeAgentOptions | None): Optional ClaudeAgentOptions configuration. Controls tool execution, working directory, model selection, permissions, and more. None uses default settingstransport (Transport | None): Optional custom Transport implementation. None uses bundled Claude Code CLI subprocess transportReturns: AsyncIterator[Message] yielding messages in order:
include_partial_messages=True)Raises:
Usage:
Simple query:
import anyio
from claude_agent_sdk import query
async def main():
async for message in query(prompt="What is 2 + 2?"):
print(message)
anyio.run(main)With options:
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock
async def main():
options = ClaudeAgentOptions(
system_prompt="You are an expert Python developer",
cwd="/home/user/project",
allowed_tools=["Read", "Write", "Bash"],
permission_mode="acceptEdits"
)
async for message in query(prompt="Create a Python web server", options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)Streaming mode (still unidirectional):
import anyio
from claude_agent_sdk import query
from typing import AsyncIterator
async def prompts() -> AsyncIterator[dict]:
"""Generate multiple prompts for streaming."""
yield {
"type": "user",
"message": {"role": "user", "content": "Hello"},
"parent_tool_use_id": None,
"session_id": "default"
}
yield {
"type": "user",
"message": {"role": "user", "content": "How are you?"},
"parent_tool_use_id": None,
"session_id": "default"
}
async def main():
async for message in query(prompt=prompts()):
print(message)
anyio.run(main)With custom transport:
import anyio
from claude_agent_sdk import query, Transport
class MyCustomTransport(Transport):
"""Custom transport implementation."""
async def connect(self): ...
async def write(self, data): ...
async def read_messages(self): ...
async def close(self): ...
def is_ready(self): ...
async def end_input(self): ...
async def main():
transport = MyCustomTransport()
async for message in query(prompt="Hello", transport=transport):
print(message)
anyio.run(main)Get only text from Claude's response:
import anyio
from claude_agent_sdk import query, AssistantMessage, TextBlock
async def get_text_response(prompt: str) -> str:
"""Extract text from Claude's response."""
texts = []
async for message in query(prompt=prompt):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
texts.append(block.text)
return " ".join(texts)
async def main():
response = await get_text_response("What is the capital of France?")
print(response)
anyio.run(main)Process multiple prompts in sequence:
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions
async def process_batch(prompts: list[str]):
"""Process multiple prompts in batch."""
options = ClaudeAgentOptions(
max_turns=1, # Limit turns for efficiency
allowed_tools=["Read"]
)
results = []
for prompt in prompts:
texts = []
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
texts.append(block.text)
results.append(" ".join(texts))
return results
async def main():
prompts = [
"Summarize file1.py",
"Summarize file2.py",
"Summarize file3.py"
]
results = await process_batch(prompts)
for i, result in enumerate(results):
print(f"\nResult {i+1}:\n{result}")
anyio.run(main)Extract cost and timing metrics:
import anyio
from claude_agent_sdk import query, ResultMessage
async def query_with_metrics(prompt: str):
"""Query and return result with metrics."""
async for message in query(prompt=prompt):
if isinstance(message, ResultMessage):
return {
"session_id": message.session_id,
"duration_ms": message.duration_ms,
"num_turns": message.num_turns,
"cost_usd": message.total_cost_usd,
"usage": message.usage,
"is_error": message.is_error
}
return None
async def main():
metrics = await query_with_metrics("Calculate 2 + 2")
print(f"Duration: {metrics['duration_ms']}ms")
print(f"Turns: {metrics['num_turns']}")
print(f"Cost: ${metrics['cost_usd']:.4f}")
print(f"Tokens: {metrics['usage']}")
anyio.run(main)Track which tools Claude used:
import anyio
from claude_agent_sdk import query, AssistantMessage, ToolUseBlock, ClaudeAgentOptions
async def track_tool_usage(prompt: str):
"""Track tools used during query."""
tools_used = []
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash", "Grep"]
)
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
tools_used.append({
"id": block.id,
"name": block.name,
"input": block.input
})
return tools_used
async def main():
tools = await track_tool_usage("Find all Python files and count lines")
print("Tools used:")
for tool in tools:
print(f" - {tool['name']}: {tool['input']}")
anyio.run(main)Robust error handling with retries:
import anyio
from claude_agent_sdk import (
query,
ClaudeSDKError,
CLIConnectionError,
ProcessError,
CLINotFoundError
)
async def query_with_retry(prompt: str, max_retries: int = 3):
"""Query with automatic retry on transient errors."""
for attempt in range(max_retries):
try:
messages = []
async for message in query(prompt=prompt):
messages.append(message)
return messages # Success
except CLINotFoundError:
# Don't retry - fatal error
print("Error: Claude Code not installed")
raise
except (CLIConnectionError, ProcessError) as e:
if attempt < max_retries - 1:
print(f"Attempt {attempt + 1} failed: {e}")
print(f"Retrying... ({max_retries - attempt - 1} attempts left)")
await anyio.sleep(2 ** attempt) # Exponential backoff
else:
print(f"All {max_retries} attempts failed")
raise
except ClaudeSDKError:
# Don't retry other errors
raise
async def main():
try:
messages = await query_with_retry("What is 2 + 2?")
print(f"Success! Received {len(messages)} messages")
except ClaudeSDKError as e:
print(f"Final error: {e}")
anyio.run(main)Add timeout to query:
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions
async def query_with_timeout(prompt: str, timeout_seconds: int = 60):
"""Query with timeout."""
options = ClaudeAgentOptions(
max_turns=10 # Prevent infinite loops
)
with anyio.fail_after(timeout_seconds):
messages = []
async for message in query(prompt=prompt, options=options):
messages.append(message)
return messages
async def main():
try:
messages = await query_with_timeout("Complex task", timeout_seconds=30)
print(f"Completed with {len(messages)} messages")
except TimeoutError:
print("Query timed out after 30 seconds")
anyio.run(main)Analyze files with Claude:
import anyio
from pathlib import Path
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock
async def analyze_files(directory: Path):
"""Analyze files in directory."""
options = ClaudeAgentOptions(
cwd=str(directory),
allowed_tools=["Read", "Grep", "Glob"],
system_prompt="You are a code reviewer. Be concise."
)
prompt = "Analyze all Python files and identify potential issues"
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
async def main():
await analyze_files(Path("/path/to/project"))
anyio.run(main)Generate code with specific requirements:
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, TextBlock
async def generate_code(requirements: str, output_file: str):
"""Generate code and save to file."""
options = ClaudeAgentOptions(
allowed_tools=["Write"],
permission_mode="acceptEdits",
system_prompt="You are an expert programmer. Write production-quality code."
)
prompt = f"Create {output_file} with the following requirements:\n{requirements}"
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
async def main():
requirements = """
- A FastAPI web server
- Endpoint /health for health checks
- Endpoint /api/users for user management
- Include error handling
- Add logging
"""
await generate_code(requirements, "server.py")
anyio.run(main)Get structured results:
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def get_structured_output(prompt: str):
"""Get structured JSON output."""
schema = {
"type": "json_schema",
"json_schema": {
"name": "analysis_result",
"strict": True,
"schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"severity": {"type": "string", "enum": ["high", "medium", "low"]},
"description": {"type": "string"}
},
"required": ["severity", "description"],
"additionalProperties": False
}
}
},
"required": ["summary", "issues"],
"additionalProperties": False
}
}
}
options = ClaudeAgentOptions(
output_format=schema,
allowed_tools=["Read", "Grep"]
)
async for message in query(prompt=prompt, options=options):
if isinstance(message, ResultMessage) and message.structured_output:
return message.structured_output
return None
async def main():
result = await get_structured_output("Analyze security in this codebase")
if result:
print(f"Summary: {result['summary']}")
print(f"Issues found: {len(result['issues'])}")
for issue in result['issues']:
print(f" [{issue['severity']}] {issue['description']}")
anyio.run(main)Limit costs for queries:
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def query_with_budget(prompt: str, max_budget_usd: float = 0.10):
"""Query with cost budget limit."""
options = ClaudeAgentOptions(
max_budget_usd=max_budget_usd,
model="haiku" # Use cheaper model
)
async for message in query(prompt=prompt, options=options):
if isinstance(message, ResultMessage):
if message.is_error:
print(f"Query stopped: {message.result}")
if message.total_cost_usd:
print(f"Cost: ${message.total_cost_usd:.4f}")
return message
async def main():
result = await query_with_budget(
"Analyze this large codebase",
max_budget_usd=0.50
)
anyio.run(main)Use query() for:
Use ClaudeSDKClient instead for:
| Feature | query() | ClaudeSDKClient |
|---|---|---|
| Conversation style | Unidirectional | Bidirectional |
| State | Stateless | Stateful |
| Follow-up messages | No | Yes |
| Interrupt support | No | Yes |
| Dynamic config changes | No | Yes (set_permission_mode, set_model) |
| Use case | One-shot, automation | Interactive, chat |
| Complexity | Simple | More complex |
Cause: Long-running operation or infinite loop
Solution: Add max_turns limit in options or use timeout with anyio.fail_after()
Cause: Tool not in allowed_tools list
Solution: Add tool name to allowed_tools. For MCP tools use format "mcp__<server>__<tool>"
Cause: Complex operations or expensive model
Solution: Set max_budget_usd limit, use cheaper model ("haiku"), or reduce max_turns
Cause: permission_mode="default" requires interactive prompts
Solution: Use permission_mode="acceptEdits" or implement can_use_tool callback
Cause: Process terminated early, budget exceeded, or max_turns reached
Solution: Check ResultMessage.is_error, increase limits, or review stderr
Choose appropriate model:
"haiku" for simple tasks (fastest, cheapest)"sonnet" for balanced performance (default)"opus" for complex reasoning (slowest, most expensive)Optimize tool usage:
allowed_tools to only what's neededpermission_mode="acceptEdits" to avoid promptsmax_turns to prevent runaway costsBatch efficiently:
asyncio.gather()ClaudeAgentOptions across queriesMonitor costs:
ResultMessage.total_cost_usdmax_budget_usd for cost controlResultMessage.usageInstall with Tessl CLI
npx tessl i tessl/pypi-claude-agent-sdk@0.1.3