Skip to content

Commit

Permalink
fix: Fix examples docs (instructor-ai#1077)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanleomk authored Oct 16, 2024
1 parent d7328ed commit 356c317
Show file tree
Hide file tree
Showing 12 changed files with 1,558 additions and 1,778 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/test_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y graphviz libcairo2-dev xdg-utils
- name: Install Poetry
uses: snok/[email protected]

Expand Down
6 changes: 3 additions & 3 deletions docs/examples/batch_job_oai.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ The Reserve Bank of Australia (RBA) came into being on 14 January 1960 as Austra
print(generate_question(text_chunk).model_dump_json(indent=2))
"""
{
"chain_of_thought": "The text talks about the establishment of the Reserve Bank of Australia, including its date, the act that created it, and some financial details. The focus is on the RBA's foundation and its role. Therefore, a suitable question would focus on when the RBA was established and what act facilitated its creation.",
"question": "When was the Reserve Bank of Australia established and what act created it?",
"answer": "The Reserve Bank of Australia was established on 14 January 1960 by the Reserve Bank Act 1959."
"chain_of_thought": "The text provides historical information about the Reserve Bank of Australia, including its establishment date and the key functions it took over from the Commonwealth Bank. It also details its assets and employee distribution. A question that captures this information was formulated.",
"question": "When was the Reserve Bank of Australia established?",
"answer": "14 January 1960."
}
"""
```
Expand Down
112 changes: 96 additions & 16 deletions docs/examples/extracting_receipts.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ This post demonstrates how to use Python's Pydantic library and OpenAI's GPT-4 m
First, we define two Pydantic models, `Item` and `Receipt`, to structure the extracted data. The `Item` class represents individual items on the receipt, with fields for name, price, and quantity. The `Receipt` class contains a list of `Item` objects and the total amount.

```python
from pydantic import BaseModel


class Item(BaseModel):
name: str
price: float
Expand All @@ -29,15 +32,15 @@ To ensure the accuracy of the extracted data, we use Pydantic's `model_validator

```python
@model_validator(mode="after")
def check_total(cls, values: "Receipt"):
items = values.items
total = values.total
def check_total(self):
items = self.items
total = self.total
calculated_total = sum(item.price * item.quantity for item in items)
if calculated_total != total:
raise ValueError(
f"Total {total} does not match the sum of item prices {calculated_total}"
)
return values
return self
```

## Extracting Receipt Data from Images
Expand All @@ -48,10 +51,35 @@ The `extract_receipt` function uses OpenAI's GPT-4 model to process an image URL
import instructor
from openai import OpenAI

client = instructor.from_openai(
client=OpenAI(),
mode=instructor.Mode.TOOLS,
)
# <%hide%>
from pydantic import BaseModel, model_validator


class Item(BaseModel):
name: str
price: float
quantity: int


class Receipt(BaseModel):
items: list[Item]
total: float

@model_validator(mode="after")
def check_total(cls, values: "Receipt"):
items = values.items
total = values.total
calculated_total = sum(item.price * item.quantity for item in items)
if calculated_total != total:
raise ValueError(
f"Total {total} does not match the sum of item prices {calculated_total}"
)
return values


# <%hide%>

client = instructor.from_openai(OpenAI())


def extract(url: str) -> Receipt:
Expand Down Expand Up @@ -82,14 +110,66 @@ def extract(url: str) -> Receipt:
In these examples, we apply the method to extract receipt data from two different images. The custom validation function ensures that the extracted total amount matches the sum of item prices.

```python
urls = [
"https://templates.mediamodifier.com/645124ff36ed2f5227cbf871/supermarket-receipt-template.jpg",
"https://ocr.space/Content/Images/receipt-ocr-original.jpg",
]

for url in urls:
receipt = extract(url)
print(receipt)
# <%hide%>
from pydantic import BaseModel, model_validator
import instructor
from openai import OpenAI


class Item(BaseModel):
name: str
price: float
quantity: int


class Receipt(BaseModel):
items: list[Item]
total: float

@model_validator(mode="after")
def check_total(cls, values: "Receipt"):
items = values.items
total = values.total
calculated_total = sum(item.price * item.quantity for item in items)
if calculated_total != total:
raise ValueError(
f"Total {total} does not match the sum of item prices {calculated_total}"
)
return values


client = instructor.from_openai(OpenAI())


def extract(url: str) -> Receipt:
return client.chat.completions.create(
model="gpt-4o",
max_tokens=4000,
response_model=Receipt,
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": url},
},
{
"type": "text",
"text": "Analyze the image and return the items in the receipt and the total amount.",
},
],
}
],
)


# <%hide%>
url = "https://templates.mediamodifier.com/645124ff36ed2f5227cbf871/supermarket-receipt-template.jpg"


receipt = extract(url)
print(receipt)
```

By combining the power of GPT-4 and Python's Pydantic library, we can accurately extract and validate receipt data from images, streamlining expense tracking and financial analysis tasks.
180 changes: 178 additions & 2 deletions docs/examples/extracting_tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,45 @@ MarkdownDataFrame = Annotated[
The `Table` class is essential for organizing the extracted data. It includes a caption and a dataframe, processed as a markdown table. Since most of the complexity is handled by the `MarkdownDataFrame` type, the `Table` class is straightforward!

```python
from pydantic import BaseModel

# <%hide%>
from io import StringIO
from typing import Annotated, Any
from pydantic import BeforeValidator, PlainSerializer, InstanceOf, WithJsonSchema
import pandas as pd


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[
InstanceOf[pd.DataFrame],
BeforeValidator(md_to_df),
PlainSerializer(lambda df: df.to_markdown()),
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",
}
),
]
# <%hide%>


class Table(BaseModel):
caption: str
dataframe: MarkdownDataFrame
Expand All @@ -66,6 +105,48 @@ The `extract_table` function uses OpenAI's vision model to process an image URL
```python
import instructor
from openai import OpenAI
from typing import Iterable

# <%hide%>
from pydantic import BaseModel
from io import StringIO
from typing import Annotated, Any
from pydantic import BeforeValidator, PlainSerializer, InstanceOf, WithJsonSchema
import pandas as pd


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[
InstanceOf[pd.DataFrame],
BeforeValidator(md_to_df),
PlainSerializer(lambda df: df.to_markdown()),
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",
}
),
]

class Table(BaseModel):
caption: str
dataframe: MarkdownDataFrame
# <%hide%>

# Apply the patch to the OpenAI client to support response_model
# Also use MD_JSON mode since the visino model does not support any special structured output mode
Expand All @@ -74,7 +155,7 @@ client = instructor.from_openai(OpenAI(), mode=instructor.function_calls.Mode.MD

def extract_table(url: str) -> Iterable[Table]:
return client.chat.completions.create(
model="gpt-4-vision-preview",
model="gpt-4o-mini",
response_model=Iterable[Table],
max_tokens=1800,
messages=[
Expand All @@ -94,11 +175,106 @@ def extract_table(url: str) -> Iterable[Table]:
In this example, we apply the method to extract data from an image showing the top grossing apps in Ireland for October 2023.

```python
# <%hide%>
import instructor
from openai import OpenAI
from typing import Iterable

from pydantic import BaseModel
from io import StringIO
from typing import Annotated, Any
from pydantic import BeforeValidator, PlainSerializer, InstanceOf, WithJsonSchema
import pandas as pd


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[
InstanceOf[pd.DataFrame],
BeforeValidator(md_to_df),
PlainSerializer(lambda df: df.to_markdown()),
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",
}
),
]


class Table(BaseModel):
caption: str
dataframe: MarkdownDataFrame


client = instructor.from_openai(OpenAI())


def extract_table(url: str) -> Iterable[Table]:
return client.chat.completions.create(
model="gpt-4o",
response_model=Iterable[Table],
max_tokens=1800,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Extract table from image."},
{"type": "image_url", "image_url": {"url": url}},
],
}
],
)
# <%hide%>

url = "https://a.storyblok.com/f/47007/2400x2000/bf383abc3c/231031_uk-ireland-in-three-charts_table_v01_b.png"
tables = extract_table(url)
for table in tables:
print(table.caption, end="\n")
print(table.caption)
#> Top 10 grossing apps in October 2023 (Ireland) - Android
"""
App Category
Rank
1 Google One Productivity
2 Disney+ Entertainment
3 TikTok - Videos, Music & LIVE Entertainment
4 Candy Crush Saga Games
5 Tinder: Dating, Chat & Friends Social networking
6 Coin Master Games
7 Roblox Games
8 Bumble - Dating & Make Friends Dating
9 Royal Match Games
10 Spotify: Music and Podcasts Music & Audio
"""
print(table.dataframe)
"""
App Name Category
Rank
1 Google One Productivity
2 Disney+ Entertainment
3 TikTok - Videos, Music & LIVE Entertainment
4 Candy Crush Saga Games
5 Tinder: Dating, Chat & Friends Social networking
6 Coin Master Games
7 Roblox Games
8 Bumble - Dating & Make Friends Dating
9 Royal Match Games
10 Spotify: Music and Podcasts Music & Audio
"""
```

??? Note "Expand to see the output"
Expand Down
Loading

0 comments on commit 356c317

Please sign in to comment.