6
6
from typing import Any , Callable , Dict , List , Optional , Tuple , Union
7
7
8
8
import boto3
9
+ from botocore .config import Config as BotocoreConfig
9
10
from botocore .exceptions import ClientError
10
11
11
12
from .constants import BlobMessage , ConversationalMessage , MessageRole , RetrievalConfig
@@ -89,7 +90,11 @@ def my_llm(user_input: str, memories: List[Dict]) -> str:
89
90
"""
90
91
91
92
def __init__ (
92
- self , memory_id : str , region_name : Optional [str ] = None , boto3_session : Optional [boto3 .Session ] = None
93
+ self ,
94
+ memory_id : str ,
95
+ region_name : Optional [str ] = None ,
96
+ boto3_session : Optional [boto3 .Session ] = None ,
97
+ boto_client_config : Optional [BotocoreConfig ] = None ,
93
98
):
94
99
"""Initialize a MemorySessionManager instance.
95
100
@@ -99,28 +104,29 @@ def __init__(
99
104
will use the region from boto3_session or default session.
100
105
boto3_session: Optional boto3 Session to use. If provided and region_name
101
106
parameter is also specified, validation will ensure they match.
107
+ boto_client_config: Optional boto3 client configuration. If provided, will be
108
+ merged with default configuration including user agent.
102
109
103
110
Raises:
104
111
ValueError: If region_name parameter conflicts with boto3_session region.
105
112
"""
113
+ # Initialize core attributes
114
+ self ._memory_id = memory_id
115
+
116
+ # Setup session and validate region consistency
106
117
session = boto3_session if boto3_session else boto3 .Session ()
107
- session_region = session . region_name
118
+ self . region_name = self . _validate_and_resolve_region ( region_name , session )
108
119
109
- # Validate region consistency if both are provided
110
- if region_name and boto3_session and session_region and region_name != session_region :
111
- raise ValueError (
112
- f"Region mismatch: provided region_name '{ region_name } ' does not match "
113
- f"boto3_session region '{ session_region } '. Please ensure both "
114
- f"parameters specify the same region or omit the region_name parameter "
115
- f"to use the session's region."
116
- )
120
+ # Configure and create boto3 client
121
+ client_config = self ._build_client_config (boto_client_config )
122
+ self ._data_plane_client = session .client (
123
+ "bedrock-agentcore" , region_name = self .region_name , config = client_config
124
+ )
117
125
118
- # Use provided region_name or fall back to session region
119
- self .region_name = region_name or session_region
120
- self ._memory_id = memory_id
121
- self ._data_plane_client = session .client ("bedrock-agentcore" , region_name = self .region_name )
126
+ # Configure timestamp serialization to use float representation
127
+ self ._configure_timestamp_serialization ()
122
128
123
- # AgentCore Memory data plane methods
129
+ # Define allowed data plane methods
124
130
self ._ALLOWED_DATA_PLANE_METHODS = {
125
131
"retrieve_memory_records" ,
126
132
"get_memory_record" ,
@@ -132,6 +138,70 @@ def __init__(
132
138
"list_events" ,
133
139
}
134
140
141
+ def _validate_and_resolve_region (self , region_name : Optional [str ], session : boto3 .Session ) -> str :
142
+ """Validate region consistency and resolve the final region to use.
143
+
144
+ Args:
145
+ region_name: Explicitly provided region name
146
+ session: Boto3 session instance
147
+
148
+ Returns:
149
+ The resolved region name to use
150
+
151
+ Raises:
152
+ ValueError: If region_name conflicts with session region
153
+ """
154
+ session_region = session .region_name
155
+
156
+ # Validate region consistency if both are provided
157
+ if region_name and session_region and isinstance (session_region , str ) and region_name != session_region :
158
+ raise ValueError (
159
+ f"Region mismatch: provided region_name '{ region_name } ' does not match "
160
+ f"boto3_session region '{ session_region } '. Please ensure both "
161
+ f"parameters specify the same region or omit the region_name parameter "
162
+ f"to use the session's region."
163
+ )
164
+
165
+ return region_name or session_region
166
+
167
+ def _build_client_config (self , boto_client_config : Optional [BotocoreConfig ]) -> BotocoreConfig :
168
+ """Build the final boto3 client configuration with SDK user agent.
169
+
170
+ Args:
171
+ boto_client_config: Optional user-provided client configuration
172
+
173
+ Returns:
174
+ Final client configuration with SDK user agent
175
+ """
176
+ sdk_user_agent = "bedrock-agentcore-sdk"
177
+
178
+ if boto_client_config :
179
+ existing_user_agent = getattr (boto_client_config , "user_agent_extra" , None )
180
+ if existing_user_agent :
181
+ new_user_agent = f"{ existing_user_agent } { sdk_user_agent } "
182
+ else :
183
+ new_user_agent = sdk_user_agent
184
+ return boto_client_config .merge (BotocoreConfig (user_agent_extra = new_user_agent ))
185
+ else :
186
+ return BotocoreConfig (user_agent_extra = sdk_user_agent )
187
+
188
+ def _configure_timestamp_serialization (self ) -> None :
189
+ """Configure the boto3 client to serialize timestamps as float values.
190
+
191
+ This method overrides the default timestamp serialization to convert datetime objects
192
+ to float timestamps (seconds since Unix epoch) which preserves millisecond precision
193
+ when sending datetime objects to the AgentCore Memory service.
194
+ """
195
+ original_serialize_timestamp = self ._data_plane_client ._serializer ._serializer ._serialize_type_timestamp
196
+
197
+ def serialize_timestamp_as_float (serialized , value , shape , name ):
198
+ if isinstance (value , datetime ):
199
+ serialized [name ] = value .timestamp () # Convert to float (seconds since epoch with fractional seconds)
200
+ else :
201
+ original_serialize_timestamp (serialized , value , shape , name )
202
+
203
+ self ._data_plane_client ._serializer ._serializer ._serialize_type_timestamp = serialize_timestamp_as_float
204
+
135
205
def __getattr__ (self , name : str ):
136
206
"""Dynamically forward method calls to the appropriate boto3 client.
137
207
0 commit comments