patchpal 0.4.2__tar.gz → 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {patchpal-0.4.2/patchpal.egg-info → patchpal-0.4.4}/PKG-INFO +1 -1
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/__init__.py +1 -1
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/agent.py +51 -10
- {patchpal-0.4.2 → patchpal-0.4.4/patchpal.egg-info}/PKG-INFO +1 -1
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_agent.py +62 -18
- {patchpal-0.4.2 → patchpal-0.4.4}/LICENSE +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/MANIFEST.in +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/README.md +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/cli.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/context.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/permissions.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/skills.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/system_prompt.md +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal/tools.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal.egg-info/SOURCES.txt +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal.egg-info/dependency_links.txt +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal.egg-info/entry_points.txt +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal.egg-info/requires.txt +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/patchpal.egg-info/top_level.txt +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/pyproject.toml +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/setup.cfg +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_cli.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_context.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_guardrails.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_operational_safety.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_skills.py +0 -0
- {patchpal-0.4.2 → patchpal-0.4.4}/tests/test_tools.py +0 -0
|
@@ -714,8 +714,8 @@ def _supports_prompt_caching(model_id: str) -> bool:
|
|
|
714
714
|
# Anthropic models support caching (direct API or via Bedrock)
|
|
715
715
|
if "anthropic" in model_id.lower() or "claude" in model_id.lower():
|
|
716
716
|
return True
|
|
717
|
-
# Bedrock
|
|
718
|
-
if model_id.startswith("bedrock/") and "
|
|
717
|
+
# Bedrock Nova models support caching
|
|
718
|
+
if model_id.startswith("bedrock/") and "amazon.nova" in model_id.lower():
|
|
719
719
|
return True
|
|
720
720
|
return False
|
|
721
721
|
|
|
@@ -738,11 +738,13 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
738
738
|
return messages
|
|
739
739
|
|
|
740
740
|
# Determine cache marker format based on provider
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
741
|
+
# Anthropic models (direct or via Bedrock) use cache_control
|
|
742
|
+
# Other Bedrock models (Nova, etc.) use cachePoint
|
|
743
|
+
if model_id.startswith("bedrock/") and "anthropic" not in model_id.lower():
|
|
744
|
+
# Non-Anthropic Bedrock models (Nova, etc.) use cachePoint
|
|
745
|
+
cache_marker = {"cachePoint": {"type": "default"}}
|
|
744
746
|
else:
|
|
745
|
-
#
|
|
747
|
+
# Anthropic models (direct or via Bedrock) use cache_control
|
|
746
748
|
cache_marker = {"cache_control": {"type": "ephemeral"}}
|
|
747
749
|
|
|
748
750
|
# Find system messages (usually at the start)
|
|
@@ -756,13 +758,52 @@ def _apply_prompt_caching(messages: List[Dict[str, Any]], model_id: str) -> List
|
|
|
756
758
|
|
|
757
759
|
# Apply caching to system messages (first 2)
|
|
758
760
|
for idx in system_messages[:2]:
|
|
759
|
-
|
|
760
|
-
|
|
761
|
+
msg = messages[idx]
|
|
762
|
+
# Skip if already has cache marker at content block level
|
|
763
|
+
if isinstance(msg.get("content"), list):
|
|
764
|
+
# Already structured - check if any block has cache_control/cachePoint
|
|
765
|
+
has_cache = any(
|
|
766
|
+
"cache_control" in block or "cachePoint" in block
|
|
767
|
+
for block in msg["content"]
|
|
768
|
+
if isinstance(block, dict)
|
|
769
|
+
)
|
|
770
|
+
if not has_cache and msg["content"]:
|
|
771
|
+
# Add cache marker to the last content block
|
|
772
|
+
last_block = msg["content"][-1]
|
|
773
|
+
if isinstance(last_block, dict):
|
|
774
|
+
last_block.update(cache_marker)
|
|
775
|
+
else:
|
|
776
|
+
# Convert simple string content to structured format with cache marker
|
|
777
|
+
content_text = msg.get("content", "")
|
|
778
|
+
messages[idx] = {
|
|
779
|
+
**msg,
|
|
780
|
+
"content": [{"type": "text", "text": content_text, **cache_marker}],
|
|
781
|
+
}
|
|
761
782
|
|
|
762
783
|
# Apply caching to last 2 messages
|
|
763
784
|
for idx in last_two_indices:
|
|
764
|
-
|
|
765
|
-
|
|
785
|
+
msg = messages[idx]
|
|
786
|
+
# Skip if already has cache marker at content block level
|
|
787
|
+
if isinstance(msg.get("content"), list):
|
|
788
|
+
# Already structured - check if any block has cache_control/cachePoint
|
|
789
|
+
has_cache = any(
|
|
790
|
+
"cache_control" in block or "cachePoint" in block
|
|
791
|
+
for block in msg["content"]
|
|
792
|
+
if isinstance(block, dict)
|
|
793
|
+
)
|
|
794
|
+
if not has_cache and msg["content"]:
|
|
795
|
+
# Add cache marker to the last content block
|
|
796
|
+
last_block = msg["content"][-1]
|
|
797
|
+
if isinstance(last_block, dict):
|
|
798
|
+
last_block.update(cache_marker)
|
|
799
|
+
else:
|
|
800
|
+
# Convert simple string content to structured format with cache marker
|
|
801
|
+
content_text = msg.get("content", "")
|
|
802
|
+
if content_text: # Only convert non-empty content
|
|
803
|
+
messages[idx] = {
|
|
804
|
+
**msg,
|
|
805
|
+
"content": [{"type": "text", "text": content_text, **cache_marker}],
|
|
806
|
+
}
|
|
766
807
|
|
|
767
808
|
return messages
|
|
768
809
|
|
|
@@ -422,7 +422,11 @@ def test_prompt_caching_detection():
|
|
|
422
422
|
assert _supports_prompt_caching("bedrock/anthropic.claude-sonnet-4-5-v1:0")
|
|
423
423
|
assert _supports_prompt_caching("bedrock/anthropic.claude-v2")
|
|
424
424
|
|
|
425
|
-
#
|
|
425
|
+
# Bedrock Nova models should support caching
|
|
426
|
+
assert _supports_prompt_caching("bedrock/amazon.nova-pro-v1:0")
|
|
427
|
+
assert _supports_prompt_caching("bedrock/amazon.nova-lite-v1:0")
|
|
428
|
+
|
|
429
|
+
# Non-Anthropic/Nova models should not support caching
|
|
426
430
|
assert not _supports_prompt_caching("openai/gpt-4o")
|
|
427
431
|
assert not _supports_prompt_caching("ollama_chat/llama3.1")
|
|
428
432
|
|
|
@@ -441,17 +445,21 @@ def test_prompt_caching_application_anthropic():
|
|
|
441
445
|
# Test with direct Anthropic API
|
|
442
446
|
cached_messages = _apply_prompt_caching(messages.copy(), "anthropic/claude-sonnet-4-5")
|
|
443
447
|
|
|
444
|
-
# System message should have cache_control
|
|
445
|
-
assert
|
|
446
|
-
assert cached_messages[0]["
|
|
448
|
+
# System message should have cache_control inside content block
|
|
449
|
+
assert isinstance(cached_messages[0]["content"], list)
|
|
450
|
+
assert cached_messages[0]["content"][0]["type"] == "text"
|
|
451
|
+
assert "cache_control" in cached_messages[0]["content"][0]
|
|
452
|
+
assert cached_messages[0]["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
447
453
|
|
|
448
|
-
# Last 2 messages should have cache_control
|
|
449
|
-
assert
|
|
450
|
-
assert "cache_control" in cached_messages[-
|
|
454
|
+
# Last 2 messages should have cache_control inside content blocks
|
|
455
|
+
assert isinstance(cached_messages[-1]["content"], list) # Last user message
|
|
456
|
+
assert "cache_control" in cached_messages[-1]["content"][0]
|
|
457
|
+
assert isinstance(cached_messages[-2]["content"], list) # Last assistant message
|
|
458
|
+
assert "cache_control" in cached_messages[-2]["content"][0]
|
|
451
459
|
|
|
452
460
|
|
|
453
|
-
def
|
|
454
|
-
"""Test that prompt caching markers use
|
|
461
|
+
def test_prompt_caching_application_bedrock_anthropic():
|
|
462
|
+
"""Test that prompt caching markers use cache_control for Bedrock Anthropic models."""
|
|
455
463
|
from patchpal.agent import _apply_prompt_caching
|
|
456
464
|
|
|
457
465
|
messages = [
|
|
@@ -461,18 +469,49 @@ def test_prompt_caching_application_bedrock():
|
|
|
461
469
|
{"role": "user", "content": "How are you?"},
|
|
462
470
|
]
|
|
463
471
|
|
|
464
|
-
# Test with Bedrock
|
|
472
|
+
# Test with Bedrock Anthropic model - should use cache_control (same as direct Anthropic)
|
|
465
473
|
cached_messages = _apply_prompt_caching(
|
|
466
474
|
messages.copy(), "bedrock/anthropic.claude-sonnet-4-5-v1:0"
|
|
467
475
|
)
|
|
468
476
|
|
|
469
|
-
# System message should have
|
|
470
|
-
assert
|
|
471
|
-
assert cached_messages[0]["
|
|
477
|
+
# System message should have cache_control inside content block (NOT cachePoint)
|
|
478
|
+
assert isinstance(cached_messages[0]["content"], list)
|
|
479
|
+
assert cached_messages[0]["content"][0]["type"] == "text"
|
|
480
|
+
assert "cache_control" in cached_messages[0]["content"][0]
|
|
481
|
+
assert cached_messages[0]["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
482
|
+
|
|
483
|
+
# Last 2 messages should have cache_control inside content blocks
|
|
484
|
+
assert isinstance(cached_messages[-1]["content"], list)
|
|
485
|
+
assert "cache_control" in cached_messages[-1]["content"][0]
|
|
486
|
+
assert isinstance(cached_messages[-2]["content"], list)
|
|
487
|
+
assert "cache_control" in cached_messages[-2]["content"][0]
|
|
472
488
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
489
|
+
|
|
490
|
+
def test_prompt_caching_application_bedrock_nova():
|
|
491
|
+
"""Test that prompt caching markers use cachePoint for Bedrock Nova models."""
|
|
492
|
+
from patchpal.agent import _apply_prompt_caching
|
|
493
|
+
|
|
494
|
+
messages = [
|
|
495
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
496
|
+
{"role": "user", "content": "Hello"},
|
|
497
|
+
{"role": "assistant", "content": "Hi there!"},
|
|
498
|
+
{"role": "user", "content": "How are you?"},
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
# Test with Bedrock Nova model - should use cachePoint
|
|
502
|
+
cached_messages = _apply_prompt_caching(messages.copy(), "bedrock/amazon.nova-pro-v1:0")
|
|
503
|
+
|
|
504
|
+
# System message should have cachePoint inside content block
|
|
505
|
+
assert isinstance(cached_messages[0]["content"], list)
|
|
506
|
+
assert cached_messages[0]["content"][0]["type"] == "text"
|
|
507
|
+
assert "cachePoint" in cached_messages[0]["content"][0]
|
|
508
|
+
assert cached_messages[0]["content"][0]["cachePoint"] == {"type": "default"}
|
|
509
|
+
|
|
510
|
+
# Last 2 messages should have cachePoint inside content blocks
|
|
511
|
+
assert isinstance(cached_messages[-1]["content"], list)
|
|
512
|
+
assert "cachePoint" in cached_messages[-1]["content"][0]
|
|
513
|
+
assert isinstance(cached_messages[-2]["content"], list)
|
|
514
|
+
assert "cachePoint" in cached_messages[-2]["content"][0]
|
|
476
515
|
|
|
477
516
|
|
|
478
517
|
def test_prompt_caching_no_modification_for_unsupported():
|
|
@@ -506,5 +545,10 @@ def test_prompt_caching_idempotent():
|
|
|
506
545
|
cached_once = _apply_prompt_caching(messages.copy(), "anthropic/claude-sonnet-4-5")
|
|
507
546
|
cached_twice = _apply_prompt_caching(cached_once.copy(), "anthropic/claude-sonnet-4-5")
|
|
508
547
|
|
|
509
|
-
# Should
|
|
510
|
-
assert cached_once == cached_twice
|
|
548
|
+
# Should have the same structure after second application
|
|
549
|
+
assert cached_once[0]["content"][0] == cached_twice[0]["content"][0]
|
|
550
|
+
assert cached_once[1]["content"][0] == cached_twice[1]["content"][0]
|
|
551
|
+
|
|
552
|
+
# Should only have one cache_control marker per message
|
|
553
|
+
assert len([k for k in cached_twice[0]["content"][0].keys() if "cache" in k]) == 1
|
|
554
|
+
assert len([k for k in cached_twice[1]["content"][0].keys() if "cache" in k]) == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|