Skip to content

Commit

Permalink
more fixes!
Browse files Browse the repository at this point in the history
  • Loading branch information
jxnl committed Feb 5, 2024
1 parent 0b075e8 commit c14f5a5
Show file tree
Hide file tree
Showing 10 changed files with 1,466 additions and 85 deletions.
7 changes: 3 additions & 4 deletions docs/concepts/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import time
import functools
import openai
import instructor
from pydantic import BaseModel

client = instructor.patch(openai.OpenAI())

Expand All @@ -32,18 +33,16 @@ def extract(data) -> UserDetail:
start = time.perf_counter() # (1)
model = extract("Extract jason is 25 years old")
print(f"Time taken: {time.perf_counter() - start}")
#> Time taken: 0.4529974169563502

start = time.perf_counter()
model = extract("Extract jason is 25 years old") # (2)
print(f"Time taken: {time.perf_counter() - start}")

#> Time taken: 0.9267581660533324
#> Time taken: 1.2080417945981026e-06 # (3)
#> Time taken: 7.080379873514175e-07
```

1. Using `time.perf_counter()` to measure the time taken to run the function is better than using `time.time()` because it's more accurate and less susceptible to system clock changes.
2. The second time we call `extract`, the result is returned from the cache, and the function is not called.
3. The second call to `extract` is much faster because the result is returned from the cache!

!!! warning "Changing the Model does not Invalidate the Cache"

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/fastapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class UserDetail(BaseModel):


@app.post("/endpoint", response_model=UserDetail)
def endpoint_function(data: UserData) -> UserDetail:
async def endpoint_function(data: UserData) -> UserDetail:
user_detail = await client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
Expand Down
56 changes: 35 additions & 21 deletions docs/concepts/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,39 +117,53 @@ users = client.chat.completions.create(
},
{
"role": "user",
"content": (
f"Consider the data below:\n{input}"
"Correctly segment it into entitites"
"Make sure the JSON is correct"
),
"content": (f"Extract `Jason is 10 and John is 10`"),
},
],
max_tokens=1000,
)

for user in users:
print(user)
#> name='Correctly segment it into entitites' age=0
#> name='Make sure the JSON is correct' age=0

#> name="Jason" "age"=10
#> name="John" "age"=10
#> name='Jason' age=10
#> name='John' age=10
```

## Asynchronous Streaming

I also just want to call out in this example that `instructor` also supports asynchronous streaming. This is useful when you want to stream a response model and process the results as they come in, but you'll need to use the `async for` syntax to iterate over the results.

```python
model = await client.chat.completions.create(
model="gpt-4",
response_model=Iterable[UserExtract],
max_retries=2,
stream=stream,
messages=[
{"role": "user", "content": "Make two up people"},
],
)
async for m in model:
assert isinstance(m, UserExtract)
import instructor
import openai
from typing import Iterable
from pydantic import BaseModel

client = instructor.patch(openai.AsyncOpenAI(), mode=instructor.Mode.TOOLS)


class UserExtract(BaseModel):
name: str
age: int


async def print_iterable_results():
model = await client.chat.completions.create(
model="gpt-4",
response_model=Iterable[UserExtract],
max_retries=2,
stream=True,
messages=[
{"role": "user", "content": "Make two up people"},
],
)
async for m in model:
print(m)
#> name='John Doe' age=30
#> name='Jane Doe' age=28


import asyncio

asyncio.run(print_iterable_results())
```
54 changes: 28 additions & 26 deletions docs/concepts/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ print(BarModel.model_fields.keys())
We can then use this information to create the model.

```python
from pydantic import BaseModel, create_model
from typing import List

types = {
'string': str,
'integer': int,
Expand All @@ -90,43 +93,42 @@ print(BarModel.model_fields.keys())
'List[str]': List[str],
}

# Mocked cursor.fetchall()
cursor = [
('name', 'string', 'The name of the user.'),
('age', 'integer', 'The age of the user.'),
('email', 'string', 'The email of the user.'),
]

BarModel = create_model(
'User',
**{
property_name: (types[property_type], description)
for property_name, property_type, description in cursor.fetchall()
for property_name, property_type, description in cursor
},
__base__=BaseModel,
)

print(BarModel.model_json_schema())
"""
{
'properties': {
'name': {'default': 'The name of the user.', 'title': 'Name', 'type': 'string'},
'age': {'default': 'The age of the user.', 'title': 'Age', 'type': 'integer'},
'email': {
'default': 'The email of the user.',
'title': 'Email',
'type': 'string',
},
},
'title': 'User',
'type': 'object',
}
"""
```

This would be useful when different users have different descriptions for the same model. We can use the same model but have different prompts for each user.

## Structural Pattern Matching

Pydantic supports structural pattern matching for models, as introduced by [PEP 636](https://peps.python.org/pep-0636/) in Python 3.10.

```python
from pydantic import BaseModel


class Pet(BaseModel):
name: str
species: str


pet = Pet(name='Bones', species='dog')

match pet:
# match `species` to 'dog', declare and initialize `dog_name`
case Pet(species='dog', name=dog_name):
print(f'{dog_name} is a dog')
#> Bones is a dog
# default case
case _:
print('No dog matched')
```

## Adding Behavior

We can add methods to our Pydantic models, just as any plain Python class. We might want to do this to add some custom logic to our models.
Expand Down
Loading

0 comments on commit c14f5a5

Please sign in to comment.