{ "cells": [ { "cell_type": "markdown", "id": "e69bb50d-b560-4d92-b8aa-51b3d10fecad", "metadata": { "id": "e69bb50d-b560-4d92-b8aa-51b3d10fecad" }, "source": [ "# PAIG - Prompt/Reply Guardrails and Observability using OpenAI\n", "\n", "This notebook demonstrates how PAIG protects prompts and replies when using OpenAI, as well as how to achieve end-to-end observability.\n", "\n", "## Prerequisites\n", "\n", "1. **OpenAI API Key**: Required to make API calls to OpenAI.\n", "\n", "> The sample prompt is around 12 tokens, and the reply is 15 tokens. The notebook uses the model `gpt-4o-mini`, which currently costs \\$0.150 per 1 million input tokens and \\$0.600 per 1 million output tokens. Therefore, each prompt/reply costs approximately \\$0.00002.\n", "\n", "## Details\n", "\n", "This notebook covers the following steps:\n", "\n", "1. Install Python packages including PAIG Shield Server, PAIG Shield Client, OpenAI, and Spacy models.\n", "2. Start the PAIG Shield Server.\n", "3. Verify that the PAIG Shield Server is up and accepting requests.\n", "4. Download the sample application configuration from the PAIG Shield Server.\n", "5. Configure the OpenAI API Key.\n", "6. Write a simple application using OpenAI and the PAIG Shield Client.\n", "7. Test a sample prompt.\n", "8. Review access audits in the PAIG Shield Server.\n", "9. Review Application Permissions\n", "10. Check the reports.\n", "\n", "## Exceptions and Assumptions\n", "\n", "1. For simplicity, authentication to the PAIG Shield Server is turned off.\n", "\n" ] }, { "cell_type": "markdown", "id": "e821fc23-5f14-49f3-bacb-1d785cffa94b", "metadata": { "id": "e821fc23-5f14-49f3-bacb-1d785cffa94b" }, "source": [ "# 1. Install Python Packages\n", "\n", "This step installs the necessary Python packages for PAIG Shield Server, PAIG Shield Client, OpenAI, and Spacy.\n", "\n", "> Note:\n", "> 1. It might take a minute or more to download and install all the packages.\n", "> 2. After everything is installed, you might see a message to restart the runtime. You can ignore this message.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "ba361f69-5dde-471f-ad0a-8daa9a049e88", "metadata": { "id": "ba361f69-5dde-471f-ad0a-8daa9a049e88" }, "outputs": [], "source": [ "!pip install -qqq paig-client openai paig-server --no-warn-conflicts\n", "!python -m spacy download en_core_web_lg\n", "!echo \"\\n\\n\"\n", "!echo \"Ignore the above messages to restart the runtime or kernel. Please continue to the next step\"" ] }, { "cell_type": "markdown", "id": "34e2e478-7777-4f9d-9056-0262ef6de25e", "metadata": { "id": "34e2e478-7777-4f9d-9056-0262ef6de25e" }, "source": [ "# 2. Start the PAIG Shield Server\n", "\n", "The command line to start PAIG Shield Server is `paig run`. Out here we will start the server in the background using Python subprocess.\n", "\n", "The default port used by PAIG Shield Server is 4545.\n", "\n", "> **Tip:** Detailed PAIG application logs can be found in a directory called \"logs\"" ] }, { "cell_type": "code", "execution_count": null, "id": "deccf140-571a-4330-a7d5-3c1d87cf1397", "metadata": { "id": "deccf140-571a-4330-a7d5-3c1d87cf1397" }, "outputs": [], "source": [ "import subprocess\n", "\n", "command = [\"paig\", \"run\"]\n", "\n", "# Start the PAIG application in the background\n", "# Note - Console logs are hidden using stdout parameter, please remove the stdout parameter to get console logs\n", "process = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n", "\n", "print(f\"Started PAIG application with PID {process.pid}\")" ] }, { "cell_type": "markdown", "id": "__XLMSPOLIDl", "metadata": { "id": "__XLMSPOLIDl" }, "source": [ "# 3. Verify that the PAIG Shield Server is Up and Accepting Requests\n", "\n", "This step ensures that the PAIG Shield Server is running and accepting requests. Once the server is up and running, it will print the URL for the PAIG Shield Server.\n", "\n", "> **Tip:** You can log into PAIG application using following credentials:-\n", "
username: admin\n", "
password: welcome1" ] }, { "cell_type": "code", "execution_count": null, "id": "NZQG-tzDLIDm", "metadata": { "id": "NZQG-tzDLIDm" }, "outputs": [], "source": [ "import requests\n", "import time\n", "url = \"http://127.0.0.1:4545/\"\n", "\n", "print('Please kindly wait until we confirm if your PAIG application is ready')\n", "while True:\n", " try:\n", " response = requests.get(url, timeout=3)\n", " response.raise_for_status()\n", " break\n", " except:\n", " print('Server is not ready yet, please hang on...')\n", " time.sleep(3)\n", "server_url = \"http://127.0.0.1:4545\"\n", "print(f'Your PAIG application is ready now \\nYou can access PAIG application on - {server_url}')\n", "print(\"You can log into PAIG application using following credentials:-\\n username: admin \\n password: welcome1\")" ] }, { "cell_type": "markdown", "id": "77325810-6ae1-47c8-b3d2-62bbf09efbe2", "metadata": { "id": "77325810-6ae1-47c8-b3d2-62bbf09efbe2" }, "source": [ "# 4. Download the Application Configuration from the PAIG Shield Server\n", "\n", "The PAIG Shield Server is bootstrapped with a sample GenAI application, which can be used to quickly test PAIG features. In this step, we will download the configuration file needed by the PAIG Shield Client. The configuration file is saved in the `privacera` sub-folder.\n", "\n", "> Note: Since authentication is disabled, the API call to get the configuration will not be authorized.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5d9ec3d1-b319-460f-aa2b-dc53bccbd746", "metadata": { "id": "5d9ec3d1-b319-460f-aa2b-dc53bccbd746" }, "outputs": [], "source": [ "import requests\n", "import shutil\n", "import os\n", "import json\n", "\n", "login_url = \"http://127.0.0.1:4545/account-service/api/login\"\n", "config_url = \"http://127.0.0.1:4545/governance-service/api/ai/application/1/config/json/download\"\n", "username = \"admin\"\n", "password = \"welcome1\"\n", "\n", "session = requests.Session()\n", "\n", "def download_and_upload_demo_app_config():\n", " login_payload = {\n", " \"username\": username, \n", " \"password\": password\n", " }\n", " login_response = session.post(login_url, data=json.dumps(login_payload))\n", " \n", " if login_response.status_code == 200:\n", " response = session.get(config_url)\n", " if response.status_code == 200:\n", " with open(\"privacera-shield-PAIG-Demo-config.json\", \"w\") as file:\n", " file.write(response.text)\n", " destination_folder = \"privacera\"\n", " if not os.path.exists(destination_folder):\n", " os.makedirs(destination_folder)\n", " \n", " shutil.move(\"privacera-shield-PAIG-Demo-config.json\", os.path.join(destination_folder, \"privacera-shield-PAIG-Demo-config.json\"))\n", " print(\"Application config file downloaded and moved to privacera folder successfully.\")\n", " else:\n", " print(f\"Failed to download Application config file. Status code: {response.status_code}\")\n", " else:\n", " print(\"User authentication failed please check username and password\")\n", "\n", "download_and_upload_demo_app_config()" ] }, { "cell_type": "markdown", "id": "5b6692c7-8fcd-4746-b56f-928d3098f929", "metadata": { "id": "5b6692c7-8fcd-4746-b56f-928d3098f929" }, "source": [ "\n", "# 5. Configure the OpenAI API Key\n", "\n", "Enter your OpenAI API key in the text box that will appear when you run this step. After you input the key, press __ENTER__.\n", "\n", "> Note: It is important to press __ENTER__ for your value to be accepted." ] }, { "cell_type": "code", "execution_count": null, "id": "88429864-81be-47e8-9b7b-61d30cfb3f3b", "metadata": { "id": "88429864-81be-47e8-9b7b-61d30cfb3f3b" }, "outputs": [], "source": [ "import os\n", "from getpass import getpass\n", "\n", "openai_api_key = getpass(\"🔑 Enter your OpenAI API key and hit Enter:\")\n", "os.environ[\"OPENAI_API_KEY\"] = openai_api_key\n", "print(\"OpenAI key has been entered. Now validating it...\")\n", "\n", "from openai import OpenAI\n", "openai_model = \"gpt-4o-mini\"\n", "client = OpenAI(\n", " api_key=os.environ.get(\"OPENAI_API_KEY\"),\n", ")\n", "\n", "chat_completion = client.chat.completions.create(\n", " messages=[\n", " {\n", " \"role\": \"user\",\n", " \"content\": \"Say Connected to OpenAI successfully!\",\n", " }\n", " ],\n", " model=openai_model,\n", ")\n", "print(chat_completion.choices[0].message.content)\n", "print(\"If connection to OpenAI is successful, then proceed to the next step.\")" ] }, { "cell_type": "markdown", "id": "72d9da12-1a03-4cb4-8106-3fbbf4fa674d", "metadata": { "id": "72d9da12-1a03-4cb4-8106-3fbbf4fa674d" }, "source": [ "\n", "# 6. Write a Simple Application Using OpenAI and the PAIG Shield Client\n", "\n", "This section demonstrates a simple Python application that uses OpenAI for inference. The PAIG Shield is integrated within the application. The PAIG Shield client is initialized using the `setup()` method and is then used to validate the prompts and replies. In this basic GenAI application, the PAIG Shield's `check_access()` method needs to be explicitly called for the prompt and reply. However, when using frameworks like LangChain, PAIG will automatically instrument the code and call the `check_access()` method for all interactions with LLMs and RAGs.\n", "\n", "To enforce user or group-specific policies, the calling username should be set as the request context before processing the prompt. This can be done using the `with paig_shield_client.create_shield_context(username=username):` syntax.\n", "\n", "To stitch together related calls, an optional thread ID can be passed to the `check_access()` method to tie them together.\n", "\n", "Depending on the policies, the `check_access()` method will perform one of the following actions:\n", "\n", "1. If the user is not permitted to use the application or if there is a policy to deny contents which are inappropriate, having unauthorized sensitive information, or is of malicious intent, then the method will throw the exception `paig_client.exception.AccessControlException`. This exception can be caught, and an alternate reply can be returned to the caller.\n", "2. If the request is permitted but contains PII or sensitive information that is not authorized and needs to be redacted, the method will return the content with the PII or sensitive data elements redacted. This behavior is consistent for prompts, requests to RAGs, replies from RAGs, requests to LLMs, and replies from LLMs.\n", "3. If there are no policy violations, the content is returned without any alterations.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "dc0a93aa-fd6c-4bf7-9136-7d856f6109d1", "metadata": { "id": "dc0a93aa-fd6c-4bf7-9136-7d856f6109d1" }, "outputs": [], "source": [ "from paig_client import client as paig_shield_client\n", "from paig_client.model import ConversationType\n", "import paig_client.exception\n", "from openai import OpenAI\n", "import uuid\n", "\n", "# Set the OPENAI_API_KEY environment variable or set it here\n", "openai_client = OpenAI(\n", " api_key=os.environ.get(\"OPENAI_API_KEY\"),\n", ")\n", "\n", "# PAIG supports frameworks like LangChain and VectorDBs like Milvus, OpenSearch. The integration to be considered should be passed as the frameworks parameter.\n", "paig_shield_client.setup(frameworks=[])\n", "\n", "# Create a function which can be called for the prompts\n", "def query_as_user(username, prompt_text):\n", " # Generate a random UUID which will be used to bind a prompt with a reply\n", " privacera_thread_id = str(uuid.uuid4())\n", "\n", " try:\n", " with paig_shield_client.create_shield_context(username=username):\n", " print(f\"PROMPT BY USER: {prompt_text}\")\n", "\n", " # Validate prompt with Privacera Shield\n", " updated_prompt_text = paig_shield_client.check_access(\n", " text=prompt_text,\n", " conversation_type=ConversationType.PROMPT,\n", " thread_id=privacera_thread_id\n", " )\n", " updated_prompt_text = updated_prompt_text[0].response_text\n", " if prompt_text != updated_prompt_text:\n", " print(f\"Updated Prompt Text: {updated_prompt_text}\")\n", " else:\n", " print(\"PROMPT VALIDATION: Prompt has not been changed by PAIG.\")\n", "\n", " # Call LLM with updated prompt text\n", " PROMPT = f\"\"\"Use the following pieces of context to answer the question at the end.\n", " {updated_prompt_text}\n", " ANSWER:\n", " \"\"\"\n", " response = openai_client.chat.completions.create(\n", " model=openai_model,\n", " messages=[{\"role\": \"user\", \"content\": PROMPT}],\n", " temperature=0\n", " )\n", " llm_response = response.choices[0].message.content\n", " print(f\"LLM Response: {llm_response}\")\n", "\n", " # Validate LLM response with Privacera Shield\n", " updated_reply_text = paig_shield_client.check_access(\n", " text=llm_response,\n", " conversation_type=ConversationType.REPLY,\n", " thread_id=privacera_thread_id\n", " )\n", " updated_reply_text = updated_reply_text[0].response_text\n", " if llm_response != updated_reply_text:\n", " print(f\"REPLY VALIDATION (UPDATED BY PAIG): {updated_reply_text}\")\n", " else:\n", " print(\"REPLY VALIDATION: The reply has not been updated by PAIG.\")\n", " return updated_reply_text\n", " except paig_client.exception.AccessControlException as e:\n", " # If access is denied, this exception will be thrown. Handle it accordingly.\n", " print(\"The query has been denied!\")\n", " print(f\"AccessControlException: {e}\")\n", " return \"DENIED: Prompt is not authorized.\"\n" ] }, { "cell_type": "markdown", "id": "033b73ac-0a25-4a72-a32a-ba53c0d814db", "metadata": { "id": "033b73ac-0a25-4a72-a32a-ba53c0d814db" }, "source": [ "\n", "# 7. Test a Sample Prompt\n", "\n", "We will call the method `query_as_user` using a test user named `sally` with a sample prompt.\n", "\n", "Since we are using the demo application configuration, which has a policy that redacts PERSON_NAME from replies, any elements matching the policy in the LLM's reply will be redacted before responding back to the caller." ] }, { "cell_type": "code", "execution_count": null, "id": "43d6091c-0eee-4e7f-bd8f-3a9224f2d001", "metadata": { "id": "43d6091c-0eee-4e7f-bd8f-3a9224f2d001" }, "outputs": [], "source": [ "# Using test user named sally\n", "reply = query_as_user(\"sally\", \"Who was the first President of USA and where did they live?\")\n", "print(f\"REPLY TO USER={reply}\")" ] }, { "cell_type": "markdown", "id": "V1xv0cN3PT07", "metadata": { "id": "V1xv0cN3PT07" }, "source": [ "# 8. Review Access Audits in the PAIG Shield Server\n", "\n", "In this step, we will open the PAIG Server portal and check the audits. The portal will be embedded within this notebook.\n", "\n", "1. In the PAIG portal, navigate to the `Security > Access Audits` section. You will see the audit record from the above request.\n", "2. Click on the `More Details` button to see the details of the prompts sent by the application to the LLM and the responses coming from the LLM.\n", "3. PAIG will identify all PII and sensitive data and tag them.\n", "4. The default policy is to redact PERSON_NAME, so the president's name will be redacted before being sent to the caller." ] }, { "cell_type": "code", "execution_count": null, "id": "lUoz5qhFPT07", "metadata": { "id": "lUoz5qhFPT07" }, "outputs": [], "source": [ "import webbrowser\n", "audit_url = f'{server_url}#/audits_security'\n", "print(f'To check access audits you can also open {audit_url}')\n", "webbrowser.open(audit_url)" ] }, { "cell_type": "markdown", "id": "1232e6c1-80a1-4179-a2b1-74596b81a62c", "metadata": {}, "source": [ "# 9. Review Application Permissions\n", "\n", "1. In the portal, go to `Application -> AI Applications -> PAIG Demo`.\n", "2. Click on the `PERMISSIONS` tab at the top.\n", "3. You will see a policy stating that any reply containing `PERSON`, `EMAIL_ADDRESS`, or `PHONE_NUMBER` should be redacted." ] }, { "cell_type": "markdown", "id": "0d906364-aed9-4a2c-96c8-69d0ad7ea615", "metadata": {}, "source": [ "# 10. Check the Reports\n", "\n", "1. Click on `Reports -> Built-in Reports -> Sensitive Data Access Overview`.\n", "2. This report provides statistics on the PII and other sensitive data found in the prompts and replies.\n", "3. Similarly, the report `Summary of Users who accessed the GenAI Application` will provide details about the GenAI applications and the users accessing them.\n" ] } ], "metadata": { "colab": { "collapsed_sections": [ "9c207905-a0f7-4849-b0c0-ca2133769679" ], "provenance": [] }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.14" } }, "nbformat": 4, "nbformat_minor": 5 }