Support for Simple Types¶
Aside from the recommended pydantic.BaseModel
, and Iterable, and Partial,
Instructor supports simple types like str
, int
, float
, bool
, Union
, Literal
, out of the box. You can use these types directly in your response models.
To add more descriptions you can also use typing.Annotated
to include more information about the type.
What happens behind the scenes?¶
We will actually wrap the response model with a pydantic.BaseModel
of the following form:
from typing import Annotated
from pydantic import create_model, Field, BaseModel
typehint = Annotated[bool, Field(description="Sample Description")]
model = create_model("Response", content=(typehint, ...), __base__=BaseModel)
print(model.model_json_schema())
"""
{
'properties': {
'content': {
'description': 'Sample Description',
'title': 'Content',
'type': 'boolean',
}
},
'required': ['content'],
'title': 'Response',
'type': 'object',
}
"""
Primitive Types (str, int, float, bool)¶
import instructor
import openai
client = instructor.from_openai(openai.OpenAI())
# Response model with simple types like str, int, float, bool
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=bool,
messages=[
{
"role": "user",
"content": "Is it true that Paris is the capital of France?",
},
],
)
assert resp is True, "Paris is the capital of France"
print(resp)
#> True
Annotated¶
Annotations can be used to add more information about the type. This can be useful for adding descriptions to the type, along with more complex information like field names, and more.
import instructor
import openai
from typing import Annotated
from pydantic import Field
client = instructor.from_openai(openai.OpenAI())
UpperCaseStr = Annotated[str, Field(description="string must be upper case")]
# Response model with simple types like str, int, float, bool
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UpperCaseStr,
messages=[
{
"role": "user",
"content": "What is the capital of france?",
},
],
)
assert resp == "PARIS", "Paris is the capital of France"
print(resp)
#> PARIS
Literal¶
When doing simple classification Literals go quite well, they support literal of string, int, bool.
import instructor
import openai
from typing import Literal
client = instructor.from_openai(openai.OpenAI())
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=Literal["BILLING", "SHIPPING"],
messages=[
{
"role": "user",
"content": "Classify the following messages: 'I am having trouble with my billing'",
},
],
)
assert resp == "BILLING"
print(resp)
#> BILLING
Enum¶
Enums are harder to get right without some addition promping but are useful if these are values that are shared across the application.
import instructor
import openai
from enum import Enum
class Label(str, Enum):
BILLING = "BILLING"
SHIPPING = "SHIPPING"
client = instructor.from_openai(openai.OpenAI())
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=Label,
messages=[
{
"role": "user",
"content": "Classify the following messages: 'I am having trouble with my billing'",
},
],
)
assert resp == Label.BILLING
print(resp)
#> BILLING
List¶
import instructor
import openai
from typing import List
client = instructor.from_openai(openai.OpenAI())
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=List[int],
messages=[
{
"role": "user",
"content": "Give me the first 5 prime numbers",
},
],
)
assert resp == [2, 3, 5, 7, 11]
print(resp)
#> [2, 3, 5, 7, 11]
Union¶
Union is a great way to handle multiple types of responses, similar to multiple function calls but not limited to the function calling api, like in JSON_SCHEMA modes.
import instructor
import openai
from pydantic import BaseModel
from typing import Union
client = instructor.from_openai(openai.OpenAI())
class Add(BaseModel):
a: int
b: int
class Weather(BaseModel):
location: str
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=Union[Add, Weather],
messages=[
{
"role": "user",
"content": "What is 5 + 5?",
},
],
)
assert resp == Add(a=5, b=5)
print(resp)
#> a=5 b=5
Complex Types¶
Pandas DataFrame¶
This is a more complex example, where we use a custom type to convert markdown to a pandas DataFrame.
from io import StringIO
from typing import Annotated, Any
from pydantic import BeforeValidator, PlainSerializer, InstanceOf, WithJsonSchema
import pandas as pd
import instructor
import openai
def md_to_df(data: Any) -> Any:
# Convert markdown to DataFrame
if isinstance(data, str):
return (
pd.read_csv(
StringIO(data), # Process data
sep="|",
index_col=1,
)
.dropna(axis=1, how="all")
.iloc[1:]
.applymap(lambda x: x.strip())
)
return data
MarkdownDataFrame = Annotated[
# Validates final type
InstanceOf[pd.DataFrame],
# Converts markdown to DataFrame
BeforeValidator(md_to_df),
# Converts DataFrame to markdown on model_dump_json
PlainSerializer(lambda df: df.to_markdown()),
# Adds a description to the type
WithJsonSchema(
{
"type": "string",
"description": """
The markdown representation of the table,
each one should be tidy, do not try to join
tables that should be seperate""",
}
),
]
client = instructor.from_openai(openai.OpenAI())
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=MarkdownDataFrame,
messages=[
{
"role": "user",
"content": "Jason is 20, Sarah is 30, and John is 40",
},
],
)
assert isinstance(resp, pd.DataFrame)
print(resp)
"""
Age
Name
Jason 20
Sarah 30
John 40
"""
Lists of Unions¶
Just like Unions we can use List of Unions to represent multiple types of responses. This will feel similar to the parallel function calls but not limited to the function calling api, like in JSON_SCHEMA modes.
import instructor
import openai
from pydantic import BaseModel
from typing import Union, List
client = instructor.from_openai(openai.OpenAI())
class Weather(BaseModel, frozen=True):
location: str
class Add(BaseModel, frozen=True):
a: int
b: int
resp = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=List[Union[Add, Weather]],
messages=[
{
"role": "user",
"content": "Add 5 and 5, and also whats the weather in Toronto?",
},
],
)
assert resp == [Add(a=5, b=5), Weather(location="Toronto")]
print(resp)
#> [Add(a=5, b=5), Weather(location='Toronto')]