-
Notifications
You must be signed in to change notification settings - Fork 188
feat: add bedrock as a new data source #730
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { synthesize } from './helpers/synthesize'; | ||
| import { | ||
| expectDataSourceOfType, | ||
| findResourcesByType, | ||
| } from './helpers/assertions'; | ||
|
|
||
| describe('examples/datasource-bedrock', () => { | ||
| let result: ReturnType<typeof synthesize>; | ||
|
|
||
| beforeAll(() => { | ||
| result = synthesize('examples/datasource-bedrock'); | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| result.cleanup(); | ||
| }); | ||
|
|
||
| it('creates an AMAZON_BEDROCK_RUNTIME data source', () => { | ||
| const ds = expectDataSourceOfType( | ||
| result.template, | ||
| 'AMAZON_BEDROCK_RUNTIME', | ||
| ); | ||
| expect(ds.resource.Properties?.Name).toBe('bedrock'); | ||
| }); | ||
|
|
||
| it('generates a service role with bedrock:InvokeModel permissions', () => { | ||
| const roles = findResourcesByType(result.template, 'AWS::IAM::Role'); | ||
| const bedrockRole = roles.find(({ resource }) => { | ||
| const policies = resource.Properties?.Policies as Array<{ | ||
| PolicyDocument?: { Statement?: Array<{ Action?: string[] }> }; | ||
| }>; | ||
| return policies?.some((policy) => | ||
| policy.PolicyDocument?.Statement?.some((statement) => | ||
| statement.Action?.includes('bedrock:InvokeModel'), | ||
| ), | ||
| ); | ||
| }); | ||
|
|
||
| expect(bedrockRole).toBeDefined(); | ||
|
Comment on lines
+26
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win Tighten this IAM assertion to the Bedrock datasource role. This passes if any synthesized role has 💡 Suggested fix it('generates a service role with bedrock:InvokeModel permissions', () => {
- const roles = findResourcesByType(result.template, 'AWS::IAM::Role');
- const bedrockRole = roles.find(({ resource }) => {
- const policies = resource.Properties?.Policies as Array<{
- PolicyDocument?: { Statement?: Array<{ Action?: string[] }> };
- }>;
- return policies?.some((policy) =>
- policy.PolicyDocument?.Statement?.some((statement) =>
- statement.Action?.includes('bedrock:InvokeModel'),
- ),
- );
- });
+ const ds = expectDataSourceOfType(result.template, 'AMAZON_BEDROCK_RUNTIME');
+ const roleLogicalId = (ds.resource.Properties?.ServiceRoleArn as {
+ 'Fn::GetAtt'?: [string, string];
+ })?.['Fn::GetAtt']?.[0];
+
+ expect(roleLogicalId).toBeDefined();
+
+ const bedrockRole = roleLogicalId
+ ? result.template.Resources?.[roleLogicalId]
+ : undefined;
+
+ const policies = bedrockRole?.Properties?.Policies as Array<{
+ PolicyDocument?: { Statement?: Array<{ Action?: string | string[] }> };
+ }>;
+
+ const hasBedrockPermissions = policies?.some((policy) =>
+ policy.PolicyDocument?.Statement?.some((statement) => {
+ const actions = Array.isArray(statement.Action)
+ ? statement.Action
+ : statement.Action
+ ? [statement.Action]
+ : [];
+ return (
+ actions.includes('bedrock:InvokeModel') &&
+ actions.includes('bedrock:Converse')
+ );
+ }),
+ );
- expect(bedrockRole).toBeDefined();
+ expect(hasBedrockPermissions).toBe(true);
});🤖 Prompt for AI Agents |
||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { invokeModel } from '@aws-appsync/utils/ai'; | ||
|
|
||
| export function request(ctx) { | ||
| return invokeModel({ | ||
| modelId: 'eu.amazon.nova-micro-v1:0', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba examples/datasource-bedrock/resolvers/summarize.js | sed -n '1,80p'
nl -ba examples/datasource-bedrock/serverless.yml | sed -n '1,260p'
rg -n "datasourceBedrockConfig|models:|modelId" examples/datasource-bedrock -C2Repository: sid88in/serverless-appsync-plugin Length of output: 120 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== summarize.js (first 120 lines) ==="
cat -n examples/datasource-bedrock/resolvers/summarize.js | sed -n '1,120p'
echo
echo "=== serverless.yml (first 320 lines) ==="
cat -n examples/datasource-bedrock/serverless.yml | sed -n '1,320p'
echo
echo "=== Search bedrock datasource config for allowlist + model id ==="
rg -n "datasourceBedrockConfig|models:|modelId" examples/datasource-bedrock -C2Repository: sid88in/serverless-appsync-plugin Length of output: 2207 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate plugin code that handles AMAZON_BEDROCK_RUNTIME and "models" allowlist
ls -la
rg -n "AMAZON_BEDROCK_RUNTIME|bedrock.*models|config\.models|invokeModel\(" -S . --glob='*.js' --glob='*.ts' --glob='*.mjs' --glob='*.cjs'Repository: sid88in/serverless-appsync-plugin Length of output: 3821 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate plugin code that handles AMAZON_BEDROCK_RUNTIME and "models" allowlist
ls -la
rg -n "AMAZON_BEDROCK_RUNTIME|bedrock.*models|config\.models|invokeModel\(" -S . --glob='*.js' --glob='*.ts' --glob='*.mjs' --glob='*.cjs'Repository: sid88in/serverless-appsync-plugin Length of output: 3821 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the AMAZON_BEDROCK_RUNTIME handling and any model allowlist logic
rg -n "case 'AMAZON_BEDROCK_RUNTIME'|config\.models|models" src/resources/DataSource.ts -n
sed -n '380,520p' src/resources/DataSource.tsRepository: sid88in/serverless-appsync-plugin Length of output: 4207 Align
🤖 Prompt for AI Agents |
||
| body: { | ||
| messages: [ | ||
| { | ||
| role: 'user', | ||
| content: [{ text: ctx.args.text }], | ||
| }, | ||
| ], | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| export function response(ctx) { | ||
| return ctx.result.output.message.content[0].text; | ||
| } | ||
|
Comment on lines
+1
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the resolver file and any nearby examples for invokeModel usage (Titan vs Nova).
repo_root="."
file="examples/datasource-bedrock/resolvers/summarize.js"
echo "== File: $file =="
if [ -f "$file" ]; then
nl -ba "$file" | sed -n '1,200p'
else
echo "Missing file: $file"
fi
echo
echo "== Find other invokeModel usages under examples/datasource-bedrock/resolvers =="
# List resolver files
fd -t f -e js examples/datasource-bedrock/resolvers | sort || true
echo
# Search for invokeModel calls
rg -n "invokeModel\\(" examples/datasource-bedrock/resolvers || true
echo
echo "== Search for ctx.result usage in these resolvers =="
rg -n "ctx\\.result" examples/datasource-bedrock/resolvers || trueRepository: sid88in/serverless-appsync-plugin Length of output: 1988 🌐 Web query:
💡 Result: When using the Amazon Bedrock InvokeModel API with Amazon Nova models, the response body follows a specific schema where the generated content is located under the output key, not a results key [1]. The response structure returned by the model in the body of the InvokeModel response is as follows [1]: { "output": { "message": { "role": "assistant", "content": [ { "text": "string" }, { "toolUse": {... } }, { "reasoningContent": {... } } ] } }, "stopReason": "string", "usage": { "inputTokens": int, "outputTokens": int, "totalTokens": int } } Key points regarding the response structure: 1. output: This is the primary container for the assistant's response. It contains a message object, which holds the role (assistant) and a content array [1]. 2. content: This array contains the actual response blocks, such as text, toolUse, or reasoningContent [1]. 3. No results key: The Nova models' Invoke API schema does not utilize a top-level results key. If you are seeing code snippets referencing results, they likely pertain to different model providers or older legacy Bedrock response formats [2]. 4. Accessing the output: To access the generated text in Python using Boto3, you should read the response body and traverse the JSON structure [3][4]: response = client.invoke_model(modelId="amazon.nova-micro-v1:0", body=json.dumps(request_body)) response_body = json.loads(response["body"].read) text_output = response_body["output"]["message"]["content"]["text"] This schema is designed to be consistent with the Converse API, providing a unified experience for developers regardless of which Bedrock API they choose [5][6]. Citations:
🌐 Web query:
💡 Result: In AWS AppSync JavaScript resolvers using Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the resolver file and any nearby examples for invokeModel usage (Titan vs Nova).
repo_root="."
file="examples/datasource-bedrock/resolvers/summarize.js"
echo "== File: $file =="
if [ -f "$file" ]; then
nl -ba "$file" | sed -n '1,200p'
else
echo "Missing file: $file"
fi
echo
echo "== Find other invokeModel usages under examples/datasource-bedrock/resolvers =="
# List resolver files
fd -t f -e js examples/datasource-bedrock/resolvers | sort || true
echo
# Search for invokeModel calls
rg -n "invokeModel\\(" examples/datasource-bedrock/resolvers || true
echo
echo "== Search for ctx.result usage in these resolvers =="
rg -n "ctx\\.result" examples/datasource-bedrock/resolvers || trueRepository: sid88in/serverless-appsync-plugin Length of output: 1988 🌐 Web query:
💡 Result: The response shape you should use depends entirely on whether you are using the Converse API or the legacy InvokeModel API. For Amazon Nova models on Amazon Bedrock, AWS recommends using the Converse API [1][2]. The InvokeModel API is supported but follows a different, model-specific response structure [1]. 1. Converse API Response Shape When using the Converse API, the response format is standardized across models [2]. The text content is accessed via a nested structure: response['output']['message']['content']['text'] [3][4][2] 2. InvokeModel API Response Shape When using the InvokeModel API, the response body structure depends on the specific model's native schema [1][5]. - If you were using older models like Amazon Titan, you might be accustomed to the results['outputText'] path [6][7]. - However, for Amazon Nova models invoked via InvokeModel, the structure aligns with the Converse schema, typically returning an output object containing the message [8][1]: response_body['output']['message']['content']['text'] Summary If you are writing code for Amazon Nova, avoid relying on results['outputText'], as this is specific to older models like Amazon Titan [6][7]. You should use the Converse API structure [1][2], which is consistent: response['output']['message']['content']['text'] [3][4][2] Citations:
🌐 Web query:
💡 Result: In AWS AppSync JavaScript resolvers using Citations:
🌐 Web query:
💡 Result: When using the Citations:
🌐 Web query:
💡 Result: When invoking Amazon Nova models via AWS AppSync using the invokeModel function, you must provide a request body that adheres to the specific schema expected by the Amazon Nova Invoke API [1]. Unlike the simpler text-based prompts used for legacy models, the Amazon Nova Invoke API requires a structured JSON payload that includes the message history and configuration [1]. To perform this in an AppSync JavaScript resolver, your invokeModel request object should be structured as follows: import { invokeModel } from ' Citations:
🌐 Web query:
💡 Result: To invoke Amazon Nova models using the Amazon Bedrock Citations:
🌐 Web query:
💡 Result: In AWS AppSync’s resolver reference (bedrock-js utilities), the pattern for reading the text returned from a Bedrock InvokeModel call is: return ctx.result.results.outputText from your response function. For example, the docs show response(ctx) { return ctx.result.results.outputText }. [1] Context / why this path: - The AppSync Bedrock invokeModel helper stores the Bedrock invocation’s response in ctx.result (and the docs’ response example reads from ctx.result.results.outputText). [1] - The underlying Bedrock runtime InvokeModel API returns an inference response body, but the AppSync resolver reference is what maps that into ctx.result.* fields for GraphQL resolvers. [2][1] Concrete example from the AppSync resolver reference: export function response(ctx) { return ctx.result.results.outputText } [1] If you instead want the raw mapped result object, the same page also shows a response(ctx) { return ctx.result } example. [1] Citations:
Fix Amazon Nova Bedrock
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| type Query { | ||
| summarize(text: String!): AWSJSON | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| service: appsync-datasource-bedrock | ||
|
|
||
| provider: | ||
| name: aws | ||
| runtime: nodejs22.x | ||
|
|
||
| plugins: | ||
| - serverless-appsync-plugin | ||
|
|
||
| appSync: | ||
| name: datasource-bedrock | ||
| authentication: | ||
| type: API_KEY | ||
| apiKeys: | ||
| - name: default | ||
|
|
||
| resolvers: | ||
| Query.summarize: | ||
| kind: UNIT | ||
| dataSource: bedrock | ||
| code: ./resolvers/summarize.js | ||
|
|
||
| dataSources: | ||
| bedrock: | ||
| type: AMAZON_BEDROCK_RUNTIME | ||
| description: Amazon Bedrock runtime for synchronous model invocations | ||
| config: | ||
| models: | ||
| - amazon.nova-micro-v1:0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call out the Bedrock model-access prerequisite.
A copied quick start can still fail until the account has access to the selected foundation model in that region. Adding that note here would prevent a pretty confusing first-run failure path for users. (docs.aws.amazon.com)
🤖 Prompt for AI Agents