Erez Weinstein e2d9590a86
feat(market-trend-agent): market trends agent added (LangGraph sample) (#306)
* Add Market Trends Agent with Bedrock AgentCore browser integration

- Implements Strands-based agent for real-time stock data and business news
- Uses Bedrock AgentCore browser_client for web scraping
- Includes tools for Yahoo Finance stock data and Bloomberg news search
- Features professional market analysis with AI-powered insights
- Supports concurrent browser sessions for multiple stock comparisons
- Complete with tests, documentation, and clean requirements

* feat: Add Market Trends Agent with AgentCore integration

- Implement conversational broker card system with memory integration
- Add browser automation for real-time market data and news
- Create LangGraph-based agent architecture with tool orchestration
- Include deployment scripts and comprehensive documentation
- Add test scripts demonstrating broker profile workflow
- Configure Docker containerization for AgentCore Runtime

* feat: optimize memory management and fix deployment issues

- Fix memory creation to check existing memory first, eliminating ValidationException errors
- Extract memory tools into separate module for better organization
- Update deployment script with enhanced error handling and IAM permissions
- Add architecture documentation and diagrams
- Successfully deployed to burner account with Claude Sonnet 4 and multi-strategy memory

* fix: remove unsupported auto_update_on_conflict parameter and enable dynamic config

- Remove auto_update_on_conflict parameter from runtime.launch() call
- Allow .bedrock_agentcore.yaml to be generated dynamically by deploy script
- Successfully deployed to burner account (484363822803) with Claude Sonnet 4
- All tests passing: 4/4 (100% success rate)
- Agent fully functional with memory, market data, and news search capabilities

* fix:image link in readme

* fix:replaced image

* fix: resolve all linting errors and code quality issues

- Remove unused imports: tool, MemoryClient, ClientError, argparse, json, os, datetime, Any, re
- Fix f-strings without placeholders by converting to regular strings
- Remove unused variables: config_response, launch_result
- Clean up code to pass GitHub checks and improve maintainability

Fixes:
- deploy.py: 4 issues (f-strings + unused variables)
- market_trends_agent.py: 6 unused imports
- test_broker_card.py: 2 issues (unused import + f-string)
- tools/broker_card_tools.py: 2 unused imports
- tools/memory_tools.py: 3 issues (unused import + 2 f-strings)

* Changes to be committed:
	modified:   02-use-cases/market-trends-agent/.gitignore
	modified:   02-use-cases/market-trends-agent/tools/memory_tools.py

* fix: add back datetime import needed for session_id generation

The datetime import was accidentally removed but is still needed on line 27 for creating session IDs in the format 'market-{datetime.now().strftime('%Y%m%d%H%M%S')}'.

* refactor: remove Dockerfile from repo, let deployment auto-generate

- Remove Dockerfile from version control
- Add Dockerfile to .gitignore
- Deployment script will auto-generate Dockerfile with proper defaults
- Reduces repo clutter while maintaining deployment functionality
- Auto-generated Dockerfile will include necessary configurations

* Add uv support and fix news source reliability

- Add pyproject.toml with uv configuration
- Update README to use only uv commands
- Fix Reuters and CNBC URL issues with better error handling
- Add interactive chat instructions for users
- Improve browser tool with retry logic and reliable fallbacks"

* Fix memory management and add uv integration

- Fix broker identity confusion by enforcing identify_broker() workflow
- Improve actor_id consistency across memory operations
- Add enhanced error handling for news sources (Reuters/CNBC 503 errors)
- Add uv integration with pyproject.toml for faster dependency management
- Streamline README to uv-only workflow with interactive chat instructions
- Add reliable fallback news sources (Yahoo Finance, MarketWatch)

* Fix broker identification workflow and message filtering

- Enhanced system prompt to enforce mandatory broker identification
- Fixed tool_use/tool_result message pairing in filtering logic
- Added comprehensive test suite for broker memory storage
- Verified multi-strategy memory works for Tim Dunk and Maria Rodriguez

* Implement distributed memory coordination using SSM Parameter Store

- Added SSM Parameter Store for distributed memory ID coordination
- Enhanced race condition protection for deployment scenarios
- Updated IAM permissions to include SSM parameter access
- Prevents multiple memory instances during AgentCore deployment
- Maintains backward compatibility with local .memory_id file

* Add comprehensive cleanup script and documentation

- Created cleanup.py script to remove all deployed AWS resources
- Cleans up AgentCore runtime, memory, ECR, CodeBuild, S3, IAM, SSM
- Added safety features: dry-run mode, confirmation prompts, detailed logging
- Updated README with complete cleanup section and troubleshooting
- Tested cleanup successfully removes all resources except runtime (manual)
- Supports selective cleanup options (--skip-iam, --region, --dry-run)

* fix: resolve all lint errors in market trends agent

- Remove unused imports (json, time, extract_actor_id)
- Fix f-strings without placeholders in browser_tool.py
- Replace bare except clauses with specific exception handling
- Add proper logging for exception cases
- All files now pass ruff lint checks

---------

Signed-off-by: Erez Weinstein <125476602+erezweinstein5@users.noreply.github.com>
Co-authored-by: Erez Weinstein <erweinst@amazon.com>
2025-09-04 13:26:50 -04:00

184 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""
Market Trends Agent Test Suite
Tests core functionality including memory, market analysis, and basic operations
"""
import boto3
import json
import os
import time
import logging
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def load_agent_arn():
"""Load the agent ARN from file"""
arn_file = '.agent_arn'
if not os.path.exists(arn_file):
print("❌ No ARN file found. Deploy the agent first.")
return None
with open(arn_file, 'r') as f:
return f.read().strip()
def invoke_agent(runtime_arn: str, prompt: str) -> str:
"""Invoke the deployed agent with a prompt"""
try:
client = boto3.client('bedrock-agentcore', region_name='us-east-1')
response = client.invoke_agent_runtime(
agentRuntimeArn=runtime_arn,
payload=json.dumps({"prompt": prompt})
)
if 'response' in response:
return response['response'].read().decode('utf-8')
else:
return str(response)
except Exception as e:
logger.error(f"Error invoking agent: {e}")
return f"Error: {e}"
def run_simple_test(runtime_arn: str):
"""Run a simple connectivity test"""
print("🧪 SIMPLE TEST: Basic Connectivity")
print("-" * 40)
test_message = "Hello, I'm testing the agent. Can you help me?"
response = invoke_agent(runtime_arn, test_message)
success = "error" not in response.lower() and len(response) > 50
print(f"✅ Response: {response[:200]}..." if len(response) > 200 else response)
print(f"🔍 Test Result: {'✅ PASSED' if success else '❌ FAILED'}")
print()
return success
def run_comprehensive_tests(runtime_arn: str):
"""Run comprehensive functionality tests"""
print("🚀 Market Trends Agent - Comprehensive Test Suite")
print("=" * 60)
print(f"📋 Testing ARN: {runtime_arn}")
print()
tests_passed = 0
total_tests = 4
# Test 1: Broker Introduction & Memory
print("📋 TEST 1: Broker Profile & Memory")
print("-" * 30)
broker_intro = "Hi, I'm Sarah Chen from Morgan Stanley. I focus on growth investing and tech stocks for younger clients. Please remember my profile."
response1 = invoke_agent(runtime_arn, broker_intro)
print("✅ Response:", response1[:200] + "..." if len(response1) > 200 else response1)
# Check if profile was acknowledged
profile_acknowledged = any(keyword in response1.lower() for keyword in ['sarah', 'morgan stanley', 'growth', 'tech', 'profile', 'remember'])
print(f"🔍 Profile Acknowledged: {'✅ YES' if profile_acknowledged else '❌ NO'}")
if profile_acknowledged:
tests_passed += 1
print()
time.sleep(5) # Wait to avoid throttling
# Test 2: Memory Recall
print("📋 TEST 2: Memory Recall")
print("-" * 30)
memory_test = "Hi, I'm Sarah Chen from Morgan Stanley. What do you remember about my investment preferences?"
response2 = invoke_agent(runtime_arn, memory_test)
print("✅ Response:", response2[:200] + "..." if len(response2) > 200 else response2)
# Check if memory was recalled
memory_recalled = any(keyword in response2.lower() for keyword in ['sarah', 'growth', 'tech', 'morgan stanley'])
print(f"🔍 Memory Recalled: {'✅ YES' if memory_recalled else '❌ NO'}")
if memory_recalled:
tests_passed += 1
print()
time.sleep(5) # Wait to avoid throttling
# Test 3: Market Data Request
print("📋 TEST 3: Market Data Request")
print("-" * 30)
market_request = "Get me the current Apple stock price and recent performance"
response3 = invoke_agent(runtime_arn, market_request)
print("✅ Response:", response3[:200] + "..." if len(response3) > 200 else response3)
# Check if market data was attempted
market_data_attempted = any(keyword in response3.lower() for keyword in ['apple', 'aapl', 'stock', 'price', 'market'])
print(f"🔍 Market Data Retrieved: {'✅ YES' if market_data_attempted else '❌ NO'}")
if market_data_attempted:
tests_passed += 1
print()
time.sleep(5) # Wait to avoid throttling
# Test 4: News Search
print("📋 TEST 4: News Search")
print("-" * 30)
news_request = "Find recent news about AI and technology stocks"
response4 = invoke_agent(runtime_arn, news_request)
print("✅ Response:", response4[:200] + "..." if len(response4) > 200 else response4)
# Check if news search was attempted
news_retrieved = any(keyword in response4.lower() for keyword in ['news', 'ai', 'technology', 'search', 'recent'])
print(f"🔍 News Retrieved: {'✅ YES' if news_retrieved else '❌ NO'}")
if news_retrieved:
tests_passed += 1
print()
# Summary
print("=" * 60)
print("📊 TEST SUMMARY")
print("=" * 60)
print(f"Tests Passed: {tests_passed}/{total_tests}")
print(f"Success Rate: {(tests_passed/total_tests)*100:.0f}%")
if tests_passed == total_tests:
print("🎉 ALL TESTS PASSED - Agent is fully functional!")
elif tests_passed >= total_tests // 2:
print("⚠️ PARTIAL SUCCESS - Some features may need attention")
else:
print("❌ ISSUES DETECTED - Agent needs attention")
return tests_passed == total_tests
def main():
"""Main test function"""
runtime_arn = load_agent_arn()
if not runtime_arn:
return False
print("Choose test type:")
print("1. Simple connectivity test")
print("2. Comprehensive functionality tests")
try:
choice = input("Enter choice (1 or 2, default=1): ").strip()
if not choice:
choice = "1"
except KeyboardInterrupt:
print("\nTest cancelled.")
return False
if choice == "2":
return run_comprehensive_tests(runtime_arn)
else:
return run_simple_test(runtime_arn)
if __name__ == "__main__":
success = main()
exit(0 if success else 1)