Skip to content

Instantly share code, notes, and snippets.

@ricklamers
Created June 21, 2023 11:09
Show Gist options
  • Save ricklamers/bba516c4be19ba255f7644eb8d902f0d to your computer and use it in GitHub Desktop.
Save ricklamers/bba516c4be19ba255f7644eb8d902f0d to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "850ecca2",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Defaulting to user installation because normal site-packages is not writeable\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: openai in /home/rick/.local/lib/python3.9/site-packages (0.27.8)\n",
"Requirement already satisfied: tenacity in /home/rick/.local/lib/python3.9/site-packages (8.2.2)\n",
"Requirement already satisfied: termcolor in /home/rick/.local/lib/python3.9/site-packages (2.3.0)\n",
"Requirement already satisfied: pydantic in /home/rick/.local/lib/python3.9/site-packages (1.10.9)\n",
"Requirement already satisfied: docstring_parser in /home/rick/.local/lib/python3.9/site-packages (0.15)\n",
"Requirement already satisfied: inflection in /home/rick/.local/lib/python3.9/site-packages (0.5.1)\n",
"Requirement already satisfied: requests>=2.20 in /home/rick/.local/lib/python3.9/site-packages (from openai) (2.28.1)\n",
"Requirement already satisfied: tqdm in /home/rick/.local/lib/python3.9/site-packages (from openai) (4.65.0)\n",
"Requirement already satisfied: aiohttp in /home/rick/.local/lib/python3.9/site-packages (from openai) (3.8.4)\n",
"Requirement already satisfied: typing-extensions>=4.2.0 in /home/rick/.local/lib/python3.9/site-packages (from pydantic) (4.3.0)\n",
"Requirement already satisfied: charset-normalizer<3,>=2 in /home/rick/.local/lib/python3.9/site-packages (from requests>=2.20->openai) (2.1.0)\n",
"Requirement already satisfied: idna<4,>=2.5 in /home/rick/.local/lib/python3.9/site-packages (from requests>=2.20->openai) (3.3)\n",
"Requirement already satisfied: urllib3<1.27,>=1.21.1 in /home/rick/.local/lib/python3.9/site-packages (from requests>=2.20->openai) (1.26.10)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /home/rick/.local/lib/python3.9/site-packages (from requests>=2.20->openai) (2022.6.15)\n",
"Requirement already satisfied: attrs>=17.3.0 in /home/rick/.local/lib/python3.9/site-packages (from aiohttp->openai) (21.4.0)\n",
"Requirement already satisfied: multidict<7.0,>=4.5 in /home/rick/.local/lib/python3.9/site-packages (from aiohttp->openai) (6.0.4)\n",
"Requirement already satisfied: async-timeout<5.0,>=4.0.0a3 in /home/rick/.local/lib/python3.9/site-packages (from aiohttp->openai) (4.0.2)\n",
"Requirement already satisfied: yarl<2.0,>=1.0 in /home/rick/.local/lib/python3.9/site-packages (from aiohttp->openai) (1.9.2)\n",
"Requirement already satisfied: frozenlist>=1.1.1 in /home/rick/.local/lib/python3.9/site-packages (from aiohttp->openai) (1.3.3)\n",
"Requirement already satisfied: aiosignal>=1.1.2 in /home/rick/.local/lib/python3.9/site-packages (from aiohttp->openai) (1.3.1)\n",
"Note: you may need to restart the kernel to use updated packages.\n"
]
}
],
"source": [
"%pip install openai tenacity termcolor pydantic docstring_parser inflection"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "1e09270d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from dotenv import load_dotenv\n",
"load_dotenv()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e48270f2-90a2-4fd2-be85-9b2214847259",
"metadata": {
"collapsed": true,
"jupyter": {
"outputs_hidden": true
},
"tags": []
},
"outputs": [],
"source": [
"import json\n",
"import inspect\n",
"\n",
"from typing import Callable, Type, Any\n",
"\n",
"import openai\n",
"import inflection\n",
"import requests\n",
"import docstring_parser\n",
"import inflection\n",
"\n",
"from pydantic import BaseModel, Field, create_model\n",
"from tenacity import retry, wait_random_exponential, stop_after_attempt\n",
"from termcolor import colored\n",
"\n",
"\n",
"GPT_MODEL = \"gpt-3.5-turbo-0613\""
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e4519f12-87d5-454f-8507-1d247bb3a333",
"metadata": {},
"outputs": [],
"source": [
"@retry(wait=wait_random_exponential(min=1, max=40), stop=stop_after_attempt(3))\n",
"def chat_completion_request(messages, functions=None, function_call=None, model=GPT_MODEL):\n",
" headers = {\n",
" \"Content-Type\": \"application/json\",\n",
" \"Authorization\": \"Bearer \" + openai.api_key,\n",
" }\n",
" json_data = {\"model\": model, \"messages\": messages}\n",
" if functions is not None:\n",
" json_data.update({\"functions\": functions})\n",
" if function_call is not None:\n",
" json_data.update({\"function_call\": function_call})\n",
" try:\n",
" response = requests.post(\n",
" \"https://api.openai.com/v1/chat/completions\",\n",
" headers=headers,\n",
" json=json_data,\n",
" )\n",
" return response\n",
" except Exception as e:\n",
" print(\"Unable to generate ChatCompletion response\")\n",
" print(f\"Exception: {e}\")\n",
" return e"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ae2b8461",
"metadata": {},
"outputs": [],
"source": [
"def pretty_print_conversation(messages):\n",
" role_to_color = {\n",
" \"system\": \"red\",\n",
" \"user\": \"green\",\n",
" \"assistant\": \"blue\",\n",
" \"function\": \"magenta\",\n",
" }\n",
" formatted_messages = []\n",
" for message in messages:\n",
" if message[\"role\"] == \"system\":\n",
" formatted_messages.append(f\"system: {message['content']}\\n\")\n",
" elif message[\"role\"] == \"user\":\n",
" formatted_messages.append(f\"user: {message['content']}\\n\")\n",
" elif message[\"role\"] == \"assistant\" and message.get(\"function_call\"):\n",
" formatted_messages.append(f\"assistant: {message['function_call']}\\n\")\n",
" elif message[\"role\"] == \"assistant\" and not message.get(\"function_call\"):\n",
" formatted_messages.append(f\"assistant: {message['content']}\\n\")\n",
" elif message[\"role\"] == \"function\":\n",
" formatted_messages.append(f\"function ({message['name']}): {message['content']}\\n\")\n",
" for formatted_message in formatted_messages:\n",
" print(\n",
" colored(\n",
" formatted_message,\n",
" role_to_color[messages[formatted_messages.index(formatted_message)][\"role\"]],\n",
" )\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "2c12eede",
"metadata": {},
"outputs": [],
"source": [
"def math_eval(math_expression: str) -> float:\n",
" \"\"\"\n",
" Convert math expression to float\n",
"\n",
" :param math_expression: The mathematical expression to evaluate, for example '2 * 2'\n",
" :type math_expression: str\n",
" :return: The result of the mathematical expression\n",
" :rtype: float\n",
" \"\"\"\n",
" return float(eval(math_expression))"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "f134b3e7",
"metadata": {},
"outputs": [],
"source": [
"def function_to_pydantic_model(function: Callable[..., Any]) -> Type[BaseModel]:\n",
" \"\"\"\n",
" Convert a function to a Pydantic model.\n",
" \n",
" :param function: The function to convert.\n",
" :type function: Callable[..., Any]\n",
" \n",
" :return: The Pydantic model.\n",
" :rtype: Type[BaseModel]\n",
" \"\"\"\n",
"\n",
" # Create a new base model class with a modified schema method\n",
" class NoTitleBaseModel(BaseModel):\n",
" @classmethod\n",
" def schema(cls, **kwargs):\n",
" schema = super().schema(**kwargs)\n",
" schema.pop(\"title\", None)\n",
" return schema\n",
"\n",
" # Parse the function signature\n",
" signature = inspect.signature(function)\n",
" parameters = signature.parameters\n",
"\n",
" # Parse the docstring\n",
" docstring = inspect.getdoc(function)\n",
" parsed_docstring = docstring_parser.parse(docstring)\n",
"\n",
" # Create the fields for the Pydantic model\n",
" fields = {\n",
" name: (param.annotation, Field(..., description=param_doc.description))\n",
" for name, param in parameters.items()\n",
" if (param_doc := next((d for d in parsed_docstring.params if d.arg_name == name), None)) is not None\n",
" }\n",
"\n",
" # Create the new Pydantic model class\n",
" model_name = inflection.camelize(function.__name__) + \"FunctionModel\"\n",
" model = create_model(model_name, __base__=NoTitleBaseModel, **fields)\n",
"\n",
" return model"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ffcc5952",
"metadata": {},
"outputs": [],
"source": [
"functionModel = function_to_pydantic_model(math_eval)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "dd1204ad",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"math_expression\": {\n",
" \"title\": \"Math Expression\",\n",
" \"description\": \"The mathematical expression to evaluate, for example '2 * 2'\",\n",
" \"type\": \"string\"\n",
" }\n",
" },\n",
" \"required\": [\n",
" \"math_expression\"\n",
" ]\n",
"}\n"
]
}
],
"source": [
"print(json.dumps(functionModel.schema(), indent=2))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "162d38b2",
"metadata": {},
"outputs": [],
"source": [
"def function_to_openai_spec(function: Callable[..., Any]) -> dict:\n",
"\n",
" pydantic_model = function_to_pydantic_model(function)\n",
"\n",
" docstring = inspect.getdoc(function)\n",
" parsed_docstring = docstring_parser.parse(docstring)\n",
"\n",
" return {\n",
" \"name\": function.__name__,\n",
" \"description\": parsed_docstring.short_description,\n",
" \"parameters\": pydantic_model.schema()\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "5248e594",
"metadata": {},
"outputs": [],
"source": [
"math_eval_description = function_to_openai_spec(math_eval)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "f65a1fd0",
"metadata": {},
"outputs": [],
"source": [
"# Get string of function definition math_eval\n",
"function_definition = inspect.getsource(math_eval)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "321888fd",
"metadata": {},
"source": [
"### OpenAI functions"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "2a112429",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Token usage {\n",
" \"prompt_tokens\": 97,\n",
" \"completion_tokens\": 27,\n",
" \"total_tokens\": 124\n",
"}\n",
"system: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
"\n",
"user: Calculate the the square root of the number of seconds in a year.\n",
"\n",
"assistant: {'name': 'math_eval', 'arguments': '{\\n \"math_expression\": \"sqrt(365 * 24 * 60 * 60)\"\\n}'}\n",
"\n"
]
}
],
"source": [
"functions = [math_eval_description]\n",
"\n",
"messages = []\n",
"\n",
"messages.append({\"role\": \"system\", \"content\": \"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"})\n",
"\n",
"messages.append({\"role\": \"user\", \"content\": \"Calculate the the square root of the number of seconds in a year.\"})\n",
"\n",
"chat_response = chat_completion_request(\n",
" messages, functions=functions\n",
")\n",
"assistant_message = chat_response.json()[\"choices\"][0][\"message\"]\n",
"print(\"Token usage\", json.dumps(chat_response.json()[\"usage\"], indent=2))\n",
"messages.append(assistant_message)\n",
"pretty_print_conversation(messages)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "5e549d67",
"metadata": {},
"source": [
"### Vanilla prompt engineering attempt 1"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "7ee1b879",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Token usage {\n",
" \"prompt_tokens\": 169,\n",
" \"completion_tokens\": 59,\n",
" \"total_tokens\": 228\n",
"}\n",
"system: \n",
"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous. The following functions are available:\n",
" \n",
"def math_eval(math_expression: str) -> float:\n",
" \"\"\"\n",
" Convert math expression to float\n",
"\n",
" :param math_expression: The mathematical expression to evaluate, for example '2 * 2'\n",
" :type math_expression: str\n",
" :return: The result of the mathematical expression\n",
" :rtype: float\n",
" \"\"\"\n",
" return float(eval(math_expression))\n",
"\n",
"\n",
"Indicate you want to call a function by sending a code snippet like this, make sure invocation values are literal arguments:\n",
"\n",
"```python\n",
"some_function(\"Literal argument 1\", 20)\n",
"```\n",
"\n",
"\n",
"user: Calculate the the square root of the number of seconds in a year.\n",
"\n",
"assistant: I can help you with that. To calculate the square root of the number of seconds in a year, I'll first need to know the total number of seconds in a year. Would you like me to assume a standard year of 365 days or do you have a specific value in mind?\n",
"\n"
]
}
],
"source": [
"messages = []\n",
"\n",
"messages.append({\"role\": \"system\", \"content\": f\"\"\"\n",
"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous. The following functions are available:\n",
" \n",
"{function_definition}\n",
"\n",
"Indicate you want to call a function by sending a code snippet like this, make sure invocation values are literal arguments:\n",
"\n",
"```python\n",
"some_function(\"Literal argument 1\", 20)\n",
"```\n",
"\"\"\"})\n",
"\n",
"messages.append({\"role\": \"user\", \"content\": \"Calculate the the square root of the number of seconds in a year.\"})\n",
"\n",
"chat_response = chat_completion_request(\n",
" messages\n",
")\n",
"assistant_message = chat_response.json()[\"choices\"][0][\"message\"]\n",
"print(\"Token usage\", json.dumps(chat_response.json()[\"usage\"], indent=2))\n",
"messages.append(assistant_message)\n",
"pretty_print_conversation(messages)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "7e94401f",
"metadata": {},
"source": [
"### Vanilla prompt engineering attempt 2"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "d1666031",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Token usage {\n",
" \"prompt_tokens\": 172,\n",
" \"completion_tokens\": 32,\n",
" \"total_tokens\": 204\n",
"}\n",
"system: \n",
"Help the user get to the right answer by calling the appropriate function. Make sure you have the neccesary information. The following functions are available:\n",
" \n",
"def math_eval(math_expression: str) -> float:\n",
" \"\"\"\n",
" Convert math expression to float\n",
"\n",
" :param math_expression: The mathematical expression to evaluate, for example '2 * 2'\n",
" :type math_expression: str\n",
" :return: The result of the mathematical expression\n",
" :rtype: float\n",
" \"\"\"\n",
" return float(eval(math_expression))\n",
"\n",
"\n",
"Indicate you want to call a function by sending a code snippet like this, make sure invocation values are literal arguments:\n",
"\n",
"```python\n",
"some_function(\"Literal argument 1\", 20)\n",
"```\n",
"\n",
"\n",
"user: Calculate the the square root of the number of seconds in a year.\n",
"\n",
"assistant: import math\n",
"\n",
"seconds_in_a_year = 365 * 24 * 60 * 60\n",
"square_root = math.sqrt(seconds_in_a_year)\n",
"square_root\n",
"\n"
]
}
],
"source": [
"messages = []\n",
"\n",
"messages.append({\"role\": \"system\", \"content\": f\"\"\"\n",
"Help the user get to the right answer by calling the appropriate function. Make sure you have the neccesary information. The following functions are available:\n",
" \n",
"{function_definition}\n",
"\n",
"Indicate you want to call a function by sending a code snippet like this, make sure invocation values are literal arguments:\n",
"\n",
"```python\n",
"some_function(\"Literal argument 1\", 20)\n",
"```\n",
"\"\"\"})\n",
"\n",
"messages.append({\"role\": \"user\", \"content\": \"Calculate the the square root of the number of seconds in a year.\"})\n",
"\n",
"chat_response = chat_completion_request(\n",
" messages\n",
")\n",
"assistant_message = chat_response.json()[\"choices\"][0][\"message\"]\n",
"print(\"Token usage\", json.dumps(chat_response.json()[\"usage\"], indent=2))\n",
"messages.append(assistant_message)\n",
"pretty_print_conversation(messages)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "1e165199",
"metadata": {},
"source": [
"### Vanilla prompt engineering attempt 3"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "cb279498",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Token usage {\n",
" \"prompt_tokens\": 183,\n",
" \"completion_tokens\": 120,\n",
" \"total_tokens\": 303\n",
"}\n",
"system: \n",
"Help the user get to the right answer by calling the appropriate function. Make sure you have the neccesary information. The following functions are available:\n",
" \n",
"def math_eval(math_expression: str) -> float:\n",
" \"\"\"\n",
" Convert math expression to float\n",
"\n",
" :param math_expression: The mathematical expression to evaluate, for example '2 * 2'\n",
" :type math_expression: str\n",
" :return: The result of the mathematical expression\n",
" :rtype: float\n",
" \"\"\"\n",
" return float(eval(math_expression))\n",
"\n",
"\n",
"Indicate you want to call a function by sending a code snippet like this, make sure invocation values are literal arguments:\n",
"\n",
"```python\n",
"some_function(\"Literal argument 1\", 20)\n",
"```\n",
"\n",
"Do not use any functions that are not listed above.\n",
"\n",
"\n",
"user: Calculate the the square root of the number of seconds in a year.\n",
"\n",
"assistant: To calculate the square root of the number of seconds in a year, we first need to find the number of seconds in a year and then calculate the square root of that number.\n",
"\n",
"There are 365 days in a year and each day consists of 24 hours, each hour consists of 60 minutes, and each minute consists of 60 seconds. So, the number of seconds in a year can be calculated as:\n",
"\n",
"number_of_seconds_in_a_year = 365 * 24 * 60 * 60\n",
"\n",
"Now, let's calculate the square root of the number_of_seconds_in_a_year.\n",
"\n"
]
}
],
"source": [
"messages = []\n",
"\n",
"messages.append({\"role\": \"system\", \"content\": f\"\"\"\n",
"Help the user get to the right answer by calling the appropriate function. Make sure you have the neccesary information. The following functions are available:\n",
" \n",
"{function_definition}\n",
"\n",
"Indicate you want to call a function by sending a code snippet like this, make sure invocation values are literal arguments:\n",
"\n",
"```python\n",
"some_function(\"Literal argument 1\", 20)\n",
"```\n",
"\n",
"Do not use any functions that are not listed above.\n",
"\"\"\"})\n",
"\n",
"messages.append({\"role\": \"user\", \"content\": \"Calculate the the square root of the number of seconds in a year.\"})\n",
"\n",
"chat_response = chat_completion_request(\n",
" messages\n",
")\n",
"assistant_message = chat_response.json()[\"choices\"][0][\"message\"]\n",
"print(\"Token usage\", json.dumps(chat_response.json()[\"usage\"], indent=2))\n",
"messages.append(assistant_message)\n",
"pretty_print_conversation(messages)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "450b557d",
"metadata": {},
"source": [
"### Vanilla prompt engineering attempt 4\n",
"\n",
"Hardcoding a JSON respsonse example"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "9d418dae",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Token usage {\n",
" \"prompt_tokens\": 173,\n",
" \"completion_tokens\": 30,\n",
" \"total_tokens\": 203\n",
"}\n",
"system: \n",
"Help the user get to the right answer by calling the appropriate function. Make sure you have the neccesary information. You must use one of the following functions:\n",
" \n",
"def math_eval(math_expression: str) -> float:\n",
" \"\"\"\n",
" Convert math expression to float\n",
"\n",
" :param math_expression: The mathematical expression to evaluate, for example '2 * 2'\n",
" :type math_expression: str\n",
" :return: The result of the mathematical expression\n",
" :rtype: float\n",
" \"\"\"\n",
" return float(eval(math_expression))\n",
"\n",
"\n",
"Indicate which function you want call a function by sending a JSON object like this:\n",
"\n",
"{'name': 'math_eval', 'arguments': '{\"math_expression\": \"2 + 2\"}'}\n",
"\n",
"\n",
"\n",
"user: Calculate the the square root of the number of seconds in a year.\n",
"\n",
"assistant: {'name': 'math_eval', 'arguments': '{\"math_expression\": \"sqrt(365 * 24 * 60 * 60)\"}'}\n",
"\n"
]
}
],
"source": [
"messages = []\n",
"\n",
"messages.append({\"role\": \"system\", \"content\": f\"\"\"\n",
"Help the user get to the right answer by calling the appropriate function. Make sure you have the neccesary information. You must use one of the following functions:\n",
" \n",
"{function_definition}\n",
"\n",
"Indicate which function you want call a function by sending a JSON object like this:\n",
"\n",
"{{'name': 'math_eval', 'arguments': '{{\"math_expression\": \"2 + 2\"}}'}}\n",
"\n",
"\"\"\"})\n",
"\n",
"messages.append({\"role\": \"user\", \"content\": \"Calculate the the square root of the number of seconds in a year.\"})\n",
"\n",
"chat_response = chat_completion_request(\n",
" messages\n",
")\n",
"assistant_message = chat_response.json()[\"choices\"][0][\"message\"]\n",
"print(\"Token usage\", json.dumps(chat_response.json()[\"usage\"], indent=2))\n",
"messages.append(assistant_message)\n",
"pretty_print_conversation(messages)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e784d35",
"metadata": {},
"outputs": [],
"source": []
}
],
"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.9.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment