@aws/agentcore 1.0.0-rc.1 → 1.0.0-rc.4

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.
Files changed (40) hide show
  1. package/LICENSE +175 -0
  2. package/README.md +93 -985
  3. package/dist/assets/agent-inspector/index.js.asset +1 -1
  4. package/dist/assets/cdk/README.md +42 -19
  5. package/dist/assets/cdk/bin/cdk.ts +22 -173
  6. package/dist/assets/cdk/lib/cdk-stack.ts +18 -80
  7. package/dist/assets/cdk/package.json +3 -10
  8. package/dist/assets/cdk/tsconfig.json +1 -1
  9. package/dist/assets/evaluators/python-lambda/README.md +5 -1
  10. package/dist/assets/templates/a2a-python-strands/pyproject.toml +1 -1
  11. package/dist/assets/templates/agent-python-langchain/README.md +5 -4
  12. package/dist/assets/templates/agent-python-langchain/main.py +7 -11
  13. package/dist/assets/templates/agent-python-strands/README.md +16 -2
  14. package/dist/assets/templates/agent-python-strands/main.py +40 -81
  15. package/dist/assets/templates/agent-python-strands/memory/session.py +1 -5
  16. package/dist/assets/templates/agent-python-strands/model/load.py +4 -4
  17. package/dist/assets/templates/agent-python-strands/parse.py +41 -0
  18. package/dist/assets/templates/agent-typescript-strands/README.md +23 -2
  19. package/dist/assets/templates/agent-typescript-strands/main.ts +4 -13
  20. package/dist/assets/templates/agent-typescript-strands/memory/memory.ts +1 -12
  21. package/dist/assets/templates/agent-typescript-strands/package.json.template +0 -1
  22. package/dist/assets/templates/agui-python-strands/README.md +1 -1
  23. package/dist/assets/templates/agui-python-strands/pyproject.toml +1 -1
  24. package/dist/assets/templates/export-harness-python/model/load.py +3 -3
  25. package/dist/assets/templates/harness/harness.yaml +225 -0
  26. package/dist/main.js +728 -220
  27. package/package.json +13 -6
  28. package/dist/assets/cdk/.prettierrc +0 -8
  29. package/dist/assets/cdk/jest.config.js +0 -9
  30. package/dist/assets/cdk/npmignore.template +0 -6
  31. package/dist/assets/cdk/test/cdk.test.ts +0 -120
  32. package/dist/assets/evaluators/autoevals-lambda/README.md +0 -23
  33. package/dist/assets/evaluators/autoevals-lambda/execution-role-policy.json +0 -15
  34. package/dist/assets/evaluators/autoevals-lambda/lambda_function.py +0 -37
  35. package/dist/assets/evaluators/autoevals-lambda/pyproject.toml +0 -23
  36. package/dist/assets/evaluators/deepeval-lambda/README.md +0 -23
  37. package/dist/assets/evaluators/deepeval-lambda/execution-role-policy.json +0 -15
  38. package/dist/assets/evaluators/deepeval-lambda/lambda_function.py +0 -29
  39. package/dist/assets/evaluators/deepeval-lambda/pyproject.toml +0 -22
  40. package/dist/assets/templates/agent-typescript-strands/mcp_client/client.ts +0 -11
@@ -1,181 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { AgentCoreStack, type HarnessConfig } from '../lib/cdk-stack';
3
- import { ConfigIO, HarnessSpecSchema, type AwsDeploymentTarget } from '@aws/agentcore-cdk';
4
- import { App, type Environment } from 'aws-cdk-lib';
5
- import * as path from 'path';
6
- import * as fs from 'fs';
7
-
8
- function toEnvironment(target: AwsDeploymentTarget): Environment {
9
- return {
10
- account: target.account,
11
- region: target.region,
12
- };
13
- }
14
-
15
- function sanitize(name: string): string {
16
- return name.replace(/_/g, '-');
17
- }
18
-
19
- function toStackName(projectName: string, targetName: string): string {
20
- return `AgentCore-${sanitize(projectName)}-${sanitize(targetName)}`;
21
- }
22
-
23
- // The vended CDK project compiles against the published @aws/agentcore-cdk schema
24
- // type, which may lag the CLI's own AgentCoreProjectSpec (e.g. payments, harnesses,
25
- // gateway fields). This alias documents each read of those not-yet-published fields.
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- type SpecWithLatestFields = any;
28
-
29
- // Extract MCP configuration from the project spec. Gateway fields are stored in
30
- // agentcore.json but may not yet be on the published spec type, so they are read
31
- // off the loosened alias.
32
- function resolveMcpSpec(spec: SpecWithLatestFields) {
33
- return spec.agentCoreGateways?.length
34
- ? {
35
- agentCoreGateways: spec.agentCoreGateways,
36
- mcpRuntimeTools: spec.mcpRuntimeTools,
37
- unassignedTargets: spec.unassignedTargets,
38
- }
39
- : undefined;
40
- }
41
-
42
- // Read non-S3 KB connector-config files and return their parsed contents keyed by
43
- // the data source's connectorConfigFile path. The L3 does not read files; it
44
- // expects these parsed connectorParameters verbatim.
45
- function resolveConnectorParametersByFile(
46
- spec: SpecWithLatestFields,
47
- projectRoot: string
48
- ): Record<string, Record<string, unknown>> {
49
- const connectorParametersByFile: Record<string, Record<string, unknown>> = {};
50
- for (const kb of spec.knowledgeBases ?? []) {
51
- for (const ds of kb.dataSources ?? []) {
52
- if (ds.type !== 'S3' && ds.connectorConfigFile) {
53
- const abs = path.resolve(projectRoot, ds.connectorConfigFile);
54
- try {
55
- connectorParametersByFile[ds.connectorConfigFile] = JSON.parse(fs.readFileSync(abs, 'utf-8'));
56
- } catch (err) {
57
- throw new Error(
58
- `Could not read connector config '${ds.connectorConfigFile}' for knowledge base '${kb.name}' at ${abs}: ${err instanceof Error ? err.message : err}`
59
- );
60
- }
61
- }
62
- }
63
- }
64
- return connectorParametersByFile;
65
- }
66
-
67
- // Synthesize a HarnessConfig for each harness entry in the spec. The full validated
68
- // spec drives the AWS::BedrockAgentCore::Harness CFN resource; the role-scoped
69
- // fields drive the IAM role + container build.
70
- function resolveHarnessConfigs(spec: SpecWithLatestFields, projectRoot: string): HarnessConfig[] {
71
- const harnessConfigs: HarnessConfig[] = [];
72
- for (const entry of spec.harnesses ?? []) {
73
- const harnessDir = path.resolve(projectRoot, entry.path);
74
- const harnessPath = path.resolve(harnessDir, 'harness.json');
75
- try {
76
- const harnessSpec = HarnessSpecSchema.parse(JSON.parse(fs.readFileSync(harnessPath, 'utf-8')));
77
- harnessConfigs.push({
78
- name: entry.name,
79
- executionRoleArn: harnessSpec.executionRoleArn,
80
- // Only an `existing` memory ref carries a name to wire IAM against; managed memory is
81
- // owned by the harness (no sibling) and disabled has none — both resolve to undefined.
82
- memoryName: harnessSpec.memory?.mode === 'existing' ? harnessSpec.memory.name : undefined,
83
- containerUri: harnessSpec.containerUri,
84
- hasDockerfile: !!harnessSpec.dockerfile,
85
- dockerfile: harnessSpec.dockerfile,
86
- codeLocation: harnessSpec.dockerfile ? harnessDir : undefined,
87
- tools: harnessSpec.tools,
88
- skills: harnessSpec.skills,
89
- apiKeyArn: harnessSpec.model?.apiKeyArn,
90
- efsAccessPoints: harnessSpec.efsAccessPoints,
91
- s3AccessPoints: harnessSpec.s3AccessPoints,
92
- apiFormat: harnessSpec.model?.apiFormat,
93
- // Full spec + dir drive the AWS::BedrockAgentCore::Harness CFN resource.
94
- spec: harnessSpec,
95
- harnessDir,
96
- });
97
- } catch (err) {
98
- throw new Error(
99
- `Could not read harness.json for "${entry.name}" at ${harnessPath}: ${err instanceof Error ? err.message : err}`
100
- );
101
- }
102
- }
103
- return harnessConfigs;
104
- }
105
-
106
- async function main() {
107
- // Config root is parent of cdk/ directory. The CLI sets process.cwd() to agentcore/cdk/.
108
- const configRoot = path.resolve(process.cwd(), '..');
109
- const configIO = new ConfigIO({ baseDir: configRoot });
110
-
111
- const spec = await configIO.readProjectSpec();
112
- const targets = await configIO.readAWSDeploymentTargets();
113
-
114
- // `project build` runs before a project has anywhere to deploy, so an empty target
115
- // list is not an error: it synthesizes a single environment-agnostic stack, which is
116
- // enough to typecheck the app and produce a template. Only a stack synthesized for a
117
- // real target is a deploy candidate; the target tag below is what marks one.
118
- const stackTargets: (AwsDeploymentTarget | undefined)[] = targets.length > 0 ? targets : [undefined];
119
-
120
- const specAny: SpecWithLatestFields = spec;
121
- const projectRoot = path.resolve(configRoot, '..');
122
-
123
- const mcpSpec = resolveMcpSpec(specAny);
124
- const connectorParametersByFile = resolveConnectorParametersByFile(specAny, projectRoot);
125
- const harnessConfigs = resolveHarnessConfigs(specAny, projectRoot);
126
-
127
- // Read deployed state for credential ARNs (populated by pre-deploy identity setup).
128
- // Under agentcore/.cli/ to match the released CLI's location.
129
- let deployedState: Record<string, unknown> | undefined;
130
- try {
131
- deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8'));
132
- } catch (err) {
133
- // A missing file is the normal first-deploy case. A malformed one is not:
134
- // surface it rather than silently synthesizing without the credential ARNs
135
- // it holds (which would drop them from the stack).
136
- if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
137
- }
2
+ import { App } from 'aws-cdk-lib';
3
+ import { readAgentCoreProject, resolveTargetStacks, transformAgentCoreJson } from '@aws/agentcore-cdk';
4
+ import { AgentCoreStack } from '../lib/cdk-stack';
138
5
 
6
+ try {
7
+ // The AgentCore CLI runs this app from agentcore/cdk/; readAgentCoreProject walks up to agentcore/.
8
+ const project = readAgentCoreProject();
139
9
  const app = new App();
140
-
141
- for (const target of stackTargets) {
142
- // An environment-agnostic stack resolves its account and region from CloudFormation
143
- // pseudo-parameters at deploy time instead of pinning them at synth time.
144
- const env = target ? toEnvironment(target) : undefined;
145
- const stackName = target ? toStackName(spec.name, target.name) : `AgentCore-${sanitize(spec.name)}`;
146
-
147
- // Extract credentials from deployed state for this target
148
- const targetState = (deployedState as Record<string, unknown>)?.targets as
149
- Record<string, Record<string, unknown>> | undefined;
150
- const targetResources = target
151
- ? (targetState?.[target.name]?.resources as Record<string, unknown> | undefined)
152
- : undefined;
153
- const credentials = targetResources?.credentials as
154
- Record<string, { credentialProviderArn: string; clientSecretArn?: string }> | undefined;
155
-
156
- new AgentCoreStack(app, stackName, {
157
- spec,
158
- mcpSpec,
159
- credentials,
160
- connectorParametersByFile,
161
- harnesses: harnessConfigs.length > 0 ? harnessConfigs : undefined,
162
- env,
163
- description: target
164
- ? `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`
165
- : `AgentCore stack for ${spec.name} (no deployment target configured)`,
166
- // Only a stack synthesized for a real target carries the target tag, which is
167
- // how deploy selects the stack to ship.
168
- tags: {
169
- 'agentcore:project-name': spec.name,
170
- ...(target ? { 'agentcore:target-name': target.name } : {}),
171
- },
10
+ for (const stack of resolveTargetStacks({
11
+ projectName: project.projectName,
12
+ targets: project.targets,
13
+ deployedState: project.deployedState,
14
+ })) {
15
+ new AgentCoreStack(app, stack.stackName, {
16
+ env: stack.env,
17
+ tags: stack.tags,
18
+ description: stack.description,
19
+ application: transformAgentCoreJson(project.agentCoreJson, {
20
+ projectRoot: project.projectRoot,
21
+ credentials: stack.credentials,
22
+ target: stack.target,
23
+ }),
172
24
  });
173
25
  }
174
-
175
26
  app.synth();
176
- }
177
-
178
- main().catch((error: unknown) => {
27
+ } catch (error) {
179
28
  console.error('AgentCore CDK synthesis failed:', error instanceof Error ? error.message : error);
180
29
  process.exit(1);
181
- });
30
+ }
@@ -1,94 +1,32 @@
1
- import {
2
- AgentCoreApplication,
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
- * The AgentCore project specification containing agents, memories, and credentials.
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
- * CDK Stack that deploys AgentCore infrastructure.
45
- *
46
- * This is a thin wrapper that instantiates L3 constructs.
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
- const { spec, mcpSpec, credentials, harnesses, connectorParametersByFile } = props;
57
-
58
- // Create AgentCoreApplication with all agents and harness roles
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- const appProps: Record<string, unknown> = { spec };
61
- if (harnesses?.length) {
62
- appProps.harnesses = harnesses;
63
- }
64
- if (connectorParametersByFile && Object.keys(connectorParametersByFile).length > 0) {
65
- appProps.connectorParametersByFile = connectorParametersByFile;
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": "0.1.0-alpha.52",
27
- "aws-cdk-lib": "~2.266.0",
19
+ "@aws/agentcore-cdk": "1.0.0-rc.2",
20
+ "aws-cdk-lib": "~2.269.0",
28
21
  "constructs": "~10.7.0"
29
22
  }
30
23
  }
@@ -23,6 +23,6 @@
23
23
  "rootDir": ".",
24
24
  "outDir": "dist"
25
25
  },
26
- "include": ["bin/**/*", "lib/**/*", "test/**/*"],
26
+ "include": ["bin/**/*", "lib/**/*"],
27
27
  "exclude": ["node_modules", "cdk.out", "dist"]
28
28
  }
@@ -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.17.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 model
11
- chunk back as a JSON event of content blocks. An `InMemorySaver` checkpointer
12
- keyed on the Runtime session id carries history between turns.
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
@@ -21,7 +22,7 @@ Run the agent locally from the project root:
21
22
 
22
23
  ```bash
23
24
  agentcore project dev
24
- agentcore project invoke runtime --local --payload '{"prompt":"What is 2 plus 3?"}'
25
+ agentcore project invoke runtime --local --name {{name}} --payload '{"prompt":"What is 2 plus 3?"}'
25
26
  ```
26
27
 
27
28
  Environment variables for local development go in `agentcore/.env.local`
@@ -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 for event in agent.astream(
38
+ async with await agent.astream_events(
40
39
  {"messages": [{"role": "user", "content": prompt}]},
41
40
  config,
42
- stream_mode="messages",
43
- version="v2",
44
- ):
45
- message, metadata = event["data"]
46
- if not isinstance(message, AIMessageChunk):
47
- continue
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 `strip_trailing_tool_use()`, which normalizes the history tail before
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 typing import Any
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 agent_factory():
33
- cache = {}
34
- def get_or_create_agent(session_id, user_id):
35
- key = f"{session_id}/{user_id}"
36
- if key not in cache:
37
- cache[key] = Agent(
38
- model=load_model(),
39
- session_manager=get_memory_session_manager(session_id, user_id),
40
- conversation_manager=_make_conversation_manager(),
41
- system_prompt=DEFAULT_SYSTEM_PROMPT,
42
- tools=tools,
43
- )
44
- return cache[key]
45
- return get_or_create_agent
46
- get_or_create_agent = agent_factory()
47
-
48
-
49
- def strip_trailing_tool_use(messages: Any) -> list[dict]:
50
- """Strip toolUse blocks from the tail until the last message has none."""
51
- if not isinstance(messages, list):
52
- raise ValueError("messages must be a list")
53
-
54
- messages = list(messages)
55
- while messages:
56
- last = messages[-1]
57
- if not isinstance(last, dict):
58
- raise ValueError("each message must be an object")
59
- original_content = last.get("content", [])
60
- if not isinstance(original_content, list) or not all(isinstance(block, dict) for block in original_content):
61
- raise ValueError("each message content value must be a list of content blocks")
62
-
63
- content = [block for block in original_content if "toolUse" not in block]
64
- if len(content) == len(original_content):
65
- break
66
- if content:
67
- messages[-1] = {**last, "content": content}
68
- break
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
- session_id: Optional[str], actor_id: str
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),