Skip to content

Conversation

@bergjaak
Copy link
Contributor

@bergjaak bergjaak commented Sep 17, 2025

Users observed that sometimes timestamps can be overlapping, causing out of order tool events. So, the fix is to increment the timestamp counter. I ran the problematic script 10 times in a row and did not have the issue once. Also, notice that boto3 (yes, the boto3 client, not the service) only resolves timestamps at 1 second resolution. So that is why I am incrementing at the 1 second level.

Testing

I ran the below script test.py as for i in {1..10}; do python test.py; done

before the fix, this gave a validation error from the Converse API about tool use and tool result being out of order. But now that never happens.

import json
import time
from datetime import datetime
from strands import Agent, tool
from botocore.exceptions import ClientError
from bedrock_agentcore.memory import MemoryClient
from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig
from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager


# Create new session manager (simulating fresh container)
from botocore.config import Config
retry_config = Config(
    retries={
        'max_attempts': 12,  # Increase from default 4 to 10
        'mode': 'adaptive'
    }
)

@tool
def get_weather(location: str) -> str:
    """Get the current weather for a location."""
    # Mock weather data
    weather_data = {
        "seattle": "Rainy, 55°F",
        "san francisco": "Foggy, 62°F", 
        "new york": "Sunny, 68°F",
        "london": "Cloudy, 52°F"
    }
    return weather_data.get(location.lower(), f"Weather data not available for {location}")

@tool
def calculate_tip(bill_amount: float, tip_percentage: float = 18.0) -> dict:
    """Calculate tip and total amount for a restaurant bill."""
    tip_amount = bill_amount * (tip_percentage / 100)
    total_amount = bill_amount + tip_amount
    
    return {
        "bill_amount": bill_amount,
        "tip_percentage": tip_percentage,
        "tip_amount": round(tip_amount, 2),
        "total_amount": round(total_amount, 2)
    }

