Skip to content

Commit c992463

Browse files
Aligning Python implementation with TS
1 parent ca700c8 commit c992463

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

aws_lambda_powertools/event_handler/bedrock_agent_function.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import inspect
4+
import logging
45
import warnings
56
from collections.abc import Callable
67
from typing import Any, Literal, TypeVar
@@ -11,6 +12,8 @@
1112
# Define a generic type for the function
1213
T = TypeVar("T", bound=Callable[..., Any])
1314

15+
logger = logging.getLogger(__name__)
16+
1417

1518
class BedrockFunctionResponse:
1619
"""Response class for Bedrock Agent Functions.
@@ -156,6 +159,9 @@ def tool(
156159

157160
def decorator(func: T) -> T:
158161
function_name = name or func.__name__
162+
163+
logger.debug(f"Registering {function_name} tool")
164+
159165
if function_name in self._tools:
160166
warnings.warn(
161167
f"Tool '{function_name}' already registered. Overwriting with new definition.",
@@ -186,9 +192,32 @@ def _resolve(self) -> dict[str, Any]:
186192

187193
function_name = self.current_event.function
188194

195+
logger.debug(f"Resolving {function_name} tool")
196+
189197
try:
198+
parameters: dict[str, Any] = {}
190199
# Extract parameters from the event
191-
parameters = {param.name: param.value for param in getattr(self.current_event, "parameters", [])}
200+
for param in getattr(self.current_event, "parameters", []):
201+
param_type = getattr(param, "type", None)
202+
if param_type == "string":
203+
parameters[param.name] = str(param.value)
204+
elif param_type == "integer":
205+
try:
206+
parameters[param.name] = int(param.value)
207+
except (ValueError, TypeError):
208+
parameters[param.name] = param.value
209+
elif param_type == "number":
210+
try:
211+
parameters[param.name] = float(param.value)
212+
except (ValueError, TypeError):
213+
parameters[param.name] = param.value
214+
elif param_type == "boolean":
215+
if isinstance(param.value, str):
216+
parameters[param.name] = param.value.lower() == "true"
217+
else:
218+
parameters[param.name] = bool(param.value)
219+
else: # "array" or any other type
220+
parameters[param.name] = param.value
192221

193222
func = self._tools[function_name]["function"]
194223
# Filter parameters to only include those expected by the function
@@ -204,7 +233,8 @@ def _resolve(self) -> dict[str, Any]:
204233
return BedrockFunctionsResponseBuilder(result).build(self.current_event)
205234
except Exception as error:
206235
# Return a formatted error response
207-
error_response = BedrockFunctionResponse(body=f"Error: {str(error)}", response_state="FAILURE")
236+
logger.error(f"Error processing function: {function_name}", exc_info=True)
237+
error_response = BedrockFunctionResponse(body=f"Error: {error.__class__.__name__}: {str(error)}")
208238
return BedrockFunctionsResponseBuilder(error_response).build(self.current_event)
209239

210240
def append_context(self, **additional_context):

tests/functional/event_handler/required_dependencies/test_bedrock_agent_functions.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ def strict_function(required_param):
281281

282282
# Function should raise a TypeError due to missing required parameter
283283
assert "Error:" in result["response"]["functionResponse"]["responseBody"]["TEXT"]["body"]
284-
assert result["response"]["functionResponse"]["responseState"] == "FAILURE"
285284

286285

287286
def test_bedrock_agent_function_with_complex_return_type():
@@ -359,3 +358,67 @@ def test_resolve_with_no_current_event():
359358
# THEN a ValueError should be raised
360359
with pytest.raises(ValueError, match="No event to process"):
361360
app._resolve()
361+
362+
363+
def test_bedrock_agent_function_with_parameters_casting():
364+
# GIVEN a Bedrock Agent Function resolver
365+
app = BedrockAgentFunctionResolver()
366+
367+
@app.tool(description="Function that accepts parameters")
368+
def vacation_request(month: int, payment: float, approved: bool):
369+
# Store received parameters for assertion
370+
assert isinstance(month, int)
371+
assert isinstance(payment, float)
372+
assert isinstance(approved, bool)
373+
return "Vacation request"
374+
375+
# WHEN calling the event handler with parameters
376+
raw_event = load_event("bedrockAgentFunctionEvent.json")
377+
raw_event["function"] = "vacation_request"
378+
raw_event["parameters"] = [
379+
{"name": "month", "value": "3", "type": "integer"},
380+
{"name": "payment", "value": "1000.5", "type": "number"},
381+
{"name": "approved", "value": False, "type": "boolean"},
382+
]
383+
result = app.resolve(raw_event, {})
384+
385+
# THEN parameters should be correctly passed to the function
386+
assert "Vacation request" == result["response"]["functionResponse"]["responseBody"]["TEXT"]["body"]
387+
388+
389+
def test_bedrock_agent_function_with_parameters_casting_errors():
390+
# GIVEN a Bedrock Agent Function resolver
391+
app = BedrockAgentFunctionResolver()
392+
393+
@app.tool(description="Function that handles parameter casting errors")
394+
def process_data(id_product: str, quantity: int, price: float, available: bool, items: list):
395+
# Check that invalid values maintain their original types
396+
assert isinstance(id_product, str)
397+
# For invalid integer, the original string should be preserved
398+
assert quantity == "invalid_number"
399+
# For invalid float, the original string should be preserved
400+
assert price == "not_a_price"
401+
# For invalid boolean, should evaluate based on Python's bool rules
402+
assert isinstance(available, bool)
403+
assert not available
404+
# Arrays should remain as is
405+
assert isinstance(items, list)
406+
return "Processed with casting errors handled"
407+
408+
# WHEN calling the event handler with parameters that cause casting errors
409+
raw_event = load_event("bedrockAgentFunctionEvent.json")
410+
raw_event["function"] = "process_data"
411+
raw_event["parameters"] = [
412+
{"name": "id_product", "value": 12345, "type": "string"}, # Integer to string (should work)
413+
{"name": "quantity", "value": "invalid_number", "type": "integer"}, # Will cause ValueError
414+
{"name": "price", "value": "not_a_price", "type": "number"}, # Will cause ValueError
415+
{"name": "available", "value": "invalid_bool", "type": "boolean"}, # Not "true"/"false"
416+
{"name": "items", "value": ["item1", "item2"], "type": "array"}, # Array should remain as is
417+
]
418+
result = app.resolve(raw_event, {})
419+
420+
# THEN parameters should be handled properly despite casting errors
421+
assert (
422+
"Processed with casting errors handled"
423+
== result["response"]["functionResponse"]["responseBody"]["TEXT"]["body"]
424+
)

0 commit comments

Comments
 (0)