AWS Tip

Best AWS, DevOps, Serverless, and more from top Medium writers .

Follow publication

Enhancing existing APIs with Generative AI: A Low-Code approach with Bedrock and AWS AppSync

Emanuel Russo
AWS Tip
Published in
11 min readApr 29, 2024

--

In the fast-paced realm of technology, staying ahead of the curve is essential. As businesses strive to deliver richer user experiences, the integration of cutting-edge technologies becomes increasingly crucial. In this blog post, we’ll explore a game-changing strategy for enhancing existing APIs with Generative AI. Through the lens of solutions like Bedrock and AWS AppSync, we’ll uncover how this fusion empowers developers to effortlessly enrich content and elevate user interactions using a low-code approach that ensures the development of these new features with adequate time to market.

To demonstrate the practical application of enhancing existing APIs with Generative AI, let’s consider a straightforward scenario. Suppose you’re developing an application that provides weather forecasts to users, leveraging the APIs from open-meteo.com for accurate weather data retrieval. Now, imagine enriching these forecasts with dynamic, contextually relevant content generated by BedRock’s Generative AI. In the following example, we’ll guide you through the process of seamlessly integrating these technologies, resulting in a customized experience for the user.

1. Prerequirement

To test the code in this article, you must have activated the Claude V3 Sonnet model from the console on the region us-east-1. To access Anthropic Claude models on Amazon Bedrock, complete the following steps:

  1. Open the Amazon Bedrock console.
  2. In the navigation pane, choose Model access.
  3. Choose Submit use case details, and then enter the information. Then, choose Submit.
  4. Wait till the Submit use case details button to become grayed out.
  5. Select Anthropic, and then choose Save changes.

In addition to model activation, you will need to have already installed the AWS CLI and AWS CDK, as well as NodeJS 18/20. In case you need to setup CDK in your account, you can look the documentation of AWS here, having focus on Typescript CDK. From now, I will assume that you already have CDK installed on your laptop and the AWS account have already the bootstapped CDK. In this demo we will use Milan Region (eu-south-1) as primary region and us-east-1 for Bedrock. CDK need to be bootstrapped in Milan.

2. Demo Setup

All code of this demo will be available on my GitHub account:

Please clone the repository of the code and install it on your account, in order to follow the overview. To do that, you just simple execute the following command and confirm the deploy changes (press y):

cdk deploy --profile <AWS_PROFILE>

In some minutes, you will have the demo available on your account and the output of CDK will be similar to this:

CDK Deploy command

To find APIs Endpoint that will be needed to test the demo, you can check AWS Console. To do this:

  1. Access to AWS AppSync console
  2. Click on APIs, and copy the API Endpoint from AppSync:
API Endpoint

3. Find API Key under weather-api and Settings menu, needed for Authentication and copy it:

API Keys

4. Now you can use Postman or AWS Console to retrieve the API and verify that everything went well during configuration. For simplicity, I will use AWS Console in the blog post. To continue, click on the Queries section of AppSync and enter the following query to verify the API response:

query GetWeatherQuery {
getWeather(lat: 45.5921, long: 9.5734) {
generated {
description
recommendation
}
daily {
precipitation_sum
rain_sum
showers_sum
temperature_2m_max
snowfall_sum
temperature_2m_min
time
weather_code
weather_description
}
latitude
longitude
}
}

5. Press run button from console to execute the query:

Query result for Milan

6. On API Response, you can find the open-meteo.com API response for forecast and under generated object you can find a GenerativeAI generated description of the forecast and suggestion for dress code that you will need to be confortable:

{
"data": {
"getWeather": {
"generated": {
"description": "Today in Milan, the weather is expected to be mostly dry with a chance of light rain showers. The temperatures will be comfortable, with a high of around 21°C and a low of 9°C. Despite the slight chance of precipitation, the overall conditions should be pleasant for outdoor activities.",
"recommendation": "For the day's weather in Milan, it is recommended to dress in layers to accommodate the temperature range. A lightweight jacket or sweater would be advisable for the cooler morning and evening hours. During the warmer parts of the day, a short-sleeved shirt or light blouse would be appropriate. It's also a good idea to carry a small umbrella or light rain jacket in case of any passing showers."
},
"daily": {
"precipitation_sum": [
0
],
"rain_sum": [
0
],
"showers_sum": [
0
],
"temperature_2m_max": [
21.5
],
"snowfall_sum": [
0
],
"temperature_2m_min": [
9.3
],
"time": [
"2024-04-29"
],
"weather_code": [
80
],
"weather_description": [
"RAIN_SHOWERS_SLIGHT"
]
},
"latitude": 45.6,
"longitude": 9.58
}
}
}