def main():
    print("🚀 Starting AgentCore Memory Example with Tools...")
    
    # Step 1: Find or create memory
    print("📝 Finding or creating memory...")
    client = MemoryClient(region_name="us-east-1")
    
    memory_name = "ToolsTestMemory"
    
    try:
        # First, try to find existing memory
        print("🔍 Listing existing memories...")
        memories = client.list_memories()
        print(f"Found {len(memories)} existing memories")
        
        existing_memory = None
        for memory in memories:
            memory_name_field = memory.get('name')
            memory_id = memory.get('id', '')
            print(f"  - {memory_name_field} (ID: {memory_id})")
            
            # Check both name field and ID prefix
            if (memory_name_field == memory_name or 
                memory_id.startswith(memory_name + '-')):
                existing_memory = memory
                break
        
        if existing_memory:
            print(f"✅ Found existing memory: {existing_memory.get('id')}")
            basic_memory = existing_memory
        else:
            print(f"📝 Creating new memory: {memory_name}")
            try:
                basic_memory = client.create_memory(
                    name=memory_name,
                    description="Memory for testing tools with short-term functionality"
                )
                print(f"✅ Memory created: {basic_memory.get('id')}")
            except Exception as create_error:
                if "already exists" in str(create_error):
                    print("⚠️  Memory exists but wasn't found in list. Retrying list...")
                    # Retry listing - sometimes there's a delay
                    memories = client.list_memories()
                    for memory in memories:
                        if memory.get('name') == memory_name:
                            basic_memory = memory
                            print(f"✅ Found memory on retry: {memory.get('id')}")
                            break
                    else:
                        raise create_error
                else:
                    raise create_error
            
    except Exception as e:
        print(f"❌ Failed to find/create memory: {e}")
        return
    
    # Step 2: Configure session manager
    MEM_ID = basic_memory.get('id')
    #ACTOR_ID = "actor_id_test_%s" % datetime.now().strftime("%Y%m%d%H%M%S")
    #SESSION_ID = "testing_session_id_%s" % datetime.now().strftime("%Y%m%d%H%M%S")
    ACTOR_ID = "JAKOB_NEW_ACTOR_ID_TEST99"
    SESSION_ID  = "JAKOB_NEW_SESSION_ID_TEST99"
    
    print(f"🔧 Configuring session manager...")
    print(f"   Memory ID: {MEM_ID}")
    print(f"   Actor ID: {ACTOR_ID}")
    print(f"   Session ID: {SESSION_ID}")
    
    agentcore_memory_config = AgentCoreMemoryConfig(
        memory_id=MEM_ID,
        session_id=SESSION_ID,
        actor_id=ACTOR_ID
    )
    
    session_manager = AgentCoreMemorySessionManager(
        agentcore_memory_config=agentcore_memory_config,
        region_name="us-east-1",
        boto_client_config=retry_config,
    )
    
    # Step 3: Create agent with tools
    print("🤖 Creating agent with tools...")
    agent = Agent(
        system_prompt="You are a helpful assistant with access to weather and tip calculation tools. Remember user preferences and use tools when appropriate.",
        session_manager=session_manager,
        tools=[get_weather, calculate_tip]
    )
    
    # Step 4: Have a conversation using tools
    print("💬 Starting conversation with tools...")
    
    print("\n--- Conversation ---")
    
    # Test weather tool
    try:
        response1 = agent("What's the weather like in Seattle?")
        print(f"User: What's the weather like in Seattle?")
        print(f"Agent: {response1}")
    except Exception as e:
        if 'Throttl' in str(e) or 'Rate exceeded' in str(e):
            print("⏳ Throttling detected, sleeping for 4 seconds...")
            time.sleep(4)
            response1 = agent("What's the weather like in Seattle?")
            print(f"User: What's the weather like in Seattle?")
            print(f"Agent: {response1}")
        else:
            raise
    
    # Test tip calculator tool
    try:
        response2 = agent("I had dinner and the bill was $85.50. Can you calculate a 20% tip?")
        print(f"\nUser: I had dinner and the bill was $85.50. Can you calculate a 20% tip?")
        print(f"Agent: {response2}")
    except Exception as e:
        if 'Throttl' in str(e) or 'Rate exceeded' in str(e):
            print("⏳ Throttling detected, sleeping for 4 seconds...")
            time.sleep(4)
            response2 = agent("I had dinner and the bill was $85.50. Can you calculate a 20% tip?")
            print(f"\nUser: I had dinner and the bill was $85.50. Can you calculate a 20% tip?")
            print(f"Agent: {response2}")
        else:
            raise
    
    # Test memory - agent should remember previous interactions
    try:
        response3 = agent("What was the weather I asked about earlier?")
        print(f"\nUser: What was the weather I asked about earlier?")
        print(f"Agent: {response3}")
    except Exception as e:
        if 'Throttl' in str(e) or 'Rate exceeded' in str(e):
            print("⏳ Throttling detected, sleeping for 4 seconds...")
            time.sleep(4)
            response3 = agent("What was the weather I asked about earlier?")
            print(f"\nUser: What was the weather I asked about earlier?")
            print(f"Agent: {response3}")
        else:
            raise
    
    # Test another tool usage
    try:
        response4 = agent("Check the weather in New York and calculate a 15% tip on a $42.75 bill")
        print(f"\nUser: Check the weather in New York and calculate a 15% tip on a $42.75 bill")
        print(f"Agent: {response4}")
    except Exception as e:
        if 'Throttl' in str(e) or 'Rate exceeded' in str(e):
            print("⏳ Throttling detected, sleeping for 4 seconds...")
            time.sleep(4)
            response4 = agent("Check the weather in New York and calculate a 15% tip on a $42.75 bill")
            print(f"\nUser: Check the weather in New York and calculate a 15% tip on a $42.75 bill")
            print(f"Agent: {response4}")
        else:
            raise
    
    print("\n🔄 Testing agent restart scenario (simulating container restart)...")
    
    # Step 5: Simulate agent restart by creating new session manager and agent
    print("🔄 Simulating agent restart - creating new session manager and agent...")
    
    new_session_manager = AgentCoreMemorySessionManager(
        agentcore_memory_config=agentcore_memory_config,
        region_name="us-east-1",
        boto_client_config=retry_config
    )
    
    # Create new agent (simulating agent restart)
    new_agent = Agent(
        system_prompt="You are a helpful assistant with access to weather and tip calculation tools. Remember user preferences and use tools when appropriate.",
        session_manager=new_session_manager,
        tools=[get_weather, calculate_tip]
    )
    
    # Test that the new agent can load conversation history without ValidationException
    try:
        print("🧪 Testing conversation continuation after restart...")
        response5 = new_agent("What tools did I use in our previous conversation?")
        print(f"\nUser: What tools did I use in our previous conversation?")
        print(f"New Agent (after restart): {response5}")
        
        print("\n✅ SUCCESS: No ValidationException occurred!")
        print("🔧 Monotonic timestamp fix prevents toolUse/toolResult ordering issues!")
        
    except Exception as e:
        if "toolResult blocks" in str(e) and "toolUse blocks" in str(e):
            print(f"\n❌ FAILED: ValidationException still occurs: {e}")
            print("🐛 The timestamp ordering fix needs more work")
            raise
        elif 'Throttling' in str(e) or 'TooManyRequests' in str(e):
            print("⏳ Throttling detected, sleeping for 4 seconds...")
            time.sleep(4)
            response5 = new_agent("What tools did I use in our previous conversation?")
            print(f"\nUser: What tools did I use in our previous conversation?")
            print(f"New Agent (after restart): {response5}")
            print("\n✅ SUCCESS: No ValidationException occurred!")
        else:
            print(f"❌ Unexpected error: {e}")
            raise
main()

@bergjaak bergjaak merged commit 5b97e5a into main Sep 17, 2025
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants