Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ed1e3ff
Adding AWS Bedrock Anthropic Claude target class
kmarsh77 Feb 7, 2025
63a9b2e
Adding unit tests for AWSBedrockClaudeTarget class
kmarsh77 Feb 7, 2025
5de223d
Add optional aws dependency (boto3)
kmarsh77 Feb 10, 2025
ac87b28
Update aws_bedrock_claude_target.py
kmarsh77 Feb 10, 2025
45785d6
Adding bedrock claude target class
kmarsh77 Feb 10, 2025
f7a8767
Update __init__.py for new target classes
kmarsh77 Feb 10, 2025
57252d0
Unit test for AWSBedrockClaudeChatTarget
kmarsh77 Feb 10, 2025
f254145
Delete pyrit/prompt_target/aws_bedrock_claude_target.py
kmarsh77 Feb 12, 2025
f0bc2bc
Update __init__.py
kmarsh77 Feb 12, 2025
2ebb519
Delete tests/unit/test_aws_bedrock_claude_target.py
kmarsh77 Feb 12, 2025
258f287
Update aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
408c308
Update test_aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
5865ac6
Update pyproject.toml
kmarsh77 Feb 12, 2025
01addd1
Update aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
627396a
Update pyrit/prompt_target/aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
59dcb7e
Update pyrit/prompt_target/aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
b5c4924
Update aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
5d8d7e0
Update aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
185bcff
Update aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
2630b43
Update test_aws_bedrock_claude_chat_target.py
kmarsh77 Feb 12, 2025
7bcb075
Merge branch 'main' into main
kmarsh77 Feb 19, 2025
6d0485d
Merge branch 'Azure:main' into main
kmarsh77 Feb 19, 2025
0e0e300
Updates to address complaints from pre-commit hooks
kmarsh77 Feb 20, 2025
187cb16
Merge branch 'main' into main
romanlutz Feb 25, 2025
3fd876b
Merge branch 'main' into main
romanlutz Feb 26, 2025
ef3ef17
Merge branch 'main' into main
romanlutz Feb 26, 2025
6d531d5
Update pyrit/prompt_target/aws_bedrock_claude_chat_target.py
romanlutz Feb 26, 2025
e7c3c54
Adding exceptions for when boto3 isn't installed
kmarsh77 Feb 27, 2025
8ddb596
Adding exceptions for when boto3 isn't installed
kmarsh77 Feb 27, 2025
b80cbda
Merge branch 'main' of https://github.com/kmarsh77/PyRIT
kmarsh77 Feb 27, 2025
b772a9c
Adding noqa statements to pass pre-commit checks
kmarsh77 Feb 28, 2025
e4b10d3
Merge branch 'Azure:main' into main
kmarsh77 Feb 28, 2025
d88919a
Update tests/unit/test_aws_bedrock_claude_chat_target.py
romanlutz Feb 28, 2025
fbf6a86
Merge branch 'main' into main
romanlutz Mar 9, 2025
ee1a220
Fixing merge conflict in pyproject.toml
kmarsh77 Mar 20, 2025
294cdc9
changing import error message
kmarsh77 Mar 20, 2025
0e84c64
Merge branch 'Azure:main' into main
kmarsh77 Mar 24, 2025
0915d70
Fixed invalid converted_value_data_type in test_aws_bedrock_claude_ch…
kmarsh77 Mar 24, 2025
37e92b5
Merge branch 'Azure:main' into main
kmarsh77 Apr 15, 2025
c913bd4
moving test_aws_bedrock_claude_chat_target.py to tests/unit/target fo…
kmarsh77 Apr 15, 2025
8bfed3d
Adding ignore statements after test_send_prompt_async and test_comple…
kmarsh77 Apr 15, 2025
9d3059b
Adding ignore after boto3 use in aws_bedrock_claude_chat_target.py
kmarsh77 Apr 15, 2025
b5f5df0
Removing ignore statements
kmarsh77 Apr 15, 2025
5541705
removing ignore statements
kmarsh77 Apr 15, 2025
e827cb4
putting boto3.client inside try statement
kmarsh77 Apr 15, 2025
78af630
fixing
kmarsh77 Apr 15, 2025
590167e
Moving boto3 import to within _complete_chat_async
kmarsh77 Apr 16, 2025
3d83b84
Merge branch 'Azure:main' into main
kmarsh77 Apr 16, 2025
b7fa256
Merge branch 'Azure:main' into main
kmarsh77 May 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions pyrit/prompt_target/aws_bedrock_claude_target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import logging
import json
import boto3
from typing import Optional
import asyncio

from botocore.exceptions import ClientError

from pyrit.models import PromptRequestResponse, construct_response_from_request
from pyrit.prompt_target import PromptTarget, limit_requests_per_minute

logger = logging.getLogger(__name__)

class AWSBedrockClaudeTarget(PromptTarget):
"""
This class initializes an AWS Bedrock target for any of the Anthropic Claude models.
Local AWS credentials (typically stored in ~/.aws) are used for authentication.
See the following for more information: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html

Parameters:
model_id (str): The model ID for target claude model
max_tokens (int): maximum number of tokens to generate
temperature (float, optional): The amount of randomness injected into the response.
top_p (float, optional): Use nucleus sampling
top_k (int, optional): Only sample from the top K options for each subsequent token
verify (bool, optional): whether or not to perform SSL certificate verification
"""

