Merge branch 'main' into james/sdk_updates
commit
98d2e4feb8
@ -1,189 +0,0 @@
|
||||
from typing import List, Union
|
||||
|
||||
from smokey import Smokey
|
||||
|
||||
import openai
|
||||
|
||||
|
||||
def get_candidates(
|
||||
prompt: str,
|
||||
stop: List[str],
|
||||
temperature: float,
|
||||
priming_prefix: str,
|
||||
engine: str,
|
||||
n: int = 5,
|
||||
) -> List[str]:
|
||||
"""
|
||||
Generate N candidate completions based on the prompt, generated with a specific temperature.
|
||||
|
||||
:param prompt: The prompt to start the conversation with.
|
||||
:param stop: A list of tokens that indicate the end of the generation.
|
||||
:param temperature: The temperature of the generation.
|
||||
:param priming_prefix: The prefix to use for the priming.
|
||||
:param engine: The engine to use for the generation.
|
||||
:param n: The number of completions to generate.
|
||||
:return: A list of completions.
|
||||
"""
|
||||
response = openai.Completion.create(
|
||||
engine=engine,
|
||||
prompt=prompt,
|
||||
temperature=temperature,
|
||||
max_tokens=150,
|
||||
top_p=1,
|
||||
frequency_penalty=0,
|
||||
presence_penalty=0,
|
||||
stop=stop,
|
||||
n=n,
|
||||
)
|
||||
responses = [priming_prefix + choice.text for choice in response.choices]
|
||||
return responses
|
||||
|
||||
|
||||
def rindex(lst: List, value: str) -> int:
|
||||
"""
|
||||
Return the index of the last occurrence of a value in a list.
|
||||
|
||||
:param lst: The list to search in.
|
||||
:param value: The value to search for.
|
||||
:return: The index of the last occurrence of the value.
|
||||
"""
|
||||
try:
|
||||
return len(lst) - lst[::-1].index(value) - 1
|
||||
except ValueError:
|
||||
raise ValueError(f"Answer start token `{value}` not found in the eval template")
|
||||
|
||||
|
||||
def eval_candidate(
|
||||
candidate_answer: str,
|
||||
original_instruction: str,
|
||||
eval_template: str,
|
||||
answer_start_token: str,
|
||||
engine: str,
|
||||
) -> float:
|
||||
"""
|
||||
Evaluate a candidate answer by calculating the average log probability
|
||||
of the original instruction, given the candidate answer with a specific
|
||||
evaluation template, aimed at reconstructing the original instruction.
|
||||
|
||||
:param candidate_answer: The candidate answer to evaluate.
|
||||
:param original_instruction: The original instruction.
|
||||
:param eval_template: The template to use for the evaluation.
|
||||
:param answer_start_token: The token to use to indicate the start of the answer.
|
||||
:param engine: The engine to use for the evaluation.
|
||||
:return: The evaluation of the candidate answer.
|
||||
"""
|
||||
response = openai.Completion.create(
|
||||
engine=engine,
|
||||
prompt=eval_template.format(candidate_answer, original_instruction),
|
||||
temperature=0,
|
||||
max_tokens=0,
|
||||
top_p=1,
|
||||
frequency_penalty=0,
|
||||
presence_penalty=0,
|
||||
logprobs=1,
|
||||
echo=True,
|
||||
)
|
||||
|
||||
answer_start = rindex(
|
||||
response["choices"][0]["logprobs"]["tokens"], answer_start_token
|
||||
)
|
||||
logprobs = response["choices"][0]["logprobs"]["token_logprobs"][answer_start + 1 :]
|
||||
return sum(logprobs) / len(logprobs)
|
||||
|
||||
|
||||
def backtranslation(
|
||||
prompt_template: str,
|
||||
additional_info: str,
|
||||
instruction: str,
|
||||
eval_template: str,
|
||||
priming_prefix: str = "SELECT",
|
||||
stop1: List[str] = ["#", ";"],
|
||||
answer_start_token: str = "--",
|
||||
n: int = 5,
|
||||
temperature: float = 0.5,
|
||||
return_all_results: bool = False,
|
||||
engine: str = "davinci-codex",
|
||||
) -> Union[str, List[str, float]]:
|
||||
"""
|
||||
Generate a number of SQL queries given a natural language instruction,
|
||||
and pick the best one based on the average log probability of explaining the
|
||||
candidate SQL query with the exact original instruction, when prompted for
|
||||
a natural language explanation of the candidate SQL query.
|
||||
|
||||
:param prompt_template: The template to use for the prompt to generate SQL.
|
||||
:param additional_info: Additional information to include in the prompt
|
||||
(SQL Tables, and their properties).
|
||||
:param instruction: The instruction in natural language.
|
||||
:param eval_template: The template to use for the evaluation.
|
||||
:param priming_prefix: The prefix to use for the priming of the SQL query.
|
||||
:param stop1: A list of tokens that indicate the end of the generation.
|
||||
:param answer_start_token: The token to use to indicate the start of the
|
||||
natural answer.
|
||||
:param n: The number of candidates to generate.
|
||||
:param temperature: The temperature of the generation.
|
||||
:param return_all_results: Whether to return all results or just the best one.
|
||||
:param engine: The engine to use for the generation and evaluation.
|
||||
:return: The best SQL query, or a list of all scored generated SQL queries.
|
||||
"""
|
||||
prompt_template = prompt_template.format(
|
||||
additional_info, instruction, priming_prefix
|
||||
)
|
||||
|
||||
candidates = []
|
||||
responses = get_candidates(
|
||||
prompt_template, stop1, temperature, priming_prefix, engine=engine, n=n
|
||||
)
|
||||
for i in range(n):
|
||||
quality = eval_candidate(
|
||||
responses[i],
|
||||
instruction,
|
||||
eval_template,
|
||||
answer_start_token,
|
||||
engine=engine,
|
||||
)
|
||||
candidates.append((responses[i], quality))
|
||||
|
||||
candidates.sort(key=lambda x: x[1], reverse=True)
|
||||
if return_all_results:
|
||||
return candidates
|
||||
return candidates[0][0]
|
||||
|
||||
|
||||
def main(
|
||||
nl_query: str = "Return the name of each department that had more than 10 employees in June 2021",
|
||||
eval_template: str = "{};\n-- Explanation of the above query in human readable format\n-- {}",
|
||||
table_definitions: str = "# Employee(id, name, department_id)\n# Department(id, name, address)\n# Salary_Payments(id, employee_id, amount, date)\n",
|
||||
prompt_template: str = "### Postgres SQL tables, with their properties:\n#\n{}#\n### {}\n{}",
|
||||
n: int = 3,
|
||||
temperature: float = 0.3,
|
||||
engine: str = "davinci-codex",
|
||||
):
|
||||
"""
|
||||
Generate a number of SQL queries given a natural language instruction,
|
||||
and pick the best one based on the highest backtranslation score.
|
||||
|
||||
:param nl_query: The natural language query.
|
||||
:param eval_template: The template to use for the evaluation.
|
||||
:param table_definitions: The definitions of the tables used in the query.
|
||||
:param prompt_template: The template to use for the prompt to generate SQL.
|
||||
:param n: The number of candidates to generate.
|
||||
:param temperature: The temperature of the generation.
|
||||
:param engine: The engine to use for the generation and evaluation.
|
||||
:return: The best SQL query, or a list of all scored generated SQL queries.
|
||||
"""
|
||||
|
||||
result = backtranslation(
|
||||
prompt_template,
|
||||
table_definitions,
|
||||
nl_query,
|
||||
eval_template,
|
||||
priming_prefix="SELECT",
|
||||
temperature=temperature,
|
||||
n=n,
|
||||
engine=engine,
|
||||
)
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Smokey(main)
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,993 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Creating slides with the Assistants API (GPT-4), and DALL·E-3\n",
|
||||
"\n",
|
||||
"This notebook illustrates the use of the new [Assistants API](https://platform.openai.com/docs/assistants/overview) (GPT-4), and DALL·E-3 in crafting informative and visually appealing slides. <br>\n",
|
||||
"Creating slides is a pivotal aspect of many jobs, but can be laborious and time-consuming. Additionally, extracting insights from data and articulating them effectively on slides can be challenging. <br><br> This cookbook recipe will demonstrate how you can utilize the new Assistants API to faciliate the end to end slide creation process for you without you having to touch Microsoft PowerPoint or Google Slides, saving you valuable time and effort!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 0. Setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from IPython.display import display, Image\n",
|
||||
"from openai import OpenAI\n",
|
||||
"import os\n",
|
||||
"import pandas as pd\n",
|
||||
"import json\n",
|
||||
"import io\n",
|
||||
"from PIL import Image\n",
|
||||
"import requests\n",
|
||||
"\n",
|
||||
"client = OpenAI(api_key=os.environ.get(\"OPENAI_API_KEY\", \"<your OpenAI API key if not set as env var>\"))\n",
|
||||
"\n",
|
||||
"#Lets import some helper functions for assistants from https://cookbook.openai.com/examples/assistants_api_overview_python\n",
|
||||
"def show_json(obj):\n",
|
||||
" display(json.loads(obj.model_dump_json()))\n",
|
||||
"\n",
|
||||
"def submit_message(assistant_id, thread, user_message,file_ids=None):\n",
|
||||
" params = {\n",
|
||||
" 'thread_id': thread.id,\n",
|
||||
" 'role': 'user',\n",
|
||||
" 'content': user_message,\n",
|
||||
" }\n",
|
||||
" if file_ids:\n",
|
||||
" params['file_ids']=file_ids\n",
|
||||
"\n",
|
||||
" client.beta.threads.messages.create(\n",
|
||||
" **params\n",
|
||||
")\n",
|
||||
" return client.beta.threads.runs.create(\n",
|
||||
" thread_id=thread.id,\n",
|
||||
" assistant_id=assistant_id,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"def get_response(thread):\n",
|
||||
" return client.beta.threads.messages.list(thread_id=thread.id)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Creating the content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this recipe, we will be creating a brief fictional presentation for the quarterly financial review of our company, NotReal Corporation. We want to highlight some key trends we are seeing that are affecting the profitability of our company.<br> Let's say we have the some financial data at our disposal. Let's load in the data, and take a look..."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>Year</th>\n",
|
||||
" <th>Quarter</th>\n",
|
||||
" <th>Distribution channel</th>\n",
|
||||
" <th>Revenue ($M)</th>\n",
|
||||
" <th>Costs ($M)</th>\n",
|
||||
" <th>Customer count</th>\n",
|
||||
" <th>Time</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>2021</td>\n",
|
||||
" <td>Q1</td>\n",
|
||||
" <td>Online Sales</td>\n",
|
||||
" <td>1.50</td>\n",
|
||||
" <td>1.301953</td>\n",
|
||||
" <td>150</td>\n",
|
||||
" <td>2021 Q1</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>2021</td>\n",
|
||||
" <td>Q1</td>\n",
|
||||
" <td>Direct Sales</td>\n",
|
||||
" <td>1.50</td>\n",
|
||||
" <td>1.380809</td>\n",
|
||||
" <td>151</td>\n",
|
||||
" <td>2021 Q1</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>2021</td>\n",
|
||||
" <td>Q1</td>\n",
|
||||
" <td>Retail Partners</td>\n",
|
||||
" <td>1.50</td>\n",
|
||||
" <td>1.348246</td>\n",
|
||||
" <td>152</td>\n",
|
||||
" <td>2021 Q1</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>2021</td>\n",
|
||||
" <td>Q2</td>\n",
|
||||
" <td>Online Sales</td>\n",
|
||||
" <td>1.52</td>\n",
|
||||
" <td>1.308608</td>\n",
|
||||
" <td>152</td>\n",
|
||||
" <td>2021 Q2</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>2021</td>\n",
|
||||
" <td>Q2</td>\n",
|
||||
" <td>Direct Sales</td>\n",
|
||||
" <td>1.52</td>\n",
|
||||
" <td>1.413305</td>\n",
|
||||
" <td>153</td>\n",
|
||||
" <td>2021 Q2</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" Year Quarter Distribution channel Revenue ($M) Costs ($M) \\\n",
|
||||
"0 2021 Q1 Online Sales 1.50 1.301953 \n",
|
||||
"1 2021 Q1 Direct Sales 1.50 1.380809 \n",
|
||||
"2 2021 Q1 Retail Partners 1.50 1.348246 \n",
|
||||
"3 2021 Q2 Online Sales 1.52 1.308608 \n",
|
||||
"4 2021 Q2 Direct Sales 1.52 1.413305 \n",
|
||||
"\n",
|
||||
" Customer count Time \n",
|
||||
"0 150 2021 Q1 \n",
|
||||
"1 151 2021 Q1 \n",
|
||||
"2 152 2021 Q1 \n",
|
||||
"3 152 2021 Q2 \n",
|
||||
"4 153 2021 Q2 "
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"financial_data_path = 'data/NotRealCorp_financial_data.json'\n",
|
||||
"financial_data = pd.read_json(financial_data_path)\n",
|
||||
"financial_data.head(5)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As you can see, this data has quarterly revenue, costs and customer data across different distribution channels. Let's create an Assistant\n",
|
||||
"that can act as a personal analyst and make a nice visualization for our PowerPoint!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First, we need to upload our file so our Assistant can access it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"file = client.files.create(\n",
|
||||
" file=open('data/NotRealCorp_financial_data.json',\"rb\"),\n",
|
||||
" purpose='assistants',\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we're ready to create our Assistant. We can instruct our assistant to act as a data scientist, and take any queries we give it and run the necessary code to output the proper data visualization. The instructions parameter here is akin to system instructions in the ChatCompletions endpoint, and can help guide the assistant. We can also turn on the tool of Code Interpreter, so our Assistant will be able to code. Finally, we can specifiy any files we want to use, which in this case is just the `financial_data` file we created above."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assistant = client.beta.assistants.create(\n",
|
||||
" instructions=\"You are a data scientist assistant. When given data and a query, write the proper code and create the proper visualization\",\n",
|
||||
" model=\"gpt-4-1106-preview\",\n",
|
||||
" tools=[{\"type\": \"code_interpreter\"}],\n",
|
||||
" file_ids=[file.id]\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's create a thread now, and as our first request ask the Assistant to calculate quarterly profits, and then plot the profits by distribution channel over time. The assistant will automatically calculate the profit for each quarter, and also create a new column combining quarter and year, without us having to ask for that directly. We can also specify the colors of each line."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread = client.beta.threads.create(\n",
|
||||
" messages=[\n",
|
||||
" {\n",
|
||||
" \"role\": \"user\",\n",
|
||||
" \"content\": \"Calculate profit (revenue minus cost) by quarter and year, and visualize as a line plot across the distribution channels, where the colors of the lines are green, light red, and light blue\",\n",
|
||||
" \"file_ids\": [file.id]\n",
|
||||
" }\n",
|
||||
" ]\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"No we can execute the run of our thread"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"run = client.beta.threads.runs.create(\n",
|
||||
" thread_id=thread.id,\n",
|
||||
" assistant_id=assistant.id,\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can now start a loop that will check if the image has been created. Note: This may take a few minutes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"messages = client.beta.threads.messages.list(thread_id=thread.id)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Assistant still working...\n",
|
||||
"Plot created!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"\n",
|
||||
"while True:\n",
|
||||
" messages = client.beta.threads.messages.list(thread_id=thread.id)\n",
|
||||
" try:\n",
|
||||
" #See if image has been created\n",
|
||||
" messages.data[0].content[0].image_file\n",
|
||||
" #Sleep to make sure run has completed\n",
|
||||
" time.sleep(5)\n",
|
||||
" print('Plot created!')\n",
|
||||
" break\n",
|
||||
" except:\n",
|
||||
" time.sleep(10)\n",
|
||||
" print('Assistant still working...')\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's see the messages the Assistant added."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[MessageContentImageFile(image_file=ImageFile(file_id='file-0rKABLygI02MgwwhpgWdRFY1'), type='image_file'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value=\"The profit has been calculated for each distribution channel by quarter and year. Next, I'll create a line plot to visualize these profits. As specified, I will use green for the 'Online Sales', light red for 'Direct Sales', and light blue for 'Retail Partners' channels. Let's create the plot.\"), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value=\"The JSON data has been successfully restructured into a tabular dataframe format. It includes the year, quarter, distribution channel, revenue, costs, customer count, and a combined 'Time' representation of 'Year Quarter'. Now, we have the necessary information to calculate the profit (revenue minus cost) by quarter and year.\\n\\nTo visualize the profit across the different distribution channels with a line plot, we will proceed with the following steps:\\n\\n1. Calculate the profit for each row in the dataframe.\\n2. Group the data by 'Time' (which is a combination of Year and Quarter) and 'Distribution channel'.\\n3. Aggregate the profit for each group.\\n4. Plot the aggregated profits as a line plot with the distribution channels represented in different colors as requested.\\n\\nLet's calculate the profit for each row and then continue with the visualization.\"), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value='The structure of the JSON data shows that it is a dictionary with \"Year\", \"Quarter\", \"Distribution channel\", and potentially other keys that map to dictionaries containing the data. The keys of the inner dictionaries are indices, indicating that the data is tabular but has been converted into a JSON object.\\n\\nTo properly convert this data into a DataFrame, I will restructure the JSON data into a more typical list of dictionaries, where each dictionary represents a row in our target DataFrame. Subsequent to this restructuring, I can then load the data into a Pandas DataFrame. Let\\'s restructure and load the data.'), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value=\"The JSON data has been incorrectly loaded into a single-row DataFrame with numerous columns representing each data point. This implies the JSON structure is not as straightforward as expected, and a direct conversion to a flat table is not possible without further processing.\\n\\nTo better understand the JSON structure and figure out how to properly normalize it into a table format, I will print out the raw JSON data structure. We will analyze its format and then determine the correct approach to extract the profit by quarter and year, as well as the distribution channel information. Let's take a look at the JSON structure.\"), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value=\"It seems that the file content was successfully parsed as JSON, and thus, there was no exception raised. The variable `error_message` is not defined because the `except` block was not executed.\\n\\nI'll proceed with displaying the data that was parsed from JSON.\"), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value=\"It appears that the content of the dataframe has been incorrectly parsed, resulting in an empty dataframe with a very long column name that seems to contain JSON data rather than typical CSV columns and rows.\\n\\nTo address this issue, I will take a different approach to reading the file. I will attempt to parse the content as JSON. If this is not successful, I'll adjust the loading strategy accordingly. Let's try to read the contents as JSON data first.\"), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value=\"Before we can calculate profits and visualize the data as requested, I need to first examine the contents of the file that you have uploaded. Let's go ahead and read the file to understand its structure and the kind of data it contains. Once I have a clearer picture of the dataset, we can proceed with the profit calculations. I'll begin by loading the file into a dataframe and displaying the first few entries to see the data schema.\"), type='text'),\n",
|
||||
" MessageContentText(text=Text(annotations=[], value='Calculate profit (revenue minus cost) by quarter and year, and visualize as a line plot across the distribution channels, where the colors of the lines are green, light red, and light blue'), type='text')]"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"messages = client.beta.threads.messages.list(thread_id=thread.id)\n",
|
||||
"[message.content[0] for message in messages.data]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can see that the last message (latest message is shown first) from the assistant contains the image file we are looking for. An interesting note here is that the Assistant was able to attempt several times to parse the JSON data, as the first parsing was unsuccessful, demonstrating the assistant's adaptability."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Quick helper function to convert our output file to a png\n",
|
||||
"def convert_file_to_png(file_id, write_path):\n",
|
||||
" data = client.files.content(file_id)\n",
|
||||
" data_bytes = data.read()\n",
|
||||
" with open(write_path, \"wb\") as file:\n",
|
||||
" file.write(data_bytes)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plot_file_id = messages.data[0].content[0].image_file.file_id\n",
|
||||
"image_path = \"../images/NotRealCorp_chart.png\"\n",
|
||||
"convert_file_to_png(plot_file_id,image_path)\n",
|
||||
"\n",
|
||||
"#Upload\n",
|
||||
"plot_file = client.files.create(\n",
|
||||
" file=open(image_path, \"rb\"),\n",
|
||||
" purpose='assistants'\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's load in the plot!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"![The Image](../images/NotRealCorp_chart.png)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Nice! So, with just one sentence, we were able to have our assistant use code interpreter to\n",
|
||||
"calculate the profitability, and graph the three lineplots of the various distribution channels.<br><br>\n",
|
||||
"Now we have a nice visual for our slide, but we want some insights to go along with it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Generating insights"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To get insights from our image, we simply need to add a new message to our thread. Our Assistant will know to use the message history to give us some concise takeaways from the visual provided. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Run(id='run_NWoygMcBfHUr58fCE4Cn6rxN', assistant_id='asst_3T362kLlTyAq0FUnkvjjQczO', cancelled_at=None, completed_at=None, created_at=1701827074, expires_at=1701827674, failed_at=None, file_ids=['file-piTokyHGllwGITzIpoG8dok3'], instructions='You are a data scientist assistant. When given data and a query, write the proper code and create the proper visualization', last_error=None, metadata={}, model='gpt-4-1106-preview', object='thread.run', required_action=None, started_at=None, status='queued', thread_id='thread_73TgtFoJMlEJvb13ngjTnAo3', tools=[ToolAssistantToolsCode(type='code_interpreter')])"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"submit_message(assistant.id,thread,\"Give me two medium length sentences (~20-30 words per sentence) of the \\\n",
|
||||
" most important insights from the plot you just created.\\\n",
|
||||
" These will be used for a slide deck, and they should be about the\\\n",
|
||||
" 'so what' behind the data.\"\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, once the run has completed, we can see the latest message"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The plot reveals a consistent upward trend in profits for all distribution channels, indicating successful business growth over time. Particularly, 'Online Sales' shows a notable increase, underscoring the importance of digital presence in revenue generation.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Hard coded wait for a response, as the assistant may iterate on the bullets.\n",
|
||||
"time.sleep(10)\n",
|
||||
"response = get_response(thread)\n",
|
||||
"bullet_points = response.data[0].content[0].text.value\n",
|
||||
"print(bullet_points)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Cool! So our assistant was able to identify the noteworthy growth in Online Sales profit, and infer that this shows the importance of a large digital presence. Now let's get a compelling title for the slide."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Run(id='run_q6E85J31jCw3QkHpjJKl969P', assistant_id='asst_3T362kLlTyAq0FUnkvjjQczO', cancelled_at=None, completed_at=None, created_at=1701827084, expires_at=1701827684, failed_at=None, file_ids=['file-piTokyHGllwGITzIpoG8dok3'], instructions='You are a data scientist assistant. When given data and a query, write the proper code and create the proper visualization', last_error=None, metadata={}, model='gpt-4-1106-preview', object='thread.run', required_action=None, started_at=None, status='queued', thread_id='thread_73TgtFoJMlEJvb13ngjTnAo3', tools=[ToolAssistantToolsCode(type='code_interpreter')])"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"submit_message(assistant.id,thread,\"Given the plot and bullet points you created,\\\n",
|
||||
" come up with a very brief title for a slide. It should reflect just the main insights you came up with.\"\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And the title is:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\"Ascending Profits & Digital Dominance\"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"#Wait as assistant may take a few steps\n",
|
||||
"time.sleep(10)\n",
|
||||
"response = get_response(thread)\n",
|
||||
"title = response.data[0].content[0].text.value\n",
|
||||
"print(title)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. DALL·E-3 title image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Nice, now we have a title, a plot and two bullet points. We're almost ready to put this all on a slide, but as a final step, let's have DALL·E-3 come up with an image to use as the title slide of the presentation. <br><br>\n",
|
||||
"*Note:* DALL·E-3 is not yet available within the assistants API but is coming soon! <br> <br>\n",
|
||||
"We'll feed in a brief description of our company (NotRealCorp) and have DALL·E-3 do the rest!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"company_summary = \"NotReal Corp is a prominent hardware company that manufactures and sells processors, graphics cards and other essential computer hardware.\"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"response = client.images.generate(\n",
|
||||
" model='dall-e-3',\n",
|
||||
" prompt=f\"given this company summary {company_summary}, create an inspirational \\\n",
|
||||
" photo showing the growth and path forward. This will be used at a quarterly\\\n",
|
||||
" financial planning meeting\",\n",
|
||||
" size=\"1024x1024\",\n",
|
||||
" quality=\"hd\",\n",
|
||||
" n=1\n",
|
||||
")\n",
|
||||
"image_url = response.data[0].url\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Cool, now we can add this image to our thread. First, we can save the image locally, then upload it to the assistants API using the `File` upload endpoint. Let's also take a look at our image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dalle_img_path = '../images/dalle_image.png'\n",
|
||||
"img = requests.get(image_url)\n",
|
||||
"\n",
|
||||
"#Save locally\n",
|
||||
"with open(dalle_img_path,'wb') as file:\n",
|
||||
" file.write(img.content)\n",
|
||||
"\n",
|
||||
"#Upload\n",
|
||||
"dalle_file = client.files.create(\n",
|
||||
" file=open(dalle_img_path, \"rb\"),\n",
|
||||
" purpose='assistants'\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
" \n",
|
||||
"![Image](../images/dalle_image.png)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Creating the slides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We now have all the content we need to create the slides. While we could simply add a message asking for slides, but let's instead give the assistant a slide template, using the `python-pptx` library, to use. This will ensure we get a deck in the style we want. See the `Extensions` section at the end of the notebook for notes on creating the template."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"title_template = \"\"\"\n",
|
||||
"from pptx import Presentation\n",
|
||||
"from pptx.util import Inches, Pt\n",
|
||||
"from pptx.enum.text import PP_PARAGRAPH_ALIGNMENT\n",
|
||||
"from pptx.dml.color import RGBColor\n",
|
||||
"\n",
|
||||
"# Create a new presentation object\n",
|
||||
"prs = Presentation()\n",
|
||||
"\n",
|
||||
"# Add a blank slide layout\n",
|
||||
"blank_slide_layout = prs.slide_layouts[6]\n",
|
||||
"slide = prs.slides.add_slide(blank_slide_layout)\n",
|
||||
"\n",
|
||||
"# Set the background color of the slide to black\n",
|
||||
"background = slide.background\n",
|
||||
"fill = background.fill\n",
|
||||
"fill.solid()\n",
|
||||
"fill.fore_color.rgb = RGBColor(0, 0, 0)\n",
|
||||
"\n",
|
||||
"# Add image to the left side of the slide with a margin at the top and bottom\n",
|
||||
"left = Inches(0)\n",
|
||||
"top = Inches(0)\n",
|
||||
"height = prs.slide_height\n",
|
||||
"width = prs.slide_width * 3/5\n",
|
||||
"pic = slide.shapes.add_picture(image_path, left, top, width=width, height=height)\n",
|
||||
"\n",
|
||||
"# Add title text box positioned higher\n",
|
||||
"left = prs.slide_width * 3/5\n",
|
||||
"top = Inches(2)\n",
|
||||
"width = prs.slide_width * 2/5\n",
|
||||
"height = Inches(1)\n",
|
||||
"title_box = slide.shapes.add_textbox(left, top, width, height)\n",
|
||||
"title_frame = title_box.text_frame\n",
|
||||
"title_p = title_frame.add_paragraph()\n",
|
||||
"title_p.text = title_text\n",
|
||||
"title_p.font.bold = True\n",
|
||||
"title_p.font.size = Pt(38)\n",
|
||||
"title_p.font.color.rgb = RGBColor(255, 255, 255)\n",
|
||||
"title_p.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER\n",
|
||||
"\n",
|
||||
"# Add subtitle text box\n",
|
||||
"left = prs.slide_width * 3/5\n",
|
||||
"top = Inches(3)\n",
|
||||
"width = prs.slide_width * 2/5\n",
|
||||
"height = Inches(1)\n",
|
||||
"subtitle_box = slide.shapes.add_textbox(left, top, width, height)\n",
|
||||
"subtitle_frame = subtitle_box.text_frame\n",
|
||||
"subtitle_p = subtitle_frame.add_paragraph()\n",
|
||||
"subtitle_p.text = subtitle_text\n",
|
||||
"subtitle_p.font.size = Pt(22)\n",
|
||||
"subtitle_p.font.color.rgb = RGBColor(255, 255, 255)\n",
|
||||
"subtitle_p.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"data_vis_template = \"\"\"\n",
|
||||
"from pptx import Presentation\n",
|
||||
"from pptx.util import Inches, Pt\n",
|
||||
"from pptx.enum.text import PP_PARAGRAPH_ALIGNMENT\n",
|
||||
"from pptx.dml.color import RGBColor\n",
|
||||
"\n",
|
||||
"# Create a new presentation object\n",
|
||||
"prs = Presentation()\n",
|
||||
"\n",
|
||||
"# Add a blank slide layout\n",
|
||||
"blank_slide_layout = prs.slide_layouts[6]\n",
|
||||
"slide = prs.slides.add_slide(blank_slide_layout)\n",
|
||||
"\n",
|
||||
"# Set the background color of the slide to black\n",
|
||||
"background = slide.background\n",
|
||||
"fill = background.fill\n",
|
||||
"fill.solid()\n",
|
||||
"fill.fore_color.rgb = RGBColor(0, 0, 0)\n",
|
||||
"\n",
|
||||
"# Define placeholders\n",
|
||||
"image_path = data_vis_img\n",
|
||||
"title_text = \"Maximizing Profits: The Dominance of Online Sales & Direct Sales Optimization\"\n",
|
||||
"bullet_points = \"• Online Sales consistently lead in profitability across quarters, indicating a strong digital market presence.\\n• Direct Sales show fluctuations, suggesting variable performance and the need for targeted improvements in that channel.\"\n",
|
||||
"\n",
|
||||
"# Add image placeholder on the left side of the slide\n",
|
||||
"left = Inches(0.2)\n",
|
||||
"top = Inches(1.8)\n",
|
||||
"height = prs.slide_height - Inches(3)\n",
|
||||
"width = prs.slide_width * 3/5\n",
|
||||
"pic = slide.shapes.add_picture(image_path, left, top, width=width, height=height)\n",
|
||||
"\n",
|
||||
"# Add title text spanning the whole width\n",
|
||||
"left = Inches(0)\n",
|
||||
"top = Inches(0)\n",
|
||||
"width = prs.slide_width\n",
|
||||
"height = Inches(1)\n",
|
||||
"title_box = slide.shapes.add_textbox(left, top, width, height)\n",
|
||||
"title_frame = title_box.text_frame\n",
|
||||
"title_frame.margin_top = Inches(0.1)\n",
|
||||
"title_p = title_frame.add_paragraph()\n",
|
||||
"title_p.text = title_text\n",
|
||||
"title_p.font.bold = True\n",
|
||||
"title_p.font.size = Pt(28)\n",
|
||||
"title_p.font.color.rgb = RGBColor(255, 255, 255)\n",
|
||||
"title_p.alignment = PP_PARAGRAPH_ALIGNMENT.CENTER\n",
|
||||
"\n",
|
||||
"# Add hardcoded \"Key Insights\" text and bullet points\n",
|
||||
"left = prs.slide_width * 2/3\n",
|
||||
"top = Inches(1.5)\n",
|
||||
"width = prs.slide_width * 1/3\n",
|
||||
"height = Inches(4.5)\n",
|
||||
"insights_box = slide.shapes.add_textbox(left, top, width, height)\n",
|
||||
"insights_frame = insights_box.text_frame\n",
|
||||
"insights_p = insights_frame.add_paragraph()\n",
|
||||
"insights_p.text = \"Key Insights:\"\n",
|
||||
"insights_p.font.bold = True\n",
|
||||
"insights_p.font.size = Pt(24)\n",
|
||||
"insights_p.font.color.rgb = RGBColor(0, 128, 100)\n",
|
||||
"insights_p.alignment = PP_PARAGRAPH_ALIGNMENT.LEFT\n",
|
||||
"insights_frame.add_paragraph()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"bullet_p = insights_frame.add_paragraph()\n",
|
||||
"bullet_p.text = bullet_points\n",
|
||||
"bullet_p.font.size = Pt(12)\n",
|
||||
"bullet_p.font.color.rgb = RGBColor(255, 255, 255)\n",
|
||||
"bullet_p.line_spacing = 1.5\n",
|
||||
"\"\"\"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's set a few quick variables for our slides. We want the company name, NotRealCorp, to be on the title slide, and the title of the presentation should 'Quartlerly financial planning metting, Q3, 2023'."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"title_text = \"NotRealCorp\"\n",
|
||||
"subtitle_text = \"Quarterly financial planning meeting, Q3 2023\"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And for the data slide, we have:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here we have a template to create a Title Slide. The template below was created by uploading the image of a desirable title slide to GPT-V, and asking for the `python-pptx` code to create that template. The inputs to the template are the image_path, title_text, and subtitle_text."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Run(id='run_taLrnOnlDhoywgQFFBOLPlg0', assistant_id='asst_3T362kLlTyAq0FUnkvjjQczO', cancelled_at=None, completed_at=None, created_at=1701827118, expires_at=1701827718, failed_at=None, file_ids=['file-piTokyHGllwGITzIpoG8dok3'], instructions='You are a data scientist assistant. When given data and a query, write the proper code and create the proper visualization', last_error=None, metadata={}, model='gpt-4-1106-preview', object='thread.run', required_action=None, started_at=None, status='queued', thread_id='thread_73TgtFoJMlEJvb13ngjTnAo3', tools=[ToolAssistantToolsCode(type='code_interpreter')])"
|
||||
]
|
||||
},
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"submit_message(assistant.id,thread,f\"Use the included code template to create a PPTX slide that follows the template format, but uses the image, company name/title, and document name/subtitle included:\\\n",
|
||||
"{title_template}. IMPORTANT: Use the image file included in this message as the image_path image in this first slide, and use the Company Name {title_text} as the title_text variable, and \\\n",
|
||||
" use the subtitle_text {subtitle_text} a the subtitle_text variable. \\\n",
|
||||
" NEST, create a SECOND slide using the following code template: {data_vis_template} to create a PPTX slide that follows the template format, but uses the company name/title, and document name/subtitle included:\\\n",
|
||||
"{data_vis_template}. IMPORTANT: Use the line plot image, that is the second attached image in this message, that you created earlier in the thread as the data_vis_img image, and use the data visualization title that you created earlier for the variable title_text, and\\\n",
|
||||
" the bullet points of insights you created earlier for the bullet_points variable. Output these TWO SLIDES as a .pptx file. Make sure the output is two slides, with each slide matching the respective template given in this message.\",\n",
|
||||
" file_ids=[dalle_file.id, plot_file.id]\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Assistant still working on PPTX...\n",
|
||||
"Successfully retrieved pptx_id: file-oa0i63qPH4IaJXYj90aA6L4Q\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"#May take 1-3 mins\n",
|
||||
"while True:\n",
|
||||
" try:\n",
|
||||
" response = get_response(thread)\n",
|
||||
" pptx_id = response.data[0].content[0].text.annotations[0].file_path.file_id\n",
|
||||
" print(\"Successfully retrieved pptx_id:\", pptx_id)\n",
|
||||
" break\n",
|
||||
" except Exception as e:\n",
|
||||
" print(\"Assistant still working on PPTX...\")\n",
|
||||
" time.sleep(10)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"pptx_id = response.data[0].content[0].text.annotations[0].file_path.file_id\n",
|
||||
"ppt_file= client.files.content(pptx_id)\n",
|
||||
"file_obj = io.BytesIO(ppt_file.read())\n",
|
||||
"with open(\"data/created_slides.pptx\", \"wb\") as f:\n",
|
||||
" f.write(file_obj.getbuffer())\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now, we have a PPTX file saved with all of our created content!. <br>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's look at the screenshots of the .pptx we just created using JUST the assistants API and DALL·E-3. We don't have a `seed` parameter yet in the Assistants API, so the DALL·E-3 image and wordings will be slightly different from what you see when you run this notebook, due to the non-determinism of LLMs, but the outputs should be directionally the same."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The title slide:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"![Title Slide](../images/title_slide.png)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And the data slide:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"![Data Slide](../images/data_vis_slide.png)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Conclusion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Woo! While these slides could use some formatting tweaks, we have made some great content using the Assistants API, GPT-4 and DALL·E-3. We were able to take a `.csv` file with financial data, and use our assisant to calculate profit by quarter across distribution channels, plot the results, identify insights and key takeaways from the visualization, and create a summarative title. And, given just a description of our company, NotRealCorp, we used DALL·E-3 to make an awesome title image. <br><br>\n",
|
||||
"While we are still a ways away from entirely automating this process without a human in the loop, hopefully this notebook can make the slide creation process a bit easier for you. More importantly, this notebook can ideally give you a glimpse into the potential of the assistants API! We're excited to see what you build."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 6. Extensions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"- When DALL·E-3 is incorporated in the Assistants API, we will have the ability to request the generated title image within the thread. \n",
|
||||
"- GPT-4-Vision is not yet supported in the Assistants API, but could have been used to gather insights from the line plot image.\n",
|
||||
"- GPT-4-Vision was used to generate the `python-pptx` template included in this recipe, so a potential extension project could be demonstrating best practices around converting images to slide templates."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "openai",
|
||||
"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.9.16"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,473 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8ea66173",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to use guardrails\n",
|
||||
"\n",
|
||||
"In this notebook we share examples of how to implement guardrails for your LLM applications. A guardrail is a generic term for **detective controls** that aim to steer your application. Greater steerability is a common requirement given the inherent randomness of LLMs, and so creating effective guardrails has become one of the most common areas of performance optimization when pushing an LLM from prototype to production. \n",
|
||||
"\n",
|
||||
"Guardrails are incredibly [diverse](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/examples/README.md) and can be deployed to virtually any context you can imagine something going wrong with LLMs. This notebook aims to give simple examples that can be extended to meet your unique use case, as well as outlining the trade-offs to consider when deciding whether to implement a guardrail, and how to do it.\n",
|
||||
"\n",
|
||||
"This notebook will focus on:\n",
|
||||
"1. **Input guardrails** that flag inappropriate content before it gets to your LLM\n",
|
||||
"2. **Output guardrails** that validate what your LLM has produced before it gets to the customer\n",
|
||||
"\n",
|
||||
"**Note:** This notebook tackles guardrails as a generic term for detective controls around an LLM - for the official libraries that provide distributions of pre-built guardrails frameworks, please check out the following:\n",
|
||||
"- [NeMo Guardrails](https://github.com/NVIDIA/NeMo-Guardrails/tree/main)\n",
|
||||
"- [Guardrails AI](https://github.com/ShreyaR/guardrails)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "ef059e71",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import openai\n",
|
||||
"\n",
|
||||
"GPT_MODEL = 'gpt-3.5-turbo'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "63d917f0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Input guardrails\n",
|
||||
"\n",
|
||||
"Input guardrails aim to prevent inappropriate content getting to the LLM in the first place - some common use cases are:\n",
|
||||
"- **Topical guardrails:** Identify when a user asks an off-topic question and give them advice on what topics the LLM can help them with.\n",
|
||||
"- **Jailbreaking:** Detect when a user is trying to hijack the LLM and override its prompting.\n",
|
||||
"- **Prompt injection:** Pick up instances of prompt injection where users try to hide malicious code that will be executed in any downstream functions the LLM executes. \n",
|
||||
"\n",
|
||||
"In all of these they act as a preventative control, running either before or in parallel with the LLM, and triggering your application to behave differently if one of these criteria are met.\n",
|
||||
"\n",
|
||||
"### Designing a guardrail\n",
|
||||
"\n",
|
||||
"When designing guardrails it is important to consider the trade-off between **accuracy**, **latency** and **cost**, where you try to achieve maximum accuracy for the least impact to your bottom line and the user's experience. \n",
|
||||
"\n",
|
||||
"We'll begin with a simple **topical guardrail** which aims to detect off-topic questions and prevent the LLM from answering if triggered. This guardrail consists of a simple prompt and uses `gpt-3.5-turbo`, maximising latency/cost over accuracy, but if we wanted to optimize further we could consider:\n",
|
||||
"- **Accuracy:** You could consider using a fine-tuned model or few-shot examples to increase the accuracy. RAG can also be effective if you have a corpus of information that can help determine whether a piece of content is allowed or not.\n",
|
||||
"- **Latency/Cost:** You could try fine-tuning smaller models, such as `babbage-002` or open-source offerings like Llama, which can perform quite well when given enough training examples. When using open-source offerings you can also tune the machines you are using for inference to maximize either cost or latency reduction.\n",
|
||||
"\n",
|
||||
"This simple guardrail aims to ensure the LLM only answers to a predefined set of topics, and responds to out-of-bounds queries with a canned message.\n",
|
||||
"\n",
|
||||
"### Embrace async\n",
|
||||
"\n",
|
||||
"A common design to minimize latency is to send your guardrails asynchronously along with your main LLM call. If your guardrails get triggered you send back their response, otherwise send back the LLM response.\n",
|
||||
"\n",
|
||||
"We'll use this approach, creating an `execute_chat_with_guardrails` function that will run our LLM's `get_chat_response` and the `topical_guardrail` guardrail in parallel, and return the LLM response only if the guardrail returns `allowed`.\n",
|
||||
"\n",
|
||||
"### Limitations\n",
|
||||
"\n",
|
||||
"You should always consider the limitations of guardrails when developing your design. A few of the key ones to be aware of are:\n",
|
||||
"- When using LLMs as a guardrail, be aware that they have the same vulnerabilities as your base LLM call itself. For example, a **prompt injection** attempt could be successful in evading both your guardrail and your actual LLM call.\n",
|
||||
"- As conversations get longer, LLMs are more susceptible to **jailbreaking** as your instructions become diluted by the extra text.\n",
|
||||
"- Guardrails can harm the user experience if you make them overly restrictive to compensate for the issues noted above. This manifests as **over-refusals**, where your guardrails reject innocuous user requests because there are similarities with prompt injection or jailbreaking attempts.\n",
|
||||
"\n",
|
||||
"### Mitigations\n",
|
||||
"\n",
|
||||
"If you can combine guardrails with rules-based or more traditional machine learning models for detection this can mitigate some of these risks. We've also seen customers have guardrails that only ever consider the latest message, to alleviate the risks of the model being confused by a long conversation.\n",
|
||||
"\n",
|
||||
"We would also recommend doing a gradual roll-out with active monitoring of conversations so you can pick up instances of prompt injection or jailbreaking, and either add more guardrails to cover these new types of behaviour, or include them as training examples to your existing guardrails."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "e95efc89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"system_prompt = \"You are a helpful assistant.\"\n",
|
||||
"\n",
|
||||
"bad_request = \"I want to talk about horses\"\n",
|
||||
"good_request = \"What are the best breeds of dog for people that like cats?\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "fee948e2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import asyncio\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def get_chat_response(user_request):\n",
|
||||
" print(\"Getting LLM response\")\n",
|
||||
" messages = [\n",
|
||||
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
||||
" {\"role\": \"user\", \"content\": user_request},\n",
|
||||
" ]\n",
|
||||
" response = openai.chat.completions.create(\n",
|
||||
" model=GPT_MODEL, messages=messages, temperature=0.5\n",
|
||||
" )\n",
|
||||
" print(\"Got LLM response\")\n",
|
||||
"\n",
|
||||
" return response.choices[0].message.content\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def topical_guardrail(user_request):\n",
|
||||
" print(\"Checking topical guardrail\")\n",
|
||||
" messages = [\n",
|
||||
" {\n",
|
||||
" \"role\": \"system\",\n",
|
||||
" \"content\": \"Your role is to assess whether the user question is allowed or not. The allowed topics are cats and dogs. If the topic is allowed, say 'allowed' otherwise say 'not_allowed'\",\n",
|
||||
" },\n",
|
||||
" {\"role\": \"user\", \"content\": user_request},\n",
|
||||
" ]\n",
|
||||
" response = openai.chat.completions.create(\n",
|
||||
" model=GPT_MODEL, messages=messages, temperature=0\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" print(\"Got guardrail response\")\n",
|
||||
" return response.choices[0].message.content\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"async def execute_chat_with_guardrail(user_request):\n",
|
||||
" topical_guardrail_task = asyncio.create_task(topical_guardrail(user_request))\n",
|
||||
" chat_task = asyncio.create_task(get_chat_response(user_request))\n",
|
||||
"\n",
|
||||
" while True:\n",
|
||||
" done, _ = await asyncio.wait(\n",
|
||||
" [topical_guardrail_task, chat_task], return_when=asyncio.FIRST_COMPLETED\n",
|
||||
" )\n",
|
||||
" if topical_guardrail_task in done:\n",
|
||||
" guardrail_response = topical_guardrail_task.result()\n",
|
||||
" if guardrail_response == \"not_allowed\":\n",
|
||||
" chat_task.cancel()\n",
|
||||
" print(\"Topical guardrail triggered\")\n",
|
||||
" return \"I can only talk about cats and dogs, the best animals that ever lived.\"\n",
|
||||
" elif chat_task in done:\n",
|
||||
" chat_response = chat_task.result()\n",
|
||||
" return chat_response\n",
|
||||
" else:\n",
|
||||
" await asyncio.sleep(0.1) # sleep for a bit before checking the tasks again"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "eba51754",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Checking topical guardrail\n",
|
||||
"Got guardrail response\n",
|
||||
"Getting LLM response\n",
|
||||
"Got LLM response\n",
|
||||
"If you're a cat lover considering getting a dog, it's important to choose a breed that typically has a more cat-like temperament. Here are some dog breeds that are known to be more cat-friendly:\n",
|
||||
"\n",
|
||||
"1. Basenji: Known as the \"barkless dog,\" Basenjis are independent, clean, and have a cat-like grooming habit.\n",
|
||||
"\n",
|
||||
"2. Shiba Inu: Shiba Inus are often described as having a cat-like personality. They are independent, clean, and tend to be reserved with strangers.\n",
|
||||
"\n",
|
||||
"3. Greyhound: Greyhounds are quiet, low-energy dogs that enjoy lounging around, much like cats. They are also known for their gentle and calm nature.\n",
|
||||
"\n",
|
||||
"4. Bichon Frise: Bichon Frises are small, friendly dogs that are often compared to cats due to their playful and curious nature. They are also hypoallergenic, making them a good choice for those with allergies.\n",
|
||||
"\n",
|
||||
"5. Cavalier King Charles Spaniel: These dogs are affectionate, gentle, and adaptable, making them a good match for cat lovers. They are known for their desire to be close to their owners and their calm demeanor.\n",
|
||||
"\n",
|
||||
"Remember, individual dogs can have different personalities, so it's important to spend time with the specific dog you're considering to see if their temperament aligns with your preferences.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Call the main function with the good request - this should go through\n",
|
||||
"response = await execute_chat_with_guardrail(good_request)\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "c7d88b57",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Checking topical guardrail\n",
|
||||
"Got guardrail response\n",
|
||||
"Getting LLM response\n",
|
||||
"Got LLM response\n",
|
||||
"Topical guardrail triggered\n",
|
||||
"I can only talk about cats and dogs, the best animals that ever lived.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Call the main function with the good request - this should get blocked\n",
|
||||
"response = await execute_chat_with_guardrail(bad_request)\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "060b408e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Looks like our guardrail worked - the first question was allowed through, but the second was blocked for being off-topic. Now we'll extend this concept to moderate the response we get from the LLM as well."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "07af0154",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Output guardrails\n",
|
||||
"\n",
|
||||
"Output guardrails govern what the LLM comes back with. These can take many forms, with some of the most common being:\n",
|
||||
"- **Hallucination/fact-checking guardrails:** Using a corpus of ground truth information or a training set of hallucinated responses to block hallucinated responses.\n",
|
||||
"- **Moderation guardrails:** Applying brand and corporate guidelines to moderate the LLM's results, and either blocking or rewriting its response if it breaches them.\n",
|
||||
"- **Syntax checks:** Structured outputs from LLMs can be returned corrupt or unable to be parsed - these guardrails detect those and either retry or fail gracefully, preventing failures in downstream applications.\n",
|
||||
" - This is a common control to apply with function calling, ensuring that the expected schema is returned in the `arguments` when the LLM returns a `function_call`.\n",
|
||||
" \n",
|
||||
"### Moderation guardrail\n",
|
||||
"\n",
|
||||
"Here we implement a **moderation guardrail** that uses a version of the [G-Eval](https://arxiv.org/abs/2303.16634) evaluation method to score the presence of unwanted content in the LLM's response. This method is demonstrated in more detail in of our other [notebooks](https://github.com/openai/openai-cookbook/blob/main/examples/evaluation/How_to_eval_abstractive_summarization.ipynb).\n",
|
||||
"\n",
|
||||
"To accomplish this we will make an extensible framework for moderating content that takes in a `domain` and applies `criteria` to a piece of `content` using a set of `steps`:\n",
|
||||
"1. We set a domain name, which describes the type of content we're going to moderate.\n",
|
||||
"2. We provide criteria, which outline clearly what the content should and should not contain.\n",
|
||||
"3. Step-by-step instructions are provided for the LLM to grade the content.\n",
|
||||
"4. The LLM returns a discrete score from 1-5.\n",
|
||||
"\n",
|
||||
"### Setting guardrail thresholds\n",
|
||||
"\n",
|
||||
"Our output guardrail will assess the LLM's response and block anything scoring a 3 or higher. Setting this threshold is a common area for optimization - we recommend building an evaluation set and grading the results using a confusion matrix to set the right tolerance for your guardrail. The trade-off here is generally:\n",
|
||||
"- More false positives leads to a fractured user experience, where customers get annoyed and the assistant seems less helpful.\n",
|
||||
"- More false negatives can cause lasting harm to your business, as people get the assistant to answer inappropriate questions, or prompt inject/jailbreak it.\n",
|
||||
"\n",
|
||||
"For example, for jailbreaking you may want to have a very low threshold, as the risk to your business if your LLM is hijacked and used to produce dangerous content that ends up on social media is very high. However, for our use case we're willing to accept a few false negatives, as the worst that could happen is someone ends up with a Bichon Frise who might have been better suited to a Labrador, which though sad will probably not cause lasting damage to our business (we hope)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "6c7b459f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"domain = \"animal breed recommendation\"\n",
|
||||
"\n",
|
||||
"animal_advice_criteria = \"\"\"\n",
|
||||
"Assess the presence of explicit recommendation of cat or dog breeds in the content.\n",
|
||||
"The content should contain only general advice about cats and dogs, not specific breeds to purchase.\"\"\"\n",
|
||||
"\n",
|
||||
"animal_advice_steps = \"\"\"\n",
|
||||
"1. Read the content and the criteria carefully.\n",
|
||||
"2. Assess how much explicit recommendation of cat or dog breeds is contained in the content.\n",
|
||||
"3. Assign an animal advice score from 1 to 5, with 1 being no explicit cat or dog breed advice, and 5 being multiple named cat or dog breeds.\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"moderation_system_prompt = \"\"\"\n",
|
||||
"You are a moderation assistant. Your role is to detect content about {domain} in the text provided, and mark the severity of that content.\n",
|
||||
"\n",
|
||||
"## {domain}\n",
|
||||
"\n",
|
||||
"### Criteria\n",
|
||||
"\n",
|
||||
"{scoring_criteria}\n",
|
||||
"\n",
|
||||
"### Instructions\n",
|
||||
"\n",
|
||||
"{scoring_steps}\n",
|
||||
"\n",
|
||||
"### Content\n",
|
||||
"\n",
|
||||
"{content}\n",
|
||||
"\n",
|
||||
"### Evaluation (score only!)\n",
|
||||
"\"\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "43e3fd36",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"async def moderation_guardrail(chat_response):\n",
|
||||
" print(\"Checking moderation guardrail\")\n",
|
||||
" mod_messages = [\n",
|
||||
" {\"role\": \"user\", \"content\": moderation_system_prompt.format(\n",
|
||||
" domain=domain,\n",
|
||||
" scoring_criteria=animal_advice_criteria,\n",
|
||||
" scoring_steps=animal_advice_steps,\n",
|
||||
" content=chat_response\n",
|
||||
" )},\n",
|
||||
" ]\n",
|
||||
" response = openai.chat.completions.create(\n",
|
||||
" model=GPT_MODEL, messages=mod_messages, temperature=0\n",
|
||||
" )\n",
|
||||
" print(\"Got moderation response\")\n",
|
||||
" return response.choices[0].message.content\n",
|
||||
" \n",
|
||||
" \n",
|
||||
"async def execute_all_guardrails(user_request):\n",
|
||||
" topical_guardrail_task = asyncio.create_task(topical_guardrail(user_request))\n",
|
||||
" chat_task = asyncio.create_task(get_chat_response(user_request))\n",
|
||||
"\n",
|
||||
" while True:\n",
|
||||
" done, _ = await asyncio.wait(\n",
|
||||
" [topical_guardrail_task, chat_task], return_when=asyncio.FIRST_COMPLETED\n",
|
||||
" )\n",
|
||||
" if topical_guardrail_task in done:\n",
|
||||
" guardrail_response = topical_guardrail_task.result()\n",
|
||||
" if guardrail_response == \"not_allowed\":\n",
|
||||
" chat_task.cancel()\n",
|
||||
" print(\"Topical guardrail triggered\")\n",
|
||||
" return \"I can only talk about cats and dogs, the best animals that ever lived.\"\n",
|
||||
" elif chat_task in done:\n",
|
||||
" chat_response = chat_task.result()\n",
|
||||
" moderation_response = await moderation_guardrail(chat_response)\n",
|
||||
"\n",
|
||||
" if int(moderation_response) >= 3:\n",
|
||||
" print(f\"Moderation guardrail flagged with a score of {int(moderation_response)}\")\n",
|
||||
" return \"Sorry, we're not permitted to give animal breed advice. I can help you with any general queries you might have.\"\n",
|
||||
"\n",
|
||||
" else:\n",
|
||||
" print('Passed moderation')\n",
|
||||
" return chat_response\n",
|
||||
" else:\n",
|
||||
" await asyncio.sleep(0.1) # sleep for a bit before checking the tasks again"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "beea1305",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Adding a request that should pass both our topical guardrail and our moderation guardrail\n",
|
||||
"great_request = 'What is some advice you can give to a new dog owner?'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "1c582b4d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Checking topical guardrail\n",
|
||||
"Got guardrail response\n",
|
||||
"Getting LLM response\n",
|
||||
"Got LLM response\n",
|
||||
"Checking moderation guardrail\n",
|
||||
"Got moderation response\n",
|
||||
"Moderation guardrail flagged with a score of 5\n",
|
||||
"Sorry, we're not permitted to give animal breed advice. I can help you with any general queries you might have.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Checking topical guardrail\n",
|
||||
"Got guardrail response\n",
|
||||
"Getting LLM response\n",
|
||||
"Got LLM response\n",
|
||||
"Topical guardrail triggered\n",
|
||||
"I can only talk about cats and dogs, the best animals that ever lived.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Checking topical guardrail\n",
|
||||
"Got guardrail response\n",
|
||||
"Getting LLM response\n",
|
||||
"Got LLM response\n",
|
||||
"Checking moderation guardrail\n",
|
||||
"Got moderation response\n",
|
||||
"Passed moderation\n",
|
||||
"As a new dog owner, here are some helpful tips:\n",
|
||||
"\n",
|
||||
"1. Choose the right breed: Research different dog breeds to find one that suits your lifestyle, activity level, and living situation. Some breeds require more exercise and attention than others.\n",
|
||||
"\n",
|
||||
"2. Puppy-proof your home: Make sure your home is safe for your new furry friend. Remove any toxic plants, secure loose wires, and store household chemicals out of reach.\n",
|
||||
"\n",
|
||||
"3. Establish a routine: Dogs thrive on routine, so establish a consistent schedule for feeding, exercise, and bathroom breaks. This will help your dog feel secure and reduce any anxiety.\n",
|
||||
"\n",
|
||||
"4. Socialize your dog: Expose your dog to different people, animals, and environments from an early age. This will help them become well-adjusted and comfortable in various situations.\n",
|
||||
"\n",
|
||||
"5. Train your dog: Basic obedience training is essential for your dog's safety and your peace of mind. Teach commands like sit, stay, and come, and use positive reinforcement techniques such as treats and praise.\n",
|
||||
"\n",
|
||||
"6. Provide mental and physical stimulation: Dogs need both mental and physical exercise to stay happy and healthy. Engage in activities like walks, playtime, puzzle toys, and training sessions to keep your dog mentally stimulated.\n",
|
||||
"\n",
|
||||
"7. Proper nutrition: Feed your dog a balanced and appropriate diet based on their age, size, and specific needs. Consult with a veterinarian to determine the best food options for your dog.\n",
|
||||
"\n",
|
||||
"8. Regular veterinary care: Schedule regular check-ups with a veterinarian to ensure your dog's health and well-being. Vaccinations, parasite prevention, and dental care are important aspects of their overall care.\n",
|
||||
"\n",
|
||||
"9. Be patient and consistent: Dogs require time, patience, and consistency to learn and adapt to their new environment. Stay positive, be patient with their training, and provide clear and consistent boundaries.\n",
|
||||
"\n",
|
||||
"10. Show love and affection: Dogs are social animals that thrive on love and affection. Spend quality time with your dog, offer praise and cuddles, and make them feel like an important part of your family.\n",
|
||||
"\n",
|
||||
"Remember, being a responsible dog owner involves commitment, time, and effort. With proper care and attention, you can build a strong bond with your new furry companion.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tests = [good_request,bad_request,great_request]\n",
|
||||
"\n",
|
||||
"for test in tests:\n",
|
||||
" result = await execute_all_guardrails(test)\n",
|
||||
" print(result)\n",
|
||||
" print('\\n\\n')\n",
|
||||
" "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4763dd2d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Conclusion\n",
|
||||
"\n",
|
||||
"Guardrails are a vibrant and evolving topic in LLMs, and we hope this notebook has given you an effective introduction to the core concepts around guardrails. To recap:\n",
|
||||
"- Guardrails are detective controls that aim to prevent harmful content getting to your applications and your users, and add steerability to your LLM in production.\n",
|
||||
"- They can take the form of input guardrails, which target content before it gets to the LLM, and output guardrails, which control the LLM's response.\n",
|
||||
"- Designing guardrails and setting their thresholds is a trade-off between accuracy, latency, and cost. Your decision should be based on clear evaluations of the performance of your guardrails, and an understanding of what the cost of a false negative and false positive are for your business.\n",
|
||||
"- By embracing asynchronous design principles, you can scale guardrails horizontally to minimize the impact to the user as your guardrails increase in number and scope.\n",
|
||||
"\n",
|
||||
"We look forward to seeing how you take this forward, and how thinking on guardrails evolves as the ecosystem matures. "
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "openai_test",
|
||||
"language": "python",
|
||||
"name": "openai_test"
|
||||
},
|
||||
"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.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,320 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Azure completions example\n",
|
||||
"\n",
|
||||
"This example will cover completions using the Azure OpenAI service. It also includes information on content filtering."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"First, we install the necessary dependencies and import the libraries we will be using."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install \"openai>=1.0.0,<2.0.0\"\n",
|
||||
"! pip install python-dotenv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import openai\n",
|
||||
"import dotenv\n",
|
||||
"\n",
|
||||
"dotenv.load_dotenv()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Authentication\n",
|
||||
"\n",
|
||||
"The Azure OpenAI service supports multiple authentication mechanisms that include API keys and Azure Active Directory token credentials."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"use_azure_active_directory = False # Set this flag to True if you are using Azure Active Directory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Authentication using API key\n",
|
||||
"\n",
|
||||
"To set up the OpenAI SDK to use an *Azure API Key*, we need to set `api_key` to a key associated with your endpoint (you can find this key in *\"Keys and Endpoints\"* under *\"Resource Management\"* in the [Azure Portal](https://portal.azure.com)). You'll also find the endpoint for your resource here."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if not use_azure_active_directory:\n",
|
||||
" endpoint = os.environ[\"AZURE_OPENAI_ENDPOINT\"]\n",
|
||||
" api_key = os.environ[\"AZURE_OPENAI_API_KEY\"]\n",
|
||||
"\n",
|
||||
" client = openai.AzureOpenAI(\n",
|
||||
" azure_endpoint=endpoint,\n",
|
||||
" api_key=api_key,\n",
|
||||
" api_version=\"2023-09-01-preview\"\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Authentication using Azure Active Directory\n",
|
||||
"Let's now see how we can autheticate via Azure Active Directory. We'll start by installing the `azure-identity` library. This library will provide the token credentials we need to authenticate and help us build a token credential provider through the `get_bearer_token_provider` helper function. It's recommended to use `get_bearer_token_provider` over providing a static token to `AzureOpenAI` because this API will automatically cache and refresh tokens for you. \n",
|
||||
"\n",
|
||||
"For more information on how to set up Azure Active Directory authentication with Azure OpenAI, see the [documentation](https://learn.microsoft.com/azure/ai-services/openai/how-to/managed-identity)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install \"azure-identity>=1.15.0\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from azure.identity import DefaultAzureCredential, get_bearer_token_provider\n",
|
||||
"\n",
|
||||
"if use_azure_active_directory:\n",
|
||||
" endpoint = os.environ[\"AZURE_OPENAI_ENDPOINT\"]\n",
|
||||
" api_key = os.environ[\"AZURE_OPENAI_API_KEY\"]\n",
|
||||
"\n",
|
||||
" client = openai.AzureOpenAI(\n",
|
||||
" azure_endpoint=endpoint,\n",
|
||||
" azure_ad_token_provider=get_bearer_token_provider(DefaultAzureCredential(), \"https://cognitiveservices.azure.com/.default\"),\n",
|
||||
" api_version=\"2023-09-01-preview\"\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> Note: the AzureOpenAI infers the following arguments from their corresponding environment variables if they are not provided:\n",
|
||||
"\n",
|
||||
"- `api_key` from `AZURE_OPENAI_API_KEY`\n",
|
||||
"- `azure_ad_token` from `AZURE_OPENAI_AD_TOKEN`\n",
|
||||
"- `api_version` from `OPENAI_API_VERSION`\n",
|
||||
"- `azure_endpoint` from `AZURE_OPENAI_ENDPOINT`\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Deployments\n",
|
||||
"\n",
|
||||
"In this section we are going to create a deployment of a model that we can use to create completions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Deployments: Create in the Azure OpenAI Studio\n",
|
||||
"Let's deploy a model to use with completions. Go to https://portal.azure.com, find your Azure OpenAI resource, and then navigate to the Azure OpenAI Studio. Click on the \"Deployments\" tab and then create a deployment for the model you want to use for completions. The deployment name that you give the model will be used in the code below."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"deployment = \"\" # Fill in the deployment name from the portal here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Completions\n",
|
||||
"\n",
|
||||
"Now let's create a completion using the client we built."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"prompt = \"The food was delicious and the waiter\"\n",
|
||||
"completion = client.completions.create(\n",
|
||||
" model=deployment,\n",
|
||||
" prompt=prompt,\n",
|
||||
" stop=\".\",\n",
|
||||
" temperature=0\n",
|
||||
")\n",
|
||||
" \n",
|
||||
"print(f\"{prompt}{completion.choices[0].text}.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Create a streaming completion\n",
|
||||
"\n",
|
||||
"We can also stream the response."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"prompt = \"The food was delicious and the waiter\"\n",
|
||||
"response = client.completions.create(\n",
|
||||
" model=deployment,\n",
|
||||
" prompt=prompt,\n",
|
||||
" stream=True,\n",
|
||||
")\n",
|
||||
"for completion in response:\n",
|
||||
" if len(completion.choices) > 0:\n",
|
||||
" print(f\"{completion.choices[0].text}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Content filtering\n",
|
||||
"\n",
|
||||
"Azure OpenAI service includes content filtering of prompts and completion responses. You can learn more about content filtering and how to configure it [here](https://learn.microsoft.com/azure/ai-services/openai/concepts/content-filter).\n",
|
||||
"\n",
|
||||
"If the prompt is flagged by the content filter, the library will raise a `BadRequestError` exception with a `content_filter` error code. Otherwise, you can access the `prompt_filter_results` and `content_filter_results` on the response to see the results of the content filtering and what categories were flagged."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Prompt flagged by content filter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" completion = client.completions.create(\n",
|
||||
" prompt=\"<text violating the content policy>\",\n",
|
||||
" model=deployment,\n",
|
||||
" )\n",
|
||||
"except openai.BadRequestError as e:\n",
|
||||
" err = json.loads(e.response.text)\n",
|
||||
" if err[\"error\"][\"code\"] == \"content_filter\":\n",
|
||||
" print(\"Content filter triggered!\")\n",
|
||||
" content_filter_result = err[\"error\"][\"innererror\"][\"content_filter_result\"]\n",
|
||||
" for category, details in content_filter_result.items():\n",
|
||||
" print(f\"{category}:\\n filtered={details['filtered']}\\n severity={details['severity']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Checking the result of the content filter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"completion = client.completions.create(\n",
|
||||
" prompt=\"What's the biggest city in Washington?\",\n",
|
||||
" model=deployment,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(f\"Answer: {completion.choices[0].text}\")\n",
|
||||
"\n",
|
||||
"# prompt content filter result in \"model_extra\" for azure\n",
|
||||
"prompt_filter_result = completion.model_extra[\"prompt_filter_results\"][0][\"content_filter_results\"]\n",
|
||||
"print(\"\\nPrompt content filter results:\")\n",
|
||||
"for category, details in prompt_filter_result.items():\n",
|
||||
" print(f\"{category}:\\n filtered={details['filtered']}\\n severity={details['severity']}\")\n",
|
||||
"\n",
|
||||
"# completion content filter result\n",
|
||||
"print(\"\\nCompletion content filter results:\")\n",
|
||||
"completion_filter_result = completion.choices[0].model_extra[\"content_filter_results\"]\n",
|
||||
"for category, details in completion_filter_result.items():\n",
|
||||
" print(f\"{category}:\\n filtered={details['filtered']}\\n severity={details['severity']}\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"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.0"
|
||||
},
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "3a5103089ab7e7c666b279eeded403fcec76de49a40685dbdfe9f9c78ad97c17"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
{"Year":{"0":2021,"1":2021,"2":2021,"3":2021,"4":2021,"5":2021,"6":2021,"7":2021,"8":2021,"9":2021,"10":2021,"11":2021,"12":2022,"13":2022,"14":2022,"15":2022,"16":2022,"17":2022,"18":2022,"19":2022,"20":2022,"21":2022,"22":2022,"23":2022,"24":2023,"25":2023,"26":2023,"27":2023,"28":2023,"29":2023,"30":2023,"31":2023,"32":2023,"33":2023,"34":2023,"35":2023,"36":2024,"37":2024,"38":2024,"39":2024,"40":2024,"41":2024,"42":2024,"43":2024,"44":2024,"45":2024,"46":2024,"47":2024},"Quarter":{"0":"Q1","1":"Q1","2":"Q1","3":"Q2","4":"Q2","5":"Q2","6":"Q3","7":"Q3","8":"Q3","9":"Q4","10":"Q4","11":"Q4","12":"Q1","13":"Q1","14":"Q1","15":"Q2","16":"Q2","17":"Q2","18":"Q3","19":"Q3","20":"Q3","21":"Q4","22":"Q4","23":"Q4","24":"Q1","25":"Q1","26":"Q1","27":"Q2","28":"Q2","29":"Q2","30":"Q3","31":"Q3","32":"Q3","33":"Q4","34":"Q4","35":"Q4","36":"Q1","37":"Q1","38":"Q1","39":"Q2","40":"Q2","41":"Q2","42":"Q3","43":"Q3","44":"Q3","45":"Q4","46":"Q4","47":"Q4"},"Distribution channel":{"0":"Online Sales","1":"Direct Sales","2":"Retail Partners","3":"Online Sales","4":"Direct Sales","5":"Retail Partners","6":"Online Sales","7":"Direct Sales","8":"Retail Partners","9":"Online Sales","10":"Direct Sales","11":"Retail Partners","12":"Online Sales","13":"Direct Sales","14":"Retail Partners","15":"Online Sales","16":"Direct Sales","17":"Retail Partners","18":"Online Sales","19":"Direct Sales","20":"Retail Partners","21":"Online Sales","22":"Direct Sales","23":"Retail Partners","24":"Online Sales","25":"Direct Sales","26":"Retail Partners","27":"Online Sales","28":"Direct Sales","29":"Retail Partners","30":"Online Sales","31":"Direct Sales","32":"Retail Partners","33":"Online Sales","34":"Direct Sales","35":"Retail Partners","36":"Online Sales","37":"Direct Sales","38":"Retail Partners","39":"Online Sales","40":"Direct Sales","41":"Retail Partners","42":"Online Sales","43":"Direct Sales","44":"Retail Partners","45":"Online Sales","46":"Direct Sales","47":"Retail Partners"},"Revenue ($M)":{"0":1.5,"1":1.5,"2":1.5,"3":1.52,"4":1.52,"5":1.52,"6":1.54,"7":1.54,"8":1.54,"9":1.56,"10":1.56,"11":1.56,"12":1.7,"13":1.6,"14":1.55,"15":1.72,"16":1.62,"17":1.57,"18":1.74,"19":1.64,"20":1.59,"21":1.76,"22":1.66,"23":1.61,"24":1.9,"25":1.7,"26":1.6,"27":1.92,"28":1.72,"29":1.62,"30":1.94,"31":1.74,"32":1.64,"33":1.96,"34":1.76,"35":1.66,"36":2.1,"37":1.8,"38":1.65,"39":2.12,"40":1.82,"41":1.67,"42":2.14,"43":1.84,"44":1.69,"45":2.16,"46":1.86,"47":1.71},"Costs ($M)":{"0":1.3019525402,"1":1.3808087359,"2":1.3482460133,"3":1.3086075747,"4":1.4133047938,"5":1.352737358,"6":1.304110535,"7":1.41112627,"8":1.330751592,"9":1.3017953273,"10":1.4148004859,"11":1.3547054199,"12":1.296946192,"13":1.4191447337,"14":1.3544838289,"15":1.3058357645,"16":1.4119663426,"17":1.3546773599,"18":1.2975034885,"19":1.3984591745,"20":1.3677499231,"21":1.31567092,"22":1.4112211671,"23":1.357272812,"24":1.3185465104,"25":1.384730977,"26":1.364380316,"27":1.2953376608,"28":1.4055968409,"29":1.3874812782,"30":1.3116690015,"31":1.3857341315,"32":1.4179052478,"33":1.3011557968,"34":1.4177867567,"35":1.4124090189,"36":1.3027217824,"37":1.4008739329,"38":1.4566706686,"39":1.3170238655,"40":1.3965864776,"41":1.4768255148,"42":1.2828414423,"43":1.3905822245,"44":1.4784153024,"45":1.283485172,"46":1.4109693476,"47":1.4951570519},"Customer count":{"0":150,"1":151,"2":152,"3":152,"4":153,"5":154,"6":154,"7":155,"8":156,"9":156,"10":157,"11":158,"12":160,"13":161,"14":162,"15":162,"16":163,"17":164,"18":164,"19":165,"20":166,"21":166,"22":167,"23":168,"24":170,"25":171,"26":172,"27":172,"28":173,"29":174,"30":174,"31":175,"32":176,"33":176,"34":177,"35":178,"36":180,"37":181,"38":182,"39":182,"40":183,"41":184,"42":184,"43":185,"44":186,"45":186,"46":187,"47":188},"Time":{"0":"2021 Q1","1":"2021 Q1","2":"2021 Q1","3":"2021 Q2","4":"2021 Q2","5":"2021 Q2","6":"2021 Q3","7":"2021 Q3","8":"2021 Q3","9":"2021 Q4","10":"2021 Q4","11":"2021 Q4","12":"2022 Q1","13":"2022 Q1","14":"2022 Q1","15":"2022 Q2","16":"2022 Q2","17":"2022 Q2","18":"2022 Q3","19":"2022 Q3","20":"2022 Q3","21":"2022 Q4","22":"2022 Q4","23":"2022 Q4","24":"2023 Q1","25":"2023 Q1","26":"2023 Q1","27":"2023 Q2","28":"2023 Q2","29":"2023 Q2","30":"2023 Q3","31":"2023 Q3","32":"2023 Q3","33":"2023 Q4","34":"2023 Q4","35":"2023 Q4","36":"2024 Q1","37":"2024 Q1","38":"2024 Q1","39":"2024 Q2","40":"2024 Q2","41":"2024 Q2","42":"2024 Q3","43":"2024 Q3","44":"2024 Q3","45":"2024 Q4","46":"2024 Q4","47":"2024 Q4"}}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,738 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Azure AI Search as a vector database for OpenAI embeddings"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This notebook provides step by step instuctions on using Azure AI Search (f.k.a Azure Cognitive Search) as a vector database with OpenAI embeddings. Azure AI Search is a cloud search service that gives developers infrastructure, APIs, and tools for building a rich search experience over private, heterogeneous content in web, mobile, and enterprise applications.\n",
|
||||
"\n",
|
||||
"## Prerequistites:\n",
|
||||
"For the purposes of this exercise you must have the following:\n",
|
||||
"- [Azure AI Search Service](https://learn.microsoft.com/azure/search/)\n",
|
||||
"- [OpenAI Key](https://platform.openai.com/account/api-keys) or [Azure OpenAI credentials](https://learn.microsoft.com/azure/cognitive-services/openai/)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install wget\n",
|
||||
"! pip install azure-search-documents \n",
|
||||
"! pip install azure-identity\n",
|
||||
"! pip install openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Import required libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json \n",
|
||||
"import wget\n",
|
||||
"import pandas as pd\n",
|
||||
"import zipfile\n",
|
||||
"from openai import AzureOpenAI\n",
|
||||
"from azure.identity import DefaultAzureCredential, get_bearer_token_provider\n",
|
||||
"from azure.core.credentials import AzureKeyCredential \n",
|
||||
"from azure.search.documents import SearchClient, SearchIndexingBufferedSender \n",
|
||||
"from azure.search.documents.indexes import SearchIndexClient \n",
|
||||
"from azure.search.documents.models import (\n",
|
||||
" QueryAnswerType,\n",
|
||||
" QueryCaptionType,\n",
|
||||
" QueryType,\n",
|
||||
" VectorizedQuery,\n",
|
||||
")\n",
|
||||
"from azure.search.documents.indexes.models import (\n",
|
||||
" HnswAlgorithmConfiguration,\n",
|
||||
" HnswParameters,\n",
|
||||
" SearchField,\n",
|
||||
" SearchableField,\n",
|
||||
" SearchFieldDataType,\n",
|
||||
" SearchIndex,\n",
|
||||
" SemanticConfiguration,\n",
|
||||
" SemanticField,\n",
|
||||
" SemanticPrioritizedFields,\n",
|
||||
" SemanticSearch,\n",
|
||||
" SimpleField,\n",
|
||||
" VectorSearch,\n",
|
||||
" VectorSearchAlgorithmKind,\n",
|
||||
" VectorSearchAlgorithmMetric,\n",
|
||||
" VectorSearchProfile,\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Configure OpenAI settings\n",
|
||||
"\n",
|
||||
"This section guides you through setting up authentication for Azure OpenAI, allowing you to securely interact with the service using either Azure Active Directory (AAD) or an API key. Before proceeding, ensure you have your Azure OpenAI endpoint and credentials ready. For detailed instructions on setting up AAD with Azure OpenAI, refer to the [official documentation](https://learn.microsoft.com/azure/ai-services/openai/how-to/managed-identity).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"endpoint: str = \"YOUR_AZURE_OPENAI_ENDPOINT\"\n",
|
||||
"api_key: str = \"YOUR_AZURE_OPENAI_KEY\"\n",
|
||||
"api_version: str = \"2023-05-15\"\n",
|
||||
"deployment = \"YOUR_AZURE_OPENAI_DEPLOYMENT_NAME\"\n",
|
||||
"credential = DefaultAzureCredential()\n",
|
||||
"token_provider = get_bearer_token_provider(\n",
|
||||
" credential, \"https://cognitiveservices.azure.com/.default\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Set this flag to True if you are using Azure Active Directory\n",
|
||||
"use_aad_for_aoai = True \n",
|
||||
"\n",
|
||||
"if use_aad_for_aoai:\n",
|
||||
" # Use Azure Active Directory (AAD) authentication\n",
|
||||
" client = AzureOpenAI(\n",
|
||||
" azure_endpoint=endpoint,\n",
|
||||
" api_version=api_version,\n",
|
||||
" azure_ad_token_provider=token_provider,\n",
|
||||
" )\n",
|
||||
"else:\n",
|
||||
" # Use API key authentication\n",
|
||||
" client = AzureOpenAI(\n",
|
||||
" api_key=api_key,\n",
|
||||
" api_version=api_version,\n",
|
||||
" azure_endpoint=endpoint,\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Configure Azure AI Search Vector Store settings\n",
|
||||
"This section explains how to set up the Azure AI Search client for integrating with the Vector Store feature. You can locate your Azure AI Search service details in the Azure Portal or programmatically via the [Search Management SDK](https://learn.microsoft.com/rest/api/searchmanagement/).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Configuration\n",
|
||||
"search_service_endpoint: str = \"YOUR_AZURE_SEARCH_ENDPOINT\"\n",
|
||||
"search_service_api_key: str = \"YOUR_AZURE_SEARCH_ADMIN_KEY\"\n",
|
||||
"index_name: str = \"azure-ai-search-openai-cookbook-demo\"\n",
|
||||
"\n",
|
||||
"# Set this flag to True if you are using Azure Active Directory\n",
|
||||
"use_aad_for_search = True \n",
|
||||
"\n",
|
||||
"if use_aad_for_search:\n",
|
||||
" # Use Azure Active Directory (AAD) authentication\n",
|
||||
" credential = DefaultAzureCredential()\n",
|
||||
"else:\n",
|
||||
" # Use API key authentication\n",
|
||||
" credential = AzureKeyCredential(search_service_api_key)\n",
|
||||
"\n",
|
||||
"# Initialize the SearchClient with the selected authentication method\n",
|
||||
"search_client = SearchClient(\n",
|
||||
" endpoint=search_service_endpoint, index_name=index_name, credential=credential\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load data\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'vector_database_wikipedia_articles_embedded.zip'"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embeddings_url = \"https://cdn.openai.com/API/examples/data/vector_database_wikipedia_articles_embedded.zip\"\n",
|
||||
"\n",
|
||||
"# The file is ~700 MB so this will take some time\n",
|
||||
"wget.download(embeddings_url)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"with zipfile.ZipFile(\"vector_database_wikipedia_articles_embedded.zip\", \"r\") as zip_ref:\n",
|
||||
" zip_ref.extractall(\"../../data\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>id</th>\n",
|
||||
" <th>url</th>\n",
|
||||
" <th>title</th>\n",
|
||||
" <th>text</th>\n",
|
||||
" <th>title_vector</th>\n",
|
||||
" <th>content_vector</th>\n",
|
||||
" <th>vector_id</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>https://simple.wikipedia.org/wiki/April</td>\n",
|
||||
" <td>April</td>\n",
|
||||
" <td>April is the fourth month of the year in the J...</td>\n",
|
||||
" <td>[0.001009464613161981, -0.020700545981526375, ...</td>\n",
|
||||
" <td>[-0.011253940872848034, -0.013491976074874401,...</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>https://simple.wikipedia.org/wiki/August</td>\n",
|
||||
" <td>August</td>\n",
|
||||
" <td>August (Aug.) is the eighth month of the year ...</td>\n",
|
||||
" <td>[0.0009286514250561595, 0.000820168002974242, ...</td>\n",
|
||||
" <td>[0.0003609954728744924, 0.007262262050062418, ...</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>https://simple.wikipedia.org/wiki/Art</td>\n",
|
||||
" <td>Art</td>\n",
|
||||
" <td>Art is a creative activity that expresses imag...</td>\n",
|
||||
" <td>[0.003393713850528002, 0.0061537534929811954, ...</td>\n",
|
||||
" <td>[-0.004959689453244209, 0.015772193670272827, ...</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>https://simple.wikipedia.org/wiki/A</td>\n",
|
||||
" <td>A</td>\n",
|
||||
" <td>A or a is the first letter of the English alph...</td>\n",
|
||||
" <td>[0.0153952119871974, -0.013759135268628597, 0....</td>\n",
|
||||
" <td>[0.024894846603274345, -0.022186409682035446, ...</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>9</td>\n",
|
||||
" <td>https://simple.wikipedia.org/wiki/Air</td>\n",
|
||||
" <td>Air</td>\n",
|
||||
" <td>Air refers to the Earth's atmosphere. Air is a...</td>\n",
|
||||
" <td>[0.02224554680287838, -0.02044147066771984, -0...</td>\n",
|
||||
" <td>[0.021524671465158463, 0.018522677943110466, -...</td>\n",
|
||||
" <td>4</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" id url title \\\n",
|
||||
"0 1 https://simple.wikipedia.org/wiki/April April \n",
|
||||
"1 2 https://simple.wikipedia.org/wiki/August August \n",
|
||||
"2 6 https://simple.wikipedia.org/wiki/Art Art \n",
|
||||
"3 8 https://simple.wikipedia.org/wiki/A A \n",
|
||||
"4 9 https://simple.wikipedia.org/wiki/Air Air \n",
|
||||
"\n",
|
||||
" text \\\n",
|
||||
"0 April is the fourth month of the year in the J... \n",
|
||||
"1 August (Aug.) is the eighth month of the year ... \n",
|
||||
"2 Art is a creative activity that expresses imag... \n",
|
||||
"3 A or a is the first letter of the English alph... \n",
|
||||
"4 Air refers to the Earth's atmosphere. Air is a... \n",
|
||||
"\n",
|
||||
" title_vector \\\n",
|
||||
"0 [0.001009464613161981, -0.020700545981526375, ... \n",
|
||||
"1 [0.0009286514250561595, 0.000820168002974242, ... \n",
|
||||
"2 [0.003393713850528002, 0.0061537534929811954, ... \n",
|
||||
"3 [0.0153952119871974, -0.013759135268628597, 0.... \n",
|
||||
"4 [0.02224554680287838, -0.02044147066771984, -0... \n",
|
||||
"\n",
|
||||
" content_vector vector_id \n",
|
||||
"0 [-0.011253940872848034, -0.013491976074874401,... 0 \n",
|
||||
"1 [0.0003609954728744924, 0.007262262050062418, ... 1 \n",
|
||||
"2 [-0.004959689453244209, 0.015772193670272827, ... 2 \n",
|
||||
"3 [0.024894846603274345, -0.022186409682035446, ... 3 \n",
|
||||
"4 [0.021524671465158463, 0.018522677943110466, -... 4 "
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"article_df = pd.read_csv(\"../../data/vector_database_wikipedia_articles_embedded.csv\")\n",
|
||||
"\n",
|
||||
"# Read vectors from strings back into a list using json.loads\n",
|
||||
"article_df[\"title_vector\"] = article_df.title_vector.apply(json.loads)\n",
|
||||
"article_df[\"content_vector\"] = article_df.content_vector.apply(json.loads)\n",
|
||||
"article_df[\"vector_id\"] = article_df[\"vector_id\"].apply(str)\n",
|
||||
"article_df.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create an index\n",
|
||||
"This code snippet demonstrates how to define and create a search index using the `SearchIndexClient` from the Azure AI Search Python SDK. The index incorporates both vector search and semantic ranker capabilities. For more details, visit our documentation on how to [Create a Vector Index](https://learn.microsoft.com/azure/search/vector-search-how-to-create-index?.tabs=config-2023-11-01%2Crest-2023-11-01%2Cpush%2Cportal-check-index)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"azure-ai-search-openai-cookbook-demo created\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Initialize the SearchIndexClient\n",
|
||||
"index_client = SearchIndexClient(\n",
|
||||
" endpoint=search_service_endpoint, credential=credential\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Define the fields for the index\n",
|
||||
"fields = [\n",
|
||||
" SimpleField(name=\"id\", type=SearchFieldDataType.String),\n",
|
||||
" SimpleField(name=\"vector_id\", type=SearchFieldDataType.String, key=True),\n",
|
||||
" SimpleField(name=\"url\", type=SearchFieldDataType.String),\n",
|
||||
" SearchableField(name=\"title\", type=SearchFieldDataType.String),\n",
|
||||
" SearchableField(name=\"text\", type=SearchFieldDataType.String),\n",
|
||||
" SearchField(\n",
|
||||
" name=\"title_vector\",\n",
|
||||
" type=SearchFieldDataType.Collection(SearchFieldDataType.Single),\n",
|
||||
" vector_search_dimensions=1536,\n",
|
||||
" vector_search_profile_name=\"my-vector-config\",\n",
|
||||
" ),\n",
|
||||
" SearchField(\n",
|
||||
" name=\"content_vector\",\n",
|
||||
" type=SearchFieldDataType.Collection(SearchFieldDataType.Single),\n",
|
||||
" vector_search_dimensions=1536,\n",
|
||||
" vector_search_profile_name=\"my-vector-config\",\n",
|
||||
" ),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"# Configure the vector search configuration\n",
|
||||
"vector_search = VectorSearch(\n",
|
||||
" algorithms=[\n",
|
||||
" HnswAlgorithmConfiguration(\n",
|
||||
" name=\"my-hnsw\",\n",
|
||||
" kind=VectorSearchAlgorithmKind.HNSW,\n",
|
||||
" parameters=HnswParameters(\n",
|
||||
" m=4,\n",
|
||||
" ef_construction=400,\n",
|
||||
" ef_search=500,\n",
|
||||
" metric=VectorSearchAlgorithmMetric.COSINE,\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
" ],\n",
|
||||
" profiles=[\n",
|
||||
" VectorSearchProfile(\n",
|
||||
" name=\"my-vector-config\",\n",
|
||||
" algorithm_configuration_name=\"my-hnsw\",\n",
|
||||
" )\n",
|
||||
" ],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Configure the semantic search configuration\n",
|
||||
"semantic_search = SemanticSearch(\n",
|
||||
" configurations=[\n",
|
||||
" SemanticConfiguration(\n",
|
||||
" name=\"my-semantic-config\",\n",
|
||||
" prioritized_fields=SemanticPrioritizedFields(\n",
|
||||
" title_field=SemanticField(field_name=\"title\"),\n",
|
||||
" keywords_fields=[SemanticField(field_name=\"url\")],\n",
|
||||
" content_fields=[SemanticField(field_name=\"text\")],\n",
|
||||
" ),\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Create the search index with the vector search and semantic search configurations\n",
|
||||
"index = SearchIndex(\n",
|
||||
" name=index_name,\n",
|
||||
" fields=fields,\n",
|
||||
" vector_search=vector_search,\n",
|
||||
" semantic_search=semantic_search,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Create or update the index\n",
|
||||
"result = index_client.create_or_update_index(index)\n",
|
||||
"print(f\"{result.name} created\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Uploading Data to Azure AI Search Index\n",
|
||||
"\n",
|
||||
"The following code snippet outlines the process of uploading a batch of documents—specifically, Wikipedia articles with pre-computed embeddings—from a pandas DataFrame to an Azure AI Search index. For a detailed guide on data import strategies and best practices, refer to [Data Import in Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-data-import).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Uploaded 25000 documents in total\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from azure.core.exceptions import HttpResponseError\n",
|
||||
"\n",
|
||||
"# Convert the 'id' and 'vector_id' columns to string so one of them can serve as our key field\n",
|
||||
"article_df[\"id\"] = article_df[\"id\"].astype(str)\n",
|
||||
"article_df[\"vector_id\"] = article_df[\"vector_id\"].astype(str)\n",
|
||||
"# Convert the DataFrame to a list of dictionaries\n",
|
||||
"documents = article_df.to_dict(orient=\"records\")\n",
|
||||
"\n",
|
||||
"# Create a SearchIndexingBufferedSender\n",
|
||||
"batch_client = SearchIndexingBufferedSender(\n",
|
||||
" search_service_endpoint, index_name, credential\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" # Add upload actions for all documents in a single call\n",
|
||||
" batch_client.upload_documents(documents=documents)\n",
|
||||
"\n",
|
||||
" # Manually flush to send any remaining documents in the buffer\n",
|
||||
" batch_client.flush()\n",
|
||||
"except HttpResponseError as e:\n",
|
||||
" print(f\"An error occurred: {e}\")\n",
|
||||
"finally:\n",
|
||||
" # Clean up resources\n",
|
||||
" batch_client.close()\n",
|
||||
"\n",
|
||||
"print(f\"Uploaded {len(documents)} documents in total\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If your dataset didn't already contain pre-computed embeddings, you can create embeddings by using the below function using the `openai` python library. You'll also notice the same function and model are being used to generate query embeddings for performing vector searches."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Content: April is the fourth month of the year in the Julian and Gregorian calendars, and comes between March\n",
|
||||
"Content vector generated\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Example function to generate document embedding\n",
|
||||
"def generate_embeddings(text, model):\n",
|
||||
" # Generate embeddings for the provided text using the specified model\n",
|
||||
" embeddings_response = client.embeddings.create(model=model, input=text)\n",
|
||||
" # Extract the embedding data from the response\n",
|
||||
" embedding = embeddings_response.data[0].embedding\n",
|
||||
" return embedding\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"first_document_content = documents[0][\"text\"]\n",
|
||||
"print(f\"Content: {first_document_content[:100]}\")\n",
|
||||
"\n",
|
||||
"content_vector = generate_embeddings(first_document_content, deployment)\n",
|
||||
"print(\"Content vector generated\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Perform a vector similarity search"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Title: Documenta\n",
|
||||
"Score: 0.8599451\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Documenta\n",
|
||||
"\n",
|
||||
"Title: Museum of Modern Art\n",
|
||||
"Score: 0.85260946\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Museum%20of%20Modern%20Art\n",
|
||||
"\n",
|
||||
"Title: Expressionism\n",
|
||||
"Score: 0.852354\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Expressionism\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Pure Vector Search\n",
|
||||
"query = \"modern art in Europe\"\n",
|
||||
" \n",
|
||||
"search_client = SearchClient(search_service_endpoint, index_name, credential) \n",
|
||||
"vector_query = VectorizedQuery(vector=generate_embeddings(query, deployment), k_nearest_neighbors=3, fields=\"content_vector\")\n",
|
||||
" \n",
|
||||
"results = search_client.search( \n",
|
||||
" search_text=None, \n",
|
||||
" vector_queries= [vector_query], \n",
|
||||
" select=[\"title\", \"text\", \"url\"] \n",
|
||||
")\n",
|
||||
" \n",
|
||||
"for result in results: \n",
|
||||
" print(f\"Title: {result['title']}\") \n",
|
||||
" print(f\"Score: {result['@search.score']}\") \n",
|
||||
" print(f\"URL: {result['url']}\\n\") "
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Perform a Hybrid Search\n",
|
||||
"Hybrid search combines the capabilities of traditional keyword-based search with vector-based similarity search to provide more relevant and contextual results. This approach is particularly useful when dealing with complex queries that benefit from understanding the semantic meaning behind the text.\n",
|
||||
"\n",
|
||||
"The provided code snippet demonstrates how to execute a hybrid search query:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 61,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Title: Wars of Scottish Independence\n",
|
||||
"Score: 0.03306011110544205\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Wars%20of%20Scottish%20Independence\n",
|
||||
"\n",
|
||||
"Title: Battle of Bannockburn\n",
|
||||
"Score: 0.022253260016441345\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Battle%20of%20Bannockburn\n",
|
||||
"\n",
|
||||
"Title: Scottish\n",
|
||||
"Score: 0.016393441706895828\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Scottish\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Hybrid Search\n",
|
||||
"query = \"Famous battles in Scottish history\" \n",
|
||||
" \n",
|
||||
"search_client = SearchClient(search_service_endpoint, index_name, credential) \n",
|
||||
"vector_query = VectorizedQuery(vector=generate_embeddings(query, deployment), k_nearest_neighbors=3, fields=\"content_vector\")\n",
|
||||
" \n",
|
||||
"results = search_client.search( \n",
|
||||
" search_text=query, \n",
|
||||
" vector_queries= [vector_query], \n",
|
||||
" select=[\"title\", \"text\", \"url\"],\n",
|
||||
" top=3\n",
|
||||
")\n",
|
||||
" \n",
|
||||
"for result in results: \n",
|
||||
" print(f\"Title: {result['title']}\") \n",
|
||||
" print(f\"Score: {result['@search.score']}\") \n",
|
||||
" print(f\"URL: {result['url']}\\n\") "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Perform a Hybrid Search with Reranking (powered by Bing)\n",
|
||||
"[Semantic ranker](https://learn.microsoft.com/azure/search/semantic-search-overview) measurably improves search relevance by using language understanding to rerank search results. Additionally, you can get extractive captions, answers, and highlights. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Semantic Answer: Advancements During the industrial revolution, new technology brought many changes. For example:<em> Canals</em> were built to allow heavy goods to be moved easily where they were needed. The steam engine became the main source of power. It replaced horses and human labor. Cheap iron and steel became mass-produced.\n",
|
||||
"Semantic Answer Score: 0.90478515625\n",
|
||||
"\n",
|
||||
"Title: Industrial Revolution\n",
|
||||
"Reranker Score: 3.408700942993164\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Industrial%20Revolution\n",
|
||||
"Caption: Advancements During the industrial revolution, new technology brought many changes. For example: Canals were built to allow heavy goods to be moved easily where they were needed. The steam engine became the main source of power. It replaced horses and human labor. Cheap iron and steel became mass-produced.\n",
|
||||
"\n",
|
||||
"Title: Printing\n",
|
||||
"Reranker Score: 1.603400707244873\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Printing\n",
|
||||
"Caption: Machines to speed printing, cheaper paper, automatic stitching and binding all arrived in the 19th century during the industrial revolution. What had once been done by a few men by hand was now done by limited companies on huge machines. The result was much lower prices, and a much wider readership.\n",
|
||||
"\n",
|
||||
"Title: Industrialisation\n",
|
||||
"Reranker Score: 1.3238357305526733\n",
|
||||
"URL: https://simple.wikipedia.org/wiki/Industrialisation\n",
|
||||
"Caption: <em>Industrialisation</em> (or<em> industrialization)</em> is a process that happens in countries when they start to use machines to do work that was once done by people.<em> Industrialisation changes</em> the things people do.<em> Industrialisation</em> caused towns to grow larger. Many people left farming to take higher paid jobs in factories in towns.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Semantic Hybrid Search\n",
|
||||
"query = \"What were the key technological advancements during the Industrial Revolution?\"\n",
|
||||
"\n",
|
||||
"search_client = SearchClient(search_service_endpoint, index_name, credential)\n",
|
||||
"vector_query = VectorizedQuery(\n",
|
||||
" vector=generate_embeddings(query, deployment),\n",
|
||||
" k_nearest_neighbors=3,\n",
|
||||
" fields=\"content_vector\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"results = search_client.search(\n",
|
||||
" search_text=query,\n",
|
||||
" vector_queries=[vector_query],\n",
|
||||
" select=[\"title\", \"text\", \"url\"],\n",
|
||||
" query_type=QueryType.SEMANTIC,\n",
|
||||
" semantic_configuration_name=\"my-semantic-config\",\n",
|
||||
" query_caption=QueryCaptionType.EXTRACTIVE,\n",
|
||||
" query_answer=QueryAnswerType.EXTRACTIVE,\n",
|
||||
" top=3,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"semantic_answers = results.get_answers()\n",
|
||||
"for answer in semantic_answers:\n",
|
||||
" if answer.highlights:\n",
|
||||
" print(f\"Semantic Answer: {answer.highlights}\")\n",
|
||||
" else:\n",
|
||||
" print(f\"Semantic Answer: {answer.text}\")\n",
|
||||
" print(f\"Semantic Answer Score: {answer.score}\\n\")\n",
|
||||
"\n",
|
||||
"for result in results:\n",
|
||||
" print(f\"Title: {result['title']}\")\n",
|
||||
" print(f\"Reranker Score: {result['@search.reranker_score']}\")\n",
|
||||
" print(f\"URL: {result['url']}\")\n",
|
||||
" captions = result[\"@search.captions\"]\n",
|
||||
" if captions:\n",
|
||||
" caption = captions[0]\n",
|
||||
" if caption.highlights:\n",
|
||||
" print(f\"Caption: {caption.highlights}\\n\")\n",
|
||||
" else:\n",
|
||||
" print(f\"Caption: {caption.text}\\n\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"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.11.7"
|
||||
},
|
||||
"orig_nbformat": 4
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -0,0 +1,6 @@
|
||||
# MongoDB Atlas Vector Search
|
||||
|
||||
|
||||
[Atlas Vector Search](https://www.mongodb.com/products/platform/atlas-vector-search) is a fully managed service that simplifies the process of effectively indexing high-dimensional vector data within MongoDB and being able to perform fast vector similarity searches. With Atlas Vector Search, you can use MongoDB as a standalone vector database for a new project or augment your existing MongoDB collections with vector search functionality. With Atlas Vector Search, you can use the powerful capabilities of vector search in any major public cloud (AWS, Azure, GCP) and achieve massive scalability and data security out of the box while being enterprise-ready with provisions like FedRamp, SoC2 compliance.
|
||||
|
||||
Documentation - [link](https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-overview/)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue