|
6 | 6 |
|
7 | 7 | import strands |
8 | 8 | from strands import Agent |
9 | | -from strands.models import BedrockModel |
| 9 | +from strands.models import BedrockModel, CacheConfig |
10 | 10 | from strands.types.content import ContentBlock |
11 | 11 |
|
12 | 12 |
|
@@ -576,3 +576,125 @@ def calculator(expression: str) -> float: |
576 | 576 | agent('Search for "python" with tags ["programming", "language"] using the search tool.') |
577 | 577 |
|
578 | 578 | assert "search" in tools_called |
| 579 | + |
| 580 | + |
| 581 | +def test_prompt_caching_cache_tools_ttl(): |
| 582 | + """Test that cache_tools_ttl propagates into the auto-injected toolConfig cache point. |
| 583 | +
|
| 584 | + Verifies that BedrockModel(cache_tools="default", cache_tools_ttl="5m") produces a |
| 585 | + Bedrock request with cachePoint.ttl on the toolConfig checkpoint, and that the call |
| 586 | + completes without a ValidationException on the TTL field. |
| 587 | +
|
| 588 | + Note: we intentionally do not assert specific cacheWriteInputTokens on the toolConfig |
| 589 | + prefix because Bedrock's tool-prefix cache threshold varies by model and region. |
| 590 | + The critical behavior under test here is that the TTL field is accepted end-to-end. |
| 591 | +
|
| 592 | + Uses Claude Haiku 4.5 which supports TTL in CachePointBlock on Bedrock. |
| 593 | + """ |
| 594 | + model = BedrockModel( |
| 595 | + model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0", |
| 596 | + streaming=False, |
| 597 | + cache_tools="default", |
| 598 | + cache_tools_ttl="5m", |
| 599 | + ) |
| 600 | + |
| 601 | + @strands.tool |
| 602 | + def lookup_fact(topic: str) -> str: |
| 603 | + """Look up a fact about the given topic. |
| 604 | +
|
| 605 | + This tool is useful when you need authoritative information. |
| 606 | + """ |
| 607 | + return f"Fact about {topic}: example" |
| 608 | + |
| 609 | + agent = Agent( |
| 610 | + model=model, |
| 611 | + tools=[lookup_fact], |
| 612 | + load_tools_from_directory=False, |
| 613 | + ) |
| 614 | + |
| 615 | + # The call must succeed — Bedrock must accept cachePoint.ttl on the toolConfig checkpoint |
| 616 | + # without raising a ValidationException. |
| 617 | + result = agent("Use the lookup_fact tool to look up 'python'.") |
| 618 | + assert len(str(result)) > 0 |
| 619 | + |
| 620 | + |
| 621 | +def test_prompt_caching_cache_config_auto_with_ttl(): |
| 622 | + """Test that CacheConfig(strategy="auto", ttl="5m") propagates TTL to the auto-injected message cache point. |
| 623 | +
|
| 624 | + Verifies that the cache point appended to the last user message by _inject_cache_point |
| 625 | + carries the configured TTL, and that Bedrock accepts the request. |
| 626 | +
|
| 627 | + Uses Claude Haiku 4.5 which supports TTL in CachePointBlock on Bedrock. |
| 628 | + """ |
| 629 | + model = BedrockModel( |
| 630 | + model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0", |
| 631 | + streaming=False, |
| 632 | + cache_config=CacheConfig(strategy="auto", ttl="5m"), |
| 633 | + ) |
| 634 | + |
| 635 | + unique_id = str(uuid.uuid4()) |
| 636 | + # Minimum 4096 tokens required for caching with Haiku 4.5 |
| 637 | + large_message = f"Context for test {unique_id}: " + ("This is important context. " * 1000) + " What is 2+2?" |
| 638 | + |
| 639 | + agent = Agent( |
| 640 | + model=model, |
| 641 | + load_tools_from_directory=False, |
| 642 | + ) |
| 643 | + |
| 644 | + # First call: auto-injected cache point on the last user message must include ttl and be accepted |
| 645 | + result1 = agent(large_message) |
| 646 | + assert len(str(result1)) > 0 |
| 647 | + |
| 648 | + # Verify cache write occurred with auto-inject + ttl |
| 649 | + assert result1.metrics.accumulated_usage.get("cacheWriteInputTokens", 0) > 0, ( |
| 650 | + "Expected cacheWriteInputTokens > 0 with CacheConfig(strategy='auto', ttl='5m')" |
| 651 | + ) |
| 652 | + |
| 653 | + |
| 654 | +def test_prompt_caching_aligned_1h_ttl_across_checkpoints(): |
| 655 | + """Regression test for Bedrock TTL non-increasing ordering rule (Issue #2121). |
| 656 | +
|
| 657 | + Bedrock processes cache checkpoints in order: toolConfig -> system -> messages, |
| 658 | + and requires TTLs to be non-increasing. Before this change, cache_tools hardcoded |
| 659 | + an implicit 5m TTL, so any 1h TTL on a later checkpoint would raise a |
| 660 | + ValidationException. |
| 661 | +
|
| 662 | + This test sets 1h TTL on all three checkpoints simultaneously and verifies the |
| 663 | + call succeeds. |
| 664 | +
|
| 665 | + Uses Claude Haiku 4.5 which supports 1h TTL. |
| 666 | + """ |
| 667 | + model = BedrockModel( |
| 668 | + model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0", |
| 669 | + streaming=False, |
| 670 | + cache_tools="default", |
| 671 | + cache_tools_ttl="1h", |
| 672 | + cache_config=CacheConfig(strategy="auto", ttl="1h"), |
| 673 | + ) |
| 674 | + |
| 675 | + # Timestamp-based uniqueness to avoid cache conflicts across CI runs |
| 676 | + unique_id = str(int(time.time() * 1000000)) |
| 677 | + large_context = f"Background context for test {unique_id}: " + ("This is important context. " * 1000) |
| 678 | + |
| 679 | + # User-supplied 1h cache point on system prompt — third checkpoint also at 1h |
| 680 | + system_prompt_with_cache = [ |
| 681 | + {"text": large_context}, |
| 682 | + {"cachePoint": {"type": "default", "ttl": "1h"}}, |
| 683 | + {"text": "You are a helpful assistant."}, |
| 684 | + ] |
| 685 | + |
| 686 | + @strands.tool |
| 687 | + def echo(value: str) -> str: |
| 688 | + """Echo the given value back.""" |
| 689 | + return value |
| 690 | + |
| 691 | + agent = Agent( |
| 692 | + model=model, |
| 693 | + system_prompt=system_prompt_with_cache, |
| 694 | + tools=[echo], |
| 695 | + load_tools_from_directory=False, |
| 696 | + ) |
| 697 | + |
| 698 | + # Must succeed without ValidationException on the non-increasing TTL rule |
| 699 | + result = agent("What is 2+2?") |
| 700 | + assert len(str(result)) > 0 |
0 commit comments