6. You can change latitude and longitude parameter of query to check forecast for other city. For example with lat: 39.2989 and 16.2531 you can check Cosenza weather:

Query result for Cosenza

Now that the demo is installed and the use case is clear, we’ll delve into the code to see how I implemented this scenario.

3. Code Overview

This use case is implemented without the need for custom Lambda code. Instead, I achieved this by creating GraphQL APIs in AppSync and employing two HTTP Resolvers within the pipeline resolver of AppSync. Utilizing Mapper templates, I seamlessly integrated an API from open-meteo.com. Leveraging the pipeline resolver, I effortlessly passed the API’s result to Bedrock for generating enriched content. The prompt for Bedrock is accessible through the getGenAI.js function and can be customized to suit various other use cases. It’s worth noting that I am not an expert in Generative AI, and the prompt provided in the example may not be optimized. However, it’s essential to clarify that the purpose of this blog post is to demonstrate integration rather than to teach how to use Generative AI. Additionally, it’s important to highlight that assigning the task of recognizing a city from GPS coordinates to a Generative AI is not advisable. The margin of error could be significant, and for such tasks, traditional techniques are more suitable. In fact, during use it may happen that the indicated city is not the correct one, but this is an example to demonstrate integration of APIs and is not a suitable use case for Generative AI.

Let’s go thoughts the code in order to describe the infrastructure code needed to implement the demo.

1. API Definition

    const api = new GraphqlApi(this, 'WeatherApi', {
name: 'weather-api',
definition: Definition.fromFile(path.join(__dirname, '../graphql/schema.graphql')),
authorizationConfig: {
defaultAuthorization: {
authorizationType: AuthorizationType.API_KEY,
},
},
logConfig: {fieldLogLevel: FieldLogLevel.ALL},
xrayEnabled: true,
});

This section of the CDK code instantiates a GraphQL API named ‘WeatherApi’. It utilizes a GraphQL schema file to configure the API schema. The configuration includes authorization through API key as the default method, logging for all fields, and enabling X-Ray for call tracing. Additionally, this section allows for intervention on the parameters of AppSync. For instance, you can adjust the authorization settings, logging levels, and X-Ray configuration to suit their specific requirements.

2. GraphQL Schema

type Weather {
latitude: Float!
longitude: Float!,
daily: DailyWeather,
generated: GeneratedFields
}

type DailyWeather {
time: [AWSDate]!
weather_code: [Int]!
weather_description: [String]!
temperature_2m_max: [Float],
temperature_2m_min: [Float],
precipitation_sum: [Float],
rain_sum: [Float],
showers_sum: [Float],
snowfall_sum: [Float]
}

type GeneratedFields {
description: String,
recommendation: String
}

scalar AWSDate

type Query {
getWeather(lat: Float!, long: Float!): Weather
}

schema {
query: Query
}

The schema consists of three main types: Weather, DailyWeather, and GeneratedFields.

  • Weather: Represents weather data for a location, including latitude, longitude, daily weather details, and Generative AI generated content.
  • DailyWeather: Provides daily weather information such as time, weather code, weather description, and various weather parameters.
  • GeneratedFields: Contains Generative AI generated content related to the weather forecast, including description and recommendation.

The getWeather query retrieves weather data based on provided latitude and longitude coordinates.

3. open-meteo HTTP Datasource

const weatherDataSource = new HttpDataSource(this, "WeatherHTTPDS", {
api: api,
name: "OpenWeatherApiDataSource",
endpoint: "https://api.open-meteo.com/",
});

const getWeather = new AppsyncFunction(this, 'GetWeather', {
name: 'getWeather',
api,
dataSource: weatherDataSource,
code: Code.fromAsset(path.join(__dirname, '../resolvers/getWeather.js')),
runtime: FunctionRuntime.JS_1_0_0,
});

This CDK code sets up an HTTP data source to connect to the Open-Meteo API endpoint. It then defines an AppSync function responsible for retrieving weather data. The function is associated with the API and utilizes the HTTP data source to fetch weather information. The resolver logic is implemented in a separate JavaScript file provided as the code asset.

