Skip to content

Commit 0a5e51c

Browse files
Kamil SobolAmplifiyer
andauthored
Stream conversation logs in sandbox (#2073)
* Stream conversation logs in sandbox * fix that * refactor that * and that * this works * Undo the hack * more tests * that * Update packages/sandbox/src/lambda_function_log_streamer.ts Co-authored-by: Amplifiyer <[email protected]> * use different output * Revert "Undo the hack" This reverts commit 3e8b590. * use different outputs * use different outputs * Revert "Revert "Undo the hack"" This reverts commit 3c2d417. * fix that --------- Co-authored-by: Amplifiyer <[email protected]>
1 parent 300a72d commit 0a5e51c

22 files changed

+375
-182
lines changed

.changeset/eleven-snails-move.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@aws-amplify/ai-constructs': minor
3+
'@aws-amplify/backend-ai': minor
4+
'@aws-amplify/backend-output-schemas': minor
5+
'@aws-amplify/backend': patch
6+
'@aws-amplify/sandbox': patch
7+
---
8+
9+
Stream conversation logs in sandbox

.prettierignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Ignore artifacts:
2+
.amplify
23
build
34
coverage
45
bin
@@ -10,6 +11,7 @@ verdaccio-cache
1011
expected-cdk-out
1112
.changeset/pre.json
1213
concurrent_workspace_script_cache.json
14+
packages/integration-tests/src/e2e-tests
1315
scripts/components/api-changes-validator/test-resources/working-directory
1416
/test-projects
15-
testDir
17+
testDir

package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ai-constructs/API.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
/// <reference types="node" />
88

9+
import { AIConversationOutput } from '@aws-amplify/backend-output-schemas';
10+
import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types';
911
import * as bedrock from '@aws-sdk/client-bedrock-runtime';
1012
import { Construct } from 'constructs';
1113
import { FunctionResources } from '@aws-amplify/plugin-types';
@@ -51,6 +53,7 @@ type ConversationHandlerFunctionProps = {
5153
modelId: string;
5254
region?: string;
5355
}>;
56+
outputStorageStrategy?: BackendOutputStorageStrategy<AIConversationOutput>;
5457
};
5558

5659
// @public (undocumented)

