Skip to main content

Command Palette

Search for a command to run...

AWS Strands SDK Masterclass: Building Custom Tools

Published
14 min read
AWS Strands SDK Masterclass: Building Custom Tools
D
I'm Ayyanar Jeyakrishnan ; aka AJ. With over 21 years in IT, I'm a passionate Multi-Cloud Architect specialising in crafting scalable and efficient cloud solutions. I've successfully designed and implemented multi-cloud architectures for diverse organisations, harnessing AWS, Azure, and GCP. My track record includes delivering Machine Learning and Data Platform projects with a focus on high availability, security, and scalability. I'm a proponent of DevOps and MLOps methodologies, accelerating development and deployment. I actively engage with the tech community, sharing knowledge in sessions, conferences, and mentoring programs. Constantly learning and pursuing certifications, I provide cutting-edge solutions to drive success in the evolving cloud and AI/ML landscape.

Published: June 16, 2025

In our previous posts, we introduced the AWS Strands Agents SDK and explored different model providers. Now, we'll focus on one of the most powerful aspects of Strands: the ability to leverage custom tools that extend your agent's capabilities. Tools allow your agent to interact with external systems, access data, and perform actions beyond simple text generation.

Understanding Tools in Strands

Tools are functions that your agent can call when needed to accomplish specific tasks. They serve as the bridge between your agent's reasoning capabilities and the external world. With tools, your agent can:

  • Access and manipulate data

  • Interact with APIs and services

  • Execute code

  • Perform calculations

  • Read and write files

  • And much more

from strands import Agent

# Create an agent with the default model (Claude 3.7 Sonnet on Bedrock)
agent = Agent()

# This is equivalent to:
agent = Agent(model="us.anthropic.claude-3-7-sonnet-20250219-v1:0")

The agent can perform basic reasoning and computation tasks without any tools:

# Ask the agent a complex multi-part question involving reasoning and computation
response = agent(
"What is 1234 multiplied by 5678, what is the square root of 1444"
)

The Anatomy of a Strands Tool

At its core, a Strands tool is a Python function decorated with the @tool decorator. This decorator transforms a regular function into a tool that can be used by your agent. Let's break down the components of a tool:

from strands import tool

@tool
def my_custom_tool(param1: str, param2: int = 0) -> str:
    """Tool description that helps the agent understand when to use this tool.

    Args:
        param1: Description of the first parameter
        param2: Description of the second parameter with a default value

    Returns:
        Description of what the tool returns
    """
    # Tool implementation
    result = f"Processed {param1} with value {param2}"
    return result

Key components:

  1. Decorator: The @tool decorator marks the function as a tool

  2. Function Signature: Defines the parameters the tool accepts

  3. Type Annotations: Help the agent understand parameter and return types

  4. Docstring: Crucial for the agent to understand when and how to use the tool

  5. Implementation: The actual functionality of the tool

Creating Your First Custom Tool

Let's create a simple custom tool that fetches weather information for a given location:

from strands import Agent, tool
import requests

@tool
def get_weather(location: str) -> str:
    """Get current weather information for a location.

    Args:
        location: City name or location (e.g., 'Seattle', 'New York')

    Returns:
        Current weather information including temperature and conditions
    """
    try:
        # Using a free weather API
        api_key = "58fdbd9249a39getyourkey"  # Get from environment variable in production
        url = f"https://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"

        response = requests.get(url)
        data = response.json()

        if response.status_code == 200:
            temp = data["main"]["temp"]
            condition = data["weather"][0]["description"]
            humidity = data["main"]["humidity"]

            return f"Weather in {location}: {condition}, {temp}°C, Humidity: {humidity}%"
        else:
            return f"Error fetching weather: {data.get('message', 'Unknown error')}"
    except Exception as e:
        return f"Failed to get weather information: {str(e)}"

# Create an agent with our custom tool
agent = Agent(
    tools=[get_weather],
    system_prompt="You are a helpful assistant that can provide weather information."
)

# Ask the agent about the weather
response = agent("What's the weather like in Chennai now?")
print(response.message)

When you run this code, the agent will use the get_weather tool to fetch real-time weather data for Chennai and provide a Response like:

I'll check the current weather in Chennai for you.
Tool #1: get_weather
Currently in Chennai, it's 32.69°C (about 91°F) with overcast clouds. The humidity is quite high at 62%, making it likely feel quite warm and muggy. This type of weather is typical for Chennai, which often experiences hot and humid conditions.

Building a Financial Assistant with Custom Tools

Now, let's build something more complex: a financial assistant that can search for and analyze stocks from US markets. This example demonstrates how to integrate multiple external APIs and create a sophisticated agent with domain-specific capabilities.

Step 1: Set Up the Required Libraries

First, we need to install the necessary libraries:

And import the required modules:

from strands import Agent, tool
import requests
import json
from typing import Dict, List, Optional, Union
from functools import lru_cache
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

Step 2 : Create Tools for US Stock Market Data - search_us_stocks and get_us_stock_price

@tool
def search_us_stocks(query: str, limit: int = 5) -> List[Dict]:
    """Search for US stocks matching the query.

    Use this tool when the user is looking for US stock information.
    The search matches partial company names and ticker symbols.

    Args:
        query: Search term for company name or ticker symbol
        limit: Maximum number of results to return (default: 5)

    Returns:
        List of matching stocks with their details
    """
    try:
        api_key = os.getenv("ALPHA_VANTAGE_API_KEY")
        url = f"https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords={query}&apikey={api_key}"

        response = requests.get(url)
        data = response.json()

        if "bestMatches" not in data:
            return f"Error: Unable to find US stocks matching '{query}'. API response: {data}"

        results = []
        for match in data["bestMatches"][:limit]:
            results.append({
                "symbol": match["1. symbol"],
                "name": match["2. name"],
                "type": match["3. type"],
                "region": match["4. region"],
                "currency": match["8. currency"],
                "market": "US"
            })

        return results
    except ConnectionError:
        return "Unable to connect to Alpha Vantage API. Please check your internet connection."
    except Exception as e:
        return f"Error searching US stocks: {str(e)}"

@tool
def get_us_stock_price(ticker: str) -> Dict:
    """Get the current price and information for a US stock ticker.

    Args:
        ticker: Stock ticker symbol (e.g., 'AAPL', 'MSFT')

    Returns:
        Current stock price information and company details
    """
    try:
        api_key = os.getenv("ALPHA_VANTAGE_API_KEY")
        url = f"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={ticker}&apikey={api_key}"

        response = requests.get(url)
        quote_data = response.json()

        if "Global Quote" not in quote_data or not quote_data["Global Quote"]:
            return f"Invalid ticker symbol: {ticker}. Please provide a valid US stock symbol."

        # Get company overview for additional information
        overview_url = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={api_key}"
        overview_response = requests.get(overview_url)
        overview_data = overview_response.json()

        quote = quote_data["Global Quote"]

        result = {
            "symbol": ticker,
            "price": quote["05. price"],
            "change": quote["09. change"],
            "change_percent": quote["10. change percent"],
            "volume": quote["06. volume"],
            "market": "US"
        }

        # Add company information if available
        if "Name" in overview_data:
            result["name"] = overview_data["Name"]
            result["sector"] = overview_data["Sector"]
            result["industry"] = overview_data["Industry"]
            result["description"] = overview_data["Description"]

        return result
    except ConnectionError:
        return "Unable to connect to Alpha Vantage API. Please check your internet connection."
    except Exception as e:
        return f"Error fetching US stock price: {str(e)}"

Step 3: Create a Financial Analysis Tool

@tool
def analyze_financial_stock(ticker: str, market: str = "US", exchange: str = "NSE") -> Dict:
    """Analyze a financial stock and provide key metrics.

    Args:
        ticker: Stock ticker symbol
        market: Market to search in ("US" or "India")
        exchange: Exchange code for Indian stocks (default: 'NSE', can be 'BSE')

    Returns:
        Dictionary with financial analysis and recommendations
    """
    try:
        if market.lower() == "us":
            # For US stocks, use Alpha Vantage
            api_key = os.getenv("ALPHA_VANTAGE_API_KEY")

            # Get company overview
            overview_url = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={api_key}"
            overview_response = requests.get(overview_url)
            overview_data = overview_response.json()

            if not overview_data or "Symbol" not in overview_data:
                return f"Invalid ticker symbol: {ticker}. Please provide a valid US stock symbol."

            # Get key metrics
            analysis = {
                "symbol": ticker,
                "name": overview_data["Name"],
                "sector": overview_data["Sector"],
                "industry": overview_data["Industry"],
                "pe_ratio": overview_data["PERatio"],
                "peg_ratio": overview_data["PEGRatio"],
                "dividend_yield": overview_data["DividendYield"],
                "eps": overview_data["EPS"],
                "52_week_high": overview_data["52WeekHigh"],
                "52_week_low": overview_data["52WeekLow"],
                "market_cap": overview_data["MarketCapitalization"],
                "profit_margin": overview_data["ProfitMargin"],
                "beta": overview_data["Beta"],
                "market": "US"
            }

            return analysis
    except Exception as e:
        return f"Error analyzing financial stock: {str(e)}"

Step 6: Create the Financial Agent

# Create the agent with our financial tools
financial_agent = Agent(
    # Using Claude 3.7 Sonnet model from Bedrock
    model="us.anthropic.claude-3-7-sonnet-20250219-v1:0",

    # Add our financial tools
    tools=[
        search_us_stocks,
        get_us_stock_price,
        analyze_financial_stock
    ],

    # Customize the system prompt for financial analysis
    system_prompt="""You are a helpful financial assistant specializing in stock market analysis.

    You can search for and analyze stocks from both US markets.

    When providing stock information:
    1. Always specify which market (US or India) the stock belongs to
    2. Include key financial metrics when available
    3. Explain what the metrics mean in simple terms
    4. Provide context about the company's industry and sector

    For financial analysis:
    - PE Ratio: Explain if it's high or low compared to industry average
    - Dividend Yield: Note if it's attractive for income investors
    - Market Cap: Categorize as large, mid, or small cap
    - Recent Performance: Note significant price movements

    Always be transparent about the limitations of your analysis and remind users
    that this information should not be considered financial advice.
    """
)

Test the Financial Agent

# Ask the agent about US technology stocks
response = financial_agent("Find technology stocks in the US market with good growth potential")
print(response.message)

Response Leveraging tools

I'd be happy to help you find some technology stocks in the US market with good growth potential. Let me search for some technology-focused stocks for you.
Tool #14: search_us_stocks
Let me search for some major tech stocks that would be more suitable for individual stock analysis:
Tool #15: search_us_stocks
Let me try searching for them individually:
Tool #16: search_us_stocks
Now I'll analyze several key technology stocks to find ones with good growth potential. Let's start with Microsoft:
Tool #17: analyze_financial_stock
Let me check Apple:
Tool #18: search_us_stocks

Tool #19: analyze_financial_stock
Let's check NVIDIA:
Tool #20: search_us_stocks

Tool #21: analyze_financial_stock
Let's also check Amazon:
Tool #22: search_us_stocks

Tool #23: analyze_financial_stock
Let's check Google/Alphabet:
Tool #24: search_us_stocks

Tool #25: analyze_financial_stock
# US Technology Stocks with Growth Potential

Based on my analysis of major US technology stocks, here's a detailed comparison of stocks with good growth potential:

| **Company** | **Ticker** | **Sector/Industry** | **PE Ratio** | **PEG Ratio** | **Dividend Yield** | **Market Cap** | **Analysis** |
|-------------|------------|---------------------|--------------|---------------|-------------------|-----------------|-------------|
| Microsoft | MSFT | Technology/Prepackaged Software | 36.76 | 2.23 | 0.70% | $3.53T | **Large Cap** leader with strong cloud exposure through Azure. Higher PE ratio shows premium pricing, but steady growth in cloud and AI services justifies valuation. |
| Apple | AAPL | Technology/Electronic Computers | 30.60 | 1.78 | 0.53% | $2.93T | **Large Cap** consumer tech giant with strong ecosystem. Lower PEG ratio than others suggests better value relative to growth. Steady performance with loyal customer base. |
| NVIDIA | NVDA | Manufacturing/Semiconductors | 45.95 | 1.76 | 0.03% | $3.46T | **Large Cap** AI chip leader with exceptional profit margin (51.7%). Higher PE but strong growth prospects in AI, gaming, and data center markets. Higher beta (2.12) indicates more volatility. |
| Amazon | AMZN | Trade & Services/Retail | 34.60 | 2.35 | None | $2.25T | **Large Cap** e-commerce and cloud (AWS) leader. No dividend but reinvests heavily in growth. Higher PEG ratio suggests premium pricing relative to growth rate. |
| Alphabet | GOOGL | Technology/Computer Programming | 19.49 | 1.34 | 0.48% | $2.13T | **Large Cap** with most attractive valuation metrics. Lowest PE and PEG ratios show good value relative to growth potential. Strong position in search, cloud, and AI development. |

