diff --git a/.doc_gen/metadata/bedrock-runtime_metadata.yaml b/.doc_gen/metadata/bedrock-runtime_metadata.yaml index 2444779ca21..9603630e1c5 100644 --- a/.doc_gen/metadata/bedrock-runtime_metadata.yaml +++ b/.doc_gen/metadata/bedrock-runtime_metadata.yaml @@ -100,6 +100,10 @@ bedrock-runtime_Converse_AmazonNovaText: - description: Send a text message to Amazon Nova, using Bedrock's Converse API. snippet_tags: - javascript.v3.bedrock-runtime.Converse_AmazonNovaText + - description: Send a conversation of messages to Amazon Nova using Bedrock's Converse API with a tool configuration. + genai: some + snippet_tags: + - Bedrock.ConverseTool.javascriptv3.SendConverseRequest Kotlin: versions: - sdk_version: 1 @@ -214,6 +218,15 @@ bedrock-runtime_Scenario_ToolUse: genai: some snippet_tags: - Bedrock.ConverseTool.dotnetv3.SendConverseRequest + JavaScript: + versions: + - sdk_version: 3 + github: javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario + excerpts: + - description: "The primary execution of the scenario flow. This scenario orchestrates the conversation between the user, the &BR; Converse API, and a weather tool." + genai: some + snippet_tags: + - Bedrock.ConverseTool.javascriptv3.Scenario Python: versions: - sdk_version: 3 @@ -1517,6 +1530,15 @@ bedrock-runtime_Scenario_ToolUseDemo_AmazonNova: genai: some snippet_tags: - Bedrock.ConverseTool.dotnetv3.SendConverseRequest + JavaScript: + versions: + - sdk_version: 3 + github: javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario + excerpts: + - description: "The primary execution of the scenario flow. This scenario orchestrates the conversation between the user, the &BR; Converse API, and a weather tool." + genai: some + snippet_tags: + - Bedrock.ConverseTool.javascriptv3.Scenario services: bedrock-runtime: {Converse} diff --git a/.doc_gen/validation.yaml b/.doc_gen/validation.yaml index 7b750b71102..3e77aa5e973 100644 --- a/.doc_gen/validation.yaml +++ b/.doc_gen/validation.yaml @@ -1,5 +1,6 @@ allow_list: # Git commits + - "cad7e7ba2c59a54837212e789a5304b9108fac47" - "cd5e746ec203c8c3c61647e0886a8df8c1e78e41" - "725feb26d6f73bc1d83dbbe075ae8ea991efb245" - "e9772d140489982e0e3704fea5ee93d536f1e275" diff --git a/.tools/base_requirements.txt b/.tools/base_requirements.txt index 6008bab011d..b97664fb0be 100644 --- a/.tools/base_requirements.txt +++ b/.tools/base_requirements.txt @@ -5,4 +5,4 @@ pathspec==0.11.2 PyYAML==6.0.1 requests==2.32.0 types-PyYAML==6.0.12.12 -yamale==4.0.4 +yamale==4.0.4 \ No newline at end of file diff --git a/.tools/readmes/requirements.txt b/.tools/readmes/requirements.txt index b2ab81a55fc..da0f63d95ed 100644 --- a/.tools/readmes/requirements.txt +++ b/.tools/readmes/requirements.txt @@ -1,4 +1,4 @@ jinja2>=3.0.3 pyyaml>=5.3.1 typer==0.15.1 -aws-doc-sdk-examples-tools @ git+https://github.com/awsdocs/aws-doc-sdk-examples-tools +aws-doc-sdk-examples-tools @ git+https://github.com/awsdocs/aws-doc-sdk-examples-tools \ No newline at end of file diff --git a/.tools/readmes/requirements_freeze.txt b/.tools/readmes/requirements_freeze.txt index f2b75cee0b9..f65fd0c9435 100644 --- a/.tools/readmes/requirements_freeze.txt +++ b/.tools/readmes/requirements_freeze.txt @@ -29,4 +29,4 @@ typer==0.15.1 types-PyYAML==6.0.12.12 typing_extensions==4.12.2 urllib3==2.3.0 -yamale==4.0.4 +yamale==4.0.4 \ No newline at end of file diff --git a/javascriptv3/example_code/bedrock-runtime/README.md b/javascriptv3/example_code/bedrock-runtime/README.md index a9670c15547..f05f968c924 100644 --- a/javascriptv3/example_code/bedrock-runtime/README.md +++ b/javascriptv3/example_code/bedrock-runtime/README.md @@ -44,6 +44,7 @@ Code examples that show you how to accomplish a specific task by calling multipl functions within the same service. - [Invoke multiple foundation models on Amazon Bedrock](scenarios/cli_text_playground.js) +- [Tool use with the Converse API](scenarios/converse_tool_scenario/converse-tool-scenario.js) ### AI21 Labs Jurassic-2 @@ -54,6 +55,7 @@ functions within the same service. - [Converse](models/amazonNovaText/converse.js#L4) - [ConverseStream](models/amazonNovaText/converseStream.js#L4) +- [Scenario: Tool use with the Converse API](scenarios/converse_tool_scenario/converse-tool-scenario.js#L4) ### Amazon Nova Canvas @@ -148,6 +150,18 @@ This example shows you how to prepare and send a prompt to a variety of large-la +#### Tool use with the Converse API + +This example shows you how to build a typical interaction between an application, a generative AI model, and connected tools or APIs to mediate interactions between the AI and the outside world. It uses the example of connecting an external weather API to the AI model so it can provide real-time weather information based on user input. + + + + + + + + + ### Tests ⚠ Running tests might result in charges to your AWS account. diff --git a/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse-with-tool.js b/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse-with-tool.js new file mode 100644 index 00000000000..3f5d787785e --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse-with-tool.js @@ -0,0 +1,193 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Bedrock.ConverseTool.javascriptv3.SendConverseRequest] + +// This example demonstrates how to send a conversation of messages to Amazon Nova using Bedrock's Converse API with a tool configuration. +// It shows how to: +// - 1. Set up the Amazon Bedrock runtime client +// - 2. Define the parameters required enable Amazon Bedrock to use a tool when formulating its response (model ID, user input, system prompt, and the tool spec) +// - 3. Send the request to Amazon Bedrock, and returns the response. +// - 4. Add the tool response to the conversation, and send it back to Amazon Bedrock. +// - 5. Publish the response. + +import { + BedrockRuntimeClient, + ConverseCommand, +} from "@aws-sdk/client-bedrock-runtime"; + +// Step 1: Create the Amazon Bedrock runtime client + +// Credentials will be automatically loaded from the environment +const bedRockRuntimeClient = new BedrockRuntimeClient({ + region: "us-east-1", +}); + +// Step 2. Define the parameters required enable Amazon Bedrock to use a tool when formulating its response. + +// The Bedrock Model ID. +const modelId = "amazon.nova-lite-v1:0"; + +// The system prompt to help Amazon Bedrock craft it's response. +const system_prompt = [ + { + text: + "You are a music expert that provides the most popular song played on a radio station, using only the\n" + + "the top_song tool, which he call sign for the radio station for which you want the most popular song. " + + "Example calls signs are WZPZ and WKRP. \n" + + "- Only use the top_song tool. Never guess or make up information. \n" + + "- If the tool errors, apologize, explain weather is unavailable, and suggest other options.\n" + + "- Only respond to queries about the most popular song played on a radio station\n" + + "Remind off-topic users of your purpose. \n" + + "- Never claim to search online, access external data, or use tools besides the top_song tool.\n", + }, +]; +// The user's question. +const message = [ + { + role: "user", + content: [{ text: "What is the most popular song on WZPZ?" }], + }, +]; +// The tool specification. In this case, it uses an example schema for +// a tool that gets the most popular song played on a radio station. +const tool_config = { + tools: [ + { + toolSpec: { + name: "top_song", + description: "Get the most popular song played on a radio station.", + inputSchema: { + json: { + type: "object", + properties: { + sign: { + type: "string", + description: + "The call sign for the radio station for which you want the most popular song. Example calls signs are WZPZ and WKRP.", + }, + }, + required: ["sign"], + }, + }, + }, + }, + ], +}; + +// Helper function to return the song and artist from top_song tool. +async function get_top_song(call_sign) { + try { + if (call_sign === "WZPZ") { + const song = "Elemental Hotel"; + const artist = "8 Storey Hike"; + return { song, artist }; + } + } catch (error) { + console.log(`${error.message}`); + } +} + +// 3. Send the request to Amazon Bedrock, and returns the response. +export async function SendConversationtoBedrock( + modelId, + message, + system_prompt, + tool_config, +) { + try { + const response = await bedRockRuntimeClient.send( + new ConverseCommand({ + modelId: modelId, + messages: message, + system: system_prompt, + toolConfig: tool_config, + }), + ); + if (response.stopReason === "tool_use") { + const toolResultFinal = []; + try { + const output_message = response.output.message; + message.push(output_message); + const toolRequests = output_message.content; + const toolMessage = toolRequests[0].text; + console.log(toolMessage.replace(/<[^>]+>/g, "")); + for (const toolRequest of toolRequests) { + if (Object.hasOwn(toolRequest, "toolUse")) { + const toolUse = toolRequest.toolUse; + const sign = toolUse.input.sign; + const toolUseID = toolUse.toolUseId; + console.log( + `Requesting tool ${toolUse.name}, Tool use id ${toolUseID}`, + ); + if (toolUse.name === "top_song") { + const toolResult = []; + try { + const top_song = await get_top_song(toolUse.input.sign).then( + (top_song) => top_song, + ); + const toolResult = { + toolResult: { + toolUseId: toolUseID, + content: [ + { + json: { song: top_song.song, artist: top_song.artist }, + }, + ], + }, + }; + toolResultFinal.push(toolResult); + } catch (err) { + const toolResult = { + toolUseId: toolUseID, + content: [{ json: { text: err.message } }], + status: "error", + }; + } + } + } + } + const toolResultMessage = { + role: "user", + content: toolResultFinal, + }; + // Step 4. Add the tool response to the conversation, and send it back to Amazon Bedrock. + + message.push(toolResultMessage); + await SendConversationtoBedrock( + modelId, + message, + system_prompt, + tool_config, + ); + } catch (caught) { + console.error(`${caught.message}`); + throw caught; + } + } + + // 4. Publish the response. + if (response.stopReason === "end_turn") { + const finalMessage = response.output.message.content[0].text; + const messageToPrint = finalMessage.replace(/<[^>]+>/g); + console.log(messageToPrint.replace(/<[^>]+>/g)); + return messageToPrint; + } + } catch (caught) { + if (caught.name === "ModelNotReady") { + console.log( + `${caught.name} - Model not ready, please wait and try again.`, + ); + throw caught; + } + if (caught.name === "BedrockRuntimeException") { + console.log( + `${caught.name} - Error occurred while sending Converse request`, + ); + throw caught; + } + } +} +await SendConversationtoBedrock(modelId, message, system_prompt, tool_config); + +// snippet-end:[Bedrock.ConverseTool.javascriptv3.SendConverseRequest] diff --git a/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse.js b/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse.js index 23c8d17dd45..4f55387c403 100644 --- a/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse.js +++ b/javascriptv3/example_code/bedrock-runtime/models/amazonNovaText/converse.js @@ -16,7 +16,7 @@ import { } from "@aws-sdk/client-bedrock-runtime"; // Step 1: Create the Amazon Bedrock runtime client -// Credentials will be automatically loaded from the environment +// Credentials will be automatically loaded from the environment. const client = new BedrockRuntimeClient({ region: "us-east-1" }); // Step 2: Specify which model to use: diff --git a/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/README.md b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/README.md new file mode 100644 index 00000000000..02450214d21 --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/README.md @@ -0,0 +1,53 @@ +# Amazon Bedrock Runtime Converse API with Tool for the SDK for JavaScript (v3) + +## Overview + +This example shows how to use AWS SDKs and the Amazon Bedrock Converse API to call a custom tool from a large language model (LLM) as part of a multistep conversation. The example creates a weather tool that leverages the Open-Meteo API to retrieve current weather information based on user input. + +## ⚠ Important + +- Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +- Running the tests might result in charges to your AWS account. +- We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +- This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + +## Scenario + +This example illustrates a typical interaction between a generative AI model, an application, and connected tools or APIs to solve a problem or achieve a specific goal. The scenario follows these steps: + +1. Set up the system prompt and tool configuration. +2. Specify the AI model to be used (e.g., Anthropic Claude 3 Sonnet). +3. Create a client to interact with Amazon Bedrock. +4. Prompt the user for their weather request. +5. Send the user input including the conversation history to the model. +6. The model processes the input and determines if a connected tool or API needs to be used. If this is the case, the model returns a tool use request with specific parameters needed to invoke the tool, and a unique tool use ID to correlate tool responses to the request. +7. The scenario application invokes the tool to fetch weather data, and append the response and tool use ID to the conversation. +8. The model uses the tool response to generate a final response. If additional tool requests are needed, the process is repeated. +9. Once the final response is received and printed, the application returns to the prompt. + +### Prerequisites + +For prerequisites, see the [README](../../../../README.md#prerequisites) in the `javascriptv3` folder. + +## Run the example + +```bash +node converse-tool-scenario.js +``` +## Tests + +⚠ Running tests might result in charges to your AWS account. + +To find instructions for running these tests, see the [README](../../../../README.md#tests) in the `javascriptv3` folder. + +## Additional resources + +- [Documentation: The Amazon Bedrock User Guide](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) +- [Tutorials: A developer's guide to Bedrock's new Converse API](https://community.aws/content/2dtauBCeDa703x7fDS9Q30MJoBA/amazon-bedrock-converse-api-developer-guide) +- [More examples: Amazon Bedrock code examples and scenarios in multiple programming languages](https://docs.aws.amazon.com/bedrock/latest/userguide/service_code_examples.html) + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/converse-tool-scenario.js b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/converse-tool-scenario.js new file mode 100644 index 00000000000..8093d8108cb --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/converse-tool-scenario.js @@ -0,0 +1,314 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[Bedrock.ConverseTool.javascriptv3.Scenario] + +/* Before running this JavaScript code example, set up your development environment, including your credentials. +This demo illustrates a tool use scenario using Amazon Bedrock's Converse API and a weather tool. +The script interacts with a foundation model on Amazon Bedrock to provide weather information based on user +input. It uses the Open-Meteo API (https://open-meteo.com) to retrieve current weather data for a given location.*/ + +import { + Scenario, + ScenarioAction, + ScenarioInput, + ScenarioOutput, +} from "@aws-doc-sdk-examples/lib/scenario/index.js"; +import { + BedrockRuntimeClient, + ConverseCommand, +} from "@aws-sdk/client-bedrock-runtime"; + +import { parseArgs } from "node:util"; +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +const __filename = fileURLToPath(import.meta.url); +import data from "./questions.json" with { type: "json" }; +import toolConfig from "./tool_config.json" with { type: "json" }; + +const systemPrompt = [ + { + text: + "You are a weather assistant that provides current weather data for user-specified locations using only\n" + + "the Weather_Tool, which expects latitude and longitude. Infer the coordinates from the location yourself.\n" + + "If the user provides coordinates, infer the approximate location and refer to it in your response.\n" + + "To use the tool, you strictly apply the provided tool specification.\n" + + "If the user specifies a state, country, or region, infer the locations of cities within that state.\n" + + "\n" + + "- Explain your step-by-step process, and give brief updates before each step.\n" + + "- Only use the Weather_Tool for data. Never guess or make up information. \n" + + "- Repeat the tool use for subsequent requests if necessary.\n" + + "- If the tool errors, apologize, explain weather is unavailable, and suggest other options.\n" + + "- Report temperatures in °C (°F) and wind in km/h (mph). Keep weather reports concise. Sparingly use\n" + + " emojis where appropriate.\n" + + "- Only respond to weather queries. Remind off-topic users of your purpose. \n" + + "- Never claim to search online, access external data, or use tools besides Weather_Tool.\n" + + "- Complete the entire process until you have all required data before sending the complete response.", + }, +]; +const tools_config = toolConfig; + +/// Starts the conversation with the user and handles the interaction with Bedrock. +async function askQuestion(userMessage) { + // The maximum number of recursive calls allowed in the tool use function. + // This helps prevent infinite loops and potential performance issues. + const max_recursions = 5; + const messages = [ + { + role: "user", + content: [{ text: userMessage }], + }, + ]; + try { + const response = await SendConversationtoBedrock(messages); + await ProcessModelResponseAsync(response, messages, max_recursions); + } catch (error) { + console.log("error ", error); + } +} + +// Sends the conversation, the system prompt, and the tool spec to Amazon Bedrock, and returns the response. +// param "messages" - The conversation history including the next message to send. +// return - The response from Amazon Bedrock. +async function SendConversationtoBedrock(messages) { + const bedRockRuntimeClient = new BedrockRuntimeClient({ + region: "us-east-1", + }); + try { + const modelId = "amazon.nova-lite-v1:0"; + const response = await bedRockRuntimeClient.send( + new ConverseCommand({ + modelId: modelId, + messages: messages, + system: systemPrompt, + toolConfig: tools_config, + }), + ); + return response; + } catch (caught) { + if (caught.name === "ModelNotReady") { + console.log( + "`${caught.name}` - Model not ready, please wait and try again.", + ); + throw caught; + } + if (caught.name === "BedrockRuntimeException") { + console.log( + '`${caught.name}` - "Error occurred while sending Converse request.', + ); + throw caught; + } + } +} + +// Processes the response received via Amazon Bedrock and performs the necessary actions based on the stop reason. +// param "response" - The model's response returned via Amazon Bedrock. +// param "messages" - The conversation history. +// param "max_recursions" - The maximum number of recursive calls allowed. +async function ProcessModelResponseAsync(response, messages, max_recursions) { + if (max_recursions <= 0) { + await HandleToolUseAsync(response, messages); + } + if (response.stopReason === "tool_use") { + await HandleToolUseAsync(response, messages, max_recursions - 1); + } + if (response.stopReason === "end_turn") { + const messageToPrint = response.output.message.content[0].text; + console.log(messageToPrint.replace(/<[^>]+>/g, "")); + } +} +// Handles the tool use case by invoking the specified tool and sending the tool's response back to Bedrock. +// The tool response is appended to the conversation, and the conversation is sent back to Amazon Bedrock for further processing. +// param "response" - the model's response containing the tool use request. +// param "messages" - the conversation history. +// param "max_recursions" - The maximum number of recursive calls allowed. +async function HandleToolUseAsync(response, messages, max_recursions) { + const toolResultFinal = []; + try { + const output_message = response.output.message; + messages.push(output_message); + const toolRequests = output_message.content; + const toolMessage = toolRequests[0].text; + console.log(toolMessage.replace(/<[^>]+>/g, "")); + for (const toolRequest of toolRequests) { + if (Object.hasOwn(toolRequest, "toolUse")) { + const toolUse = toolRequest.toolUse; + const latitude = toolUse.input.latitude; + const longitude = toolUse.input.longitude; + const toolUseID = toolUse.toolUseId; + console.log( + `Requesting tool ${toolUse.name}, Tool use id ${toolUseID}`, + ); + if (toolUse.name === "Weather_Tool") { + try { + const current_weather = await callWeatherTool( + longitude, + latitude, + ).then((current_weather) => current_weather); + const currentWeather = current_weather; + const toolResult = { + toolResult: { + toolUseId: toolUseID, + content: [{ json: currentWeather }], + }, + }; + toolResultFinal.push(toolResult); + } catch (err) { + console.log("An error occurred. ", err); + } + } + } + } + + const toolResultMessage = { + role: "user", + content: toolResultFinal, + }; + messages.push(toolResultMessage); + // Send the conversation to Amazon Bedrock + await ProcessModelResponseAsync( + await SendConversationtoBedrock(messages), + messages, + ); + } catch (error) { + console.log("An error occurred. ", error); + } +} +// Call the Weathertool. +// param = longitude of location +// param = latitude of location +async function callWeatherTool(longitude, latitude) { + // Open-Meteo API endpoint + const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`; + + // Fetch the weather data. + return fetch(apiUrl) + .then((response) => { + return response.json().then((current_weather) => { + return current_weather; + }); + }) + .catch((error) => { + console.error("Error fetching weather data:", error); + }); +} +/** + * Used repeatedly to have the user press enter. + * @type {ScenarioInput} + */ +const pressEnter = new ScenarioInput("continue", "Press Enter to continue", { + type: "input", +}); + +const greet = new ScenarioOutput( + "greet", + "Welcome to the Amazon Bedrock Tool Use demo! \n" + + "This assistant provides current weather information for user-specified locations. " + + "You can ask for weather details by providing the location name or coordinates." + + "Weather information will be provided using a custom Tool and open-meteo API." + + "For the purposes of this example, we'll use in order the questions in ./questions.json :\n" + + "What's the weather like in Seattle? " + + "What's the best kind of cat? " + + "Where is the warmest city in Washington State right now? " + + "What's the warmest city in California right now?\n" + + "To exit the program, simply type 'x' and press Enter.\n" + + "Have fun and experiment with the app by editing the questions in ./questions.json! " + + "P.S.: You're not limited to single locations, or even to using English! ", + + { header: true }, +); +const displayAskQuestion1 = new ScenarioOutput( + "displayAskQuestion1", + "Press enter to ask question number 1 (default is 'What's the weather like in Seattle?')", +); + +const askQuestion1 = new ScenarioAction( + "askQuestion1", + async (/** @type {State} */ state) => { + const userMessage1 = data.questions["question-1"]; + await askQuestion(userMessage1); + }, +); + +const displayAskQuestion2 = new ScenarioOutput( + "displayAskQuestion2", + "Press enter to ask question number 2 (default is 'What's the best kind of cat?')", +); + +const askQuestion2 = new ScenarioAction( + "askQuestion2", + async (/** @type {State} */ state) => { + const userMessage2 = data.questions["question-2"]; + await askQuestion(userMessage2); + }, +); +const displayAskQuestion3 = new ScenarioOutput( + "displayAskQuestion3", + "Press enter to ask question number 3 (default is 'Where is the warmest city in Washington State right now?')", +); + +const askQuestion3 = new ScenarioAction( + "askQuestion3", + async (/** @type {State} */ state) => { + const userMessage3 = data.questions["question-3"]; + await askQuestion(userMessage3); + }, +); + +const displayAskQuestion4 = new ScenarioOutput( + "displayAskQuestion4", + "Press enter to ask question number 4 (default is 'What's the warmest city in California right now?')", +); + +const askQuestion4 = new ScenarioAction( + "askQuestion4", + async (/** @type {State} */ state) => { + const userMessage4 = data.questions["question-4"]; + await askQuestion(userMessage4); + }, +); + +const goodbye = new ScenarioOutput( + "goodbye", + "Thank you for checking out the Amazon Bedrock Tool Use demo. We hope you\n" + + "learned something new, or got some inspiration for your own apps today!\n" + + "For more Bedrock examples in different programming languages, have a look at:\n" + + "https://docs.aws.amazon.com/bedrock/latest/userguide/service_code_examples.html", +); + +const myScenario = new Scenario("Converse Tool Scenario", [ + greet, + pressEnter, + displayAskQuestion1, + askQuestion1, + pressEnter, + displayAskQuestion2, + askQuestion2, + pressEnter, + displayAskQuestion3, + askQuestion3, + pressEnter, + displayAskQuestion4, + askQuestion4, + pressEnter, + goodbye, +]); + +/** @type {{ stepHandlerOptions: StepHandlerOptions }} */ +export const main = async (stepHandlerOptions) => { + await myScenario.run(stepHandlerOptions); +}; + +// Invoke main function if this file was run directly. +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const { values } = parseArgs({ + options: { + yes: { + type: "boolean", + short: "y", + }, + }, + }); + main({ confirmAll: values.yes }); +} +// snippet-end:[Bedrock.ConverseTool.javascriptv3.Scenario] diff --git a/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/questions.json b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/questions.json new file mode 100644 index 00000000000..8f33af0eea8 --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/questions.json @@ -0,0 +1,8 @@ +{ + "questions": { + "question-1": "What's the weather like in Seattle right now?", + "question-2": "What's the best kind of cat?", + "question-3": "Where is the warmest city in Washington State right now?", + "question-4": "What's the warmest city in California right now?" + } +} diff --git a/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/tool_config.json b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/tool_config.json new file mode 100644 index 00000000000..b643763f2ea --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/scenarios/converse_tool_scenario/tool_config.json @@ -0,0 +1,26 @@ +{ + "tools": [ + { + "toolSpec": { + "name": "Weather_Tool", + "description": "Get the current weather for a given location, based on its WGS84 coordinates.", + "inputSchema": { + "json": { + "type": "object", + "properties": { + "latitude": { + "type": "string", + "description": "Geographical WGS84 latitude of the location." + }, + "longitude": { + "type": "string", + "description": "Geographical WGS84 longitude of the location." + } + }, + "required": ["latitude", "longitude"] + } + } + } + } + ] +} diff --git a/javascriptv3/example_code/bedrock-runtime/tests/converse-tool-scenario.integration.test.js b/javascriptv3/example_code/bedrock-runtime/tests/converse-tool-scenario.integration.test.js new file mode 100644 index 00000000000..4ca3a873121 --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/tests/converse-tool-scenario.integration.test.js @@ -0,0 +1,15 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it } from "vitest"; +import { main } from "../scenarios/converse_tool_scenario/converse-tool-scenario.js"; + +describe("basic scenario", () => { + it( + "should run without error", + async () => { + await main({ confirmAll: true }); + }, + { timeout: 600000 }, + ); +}); diff --git a/javascriptv3/example_code/bedrock-runtime/tests/converse-with-tool.integration.test.js b/javascriptv3/example_code/bedrock-runtime/tests/converse-with-tool.integration.test.js new file mode 100644 index 00000000000..ae25ea15c71 --- /dev/null +++ b/javascriptv3/example_code/bedrock-runtime/tests/converse-with-tool.integration.test.js @@ -0,0 +1,81 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { expect, test, vi } from "vitest"; +import { SendConversationtoBedrock } from "../models/amazonNovaText/converse-with-tool.js"; +import { + BedrockRuntimeClient, + ConverseCommand, +} from "@aws-sdk/client-bedrock-runtime"; + +vi.mock("@aws-sdk/client-bedrock-runtime", () => ({ + BedrockRuntimeClient: vi.fn().mockImplementation(() => ({ + send: vi.fn().mockResolvedValue({ + stopReason: "end_turn", + output: { + message: { + content: [ + { + text: "The most popular song on WZPZ is Elemental Hotel by 8 Storey Hike.", + }, + ], + }, + }, + }), + })), + ConverseCommand: vi.fn(), +})); + +vi.mock("./converse-with-tool.js", () => ({ + get_top_song: vi.fn().mockResolvedValue({ + song: "Elemental Hotel", + artist: "8 Storey Hike", + }), +})); + +test("SendConversationtoBedrock", async () => { + const modelId = "amazon.nova-lite-v1:0"; + const message = [ + { + role: "user", + content: [{ text: "What is the most popular song on WZPZ?" }], + }, + ]; + const system_prompt = [ + { + text: "You are a music expert that provides the most popular song played on a radio station, using only the top_song tool.", + }, + ]; + const tool_config = { + tools: [ + { + toolSpec: { + name: "top_song", + description: "Get the most popular song played on a radio station.", + inputSchema: { + json: { + type: "object", + properties: { + sign: { + type: "string", + description: + "The call sign for the radio station for which you want the most popular song. Example calls signs are WZPZ and WKRP.", + }, + }, + required: ["sign"], + }, + }, + }, + }, + ], + }; + + const result = await SendConversationtoBedrock( + modelId, + message, + system_prompt, + tool_config, + ); + + expect(result).toContain("The most popular song"); +});