@aws/agentcore 1.0.0-rc.1 → 1.0.0-rc.3
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/README.md +251 -11
- package/dist/assets/cdk/README.md +42 -19
- package/dist/assets/cdk/bin/cdk.ts +22 -173
- package/dist/assets/cdk/lib/cdk-stack.ts +18 -80
- package/dist/assets/cdk/package.json +3 -10
- package/dist/assets/cdk/tsconfig.json +1 -1
- package/dist/assets/evaluators/python-lambda/README.md +5 -1
- package/dist/assets/templates/a2a-python-strands/pyproject.toml +1 -1
- package/dist/assets/templates/agent-python-langchain/README.md +4 -3
- package/dist/assets/templates/agent-python-langchain/main.py +7 -11
- package/dist/assets/templates/agent-python-strands/README.md +16 -2
- package/dist/assets/templates/agent-python-strands/main.py +40 -81
- package/dist/assets/templates/agent-python-strands/memory/session.py +1 -5
- package/dist/assets/templates/agent-python-strands/model/load.py +4 -4
- package/dist/assets/templates/agent-python-strands/parse.py +41 -0
- package/dist/assets/templates/agent-typescript-strands/README.md +23 -2
- package/dist/assets/templates/agent-typescript-strands/main.ts +4 -13
- package/dist/assets/templates/agent-typescript-strands/memory/memory.ts +1 -12
- package/dist/assets/templates/agent-typescript-strands/package.json.template +0 -1
- package/dist/assets/templates/agui-python-strands/pyproject.toml +1 -1
- package/dist/assets/templates/export-harness-python/model/load.py +3 -3
- package/dist/assets/templates/harness/harness.yaml +157 -0
- package/dist/main.js +524 -132
- package/package.json +6 -4
- package/dist/assets/cdk/.prettierrc +0 -8
- package/dist/assets/cdk/jest.config.js +0 -9
- package/dist/assets/cdk/npmignore.template +0 -6
- package/dist/assets/cdk/test/cdk.test.ts +0 -120
- package/dist/assets/evaluators/autoevals-lambda/README.md +0 -23
- package/dist/assets/evaluators/autoevals-lambda/execution-role-policy.json +0 -15
- package/dist/assets/evaluators/autoevals-lambda/lambda_function.py +0 -37
- package/dist/assets/evaluators/autoevals-lambda/pyproject.toml +0 -23
- package/dist/assets/evaluators/deepeval-lambda/README.md +0 -23
- package/dist/assets/evaluators/deepeval-lambda/execution-role-policy.json +0 -15
- package/dist/assets/evaluators/deepeval-lambda/lambda_function.py +0 -29
- package/dist/assets/evaluators/deepeval-lambda/pyproject.toml +0 -22
- package/dist/assets/templates/agent-typescript-strands/mcp_client/client.ts +0 -11
|
@@ -1,94 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
AgentCoreMcp,
|
|
4
|
-
AgentCorePayments,
|
|
5
|
-
type AgentCoreProjectSpec,
|
|
6
|
-
type AgentCoreMcpSpec,
|
|
7
|
-
type HarnessDeploymentConfig,
|
|
8
|
-
} from '@aws/agentcore-cdk';
|
|
9
|
-
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
|
|
1
|
+
import { AgentCoreApplication, type AgentCoreApplicationProps } from '@aws/agentcore-cdk';
|
|
2
|
+
import { Stack, type StackProps } from 'aws-cdk-lib';
|
|
10
3
|
import { Construct } from 'constructs';
|
|
11
4
|
|
|
12
|
-
/**
|
|
13
|
-
* Harness deployment config: role-scoped fields (for IAM role + container build)
|
|
14
|
-
* plus the full validated spec + its config directory so the L3 construct can
|
|
15
|
-
* synthesize the AWS::BedrockAgentCore::Harness resource.
|
|
16
|
-
*/
|
|
17
|
-
export type HarnessConfig = HarnessDeploymentConfig;
|
|
18
|
-
|
|
19
5
|
export interface AgentCoreStackProps extends StackProps {
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
spec: AgentCoreProjectSpec;
|
|
24
|
-
/**
|
|
25
|
-
* The MCP specification containing gateways and servers.
|
|
26
|
-
*/
|
|
27
|
-
mcpSpec?: AgentCoreMcpSpec;
|
|
28
|
-
/**
|
|
29
|
-
* Credential provider ARNs from deployed state, keyed by credential name.
|
|
30
|
-
*/
|
|
31
|
-
credentials?: Record<string, { credentialProviderArn: string; clientSecretArn?: string }>;
|
|
32
|
-
/**
|
|
33
|
-
* Harness role configurations.
|
|
34
|
-
*/
|
|
35
|
-
harnesses?: HarnessConfig[];
|
|
36
|
-
/**
|
|
37
|
-
* Parsed connectorParameters for non-S3 KB data sources, keyed by
|
|
38
|
-
* connectorConfigFile path. Forwarded to AgentCoreApplication.
|
|
39
|
-
*/
|
|
40
|
-
connectorParametersByFile?: Record<string, Record<string, unknown>>;
|
|
6
|
+
/** Props for the AgentCore application, produced by transformAgentCoreJson from agentcore/agentcore.json. */
|
|
7
|
+
application: AgentCoreApplicationProps;
|
|
41
8
|
}
|
|
42
9
|
|
|
43
10
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* All resource logic and outputs are contained within the L3 constructs.
|
|
11
|
+
* The stack the AgentCore CLI deploys. Everything declared in agentcore/agentcore.json is
|
|
12
|
+
* created by the AgentCoreApplication construct. Add your own resources below it and wire
|
|
13
|
+
* them to your runtimes and harnesses through the application's accessors.
|
|
48
14
|
*/
|
|
49
15
|
export class AgentCoreStack extends Stack {
|
|
50
|
-
/** The AgentCore application containing all agent environments */
|
|
51
16
|
public readonly application: AgentCoreApplication;
|
|
52
17
|
|
|
53
18
|
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
|
|
54
19
|
super(scope, id, props);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
if (credentials) {
|
|
68
|
-
appProps.credentials = credentials;
|
|
69
|
-
}
|
|
70
|
-
this.application = new AgentCoreApplication(this, 'Application', appProps as any);
|
|
71
|
-
new AgentCorePayments(this, 'Payments', {
|
|
72
|
-
spec,
|
|
73
|
-
credentials,
|
|
74
|
-
agentCoreApplication: this.application,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Create AgentCoreMcp if there are gateways configured
|
|
78
|
-
if (mcpSpec?.agentCoreGateways && mcpSpec.agentCoreGateways.length > 0) {
|
|
79
|
-
new AgentCoreMcp(this, 'Mcp', {
|
|
80
|
-
projectName: spec.name,
|
|
81
|
-
mcpSpec,
|
|
82
|
-
agentCoreApplication: this.application,
|
|
83
|
-
credentials,
|
|
84
|
-
projectTags: spec.tags,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Stack-level output
|
|
89
|
-
new CfnOutput(this, 'StackNameOutput', {
|
|
90
|
-
description: 'Name of the CloudFormation Stack',
|
|
91
|
-
value: this.stackName,
|
|
92
|
-
});
|
|
20
|
+
this.application = new AgentCoreApplication(this, 'Application', props.application);
|
|
21
|
+
|
|
22
|
+
// Example: give a runtime a DynamoDB table.
|
|
23
|
+
//
|
|
24
|
+
// import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
25
|
+
// const orders = new dynamodb.Table(this, 'Orders', {
|
|
26
|
+
// partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
27
|
+
// });
|
|
28
|
+
// const checkout = this.application.runtime('checkout'); // or .harness('support')
|
|
29
|
+
// checkout.grantReadWrite(orders);
|
|
30
|
+
// checkout.addEnvironmentVariable('ORDERS_TABLE', orders.tableName);
|
|
93
31
|
}
|
|
94
32
|
}
|
|
@@ -7,24 +7,17 @@
|
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"watch": "tsc -w",
|
|
10
|
-
"test": "jest",
|
|
11
10
|
"cdk": "npm run build && cdk",
|
|
12
|
-
"clean": "rm -rf dist"
|
|
13
|
-
"format": "prettier --write .",
|
|
14
|
-
"format:check": "prettier --check ."
|
|
11
|
+
"clean": "rm -rf dist"
|
|
15
12
|
},
|
|
16
13
|
"devDependencies": {
|
|
17
|
-
"@types/jest": "~29.5.14",
|
|
18
14
|
"@types/node": "~24.13.3",
|
|
19
|
-
"jest": "~29.7.0",
|
|
20
|
-
"ts-jest": "~29.4.11",
|
|
21
15
|
"aws-cdk": "~2.1126.0",
|
|
22
|
-
"prettier": "~3.9.5",
|
|
23
16
|
"typescript": "~5.9.3"
|
|
24
17
|
},
|
|
25
18
|
"dependencies": {
|
|
26
|
-
"@aws/agentcore-cdk": "
|
|
27
|
-
"aws-cdk-lib": "~2.
|
|
19
|
+
"@aws/agentcore-cdk": "1.0.0-rc.1",
|
|
20
|
+
"aws-cdk-lib": "~2.269.0",
|
|
28
21
|
"constructs": "~10.7.0"
|
|
29
22
|
}
|
|
30
23
|
}
|
|
@@ -13,7 +13,11 @@ with your own logic.
|
|
|
13
13
|
- `pyproject.toml` — Python dependencies, managed with
|
|
14
14
|
[uv](https://docs.astral.sh/uv/).
|
|
15
15
|
- `execution-role-policy.json` — extra IAM the evaluator Lambda gets at runtime.
|
|
16
|
-
Add statements here for anything your logic calls (DynamoDB, S3, …).
|
|
16
|
+
Add statements here for anything your logic calls (Bedrock, DynamoDB, S3, …).
|
|
17
|
+
|
|
18
|
+
Third-party evaluation libraries are ordinary application dependencies. Add
|
|
19
|
+
them to `pyproject.toml`, configure them in `lambda_function.py`, and grant any
|
|
20
|
+
required permissions in `execution-role-policy.json`.
|
|
17
21
|
|
|
18
22
|
## Write your evaluator
|
|
19
23
|
|
|
@@ -10,7 +10,7 @@ readme = "README.md"
|
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"a2a-sdk[all] ~= 0.3.0",
|
|
13
|
-
"aws-opentelemetry-distro ~= 0.
|
|
13
|
+
"aws-opentelemetry-distro ~= 0.18.0",
|
|
14
14
|
"bedrock-agentcore[a2a] ~= 1.9.1",
|
|
15
15
|
"botocore[crt] ~= 1.43.0",
|
|
16
16
|
"strands-agents ~= 1.15.0",
|
|
@@ -7,9 +7,10 @@ session, and streams its response.
|
|
|
7
7
|
## What's here
|
|
8
8
|
|
|
9
9
|
- `main.py`: the agent. A `BedrockAgentCoreApp` wraps an async entrypoint
|
|
10
|
-
that reads `prompt` from the payload, runs the agent, and streams each
|
|
11
|
-
|
|
12
|
-
keyed on the Runtime session id carries history
|
|
10
|
+
that reads `prompt` from the payload, runs the agent, and streams each
|
|
11
|
+
completed model message back as a JSON event of content blocks. An
|
|
12
|
+
`InMemorySaver` checkpointer keyed on the Runtime session id carries history
|
|
13
|
+
between turns.
|
|
13
14
|
- `model/load.py`: creates the Bedrock chat model with `init_chat_model`.
|
|
14
15
|
- `pyproject.toml`: Python dependencies, managed with
|
|
15
16
|
[uv](https://docs.astral.sh/uv/). `agentcore project create` has already run
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from langchain.agents import create_agent
|
|
2
2
|
from langchain.tools import tool
|
|
3
|
-
from langchain_core.messages import AIMessageChunk
|
|
4
3
|
from langgraph.checkpoint.memory import InMemorySaver
|
|
5
4
|
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
6
5
|
from model.load import load_model
|
|
@@ -36,18 +35,15 @@ async def invoke(payload, context):
|
|
|
36
35
|
session_id = context.session_id or "default-session"
|
|
37
36
|
config = {"configurable": {"thread_id": session_id}}
|
|
38
37
|
|
|
39
|
-
async
|
|
38
|
+
async with await agent.astream_events(
|
|
40
39
|
{"messages": [{"role": "user", "content": prompt}]},
|
|
41
40
|
config,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
blocks = message.content_blocks
|
|
49
|
-
if blocks:
|
|
50
|
-
yield {"node": metadata["langgraph_node"], "content": blocks}
|
|
41
|
+
version="v3",
|
|
42
|
+
) as stream:
|
|
43
|
+
async for message in stream.messages:
|
|
44
|
+
blocks = (await message.output).content_blocks
|
|
45
|
+
if blocks:
|
|
46
|
+
yield {"node": message.node, "content": blocks}
|
|
51
47
|
|
|
52
48
|
|
|
53
49
|
if __name__ == "__main__":
|
|
@@ -16,8 +16,22 @@ file defines a Starlette ASGI app with the Strands Agent SDK running within.
|
|
|
16
16
|
## Input Validation
|
|
17
17
|
|
|
18
18
|
Validate invocation input before forwarding it to Strands. Keep plain prompts typed as strings. If the app accepts a
|
|
19
|
-
caller-supplied message history, retain `
|
|
20
|
-
invoking the agent.
|
|
19
|
+
caller-supplied message history, retain `_strip_trailing_tool_use()` in `parse.py`, which normalizes the history tail
|
|
20
|
+
before invoking the agent.
|
|
21
|
+
|
|
22
|
+
## Payload
|
|
23
|
+
|
|
24
|
+
The Runtime accepts a JSON object. `parse_payload()` reads:
|
|
25
|
+
|
|
26
|
+
- `prompt` (string) — a single user message. Used when `messages` is absent; defaults to `""`.
|
|
27
|
+
- `messages` (array) — a full conversation history (`[{"role": ..., "content": [...]}]`). Takes precedence over `prompt`; trailing `toolUse` blocks are stripped before the agent runs.
|
|
28
|
+
- `actorId` (string, optional) — identifies the end user for Memory scoping (e.g. `/users/{actorId}/facts`). Defaults to `"default"`.
|
|
29
|
+
|
|
30
|
+
Provide either `prompt` or `messages`. The `session_id` is not in the body — it comes from the `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header (`context.session_id`); reuse it to continue a conversation.
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{ "prompt": "What's the weather?", "actorId": "user-123" }
|
|
34
|
+
```
|
|
21
35
|
|
|
22
36
|
## Environment Variables
|
|
23
37
|
|
|
@@ -1,106 +1,65 @@
|
|
|
1
|
-
from
|
|
1
|
+
from functools import lru_cache
|
|
2
2
|
|
|
3
3
|
from strands import Agent, tool
|
|
4
4
|
from strands.agent.conversation_manager.null_conversation_manager import NullConversationManager
|
|
5
5
|
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
6
6
|
from model.load import load_model
|
|
7
|
+
from parse import parse_payload
|
|
7
8
|
from memory.session import get_memory_session_manager
|
|
8
9
|
|
|
9
|
-
app = BedrockAgentCoreApp()
|
|
10
|
-
log = app.logger
|
|
11
|
-
|
|
12
10
|
DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant. Use tools when appropriate."
|
|
13
11
|
|
|
14
12
|
# Define a collection of tools used by the model
|
|
15
13
|
tools = []
|
|
16
14
|
|
|
17
|
-
|
|
18
15
|
# Define a simple function tool
|
|
19
16
|
@tool
|
|
20
17
|
def add_numbers(a: int, b: int) -> int:
|
|
21
18
|
"""Return the sum of two numbers"""
|
|
22
19
|
return a + b
|
|
23
20
|
|
|
24
|
-
|
|
25
21
|
tools.append(add_numbers)
|
|
26
22
|
|
|
27
|
-
|
|
28
23
|
def _make_conversation_manager():
|
|
29
24
|
return NullConversationManager()
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
messages.pop()
|
|
70
|
-
|
|
71
|
-
return messages
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _extract_prompt(payload: dict):
|
|
75
|
-
"""Accept a caller-supplied message history or a plain prompt string."""
|
|
76
|
-
if not isinstance(payload, dict):
|
|
77
|
-
raise ValueError("payload must be a JSON object")
|
|
78
|
-
if "messages" in payload:
|
|
79
|
-
return strip_trailing_tool_use(payload["messages"])
|
|
80
|
-
prompt = payload.get("prompt", "")
|
|
81
|
-
if not isinstance(prompt, str):
|
|
82
|
-
raise ValueError("prompt must be a string")
|
|
83
|
-
return prompt
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@app.entrypoint
|
|
87
|
-
async def invoke(payload, context):
|
|
88
|
-
log.info("Invoking Agent.....")
|
|
89
|
-
|
|
90
|
-
session_id = getattr(context, "session_id", "default-session")
|
|
91
|
-
user_id = getattr(context, "user_id", "default-user")
|
|
92
|
-
agent = get_or_create_agent(session_id, user_id)
|
|
93
|
-
|
|
94
|
-
prompt = _extract_prompt(payload)
|
|
95
|
-
|
|
96
|
-
async for event in agent.stream_async(prompt):
|
|
97
|
-
if not isinstance(event, dict) or "event" not in event:
|
|
98
|
-
continue
|
|
99
|
-
cbs = event["event"].get("contentBlockStart")
|
|
100
|
-
if cbs is not None and not cbs.get("start"):
|
|
101
|
-
continue
|
|
102
|
-
yield event
|
|
103
|
-
|
|
104
|
-
|
|
26
|
+
@lru_cache(maxsize=128)
|
|
27
|
+
def _get_agent(session_id: str, actor_id: str) -> Agent:
|
|
28
|
+
"""Given a session_id and actor_id, return or construct the corresponding Strands agent.
|
|
29
|
+
Note: caching helps avoid repeated identity fetches on non-bedrock model loads
|
|
30
|
+
and supports in-memory session management for local dev."""
|
|
31
|
+
return Agent(
|
|
32
|
+
model=load_model(),
|
|
33
|
+
session_manager=get_memory_session_manager(session_id, actor_id),
|
|
34
|
+
conversation_manager=_make_conversation_manager(),
|
|
35
|
+
system_prompt=DEFAULT_SYSTEM_PROMPT,
|
|
36
|
+
tools=tools,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def create_app():
|
|
40
|
+
app = BedrockAgentCoreApp()
|
|
41
|
+
log = app.logger
|
|
42
|
+
|
|
43
|
+
@app.entrypoint
|
|
44
|
+
async def invoke(payload, context):
|
|
45
|
+
log.info("Invoking Agent.....")
|
|
46
|
+
|
|
47
|
+
session_id = getattr(context, "session_id", None) or "default-session"
|
|
48
|
+
prompt, actor_id = parse_payload(payload)
|
|
49
|
+
|
|
50
|
+
log.info(f"Invoking with session_id={session_id} and actor_id={actor_id}")
|
|
51
|
+
agent = _get_agent(session_id, actor_id)
|
|
52
|
+
|
|
53
|
+
async for event in agent.stream_async(prompt):
|
|
54
|
+
if not isinstance(event, dict) or "event" not in event:
|
|
55
|
+
continue
|
|
56
|
+
cbs = event["event"].get("contentBlockStart")
|
|
57
|
+
if cbs is not None and not cbs.get("start"):
|
|
58
|
+
continue
|
|
59
|
+
yield event
|
|
60
|
+
|
|
61
|
+
return app
|
|
62
|
+
|
|
63
|
+
app = create_app()
|
|
105
64
|
if __name__ == "__main__":
|
|
106
65
|
app.run()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import uuid
|
|
3
2
|
from typing import Optional
|
|
4
3
|
|
|
5
4
|
from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig, RetrievalConfig
|
|
@@ -8,15 +7,12 @@ from bedrock_agentcore.memory.integrations.strands.session_manager import AgentC
|
|
|
8
7
|
MEMORY_ID = os.getenv("{{memoryEnvVarName}}")
|
|
9
8
|
REGION = os.getenv("AWS_REGION")
|
|
10
9
|
|
|
11
|
-
|
|
12
10
|
def get_memory_session_manager(
|
|
13
|
-
|
|
11
|
+
session_id: str, actor_id: str,
|
|
14
12
|
) -> Optional[AgentCoreMemorySessionManager]:
|
|
15
13
|
if not MEMORY_ID:
|
|
16
14
|
return None
|
|
17
15
|
|
|
18
|
-
session_id = session_id or uuid.uuid4().hex
|
|
19
|
-
|
|
20
16
|
retrieval_config = {
|
|
21
17
|
f"/users/{actor_id}/facts": RetrievalConfig(top_k=3, relevance_score=0.5),
|
|
22
18
|
f"/users/{actor_id}/preferences": RetrievalConfig(top_k=3, relevance_score=0.5),
|
|
@@ -12,7 +12,7 @@ import os
|
|
|
12
12
|
from strands.models.anthropic import AnthropicModel
|
|
13
13
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
14
14
|
|
|
15
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
15
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
16
16
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
17
17
|
|
|
18
18
|
|
|
@@ -51,7 +51,7 @@ import os
|
|
|
51
51
|
from strands.models.openai import OpenAIModel
|
|
52
52
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
53
53
|
|
|
54
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
54
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
55
55
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
56
56
|
|
|
57
57
|
|
|
@@ -89,7 +89,7 @@ import os
|
|
|
89
89
|
from strands.models.gemini import GeminiModel
|
|
90
90
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
91
91
|
|
|
92
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
92
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
93
93
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
94
94
|
|
|
95
95
|
|
|
@@ -130,7 +130,7 @@ from strands.models.litellm import LiteLLMModel
|
|
|
130
130
|
{{#if identityProviders.[0].name}}
|
|
131
131
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
132
132
|
|
|
133
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
133
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
134
134
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
135
135
|
|
|
136
136
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
def _strip_trailing_tool_use(messages: Any) -> list[dict]:
|
|
4
|
+
"""Strip toolUse blocks from the tail until the last message has none."""
|
|
5
|
+
if not isinstance(messages, list):
|
|
6
|
+
raise ValueError("messages must be a list")
|
|
7
|
+
|
|
8
|
+
messages = list(messages)
|
|
9
|
+
while messages:
|
|
10
|
+
last = messages[-1]
|
|
11
|
+
if not isinstance(last, dict):
|
|
12
|
+
raise ValueError("each message must be an object")
|
|
13
|
+
original_content = last.get("content", [])
|
|
14
|
+
if not isinstance(original_content, list) or not all(isinstance(block, dict) for block in original_content):
|
|
15
|
+
raise ValueError("each message content value must be a list of content blocks")
|
|
16
|
+
|
|
17
|
+
content = [block for block in original_content if "toolUse" not in block]
|
|
18
|
+
if len(content) == len(original_content):
|
|
19
|
+
break
|
|
20
|
+
if content:
|
|
21
|
+
messages[-1] = {**last, "content": content}
|
|
22
|
+
break
|
|
23
|
+
messages.pop()
|
|
24
|
+
|
|
25
|
+
return messages
|
|
26
|
+
|
|
27
|
+
def parse_payload(payload: dict):
|
|
28
|
+
"""Accept a caller-supplied message history or a plain prompt string."""
|
|
29
|
+
if not isinstance(payload, dict):
|
|
30
|
+
raise ValueError("payload must be a JSON object")
|
|
31
|
+
actor_id = payload.get("actorId", "default")
|
|
32
|
+
if not isinstance(actor_id, str):
|
|
33
|
+
raise ValueError("actorId must be a string")
|
|
34
|
+
if not actor_id:
|
|
35
|
+
actor_id = "default"
|
|
36
|
+
if "messages" in payload:
|
|
37
|
+
return _strip_trailing_tool_use(payload["messages"]), actor_id
|
|
38
|
+
prompt = payload.get("prompt", "")
|
|
39
|
+
if not isinstance(prompt, str):
|
|
40
|
+
raise ValueError("prompt must be a string")
|
|
41
|
+
return prompt, actor_id
|
|
@@ -15,8 +15,23 @@ defines an HTTP server that streams tokens from your chosen Agent framework SDK.
|
|
|
15
15
|
|
|
16
16
|
## Input Validation
|
|
17
17
|
|
|
18
|
-
The generated Zod request schema keeps
|
|
19
|
-
validation when extending the request shape, and pass only prompt text to the agent.
|
|
18
|
+
The generated Zod request schema keeps the prompt and actor ID typed as strings before forwarding them to Strands.
|
|
19
|
+
Retain this validation when extending the request shape, and pass only prompt text to the agent.
|
|
20
|
+
|
|
21
|
+
## Payload
|
|
22
|
+
|
|
23
|
+
The Runtime accepts a JSON object with:
|
|
24
|
+
|
|
25
|
+
- `prompt` (string) — a single user message; defaults to `""`.
|
|
26
|
+
- `actorId` (string, optional) — identifies the end user for Memory scoping (for example,
|
|
27
|
+
`/users/{actorId}/facts`); defaults to `"default"`.
|
|
28
|
+
|
|
29
|
+
The `sessionId` is not in the body. It comes from the
|
|
30
|
+
`X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header (`context.sessionId`); reuse it to continue a conversation.
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{ "prompt": "What's the weather?", "actorId": "user-123" }
|
|
34
|
+
```
|
|
20
35
|
|
|
21
36
|
# Developing locally
|
|
22
37
|
|
|
@@ -27,3 +42,9 @@ If installation was successful, `node_modules/` is already populated with depend
|
|
|
27
42
|
# Deployment
|
|
28
43
|
|
|
29
44
|
After providing credentials, `agentcore project deploy` will deploy your project into Amazon Bedrock AgentCore.
|
|
45
|
+
|
|
46
|
+
Invoke the deployed Runtime with its native payload:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
agentcore project invoke runtime --payload '{"prompt":"Hello!","actorId":"user-123"}'
|
|
50
|
+
```
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';
|
|
2
|
-
import { Agent,
|
|
2
|
+
import { Agent, tool, type ToolList } from '@strands-agents/sdk';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { loadModel } from './model/load.js';
|
|
5
|
-
import {
|
|
6
|
-
import { getActorId, getOrCreateMemoryManager } from './memory/memory.js';
|
|
7
|
-
|
|
8
|
-
// Define a collection of MCP clients (filter out anything that failed to initialize)
|
|
9
|
-
const mcpClients: McpClient[] = [getStreamableHttpMcpClient()].filter(
|
|
10
|
-
(client): client is McpClient => Boolean(client)
|
|
11
|
-
);
|
|
5
|
+
import { getOrCreateMemoryManager } from './memory/memory.js';
|
|
12
6
|
|
|
13
7
|
// Define a collection of tools used by the model
|
|
14
8
|
const tools: ToolList = [];
|
|
@@ -25,16 +19,13 @@ const addNumbers = tool({
|
|
|
25
19
|
});
|
|
26
20
|
tools.push(addNumbers);
|
|
27
21
|
|
|
28
|
-
// Add MCP clients to tools
|
|
29
|
-
tools.push(...mcpClients);
|
|
30
|
-
|
|
31
22
|
const SYSTEM_PROMPT = `
|
|
32
23
|
You are a helpful assistant. Use tools when appropriate.
|
|
33
24
|
`;
|
|
34
25
|
|
|
35
26
|
const requestSchema = z.object({
|
|
36
27
|
prompt: z.string().default(''),
|
|
37
|
-
|
|
28
|
+
actorId: z.string().default('default').transform((actorId) => actorId || 'default'),
|
|
38
29
|
});
|
|
39
30
|
|
|
40
31
|
const agentCache = new Map<string, Agent>();
|
|
@@ -60,7 +51,7 @@ const app = new BedrockAgentCoreApp({
|
|
|
60
51
|
requestSchema,
|
|
61
52
|
async *process(payload, context) {
|
|
62
53
|
const sessionId = context?.sessionId ?? 'default-session';
|
|
63
|
-
const actorId =
|
|
54
|
+
const actorId = payload.actorId;
|
|
64
55
|
const agent = await getOrCreateAgent(sessionId, actorId);
|
|
65
56
|
|
|
66
57
|
try {
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { MemoryManager } from '@strands-agents/sdk';
|
|
3
2
|
import { createAgentCoreMemoryStores } from 'bedrock-agentcore/experimental/memory/strands';
|
|
4
3
|
|
|
5
4
|
const MEMORY_ID = process.env.{{memoryEnvVarName}};
|
|
6
5
|
|
|
7
|
-
const CUSTOM_ACTOR_ID_HEADER = 'x-amzn-bedrock-agentcore-runtime-custom-actor-id';
|
|
8
|
-
|
|
9
|
-
export function getActorId(payload: any, context: any): string {
|
|
10
|
-
const raw =
|
|
11
|
-
context?.headers?.[CUSTOM_ACTOR_ID_HEADER] ||
|
|
12
|
-
payload?.userId ||
|
|
13
|
-
context?.sessionId;
|
|
14
|
-
return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : randomUUID();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
const memoryManagerCache = new Map<string, MemoryManager>();
|
|
18
7
|
|
|
19
8
|
export function getOrCreateMemoryManager(sessionId: string, actorId: string): MemoryManager | null {
|
|
@@ -31,7 +20,7 @@ export function getOrCreateMemoryManager(sessionId: string, actorId: string): Me
|
|
|
31
20
|
{ namespace: '/users/{actorId}/facts' },
|
|
32
21
|
{ namespace: '/users/{actorId}/preferences' },
|
|
33
22
|
{ namespace: '/episodes/{actorId}/{sessionId}' },
|
|
34
|
-
{ namespace: '/summaries/{actorId}
|
|
23
|
+
{ namespace: '/summaries/{actorId}' },
|
|
35
24
|
],
|
|
36
25
|
// readMode defaults to 'per-namespace' (one retrieve call per namespace).
|
|
37
26
|
// Switch to 'subtree' to consolidate to a single hierarchical recall call.
|
|
@@ -11,7 +11,7 @@ requires-python = ">=3.12"
|
|
|
11
11
|
dependencies = [
|
|
12
12
|
"ag-ui-strands >= 0.3.0, < 0.4.0",
|
|
13
13
|
"ag-ui-protocol >= 0.1.19, < 0.2.0",
|
|
14
|
-
"aws-opentelemetry-distro ~= 0.
|
|
14
|
+
"aws-opentelemetry-distro ~= 0.18.0",
|
|
15
15
|
"bedrock-agentcore ~= 1.9.1",
|
|
16
16
|
"botocore[crt] ~= 1.43.0",
|
|
17
17
|
"strands-agents ~= 1.15.0",
|
|
@@ -89,7 +89,7 @@ from strands.models.openai import OpenAIModel
|
|
|
89
89
|
{{/if}}
|
|
90
90
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
91
91
|
|
|
92
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
92
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
93
93
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
94
94
|
|
|
95
95
|
|
|
@@ -138,7 +138,7 @@ import os
|
|
|
138
138
|
from strands.models.gemini import GeminiModel
|
|
139
139
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
140
140
|
|
|
141
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
141
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
142
142
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
143
143
|
|
|
144
144
|
|
|
@@ -194,7 +194,7 @@ from strands.models.litellm import LiteLLMModel
|
|
|
194
194
|
{{#if identityProviders.[0].name}}
|
|
195
195
|
from bedrock_agentcore.identity.auth import requires_api_key
|
|
196
196
|
|
|
197
|
-
IDENTITY_PROVIDER_NAME = "{{identityProviders.[0].name}}"
|
|
197
|
+
IDENTITY_PROVIDER_NAME = os.environ.get("{{identityProviders.[0].envVarName}}_NAME", "{{identityProviders.[0].name}}")
|
|
198
198
|
IDENTITY_ENV_VAR = "{{identityProviders.[0].envVarName}}"
|
|
199
199
|
|
|
200
200
|
|