## Key Insights:

1. **Best Value for Growth**: Alphabet (GOOGL) has the lowest PE ratio (19.49) and PEG ratio (1.34) among these tech giants, suggesting it may offer the best value relative to its growth rate.

2. **Highest Growth Premium**: NVIDIA (NVDA) has the highest PE ratio (45.95), reflecting investors' expectations for continued exceptional growth in AI chip demand, though its PEG ratio of 1.76 is more reasonable.

3. **Profit Margin Leader**: NVIDIA stands out with an impressive 51.7% profit margin, significantly higher than peers, demonstrating exceptional operational efficiency.

4. **Dividend Income**: Microsoft offers the highest dividend yield at 0.70%, making it more attractive for income-oriented investors.

5. **Risk Assessment**: NVIDIA has the highest beta (2.12), indicating greater price volatility compared to the market, while Microsoft (1.026) and Alphabet (1.014) have betas closest to the market average.

**Reminder**: This analysis is for informational purposes only and should not be considered financial advice. Stock markets involve risk, and past performance doesn't guarantee future results. Consider consulting a financial advisor before making investment decisions based on this information.{'role': 'assistant', 'content': [{'text': "# US Technology Stocks with Growth Potential\n\nBased on my analysis of major US technology stocks, here's a detailed comparison of stocks with good growth potential:\n\n| **Company** | **Ticker** | **Sector/Industry** | **PE Ratio** | **PEG Ratio** | **Dividend Yield** | **Market Cap** | **Analysis** |\n|-------------|------------|---------------------|--------------|---------------|-------------------|-----------------|-------------|\n| Microsoft | MSFT | Technology/Prepackaged Software | 36.76 | 2.23 | 0.70% | $3.53T | **Large Cap** leader with strong cloud exposure through Azure. Higher PE ratio shows premium pricing, but steady growth in cloud and AI services justifies valuation. |\n| Apple | AAPL | Technology/Electronic Computers | 30.60 | 1.78 | 0.53% | $2.93T | **Large Cap** consumer tech giant with strong ecosystem. Lower PEG ratio than others suggests better value relative to growth. Steady performance with loyal customer base. |\n| NVIDIA | NVDA | Manufacturing/Semiconductors | 45.95 | 1.76 | 0.03% | $3.46T | **Large Cap** AI chip leader with exceptional profit margin (51.7%). Higher PE but strong growth prospects in AI, gaming, and data center markets. Higher beta (2.12) indicates more volatility. |\n| Amazon | AMZN | Trade & Services/Retail | 34.60 | 2.35 | None | $2.25T | **Large Cap** e-commerce and cloud (AWS) leader. No dividend but reinvests heavily in growth. Higher PEG ratio suggests premium pricing relative to growth rate. |\n| Alphabet | GOOGL | Technology/Computer Programming | 19.49 | 1.34 | 0.48% | $2.13T | **Large Cap** with most attractive valuation metrics. Lowest PE and PEG ratios show good value relative to growth potential. Strong position in search, cloud, and AI development. |\n\n## Key Insights:\n\n1. **Best Value for Growth**: Alphabet (GOOGL) has the lowest PE ratio (19.49) and PEG ratio (1.34) among these tech giants, suggesting it may offer the best value relative to its growth rate.\n\n2. **Highest Growth Premium**: NVIDIA (NVDA) has the highest PE ratio (45.95), reflecting investors' expectations for continued exceptional growth in AI chip demand, though its PEG ratio of 1.76 is more reasonable.\n\n3. **Profit Margin Leader**: NVIDIA stands out with an impressive 51.7% profit margin, significantly higher than peers, demonstrating exceptional operational efficiency.\n\n4. **Dividend Income**: Microsoft offers the highest dividend yield at 0.70%, making it more attractive for income-oriented investors.\n\n5. **Risk Assessment**: NVIDIA has the highest beta (2.12), indicating greater price volatility compared to the market, while Microsoft (1.026) and Alphabet (1.014) have betas closest to the market average.\n\n**Reminder**: This analysis is for informational purposes only and should not be considered financial advice. Stock markets involve risk, and past performance doesn't guarantee future results. Consider consulting a financial advisor before making investment decisions based on this information."}]}