packages/ai-constructs/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
},
2727
"license": "Apache-2.0",
2828
"dependencies": {
29+
"@aws-amplify/backend-output-schemas": "^1.2.1",
30+
"@aws-amplify/platform-core": "^1.1.0",
2931
"@aws-amplify/plugin-types": "^1.0.1",
3032
"@aws-sdk/client-bedrock-runtime": "^3.622.0",
3133
"@smithy/types": "^3.3.0",
3234
"json-schema-to-ts": "^3.1.1"
3335
},
3436
"devDependencies": {
37+
"@aws-amplify/backend-output-storage": "^1.1.2",
3538
"typescript": "^5.0.0"
3639
},
3740
"peerDependencies": {

packages/ai-constructs/src/conversation/conversation_handler_construct.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { App, Stack } from 'aws-cdk-lib';
44
import { ConversationHandlerFunction } from './conversation_handler_construct';
55
import { Template } from 'aws-cdk-lib/assertions';
66
import path from 'path';
7+
import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage';
78

89
void describe('Conversation Handler Function construct', () => {
910
void it('creates handler with log group with JWT token redacting policy', () => {
@@ -140,6 +141,56 @@ void describe('Conversation Handler Function construct', () => {
140141
});
141142
});
142143

144+
void it('does not store output if output strategy is absent', () => {
145+
const app = new App();
146+
const stack = new Stack(app);
147+
new ConversationHandlerFunction(stack, 'conversationHandler', {
148+
models: [
149+
{
150+
modelId: 'testModelId',
151+
},
152+
],
153+
outputStorageStrategy: undefined,
154+
});
155+
const template = Template.fromStack(stack);
156+
const output = template.findOutputs(
157+
'definedConversationHandlers'
158+
).definedConversationHandlers;
159+
assert.ok(!output);
160+
});
161+
162+
void it('stores output if output strategy is present', () => {
163+
const app = new App();
164+
const stack = new Stack(app);
165+
new ConversationHandlerFunction(stack, 'conversationHandler', {
166+
models: [
167+
{
168+
modelId: 'testModelId',
169+
},
170+
],
171+
outputStorageStrategy: new StackMetadataBackendOutputStorageStrategy(
172+
stack
173+
),
174+
});
175+
const template = Template.fromStack(stack);
176+
const outputValue = template.findOutputs('definedConversationHandlers')
177+
.definedConversationHandlers.Value;
178+
assert.deepStrictEqual(outputValue, {
179+
'Fn::Join': [
180+
'',
181+
[
182+
'["',
183+
{
184+
/* eslint-disable spellcheck/spell-checker */
185+
Ref: 'conversationHandlerconversationHandlerFunction45BC2E1F',
186+
/* eslint-enable spellcheck/spell-checker */
187+
},
188+
'"]',
189+
],
190+
],
191+
});
192+
});
193+
143194
void it('throws if entry is not absolute', () => {
144195
const app = new App();
145196
const stack = new Stack(app);

packages/ai-constructs/src/conversation/conversation_handler_construct.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { FunctionResources, ResourceProvider } from '@aws-amplify/plugin-types';
2-
import { Duration, Stack } from 'aws-cdk-lib';
1+
import {
2+
BackendOutputStorageStrategy,
3+
FunctionResources,
4+
ResourceProvider,
5+
} from '@aws-amplify/plugin-types';
6+
import { Duration, Stack, Tags } from 'aws-cdk-lib';
37
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
4-
import { CfnFunction, Runtime as LambdaRuntime } from 'aws-cdk-lib/aws-lambda';
8+
import {
9+
CfnFunction,
10+
Runtime as LambdaRuntime,
11+
LoggingFormat,
12+
} from 'aws-cdk-lib/aws-lambda';
513
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
614
import {
715
CustomDataIdentifier,
@@ -11,6 +19,11 @@ import {
1119
} from 'aws-cdk-lib/aws-logs';
1220
import { Construct } from 'constructs';
1321
import path from 'path';
22+
import { TagName } from '@aws-amplify/platform-core';
23+
import {
24+
AIConversationOutput,
25+
aiConversationOutputKey,
26+
} from '@aws-amplify/backend-output-schemas';
1427

1528
const resourcesRoot = path.normalize(path.join(__dirname, 'runtime'));
1629
const defaultHandlerFilePath = path.join(resourcesRoot, 'default_handler.js');
@@ -21,6 +34,10 @@ export type ConversationHandlerFunctionProps = {
2134
modelId: string;
2235
region?: string;
2336
}>;
37+
/**
38+
* @internal
39+
*/
40+
outputStorageStrategy?: BackendOutputStorageStrategy<AIConversationOutput>;
2441
};
2542

2643
/**
@@ -53,6 +70,8 @@ export class ConversationHandlerFunction
5370
throw new Error('Entry must be absolute path');
5471
}
5572

73+
Tags.of(this).add(TagName.FRIENDLY_NAME, id);
74+
5675
const conversationHandler = new NodejsFunction(
5776
this,
5877
`conversationHandlerFunction`,
@@ -67,6 +86,7 @@ export class ConversationHandlerFunction
6786
// For custom entry we do bundle SDK as we can't control version customer is coding against.
6887
bundleAwsSDK: !!this.props.entry,
6988
},
89+
loggingFormat: LoggingFormat.JSON,
7090
logGroup: new LogGroup(this, 'conversationHandlerFunctionLogGroup', {
7191
retention: RetentionDays.INFINITE,
7292
dataProtectionPolicy: new DataProtectionPolicy({
@@ -105,5 +125,23 @@ export class ConversationHandlerFunction
105125
) as CfnFunction,
106126
},
107127
};
128+
129+
this.storeOutput(this.props.outputStorageStrategy);
108130
}
131+
132+
/**
133+
* Append conversation handler to defined functions.
134+
*/
135+
private storeOutput = (
136+
outputStorageStrategy:
137+
| BackendOutputStorageStrategy<AIConversationOutput>
138+
| undefined
139+
): void => {
140+
outputStorageStrategy?.appendToBackendOutputList(aiConversationOutputKey, {
141+
version: '1',
142+
payload: {
143+
definedConversationHandlers: this.resources.lambda.functionName,
144+
},
145+
});
146+
};
109147
}

packages/ai-constructs/src/conversation/runtime/bedrock_converse_adapter.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ export class BedrockConverseAdapter {
4141
eventToolsProvider = new ConversationTurnEventToolsProvider(event),
4242
private readonly messageHistoryRetriever = new ConversationMessageHistoryRetriever(
4343
event
44-
)
44+
),
45+
private readonly logger = console
4546
) {
4647
this.executableTools = [
4748
...eventToolsProvider.getEventTools(),
@@ -91,9 +92,16 @@ export class BedrockConverseAdapter {
9192
inferenceConfig: inferenceConfiguration,
9293
toolConfig,
9394
};
95+
this.logger.info('Sending Bedrock Converse request');
96+
this.logger.debug('Bedrock Converse request:', converseCommandInput);
9497
bedrockResponse = await this.bedrockClient.send(
9598
new ConverseCommand(converseCommandInput)
9699
);
100+
this.logger.info(
101+
`Received Bedrock Converse response, requestId=${bedrockResponse.$metadata.requestId}`,
102+
bedrockResponse.usage
103+
);
104+
this.logger.debug('Bedrock Converse response:', bedrockResponse);
97105
if (bedrockResponse.output?.message) {
98106
messages.push(bedrockResponse.output?.message);
99107
}
@@ -194,7 +202,11 @@ export class BedrockConverseAdapter {
194202
);
195203
}
196204
try {
205+
this.logger.info(`Invoking tool ${tool.name}`);
206+
this.logger.debug('Tool input:', toolUseBlock.toolUse.input);
197207
const toolResponse = await tool.execute(toolUseBlock.toolUse.input);
208+
this.logger.info(`Received response from ${tool.name} tool`);
209+
this.logger.debug(toolResponse);
198210
return {
199211
role: 'user',
200212
content: [

packages/ai-constructs/src/conversation/runtime/conversation_turn_executor.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ void describe('Conversation turn executor', () => {
4747

4848
const consoleErrorMock = mock.fn();
4949
const consoleLogMock = mock.fn();
50+
const consoleDebugMock = mock.fn();
5051
const consoleMock = {
5152
error: consoleErrorMock,
5253
log: consoleLogMock,
54+
debug: consoleDebugMock,
5355
} as unknown as Console;
5456

5557
await new ConversationTurnExecutor(
@@ -100,9 +102,11 @@ void describe('Conversation turn executor', () => {
100102

101103
const consoleErrorMock = mock.fn();
102104
const consoleLogMock = mock.fn();
105+
const consoleDebugMock = mock.fn();
103106
const consoleMock = {
104107
error: consoleErrorMock,
105108
log: consoleLogMock,
109+
debug: consoleDebugMock,
106110
} as unknown as Console;
107111

108112
await assert.rejects(
@@ -164,9 +168,11 @@ void describe('Conversation turn executor', () => {
164168

165169
const consoleErrorMock = mock.fn();
166170
const consoleLogMock = mock.fn();
171+
const consoleDebugMock = mock.fn();
167172
const consoleMock = {
168173
error: consoleErrorMock,
169174
log: consoleLogMock,
175+
debug: consoleDebugMock,
170176
} as unknown as Console;
171177

172178
await assert.rejects(

packages/ai-constructs/src/conversation/runtime/conversation_turn_executor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export class ConversationTurnExecutor {
2929
this.logger.log(
3030
`Handling conversation turn event, currentMessageId=${this.event.currentMessageId}, conversationId=${this.event.conversationId}`
3131
);
32+
this.logger.debug('Event received:', this.event);
3233

3334
const assistantResponse = await this.bedrockConverseAdapter.askBedrock();
3435

0 commit comments

Comments
 (0)