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

feat(generation-transformer): add generation transformer #2820

Merged
merged 36 commits into from
Aug 30, 2024

Conversation

atierian
Copy link
Member

Description of changes

Amplify GraphQL Generation Transformer

The Amplify GraphQL Generation Transformer is a tool that enables the quick and easy creation of AI-powered Generation routes within your AWS AppSync API. This transformer can be leveraged by using the @generation directive to configure AI models and system prompts for generating content.

Directive Definition

The @generation directive is defined as follows:

directive @generation(
    aiModel: String!,
    systemPrompt: String!,
    inferenceConfiguration: GenerationInferenceConfiguration
) on FIELD_DEFINITION

Features

  1. AI Model Integration: Specify the AI model to be used for generation.
  2. System Prompt Configuration: Define a system prompt to guide the AI's output.
  3. Inference Configuration: Fine-tune generation parameters like max tokens, temperature, and top-p.
  4. Integrates with @auth Directive: Supports existing auth modes like IAM, API key, and Amazon Cognito User Pools.
  5. Resolver Creation: Generates resolvers with tool definitions based on the Query field's return type to interact with the specified AI model.
  6. Bedrock HTTP Data Source Creation: Creates a AppSync HTTP Data Source for Bedrock to interact with the specified AI model.

Examples

Basic Usage

Scalar Type Generation

type Query {
  generateStory(topic: String!): String @generation(
    aiModel: "anthropic.claude-3-haiku-20240307-v1:0",
    systemPrompt: "You are a creative storyteller. Generate a short story based on the given topic."
  )
}

Complex Type Generation

type Recipe {
  name: String!
  ingredients: [String!]!
  instructions: [String!]!
  prepTime: Int!
  cookTime: Int!
  servings: Int!
  difficulty: String!
}

type Query {
  generateRecipe(cuisine: String!, dietaryRestrictions: [String]): Recipe
  @generation(
    aiModel: "anthropic.claude-3-haiku-20240307-v1:0",
    systemPrompt: "You are a professional chef specializing in creating recipes. Generate a detailed recipe based on the given cuisine and dietary restrictions."
  )
}

Advanced Configuration

type Query {
  generateCode(description: String!): String @generation(
    aiModel: "anthropic.claude-3-haiku-20240307-v1:0",
    systemPrompt: "You are an expert programmer. Generate code based on the given description.",
    inferenceConfiguration: {
      maxTokens: 500,
      temperature: 0.7,
      topP: 0.9
    }
  )
}

Limitations

  • The @generation directive can only be used on Query fields.
  • The AI model specified must:
    • be supported by Amazon Bedrock's /converse API
    • support tool usage
  • Only the following GraphQL / AppSync scalar types are supported as required properties
    • Boolean
    • Int
    • Float
    • String
    • ID
    • AWSJSON
CDK / CloudFormation Parameters Changed

N/A

Issue #, if available

N/A

Description of how you validated changes

  • Snapshot tests
  • E2E tests
  • Manual testing

Checklist

  • PR description included
  • yarn test passes
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced) Docs PRs are WIP
  • 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.

};
const solveEquationResult = await doAppSyncGraphqlQuery({ ...args, query: solveEquation, variables });
const solution = solveEquationResult.body.data.solveEquation;
expect(solution).toBeDefined();
Copy link
Member Author

Choose a reason for hiding this comment

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

We're asserting that we get an answer here, not the correct answer because LLMs are not good (being generous) at math.