Best Practices for Tool Design

Based on our experience building the financial agent, here are some best practices for designing effective tools:

1. Clear and Descriptive Docstrings

The docstring is how your agent understands when and how to use your tool. Make it clear and comprehensive:

@tool
def search_stocks(query: str, market: str = "US", limit: int = 5) -> List[Dict]:
    """Search for stocks matching the query in the specified market.

    Use this tool when the user is looking for stock information.
    The search matches partial company names and ticker symbols.

    Args:
        query: Search term for company name or ticker symbol
        market: Market to search in ("US" or "India", default: "US")
        limit: Maximum number of results to return (default: 5)

    Returns:
        List of matching stocks with their details
    """
    # Implementation...

2. Proper Error Handling

Tools should handle errors gracefully and return informative messages:

@tool
def get_stock_price(ticker: str, market: str = "US") -> Union[Dict, str]:
    """Get the current price and information for a stock ticker.

    Args:
        ticker: Stock ticker symbol (e.g., 'AAPL', 'RELIANCE')
        market: Market to search in ("US" or "India", default: "US")

    Returns:
        Current stock price information and company details or error message
    """
    try:
        # Implementation...

        if not is_valid_ticker(ticker):
            return f"Invalid ticker symbol: {ticker}. Please provide a valid stock symbol."

        # Fetch and return price...
    except ConnectionError:
        return "Unable to connect to financial data service. Please try again later."
    except Exception as e:
        return f"Error fetching stock price: {str(e)}"

3. Caching for Performance

Use caching for expensive API calls to improve performance:

from functools import lru_cache

@lru_cache(maxsize=100)
def cached_api_call(param1, param2):
    """Cache API calls to reduce rate limiting and improve performance"""
    # Make the actual API call
    return make_api_call(param1, param2)

@tool
def get_data(param1: str, param2: str) -> Dict:
    """Get data using cached API calls.

    Args:
        param1: First parameter
        param2: Second parameter

    Returns:
        Data from the API
    """
    return cached_api_call(param1, param2)

4. Environment Variables for Secrets

Never hardcode API keys or secrets in your tools:

import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

@tool
def api_call(param: str) -> Dict:
    """Make an API call with authentication.

    Args:
        param: Parameter for the API call

    Returns:
        API response
    """
    api_key = os.getenv("API_KEY")
    if not api_key:
        return "API key not found in environment variables"

    # Make authenticated API call
    # ...

5. Type Annotations

Always use type annotations to help the agent understand parameter and return types:

from typing import Dict, List, Union, Optional

@tool
def complex_tool(
    required_param: str,
    optional_param: Optional[int] = None,
    list_param: List[str] = []
) -> Union[Dict, str]:
    """Tool with complex parameter types.

    Args:
        required_param: A required string parameter
        optional_param: An optional integer parameter
        list_param: A list of strings

    Returns:
        Either a dictionary with results or an error message
    """
    # Implementation...

Conclusion

Custom tools are what make Strands agents truly powerful and versatile. By creating well-designed tools, you can extend your agent's capabilities to interact with virtually any system or service. In this blog post, we've explored how to create custom tools for a financial assistant that can search for and analyze stocks from US markets.

Key takeaways:

  1. Tools bridge the gap between your agent's reasoning capabilities and external systems

  2. Well-designed tools have clear docstrings, proper error handling, and focused functionality

  3. Type annotations help the agent understand how to use your tools

  4. Caching can improve performance for expensive API calls

  5. Environment variables should be used for secrets and API keys

  6. Testing API connections before using them in tools can save debugging time

In our next post, we'll explore advanced tool patterns, including tool composition, tool chaining, and dynamic tool loading.

Resources


This post is part of the AWS Strands SDK Masterclass series, where we explore building intelligent AI agents using AWS Strands Agents SDK.