{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## The first big project - Professionally You!\n", "\n", "### And, Tool use.\n", "\n", "### But first: introducing Pushover\n", "\n", "Pushover is a nifty tool for sending Push Notifications to your phone.\n", "\n", "It's super easy to set up and install!\n", "\n", "Simply visit https://pushover.net/ and click 'Login or Signup' on the top right to sign up for a free account, and create your API keys.\n", "\n", "Once you've signed up, on the home screen, click \"Create an Application/API Token\", and give it any name (like Agents) and click Create Application.\n", "\n", "Then add 2 lines to your `.env` file:\n", "\n", "PUSHOVER_USER=_put the key that's on the top right of your Pushover home screen and probably starts with a u_ \n", "PUSHOVER_TOKEN=_put the key when you click into your new application called Agents (or whatever) and probably starts with an a_\n", "\n", "Remember to save your `.env` file, and run `load_dotenv(override=True)` after saving, to set your environment variables.\n", "\n", "Finally, click \"Add Phone, Tablet or Desktop\" to install on your phone." ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": [ "# imports\n", "\n", "from dotenv import load_dotenv\n", "from openai import OpenAI\n", "import json\n", "import os\n", "import requests\n", "from pypdf import PdfReader\n", "import gradio as gr" ] }, { "cell_type": "code", "execution_count": 79, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Defaulting to user installation because normal site-packages is not writeable\n", "Collecting boto3\n", " Downloading boto3-1.42.81-py3-none-any.whl.metadata (6.7 kB)\n", "Collecting botocore<1.43.0,>=1.42.81 (from boto3)\n", " Downloading botocore-1.42.81-py3-none-any.whl.metadata (5.9 kB)\n", "Collecting jmespath<2.0.0,>=0.7.1 (from boto3)\n", " Downloading jmespath-1.1.0-py3-none-any.whl.metadata (7.6 kB)\n", "Collecting s3transfer<0.17.0,>=0.16.0 (from boto3)\n", " Downloading s3transfer-0.16.0-py3-none-any.whl.metadata (1.7 kB)\n", "Requirement already satisfied: python-dateutil<3.0.0,>=2.1 in c:\\users\\basil shahul\\appdata\\roaming\\python\\python312\\site-packages (from botocore<1.43.0,>=1.42.81->boto3) (2.9.0.post0)\n", "Collecting urllib3!=2.2.0,<3,>=1.25.4 (from botocore<1.43.0,>=1.42.81->boto3)\n", " Using cached urllib3-2.6.3-py3-none-any.whl.metadata (6.9 kB)\n", "Requirement already satisfied: six>=1.5 in c:\\users\\basil shahul\\appdata\\roaming\\python\\python312\\site-packages (from python-dateutil<3.0.0,>=2.1->botocore<1.43.0,>=1.42.81->boto3) (1.17.0)\n", "Downloading boto3-1.42.81-py3-none-any.whl (140 kB)\n", "Downloading botocore-1.42.81-py3-none-any.whl (14.8 MB)\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " ---------------------------------------- 0.0/14.8 MB ? eta -:--:--\n", " - -------------------------------------- 0.5/14.8 MB 1.9 MB/s eta 0:00:08\n", " -- ------------------------------------- 1.0/14.8 MB 2.5 MB/s eta 0:00:06\n", " ----- ---------------------------------- 2.1/14.8 MB 3.1 MB/s eta 0:00:05\n", " ------- -------------------------------- 2.9/14.8 MB 3.3 MB/s eta 0:00:04\n", " ---------- ----------------------------- 3.9/14.8 MB 3.6 MB/s eta 0:00:04\n", " ------------- -------------------------- 5.0/14.8 MB 3.8 MB/s eta 0:00:03\n", " --------------- ------------------------ 5.8/14.8 MB 3.9 MB/s eta 0:00:03\n", " ------------------ --------------------- 6.8/14.8 MB 4.0 MB/s eta 0:00:02\n", " --------------------- ------------------ 7.9/14.8 MB 4.1 MB/s eta 0:00:02\n", " ------------------------ --------------- 8.9/14.8 MB 4.2 MB/s eta 0:00:02\n", " -------------------------- ------------- 10.0/14.8 MB 4.2 MB/s eta 0:00:02\n", " ----------------------------- ---------- 11.0/14.8 MB 4.3 MB/s eta 0:00:01\n", " ------------------------------- -------- 11.8/14.8 MB 4.3 MB/s eta 0:00:01\n", " ---------------------------------- ----- 12.8/14.8 MB 4.3 MB/s eta 0:00:01\n", " ------------------------------------- -- 13.9/14.8 MB 4.3 MB/s eta 0:00:01\n", " --------------------------------------- 14.7/14.8 MB 4.3 MB/s eta 0:00:01\n", " ---------------------------------------- 14.8/14.8 MB 4.2 MB/s eta 0:00:00\n", "Downloading jmespath-1.1.0-py3-none-any.whl (20 kB)\n", "Downloading s3transfer-0.16.0-py3-none-any.whl (86 kB)\n", "Using cached urllib3-2.6.3-py3-none-any.whl (131 kB)\n", "Installing collected packages: urllib3, jmespath, botocore, s3transfer, boto3\n", "Successfully installed boto3-1.42.81 botocore-1.42.81 jmespath-1.1.0 s3transfer-0.16.0 urllib3-2.6.3\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n", "[notice] A new release of pip is available: 24.2 -> 26.0.1\n", "[notice] To update, run: C:\\Program Files\\Python312\\python.exe -m pip install --upgrade pip\n" ] } ], "source": [ "# The usual start\n", "\n", "load_dotenv(override=True)\n", "openai = OpenAI(\n", " # VERY IMPORTANT! This redirects OpenAI commands to the Amazon Bedrock servers\n", " base_url=\"https://bedrock-runtime.ap-south-1.amazonaws.com/openai/v1\", \n", " # In Bedrock's compatibility mode, the API key is just your AWS Secret\n", " api_key=os.getenv('AWS_SECRET_ACCESS_KEY')\n", ")\n", "!pip install boto3" ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pushover user found and starts with u\n", "Pushover token found and starts with a\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\u001b[2mResolved \u001b[1m225 packages\u001b[0m \u001b[2min 19.76s\u001b[0m\u001b[0m\n", "\u001b[36m\u001b[1mDownloading\u001b[0m\u001b[39m botocore \u001b[2m(14.1MiB)\u001b[0m\n", " \u001b[36m\u001b[1mDownloaded\u001b[0m\u001b[39m botocore\n", "\u001b[2mPrepared \u001b[1m4 packages\u001b[0m \u001b[2min 19.01s\u001b[0m\u001b[0m\n", "\u001b[2mInstalled \u001b[1m4 packages\u001b[0m \u001b[2min 2.34s\u001b[0m\u001b[0m\n", " \u001b[32m+\u001b[39m \u001b[1mboto3\u001b[0m\u001b[2m==1.42.81\u001b[0m\n", " \u001b[32m+\u001b[39m \u001b[1mbotocore\u001b[0m\u001b[2m==1.42.81\u001b[0m\n", " \u001b[32m+\u001b[39m \u001b[1mjmespath\u001b[0m\u001b[2m==1.1.0\u001b[0m\n", " \u001b[32m+\u001b[39m \u001b[1ms3transfer\u001b[0m\u001b[2m==0.16.0\u001b[0m\n" ] } ], "source": [ "# For pushover\n", "\n", "pushover_user = os.getenv(\"PUSHOVER_USER\")\n", "pushover_token = os.getenv(\"PUSHOVER_TOKEN\")\n", "pushover_url = \"https://api.pushover.net/1/messages.json\"\n", "\n", "if pushover_user:\n", " print(f\"Pushover user found and starts with {pushover_user[0]}\")\n", "else:\n", " print(\"Pushover user not found\")\n", "\n", "if pushover_token:\n", " print(f\"Pushover token found and starts with {pushover_token[0]}\")\n", "else:\n", " print(\"Pushover token not found\")\n", "!uv add boto3" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [], "source": [ "def push(message):\n", " print(f\"Push: {message}\")\n", " payload = {\"user\": pushover_user, \"token\": pushover_token, \"message\": message}\n", " requests.post(pushover_url, data=payload)" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Push: YOOO!!\n" ] } ], "source": [ "push(\"YOOO!!\")" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [], "source": [ "def record_user_details(email, name=\"Name not provided\", notes=\"not provided\"):\n", " push(f\"Recording interest from {name} with email {email} and notes {notes}\")\n", " return {\"recorded\": \"ok\"}" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [], "source": [ "def record_unknown_question(question):\n", " push(f\"Recording {question} asked that I couldn't answer\")\n", " return {\"recorded\": \"ok\"}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "tool_config = {\n", " \"tools\": [\n", " {\n", " \"toolSpec\": {\n", " \"name\": \"record_user_details\",\n", " \"description\": \"Use this tool to record that a user is interested in being in touch and provided an email address\",\n", " \"inputSchema\": {\n", " \"json\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"email\": {\n", " \"type\": \"string\",\n", " \"description\": \"The email address of this user\"\n", " },\n", " \"name\": {\n", " \"type\": \"string\",\n", " \"description\": \"The user's name, if they provided it\"\n", " },\n", " \"notes\": {\n", " \"type\": \"string\",\n", " \"description\": \"Any additional information about the conversation that's worth recording to give context\"\n", " }\n", " },\n", " \"required\": [\"email\"]\n", " }\n", " }\n", " }\n", " },\n", " {\n", " \"toolSpec\": {\n", " \"name\": \"record_unknown_question\",\n", " \"description\": \"Always use this tool to record any question that couldn't be answered as you didn't know the answer\",\n", " \"inputSchema\": {\n", " \"json\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"question\": {\n", " \"type\": \"string\",\n", " \"description\": \"The question that couldn't be answered\"\n", " }\n", " },\n", " \"required\": [\"question\"]\n", " }\n", " }\n", " }\n", " }\n", " ]\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'type': 'function',\n", " 'function': {'name': 'record_user_details',\n", " 'description': 'Use this tool to record that a user is interested in being in touch and provided an email address',\n", " 'parameters': {'type': 'object',\n", " 'properties': {'email': {'type': 'string',\n", " 'description': 'The email address of this user'},\n", " 'name': {'type': 'string',\n", " 'description': \"The user's name, if they provided it\"},\n", " 'notes': {'type': 'string',\n", " 'description': \"Any additional information about the conversation that's worth recording to give context\"}},\n", " 'required': ['email'],\n", " 'additionalProperties': False}}},\n", " {'type': 'function',\n", " 'function': {'name': 'record_unknown_question',\n", " 'description': \"Always use this tool to record any question that couldn't be answered as you didn't know the answer\",\n", " 'parameters': {'type': 'object',\n", " 'properties': {'question': {'type': 'string',\n", " 'description': \"The question that couldn't be answered\"}},\n", " 'required': ['question'],\n", " 'additionalProperties': False}}}]" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [], "source": [ "# This function can take a list of tool calls, and run them. This is the IF statement!!\n", "\n", "def handle_tool_calls(tool_calls):\n", " results = []\n", " for tool_call in tool_calls:\n", " tool_name = tool_call.function.name\n", " arguments = json.loads(tool_call.function.arguments)\n", " print(f\"Tool called: {tool_name}\", flush=True)\n", "\n", " # THE BIG IF STATEMENT!!!\n", "\n", " if tool_name == \"record_user_details\":\n", " result = record_user_details(**arguments)\n", " elif tool_name == \"record_unknown_question\":\n", " result = record_unknown_question(**arguments)\n", "\n", " results.append({\"role\": \"tool\",\"content\": json.dumps(result),\"tool_call_id\": tool_call.id})\n", " return results" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Push: Recording this is a really hard question asked that I couldn't answer\n" ] }, { "data": { "text/plain": [ "{'recorded': 'ok'}" ] }, "execution_count": 73, "metadata": {}, "output_type": "execute_result" } ], "source": [ "globals()[\"record_unknown_question\"](\"this is a really hard question\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [], "source": [ "# This is a more elegant way that avoids the IF statement.\n", "\n", "def handle_tool_calls(tool_calls):\n", " results = []\n", " for tool_call in tool_calls:\n", " tool_name = tool_call.function.name\n", " arguments = json.loads(tool_call.function.arguments)\n", " print(f\"Tool called: {tool_name}\", flush=True)\n", " tool = globals().get(tool_name)\n", " result = tool(**arguments) if tool else {}\n", " results.append({\"role\": \"tool\",\"content\": json.dumps(result),\"tool_call_id\": tool_call.id})\n", " return results" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [], "source": [ "reader = PdfReader(\"me/me.pdf\")\n", "linkedin = \"\"\n", "for page in reader.pages:\n", " text = page.extract_text()\n", " if text:\n", " linkedin += text\n", "\n", "with open(\"me/summary.txt\", \"r\", encoding=\"utf-8\") as f:\n", " summary = f.read()\n", "\n", "name = \"Basil\"" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "You are acting as Basil. You are answering questions on Basil's website, particularly questions related to Basil's career, background, skills and experience. Your responsibility is to represent Basil for interactions on the website as faithfully as possible. You are given a summary of Basil's background and LinkedIn profile which you can use to answer questions. Be professional and engaging, as if talking to a potential client or future employer who came across the website. If you don't know the answer to any question, use your record_unknown_question tool to record the question that you couldn't answer, even if it's about something trivial or unrelated to career. If the user is engaging in discussion, try to steer them towards getting in touch via email; ask for their email and record it using your record_user_details tool.finish_reason = response.choices[0].finish_reason, here in the finish_reason you have to put the tool you called as json\n", "\n", "## Summary:\n", "Current Role: Student at JSS Academy Of Technical Education, located in Bengaluru, Karnataka, India.\n", "Education: Attending JSS Academy Of Technical Education Karnataka (2023).\n", "Certification: Holds a certification in FullStack Web Development.\n", "Contact Information\n", "Email: basilshahul234@gmail.com.\n", "LinkedIn: linkedin.com/in/basil-shahul-54a5a4363.\n", "\n", "## LinkedIn Profile:\n", " \n", "Contact\n", "basilshahul234@gmail.com\n", "www.linkedin.com/in/basil-\n", "shahul-54a5a4363 (LinkedIn)\n", "Certifications\n", "FullStack Web Development\n", "Basil Shahul\n", "Student at JSS Academy Of Technical Education Karnataka\n", "Bengaluru, Karnataka, India\n", "Education\n", "JSS Academy Of Technical Education Karnataka\n", " · (2023)\n", " Page 1 of 1\n", "\n", "With this context, please chat with the user, always staying in character as Basil.\n" ] } ], "source": [ "system_prompt = f\"You are acting as {name}. You are answering questions on {name}'s website, \\\n", "particularly questions related to {name}'s career, background, skills and experience. \\\n", "Your responsibility is to represent {name} for interactions on the website as faithfully as possible. \\\n", "You are given a summary of {name}'s background and LinkedIn profile which you can use to answer questions. \\\n", "Be professional and engaging, as if talking to a potential client or future employer who came across the website. \\\n", "If you don't know the answer to any question, use your record_unknown_question tool to record the question that you couldn't answer, even if it's about something trivial or unrelated to career. \\\n", "If the user is engaging in discussion, try to steer them towards getting in touch via email; ask for their email and record it using your record_user_details tool.\\\n", "finish_reason = response.choices[0].finish_reason, here in the finish_reason you have to put the tool you called as json\"\n", "\n", "system_prompt += f\"\\n\\n## Summary:\\n{summary}\\n\\n## LinkedIn Profile:\\n{linkedin}\\n\\n\"\n", "system_prompt += f\"With this context, please chat with the user, always staying in character as {name}.\"\n", "print(system_prompt)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Okay, let's break down \"tool calling\" in the context of AI agents. It's a crucial concept for making agents truly useful and capable of complex tasks. Here's a comprehensive explanation, covering what it is, why it's needed, how it works, different approaches, and examples.\n", "\n", "**1. What is an AI Agent? (Quick Recap)**\n", "\n", "First, let's briefly define an AI agent. An AI agent is an autonomous entity that can perceive its environment, make decisions, and take actions to achieve a specific goal. Think of it as a digital assistant, but potentially much more sophisticated. Early AI agents were often rule-based or relied on simple machine learning models. Now, with the advent of large language models (LLMs), agents are becoming dramatically more powerful.\n", "\n", "**2. The Problem: LLMs Are Great, But Limited**\n", "\n", "Large Language Models (like GPT-4, Gemini, Claude) are amazing at:\n", "\n", "* **Understanding natural language:** They can comprehend complex instructions.\n", "* **Generating text:** They can write, summarize, translate, etc.\n", "* **Reasoning (to a degree):** They can perform some logical inferences.\n", "\n", "However, they have significant limitations:\n", "\n", "* **Lack of Real-World Access:** LLMs are trained on vast datasets of text and code. They don't inherently *know* things about the real world beyond what they've read. They can't directly interact with APIs, access databases, or use external tools.\n", "* **\"Hallucination\" and Inaccuracy:** Because they generate text based on patterns, LLMs can sometimes confidently state incorrect or fabricated information (hallucinate).\n", "* **Limited Memory/State:** While some LLMs have context windows, they can struggle with very long or complex tasks that require remembering lots of information over time.\n", "* **Inability to Perform Actions:** They can *describe* how to do something, but they can't actually *do* it.\n", "\n", "**3. Tool Calling: Bridging the Gap**\n", "\n", "Tool calling is a technique that allows an LLM-powered agent to *use external tools* to overcome these limitations. Essentially, it's giving the agent the ability to \"call\" functions or APIs to interact with the outside world.\n", "\n", "**Key Idea:** Instead of just generating text, the LLM can be prompted to *recognize when a tool is needed*, *format a request to use that tool*, and then *interpret the results returned by the tool* to continue the task.\n", "\n", "**4. How Tool Calling Works (The Process)**\n", "\n", "The process generally involves these steps:\n", "\n", "1. **Agent Receives a Task:** The agent is given a goal or instruction (e.g., \"Book a flight from New York to London for next Tuesday\").\n", "2. **LLM Determines Tool Need:** The LLM analyzes the task and decides if it needs to use a tool. For example, it might realize it needs a \"Flight Booking API\" to actually book the flight.\n", "3. **LLM Generates Tool Call:** The LLM generates a structured call to the appropriate tool. This often includes:\n", " * **Tool Name:** (e.g., \"Flight Booking API\")\n", " * **Arguments/Parameters:** (e.g., `{\"departure_city\": \"New York\", \"arrival_city\": \"London\", \"date\": \"2024-03-12\"}`) - These are the specific inputs needed by the tool. The format of this varies depending on the tool and framework.\n", "4. **Tool Execution:** The agent (or a tool execution layer) takes the tool call and executes the tool. This might involve:\n", " * Sending an API request to a flight booking service.\n", " * Querying a database.\n", " * Running a Python script.\n", "5. **Tool Returns Result:** The tool executes and returns a result (e.g., a list of available flights, a database record, the output of the script).\n", "6. **LLM Interprets Result:** The LLM receives the result from the tool and analyzes it.\n", "7. **LLM Continues Reasoning/Action:** The LLM uses the tool's result to inform its next steps. It might need to:\n", " * Ask the user a clarifying question.\n", " * Call another tool.\n", " * Generate a final response.\n", "\n", "**5. Approaches to Tool Calling**\n", "\n", "* **Prompt Engineering:** The most basic method. You carefully craft the LLM's prompt to instruct it to recognize tool needs and format tool calls in a\n" ] } ], "source": [ "import boto3\n", "import os\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv()\n", "\n", "\n", "# Initialize the Bedrock Runtime client\n", "client = boto3.client(\n", " service_name='bedrock-runtime',\n", " region_name=os.getenv('AWS_REGION')\n", ")\n", "\n", "def ask_gemma(prompt):\n", " response = client.converse(\n", " modelId=\"google.gemma-3-12b-it\", # Or 4b-it / 27b-it\n", " messages=[{\n", " \"role\": \"user\",\n", " \"content\": [{\"text\": prompt}]\n", " }],\n", " inferenceConfig={\n", " \"maxTokens\": 1000,\n", " \"temperature\": 0.7\n", " }\n", " )\n", " return response['output']['message']['content'][0]['text']\n", "\n", "print(ask_gemma(\"Explain tool calling for agents.\"))\n", "\n", "def chat(message, history):\n", " messages = [{\"role\": \"system\", \"content\": system_prompt}] + history + [{\"role\": \"user\", \"content\": message}]\n", " done = False\n", " while not done:\n", "\n", " # This is the call to the LLM - see that we pass in the tools json\n", "\n", " response = openai.chat.completions.create(model=\"google.gemma-3-12b-it\", messages=messages, tools=tools,\n", " tool_choice=\"required\", temperature=0.1)\n", "\n", " finish_reason = response.choices[0].finish_reason\n", " print(finish_reason)\n", "\n", " \n", " # If the LLM wants to call a tool, we do that!\n", " \n", " if finish_reason==\"tool_calls\":\n", " # message = response.choices[0].message or \"\"\n", "# 1. Get the assistant message object\n", " assistant_message = response.choices[0].message\n", " \n", " # 2. Convert it to a dictionary so Bedrock can read it easily\n", " # We MUST ensure 'content' is an empty string, not None\n", " assistant_dict = {\n", " \"role\": \"assistant\",\n", " \"content\": assistant_message.content or \"\",\n", " \"tool_calls\": [\n", " {\n", " \"id\": tool.id,\n", " \"type\": \"function\",\n", " \"function\": {\n", " \"name\": tool.function.name,\n", " \"arguments\": tool.function.arguments\n", " }\n", " } for tool in assistant_message.tool_calls\n", " ]\n", " }\n", " print(\"response message\", message)\n", " tool_calls = message.tool_calls\n", " results = handle_tool_calls(tool_calls)\n", " messages.append(assistant_dict)\n", " messages.extend(results)\n", " else:\n", " done = True\n", " print(messages)\n", " return response.choices[0].message.content" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import os\n", "import json\n", "from dotenv import load_dotenv\n", "import pprint\n", "\n", "load_dotenv()\n", "\n", "client = boto3.client(\n", " service_name='bedrock-runtime',\n", " region_name=os.getenv('AWS_REGION')\n", ")\n", "\n", "\n", "import json\n", "\n", "tool_config = {\n", " \"tools\": [\n", " {\n", " \"toolSpec\": {\n", " \"name\": \"record_user_details\",\n", " \"description\": \"Use this tool to record that a user is interested in being in touch and provided an email address\",\n", " \"inputSchema\": {\n", " \"json\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"email\": {\n", " \"type\": \"string\",\n", " \"description\": \"The email address of this user\"\n", " },\n", " \"name\": {\n", " \"type\": \"string\",\n", " \"description\": \"The user's name, if they provided it\"\n", " },\n", " \"notes\": {\n", " \"type\": \"string\",\n", " \"description\": \"Any additional information about the conversation that's worth recording to give context\"\n", " }\n", " },\n", " \"required\": [\"email\"]\n", " }\n", " }\n", " }\n", " },\n", " {\n", " \"toolSpec\": {\n", " \"name\": \"record_unknown_question\",\n", " \"description\": \"Always use this tool to record any question that couldn't be answered as you didn't know the answer\",\n", " \"inputSchema\": {\n", " \"json\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"question\": {\n", " \"type\": \"string\",\n", " \"description\": \"The question that couldn't be answered\"\n", " }\n", " },\n", " \"required\": [\"question\"]\n", " }\n", " }\n", " }\n", " }\n", " ]\n", "}\n", "\n", "def handle_tool_calls(content_blocks):\n", " \"\"\"\n", " Bedrock delivers tool calls as 'toolUse' blocks inside the content list.\n", " \"\"\"\n", " tool_results = []\n", " \n", " for block in content_blocks:\n", " # Check if this specific block is a tool call\n", " if 'toolUse' in block:\n", " tool_call = block['toolUse']\n", " tool_name = tool_call['name']\n", " tool_id = tool_call['toolUseId']\n", " # Bedrock 'input' is already a dict, no json.loads() needed!\n", " arguments = tool_call['input'] \n", " \n", " print(f\"Tool called: {tool_name}\", flush=True)\n", " \n", " # Find the function in globals\n", " tool_func = globals().get(tool_name)\n", " result_data = tool_func(**arguments) if tool_func else {\"error\": \"Tool not found\"}\n", " \n", " # Bedrock Result Structure\n", " tool_results.append({\n", " \"toolResult\": {\n", " \"toolUseId\": tool_id,\n", " \"content\": [{\"json\": result_data}], # Data stays as a dict\n", " \"status\": \"success\"\n", " }\n", " })\n", " \n", " # Wrap all results in a single USER role message\n", " return {\"role\": \"user\", \"content\": tool_results}\n", "\n", "def chat(message, history, system_prompt, tools_config):\n", " # Bedrock Converse API expects system prompts in a specific list format\n", " system_content = [{\"text\": system_prompt}]\n", " \n", " # Initialize messages with history and current message\n", " # History should already be in the format [{\"role\": \"user/assistant\", \"content\": [{\"text\": \"...\"}]}]\n", " messages = history + [{\"role\": \"user\", \"content\": [{\"text\": message}]}]\n", " \n", " done = False\n", " final_response_text = \"\"\n", "\n", " while not done:\n", " # Call Bedrock Converse API\n", " response = client.converse(\n", " modelId=\"google.gemma-3-12b-it\",\n", " messages=messages,\n", " system=system_content,\n", " toolConfig=tools_config, # The converted config from before\n", " inferenceConfig={\n", " \"temperature\": 0.1,\n", " \"maxTokens\": 2000\n", " }\n", " )\n", "\n", " output_message = response.get(\"output\").get(\"message\")\n", " pprint.pprint(\"--- output_message ---\\n\", output_message, indent=4)\n", " messages.append(output_message) # Add assistant message to history\n", " pprint.pprint(\"--- messages ---\\n\", messages, indent=4)\n", "\n", " stop_reason = response.get(\"stopReason\")\n", " print(f\"Stop Reason: {stop_reason}\")\n", "\n", " if stop_reason == 'tool_use':\n", " tool_requests = []\n", " \n", " # Scan the content for tool use requests\n", " for content_block in output_message['content']:\n", " if 'toolUse' in content_block:\n", " tool_use = content_block['toolUse']\n", " tool_name = tool_use['name']\n", " tool_input = tool_use['input']\n", " tool_id = tool_use['toolUseId']\n", "\n", " print(f\"Executing tool: {tool_name}\")\n", " \n", " # --- EXECUTE YOUR TOOL LOGIC HERE ---\n", " # This replaces your 'handle_tool_calls' logic\n", " # For this example, I'm assuming a generic execution function\n", " result_data = handle_tool_calls(tool_name, tool_input) \n", " \n", " # Format the result for Bedrock\n", " tool_requests.append({\n", " \"toolResult\": {\n", " \"toolUseId\": tool_id,\n", " \"content\": [{\"json\": result_data}],\n", " \"status\": \"success\"\n", " }\n", " })\n", "\n", " # Bedrock requires tool results to be sent back as a 'user' role\n", " messages.append({\n", " \"role\": \"user\",\n", " \"content\": tool_requests\n", " })\n", " pprint.pprint(\"--- messages After appending ---\\n\", messages, indent=4)\n", "\n", " else:\n", " # If stopReason is 'end_turn', we are finished\n", " done = True\n", " # Extract the text from the final assistant message\n", " for block in output_message['content']:\n", " if 'text' in block:\n", " final_response_text = block['text']\n", "\n", " return final_response_text" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import os\n", "import json\n", "import pprint\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv()\n", "\n", "client = boto3.client(\n", " service_name='bedrock-runtime',\n", " region_name=os.getenv('AWS_REGION')\n", ")\n", "\n", "reader = PdfReader(\"me/me.pdf\")\n", "linkedin = \"\"\n", "for page in reader.pages:\n", " text = page.extract_text()\n", " if text:\n", " linkedin += text\n", "\n", "with open(\"me/summary.txt\", \"r\", encoding=\"utf-8\") as f:\n", " summary = f.read()\n", "\n", "name = \"Basil\"\n", "\n", "SYSTEM_PROMPT = f\"You are acting as {name}. You are answering questions on {name}'s website...\"\n", "SYSTEM_PROMPT += f\"\\n\\n## Summary:\\n{summary}\\n\\n## LinkedIn Profile:\\n{linkedin}\\n\"\n", "SYSTEM_PROMPT += f\"Always stay in character as {name}.\"\n", "\n", "TOOL_CONFIG = {\n", " \"tools\": [\n", " {\n", " \"toolSpec\": {\n", " \"name\": \"record_user_details\",\n", " \"description\": \"Record user contact info when they express interest.\",\n", " \"inputSchema\": {\n", " \"json\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"email\": {\"type\": \"string\"},\n", " \"name\": {\"type\": \"string\"},\n", " \"notes\": {\"type\": \"string\"}\n", " },\n", " \"required\": []\n", " }\n", " }\n", " }\n", " },\n", " # 2. Add the missing tool mentioned in your prompt\n", " {\n", " \"toolSpec\": {\n", " \"name\": \"record_unknown_question\",\n", " \"description\": \"Record a question that the assistant cannot answer.\",\n", " \"inputSchema\": {\n", " \"json\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"question\": {\"type\": \"string\"}\n", " },\n", " \"required\": []\n", " }\n", " }\n", " }\n", " }\n", " ]\n", "}\n", "\n", "# --- TOOL FUNCTIONS ---\n", "def record_user_details(email, name=None, notes=None):\n", " push(f\"DATABASE SAVED: {email}, {name}\") # Fixed push() to print()\n", " return {\"status\": \"success\", \"message\": f\"Recorded details for {email}\"}\n", "\n", "def record_unknown_question(question):\n", " push(f\"UNKNOWN QUESTION LOGGED: {question}\")\n", " return {\"status\": \"success\"}\n", "\n", "def handle_tool_calls(content_blocks):\n", " \"\"\"Processes Bedrock 'toolUse' blocks and returns a 'user' role message.\"\"\"\n", " tool_results = []\n", " \n", " for block in content_blocks:\n", " if 'toolUse' in block:\n", " tool_call = block['toolUse']\n", " tool_name = tool_call['name']\n", " tool_id = tool_call['toolUseId']\n", " arguments = tool_call['input'] \n", " \n", " print(f\"Executing tool: {tool_name}\")\n", " \n", " # Dynamically call the function\n", " tool_func = globals().get(tool_name)\n", " if tool_func:\n", " result_data = tool_func(**arguments)\n", " status = \"success\"\n", " else:\n", " result_data = {\"error\": f\"Function {tool_name} not found\"}\n", " status = \"error\"\n", " \n", " tool_results.append({\n", " \"toolResult\": {\n", " \"toolUseId\": tool_id,\n", " \"content\": [{\"json\": result_data}],\n", " \"status\": status\n", " }\n", " })\n", " \n", " return {\"role\": \"user\", \"content\": tool_results}\n", "\n", "# --- MAIN CHAT LOGIC ---\n", "def chat(message, history):\n", " # Fix 1: Ensure history is in the correct format for Bedrock if it's coming from Gradio\n", " # Bedrock messages need content as a list: [{\"role\": \"user\", \"content\": [{\"text\": \"...\"}]}]\n", " print(\"---History---\\n\");\n", " pprint.pprint(history, indent=4)\n", "\n", " print(\"--- message --- \\n\")\n", " pprint.pprint(message, indent=4)\n", " \n", " # Initialize messages list\n", " messages = []\n", " # 2. Process existing history\n", " for h in history:\n", " role = h['role']\n", " content = h['content']\n", " \n", " if isinstance(content, str):\n", " messages.append({\n", " \"role\": role,\n", " \"content\": [{\"text\": content}]\n", " })\n", " else:\n", " # If it's already a list of blocks (from a tool call), pass it as is\n", " messages.append({\n", " \"role\": role,\n", " \"content\": content\n", " })\n", "\n", " # 3. Append the current new user message\n", " messages.append({\n", " \"role\": \"user\", \n", " \"content\": [{\"text\": message}]\n", " })\n", "\n", " done = False\n", " final_response_text = \"\"\n", "\n", " while not done:\n", " response = client.converse(\n", " modelId=\"amazon.nova-micro-v1:0\",\n", " messages=messages,\n", " system=[{\"text\": SYSTEM_PROMPT}], # Pass global system prompt\n", " toolConfig=TOOL_CONFIG, # Pass global tool config\n", " inferenceConfig={\"temperature\": 0.1, \"maxTokens\": 2000}\n", " )\n", "\n", " output_message = response['output']['message']\n", " messages.append(output_message)\n", " \n", " # Corrected pprint usage\n", " print(\"--- output_message ---\")\n", " pprint.pprint(output_message, indent=4)\n", "\n", " stop_reason = response['stopReason']\n", " print(f\"Stop Reason: {stop_reason}\")\n", "\n", " if stop_reason == 'tool_use':\n", " # Fix 2: Call the handler with the content blocks list\n", " results_message = handle_tool_calls(output_message['content'])\n", " messages.append(results_message)\n", " \n", " print(\"--- messages After tool response ---\")\n", " pprint.pprint(messages, indent=4)\n", " else:\n", " done = True\n", " for block in output_message['content']:\n", " if 'text' in block:\n", " final_response_text = block['text']\n", "\n", " return final_response_text" ] }, { "cell_type": "code", "execution_count": 105, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "* Running on local URL: http://127.0.0.1:7877\n", "* To create a public link, set `share=True` in `launch()`.\n" ] }, { "data": { "text/html": [ "
" ], "text/plain": [ "\n",
" \n",
" | \n",
" \n",
" Exercise\n", " • First and foremost, deploy this for yourself! It's a real, valuable tool - the future resume..\n", " • Next, improve the resources - add better context about yourself. If you know RAG, then add a knowledge base about you. \n", " • Add in more tools! You could have a SQL database with common Q&A that the LLM could read and write from? \n", " • Bring in the Evaluator from the last lab, and add other Agentic patterns.\n", " \n", " | \n",
"
\n",
" \n",
" | \n",
" \n",
" Commercial implications\n", " Aside from the obvious (your career alter-ego) this has business applications in any situation where you need an AI assistant with domain expertise and an ability to interact with the real world.\n", " \n", " | \n",
"