bedrock-agentcore-starter-toolkit 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.

Files changed (25) hide show
  1. bedrock_agentcore_starter_toolkit/cli/__init__.py +1 -0
  2. bedrock_agentcore_starter_toolkit/cli/cli.py +2 -0
  3. bedrock_agentcore_starter_toolkit/cli/import_agent/README.md +35 -0
  4. bedrock_agentcore_starter_toolkit/cli/import_agent/__init__.py +1 -0
  5. bedrock_agentcore_starter_toolkit/cli/import_agent/agent_info.py +230 -0
  6. bedrock_agentcore_starter_toolkit/cli/import_agent/commands.py +518 -0
  7. bedrock_agentcore_starter_toolkit/operations/gateway/client.py +2 -2
  8. bedrock_agentcore_starter_toolkit/services/__init__.py +1 -0
  9. bedrock_agentcore_starter_toolkit/services/import_agent/__init__.py +1 -0
  10. bedrock_agentcore_starter_toolkit/services/import_agent/assets/memory_manager_template.py +207 -0
  11. bedrock_agentcore_starter_toolkit/services/import_agent/assets/requirements_langchain.j2 +9 -0
  12. bedrock_agentcore_starter_toolkit/services/import_agent/assets/requirements_strands.j2 +5 -0
  13. bedrock_agentcore_starter_toolkit/services/import_agent/assets/template_fixtures_merged.json +1102 -0
  14. bedrock_agentcore_starter_toolkit/services/import_agent/scripts/__init__.py +1 -0
  15. bedrock_agentcore_starter_toolkit/services/import_agent/scripts/base_bedrock_translate.py +1668 -0
  16. bedrock_agentcore_starter_toolkit/services/import_agent/scripts/bedrock_to_langchain.py +382 -0
  17. bedrock_agentcore_starter_toolkit/services/import_agent/scripts/bedrock_to_strands.py +374 -0
  18. bedrock_agentcore_starter_toolkit/services/import_agent/utils.py +417 -0
  19. bedrock_agentcore_starter_toolkit/utils/runtime/templates/execution_role_policy.json.j2 +2 -1
  20. {bedrock_agentcore_starter_toolkit-0.1.3.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/METADATA +22 -2
  21. {bedrock_agentcore_starter_toolkit-0.1.3.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/RECORD +25 -9
  22. {bedrock_agentcore_starter_toolkit-0.1.3.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/WHEEL +0 -0
  23. {bedrock_agentcore_starter_toolkit-0.1.3.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/entry_points.txt +0 -0
  24. {bedrock_agentcore_starter_toolkit-0.1.3.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/licenses/LICENSE.txt +0 -0
  25. {bedrock_agentcore_starter_toolkit-0.1.3.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/licenses/NOTICE.txt +0 -0
@@ -0,0 +1,1668 @@
1
+ """Base class for Bedrock Agent translation services.
2
+
3
+ This module provides a base class with common functionality for translating
4
+ AWS Bedrock Agent configurations into different frameworks.
5
+
6
+ Contains all the common logic between Langchain and Strands translations.
7
+ """
8
+
9
+ import io
10
+ import json
11
+ import os
12
+ import time
13
+ import uuid
14
+ import zipfile
15
+ from typing import Dict, Tuple
16
+
17
+ import autopep8
18
+ import boto3
19
+ from bedrock_agentcore.memory import MemoryClient
20
+ from openapi_schema_to_json_schema import to_json_schema
21
+
22
+ from ....operations.gateway import GatewayClient
23
+ from ..utils import (
24
+ clean_variable_name,
25
+ generate_pydantic_models,
26
+ get_base_dir,
27
+ get_template_fixtures,
28
+ prune_tool_name,
29
+ safe_substitute_placeholders,
30
+ unindent_by_one,
31
+ )
32
+
33
+
34
+ class BaseBedrockTranslator:
35
+ """Base class for Bedrock Agent translation services."""
36
+
37
+ def __init__(self, agent_config, debug: bool, output_dir: str, enabled_primitives: dict):
38
+ """Initialize the base translator with common configuration.
39
+
40
+ Args:
41
+ agent_config: The agent configuration dictionary
42
+ debug: Whether to enable debug mode
43
+ output_dir: The directory to output generated files
44
+ enabled_primitives: Dictionary of enabled primitives for the agent
45
+ """
46
+ self.agent_info = agent_config["agent"]
47
+ self.debug = debug
48
+ self.output_dir = output_dir
49
+ self.user_id = uuid.uuid4().hex[:8]
50
+ self.cleaned_agent_name = self.agent_info["agentName"].replace(" ", "_").replace("-", "_").lower()[:30]
51
+
52
+ # agent metadata
53
+ self.model_id = self.agent_info.get("foundationModel", "")
54
+ self.agent_region = self.agent_info["agentArn"].split(":")[3]
55
+ self.instruction = self.agent_info.get("instruction", "")
56
+ self.enabled_prompts = []
57
+ self.idle_timeout = self.agent_info.get("idleSessionTTLInSeconds", 600)
58
+
59
+ # memory
60
+ self.memory_config = self.agent_info.get("memoryConfiguration", {})
61
+ self.memory_enabled = bool(self.memory_config)
62
+ self.memory_enabled_types = self.memory_config.get("enabledMemoryTypes", [])
63
+
64
+ # kbs
65
+ self.knowledge_bases = agent_config.get("knowledge_bases", [])
66
+ self.single_kb = len(self.knowledge_bases) == 1
67
+ self.kb_generation_prompt_enabled = False
68
+ self.single_kb_optimization_enabled = False
69
+
70
+ # multi agent collaboration
71
+ self.multi_agent_enabled = (
72
+ self.agent_info.get("agentCollaboration", "DISABLED") != "DISABLED" and agent_config["collaborators"]
73
+ )
74
+ self.supervision_type = self.agent_info.get("agentCollaboration", "SUPERVISOR")
75
+ self.collaborators = agent_config.get("collaborators", [])
76
+ self.collaborator_map = {
77
+ collaborator.get("collaboratorName", ""): collaborator for collaborator in self.collaborators
78
+ }
79
+ self.collaborator_descriptions = [
80
+ f"{{'agentName': '{collaborator['agent'].get('agentName', '')}', 'collaboratorName (for invocation)': 'invoke_{collaborator.get('collaboratorName', '')}', 'collaboratorInstruction': '{collaborator.get('collaborationInstruction', '')}}}"
81
+ for collaborator in self.collaborators
82
+ ]
83
+ self.is_collaborator = "collaboratorName" in agent_config
84
+ self.is_accepting_relays = agent_config.get("relayConversationHistory", "DISABLED") == "TO_COLLABORATOR"
85
+ self.collaboration_instruction = agent_config.get("collaborationInstruction", "")
86
+ self.collaborator_name = agent_config.get("collaboratorName", "")
87
+
88
+ # action groups and tools
89
+ self.action_groups = [
90
+ group
91
+ for group in agent_config.get("action_groups", [])
92
+ if group.get("actionGroupState", "DISABLED") == "ENABLED"
93
+ ]
94
+ self.custom_ags = [group for group in self.action_groups if "parentActionSignature" not in group]
95
+ self.tools = []
96
+ self.mcp_tools = []
97
+ self.action_group_tools = []
98
+
99
+ # user input and code interpreter
100
+ self.code_interpreter_enabled = any(
101
+ group["actionGroupName"] == "codeinterpreteraction" and group["actionGroupState"] == "ENABLED"
102
+ for group in self.action_groups
103
+ )
104
+ self.user_input_enabled = any(
105
+ group["actionGroupName"] == "userinputaction" and group["actionGroupState"] == "ENABLED"
106
+ for group in self.action_groups
107
+ )
108
+
109
+ # orchestration steps
110
+ self.prompt_configs = self.agent_info.get("promptOverrideConfiguration", {}).get("promptConfigurations", [])
111
+
112
+ # guardrails
113
+ self.guardrail_config = {}
114
+ if "guardrailConfiguration" in self.agent_info:
115
+ guardrail_id = self.agent_info["guardrailConfiguration"].get("guardrailId", "")
116
+ guardrail_version = self.agent_info["guardrailConfiguration"].get("version", "")
117
+ if guardrail_id:
118
+ self.guardrail_config = {"guardrailIdentifier": guardrail_id, "guardrailVersion": guardrail_version}
119
+
120
+ # AgentCore
121
+ self.enabled_primitives = enabled_primitives
122
+ self.gateway_enabled = enabled_primitives.get("gateway", False) and self.custom_ags
123
+ self.gateway_cognito_result = {} # Initialize before create_gateway() call
124
+ self.created_gateway = self.create_gateway() if self.gateway_enabled else {}
125
+
126
+ self.agentcore_memory_enabled = enabled_primitives.get("memory", False) and self.memory_enabled
127
+ self.observability_enabled = enabled_primitives.get("observability", False)
128
+ self.code1p = enabled_primitives.get("code_interpreter", False) and self.code_interpreter_enabled
129
+
130
+ # Initialize imports
131
+ self.imports_code = """ # ---------- NOTE: This file is auto-generated by the Bedrock AgentCore Starter Toolkit. ----------
132
+ # Use this agent definition as a starting point for your custom agent implementation.
133
+ # Review the generated code, evaluate agent behavior, and make necessary changes before deploying.
134
+ # Extend the agent with additional tools, memory, and other features as required.
135
+ # -------------------------------------------------------------------------------------------------
136
+
137
+ import json, sys, os, re, io, uuid, asyncio
138
+ from typing import Union, Optional, Annotated, Dict, List, Any, Literal
139
+ from inputimeout import inputimeout, TimeoutOccurred # pylint: disable=import-error # type: ignore
140
+ from pydantic import BaseModel, Field
141
+ import boto3
142
+ from dotenv import load_dotenv
143
+
144
+ from bedrock_agentcore.runtime.context import RequestContext
145
+ from bedrock_agentcore import BedrockAgentCoreApp
146
+
147
+ load_dotenv()
148
+ """
149
+
150
+ # If this agent is not a collaborator, create a BedrockAgentCore entrypoint
151
+ if not self.is_collaborator:
152
+ self.imports_code += """
153
+ app = BedrockAgentCoreApp()
154
+ """
155
+
156
+ # Initialize code sections
157
+ self.prompts_code = ""
158
+ self.models_code = ""
159
+ self.tools_code = ""
160
+ self.memory_code = ""
161
+ self.kb_code = ""
162
+ self.collaboration_code = ""
163
+ self.agent_setup_code = ""
164
+ self.usage_code = ""
165
+
166
+ def _clean_fixtures_and_prompt(self, base_template, fixtures) -> Tuple[str, Dict]:
167
+ """Clean up the base template and fixtures by removing unused keys.
168
+
169
+ Args:
170
+ base_template: The template string to clean
171
+ fixtures: Dictionary of fixtures to clean
172
+
173
+ Returns:
174
+ Tuple containing the cleaned template and fixtures
175
+ """
176
+ removed_keys = []
177
+
178
+ # Remove KBs
179
+ if not self.knowledge_bases:
180
+ for key in list(fixtures.keys()):
181
+ if "knowledge_base" in key:
182
+ removed_keys.append(key)
183
+
184
+ # Remove Memory
185
+ if not self.memory_enabled_types:
186
+ for key in list(fixtures.keys()):
187
+ if key.startswith("$memory"):
188
+ removed_keys.append(key)
189
+
190
+ # Remove User Input
191
+ if not self.user_input_enabled:
192
+ removed_keys.append("$ask_user_missing_information$")
193
+ removed_keys.append("$respond_to_user_guideline$")
194
+
195
+ if not self.action_groups:
196
+ removed_keys.append("$prompt_session_attributes$")
197
+
198
+ if not self.code_interpreter_enabled:
199
+ removed_keys.append("$code_interpreter_guideline$")
200
+ removed_keys.append("$code_interpreter_files$")
201
+
202
+ for key in removed_keys:
203
+ if key in fixtures:
204
+ del fixtures[key]
205
+ base_template = base_template.replace(key, "")
206
+
207
+ return base_template, fixtures
208
+
209
+ def generate_prompt(self, config: Dict):
210
+ """Generate prompt code based on the configuration."""
211
+ prompt_type = config.get("promptType", "")
212
+ self.enabled_prompts.append(prompt_type)
213
+
214
+ if prompt_type == "ORCHESTRATION":
215
+ orchestration_fixtures = get_template_fixtures("orchestrationBasePrompts", "REACT_MULTI_ACTION")
216
+ orchestration_base_template: str = config["basePromptTemplate"]["system"]
217
+
218
+ orchestration_base_template, orchestration_fixtures = self._clean_fixtures_and_prompt(
219
+ orchestration_base_template, orchestration_fixtures
220
+ )
221
+
222
+ injected_orchestration_prompt = safe_substitute_placeholders(
223
+ orchestration_base_template, orchestration_fixtures
224
+ )
225
+ injected_orchestration_prompt = safe_substitute_placeholders(
226
+ injected_orchestration_prompt, {"instruction": self.instruction}
227
+ )
228
+ injected_orchestration_prompt = safe_substitute_placeholders(
229
+ injected_orchestration_prompt, {"$agent_collaborators$ ": ",".join(self.collaborator_descriptions)}
230
+ )
231
+
232
+ # This tool does not apply
233
+ injected_orchestration_prompt = injected_orchestration_prompt.replace(
234
+ "using the AgentCommunication__sendMessage tool", ""
235
+ )
236
+
237
+ self.prompts_code += f"""
238
+ ORCHESTRATION_TEMPLATE=\"""\n{injected_orchestration_prompt}\""" """
239
+
240
+ elif prompt_type == "MEMORY_SUMMARIZATION":
241
+ self.prompts_code += f"""
242
+ MEMORY_TEMPLATE=\"""\n
243
+ {config["basePromptTemplate"]["messages"][0]["content"]}
244
+ \"""
245
+ """
246
+ elif prompt_type == "PRE_PROCESSING":
247
+ self.prompts_code += f"""
248
+ PRE_PROCESSING_TEMPLATE=\"""\n
249
+ {config["basePromptTemplate"]["system"]}
250
+ \"""
251
+ """
252
+ elif prompt_type == "POST_PROCESSING":
253
+ self.prompts_code += f"""
254
+ POST_PROCESSING_TEMPLATE=\"""\n
255
+ {config["basePromptTemplate"]["messages"][0]["content"][0]["text"]}
256
+ \"""
257
+ """
258
+ elif prompt_type == "KNOWLEDGE_BASE_RESPONSE_GENERATION" and self.knowledge_bases:
259
+ self.kb_generation_prompt_enabled = True
260
+
261
+ self.prompts_code += f"""
262
+ KB_GENERATION_TEMPLATE=\"""\n
263
+ {config["basePromptTemplate"]}
264
+ \"""
265
+ """
266
+ elif prompt_type == "ROUTING_CLASSIFIER" and self.supervision_type == "SUPERVISOR_ROUTER":
267
+ routing_fixtures = get_template_fixtures("routingClassifierBasePrompt", "")
268
+ routing_template: str = config.get("basePromptTemplate", "")
269
+
270
+ injected_routing_template = safe_substitute_placeholders(routing_template, routing_fixtures)
271
+ injected_routing_template = safe_substitute_placeholders(
272
+ injected_routing_template, {"$reachable_agents$": ",".join(self.collaborator_descriptions)}
273
+ )
274
+ injected_routing_template = safe_substitute_placeholders(
275
+ injected_routing_template, {"$tools_for_routing$": str(self.action_group_tools + self.tools)}
276
+ )
277
+ injected_routing_template = safe_substitute_placeholders(
278
+ injected_routing_template, {"$knowledge_bases_for_routing$": str(self.knowledge_bases)}
279
+ )
280
+
281
+ self.prompts_code += f"""
282
+ ROUTING_TEMPLATE=\"""\n
283
+ {injected_routing_template}\"""
284
+ """
285
+
286
+ def generate_memory_configuration(self, memory_saver: str) -> str:
287
+ """Generate memory configuration for LangChain agent."""
288
+ # Short Term Memory
289
+ output = f"""
290
+ checkpointer_STM = {memory_saver}()
291
+ """
292
+
293
+ if self.agentcore_memory_enabled:
294
+ self.imports_code += "\nfrom bedrock_agentcore.memory import MemoryClient\n"
295
+
296
+ memory_client = MemoryClient(region_name=self.agent_region)
297
+
298
+ print(" Creating AgentCore Memory (This will take a few minutes)...")
299
+ memory = memory_client.create_memory_and_wait(
300
+ name=f"{self.cleaned_agent_name}_memory_{uuid.uuid4().hex[:3].lower()}",
301
+ strategies=[
302
+ {
303
+ "summaryMemoryStrategy": {
304
+ "name": "SessionSummarizer",
305
+ "namespaces": ["/summaries/{actorId}/{sessionId}"],
306
+ }
307
+ }
308
+ ],
309
+ )
310
+
311
+ memory_id = memory["id"]
312
+
313
+ output += f"""
314
+ memory_client = MemoryClient(region_name='{self.agent_region}')
315
+ memory_id = "{memory_id}"
316
+ """
317
+
318
+ elif self.memory_enabled:
319
+ memory_manager_path = os.path.join(self.output_dir, "LTM_memory_manager.py")
320
+ max_sessions = (
321
+ self.agent_info["memoryConfiguration"]
322
+ .get("sessionSummaryConfiguration", {})
323
+ .get("maxRecentSessions", 20)
324
+ )
325
+ max_days = self.agent_info["memoryConfiguration"].get("storageDays", 30)
326
+
327
+ with (
328
+ open(memory_manager_path, "a", encoding="utf-8") as target,
329
+ open(
330
+ os.path.join(get_base_dir(__file__), "assets", "memory_manager_template.py"),
331
+ "r",
332
+ encoding="utf-8",
333
+ ) as template,
334
+ ):
335
+ target.truncate(0)
336
+ for line in template:
337
+ target.write(line)
338
+
339
+ self.imports_code += """
340
+ from .LTM_memory_manager import LongTermMemoryManager"""
341
+
342
+ output += f"""
343
+ memory_manager = LongTermMemoryManager(llm_MEMORY_SUMMARIZATION, max_sessions = {max_sessions}, summarization_prompt = MEMORY_TEMPLATE, max_days = {max_days}, platform = {'"langchain"' if memory_saver == "InMemorySaver" else '"strands"'}, storage_path = "{self.output_dir}/session_summaries_{self.agent_info["agentName"]}.json")
344
+ """
345
+
346
+ return output
347
+
348
+ def generate_action_groups_code(self, platform: str) -> str:
349
+ """Generate code for action groups and tools."""
350
+ if not self.action_groups:
351
+ return ""
352
+
353
+ tool_code = ""
354
+ tool_instances = []
355
+
356
+ # OpenAPI and Function Action Groups
357
+ if self.gateway_enabled:
358
+ self.create_gateway_proxy_and_targets()
359
+
360
+ self.imports_code += "\nfrom bedrock_agentcore_starter_toolkit.operations.gateway import GatewayClient\n"
361
+ tool_code += f"""
362
+ gateway_client = GatewayClient(region_name="{self.agent_region}")
363
+ client_info = {{
364
+ "client_id": os.environ.get("cognito_client_id", ""),
365
+ "client_secret": os.environ.get("cognito_client_secret", ""),
366
+ "user_pool_id": os.environ.get("cognito_user_pool_id", ""),
367
+ "token_endpoint": os.environ.get("cognito_token_endpoint", ""),
368
+ "scope": os.environ.get("cognito_scope", ""),
369
+ "domain_prefix": os.environ.get("cognito_domain_prefix", ""),
370
+ }}
371
+
372
+ access_token = gateway_client.get_access_token_for_cognito(client_info)
373
+ """
374
+
375
+ if platform == "langchain":
376
+ self.imports_code += "\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\n"
377
+ tool_code += f"""
378
+ mcp_url = '{self.created_gateway.get("gatewayUrl", "")}'
379
+ headers = {{
380
+ "Content-Type": "application/json",
381
+ "Authorization": f"Bearer {{access_token}}",
382
+ }}
383
+
384
+ mcp_client = MultiServerMCPClient({{
385
+ "agent": {{
386
+ "transport": "streamable_http",
387
+ "url": mcp_url,
388
+ "headers": headers,
389
+ }}
390
+ }})
391
+
392
+ mcp_tools = asyncio.run(mcp_client.get_tools())
393
+ """
394
+ else:
395
+ self.imports_code += """
396
+ from mcp.client.streamable_http import streamablehttp_client
397
+ from strands.tools.mcp.mcp_client import MCPClient
398
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
399
+ """
400
+ tool_code += f"""
401
+ mcp_url = '{self.created_gateway.get("gatewayUrl", "")}'
402
+ headers = {{
403
+ "Content-Type": "application/json",
404
+ "Authorization": f"Bearer {{access_token}}",
405
+ }}
406
+
407
+ streamable_http_mcp_client = MCPClient(lambda: streamablehttp_client(mcp_url, headers=headers))
408
+
409
+ # To avoid erroring out on tool discovery
410
+ try:
411
+ def init_mcp():
412
+ streamable_http_mcp_client.start()
413
+ return streamable_http_mcp_client.list_tools_sync()
414
+
415
+ with ThreadPoolExecutor() as executor:
416
+ future = executor.submit(init_mcp)
417
+ mcp_tools = future.result(timeout=10)
418
+
419
+ except (FutureTimeoutError, Exception):
420
+ mcp_tools = []
421
+ """
422
+
423
+ remaining_action_groups = (
424
+ self.custom_ags
425
+ if not self.gateway_enabled
426
+ else [ag for ag in self.custom_ags if "lambda" not in ag.get("actionGroupExecutor", {})]
427
+ )
428
+
429
+ for action_group in remaining_action_groups:
430
+ additional_tool_instances = []
431
+ additional_code = ""
432
+
433
+ if action_group.get("apiSchema", False):
434
+ additional_tool_instances, additional_code = self.generate_openapi_ag_code(action_group, platform)
435
+
436
+ elif action_group.get("functionSchema", False):
437
+ additional_tool_instances, additional_code = self.generate_structured_ag_code(action_group, platform)
438
+
439
+ tool_code += additional_code
440
+ tool_instances.extend(additional_tool_instances)
441
+
442
+ # User Input Action Group
443
+ if self.user_input_enabled:
444
+ tool_code += """
445
+ # User Input Tool
446
+ @tool
447
+ def user_input_tool(user_targeted_question: str):
448
+ \"\"\"You can ask a human for guidance when you think you got stuck or you are not sure what to do next.
449
+ The input should be a question for the human. If you do not have the parameters to invoke a function,
450
+ then use this tool to ask the user for them.\"\"\"
451
+ return input(user_targeted_question)
452
+ """
453
+ tool_instances.append("user_input_tool")
454
+
455
+ # Code Interpreter Action Group
456
+ if self.code_interpreter_enabled:
457
+ tool_code += self.generate_code_interpreter(platform)
458
+ tool_instances.append("code_tool")
459
+
460
+ # Collect Action Group Tools
461
+ tool_code += f"""
462
+ action_group_tools = [{", ".join(tool_instances)}]\n"""
463
+ self.action_group_tools = tool_instances
464
+
465
+ return tool_code
466
+
467
+ def generate_openapi_ag_code(self, ag: Dict, platform: str) -> Tuple[list, str]:
468
+ """Generate code for OpenAPI Action Groups."""
469
+ tool_code = ""
470
+ tool_instances = []
471
+
472
+ executor_is_lambda = bool(ag["actionGroupExecutor"].get("lambda", False))
473
+ action_group_name = ag.get("actionGroupName", "")
474
+ action_group_desc = ag.get("description", "").replace('"', '\\"')
475
+
476
+ if executor_is_lambda:
477
+ lambda_arn = ag.get("actionGroupExecutor", {}).get("lambda", "")
478
+ lambda_region = lambda_arn.split(":")[3] if lambda_arn else "us-west-2"
479
+
480
+ openapi_schema = ag.get("apiSchema", {}).get("payload", {})
481
+
482
+ for func_name, func_spec in openapi_schema.get("paths", {}).items():
483
+ # Function metadata
484
+ clean_func_name = clean_variable_name(func_name)
485
+
486
+ for method, method_spec in func_spec.items():
487
+ # Naming
488
+ tool_name = prune_tool_name(f"{action_group_name}_{clean_func_name}_{method}")
489
+ param_model_name = f"{tool_name}_Params"
490
+ input_model_name = f"{tool_name}_Input"
491
+ request_model_name = ""
492
+
493
+ # Data
494
+ params = method_spec.get("parameters", [])
495
+ request_body = method_spec.get("requestBody", {})
496
+ content = request_body.get("content", {})
497
+ content_models = []
498
+
499
+ if params:
500
+ nested_schema, param_model_name = generate_pydantic_models(params, f"{tool_name}_Params")
501
+ tool_code += nested_schema
502
+
503
+ if request_body:
504
+ for content_type, content_schema in content.items():
505
+ content_type_safe = clean_variable_name(content_type)
506
+ model_name = f"{tool_name}_{content_type_safe}"
507
+
508
+ nested_schema, model_name = generate_pydantic_models(content_schema, model_name, content_type)
509
+ tool_code += nested_schema
510
+ content_models.append(model_name)
511
+
512
+ # Create a union model if there are multiple content models
513
+ if len(content_models) > 1:
514
+ request_model_name = f"{tool_name}_Request_Body"
515
+ tool_code += f"""
516
+
517
+ {request_model_name} = Union[{", ".join(content_models)}]"""
518
+ elif len(content_models) == 1:
519
+ request_model_name = next(iter(content_models))
520
+
521
+ # un-nest if only one type of input is provided
522
+ if params and content_models:
523
+ params_model_code = f"{param_model_name} |" if params else ""
524
+ request_model_code = (
525
+ f'request_body: {request_model_name} | None = Field(None, description = "Request body (ie. for a POST method) for this API Call")'
526
+ if content_models
527
+ else ""
528
+ )
529
+ tool_code += f"""
530
+ class {input_model_name}(BaseModel):
531
+ parameters: {params_model_code} None = Field(None, description = \"Parameters (ie. for a GET method) for this API Call\")
532
+ {request_model_code}
533
+ """
534
+ elif params:
535
+ input_model_name = param_model_name
536
+ elif content_models:
537
+ input_model_name = request_model_name
538
+ else:
539
+ input_model_name = "None"
540
+
541
+ func_desc = method_spec.get("description", method_spec.get("summary", "No Description Provided."))
542
+ func_desc += f"\\nThis tool is part of the group of tools called {action_group_name}{f' (description: {action_group_desc})' if action_group_desc else ''}."
543
+
544
+ schema_code_strands = (
545
+ f"inputSchema={input_model_name}.model_json_schema()" if input_model_name != "None" else ""
546
+ )
547
+ schema_code_langchain = f"args_schema={input_model_name}" if input_model_name != "None" else ""
548
+ tool_code += f"@tool({schema_code_strands if platform == 'strands' else schema_code_langchain})\n"
549
+
550
+ if executor_is_lambda:
551
+ tool_code += f"""
552
+
553
+ def {tool_name}({f"input_data: {input_model_name}" if input_model_name != "None" else ""}) -> str:
554
+ \"\"\"{func_desc}\"\"\"
555
+ lambda_client = boto3.client('lambda', region_name="{lambda_region}")
556
+ """
557
+ nested_code = """
558
+ request_body_dump = model_dump.get("request_body", model_dump)
559
+ content_type = request_body_dump.get("content_type_annotation", "*") if request_body_dump else None
560
+
561
+ request_body = {"content": {content_type: {"properties": []}}}
562
+ for param_name, param_value in request_body_dump.items():
563
+ if param_name != "content_type_annotation":
564
+ request_body["content"][content_type]["properties"].append({
565
+ "name": param_name,
566
+ "value": param_value
567
+ })
568
+ """
569
+
570
+ param_code = (
571
+ f"""model_dump = input_data.model_dump(exclude_unset = True)
572
+ model_dump = model_dump.get("parameters", model_dump)
573
+
574
+ for param_name, param_value in model_dump.items():
575
+ parameters.append({{
576
+ "name": param_name,
577
+ "value": param_value
578
+ }})
579
+ {nested_code if content_models else ""}"""
580
+ if input_model_name != "None"
581
+ else ""
582
+ )
583
+
584
+ content_model_code = """
585
+ if request_body:
586
+ payload["requestBody"] = request_body
587
+ """
588
+
589
+ tool_code += f"""
590
+
591
+ parameters = []
592
+
593
+ {param_code}
594
+
595
+ try:
596
+ payload = {{
597
+ "messageVersion": "1.0",
598
+ "agent": {{
599
+ "name": "{self.agent_info.get("agentName", "")}",
600
+ "id": "{self.agent_info.get("agentId", "")}",
601
+ "alias": "{self.agent_info.get("alias", "")}",
602
+ "version": "{self.agent_info.get("version", "")}"
603
+ }},
604
+ "sessionId": "",
605
+ "sessionAttributes": {{}},
606
+ "promptSessionAttributes": {{}},
607
+ "actionGroup": "{action_group_name}",
608
+ "apiPath": "{func_name}",
609
+ "inputText": last_input,
610
+ "httpMethod": "{method.upper()}",
611
+ "parameters": {"parameters" if param_model_name else "{}"}
612
+ }}
613
+
614
+ {content_model_code if content_models else ""}
615
+
616
+ response = lambda_client.invoke(
617
+ FunctionName="{lambda_arn}",
618
+ InvocationType='RequestResponse',
619
+ Payload=json.dumps(payload)
620
+ )
621
+
622
+ response_payload = json.loads(response['Payload'].read().decode('utf-8'))
623
+
624
+ return str(response_payload)
625
+
626
+ except Exception as e:
627
+ return f"Error executing {clean_func_name}/{method}: {{str(e)}}"
628
+ """
629
+ else:
630
+ tool_code += f"""
631
+ def {tool_name}(input_data) -> str:
632
+ \"\"\"{func_desc}\"\"\"
633
+ return input(f"Return of control: {tool_name} was called with the input {{input_data}}, enter desired output:")
634
+ """
635
+ tool_instances.append(tool_name)
636
+
637
+ return tool_instances, tool_code
638
+
639
+ def generate_structured_ag_code(self, ag: Dict, platform: str) -> Tuple[list, str]:
640
+ """Generate code for Structured Function Action Groups."""
641
+ tool_code = ""
642
+ tool_instances = []
643
+
644
+ executor_is_lambda = bool(ag["actionGroupExecutor"].get("lambda", False))
645
+ action_group_name = ag.get("actionGroupName", "")
646
+ action_group_desc = ag.get("description", "").replace('"', '\\"')
647
+
648
+ if executor_is_lambda:
649
+ lambda_arn = ag.get("actionGroupExecutor", {}).get("lambda", "")
650
+ lambda_region = lambda_arn.split(":")[3] if lambda_arn else "us-west-2"
651
+
652
+ function_schema = ag.get("functionSchema", {}).get("functions", [])
653
+
654
+ for func in function_schema:
655
+ # Function metadata
656
+ func_name = func.get("name", "")
657
+ clean_func_name = clean_variable_name(func_name)
658
+ func_desc = func.get("description", "").replace('"', '\\"')
659
+ func_desc += f"\\nThis tool is part of the group of tools called {action_group_name}" + (
660
+ f" (description: {action_group_desc})" if action_group_desc else ""
661
+ )
662
+
663
+ # Naming
664
+ tool_name = prune_tool_name(f"{action_group_name}_{clean_func_name}")
665
+ model_name = f"{action_group_name}_{clean_func_name}_Input"
666
+
667
+ # Parameter Signature Generation
668
+ params = func.get("parameters", {})
669
+ param_list = []
670
+
671
+ tool_code += f"""
672
+ class {model_name}(BaseModel):"""
673
+
674
+ if params:
675
+ for param_name, param_info in params.items():
676
+ param_type = param_info.get("type", "string")
677
+ param_desc = param_info.get("description", "").replace('"', '\\"')
678
+ required = param_info.get("required", False)
679
+
680
+ # Map JSON Schema types to Python types
681
+ type_mapping = {
682
+ "string": "str",
683
+ "number": "float",
684
+ "integer": "int",
685
+ "boolean": "bool",
686
+ "array": "list",
687
+ "object": "dict",
688
+ }
689
+ py_type = type_mapping.get(param_type, "str")
690
+ param_list.append(f"{param_name}: {py_type} = None")
691
+
692
+ if required:
693
+ tool_code += f"""
694
+ {param_name}: {py_type} = Field(..., description="{param_desc}")"""
695
+ else:
696
+ tool_code += f"""
697
+ {param_name}: {py_type} = Field(None, description="{param_desc}")"""
698
+ else:
699
+ tool_code += """
700
+ pass"""
701
+
702
+ param_signature = ", ".join(param_list)
703
+ params_input = ", ".join(
704
+ [
705
+ f"{{'name': '{param_name}', 'type': '{param_info.get('type', 'string')}', 'value': {param_name}}}"
706
+ for param_name, param_info in params.items()
707
+ ]
708
+ )
709
+
710
+ schema_code_strands = f"inputSchema={model_name}.model_json_schema()" if params else ""
711
+ schema_code_langchain = f"args_schema={model_name}" if params else ""
712
+ tool_code += f"""
713
+ @tool({schema_code_strands if platform == "strands" else schema_code_langchain})
714
+ """
715
+
716
+ # Tool Function Code Generation
717
+ if executor_is_lambda:
718
+ tool_code += f"""
719
+ def {tool_name}({param_signature}) -> str:
720
+ \"\"\"{func_desc}\"\"\"
721
+ lambda_client = boto3.client('lambda', region_name="{lambda_region}")
722
+
723
+ # Prepare parameters
724
+ parameters = [{params_input}]"""
725
+
726
+ # Lambda invocation code
727
+ tool_code += f"""
728
+
729
+ # Invoke Lambda function
730
+ try:
731
+ payload = {{
732
+ "actionGroup": "{action_group_name}",
733
+ "function": "{func_name}",
734
+ "inputText": last_input,
735
+ "parameters": parameters,
736
+ "agent": {{
737
+ "name": "{self.agent_info.get("agentName", "")}",
738
+ "id": "{self.agent_info.get("agentId", "")}",
739
+ "alias": "{self.agent_info.get("alias", "")}",
740
+ "version": "{self.agent_info.get("version", "")}"
741
+ }},
742
+ "sessionId": "",
743
+ "sessionAttributes": {{}},
744
+ "promptSessionAttributes": {{}},
745
+ "messageVersion": "1.0"
746
+ }}
747
+
748
+ response = lambda_client.invoke(
749
+ FunctionName="{lambda_arn}",
750
+ InvocationType='RequestResponse',
751
+ Payload=json.dumps(payload)
752
+ )
753
+
754
+ response_payload = json.loads(response['Payload'].read().decode('utf-8'))
755
+
756
+ return str(response_payload)
757
+
758
+ except Exception as e:
759
+ return f"Error executing {func_name}: {{str(e)}}"
760
+ """
761
+
762
+ else:
763
+ tool_code += f"""
764
+ def {tool_name}({param_signature}) -> str:
765
+ \"\"\"{func_desc}\"\"\"
766
+ return input(f"Return of control: {action_group_name}_{func_name} was called with the input {{{", ".join(params.keys())}}}, enter desired output:")
767
+ """
768
+
769
+ tool_instances.append(tool_name)
770
+
771
+ return tool_instances, tool_code
772
+
773
+ def generate_example_usage(self) -> str:
774
+ """Generate example usage code for the agent."""
775
+ memory_code = (
776
+ "LongTermMemoryManager.end_all_sessions()"
777
+ if self.memory_enabled and not self.agentcore_memory_enabled
778
+ else ""
779
+ )
780
+ run_code = "else: app.run()" if not self.is_collaborator else ""
781
+ return f"""
782
+
783
+ def cli():
784
+ global user_id
785
+ user_id = "{uuid.uuid4().hex[:8].lower()}" # change user_id if necessary
786
+ session_id = uuid.uuid4().hex[:8].lower()
787
+ try:
788
+ while True:
789
+ try:
790
+ query = inputimeout("\\nEnter your question (or 'exit' to quit): ", timeout={self.idle_timeout})
791
+
792
+ if query.lower() == "exit":
793
+ break
794
+
795
+ result = endpoint({{"message": query}}, RequestContext(session_id=session_id)).get('result', {{}})
796
+ if not result:
797
+ print(" Error:" + str(result.get('error', {{}})))
798
+ continue
799
+
800
+ print(f"\\nResponse: {{result.get('response', 'No response provided')}}")
801
+
802
+ if result["sources"]:
803
+ print(f" Sources: {{', '.join(set(result.get('sources', [])))}}")
804
+
805
+ if result["tools_used"]:
806
+ tools_used.update(result.get('tools_used', []))
807
+ print(f"\\n Tools Used: {{', '.join(tools_used)}}")
808
+
809
+ tools_used.clear()
810
+ except KeyboardInterrupt:
811
+ print("\\n\\nExiting...")
812
+ break
813
+ except TimeoutOccurred:
814
+ print("\\n\\nNo input received in the last {0} seconds. Exiting...")
815
+ break
816
+ except Exception as e:
817
+ print("\\n\\nError: {{}}".format(e))
818
+ finally:
819
+ {memory_code}
820
+ print("Session ended.")
821
+
822
+ if __name__ == "__main__":
823
+ if len(sys.argv) > 1 and sys.argv[1] == "--cli":
824
+ cli() # Run the CLI interface
825
+ {run_code}
826
+ """
827
+
828
+ def generate_code_interpreter(self, platform: str):
829
+ """Generate code for third-party code interpreter used in the agent."""
830
+ if not self.code1p:
831
+ self.imports_code += """
832
+ from interpreter import interpreter"""
833
+
834
+ return f"""
835
+
836
+ # Code Interpreter Tool
837
+ interpreter.llm.model = "bedrock/{self.model_id}"
838
+ interpreter.llm.supports_functions = True
839
+ interpreter.computer.emit_images = True
840
+ interpreter.llm.supports_vision = True
841
+ interpreter.auto_run = True
842
+ interpreter.messages = []
843
+ interpreter.anonymized_telemetry = False
844
+ interpreter.system_message += "USER NOTES: DO NOT give further clarification or remarks on the code, or ask the user any questions. DO NOT write long running code that awaits user input. Remember that you can write to files using cat. Remember to keep track of your current working directory. Output the code you wrote so that the parent agent calling you can use it as part of a larger answer. \\n" + interpreter.system_message
845
+
846
+ @tool
847
+ def code_tool(original_question: str) -> str:
848
+ \"""
849
+ INPUT: The original question asked by the user.
850
+ OUTPUT: The output of the code interpreter.
851
+ CAPABILITIES: writing custom code for difficult calculations or questions, executing system-level code to control the user's computer and accomplish tasks, and develop code for the user.
852
+
853
+ TOOL DESCRIPTION: This tool is capable of almost any code-enabled task. DO NOT pass code to this tool. Instead, call on it to write and execute any code safely.
854
+ Pass any and all coding tasks to this tool in the form of the original question you got from the user. It can handle tasks that involve writing, running,
855
+ testing, and troubleshooting code. Use it for system calls, generating and running code, and more.
856
+
857
+ EXAMPLES: Opening an application and performing tasks programatically, solving or calculating difficult questions via code, etc.
858
+
859
+ IMPORTANT: Before responding to the user that you cannot accomplish a task, think whether this tool can be used.
860
+ IMPORTANT: Do not tell the code interpreter to do long running tasks such as waiting for user input or running indefinitely.\"""
861
+ return interpreter.chat(original_question, display=False)
862
+ """
863
+ else:
864
+ self.imports_code += """
865
+ from bedrock_agentcore.tools import code_interpreter_client"""
866
+
867
+ code_1p = """
868
+ # Code Interpreter Tool
869
+ @tool
870
+ def code_tool(original_question: str):
871
+ \"""
872
+ INPUT: The original question asked by the user.
873
+ OUTPUT: The output of the code interpreter.
874
+ CAPABILITIES: writing custom code for difficult calculations or questions, executing system-level code to control the user's computer and accomplish tasks, and develop code for the user.
875
+
876
+ TOOL DESCRIPTION: This tool is capable of almost any code-enabled task. DO NOT pass code to this tool. Instead, call on it to write and execute any code safely.
877
+ Pass any and all coding tasks to this tool in the form of the original question you got from the user. It can handle tasks that involve writing, running,
878
+ testing, and troubleshooting code. Use it for system calls, generating and running code, and more.
879
+
880
+ EXAMPLES: Opening an application and performing tasks programatically, solving or calculating difficult questions via code, etc.
881
+
882
+ IMPORTANT: Before responding to the user that you cannot accomplish a task, think whether this tool can be used.
883
+ IMPORTANT: Do not tell the code interpreter to do long running tasks such as waiting for user input or running indefinitely.\"""
884
+
885
+ with code_interpreter_client.code_session(region="us-west-2") as session:
886
+ print(f"Session started with ID: {session.session_id}")
887
+ print(f"Code Interpreter Identifier: {session.identifier}")
888
+
889
+ def get_result(response):
890
+ if "stream" in response:
891
+ event_stream = response["stream"]
892
+
893
+ try:
894
+ for event in event_stream:
895
+ if "result" in event:
896
+ result = event["result"]
897
+
898
+ if result.get("isError", False):
899
+ return {"error": True, "message": result.get("content", "Unknown error")}
900
+ else:
901
+ return {"success": True, "content": result.get("content", {})}
902
+
903
+ return {"error": True, "message": "No result found in event stream"}
904
+
905
+ except Exception as e:
906
+ return {"error": True, "message": f"Failed to process event stream: {str(e)}"}
907
+
908
+ @tool
909
+ def execute_code(code: str, language: str):
910
+ \"""
911
+ Execute code in the code interpreter sandbox.
912
+ Args:
913
+ code (str): The code to execute in the sandbox. This should be a complete code snippet that can run
914
+ independently. If you created a file, pass the file content as a string.
915
+ language (str): The programming language of the code (e.g., "python", "javascript").
916
+ Returns:
917
+ dict: The response from the code interpreter service, including execution results or error messages.
918
+ Example:
919
+ code = "print('Hello, World!')"
920
+ language = "python"
921
+ \"""
922
+
923
+ response = session.invoke(method="executeCode", params={"code": code, "language": language})
924
+ return get_result(response)
925
+
926
+ @tool
927
+ def list_files(path: str) -> List[str]:
928
+ \"""
929
+ List files in the code interpreter sandbox.
930
+ Args:
931
+ path (str): The directory path to list files from in the sandbox.
932
+ Returns:
933
+ dict: The response from the code interpreter service, including file paths or error messages.
934
+ Example:
935
+ path = "/home/user/sandbox"
936
+ \"""
937
+
938
+ if not path:
939
+ path = "/"
940
+
941
+ response = session.invoke(method="listFiles", params={"path": path})
942
+ return get_result(response)
943
+
944
+ @tool
945
+ def read_files(file_paths: List[str]):
946
+ \"""
947
+ Read files from the code interpreter sandbox.
948
+ Args:
949
+ file_paths (List[str]): List of file paths to read from the sandbox.
950
+ Returns:
951
+ dict: The response from the code interpreter service, including file contents or error messages.
952
+ Example:
953
+ file_paths = ["example.txt", "script.py"]
954
+ \"""
955
+ response = session.invoke(method="readFiles", params={"paths": file_paths})
956
+ return get_result(response)
957
+
958
+ @tool
959
+ def write_files(files_to_create: List[Dict[str, str]]):
960
+ \"""
961
+ Write files to the code interpreter sandbox.
962
+ Args:
963
+ files_to_create (List[Dict[str, str]]): List of dictionaries with 'path' and 'text' keys,
964
+ where 'path' is the file path and 'text' is the content to write.
965
+ Returns:
966
+ dict: The response from the code interpreter service, including success status and error messages.
967
+ Example:
968
+ files_to_create = [{"path": "example.txt", "text": "Hello, World!"},
969
+ {"path": "script.py", "text": "print('Hello from script!')"}]
970
+ \"""
971
+
972
+ response = session.invoke(method="writeFiles", params={"content": files_to_create})
973
+ return get_result(response)
974
+
975
+ @tool
976
+ def remove_files(file_paths: List[str]):
977
+ \"""
978
+ Removes files from the code interpreter sandbox.
979
+ Args:
980
+ file_paths (List[str]): List of file paths to remove from the sandbox.
981
+ Returns:
982
+ dict: The response from the code interpreter service, including file contents or error messages.
983
+ Example:
984
+ file_paths = ["example.txt", "script.py"]
985
+ \"""
986
+ response = session.invoke(method="removeFiles", params={"paths": file_paths})
987
+ return get_result(response)
988
+
989
+ coding_tools = [
990
+ execute_code,
991
+ list_files,
992
+ read_files,
993
+ write_files,
994
+ remove_files,
995
+ ]
996
+
997
+ coding_prompt = \"""
998
+ You are a code interpreter tool that can execute code in various programming languages.
999
+ You'll be given a query that describes a coding task or question.
1000
+ You will write and execute code to answer the query.
1001
+ You can handle tasks that involve writing, running, testing, and troubleshooting code.
1002
+ You can handle errors and return results, making you useful for tasks that require code execution.
1003
+ You can run Python scripts, execute Java code, and more.
1004
+
1005
+ IMPORTANT: Ensure that the code is safe to execute and does not contain malicious content.
1006
+ IMPORTANT: Do not run indefinitely or wait for user input.
1007
+ IMPORTANT: After executing code and receiving results, you MUST provide a clear response that
1008
+ includes the answer to the user's question.
1009
+ IMPORTANT: Always respond with the actual result or answer, not just "I executed the code" or
1010
+ "The result is displayed above".
1011
+ IMPORTANT: If code execution produces output, include that output in your response to the user.
1012
+ \"""
1013
+ """
1014
+ if platform == "langchain":
1015
+ code_1p += """
1016
+ coding_agent = create_react_agent(model=llm_ORCHESTRATION, prompt=coding_prompt, tools=coding_tools)
1017
+ coding_agent_input = {"messages": [{"role": "user", "content": original_question}]}
1018
+
1019
+ return coding_agent.invoke(coding_agent_input)["messages"][-1].content
1020
+ """
1021
+ else:
1022
+ code_1p += """
1023
+ coding_agent = Agent(
1024
+ model=llm_ORCHESTRATION,
1025
+ system_prompt=coding_prompt,
1026
+ tools=coding_tools,
1027
+ )
1028
+
1029
+ return str(coding_agent(original_question))
1030
+ """
1031
+
1032
+ return code_1p
1033
+
1034
+ def generate_entrypoint_code(self, platform: str) -> str:
1035
+ """Generate entrypoint code for the agent."""
1036
+ entrypoint_code = ""
1037
+
1038
+ if not self.is_collaborator:
1039
+ entrypoint_code += """
1040
+ @app.entrypoint
1041
+ """
1042
+
1043
+ agentcore_memory_entrypoint_code = (
1044
+ """
1045
+ event = memory_client.create_event(
1046
+ memory_id=memory_id,
1047
+ actor_id=user_id,
1048
+ session_id=session_id,
1049
+ messages=formatted_messages
1050
+ )
1051
+ """
1052
+ if self.agentcore_memory_enabled
1053
+ else ""
1054
+ )
1055
+
1056
+ tools_used_update_code = (
1057
+ "tools_used.update(list(agent_result.metrics.tool_metrics.keys()))"
1058
+ if platform == "strands"
1059
+ else "tools_used.update([msg.name for msg in agent_result if isinstance(msg, ToolMessage)])"
1060
+ )
1061
+ response_content_code = "str(agent_result)" if platform == "strands" else "agent_result[-1].content"
1062
+
1063
+ entrypoint_code += f"""
1064
+ def endpoint(payload, context):
1065
+ try:
1066
+ {"global user_id" if self.agentcore_memory_enabled else ""}
1067
+ {'user_id = user_id or payload.get("userId", uuid.uuid4().hex[:8])' if self.agentcore_memory_enabled else ""}
1068
+ session_id = context.session_id or payload.get("sessionId", uuid.uuid4().hex[:8])
1069
+
1070
+ tools_used.clear()
1071
+ agent_query = payload.get("message", "")
1072
+ if not agent_query:
1073
+ return {{'error': "No query provided, please provide a 'message' field in the payload."}}
1074
+
1075
+ agent_result = invoke_agent(agent_query)
1076
+
1077
+ {tools_used_update_code}
1078
+ response_content = {response_content_code}
1079
+
1080
+ # Gathering sources from the response
1081
+ sources = []
1082
+ urls = re.findall(r'(?:https?://|www\.)(?:[a-zA-Z0-9\-]+\.)+[a-zA-Z]{{2,}}(?:/[^/\s]*)*', response_content)
1083
+ source_tags = re.findall(r"<source>(.*?)</source>", response_content)
1084
+ sources.extend(urls)
1085
+ sources.extend(source_tags)
1086
+ sources = list(set(sources))
1087
+
1088
+ formatted_messages = [(agent_query, "USER"), (response_content if response_content else "No Response.", "ASSISTANT")]
1089
+
1090
+ {agentcore_memory_entrypoint_code}
1091
+
1092
+ return {{'result': {{'response': response_content, 'sources': sources, 'tools_used': list(tools_used), 'sessionId': session_id, 'messages': formatted_messages}}}}
1093
+ except Exception as e:
1094
+ return {{'error': str(e)}}
1095
+ """
1096
+ return entrypoint_code
1097
+
1098
+ def translate(self, output_path: str, code_sections: list, platform: str):
1099
+ """Translate Bedrock agent config to LangChain code."""
1100
+ code = "\n".join(code_sections)
1101
+ code = unindent_by_one(code)
1102
+
1103
+ code = autopep8.fix_code(code, options={"aggressive": 1, "max_line_length": 120})
1104
+
1105
+ with open(output_path, "a+", encoding="utf-8") as f:
1106
+ f.truncate(0)
1107
+ f.write(code)
1108
+
1109
+ environment_variables = {}
1110
+ if self.gateway_cognito_result:
1111
+ client_info = self.gateway_cognito_result.get("client_info", {})
1112
+ environment_variables.update(
1113
+ {
1114
+ "cognito_client_id": client_info.get("client_id", ""),
1115
+ "cognito_client_secret": client_info.get("client_secret", ""),
1116
+ "cognito_user_pool_id": client_info.get("user_pool_id", ""),
1117
+ "cognito_token_endpoint": client_info.get("token_endpoint", ""),
1118
+ "cognito_scope": client_info.get("scope", ""),
1119
+ "cognito_domain_prefix": client_info.get("domain_prefix", ""),
1120
+ }
1121
+ )
1122
+
1123
+ # Write a .env file with the environment variables
1124
+ env_file_path = os.path.join(self.output_dir, ".env")
1125
+ with open(env_file_path, "w", encoding="utf-8") as env_file:
1126
+ for key, value in environment_variables.items():
1127
+ env_file.write(f"{key}={value}\n")
1128
+
1129
+ # Copy over requirements.txt
1130
+ requirements_path = os.path.join(get_base_dir(__file__), "assets", f"requirements_{platform}.j2")
1131
+ if os.path.exists(requirements_path):
1132
+ with (
1133
+ open(requirements_path, "r", encoding="utf-8") as src_file,
1134
+ open(os.path.join(self.output_dir, "requirements.txt"), "w", encoding="utf-8") as dest_file,
1135
+ ):
1136
+ dest_file.truncate(0)
1137
+ dest_file.write(src_file.read())
1138
+
1139
+ return environment_variables
1140
+
1141
+ # --------------------------------
1142
+ # START: AgentCore Gateway Functions
1143
+ # --------------------------------
1144
+
1145
+ def create_gateway(self):
1146
+ """Create the gateway and proxy for the agent."""
1147
+ print(" Creating Gateway for Agent...")
1148
+ gateway_client = GatewayClient(region_name=self.agent_region)
1149
+ gateway_name = f"{self.cleaned_agent_name.replace('_', '-')}-gateway-{uuid.uuid4().hex[:5].lower()}"
1150
+
1151
+ self.gateway_cognito_result = gateway_client.create_oauth_authorizer_with_cognito(gateway_name=gateway_name)
1152
+
1153
+ gateway = gateway_client.create_mcp_gateway(
1154
+ name=gateway_name,
1155
+ enable_semantic_search=True,
1156
+ authorizer_config=self.gateway_cognito_result["authorizer_config"],
1157
+ )
1158
+ return gateway
1159
+
1160
+ def create_gateway_proxy_and_targets(self):
1161
+ """Create gateway proxy for the agent."""
1162
+ action_groups = self.custom_ags
1163
+ function_name = f"gateway_proxy_{uuid.uuid4().hex[:8].lower()}"
1164
+ account_id = boto3.client("sts").get_caller_identity().get("Account")
1165
+ lambda_arn = f"arn:aws:lambda:{self.agent_region}:{account_id}:function:{function_name}"
1166
+
1167
+ # Aggregate info from the action_groups
1168
+ tool_mappings = {}
1169
+
1170
+ for ag in action_groups:
1171
+ time.sleep(10) # Sleep to avoid throttling issues with the Gateway API
1172
+
1173
+ if "lambda" not in ag.get("actionGroupExecutor", {}):
1174
+ continue
1175
+
1176
+ action_group_name = ag.get("actionGroupName", "AG")
1177
+ clean_action_group_name = clean_variable_name(action_group_name)
1178
+ action_group_desc = ag.get("description", "").replace('"', '\\"')
1179
+ end_lambda_arn = ag.get("actionGroupExecutor", {}).get("lambda", "")
1180
+ tools = []
1181
+
1182
+ if ag.get("apiSchema", False):
1183
+ openapi_schema = ag.get("apiSchema", {}).get("payload", {})
1184
+
1185
+ for func_name, func_spec in openapi_schema.get("paths", {}).items():
1186
+ clean_func_name = clean_variable_name(func_name)
1187
+ for method, method_spec in func_spec.items():
1188
+ tool_name_unpruned = f"{action_group_name}_{clean_func_name}_{method}"
1189
+ tool_name = prune_tool_name(
1190
+ tool_name_unpruned, length=(54 - len(clean_action_group_name))
1191
+ ) # to ensure the tool is below 64 characters
1192
+
1193
+ tool_mappings[f"{clean_action_group_name}___{tool_name}"] = {
1194
+ "actionGroup": action_group_name,
1195
+ "apiPath": func_name,
1196
+ "httpMethod": method.upper(),
1197
+ "type": "openapi",
1198
+ "lambdaArn": end_lambda_arn,
1199
+ "lambdaRegion": end_lambda_arn.split(":")[3] if end_lambda_arn else "us-west-2",
1200
+ }
1201
+
1202
+ func_desc = method_spec.get(
1203
+ "description", method_spec.get("summary", "No Description Provided.")
1204
+ )
1205
+ func_desc += f"\\nThis tool is part of the group of tools called {action_group_name}{f' (description: {action_group_desc})' if action_group_desc else ''}."
1206
+
1207
+ # Convert AG OpenAPI Schema to JSON Schema
1208
+
1209
+ # Gateway does not support oneOf yet, so we need to flatten the schema
1210
+ GATEWAY_ONEOF_NOT_SUPPORTED = True
1211
+ parameters = method_spec.get("parameters", [])
1212
+
1213
+ request_body_required = method_spec.get("requestBody", {}).get("required", False)
1214
+ request_body = method_spec.get("requestBody", {}).get("content", {})
1215
+
1216
+ requirements = []
1217
+ if parameters:
1218
+ requirements.append("parameters")
1219
+ if request_body_required:
1220
+ requirements.append("requestBody")
1221
+
1222
+ content_schemas = []
1223
+ for content_type, content_schema in request_body.items():
1224
+ content_schema = content_schema.get("schema", {})
1225
+ converted = to_json_schema(content_schema)
1226
+ converted.get("properties", {}).update(
1227
+ {
1228
+ "contentType": {"description": f"MUST BE SET TO {content_type}", "type": "string"}
1229
+ } # NOTE: GATEWAY DOES NOT SUPPORT ENUM OR CONST YET
1230
+ )
1231
+ converted.get("required", []).append("contentType")
1232
+ del converted["$schema"]
1233
+ content_schemas.append(converted)
1234
+
1235
+ param_properties = {}
1236
+ required_params = []
1237
+ for parameter in parameters:
1238
+ param_name = parameter.get("name", "")
1239
+ param_desc = parameter.get("description", "").replace('"', '\\"')
1240
+ param_required = parameter.get("required", False)
1241
+ if "schema" in parameter:
1242
+ param_type = parameter.get("schema", {}).get("type", "string")
1243
+
1244
+ param_properties[param_name] = {
1245
+ "type": param_type,
1246
+ "description": param_desc,
1247
+ }
1248
+ else:
1249
+ param_content = parameter.get("content", {})
1250
+ content_schemas = []
1251
+ for content_type, content_schema in param_content.items():
1252
+ content_schema = content_schema.get("schema", {})
1253
+ converted = to_json_schema(content_schema)
1254
+ converted.get("properties", {}).update(
1255
+ {
1256
+ "contentType": {
1257
+ "description": f"MUST BE SET TO {content_type}",
1258
+ "type": "string",
1259
+ }
1260
+ } # NOTE: GATEWAY DOES NOT SUPPORT ENUM OR CONST YET
1261
+ )
1262
+ converted.get("required", []).append("contentType")
1263
+ del converted["$schema"]
1264
+ content_schemas.append(converted)
1265
+
1266
+ param_properties[param_name] = (
1267
+ content_schemas[0]
1268
+ if len(content_schemas) == 1
1269
+ or (GATEWAY_ONEOF_NOT_SUPPORTED and len(content_schemas) > 1)
1270
+ else {
1271
+ "type": "object",
1272
+ "description": param_desc,
1273
+ "oneOf": content_schemas, # NOTE: GATEWAY DOES NOT SUPPORT ONEOF YET
1274
+ }
1275
+ )
1276
+
1277
+ if param_required:
1278
+ required_params.append(param_name)
1279
+
1280
+ input_schema = {
1281
+ "type": "object",
1282
+ "properties": {},
1283
+ "required": requirements,
1284
+ }
1285
+
1286
+ if parameters:
1287
+ input_schema["properties"]["parameters"] = {
1288
+ "type": "object",
1289
+ "properties": param_properties,
1290
+ "required": required_params,
1291
+ }
1292
+ if content_schemas:
1293
+ input_schema["properties"]["requestBody"] = (
1294
+ content_schemas[0]
1295
+ if len(content_schemas) == 1
1296
+ or (GATEWAY_ONEOF_NOT_SUPPORTED and len(content_schemas) > 1)
1297
+ else {
1298
+ "type": "object",
1299
+ "oneOf": content_schemas,
1300
+ } # NOTE: GATEWAY DOES NOT SUPPORT ONEOF YET
1301
+ )
1302
+
1303
+ tools.append({"name": tool_name, "description": func_desc, "inputSchema": input_schema})
1304
+
1305
+ elif ag.get("functionSchema", False):
1306
+ function_schema = ag.get("functionSchema", {}).get("functions", [])
1307
+
1308
+ for func in function_schema:
1309
+ func_name = func.get("name", "")
1310
+ clean_func_name = clean_variable_name(func_name)
1311
+ tool_name = prune_tool_name(f"{action_group_name}_{clean_func_name}")
1312
+
1313
+ tool_mappings[f"{clean_action_group_name}___{tool_name}"] = {
1314
+ "actionGroup": action_group_name,
1315
+ "function": func_name,
1316
+ "type": "structured",
1317
+ "lambdaArn": end_lambda_arn,
1318
+ "lambdaRegion": end_lambda_arn.split(":")[3] if end_lambda_arn else "us-west-2",
1319
+ }
1320
+
1321
+ func_desc = func_spec.get("description", "No Description Provided.")
1322
+ func_desc += f"\\nThis tool is part of the group of tools called {action_group_name}{f' (description: {action_group_desc})' if action_group_desc else ''}."
1323
+
1324
+ func_parameters = func.get("parameters", {})
1325
+
1326
+ # Convert AG Function Schema to JSON Schema
1327
+ new_properties = {}
1328
+ required_params = []
1329
+ for param_name, param_info in func_parameters.items():
1330
+ param_type = param_info.get("type", "string")
1331
+ param_desc = param_info.get("description", "").replace('"', '\\"')
1332
+ param_required = param_info.get("required", False)
1333
+
1334
+ new_properties[param_name] = {
1335
+ "type": param_type,
1336
+ "description": param_desc,
1337
+ }
1338
+
1339
+ if param_required:
1340
+ required_params.append(param_name)
1341
+
1342
+ tools.append(
1343
+ {
1344
+ "name": tool_name,
1345
+ "description": func_desc,
1346
+ "inputSchema": {
1347
+ "type": "object",
1348
+ "properties": new_properties,
1349
+ "required": required_params,
1350
+ },
1351
+ }
1352
+ )
1353
+
1354
+ if tools:
1355
+ self.create_gateway_lambda_target(tools, lambda_arn, clean_action_group_name)
1356
+
1357
+ agent_metadata = {
1358
+ "name": self.agent_info.get("agentName", ""),
1359
+ "id": self.agent_info.get("agentId", ""),
1360
+ "alias": self.agent_info.get("alias", ""),
1361
+ "version": self.agent_info.get("version", ""),
1362
+ }
1363
+
1364
+ lambda_code = f"""
1365
+ import boto3
1366
+ import json
1367
+
1368
+ agent_metadata = {agent_metadata}
1369
+ tool_mappings = {tool_mappings}
1370
+
1371
+ def get_json_type(value):
1372
+ if isinstance(value, str):
1373
+ return "string"
1374
+ elif isinstance(value, bool):
1375
+ return "boolean"
1376
+ elif isinstance(value, int):
1377
+ return "integer"
1378
+ elif isinstance(value, float):
1379
+ return "number"
1380
+ elif isinstance(value, list):
1381
+ return "array"
1382
+ elif isinstance(value, dict):
1383
+ return "object"
1384
+ elif value is None:
1385
+ return "null"
1386
+ else:
1387
+ return "unknown"
1388
+
1389
+ def transform_object(event_obj):
1390
+ result = []
1391
+ for key, value in event_obj.items():
1392
+ json_type = get_json_type(value)
1393
+ if json_type == "array":
1394
+ value = [transform_object(item) if isinstance(item, dict) else item for item in value]
1395
+ elif json_type == "object":
1396
+ value = transform_object(value)
1397
+
1398
+ result.append({{
1399
+ "name": key,
1400
+ "value": value,
1401
+ "type": json_type
1402
+ }})
1403
+ return result
1404
+
1405
+ def lambda_handler(event, context):
1406
+ tool_name = context.client_context.custom.get('bedrockAgentCoreToolName', '')
1407
+ session_id = context.client_context.custom.get('bedrockAgentCoreSessionId', '')
1408
+
1409
+ tool_info = tool_mappings.get(tool_name, {{}})
1410
+ if not tool_info:
1411
+ return {{'statusCode': 400, 'body': f"Tool {{tool_name}} not found"}}
1412
+
1413
+ action_group = tool_info.get('actionGroup', '')
1414
+ end_lambda_arn = tool_info.get('lambdaArn', '')
1415
+ lambda_region = tool_info.get('lambdaRegion', 'us-west-2')
1416
+
1417
+ lambda_client = boto3.client("lambda", region_name=lambda_region)
1418
+
1419
+ payload = {{
1420
+ "messageVersion": "1.0",
1421
+ "agent": agent_metadata,
1422
+ "actionGroup": action_group,
1423
+ "sessionId": session_id,
1424
+ "sessionAttributes": {{}},
1425
+ "promptSessionAttributes": {{}},
1426
+ "inputText": ''
1427
+ }}
1428
+
1429
+ if tool_info.get('type') == 'openapi':
1430
+ request_body_properties = transform_object(event.get('requestBody', {{}}))
1431
+ parameters_properties = transform_object(event.get('parameters', {{}}))
1432
+ content_type = event.get('requestBody', {{}}).get('contentType', 'application/json')
1433
+
1434
+ payload.update({{
1435
+ "apiPath": tool_info.get('apiPath', ''),
1436
+ "httpMethod": tool_info.get('httpMethod', 'GET'),
1437
+ "parameters": parameters_properties,
1438
+ "requestBody": {{
1439
+ "content": {{
1440
+ content_type: {{
1441
+ "properties": request_body_properties,
1442
+ }}
1443
+ }}
1444
+ }},
1445
+ }})
1446
+ elif tool_info.get('type') == 'structured':
1447
+ payload.update({{
1448
+ "function": tool_info.get('function', ''),
1449
+ "parameters": transform_object(event)
1450
+ }})
1451
+
1452
+ try:
1453
+ response = lambda_client.invoke(
1454
+ FunctionName=end_lambda_arn,
1455
+ InvocationType='RequestResponse',
1456
+ Payload=json.dumps(payload)
1457
+ )
1458
+
1459
+ response_payload = json.loads(response['Payload'].read().decode('utf-8'))
1460
+
1461
+ return {{'statusCode': 200, 'body': json.dumps(response_payload)}}
1462
+
1463
+ except Exception as e:
1464
+ return {{'statusCode': 500, 'body': f'Error invoking Lambda: {{str(e)}}'}}
1465
+ """
1466
+
1467
+ self.create_lambda(lambda_code, function_name)
1468
+
1469
+ def _update_gateway_role_with_lambda_permission(self, function_name):
1470
+ """Update the gateway role with lambda invoke permission."""
1471
+ if not self.created_gateway or not self.created_gateway.get("roleArn"):
1472
+ return
1473
+
1474
+ iam = boto3.client("iam")
1475
+ account_id = boto3.client("sts").get_caller_identity().get("Account")
1476
+
1477
+ # Extract role name from ARN
1478
+ gateway_role_arn = self.created_gateway["roleArn"]
1479
+ gateway_role_name = gateway_role_arn.split("/")[-1]
1480
+
1481
+ # Create the lambda invoke policy for the gateway role
1482
+ gateway_lambda_invoke_policy = {
1483
+ "Version": "2012-10-17",
1484
+ "Statement": [
1485
+ {
1486
+ "Sid": "AmazonBedrockAgentCoreGatewayLambdaProd",
1487
+ "Effect": "Allow",
1488
+ "Action": ["lambda:InvokeFunction"],
1489
+ "Resource": [f"arn:aws:lambda:*:{account_id}:function:*:*"],
1490
+ "Condition": {"StringEquals": {"aws:ResourceAccount": account_id}},
1491
+ }
1492
+ ],
1493
+ }
1494
+
1495
+ # Create and attach the policy to the gateway role
1496
+ policy_name = "GatewayLambdaInvokePolicy"
1497
+ try:
1498
+ policy_response = iam.create_policy(
1499
+ PolicyName=policy_name,
1500
+ PolicyDocument=json.dumps(gateway_lambda_invoke_policy),
1501
+ Description=f"Policy to allow gateway role to invoke Lambda function {function_name}",
1502
+ )
1503
+ policy_arn = policy_response["Policy"]["Arn"]
1504
+ print(f" Created policy {policy_name} with ARN {policy_arn}")
1505
+ except iam.exceptions.EntityAlreadyExistsException:
1506
+ # Policy already exists, get its ARN
1507
+ policy_arn = f"arn:aws:iam::{account_id}:policy/{policy_name}"
1508
+ print(f" Policy {policy_name} already exists")
1509
+
1510
+ # Attach the policy to the gateway role
1511
+ try:
1512
+ iam.attach_role_policy(
1513
+ RoleName=gateway_role_name,
1514
+ PolicyArn=policy_arn,
1515
+ )
1516
+ print(f" Attached lambda invoke policy to gateway role {gateway_role_name}")
1517
+ except iam.exceptions.EntityAlreadyExistsException:
1518
+ print(f" Policy already attached to gateway role {gateway_role_name}")
1519
+ except Exception as e:
1520
+ print(f" Warning: Could not attach lambda invoke policy to gateway role {gateway_role_name}: {str(e)}")
1521
+
1522
+ def create_lambda(self, code, function_name):
1523
+ """Create a Lambda function for the agent proxy."""
1524
+ lambda_client = boto3.client("lambda", region_name=self.agent_region)
1525
+ iam = boto3.client("iam")
1526
+
1527
+ role_name = "AgentCoreTestLambdaRole"
1528
+
1529
+ lambda_trust_policy = {
1530
+ "Version": "2012-10-17",
1531
+ "Statement": [
1532
+ {
1533
+ "Effect": "Allow",
1534
+ "Principal": {"Service": "lambda.amazonaws.com"},
1535
+ "Action": "sts:AssumeRole",
1536
+ }
1537
+ ],
1538
+ }
1539
+
1540
+ # Lambda invoke policy for the proxy to call other Lambda functions
1541
+ lambda_invoke_policy = {
1542
+ "Version": "2012-10-17",
1543
+ "Statement": [
1544
+ {"Effect": "Allow", "Action": ["lambda:InvokeFunction"], "Resource": "arn:aws:lambda:*:*:function:*"}
1545
+ ],
1546
+ }
1547
+
1548
+ # Create zip file
1549
+ zip_buffer = io.BytesIO()
1550
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
1551
+ zip_file.writestr("lambda_function.py", code)
1552
+ zip_buffer.seek(0)
1553
+
1554
+ # Create Lambda execution role
1555
+ try:
1556
+ role_response = iam.create_role(
1557
+ RoleName=role_name, AssumeRolePolicyDocument=json.dumps(lambda_trust_policy)
1558
+ )
1559
+
1560
+ # Attach basic execution role for CloudWatch logs
1561
+ iam.attach_role_policy(
1562
+ RoleName=role_name,
1563
+ PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
1564
+ )
1565
+
1566
+ # Create and attach custom policy for Lambda invocation
1567
+ try:
1568
+ policy_response = iam.create_policy(
1569
+ PolicyName="AgentCoreLambdaInvokePolicy",
1570
+ PolicyDocument=json.dumps(lambda_invoke_policy),
1571
+ Description="Policy to allow Lambda proxy to invoke other Lambda functions",
1572
+ )
1573
+ lambda_invoke_policy_arn = policy_response["Policy"]["Arn"]
1574
+ except iam.exceptions.EntityAlreadyExistsException:
1575
+ # Policy already exists, get its ARN
1576
+ account_id = boto3.client("sts").get_caller_identity().get("Account")
1577
+ lambda_invoke_policy_arn = f"arn:aws:iam::{account_id}:policy/AgentCoreLambdaInvokePolicy"
1578
+
1579
+ iam.attach_role_policy(
1580
+ RoleName=role_name,
1581
+ PolicyArn=lambda_invoke_policy_arn,
1582
+ )
1583
+
1584
+ role_arn = role_response["Role"]["Arn"]
1585
+
1586
+ # Wait a bit for role to propagate
1587
+ time.sleep(10)
1588
+ print(f" Created Lambda role {role_name} with ARN {role_arn}")
1589
+
1590
+ except iam.exceptions.EntityAlreadyExistsException:
1591
+ role = iam.get_role(RoleName=role_name)
1592
+ role_arn = role["Role"]["Arn"]
1593
+
1594
+ # Ensure the existing role has the Lambda invoke policy attached
1595
+ try:
1596
+ account_id = boto3.client("sts").get_caller_identity().get("Account")
1597
+ lambda_invoke_policy_arn = f"arn:aws:iam::{account_id}:policy/AgentCoreLambdaInvokePolicy"
1598
+ iam.attach_role_policy(
1599
+ RoleName=role_name,
1600
+ PolicyArn=lambda_invoke_policy_arn,
1601
+ )
1602
+ except iam.exceptions.EntityAlreadyExistsException:
1603
+ # Policy is already attached, which is fine
1604
+ pass
1605
+ except Exception:
1606
+ # If the policy doesn't exist, create it
1607
+ try:
1608
+ policy_response = iam.create_policy(
1609
+ PolicyName="AgentCoreLambdaInvokePolicy",
1610
+ PolicyDocument=json.dumps(lambda_invoke_policy),
1611
+ Description="Policy to allow Lambda proxy to invoke other Lambda functions",
1612
+ )
1613
+ lambda_invoke_policy_arn = policy_response["Policy"]["Arn"]
1614
+ iam.attach_role_policy(
1615
+ RoleName=role_name,
1616
+ PolicyArn=lambda_invoke_policy_arn,
1617
+ )
1618
+ except Exception:
1619
+ # If we still can't attach the policy, log a warning but continue
1620
+ print(f"Warning: Could not attach Lambda invoke policy to role {role_name}")
1621
+
1622
+ # Create Lambda function
1623
+ try:
1624
+ response = lambda_client.create_function(
1625
+ FunctionName=function_name,
1626
+ Runtime="python3.10",
1627
+ Role=role_arn,
1628
+ Handler="lambda_function.lambda_handler",
1629
+ Code={"ZipFile": zip_buffer.read()},
1630
+ Description="Proxy Lambda for AgentCore Gateway",
1631
+ )
1632
+
1633
+ lambda_arn = response["FunctionArn"]
1634
+
1635
+ lambda_client.add_permission(
1636
+ FunctionName=function_name,
1637
+ StatementId="AllowAgentCoreInvoke",
1638
+ Action="lambda:InvokeFunction",
1639
+ Principal=self.created_gateway["roleArn"],
1640
+ )
1641
+
1642
+ print(f" Created Gateway Proxy Lambda function {function_name} with ARN {lambda_arn}")
1643
+
1644
+ except lambda_client.exceptions.ResourceConflictException:
1645
+ response = lambda_client.get_function(FunctionName=function_name)
1646
+ lambda_arn = response["Configuration"]["FunctionArn"]
1647
+
1648
+ # Update gateway role with lambda invoke permission
1649
+ self._update_gateway_role_with_lambda_permission(function_name)
1650
+
1651
+ return lambda_arn
1652
+
1653
+ def create_gateway_lambda_target(self, tools, lambda_arn, target_name):
1654
+ """Create a Lambda target for the gateway."""
1655
+ target = GatewayClient(region_name=self.agent_region).create_mcp_gateway_target(
1656
+ gateway=self.created_gateway,
1657
+ target_type="lambda",
1658
+ target_payload={"lambdaArn": lambda_arn, "toolSchema": {"inlinePayload": tools}},
1659
+ name=target_name,
1660
+ )
1661
+ return target
1662
+
1663
+ # --------------------------------
1664
+ # END: AgentCore Gateway Functions
1665
+ # --------------------------------
1666
+
1667
+
1668
+ # ruff: noqa: E501