Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stream conversation logs in sandbox #2073

Merged
merged 16 commits into from
Oct 4, 2024
9 changes: 9 additions & 0 deletions .changeset/eleven-snails-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@aws-amplify/ai-constructs': minor
'@aws-amplify/backend-ai': minor
'@aws-amplify/backend-output-schemas': minor
'@aws-amplify/backend': patch
'@aws-amplify/sandbox': patch
---

Stream conversation logs in sandbox
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Ignore artifacts:
.amplify
build
coverage
bin
Expand All @@ -10,6 +11,7 @@ verdaccio-cache
expected-cdk-out
.changeset/pre.json
concurrent_workspace_script_cache.json
packages/integration-tests/src/e2e-tests
scripts/components/api-changes-validator/test-resources/working-directory
/test-projects
testDir
testDir
49 changes: 26 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/ai-constructs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

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

import { AIConversationOutput } from '@aws-amplify/backend-output-schemas';
import { BackendOutputStorageStrategy } from '@aws-amplify/plugin-types';
import * as bedrock from '@aws-sdk/client-bedrock-runtime';
import { Construct } from 'constructs';
import { FunctionResources } from '@aws-amplify/plugin-types';
Expand Down Expand Up @@ -49,6 +51,7 @@ type ConversationHandlerFunctionProps = {
modelId: string;
region?: string;
}>;
outputStorageStrategy?: BackendOutputStorageStrategy<AIConversationOutput>;
};

