mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
256 lines
7.7 KiB
Python
256 lines
7.7 KiB
Python
import logging
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from exchange.content import Text, ToolResult, ToolUse
|
|
from exchange.message import Message
|
|
from exchange.providers.base import MissingProviderEnvVariableError
|
|
from exchange.providers.bedrock import BedrockProvider
|
|
from exchange.tool import Tool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"env_var_name",
|
|
[
|
|
("AWS_ACCESS_KEY_ID"),
|
|
("AWS_SECRET_ACCESS_KEY"),
|
|
("AWS_SESSION_TOKEN"),
|
|
],
|
|
)
|
|
def test_from_env_throw_error_when_missing_env_var(env_var_name):
|
|
with patch.dict(
|
|
os.environ,
|
|
{
|
|
"AWS_ACCESS_KEY_ID": "test_access_key_id",
|
|
"AWS_SECRET_ACCESS_KEY": "test_secret_access_key",
|
|
"AWS_SESSION_TOKEN": "test_session_token",
|
|
},
|
|
clear=True,
|
|
):
|
|
os.environ.pop(env_var_name)
|
|
with pytest.raises(MissingProviderEnvVariableError) as context:
|
|
BedrockProvider.from_env()
|
|
assert context.value.provider == "bedrock"
|
|
assert context.value.env_variable == env_var_name
|
|
assert context.value.message == f"Missing environment variable: {env_var_name} for provider bedrock."
|
|
|
|
|
|
@pytest.fixture
|
|
@patch.dict(
|
|
os.environ,
|
|
{
|
|
"AWS_REGION": "us-east-1",
|
|
"AWS_ACCESS_KEY_ID": "fake-access-key",
|
|
"AWS_SECRET_ACCESS_KEY": "fake-secret-key",
|
|
"AWS_SESSION_TOKEN": "fake-session-token",
|
|
},
|
|
)
|
|
def bedrock_provider():
|
|
return BedrockProvider.from_env()
|
|
|
|
|
|
@patch("time.time", return_value=1624250000)
|
|
def test_sign_and_get_headers(mock_time, bedrock_provider):
|
|
# Create sample values
|
|
method = "POST"
|
|
url = "https://bedrock-runtime.us-east-1.amazonaws.com/some/path"
|
|
payload = {"key": "value"}
|
|
service = "bedrock"
|
|
# Generate headers
|
|
headers = bedrock_provider.client.sign_and_get_headers(
|
|
method,
|
|
url,
|
|
payload,
|
|
service,
|
|
)
|
|
# Assert that headers contain expected keys
|
|
assert "Authorization" in headers
|
|
assert "Content-Type" in headers
|
|
assert "X-Amz-date" in headers
|
|
assert "x-amz-content-sha256" in headers
|
|
assert "X-Amz-Security-Token" in headers
|
|
|
|
|
|
@patch("httpx.Client.post")
|
|
def test_complete(mock_post, bedrock_provider):
|
|
# Mocked response from the server
|
|
mock_response = {
|
|
"output": {"message": {"role": "assistant", "content": [{"text": "Hello, world!"}]}},
|
|
"usage": {"inputTokens": 10, "outputTokens": 15, "totalTokens": 25},
|
|
}
|
|
mock_post.return_value.json.return_value = mock_response
|
|
|
|
model = "test-model"
|
|
system = "You are a helpful assistant."
|
|
messages = [Message.user("Hello")]
|
|
tools = ()
|
|
|
|
reply_message, reply_usage = bedrock_provider.complete(model=model, system=system, messages=messages, tools=tools)
|
|
|
|
# Assertions for reply message
|
|
assert reply_message.content[0].text == "Hello, world!"
|
|
assert reply_usage.total_tokens == 25
|
|
|
|
|
|
def test_message_to_bedrock_spec_text(bedrock_provider):
|
|
message = Message(role="user", content=[Text("Hello, world!")])
|
|
expected = {"role": "user", "content": [{"text": "Hello, world!"}]}
|
|
assert bedrock_provider.message_to_bedrock_spec(message) == expected
|
|
|
|
|
|
def test_message_to_bedrock_spec_tool_use(bedrock_provider):
|
|
tool_use = ToolUse(id="tool-1", name="WordCount", parameters={"text": "Hello, world!"})
|
|
message = Message(role="assistant", content=[tool_use])
|
|
expected = {
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"toolUse": {
|
|
"toolUseId": "tool-1",
|
|
"name": "WordCount",
|
|
"input": {"text": "Hello, world!"},
|
|
}
|
|
}
|
|
],
|
|
}
|
|
assert bedrock_provider.message_to_bedrock_spec(message) == expected
|
|
|
|
|
|
def test_message_to_bedrock_spec_tool_result(bedrock_provider):
|
|
message = Message(
|
|
role="assistant",
|
|
content=[ToolUse(id="tool-1", name="WordCount", parameters={"text": "Hello, world!"})],
|
|
)
|
|
expected = {
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"toolUse": {
|
|
"toolUseId": "tool-1",
|
|
"name": "WordCount",
|
|
"input": {"text": "Hello, world!"},
|
|
}
|
|
}
|
|
],
|
|
}
|
|
assert bedrock_provider.message_to_bedrock_spec(message) == expected
|
|
|
|
|
|
def test_message_to_bedrock_spec_tool_result_text(bedrock_provider):
|
|
tool_result = ToolResult(tool_use_id="tool-1", output="Error occurred", is_error=True)
|
|
message = Message(role="user", content=[tool_result])
|
|
expected = {
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"toolResult": {
|
|
"toolUseId": "tool-1",
|
|
"content": [{"text": "Error occurred"}],
|
|
"status": "error",
|
|
}
|
|
}
|
|
],
|
|
}
|
|
assert bedrock_provider.message_to_bedrock_spec(message) == expected
|
|
|
|
|
|
def test_message_to_bedrock_spec_invalid(bedrock_provider):
|
|
with pytest.raises(Exception):
|
|
bedrock_provider.message_to_bedrock_spec(Message(role="user", content=[]))
|
|
|
|
|
|
def test_response_to_message_text(bedrock_provider):
|
|
response = {"role": "user", "content": [{"text": "Hello, world!"}]}
|
|
message = bedrock_provider.response_to_message(response)
|
|
assert message.role == "user"
|
|
assert message.content[0].text == "Hello, world!"
|
|
|
|
|
|
def test_response_to_message_tool_use(bedrock_provider):
|
|
response = {
|
|
"role": "assistant",
|
|
"content": [
|
|
{
|
|
"toolUse": {
|
|
"toolUseId": "tool-1",
|
|
"name": "WordCount",
|
|
"input": {"text": "Hello, world!"},
|
|
}
|
|
}
|
|
],
|
|
}
|
|
message = bedrock_provider.response_to_message(response)
|
|
assert message.role == "assistant"
|
|
assert message.content[0].name == "WordCount"
|
|
assert message.content[0].parameters == {"text": "Hello, world!"}
|
|
|
|
|
|
def test_response_to_message_tool_result(bedrock_provider):
|
|
response = {
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"toolResult": {
|
|
"toolResultId": "tool-1",
|
|
"content": [{"json": {"result": 2}}],
|
|
}
|
|
}
|
|
],
|
|
}
|
|
message = bedrock_provider.response_to_message(response)
|
|
assert message.role == "user"
|
|
assert message.content[0].tool_use_id == "tool-1"
|
|
assert message.content[0].output == {"result": 2}
|
|
|
|
|
|
def test_response_to_message_invalid(bedrock_provider):
|
|
with pytest.raises(Exception):
|
|
bedrock_provider.response_to_message({})
|
|
|
|
|
|
def test_tools_to_bedrock_spec(bedrock_provider):
|
|
def word_count(text: str):
|
|
return len(text.split())
|
|
|
|
tool = Tool(
|
|
name="WordCount",
|
|
description="Counts words.",
|
|
parameters={"text": "string"},
|
|
function=word_count,
|
|
)
|
|
expected = {
|
|
"tools": [
|
|
{
|
|
"toolSpec": {
|
|
"name": "WordCount",
|
|
"description": "Counts words.",
|
|
"inputSchema": {"json": {"text": "string"}},
|
|
}
|
|
}
|
|
]
|
|
}
|
|
assert bedrock_provider.tools_to_bedrock_spec((tool,)) == expected
|
|
|
|
|
|
def test_tools_to_bedrock_spec_duplicate(bedrock_provider):
|
|
def word_count(text: str):
|
|
return len(text.split())
|
|
|
|
tool = Tool(
|
|
name="WordCount",
|
|
description="Counts words.",
|
|
parameters={"text": "string"},
|
|
function=word_count,
|
|
)
|
|
tool_duplicate = Tool(
|
|
name="WordCount",
|
|
description="Counts words.",
|
|
parameters={"text": "string"},
|
|
function=word_count,
|
|
)
|
|
tools = bedrock_provider.tools_to_bedrock_spec((tool, tool_duplicate))
|
|
assert set(tool["toolSpec"]["name"] for tool in tools["tools"]) == {"WordCount"}
|