Last chance! 50% off DataCamp Premium
Sale ends in
Anthropic recently launched the updated Claude Sonnet 4.5, Claude Agents SDK, and Imagine with Claude.
With the release of Claude Sonnet 4, developers now have access to powerful new capabilities—including native code execution, a flexible Files API, and dynamic tool use. These features open up exciting possibilities for building AI-powered applications that go beyond simple text interactions.
In this tutorial, I’ll walk you through these new features by building a Python-based agent that can solve advanced math problems right from your terminal. You’ll learn to use the newly introduced native code execution tool and the files API.
By the end, you’ll have a working agent powered by Claude Sonnet 4 to analyze problems, write and run Python code, and render visuals like the ones below.

Check out the GitHub repository for this project to see all the code for building the application.
If you're just beginning your journey into building with LLMs like Claude, this AI developer roadmap outlines essential tools, skills, and learning paths to get started.
Before we go into the tutorial, let’s look at all the recently released new features available through the Anthropic API. Feel free to skip ahead if you’re already aware of them.
I think the entire Claude 4 announcement can be summarized in this one image:

Claude 4 models (Opus and Sonnet 4) beat the competition in verified software engineering tasks by a significant margin. Anthropic achieved this feat by introducing a few important changes to how the models behave:
Additionally, as part of the Claude 4 model family announcement, Anthropic has added four new features for building AI agents: the code execution tool, Files API, MCP connector, and extended prompt caching. These tools change how developers can build AI applications.
The code execution tool allows Claude to run Python code in a secure sandbox environment. Instead of just writing code suggestions, Claude can now execute code, analyze results, and iterate on solutions.
To use the code execution tool, add it to your tools array and include the required beta header:
from anthropic import Anthropic
# Initialize client with the code execution beta header
client = Anthropic(
default_headers={
"anthropic-beta": "code-execution-2025-05-22"
}
)
# Send a request that triggers code execution
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
messages=[{
"role": "user",
"content": "Solve the quadratic equation 2x² + 5x - 3 = 0 and plot the parabola"
}],
tools=[{
"type": "code_execution_20250522",
"name": "code_execution"
}]
)
When Claude executes code, the response contains both the generated code and its execution results. Here’s how to parse the different components:
# Parse the response to extract code and results
for item in response.content:
if item.type == "server_tool_use":
# This contains the actual Python code Claude wrote
print(f"Code executed:\n{item.input['code']}")
elif item.type == "code_execution_tool_result":
# This contains the output from running the code
print(f"Output:\n{item.content['stdout']}")
if item.content.get('stderr'):
print(f"Errors:\n{item.content['stderr']}")
Sandbox limitations: The code execution environment has several built-in security restrictions. Claude runs in an isolated container with no internet access, limited computational resources (1 GB RAM, 5 GB disk space, and 1 CPU), and cannot access your local file system. The environment comes pre-installed with popular libraries like numpy, pandas, matplotlib, and scipy, but you cannot install additional packages during execution.
The Files API allows you to upload documents once and reference them across multiple conversations. This removes the need to include large documents in every API request.
First, upload your file to Anthropic’s servers:
# Upload a file once - this stores it on Anthropic's servers
uploaded_file = client.beta.files.upload(
file=open("sales_data.csv", "rb"),
purpose="user_upload"
)
# The uploaded_file.id can now be referenced in multiple conversations
Once uploaded, reference the file in your API calls using the container_upload content type:
# Reference the uploaded file in a conversation
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Create a quarterly sales report from this data"},
{"type": "container_upload", "file_id": uploaded_file.id}
]
}],
tools=[{
"type": "code_execution_20250522",
"name": "code_execution"
}]
)
When Claude creates files during code execution (like plots or processed data), you can extract and download them:
# Extract file IDs of any files Claude created during execution
def extract_created_files(response):
file_ids = []
for item in response.content:
if item.type == 'code_execution_tool_result':
content = item.content
if content.get('type') == 'code_execution_result':
# Look for file objects in the execution results
for file_obj in content.get('content', []):
if 'file_id' in file_obj:
file_ids.append(file_obj['file_id'])
return file_ids
# Download any files Claude created
created_files = extract_created_files(response)
for file_id in created_files:
file_content = client.beta.files.download(file_id)
file_content.write_to_file(f"output_{file_id}.png")
The MCP connector brings Model Context Protocol support directly to the API. MCPs allow Claude to connect to external services like databases, APIs, and productivity tools without custom integration code.
Previously, connecting to MCP servers required building client infrastructure. Now, you can simply provide a remote MCP server URL in your API request, and Claude automatically discovers available tools, manages connections, and handles authentication.
This opens up access to a growing ecosystem of MCP servers from companies like Zapier and Asana, allowing you to build agents that can read your calendar, update project management tools, or integrate with hundreds of other services.
Now that we understand the new API features, let’s build our interactive math problem solver. It will combine Claude’s code execution capabilities with file handling to create a complete mathematical assistant.
Our application will accept natural language math questions, execute Python code to solve them, generate visualizations, and save comprehensive reports.
For a structured approach to building projects like this, check out the Developing AI Applications track.
> Note: Since we will be breaking up long code blocks with explanations, the code indentation may not be entirely accurate in snippets. For this reason, we recommend opening the GitHub repo for this project in a separate tab while following along.
Before writing any code, let’s set up our development environment and dependencies.
Create a new directory for your project and set up the required files:
mkdir math-solver
cd math-solver
touch math_solver.py demo_problems.py requirements.txt README.md
Create a requirements.txt file with the necessary dependencies:
anthropic>=0.42.0
Install the dependencies:
pip install -r requirements.txt
Set your Anthropic API key as an environment variable:
export ANTHROPIC_API_KEY="your-api-key-here"
Let’s start by creating the basic structure of our MathSolver class. This class will handle all the main functionality: API communication, file management, and user interaction.
First, let’s set up the imports and basic structure:
#!/usr/bin/env python3
"""
Interactive Math Problem Solver using Claude 4 Code Execution Tool
This application allows users to ask math questions in natural language,
gets solutions using Claude's code execution capabilities, and saves
both visualizations and detailed markdown reports.
"""
import os
import json
import datetime
from pathlib import Path
from typing import List, Dict, Any
from anthropic import Anthropic
Now let’s define our main class structure:
class MathSolver:
def __init__(self, api_key: str = None):
"""Initialize the Math Solver with Anthropic client."""
# API setup and directory creation will go here
pass
def solve_problem(self, question: str) -> Dict[str, Any]:
"""Send a math question to Claude and get solution with code execution."""
pass
Here are the methods for file and report handling:
def extract_files_from_response(self, response) -> List[str]:
"""Extract file IDs from Claude's response."""
pass
def download_files(self, file_ids: List[str]) -> List[str]:
"""Download files created by code execution to local storage."""
pass
def extract_code_blocks(self, response) -> List[str]:
"""Extract all code blocks from the response."""
Pass
Finally, we add the session management and entry point:
def generate_markdown_report(self, result: Dict[str, Any], downloaded_files: List[str]) -> str:
"""Generate a comprehensive markdown report of the solution."""
pass
def run_interactive_session(self):
"""Run the main interactive session loop."""
pass
def main():
"""Main entry point for the application."""
pass
This structure gives us a clear roadmap: initialization, problem solving, file handling, report generation, and user interaction. Each method has a specific responsibility in our math-solving pipeline.
Now, let’s implement core functionality using Claude's code execution tool and streaming responses.
Let’s implement the __init__() method to handle API client setup and directory creation.
First, we handle API key management:
def __init__(self, api_key: str = None):
"""Initialize the Math Solver with Anthropic client."""
if not api_key:
api_key = os.getenv("ANTHROPIC_API_KEY")
if not api_key:
raise ValueError(
"Please set ANTHROPIC_API_KEY environment variable or provide api_key"
)
Next, we set up the Anthropic client with the required beta headers:
# Initialize client with both code execution and files API headers
self.client = Anthropic(
api_key=api_key,
default_headers={
"anthropic-beta": "code-execution-2025-05-22,files-api-2025-04-14"
},
)
Finally, we create the output directory structure:
# Create organized output directories
self.output_dir = Path("math_solver_output")
self.images_dir = self.output_dir / "images"
self.reports_dir = self.output_dir / "reports"
self.output_dir.mkdir(exist_ok=True)
self.images_dir.mkdir(exist_ok=True)
self.reports_dir.mkdir(exist_ok=True)
print(f"📁 Output directories created:")
print(f" • Images: {self.images_dir}")
print(f" • Reports: {self.reports_dir}")
This initialization does three important things:
The solve_problem() method is the heart of our application. It sends requests to Claude with streaming support.
Let’s start with the function signature and initial setup:
def solve_problem(self, question: str) -> Dict[str, Any]:
"""
Send a math question to Claude and get solution with code execution.
"""
print(f"\n🤔 Thinking about: {question}")
try:
# Use streaming to show real-time progress
with self.client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=4096,
Now we configure the message and tool specification:
messages=[
{
"role": "user",
"content": f"""Solve this math problem using code execution:
Problem: {question}
Please:
1. Solve the problem with actual Python code
2. Create visualizations using matplotlib if helpful
3. Save any plots as PNG files using plt.savefig()
4. Show your calculations step by step
5. Use descriptive filenames for saved plots
Execute Python code to solve this problem.""",
}
],
tools=[{"type": "code_execution_20250522", "name": "code_execution"}],
) as stream:
The streaming event processing handles real-time updates:
print("\n💭 Claude is working...")
# Process streaming events and show progress
for event in stream:
if event.type == "content_block_start":
if hasattr(event.content_block, "type"):
if event.content_block.type == "text":
print("\n📝 Response:", end=" ", flush=True)
elif event.content_block.type == "server_tool_use":
print(f"\n🔧 Using tool: {event.content_block.name}")
We handle different types of streaming events:
elif event.type == "content_block_delta":
if hasattr(event.delta, "text"):
print(event.delta.text, end="", flush=True)
elif event.type == "content_block_stop":
print("", flush=True) # New line
elif event.type == "message_delta":
if hasattr(event.delta, "stop_reason"):
print(f"\n✅ Completed: {event.delta.stop_reason}")
Finally, we return the result or handle errors:
# Get the final message
final_message = stream.get_final_message()
return {
"response": final_message,
"question": question,
"timestamp": datetime.datetime.now().isoformat(),
}
except Exception as e:
print(f"❌ Error solving problem: {e}")
return None
This function demonstrates several important concepts:
client.messages.stream() instead of client.messages.create() to get real-time feedback as Claude works through the problem.content_block_start: When Claude begins writing text or using a toolcontent_block_delta: As content is being generatedcontent_block_stop: When a content block is finishedmessage_delta: When the entire message is completetools array with the exact type "code_execution_20250522".When Claude creates files during code execution (like matplotlib plots), we need to extract their file IDs and download them locally.
The file extraction method navigates through the response structure:
def extract_files_from_response(self, response) -> List[str]:
"""Extract file IDs from Claude's response."""
file_ids = []
for item in response.content:
if item.type == "code_execution_tool_result":
content_item = item.content
if isinstance(content_item, dict):
if content_item.get("type") == "code_execution_result":
content_list = content_item.get("content", [])
Then we search for file IDs in the content:
for file_item in content_list:
if isinstance(file_item, dict) and "file_id" in file_item:
file_ids.append(file_item["file_id"])
return file_ids
The download process retrieves files and saves them locally:
def download_files(self, file_ids: List[str]) -> List[str]:
"""Download files created by code execution to local storage."""
downloaded_files = []
for file_id in file_ids:
try:
# Get file metadata and download content
file_metadata = self.client.beta.files.retrieve_metadata(file_id)
filename = file_metadata.filename
file_content = self.client.beta.files.download(file_id)
local_path = self.images_dir / filename
file_content.write_to_file(str(local_path))
We track downloaded files and handle errors:
downloaded_files.append(str(local_path))
print(f"✅ Downloaded: {filename}")
except Exception as e:
print(f"❌ Error downloading file {file_id}: {e}")
return downloaded_files
extract_files_from_response() method navigates through the response structure to find file IDs. Code execution results are nested in a specific format where files appear in the content array.download_files() method:Now, let’s reuse and audit Claude’s generated code and outputs.
For our markdown reports, we want to include the actual Python code that Claude executed:
def extract_code_blocks(self, response) -> List[str]:
"""Extract all code blocks from the response."""
code_blocks = []
for item in response.content:
if item.type == "server_tool_use" and item.name == "code_execution":
if (
hasattr(item, "input")
and isinstance(item.input, dict)
and "code" in item.input
):
code_blocks.append(item.input["code"])
return code_blocks
This method looks for server_tool_use items with the name "code_execution" and extracts the Python code from the input field. This gives us the actual code Claude wrote and executed.
Our report generator creates comprehensive documentation of each solution.
First, we extract content and set up the report structure:
def generate_markdown_report(self, result: Dict[str, Any], downloaded_files: List[str]) -> str:
"""Generate a comprehensive markdown report of the solution."""
response = result["response"]
question = result["question"]
timestamp = result["timestamp"]
# Extract text content and code blocks
text_content = []
code_blocks = self.extract_code_blocks(response)
for item in response.content:
if item.type == "text":
text_content.append(item.text)
Next, we generate a safe filename with a timestamp:
# Generate filename with timestamp
safe_question = "".join(
c for c in question[:50] if c.isalnum() or c in (" ", "-", "_")
).strip()
filename = f"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}_{safe_question.replace(' ', '_')}.md"
filepath = self.reports_dir / filename
We build the markdown content with a header and a problem statement:
# Create markdown content
markdown_content = f"""# Math Problem Solution Report
**Generated:** {timestamp}
**Question:** {question}
---
## Problem Statement
{question}
---
## Solution
"""
Add the solution explanation and code blocks:
# Add the main explanation
for text in text_content:
markdown_content += f"{text}\n\n"
# Add code sections
if code_blocks:
markdown_content += "---\n\n## Code Used\n\n"
for i, code in enumerate(code_blocks, 1):
markdown_content += f"### Code Block {i}\n\n```python\n{code}\n```\n\n"
Include any generated visualizations:
# Add images section
if downloaded_files:
markdown_content += "---\n\n## Generated Visualizations\n\n"
for file_path in downloaded_files:
filename = Path(file_path).name
relative_path = f"../images/{filename}"
markdown_content += f"\n\n"
Finally, add metadata and save the report:
# Add footer
markdown_content += f"""---
## Report Details
- **Generated by:** Claude 4 Math Solver
- **Model:** claude-sonnet-4-20250514
- **Timestamp:** {timestamp}
- **Files created:** {len(downloaded_files)} visualization(s)
---
*This report was automatically generated using Claude's code execution capabilities.*
"""
# Save the report
with open(filepath, "w", encoding="utf-8") as f:
f.write(markdown_content)
return str(filepath)
This report generator creates a structured markdown document containing:
The filename generation creates safe, timestamped filenames that won’t conflict with existing files.
Next, let’s focus on the user interaction loop, demo tools, and testing.
The interactive session loop handles user input and coordinates all our components.
We start with the session setup and welcome message:
def run_interactive_session(self):
"""Run the main interactive session loop."""
print("🧮 Interactive Math Problem Solver with Claude 4")
print("=" * 60)
print("Ask me any math question and I'll solve it step by step!")
print("I can handle algebra, calculus, statistics, geometry, and more.")
print("Type 'quit', 'exit', or 'q' to end the session.")
print("=" * 60)
session_count = 0
The main loop handles user input and exit conditions:
while True:
try:
# Get user input
question = input(f"\n📝 Question #{session_count + 1}: ").strip()
if question.lower() in ["quit", "exit", "q"]:
print("\n👋 Thanks for using the Math Solver! Goodbye!")
break
if not question:
print("Please enter a question.")
continue
session_count += 1
For each valid question, we process the complete workflow:
# Solve the problem
result = self.solve_problem(question)
if not result:
continue
# Extract and download any files created
file_ids = self.extract_files_from_response(result["response"])
downloaded_files = []
if file_ids:
print(f"📥 Downloading {len(file_ids)} file(s)...")
downloaded_files = self.download_files(file_ids)
We finish with report generation and user feedback:
# Generate markdown report
print(f"📝 Generating report...")
report_path = self.generate_markdown_report(result, downloaded_files)
print(f"\n✅ Solution complete!")
print(f"📄 Report saved: {report_path}")
if downloaded_files:
print(f"🖼️ Visualizations: {len(downloaded_files)} file(s) saved")
except KeyboardInterrupt:
print("\n\n👋 Session interrupted. Goodbye!")
break
except Exception as e:
print(f"❌ An error occurred: {e}")
print("Please try again with a different question.")
This loop demonstrates a complete workflow:
solve_problem() methodFinally, we need a main() function to initialize and start our application:
def main():
"""Main entry point for the application."""
try:
solver = MathSolver()
solver.run_interactive_session()
except Exception as e:
print(f"❌ Failed to initialize Math Solver: {e}")
print("Make sure you have set the ANTHROPIC_API_KEY environment variable.")
return 1
return 0
if __name__ == "__main__":
exit(main())
This simple entry point handles initialization errors (like missing API keys) and provides helpful error messages.
To help users get started, let’s create a separate script with example problems. Create demo_problems.py.
First, we define our problem categories:
# demo_problems.py
#!/usr/bin/env python3
"""
Demo problems for the Interactive Math Problem Solver
"""
# Sample problems organized by difficulty and topic
DEMO_PROBLEMS = {
"Beginner": [
"What is 15% of 240?",
"Solve for x: 3x + 7 = 22",
"Find the area of a circle with radius 5",
"Convert 45 degrees to radians",
"Calculate the hypotenuse of a right triangle with legs 3 and 4",
],
Add intermediate and advanced problem sets:
"Intermediate": [
"Solve the quadratic equation: 2x^2 + 5x - 3 = 0",
"Graph y = x^2 - 4x + 3 and find its vertex",
"Calculate mean, median, and standard deviation of [12, 15, 18, 22, 25, 28]",
"Find where f(x) = x^3 - 3x^2 + 2 crosses the x-axis",
"Solve the system of equations: 2x + 3y = 7, x - y = 1",
],
"Advanced": [
"Find the derivative of sin(x) * e^x and plot both functions",
"Calculate the integral of x^2 from 0 to 5",
"Use Newton's method to find the root of x^3 - 2x - 5 = 0",
"Perform linear regression on data points and plot the results",
"Solve the differential equation dy/dx = x*y with initial condition y(0) = 1",
],
}
Create helper functions for random selection and display:
def get_random_problem(category=None):
"""Get a random problem from a specific category or all categories."""
import random
if category and category in DEMO_PROBLEMS:
return random.choice(DEMO_PROBLEMS[category])
else:
# Pick from all problems
all_problems = []
for problems in DEMO_PROBLEMS.values():
all_problems.extend(problems)
return random.choice(all_problems)
Add a function to display all available problems:
def print_demo_problems():
"""Print all demo problems organized by category."""
print("🧮 Demo Problems for Interactive Math Solver")
print("=" * 60)
print("Here are example problems you can try, organized by difficulty:\n")
for category, problems in DEMO_PROBLEMS.items():
print(f"### {category}")
print("-" * (len(category) + 4))
for i, problem in enumerate(problems, 1):
print(f"{i:2d}. {problem}")
print()
print("💡 Tips:")
print(" • Copy and paste any question into the math solver")
print(" • All visualizations and reports will be saved automatically")
print("\n🚀 Start the solver with: python math_solver.py")
if __name__ == "__main__":
print_demo_problems()
This demo script provides:
Now let’s test our complete application! Run the math solver:
python math_solver.py
Try some example questions:
Each question will:
You can also run the demo problems script to see all available examples:
python demo_problems.py
Our math problem solver demonstrates several powerful features of Claude 4 and the new API capabilities:
This project shows how Claude 4’s new capabilities can be combined to create sophisticated applications beyond simple chat interactions. The code execution tool, combined with proper file handling and streaming, creates a powerful platform for mathematical exploration and analysis.
You can also learn to build similar agents using other platforms by taking the course on Developing AI Systems with the OpenAI API.
Building this math problem solver has demonstrated how Claude 4’s new capabilities transform what’s possible with AI applications.
Our application handles everything from basic calculations to complex mathematical visualizations, showing how these API features work together to create great user experiences. The code execution tool particularly stands out as a game-changer, allowing Claude to iterate on solutions and generate visual outputs that would have required separate tools and complex integrations in the past.
From here, you can extend this math solver with web interfaces, integrate it with external data sources through MCPs, or apply similar patterns to build domain-specific assistants for other fields, such as data analysis, scientific computing, or educational tools.
Claude 4 introduces native code execution, extended thinking with tool use, parallel tool execution, improved memory capabilities, and support for several hours of continuous task execution. These features make it particularly strong at software engineering tasks.
Yes, Claude 4 can execute Python code in a secure sandbox environment using the new code execution tool. It can run calculations, generate visualizations with matplotlib, and iterate on solutions based on execution results.
The sandbox has no internet access, limited computational resources, and cannot access your local file system. It comes with popular libraries like numpy, pandas, and matplotlib pre-installed, but you cannot install additional packages.
The Files API lets you upload documents once and reference them across conversations. When Claude creates files during code execution (like plots), you can extract their file IDs and download them locally using the API.
You need to include the beta header "anthropic-beta": "code-execution-2025-05-22" in your API requests and use the claude-sonnet-4-20250514 model. The code execution tool is specified as "code_execution_20250522" in your tools array.
Bex Tuychiev builds voice agents and LLM systems, and most of his work goes into making them reliable enough to run in production, preferably in healthcare.
Learn more about AI with these courses!
Kurs
Lärstig
Kurs

blogg
Matt Crabtree
8 min

blogg
Alex Olteanu
8 min

blogg
Tom Farnschläder
10 min
Handledning
Abid Ali Awan
9 min

Handledning
Abid Ali Awan
9 min

Handledning
Khalid Abdelaty
15 min