4. open-meteo HTTP Resolver Function

import {util} from "@aws-appsync/utils";

export function request(ctx) {
const { lat, long } = ctx.stash;
return {
method: "GET",
resourcePath: `/v1/forecast?latitude=${lat}&longitude=${long}&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum,rain_sum,showers_sum,snowfall_sum&timezone=Europe%2FBerlin&forecast_days=1`,
};
}

export function response(ctx) {
const { result } = ctx;

const WeatherCondition = {
0: "CLEAR_SKY",
1: "MAINLY_CLEAR",
2: "PARTLY_CLOUDY",
3: "OVERCAST",
45: "FOG",
48: "RIME_FOG",
51: "DRIZZLE_LIGHT",
53: "DRIZZLE_MODERATE",
55: "DRIZZLE_DENSE",
56: "FREEZING_DRIZZLE_LIGHT",
57: "FREEZING_DRIZZLE_DENSE",
61: "RAIN_SLIGHT",
63: "RAIN_MODERATE",
65: "RAIN_HEAVY",
66: "FREEZING_RAIN_LIGHT",
67: "FREEZING_RAIN_HEAVY",
71: "SNOWFALL_SLIGHT",
73: "SNOWFALL_MODERATE",
75: "SNOWFALL_HEAVY",
77: "SNOW_GRAINS",
80: "RAIN_SHOWERS_SLIGHT",
81: "RAIN_SHOWERS_MODERATE",
82: "RAIN_SHOWERS_VIOLENT",
85: "SNOW_SHOWERS_SLIGHT",
86: "SNOW_SHOWERS_HEAVY",
95: "THUNDERSTORM_SLIGHT",
96: "THUNDERSTORM_SLIGHT_HAIL",
99: "THUNDERSTORM_HEAVY_HAIL",
};

if (result.statusCode !== 200) {
return util.appendError(result.body, `${result.statusCode}`);
}
const objectResult = JSON.parse(result.body);
objectResult.daily.weather_description = objectResult.daily.weather_code.map(code => WeatherCondition[code] ? WeatherCondition[code] : "NOT_DEFINED");
return objectResult;
}

This resolver function manages the retrieval of weather forecast data from the external API. In the request function, it constructs an API request tailored to geographic coordinates that are in the input. Upon receiving the API response, the response function processes the data, mapping weather condition codes to descriptive labels for clarity. In case of error, a detailed messages appended to responses as needed.

5. Bedrock HTTP Datasource

const bedrockDataSource = api.addHttpDataSource(
'bedrockDS',
'https://bedrock-runtime.us-east-1.amazonaws.com',
{
authorizationConfig: {
signingRegion: 'us-east-1',
signingServiceName: 'bedrock',
},
}
);

bedrockDataSource.grantPrincipal.addToPrincipalPolicy(
new PolicyStatement({
resources: [
'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0',
],
actions: ['bedrock:InvokeModel'],
})
);

const getGenAI = new AppsyncFunction(this, 'GetGenAI', {
name: 'getGenAi',
api,
dataSource: bedrockDataSource,
code: Code.fromAsset(path.join(__dirname, '../resolvers/getGenAI.js')),
runtime: FunctionRuntime.JS_1_0_0,
});

This CDK code configures an HTTP data source, named bedrockDS, to interact with the Bedrock service at the specified endpoint https://bedrock-runtime.<region>.amazonaws.com. It includes authorization settings, defining the signing region and service name for requests to the Bedrock service.

Additionally, it grants permission to the data source to invoke the Bedrock model specified by its ARN. This permission is necessary for the data source to interact with the Bedrock service effectively.

Furthermore, an AppSync function named GetGenAI is defined. This function is associated with the API and utilizes the bedrockDS data source to interact with the Bedrock service. The resolver logic is implemented in the getGenAI.js file, which is included as the code asset for the function.

6. Bedrock HTTP Resolver Function

import {util} from "@aws-appsync/utils";

