#!/usr/bin/env python3 """ Debug script to find where .lower() is being called on non-strings """ import os import sys # Set up path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # Set minimal env vars os.environ["ANTHROPIC_API_KEY"] = "test-key" def find_lower_calls(): """Find all .lower() calls in the code""" print("Searching for all .lower() calls in app.py...") print("-" * 60) with open('app.py', 'r') as f: lines = f.readlines() lower_calls = [] for i, line in enumerate(lines, 1): if '.lower()' in line: lower_calls.append((i, line.strip())) print(f"Found {len(lower_calls)} .lower() calls:\n") for line_num, line in lower_calls: print(f"Line {line_num}: {line}") # Check if there's protection if 'isinstance' in lines[line_num-2:line_num]: print(" ✅ Has type checking") else: print(" ⚠️ No type checking nearby") print() def test_problematic_inputs(): """Test inputs that might cause .lower() errors""" print("\nTesting problematic inputs...") print("-" * 60) # Test cases that might break .lower() test_inputs = [ "normal string", ["list", "of", "strings"], {"dict": "value"}, 123, None, [{"nested": "structure"}], b"bytes string", ] for test_input in test_inputs: print(f"\nInput: {repr(test_input)} (type: {type(test_input)})") # Test direct .lower() try: result = test_input.lower() print(f" ✅ .lower() works: {result}") except AttributeError as e: print(f" ❌ .lower() fails: {e}") # Test with type checking try: if isinstance(test_input, str): result = test_input.lower() print(f" ✅ With type check: {result}") else: result = str(test_input).lower() print(f" ✅ With str() conversion: {result}") except Exception as e: print(f" ❌ Even with protection: {e}") def test_message_content(): """Test what might be in message.content""" print("\n\nTesting message content scenarios...") print("-" * 60) # Simulate different message contents class MockMessage: def __init__(self, content): self.content = content test_messages = [ MockMessage("Normal text content"), MockMessage(["List", "content"]), # This might happen! MockMessage({"type": "text", "content": "dict content"}), MockMessage(None), ] for i, msg in enumerate(test_messages): print(f"\nMessage {i}: content = {repr(msg.content)}") # Simulate what might happen in the code if hasattr(msg, "content") and msg.content: content = msg.content print(f" Content type: {type(content)}") # This would fail on non-strings! try: content = content.strip() print(f" ✅ .strip() works") except AttributeError: print(f" ❌ .strip() fails - content is not a string!") # Safe approach if isinstance(content, list): content = " ".join(str(item) for item in content) print(f" ✅ Converted list to string: {content}") elif not isinstance(content, str): content = str(content) print(f" ✅ Converted to string: {content}") if __name__ == "__main__": print("=" * 80) print("DEBUG: Finding .lower() error sources") print("=" * 80) find_lower_calls() test_problematic_inputs() test_message_content() print("\n" + "=" * 80) print("CONCLUSION:") print("The error likely occurs when message.content is a list instead of string") print("This can happen with multimodal messages or tool responses") print("Solution: Always check type before calling .lower() or .strip()") print("=" * 80)