Python: Add BaseAgent implementation for Claude Agent SDK (#3509)

* Added ClaudeAgent implementation

* Updated streaming logic

* Small updates

* Small update

* Fixes

* Small fix

* Naming improvements

* Updated imports

* Addressed comments

* Updated package versions
This commit is contained in:
Dmytro Struk
2026-01-30 10:11:41 -08:00
committed by GitHub
Unverified
parent 0fcf075ea7
commit 8b475afe17
39 changed files with 2180 additions and 52 deletions
@@ -2,7 +2,7 @@
This folder contains examples demonstrating how to use Anthropic's Claude models with the Agent Framework.
## Examples
## Anthropic Client Examples
| File | Description |
|------|-------------|
@@ -11,14 +11,36 @@ This folder contains examples demonstrating how to use Anthropic's Claude models
| [`anthropic_skills.py`](anthropic_skills.py) | Illustrates how to use Anthropic-managed Skills with an agent, including the Code Interpreter tool and file generation and saving. |
| [`anthropic_foundry.py`](anthropic_foundry.py) | Example of using Foundry's Anthropic integration with the Agent Framework. |
## Claude Agent Examples
| File | Description |
|------|-------------|
| [`anthropic_claude_basic.py`](anthropic_claude_basic.py) | Basic usage of ClaudeAgent with streaming, non-streaming, and custom tools. |
| [`anthropic_claude_with_tools.py`](anthropic_claude_with_tools.py) | Using built-in tools (Read, Glob, Grep, etc.). |
| [`anthropic_claude_with_shell.py`](anthropic_claude_with_shell.py) | Shell command execution with interactive permission handling. |
| [`anthropic_claude_with_multiple_permissions.py`](anthropic_claude_with_multiple_permissions.py) | Combining multiple tools (Bash, Read, Write) with permission prompts. |
| [`anthropic_claude_with_url.py`](anthropic_claude_with_url.py) | Fetching and processing web content with WebFetch. |
| [`anthropic_claude_with_mcp.py`](anthropic_claude_with_mcp.py) | Local (stdio) and remote (HTTP) MCP server configuration. |
| [`anthropic_claude_with_session.py`](anthropic_claude_with_session.py) | Session management, persistence, and resumption. |
## Environment Variables
Set the following environment variables before running the examples:
### Anthropic Client
- `ANTHROPIC_API_KEY`: Your Anthropic API key (get one from [Anthropic Console](https://console.anthropic.com/))
- `ANTHROPIC_CHAT_MODEL_ID`: The Claude model to use (e.g., `claude-haiku-4-5`, `claude-sonnet-4-5-20250929`)
Or, for Foundry:
### Foundry
- `ANTHROPIC_FOUNDRY_API_KEY`: Your Foundry Anthropic API key
- `ANTHROPIC_FOUNDRY_ENDPOINT`: The endpoint URL for your Foundry Anthropic resource
- `ANTHROPIC_CHAT_MODEL_ID`: The Claude model to use in Foundry (e.g., `claude-haiku-4-5`)
### Claude Agent
- `CLAUDE_AGENT_CLI_PATH`: Path to the Claude Code CLI executable
- `CLAUDE_AGENT_MODEL`: Model to use (sonnet, opus, haiku)
- `CLAUDE_AGENT_CWD`: Working directory for Claude CLI
- `CLAUDE_AGENT_PERMISSION_MODE`: Permission mode (default, acceptEdits, plan, bypassPermissions)
- `CLAUDE_AGENT_MAX_TURNS`: Maximum number of conversation turns
- `CLAUDE_AGENT_MAX_BUDGET_USD`: Maximum budget in USD
@@ -0,0 +1,76 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent Basic Example
This sample demonstrates using ClaudeAgent for basic interactions
with Claude Agent SDK.
Prerequisites:
- Claude Code CLI must be installed and configured
- pip install agent-framework-claude
Environment variables:
- CLAUDE_AGENT_MODEL: Model to use (sonnet, opus, haiku)
- CLAUDE_AGENT_PERMISSION_MODE: Permission mode (default, acceptEdits, bypassPermissions)
"""
import asyncio
from typing import Annotated
from agent_framework import tool
from agent_framework_claude import ClaudeAgent
@tool
def get_weather(location: Annotated[str, "The city name"]) -> str:
"""Get the current weather for a location."""
return f"The weather in {location} is sunny with a high of 25C."
async def non_streaming_example() -> None:
"""Example of non-streaming response."""
print("=== Non-streaming Example ===")
agent = ClaudeAgent(
name="BasicAgent",
instructions="You are a helpful assistant. Keep responses concise.",
tools=[get_weather],
)
async with agent:
query = "What's the weather in Seattle?"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result.text}\n")
async def streaming_example() -> None:
"""Example of streaming response."""
print("=== Streaming Example ===")
agent = ClaudeAgent(
name="StreamingAgent",
instructions="You are a helpful assistant.",
tools=[get_weather],
)
async with agent:
query = "What's the weather in Paris?"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
async for chunk in agent.run_stream(query):
if chunk.text:
print(chunk.text, end="", flush=True)
print("\n")
async def main() -> None:
print("=== Claude Agent Basic Example ===\n")
await non_streaming_example()
await streaming_example()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent with MCP Servers
This sample demonstrates how to configure MCP (Model Context Protocol) servers
with ClaudeAgent. It shows both local (stdio) and remote (HTTP) server
configurations, giving the agent access to external tools and data sources.
Supported MCP server types:
- "stdio": Local process-based server
- "http": Remote HTTP server
- "sse": Remote SSE (Server-Sent Events) server
SECURITY NOTE: MCP servers can expose powerful capabilities. Only configure
servers you trust. Use permission handlers to control what actions are allowed.
"""
import asyncio
from typing import Any
from agent_framework_claude import ClaudeAgent
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
async def prompt_permission(
tool_name: str,
tool_input: dict[str, Any],
context: object,
) -> PermissionResultAllow | PermissionResultDeny:
"""Permission handler that prompts the user for approval."""
print(f"\n[Permission Request: {tool_name}]")
response = input("Approve? (y/n): ").strip().lower()
if response in ("y", "yes"):
return PermissionResultAllow()
return PermissionResultDeny(message="Denied by user")
async def main() -> None:
print("=== Claude Agent with MCP Servers ===\n")
# Configure both local and remote MCP servers
mcp_servers: dict[str, Any] = {
# Local stdio server: provides filesystem access tools
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
},
# Remote HTTP server: Microsoft Learn documentation
"microsoft-learn": {
"type": "http",
"url": "https://learn.microsoft.com/api/mcp",
},
}
agent = ClaudeAgent(
instructions="You are a helpful assistant with access to the local filesystem and Microsoft Learn.",
default_options={
"can_use_tool": prompt_permission,
"mcp_servers": mcp_servers,
},
)
async with agent:
# Query that exercises the local filesystem MCP server
query1 = "List the first three files in the current directory"
print(f"User: {query1}")
result1 = await agent.run(query1)
print(f"Agent: {result1.text}\n")
# Query that exercises the remote Microsoft Learn MCP server
query2 = "Search Microsoft Learn for 'Azure Functions Python' and summarize the top result"
print(f"User: {query2}")
result2 = await agent.run(query2)
print(f"Agent: {result2.text}\n")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,69 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent with Multiple Permissions
This sample demonstrates how to enable multiple permission types with ClaudeAgent.
By combining different tools and using a permission handler, the agent can perform
complex tasks that require multiple capabilities.
Available built-in tools:
- "Bash": Execute shell commands
- "Read": Read files from the filesystem
- "Write": Write files to the filesystem
- "Edit": Edit existing files
- "Glob": Search for files by pattern
- "Grep": Search file contents
SECURITY NOTE: Only enable permissions that are necessary for your use case.
More permissions mean more potential for unintended actions.
"""
import asyncio
from typing import Any
from agent_framework_claude import ClaudeAgent
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
async def prompt_permission(
tool_name: str,
tool_input: dict[str, Any],
context: object,
) -> PermissionResultAllow | PermissionResultDeny:
"""Permission handler that prompts the user for approval."""
print(f"\n[Permission Request: {tool_name}]")
if "command" in tool_input:
print(f" Command: {tool_input.get('command')}")
if "file_path" in tool_input:
print(f" Path: {tool_input.get('file_path')}")
if "pattern" in tool_input:
print(f" Pattern: {tool_input.get('pattern')}")
response = input("Approve? (y/n): ").strip().lower()
if response in ("y", "yes"):
return PermissionResultAllow()
return PermissionResultDeny(message="Denied by user")
async def main() -> None:
print("=== Claude Agent with Multiple Permissions ===\n")
agent = ClaudeAgent(
instructions="You are a helpful development assistant that can read, write files and run commands.",
tools=["Bash", "Read", "Write", "Glob"],
default_options={
"can_use_tool": prompt_permission,
},
)
async with agent:
query = "List the first 3 Python files, then read the first one and create a summary in summary.txt"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result.text}\n")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,145 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent with Session Management
This sample demonstrates session management with ClaudeAgent, showing
persistent conversation capabilities. Sessions are automatically persisted
by the Claude Code CLI.
"""
import asyncio
from random import randint
from typing import Annotated
from agent_framework import tool
from agent_framework_claude import ClaudeAgent
from pydantic import Field
@tool
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
async def example_with_automatic_session_creation() -> None:
"""Each agent instance creates a new session."""
print("=== Automatic Session Creation Example ===")
# First agent - first session
agent1 = ClaudeAgent(
instructions="You are a helpful weather agent.",
tools=[get_weather],
)
async with agent1:
query1 = "What's the weather like in Seattle?"
print(f"User: {query1}")
result1 = await agent1.run(query1)
print(f"Agent: {result1.text}")
# Second agent - new session, no memory of previous conversation
agent2 = ClaudeAgent(
instructions="You are a helpful weather agent.",
tools=[get_weather],
)
async with agent2:
query2 = "What was the last city I asked about?"
print(f"\nUser: {query2}")
result2 = await agent2.run(query2)
print(f"Agent: {result2.text}")
print("Note: Each agent instance creates a separate session, so the agent doesn't remember previous context.\n")
async def example_with_session_persistence() -> None:
"""Reuse session via thread object for multi-turn conversations."""
print("=== Session Persistence Example ===")
agent = ClaudeAgent(
instructions="You are a helpful weather agent.",
tools=[get_weather],
)
async with agent:
# Create a thread to maintain conversation context
thread = agent.get_new_thread()
# First query
query1 = "What's the weather like in Tokyo?"
print(f"User: {query1}")
result1 = await agent.run(query1, thread=thread)
print(f"Agent: {result1.text}")
# Second query - using same thread maintains context
query2 = "How about London?"
print(f"\nUser: {query2}")
result2 = await agent.run(query2, thread=thread)
print(f"Agent: {result2.text}")
# Third query - agent should remember both previous cities
query3 = "Which of the cities I asked about has better weather?"
print(f"\nUser: {query3}")
result3 = await agent.run(query3, thread=thread)
print(f"Agent: {result3.text}")
print("Note: The agent remembers context from previous messages in the same session.\n")
async def example_with_existing_session_id() -> None:
"""Resume session in new agent instance using service_thread_id."""
print("=== Existing Session ID Example ===")
existing_session_id = None
# First agent instance - start a conversation
agent1 = ClaudeAgent(
instructions="You are a helpful weather agent.",
tools=[get_weather],
)
async with agent1:
thread = agent1.get_new_thread()
query1 = "What's the weather in Paris?"
print(f"User: {query1}")
result1 = await agent1.run(query1, thread=thread)
print(f"Agent: {result1.text}")
# Capture the session ID for later use
existing_session_id = thread.service_thread_id
print(f"Session ID: {existing_session_id}")
if existing_session_id:
print("\n--- Continuing with the same session ID in a new agent instance ---")
# Second agent instance - resume the conversation
agent2 = ClaudeAgent(
instructions="You are a helpful weather agent.",
tools=[get_weather],
)
async with agent2:
# Create thread with existing session ID
thread = agent2.get_new_thread(service_thread_id=existing_session_id)
query2 = "What was the last city I asked about?"
print(f"User: {query2}")
result2 = await agent2.run(query2, thread=thread)
print(f"Agent: {result2.text}")
print("Note: The agent continues the conversation using the session ID.\n")
async def main() -> None:
print("=== Claude Agent Session Management Examples ===\n")
await example_with_automatic_session_creation()
await example_with_session_persistence()
await example_with_existing_session_id()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,57 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent with Shell Permissions
This sample demonstrates how to enable shell command execution with ClaudeAgent.
By providing a permission handler via `can_use_tool`, the agent can execute
shell commands to perform tasks like listing files, running scripts, or executing system commands.
SECURITY NOTE: Only enable shell permissions when you trust the agent's actions.
Shell commands have full access to your system within the permissions of the running process.
"""
import asyncio
from typing import Any
from agent_framework_claude import ClaudeAgent
from claude_agent_sdk import PermissionResultAllow, PermissionResultDeny
async def prompt_permission(
tool_name: str,
tool_input: dict[str, Any],
context: object,
) -> PermissionResultAllow | PermissionResultDeny:
"""Permission handler that prompts the user for approval."""
print(f"\n[Permission Request: {tool_name}]")
if "command" in tool_input:
print(f" Command: {tool_input.get('command')}")
response = input("Approve? (y/n): ").strip().lower()
if response in ("y", "yes"):
return PermissionResultAllow()
return PermissionResultDeny(message="Denied by user")
async def main() -> None:
print("=== Claude Agent with Shell Permissions ===\n")
agent = ClaudeAgent(
instructions="You are a helpful assistant that can execute shell commands.",
tools=["Bash"],
default_options={
"can_use_tool": prompt_permission,
},
)
async with agent:
query = "List the first 3 Python files in the current directory"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result.text}\n")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,40 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent with Built-in Tools
This sample demonstrates using ClaudeAgent with built-in tools for file operations.
Built-in tools are specified as strings in the tools parameter.
Available built-in tools:
- "Bash": Execute shell commands
- "Read": Read files from the filesystem
- "Write": Write files to the filesystem
- "Edit": Edit existing files
- "Glob": Search for files by pattern
- "Grep": Search file contents
"""
import asyncio
from agent_framework_claude import ClaudeAgent
async def main() -> None:
print("=== Claude Agent with Built-in Tools ===\n")
# Built-in tools can be specified as strings in the tools parameter
agent = ClaudeAgent(
instructions="You are a helpful assistant that can read files.",
tools=["Read", "Glob"],
)
async with agent:
query = "List the first 3 Python files in the current directory"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result.text}\n")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,38 @@
# Copyright (c) Microsoft. All rights reserved.
"""
Claude Agent with URL Fetching
This sample demonstrates how to enable URL fetching with ClaudeAgent.
By enabling the WebFetch tool, the agent can fetch and process content from web URLs.
Available web tools:
- "WebFetch": Fetch content from URLs
- "WebSearch": Search the web
SECURITY NOTE: Only enable URL permissions when you trust the agent's actions.
URL fetching allows the agent to access any URL accessible from your network.
"""
import asyncio
from agent_framework_claude import ClaudeAgent
async def main() -> None:
print("=== Claude Agent with URL Fetching ===\n")
agent = ClaudeAgent(
instructions="You are a helpful assistant that can fetch and summarize web content.",
tools=["WebFetch"],
)
async with agent:
query = "Fetch https://learn.microsoft.com/agent-framework/tutorials/quick-start and summarize its contents"
print(f"User: {query}")
result = await agent.run(query)
print(f"Agent: {result.text}\n")
if __name__ == "__main__":
asyncio.run(main())