export function request(ctx) {
const assistant = `You will a meteo expert. In the response, create a JSON with 2 field. In the field description you will generate a long description of weather based on response of open-weather API in JSON. I will send the result of API and you will reply with a this description as response. Please generated long text, adding details and context considering the city. Please detect city looking at latitude and longitude provided in the JSON in input. Answer directly with generated content, without adding any additional sentence. In the recommendation field within the JSON you generate, you will generate recommendations regarding clothing to wear, considering temperatures and precipitation. Be specific by giving concrete examples. Below is an example of the response to be generated. '{description: "Today in milan is really a nice day. No rain is expected and the temperature is comfortable."{recommendation: 'it is recommended to wear a long-sleeved T-shirt, with a jacket. Since no precipitation is expected, do not bring an umbrella with you."}'`
const prompt = ctx.prev.result

return {
resourcePath: '/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke',
method: 'POST',
params: {
headers: {
'Content-Type': 'application/json',
},
body: {
anthropic_version: "bedrock-2023-05-31",
max_tokens: 1024,
system: assistant,
temperature: 0.5,
messages: [{role: "user", content: [{type: "text", text: JSON.stringify(prompt)}]}]
},
},
}
}

export function response(ctx) {
const { result } = ctx;
if (result.statusCode !== 200) {
return util.appendError(result.body, `${result.statusCode}`);
}
console.log("Previous Result:")
let result_old = ctx.prev.result;
console.log(result_old);
console.log("Adding generated result: ");
let bodyObject = JSON.parse(result.body);
const result_final = { ...result_old, generated: {...JSON.parse(bodyObject.content[0].text) }};
console.log(result_final);
return result_final;
}

This resolver function facilitates the integration with the Anthropic Claude Generative AI model to generate detailed weather descriptions and clothing recommendations based on the weather forecast retrieved from the open-weather API.

In the request function, it prepares the request to the Anthropic Claude model endpoint by specifying the resource path, HTTP method, and parameters including the request body. The body contains configuration parameters for the model invocation, such as the version of the Anthropic model, maximum token limit, system configuration, temperature, and the prompt message derived from the previous response. The assistant const contains the context for model to generate the output based on the API response. In the prompt variable, JSON API response from previous function is passed.

Upon receiving the response from the Anthropic Claude model, the response function processes the result. It checks the response status code for any errors and appends error details if necessary. The function then combines the generated weather description and clothing recommendations with the previous response, creating a final response object containing the enriched weather forecast information.

This resolver enables the seamless integration of Generative AI capabilities into the existing APIs, without taking care of orchestration of the services.

7. Pipeline Resolver

    new Resolver(this, 'PipelineResolverGetWeather', {
api,
typeName: 'Query',
fieldName: 'getWeather',
runtime: FunctionRuntime.JS_1_0_0,
code: Code.fromAsset(path.join(__dirname, '../resolvers/pipeline.js')),
pipelineConfig: [getWeather, getGenAI],
});

This CDK code sets up a resolver named “PipelineResolverGetWeather” for the “getWeather” query in the GraphQL API. It specifies the functions to be executed in sequence (in this case getWather and getGenAI) and utilizing resolver defined in the pipeline.js file that just set some variable in the stash. These functions likely handle data fetching and processing, enabling complex operations to be performed seamlessly within the AppSync and without the need to implement custom logic for orchestration.

4. Conclusion

By seamlessly integrating Generative AI capabilities into your existing APIs, you open up a world of possibilities for enriching user experiences with dynamic, contextually relevant content. The process, as demonstrated in the code examples, is remarkably straightforward. With simply changing the prompt in the Bedrock Resolver function and setting up new HTTP data sources for the API, the power of generative artificial intelligence can be harnessed to create custom content in other use cases.

One of the most compelling aspects of this integration is the speed at which it can be accomplished. AppSync handles the orchestration between your custom APIs and Bedrock APIs, streamlining the development process and reducing the time and effort required to implement these enhancements. What once might have seemed a daunting task, requiring custom Lambda code and complex orchestration, is now achievable in a very short time, including testing to ensure that the prompt generates the desired content.

This rapid time to market is invaluable in today’s fast-paced digital landscape, where businesses must continuously innovate to stay ahead of the curve. By leveraging low-code/no-code solutions like Bedrock and AWS AppSync, developers can quickly iterate on their APIs, incorporating cutting-edge technologies like Generative AI without the need for extensive rearchitecting or custom development. As a result, businesses can deliver richer, more engaging experiences to their users, seizing opportunities and maintaining a competitive edge in the ever-evolving market.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response