{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8bb7d0d0-2b7f-4e9e-8565-467dc5c6fd22",
   "metadata": {},
   "source": [
    "# General Tips on Prompting\n",
    "\n",
    "Before we get into some big applications of schema engineering I want to equip you with the tools for success.\n",
    "This notebook is to share some general advice when using prompts to get the most of your models.\n",
    "\n",
    "Before you might think of prompt engineering as massaging this wall of text, almost like coding in a notepad. But with schema engineering you can get a lot more out of your prompts with a lot less work.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a785c25-b08d-4ab4-bbd7-22e3b090c2ed",
   "metadata": {},
   "source": [
    "## Classification\n",
    "\n",
    "For classification we've found theres generally two methods of modeling.\n",
    "\n",
    "1. using Enums\n",
    "2. using Literals\n",
    "\n",
    "Use an enum in Python when you need a set of named constants that are related and you want to ensure type safety, readability, and prevent invalid values. Enums are helpful for grouping and iterating over these constants.\n",
    "\n",
    "Use literals when you have a small, unchanging set of values that you don't need to group or iterate over, and when type safety and preventing invalid values is less of a concern. Literals are simpler and more direct for basic, one-off values.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "fdf5e1d9-31ad-4e8a-a55e-e2e70fff598d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'age': 17, 'name': 'Harry Potter', 'house': <House.Gryffindor: 'gryffindor'>}"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import instructor\n",
    "from openai import OpenAI\n",
    "\n",
    "from enum import Enum\n",
    "from pydantic import BaseModel, Field\n",
    "from typing_extensions import Literal\n",
    "\n",
    "\n",
    "client = instructor.patch(OpenAI())\n",
    "\n",
    "\n",
    "# Tip: Do not use auto() as they cast to 1,2,3,4\n",
    "class House(Enum):\n",
    "    Gryffindor = \"gryffindor\"\n",
    "    Hufflepuff = \"hufflepuff\"\n",
    "    Ravenclaw = \"ravenclaw\"\n",
    "    Slytherin = \"slytherin\"\n",
    "\n",
    "\n",
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "    house: House\n",
    "\n",
    "    def say_hello(self):\n",
    "        print(\n",
    "            f\"Hello, I'm {self.name}, I'm {self.age} years old and I'm from {self.house.value.title()}\"\n",
    "        )\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"Harry Potter\"}],\n",
    "    response_model=Character,\n",
    ")\n",
    "resp.model_dump()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c609eb44",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello, I'm Harry Potter, I'm 17 years old and I'm from Gryffindor\n"
     ]
    }
   ],
   "source": [
    "resp.say_hello()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "03db160c-81e9-4373-bfec-7a107224b6dd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'age': 11, 'name': 'Harry Potter', 'house': 'Gryffindor'}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "    house: Literal[\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"Harry Potter\"}],\n",
    "    response_model=Character,\n",
    ")\n",
    "resp.model_dump()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "803e0ce6-6e7e-4d86-a7a8-49ebaad0a40b",
   "metadata": {},
   "source": [
    "## Arbitrary properties\n",
    "\n",
    "Often times there are long properties that you might want to extract from data that we can not specify in advanced. We can get around this by defining an arbitrary key value store like so:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0e7938b8-4666-4df4-bd80-f53e8baf7550",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'age': 38,\n",
       " 'name': 'Severus Snape',\n",
       " 'house': 'Slytherin',\n",
       " 'properties': [{'key': 'role', 'value': 'Potions Master'},\n",
       "  {'key': 'patronus', 'value': 'Doe'},\n",
       "  {'key': 'loyalty', 'value': 'Dumbledore'},\n",
       "  {'key': 'played_by', 'value': 'Alan Rickman'}]}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from typing import List\n",
    "\n",
    "\n",
    "class Property(BaseModel):\n",
    "    key: str = Field(description=\"Must be snake case\")\n",
    "    value: str\n",
    "\n",
    "\n",
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "    house: Literal[\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]\n",
    "    properties: List[Property]\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"Snape from Harry Potter\"}],\n",
    "    response_model=Character,\n",
    ")\n",
    "resp.model_dump()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3e62f68-a79f-4f65-9c1f-726e4e2d340a",
   "metadata": {},
   "source": [
    "## Limiting the length of lists\n",
    "\n",
    "In later chapters we'll talk about how to use validators to assert the length of lists but we can also use prompting tricks to enumerate values. Here we'll define a index to count the properties.\n",
    "\n",
    "In this following example instead of extraction we're going to work on generation instead.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "69a58d01-ab6f-41b6-bc0c-b0e55fdb6fe4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'age': 38,\n",
       " 'name': 'Severus Snape',\n",
       " 'house': 'Slytherin',\n",
       " 'properties': [{'index': '1',\n",
       "   'key': 'position_at_hogwarts',\n",
       "   'value': 'Potions Master'},\n",
       "  {'index': '2', 'key': 'patronus_form', 'value': 'Doe'},\n",
       "  {'index': '3', 'key': 'loyalty', 'value': 'Albus Dumbledore'},\n",
       "  {'index': '4', 'key': 'played_by', 'value': 'Alan Rickman'},\n",
       "  {'index': '5', 'key': 'final_act', 'value': 'Protecting Harry Potter'}]}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class Property(BaseModel):\n",
    "    index: str = Field(..., description=\"Monotonically increasing ID\")\n",
    "    key: str = Field(description=\"Must be snake case\")\n",
    "    value: str\n",
    "\n",
    "\n",
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "    house: Literal[\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]\n",
    "    properties: List[Property] = Field(\n",
    "        ...,\n",
    "        description=\"Numbered list of arbitrary extracted properties, should be exactly 5\",\n",
    "    )\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"Snape from Harry Potter\"}],\n",
    "    response_model=Character,\n",
    ")\n",
    "resp.model_dump()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bbc1d900-617a-4e4d-a401-6d10a5153cda",
   "metadata": {},
   "source": [
    "## Defining Multiple Entities\n",
    "\n",
    "Now that we see a single entity with many properties we can continue to nest them into many users\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1f2a2b14-a956-4f96-90c9-e11ca04ab7d1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "age=11 name='Harry Potter' house='Gryffindor'\n",
      "age=11 name='Hermione Granger' house='Gryffindor'\n",
      "age=11 name='Ron Weasley' house='Gryffindor'\n",
      "age=11 name='Draco Malfoy' house='Slytherin'\n",
      "age=11 name='Neville Longbottom' house='Gryffindor'\n"
     ]
    }
   ],
   "source": [
    "from typing import Iterable\n",
    "\n",
    "\n",
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "    house: Literal[\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"Five characters from Harry Potter\"}],\n",
    "    response_model=Iterable[Character],\n",
    ")\n",
    "\n",
    "for character in resp:\n",
    "    print(character)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a3091aba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "age=11 name='Harry Potter' house='Gryffindor'\n",
      "age=11 name='Hermione Granger' house='Gryffindor'\n",
      "age=11 name='Ron Weasley' house='Gryffindor'\n",
      "age=17 name='Draco Malfoy' house='Slytherin'\n",
      "age=11 name='Luna Lovegood' house='Ravenclaw'\n"
     ]
    }
   ],
   "source": [
    "from typing import Iterable\n",
    "\n",
    "\n",
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "    house: Literal[\"Gryffindor\", \"Hufflepuff\", \"Ravenclaw\", \"Slytherin\"]\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"Five characters from Harry Potter\"}],\n",
    "    stream=True,\n",
    "    response_model=Iterable[Character],\n",
    ")\n",
    "\n",
    "for character in resp:\n",
    "    print(character)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6ed3144-bde1-4033-9c94-a6926fa079d2",
   "metadata": {},
   "source": [
    "## Defining Relationships\n",
    "\n",
    "Now only can we define lists of users, with list of properties one of the more interesting things I've learned about prompting is that we can also easily define lists of references.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "6de8768e-b36a-4a51-9cf9-940d178552f6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "id=1 name='Harry Potter' friends_array=[2, 3, 4, 5, 6]\n",
      "id=2 name='Hermione Granger' friends_array=[1, 3, 4, 5]\n",
      "id=3 name='Ron Weasley' friends_array=[1, 2, 4, 6]\n",
      "id=4 name='Neville Longbottom' friends_array=[1, 2, 3, 5]\n",
      "id=5 name='Luna Lovegood' friends_array=[1, 2, 4, 6]\n",
      "id=6 name='Draco Malfoy' friends_array=[1, 3, 5]\n"
     ]
    }
   ],
   "source": [
    "class Character(BaseModel):\n",
    "    id: int\n",
    "    name: str\n",
    "    friends_array: List[int] = Field(description=\"Relationships to their friends using the id\")\n",
    "\n",
    "\n",
    "resp = client.chat.completions.create(\n",
    "    model=\"gpt-4-1106-preview\",\n",
    "    messages=[{\"role\": \"user\", \"content\": \"5 kids from Harry Potter\"}],\n",
    "    stream=True,\n",
    "    response_model=Iterable[Character],\n",
    ")\n",
    "\n",
    "for character in resp:\n",
    "    print(character)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "523b5797-71a5-4a96-a4b7-21280fb73015",
   "metadata": {},
   "source": [
    "With the tools we've discussed, we can find numerous real-world applications in production settings. These include extracting action items from transcripts, generating fake data, filling out forms, and creating objects that correspond to generative UI. These simple tricks will be highly useful.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9d20fd9-0cd0-4300-a8c1-d16388969e8e",
   "metadata": {},
   "source": [
    "# Missing Data\n",
    "\n",
    "The Maybe pattern is a concept in functional programming used for error handling. Instead of raising exceptions or returning None, you can use a Maybe type to encapsulate both the result and potential errors.\n",
    "\n",
    "This pattern is particularly useful when making LLM calls, as providing language models with an escape hatch can effectively reduce hallucinations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c04f44aa-dc4b-4499-a151-e812512e77e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Optional\n",
    "\n",
    "class Character(BaseModel):\n",
    "    age: int\n",
    "    name: str\n",
    "\n",
    "class MaybeCharacter(BaseModel):\n",
    "    result: Optional[Character] = Field(default=None)\n",
    "    error: bool = Field(default=False)\n",
    "    message: Optional[str]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a2155190-e104-4ed6-a17f-e0732499dd51",
   "metadata": {},
   "outputs": [],
   "source": [
    "def extract(content: str) -> MaybeCharacter:\n",
    "    return client.chat.completions.create(\n",
    "        model=\"gpt-3.5-turbo\",\n",
    "        response_model=MaybeCharacter,\n",
    "        messages=[\n",
    "            {\"role\": \"user\", \"content\": f\"Extract `{content}`\"},\n",
    "        ],\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "a7b59afa-9bf0-4dc0-a5ca-de584514f33b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "MaybeCharacter(result=Character(age=17, name='Harry Potter'), error=False, message=None)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "extract(\"Harry Potter\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "b5ddd5c1-ca75-49a9-95ad-181170435291",
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "404 Error",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m/Users/jasonliu/dev/instructor/docs/tutorials/2-tips.ipynb Cell 20\u001b[0m line \u001b[0;36m4\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/jasonliu/dev/instructor/docs/tutorials/2-tips.ipynb#X25sZmlsZQ%3D%3D?line=0'>1</a>\u001b[0m user \u001b[39m=\u001b[39m extract(\u001b[39m\"\u001b[39m\u001b[39m404 Error\u001b[39m\u001b[39m\"\u001b[39m)\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/jasonliu/dev/instructor/docs/tutorials/2-tips.ipynb#X25sZmlsZQ%3D%3D?line=2'>3</a>\u001b[0m \u001b[39mif\u001b[39;00m user\u001b[39m.\u001b[39merror:\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/jasonliu/dev/instructor/docs/tutorials/2-tips.ipynb#X25sZmlsZQ%3D%3D?line=3'>4</a>\u001b[0m     \u001b[39mraise\u001b[39;00m \u001b[39mValueError\u001b[39;00m(user\u001b[39m.\u001b[39mmessage)\n",
      "\u001b[0;31mValueError\u001b[0m: 404 Error"
     ]
    }
   ],
   "source": [
    "user = extract(\"404 Error\")\n",
    "\n",
    "if user.error:\n",
    "    raise ValueError(user.message)"
   ]
  }
 ],
 "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.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
