bedrock-agentcore-starter-toolkit 0.1.2__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.
- bedrock_agentcore_starter_toolkit/cli/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/cli/cli.py +3 -1
- bedrock_agentcore_starter_toolkit/cli/common.py +1 -1
- bedrock_agentcore_starter_toolkit/cli/import_agent/README.md +35 -0
- bedrock_agentcore_starter_toolkit/cli/import_agent/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/cli/import_agent/agent_info.py +230 -0
- bedrock_agentcore_starter_toolkit/cli/import_agent/commands.py +518 -0
- bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +132 -42
- bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +120 -22
- bedrock_agentcore_starter_toolkit/operations/gateway/client.py +2 -2
- bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +5 -2
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +1 -1
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +108 -30
- bedrock_agentcore_starter_toolkit/operations/runtime/models.py +1 -1
- bedrock_agentcore_starter_toolkit/services/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/assets/memory_manager_template.py +207 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/assets/requirements_langchain.j2 +9 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/assets/requirements_strands.j2 +5 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/assets/template_fixtures_merged.json +1102 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/scripts/__init__.py +1 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/scripts/base_bedrock_translate.py +1668 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/scripts/bedrock_to_langchain.py +382 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/scripts/bedrock_to_strands.py +374 -0
- bedrock_agentcore_starter_toolkit/services/import_agent/utils.py +417 -0
- bedrock_agentcore_starter_toolkit/services/runtime.py +35 -12
- bedrock_agentcore_starter_toolkit/utils/runtime/container.py +54 -3
- bedrock_agentcore_starter_toolkit/utils/runtime/entrypoint.py +11 -5
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/execution_role_policy.json.j2 +2 -1
- {bedrock_agentcore_starter_toolkit-0.1.2.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/METADATA +22 -2
- {bedrock_agentcore_starter_toolkit-0.1.2.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/RECORD +35 -19
- {bedrock_agentcore_starter_toolkit-0.1.2.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.2.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.2.dist-info → bedrock_agentcore_starter_toolkit-0.1.4.dist-info}/licenses/LICENSE.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.2.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
|