From 211961f30c86d252c6c9f832fcf7252a045154de Mon Sep 17 00:00:00 2001 From: Agi K Thomas Date: Wed, 26 Nov 2025 14:03:09 +0530 Subject: [PATCH 1/6] AWS Bedrock Agentcore - Travel_assistant app --- .../travel_assistant/.gitignore | 66 +++ .../travel_assistant/QUICKSTART.md | 149 +++++++ .../travel_assistant/README.md | 376 ++++++++++++++++++ .../travel_assistant/claudeserver.py | 243 +++++++++++ .../travel_assistant/env.example | 49 +++ .../travel_assistant/invoke.sh | 8 + .../invoke_india_best_places.sh | 9 + .../launch_with_observability.sh | 16 + .../travel_assistant/requirements.txt | 9 + 9 files changed, 925 insertions(+) create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/.gitignore create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/README.md create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/env.example create mode 100755 aws/amazon-bedrock-agentcore/travel_assistant/invoke.sh create mode 100755 aws/amazon-bedrock-agentcore/travel_assistant/invoke_india_best_places.sh create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/launch_with_observability.sh create mode 100644 aws/amazon-bedrock-agentcore/travel_assistant/requirements.txt diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/.gitignore b/aws/amazon-bedrock-agentcore/travel_assistant/.gitignore new file mode 100644 index 0000000..a5cbf6c --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/.gitignore @@ -0,0 +1,66 @@ +# Environment variables and secrets +.env +.env.local +.env.*.local +*.key +*.pem + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Logs +*.log + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# Bedrock AgentCore +.bedrock_agentcore/ +.bedrock_agentcore.yaml + +# Screenshots (if generated) +screenshots/ +*.png +!docs/*.png + +# Temporary files +tmp/ +temp/ +*.tmp + diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md b/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md new file mode 100644 index 0000000..632badb --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md @@ -0,0 +1,149 @@ +# Quick Start Guide + +Get the Travel Assistant Agent running in 5 minutes! + +## Prerequisites Checklist + +- [ ] Python 3.11+ installed +- [ ] AWS Account with Bedrock access +- [ ] AWS CLI configured (`aws configure`) +- [ ] Bedrock AgentCore SDK installed (`pip install bedrock-agentcore`) +- [ ] (Optional) AgentCore CLI access for deployment + +## Step 1: Get API Keys + +### Tavily API Key (Required) +1. Visit [https://tavily.com](https://tavily.com) +2. Sign up and get your API key +3. Save it for the next step + +### Elastic API Key (Optional - for observability) +1. Visit [https://cloud.elastic.co](https://cloud.elastic.co) +2. Create a serverless deployment +3. Go to Management > API Keys > Create API Key +4. Save the key and OTLP endpoint + +## Step 2: Install Dependencies + +```bash +pip install -r requirements.txt +``` + +## Step 3: Configure Environment + +```bash +# Option 1: Export directly +export TAVILY_API_KEY="your-key-here" + +# Option 2: Create .env file +cp env.example .env +# Edit .env with your values +``` + +## Step 4: Launch the Agent + +Choose one of the following methods: + +**Note**: Requires access to the `agentcore` CLI tool from Bedrock AgentCore Starter Toolkit. + +1. Set AWS credentials (required before running agentcore): +```bash +export AWS_ACCESS_KEY_ID=your-aws-access-key-id +export AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key +export AWS_REGION=us-east-1 +export AWS_PROFILE=your-aws-profile # Optional +``` + +2. Configure the entrypoint: +```bash +agentcore configure --entrypoint claudeserver.py --name travel_assistant_quickstart +``` + +3. Launch with minimal config: +```bash +agentcore launch --env TAVILY_API_KEY="your-tavily-key" +``` + +4. Or launch with full observability: +```bash +agentcore launch \ + --env OTEL_EXPORTER_OTLP_ENDPOINT="https://your-id.ingest.region.cloud.elastic.co:443" \ + --env OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey your-elastic-key" \ + --env OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \ + --env OTEL_METRICS_EXPORTER="otlp" \ + --env OTEL_TRACES_EXPORTER="otlp" \ + --env OTEL_LOGS_EXPORTER="otlp" \ + --env OTEL_RESOURCE_ATTRIBUTES="service.name=travel_assistant_quickstart,service.version=1.0.0" \ + --env AGENT_OBSERVABILITY_ENABLED="true" \ + --env DISABLE_ADOT_OBSERVABILITY="true" \ + --env TAVILY_API_KEY="your-tavily-key" +``` + +## Step 5: Test It! + +**Quick test:** +```bash +agentcore invoke '{"prompt":"Best places to visit in Netherlands"}' +``` + +**Using test scripts:** + +Fast search (5-10 seconds): +```bash +./invoke.sh +``` + +Delayed search (120+ seconds): +```bash +./invoke_india_best_places.sh +``` + +## Expected Output + +You should see: +- Search results from both Tavily and DuckDuckGo +- Travel recommendations with descriptions +- Source URLs for more information + +## Step 6: Verify Observability (If Configured) + +1. Go to your Elastic Cloud deployment +2. Navigate to **Observability** > **APM** +3. Find the `travel_assistant` service +4. View traces, metrics, and logs + +## Troubleshooting + +**"TAVILY_API_KEY not set"** +- Make sure you exported the variable or passed it via `--env` + +**"Bedrock Access Denied"** +- Enable Claude 3.5 Sonnet model in AWS Bedrock console +- Check IAM permissions + +**"Timeout Error"** +- Increase timeout settings +- For India search, this is expected (120s delay) + +**No Observability Data** +- Verify OTLP endpoint is correct +- Check Elastic API key permissions +- Ensure all OTEL_* variables are set + +## Next Steps + +- Read the full [README.md](README.md) for detailed documentation +- Check out [CONTRIBUTING.md](CONTRIBUTING.md) to contribute +- Explore the code in `claudeserver.py` + +## Need Help? + +- Review the full README for detailed explanations +- Check AWS Bedrock documentation +- Visit Tavily documentation for API help +- Review Elastic documentation for observability setup + +## Success! 🎉 + +Your Travel Assistant Agent is now running and ready to help plan trips! + diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/README.md b/aws/amazon-bedrock-agentcore/travel_assistant/README.md new file mode 100644 index 0000000..951255b --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/README.md @@ -0,0 +1,376 @@ +# Travel Assistant Agent + +A multi-search travel recommendation agent built with AWS Bedrock AgentCore, Strands, and multiple search providers. This agent compares search latency and results between **Tavily** and **DuckDuckGo** search engines to provide comprehensive travel recommendations. + +## Overview + +This travel assistant agent demonstrates: + +- **Dual Search Engine Integration**: Simultaneously uses Tavily and DuckDuckGo to compare search performance and results +- **Latency Comparison**: Measures and compares response times between different search providers +- **Custom Tool with Delay**: Includes a special `country_specific_search` tool with intentional delay for testing long-running operations +- **Observability**: Full integration with Elastic Observability for monitoring and tracing +- **AWS Bedrock Integration**: Uses Claude 3.5 Sonnet via AWS Bedrock for intelligent travel recommendations + +## Features + +### Search Tools + +1. **web_search (Tavily)**: Fast, AI-optimized search using Tavily Search API +2. **web_search_ddg (DuckDuckGo)**: Traditional web search using DuckDuckGo +3. **country_specific_search**: Specialized tool for India travel searches with 120-second delay + +### Country-Specific Search Logic + +The `country_specific_search` tool includes a **120-second delay** before executing. This is designed to: + +- **Test Long-Running Operations**: Simulate real-world scenarios where certain operations take extended time +- **Observability Testing**: Validate that tracing and monitoring correctly handle long-duration spans +- **Timeout Handling**: Test the agent's behavior with slow-responding tools +- **User Experience Testing**: Demonstrate how to handle operations that require significant wait times + +The delay is intentional and serves as a testing mechanism for production-grade agent deployments where some operations may naturally take longer. + +## Architecture + +``` +┌─────────────────┐ +│ AgentCore │ +│ Runtime │ +└────────┬────────┘ + │ + v +┌─────────────────┐ ┌──────────────┐ +│ Strands Agent │────>│ Claude 3.5 │ +│ │ │ (Bedrock) │ +└────────┬────────┘ └──────────────┘ + │ + ├────> Tavily Search (Fast) + ├────> DuckDuckGo Search (Comparison) + └────> Country-Specific Search (Delayed) +``` + +## Prerequisites + +- AWS Account with Bedrock access +- Python 3.11+ +- Docker (optional, for containerized deployment) +- AgentCore CLI installed + +## Setup Instructions + +### 1. Install AgentCore SDK + +```bash +pip install bedrock-agentcore +``` + +**Note**: The `agentcore` CLI command requires the Bedrock AgentCore Starter Toolkit, which may need to be installed separately or accessed through AWS preview programs. + +### 2. Obtain Tavily API Key + +1. Visit [https://tavily.com](https://tavily.com) +2. Sign up for a free account +3. Navigate to your dashboard +4. Copy your API key from the API Keys section +5. Free tier includes 1,000 searches per month + +### 3. Set Up Elastic Observability (Optional but Recommended) + +#### Create Elastic Serverless Account + +1. Go to [https://cloud.elastic.co](https://cloud.elastic.co) +2. Click "Start free trial" or "Sign up" +3. Choose "Serverless" deployment option +4. Select your cloud provider and region +5. Create your deployment (usually takes 1-2 minutes) + +#### Obtain Elastic API Key + +1. Once your Elastic deployment is ready, navigate to your deployment +2. Go to **Management** > **Stack Management** > **API Keys** +3. Click **"Create API Key"** +4. Give it a name (e.g., "AgentCore Observability") +5. Set appropriate privileges or use default +6. Click **Create API Key** +7. **Copy and save** the API key immediately (you won't be able to see it again) + +#### Get OTLP Endpoint + +1. In your Elastic deployment, go to **Observability** > **APM** +2. Click **"Add data"** +3. Select **"OpenTelemetry"** +4. Copy the **OTLP Endpoint URL** displayed (format: `https://.ingest..cloud.elastic.co:443`) + +### 4. Configure AWS Credentials + +Ensure your AWS credentials are configured. You can use AWS CLI configuration: + +```bash +aws configure +``` + +Or set environment variables directly (required for agentcore CLI): + +```bash +export AWS_ACCESS_KEY_ID=your-aws-access-key-id +export AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key +export AWS_REGION=us-east-1 +export AWS_PROFILE=your-aws-profile # Optional, if using AWS profiles +``` + +**Note**: The agentcore CLI requires these environment variables to be set before running configuration and deployment commands. + +### 5. Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### 6. Configure AgentCore (CLI Method) + +**If you have access to the `agentcore` CLI**, first set your AWS credentials: + +```bash +# Set AWS credentials for AgentCore +export AWS_ACCESS_KEY_ID=your-aws-access-key-id +export AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key +export AWS_REGION=us-east-1 +export AWS_PROFILE=your-aws-profile # Optional, if using AWS profiles +``` + +Then configure the entrypoint: + +```bash +agentcore configure --entrypoint claudeserver.py --name travel_assistant_quickstart +``` + +This command: +- Sets `claudeserver.py` as the agent entrypoint +- Names the agent `travel_assistant_quickstart` +- Configures the runtime environment +- Creates `.bedrock_agentcore.yaml` configuration file (excluded from git) + +## Configuration + +### Environment Variables + +Create a `.env` file or export the following: + +```bash +# Required +export TAVILY_API_KEY="your-tavily-api-key-here" + +# Optional - AWS Configuration +export AWS_DEFAULT_REGION="us-east-1" +export BEDROCK_MODEL_ID="us.anthropic.claude-3-5-sonnet-20240620-v1:0" + +# Optional - Observability (Elastic) +export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-deployment-id.ingest.region.cloud.elastic.co:443" +export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey your-elastic-api-key-here" +export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" +export OTEL_METRICS_EXPORTER="otlp" +export OTEL_TRACES_EXPORTER="otlp" +export OTEL_LOGS_EXPORTER="otlp" +export OTEL_RESOURCE_ATTRIBUTES="service.name=travel_assistant,service.version=1.0.0,deployment.environment=production" +export AGENT_OBSERVABILITY_ENABLED="true" +export DISABLE_ADOT_OBSERVABILITY="true" + +# Optional - Logging +export AGENT_RUNTIME_LOG_LEVEL="INFO" +``` + +## Launching the Agent + +**Important**: Make sure you've run `agentcore configure --entrypoint claudeserver.py --name travel_assistant_quickstart` before launching (see step 6 above). + +### Basic Launch (Without Observability) + +```bash +agentcore launch --env TAVILY_API_KEY="your-tavily-api-key" +``` + +### Full Launch (With Elastic Observability) + +```bash +agentcore launch \ + --env OTEL_EXPORTER_OTLP_ENDPOINT="https://.ingest..cloud.elastic.co:443" \ + --env OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey " \ + --env OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \ + --env OTEL_METRICS_EXPORTER="otlp" \ + --env OTEL_TRACES_EXPORTER="otlp" \ + --env OTEL_LOGS_EXPORTER="otlp" \ + --env OTEL_RESOURCE_ATTRIBUTES="service.name=travel_assistant_quickstart,service.version=1.0.0,deployment.environment=production" \ + --env AGENT_OBSERVABILITY_ENABLED="true" \ + --env DISABLE_ADOT_OBSERVABILITY="true" \ + --env TAVILY_API_KEY="" +``` + +**Note**: Replace placeholders (`<...>`) with your actual credentials. + +## Testing the Agent + +### Regular Search (Netherlands Example) + +This will use both Tavily and DuckDuckGo search engines: + +```bash +./invoke.sh +``` + +Or manually: + +```bash +START=$(date +%s) +agentcore invoke '{"prompt":"Best places to visit in Netherlands"}' +END=$(date +%s) +echo "Time taken: $((END - START)) seconds" +``` + +### Delayed Search (India Example) + +This triggers the `country_specific_search` tool with 120-second delay: + +```bash +./invoke_india_best_places.sh +``` + +Or manually: + +```bash +START=$(date +%s) +agentcore invoke '{"prompt":"Best places to visit in India"}' +END=$(date +%s) +echo "Time taken: $((END - START)) seconds" +``` + +**Expected behavior**: The India search will take approximately 120+ seconds due to the intentional delay in the `country_specific_search` tool. + +## Comparing Search Latency + +The agent is designed to compare latency between: + +1. **Tavily Search** (`web_search`): Typically faster, optimized for AI applications +2. **DuckDuckGo Search** (`web_search_ddg`): Traditional web search, may vary in speed + +The agent will call both search engines for most queries, allowing you to observe: +- Response time differences +- Result quality differences +- API reliability + +You can monitor these metrics in Elastic Observability if configured. + +## Project Structure + +``` +travel_assistant/ +├── claudeserver.py # Main agent implementation +├── requirements.txt # Python dependencies +├── Dockerfile # Container configuration +├── .dockerignore # Docker ignore patterns +├── .gitignore # Git ignore patterns +├── LICENSE # MIT License +├── README.md # This file +├── QUICKSTART.md # 5-minute setup guide +├── CONTRIBUTING.md # Contribution guidelines +├── env.example # Environment template +├── invoke.sh # Test script (Netherlands) +├── invoke_india_best_places.sh # Test script (India with delay) +└── launch_with_observability.sh # Full launch script with observability +``` + +## Key Components + +### claudeserver.py + +- **Tools**: Three search tools (Tavily, DuckDuckGo, Country-specific) +- **Agent**: Strands Agent with Claude 3.5 Sonnet +- **Telemetry**: OpenTelemetry integration for observability +- **Entrypoint**: BedrockAgentCore app entrypoint + + +## Observability + +When Elastic Observability is configured, you can monitor: + +- **Traces**: Full execution traces including tool calls +- **Metrics**: Performance metrics, latency distributions +- **Logs**: Structured logs from the agent +- **Spans**: Individual operation timing (search calls, LLM calls, etc.) + +Access your Elastic deployment to view: +- APM (Application Performance Monitoring) +- Service maps +- Trace timelines +- Error tracking + +## Docker Deployment + +### Build the Image + +```bash +docker build -t travel-assistant . +``` + +### Run the Container + +```bash +docker run -p 8080:8080 \ + -e TAVILY_API_KEY="your-tavily-api-key" \ + -e AWS_ACCESS_KEY_ID="your-aws-key" \ + -e AWS_SECRET_ACCESS_KEY="your-aws-secret" \ + -e AWS_DEFAULT_REGION="us-east-1" \ + travel-assistant +``` + +## Troubleshooting + +### Common Issues + +1. **agentcore: command not found** + - Install the Bedrock AgentCore Starter Toolkit: `pip install bedrock-agentcore-starter-toolkit` + - Verify installation: `agentcore --help` + +2. **Tavily API Key Error** + - Ensure `TAVILY_API_KEY` is set correctly + - Verify your API key is active at tavily.com + +3. **AWS Bedrock Access Denied** + - Enable Bedrock model access in AWS Console + - Verify IAM permissions for Bedrock + +4. **Observability Not Working** + - Check Elastic OTLP endpoint is accessible + - Verify API key has correct permissions + - Ensure OTEL environment variables are set + +5. **Timeout on India Search** + - This is expected! The tool has a 120-second delay + - Ensure your timeout settings are >= 130 seconds + + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Contributing + +Contributions are welcome! Please ensure: +- Code follows existing style +- Tests pass +- Documentation is updated +- No sensitive credentials in commits + +## Security Notes + +- Never commit API keys or credentials +- Use environment variables for sensitive data +- Rotate API keys regularly +- Use AWS Secrets Manager for production deployments + +## Support + +For issues or questions: +- AWS Bedrock AgentCore: [AWS Documentation](https://docs.aws.amazon.com/bedrock/) +- Tavily API: [Tavily Documentation](https://docs.tavily.com/) +- Elastic Observability: [Elastic Documentation](https://www.elastic.co/guide/) diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py b/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py new file mode 100644 index 0000000..a75d776 --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py @@ -0,0 +1,243 @@ +import os +import logging +import time +from bedrock_agentcore.runtime import BedrockAgentCoreApp +from strands import Agent, tool +from strands.models import BedrockModel +from strands.telemetry import StrandsTelemetry +from tavily import TavilyClient +from duckduckgo_search import DDGS + +# Optional OpenTelemetry imports for custom resource configuration +try: + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry import trace as trace_api + from opentelemetry import propagate + from opentelemetry.baggage.propagation import W3CBaggagePropagator + from opentelemetry.propagators.composite import CompositePropagator + from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + OTEL_AVAILABLE = True +except ImportError: + OTEL_AVAILABLE = False + +logging.basicConfig(level=logging.ERROR, format="[%(levelname)s] %(message)s") +logger = logging.getLogger(__name__) +logger.setLevel(os.getenv("AGENT_RUNTIME_LOG_LEVEL", "INFO").upper()) + + + + +@tool +def web_search(query: str) -> str: + """ + Search the web for information using Tavily Search API. + + Args: + query: The search query + + Returns: + A string containing the search results + """ + try: + # Get Tavily API key from environment variable + api_key = os.getenv("TAVILY_API_KEY") + if not api_key: + return "Error: TAVILY_API_KEY environment variable not set. Please sign up at https://tavily.com to get your API key." + + tavily_client = TavilyClient(api_key=api_key) + response = tavily_client.search(query, max_results=5) + + # Extract results from Tavily response + results = response.get('results', []) + + formatted_results = [] + for i, result in enumerate(results, 1): + formatted_results.append( + f"{i}. {result.get('title', 'No title')}\n" + f" {result.get('content', 'No summary')}\n" + f" Source: {result.get('url', 'No URL')}\n" + ) + + return "\n".join(formatted_results) if formatted_results else "No results found." + + except Exception as e: + return f"Error searching the web: {str(e)}" + +@tool +def web_search_ddg(query: str) -> str: + """ + Search the web for information using DuckDuckGo Search. + + Args: + query: The search query + + Returns: + A string containing the search results + """ + try: + # Initialize DuckDuckGo search client + ddgs = DDGS() + + # Perform search with max_results=5 + results = list(ddgs.text(query, max_results=5)) + + formatted_results = [] + for i, result in enumerate(results, 1): + formatted_results.append( + f"{i}. {result.get('title', 'No title')}\n" + f" {result.get('body', 'No summary')}\n" + f" Source: {result.get('href', 'No URL')}\n" + ) + + return "\n".join(formatted_results) if formatted_results else "No results found." + + except Exception as e: + return f"Error searching the web with DuckDuckGo: {str(e)}" + +@tool +def country_specific_search(query: str) -> str: + """ + Search for best places to visit in India with a 120-second delay before performing the search. + This tool waits for 2 minutes before executing a Tavily search about India travel destinations. + + Args: + query: The search query (should be about best places to visit in India) + + Returns: + A string containing the search results after the delay + """ + try: + logger.info("Delayed India search initiated. Waiting 120 seconds before performing search...") + + # Wait for 120 seconds + time.sleep(120) + + logger.info("Wait complete. Performing Tavily search for India destinations...") + + # Get Tavily API key from environment variable + api_key = os.getenv("TAVILY_API_KEY") + if not api_key: + return "Error: TAVILY_API_KEY environment variable not set. Please sign up at https://tavily.com to get your API key." + + # Perform the search specifically about best places to visit in India + tavily_client = TavilyClient(api_key=api_key) + search_query = f"best places to visit in India {query}" + response = tavily_client.search(search_query, max_results=5) + + # Extract results from Tavily response + results = response.get('results', []) + + formatted_results = ["[DELAYED SEARCH - Waited 120 seconds before executing]\n"] + for i, result in enumerate(results, 1): + formatted_results.append( + f"{i}. {result.get('title', 'No title')}\n" + f" {result.get('content', 'No summary')}\n" + f" Source: {result.get('url', 'No URL')}\n" + ) + + return "\n".join(formatted_results) if len(formatted_results) > 1 else "No results found after delayed search." + + except Exception as e: + return f"Error in delayed India search: {str(e)}" + +# Function to initialize Bedrock model +def get_bedrock_model(): + region = os.getenv("AWS_DEFAULT_REGION", "us-east-1") + model_id = os.getenv("BEDROCK_MODEL_ID", "us.anthropic.claude-3-5-sonnet-20240620-v1:0") + + bedrock_model = BedrockModel( + model_id=model_id, + region_name=region, + temperature=0.0, + max_tokens=1024 + ) + return bedrock_model + +# Initialize the Bedrock model +bedrock_model = get_bedrock_model() + +# Define the agent's system prompt +system_prompt = """You are an experienced travel agent specializing in personalized travel recommendations +with access to real-time web information from multiple search engines. Your role is to find dream destinations +matching user preferences by using BOTH available search tools to gather comprehensive information. + +IMPORTANT: When responding to queries: +1. Use BOTH web_search (Tavily) and web_search_ddg (DuckDuckGo) tools for each query +2. Clearly identify results from each search engine in your response +3. Format your response to show: + - Results from Tavily Search (use web_search) + - Results from DuckDuckGo Search (use web_search_ddg) +4. SPECIAL CASE: If someone asks specifically about "best places to visit in India", use the + country_specific_search tool which will wait 120 seconds before performing a specialized Tavily search +5. Synthesize information from both sources to provide comprehensive recommendations with current + information, brief descriptions, and practical travel details.""" + +app = BedrockAgentCoreApp() + +def initialize_agent(): + """Initialize the agent with proper telemetry configuration.""" + + if OTEL_AVAILABLE: + try: + # Create a custom resource that respects OTEL_RESOURCE_ATTRIBUTES from environment + # This will automatically merge env vars with any explicit attributes + custom_resource = Resource.create( + attributes={ + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.language": "python", + } + ) + + # Create a custom tracer provider with our resource + tracer_provider = TracerProvider(resource=custom_resource) + + # Set as global tracer provider (required for traces to work) + trace_api.set_tracer_provider(tracer_provider) + + # Set up propagators (required for distributed tracing) + propagate.set_global_textmap( + CompositePropagator( + [ + W3CBaggagePropagator(), + TraceContextTextMapPropagator(), + ] + ) + ) + + # Initialize Strands telemetry with the custom tracer provider + strands_telemetry = StrandsTelemetry(tracer_provider=tracer_provider) + strands_telemetry.setup_otlp_exporter() + + logger.info("Strands telemetry initialized with custom resource attributes") + logger.info(f"Resource attributes: {custom_resource.attributes}") + except Exception as e: + logger.warning("Telemetry setup failed: %s", str(e)) + else: + logger.warning("Telemetry setup skipped - OpenTelemetry packages not available") + + # Create and cache the agent + agent = Agent( + model=bedrock_model, + system_prompt=system_prompt, + tools=[web_search, web_search_ddg, country_specific_search] + ) + + return agent + +@app.entrypoint +def strands_agent_bedrock(payload, context=None): + """ + Invoke the agent with a payload + """ + user_input = payload.get("prompt") + logger.info("[%s] User input: %s", context.session_id, user_input) + + # Initialize agent with proper configuration + agent = initialize_agent() + + response = agent(user_input) + return response.message['content'][0]['text'] + +if __name__ == "__main__": + app.run() \ No newline at end of file diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/env.example b/aws/amazon-bedrock-agentcore/travel_assistant/env.example new file mode 100644 index 0000000..c2014b3 --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/env.example @@ -0,0 +1,49 @@ +# =========================================== +# Travel Assistant Agent Configuration +# =========================================== +# Copy this file to .env and fill in your actual values + +# ============================================ +# REQUIRED: Tavily Search API Key +# ============================================ +# Get your API key from: https://tavily.com +TAVILY_API_KEY=your-tavily-api-key-here + +# ============================================ +# AWS Configuration +# ============================================ +AWS_DEFAULT_REGION=us-east-1 +AWS_ACCESS_KEY_ID=your-aws-access-key +AWS_SECRET_ACCESS_KEY=your-aws-secret-key +BEDROCK_MODEL_ID=us.anthropic.claude-3-5-sonnet-20240620-v1:0 + +# ============================================ +# Elastic Observability (Optional) +# ============================================ +# Get these from your Elastic Cloud deployment +# Documentation: https://cloud.elastic.co + +# OTLP Endpoint - Replace with your deployment ID and region +OTEL_EXPORTER_OTLP_ENDPOINT=https://your-deployment-id.ingest.your-region.cloud.elastic.co:443 + +# Elastic API Key - Replace with your actual API key +OTEL_EXPORTER_OTLP_HEADERS=Authorization=ApiKey your-elastic-api-key-here + +# OpenTelemetry Configuration +OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf +OTEL_METRICS_EXPORTER=otlp +OTEL_TRACES_EXPORTER=otlp +OTEL_LOGS_EXPORTER=otlp + +# Resource Attributes - Customize as needed +OTEL_RESOURCE_ATTRIBUTES=service.name=travel_assistant,service.version=1.0.0,deployment.environment=production + +# AgentCore Observability Flags +AGENT_OBSERVABILITY_ENABLED=true +DISABLE_ADOT_OBSERVABILITY=true + +# ============================================ +# Agent Configuration +# ============================================ +AGENT_RUNTIME_LOG_LEVEL=INFO + diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/invoke.sh b/aws/amazon-bedrock-agentcore/travel_assistant/invoke.sh new file mode 100755 index 0000000..c8a1a98 --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/invoke.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# Test script for regular search (no delay) +# This will use both Tavily and DuckDuckGo search engines + +START=$(date +%s) +agentcore invoke '{"prompt":"Best places to visit in Netherlands"}' +END=$(date +%s) +echo "Time taken: $((END - START)) seconds" diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/invoke_india_best_places.sh b/aws/amazon-bedrock-agentcore/travel_assistant/invoke_india_best_places.sh new file mode 100755 index 0000000..5710371 --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/invoke_india_best_places.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Test script for country-specific search with 120-second delay +# This triggers the special India search tool + +START=$(date +%s) +agentcore invoke '{"prompt":"Best places to visit in India"}' +END=$(date +%s) +echo "Time taken: $((END - START)) seconds" +echo "Note: Expected time is 120+ seconds due to intentional delay" diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/launch_with_observability.sh b/aws/amazon-bedrock-agentcore/travel_assistant/launch_with_observability.sh new file mode 100644 index 0000000..4a60a62 --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/launch_with_observability.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Launch script with full observability configuration +# Make sure to replace placeholder values with your actual credentials + +agentcore launch \ + --env OTEL_EXPORTER_OTLP_ENDPOINT="https://your-deployment-id.ingest.your-region.cloud.elastic.co:443" \ + --env OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey your-elastic-api-key" \ + --env OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \ + --env OTEL_METRICS_EXPORTER="otlp" \ + --env OTEL_TRACES_EXPORTER="otlp" \ + --env OTEL_LOGS_EXPORTER="otlp" \ + --env OTEL_RESOURCE_ATTRIBUTES="service.name=travel_assistant_quickstart,service.version=1.0.0,deployment.environment=production" \ + --env AGENT_OBSERVABILITY_ENABLED="true" \ + --env DISABLE_ADOT_OBSERVABILITY="true" \ + --env TAVILY_API_KEY="your-tavily-api-key" + diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/requirements.txt b/aws/amazon-bedrock-agentcore/travel_assistant/requirements.txt new file mode 100644 index 0000000..c7fb0db --- /dev/null +++ b/aws/amazon-bedrock-agentcore/travel_assistant/requirements.txt @@ -0,0 +1,9 @@ +bedrock-agentcore +bedrock-agentcore-starter-toolkit +strands-agents==1.17.0 +strands-agents-tools +tavily-python +duckduckgo-search +opentelemetry-api +opentelemetry-sdk +opentelemetry-exporter-otlp-proto-http \ No newline at end of file From e77d93b2a9100fd4767a2bebc624bc745209f957 Mon Sep 17 00:00:00 2001 From: Agi K Thomas Date: Wed, 26 Nov 2025 14:17:58 +0530 Subject: [PATCH 2/6] Removed unused instructions --- .../travel_assistant/README.md | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/README.md b/aws/amazon-bedrock-agentcore/travel_assistant/README.md index 951255b..46eff18 100644 --- a/aws/amazon-bedrock-agentcore/travel_assistant/README.md +++ b/aws/amazon-bedrock-agentcore/travel_assistant/README.md @@ -54,7 +54,6 @@ The delay is intentional and serves as a testing mechanism for production-grade - AWS Account with Bedrock access - Python 3.11+ -- Docker (optional, for containerized deployment) - AgentCore CLI installed ## Setup Instructions @@ -266,8 +265,6 @@ You can monitor these metrics in Elastic Observability if configured. travel_assistant/ ├── claudeserver.py # Main agent implementation ├── requirements.txt # Python dependencies -├── Dockerfile # Container configuration -├── .dockerignore # Docker ignore patterns ├── .gitignore # Git ignore patterns ├── LICENSE # MIT License ├── README.md # This file @@ -304,24 +301,6 @@ Access your Elastic deployment to view: - Trace timelines - Error tracking -## Docker Deployment - -### Build the Image - -```bash -docker build -t travel-assistant . -``` - -### Run the Container - -```bash -docker run -p 8080:8080 \ - -e TAVILY_API_KEY="your-tavily-api-key" \ - -e AWS_ACCESS_KEY_ID="your-aws-key" \ - -e AWS_SECRET_ACCESS_KEY="your-aws-secret" \ - -e AWS_DEFAULT_REGION="us-east-1" \ - travel-assistant -``` ## Troubleshooting From 4ed429553b602620c56f3eb211f104b9b70d17bd Mon Sep 17 00:00:00 2001 From: Agi K Thomas Date: Wed, 26 Nov 2025 14:23:46 +0530 Subject: [PATCH 3/6] Updated README --- .../travel_assistant/README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/README.md b/aws/amazon-bedrock-agentcore/travel_assistant/README.md index 46eff18..4ca18ee 100644 --- a/aws/amazon-bedrock-agentcore/travel_assistant/README.md +++ b/aws/amazon-bedrock-agentcore/travel_assistant/README.md @@ -266,10 +266,8 @@ travel_assistant/ ├── claudeserver.py # Main agent implementation ├── requirements.txt # Python dependencies ├── .gitignore # Git ignore patterns -├── LICENSE # MIT License ├── README.md # This file ├── QUICKSTART.md # 5-minute setup guide -├── CONTRIBUTING.md # Contribution guidelines ├── env.example # Environment template ├── invoke.sh # Test script (Netherlands) ├── invoke_india_best_places.sh # Test script (India with delay) @@ -328,18 +326,6 @@ Access your Elastic deployment to view: - Ensure your timeout settings are >= 130 seconds -## License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## Contributing - -Contributions are welcome! Please ensure: -- Code follows existing style -- Tests pass -- Documentation is updated -- No sensitive credentials in commits - ## Security Notes - Never commit API keys or credentials From 8d64012883bfcaaabb10877d6f9957b93f1525e4 Mon Sep 17 00:00:00 2001 From: Agi K Thomas Date: Wed, 26 Nov 2025 14:25:37 +0530 Subject: [PATCH 4/6] Updated unused lines in Quickstart --- .../travel_assistant/QUICKSTART.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md b/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md index 632badb..0011ffe 100644 --- a/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md +++ b/aws/amazon-bedrock-agentcore/travel_assistant/QUICKSTART.md @@ -130,20 +130,4 @@ You should see: - Check Elastic API key permissions - Ensure all OTEL_* variables are set -## Next Steps - -- Read the full [README.md](README.md) for detailed documentation -- Check out [CONTRIBUTING.md](CONTRIBUTING.md) to contribute -- Explore the code in `claudeserver.py` - -## Need Help? - -- Review the full README for detailed explanations -- Check AWS Bedrock documentation -- Visit Tavily documentation for API help -- Review Elastic documentation for observability setup - -## Success! 🎉 - -Your Travel Assistant Agent is now running and ready to help plan trips! From 4b4ed76caa7e65b10fa49798dc36dbda57047863 Mon Sep 17 00:00:00 2001 From: Agi K Thomas Date: Wed, 26 Nov 2025 14:33:03 +0530 Subject: [PATCH 5/6] Fixed lint issues --- langchainChat/langtrace-elastic-demo-readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchainChat/langtrace-elastic-demo-readme.md b/langchainChat/langtrace-elastic-demo-readme.md index 81e5042..bee3d86 100644 --- a/langchainChat/langtrace-elastic-demo-readme.md +++ b/langchainChat/langtrace-elastic-demo-readme.md @@ -75,7 +75,7 @@ Type 'quit' to exit the chat. ## Example Trace -![Alt text](/langtrace/img/Xnapper-2024-08-16-12.36.03.png "Langtrace traces") +![Alt text](img/Xnapper-2024-08-16-12.36.03.png "Langtrace traces") ## How it works From c54d8f7457b227ea76521ee1785abeaf278fbb87 Mon Sep 17 00:00:00 2001 From: Agi K Thomas Date: Wed, 26 Nov 2025 14:47:39 +0530 Subject: [PATCH 6/6] Removed formatting issues --- .../travel_assistant/claudeserver.py | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py b/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py index a75d776..e1c35aa 100644 --- a/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py +++ b/aws/amazon-bedrock-agentcore/travel_assistant/claudeserver.py @@ -16,7 +16,10 @@ from opentelemetry import propagate from opentelemetry.baggage.propagation import W3CBaggagePropagator from opentelemetry.propagators.composite import CompositePropagator - from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, + ) + OTEL_AVAILABLE = True except ImportError: OTEL_AVAILABLE = False @@ -26,8 +29,6 @@ logger.setLevel(os.getenv("AGENT_RUNTIME_LOG_LEVEL", "INFO").upper()) - - @tool def web_search(query: str) -> str: """ @@ -44,13 +45,13 @@ def web_search(query: str) -> str: api_key = os.getenv("TAVILY_API_KEY") if not api_key: return "Error: TAVILY_API_KEY environment variable not set. Please sign up at https://tavily.com to get your API key." - + tavily_client = TavilyClient(api_key=api_key) response = tavily_client.search(query, max_results=5) - + # Extract results from Tavily response - results = response.get('results', []) - + results = response.get("results", []) + formatted_results = [] for i, result in enumerate(results, 1): formatted_results.append( @@ -64,6 +65,7 @@ def web_search(query: str) -> str: except Exception as e: return f"Error searching the web: {str(e)}" + @tool def web_search_ddg(query: str) -> str: """ @@ -78,10 +80,10 @@ def web_search_ddg(query: str) -> str: try: # Initialize DuckDuckGo search client ddgs = DDGS() - + # Perform search with max_results=5 results = list(ddgs.text(query, max_results=5)) - + formatted_results = [] for i, result in enumerate(results, 1): formatted_results.append( @@ -95,6 +97,7 @@ def web_search_ddg(query: str) -> str: except Exception as e: return f"Error searching the web with DuckDuckGo: {str(e)}" + @tool def country_specific_search(query: str) -> str: """ @@ -109,25 +112,25 @@ def country_specific_search(query: str) -> str: """ try: logger.info("Delayed India search initiated. Waiting 120 seconds before performing search...") - + # Wait for 120 seconds time.sleep(120) - + logger.info("Wait complete. Performing Tavily search for India destinations...") - + # Get Tavily API key from environment variable api_key = os.getenv("TAVILY_API_KEY") if not api_key: return "Error: TAVILY_API_KEY environment variable not set. Please sign up at https://tavily.com to get your API key." - + # Perform the search specifically about best places to visit in India tavily_client = TavilyClient(api_key=api_key) search_query = f"best places to visit in India {query}" response = tavily_client.search(search_query, max_results=5) - + # Extract results from Tavily response - results = response.get('results', []) - + results = response.get("results", []) + formatted_results = ["[DELAYED SEARCH - Waited 120 seconds before executing]\n"] for i, result in enumerate(results, 1): formatted_results.append( @@ -141,22 +144,20 @@ def country_specific_search(query: str) -> str: except Exception as e: return f"Error in delayed India search: {str(e)}" + # Function to initialize Bedrock model def get_bedrock_model(): region = os.getenv("AWS_DEFAULT_REGION", "us-east-1") model_id = os.getenv("BEDROCK_MODEL_ID", "us.anthropic.claude-3-5-sonnet-20240620-v1:0") - bedrock_model = BedrockModel( - model_id=model_id, - region_name=region, - temperature=0.0, - max_tokens=1024 - ) + bedrock_model = BedrockModel(model_id=model_id, region_name=region, temperature=0.0, max_tokens=1024) return bedrock_model + # Initialize the Bedrock model bedrock_model = get_bedrock_model() + # Define the agent's system prompt system_prompt = """You are an experienced travel agent specializing in personalized travel recommendations with access to real-time web information from multiple search engines. Your role is to find dream destinations @@ -173,8 +174,10 @@ def get_bedrock_model(): 5. Synthesize information from both sources to provide comprehensive recommendations with current information, brief descriptions, and practical travel details.""" + app = BedrockAgentCoreApp() + def initialize_agent(): """Initialize the agent with proper telemetry configuration.""" @@ -188,13 +191,13 @@ def initialize_agent(): "telemetry.sdk.language": "python", } ) - + # Create a custom tracer provider with our resource tracer_provider = TracerProvider(resource=custom_resource) - + # Set as global tracer provider (required for traces to work) trace_api.set_tracer_provider(tracer_provider) - + # Set up propagators (required for distributed tracing) propagate.set_global_textmap( CompositePropagator( @@ -204,27 +207,28 @@ def initialize_agent(): ] ) ) - + # Initialize Strands telemetry with the custom tracer provider strands_telemetry = StrandsTelemetry(tracer_provider=tracer_provider) strands_telemetry.setup_otlp_exporter() - + logger.info("Strands telemetry initialized with custom resource attributes") logger.info(f"Resource attributes: {custom_resource.attributes}") except Exception as e: logger.warning("Telemetry setup failed: %s", str(e)) else: logger.warning("Telemetry setup skipped - OpenTelemetry packages not available") - + # Create and cache the agent agent = Agent( model=bedrock_model, system_prompt=system_prompt, - tools=[web_search, web_search_ddg, country_specific_search] + tools=[web_search, web_search_ddg, country_specific_search], ) - + return agent + @app.entrypoint def strands_agent_bedrock(payload, context=None): """ @@ -232,12 +236,13 @@ def strands_agent_bedrock(payload, context=None): """ user_input = payload.get("prompt") logger.info("[%s] User input: %s", context.session_id, user_input) - + # Initialize agent with proper configuration agent = initialize_agent() - + response = agent(user_input) - return response.message['content'][0]['text'] + return response.message["content"][0]["text"] + if __name__ == "__main__": - app.run() \ No newline at end of file + app.run()