Skip to content

Commit 22fb89a

Browse files
committed
appsync websockets with lambda and bedrock
1 parent c36f3cb commit 22fb89a

File tree

8 files changed

+455
-0
lines changed

8 files changed

+455
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.aws-sam/
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# AI Chat Application using AWS AppSync (WebSockets), AWS Lambda, and Amazon Bedrock.
2+
3+
This AI chat application works through a simple request-response flow. A client sends a GraphQL mutation to the AWS AppSync API endpoint. AWS AppSync then routes the request to the AWS Lambda function via the configured data source and resolver. The AWS Lambda function receives the user's input message, then constructs a properly formatted request for Amazon Bedrock's Claude model (including the Anthropic API version, token limit, and user message). This is then sent to Amazon Bedrock using the AWS SDK, waits for the AI response, and returns the generated text back to AWS AppSync. AWS AppSync then delivers this response to the client, while the subscription feature enables real-time notifications to connected clients when new responses are available, creating an interactive chat experience where users can send messages and receive AI-generated replies in real-time.
4+
5+
6+
Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/appsync-ws-lambda-bedrock-sam)
7+
8+
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
9+
10+
## Prerequisites
11+
12+
* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
13+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
14+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
15+
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
16+
* [NOTE! Manage Access to Amazon Bedrock Foundation Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html)
17+
18+
19+
## Deployment Instructions
20+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
21+
```
22+
git clone https://github.com/aws-samples/serverless-patterns
23+
```
24+
2. Change directory to the pattern directory:
25+
```
26+
cd appsync-ws-lambda-bedrock-sam
27+
```
28+
3. Install dependencies
29+
```
30+
cd src && npm install && cd ..
31+
```
32+
4. From the command line, use AWS SAM build to prepare an application for subsequent steps in the developer workflow, such as local testing or deploying to the AWS Cloud:
33+
```
34+
sam build
35+
```
36+
5. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
37+
```
38+
sam deploy --guided
39+
```
40+
6. During the prompts:
41+
* Enter a stack name
42+
* Enter the desired AWS Region
43+
* Enter the desired ModelId
44+
* Enter the desired BedrockRegion
45+
* Allow SAM to create roles with the required permissions if needed.
46+
47+
7. Note the outputs **GraphQLAPIURL** and **GraphQLAPIKey** from the SAM deployment process.
48+
These contain the resource names and/or ARNs which are used for testing.
49+
50+
51+
## Architecture
52+
![apigw-1](images/appsync-ws-lambda-bedrock.png)
53+
54+
55+
## How it Works
56+
Client Request Flow:
57+
1. Client sends a GraphQL mutation invokeModel(input: "user message") to the AppSync API endpoint using the provided API key.
58+
2. AppSync receives the mutation and routes it through the InvokeModelResolver to the LambdaDataSource.
59+
3. The resolver triggers the Lambda function, passing the user's input as event.arguments.input.
60+
61+
Lambda Processing:
62+
63+
4. Lambda function receives the event and extracts the user message from event.arguments.input.
64+
5. Creates a Bedrock request payload with Claude's required format: anthropic_version, max_tokens (1000), and the user message in a messages array.
65+
6. Uses BedrockRuntimeClient to send an InvokeModelCommand to the specified Claude model.
66+
7. Waits synchronously for Bedrock's response, then parses the JSON response and extracts the AI-generated text from result.content[0].text.
67+
68+
Response Delivery:
69+
70+
8. Lambda returns the AI text response to AppSync.
71+
9. AppSync delivers the response back to the client through the GraphQL mutation response.
72+
10. Clients subscribed to onModelResponse receive real-time notifications of new AI responses via WebSocket connections.
73+
74+
The architecture uses AppSync's built-in WebSocket capabilities for real-time subscriptions, eliminating the need for manual connection management.
75+
76+
## Testing
77+
78+
### Interactive Web Interface
79+
```
80+
To use the test interface:
81+
1. Deploy the application using SAM
82+
2. Copy the GraphQLAPIURL Value and GraphQLAPIKey Value from the deployment outputs
83+
3. Open 'test.html'
84+
4. Update the 'API_URL' variable with your GraphQLAPIURL
85+
5. Update the 'API_KEY' variable with your GraphQLAPIKey
86+
6. Save 'test.html'
87+
7. Open the HTML file in a browser
88+
8. Click "Connect" to establish a WebSocket connection
89+
9. Type your message and click "Send"
90+
```
91+
92+
### AWS AppSync Console
93+
* Go to AWS AppSync console → Your API → Queries
94+
* Use the built-in GraphQL explorer
95+
* Run this mutation:
96+
```bash
97+
mutation {
98+
invokeModel(input: "What is AWS Lambda?")
99+
}
100+
```
101+
102+
### Terminal using curl
103+
```bash
104+
curl -X POST \
105+
-H "Content-Type: application/json" \
106+
-H "x-api-key: YOUR_API_KEY" \
107+
-d '{"query": "mutation { invokeModel(input: \"What is AWS Lambda?\") }"}' \
108+
YOUR_GRAPHQL_API_URL
109+
```
110+
111+
## Cleanup
112+
1. Delete the stack
113+
```bash
114+
sam delete
115+
```
116+
2. Confirm the stack has been deleted
117+
```bash
118+
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus"
119+
```
120+
----
121+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
122+
123+
SPDX-License-Identifier: MIT-0
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"title": "AI Chat with AWS AppSync (WebSockets), AWS Lambda, and Amazon Bedrock",
3+
4+
"description": "WebSocket-Enabled AI Chat Using AWS Services.",
5+
6+
"language": "",
7+
"level": "200",
8+
"framework": "SAM",
9+
"introBox": {
10+
"headline": "How it works",
11+
"text": [
12+
"This AI chat application works through a simple request-response flow.",
13+
"A client sends a GraphQL mutation to the AWS AppSync API endpoint.",
14+
"AWS AppSync then routes the request to a AWS Lambda function via the configured data source and resolver.",
15+
"The AWS Lambda function receives the user's input message, constructs a properly formatted request for Amazon Bedrock's Claude model (including the Anthropic API version, token limit, and user message).",
16+
"Then sends it to Amazon Bedrock using the AWS SDK, waits for the AI response, and returns the generated text back to AWS AppSync. ",
17+
"AWS AppSync then delivers this response to the client, while the subscription feature enables real-time notifications to connected clients when new responses are available, creating an interactive chat experience where users can send messages and receive AI-generated replies in real-time."
18+
]
19+
},
20+
"gitHub": {
21+
"template": {
22+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/appsync-ws-lambda-bedrock-sam",
23+
"templateURL": "serverless-patterns/appsync-ws-lambda-bedrock-sam",
24+
"projectFolder": "appsync-ws-lambda-bedrock-sam",
25+
"templateFile": "template.yml"
26+
}
27+
},
28+
"resources": {
29+
"bullets": [
30+
{
31+
"text": "AWS AppSync",
32+
"link": "https://aws.amazon.com/appsync/"
33+
},
34+
{
35+
"text": "AWS AppSync WebSocket",
36+
"link": "https://docs.aws.amazon.com/appsync/latest/devguide/aws-appsync-real-time-data.html"
37+
},
38+
{
39+
"text": "AWS Lambda",
40+
"link": "https://aws.amazon.com/lambda/"
41+
},
42+
{
43+
"text": "Amazon Bedrock",
44+
"link": "https://aws.amazon.com/bedrock/"
45+
}
46+
]
47+
},
48+
"deploy": {
49+
"text": [
50+
"sam deploy"
51+
]
52+
},
53+
"testing": {
54+
"text": [
55+
"See the GitHub repo for detailed testing instructions."
56+
]
57+
},
58+
"cleanup": {
59+
"text": [
60+
"<code>sam delete</code>"
61+
]
62+
},
63+
"authors": [
64+
{
65+
"name": "Mike Hume",
66+
"image": "https://media.licdn.com/dms/image/D4E03AQEiUfmBiUOw_A/profile-displayphoto-shrink_200_200/0/1718324029612?e=1727308800&v=beta&t=ybhm76l-CP5xcUsHbdq2IaJOlfyycvQ6gNwuCSd3Z0w",
67+
"bio": "AWS Senior Solutions Architect & UKPS Serverless Lead.",
68+
"linkedin": "michael-hume-4663bb64",
69+
"twitter": ""
70+
}
71+
]
72+
}
110 KB
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');
2+
3+
const client = new BedrockRuntimeClient({ region: process.env.BEDROCK_REGION });
4+
5+
exports.handler = async (event) => {
6+
const input = {
7+
modelId: process.env.MODEL_ID,
8+
contentType: 'application/json',
9+
accept: 'application/json',
10+
body: JSON.stringify({
11+
anthropic_version: 'bedrock-2023-05-31',
12+
max_tokens: 1000,
13+
messages: [{
14+
role: 'user',
15+
content: event.arguments.input
16+
}]
17+
})
18+
};
19+
20+
const command = new InvokeModelCommand(input);
21+
const response = await client.send(command);
22+
const result = JSON.parse(new TextDecoder().decode(response.body));
23+
24+
return result.content[0].text;
25+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "lambda-function",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"@aws-sdk/client-bedrock-runtime": "^3.0.0"
6+
}
7+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: SAM template for LLM-powered API with AppSync and Lambda
4+
5+
Parameters:
6+
ModelId:
7+
Type: String
8+
Default: anthropic.claude-3-sonnet-20240229-v1:0
9+
Description: Bedrock model ID
10+
BedrockRegion:
11+
Type: String
12+
Default: eu-west-2
13+
Description: AWS region
14+
15+
Resources:
16+
# AppSync API
17+
GraphQLAPI:
18+
Type: AWS::AppSync::GraphQLApi
19+
Properties:
20+
Name: LLMPoweredAPI
21+
AuthenticationType: API_KEY
22+
23+
# AppSync API Key
24+
GraphQLAPIKey:
25+
Type: AWS::AppSync::ApiKey
26+
Properties:
27+
ApiId: !GetAtt GraphQLAPI.ApiId
28+
29+
# AppSync Schema
30+
GraphQLSchema:
31+
Type: AWS::AppSync::GraphQLSchema
32+
Properties:
33+
ApiId: !GetAtt GraphQLAPI.ApiId
34+
Definition: |
35+
type Query {
36+
hello: String
37+
}
38+
39+
type Mutation {
40+
invokeModel(input: String!): String!
41+
}
42+
43+
type Subscription {
44+
onModelResponse: String
45+
@aws_subscribe(mutations: ["invokeModel"])
46+
}
47+
48+
schema {
49+
query: Query
50+
mutation: Mutation
51+
subscription: Subscription
52+
}
53+
54+
# Lambda Function
55+
LambdaFunction:
56+
Type: AWS::Serverless::Function
57+
Properties:
58+
CodeUri: ./lambda_function/
59+
Handler: app.handler
60+
Runtime: nodejs22.x
61+
Timeout: 30
62+
Environment:
63+
Variables:
64+
MODEL_ID: !Ref ModelId
65+
BEDROCK_REGION: !Ref BedrockRegion
66+
Policies:
67+
- AWSLambdaBasicExecutionRole
68+
- Statement:
69+
- Effect: Allow
70+
Action:
71+
- bedrock:InvokeModel
72+
Resource: '*'
73+
74+
# AppSync DataSource (Lambda)
75+
LambdaDataSource:
76+
Type: AWS::AppSync::DataSource
77+
Properties:
78+
ApiId: !GetAtt GraphQLAPI.ApiId
79+
Name: LambdaDataSource
80+
Type: AWS_LAMBDA
81+
ServiceRoleArn: !GetAtt AppSyncServiceRole.Arn
82+
LambdaConfig:
83+
LambdaFunctionArn: !GetAtt LambdaFunction.Arn
84+
85+
# AppSync Resolver
86+
InvokeModelResolver:
87+
Type: AWS::AppSync::Resolver
88+
Properties:
89+
ApiId: !GetAtt GraphQLAPI.ApiId
90+
TypeName: Mutation
91+
FieldName: invokeModel
92+
DataSourceName: !GetAtt LambdaDataSource.Name
93+
94+
# IAM Role for AppSync
95+
AppSyncServiceRole:
96+
Type: AWS::IAM::Role
97+
Properties:
98+
AssumeRolePolicyDocument:
99+
Version: '2012-10-17'
100+
Statement:
101+
- Effect: Allow
102+
Principal:
103+
Service: appsync.amazonaws.com
104+
Action: sts:AssumeRole
105+
Policies:
106+
- PolicyName: InvokeLambdaPolicy
107+
PolicyDocument:
108+
Version: '2012-10-17'
109+
Statement:
110+
- Effect: Allow
111+
Action:
112+
- lambda:InvokeFunction
113+
Resource: !GetAtt LambdaFunction.Arn
114+
115+
Outputs:
116+
GraphQLAPIURL:
117+
Description: The URL of the GraphQL API
118+
Value: !GetAtt GraphQLAPI.GraphQLUrl
119+
GraphQLAPIKey:
120+
Description: The API Key for the GraphQL API
121+
Value: !GetAtt GraphQLAPIKey.ApiKey

0 commit comments

Comments
 (0)