feat(observability): add Dash0 as partner observability integration (#1539)
feat(observability): add Dash0 as partner observability integration Signed-off-by: Julia Furst Morgado <52685951+juliafmorgado@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
423e7d7f5d
commit
580e6b5e00
@@ -0,0 +1,16 @@
|
||||
# Dash0 credentials — get from https://app.dash0.com → Settings → Auth Tokens
|
||||
DASH0_AUTH_TOKEN=your-dash0-auth-token
|
||||
|
||||
# OTLP ingress base URL for your region — get from Settings → Endpoints
|
||||
# US West 2: https://ingress.us-west-2.aws.dash0.com
|
||||
# EU West 1: https://ingress.eu-west-1.aws.dash0.com
|
||||
DASH0_OTLP_ENDPOINT=https://ingress.us-west-2.aws.dash0.com
|
||||
|
||||
# Dataset to route telemetry to (default works for most setups)
|
||||
DASH0_DATASET=default
|
||||
|
||||
# Service name visible in Dash0 tracing, metrics, and logs
|
||||
OTEL_SERVICE_NAME=agentcore-travel-agent
|
||||
|
||||
# AWS region
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
@@ -0,0 +1,68 @@
|
||||
# AgentCore + Dash0 observability
|
||||
|
||||
Deploy a Strands travel agent to AgentCore Runtime with traces, metrics, and logs sent to [Dash0](https://www.dash0.com/) via OTLP HTTP.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
AgentCore runtime → utils/travel_agent.py
|
||||
└── OTel SDK
|
||||
├── OTLPSpanExporter → {DASH0_OTLP_ENDPOINT}/v1/traces
|
||||
├── OTLPMetricExporter → {DASH0_OTLP_ENDPOINT}/v1/metrics
|
||||
└── OTLPLogExporter → {DASH0_OTLP_ENDPOINT}/v1/logs
|
||||
headers: Authorization: Bearer <token>, Dash0-Dataset: <dataset>
|
||||
└── Dash0 → Tracing, Metrics, Logs
|
||||
```
|
||||
|
||||
`DISABLE_ADOT_OBSERVABILITY=true` bypasses the default CloudWatch ADOT pipeline so Dash0 receives all telemetry.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10+, [uv](https://docs.astral.sh/uv/)
|
||||
- AWS credentials configured
|
||||
- [Dash0 account](https://www.dash0.com/)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
pip install bedrock-agentcore boto3 python-dotenv
|
||||
cp .env.example .env
|
||||
# Edit .env: set DASH0_AUTH_TOKEN and DASH0_OTLP_ENDPOINT for your region
|
||||
python deploy.py
|
||||
python invoke.py
|
||||
# View telemetry: https://app.dash0.com → Tracing / Metrics / Logs
|
||||
python cleanup.py
|
||||
```
|
||||
|
||||
## Dash0 Regions
|
||||
|
||||
| Region | DASH0_OTLP_ENDPOINT |
|
||||
|:-------|:--------------------|
|
||||
| US West 2 (default) | `https://ingress.us-west-2.aws.dash0.com` |
|
||||
| EU West 1 | `https://ingress.eu-west-1.aws.dash0.com` |
|
||||
|
||||
> Find your endpoint at **app.dash0.com → Settings → Endpoints**.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|:---------|:------------|:--------|
|
||||
| `DASH0_AUTH_TOKEN` | Auth token from **Settings → Auth Tokens** | _(required)_ |
|
||||
| `DASH0_OTLP_ENDPOINT` | OTLP ingress base URL for your region | `https://ingress.us-west-2.aws.dash0.com` |
|
||||
| `DASH0_DATASET` | Dataset to route telemetry to | `default` |
|
||||
| `OTEL_SERVICE_NAME` | Service name shown in Dash0 | `agentcore-travel-agent` |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|:-----|:------------|
|
||||
| `utils/travel_agent.py` | Agent with Dash0 OTel setup (traces, metrics, logs) |
|
||||
| `deploy.py` | Deploys to AgentCore Runtime with Dash0 env vars |
|
||||
| `invoke.py` | Invokes the deployed agent with sample travel prompts |
|
||||
| `cleanup.py` | Deletes all created AWS resources |
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Dash0 Documentation](https://dash0.com/docs)
|
||||
- [Dash0 Endpoints glossary](https://dash0.com/docs/dash0/miscellaneous/glossary/endpoints)
|
||||
- [AgentCore observability](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-configure.html)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Delete all resources created by deploy.py. Reads runtime_config.json."""
|
||||
|
||||
import json
|
||||
import time
|
||||
import boto3
|
||||
|
||||
with open("runtime_config.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
region = config["region"]
|
||||
runtime_id = config["runtime_id"]
|
||||
|
||||
control = boto3.client("bedrock-agentcore-control", region_name=region)
|
||||
iam = boto3.client("iam", region_name=region)
|
||||
s3 = boto3.client("s3", region_name=region)
|
||||
|
||||
# Delete endpoint
|
||||
print("Deleting endpoint 'default'...")
|
||||
try:
|
||||
control.delete_agent_runtime_endpoint(agentRuntimeId=runtime_id, name="default")
|
||||
time.sleep(5)
|
||||
except Exception as e:
|
||||
print(f" {e}")
|
||||
|
||||
# Delete runtime
|
||||
print(f"Deleting runtime {runtime_id}...")
|
||||
try:
|
||||
control.delete_agent_runtime(agentRuntimeId=runtime_id)
|
||||
except Exception as e:
|
||||
print(f" {e}")
|
||||
|
||||
# Delete IAM role
|
||||
role_name = config.get("role_name")
|
||||
if role_name:
|
||||
print(f"Deleting IAM role {role_name}...")
|
||||
try:
|
||||
for policy in iam.list_role_policies(RoleName=role_name).get("PolicyNames", []):
|
||||
iam.delete_role_policy(RoleName=role_name, PolicyName=policy)
|
||||
iam.delete_role(RoleName=role_name)
|
||||
except Exception as e:
|
||||
print(f" {e}")
|
||||
|
||||
# Delete S3 objects
|
||||
s3_bucket = config.get("s3_bucket")
|
||||
s3_prefix = config.get("s3_prefix")
|
||||
if s3_bucket and s3_prefix:
|
||||
print(f"Deleting s3://{s3_bucket}/{s3_prefix}...")
|
||||
try:
|
||||
s3.delete_object(Bucket=s3_bucket, Key=s3_prefix)
|
||||
except Exception as e:
|
||||
print(f" {e}")
|
||||
|
||||
print("Cleanup complete.")
|
||||
@@ -0,0 +1,234 @@
|
||||
"""
|
||||
Deploy the Travel Agent with Dash0 observability to AgentCore Runtime.
|
||||
|
||||
Usage:
|
||||
cp .env.example .env # fill in your Dash0 auth token
|
||||
python deploy.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import boto3
|
||||
from boto3.session import Session
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
AGENT_NAME = f"dash0_obs_{int(time.time()) % 100000}"
|
||||
PROTOCOL = "HTTP"
|
||||
PYTHON_RUNTIME = "PYTHON_3_13"
|
||||
ENTRY_POINT = "travel_agent.py"
|
||||
AGENT_FILES = ["utils/travel_agent.py"]
|
||||
|
||||
PLATFORM_ENV_VARS = {
|
||||
"DASH0_AUTH_TOKEN": os.getenv("DASH0_AUTH_TOKEN", ""),
|
||||
"DASH0_OTLP_ENDPOINT": os.getenv("DASH0_OTLP_ENDPOINT", "https://ingress.us-west-2.aws.dash0.com"),
|
||||
"DASH0_DATASET": os.getenv("DASH0_DATASET", "default"),
|
||||
"OTEL_SERVICE_NAME": os.getenv("OTEL_SERVICE_NAME", "agentcore-travel-agent"),
|
||||
"DISABLE_ADOT_OBSERVABILITY": "true",
|
||||
"BEDROCK_MODEL_ID": "global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
}
|
||||
|
||||
session = Session()
|
||||
REGION = session.region_name
|
||||
ACCOUNT_ID = session.client("sts").get_caller_identity()["Account"]
|
||||
S3_BUCKET = f"agentcore-code-{ACCOUNT_ID}-{REGION}"
|
||||
S3_PREFIX = f"{AGENT_NAME}/code.zip"
|
||||
|
||||
print(f"Region: {REGION}\nAccount: {ACCOUNT_ID}\nAgent: {AGENT_NAME}")
|
||||
|
||||
|
||||
def create_execution_role() -> str:
|
||||
iam = boto3.client("iam", region_name=REGION)
|
||||
role_name = f"agentcore-{AGENT_NAME}-role"
|
||||
trust_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
|
||||
"Action": "sts:AssumeRole",
|
||||
"Condition": {"StringEquals": {"aws:SourceAccount": ACCOUNT_ID}},
|
||||
}
|
||||
],
|
||||
}
|
||||
inline_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["logs:DescribeLogStreams", "logs:CreateLogGroup"],
|
||||
"Resource": [f"arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*"],
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["logs:DescribeLogGroups"],
|
||||
"Resource": [f"arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:*"],
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
|
||||
"Resource": [
|
||||
f"arn:aws:logs:{REGION}:{ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"
|
||||
],
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream",
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:bedrock:*::foundation-model/*",
|
||||
f"arn:aws:bedrock:{REGION}:{ACCOUNT_ID}:*",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
try:
|
||||
resp = iam.create_role(
|
||||
RoleName=role_name,
|
||||
AssumeRolePolicyDocument=json.dumps(trust_policy),
|
||||
Description=f"Execution role for {AGENT_NAME}",
|
||||
)
|
||||
role_arn = resp["Role"]["Arn"]
|
||||
print(f"\nCreated IAM role: {role_arn}")
|
||||
except iam.exceptions.EntityAlreadyExistsException:
|
||||
role_arn = f"arn:aws:iam::{ACCOUNT_ID}:role/{role_name}"
|
||||
print(f"\nIAM role exists: {role_arn}")
|
||||
iam.put_role_policy(
|
||||
RoleName=role_name,
|
||||
PolicyName=f"{AGENT_NAME}-policy",
|
||||
PolicyDocument=json.dumps(inline_policy),
|
||||
)
|
||||
time.sleep(10)
|
||||
return role_arn
|
||||
|
||||
|
||||
def build_and_upload_package():
|
||||
s3 = boto3.client("s3", region_name=REGION)
|
||||
pkg_dir, zip_file = "deployment_package", "deployment_package.zip"
|
||||
try:
|
||||
if REGION == "us-east-1":
|
||||
s3.create_bucket(Bucket=S3_BUCKET)
|
||||
else:
|
||||
s3.create_bucket(
|
||||
Bucket=S3_BUCKET,
|
||||
CreateBucketConfiguration={"LocationConstraint": REGION},
|
||||
)
|
||||
except (s3.exceptions.BucketAlreadyOwnedByYou, s3.exceptions.BucketAlreadyExists):
|
||||
pass
|
||||
if os.path.isdir(pkg_dir):
|
||||
shutil.rmtree(pkg_dir)
|
||||
if os.path.exists(zip_file):
|
||||
os.remove(zip_file)
|
||||
subprocess.run(
|
||||
[
|
||||
"uv",
|
||||
"pip",
|
||||
"install",
|
||||
"--python-platform",
|
||||
"aarch64-manylinux2014",
|
||||
"--python-version",
|
||||
"3.13",
|
||||
"--target",
|
||||
pkg_dir,
|
||||
"--only-binary",
|
||||
":all:",
|
||||
"-r",
|
||||
"requirements.txt",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(
|
||||
["zip", "-r", f"../{zip_file}", "."],
|
||||
cwd=pkg_dir,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
for src_file in AGENT_FILES:
|
||||
subprocess.run(["zip", zip_file, "-j", src_file], check=True, capture_output=True)
|
||||
s3.upload_file(zip_file, S3_BUCKET, S3_PREFIX)
|
||||
shutil.rmtree(pkg_dir)
|
||||
os.remove(zip_file)
|
||||
print(f" Package uploaded to s3://{S3_BUCKET}/{S3_PREFIX}")
|
||||
|
||||
|
||||
def create_runtime(role_arn: str) -> dict:
|
||||
control = boto3.client("bedrock-agentcore-control", region_name=REGION)
|
||||
response = control.create_agent_runtime(
|
||||
agentRuntimeName=AGENT_NAME,
|
||||
agentRuntimeArtifact={
|
||||
"codeConfiguration": {
|
||||
"code": {"s3": {"bucket": S3_BUCKET, "prefix": S3_PREFIX}},
|
||||
"runtime": PYTHON_RUNTIME,
|
||||
"entryPoint": [ENTRY_POINT],
|
||||
}
|
||||
},
|
||||
roleArn=role_arn,
|
||||
networkConfiguration={"networkMode": "PUBLIC"},
|
||||
protocolConfiguration={"serverProtocol": PROTOCOL},
|
||||
environmentVariables=PLATFORM_ENV_VARS,
|
||||
description="Travel agent with Dash0 observability",
|
||||
)
|
||||
runtime_id, runtime_arn = response["agentRuntimeId"], response["agentRuntimeArn"]
|
||||
print(f" Runtime created: {runtime_id}")
|
||||
while True:
|
||||
status_resp = control.get_agent_runtime(agentRuntimeId=runtime_id)
|
||||
status = status_resp["status"]
|
||||
print(f" Status: {status}")
|
||||
if status == "READY":
|
||||
break
|
||||
if status in ("CREATE_FAILED", "UPDATE_FAILED"):
|
||||
sys.exit(1)
|
||||
time.sleep(15)
|
||||
return {"runtime_id": runtime_id, "runtime_arn": runtime_arn}
|
||||
|
||||
|
||||
def create_endpoint(runtime_id: str):
|
||||
control = boto3.client("bedrock-agentcore-control", region_name=REGION)
|
||||
control.create_agent_runtime_endpoint(agentRuntimeId=runtime_id, name="default")
|
||||
while True:
|
||||
for ep in control.list_agent_runtime_endpoints(agentRuntimeId=runtime_id).get("runtimeEndpoints", []):
|
||||
if ep["name"] == "default":
|
||||
print(f" Status: {ep['status']}")
|
||||
if ep["status"] == "READY":
|
||||
return ep
|
||||
if ep["status"] in ("CREATE_FAILED", "UPDATE_FAILED"):
|
||||
sys.exit(1)
|
||||
time.sleep(15)
|
||||
|
||||
|
||||
def main():
|
||||
if not PLATFORM_ENV_VARS.get("DASH0_AUTH_TOKEN"):
|
||||
print("ERROR: DASH0_AUTH_TOKEN not set. Copy .env.example → .env and fill in your credentials.")
|
||||
sys.exit(1)
|
||||
print("=" * 60)
|
||||
print("Deploying Travel Agent with Dash0 Observability")
|
||||
print("=" * 60)
|
||||
role_arn = create_execution_role()
|
||||
build_and_upload_package()
|
||||
runtime = create_runtime(role_arn)
|
||||
create_endpoint(runtime["runtime_id"])
|
||||
config = {
|
||||
"agent_name": AGENT_NAME,
|
||||
"runtime_id": runtime["runtime_id"],
|
||||
"runtime_arn": runtime["runtime_arn"],
|
||||
"region": REGION,
|
||||
"role_name": f"agentcore-{AGENT_NAME}-role",
|
||||
"s3_bucket": S3_BUCKET,
|
||||
"s3_prefix": S3_PREFIX,
|
||||
}
|
||||
with open("runtime_config.json", "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
print(f"\nDeployment complete! Runtime ARN: {runtime['runtime_arn']}")
|
||||
print("Next: python invoke.py | Open app.dash0.com → Tracing")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Invoke the deployed travel agent. Reads runtime_config.json written by deploy.py."""
|
||||
|
||||
import json
|
||||
import boto3
|
||||
|
||||
with open("runtime_config.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
client = boto3.client("bedrock-agentcore", region_name=config["region"])
|
||||
|
||||
prompts = [
|
||||
"I'm planning a weekend trip to Kyoto in spring. What are the must-visit places?",
|
||||
"What are the best beaches in Thailand for a budget traveler?",
|
||||
"Suggest a 5-day itinerary for first-time visitors to Paris.",
|
||||
]
|
||||
|
||||
for prompt in prompts:
|
||||
print(f"\nPrompt: {prompt}")
|
||||
print("-" * 60)
|
||||
response = client.invoke_agent_runtime(
|
||||
agentRuntimeArn=config["runtime_arn"],
|
||||
qualifier="DEFAULT",
|
||||
payload=json.dumps({"prompt": prompt}).encode(),
|
||||
)
|
||||
body = response["response"].read().decode()
|
||||
try:
|
||||
print(json.loads(body))
|
||||
except json.JSONDecodeError:
|
||||
print(body)
|
||||
@@ -0,0 +1,8 @@
|
||||
bedrock-agentcore>=1.5.0
|
||||
boto3
|
||||
strands-agents[otel]
|
||||
strands-agents-tools
|
||||
opentelemetry-sdk
|
||||
opentelemetry-exporter-otlp-proto-http
|
||||
ddgs
|
||||
python-dotenv
|
||||
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Travel agent for AgentCore Runtime with Dash0 observability.
|
||||
|
||||
Configures OTel TracerProvider, MeterProvider, and LoggerProvider exporting
|
||||
traces, metrics, and logs to Dash0's OTLP HTTP endpoint via Bearer token auth.
|
||||
DISABLE_ADOT_OBSERVABILITY=true bypasses CloudWatch so Dash0 receives the telemetry.
|
||||
|
||||
Required env vars (set via deploy.py → create_agent_runtime environmentVariables):
|
||||
DASH0_AUTH_TOKEN — Auth token from Settings → Auth Tokens
|
||||
DASH0_OTLP_ENDPOINT — OTLP ingress base URL (default: https://ingress.us-west-2.aws.dash0.com)
|
||||
DASH0_DATASET — Dataset name (default: default)
|
||||
OTEL_SERVICE_NAME — Service name visible in Dash0
|
||||
DISABLE_ADOT_OBSERVABILITY — must be "true"
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
logging.basicConfig(level=logging.ERROR, format="[%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(os.getenv("AGENT_RUNTIME_LOG_LEVEL", "INFO").upper())
|
||||
|
||||
# ── Dash0 OTel Setup ───────────────────────────────────────────────────────────
|
||||
# Must be configured BEFORE any other OTel imports.
|
||||
|
||||
auth_token = os.environ.get("DASH0_AUTH_TOKEN", "")
|
||||
otlp_base = os.environ.get("DASH0_OTLP_ENDPOINT", "https://ingress.us-west-2.aws.dash0.com").rstrip("/")
|
||||
dataset = os.environ.get("DASH0_DATASET", "default")
|
||||
service_name = os.environ.get("OTEL_SERVICE_NAME", "agentcore-travel-agent")
|
||||
|
||||
if auth_token:
|
||||
from opentelemetry import metrics, trace
|
||||
from opentelemetry._logs import set_logger_provider
|
||||
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
|
||||
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.sdk._logs import LoggerProvider
|
||||
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
|
||||
from opentelemetry.sdk._logs import LoggingHandler
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
||||
|
||||
headers = {"Authorization": f"Bearer {auth_token}", "Dash0-Dataset": dataset}
|
||||
resource = Resource.create({"service.name": service_name})
|
||||
|
||||
# Traces
|
||||
trace_provider = TracerProvider(resource=resource)
|
||||
trace_provider.add_span_processor(
|
||||
SimpleSpanProcessor(OTLPSpanExporter(endpoint=f"{otlp_base}/v1/traces", headers=headers))
|
||||
)
|
||||
trace.set_tracer_provider(trace_provider)
|
||||
|
||||
# Metrics
|
||||
metrics.set_meter_provider(
|
||||
MeterProvider(
|
||||
resource=resource,
|
||||
metric_readers=[
|
||||
PeriodicExportingMetricReader(OTLPMetricExporter(endpoint=f"{otlp_base}/v1/metrics", headers=headers))
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# Logs
|
||||
log_provider = LoggerProvider(resource=resource)
|
||||
log_provider.add_log_record_processor(
|
||||
BatchLogRecordProcessor(OTLPLogExporter(endpoint=f"{otlp_base}/v1/logs", headers=headers))
|
||||
)
|
||||
set_logger_provider(log_provider)
|
||||
logging.getLogger().addHandler(LoggingHandler(level=logging.NOTSET, logger_provider=log_provider))
|
||||
|
||||
logger.info(
|
||||
"Dash0 OTel configured (service: %s, dataset: %s, endpoint: %s)",
|
||||
service_name,
|
||||
dataset,
|
||||
otlp_base,
|
||||
)
|
||||
else:
|
||||
logger.warning("DASH0_AUTH_TOKEN not set — telemetry will not be sent to Dash0")
|
||||
|
||||
# ── Agent ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402
|
||||
from ddgs import DDGS # noqa: E402
|
||||
from strands import Agent, tool # noqa: E402
|
||||
from strands.models import BedrockModel # noqa: E402
|
||||
|
||||
app = BedrockAgentCoreApp()
|
||||
|
||||
|
||||
@tool
|
||||
def web_search(query: str) -> str:
|
||||
"""Search the web for current travel information."""
|
||||
try:
|
||||
ddgs = DDGS()
|
||||
results = ddgs.text(query, max_results=5)
|
||||
formatted = []
|
||||
for i, r in enumerate(results, 1):
|
||||
formatted.append(
|
||||
f"{i}. {r.get('title', 'No title')}\n"
|
||||
f" {r.get('body', 'No summary')}\n"
|
||||
f" Source: {r.get('href', 'No URL')}\n"
|
||||
)
|
||||
return "\n".join(formatted) if formatted else "No results found."
|
||||
except Exception as e:
|
||||
return f"Search error: {str(e)}"
|
||||
|
||||
|
||||
def create_agent():
|
||||
model_id = os.getenv("BEDROCK_MODEL_ID", "global.anthropic.claude-haiku-4-5-20251001-v1:0")
|
||||
region = os.getenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
model = BedrockModel(model_id=model_id, region_name=region, temperature=0.0, max_tokens=1024)
|
||||
return Agent(
|
||||
model=model,
|
||||
system_prompt=(
|
||||
"You are an experienced travel agent. Use web_search for destination research "
|
||||
"and provide concise, well-sourced recommendations."
|
||||
),
|
||||
tools=[web_search],
|
||||
)
|
||||
|
||||
|
||||
@app.entrypoint
|
||||
def invoke(payload, context=None):
|
||||
user_input = payload.get("prompt", "")
|
||||
logger.info("[%s] %s", getattr(context, "session_id", "local"), user_input)
|
||||
agent = create_agent()
|
||||
response = agent(user_input)
|
||||
return response.message["content"][0]["text"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -40,6 +40,7 @@ This folder contains framework and protocol integrations that demonstrate how to
|
||||
* **[Arize](./observability/arize/)**: LLM observability and evaluation with Arize Phoenix
|
||||
* **[Braintrust](./observability/braintrust/)**: AI evaluation and observability platform integration
|
||||
* **[Datadog](./observability/datadog/)**: Infrastructure and LLM monitoring with Datadog
|
||||
* **[Dash0](./3p-observability/dash0/)**: OpenTelemetry-native observability platform with traces, metrics, and logs via OTLP HTTP
|
||||
* **[Dynatrace](./observability/dynatrace/)**: Application performance monitoring integration with travel agent example
|
||||
* **[Honeycomb](./observability/honeycomb/)**: Distributed tracing and observability with Honeycomb
|
||||
* **[Instana](./observability/instana/)**: IBM Instana application performance monitoring
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
- Cristiano Scandura (scandura)
|
||||
- palbiren
|
||||
- Gui Ruggiero (guiruggiero)
|
||||
- Julia Furst Morgado (juliafmorgado)
|
||||
- Visakh Madathil (vmmadathil)
|
||||
- JobRamos (jobdram)
|
||||
- Will Matos (wilmatos)
|
||||
|
||||
Reference in New Issue
Block a user