Spaces:
Build error
Build error
| """Formal evals for SmartCrusher quality retention. | |
| These tests verify that SmartCrusher GUARANTEES 100% retention of critical items: | |
| 1. Error items: Items containing error keywords | |
| 2. Anomaly items: Items with values > 2 std from mean | |
| 3. Relevance items: Items matching user query context | |
| This is a FORMAL EVAL - any failure here is a CRITICAL BUG. | |
| """ | |
| import json | |
| import pytest | |
| from headroom.providers.anthropic import AnthropicTokenCounter | |
| from headroom.tokenizer import Tokenizer | |
| from headroom.transforms.smart_crusher import ( | |
| SmartCrusher, | |
| SmartCrusherConfig, | |
| smart_crush_tool_output, | |
| ) | |
| class TestErrorRetention: | |
| """Verify 100% retention of error items.""" | |
| ERROR_KEYWORDS = ["error", "exception", "failed", "failure", "critical", "fatal"] | |
| def large_dataset(self): | |
| """Create large dataset with known errors.""" | |
| items = [] | |
| error_indices = [] | |
| for i in range(1000): | |
| items.append( | |
| { | |
| "id": f"item_{i}", | |
| "value": i, | |
| "status": "ok", | |
| "message": f"Normal operation {i}", | |
| } | |
| ) | |
| # Insert errors at specific positions | |
| for idx in [10, 50, 100, 250, 500, 750, 999]: | |
| items[idx]["status"] = "failed" | |
| items[idx]["error"] = f"Error at position {idx}" | |
| error_indices.append(idx) | |
| return items, error_indices | |
| def test_all_error_items_retained(self, large_dataset): | |
| """CRITICAL: Every item with error keywords MUST be retained.""" | |
| items, error_indices = large_dataset | |
| config = SmartCrusherConfig(max_items_after_crush=20) | |
| content = json.dumps(items) | |
| compressed_str, _, _ = smart_crush_tool_output(content, config) | |
| compressed = json.loads(compressed_str) | |
| # Count errors before and after | |
| errors_before = len(error_indices) | |
| errors_after = sum(1 for x in compressed if x.get("error")) | |
| assert errors_after == errors_before, ( | |
| f"QUALITY FAILURE: Lost {errors_before - errors_after} error items! " | |
| f"Expected {errors_before}, got {errors_after}" | |
| ) | |
| def test_each_error_keyword_detected(self, keyword): | |
| """Each error keyword must trigger retention.""" | |
| items = [{"id": f"item_{i}", "msg": f"Normal {i}"} for i in range(100)] | |
| items[50]["msg"] = f"This contains {keyword} keyword" | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| matching = [x for x in compressed if keyword in str(x).lower()] | |
| assert len(matching) >= 1, f"Item with '{keyword}' keyword was dropped!" | |
| def test_error_in_nested_structure(self): | |
| """Errors in nested objects must be detected.""" | |
| items = [{"id": i, "data": {"status": "ok"}} for i in range(100)] | |
| items[50]["data"]["status"] = "failed" | |
| items[50]["data"]["error"] = "Nested error" | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| nested_errors = [x for x in compressed if x.get("data", {}).get("error")] | |
| assert len(nested_errors) >= 1, "Nested error item was dropped!" | |
| def test_multiple_errors_all_retained(self): | |
| """When errors exceed max_items, ALL errors must still be retained.""" | |
| # Create 100 items where 30 are errors (more than max_items_after_crush) | |
| items = [] | |
| for i in range(100): | |
| item = {"id": i, "value": i} | |
| if i % 3 == 0: # Every 3rd item is an error (33 total) | |
| item["error"] = f"Error {i}" | |
| item["status"] = "failed" | |
| items.append(item) | |
| error_count_before = sum(1 for x in items if x.get("error")) | |
| assert error_count_before == 34 # 0,3,6,...,99 = 34 items | |
| # Compress with max 20 items | |
| config = SmartCrusherConfig(max_items_after_crush=20) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| error_count_after = sum(1 for x in compressed if x.get("error")) | |
| # When errors > max_items, we should keep ALL errors (errors take priority) | |
| # This tests the _prioritize_indices logic | |
| assert error_count_after == error_count_before, ( | |
| f"CRITICAL: Errors were dropped! " | |
| f"Before: {error_count_before}, After: {error_count_after}" | |
| ) | |
| class TestAnomalyRetention: | |
| """Verify 100% retention of anomalous numeric values.""" | |
| def test_numeric_anomalies_retained(self): | |
| """Items with values > 2 std from mean must be retained.""" | |
| items = [] | |
| anomaly_indices = [] | |
| # Create items with normal values around mean=100, std=10 | |
| for i in range(1000): | |
| items.append( | |
| { | |
| "id": f"item_{i}", | |
| "value": 100 + (i % 20) - 10, # Values 90-110 | |
| "name": f"Normal item {i}", | |
| } | |
| ) | |
| # Insert anomalies (> 2 std = > 120 or < 80) | |
| for idx in [100, 300, 500, 700, 900]: | |
| items[idx]["value"] = 999999 # Extreme anomaly | |
| items[idx]["is_anomaly"] = True # Mark for verification | |
| anomaly_indices.append(idx) | |
| config = SmartCrusherConfig(max_items_after_crush=20) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| anomalies_after = sum(1 for x in compressed if x.get("is_anomaly")) | |
| assert anomalies_after == len(anomaly_indices), ( | |
| f"QUALITY FAILURE: Lost anomaly items! " | |
| f"Expected {len(anomaly_indices)}, got {anomalies_after}" | |
| ) | |
| def test_negative_anomalies_retained(self): | |
| """Negative outliers must also be retained.""" | |
| items = [{"id": i, "value": 100} for i in range(100)] | |
| items[50]["value"] = -999 # Negative anomaly | |
| items[50]["is_anomaly"] = True | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| anomalies = [x for x in compressed if x.get("is_anomaly")] | |
| assert len(anomalies) == 1, "Negative anomaly was dropped!" | |
| class TestRelevanceRetention: | |
| """Verify retention of items matching query context.""" | |
| def test_relevance_with_query_context(self): | |
| """Items matching query should be retained when context is provided.""" | |
| items = [{"id": i, "content": f"Generic content about topic {i}"} for i in range(100)] | |
| # Insert a specific item that matches our query | |
| # Note: This also contains "error" keyword which will trigger error retention | |
| items[50]["content"] = "Authentication error: invalid JWT token expired" | |
| items[50]["is_target"] = True | |
| # Use SmartCrusher with query context (via message-based API) | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| crusher = SmartCrusher(config) | |
| # Create tokenizer with proper counter | |
| model = "claude-3-5-sonnet-20241022" | |
| token_counter = AnthropicTokenCounter(model) | |
| tokenizer = Tokenizer(token_counter, model) | |
| # Create messages with query context | |
| messages = [ | |
| {"role": "user", "content": "Why is JWT authentication failing?"}, | |
| {"role": "tool", "tool_call_id": "call_1", "content": json.dumps(items)}, | |
| ] | |
| result = crusher.apply(messages, tokenizer) | |
| tool_msg = next(m for m in result.messages if m.get("role") == "tool") | |
| compressed = json.loads(tool_msg["content"].split("\n")[0]) # Remove marker | |
| targets = [x for x in compressed if x.get("is_target")] | |
| assert len(targets) >= 1, "Target item was dropped despite matching query context!" | |
| class TestFirstLastRetention: | |
| """Verify first K and last K items are always retained.""" | |
| def test_first_items_retained(self): | |
| """First 3 items must always be retained.""" | |
| items = [{"id": i, "value": i} for i in range(100)] | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| ids = [x["id"] for x in compressed] | |
| assert 0 in ids, "First item (id=0) was dropped!" | |
| assert 1 in ids, "Second item (id=1) was dropped!" | |
| assert 2 in ids, "Third item (id=2) was dropped!" | |
| def test_last_items_retained(self): | |
| """Last 2 items must always be retained.""" | |
| items = [{"id": i, "value": i} for i in range(100)] | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| ids = [x["id"] for x in compressed] | |
| assert 98 in ids, "Second-to-last item (id=98) was dropped!" | |
| assert 99 in ids, "Last item (id=99) was dropped!" | |
| class TestCombinedRetention: | |
| """Test retention when multiple preservation criteria apply.""" | |
| def test_error_and_anomaly_both_retained(self): | |
| """Items that are both errors AND anomalies must be retained.""" | |
| items = [{"id": i, "value": 100} for i in range(100)] | |
| # Item is both an error AND an anomaly | |
| items[50]["value"] = 999999 | |
| items[50]["error"] = "Critical failure" | |
| items[50]["is_both"] = True | |
| config = SmartCrusherConfig(max_items_after_crush=10) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| both = [x for x in compressed if x.get("is_both")] | |
| assert len(both) == 1, "Item with both error and anomaly was dropped!" | |
| def test_high_volume_critical_items(self): | |
| """Even with many critical items, none should be dropped.""" | |
| items = [] | |
| critical_count = 0 | |
| for i in range(500): | |
| item = {"id": i, "value": 100} | |
| # Make every 5th item an error | |
| if i % 5 == 0: | |
| item["error"] = f"Error {i}" | |
| critical_count += 1 | |
| # Make every 7th item an anomaly (some overlap) | |
| if i % 7 == 0: | |
| item["value"] = 999999 | |
| if "error" not in item: | |
| critical_count += 1 | |
| items.append(item) | |
| config = SmartCrusherConfig(max_items_after_crush=30) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| # Count retained critical items | |
| errors_retained = sum(1 for x in compressed if x.get("error")) | |
| sum(1 for x in compressed if x.get("value", 0) > 900000) | |
| # All errors should be retained | |
| errors_original = sum(1 for x in items if x.get("error")) | |
| assert errors_retained == errors_original, ( | |
| f"Some errors dropped: {errors_original} -> {errors_retained}" | |
| ) | |
| class TestCompressionRatio: | |
| """Verify compression achieves target while preserving quality.""" | |
| def test_compression_with_quality(self): | |
| """Compression should reduce size significantly while keeping critical items.""" | |
| # Create realistic large dataset | |
| items = [] | |
| for i in range(1000): | |
| items.append( | |
| { | |
| "id": f"doc_{i}", | |
| "score": 0.5, | |
| "title": f"Document {i} about various topics", | |
| "snippet": "Lorem ipsum " * 20, | |
| "metadata": {"source": "web", "date": "2024-01-01"}, | |
| } | |
| ) | |
| # Add some critical items | |
| items[100]["error"] = "Parse error" | |
| items[500]["value"] = 999999 # Add numeric field for anomaly | |
| original_size = len(json.dumps(items)) | |
| config = SmartCrusherConfig(max_items_after_crush=50) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| compressed_size = len(json.dumps(compressed)) | |
| # Should achieve significant compression | |
| compression_ratio = 1 - (compressed_size / original_size) | |
| assert compression_ratio > 0.9, f"Compression too low: {compression_ratio:.1%}" | |
| # But critical items must be preserved | |
| assert any(x.get("error") for x in compressed), "Error item lost during compression!" | |
| class TestEdgeCases: | |
| """Test edge cases and boundary conditions.""" | |
| def test_empty_array(self): | |
| """Empty array should return empty.""" | |
| compressed_str, was_modified, _ = smart_crush_tool_output("[]") | |
| assert compressed_str == "[]" | |
| assert not was_modified | |
| def test_small_array_unchanged(self): | |
| """Arrays smaller than min_items_to_analyze should be unchanged.""" | |
| items = [{"id": i} for i in range(3)] | |
| original = json.dumps(items) | |
| compressed_str, was_modified, _ = smart_crush_tool_output(original) | |
| # Small arrays shouldn't be modified | |
| assert json.loads(compressed_str) == items | |
| def test_all_items_are_errors(self): | |
| """When all items are errors, all should be retained.""" | |
| items = [{"id": i, "error": f"Error {i}"} for i in range(50)] | |
| config = SmartCrusherConfig(max_items_after_crush=20) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| # All 50 errors should be retained (errors override max_items) | |
| assert len(compressed) == 50, ( | |
| f"Some errors dropped when all items are errors! Expected 50, got {len(compressed)}" | |
| ) | |
| def test_unicode_content(self): | |
| """Unicode content should not break error detection.""" | |
| items = [{"id": i, "content": f"内容 {i}"} for i in range(100)] | |
| items[50]["error"] = "错误: Unicode error message" | |
| config = SmartCrusherConfig(max_items_after_crush=15) | |
| compressed_str, _, _ = smart_crush_tool_output(json.dumps(items), config) | |
| compressed = json.loads(compressed_str) | |
| errors = [x for x in compressed if x.get("error")] | |
| assert len(errors) == 1, "Unicode error item was dropped!" | |