From 9c803f887e68a9ef81786794458334373b6642bd Mon Sep 17 00:00:00 2001 From: sahusiddharth Date: Fri, 21 Mar 2025 18:52:26 +0530 Subject: [PATCH 1/2] aws bedrock tutorial notebook and converter code --- integrations/aws_bedrock/agent.py | 313 ++ .../aws_bedrock/awsBedrock_x_ragas.ipynb | 2610 +++++++++++++++++ integrations/aws_bedrock/aws_bedrock.py | 134 + .../dataset/Restaurant_Childrens_Menu.pdf | Bin 0 -> 31015 bytes .../dataset/Restaurant_Dinner_Menu.pdf | Bin 0 -> 31084 bytes .../dataset/Restaurant_week_specials.pdf | Bin 0 -> 75100 bytes .../aws_bedrock/images/architecture.png | Bin 0 -> 331269 bytes integrations/aws_bedrock/knowledge_base.py | 632 ++++ integrations/aws_bedrock/lambda_function.py | 127 + 9 files changed, 3816 insertions(+) create mode 100644 integrations/aws_bedrock/agent.py create mode 100644 integrations/aws_bedrock/awsBedrock_x_ragas.ipynb create mode 100644 integrations/aws_bedrock/aws_bedrock.py create mode 100644 integrations/aws_bedrock/dataset/Restaurant_Childrens_Menu.pdf create mode 100644 integrations/aws_bedrock/dataset/Restaurant_Dinner_Menu.pdf create mode 100644 integrations/aws_bedrock/dataset/Restaurant_week_specials.pdf create mode 100644 integrations/aws_bedrock/images/architecture.png create mode 100644 integrations/aws_bedrock/knowledge_base.py create mode 100644 integrations/aws_bedrock/lambda_function.py diff --git a/integrations/aws_bedrock/agent.py b/integrations/aws_bedrock/agent.py new file mode 100644 index 0000000..0e2b023 --- /dev/null +++ b/integrations/aws_bedrock/agent.py @@ -0,0 +1,313 @@ +import boto3 +import json +import time +import zipfile +from io import BytesIO + +iam_client = boto3.client('iam') +sts_client = boto3.client('sts') +session = boto3.session.Session() +region = session.region_name +account_id = sts_client.get_caller_identity()["Account"] +dynamodb_client = boto3.client('dynamodb') +dynamodb_resource = boto3.resource('dynamodb') +lambda_client = boto3.client('lambda') +bedrock_agent_client = boto3.client('bedrock-agent') + + +def create_dynamodb(table_name): + table = dynamodb_resource.create_table( + TableName=table_name, + KeySchema=[ + { + 'AttributeName': 'booking_id', + 'KeyType': 'HASH' + } + ], + AttributeDefinitions=[ + { + 'AttributeName': 'booking_id', + 'AttributeType': 'S' + } + ], + BillingMode='PAY_PER_REQUEST' # Use on-demand capacity mode + ) + + # Wait for the table to be created + print(f'Creating table {table_name}...') + table.wait_until_exists() + print(f'Table {table_name} created successfully!') + return + + +def create_lambda(lambda_function_name, lambda_iam_role): + # add to function + + # Package up the lambda function code + s = BytesIO() + z = zipfile.ZipFile(s, 'w') + z.write("lambda_function.py") + z.close() + zip_content = s.getvalue() + + # Create Lambda Function + lambda_function = lambda_client.create_function( + FunctionName=lambda_function_name, + Runtime='python3.12', + Timeout=60, + Role=lambda_iam_role['Role']['Arn'], + Code={'ZipFile': zip_content}, + Handler='lambda_function.lambda_handler' + ) + return lambda_function + + +def create_lambda_role(agent_name, dynamodb_table_name): + lambda_function_role = f'{agent_name}-lambda-role' + dynamodb_access_policy_name = f'{agent_name}-dynamodb-policy' + # Create IAM Role for the Lambda function + try: + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + } + + assume_role_policy_document_json = json.dumps(assume_role_policy_document) + + lambda_iam_role = iam_client.create_role( + RoleName=lambda_function_role, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Pause to make sure role is created + time.sleep(10) + except iam_client.exceptions.EntityAlreadyExistsException: + lambda_iam_role = iam_client.get_role(RoleName=lambda_function_role) + + # Attach the AWSLambdaBasicExecutionRole policy + iam_client.attach_role_policy( + RoleName=lambda_function_role, + PolicyArn='arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + ) + + # Create a policy to grant access to the DynamoDB table + dynamodb_access_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:DeleteItem" + ], + "Resource": "arn:aws:dynamodb:{}:{}:table/{}".format( + region, account_id, dynamodb_table_name + ) + } + ] + } + + # Create the policy + dynamodb_access_policy_json = json.dumps(dynamodb_access_policy) + dynamodb_access_policy_response = iam_client.create_policy( + PolicyName=dynamodb_access_policy_name, + PolicyDocument=dynamodb_access_policy_json + ) + + # Attach the policy to the Lambda function's role + iam_client.attach_role_policy( + RoleName=lambda_function_role, + PolicyArn=dynamodb_access_policy_response['Policy']['Arn'] + ) + return lambda_iam_role + + +def create_agent_role_and_policies(agent_name, agent_foundation_model, kb_id=None): + agent_bedrock_allow_policy_name = f"{agent_name}-ba" + agent_role_name = f'AmazonBedrockExecutionRoleForAgents_{agent_name}' + # Create IAM policies for agent + statements = [ + { + "Sid": "AmazonBedrockAgentBedrockFoundationModelPolicy", + "Effect": "Allow", + "Action": "bedrock:InvokeModel", + "Resource": [ + f"arn:aws:bedrock:{region}::foundation-model/{agent_foundation_model}" + ] + } + ] + # add Knowledge Base retrieve and retrieve and generate permissions if agent has KB attached to it + if kb_id: + statements.append( + { + "Sid": "QueryKB", + "Effect": "Allow", + "Action": [ + "bedrock:Retrieve", + "bedrock:RetrieveAndGenerate" + ], + "Resource": [ + f"arn:aws:bedrock:{region}:{account_id}:knowledge-base/{kb_id}" + ] + } + ) + + bedrock_agent_bedrock_allow_policy_statement = { + "Version": "2012-10-17", + "Statement": statements + } + + bedrock_policy_json = json.dumps(bedrock_agent_bedrock_allow_policy_statement) + + agent_bedrock_policy = iam_client.create_policy( + PolicyName=agent_bedrock_allow_policy_name, + PolicyDocument=bedrock_policy_json + ) + + # Create IAM Role for the agent and attach IAM policies + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Principal": { + "Service": "bedrock.amazonaws.com" + }, + "Action": "sts:AssumeRole" + }] + } + + assume_role_policy_document_json = json.dumps(assume_role_policy_document) + agent_role = iam_client.create_role( + RoleName=agent_role_name, + AssumeRolePolicyDocument=assume_role_policy_document_json + ) + + # Pause to make sure role is created + time.sleep(10) + + iam_client.attach_role_policy( + RoleName=agent_role_name, + PolicyArn=agent_bedrock_policy['Policy']['Arn'] + ) + return agent_role + + +def delete_agent_roles_and_policies(agent_name): + agent_bedrock_allow_policy_name = f"{agent_name}-ba" + agent_role_name = f'AmazonBedrockExecutionRoleForAgents_{agent_name}' + dynamodb_access_policy_name = f'{agent_name}-dynamodb-policy' + lambda_function_role = f'{agent_name}-lambda-role' + + for policy in [agent_bedrock_allow_policy_name]: + try: + iam_client.detach_role_policy( + RoleName=agent_role_name, + PolicyArn=f'arn:aws:iam::{account_id}:policy/{policy}' + ) + except Exception as e: + print(f"Could not detach {policy} from {agent_role_name}") + print(e) + + for policy in [dynamodb_access_policy_name]: + try: + iam_client.detach_role_policy( + RoleName=lambda_function_role, + PolicyArn=f'arn:aws:iam::{account_id}:policy/{policy}' + ) + except Exception as e: + print(f"Could not detach {policy} from {lambda_function_role}") + print(e) + + try: + iam_client.detach_role_policy( + RoleName=lambda_function_role, + PolicyArn='arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + ) + except Exception as e: + print(f"Could not detach AWSLambdaBasicExecutionRole from {lambda_function_role}") + print(e) + + for role_name in [agent_role_name, lambda_function_role]: + try: + iam_client.delete_role( + RoleName=role_name + ) + except Exception as e: + print(f"Could not delete role {role_name}") + print(e) + + for policy in [agent_bedrock_allow_policy_name, dynamodb_access_policy_name]: + try: + iam_client.delete_policy( + PolicyArn=f'arn:aws:iam::{account_id}:policy/{policy}' + ) + except Exception as e: + print(f"Could not delete policy {policy}") + print(e) + + +def clean_up_resources( + table_name, lambda_function, lambda_function_name, agent_action_group_response, agent_functions, + agent_id, kb_id, alias_id +): + action_group_id = agent_action_group_response['agentActionGroup']['actionGroupId'] + action_group_name = agent_action_group_response['agentActionGroup']['actionGroupName'] + # Delete Agent Action Group, Agent Alias, and Agent + try: + bedrock_agent_client.update_agent_action_group( + agentId=agent_id, + agentVersion='DRAFT', + actionGroupId= action_group_id, + actionGroupName=action_group_name, + actionGroupExecutor={ + 'lambda': lambda_function['FunctionArn'] + }, + functionSchema={ + 'functions': agent_functions + }, + actionGroupState='DISABLED', + ) + bedrock_agent_client.disassociate_agent_knowledge_base( + agentId=agent_id, + agentVersion='DRAFT', + knowledgeBaseId=kb_id + ) + bedrock_agent_client.delete_agent_action_group( + agentId=agent_id, + agentVersion='DRAFT', + actionGroupId=action_group_id + ) + bedrock_agent_client.delete_agent_alias( + agentAliasId=alias_id, + agentId=agent_id + ) + bedrock_agent_client.delete_agent(agentId=agent_id) + print(f"Agent {agent_id}, Agent Alias {alias_id}, and Action Group have been deleted.") + except Exception as e: + print(f"Error deleting Agent resources: {e}") + + # Delete Lambda function + try: + lambda_client.delete_function(FunctionName=lambda_function_name) + print(f"Lambda function {lambda_function_name} has been deleted.") + except Exception as e: + print(f"Error deleting Lambda function {lambda_function_name}: {e}") + + # Delete DynamoDB table + try: + dynamodb_client.delete_table(TableName=table_name) + print(f"Table {table_name} is being deleted...") + waiter = dynamodb_client.get_waiter('table_not_exists') + waiter.wait(TableName=table_name) + print(f"Table {table_name} has been deleted.") + except Exception as e: + print(f"Error deleting table {table_name}: {e}") \ No newline at end of file diff --git a/integrations/aws_bedrock/awsBedrock_x_ragas.ipynb b/integrations/aws_bedrock/awsBedrock_x_ragas.ipynb new file mode 100644 index 0000000..c1a3bda --- /dev/null +++ b/integrations/aws_bedrock/awsBedrock_x_ragas.ipynb @@ -0,0 +1,2610 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "50862762", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "if \"OPENAI_API_KEY\" not in os.environ:\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(\"Enter your OpenAI API key: \")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "663fd5cf", + "metadata": {}, + "outputs": [], + "source": [ + "from ragas.llms import LangchainLLMWrapper\n", + "from ragas.embeddings import LangchainEmbeddingsWrapper\n", + "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", + "\n", + "\n", + "evaluator_llm = LangchainLLMWrapper(ChatOpenAI(model=\"gpt-4o-mini\"))\n", + "evaluator_embeddings = LangchainEmbeddingsWrapper(\n", + " OpenAIEmbeddings(model=\"text-embedding-3-small\")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a0bb5c39-2fde-4336-8127-8debe7cb2741", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Create and Evaluate an Agent Integrated with Bedrock Knowledge Bases and Attached Action Group\n", + "\n", + "In this notebook, you will learn how to evaluate an Amazon Bedrock Agent. The agent we'll evaluate is a restaurant agent whose tasks include providing clients with information about adult and children's menus and managing the table booking system. This agent is inspired by a [features example notebooks](https://github.com/aws-samples/amazon-bedrock-samples/tree/main/agents-and-function-calling/bedrock-agents/features-examples/05-create-agent-with-knowledge-base-and-action-group) of [Amazon Bedrock Agents](https://aws.amazon.com/bedrock/agents/) with minor changes. You can learn more about the agent creation process [here](https://github.com/aws-samples/amazon-bedrock-samples/tree/main/agents-and-function-calling/bedrock-agents/features-examples/05-create-agent-with-knowledge-base-and-action-group).\n", + "\n", + "The architecture is illustrated below:\n", + "\n", + "\n", + "
\n", + "\n", + "The steps covered in this notebook include:\n", + "\n", + "1. Importing necessary libraries\n", + "2. Creating the agent\n", + "3. Defining the Ragas metrics\n", + "4. Evaluating the agent\n", + "5. Cleaning up the created resources" + ] + }, + { + "cell_type": "markdown", + "id": "076a5aba-9735-4e98-8a53-0daccd7e94b0", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 1. Import the needed libraries" + ] + }, + { + "cell_type": "markdown", + "id": "4fa67d7a", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "First step is to install the pre-requisites packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac05c073-d45b-4d85-9bf8-ae10aa78be8d", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%pip install --upgrade -q boto3 opensearch-py botocore awscli retrying ragas" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8ad6ec2-b283-4c5d-879f-e397e46568c0", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "import boto3\n", + "import logging\n", + "import pprint\n", + "import json\n", + "\n", + "from knowledge_base import BedrockKnowledgeBase\n", + "from agent import (\n", + " create_agent_role_and_policies,\n", + " create_lambda_role,\n", + " delete_agent_roles_and_policies,\n", + " create_dynamodb,\n", + " create_lambda,\n", + " clean_up_resources,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2b2d607-c1f2-4cbb-9f89-d935676e0101", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Clients\n", + "s3_client = boto3.client(\"s3\")\n", + "sts_client = boto3.client(\"sts\")\n", + "session = boto3.session.Session()\n", + "region = session.region_name\n", + "account_id = sts_client.get_caller_identity()[\"Account\"]\n", + "bedrock_agent_client = boto3.client(\"bedrock-agent\")\n", + "bedrock_agent_runtime_client = boto3.client(\"bedrock-agent-runtime\")\n", + "logging.basicConfig(\n", + " format=\"[%(asctime)s] p%(process)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s\",\n", + " level=logging.INFO,\n", + ")\n", + "logger = logging.getLogger(__name__)\n", + "region, account_id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d647d2a3", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "suffix = f\"{region}-{account_id}\"\n", + "agent_name = \"booking-agent\"\n", + "knowledge_base_name = f\"{agent_name}-kb\"\n", + "knowledge_base_description = (\n", + " \"Knowledge Base containing the restaurant menu's collection\"\n", + ")\n", + "agent_alias_name = \"booking-agent-alias\"\n", + "bucket_name = f\"{agent_name}-{suffix}\"\n", + "agent_bedrock_allow_policy_name = f\"{agent_name}-ba\"\n", + "agent_role_name = f\"AmazonBedrockExecutionRoleForAgents_{agent_name}\"\n", + "agent_foundation_model = \"amazon.nova-pro-v1:0\"\n", + "\n", + "agent_description = \"Agent in charge of a restaurants table bookings\"\n", + "agent_instruction = \"\"\"\n", + "You are a restaurant agent responsible for managing clients’ bookings (retrieving, creating, or canceling reservations) and assisting with menu inquiries. When handling menu requests, provide detailed information about the requested items. Offer recommendations only when:\n", + "\n", + "1. The customer explicitly asks for a recommendation, even if the item is available (include complementary dishes).\n", + "2. The requested item is unavailable—inform the customer and suggest suitable alternatives.\n", + "3. For general menu inquiries, provide the full menu and add a recommendation only if the customer asks for one.\n", + "\n", + "In all cases, ensure that any recommended items are present in the menu.\n", + "\n", + "Ensure all responses are clear, contextually relevant, and enhance the customer's experience.\n", + "\"\"\"\n", + "\n", + "agent_action_group_description = \"\"\"\n", + "Actions for getting table booking information, create a new booking or delete an existing booking\"\"\"\n", + "\n", + "agent_action_group_name = \"TableBookingsActionGroup\"" + ] + }, + { + "cell_type": "markdown", + "id": "b88af964", + "metadata": {}, + "source": [ + "## 2. Setting up Agent" + ] + }, + { + "cell_type": "markdown", + "id": "38c38fcb-9b87-414e-a644-04c263eea5c9", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.1 Create Knowledge Base for Amazon Bedrock\n", + "\n", + "Let's start by creating a [Knowledge Base for Amazon Bedrock](https://aws.amazon.com/bedrock/knowledge-bases/) to store the restaurant menus. For this example, we will integrate the knowledge base with Amazon OpenSearch Serverless." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c39c2d9-7965-4c22-a74b-65d1961c4166", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "knowledge_base = BedrockKnowledgeBase(\n", + " kb_name=knowledge_base_name,\n", + " kb_description=knowledge_base_description,\n", + " data_bucket_name=bucket_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cd8dab17", + "metadata": {}, + "source": [ + "### 2.2 Upload the Dataset to Amazon S3\n", + "\n", + "Now that we have created the knowledge base, let’s populate it with the restaurant menus dataset. In this example, we will use the [boto3 abstraction](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent/client/start_ingestion_job.html) of the API, via our helper classe. \n", + "\n", + "Let’s first upload the menu data available in the dataset folder to Amazon S3." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c74385d9", + "metadata": {}, + "outputs": [], + "source": [ + "def upload_directory(path, bucket_name):\n", + " for root, dirs, files in os.walk(path):\n", + " for file in files:\n", + " file_to_upload = os.path.join(root, file)\n", + " print(f\"uploading file {file_to_upload} to {bucket_name}\")\n", + " s3_client.upload_file(file_to_upload, bucket_name, file)\n", + "\n", + "\n", + "upload_directory(\"dataset\", bucket_name)" + ] + }, + { + "cell_type": "markdown", + "id": "48e46273-0267-4ecb-a686-2b1694b5a604", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Now we start the ingestion job" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bb7a9bc-5bba-4066-8fa3-a4c5a1385e95", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# ensure that the kb is available\n", + "time.sleep(30)\n", + "# sync knowledge base\n", + "knowledge_base.start_ingestion_job()" + ] + }, + { + "cell_type": "markdown", + "id": "9c85cbee-9359-4927-ae16-fa7af42cf981", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Finally we collect the Knowledge Base Id to integrate it with our Agent later on." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f71287af-0b7f-44d7-99ed-fa292ede4001", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "kb_id = knowledge_base.get_knowledge_base_id()" + ] + }, + { + "cell_type": "markdown", + "id": "315e0df7-4008-4fb4-b28d-a4df6ff446f6", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "#### Testing Knowledge Base with Retrieve and Generate API\n", + "\n", + "First, let’s test the knowledge base using the Retrieve and Generate API to ensure that the knowledge base is functioning correctly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f199e822-6e06-4bac-9dbf-40ff0be98598", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "response = bedrock_agent_runtime_client.retrieve_and_generate(\n", + " input={\"text\": \"Which are the mains available in the childrens menu?\"},\n", + " retrieveAndGenerateConfiguration={\n", + " \"type\": \"KNOWLEDGE_BASE\",\n", + " \"knowledgeBaseConfiguration\": {\n", + " \"knowledgeBaseId\": kb_id,\n", + " \"modelArn\": \"arn:aws:bedrock:{}::foundation-model/{}\".format(\n", + " region, agent_foundation_model\n", + " ),\n", + " \"retrievalConfiguration\": {\n", + " \"vectorSearchConfiguration\": {\"numberOfResults\": 5}\n", + " },\n", + " },\n", + " },\n", + ")\n", + "\n", + "print(response[\"output\"][\"text\"], end=\"\\n\" * 2)" + ] + }, + { + "cell_type": "markdown", + "id": "c5edf3bd-3214-4fe3-a9bc-df927e30b8b4", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.3 Create the DynamoDB Table\n", + "\n", + "We will create a DynamoDB table that contains restaurant booking information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18ff65c0-0207-4e34-932f-6d291f4d5c8d", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "table_name = \"restaurant_bookings\"\n", + "create_dynamodb(table_name)" + ] + }, + { + "cell_type": "markdown", + "id": "8460c785-e13f-4182-84d7-3bf7aafd3842", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.4 Create the Lambda Function\n", + "\n", + "We will now create a Lambda function that interacts with the DynamoDB table." + ] + }, + { + "cell_type": "markdown", + "id": "68871530-2953-4a37-854e-10323cabf095", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "#### Create the Function Code\n", + "\n", + "Create the Lambda function that implements the functions for `get_booking_details`, `create_booking`, and `delete_booking`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12271f1f-4739-472b-8909-cc81c148a941", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "%%writefile lambda_function.py\n", + "import json\n", + "import uuid\n", + "import boto3\n", + "\n", + "dynamodb = boto3.resource('dynamodb')\n", + "table = dynamodb.Table('restaurant_bookings')\n", + "\n", + "def get_named_parameter(event, name):\n", + " \"\"\"\n", + " Get a parameter from the lambda event\n", + " \"\"\"\n", + " return next(item for item in event['parameters'] if item['name'] == name)['value']\n", + "\n", + "\n", + "def get_booking_details(booking_id):\n", + " \"\"\"\n", + " Retrieve details of a restaurant booking\n", + " \n", + " Args:\n", + " booking_id (string): The ID of the booking to retrieve\n", + " \"\"\"\n", + " try:\n", + " response = table.get_item(Key={'booking_id': booking_id})\n", + " if 'Item' in response:\n", + " return response['Item']\n", + " else:\n", + " return {'message': f'No booking found with ID {booking_id}'}\n", + " except Exception as e:\n", + " return {'error': str(e)}\n", + "\n", + "\n", + "def create_booking(date, name, hour, num_guests):\n", + " \"\"\"\n", + " Create a new restaurant booking\n", + " \n", + " Args:\n", + " date (string): The date of the booking\n", + " name (string): Name to idenfity your reservation\n", + " hour (string): The hour of the booking\n", + " num_guests (integer): The number of guests for the booking\n", + " \"\"\"\n", + " try:\n", + " booking_id = str(uuid.uuid4())[:8]\n", + " table.put_item(\n", + " Item={\n", + " 'booking_id': booking_id,\n", + " 'date': date,\n", + " 'name': name,\n", + " 'hour': hour,\n", + " 'num_guests': num_guests\n", + " }\n", + " )\n", + " return {'booking_id': booking_id}\n", + " except Exception as e:\n", + " return {'error': str(e)}\n", + "\n", + "\n", + "def delete_booking(booking_id):\n", + " \"\"\"\n", + " Delete an existing restaurant booking\n", + " \n", + " Args:\n", + " booking_id (str): The ID of the booking to delete\n", + " \"\"\"\n", + " try:\n", + " response = table.delete_item(Key={'booking_id': booking_id})\n", + " if response['ResponseMetadata']['HTTPStatusCode'] == 200:\n", + " return {'message': f'Booking with ID {booking_id} deleted successfully'}\n", + " else:\n", + " return {'message': f'Failed to delete booking with ID {booking_id}'}\n", + " except Exception as e:\n", + " return {'error': str(e)}\n", + " \n", + "\n", + "def lambda_handler(event, context):\n", + " # get the action group used during the invocation of the lambda function\n", + " actionGroup = event.get('actionGroup', '')\n", + " \n", + " # name of the function that should be invoked\n", + " function = event.get('function', '')\n", + " \n", + " # parameters to invoke function with\n", + " parameters = event.get('parameters', [])\n", + "\n", + " if function == 'get_booking_details':\n", + " booking_id = get_named_parameter(event, \"booking_id\")\n", + " if booking_id:\n", + " response = str(get_booking_details(booking_id))\n", + " responseBody = {'TEXT': {'body': json.dumps(response)}}\n", + " else:\n", + " responseBody = {'TEXT': {'body': 'Missing booking_id parameter'}}\n", + "\n", + " elif function == 'create_booking':\n", + " date = get_named_parameter(event, \"date\")\n", + " name = get_named_parameter(event, \"name\")\n", + " hour = get_named_parameter(event, \"hour\")\n", + " num_guests = get_named_parameter(event, \"num_guests\")\n", + "\n", + " if date and hour and num_guests:\n", + " response = str(create_booking(date, name, hour, num_guests))\n", + " responseBody = {'TEXT': {'body': json.dumps(response)}}\n", + " else:\n", + " responseBody = {'TEXT': {'body': 'Missing required parameters'}}\n", + "\n", + " elif function == 'delete_booking':\n", + " booking_id = get_named_parameter(event, \"booking_id\")\n", + " if booking_id:\n", + " response = str(delete_booking(booking_id))\n", + " responseBody = {'TEXT': {'body': json.dumps(response)}}\n", + " else:\n", + " responseBody = {'TEXT': {'body': 'Missing booking_id parameter'}}\n", + "\n", + " else:\n", + " responseBody = {'TEXT': {'body': 'Invalid function'}}\n", + "\n", + " action_response = {\n", + " 'actionGroup': actionGroup,\n", + " 'function': function,\n", + " 'functionResponse': {\n", + " 'responseBody': responseBody\n", + " }\n", + " }\n", + "\n", + " function_response = {'response': action_response, 'messageVersion': event['messageVersion']}\n", + " print(\"Response: {}\".format(function_response))\n", + "\n", + " return function_response" + ] + }, + { + "cell_type": "markdown", + "id": "b8a4eefa-0ee8-4505-bba4-b62f5ba54a79", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "#### Create the required permissions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "490addc9-16a0-48e1-bd8f-02abb56bb520", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "lambda_iam_role = create_lambda_role(agent_name, table_name)" + ] + }, + { + "cell_type": "markdown", + "id": "895032f3", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "#### Create the function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "682ea2b4-5654-4e2f-b3dd-bc763a3c5918", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "lambda_function_name = f\"{agent_name}-lambda\"\n", + "lambda_function = create_lambda(lambda_function_name, lambda_iam_role)" + ] + }, + { + "cell_type": "markdown", + "id": "aef843aa-9f0f-473f-ab4e-1c4827974b87", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.5 Create the IAM Policies Needed for the Agent\n", + "\n", + "Now that we have created the Knowledge Base, our DynamoDB table, and the Lambda function to execute the tasks for our Agent, let’s start creating our Agent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa92a151-36ca-4099-8bc6-156b49b0c3e4", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "agent_role = create_agent_role_and_policies(\n", + " agent_name, agent_foundation_model, kb_id=kb_id\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "595bf767-3a26-4f92-b629-ba2908b90d81", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.6 Create the Agent\n", + "\n", + "Now that we have created the necessary IAM role, we can use the [`create_agent`](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent/client/create_agent.html) API from boto3 to create a new agent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ccaf856-7eff-4bd1-ac47-c89b6fbf7e8f", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "response = bedrock_agent_client.create_agent(\n", + " agentName=agent_name,\n", + " agentResourceRoleArn=agent_role[\"Role\"][\"Arn\"],\n", + " description=agent_description,\n", + " idleSessionTTLInSeconds=1800,\n", + " foundationModel=agent_foundation_model,\n", + " instruction=agent_instruction,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b6b5884e", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "Let's get our Agent ID. It will be important to perform operations with our agent" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40c48d3c-ed67-450b-b840-23ec1a99fee7", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "agent_id = response[\"agent\"][\"agentId\"]\n", + "print(\"The agent id is:\", agent_id)" + ] + }, + { + "cell_type": "markdown", + "id": "3f30f874-eb63-4ec3-8c19-36fab0f598df", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.7 Create the Agent Action Group\n", + "\n", + "We will now create an Agent Action Group that uses the Lambda function created earlier. To inform the agent about the capabilities of the action group, we will provide a description outlining its functionalities.\n", + "\n", + "To define the functions using a function schema, you need to provide the name, description, and parameters for each function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91c4c380-ca58-467a-ab67-b475605a0274", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "agent_functions = [\n", + " {\n", + " \"name\": \"get_booking_details\",\n", + " \"description\": \"Retrieve details of a restaurant booking\",\n", + " \"parameters\": {\n", + " \"booking_id\": {\n", + " \"description\": \"The ID of the booking to retrieve\",\n", + " \"required\": True,\n", + " \"type\": \"string\",\n", + " }\n", + " },\n", + " },\n", + " {\n", + " \"name\": \"create_booking\",\n", + " \"description\": \"Create a new restaurant booking\",\n", + " \"parameters\": {\n", + " \"date\": {\n", + " \"description\": \"The date of the booking\",\n", + " \"required\": True,\n", + " \"type\": \"string\",\n", + " },\n", + " \"name\": {\n", + " \"description\": \"Name to idenfity your reservation\",\n", + " \"required\": True,\n", + " \"type\": \"string\",\n", + " },\n", + " \"hour\": {\n", + " \"description\": \"The hour of the booking\",\n", + " \"required\": True,\n", + " \"type\": \"string\",\n", + " },\n", + " \"num_guests\": {\n", + " \"description\": \"The number of guests for the booking\",\n", + " \"required\": True,\n", + " \"type\": \"integer\",\n", + " },\n", + " },\n", + " },\n", + " {\n", + " \"name\": \"delete_booking\",\n", + " \"description\": \"Delete an existing restaurant booking\",\n", + " \"parameters\": {\n", + " \"booking_id\": {\n", + " \"description\": \"The ID of the booking to delete\",\n", + " \"required\": True,\n", + " \"type\": \"string\",\n", + " }\n", + " },\n", + " },\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "60d174f6", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "We now use the function schema to create the agent action group using the [`create_agent_action_group`](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent/client/create_agent_action_group.html) API" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24559f1c-e081-4f03-959f-a82f9444aa49", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Pause to make sure agent is created\n", + "time.sleep(30)\n", + "\n", + "# Now, we can configure and create an action group here:\n", + "agent_action_group_response = bedrock_agent_client.create_agent_action_group(\n", + " agentId=agent_id,\n", + " agentVersion=\"DRAFT\",\n", + " actionGroupExecutor={\"lambda\": lambda_function[\"FunctionArn\"]},\n", + " actionGroupName=agent_action_group_name,\n", + " functionSchema={\"functions\": agent_functions},\n", + " description=agent_action_group_description,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b7d27e07-7d4c-4c59-987d-41a606c6af65", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.8 Allow the Agent to invoke the Action Group Lambda" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3262f6dd-fb05-4351-904d-8097c0f52134", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Create allow to invoke permission on lambda\n", + "lambda_client = boto3.client(\"lambda\")\n", + "response = lambda_client.add_permission(\n", + " FunctionName=lambda_function_name,\n", + " StatementId=\"allow_bedrock\",\n", + " Action=\"lambda:InvokeFunction\",\n", + " Principal=\"bedrock.amazonaws.com\",\n", + " SourceArn=f\"arn:aws:bedrock:{region}:{account_id}:agent/{agent_id}\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2c8536a4-e625-464c-bae2-d28dba82c556", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.9 Associate the Knowledge Base to the agent" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95c7b781-7e82-47b6-a268-237fa97ab58d", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "response = bedrock_agent_client.associate_agent_knowledge_base(\n", + " agentId=agent_id,\n", + " agentVersion=\"DRAFT\",\n", + " description=\"Access the knowledge base when customers ask about the plates in the menu.\",\n", + " knowledgeBaseId=kb_id,\n", + " knowledgeBaseState=\"ENABLED\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b42a8caf-4c76-4760-a7cd-4c6b189f9f5b", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "### 2.10 Prepare the Agent and create an alias\n", + "\n", + "Let's create a DRAFT version of the agent that can be used for internal testing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4013c967-9a14-4689-b0b5-61aa4f75748c", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "response = bedrock_agent_client.prepare_agent(agentId=agent_id)\n", + "print(response)\n", + "# Pause to make sure agent is prepared\n", + "time.sleep(30)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b9889aaf-9238-479d-b9f1-2ab13b1ec9c0", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "response = bedrock_agent_client.create_agent_alias(\n", + " agentAliasName=\"TestAlias\",\n", + " agentId=agent_id,\n", + " description=\"Test alias\",\n", + ")\n", + "\n", + "alias_id = response[\"agentAlias\"][\"agentAliasId\"]\n", + "print(\"The Agent alias is:\", alias_id)\n", + "time.sleep(30)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ee4a530", + "metadata": {}, + "outputs": [], + "source": [ + "def invokeAgent(query, session_id, enable_trace=True, session_state=dict()):\n", + " end_session: bool = False\n", + "\n", + " # invoke the agent API\n", + " agentResponse = bedrock_agent_runtime_client.invoke_agent(\n", + " inputText=query,\n", + " agentId=agent_id,\n", + " agentAliasId=alias_id,\n", + " sessionId=session_id,\n", + " enableTrace=enable_trace,\n", + " endSession=end_session,\n", + " sessionState=session_state,\n", + " )\n", + "\n", + " event_stream = agentResponse[\"completion\"]\n", + " try:\n", + " traces = []\n", + " for event in event_stream:\n", + " if \"chunk\" in event:\n", + " data = event[\"chunk\"][\"bytes\"]\n", + " agent_answer = data.decode(\"utf8\")\n", + " end_event_received = True\n", + " return agent_answer, traces\n", + " # End event indicates that the request finished successfully\n", + " elif \"trace\" in event:\n", + " if enable_trace:\n", + " traces.append(event[\"trace\"])\n", + " else:\n", + " raise Exception(\"unexpected event.\", event)\n", + " return agent_answer, traces\n", + " except Exception as e:\n", + " raise Exception(\"unexpected event.\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "9ff4105c", + "metadata": {}, + "source": [ + "## 3. Defining the Ragas metrics" + ] + }, + { + "cell_type": "markdown", + "id": "9cfc3bb2", + "metadata": {}, + "source": [ + "Evaluating agents is different from testing traditional software, where you can simply verify whether the output matches expected results. These agents perform complex tasks that often have multiple valid approaches.\n", + "\n", + "Given their inherent autonomy, evaluating agents is essential to ensure they function properly.\n", + "\n", + "#### Choosing What to Evaluate in Your Agent\n", + "\n", + "Selecting evaluation metrics depends entirely on your use case. A good rule of thumb is to select metrics directly tied to user needs or metrics that clearly drive business value. In the restaurant agent example above, we want the agent to fulfill user requests without unnecessary repetition, provide helpful recommendations when appropriate to enhance customer experience, and maintain consistency with the brand tone.\n", + "\n", + "We’ll define metrics to evaluate these priorities. Ragas provides several user-defined metrics for evaluations.\n", + "\n", + "When defining evaluation criteria, focus on binary decisions or discrete classification scores rather than ambiguous scores. Binary or clear classifications compel you to explicitly define success criteria. Avoid metrics yielding scores between 0 and 100 without clear interpretation, as distinguishing between close scores like 87 and 91 can be challenging, especially when evaluations occur independently.\n", + "\n", + "Ragas includes metrics suited to such evaluations, and we will explore some of them in action:\n", + "- [**Aspect Critic Metric**](): Evaluates whether a submission follows user-defined criteria by leveraging LLM judgments to yield a binary outcome.\n", + "- [**Rubric Score Metric**](): Assesses responses against detailed, user-defined rubrics to consistently assign scores reflecting quality." + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "7e13a331", + "metadata": {}, + "outputs": [], + "source": [ + "from ragas.metrics import AspectCritic, RubricsScore\n", + "from ragas.dataset_schema import SingleTurnSample, MultiTurnSample, EvaluationDataset\n", + "from ragas import evaluate\n", + "\n", + "rubrics = {\n", + " \"score-1_description\": (\n", + " \"The item requested by the customer is not present in the menu and no recommendations were made.\"\n", + " ),\n", + " \"score0_description\": (\n", + " \"Either the item requested by the customer is present in the menu, or the conversation does not include any food or menu inquiry (e.g., booking, cancellation), \"\n", + " \"regardless of whether any recommendation was provided.\"\n", + " ),\n", + " \"score1_description\": (\n", + " \"The item requested by the customer is not present in the menu and a recommendation was provided.\"\n", + " ),\n", + "}\n", + "\n", + "recommendations = RubricsScore(rubrics=rubrics, llm=evaluator_llm, name=\"Recommendations\")\n", + "\n", + "\n", + "# Metric to evaluate if the AI fulfills all human requests completely.\n", + "request_completeness = AspectCritic(\n", + " name=\"Request Completeness\",\n", + " llm=evaluator_llm,\n", + " definition=(\n", + " \"Return 1 The agent completely fulfills all the user requests with no omissions. \"\n", + " \"otherwise, return 0.\"\n", + " ),\n", + ")\n", + "\n", + "# Metric to assess if the AI's communication aligns with the desired brand voice.\n", + "brand_tone = AspectCritic(\n", + " name=\"Brand Voice Metric\",\n", + " llm=evaluator_llm,\n", + " definition=(\n", + " \"Return 1 if the AI's communication is friendly, approachable, helpful, clear, and concise; \"\n", + " \"otherwise, return 0.\"\n", + " ),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ca5b4a45", + "metadata": {}, + "source": [ + "## 4. Evaluating Agent with Ragas" + ] + }, + { + "cell_type": "markdown", + "id": "5def6241", + "metadata": {}, + "source": [ + "In order to perform evaluations using Ragas, the traces need to be converted into the format recognized by Ragas. To convert an AWS Bedrock agent trace into a format suitable for Ragas evaluation, Ragas provides the function [convert_to_ragas_messages][ragas.integrations.swarm.convert_to_ragas_messages], which can be used to transform AWS Bedrock messages into the format expected by Ragas. You can read more about it [here]()." + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "1c3b4f99", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "Your booking for 2 people at 7pm on the 5th of May 2025 has been successfully created. Your booking ID is ca2fab70.\n", + "\n", + "\n", + "\n", + "\n", + "CPU times: user 38.3 ms, sys: 46.6 ms, total: 84.9 ms\n", + "Wall time: 10.6 s\n" + ] + } + ], + "source": [ + "%%time\n", + "import uuid\n", + "session_id:str = str(uuid.uuid1())\n", + "query = \"If you have children food then book a table for 2 people at 7pm on the 5th of May 2025.\"\n", + "agent_answer, traces_1 = invokeAgent(query, session_id)\n", + "\n", + "print(agent_answer)" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "3ac474b8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Your reservation was found and has been successfully canceled.\n", + "\n" + ] + } + ], + "source": [ + "query = \"Can you check if my previous booking? can you please delete the booking\"\n", + "agent_answer, traces_2 = invokeAgent(query, session_id)\n", + "\n", + "print(agent_answer)" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "3d181de4", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "710c7aeb02884fdf8a33a3abf0e425a7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Evaluating: 0%| | 0/4 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_inputRequest CompletenessBrand Voice Metric
0[{'content': '[{text=If you have children food...11
1[{'content': '[{text=If you have children food...11
\n", + "" + ], + "text/plain": [ + " user_input Request Completeness \\\n", + "0 [{'content': '[{text=If you have children food... 1 \n", + "1 [{'content': '[{text=If you have children food... 1 \n", + "\n", + " Brand Voice Metric \n", + "0 1 \n", + "1 1 " + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aws_bedrock import convert_to_ragas_messages\n", + "\n", + "# Convert AWS traces to messages accepted by RAGAS.\n", + "# The convert_to_ragas_messages function transforms AWS-specific trace data \n", + "# into a format that RAGAS can process as conversation messages.\n", + "ragas_messages_trace_1 = convert_to_ragas_messages(traces_1)\n", + "ragas_messages_trace_2 = convert_to_ragas_messages(traces_2)\n", + "\n", + "# Initialize MultiTurnSample objects.\n", + "# MultiTurnSample is a data type defined in RAGAS that encapsulates conversation\n", + "# data for multi-turn evaluation. This conversion is necessary to perform evaluations.\n", + "sample_1 = MultiTurnSample(user_input=ragas_messages_trace_1)\n", + "sample_2 = MultiTurnSample(user_input=ragas_messages_trace_2)\n", + "\n", + "result = evaluate(\n", + " # Create an evaluation dataset from the multi-turn samples\n", + " dataset=EvaluationDataset(samples=[sample_1, sample_2]),\n", + " metrics=[request_completeness, brand_tone],\n", + ")\n", + "\n", + "result.to_pandas()" + ] + }, + { + "cell_type": "markdown", + "id": "af8cd835", + "metadata": {}, + "source": [ + "The scores of 1 were awarded because the agent fully met all user requests without any omissions (completeness) and communicated in a friendly, approachable, helpful, clear, and concise manner (brand voice) for both the conversations." + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "496fcb43", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Yes, we serve Chicken Wings. Here are the details:\n", + "- **Buffalo Chicken Wings**: Classic buffalo wings served with celery sticks and blue cheese dressing. Allergens: Dairy (in blue cheese dressing), Gluten (in the coating), possible Soy (in the sauce).\n", + "\n", + "CPU times: user 34.3 ms, sys: 40.3 ms, total: 74.6 ms\n", + "Wall time: 8.14 s\n" + ] + } + ], + "source": [ + "%%time\n", + "import uuid\n", + "\n", + "session_id:str = str(uuid.uuid1())\n", + "query = \"Do you serve Chicken Wings?\"\n", + "\n", + "agent_answer, traces_3 = invokeAgent(query, session_id)\n", + "print(agent_answer)" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "581d481d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I'm sorry, but we do not have chocolate truffle cake on our dessert menu. However, we have several delicious alternatives you might enjoy:\n", + "\n", + "1. **Classic New York Cheesecake** - Creamy cheesecake with a graham cracker crust, topped with a choice of fruit compote or chocolate ganache.\n", + "2. **Apple Pie à la Mode** - Warm apple pie with a flaky crust, served with a scoop of vanilla ice cream and a drizzle of caramel sauce.\n", + "3. **Chocolate Lava Cake** - Rich and gooey chocolate cake with a molten center, dusted with powdered sugar and served with a scoop of raspberry sorbet.\n", + "4. **Pecan Pie Bars** - Buttery shortbread crust topped with a gooey pecan filling, cut into bars for easy serving.\n", + "5. **Banana Pudding Parfait** - Layers of vanilla pudding, sliced bananas, and vanilla wafers, topped with whipped cream and a sprinkle of crushed nuts.\n", + "\n", + "May I recommend the **Chocolate Lava Cake** for a decadent treat?\n", + "CPU times: user 27.9 ms, sys: 30.7 ms, total: 58.5 ms\n", + "Wall time: 10.9 s\n" + ] + } + ], + "source": [ + "%%time\n", + "session_id:str = str(uuid.uuid1())\n", + "query = \"For desserts, do you have chocolate truffle cake?\"\n", + "agent_answer, traces_4 = invokeAgent(query, session_id)\n", + "print(agent_answer)" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "a1471ddc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I could not find Indian food on our menu. However, we offer a variety of other cuisines including American, Italian, and vegetarian options. Would you like to know more about these options? \n", + "CPU times: user 24.1 ms, sys: 20 ms, total: 44.1 ms\n", + "Wall time: 6.55 s\n" + ] + } + ], + "source": [ + "%%time\n", + "from datetime import datetime\n", + "today = datetime.today().strftime('%b-%d-%Y')\n", + "\n", + "session_id:str = str(uuid.uuid1())\n", + "query = \"Do you have indian food?\"\n", + "session_state = {\n", + " \"promptSessionAttributes\": {\n", + " \"name\": \"John\",\n", + " \"today\": today\n", + " }\n", + "}\n", + "\n", + "agent_answer, traces_5 = invokeAgent(query, session_id, session_state=session_state)\n", + "print(agent_answer)" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "15590e19", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a6b520d376f345caa86ced0d9c3e2367", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Evaluating: 0%| | 0/3 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_inputRecommendations
0[{'content': '[{text=Do you serve Chicken Wing...0
1[{'content': '[{text=For desserts, do you have...1
2[{'content': '[{text=Do you have indian food?}...1
\n", + "" + ], + "text/plain": [ + " user_input Recommendations\n", + "0 [{'content': '[{text=Do you serve Chicken Wing... 0\n", + "1 [{'content': '[{text=For desserts, do you have... 1\n", + "2 [{'content': '[{text=Do you have indian food?}... 1" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aws_bedrock import convert_to_ragas_messages\n", + "\n", + "ragas_messages_trace_3 = convert_to_ragas_messages(traces_3)\n", + "ragas_messages_trace_4 = convert_to_ragas_messages(traces_4)\n", + "ragas_messages_trace_5 = convert_to_ragas_messages(traces_5)\n", + "\n", + "sample_3 = MultiTurnSample(user_input=ragas_messages_trace_3)\n", + "sample_4 = MultiTurnSample(user_input=ragas_messages_trace_4)\n", + "sample_5 = MultiTurnSample(user_input=ragas_messages_trace_5)\n", + "\n", + "result = evaluate(\n", + " dataset=EvaluationDataset(samples=[sample_3, sample_4, sample_5]),\n", + " metrics=[recommendations],\n", + ")\n", + "\n", + "result.to_pandas()" + ] + }, + { + "cell_type": "markdown", + "id": "851b63b1", + "metadata": {}, + "source": [ + "For the Recommendation metric, the chicken wings inquiry scored 0 since the item was available, while both the chocolate truffle cake and Indian food inquiries scored 1 because the requested items were not on the menu and alternative recommendations were provided." + ] + }, + { + "cell_type": "markdown", + "id": "9351a76f", + "metadata": {}, + "source": [ + "To evaluate how well our agent utilizes information retrieved from the knowledge base, we use the RAG evaluation metrics provided by Ragas. You can learn more about these metrics [here]().\n", + "\n", + "In this tutorial, we will use the following RAG metrics:\n", + "- [**ContextRelevance**](): Measures how well the retrieved contexts address the user’s query by evaluating their pertinence through dual LLM judgments.\n", + "- [**Faithfulness**](): Assesses the factual consistency of the response by determining whether all its claims can be supported by the provided retrieved contexts.\n", + "- [**ResponseGroundedness**](): Determines the extent to which each claim in the response is directly supported or “grounded” in the provided contexts." + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "457a10c4", + "metadata": {}, + "outputs": [], + "source": [ + "from ragas.metrics import ContextRelevance, Faithfulness, ResponseGroundedness\n", + "\n", + "metrics = [\n", + " ContextRelevance(llm=evaluator_llm),\n", + " Faithfulness(llm=evaluator_llm),\n", + " ResponseGroundedness(llm=evaluator_llm),\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "283cf8d4", + "metadata": {}, + "outputs": [], + "source": [ + "from aws_bedrock import extract_kb_call_trace\n", + "\n", + "kb_trace_3 = extract_kb_call_trace(traces_3)\n", + "kb_trace_4 = extract_kb_call_trace(traces_4)\n", + "\n", + "trace_3_single_turn_sample = SingleTurnSample(\n", + " user_input=kb_trace_3[0].get(\"user_input\"),\n", + " retrieved_contexts=kb_trace_3[0].get(\"retrieved_contexts\"),\n", + " response=kb_trace_3[0].get(\"response\"),\n", + " reference=\"Yes, we do serve chicken wings prepared in Buffalo style, chicken wing that’s typically deep-fried and then tossed in a tangy, spicy Buffalo sauce.\",\n", + ")\n", + "\n", + "trace_4_single_turn_sample = SingleTurnSample(\n", + " user_input=kb_trace_4[0].get(\"user_input\"),\n", + " retrieved_contexts=kb_trace_4[0].get(\"retrieved_contexts\"),\n", + " response=kb_trace_4[0].get(\"response\"),\n", + " reference=\"The desserts on the adult menu are:\\n1. Classic New York Cheesecake\\n2. Apple Pie à la Mode\\n3. Chocolate Lava Cake\\4. Pecan Pie Bars\\n5. Banana Pudding Parfait\",\n", + ")\n", + "\n", + "single_turn_samples = [trace_3_single_turn_sample, trace_4_single_turn_sample]\n", + "\n", + "dataset = EvaluationDataset(samples=single_turn_samples)" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "1b25e0a1", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ec1b31fd7e0d4938ad2cc38b8da268fd", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Evaluating: 0%| | 0/6 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_inputretrieved_contextsresponsereferencenv_context_relevancefaithfulnessnv_response_groundedness
0Chicken Wings[The Regrettable Experience -- Dinner Menu Ent...Yes, we serve Chicken Wings. Here are the deta...Yes, we do serve chicken wings prepared in Buf...1.01.001.0
1chocolate truffle cake[Allergens: Gluten (in the breading). 3. B...I'm sorry, but we do not have chocolate truffl...The desserts on the adult menu are:\\n1. Classi...0.00.750.5
\n", + "" + ], + "text/plain": [ + " user_input retrieved_contexts \\\n", + "0 Chicken Wings [The Regrettable Experience -- Dinner Menu Ent... \n", + "1 chocolate truffle cake [Allergens: Gluten (in the breading). 3. B... \n", + "\n", + " response \\\n", + "0 Yes, we serve Chicken Wings. Here are the deta... \n", + "1 I'm sorry, but we do not have chocolate truffl... \n", + "\n", + " reference nv_context_relevance \\\n", + "0 Yes, we do serve chicken wings prepared in Buf... 1.0 \n", + "1 The desserts on the adult menu are:\\n1. Classi... 0.0 \n", + "\n", + " faithfulness nv_response_groundedness \n", + "0 1.00 1.0 \n", + "1 0.75 0.5 " + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "kb_results = evaluate(dataset=dataset, metrics=metrics)\n", + "kb_results.to_pandas()" + ] + }, + { + "cell_type": "markdown", + "id": "63fde378", + "metadata": {}, + "source": [ + "Corrected Snippet:\n", + "\n", + "To evaluate whether the agent is able to achieve its goal, we can use the following metrics:\n", + "- [**AgentGoalAccuracyWithReference**](): Determines if the AI achieved the user’s goal by comparing its final outcome against an annotated ideal outcome, yielding a binary result.\n", + "- [**AgentGoalAccuracyWithoutReference**](): Infers whether the AI met the user’s goal solely based on conversational interactions, providing a binary success indicator without an explicit reference." + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "6d141ec5", + "metadata": {}, + "outputs": [], + "source": [ + "from ragas.metrics import (\n", + " AgentGoalAccuracyWithoutReference,\n", + " AgentGoalAccuracyWithReference,\n", + ")\n", + "\n", + "goal_accuracy_with_reference = AgentGoalAccuracyWithReference(llm=evaluator_llm)\n", + "goal_accuracy_without_reference = AgentGoalAccuracyWithoutReference(llm=evaluator_llm)" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "id": "d80f5d80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Here are the entrees available for children:\n", + "1. CHICKEN NUGGETS - Crispy chicken nuggets served with a side of ketchup or ranch dressing. Allergens: Gluten (in the coating), possible Soy. Suitable for Vegetarians: No\n", + "2. MACARONI AND CHEESE - Classic macaroni pasta smothered in creamy cheese sauce. Allergens: Dairy, Gluten. Suitable for Vegetarians: Yes\n", + "3. MINI CHEESE QUESADILLAS - Small flour tortillas filled with melted cheese, served with a mild salsa. Allergens: Dairy, Gluten. Suitable for Vegetarians: Yes\n", + "4. PEANUT BUTTER AND BANANA SANDWICH - Peanut butter and banana slices on whole wheat bread. Allergens: Nuts (peanut), Gluten. Suitable for Vegetarians: Yes (if using vegetarian peanut butter)\n", + "5. VEGGIE PITA POCKETS - Mini whole wheat pita pockets filled with hummus, cucumber, and cherry tomatoes. Allergens: Gluten, possible Soy. Suitable for Vegetarians: Yes\n", + "\n", + "\n", + "\n", + "CPU times: user 21.6 ms, sys: 28.1 ms, total: 49.7 ms\n", + "Wall time: 11.6 s\n" + ] + } + ], + "source": [ + "%%time\n", + "import uuid\n", + "\n", + "session_id:str = str(uuid.uuid1())\n", + "query = \"What entrees do you have for children?\"\n", + "\n", + "agent_answer, traces_6 = invokeAgent(query, session_id)\n", + "print(agent_answer)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce08fe21", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cece52e7cc054b399b41e083df347788", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Evaluating: 0%| | 0/1 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_inputreferenceagent_goal_accuracy
0[{'content': '[{text=What entrees do you have ...The final outcome provides child-friendly entr...1.0
\n", + "" + ], + "text/plain": [ + " user_input \\\n", + "0 [{'content': '[{text=What entrees do you have ... \n", + "\n", + " reference agent_goal_accuracy \n", + "0 The final outcome provides child-friendly entr... 1.0 " + ] + }, + "execution_count": 107, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from aws_bedrock import convert_to_ragas_messages\n", + "\n", + "ragas_messages_trace_6 = convert_to_ragas_messages(traces_6)\n", + "\n", + "sample_6 = MultiTurnSample(\n", + " user_input=ragas_messages_trace_6,\n", + " reference=\"Response contains entrees food items for the children.\",\n", + ")\n", + "\n", + "result = evaluate(\n", + " dataset=EvaluationDataset(samples=[sample_6]),\n", + " metrics=[goal_accuracy_with_reference],\n", + ")\n", + "\n", + "result.to_pandas()" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "a2b12d76", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b400d8aeecc14c4ab0cde031afa72c5e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Evaluating: 0%| | 0/1 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
user_inputagent_goal_accuracy
0[{'content': '[{text=What entrees do you have ...1.0
\n", + "" + ], + "text/plain": [ + " user_input agent_goal_accuracy\n", + "0 [{'content': '[{text=What entrees do you have ... 1.0" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sample_6 = MultiTurnSample(user_input=ragas_messages_trace_6)\n", + "\n", + "result = evaluate(\n", + " dataset=EvaluationDataset(samples=[sample_6]),\n", + " metrics=[goal_accuracy_without_reference],\n", + ")\n", + "\n", + "result.to_pandas()" + ] + }, + { + "cell_type": "markdown", + "id": "8bb5d818", + "metadata": {}, + "source": [ + "In both scenarios, the agent earned a score of 1 by comprehensively providing all available options—whether listing all children’s entrees." + ] + }, + { + "cell_type": "markdown", + "id": "8ebf4438-1f48-4642-a57c-530a16815064", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 5. Clean-up \n", + "Let's delete all the associated resources created to avoid unnecessary costs. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ed0cb4f-31ab-4535-a1d7-93e33c706b32", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "clean_up_resources(\n", + " table_name,\n", + " lambda_function,\n", + " lambda_function_name,\n", + " agent_action_group_response,\n", + " agent_functions,\n", + " agent_id,\n", + " kb_id,\n", + " alias_id,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "id": "6a9db157", + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Delete the agent roles and policies\n", + "delete_agent_roles_and_policies(agent_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17ec0e70", + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# delete KB\n", + "knowledge_base.delete_kb(delete_s3_bucket=True, delete_iam_roles_and_policies=True)" + ] + } + ], + "metadata": { + "availableInstances": [ + { + "_defaultOrder": 0, + "_isFastLaunch": true, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 4, + "name": "ml.t3.medium", + "vcpuNum": 2 + }, + { + "_defaultOrder": 1, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.t3.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 2, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.t3.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 3, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.t3.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 4, + "_isFastLaunch": true, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.m5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 5, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.m5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 6, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.m5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 7, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.m5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 8, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.m5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 9, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.m5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 10, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.m5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 11, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.m5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 12, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.m5d.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 13, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.m5d.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 14, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.m5d.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 15, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.m5d.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 16, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.m5d.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 17, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.m5d.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 18, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.m5d.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 19, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.m5d.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 20, + "_isFastLaunch": false, + "category": "General purpose", + "gpuNum": 0, + "hideHardwareSpecs": true, + "memoryGiB": 0, + "name": "ml.geospatial.interactive", + "supportedImageNames": [ + "sagemaker-geospatial-v1-0" + ], + "vcpuNum": 0 + }, + { + "_defaultOrder": 21, + "_isFastLaunch": true, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 4, + "name": "ml.c5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 22, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 8, + "name": "ml.c5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 23, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.c5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 24, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.c5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 25, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 72, + "name": "ml.c5.9xlarge", + "vcpuNum": 36 + }, + { + "_defaultOrder": 26, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 96, + "name": "ml.c5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 27, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 144, + "name": "ml.c5.18xlarge", + "vcpuNum": 72 + }, + { + "_defaultOrder": 28, + "_isFastLaunch": false, + "category": "Compute optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.c5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 29, + "_isFastLaunch": true, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.g4dn.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 30, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.g4dn.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 31, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.g4dn.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 32, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.g4dn.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 33, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.g4dn.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 34, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.g4dn.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 35, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 61, + "name": "ml.p3.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 36, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 244, + "name": "ml.p3.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 37, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 488, + "name": "ml.p3.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 38, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.p3dn.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 39, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.r5.large", + "vcpuNum": 2 + }, + { + "_defaultOrder": 40, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.r5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 41, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.r5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 42, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.r5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 43, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.r5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 44, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.r5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 45, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 512, + "name": "ml.r5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 46, + "_isFastLaunch": false, + "category": "Memory Optimized", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.r5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 47, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 16, + "name": "ml.g5.xlarge", + "vcpuNum": 4 + }, + { + "_defaultOrder": 48, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.g5.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 49, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 64, + "name": "ml.g5.4xlarge", + "vcpuNum": 16 + }, + { + "_defaultOrder": 50, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 128, + "name": "ml.g5.8xlarge", + "vcpuNum": 32 + }, + { + "_defaultOrder": 51, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 1, + "hideHardwareSpecs": false, + "memoryGiB": 256, + "name": "ml.g5.16xlarge", + "vcpuNum": 64 + }, + { + "_defaultOrder": 52, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 192, + "name": "ml.g5.12xlarge", + "vcpuNum": 48 + }, + { + "_defaultOrder": 53, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 4, + "hideHardwareSpecs": false, + "memoryGiB": 384, + "name": "ml.g5.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 54, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 768, + "name": "ml.g5.48xlarge", + "vcpuNum": 192 + }, + { + "_defaultOrder": 55, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 1152, + "name": "ml.p4d.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 56, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 8, + "hideHardwareSpecs": false, + "memoryGiB": 1152, + "name": "ml.p4de.24xlarge", + "vcpuNum": 96 + }, + { + "_defaultOrder": 57, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 32, + "name": "ml.trn1.2xlarge", + "vcpuNum": 8 + }, + { + "_defaultOrder": 58, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 512, + "name": "ml.trn1.32xlarge", + "vcpuNum": 128 + }, + { + "_defaultOrder": 59, + "_isFastLaunch": false, + "category": "Accelerated computing", + "gpuNum": 0, + "hideHardwareSpecs": false, + "memoryGiB": 512, + "name": "ml.trn1n.32xlarge", + "vcpuNum": 128 + } + ], + "instance_type": "ml.t3.medium", + "kernelspec": { + "display_name": "agents", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/integrations/aws_bedrock/aws_bedrock.py b/integrations/aws_bedrock/aws_bedrock.py new file mode 100644 index 0000000..6957727 --- /dev/null +++ b/integrations/aws_bedrock/aws_bedrock.py @@ -0,0 +1,134 @@ +import json +from ragas.messages import HumanMessage, AIMessage + + +def get_last_orchestration_value(traces, key): + """ + Iterates through the traces to find the last occurrence of a specified key + within the orchestrationTrace. + + Returns: + (index, value): Tuple where index is the last index at which the key was found, + and value is the corresponding value, or (None, None) if not found. + """ + last_index = -1 + last_value = None + for i, trace in enumerate(traces): + orchestration = trace.get("trace", {}).get("orchestrationTrace", {}) + if key in orchestration: + last_index = i + last_value = orchestration[key] + return last_index, last_value + + +def extract_messages_from_model_invocation(model_inv): + """ + Extracts messages from the 'text' field of the modelInvocationInput. + Ensures that each message's content is cast to a string. + + Returns: + List of messages as HumanMessage or AIMessage objects. + """ + messages = [] + text_json = json.loads(model_inv.get("text", "{}")) + for msg in text_json.get("messages", []): + content_str = str(msg.get("content", "")) + role = msg.get("role") + if role == "user": + messages.append(HumanMessage(content=content_str)) + elif role == "assistant": + messages.append(AIMessage(content=content_str)) + return messages[:-1] + + +def convert_to_ragas_messages(traces): + """ + Converts a list of trace dictionaries into a list of messages. + It extracts messages from the last modelInvocationInput and appends + the finalResponse from the observation (if it occurs after the model invocation). + + Returns: + List of HumanMessage and AIMessage objects. + """ + result = [] + + # Get the last modelInvocationInput from the traces. + last_model_inv_index, last_model_inv = get_last_orchestration_value( + traces, "modelInvocationInput" + ) + if last_model_inv is not None: + result.extend(extract_messages_from_model_invocation(last_model_inv)) + + # Get the last observation from the traces. + last_obs_index, last_observation = get_last_orchestration_value( + traces, "observation" + ) + if last_observation is not None and last_obs_index > last_model_inv_index: + final_text = str(last_observation.get("finalResponse", {}).get("text", "")) + result.append(AIMessage(content=final_text)) + + return result + + +def extract_kb_call_trace(logs): + """ + Extracts groups of logs that follow the specific order: + 1. An element with 'trace' -> 'orchestrationTrace' containing an 'invocationInput' + with invocationType == "KNOWLEDGE_BASE" + 2. Followed (later in the list or within the same log) by an element with an 'observation' + that contains 'knowledgeBaseLookupOutput' + 3. Followed by an element with an 'observation' that contains 'finalResponse' + + Returns a list of dictionaries each with keys: + 'user_input', 'retrieved_contexts', and 'response' + + This version supports multiple knowledge base invocation groups. + """ + results = [] + groups_in_progress = [] # list to keep track of groups in progress + + for log in logs: + orchestration = log.get("trace", {}).get("orchestrationTrace", {}) + + # 1. Look for a KB invocation input. + inv_input = orchestration.get("invocationInput") + if inv_input and inv_input.get("invocationType") == "KNOWLEDGE_BASE": + kb_input = inv_input.get("knowledgeBaseLookupInput", {}) + # Start a new group with the user's input text. + groups_in_progress.append({"user_input": kb_input.get("text")}) + + # 2. Process observations. + obs = orchestration.get("observation", {}) + if obs: + # If the observation contains a KB output, assign it to the earliest group + # that does not yet have a 'retrieved_contexts' key. + if "knowledgeBaseLookupOutput" in obs: + for group in groups_in_progress: + if "user_input" in group and "retrieved_contexts" not in group: + kb_output = obs["knowledgeBaseLookupOutput"] + group["retrieved_contexts"] = [ + retrieved.get("content", {}).get("text") + for retrieved in kb_output.get("retrievedReferences", []) + ] + break + + # 3. When we see a final response, assign it to all groups that have already + # received their KB output but still lack a response. + if "finalResponse" in obs: + final_text = obs["finalResponse"].get("text") + completed_groups = [] + for group in groups_in_progress: + if ( + "user_input" in group + and "retrieved_contexts" in group + and "response" not in group + ): + group["response"] = final_text + completed_groups.append(group) + # Remove completed groups from the in-progress list and add to the final results. + groups_in_progress = [ + g for g in groups_in_progress if g not in completed_groups + ] + results.extend(completed_groups) + + return results diff --git a/integrations/aws_bedrock/dataset/Restaurant_Childrens_Menu.pdf b/integrations/aws_bedrock/dataset/Restaurant_Childrens_Menu.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9c27571704f8c41d7bf1d686e98312890f780807 GIT binary patch literal 31015 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 literal 0 HcmV?d00001 diff --git a/integrations/aws_bedrock/dataset/Restaurant_Dinner_Menu.pdf b/integrations/aws_bedrock/dataset/Restaurant_Dinner_Menu.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ce31d431d7cea5f87f87cd5bcac5e371192e2909 GIT binary patch literal 31084 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