def __init__(
self,
*,
model_id: str,
max_tokens: int,
temperature: Optional[float] = None,
top_p: Optional[float] = None,
top_k: Optional[int] = None,
verify: bool = True,
max_requests_per_minute: Optional[int] = None,
) -> None:
super().__init__(max_requests_per_minute=max_requests_per_minute)

self._model_id = model_id
self._max_tokens = max_tokens
self._temperature = temperature
self._top_p = top_p
self._top_k = top_k
self._verify = verify

@limit_requests_per_minute
async def send_prompt_async(self, *, prompt_request: PromptRequestResponse) -> PromptRequestResponse:

self._validate_request(prompt_request=prompt_request)
request = prompt_request.request_pieces[0]

logger.info(f"Sending the following prompt to the prompt target: {request}")

response = await self._complete_text_async(request.converted_value)

response_entry = construct_response_from_request(request=request, response_text_pieces=[response])

return response_entry

def _validate_request(self, *, prompt_request: PromptRequestResponse) -> None:
if len(prompt_request.request_pieces) != 1:
raise ValueError("This target only supports a single prompt request piece.")

if prompt_request.request_pieces[0].converted_value_data_type != "text":
raise ValueError("This target only supports text prompt input.")

async def _complete_text_async(self, text: str) -> str:
brt = boto3.client(service_name="bedrock-runtime", verify=self._verify)

native_request = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": self._max_tokens,
"messages": [
{
"role": "user",
"content": text
}
]
}

if self._temperature:
native_request['temperature'] = self._temperature
if self._top_p:
native_request['top_p'] = self._top_p
if self._top_k:
native_request['top_k'] = self._top_k

request = json.dumps(native_request)

try:
#response = brt.invoke_model(modelId=self._model_id, body=request)
response = await asyncio.to_thread(brt.invoke_model, modelId=self._model_id, body=request)
except (ClientError, Exception) as e:
print(f"ERROR: Can't invoke '{self._model_id}'. Reason: {e}")
exit()

model_response = json.loads(response["body"].read())

answer = model_response["content"][0]["text"]

logger.info(f'Received the following response from the prompt target "{answer}"')
return answer
73 changes: 73 additions & 0 deletions tests/unit/test_aws_bedrock_claude_target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import pytest
import json
from unittest.mock import AsyncMock, patch, MagicMock

from pyrit.models import PromptRequestResponse, PromptRequestPiece
from pyrit.prompt_target.aws_bedrock_claude_target import AWSBedrockClaudeTarget

@pytest.fixture
def aws_target() -> AWSBedrockClaudeTarget:
return AWSBedrockClaudeTarget(
model_id="anthropic.claude-v2",
max_tokens=100,
temperature=0.7,
top_p=0.9,
top_k=50,
verify=True,
)

@pytest.fixture
def mock_prompt_request():
request_piece = PromptRequestPiece(
role="user",
original_value="Hello, Claude!",
converted_value="Hello, how are you?"
)
return PromptRequestResponse(request_pieces=[request_piece])

@pytest.mark.asyncio
async def test_send_prompt_async(aws_target, mock_prompt_request):
with patch("boto3.client", new_callable=MagicMock) as mock_boto:
mock_client = mock_boto.return_value
mock_client.invoke_model.return_value = {
"body": MagicMock(read=MagicMock(return_value=json.dumps({"content": [{"text": "I'm good, thanks!"}]})))
}

response = await aws_target.send_prompt_async(prompt_request=mock_prompt_request)

assert response.request_pieces[0].converted_value == "I'm good, thanks!"

@pytest.mark.asyncio
async def test_validate_request_valid(aws_target, mock_prompt_request):
aws_target._validate_request(prompt_request=mock_prompt_request)

@pytest.mark.asyncio
async def test_validate_request_invalid_multiple_pieces(aws_target):
request_pieces = [
PromptRequestPiece(role="user", original_value="test", converted_value="Text 1", converted_value_data_type="text"),
PromptRequestPiece(role="user", original_value="test", converted_value="Text 2", converted_value_data_type="text")
]
invalid_request = PromptRequestResponse(request_pieces=request_pieces)

with pytest.raises(ValueError, match="This target only supports a single prompt request piece."):
aws_target._validate_request(prompt_request=invalid_request)

@pytest.mark.asyncio
async def test_validate_request_invalid_data_type(aws_target):
request_pieces = [PromptRequestPiece(role="user", original_value="test", converted_value="ImageData", converted_value_data_type="image_path")]
invalid_request = PromptRequestResponse(request_pieces=request_pieces)

with pytest.raises(ValueError, match="This target only supports text prompt input."):
aws_target._validate_request(prompt_request=invalid_request)

@pytest.mark.asyncio
async def test_complete_text_async(aws_target):
with patch("boto3.client", new_callable=MagicMock) as mock_boto:
mock_client = mock_boto.return_value
mock_client.invoke_model.return_value = {
"body": MagicMock(read=MagicMock(return_value=json.dumps({"content": [{"text": "Test Response"}]})))
}

response = await aws_target._complete_text_async("Test Input")

assert response == "Test Response"