@aws/agentcore 0.8.2 → 0.10.0
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.
- package/dist/agent-inspector/favicon.svg +60 -0
- package/dist/agent-inspector/index.css +1 -0
- package/dist/agent-inspector/index.html +14 -0
- package/dist/agent-inspector/index.js +274 -0
- package/dist/assets/README.md +56 -31
- package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +1669 -245
- package/dist/assets/__tests__/__snapshots__/dockerfile-render.test.ts.snap +77 -0
- package/dist/assets/__tests__/dockerfile-render.test.ts +24 -0
- package/dist/assets/agents/AGENTS.md +84 -55
- package/dist/assets/cdk/cdk.json +1 -1
- package/dist/assets/cdk/package.json +1 -1
- package/dist/assets/container/python/Dockerfile +4 -0
- package/dist/assets/evaluators/python-lambda/execution-role-policy.json +1 -1
- package/dist/assets/python/a2a/googleadk/base/main.py +65 -3
- package/dist/assets/python/a2a/langchain_langgraph/base/main.py +64 -1
- package/dist/assets/python/a2a/strands/base/main.py +65 -2
- package/dist/assets/python/agui/googleadk/base/README.md +30 -0
- package/dist/assets/python/agui/googleadk/base/gitignore.template +41 -0
- package/dist/assets/python/agui/googleadk/base/main.py +31 -0
- package/dist/assets/python/agui/googleadk/base/model/__init__.py +1 -0
- package/dist/assets/python/agui/googleadk/base/model/load.py +41 -0
- package/dist/assets/python/agui/googleadk/base/pyproject.toml +24 -0
- package/dist/assets/python/agui/langchain_langgraph/base/README.md +22 -0
- package/dist/assets/python/agui/langchain_langgraph/base/gitignore.template +41 -0
- package/dist/assets/python/agui/langchain_langgraph/base/main.py +74 -0
- package/dist/assets/python/agui/langchain_langgraph/base/model/__init__.py +1 -0
- package/dist/assets/python/agui/langchain_langgraph/base/model/load.py +123 -0
- package/dist/assets/python/agui/langchain_langgraph/base/pyproject.toml +30 -0
- package/dist/assets/python/agui/strands/base/README.md +22 -0
- package/dist/assets/python/agui/strands/base/gitignore.template +41 -0
- package/dist/assets/python/agui/strands/base/main.py +43 -0
- package/dist/assets/python/agui/strands/base/model/__init__.py +1 -0
- package/dist/assets/python/agui/strands/base/model/load.py +123 -0
- package/dist/assets/python/agui/strands/base/pyproject.toml +27 -0
- package/dist/assets/python/agui/strands/capabilities/memory/__init__.py +1 -0
- package/dist/assets/python/agui/strands/capabilities/memory/session.py +38 -0
- package/dist/assets/python/http/autogen/base/main.py +61 -1
- package/dist/assets/python/http/googleadk/base/main.py +62 -2
- package/dist/assets/python/http/langchain_langgraph/base/main.py +61 -1
- package/dist/assets/python/http/openaiagents/base/main.py +70 -4
- package/dist/assets/python/http/strands/base/main.py +64 -6
- package/dist/cli/index.mjs +480 -457
- package/dist/lib/errors/config.d.ts.map +1 -1
- package/dist/lib/errors/config.js +5 -2
- package/dist/lib/errors/config.js.map +1 -1
- package/dist/schema/constants.d.ts +1 -0
- package/dist/schema/constants.d.ts.map +1 -1
- package/dist/schema/constants.js +8 -1
- package/dist/schema/constants.js.map +1 -1
- package/dist/schema/schemas/agent-env.d.ts +20 -0
- package/dist/schema/schemas/agent-env.d.ts.map +1 -1
- package/dist/schema/schemas/agent-env.js +17 -2
- package/dist/schema/schemas/agent-env.js.map +1 -1
- package/dist/schema/schemas/agentcore-project.d.ts +6 -0
- package/dist/schema/schemas/agentcore-project.d.ts.map +1 -1
- package/dist/schema/schemas/aws-targets.d.ts +3 -0
- package/dist/schema/schemas/aws-targets.d.ts.map +1 -1
- package/dist/schema/schemas/aws-targets.js +1 -0
- package/dist/schema/schemas/aws-targets.js.map +1 -1
- package/dist/schema/schemas/primitives/evaluator.d.ts.map +1 -1
- package/dist/schema/schemas/primitives/evaluator.js +0 -1
- package/dist/schema/schemas/primitives/evaluator.js.map +1 -1
- package/package.json +4 -1
- package/scripts/bump-version.ts +7 -80
- package/scripts/copy-assets.mjs +14 -0
|
@@ -149,7 +149,7 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/cdk.json should match
|
|
|
149
149
|
"@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true,
|
|
150
150
|
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
|
151
151
|
"@aws-cdk/core:checkSecretUsage": true,
|
|
152
|
-
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
|
|
152
|
+
"@aws-cdk/core:target-partitions": ["aws", "aws-cn", "aws-us-gov"],
|
|
153
153
|
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
|
|
154
154
|
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
|
|
155
155
|
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
|
|
@@ -357,7 +357,7 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/package.json should m
|
|
|
357
357
|
"typescript": "~5.9.3"
|
|
358
358
|
},
|
|
359
359
|
"dependencies": {
|
|
360
|
-
"@aws/agentcore-cdk": "0.1.0-alpha.19",
|
|
360
|
+
"@aws/agentcore-cdk": "^0.1.0-alpha.19",
|
|
361
361
|
"aws-cdk-lib": "^2.248.0",
|
|
362
362
|
"constructs": "^10.0.0"
|
|
363
363
|
}
|
|
@@ -475,6 +475,26 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
|
|
|
475
475
|
"python/a2a/strands/base/pyproject.toml",
|
|
476
476
|
"python/a2a/strands/capabilities/memory/__init__.py",
|
|
477
477
|
"python/a2a/strands/capabilities/memory/session.py",
|
|
478
|
+
"python/agui/googleadk/base/README.md",
|
|
479
|
+
"python/agui/googleadk/base/gitignore.template",
|
|
480
|
+
"python/agui/googleadk/base/main.py",
|
|
481
|
+
"python/agui/googleadk/base/model/__init__.py",
|
|
482
|
+
"python/agui/googleadk/base/model/load.py",
|
|
483
|
+
"python/agui/googleadk/base/pyproject.toml",
|
|
484
|
+
"python/agui/langchain_langgraph/base/README.md",
|
|
485
|
+
"python/agui/langchain_langgraph/base/gitignore.template",
|
|
486
|
+
"python/agui/langchain_langgraph/base/main.py",
|
|
487
|
+
"python/agui/langchain_langgraph/base/model/__init__.py",
|
|
488
|
+
"python/agui/langchain_langgraph/base/model/load.py",
|
|
489
|
+
"python/agui/langchain_langgraph/base/pyproject.toml",
|
|
490
|
+
"python/agui/strands/base/README.md",
|
|
491
|
+
"python/agui/strands/base/gitignore.template",
|
|
492
|
+
"python/agui/strands/base/main.py",
|
|
493
|
+
"python/agui/strands/base/model/__init__.py",
|
|
494
|
+
"python/agui/strands/base/model/load.py",
|
|
495
|
+
"python/agui/strands/base/pyproject.toml",
|
|
496
|
+
"python/agui/strands/capabilities/memory/__init__.py",
|
|
497
|
+
"python/agui/strands/capabilities/memory/session.py",
|
|
478
498
|
"python/http/autogen/base/README.md",
|
|
479
499
|
"python/http/autogen/base/gitignore.template",
|
|
480
500
|
"python/http/autogen/base/main.py",
|
|
@@ -968,7 +988,8 @@ Thumbs.db
|
|
|
968
988
|
`;
|
|
969
989
|
|
|
970
990
|
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/main.py should match snapshot 1`] = `
|
|
971
|
-
"
|
|
991
|
+
"import os
|
|
992
|
+
from google.adk.agents import Agent
|
|
972
993
|
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
|
|
973
994
|
from google.adk.runners import Runner
|
|
974
995
|
from google.adk.sessions import InMemorySessionService
|
|
@@ -976,55 +997,1058 @@ from a2a.types import AgentCapabilities, AgentCard, AgentSkill
|
|
|
976
997
|
from bedrock_agentcore.runtime import serve_a2a
|
|
977
998
|
from model.load import load_model
|
|
978
999
|
|
|
1000
|
+
load_model() # Sets GOOGLE_API_KEY env var (returns None)
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def add_numbers(a: int, b: int) -> int:
|
|
1004
|
+
"""Return the sum of two numbers."""
|
|
1005
|
+
return a + b
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
tools = [add_numbers]
|
|
1009
|
+
|
|
1010
|
+
{{#if sessionStorageMountPath}}
|
|
1011
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
1012
|
+
|
|
1013
|
+
def _safe_resolve(path: str) -> str:
|
|
1014
|
+
"""Resolve path safely within the storage boundary."""
|
|
1015
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
1016
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
1017
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
1018
|
+
return resolved
|
|
1019
|
+
|
|
1020
|
+
def file_read(path: str) -> str:
|
|
1021
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
1022
|
+
try:
|
|
1023
|
+
full_path = _safe_resolve(path)
|
|
1024
|
+
with open(full_path) as f:
|
|
1025
|
+
return f.read()
|
|
1026
|
+
except ValueError as e:
|
|
1027
|
+
return str(e)
|
|
1028
|
+
except OSError as e:
|
|
1029
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
1030
|
+
|
|
1031
|
+
def file_write(path: str, content: str) -> str:
|
|
1032
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
1033
|
+
try:
|
|
1034
|
+
full_path = _safe_resolve(path)
|
|
1035
|
+
parent = os.path.dirname(full_path)
|
|
1036
|
+
if parent:
|
|
1037
|
+
os.makedirs(parent, exist_ok=True)
|
|
1038
|
+
with open(full_path, "w") as f:
|
|
1039
|
+
f.write(content)
|
|
1040
|
+
return f"Written to {path}"
|
|
1041
|
+
except ValueError as e:
|
|
1042
|
+
return str(e)
|
|
1043
|
+
except OSError as e:
|
|
1044
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
1045
|
+
|
|
1046
|
+
def list_files(directory: str = "") -> str:
|
|
1047
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
1048
|
+
try:
|
|
1049
|
+
target = _safe_resolve(directory)
|
|
1050
|
+
entries = os.listdir(target)
|
|
1051
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
1052
|
+
except ValueError as e:
|
|
1053
|
+
return str(e)
|
|
1054
|
+
except OSError as e:
|
|
1055
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
1056
|
+
|
|
1057
|
+
tools.extend([file_read, file_write, list_files])
|
|
1058
|
+
{{/if}}
|
|
1059
|
+
|
|
1060
|
+
AGENT_INSTRUCTION = """
|
|
1061
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
1062
|
+
{{#if sessionStorageMountPath}}
|
|
1063
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
1064
|
+
{{/if}}
|
|
1065
|
+
"""
|
|
1066
|
+
|
|
1067
|
+
agent = Agent(
|
|
1068
|
+
model="gemini-2.5-flash",
|
|
1069
|
+
name="{{ name }}",
|
|
1070
|
+
description="A helpful assistant that can use tools.",
|
|
1071
|
+
instruction=AGENT_INSTRUCTION,
|
|
1072
|
+
tools=tools,
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
runner = Runner(
|
|
1076
|
+
app_name=agent.name,
|
|
1077
|
+
agent=agent,
|
|
1078
|
+
session_service=InMemorySessionService(),
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
card = AgentCard(
|
|
1082
|
+
name=agent.name,
|
|
1083
|
+
description=agent.description,
|
|
1084
|
+
url="http://localhost:9000/",
|
|
1085
|
+
version="0.1.0",
|
|
1086
|
+
capabilities=AgentCapabilities(streaming=True),
|
|
1087
|
+
skills=[
|
|
1088
|
+
AgentSkill(
|
|
1089
|
+
id="tools",
|
|
1090
|
+
name="tools",
|
|
1091
|
+
description="Use tools to help answer questions",
|
|
1092
|
+
tags=["tools"],
|
|
1093
|
+
)
|
|
1094
|
+
],
|
|
1095
|
+
default_input_modes=["text"],
|
|
1096
|
+
default_output_modes=["text"],
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
if __name__ == "__main__":
|
|
1100
|
+
serve_a2a(A2aAgentExecutor(runner=runner), card)
|
|
1101
|
+
"
|
|
1102
|
+
`;
|
|
1103
|
+
|
|
1104
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/model/__init__.py should match snapshot 1`] = `
|
|
1105
|
+
"# Package marker
|
|
1106
|
+
"
|
|
1107
|
+
`;
|
|
1108
|
+
|
|
1109
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/model/load.py should match snapshot 1`] = `
|
|
1110
|
+
"import os
|
|
1111
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1112
|
+
|
|
1113
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1114
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1118
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1119
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1120
|
+
return api_key
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
def _get_api_key() -> str:
|
|
1124
|
+
"""
|
|
1125
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1126
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1127
|
+
"""
|
|
1128
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1129
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1130
|
+
if not api_key:
|
|
1131
|
+
raise RuntimeError(
|
|
1132
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1133
|
+
)
|
|
1134
|
+
return api_key
|
|
1135
|
+
return _agentcore_identity_api_key_provider()
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def load_model() -> None:
|
|
1139
|
+
"""
|
|
1140
|
+
Set up Gemini API key authentication.
|
|
1141
|
+
Uses AgentCore Identity for API key management in deployed environments,
|
|
1142
|
+
and falls back to .env file for local development.
|
|
1143
|
+
Sets the GOOGLE_API_KEY environment variable for the Google ADK.
|
|
1144
|
+
"""
|
|
1145
|
+
api_key = _get_api_key()
|
|
1146
|
+
# Use Google AI Studios API Key Authentication.
|
|
1147
|
+
# https://google.github.io/adk-docs/agents/models/#google-ai-studio
|
|
1148
|
+
os.environ["GOOGLE_API_KEY"] = api_key
|
|
1149
|
+
# Set to TRUE is using Google Vertex AI, Set to FALSE for Google AI Studio
|
|
1150
|
+
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "FALSE"
|
|
1151
|
+
"
|
|
1152
|
+
`;
|
|
1153
|
+
|
|
1154
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/googleadk/base/pyproject.toml should match snapshot 1`] = `
|
|
1155
|
+
"[build-system]
|
|
1156
|
+
requires = ["hatchling"]
|
|
1157
|
+
build-backend = "hatchling.build"
|
|
1158
|
+
|
|
1159
|
+
[project]
|
|
1160
|
+
name = "{{ name }}"
|
|
1161
|
+
version = "0.1.0"
|
|
1162
|
+
description = "AgentCore A2A Agent using Google ADK"
|
|
1163
|
+
readme = "README.md"
|
|
1164
|
+
requires-python = ">=3.10"
|
|
1165
|
+
dependencies = [
|
|
1166
|
+
"a2a-sdk >= 0.2.0",
|
|
1167
|
+
"aws-opentelemetry-distro",
|
|
1168
|
+
"bedrock-agentcore[a2a] >= 1.0.3",
|
|
1169
|
+
"google-adk >= 1.0.0",
|
|
1170
|
+
"google-genai >= 1.0.0",
|
|
1171
|
+
]
|
|
1172
|
+
|
|
1173
|
+
[tool.hatch.build.targets.wheel]
|
|
1174
|
+
packages = ["."]
|
|
1175
|
+
"
|
|
1176
|
+
`;
|
|
1177
|
+
|
|
1178
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/README.md should match snapshot 1`] = `
|
|
1179
|
+
"# {{ name }}
|
|
1180
|
+
|
|
1181
|
+
An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using LangChain + LangGraph.
|
|
1182
|
+
|
|
1183
|
+
## Overview
|
|
1184
|
+
|
|
1185
|
+
This agent implements the A2A protocol using LangGraph, enabling agent-to-agent communication.
|
|
1186
|
+
|
|
1187
|
+
## Local Development
|
|
1188
|
+
|
|
1189
|
+
\`\`\`bash
|
|
1190
|
+
uv sync
|
|
1191
|
+
uv run python main.py
|
|
1192
|
+
\`\`\`
|
|
1193
|
+
|
|
1194
|
+
The agent starts on port 9000.
|
|
1195
|
+
|
|
1196
|
+
## Deploy
|
|
1197
|
+
|
|
1198
|
+
\`\`\`bash
|
|
1199
|
+
agentcore deploy
|
|
1200
|
+
\`\`\`
|
|
1201
|
+
"
|
|
1202
|
+
`;
|
|
1203
|
+
|
|
1204
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/gitignore.template should match snapshot 1`] = `
|
|
1205
|
+
"# Environment variables
|
|
1206
|
+
.env
|
|
1207
|
+
|
|
1208
|
+
# Python
|
|
1209
|
+
__pycache__/
|
|
1210
|
+
*.py[cod]
|
|
1211
|
+
*$py.class
|
|
1212
|
+
*.so
|
|
1213
|
+
.Python
|
|
1214
|
+
build/
|
|
1215
|
+
develop-eggs/
|
|
1216
|
+
dist/
|
|
1217
|
+
downloads/
|
|
1218
|
+
eggs/
|
|
1219
|
+
.eggs/
|
|
1220
|
+
lib/
|
|
1221
|
+
lib64/
|
|
1222
|
+
parts/
|
|
1223
|
+
sdist/
|
|
1224
|
+
var/
|
|
1225
|
+
wheels/
|
|
1226
|
+
*.egg-info/
|
|
1227
|
+
.installed.cfg
|
|
1228
|
+
*.egg
|
|
1229
|
+
|
|
1230
|
+
# Virtual environments
|
|
1231
|
+
.venv/
|
|
1232
|
+
venv/
|
|
1233
|
+
ENV/
|
|
1234
|
+
env/
|
|
1235
|
+
|
|
1236
|
+
# IDE
|
|
1237
|
+
.vscode/
|
|
1238
|
+
.idea/
|
|
1239
|
+
*.swp
|
|
1240
|
+
*.swo
|
|
1241
|
+
*~
|
|
1242
|
+
|
|
1243
|
+
# OS
|
|
1244
|
+
.DS_Store
|
|
1245
|
+
Thumbs.db
|
|
1246
|
+
"
|
|
1247
|
+
`;
|
|
1248
|
+
|
|
1249
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/main.py should match snapshot 1`] = `
|
|
1250
|
+
"import os
|
|
1251
|
+
from langchain_core.tools import tool
|
|
1252
|
+
from langgraph.prebuilt import create_react_agent
|
|
1253
|
+
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
1254
|
+
from a2a.server.agent_execution import AgentExecutor, RequestContext
|
|
1255
|
+
from a2a.server.events import EventQueue
|
|
1256
|
+
from a2a.server.tasks import TaskUpdater
|
|
1257
|
+
from a2a.types import AgentCapabilities, AgentCard, AgentSkill, Part, TextPart
|
|
1258
|
+
from a2a.utils import new_task
|
|
1259
|
+
from bedrock_agentcore.runtime import serve_a2a
|
|
1260
|
+
from model.load import load_model
|
|
1261
|
+
|
|
1262
|
+
LangchainInstrumentor().instrument()
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
@tool
|
|
1266
|
+
def add_numbers(a: int, b: int) -> int:
|
|
1267
|
+
"""Return the sum of two numbers."""
|
|
1268
|
+
return a + b
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
tools = [add_numbers]
|
|
1272
|
+
|
|
1273
|
+
{{#if sessionStorageMountPath}}
|
|
1274
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
1275
|
+
|
|
1276
|
+
def _safe_resolve(path: str) -> str:
|
|
1277
|
+
"""Resolve path safely within the storage boundary."""
|
|
1278
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
1279
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
1280
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
1281
|
+
return resolved
|
|
1282
|
+
|
|
1283
|
+
@tool
|
|
1284
|
+
def file_read(path: str) -> str:
|
|
1285
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
1286
|
+
try:
|
|
1287
|
+
full_path = _safe_resolve(path)
|
|
1288
|
+
with open(full_path) as f:
|
|
1289
|
+
return f.read()
|
|
1290
|
+
except ValueError as e:
|
|
1291
|
+
return str(e)
|
|
1292
|
+
except OSError as e:
|
|
1293
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
1294
|
+
|
|
1295
|
+
@tool
|
|
1296
|
+
def file_write(path: str, content: str) -> str:
|
|
1297
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
1298
|
+
try:
|
|
1299
|
+
full_path = _safe_resolve(path)
|
|
1300
|
+
parent = os.path.dirname(full_path)
|
|
1301
|
+
if parent:
|
|
1302
|
+
os.makedirs(parent, exist_ok=True)
|
|
1303
|
+
with open(full_path, "w") as f:
|
|
1304
|
+
f.write(content)
|
|
1305
|
+
return f"Written to {path}"
|
|
1306
|
+
except ValueError as e:
|
|
1307
|
+
return str(e)
|
|
1308
|
+
except OSError as e:
|
|
1309
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
1310
|
+
|
|
1311
|
+
@tool
|
|
1312
|
+
def list_files(directory: str = "") -> str:
|
|
1313
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
1314
|
+
try:
|
|
1315
|
+
target = _safe_resolve(directory)
|
|
1316
|
+
entries = os.listdir(target)
|
|
1317
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
1318
|
+
except ValueError as e:
|
|
1319
|
+
return str(e)
|
|
1320
|
+
except OSError as e:
|
|
1321
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
1322
|
+
|
|
1323
|
+
tools.extend([file_read, file_write, list_files])
|
|
1324
|
+
{{/if}}
|
|
1325
|
+
|
|
1326
|
+
SYSTEM_PROMPT = """
|
|
1327
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
1328
|
+
{{#if sessionStorageMountPath}}
|
|
1329
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
1330
|
+
{{/if}}
|
|
1331
|
+
"""
|
|
1332
|
+
|
|
1333
|
+
model = load_model()
|
|
1334
|
+
graph = create_react_agent(model, tools=tools, prompt=SYSTEM_PROMPT)
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
class LangGraphA2AExecutor(AgentExecutor):
|
|
1338
|
+
"""Wraps a LangGraph CompiledGraph as an a2a-sdk AgentExecutor."""
|
|
1339
|
+
|
|
1340
|
+
def __init__(self, graph):
|
|
1341
|
+
self.graph = graph
|
|
1342
|
+
|
|
1343
|
+
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
1344
|
+
task = context.current_task or new_task(context.message)
|
|
1345
|
+
if not context.current_task:
|
|
1346
|
+
await event_queue.enqueue_event(task)
|
|
1347
|
+
updater = TaskUpdater(event_queue, task.id, task.context_id)
|
|
1348
|
+
|
|
1349
|
+
user_text = context.get_user_input()
|
|
1350
|
+
result = await self.graph.ainvoke({"messages": [("user", user_text)]})
|
|
1351
|
+
response = result["messages"][-1].content
|
|
1352
|
+
|
|
1353
|
+
await updater.add_artifact([Part(root=TextPart(text=response))])
|
|
1354
|
+
await updater.complete()
|
|
1355
|
+
|
|
1356
|
+
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
1357
|
+
pass
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
card = AgentCard(
|
|
1361
|
+
name="{{ name }}",
|
|
1362
|
+
description="A LangGraph agent on Bedrock AgentCore",
|
|
1363
|
+
url="http://localhost:9000/",
|
|
1364
|
+
version="0.1.0",
|
|
1365
|
+
capabilities=AgentCapabilities(streaming=True),
|
|
1366
|
+
skills=[
|
|
1367
|
+
AgentSkill(
|
|
1368
|
+
id="tools",
|
|
1369
|
+
name="tools",
|
|
1370
|
+
description="Use tools to help answer questions",
|
|
1371
|
+
tags=["tools"],
|
|
1372
|
+
)
|
|
1373
|
+
],
|
|
1374
|
+
default_input_modes=["text"],
|
|
1375
|
+
default_output_modes=["text"],
|
|
1376
|
+
)
|
|
1377
|
+
|
|
1378
|
+
if __name__ == "__main__":
|
|
1379
|
+
serve_a2a(LangGraphA2AExecutor(graph), card)
|
|
1380
|
+
"
|
|
1381
|
+
`;
|
|
1382
|
+
|
|
1383
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/model/__init__.py should match snapshot 1`] = `
|
|
1384
|
+
"# Package marker
|
|
1385
|
+
"
|
|
1386
|
+
`;
|
|
1387
|
+
|
|
1388
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/model/load.py should match snapshot 1`] = `
|
|
1389
|
+
"{{#if (eq modelProvider "Bedrock")}}
|
|
1390
|
+
from langchain_aws import ChatBedrock
|
|
1391
|
+
|
|
1392
|
+
# Uses global inference profile for Claude Sonnet 4.5
|
|
1393
|
+
# https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
|
|
1394
|
+
MODEL_ID = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
def load_model() -> ChatBedrock:
|
|
1398
|
+
"""Get Bedrock model client using IAM credentials."""
|
|
1399
|
+
return ChatBedrock(model_id=MODEL_ID)
|
|
1400
|
+
{{/if}}
|
|
1401
|
+
{{#if (eq modelProvider "Anthropic")}}
|
|
1402
|
+
import os
|
|
1403
|
+
from langchain_anthropic import ChatAnthropic
|
|
1404
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1405
|
+
|
|
1406
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1407
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1411
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1412
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1413
|
+
return api_key
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
def _get_api_key() -> str:
|
|
1417
|
+
"""
|
|
1418
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1419
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1420
|
+
"""
|
|
1421
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1422
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1423
|
+
if not api_key:
|
|
1424
|
+
raise RuntimeError(
|
|
1425
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1426
|
+
)
|
|
1427
|
+
return api_key
|
|
1428
|
+
return _agentcore_identity_api_key_provider()
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
def load_model() -> ChatAnthropic:
|
|
1432
|
+
"""Get authenticated Anthropic model client."""
|
|
1433
|
+
return ChatAnthropic(
|
|
1434
|
+
model="claude-sonnet-4-5-20250929",
|
|
1435
|
+
api_key=_get_api_key()
|
|
1436
|
+
)
|
|
1437
|
+
{{/if}}
|
|
1438
|
+
{{#if (eq modelProvider "OpenAI")}}
|
|
1439
|
+
import os
|
|
1440
|
+
from langchain_openai import ChatOpenAI
|
|
1441
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1442
|
+
|
|
1443
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1444
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1448
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1449
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1450
|
+
return api_key
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def _get_api_key() -> str:
|
|
1454
|
+
"""
|
|
1455
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1456
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1457
|
+
"""
|
|
1458
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1459
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1460
|
+
if not api_key:
|
|
1461
|
+
raise RuntimeError(
|
|
1462
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1463
|
+
)
|
|
1464
|
+
return api_key
|
|
1465
|
+
return _agentcore_identity_api_key_provider()
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
def load_model() -> ChatOpenAI:
|
|
1469
|
+
"""Get authenticated OpenAI model client."""
|
|
1470
|
+
return ChatOpenAI(
|
|
1471
|
+
model="gpt-4.1",
|
|
1472
|
+
api_key=_get_api_key()
|
|
1473
|
+
)
|
|
1474
|
+
{{/if}}
|
|
1475
|
+
{{#if (eq modelProvider "Gemini")}}
|
|
1476
|
+
import os
|
|
1477
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
1478
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1479
|
+
|
|
1480
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1481
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1485
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1486
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1487
|
+
return api_key
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
def _get_api_key() -> str:
|
|
1491
|
+
"""
|
|
1492
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1493
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1494
|
+
"""
|
|
1495
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1496
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1497
|
+
if not api_key:
|
|
1498
|
+
raise RuntimeError(
|
|
1499
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1500
|
+
)
|
|
1501
|
+
return api_key
|
|
1502
|
+
return _agentcore_identity_api_key_provider()
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
def load_model() -> ChatGoogleGenerativeAI:
|
|
1506
|
+
"""Get authenticated Gemini model client."""
|
|
1507
|
+
return ChatGoogleGenerativeAI(
|
|
1508
|
+
model="gemini-2.5-flash",
|
|
1509
|
+
api_key=_get_api_key()
|
|
1510
|
+
)
|
|
1511
|
+
{{/if}}
|
|
1512
|
+
"
|
|
1513
|
+
`;
|
|
1514
|
+
|
|
1515
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/langchain_langgraph/base/pyproject.toml should match snapshot 1`] = `
|
|
1516
|
+
"[build-system]
|
|
1517
|
+
requires = ["hatchling"]
|
|
1518
|
+
build-backend = "hatchling.build"
|
|
1519
|
+
|
|
1520
|
+
[project]
|
|
1521
|
+
name = "{{ name }}"
|
|
1522
|
+
version = "0.1.0"
|
|
1523
|
+
description = "AgentCore A2A Agent using LangChain + LangGraph"
|
|
1524
|
+
readme = "README.md"
|
|
1525
|
+
requires-python = ">=3.10"
|
|
1526
|
+
dependencies = [
|
|
1527
|
+
"a2a-sdk >= 0.2.0",
|
|
1528
|
+
{{#if (eq modelProvider "Anthropic")}}"langchain-anthropic >= 0.3.0",
|
|
1529
|
+
{{/if}}{{#if (eq modelProvider "Bedrock")}}"langchain-aws >= 0.2.0",
|
|
1530
|
+
{{/if}}{{#if (eq modelProvider "Gemini")}}"langchain-google-genai >= 2.0.0",
|
|
1531
|
+
{{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
|
|
1532
|
+
{{/if}}"aws-opentelemetry-distro",
|
|
1533
|
+
"opentelemetry-instrumentation-langchain >= 0.59.0",
|
|
1534
|
+
"bedrock-agentcore[a2a] >= 1.0.3",
|
|
1535
|
+
"botocore[crt] >= 1.35.0",
|
|
1536
|
+
"langgraph >= 0.2.0",
|
|
1537
|
+
]
|
|
1538
|
+
|
|
1539
|
+
[tool.hatch.build.targets.wheel]
|
|
1540
|
+
packages = ["."]
|
|
1541
|
+
"
|
|
1542
|
+
`;
|
|
1543
|
+
|
|
1544
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/README.md should match snapshot 1`] = `
|
|
1545
|
+
"# {{ name }}
|
|
1546
|
+
|
|
1547
|
+
An A2A (Agent-to-Agent) agent deployed on Amazon Bedrock AgentCore using Strands SDK.
|
|
1548
|
+
|
|
1549
|
+
## Overview
|
|
1550
|
+
|
|
1551
|
+
This agent implements the A2A protocol, enabling agent-to-agent communication. Other agents can discover and interact with this agent via the \`/.well-known/agent-card.json\` endpoint.
|
|
1552
|
+
|
|
1553
|
+
## Local Development
|
|
1554
|
+
|
|
1555
|
+
\`\`\`bash
|
|
1556
|
+
uv sync
|
|
1557
|
+
uv run python main.py
|
|
1558
|
+
\`\`\`
|
|
1559
|
+
|
|
1560
|
+
The agent starts on port 9000.
|
|
1561
|
+
|
|
1562
|
+
## Deploy
|
|
1563
|
+
|
|
1564
|
+
\`\`\`bash
|
|
1565
|
+
agentcore deploy
|
|
1566
|
+
\`\`\`
|
|
1567
|
+
"
|
|
1568
|
+
`;
|
|
1569
|
+
|
|
1570
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/gitignore.template should match snapshot 1`] = `
|
|
1571
|
+
"# Environment variables
|
|
1572
|
+
.env
|
|
1573
|
+
|
|
1574
|
+
# Python
|
|
1575
|
+
__pycache__/
|
|
1576
|
+
*.py[cod]
|
|
1577
|
+
*$py.class
|
|
1578
|
+
*.so
|
|
1579
|
+
.Python
|
|
1580
|
+
build/
|
|
1581
|
+
develop-eggs/
|
|
1582
|
+
dist/
|
|
1583
|
+
downloads/
|
|
1584
|
+
eggs/
|
|
1585
|
+
.eggs/
|
|
1586
|
+
lib/
|
|
1587
|
+
lib64/
|
|
1588
|
+
parts/
|
|
1589
|
+
sdist/
|
|
1590
|
+
var/
|
|
1591
|
+
wheels/
|
|
1592
|
+
*.egg-info/
|
|
1593
|
+
.installed.cfg
|
|
1594
|
+
*.egg
|
|
1595
|
+
|
|
1596
|
+
# Virtual environments
|
|
1597
|
+
.venv/
|
|
1598
|
+
venv/
|
|
1599
|
+
ENV/
|
|
1600
|
+
env/
|
|
1601
|
+
|
|
1602
|
+
# IDE
|
|
1603
|
+
.vscode/
|
|
1604
|
+
.idea/
|
|
1605
|
+
*.swp
|
|
1606
|
+
*.swo
|
|
1607
|
+
*~
|
|
1608
|
+
|
|
1609
|
+
# OS
|
|
1610
|
+
.DS_Store
|
|
1611
|
+
Thumbs.db
|
|
1612
|
+
"
|
|
1613
|
+
`;
|
|
1614
|
+
|
|
1615
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/main.py should match snapshot 1`] = `
|
|
1616
|
+
"from strands import Agent, tool
|
|
1617
|
+
from strands.multiagent.a2a.executor import StrandsA2AExecutor
|
|
1618
|
+
from bedrock_agentcore.runtime import serve_a2a
|
|
1619
|
+
from model.load import load_model
|
|
1620
|
+
{{#if hasMemory}}
|
|
1621
|
+
from memory.session import get_memory_session_manager
|
|
1622
|
+
{{/if}}
|
|
1623
|
+
{{#if sessionStorageMountPath}}
|
|
1624
|
+
import os
|
|
1625
|
+
{{/if}}
|
|
1626
|
+
|
|
1627
|
+
|
|
1628
|
+
@tool
|
|
1629
|
+
def add_numbers(a: int, b: int) -> int:
|
|
1630
|
+
"""Return the sum of two numbers."""
|
|
1631
|
+
return a + b
|
|
1632
|
+
|
|
1633
|
+
|
|
1634
|
+
tools = [add_numbers]
|
|
1635
|
+
|
|
1636
|
+
{{#if sessionStorageMountPath}}
|
|
1637
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
1638
|
+
|
|
1639
|
+
def _safe_resolve(path: str) -> str:
|
|
1640
|
+
"""Resolve path safely within the storage boundary."""
|
|
1641
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
1642
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
1643
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
1644
|
+
return resolved
|
|
1645
|
+
|
|
1646
|
+
@tool
|
|
1647
|
+
def file_read(path: str) -> str:
|
|
1648
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
1649
|
+
try:
|
|
1650
|
+
full_path = _safe_resolve(path)
|
|
1651
|
+
with open(full_path) as f:
|
|
1652
|
+
return f.read()
|
|
1653
|
+
except ValueError as e:
|
|
1654
|
+
return str(e)
|
|
1655
|
+
except OSError as e:
|
|
1656
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
1657
|
+
|
|
1658
|
+
@tool
|
|
1659
|
+
def file_write(path: str, content: str) -> str:
|
|
1660
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
1661
|
+
try:
|
|
1662
|
+
full_path = _safe_resolve(path)
|
|
1663
|
+
parent = os.path.dirname(full_path)
|
|
1664
|
+
if parent:
|
|
1665
|
+
os.makedirs(parent, exist_ok=True)
|
|
1666
|
+
with open(full_path, "w") as f:
|
|
1667
|
+
f.write(content)
|
|
1668
|
+
return f"Written to {path}"
|
|
1669
|
+
except ValueError as e:
|
|
1670
|
+
return str(e)
|
|
1671
|
+
except OSError as e:
|
|
1672
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
1673
|
+
|
|
1674
|
+
@tool
|
|
1675
|
+
def list_files(directory: str = "") -> str:
|
|
1676
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
1677
|
+
try:
|
|
1678
|
+
target = _safe_resolve(directory)
|
|
1679
|
+
entries = os.listdir(target)
|
|
1680
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
1681
|
+
except ValueError as e:
|
|
1682
|
+
return str(e)
|
|
1683
|
+
except OSError as e:
|
|
1684
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
1685
|
+
|
|
1686
|
+
tools.extend([file_read, file_write, list_files])
|
|
1687
|
+
{{/if}}
|
|
1688
|
+
|
|
1689
|
+
SYSTEM_PROMPT = """
|
|
1690
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
1691
|
+
{{#if sessionStorageMountPath}}
|
|
1692
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
1693
|
+
{{/if}}
|
|
1694
|
+
"""
|
|
1695
|
+
|
|
1696
|
+
{{#if hasMemory}}
|
|
1697
|
+
def agent_factory():
|
|
1698
|
+
cache = {}
|
|
1699
|
+
def get_or_create_agent(session_id, user_id):
|
|
1700
|
+
key = f"{session_id}/{user_id}"
|
|
1701
|
+
if key not in cache:
|
|
1702
|
+
cache[key] = Agent(
|
|
1703
|
+
model=load_model(),
|
|
1704
|
+
session_manager=get_memory_session_manager(session_id, user_id),
|
|
1705
|
+
system_prompt=SYSTEM_PROMPT,
|
|
1706
|
+
tools=tools,
|
|
1707
|
+
)
|
|
1708
|
+
return cache[key]
|
|
1709
|
+
return get_or_create_agent
|
|
1710
|
+
|
|
1711
|
+
get_or_create_agent = agent_factory()
|
|
1712
|
+
agent = get_or_create_agent("default-session", "default-user")
|
|
1713
|
+
{{else}}
|
|
1714
|
+
agent = Agent(
|
|
1715
|
+
model=load_model(),
|
|
1716
|
+
system_prompt=SYSTEM_PROMPT,
|
|
1717
|
+
tools=tools,
|
|
1718
|
+
)
|
|
1719
|
+
{{/if}}
|
|
1720
|
+
|
|
1721
|
+
if __name__ == "__main__":
|
|
1722
|
+
serve_a2a(StrandsA2AExecutor(agent))
|
|
1723
|
+
"
|
|
1724
|
+
`;
|
|
1725
|
+
|
|
1726
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/model/__init__.py should match snapshot 1`] = `
|
|
1727
|
+
"# Package marker
|
|
1728
|
+
"
|
|
1729
|
+
`;
|
|
1730
|
+
|
|
1731
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/model/load.py should match snapshot 1`] = `
|
|
1732
|
+
"{{#if (eq modelProvider "Bedrock")}}
|
|
1733
|
+
from strands.models.bedrock import BedrockModel
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
def load_model() -> BedrockModel:
|
|
1737
|
+
"""Get Bedrock model client using IAM credentials."""
|
|
1738
|
+
return BedrockModel(model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0")
|
|
1739
|
+
{{/if}}
|
|
1740
|
+
{{#if (eq modelProvider "Anthropic")}}
|
|
1741
|
+
import os
|
|
1742
|
+
|
|
1743
|
+
from strands.models.anthropic import AnthropicModel
|
|
1744
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1745
|
+
|
|
1746
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1747
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1748
|
+
|
|
1749
|
+
|
|
1750
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1751
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1752
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1753
|
+
return api_key
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
def _get_api_key() -> str:
|
|
1757
|
+
"""
|
|
1758
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1759
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1760
|
+
"""
|
|
1761
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1762
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1763
|
+
if not api_key:
|
|
1764
|
+
raise RuntimeError(
|
|
1765
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1766
|
+
)
|
|
1767
|
+
return api_key
|
|
1768
|
+
return _agentcore_identity_api_key_provider()
|
|
1769
|
+
|
|
1770
|
+
|
|
1771
|
+
def load_model() -> AnthropicModel:
|
|
1772
|
+
"""Get authenticated Anthropic model client."""
|
|
1773
|
+
return AnthropicModel(
|
|
1774
|
+
client_args={"api_key": _get_api_key()},
|
|
1775
|
+
model_id="claude-sonnet-4-5-20250929",
|
|
1776
|
+
max_tokens=5000,
|
|
1777
|
+
)
|
|
1778
|
+
{{/if}}
|
|
1779
|
+
{{#if (eq modelProvider "OpenAI")}}
|
|
1780
|
+
import os
|
|
1781
|
+
|
|
1782
|
+
from strands.models.openai import OpenAIModel
|
|
1783
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1784
|
+
|
|
1785
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1786
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1787
|
+
|
|
1788
|
+
|
|
1789
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1790
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1791
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1792
|
+
return api_key
|
|
1793
|
+
|
|
1794
|
+
|
|
1795
|
+
def _get_api_key() -> str:
|
|
1796
|
+
"""
|
|
1797
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1798
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1799
|
+
"""
|
|
1800
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1801
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1802
|
+
if not api_key:
|
|
1803
|
+
raise RuntimeError(
|
|
1804
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1805
|
+
)
|
|
1806
|
+
return api_key
|
|
1807
|
+
return _agentcore_identity_api_key_provider()
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
def load_model() -> OpenAIModel:
|
|
1811
|
+
"""Get authenticated OpenAI model client."""
|
|
1812
|
+
return OpenAIModel(
|
|
1813
|
+
client_args={"api_key": _get_api_key()},
|
|
1814
|
+
model_id="gpt-4.1",
|
|
1815
|
+
)
|
|
1816
|
+
{{/if}}
|
|
1817
|
+
{{#if (eq modelProvider "Gemini")}}
|
|
1818
|
+
import os
|
|
1819
|
+
|
|
1820
|
+
from strands.models.gemini import GeminiModel
|
|
1821
|
+
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1822
|
+
|
|
1823
|
+
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
1824
|
+
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
@requires_api_key(provider_name=IDENTITY_PROVIDER_NAME)
|
|
1828
|
+
def _agentcore_identity_api_key_provider(api_key: str) -> str:
|
|
1829
|
+
"""Fetch API key from AgentCore Identity."""
|
|
1830
|
+
return api_key
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def _get_api_key() -> str:
|
|
1834
|
+
"""
|
|
1835
|
+
Uses AgentCore Identity for API key management in deployed environments.
|
|
1836
|
+
For local development, run via 'agentcore dev' which loads agentcore/.env.
|
|
1837
|
+
"""
|
|
1838
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
1839
|
+
api_key = os.getenv(IDENTITY_ENV_VAR)
|
|
1840
|
+
if not api_key:
|
|
1841
|
+
raise RuntimeError(
|
|
1842
|
+
f"{IDENTITY_ENV_VAR} not found. Add {IDENTITY_ENV_VAR}=your-key to .env.local"
|
|
1843
|
+
)
|
|
1844
|
+
return api_key
|
|
1845
|
+
return _agentcore_identity_api_key_provider()
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
def load_model() -> GeminiModel:
|
|
1849
|
+
"""Get authenticated Gemini model client."""
|
|
1850
|
+
return GeminiModel(
|
|
1851
|
+
client_args={"api_key": _get_api_key()},
|
|
1852
|
+
model_id="gemini-2.5-flash",
|
|
1853
|
+
)
|
|
1854
|
+
{{/if}}
|
|
1855
|
+
"
|
|
1856
|
+
`;
|
|
1857
|
+
|
|
1858
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/base/pyproject.toml should match snapshot 1`] = `
|
|
1859
|
+
"[build-system]
|
|
1860
|
+
requires = ["hatchling"]
|
|
1861
|
+
build-backend = "hatchling.build"
|
|
1862
|
+
|
|
1863
|
+
[project]
|
|
1864
|
+
name = "{{ name }}"
|
|
1865
|
+
version = "0.1.0"
|
|
1866
|
+
description = "AgentCore A2A Agent using Strands SDK"
|
|
1867
|
+
readme = "README.md"
|
|
1868
|
+
requires-python = ">=3.10"
|
|
1869
|
+
dependencies = [
|
|
1870
|
+
{{#if (eq modelProvider "Anthropic")}}"anthropic >= 0.30.0",
|
|
1871
|
+
{{/if}}"a2a-sdk[all] >= 0.2.0",
|
|
1872
|
+
"aws-opentelemetry-distro",
|
|
1873
|
+
"bedrock-agentcore[a2a] >= 1.0.3",
|
|
1874
|
+
"botocore[crt] >= 1.35.0",
|
|
1875
|
+
{{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0",
|
|
1876
|
+
{{/if}}{{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0",
|
|
1877
|
+
{{/if}}"strands-agents >= 1.13.0",
|
|
1878
|
+
]
|
|
1879
|
+
|
|
1880
|
+
[tool.hatch.build.targets.wheel]
|
|
1881
|
+
packages = ["."]
|
|
1882
|
+
"
|
|
1883
|
+
`;
|
|
1884
|
+
|
|
1885
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/capabilities/memory/__init__.py should match snapshot 1`] = `
|
|
1886
|
+
"# Package marker
|
|
1887
|
+
"
|
|
1888
|
+
`;
|
|
1889
|
+
|
|
1890
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/a2a/strands/capabilities/memory/session.py should match snapshot 1`] = `
|
|
1891
|
+
"import os
|
|
1892
|
+
from typing import Optional
|
|
1893
|
+
|
|
1894
|
+
from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig{{#if memoryProviders.[0].strategies.length}}, RetrievalConfig{{/if}}
|
|
1895
|
+
from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager
|
|
1896
|
+
|
|
1897
|
+
MEMORY_ID = os.getenv("{{memoryProviders.[0].envVarName}}")
|
|
1898
|
+
REGION = os.getenv("AWS_REGION")
|
|
1899
|
+
|
|
1900
|
+
def get_memory_session_manager(session_id: str, actor_id: str) -> Optional[AgentCoreMemorySessionManager]:
|
|
1901
|
+
if not MEMORY_ID:
|
|
1902
|
+
return None
|
|
1903
|
+
|
|
1904
|
+
{{#if memoryProviders.[0].strategies.length}}
|
|
1905
|
+
retrieval_config = {
|
|
1906
|
+
{{#if (includes memoryProviders.[0].strategies "SEMANTIC")}}
|
|
1907
|
+
f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5),
|
|
1908
|
+
{{/if}}
|
|
1909
|
+
{{#if (includes memoryProviders.[0].strategies "USER_PREFERENCE")}}
|
|
1910
|
+
f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5),
|
|
1911
|
+
{{/if}}
|
|
1912
|
+
{{#if (includes memoryProviders.[0].strategies "SUMMARIZATION")}}
|
|
1913
|
+
f"/summaries/{actor_id}/{session_id}": RetrievalConfig(top_k=3, relevance_score=0.5),
|
|
1914
|
+
{{/if}}
|
|
1915
|
+
}
|
|
1916
|
+
{{/if}}
|
|
1917
|
+
|
|
1918
|
+
return AgentCoreMemorySessionManager(
|
|
1919
|
+
AgentCoreMemoryConfig(
|
|
1920
|
+
memory_id=MEMORY_ID,
|
|
1921
|
+
session_id=session_id,
|
|
1922
|
+
actor_id=actor_id,
|
|
1923
|
+
{{#if memoryProviders.[0].strategies.length}}
|
|
1924
|
+
retrieval_config=retrieval_config,
|
|
1925
|
+
{{/if}}
|
|
1926
|
+
),
|
|
1927
|
+
REGION
|
|
1928
|
+
)
|
|
1929
|
+
"
|
|
1930
|
+
`;
|
|
1931
|
+
|
|
1932
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/googleadk/base/README.md should match snapshot 1`] = `
|
|
1933
|
+
"# {{ name }}
|
|
1934
|
+
|
|
1935
|
+
An AG-UI agent deployed on Amazon Bedrock AgentCore using Google ADK.
|
|
1936
|
+
|
|
1937
|
+
## Overview
|
|
1938
|
+
|
|
1939
|
+
This agent implements the AG-UI protocol using Google's Agent Development Kit, enabling rich agent-user interaction via the AG-UI event stream.
|
|
1940
|
+
|
|
1941
|
+
## Local Development
|
|
1942
|
+
|
|
1943
|
+
\`\`\`bash
|
|
1944
|
+
uv sync
|
|
1945
|
+
uv run python main.py
|
|
1946
|
+
\`\`\`
|
|
1947
|
+
|
|
1948
|
+
The agent starts on port 8080 and serves requests at \`/invocations\`.
|
|
1949
|
+
|
|
1950
|
+
## Health Check
|
|
1951
|
+
|
|
1952
|
+
\`\`\`
|
|
1953
|
+
GET /ping
|
|
1954
|
+
\`\`\`
|
|
1955
|
+
|
|
1956
|
+
Returns \`{"status": "healthy"}\`.
|
|
1957
|
+
|
|
1958
|
+
## Deploy
|
|
1959
|
+
|
|
1960
|
+
\`\`\`bash
|
|
1961
|
+
agentcore deploy
|
|
1962
|
+
\`\`\`
|
|
1963
|
+
"
|
|
1964
|
+
`;
|
|
1965
|
+
|
|
1966
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/googleadk/base/gitignore.template should match snapshot 1`] = `
|
|
1967
|
+
"# Environment variables
|
|
1968
|
+
.env
|
|
1969
|
+
|
|
1970
|
+
# Python
|
|
1971
|
+
__pycache__/
|
|
1972
|
+
*.py[cod]
|
|
1973
|
+
*$py.class
|
|
1974
|
+
*.so
|
|
1975
|
+
.Python
|
|
1976
|
+
build/
|
|
1977
|
+
develop-eggs/
|
|
1978
|
+
dist/
|
|
1979
|
+
downloads/
|
|
1980
|
+
eggs/
|
|
1981
|
+
.eggs/
|
|
1982
|
+
lib/
|
|
1983
|
+
lib64/
|
|
1984
|
+
parts/
|
|
1985
|
+
sdist/
|
|
1986
|
+
var/
|
|
1987
|
+
wheels/
|
|
1988
|
+
*.egg-info/
|
|
1989
|
+
.installed.cfg
|
|
1990
|
+
*.egg
|
|
1991
|
+
|
|
1992
|
+
# Virtual environments
|
|
1993
|
+
.venv/
|
|
1994
|
+
venv/
|
|
1995
|
+
ENV/
|
|
1996
|
+
env/
|
|
1997
|
+
|
|
1998
|
+
# IDE
|
|
1999
|
+
.vscode/
|
|
2000
|
+
.idea/
|
|
2001
|
+
*.swp
|
|
2002
|
+
*.swo
|
|
2003
|
+
*~
|
|
979
2004
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
2005
|
+
# OS
|
|
2006
|
+
.DS_Store
|
|
2007
|
+
Thumbs.db
|
|
2008
|
+
"
|
|
2009
|
+
`;
|
|
2010
|
+
|
|
2011
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/googleadk/base/main.py should match snapshot 1`] = `
|
|
2012
|
+
"import os
|
|
2013
|
+
import uvicorn
|
|
2014
|
+
from google.adk.agents import LlmAgent
|
|
2015
|
+
from ag_ui_adk import ADKAgent, AGUIToolset, create_adk_app
|
|
2016
|
+
from model.load import load_model
|
|
983
2017
|
|
|
2018
|
+
load_model()
|
|
984
2019
|
|
|
985
|
-
agent =
|
|
986
|
-
model=load_model(),
|
|
2020
|
+
agent = LlmAgent(
|
|
987
2021
|
name="{{ name }}",
|
|
988
|
-
|
|
989
|
-
instruction="You are a helpful assistant.
|
|
990
|
-
tools=[
|
|
2022
|
+
model="gemini-2.5-flash",
|
|
2023
|
+
instruction="You are a helpful assistant.",
|
|
2024
|
+
tools=[AGUIToolset()],
|
|
991
2025
|
)
|
|
992
2026
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
2027
|
+
adk_agent = ADKAgent(
|
|
2028
|
+
adk_agent=agent,
|
|
2029
|
+
app_name="{{ name }}",
|
|
2030
|
+
use_in_memory_services=True,
|
|
997
2031
|
)
|
|
998
2032
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
AgentSkill(
|
|
1007
|
-
id="tools",
|
|
1008
|
-
name="tools",
|
|
1009
|
-
description="Use tools to help answer questions",
|
|
1010
|
-
tags=["tools"],
|
|
1011
|
-
)
|
|
1012
|
-
],
|
|
1013
|
-
default_input_modes=["text"],
|
|
1014
|
-
default_output_modes=["text"],
|
|
1015
|
-
)
|
|
2033
|
+
app = create_adk_app(adk_agent, path="/invocations")
|
|
2034
|
+
|
|
2035
|
+
|
|
2036
|
+
@app.get("/ping")
|
|
2037
|
+
async def ping():
|
|
2038
|
+
return {"status": "healthy"}
|
|
2039
|
+
|
|
1016
2040
|
|
|
1017
2041
|
if __name__ == "__main__":
|
|
1018
|
-
|
|
2042
|
+
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
|
|
1019
2043
|
"
|
|
1020
2044
|
`;
|
|
1021
2045
|
|
|
1022
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2046
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/googleadk/base/model/__init__.py should match snapshot 1`] = `
|
|
1023
2047
|
"# Package marker
|
|
1024
2048
|
"
|
|
1025
2049
|
`;
|
|
1026
2050
|
|
|
1027
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2051
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/googleadk/base/model/load.py should match snapshot 1`] = `
|
|
1028
2052
|
"import os
|
|
1029
2053
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
1030
2054
|
|
|
@@ -1069,7 +2093,7 @@ def load_model() -> None:
|
|
|
1069
2093
|
"
|
|
1070
2094
|
`;
|
|
1071
2095
|
|
|
1072
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2096
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/googleadk/base/pyproject.toml should match snapshot 1`] = `
|
|
1073
2097
|
"[build-system]
|
|
1074
2098
|
requires = ["hatchling"]
|
|
1075
2099
|
build-backend = "hatchling.build"
|
|
@@ -1077,15 +2101,19 @@ build-backend = "hatchling.build"
|
|
|
1077
2101
|
[project]
|
|
1078
2102
|
name = "{{ name }}"
|
|
1079
2103
|
version = "0.1.0"
|
|
1080
|
-
description = "AgentCore
|
|
2104
|
+
description = "AgentCore AG-UI Agent using Google ADK"
|
|
1081
2105
|
readme = "README.md"
|
|
1082
2106
|
requires-python = ">=3.10"
|
|
1083
2107
|
dependencies = [
|
|
1084
|
-
"
|
|
1085
|
-
"
|
|
1086
|
-
"bedrock-agentcore
|
|
1087
|
-
"
|
|
2108
|
+
"ag-ui-adk >= 0.6.0",
|
|
2109
|
+
"ag-ui-protocol >= 0.1.10",
|
|
2110
|
+
"bedrock-agentcore >= 1.0.3",
|
|
2111
|
+
"fastapi >= 0.115.12",
|
|
2112
|
+
"google-adk >= 1.16.0",
|
|
1088
2113
|
"google-genai >= 1.0.0",
|
|
2114
|
+
"opentelemetry-distro",
|
|
2115
|
+
"opentelemetry-exporter-otlp",
|
|
2116
|
+
"uvicorn >= 0.34.3",
|
|
1089
2117
|
]
|
|
1090
2118
|
|
|
1091
2119
|
[tool.hatch.build.targets.wheel]
|
|
@@ -1093,14 +2121,14 @@ packages = ["."]
|
|
|
1093
2121
|
"
|
|
1094
2122
|
`;
|
|
1095
2123
|
|
|
1096
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2124
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/langchain_langgraph/base/README.md should match snapshot 1`] = `
|
|
1097
2125
|
"# {{ name }}
|
|
1098
2126
|
|
|
1099
|
-
An
|
|
2127
|
+
An AG-UI agent deployed on Amazon Bedrock AgentCore using LangChain + LangGraph.
|
|
1100
2128
|
|
|
1101
2129
|
## Overview
|
|
1102
2130
|
|
|
1103
|
-
This agent implements the
|
|
2131
|
+
This agent implements the AG-UI protocol using LangGraph, enabling seamless frontend-to-agent communication with support for streaming, tool calls, and frontend-injected tools.
|
|
1104
2132
|
|
|
1105
2133
|
## Local Development
|
|
1106
2134
|
|
|
@@ -1109,7 +2137,7 @@ uv sync
|
|
|
1109
2137
|
uv run python main.py
|
|
1110
2138
|
\`\`\`
|
|
1111
2139
|
|
|
1112
|
-
The agent starts on port
|
|
2140
|
+
The agent starts on port 8080.
|
|
1113
2141
|
|
|
1114
2142
|
## Deploy
|
|
1115
2143
|
|
|
@@ -1119,7 +2147,7 @@ agentcore deploy
|
|
|
1119
2147
|
"
|
|
1120
2148
|
`;
|
|
1121
2149
|
|
|
1122
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2150
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/langchain_langgraph/base/gitignore.template should match snapshot 1`] = `
|
|
1123
2151
|
"# Environment variables
|
|
1124
2152
|
.env
|
|
1125
2153
|
|
|
@@ -1164,16 +2192,22 @@ Thumbs.db
|
|
|
1164
2192
|
"
|
|
1165
2193
|
`;
|
|
1166
2194
|
|
|
1167
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
1168
|
-
"
|
|
1169
|
-
|
|
2195
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/langchain_langgraph/base/main.py should match snapshot 1`] = `
|
|
2196
|
+
"import os
|
|
2197
|
+
|
|
2198
|
+
os.environ["LANGGRAPH_FAST_API"] = "true"
|
|
2199
|
+
|
|
2200
|
+
import uvicorn
|
|
2201
|
+
from typing import Any, List
|
|
2202
|
+
from fastapi import FastAPI
|
|
2203
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
2204
|
+
from langgraph.graph import StateGraph, START
|
|
2205
|
+
from langgraph.graph.message import MessagesState
|
|
2206
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
2207
|
+
from langgraph.prebuilt import ToolNode, tools_condition
|
|
2208
|
+
from langchain_core.tools import tool
|
|
1170
2209
|
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
1171
|
-
from
|
|
1172
|
-
from a2a.server.events import EventQueue
|
|
1173
|
-
from a2a.server.tasks import TaskUpdater
|
|
1174
|
-
from a2a.types import AgentCapabilities, AgentCard, AgentSkill, Part, TextPart
|
|
1175
|
-
from a2a.utils import new_task
|
|
1176
|
-
from bedrock_agentcore.runtime import serve_a2a
|
|
2210
|
+
from ag_ui_langgraph import LangGraphAgent, add_langgraph_fastapi_endpoint
|
|
1177
2211
|
from model.load import load_model
|
|
1178
2212
|
|
|
1179
2213
|
LangchainInstrumentor().instrument()
|
|
@@ -1185,62 +2219,63 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
1185
2219
|
return a + b
|
|
1186
2220
|
|
|
1187
2221
|
|
|
2222
|
+
backend_tools = [add_numbers]
|
|
1188
2223
|
model = load_model()
|
|
1189
|
-
graph = create_react_agent(model, tools=[add_numbers])
|
|
1190
2224
|
|
|
1191
2225
|
|
|
1192
|
-
class
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
def __init__(self, graph):
|
|
1196
|
-
self.graph = graph
|
|
1197
|
-
|
|
1198
|
-
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
1199
|
-
task = context.current_task or new_task(context.message)
|
|
1200
|
-
if not context.current_task:
|
|
1201
|
-
await event_queue.enqueue_event(task)
|
|
1202
|
-
updater = TaskUpdater(event_queue, task.id, task.context_id)
|
|
2226
|
+
class AgentState(MessagesState):
|
|
2227
|
+
tools: List[Any]
|
|
1203
2228
|
|
|
1204
|
-
user_text = context.get_user_input()
|
|
1205
|
-
result = await self.graph.ainvoke({"messages": [("user", user_text)]})
|
|
1206
|
-
response = result["messages"][-1].content
|
|
1207
2229
|
|
|
1208
|
-
|
|
1209
|
-
|
|
2230
|
+
def chat_node(state: AgentState):
|
|
2231
|
+
bound_model = model.bind_tools(
|
|
2232
|
+
[*state.get("tools", []), *backend_tools],
|
|
2233
|
+
)
|
|
2234
|
+
response = bound_model.invoke(state["messages"])
|
|
2235
|
+
return {"messages": [response]}
|
|
1210
2236
|
|
|
1211
|
-
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
1212
|
-
pass
|
|
1213
2237
|
|
|
2238
|
+
builder = StateGraph(AgentState)
|
|
2239
|
+
builder.add_node("chat", chat_node)
|
|
2240
|
+
builder.add_node("tools", ToolNode(tools=backend_tools))
|
|
2241
|
+
builder.add_edge(START, "chat")
|
|
2242
|
+
builder.add_conditional_edges("chat", tools_condition)
|
|
2243
|
+
builder.add_edge("tools", "chat")
|
|
2244
|
+
graph = builder.compile(checkpointer=MemorySaver())
|
|
1214
2245
|
|
|
1215
|
-
|
|
2246
|
+
agent = LangGraphAgent(
|
|
1216
2247
|
name="{{ name }}",
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
)
|
|
1228
|
-
],
|
|
1229
|
-
default_input_modes=["text"],
|
|
1230
|
-
default_output_modes=["text"],
|
|
2248
|
+
graph=graph,
|
|
2249
|
+
description="A helpful assistant",
|
|
2250
|
+
)
|
|
2251
|
+
|
|
2252
|
+
app = FastAPI()
|
|
2253
|
+
app.add_middleware(
|
|
2254
|
+
CORSMiddleware,
|
|
2255
|
+
allow_origins=["*"],
|
|
2256
|
+
allow_methods=["*"],
|
|
2257
|
+
allow_headers=["*"],
|
|
1231
2258
|
)
|
|
1232
2259
|
|
|
2260
|
+
add_langgraph_fastapi_endpoint(app=app, agent=agent, path="/invocations")
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
@app.get("/ping")
|
|
2264
|
+
async def ping():
|
|
2265
|
+
return {"status": "healthy"}
|
|
2266
|
+
|
|
2267
|
+
|
|
1233
2268
|
if __name__ == "__main__":
|
|
1234
|
-
|
|
2269
|
+
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
|
|
1235
2270
|
"
|
|
1236
2271
|
`;
|
|
1237
2272
|
|
|
1238
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2273
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/langchain_langgraph/base/model/__init__.py should match snapshot 1`] = `
|
|
1239
2274
|
"# Package marker
|
|
1240
2275
|
"
|
|
1241
2276
|
`;
|
|
1242
2277
|
|
|
1243
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2278
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/langchain_langgraph/base/model/load.py should match snapshot 1`] = `
|
|
1244
2279
|
"{{#if (eq modelProvider "Bedrock")}}
|
|
1245
2280
|
from langchain_aws import ChatBedrock
|
|
1246
2281
|
|
|
@@ -1367,7 +2402,7 @@ def load_model() -> ChatGoogleGenerativeAI:
|
|
|
1367
2402
|
"
|
|
1368
2403
|
`;
|
|
1369
2404
|
|
|
1370
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2405
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/langchain_langgraph/base/pyproject.toml should match snapshot 1`] = `
|
|
1371
2406
|
"[build-system]
|
|
1372
2407
|
requires = ["hatchling"]
|
|
1373
2408
|
build-backend = "hatchling.build"
|
|
@@ -1375,20 +2410,25 @@ build-backend = "hatchling.build"
|
|
|
1375
2410
|
[project]
|
|
1376
2411
|
name = "{{ name }}"
|
|
1377
2412
|
version = "0.1.0"
|
|
1378
|
-
description = "AgentCore
|
|
2413
|
+
description = "AgentCore AG-UI Agent using LangChain + LangGraph"
|
|
1379
2414
|
readme = "README.md"
|
|
1380
2415
|
requires-python = ">=3.10"
|
|
1381
2416
|
dependencies = [
|
|
1382
|
-
"
|
|
2417
|
+
"ag-ui-langgraph >= 0.0.31",
|
|
2418
|
+
"ag-ui-protocol >= 0.1.10",
|
|
1383
2419
|
{{#if (eq modelProvider "Anthropic")}}"langchain-anthropic >= 0.3.0",
|
|
1384
2420
|
{{/if}}{{#if (eq modelProvider "Bedrock")}}"langchain-aws >= 0.2.0",
|
|
1385
2421
|
{{/if}}{{#if (eq modelProvider "Gemini")}}"langchain-google-genai >= 2.0.0",
|
|
1386
2422
|
{{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
|
|
1387
2423
|
{{/if}}"aws-opentelemetry-distro",
|
|
1388
2424
|
"opentelemetry-instrumentation-langchain >= 0.59.0",
|
|
1389
|
-
"bedrock-agentcore
|
|
2425
|
+
"bedrock-agentcore >= 1.0.3",
|
|
1390
2426
|
"botocore[crt] >= 1.35.0",
|
|
1391
|
-
"langgraph >= 0.
|
|
2427
|
+
"langgraph >= 0.3.25",
|
|
2428
|
+
"langchain >= 0.3.0",
|
|
2429
|
+
"langchain-core >= 0.3.0",
|
|
2430
|
+
"fastapi >= 0.115.12",
|
|
2431
|
+
"uvicorn >= 0.34.3",
|
|
1392
2432
|
]
|
|
1393
2433
|
|
|
1394
2434
|
[tool.hatch.build.targets.wheel]
|
|
@@ -1396,14 +2436,14 @@ packages = ["."]
|
|
|
1396
2436
|
"
|
|
1397
2437
|
`;
|
|
1398
2438
|
|
|
1399
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2439
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/base/README.md should match snapshot 1`] = `
|
|
1400
2440
|
"# {{ name }}
|
|
1401
2441
|
|
|
1402
|
-
An
|
|
2442
|
+
An AG-UI agent deployed on Amazon Bedrock AgentCore using Strands SDK.
|
|
1403
2443
|
|
|
1404
2444
|
## Overview
|
|
1405
2445
|
|
|
1406
|
-
This agent implements the
|
|
2446
|
+
This agent implements the AG-UI protocol, enabling streaming agent-to-UI communication. The agent exposes an \`/invocations\` endpoint that accepts AG-UI protocol requests and streams responses back to the client.
|
|
1407
2447
|
|
|
1408
2448
|
## Local Development
|
|
1409
2449
|
|
|
@@ -1412,7 +2452,7 @@ uv sync
|
|
|
1412
2452
|
uv run python main.py
|
|
1413
2453
|
\`\`\`
|
|
1414
2454
|
|
|
1415
|
-
The agent starts on port
|
|
2455
|
+
The agent starts on port 8080.
|
|
1416
2456
|
|
|
1417
2457
|
## Deploy
|
|
1418
2458
|
|
|
@@ -1422,7 +2462,7 @@ agentcore deploy
|
|
|
1422
2462
|
"
|
|
1423
2463
|
`;
|
|
1424
2464
|
|
|
1425
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2465
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/base/gitignore.template should match snapshot 1`] = `
|
|
1426
2466
|
"# Environment variables
|
|
1427
2467
|
.env
|
|
1428
2468
|
|
|
@@ -1467,10 +2507,16 @@ Thumbs.db
|
|
|
1467
2507
|
"
|
|
1468
2508
|
`;
|
|
1469
2509
|
|
|
1470
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
1471
|
-
"
|
|
1472
|
-
|
|
1473
|
-
|
|
2510
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/base/main.py should match snapshot 1`] = `
|
|
2511
|
+
"import os
|
|
2512
|
+
|
|
2513
|
+
# Suppress OpenTelemetry warnings during local development; remove for production
|
|
2514
|
+
if os.getenv("LOCAL_DEV") == "1":
|
|
2515
|
+
os.environ["OTEL_SDK_DISABLED"] = "true"
|
|
2516
|
+
|
|
2517
|
+
import uvicorn
|
|
2518
|
+
from strands import Agent, tool
|
|
2519
|
+
from ag_ui_strands import StrandsAgent, StrandsAgentConfig, create_strands_app
|
|
1474
2520
|
from model.load import load_model
|
|
1475
2521
|
{{#if hasMemory}}
|
|
1476
2522
|
from memory.session import get_memory_session_manager
|
|
@@ -1485,42 +2531,35 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
1485
2531
|
|
|
1486
2532
|
tools = [add_numbers]
|
|
1487
2533
|
|
|
1488
|
-
{{#if hasMemory}}
|
|
1489
|
-
def agent_factory():
|
|
1490
|
-
cache = {}
|
|
1491
|
-
def get_or_create_agent(session_id, user_id):
|
|
1492
|
-
key = f"{session_id}/{user_id}"
|
|
1493
|
-
if key not in cache:
|
|
1494
|
-
cache[key] = Agent(
|
|
1495
|
-
model=load_model(),
|
|
1496
|
-
session_manager=get_memory_session_manager(session_id, user_id),
|
|
1497
|
-
system_prompt="You are a helpful assistant. Use tools when appropriate.",
|
|
1498
|
-
tools=tools,
|
|
1499
|
-
)
|
|
1500
|
-
return cache[key]
|
|
1501
|
-
return get_or_create_agent
|
|
1502
|
-
|
|
1503
|
-
get_or_create_agent = agent_factory()
|
|
1504
|
-
agent = get_or_create_agent("default-session", "default-user")
|
|
1505
|
-
{{else}}
|
|
1506
2534
|
agent = Agent(
|
|
1507
2535
|
model=load_model(),
|
|
1508
2536
|
system_prompt="You are a helpful assistant. Use tools when appropriate.",
|
|
1509
2537
|
tools=tools,
|
|
1510
2538
|
)
|
|
2539
|
+
|
|
2540
|
+
{{#if hasMemory}}
|
|
2541
|
+
def session_manager_provider(input_data):
|
|
2542
|
+
return get_memory_session_manager(input_data.thread_id, "default-user")
|
|
2543
|
+
|
|
2544
|
+
config = StrandsAgentConfig(session_manager_provider=session_manager_provider)
|
|
2545
|
+
{{else}}
|
|
2546
|
+
config = StrandsAgentConfig()
|
|
1511
2547
|
{{/if}}
|
|
1512
2548
|
|
|
2549
|
+
agui_agent = StrandsAgent(agent=agent, name="{{ name }}", description="A helpful assistant", config=config)
|
|
2550
|
+
app = create_strands_app(agui_agent, path="/invocations", ping_path="/ping")
|
|
2551
|
+
|
|
1513
2552
|
if __name__ == "__main__":
|
|
1514
|
-
|
|
2553
|
+
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
|
|
1515
2554
|
"
|
|
1516
2555
|
`;
|
|
1517
2556
|
|
|
1518
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2557
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/base/model/__init__.py should match snapshot 1`] = `
|
|
1519
2558
|
"# Package marker
|
|
1520
2559
|
"
|
|
1521
2560
|
`;
|
|
1522
2561
|
|
|
1523
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2562
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/base/model/load.py should match snapshot 1`] = `
|
|
1524
2563
|
"{{#if (eq modelProvider "Bedrock")}}
|
|
1525
2564
|
from strands.models.bedrock import BedrockModel
|
|
1526
2565
|
|
|
@@ -1647,7 +2686,7 @@ def load_model() -> GeminiModel:
|
|
|
1647
2686
|
"
|
|
1648
2687
|
`;
|
|
1649
2688
|
|
|
1650
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2689
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/base/pyproject.toml should match snapshot 1`] = `
|
|
1651
2690
|
"[build-system]
|
|
1652
2691
|
requires = ["hatchling"]
|
|
1653
2692
|
build-backend = "hatchling.build"
|
|
@@ -1655,18 +2694,22 @@ build-backend = "hatchling.build"
|
|
|
1655
2694
|
[project]
|
|
1656
2695
|
name = "{{ name }}"
|
|
1657
2696
|
version = "0.1.0"
|
|
1658
|
-
description = "AgentCore
|
|
2697
|
+
description = "AgentCore AG-UI Agent using Strands SDK"
|
|
1659
2698
|
readme = "README.md"
|
|
1660
|
-
requires-python = ">=3.
|
|
2699
|
+
requires-python = ">=3.12"
|
|
1661
2700
|
dependencies = [
|
|
1662
2701
|
{{#if (eq modelProvider "Anthropic")}}"anthropic >= 0.30.0",
|
|
1663
|
-
{{/if}}"
|
|
2702
|
+
{{/if}}"ag-ui-strands >= 0.1.7",
|
|
2703
|
+
"ag-ui-protocol >= 0.1.10",
|
|
1664
2704
|
"aws-opentelemetry-distro",
|
|
1665
|
-
"bedrock-agentcore
|
|
2705
|
+
"bedrock-agentcore >= 1.0.3",
|
|
1666
2706
|
"botocore[crt] >= 1.35.0",
|
|
2707
|
+
"fastapi >= 0.115.12",
|
|
1667
2708
|
{{#if (eq modelProvider "Gemini")}}"google-genai >= 1.0.0",
|
|
1668
2709
|
{{/if}}{{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0",
|
|
1669
|
-
{{/if}}"strands-agents >= 1.
|
|
2710
|
+
{{/if}}"strands-agents >= 1.15.0",
|
|
2711
|
+
"strands-agents-tools >= 0.2.14",
|
|
2712
|
+
"uvicorn >= 0.34.3",
|
|
1670
2713
|
]
|
|
1671
2714
|
|
|
1672
2715
|
[tool.hatch.build.targets.wheel]
|
|
@@ -1674,12 +2717,12 @@ packages = ["."]
|
|
|
1674
2717
|
"
|
|
1675
2718
|
`;
|
|
1676
2719
|
|
|
1677
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2720
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/capabilities/memory/__init__.py should match snapshot 1`] = `
|
|
1678
2721
|
"# Package marker
|
|
1679
2722
|
"
|
|
1680
2723
|
`;
|
|
1681
2724
|
|
|
1682
|
-
exports[`Assets Directory Snapshots > Python framework assets > python/python/
|
|
2725
|
+
exports[`Assets Directory Snapshots > Python framework assets > python/python/agui/strands/capabilities/memory/session.py should match snapshot 1`] = `
|
|
1683
2726
|
"import os
|
|
1684
2727
|
from typing import Optional
|
|
1685
2728
|
|
|
@@ -1834,6 +2877,66 @@ add_numbers_tool = FunctionTool(
|
|
|
1834
2877
|
# Define a collection of tools used by the model
|
|
1835
2878
|
tools = [add_numbers_tool]
|
|
1836
2879
|
|
|
2880
|
+
{{#if sessionStorageMountPath}}
|
|
2881
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
2882
|
+
|
|
2883
|
+
def _safe_resolve(path: str) -> str:
|
|
2884
|
+
"""Resolve path safely within the storage boundary."""
|
|
2885
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
2886
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
2887
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
2888
|
+
return resolved
|
|
2889
|
+
|
|
2890
|
+
def file_read(path: str) -> str:
|
|
2891
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
2892
|
+
try:
|
|
2893
|
+
full_path = _safe_resolve(path)
|
|
2894
|
+
with open(full_path) as f:
|
|
2895
|
+
return f.read()
|
|
2896
|
+
except ValueError as e:
|
|
2897
|
+
return str(e)
|
|
2898
|
+
except OSError as e:
|
|
2899
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
2900
|
+
|
|
2901
|
+
def file_write(path: str, content: str) -> str:
|
|
2902
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
2903
|
+
try:
|
|
2904
|
+
full_path = _safe_resolve(path)
|
|
2905
|
+
parent = os.path.dirname(full_path)
|
|
2906
|
+
if parent:
|
|
2907
|
+
os.makedirs(parent, exist_ok=True)
|
|
2908
|
+
with open(full_path, "w") as f:
|
|
2909
|
+
f.write(content)
|
|
2910
|
+
return f"Written to {path}"
|
|
2911
|
+
except ValueError as e:
|
|
2912
|
+
return str(e)
|
|
2913
|
+
except OSError as e:
|
|
2914
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
2915
|
+
|
|
2916
|
+
def list_files(directory: str = "") -> str:
|
|
2917
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
2918
|
+
try:
|
|
2919
|
+
target = _safe_resolve(directory)
|
|
2920
|
+
entries = os.listdir(target)
|
|
2921
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
2922
|
+
except ValueError as e:
|
|
2923
|
+
return str(e)
|
|
2924
|
+
except OSError as e:
|
|
2925
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
2926
|
+
|
|
2927
|
+
tools.extend([
|
|
2928
|
+
FunctionTool(file_read, description="Read a file from persistent storage. The path is relative to the storage root."),
|
|
2929
|
+
FunctionTool(file_write, description="Write content to a file in persistent storage. The path is relative to the storage root."),
|
|
2930
|
+
FunctionTool(list_files, description="List files in persistent storage. The directory is relative to the storage root."),
|
|
2931
|
+
])
|
|
2932
|
+
{{/if}}
|
|
2933
|
+
|
|
2934
|
+
SYSTEM_MESSAGE = """
|
|
2935
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
2936
|
+
{{#if sessionStorageMountPath}}
|
|
2937
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
2938
|
+
{{/if}}
|
|
2939
|
+
"""
|
|
1837
2940
|
|
|
1838
2941
|
@app.entrypoint
|
|
1839
2942
|
async def invoke(payload, context):
|
|
@@ -1847,7 +2950,7 @@ async def invoke(payload, context):
|
|
|
1847
2950
|
name="{{ name }}",
|
|
1848
2951
|
model_client=load_model(),
|
|
1849
2952
|
tools=tools + mcp_tools,
|
|
1850
|
-
system_message=
|
|
2953
|
+
system_message=SYSTEM_MESSAGE,
|
|
1851
2954
|
)
|
|
1852
2955
|
|
|
1853
2956
|
# Process the user prompt
|
|
@@ -2202,6 +3305,66 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
2202
3305
|
return a + b
|
|
2203
3306
|
|
|
2204
3307
|
|
|
3308
|
+
# Define a collection of tools used by the model
|
|
3309
|
+
tools = [add_numbers]
|
|
3310
|
+
|
|
3311
|
+
{{#if sessionStorageMountPath}}
|
|
3312
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
3313
|
+
|
|
3314
|
+
def _safe_resolve(path: str) -> str:
|
|
3315
|
+
"""Resolve path safely within the storage boundary."""
|
|
3316
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
3317
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
3318
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
3319
|
+
return resolved
|
|
3320
|
+
|
|
3321
|
+
def file_read(path: str) -> str:
|
|
3322
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
3323
|
+
try:
|
|
3324
|
+
full_path = _safe_resolve(path)
|
|
3325
|
+
with open(full_path) as f:
|
|
3326
|
+
return f.read()
|
|
3327
|
+
except ValueError as e:
|
|
3328
|
+
return str(e)
|
|
3329
|
+
except OSError as e:
|
|
3330
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
3331
|
+
|
|
3332
|
+
def file_write(path: str, content: str) -> str:
|
|
3333
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
3334
|
+
try:
|
|
3335
|
+
full_path = _safe_resolve(path)
|
|
3336
|
+
parent = os.path.dirname(full_path)
|
|
3337
|
+
if parent:
|
|
3338
|
+
os.makedirs(parent, exist_ok=True)
|
|
3339
|
+
with open(full_path, "w") as f:
|
|
3340
|
+
f.write(content)
|
|
3341
|
+
return f"Written to {path}"
|
|
3342
|
+
except ValueError as e:
|
|
3343
|
+
return str(e)
|
|
3344
|
+
except OSError as e:
|
|
3345
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
3346
|
+
|
|
3347
|
+
def list_files(directory: str = "") -> str:
|
|
3348
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
3349
|
+
try:
|
|
3350
|
+
target = _safe_resolve(directory)
|
|
3351
|
+
entries = os.listdir(target)
|
|
3352
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
3353
|
+
except ValueError as e:
|
|
3354
|
+
return str(e)
|
|
3355
|
+
except OSError as e:
|
|
3356
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
3357
|
+
|
|
3358
|
+
tools.extend([file_read, file_write, list_files])
|
|
3359
|
+
{{/if}}
|
|
3360
|
+
|
|
3361
|
+
AGENT_INSTRUCTION = """
|
|
3362
|
+
I can answer your questions using the knowledge I have!
|
|
3363
|
+
{{#if sessionStorageMountPath}}
|
|
3364
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
3365
|
+
{{/if}}
|
|
3366
|
+
"""
|
|
3367
|
+
|
|
2205
3368
|
# Get MCP Toolset
|
|
2206
3369
|
{{#if hasGateway}}
|
|
2207
3370
|
mcp_toolset = get_all_gateway_mcp_toolsets()
|
|
@@ -2224,8 +3387,8 @@ agent = Agent(
|
|
|
2224
3387
|
model=MODEL_ID,
|
|
2225
3388
|
name="{{ name }}",
|
|
2226
3389
|
description="Agent to answer questions",
|
|
2227
|
-
instruction=
|
|
2228
|
-
tools=mcp_toolset +
|
|
3390
|
+
instruction=AGENT_INSTRUCTION,
|
|
3391
|
+
tools=mcp_toolset + tools,
|
|
2229
3392
|
)
|
|
2230
3393
|
|
|
2231
3394
|
|
|
@@ -2563,6 +3726,66 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
2563
3726
|
# Define a collection of tools used by the model
|
|
2564
3727
|
tools = [add_numbers]
|
|
2565
3728
|
|
|
3729
|
+
{{#if sessionStorageMountPath}}
|
|
3730
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
3731
|
+
|
|
3732
|
+
def _safe_resolve(path: str) -> str:
|
|
3733
|
+
"""Resolve path safely within the storage boundary."""
|
|
3734
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
3735
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
3736
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
3737
|
+
return resolved
|
|
3738
|
+
|
|
3739
|
+
@tool
|
|
3740
|
+
def file_read(path: str) -> str:
|
|
3741
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
3742
|
+
try:
|
|
3743
|
+
full_path = _safe_resolve(path)
|
|
3744
|
+
with open(full_path) as f:
|
|
3745
|
+
return f.read()
|
|
3746
|
+
except ValueError as e:
|
|
3747
|
+
return str(e)
|
|
3748
|
+
except OSError as e:
|
|
3749
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
3750
|
+
|
|
3751
|
+
@tool
|
|
3752
|
+
def file_write(path: str, content: str) -> str:
|
|
3753
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
3754
|
+
try:
|
|
3755
|
+
full_path = _safe_resolve(path)
|
|
3756
|
+
parent = os.path.dirname(full_path)
|
|
3757
|
+
if parent:
|
|
3758
|
+
os.makedirs(parent, exist_ok=True)
|
|
3759
|
+
with open(full_path, "w") as f:
|
|
3760
|
+
f.write(content)
|
|
3761
|
+
return f"Written to {path}"
|
|
3762
|
+
except ValueError as e:
|
|
3763
|
+
return str(e)
|
|
3764
|
+
except OSError as e:
|
|
3765
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
3766
|
+
|
|
3767
|
+
@tool
|
|
3768
|
+
def list_files(directory: str = "") -> str:
|
|
3769
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
3770
|
+
try:
|
|
3771
|
+
target = _safe_resolve(directory)
|
|
3772
|
+
entries = os.listdir(target)
|
|
3773
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
3774
|
+
except ValueError as e:
|
|
3775
|
+
return str(e)
|
|
3776
|
+
except OSError as e:
|
|
3777
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
3778
|
+
|
|
3779
|
+
tools.extend([file_read, file_write, list_files])
|
|
3780
|
+
{{/if}}
|
|
3781
|
+
|
|
3782
|
+
SYSTEM_PROMPT = """
|
|
3783
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
3784
|
+
{{#if sessionStorageMountPath}}
|
|
3785
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
3786
|
+
{{/if}}
|
|
3787
|
+
"""
|
|
3788
|
+
|
|
2566
3789
|
|
|
2567
3790
|
@app.entrypoint
|
|
2568
3791
|
async def invoke(payload, context):
|
|
@@ -2581,7 +3804,7 @@ async def invoke(payload, context):
|
|
|
2581
3804
|
mcp_tools = await mcp_client.get_tools()
|
|
2582
3805
|
|
|
2583
3806
|
# Define the agent using create_react_agent
|
|
2584
|
-
graph = create_react_agent(get_or_create_model(), tools=mcp_tools + tools)
|
|
3807
|
+
graph = create_react_agent(get_or_create_model(), tools=mcp_tools + tools, prompt=SYSTEM_PROMPT)
|
|
2585
3808
|
|
|
2586
3809
|
# Process the user prompt
|
|
2587
3810
|
prompt = payload.get("prompt", "What can you help me with?")
|
|
@@ -2986,6 +4209,68 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
2986
4209
|
return a + b
|
|
2987
4210
|
|
|
2988
4211
|
|
|
4212
|
+
tools = [add_numbers]
|
|
4213
|
+
|
|
4214
|
+
{{#if sessionStorageMountPath}}
|
|
4215
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
4216
|
+
|
|
4217
|
+
def _safe_resolve(path: str) -> str:
|
|
4218
|
+
"""Resolve path safely within the storage boundary."""
|
|
4219
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
4220
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
4221
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
4222
|
+
return resolved
|
|
4223
|
+
|
|
4224
|
+
@function_tool
|
|
4225
|
+
def file_read(path: str) -> str:
|
|
4226
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
4227
|
+
try:
|
|
4228
|
+
full_path = _safe_resolve(path)
|
|
4229
|
+
with open(full_path) as f:
|
|
4230
|
+
return f.read()
|
|
4231
|
+
except ValueError as e:
|
|
4232
|
+
return str(e)
|
|
4233
|
+
except OSError as e:
|
|
4234
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
4235
|
+
|
|
4236
|
+
@function_tool
|
|
4237
|
+
def file_write(path: str, content: str) -> str:
|
|
4238
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
4239
|
+
try:
|
|
4240
|
+
full_path = _safe_resolve(path)
|
|
4241
|
+
parent = os.path.dirname(full_path)
|
|
4242
|
+
if parent:
|
|
4243
|
+
os.makedirs(parent, exist_ok=True)
|
|
4244
|
+
with open(full_path, "w") as f:
|
|
4245
|
+
f.write(content)
|
|
4246
|
+
return f"Written to {path}"
|
|
4247
|
+
except ValueError as e:
|
|
4248
|
+
return str(e)
|
|
4249
|
+
except OSError as e:
|
|
4250
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
4251
|
+
|
|
4252
|
+
@function_tool
|
|
4253
|
+
def list_files(directory: str = "") -> str:
|
|
4254
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
4255
|
+
try:
|
|
4256
|
+
target = _safe_resolve(directory)
|
|
4257
|
+
entries = os.listdir(target)
|
|
4258
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
4259
|
+
except ValueError as e:
|
|
4260
|
+
return str(e)
|
|
4261
|
+
except OSError as e:
|
|
4262
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
4263
|
+
|
|
4264
|
+
tools.extend([file_read, file_write, list_files])
|
|
4265
|
+
{{/if}}
|
|
4266
|
+
|
|
4267
|
+
INSTRUCTIONS = """
|
|
4268
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
4269
|
+
{{#if sessionStorageMountPath}}
|
|
4270
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
4271
|
+
{{/if}}
|
|
4272
|
+
"""
|
|
4273
|
+
|
|
2989
4274
|
# Define the agent execution
|
|
2990
4275
|
async def main(query):
|
|
2991
4276
|
ensure_credentials_loaded()
|
|
@@ -2995,8 +4280,9 @@ async def main(query):
|
|
|
2995
4280
|
agent = Agent(
|
|
2996
4281
|
name="{{ name }}",
|
|
2997
4282
|
model="gpt-4.1",
|
|
4283
|
+
instructions=INSTRUCTIONS,
|
|
2998
4284
|
mcp_servers=mcp_servers,
|
|
2999
|
-
tools=
|
|
4285
|
+
tools=tools
|
|
3000
4286
|
)
|
|
3001
4287
|
result = await Runner.run(agent, query)
|
|
3002
4288
|
return result
|
|
@@ -3004,8 +4290,9 @@ async def main(query):
|
|
|
3004
4290
|
agent = Agent(
|
|
3005
4291
|
name="{{ name }}",
|
|
3006
4292
|
model="gpt-4.1",
|
|
4293
|
+
instructions=INSTRUCTIONS,
|
|
3007
4294
|
mcp_servers=[],
|
|
3008
|
-
tools=
|
|
4295
|
+
tools=tools
|
|
3009
4296
|
)
|
|
3010
4297
|
result = await Runner.run(agent, query)
|
|
3011
4298
|
return result
|
|
@@ -3016,8 +4303,9 @@ async def main(query):
|
|
|
3016
4303
|
agent = Agent(
|
|
3017
4304
|
name="{{ name }}",
|
|
3018
4305
|
model="gpt-4.1",
|
|
4306
|
+
instructions=INSTRUCTIONS,
|
|
3019
4307
|
mcp_servers=active_servers,
|
|
3020
|
-
tools=
|
|
4308
|
+
tools=tools
|
|
3021
4309
|
)
|
|
3022
4310
|
result = await Runner.run(agent, query)
|
|
3023
4311
|
return result
|
|
@@ -3025,8 +4313,9 @@ async def main(query):
|
|
|
3025
4313
|
agent = Agent(
|
|
3026
4314
|
name="{{ name }}",
|
|
3027
4315
|
model="gpt-4.1",
|
|
4316
|
+
instructions=INSTRUCTIONS,
|
|
3028
4317
|
mcp_servers=[],
|
|
3029
|
-
tools=
|
|
4318
|
+
tools=tools
|
|
3030
4319
|
)
|
|
3031
4320
|
result = await Runner.run(agent, query)
|
|
3032
4321
|
return result
|
|
@@ -3308,6 +4597,9 @@ from mcp_client.client import get_streamable_http_mcp_client
|
|
|
3308
4597
|
{{#if hasMemory}}
|
|
3309
4598
|
from memory.session import get_memory_session_manager
|
|
3310
4599
|
{{/if}}
|
|
4600
|
+
{{#if sessionStorageMountPath}}
|
|
4601
|
+
import os
|
|
4602
|
+
{{/if}}
|
|
3311
4603
|
|
|
3312
4604
|
app = BedrockAgentCoreApp()
|
|
3313
4605
|
log = app.logger
|
|
@@ -3329,11 +4621,70 @@ def add_numbers(a: int, b: int) -> int:
|
|
|
3329
4621
|
return a+b
|
|
3330
4622
|
tools.append(add_numbers)
|
|
3331
4623
|
|
|
4624
|
+
{{#if sessionStorageMountPath}}
|
|
4625
|
+
SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
|
|
4626
|
+
|
|
4627
|
+
def _safe_resolve(path: str) -> str:
|
|
4628
|
+
"""Resolve path safely within the storage boundary."""
|
|
4629
|
+
resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
|
|
4630
|
+
if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
|
|
4631
|
+
raise ValueError(f"Path '{path}' is outside the storage boundary")
|
|
4632
|
+
return resolved
|
|
4633
|
+
|
|
4634
|
+
@tool
|
|
4635
|
+
def file_read(path: str) -> str:
|
|
4636
|
+
"""Read a file from persistent storage. The path is relative to the storage root."""
|
|
4637
|
+
try:
|
|
4638
|
+
full_path = _safe_resolve(path)
|
|
4639
|
+
with open(full_path) as f:
|
|
4640
|
+
return f.read()
|
|
4641
|
+
except ValueError as e:
|
|
4642
|
+
return str(e)
|
|
4643
|
+
except OSError as e:
|
|
4644
|
+
return f"Error reading '{path}': {e.strerror}"
|
|
4645
|
+
|
|
4646
|
+
@tool
|
|
4647
|
+
def file_write(path: str, content: str) -> str:
|
|
4648
|
+
"""Write content to a file in persistent storage. The path is relative to the storage root."""
|
|
4649
|
+
try:
|
|
4650
|
+
full_path = _safe_resolve(path)
|
|
4651
|
+
parent = os.path.dirname(full_path)
|
|
4652
|
+
if parent:
|
|
4653
|
+
os.makedirs(parent, exist_ok=True)
|
|
4654
|
+
with open(full_path, "w") as f:
|
|
4655
|
+
f.write(content)
|
|
4656
|
+
return f"Written to {path}"
|
|
4657
|
+
except ValueError as e:
|
|
4658
|
+
return str(e)
|
|
4659
|
+
except OSError as e:
|
|
4660
|
+
return f"Error writing '{path}': {e.strerror}"
|
|
4661
|
+
|
|
4662
|
+
@tool
|
|
4663
|
+
def list_files(directory: str = "") -> str:
|
|
4664
|
+
"""List files in persistent storage. The directory is relative to the storage root."""
|
|
4665
|
+
try:
|
|
4666
|
+
target = _safe_resolve(directory)
|
|
4667
|
+
entries = os.listdir(target)
|
|
4668
|
+
return "\\n".join(entries) if entries else "(empty directory)"
|
|
4669
|
+
except ValueError as e:
|
|
4670
|
+
return str(e)
|
|
4671
|
+
except OSError as e:
|
|
4672
|
+
return f"Error listing '{directory}': {e.strerror}"
|
|
4673
|
+
|
|
4674
|
+
tools.extend([file_read, file_write, list_files])
|
|
4675
|
+
{{/if}}
|
|
4676
|
+
|
|
3332
4677
|
# Add MCP client to tools if available
|
|
3333
4678
|
for mcp_client in mcp_clients:
|
|
3334
4679
|
if mcp_client:
|
|
3335
4680
|
tools.append(mcp_client)
|
|
3336
4681
|
|
|
4682
|
+
SYSTEM_PROMPT = """
|
|
4683
|
+
You are a helpful assistant. Use tools when appropriate.
|
|
4684
|
+
{{#if sessionStorageMountPath}}
|
|
4685
|
+
You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
|
|
4686
|
+
{{/if}}
|
|
4687
|
+
"""
|
|
3337
4688
|
|
|
3338
4689
|
{{#if hasMemory}}
|
|
3339
4690
|
def agent_factory():
|
|
@@ -3345,9 +4696,7 @@ def agent_factory():
|
|
|
3345
4696
|
cache[key] = Agent(
|
|
3346
4697
|
model=load_model(),
|
|
3347
4698
|
session_manager=get_memory_session_manager(session_id, user_id),
|
|
3348
|
-
system_prompt=
|
|
3349
|
-
You are a helpful assistant. Use tools when appropriate.
|
|
3350
|
-
""",
|
|
4699
|
+
system_prompt=SYSTEM_PROMPT,
|
|
3351
4700
|
tools=tools
|
|
3352
4701
|
)
|
|
3353
4702
|
return cache[key]
|
|
@@ -3361,9 +4710,7 @@ def get_or_create_agent():
|
|
|
3361
4710
|
if _agent is None:
|
|
3362
4711
|
_agent = Agent(
|
|
3363
4712
|
model=load_model(),
|
|
3364
|
-
system_prompt=
|
|
3365
|
-
You are a helpful assistant. Use tools when appropriate.
|
|
3366
|
-
""",
|
|
4713
|
+
system_prompt=SYSTEM_PROMPT,
|
|
3367
4714
|
tools=tools
|
|
3368
4715
|
)
|
|
3369
4716
|
return _agent
|
|
@@ -3826,27 +5173,47 @@ exports[`Assets Directory Snapshots > Root-level assets > AGENTS.md should match
|
|
|
3826
5173
|
|
|
3827
5174
|
This directory stores:
|
|
3828
5175
|
|
|
3829
|
-
- Template assets for agents written in different
|
|
5176
|
+
- Template assets for agents written in different languages, SDKs, and configurations
|
|
3830
5177
|
- Container templates (\`container/python/\`) with \`Dockerfile\` and \`.dockerignore\` for Container build agents
|
|
5178
|
+
- Vended documentation (\`README.md\`, \`agents/AGENTS.md\`) copied into user projects at create time
|
|
5179
|
+
- CDK project template (\`cdk/\`) using \`@aws/agentcore-cdk\` L3 constructs
|
|
5180
|
+
- Evaluator templates (\`evaluators/\`) for code-based evaluators
|
|
5181
|
+
- MCP tool templates (\`mcp/\`) for Lambda and AgentCoreRuntime compute
|
|
3831
5182
|
|
|
3832
5183
|
### Directory Layout
|
|
3833
5184
|
|
|
3834
5185
|
\`\`\`
|
|
3835
5186
|
assets/
|
|
3836
|
-
├──
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
│
|
|
3840
|
-
|
|
3841
|
-
│
|
|
5187
|
+
├── README.md # Vended to project root as project README
|
|
5188
|
+
├── AGENTS.md # This file — internal dev context
|
|
5189
|
+
├── agents/
|
|
5190
|
+
│ └── AGENTS.md # Vended to project root for AI coding assistants
|
|
5191
|
+
├── python/ # Framework templates (one per SDK per protocol)
|
|
5192
|
+
│ ├── http/ # HTTP protocol agents
|
|
5193
|
+
│ │ ├── strands/
|
|
5194
|
+
│ │ ├── langchain_langgraph/
|
|
5195
|
+
│ │ ├── googleadk/
|
|
5196
|
+
│ │ ├── openaiagents/
|
|
5197
|
+
│ │ └── autogen/
|
|
5198
|
+
│ ├── mcp/ # MCP protocol agents
|
|
5199
|
+
│ │ └── standalone/
|
|
5200
|
+
│ └── a2a/ # A2A protocol agents
|
|
5201
|
+
│ ├── strands/
|
|
5202
|
+
│ ├── langchain_langgraph/
|
|
5203
|
+
│ └── googleadk/
|
|
5204
|
+
├── typescript/ # TypeScript agent templates
|
|
3842
5205
|
├── container/ # Container build templates
|
|
3843
5206
|
│ └── python/
|
|
3844
5207
|
│ ├── Dockerfile
|
|
3845
5208
|
│ └── dockerignore.template
|
|
3846
|
-
|
|
5209
|
+
├── cdk/ # CDK project template (@aws/agentcore-cdk)
|
|
5210
|
+
├── evaluators/ # Code-based evaluator templates
|
|
5211
|
+
└── mcp/ # MCP tool templates (Lambda + AgentCoreRuntime)
|
|
5212
|
+
├── python/
|
|
5213
|
+
└── python-lambda/
|
|
3847
5214
|
\`\`\`
|
|
3848
5215
|
|
|
3849
|
-
The rendering logic is rooted in the \`AgentEnvSpec\` and must ALWAYS respect the configuration in the
|
|
5216
|
+
The rendering logic is rooted in the \`AgentEnvSpec\` and must ALWAYS respect the configuration in the spec.
|
|
3850
5217
|
|
|
3851
5218
|
For Container builds, \`BaseRenderer.render()\` automatically copies the \`container/<language>/\` templates (Dockerfile,
|
|
3852
5219
|
.dockerignore) into the agent directory when \`buildType === 'Container'\`.
|
|
@@ -3855,10 +5222,13 @@ For Container builds, \`BaseRenderer.render()\` automatically copies the \`conta
|
|
|
3855
5222
|
|
|
3856
5223
|
- Always make sure the templates are as close to working code as possible
|
|
3857
5224
|
- AVOID as much as possible using any conditionals within the templates
|
|
5225
|
+
- Test template rendering with \`agentcore add agent\` for each framework/protocol combination
|
|
3858
5226
|
|
|
3859
5227
|
## How to use the assets in this directory
|
|
3860
5228
|
|
|
3861
|
-
- These assets are rendered by the CLI's template renderer in \`src/cli/templates
|
|
5229
|
+
- These assets are rendered by the CLI's template renderer in \`src/cli/templates/\`
|
|
5230
|
+
- The \`README.md\` and \`agents/AGENTS.md\` are copied verbatim (no template rendering) during project creation
|
|
5231
|
+
- The \`.llm-context/\` files are sourced from \`src/schema/llm-compacted/\` and written during init
|
|
3862
5232
|
"
|
|
3863
5233
|
`;
|
|
3864
5234
|
|
|
@@ -3870,14 +5240,19 @@ This project was created with the [AgentCore CLI](https://github.com/aws/agentco
|
|
|
3870
5240
|
## Project Structure
|
|
3871
5241
|
|
|
3872
5242
|
\`\`\`
|
|
3873
|
-
.
|
|
3874
5243
|
my-project/
|
|
5244
|
+
├── AGENTS.md # AI coding assistant context
|
|
3875
5245
|
├── agentcore/
|
|
3876
|
-
│ ├── .
|
|
3877
|
-
│ ├──
|
|
3878
|
-
│ ├──
|
|
3879
|
-
│
|
|
3880
|
-
├──
|
|
5246
|
+
│ ├── agentcore.json # Project config (agents, memories, credentials, gateways, evaluators)
|
|
5247
|
+
│ ├── aws-targets.json # Deployment targets (account + region)
|
|
5248
|
+
│ ├── .env.local # Secrets — API keys (gitignored)
|
|
5249
|
+
│ ├── .llm-context/ # TypeScript type definitions for AI assistants
|
|
5250
|
+
│ │ ├── agentcore.ts # AgentCoreProjectSpec types
|
|
5251
|
+
│ │ ├── aws-targets.ts # Deployment target types
|
|
5252
|
+
│ │ └── mcp.ts # Gateway and MCP tool types
|
|
5253
|
+
│ └── cdk/ # CDK infrastructure (@aws/agentcore-cdk)
|
|
5254
|
+
├── app/ # Agent application code
|
|
5255
|
+
└── evaluators/ # Custom evaluator code (if any)
|
|
3881
5256
|
\`\`\`
|
|
3882
5257
|
|
|
3883
5258
|
## Getting Started
|
|
@@ -3885,7 +5260,9 @@ my-project/
|
|
|
3885
5260
|
### Prerequisites
|
|
3886
5261
|
|
|
3887
5262
|
- **Node.js** 20.x or later
|
|
3888
|
-
- **uv** for Python agents ([install](https://docs.astral.sh/uv/getting-started/installation/))
|
|
5263
|
+
- **Python 3.10+** and **uv** for Python agents ([install uv](https://docs.astral.sh/uv/getting-started/installation/))
|
|
5264
|
+
- **AWS credentials** configured (\`aws configure\` or environment variables)
|
|
5265
|
+
- **Docker** (only for Container build agents)
|
|
3889
5266
|
|
|
3890
5267
|
### Development
|
|
3891
5268
|
|
|
@@ -3903,44 +5280,62 @@ Deploy to AWS:
|
|
|
3903
5280
|
agentcore deploy
|
|
3904
5281
|
\`\`\`
|
|
3905
5282
|
|
|
3906
|
-
|
|
5283
|
+
## Commands
|
|
3907
5284
|
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
5285
|
+
| Command | Description |
|
|
5286
|
+
| --- | --- |
|
|
5287
|
+
| \`agentcore create\` | Create a new AgentCore project |
|
|
5288
|
+
| \`agentcore add\` | Add resources (agent, memory, credential, gateway, evaluator, policy) |
|
|
5289
|
+
| \`agentcore remove\` | Remove resources |
|
|
5290
|
+
| \`agentcore dev\` | Run agent locally with hot-reload |
|
|
5291
|
+
| \`agentcore deploy\` | Deploy to AWS via CDK |
|
|
5292
|
+
| \`agentcore status\` | Show deployment status |
|
|
5293
|
+
| \`agentcore invoke\` | Invoke agent (local or deployed) |
|
|
5294
|
+
| \`agentcore logs\` | View agent logs |
|
|
5295
|
+
| \`agentcore traces\` | View agent traces |
|
|
5296
|
+
| \`agentcore eval\` | Run evaluations |
|
|
5297
|
+
| \`agentcore package\` | Package agent artifacts |
|
|
5298
|
+
| \`agentcore validate\` | Validate configuration |
|
|
5299
|
+
| \`agentcore pause\` | Pause a deployed agent |
|
|
5300
|
+
| \`agentcore resume\` | Resume a paused agent |
|
|
5301
|
+
| \`agentcore fetch\` | Fetch remote resource definitions |
|
|
5302
|
+
| \`agentcore import\` | Import existing resources |
|
|
5303
|
+
| \`agentcore update\` | Check for CLI updates |
|
|
3912
5304
|
|
|
3913
5305
|
## Configuration
|
|
3914
5306
|
|
|
3915
|
-
Edit the JSON files in \`agentcore/\` to configure your
|
|
3916
|
-
type definitions and validation constraints.
|
|
5307
|
+
Edit the JSON files in \`agentcore/\` to configure your project. See \`agentcore/.llm-context/\` for type definitions and validation constraints.
|
|
3917
5308
|
|
|
3918
|
-
The project uses a **flat resource model**
|
|
3919
|
-
\`agentcore.json\`.
|
|
5309
|
+
The project uses a **flat resource model** — agents, memories, credentials, gateways, evaluators, and policies are top-level arrays in \`agentcore.json\`. Resources are independent; agents discover memories and credentials at runtime via environment variables or SDK calls.
|
|
3920
5310
|
|
|
3921
|
-
##
|
|
5311
|
+
## Resources
|
|
3922
5312
|
|
|
3923
|
-
|
|
|
3924
|
-
|
|
|
3925
|
-
|
|
|
3926
|
-
|
|
|
3927
|
-
|
|
|
3928
|
-
|
|
|
3929
|
-
|
|
|
3930
|
-
|
|
|
3931
|
-
|
|
|
3932
|
-
|
|
|
3933
|
-
| \`agentcore validate\` | Validate configuration |
|
|
3934
|
-
| \`agentcore update\` | Check for CLI updates |
|
|
5313
|
+
| Resource | Purpose |
|
|
5314
|
+
| --- | --- |
|
|
5315
|
+
| Agent (runtime) | HTTP, MCP, or A2A agent deployed to AgentCore Runtime |
|
|
5316
|
+
| Memory | Persistent context storage with configurable strategies |
|
|
5317
|
+
| Credential | API key or OAuth credential providers |
|
|
5318
|
+
| Gateway | MCP gateway that routes tool calls to targets |
|
|
5319
|
+
| Gateway Target | Tool implementation (Lambda, MCP server, OpenAPI, Smithy, API Gateway) |
|
|
5320
|
+
| Evaluator | Custom LLM-as-a-Judge or code-based evaluation |
|
|
5321
|
+
| Online Eval Config | Continuous evaluation pipeline for deployed agents |
|
|
5322
|
+
| Policy | Cedar authorization policies for gateway tools |
|
|
3935
5323
|
|
|
3936
5324
|
### Agent Types
|
|
3937
5325
|
|
|
3938
|
-
- **Template agents**: Created from framework templates (Strands,
|
|
5326
|
+
- **Template agents**: Created from framework templates (Strands, LangChain/LangGraph, GoogleADK, OpenAI Agents, Autogen)
|
|
3939
5327
|
- **BYO agents**: Bring your own code with \`agentcore add agent --type byo\`
|
|
5328
|
+
- **Import agents**: Import existing Bedrock agents with \`agentcore import\`
|
|
5329
|
+
|
|
5330
|
+
### Build Types
|
|
5331
|
+
|
|
5332
|
+
- **CodeZip**: Python source packaged as a zip and deployed directly to AgentCore Runtime
|
|
5333
|
+
- **Container**: Docker image built via CodeBuild (ARM64), pushed to ECR, and deployed to AgentCore Runtime
|
|
3940
5334
|
|
|
3941
5335
|
## Documentation
|
|
3942
5336
|
|
|
3943
|
-
- [AgentCore CLI
|
|
5337
|
+
- [AgentCore CLI](https://github.com/aws/agentcore-cli)
|
|
5338
|
+
- [AgentCore CDK Constructs](https://github.com/aws/agentcore-l3-cdk-constructs)
|
|
3944
5339
|
- [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/)
|
|
3945
5340
|
"
|
|
3946
5341
|
`;
|
|
@@ -3950,103 +5345,114 @@ exports[`Assets Directory Snapshots > Root-level assets > agents/AGENTS.md shoul
|
|
|
3950
5345
|
|
|
3951
5346
|
This project contains configuration and infrastructure for an Amazon Bedrock AgentCore application.
|
|
3952
5347
|
|
|
3953
|
-
The \`agentcore/\` directory
|
|
3954
|
-
|
|
3955
|
-
model** where agents, memories, and credentials are top-level arrays.
|
|
5348
|
+
The \`agentcore/\` directory is a declarative model of the project. The \`agentcore/cdk/\` subdirectory uses the
|
|
5349
|
+
\`@aws/agentcore-cdk\` L3 constructs to deploy the configuration to AWS.
|
|
3956
5350
|
|
|
3957
5351
|
## Mental Model
|
|
3958
5352
|
|
|
3959
|
-
The project uses a **flat resource model**. Agents, memories,
|
|
3960
|
-
\`agentcore.json\`. There is no binding
|
|
3961
|
-
independently.
|
|
3962
|
-
|
|
5353
|
+
The project uses a **flat resource model**. Agents, memories, credentials, gateways, evaluators, and policies are
|
|
5354
|
+
independent top-level arrays in \`agentcore.json\`. There is no binding between resources in the schema — each resource is
|
|
5355
|
+
provisioned independently. Agents discover memories and credentials at runtime via environment variables or SDK calls.
|
|
5356
|
+
Tags defined in \`agentcore.json\` flow through to deployed CloudFormation resources.
|
|
3963
5357
|
|
|
3964
5358
|
## Critical Invariants
|
|
3965
5359
|
|
|
3966
|
-
1. **Schema-First Authority:** The \`.json\` files are the
|
|
3967
|
-
|
|
3968
|
-
2. **Resource Identity:** The \`name\` field
|
|
3969
|
-
- **Renaming**
|
|
3970
|
-
- **Modifying** other fields
|
|
3971
|
-
3. **
|
|
3972
|
-
|
|
3973
|
-
4. **Resource Removal:**
|
|
3974
|
-
|
|
5360
|
+
1. **Schema-First Authority:** The \`.json\` files are the source of truth. Do not modify agent behavior by editing
|
|
5361
|
+
generated CDK code in \`cdk/\`.
|
|
5362
|
+
2. **Resource Identity:** The \`name\` field determines the CloudFormation Logical ID.
|
|
5363
|
+
- **Renaming** a resource will **destroy and recreate** it.
|
|
5364
|
+
- **Modifying** other fields will update the resource **in-place**.
|
|
5365
|
+
3. **Schema Validation:** If your JSON conforms to the types in \`.llm-context/\`, it will deploy successfully. Run
|
|
5366
|
+
\`agentcore validate\` to check.
|
|
5367
|
+
4. **Resource Removal:** Use \`agentcore remove\` to remove resources. Run \`agentcore deploy\` after removal to tear down
|
|
5368
|
+
deployed infrastructure.
|
|
3975
5369
|
|
|
3976
5370
|
## Directory Structure
|
|
3977
5371
|
|
|
3978
5372
|
\`\`\`
|
|
3979
|
-
|
|
3980
|
-
├── AGENTS.md # This file
|
|
3981
|
-
├── agentcore/
|
|
5373
|
+
myProject/
|
|
5374
|
+
├── AGENTS.md # This file — AI coding assistant context
|
|
5375
|
+
├── agentcore/
|
|
3982
5376
|
│ ├── agentcore.json # Main project config (AgentCoreProjectSpec)
|
|
3983
|
-
│ ├── aws-targets.json # Deployment targets
|
|
3984
|
-
│ ├── .
|
|
3985
|
-
│
|
|
5377
|
+
│ ├── aws-targets.json # Deployment targets (account + region)
|
|
5378
|
+
│ ├── .env.local # Secrets — API keys (gitignored)
|
|
5379
|
+
│ ├── .llm-context/ # TypeScript type definitions for AI assistants
|
|
5380
|
+
│ │ ├── README.md # Guide to using schema files
|
|
3986
5381
|
│ │ ├── agentcore.ts # AgentCoreProjectSpec types
|
|
3987
|
-
│ │
|
|
3988
|
-
│ └──
|
|
3989
|
-
└──
|
|
5382
|
+
│ │ ├── aws-targets.ts # AWS deployment target types
|
|
5383
|
+
│ │ └── mcp.ts # Gateway and MCP tool types
|
|
5384
|
+
│ └── cdk/ # AWS CDK project (@aws/agentcore-cdk L3 constructs)
|
|
5385
|
+
├── app/ # Agent application code
|
|
5386
|
+
└── evaluators/ # Custom evaluator code (if any)
|
|
3990
5387
|
\`\`\`
|
|
3991
5388
|
|
|
3992
5389
|
## Schema Reference
|
|
3993
5390
|
|
|
3994
5391
|
The \`agentcore/.llm-context/\` directory contains TypeScript type definitions optimized for AI coding assistants. Each
|
|
3995
|
-
file maps to a JSON config file and includes validation constraints as comments.
|
|
5392
|
+
file maps to a JSON config file and includes validation constraints as comments (\`@regex\`, \`@min\`, \`@max\`).
|
|
3996
5393
|
|
|
3997
|
-
| JSON Config
|
|
3998
|
-
|
|
|
3999
|
-
| \`agentcore/agentcore.json\`
|
|
4000
|
-
| \`agentcore/
|
|
5394
|
+
| JSON Config | Schema File | Root Type |
|
|
5395
|
+
| --- | --- | --- |
|
|
5396
|
+
| \`agentcore/agentcore.json\` | \`agentcore/.llm-context/agentcore.ts\` | \`AgentCoreProjectSpec\` |
|
|
5397
|
+
| \`agentcore/agentcore.json\` (gateways) | \`agentcore/.llm-context/mcp.ts\` | \`AgentCoreMcpSpec\` |
|
|
5398
|
+
| \`agentcore/aws-targets.json\` | \`agentcore/.llm-context/aws-targets.ts\` | \`AwsDeploymentTarget[]\` |
|
|
4001
5399
|
|
|
4002
5400
|
### Key Types
|
|
4003
5401
|
|
|
4004
|
-
- **AgentCoreProjectSpec**: Root
|
|
4005
|
-
- **AgentEnvSpec**: Agent configuration (
|
|
4006
|
-
- **Memory**: Memory resource with strategies and expiry
|
|
4007
|
-
- **Credential**: API key credential provider
|
|
5402
|
+
- **AgentCoreProjectSpec**: Root config with \`runtimes\`, \`memories\`, \`credentials\`, \`agentCoreGateways\`, \`evaluators\`, \`onlineEvalConfigs\`, \`policyEngines\` arrays
|
|
5403
|
+
- **AgentEnvSpec**: Agent configuration (build type, entrypoint, code location, runtime version, network mode)
|
|
5404
|
+
- **Memory**: Memory resource with strategies (SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC) and expiry
|
|
5405
|
+
- **Credential**: API key or OAuth credential provider
|
|
5406
|
+
- **AgentCoreGateway**: MCP gateway with targets (Lambda, MCP server, OpenAPI, Smithy, API Gateway)
|
|
5407
|
+
- **Evaluator**: LLM-as-a-Judge or code-based evaluator
|
|
5408
|
+
- **OnlineEvalConfig**: Continuous evaluation pipeline bound to an agent
|
|
4008
5409
|
|
|
4009
5410
|
### Common Enum Values
|
|
4010
5411
|
|
|
4011
5412
|
- **BuildType**: \`'CodeZip'\` | \`'Container'\`
|
|
4012
|
-
- **NetworkMode**: \`'PUBLIC'\`
|
|
4013
|
-
- **RuntimeVersion**: \`'PYTHON_3_10'\` | \`'PYTHON_3_11'\` | \`'PYTHON_3_12'\` | \`'PYTHON_3_13'\` | \`'PYTHON_3_14'\`
|
|
5413
|
+
- **NetworkMode**: \`'PUBLIC'\` | \`'VPC'\`
|
|
5414
|
+
- **RuntimeVersion**: \`'PYTHON_3_10'\` | \`'PYTHON_3_11'\` | \`'PYTHON_3_12'\` | \`'PYTHON_3_13'\` | \`'PYTHON_3_14'\` | \`'NODE_18'\` | \`'NODE_20'\` | \`'NODE_22'\`
|
|
4014
5415
|
- **MemoryStrategyType**: \`'SEMANTIC'\` | \`'SUMMARIZATION'\` | \`'USER_PREFERENCE'\` | \`'EPISODIC'\`
|
|
5416
|
+
- **GatewayTargetType**: \`'lambda'\` | \`'mcpServer'\` | \`'openApiSchema'\` | \`'smithyModel'\` | \`'apiGateway'\` | \`'lambdaFunctionArn'\`
|
|
5417
|
+
- **ModelProvider**: \`'Bedrock'\` | \`'Gemini'\` | \`'OpenAI'\` | \`'Anthropic'\`
|
|
4015
5418
|
|
|
4016
5419
|
### Build Types
|
|
4017
5420
|
|
|
4018
|
-
- **CodeZip**: Python source
|
|
4019
|
-
- **Container**:
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
container is built and run locally with volume-mounted hot-reload.
|
|
5421
|
+
- **CodeZip**: Python source packaged as a zip and deployed directly to AgentCore Runtime.
|
|
5422
|
+
- **Container**: Docker image built in CodeBuild (ARM64), pushed to a per-agent ECR repository. Requires a \`Dockerfile\`
|
|
5423
|
+
in the agent's \`codeLocation\` directory. For local development (\`agentcore dev\`), the container is built and run
|
|
5424
|
+
locally with volume-mounted hot-reload.
|
|
4023
5425
|
|
|
4024
5426
|
### Supported Frameworks (for template agents)
|
|
4025
5427
|
|
|
4026
|
-
- **Strands**
|
|
4027
|
-
- **
|
|
4028
|
-
- **GoogleADK**
|
|
4029
|
-
- **
|
|
5428
|
+
- **Strands** — Bedrock, Anthropic, OpenAI, Gemini
|
|
5429
|
+
- **LangChain/LangGraph** — Bedrock, Anthropic, OpenAI, Gemini
|
|
5430
|
+
- **GoogleADK** — Gemini
|
|
5431
|
+
- **OpenAI Agents** — OpenAI
|
|
5432
|
+
- **Autogen** — Bedrock, Anthropic, OpenAI, Gemini
|
|
4030
5433
|
|
|
5434
|
+
### Protocols
|
|
4031
5435
|
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
deployment options are available.
|
|
5436
|
+
- **HTTP** — Standard HTTP agent endpoint
|
|
5437
|
+
- **MCP** — Model Context Protocol server
|
|
5438
|
+
- **A2A** — Agent-to-Agent protocol (Google A2A)
|
|
4036
5439
|
|
|
4037
5440
|
## Deployment
|
|
4038
5441
|
|
|
4039
|
-
|
|
5442
|
+
Deployments are orchestrated through the CLI:
|
|
4040
5443
|
|
|
4041
|
-
|
|
5444
|
+
\`\`\`bash
|
|
5445
|
+
agentcore deploy # Synthesizes CDK and deploys to AWS
|
|
5446
|
+
agentcore status # Shows deployment status
|
|
5447
|
+
\`\`\`
|
|
4042
5448
|
|
|
4043
|
-
Alternatively,
|
|
5449
|
+
Alternatively, deploy directly via CDK:
|
|
4044
5450
|
|
|
4045
5451
|
\`\`\`bash
|
|
4046
5452
|
cd agentcore/cdk
|
|
4047
5453
|
npm install
|
|
4048
|
-
npx cdk synth
|
|
4049
|
-
npx cdk deploy
|
|
5454
|
+
npx cdk synth
|
|
5455
|
+
npx cdk deploy
|
|
4050
5456
|
\`\`\`
|
|
4051
5457
|
|
|
4052
5458
|
## Editing Schemas
|
|
@@ -4057,7 +5463,25 @@ When modifying JSON config files:
|
|
|
4057
5463
|
2. Check validation constraint comments (\`@regex\`, \`@min\`, \`@max\`)
|
|
4058
5464
|
3. Use exact enum values as string literals
|
|
4059
5465
|
4. Use CloudFormation-safe names (alphanumeric, start with letter)
|
|
4060
|
-
5. Run \`agentcore validate\`
|
|
5466
|
+
5. Run \`agentcore validate\` to verify changes
|
|
5467
|
+
|
|
5468
|
+
## CLI Commands
|
|
5469
|
+
|
|
5470
|
+
| Command | Description |
|
|
5471
|
+
| --- | --- |
|
|
5472
|
+
| \`agentcore create\` | Create a new project |
|
|
5473
|
+
| \`agentcore add <resource>\` | Add agent, memory, credential, gateway, evaluator, policy |
|
|
5474
|
+
| \`agentcore remove <resource>\` | Remove a resource |
|
|
5475
|
+
| \`agentcore dev\` | Run agent locally with hot-reload |
|
|
5476
|
+
| \`agentcore deploy\` | Deploy to AWS |
|
|
5477
|
+
| \`agentcore status\` | Show deployment status |
|
|
5478
|
+
| \`agentcore invoke\` | Invoke agent (local or deployed) |
|
|
5479
|
+
| \`agentcore logs\` | View agent logs |
|
|
5480
|
+
| \`agentcore traces\` | View agent traces |
|
|
5481
|
+
| \`agentcore eval\` | Run evaluations against an agent |
|
|
5482
|
+
| \`agentcore package\` | Package agent artifacts |
|
|
5483
|
+
| \`agentcore validate\` | Validate configuration |
|
|
5484
|
+
| \`agentcore pause\` / \`resume\` | Pause or resume a deployed agent |
|
|
4061
5485
|
"
|
|
4062
5486
|
`;
|
|
4063
5487
|
|