// @public (undocumented)
Expand Down
5 changes: 5 additions & 0 deletions packages/ai-constructs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@
},
"license": "Apache-2.0",
"dependencies": {
"@aws-amplify/backend-output-schemas": "^1.2.1",
"@aws-amplify/platform-core": "^1.1.0",
"@aws-amplify/plugin-types": "^1.0.1",
"@aws-sdk/client-bedrock-runtime": "^3.622.0",
"@smithy/types": "^3.3.0"
},
"devDependencies": {
"@aws-amplify/backend-output-storage": "^1.1.2"
},
"peerDependencies": {
"aws-cdk-lib": "^2.152.0",
"constructs": "^10.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { App, Stack } from 'aws-cdk-lib';
import { ConversationHandlerFunction } from './conversation_handler_construct';
import { Template } from 'aws-cdk-lib/assertions';
import path from 'path';
import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage';

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

void it('does not store output if output strategy is absent', () => {
const app = new App();
const stack = new Stack(app);
new ConversationHandlerFunction(stack, 'conversationHandler', {
models: [
{
modelId: 'testModelId',
},
],
outputStorageStrategy: undefined,
});
const template = Template.fromStack(stack);
const output = template.findOutputs(
'definedConversationHandlers'
).definedConversationHandlers;
assert.ok(!output);
});

void it('stores output if output strategy is present', () => {
const app = new App();
const stack = new Stack(app);
new ConversationHandlerFunction(stack, 'conversationHandler', {
models: [
{
modelId: 'testModelId',
},
],
outputStorageStrategy: new StackMetadataBackendOutputStorageStrategy(
stack
),
});
const template = Template.fromStack(stack);
const outputValue = template.findOutputs('definedConversationHandlers')
.definedConversationHandlers.Value;
assert.deepStrictEqual(outputValue, {
'Fn::Join': [
'',
[
'["',
{
/* eslint-disable spellcheck/spell-checker */
Ref: 'conversationHandlerconversationHandlerFunction45BC2E1F',
/* eslint-enable spellcheck/spell-checker */
},
'"]',
],
],
});
});

void it('throws if entry is not absolute', () => {
const app = new App();
const stack = new Stack(app);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { FunctionResources, ResourceProvider } from '@aws-amplify/plugin-types';
import { Duration, Stack } from 'aws-cdk-lib';
import {
BackendOutputStorageStrategy,
FunctionResources,
ResourceProvider,
} from '@aws-amplify/plugin-types';
import { Duration, Stack, Tags } from 'aws-cdk-lib';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { CfnFunction, Runtime as LambdaRuntime } from 'aws-cdk-lib/aws-lambda';
import {
CfnFunction,
Runtime as LambdaRuntime,
LoggingFormat,
} from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import {
CustomDataIdentifier,
Expand All @@ -11,6 +19,11 @@ import {
} from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';
import path from 'path';
import { TagName } from '@aws-amplify/platform-core';
import {
AIConversationOutput,
aiConversationOutputKey,
} from '@aws-amplify/backend-output-schemas';

const resourcesRoot = path.normalize(path.join(__dirname, 'runtime'));
const defaultHandlerFilePath = path.join(resourcesRoot, 'default_handler.js');
Expand All @@ -21,6 +34,10 @@ export type ConversationHandlerFunctionProps = {
modelId: string;
region?: string;
}>;
/**
* @internal
*/
outputStorageStrategy?: BackendOutputStorageStrategy<AIConversationOutput>;
};

/**
Expand Down Expand Up @@ -53,6 +70,8 @@ export class ConversationHandlerFunction
throw new Error('Entry must be absolute path');
}

Tags.of(this).add(TagName.FRIENDLY_NAME, id);

const conversationHandler = new NodejsFunction(
this,
`conversationHandlerFunction`,
Expand All @@ -67,6 +86,7 @@ export class ConversationHandlerFunction
// For custom entry we do bundle SDK as we can't control version customer is coding against.
bundleAwsSDK: !!this.props.entry,
},
loggingFormat: LoggingFormat.JSON,
logGroup: new LogGroup(this, 'conversationHandlerFunctionLogGroup', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be able to actually see the debug logs, don't we need to also expose the ApplicationLogLevel property so customers can configure it? if I recall cdk sets the default as INFO?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I called this out in the "Out of scope" section in the PR description.

retention: RetentionDays.INFINITE,
dataProtectionPolicy: new DataProtectionPolicy({
Expand Down Expand Up @@ -105,5 +125,23 @@ export class ConversationHandlerFunction
) as CfnFunction,
},
};

this.storeOutput(this.props.outputStorageStrategy);
}

/**
* Append conversation handler to defined functions.
*/
private storeOutput = (
outputStorageStrategy:
| BackendOutputStorageStrategy<AIConversationOutput>
| undefined
): void => {
outputStorageStrategy?.appendToBackendOutputList(aiConversationOutputKey, {
version: '1',
payload: {
definedConversationHandlers: this.resources.lambda.functionName,
},
});
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export class BedrockConverseAdapter {
eventToolsProvider = new ConversationTurnEventToolsProvider(event),
private readonly messageHistoryRetriever = new ConversationMessageHistoryRetriever(
event
)
),
private readonly logger = console
) {
this.executableTools = [
...eventToolsProvider.getEventTools(),
Expand Down Expand Up @@ -90,9 +91,16 @@ export class BedrockConverseAdapter {
inferenceConfig: inferenceConfiguration,
toolConfig,
};
this.logger.info('Sending Bedrock Converse request');
this.logger.debug('Bedrock Converse request:', converseCommandInput);
bedrockResponse = await this.bedrockClient.send(
new ConverseCommand(converseCommandInput)
);
this.logger.info(
`Received Bedrock Converse response, requestId=${bedrockResponse.$metadata.requestId}`,
bedrockResponse.usage
);
this.logger.debug('Bedrock Converse response:', bedrockResponse);
if (bedrockResponse.output?.message) {
messages.push(bedrockResponse.output?.message);
}
Expand Down Expand Up @@ -191,7 +199,11 @@ export class BedrockConverseAdapter {
);
}
try {
this.logger.info(`Invoking tool ${tool.name}`);
this.logger.debug('Tool input:', toolUseBlock.toolUse.input);
const toolResponse = await tool.execute(toolUseBlock.toolUse.input);
this.logger.info(`Received response from ${tool.name} tool`);
this.logger.debug(toolResponse);
return {
role: 'user',
content: [
Expand Down
Loading
Loading