Comment on lines +128 to +130
// TODO: This currently doesn't work because LLMs are not great at following regex pattern requirements, they'll sometimes return "<UNKNOWN>"
// which fails GraphQL type validation for implicitly generated required model values like id, createdAt, updatedAt.
xtest('should generate a model', async () => {
Copy link
Member Author

Choose a reason for hiding this comment

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

The test is currently disabled because we throw in the transformer when the return type contains required fields typed as certain AppSync scalars.

We're exploring options, including prompt improvements, better regex pattern in JSON Schema tool definitions, and special case handling for models (omitting createdAt, updatedAt, and id in tool definition, and populating them in the resolver).

For now, this is an accepted current limitation.

resolverResourceId,
invokeBedrockFunction.req,
invokeBedrockFunction.res,
['auth'],
Copy link
Member Author

@atierian atierian Aug 30, 2024

Choose a reason for hiding this comment

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

This allows existing VTL @auth generated resolver functions to be inserted into the pipeline resolver.

['auth'],
[],
dataSource as any,
{ name: 'APPSYNC_JS', runtimeVersion: '1.0.0' },
Copy link
Member Author

Choose a reason for hiding this comment

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

Probably should eventually be a constant defined in amplify-graphql-transformer-core, but it's only being used here for now. Will add one in a follow up if / when it becomes necessary.

* @returns {MappingTemplateProvider} A MappingTemplateProvider for the response function.
*/
const createInvokeBedrockResponseFunction = (): MappingTemplateProvider => {
// TODO: add stopReason: max_tokens error handling
Copy link
Member Author

Choose a reason for hiding this comment

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

Planned followup to improve the error message.

@atierian
Copy link
Member Author

----- MARK REVIEW -----

palpatim
palpatim previously approved these changes Aug 30, 2024
phani-srikar
phani-srikar previously approved these changes Aug 30, 2024

export type GenerationDirectiveConfiguration = {
parent: ObjectTypeDefinitionNode;
directive: DirectiveNode;
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to store the directive definition when the actual directive arguments are already listed out separately below?

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 point, we don't! I'll remove this in a follow up. Thanks!

};

const stackName = `Generation${this.capitalizeFirstLetter(fieldName)}BedrockDataSourceStack`;
const stack = this.createStack(ctx, stackName);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think Tim has a good point here and might be worth discussing the trade offs with team before we release. I couldn't find any AppSync imposed limit on number of datasources, but feel like re-use will save us from trouble in the future.

toolSpec: ToolSpec;
};

export type Tools = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you use this type elsewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't, and your comment made me realize that we only actually need to export ToolConfig. The extra exports and Tools type definition are relics from previous structure. Will remove in a follow up. Thanks for the callout!

exports[`generation route all scalar types 2`] = `
"export function request(ctx) {
const toolConfig = {\\"tools\\":[{\\"toolSpec\\":{\\"name\\":\\"responseType\\",\\"description\\":\\"Generate a response type for the given field\\",\\"inputSchema\\":{\\"json\\":{\\"type\\":\\"object\\",\\"properties\\":{\\"value\\":{\\"type\\":\\"object\\",\\"properties\\":{\\"int\\":{\\"type\\":\\"number\\",\\"description\\":\\"A signed 32-bit integer value.\\"},\\"float\\":{\\"type\\":\\"number\\",\\"description\\":\\"An IEEE 754 floating point value.\\"},\\"string\\":{\\"type\\":\\"string\\",\\"description\\":\\"A UTF-8 character sequence.\\"},\\"id\\":{\\"type\\":\\"string\\",\\"description\\":\\"A unique identifier for an object. This scalar is serialized like a String but isn't meant to be human-readable.\\"},\\"boolean\\":{\\"type\\":\\"boolean\\",\\"description\\":\\"A boolean value.\\"},\\"awsjson\\":{\\"type\\":\\"string\\",\\"description\\":\\"A JSON string. Any valid JSON construct is automatically parsed and loaded in the resolver code as maps, lists, or scalar values rather than as the literal input strings. Unquoted strings or otherwise invalid JSON result in a GraphQL validation error.\\"},\\"awsemail\\":{\\"type\\":\\"string\\",\\"description\\":\\"An email address in the format local-part@domain-part as defined by RFC 822.\\",\\"pattern\\":\\"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,}$\\"},\\"awsdate\\":{\\"type\\":\\"string\\",\\"description\\":\\"An extended ISO 8601 date string in the format YYYY-MM-DD.\\",\\"pattern\\":\\"^\\\\\\\\d{4}-d{2}-d{2}$\\"},\\"awstime\\":{\\"type\\":\\"string\\",\\"description\\":\\"An extended ISO 8601 time string in the format hh:mm:ss.sss.\\",\\"pattern\\":\\"^\\\\\\\\d{2}:\\\\\\\\d{2}:\\\\\\\\d{2}\\\\\\\\.\\\\\\\\d{3}$\\"},\\"awsdatetime\\":{\\"type\\":\\"string\\",\\"description\\":\\"An extended ISO 8601 date and time string in the format YYYY-MM-DDThh:mm:ss.sssZ.\\",\\"pattern\\":\\"^\\\\\\\\d{4}-\\\\\\\\d{2}-\\\\\\\\d{2}T\\\\\\\\d{2}:\\\\\\\\d{2}:\\\\\\\\d{2}\\\\\\\\.\\\\\\\\d{3}Z$\\"},\\"awstimestamp\\":{\\"type\\":\\"string\\",\\"description\\":\\"An integer value representing the number of seconds before or after 1970-01-01-T00:00Z.\\",\\"pattern\\":\\"^\\\\\\\\d+$\\"},\\"awsphone\\":{\\"type\\":\\"string\\",\\"description\\":\\"A phone number. This value is stored as a string. Phone numbers can contain either spaces or hyphens to separate digit groups. Phone numbers without a country code are assumed to be US/North American numbers adhering to the North American Numbering Plan (NANP).\\",\\"pattern\\":\\"^\\\\\\\\d{3}-d{3}-d{4}$\\"},\\"awsurl\\":{\\"type\\":\\"string\\",\\"description\\":\\"A URL as defined by RFC 1738. For example, https://www.amazon.com/dp/B000NZW3KC/ or mailto:[email protected]. URLs must contain a schema (http, mailto) and can't contain two forward slashes (//) in the path part.\\",\\"pattern\\":\\"^(https?|mailto)://[^s/$.?#].[^s]*$\\"},\\"awsipaddress\\":{\\"type\\":\\"string\\",\\"description\\":\\"A valid IPv4 or IPv6 address. IPv4 addresses are expected in quad-dotted notation (123.12.34.56). IPv6 addresses are expected in non-bracketed, colon-separated format (1a2b:3c4b::1234:4567). You can include an optional CIDR suffix (123.45.67.89/16) to indicate subnet mask.\\"}},\\"required\\":[]}},\\"required\\":[\\"value\\"]}}}}],\\"toolChoice\\":{\\"tool\\":{\\"name\\":\\"responseType\\"}}};
const prompt = \\"\\";
Copy link
Contributor

Choose a reason for hiding this comment

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

We give so much information and it still returns a response with wrong format? :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea 😞
We haven't found a generic solution yet, but we've seen some promising results with various techniques, and are cautiously optimistic that we can get there.

@atierian atierian dismissed stale reviews from phani-srikar and palpatim via 174470b August 30, 2024 21:47
@atierian atierian enabled auto-merge (squash) August 30, 2024 21:58
@atierian atierian merged commit a86db4e into feature/raven Aug 30, 2024
5 of 6 checks passed
@atierian atierian deleted the ai-generation branch August 30, 2024 22:18
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