Skip to content

Commit 44544cf

Browse files
committed
feat(bedrock_agents): add optional fields to response payload
1 parent 8550f52 commit 44544cf

File tree

5 files changed

+144
-9
lines changed

5 files changed

+144
-9
lines changed

aws_lambda_powertools/event_handler/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
Response,
1212
)
1313
from aws_lambda_powertools.event_handler.appsync import AppSyncResolver
14-
from aws_lambda_powertools.event_handler.bedrock_agent import BedrockAgentResolver
14+
from aws_lambda_powertools.event_handler.bedrock_agent import BedrockAgentResolver, BedrockResponse
1515
from aws_lambda_powertools.event_handler.lambda_function_url import (
1616
LambdaFunctionUrlResolver,
1717
)
@@ -24,6 +24,7 @@
2424
"ALBResolver",
2525
"ApiGatewayResolver",
2626
"BedrockAgentResolver",
27+
"BedrockResponse",
2728
"CORSConfig",
2829
"LambdaFunctionUrlResolver",
2930
"Response",

aws_lambda_powertools/event_handler/bedrock_agent.py

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,43 @@
2121
from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent
2222

2323

24+
class BedrockResponse:
25+
"""
26+
Response class for Bedrock Agents Lambda functions.
27+
28+
Parameters
29+
----------
30+
status_code: int
31+
HTTP status code for the response
32+
body: Any
33+
Response body content
34+
content_type: str, optional
35+
Content type of the response (default: application/json)
36+
session_attributes: dict[str, Any], optional
37+
Session attributes to maintain state
38+
prompt_session_attributes: dict[str, Any], optional
39+
Prompt-specific session attributes
40+
knowledge_bases_configuration: dict[str, Any], optional
41+
Knowledge base configuration settings
42+
"""
43+
44+
def __init__(
45+
self,
46+
status_code: int,
47+
body: Any,
48+
content_type: str = "application/json",
49+
session_attributes: dict[str, Any] | None = None,
50+
prompt_session_attributes: dict[str, Any] | None = None,
51+
knowledge_bases_configuration: dict[str, Any] | None = None,
52+
) -> None:
53+
self.status_code = status_code
54+
self.body = body
55+
self.content_type = content_type
56+
self.session_attributes = session_attributes
57+
self.prompt_session_attributes = prompt_session_attributes
58+
self.knowledge_bases_configuration = knowledge_bases_configuration
59+
60+
2461
class BedrockResponseBuilder(ResponseBuilder):
2562
"""
2663
Bedrock Response Builder. This builds the response dict to be returned by Lambda when using Bedrock Agents.
@@ -30,14 +67,12 @@ class BedrockResponseBuilder(ResponseBuilder):
3067

3168
@override
3269
def build(self, event: BedrockAgentEvent, *args) -> dict[str, Any]:
33-
"""Build the full response dict to be returned by the lambda"""
70+
"""
71+
Build the response dictionary to be returned by the Lambda function.
72+
"""
3473
self._route(event, None)
3574

36-
body = self.response.body
37-
if self.response.is_json() and not isinstance(self.response.body, str):
38-
body = self.serializer(self.response.body)
39-
40-
return {
75+
base_response = {
4176
"messageVersion": "1.0",
4277
"response": {
4378
"actionGroup": event.action_group,
@@ -46,12 +81,44 @@ def build(self, event: BedrockAgentEvent, *args) -> dict[str, Any]:
4681
"httpStatusCode": self.response.status_code,
4782
"responseBody": {
4883
self.response.content_type: {
49-
"body": body,
84+
"body": self._get_formatted_body(),
5085
},
5186
},
5287
},
5388
}
5489

90+
if isinstance(self.response, BedrockResponse):
91+
self._add_bedrock_specific_configs(base_response)
92+
93+
return base_response
94+
95+
def _get_formatted_body(self) -> Any:
96+
"""Format the response body based on content type"""
97+
if not isinstance(self.response, BedrockResponse):
98+
if self.response.is_json() and not isinstance(self.response.body, str):
99+
return self.serializer(self.response.body)
100+
return self.response.body
101+
102+
def _add_bedrock_specific_configs(self, response: dict[str, Any]) -> None:
103+
"""
104+
Add Bedrock-specific configurations to the response if present.
105+
106+
Parameters
107+
----------
108+
response: dict[str, Any]
109+
The base response dictionary to be updated
110+
"""
111+
if not isinstance(self.response, BedrockResponse):
112+
return
113+
114+
optional_configs = {
115+
"sessionAttributes": self.response.session_attributes,
116+
"promptSessionAttributes": self.response.prompt_session_attributes,
117+
"knowledgeBasesConfiguration": self.response.knowledge_bases_configuration,
118+
}
119+
120+
response.update({k: v for k, v in optional_configs.items() if v is not None})
121+
55122

56123
class BedrockAgentResolver(ApiGatewayResolver):
57124
"""Bedrock Agent Resolver

docs/core/event_handler/bedrock_agents.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,19 @@ To implement these customizations, include extra parameters when defining your r
313313
--8<-- "examples/event_handler_bedrock_agents/src/customizing_bedrock_api_operations.py"
314314
```
315315

316+
### Fine grained responses
317+
318+
`BedrockResponse` class that provides full control over Bedrock Agent responses.
319+
320+
You can use this class to add additional fields as needed, such as [session attributes, prompt session attributes, and knowledge base configurations](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-response).
321+
322+
???+ info "Note"
323+
The default response only includes the essential fields to keep the payload size minimal, as AWS Lambda has a maximum response size of 25 KB.
324+
325+
```python title="bedrockresponse.py" title="Customzing your Bedrock Response"
326+
--8<-- "examples/event_handler_bedrock_agents/src/bedrockresponse.py"
327+
```
328+
316329
## Testing your code
317330

318331
Test your routes by passing an [Agent for Amazon Bedrock proxy event](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-lambda.html#agents-lambda-input) request:
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from http import HTTPStatus
2+
3+
from aws_lambda_powertools.event_handler import BedrockResponse
4+
5+
response = BedrockResponse(
6+
status_code=HTTPStatus.OK.value,
7+
body={"message": "Hello from Bedrock!"},
8+
session_attributes={"user_id": "123"},
9+
prompt_session_attributes={"context": "testing"},
10+
knowledge_bases_configuration={
11+
"knowledgeBaseId": "kb-123",
12+
"retrievalConfiguration": {"vectorSearchConfiguration": {"numberOfResults": 3, "overrideSearchType": "HYBRID"}},
13+
},
14+
)

tests/functional/event_handler/_pydantic/test_bedrock_agent.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from typing_extensions import Annotated
66

7-
from aws_lambda_powertools.event_handler import BedrockAgentResolver, Response, content_types
7+
from aws_lambda_powertools.event_handler import BedrockAgentResolver, BedrockResponse, Response, content_types
88
from aws_lambda_powertools.event_handler.openapi.params import Body
99
from aws_lambda_powertools.utilities.data_classes import BedrockAgentEvent
1010
from tests.functional.utils import load_event
@@ -200,3 +200,43 @@ def handler() -> Optional[Dict]:
200200
# THEN the schema must be a valid 3.0.3 version
201201
assert openapi30_schema(schema)
202202
assert schema.get("openapi") == "3.0.3"
203+
204+
205+
def test_bedrock_agent_with_bedrock_response():
206+
# GIVEN a Bedrock Agent resolver
207+
app = BedrockAgentResolver()
208+
209+
@app.get("/claims", description="Gets claims")
210+
def claims():
211+
return BedrockResponse(
212+
status_code=200,
213+
body={"output": claims_response},
214+
session_attributes={"last_request": "get_claims"},
215+
prompt_session_attributes={"context": "claims_query"},
216+
knowledge_bases_configuration={
217+
"knowledgeBaseId": "kb-123",
218+
"retrievalConfiguration": {"vectorSearchConfiguration": {"numberOfResults": 3}},
219+
},
220+
)
221+
222+
# WHEN calling the event handler
223+
result = app(load_event("bedrockAgentEvent.json"), {})
224+
225+
# THEN process event correctly
226+
assert result["messageVersion"] == "1.0"
227+
assert result["response"]["apiPath"] == "/claims"
228+
assert result["response"]["actionGroup"] == "ClaimManagementActionGroup"
229+
assert result["response"]["httpMethod"] == "GET"
230+
assert result["response"]["httpStatusCode"] == 200
231+
232+
# AND return the correct body
233+
body = result["response"]["responseBody"]["application/json"]["body"]
234+
assert json.loads(body) == {"output": claims_response}
235+
236+
# AND include the optional configurations
237+
assert result["sessionAttributes"] == {"last_request": "get_claims"}
238+
assert result["promptSessionAttributes"] == {"context": "claims_query"}
239+
assert result["knowledgeBasesConfiguration"] == {
240+
"knowledgeBaseId": "kb-123",
241+
"retrievalConfiguration": {"vectorSearchConfiguration": {"numberOfResults": 3}},
242+
}

0 commit comments

Comments
 (0)