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

refactor(conversation-transformer): use file templates for JS resolvers #2897

Merged
merged 9 commits into from
Sep 27, 2024

Conversation

atierian
Copy link
Member

@atierian atierian commented Sep 24, 2024

Description of changes

Note

No functional changes

  • moves string defined JS resolvers to JavaScript file templates using [[KEY]] marker for template substitution.
  • adds copy-js-resolver-templates to move JS templates to /lib as part of build script.
  • adds packages/amplify-graphql-conversation-transformer/src/resolvers/*.js to .prettierignore

Why use [[KEY]] as the marker?
This marker format satisfies two key goals:

  1. Minimal risk of overwriting previously substituted values, e.g. the value of [[KEY1]] containing "[[KEY2]]".
  2. Not break syntax highlighting in the JS file templates. The motivation behind moving to file templates is to improve maintainability of the resolver functions, for which syntax highlighting plays a vital role.

I'm satisfied that [[KEY]] is the appropriate format here. That said if you have an alternative in mind that satisfies both 1. and 2., and has other benefits, I'm happy to consider it.

CDK / CloudFormation Parameters Changed

N/A

Issue #, if available

N/A

Description of how you validated changes

  • CI PR workflow
  • locally run conversation E2E tests

Checklist

  • PR description included
  • yarn test passes
  • E2E test run linked
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)
  • New AWS SDK calls or CloudFormation actions have been added to relevant test and service IAM policies
  • Any CDK or CloudFormation parameter changes are called out explicitly

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@atierian atierian marked this pull request as ready for review September 24, 2024 20:29
@atierian atierian requested a review from a team as a code owner September 24, 2024 20:29
Comment on lines +44 to +47
// This unfortunately needs to be an inline template because an s3 mapping template doesn't allow the CDK
// to substitute token values, which is necessary for this resolver function due to its reference of
// `ctx.api.graphqlUrl`.
return MappingTemplate.inlineTemplateFromString(resolver);
Copy link
Member Author

Choose a reason for hiding this comment

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

In a follow up, I'm planning on moving the ctx.api.graphqlUrl reference to the "Resolver code" portion of the pipeline along with the existing ctx.stash. typeName / fieldName / etc values.

This will allow us to make this an S3 asset and support hotswapping for schema defined values like system prompt, tools, model (LLM), and model inference configuration.

@@ -0,0 +1,56 @@
import { util } from '@aws-appsync/utils';

export function request(ctx) {
Copy link
Member

Choose a reason for hiding this comment

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

For future PR:

  1. Please add a doc comment noting the replacement logic
  2. Rename the file to something other than .js (e.g., .js.template or something), since this isn't actually executable JS as-is

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call.
I addressed 2. in b05fbac because it's core to the scope of change.
I'll follow up for 1. in #2897 (comment)


export function request(ctx) {
const { args, request, prev } = ctx;
[[TOOL_DEFINITIONS_LINE]]
Copy link
Member

Choose a reason for hiding this comment

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

Here and throughout: Does your template substitution logic guarantee that these will be:

  1. Always one line?
  2. Always appropriately escaped for the expected quotes?

How can we ensure that the template always uses the expected quoting scheme so the caller passing the substitution values always escapes appropriately?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. Always one line?

Not always one line. For example, [[TOOL_DEFINITIONS_LINE]] here can be:

const dataTools = toolDefinitions.tools;
const toolsConfiguration = {
  dataTools,
  clientTools,
};
  1. Always appropriately escaped for the expected quotes?

The only safeguard today are the existing snapshots, which is admittedly not great. I plan to add integration tests using AppSync's EvaluateCode API for the resolver functions in the future (reference). That will give us confidence in future changes.

How can we ensure that the template always uses the expected quoting scheme so the caller passing the substitution values always escapes appropriately?

Same answer as 2. Additional context -- the majority of these values are derived from GraphQL inputs (field name, structured directive input types) where values that would cause escaping issues would have failed upstream in GraphQL validation. The exceptions to this rule are the model identifier and system prompt, which are string inputs provided when defining a conversation route. The system prompt is already stringified to prevent issues here. The model identifier can also be stringified prior to substitution -- I'll look at this in a follow up.


let resolver = fs.readFileSync(path.join(__dirname, 'invoke-lambda-resolver-fn.js'), 'utf8');
Object.entries(substitutions).forEach(([key, value]) => {
const replaced = resolver.replace(new RegExp(`\\[\\[${key}\\]\\]`, 'g'), value);
Copy link
Member

Choose a reason for hiding this comment

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

One day Node will give us a String.prototype.replaceAll method so we can forego this nonsense. :(

@@ -0,0 +1,30 @@
export function request(ctx) {
const { authFilter } = ctx.stash;

Copy link
Member

Choose a reason for hiding this comment

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

Here and throughout the PR: let's add a defensive existence assertion for authFilter. I'm comfortable that the existing implementation sets it appropriately, but if a future update accidentally forgets it, I'd prefer to catch that early instead of performing a query without the filter in place. (It's possible that the toDynamoDBFilterExpression util below would fail in that case, but I'd prefer to make the check explicit.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Unless you strongly object, I'll include this in a follow up (I have a draft PR queued up that makes some changes here). I know that this is a safe (and correct) change to make, but I'd really prefer to keep this PR non-functional.

Copy link
Member Author

Choose a reason for hiding this comment

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

Noted in the aforementioned draft PR as a reminder.
#2910 (comment)

palpatim
palpatim previously approved these changes Sep 26, 2024
@atierian atierian enabled auto-merge (squash) September 26, 2024 16:39
@atierian atierian merged commit 5fefb41 into main Sep 27, 2024
5 checks passed
@atierian atierian deleted the ai.conversation-js-resolver-templates branch September 27, 2024 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants