Introduction#

  • Create signature v4
  • Send request to bedrock endpoint
  • Parse response chunk by chunk

Here is the full script

bedrock-wo-sdk.js
// https://blog.alu.ai/signing-aws-api-requests-in-node-js-2023-version/
// const { SignatureV4 } = require("@smithy/signature-v4");
import { SignatureV4 } from '@smithy/signature-v4'
import { Sha256 } from '@aws-crypto/sha256-js'
import { HttpRequest } from '@aws-sdk/protocol-http'
const decoder = new TextDecoder()
// const ENDPOINT = "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-v2/invoke";
const ENDPOINT =
'https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-v2/invoke-with-response-stream'
async function signRequest() {
// create a http request
// path: /BUCKET-NAME/web-css/osaka-street.jpeg
// host: s3.us-east-1.amazonaws.com
const request = new HttpRequest({
path: new URL(ENDPOINT).pathname,
hostname: new URL(ENDPOINT).hostname,
protocol: 'https:',
method: 'POST',
headers: {
host: new URL(ENDPOINT).hostname,
Accept: 'application/vnd.amazon.eventstream',
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: '\n\nHuman: How to cook chicken soup? \n\nAssistant: ',
max_tokens_to_sample: 128
})
})
// create a signer using singnaturev4
const signer = new SignatureV4({
service: 'bedrock',
region: 'us-east-1',
credentials: {
accessKeyId: '',
secretAccessKey: '',
sessionToken: ''
},
sha256: Sha256
})
const signedRequest = await signer.sign(request, {
// signingRegion: "us-east-1",
// signingService: "bedrock",
// signingDate: new Date(),
// date: new Date(),
// expires: new Date(Date.now() + 15 * 60 * 1000), // 15 minutes
// // list of headers used to compute signature
// signedHeaders: "host;x-amz-date",
})
console.log(signedRequest)
console.log(`https://${signedRequest.hostname}${signedRequest.path}`)
const response = await fetch(
`https://${signedRequest.hostname}${signedRequest.path}`,
signedRequest
)
// const json = await response.json();
// console.log(json);
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let answer = ''
const regex = /\{(.*?)\}/g
let match
while (true) {
const { done, value } = await reader.read()
if (done) {
console.log('Done with stream')
break
}
const x = decoder.decode(value)
try {
while ((match = regex.exec(x)) !== null) {
const y = '{' + match[1] + '}'
const z = atob(JSON.parse(y).bytes)
console.log(JSON.parse(z).completion)
answer += JSON.parse(z).completion
}
} catch (error) {
console.log(error)
answer += 'ERROR'
}
}
console.log(answer)
// console.log(response.data);
// fs.writeFileSync("./hehe.jpg", response.data);
// console.log(signedRequest.headers);
// console.log(response.data);
// pipe stream to file
// response.data.pipe(fs.createWriteStream("./hehe.jpg"));
// response.data event emitter data chunk
// response.data.on("data", (chunk) => {
// console.log(chunk.length);
// });
}
// Example usage
const main = async () => {
await signRequest()
}
main()

And here is package.json

package.json
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"@aws-sdk/credential-provider-ini": "^3.529.1",
"@aws-sdk/protocol-http": "^3.374.0",
"@smithy/signature-v4": "^2.1.4",
"axios": "^1.6.7"
}
}

Create Request#

Let create a request

const ENDPOINT =
'https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-v2/invoke-with-response-stream'
const request = new HttpRequest({
// path: /cdk-entest-videos/web-css/osaka-street.jpeg
path: new URL(ENDPOINT).pathname,
// host: s3.us-east-1.amazonaws.com
hostname: new URL(ENDPOINT).hostname,
protocol: 'https:',
method: 'POST',
headers: {
host: new URL(ENDPOINT).hostname,
Accept: 'application/vnd.amazon.eventstream',
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: '\n\nHuman: How to cook chicken soup? \n\nAssistant: ',
max_tokens_to_sample: 128
})
})

Sign Request#

Let create a signature v4 and sign the request

// create a signer using singnaturev4
const signer = new SignatureV4({
service: 'bedrock',
region: 'us-east-1',
credentials: {
accessKeyId: '',
secretAccessKey: '',
sessionToken: ''
},
sha256: Sha256
})
const signedRequest = await signer.sign(request, {
// signingRegion: "us-east-1",
// signingService: "bedrock",
// signingDate: new Date(),
// date: new Date(),
// expires: new Date(Date.now() + 15 * 60 * 1000), // 15 minutes
// // list of headers used to compute signature
// signedHeaders: "host;x-amz-date",
})
console.log(signedRequest)

Parse Response#

In case of streaming response, let parse it chunk by chunk, use regex to detect the completion, the decode the based64.

  • Parse chunk by chunk
  • Regex to capture answer
  • Base64 decode to string
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let answer = ''
const regex = /\{(.*?)\}/g
let match
while (true) {
const { done, value } = await reader.read()
if (done) {
console.log('Done with stream')
break
}
const x = decoder.decode(value)
try {
while ((match = regex.exec(x)) !== null) {
const y = '{' + match[1] + '}'
const z = atob(JSON.parse(y).bytes)
console.log(JSON.parse(z).completion)
answer += JSON.parse(z).completion
}
} catch (error) {
console.log(error)
answer += 'ERROR'
}
}
console.log(answer)

In case of normal response

const json = await response.json()
console.log(json)

Reference#

  • bedrock runtime api docs