Hosted with nbsanity. See source notebook on GitHub.

L3: Retry-based Structured Output

⏳ Note (Kernel Starting): This notebook takes about 30 seconds to be ready to use. You may start and watch the video while you wait.

import os
from utils import get_together_api_key
import instructor
from openai import OpenAI

# Instantiate the client
together_client = OpenAI(
    base_url=f"{os.getenv('DLAI_TOGETHER_API_BASE', 'https://api.together.xyz')}/v1",
    api_key=get_together_api_key(),
)

💻   Access requirements.txt and helper.py files: 1) click on the “File” option on the top menu of the notebook and then 2) click on “Open”.

⬇   Download Notebooks: 1) click on the “File” option on the top menu of the notebook and then 2) click on “Download as” and select “Notebook (.ipynb)”.

📒   For more help, please see the “Appendix – Tips, Help, and Download” Lesson.

response = together_client.chat.completions.create(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
    messages=[{"role": "user", "content": "sup"}],
)

response.choices[0].message.content
"Sup back at you. How's your day going so far?"

Adding Instructor

# Wrap together with the instructor client
instructor_client = instructor.from_openai(together_client)
from pydantic import BaseModel


class Greeting(BaseModel):
    hello: str
response = instructor_client.chat.completions.create(
    model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
    messages=[{"role": "user", "content": "sup"}],
    # Note: OpenAI uses response_format, instructor
    #       uses response_model!
    response_model=Greeting,
)

response
Greeting(hello='sup')

Defining a calendar event

from pydantic import Field
from datetime import date
from typing import List


# Person only has a name, but we can easily extend it with other fields
class Person(BaseModel):
    name: str


class CalendarEvent(BaseModel):
    name: str

    # not supported by OpenAI. We want a format like 2025-01-30
    date: date
    participants: List[Person]

    address_number: str
    street_name: str
    city_name: str

    # Inline regex patterns not supported by OpenAI restrict state code
    # to be two capital letters (OpenAI does not support pattern fields)
    state_code: str = Field(pattern=r"[A-Z]{2}")

    # Zip code must be five digits
    zip_code: str = Field(pattern=r"\d{6}")
event_description = """
Rahul and Priya are going to a science fair on Friday, January 2024. 
The science fair is hosted at the auditorium of National Public School, Indiranagar at 12th Main Road, HAL 2nd Stage in Bangalore, Karnataka.
"""
def generate(
    response_model,
    user_prompt,
    system_prompt="You are a helpful assistant.",
    model="meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
    # Can use 70b for a higher-quality model
    # model="meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
    max_retries=3,
):
    event = instructor_client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt},
        ],
        response_model=response_model,
        max_retries=max_retries,
    )

    return event
system_prompt = """
Make a calendar event. Respond in JSON with
the event name, date, list of participants,
and the address.
"""

user_prompt = "Event description: " + event_description

event = generate(CalendarEvent, user_prompt, system_prompt=system_prompt)
event
CalendarEvent(name='Science Fair', date=datetime.date(2024, 1, 5), participants=[Person(name='Rahul'), Person(name='Priya')], address_number='12th Main Road', street_name='HAL 2nd Stage', city_name='Bangalore', state_code='KA', zip_code='560038')
event.model_dump()
{'name': 'Science Fair',
 'date': datetime.date(2024, 1, 5),
 'participants': [{'name': 'Rahul'}, {'name': 'Priya'}],
 'address_number': '12th Main Road',
 'street_name': 'HAL 2nd Stage',
 'city_name': 'Bangalore',
 'state_code': 'KA',
 'zip_code': '560038'}

Retries

from utils import UsageTracker

# Clear any completion response hooks -- prevents
# duplicate writes to the usage tracker.
instructor_client.clear("completion:response")

# Create a new tracker
tracker = UsageTracker()


# Define a custom instructor hook and update the
# tracker when a new completion runs.
def log_completion_kwargs(*args, **kwargs):
    usage = args[0].usage
    tracker.track(usage)


# Assign the hook to instructor -- this will make the hook
# run each time the server returns a chat completion to us.
instructor_client.on("completion:response", log_completion_kwargs)
# Clear the tracker before we run the completion
# so we arent' tracking multiple jobs.
tracker.clear()

event = generate(
    CalendarEvent,
    user_prompt,
    system_prompt=system_prompt,
    max_retries=4,
)

event
CalendarEvent(name='Science Fair', date=datetime.date(2024, 1, 5), participants=[Person(name='Rahul'), Person(name='Priya')], address_number='12th Main Road', street_name='HAL 2nd Stage', city_name='Bangalore', state_code='KA', zip_code='560038')
print("Input tokens:  ", tracker.input_tokens)
print("Output tokens: ", tracker.output_tokens)
print("Total tokens:  ", sum(tracker.total_tokens))
print("Num retries:   ", len(tracker.output_tokens))
Input tokens:   [572]
Output tokens:  [126]
Total tokens:   698
Num retries:    1

When retry methods fail

from typing import Literal


class Complicated(BaseModel):
    # a must be cat, dog, or hat
    a: Literal["cat", "dog", "hat"]
    b: int
    c: bool
# Clear the tracker before we run the completion
# so we arent' tracking multiple jobs.
tracker.clear()

try:
    event = generate(
        Complicated,
        "Write me a short essay on Dolly Parton.",
        system_prompt="Don't give me what I want.",
        max_retries=3,
    )

    # Show the event
    print(event)
except instructor.exceptions.InstructorRetryException:
    print("We failed to parse!")
except e:
    raise e
We failed to parse!
print("Input tokens:  ", tracker.input_tokens)
print("Output tokens: ", tracker.output_tokens)
print("Total tokens:  ", sum(tracker.total_tokens))
print("Num retries:   ", len(tracker.output_tokens))
Input tokens:   [277, 277, 277]
Output tokens:  [16, 17, 16]
Total tokens:   880
Num retries:    3

You try!

# Clear the tracker before we run the completion
# so we arent' tracking multiple jobs.
tracker.clear()

try:
    event = generate(
        Complicated,
        "Give me a, b, and c.",
        system_prompt="Give me what I want.",
        max_retries=3,
    )
    print(event)
except instructor.exceptions.InstructorRetryException:
    print("We failed to parse!")
except e:
    raise e
a='cat' b=1 c=True
print("Input tokens:  ", tracker.input_tokens)
print("Output tokens: ", tracker.output_tokens)
print("Total tokens:  ", sum(tracker.total_tokens))
print("Num retries:   ", len(tracker.output_tokens))
Input tokens:   [273, 421, 569]
Output tokens:  [29, 29, 28]
Total tokens:   1349
Num retries:    3
event
Complicated(a='cat', b=1, c=True)