@aws-blocks/bb-agent 0.3.4 → 0.3.5
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/DESIGN.md +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -0
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +3 -1
- package/dist/index.cdk.test.d.ts +2 -0
- package/dist/index.cdk.test.d.ts.map +1 -0
- package/dist/index.cdk.test.js +66 -0
- package/dist/job-event-source.d.ts +19 -0
- package/dist/job-event-source.d.ts.map +1 -0
- package/dist/job-event-source.js +20 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -8
- package/src/agent.ts +2 -0
- package/src/index.cdk.test.ts +75 -0
- package/src/index.cdk.ts +3 -1
- package/src/job-event-source.ts +21 -0
- package/src/version.ts +1 -1
package/DESIGN.md
CHANGED
|
@@ -42,6 +42,7 @@ The CDK class mirrors the runtime's BB creation:
|
|
|
42
42
|
- **DistributedTable:** `${id}-messages` — conversation history (only when `inferenceOnly: false`)
|
|
43
43
|
- **Realtime:** `${id}-rt` — streaming namespace `chunks`
|
|
44
44
|
- **AsyncJob:** `${id}-job` — job payload: `{ message, conversationId?, channelId }`
|
|
45
|
+
- Event source uses `batchSize: 1` / `maxBatchingWindowSeconds: 0`: the caller is blocked on the job starting, so the Agent opts out of AsyncJob's batching defaults (10 / 5s) rather than add up to 5s of latency to an interactive turn.
|
|
45
46
|
|
|
46
47
|
> **Note:** Internal Building Blocks are created on the parent scope (not `this`) to ensure correct nested-scope resolution on AWS.
|
|
47
48
|
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAA6C,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAyB,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIlF,OAAO,KAAK,EAAE,WAAW,EAAoB,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAyC,WAAW,EAAa,iBAAiB,EAAE,kBAAkB,EAA6B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/agent.js
CHANGED
|
@@ -10,6 +10,7 @@ import { z } from 'zod';
|
|
|
10
10
|
import { createStrandsModel, checkModelHealth } from './model-factory.js';
|
|
11
11
|
import { messageSchema, conversationSchema, agentStreamChunkSchema } from './schemas.js';
|
|
12
12
|
import { AgentErrors, blocksAgentError, InterruptError } from './errors.js';
|
|
13
|
+
import { INTERACTIVE_JOB_EVENT_SOURCE } from './job-event-source.js';
|
|
13
14
|
import { BB_NAME, BB_VERSION } from './version.js';
|
|
14
15
|
import { ulid } from 'ulid';
|
|
15
16
|
/** Payload submitted to the internal AsyncJob BB. */
|
|
@@ -147,6 +148,7 @@ export class AgentBase extends Scope {
|
|
|
147
148
|
});
|
|
148
149
|
this.job = new AsyncJob(this, 'job', {
|
|
149
150
|
schema: jobPayloadSchema,
|
|
151
|
+
...INTERACTIVE_JOB_EVENT_SOURCE,
|
|
150
152
|
handler: async (payload) => {
|
|
151
153
|
try {
|
|
152
154
|
await this.runAgent(payload.message, payload.conversationId, payload.channelId, payload.userId, payload.interruptResponses, payload.context);
|
package/dist/index.cdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AASpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO1D,qBAAa,KAAM,SAAQ,KAAK;IAC/B;;;;;;OAMG;gBACS,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG;CAyCxD"}
|
package/dist/index.cdk.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Realtime } from '@aws-blocks/bb-realtime';
|
|
|
7
7
|
import { AsyncJob } from '@aws-blocks/bb-async-job';
|
|
8
8
|
import { FileBucket } from '@aws-blocks/bb-file-bucket';
|
|
9
9
|
import { messageSchema, conversationSchema, agentStreamChunkSchema } from './schemas.js';
|
|
10
|
+
import { INTERACTIVE_JOB_EVENT_SOURCE } from './job-event-source.js';
|
|
10
11
|
import { z } from 'zod';
|
|
11
12
|
export { AgentErrors } from './errors.js';
|
|
12
13
|
export { BedrockModels, OllamaModels } from './models.js';
|
|
@@ -24,7 +25,7 @@ export class Agent extends Scope {
|
|
|
24
25
|
*/
|
|
25
26
|
constructor(scope, id, config) {
|
|
26
27
|
super(id, { parent: scope });
|
|
27
|
-
this.
|
|
28
|
+
this.executionRole.addToPrincipalPolicy(new PolicyStatement({
|
|
28
29
|
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream', 'bedrock:GetFoundationModel', 'bedrock:ListFoundationModels', 'bedrock:GetInferenceProfile'],
|
|
29
30
|
resources: [
|
|
30
31
|
'arn:aws:bedrock:*::foundation-model/*',
|
|
@@ -54,6 +55,7 @@ export class Agent extends Scope {
|
|
|
54
55
|
});
|
|
55
56
|
new AsyncJob(this, 'job', {
|
|
56
57
|
schema: jobPayloadSchema,
|
|
58
|
+
...INTERACTIVE_JOB_EVENT_SOURCE,
|
|
57
59
|
handler: async () => { },
|
|
58
60
|
});
|
|
59
61
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cdk.test.d.ts","sourceRoot":"","sources":["../src/index.cdk.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* CDK-side regression tests for the Agent's internal AsyncJob event source.
|
|
5
|
+
*
|
|
6
|
+
* `stream()` submits one job per interactive turn (and a second on HITL resume),
|
|
7
|
+
* so the caller is blocked on that job starting. AsyncJob's defaults
|
|
8
|
+
* (batchSize 10 / maxBatchingWindowSeconds 5) would add up to 5s of SQS
|
|
9
|
+
* batching delay to that human-blocking path, so the Agent opts out at both
|
|
10
|
+
* construction sites. These tests pin the opt-out to the synthesized template:
|
|
11
|
+
* if the defaults are ever inherited again, the latency regression fails here
|
|
12
|
+
* instead of surfacing as a slow agent in production.
|
|
13
|
+
*
|
|
14
|
+
* Must run under `--conditions=cdk`; otherwise the internal BBs resolve to
|
|
15
|
+
* their mock implementations and no CloudFormation resources are produced.
|
|
16
|
+
*/
|
|
17
|
+
import { test, afterEach } from 'node:test';
|
|
18
|
+
import assert from 'node:assert';
|
|
19
|
+
import * as cdk from 'aws-cdk-lib';
|
|
20
|
+
import { Template } from 'aws-cdk-lib/assertions';
|
|
21
|
+
import { Scope, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
|
|
22
|
+
import { Agent } from './index.cdk.js';
|
|
23
|
+
class StubBlocksStack extends cdk.Stack {
|
|
24
|
+
handler;
|
|
25
|
+
executionRole;
|
|
26
|
+
id;
|
|
27
|
+
constructor(scope, id) {
|
|
28
|
+
super(scope, id);
|
|
29
|
+
this.id = id;
|
|
30
|
+
globalThis.CURRENT_BLOCKS_STACK = this;
|
|
31
|
+
this.executionRole = new cdk.aws_iam.Role(this, 'BlocksRole', {
|
|
32
|
+
assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'),
|
|
33
|
+
});
|
|
34
|
+
this.handler = new cdk.aws_lambda.Function(this, 'StubHandler', {
|
|
35
|
+
runtime: DEFAULT_NODE_RUNTIME,
|
|
36
|
+
handler: 'index.handler',
|
|
37
|
+
code: cdk.aws_lambda.Code.fromInline('exports.handler = async () => {};'),
|
|
38
|
+
role: this.executionRole,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// synthAgent() installs its own stack as the ambient CURRENT_BLOCKS_STACK; clear it
|
|
43
|
+
// afterwards so no test observes a stack left behind by the previous one (node
|
|
44
|
+
// --test isolates files by default, so this is hygiene against future sharing).
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
delete globalThis.CURRENT_BLOCKS_STACK;
|
|
47
|
+
});
|
|
48
|
+
function synthAgent() {
|
|
49
|
+
const app = new cdk.App();
|
|
50
|
+
const stack = new StubBlocksStack(app, 'teststack');
|
|
51
|
+
const parent = new Scope('app');
|
|
52
|
+
new Agent(parent, 'agent', { inferenceOnly: true });
|
|
53
|
+
const mappings = Template.fromStack(stack).findResources('AWS::Lambda::EventSourceMapping');
|
|
54
|
+
const keys = Object.keys(mappings);
|
|
55
|
+
assert.strictEqual(keys.length, 1, 'exactly one event source mapping expected for the agent job');
|
|
56
|
+
return mappings[keys[0]].Properties;
|
|
57
|
+
}
|
|
58
|
+
test('CDK: the Agent job takes one message per invocation (no batching on the interactive path)', () => {
|
|
59
|
+
assert.strictEqual(synthAgent().BatchSize, 1);
|
|
60
|
+
});
|
|
61
|
+
test('CDK: the Agent job has no SQS batching window (no added latency for the caller)', () => {
|
|
62
|
+
assert.strictEqual(synthAgent().MaximumBatchingWindowInSeconds, 0);
|
|
63
|
+
});
|
|
64
|
+
test('CDK: the Agent job still reports partial batch failures', () => {
|
|
65
|
+
assert.deepStrictEqual(synthAgent().FunctionResponseTypes, ['ReportBatchItemFailures']);
|
|
66
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQS event source options for the Agent's internal AsyncJob, shared by the
|
|
3
|
+
* runtime (`agent.ts`) and CDK (`index.cdk.ts`) construction sites so an edit to
|
|
4
|
+
* one site's numbers cannot silently miss the other. Only the CDK site's values
|
|
5
|
+
* reach a real event source mapping today — the runtime entry points do not read
|
|
6
|
+
* these options — so this is a single source of truth for the intent, not a
|
|
7
|
+
* runtime/CDK sync guarantee.
|
|
8
|
+
*
|
|
9
|
+
* `stream()` submits one job per interactive turn (and a second on HITL
|
|
10
|
+
* resume) while the caller is blocked on that job starting, so AsyncJob's
|
|
11
|
+
* batching defaults would add up to 5s of delivery latency to a human-facing
|
|
12
|
+
* path. `batchSize: 1` also keeps one failing turn out of a shared batch,
|
|
13
|
+
* which matters because the handler is not idempotent.
|
|
14
|
+
*/
|
|
15
|
+
export declare const INTERACTIVE_JOB_EVENT_SOURCE: {
|
|
16
|
+
readonly batchSize: 1;
|
|
17
|
+
readonly maxBatchingWindowSeconds: 0;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=job-event-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-event-source.d.ts","sourceRoot":"","sources":["../src/job-event-source.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;CAG/B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* SQS event source options for the Agent's internal AsyncJob, shared by the
|
|
5
|
+
* runtime (`agent.ts`) and CDK (`index.cdk.ts`) construction sites so an edit to
|
|
6
|
+
* one site's numbers cannot silently miss the other. Only the CDK site's values
|
|
7
|
+
* reach a real event source mapping today — the runtime entry points do not read
|
|
8
|
+
* these options — so this is a single source of truth for the intent, not a
|
|
9
|
+
* runtime/CDK sync guarantee.
|
|
10
|
+
*
|
|
11
|
+
* `stream()` submits one job per interactive turn (and a second on HITL
|
|
12
|
+
* resume) while the caller is blocked on that job starting, so AsyncJob's
|
|
13
|
+
* batching defaults would add up to 5s of delivery latency to a human-facing
|
|
14
|
+
* path. `batchSize: 1` also keeps one failing turn out of a shared batch,
|
|
15
|
+
* which matters because the handler is not idempotent.
|
|
16
|
+
*/
|
|
17
|
+
export const INTERACTIVE_JOB_EVENT_SOURCE = {
|
|
18
|
+
batchSize: 1,
|
|
19
|
+
maxBatchingWindowSeconds: 0,
|
|
20
|
+
};
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/bb-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
|
|
@@ -40,15 +40,15 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"prebuild": "node ../../scripts/generate-version.mjs Agent",
|
|
42
42
|
"build": "tsc --build",
|
|
43
|
-
"test": "node --test dist/index.test.js"
|
|
43
|
+
"test": "node --test dist/index.test.js && node --conditions=cdk --test dist/index.cdk.test.js"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@aws-blocks/bb-async-job": "^0.1.
|
|
47
|
-
"@aws-blocks/bb-distributed-table": "^0.1.
|
|
48
|
-
"@aws-blocks/bb-file-bucket": "^0.1.
|
|
49
|
-
"@aws-blocks/bb-logger": "^0.1.
|
|
50
|
-
"@aws-blocks/bb-realtime": "^0.1.
|
|
51
|
-
"@aws-blocks/core": "^0.
|
|
46
|
+
"@aws-blocks/bb-async-job": "^0.1.5",
|
|
47
|
+
"@aws-blocks/bb-distributed-table": "^0.1.6",
|
|
48
|
+
"@aws-blocks/bb-file-bucket": "^0.1.5",
|
|
49
|
+
"@aws-blocks/bb-logger": "^0.1.5",
|
|
50
|
+
"@aws-blocks/bb-realtime": "^0.1.5",
|
|
51
|
+
"@aws-blocks/core": "^0.3.0",
|
|
52
52
|
"@aws-sdk/client-bedrock": "^3.700.0",
|
|
53
53
|
"@opentelemetry/api": "^1.9.0",
|
|
54
54
|
"@strands-agents/sdk": "~1.3.0",
|
package/src/agent.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { createStrandsModel, checkModelHealth } from './model-factory.js';
|
|
|
17
17
|
import { messageSchema, conversationSchema, agentStreamChunkSchema } from './schemas.js';
|
|
18
18
|
import type { AgentConfig, AgentStreamChunk, AgentStreamResult, StreamOptions, Message, Conversation, TokenUsage, ConversationManagerConfig, ModelConfig, JSONValue, InterruptResponse, DefaultToolContext, AgentTool, ToolDefinition } from './types.js';
|
|
19
19
|
import { AgentErrors, blocksAgentError, InterruptError } from './errors.js';
|
|
20
|
+
import { INTERACTIVE_JOB_EVENT_SOURCE } from './job-event-source.js';
|
|
20
21
|
import { BB_NAME, BB_VERSION } from './version.js';
|
|
21
22
|
import { ulid } from 'ulid';
|
|
22
23
|
|
|
@@ -167,6 +168,7 @@ export class AgentBase<TContext = DefaultToolContext> extends Scope {
|
|
|
167
168
|
|
|
168
169
|
this.job = new AsyncJob(this, 'job', {
|
|
169
170
|
schema: jobPayloadSchema,
|
|
171
|
+
...INTERACTIVE_JOB_EVENT_SOURCE,
|
|
170
172
|
handler: async (payload) => {
|
|
171
173
|
try {
|
|
172
174
|
await this.runAgent(payload.message, payload.conversationId, payload.channelId, payload.userId, payload.interruptResponses, payload.context);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CDK-side regression tests for the Agent's internal AsyncJob event source.
|
|
6
|
+
*
|
|
7
|
+
* `stream()` submits one job per interactive turn (and a second on HITL resume),
|
|
8
|
+
* so the caller is blocked on that job starting. AsyncJob's defaults
|
|
9
|
+
* (batchSize 10 / maxBatchingWindowSeconds 5) would add up to 5s of SQS
|
|
10
|
+
* batching delay to that human-blocking path, so the Agent opts out at both
|
|
11
|
+
* construction sites. These tests pin the opt-out to the synthesized template:
|
|
12
|
+
* if the defaults are ever inherited again, the latency regression fails here
|
|
13
|
+
* instead of surfacing as a slow agent in production.
|
|
14
|
+
*
|
|
15
|
+
* Must run under `--conditions=cdk`; otherwise the internal BBs resolve to
|
|
16
|
+
* their mock implementations and no CloudFormation resources are produced.
|
|
17
|
+
*/
|
|
18
|
+
import { test, afterEach } from 'node:test';
|
|
19
|
+
import assert from 'node:assert';
|
|
20
|
+
import * as cdk from 'aws-cdk-lib';
|
|
21
|
+
import type { Construct } from 'constructs';
|
|
22
|
+
import { Template } from 'aws-cdk-lib/assertions';
|
|
23
|
+
import { Scope, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
|
|
24
|
+
import { Agent } from './index.cdk.js';
|
|
25
|
+
|
|
26
|
+
class StubBlocksStack extends cdk.Stack {
|
|
27
|
+
public readonly handler: cdk.aws_lambda.Function;
|
|
28
|
+
public readonly executionRole: cdk.aws_iam.IRole;
|
|
29
|
+
public readonly id: string;
|
|
30
|
+
constructor(scope: Construct, id: string) {
|
|
31
|
+
super(scope, id);
|
|
32
|
+
this.id = id;
|
|
33
|
+
(globalThis as any).CURRENT_BLOCKS_STACK = this;
|
|
34
|
+
this.executionRole = new cdk.aws_iam.Role(this, 'BlocksRole', {
|
|
35
|
+
assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'),
|
|
36
|
+
});
|
|
37
|
+
this.handler = new cdk.aws_lambda.Function(this, 'StubHandler', {
|
|
38
|
+
runtime: DEFAULT_NODE_RUNTIME,
|
|
39
|
+
handler: 'index.handler',
|
|
40
|
+
code: cdk.aws_lambda.Code.fromInline('exports.handler = async () => {};'),
|
|
41
|
+
role: this.executionRole,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// synthAgent() installs its own stack as the ambient CURRENT_BLOCKS_STACK; clear it
|
|
47
|
+
// afterwards so no test observes a stack left behind by the previous one (node
|
|
48
|
+
// --test isolates files by default, so this is hygiene against future sharing).
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
delete (globalThis as any).CURRENT_BLOCKS_STACK;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function synthAgent(): any {
|
|
54
|
+
const app = new cdk.App();
|
|
55
|
+
const stack = new StubBlocksStack(app, 'teststack');
|
|
56
|
+
const parent = new Scope('app');
|
|
57
|
+
new Agent(parent, 'agent', { inferenceOnly: true });
|
|
58
|
+
|
|
59
|
+
const mappings = Template.fromStack(stack).findResources('AWS::Lambda::EventSourceMapping');
|
|
60
|
+
const keys = Object.keys(mappings);
|
|
61
|
+
assert.strictEqual(keys.length, 1, 'exactly one event source mapping expected for the agent job');
|
|
62
|
+
return mappings[keys[0]].Properties;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
test('CDK: the Agent job takes one message per invocation (no batching on the interactive path)', () => {
|
|
66
|
+
assert.strictEqual(synthAgent().BatchSize, 1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('CDK: the Agent job has no SQS batching window (no added latency for the caller)', () => {
|
|
70
|
+
assert.strictEqual(synthAgent().MaximumBatchingWindowInSeconds, 0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('CDK: the Agent job still reports partial batch failures', () => {
|
|
74
|
+
assert.deepStrictEqual(synthAgent().FunctionResponseTypes, ['ReportBatchItemFailures']);
|
|
75
|
+
});
|
package/src/index.cdk.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Realtime } from '@aws-blocks/bb-realtime';
|
|
|
9
9
|
import { AsyncJob } from '@aws-blocks/bb-async-job';
|
|
10
10
|
import { FileBucket } from '@aws-blocks/bb-file-bucket';
|
|
11
11
|
import { messageSchema, conversationSchema, agentStreamChunkSchema } from './schemas.js';
|
|
12
|
+
import { INTERACTIVE_JOB_EVENT_SOURCE } from './job-event-source.js';
|
|
12
13
|
import { z } from 'zod';
|
|
13
14
|
|
|
14
15
|
export { AgentErrors } from './errors.js';
|
|
@@ -30,7 +31,7 @@ export class Agent extends Scope {
|
|
|
30
31
|
constructor(scope: ScopeParent, id: string, config?: any) {
|
|
31
32
|
super(id, { parent: scope });
|
|
32
33
|
|
|
33
|
-
this.
|
|
34
|
+
this.executionRole.addToPrincipalPolicy(new PolicyStatement({
|
|
34
35
|
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream', 'bedrock:GetFoundationModel', 'bedrock:ListFoundationModels', 'bedrock:GetInferenceProfile'],
|
|
35
36
|
resources: [
|
|
36
37
|
'arn:aws:bedrock:*::foundation-model/*',
|
|
@@ -64,6 +65,7 @@ export class Agent extends Scope {
|
|
|
64
65
|
|
|
65
66
|
new AsyncJob(this, 'job', {
|
|
66
67
|
schema: jobPayloadSchema,
|
|
68
|
+
...INTERACTIVE_JOB_EVENT_SOURCE,
|
|
67
69
|
handler: async () => {},
|
|
68
70
|
});
|
|
69
71
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SQS event source options for the Agent's internal AsyncJob, shared by the
|
|
6
|
+
* runtime (`agent.ts`) and CDK (`index.cdk.ts`) construction sites so an edit to
|
|
7
|
+
* one site's numbers cannot silently miss the other. Only the CDK site's values
|
|
8
|
+
* reach a real event source mapping today — the runtime entry points do not read
|
|
9
|
+
* these options — so this is a single source of truth for the intent, not a
|
|
10
|
+
* runtime/CDK sync guarantee.
|
|
11
|
+
*
|
|
12
|
+
* `stream()` submits one job per interactive turn (and a second on HITL
|
|
13
|
+
* resume) while the caller is blocked on that job starting, so AsyncJob's
|
|
14
|
+
* batching defaults would add up to 5s of delivery latency to a human-facing
|
|
15
|
+
* path. `batchSize: 1` also keeps one failing turn out of a shared batch,
|
|
16
|
+
* which matters because the handler is not idempotent.
|
|
17
|
+
*/
|
|
18
|
+
export const INTERACTIVE_JOB_EVENT_SOURCE = {
|
|
19
|
+
batchSize: 1,
|
|
20
|
+
maxBatchingWindowSeconds: 0,
|
|
21
|
+
} as const;
|
package/src/version.ts
CHANGED