Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 131 additions & 28 deletions 03-integrations/observability/dynatrace/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
# Amazon Bedrock Agent Integration with Dynatrace
# Travel Agent Deployed on Amazon Bedrock AgentCore with Dynatrace Observability

This example contains a demo of a Personal Assistant Agent built on top of [Bedrock AgentCore Agents](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html).
This example demonstrates a Travel Agent built on top of [Bedrock AgentCore](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html) with full observability through Dynatrace.

## Architecture

The following diagram illustrates the architecture of the Travel Agent deployed via Bedrock Agentcore with Dynatrace observability integration:

![Architecture Diagram](./dynatrace-agentcore.png)

The architecture consists of:
- **Travel Agent**: Built using Strands framework and deployed on Bedrock AgentCore runtime
- **Agent Tools**: web search and get_weather
- **Amazon Bedrock**: Provides the foundation model of choice
- **OpenTelemetry**: Captures traces, logs, and metrics from the agent
- **Dynatrace**: Receives and visualizes observability data via OTLP

## What is the Travel Agent?

The Travel Agent is an intelligent assistant designed to help users plan trips, check weather conditions and search for travel information. It leverages multiple tools to provide comprehensive travel assistance.

### Agent Capabilities

The Travel Agent is equipped with the following tools:

1. **Web Search** (`web_search`)
- Searches the web using DuckDuckGo to find current travel information
- Retrieves information about destinations, flights, hotels, attractions, and travel tips
- Returns formatted results with titles, URLs, and descriptions
- Parameters: `query` (search string), `max_results` (default: 5)

2. **Weather Information** (`get_weather`)
- Provides real-time weather data for any location worldwide
- Uses the Open-Meteo API for accurate weather forecasts
- Returns temperature (actual and feels-like), weather conditions, humidity, wind speed, and precipitation
- Parameters: `location` (city name or location)

### Example Use Cases

- "Give me suggestions for a 7 day trip to Italy in August based on what the weather is going to be like each day"
- "I'm planning a 5-day trip to Japan in spring. What's the weather forecast and what are the must-see cherry blossom spots?"
- "Help me plan a weekend getaway to Barcelona. Check the weather and recommend outdoor activities if it's sunny"
- "I have a 10-day vacation budget of $3000. Suggest destinations in Southeast Asia with good weather in December and calculate daily spending"
- "What is the weather like in Iceland right now? Should I visit the Blue Lagoon today or wait for better conditions?"

## Prerequisites

Expand All @@ -11,7 +51,6 @@ This example contains a demo of a Personal Assistant Agent built on top of [Bedr
- Access to the following AWS services:
- Amazon Bedrock


## Dynatrace Instrumentation

> [!TIP]
Expand All @@ -23,32 +62,49 @@ Hence, we just need to register an [OpenTelemetry SDK](https://github.com/open-t
We simplified this process, hiding all the complexity inside [dynatrace.py](./dynatrace.py).
For sending data to your Dynatrace tenant, you can configure the `OTEL_ENDPOINT` env var with your Dynatrace URL for ingesting [OTLP](https://docs.dynatrace.com/docs/shortlink/otel-getstarted-otlpexport), for example: `https://wkf10640.live.dynatrace.com/api/v2/otlp`.

The API access token will be read from your filesystem under `/etc/secrets/dynatrace_otel` or from the environment variable `DT_TOKEN`.

The API access token will be read from your filesystem under `/etc/secrets/dynatrace_otel` or from the environment variable `DT_TOKEN`.

## How to use

### Setting your AWS keys
### Setting your AWS Credentials

#### Step 1: Create AWS Access Keys

1. Sign in to the [AWS Management Console](https://console.aws.amazon.com/)
2. Navigate to **IAM** (Identity and Access Management)
3. In the left navigation pane, select **Users**
4. Select your IAM user name (or [create a new user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) if needed)
5. Select the **Security credentials** tab
6. Under **Access keys**, select **Create access key**
7. Choose **Command Line Interface (CLI)** as the use case
8. Check the confirmation box and select **Next**
9. (Optional) Add a description tag for the access key
10. Select **Create access key**
11. **Important**: Copy both the **Access key ID** and **Secret access key** immediately - you won't be able to see the secret key again

Follow the [Amazon Bedrock AgentCore documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html) to configure your AWS Role with the correct policies.
Afterwards, you can set your AWS keys in your environment variables by running the following command in your terminal:
#### Step 2: Configure AWS Credentials

Run the following command:

```bash
export AWS_ACCESS_KEY_ID==your_api_key
export AWS_SECRET_ACCESS_KEY==your_secret_key
export AWS_REGION=your_region
aws configure
```

Ensure your account has access to the model `eu.anthropic.claude-3-7-sonnet-20250219-v1:0` used in this example. Please, refer to the
[Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-permissions.html) to see how to enable access to the model.
You can change the model used by configuring the environment variable `BEDROCK_MODEL_ID`.
When prompted, enter:
- AWS Access Key ID: `your_access_key_id`
- AWS Secret Access Key: `your_secret_access_key`
- Default region name: `us-east-1` (or your preferred region)
- Default output format: (press enter and leave it default or set it per your perference)

#### Step 3: Configure IAM Permissions

Ensure your IAM user has the necessary permissions. Follow the [Amazon Bedrock AgentCore documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html) to attach the required policies.

### Setting your Dynatrace Token

Create a [Free Dynatrace Trial](https://www.dynatrace.com/signup/) for 15 days.
After a few minutes, you will get redirected to your tenant. The URL will look like `https://wkf10640.apps.dynatrace.com/`.
The value `wkf10640` is your environment id which will be needed later.
The value in your URL in place of `wkf10640` is your environment id which will be needed later.

After that, you can create an Access Token:

Expand All @@ -63,31 +119,78 @@ After that, you can create an Access Token:
6. Select **Generate token**.
7. Copy the generated token to the clipboard. Store the token in a password manager for future use.


Afterwards, you can set your Dynatrace information in your environment variables by running the following command in your terminal:

```bash
export DT_TOKEN==your_access_token
export OTEL_ENDPOINT==https://{your-environment-id}.live.dynatrace.com/api/v2/otlp
export DT_TOKEN=your_access_token
export OTEL_ENDPOINT=https://{your-environment-id}.live.dynatrace.com/api/v2/otlp
```

Replace `{your-environment-id}` with your actual Dynatrace environment ID (e.g., `wkf10640`).

### Install Dependencies

Install the required dependencies using uv:

```bash
uv sync
```

### Run the app

You can start the example with the following command: `uv run main.py`
This will create an HTTP server that listens on the port `8080` that implements the required `/invocations` endpoint for processing the agent's requirements.
Start the Travel Agent with the following command:

The Agent is now ready to be deployed. The best practice is to package code as container and push to ECR using CI/CD pipelines and IaC.
You can follow the guide
[here](https://github.com/awslabs/amazon-bedrock-agentcore-samples/blob/main/01-tutorials/01-AgentCore-runtime/01-hosting-agent/01-strands-with-bedrock-model/runtime_with_strands_and_bedrock_models.ipynb)
to have a full step-by-step tutorial.
```bash
uv run main.py
```

This will create an HTTP server that listens on port `8080` and implements the required `/invocations` endpoint for processing the agent's requests.

### Interact with the Travel Agent

You can interact with your Travel Agent using curl commands:

You can interact with your agent with the following command:
**Plan a multi-day trip with weather considerations:**
```bash
curl -X POST http://127.0.0.1:8080/invocations \
--data '{"prompt": "Give me suggestions for a 7 day trip to Italy in August based on what the weather is going to be like each day"}'
```

**Get weather-based activity recommendations:**
```bash
curl -X POST http://127.0.0.1:8080/invocations \
--data '{"prompt": "Help me plan a weekend in Barcelona. Check the weather and recommend outdoor activities if it is sunny"}'
```

**Plan a trip with budget calculations:**
```bash
curl -X POST http://127.0.0.1:8080/invocations --data '{"prompt": "What is the weather now?"}'
curl -X POST http://127.0.0.1:8080/invocations \
--data '{"prompt": "I have a 10-day vacation budget of $3000. Suggest destinations in Southeast Asia with good weather in December and calculate daily spending"}'
```

Now you have full observability of your Bedrock AgentCore Agents in Dynatrace 🚀
**Check current weather for travel decisions:**
```bash
curl -X POST http://127.0.0.1:8080/invocations \
--data '{"prompt": "What is the weather like in Iceland right now? Should I visit the Blue Lagoon today or wait for better conditions?"}'
```

**Seasonal trip planning:**
```bash
curl -X POST http://127.0.0.1:8080/invocations \
--data '{"prompt": "I am planning a 5-day trip to Japan in spring. What is the weather forecast and what are the must-see cherry blossom spots?"}'
```

### Deployment

The Agent is now ready to be deployed. The best practice is to package the code as a container and push to ECR using CI/CD pipelines and IaC.
You can follow the guide
[here](https://github.com/awslabs/amazon-bedrock-agentcore-samples/blob/main/01-tutorials/01-AgentCore-runtime/01-hosting-agent/01-strands-with-bedrock-model/runtime_with_strands_and_bedrock_models.ipynb)
for a full step-by-step tutorial.

## Observability in Dynatrace

Now you have full observability of your Bedrock AgentCore Agents in Dynatrace!

You can view detailed traces, metrics, and logs for your Travel Agent:

![Tracing](./dynatrace.png)
![Dynatrace Tracing](./dynatrace.png)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified 03-integrations/observability/dynatrace/dynatrace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions 03-integrations/observability/dynatrace/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ dependencies = [
"strands-agents-tools",
"strands-agents",
"opentelemetry-sdk",
"opentelemetry-exporter-otlp-proto-http",
"duckduckgo-search",
"requests",
]
124 changes: 115 additions & 9 deletions 03-integrations/observability/dynatrace/travel_agent.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,132 @@
import os
import requests
from strands import Agent, tool
from strands_tools import calculator # Import the calculator tool
from strands.models import BedrockModel
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from duckduckgo_search import DDGS

app = BedrockAgentCoreApp()

# Create a custom tool

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

Args:
location: City name or location (e.g., "London", "New York", "Tokyo")

Returns:
Current weather information including temperature, conditions, and wind speed
"""
try:
# First, get coordinates for the location using geocoding
geocoding_url = "https://geocoding-api.open-meteo.com/v1/search"
geocoding_params = {
"name": location,
"count": 1,
"language": "en",
"format": "json"
}

geo_response = requests.get(geocoding_url, params=geocoding_params, timeout=10)
geo_response.raise_for_status()
geo_data = geo_response.json()

if not geo_data.get("results"):
return f"Location '{location}' not found. Please provide a valid city name."

result = geo_data["results"][0]
latitude = result["latitude"]
longitude = result["longitude"]
location_name = result["name"]
country = result.get("country", "")

# Get weather data
weather_url = "https://api.open-meteo.com/v1/forecast"
weather_params = {
"latitude": latitude,
"longitude": longitude,
"current": "temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,wind_speed_10m",
"temperature_unit": "celsius",
"wind_speed_unit": "kmh"
}

weather_response = requests.get(weather_url, params=weather_params, timeout=10)
weather_response.raise_for_status()
weather_data = weather_response.json()

current = weather_data["current"]

# Weather code interpretation
weather_codes = {
0: "Clear sky",
1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
45: "Foggy", 48: "Depositing rime fog",
51: "Light drizzle", 53: "Moderate drizzle", 55: "Dense drizzle",
61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain",
71: "Slight snow", 73: "Moderate snow", 75: "Heavy snow",
77: "Snow grains",
80: "Slight rain showers", 81: "Moderate rain showers", 82: "Violent rain showers",
85: "Slight snow showers", 86: "Heavy snow showers",
95: "Thunderstorm", 96: "Thunderstorm with slight hail", 99: "Thunderstorm with heavy hail"
}

weather_description = weather_codes.get(current["weather_code"], "Unknown")

return (
f"Weather in {location_name}, {country}:\n"
f"Temperature: {current['temperature_2m']}°C (feels like {current['apparent_temperature']}°C)\n"
f"Conditions: {weather_description}\n"
f"Humidity: {current['relative_humidity_2m']}%\n"
f"Wind Speed: {current['wind_speed_10m']} km/h\n"
f"Precipitation: {current['precipitation']} mm"
)

except requests.exceptions.RequestException as e:
return f"Error fetching weather data: {str(e)}"
except Exception as e:
return f"Error processing weather request: {str(e)}"


@tool
def weather():
"""Get weather""" # Dummy implementation
return "sunny"
def web_search(query: str, max_results: int = 5):
"""
Search the web using DuckDuckGo.

Args:
query: The search query string
max_results: Maximum number of results to return (default: 5)

Returns:
A list of search results with title, link, and snippet
"""
try:
with DDGS() as ddgs:
results = list(ddgs.text(query, max_results=max_results))
if not results:
return "No results found."

formatted_results = []
for i, result in enumerate(results, 1):
formatted_results.append(
f"{i}. {result.get('title', 'No title')}\n"
f" URL: {result.get('href', 'No URL')}\n"
f" {result.get('body', 'No description')}"
)
return "\n\n".join(formatted_results)
except Exception as e:
return f"Error performing web search: {str(e)}"


model_id = os.getenv("BEDROCK_MODEL_ID", "eu.anthropic.claude-3-7-sonnet-20250219-v1:0")
model_id = os.getenv("BEDROCK_MODEL_ID", "us.anthropic.claude-3-7-sonnet-20250219-v1:0")
model = BedrockModel(
model_id=model_id,
)
agent = Agent(
model=model,
tools=[calculator, weather],
system_prompt="You're a helpful assistant. You can do simple math calculation, and tell the weather.",
tools=[get_weather, web_search],
system_prompt="You're a helpful travel assistant. You can search the web for travel information, check current weather conditions for any location, and do simple math calculations. Use web search to find current information about destinations, flights, hotels, attractions, and travel tips. Use get_weather to provide accurate, real-time weather information for travelers.",
)

@app.entrypoint
Expand All @@ -30,4 +136,4 @@ def strands_agent_bedrock(payload):
"""
user_input = payload.get("prompt")
response = agent(user_input)
return response.message["content"][0]["text"]
return response.message["content"][0]["text"]
Loading