Skip to content

Commit

Permalink
Merge 7b55741 into ce579eb
Browse files Browse the repository at this point in the history
  • Loading branch information
jxnl committed Apr 1, 2024
2 parents ce579eb + 7b55741 commit a7ca6ad
Show file tree
Hide file tree
Showing 151 changed files with 1,568 additions and 458 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ jobs:
run: poetry install --with dev,anthropic

- name: Run tests
run: poetry run pytest tests/ -k "not openai and not anthropic and not evals"
run: poetry run pytest tests/ -k "not openai and not anthropic and not evals and not docs"
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

- name: Generate coverage report
if: matrix.python-version == '3.11'
run: |
poetry run coverage run -m pytest tests/
poetry run coverage run -m pytest tests/ -k "not docs"
poetry run coverage report
poetry run coverage html
env:
Expand Down
77 changes: 60 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,53 +26,69 @@ pip install -U instructor
Now, let's see Instructor in action with a simple example:

```python
import instructor
from pydantic import BaseModel
from instructor import patch
from openai import OpenAI


# Define your desired output structure
class UserInfo(BaseModel):
name: str
age: int


# Patch the OpenAI client
client = patch(OpenAI())
client = instructor.from_openai(OpenAI())

# Extract structured data from natural language
user_info = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserInfo,
messages=[
{"role": "user", "content": "John Doe is 30 years old."}
]
response_model=UserInfo,
messages=[{"role": "user", "content": "John Doe is 30 years old."}],
)

print(user_info.name) # "John Doe"
print(user_info.age) # 30
#> John Doe
print(user_info.age) # 30
#> 30
```

## 🎯 Validation Made Easy

Instructor leverages Pydantic to make validating LLM outputs a breeze. Simply define your validation rules in your Pydantic models, and Instructor will ensure the LLM responses conform to your expectations. No more manual checking or parsing!

```python
from pydantic import BaseModel, ValidationError, BeforeValidator
from pydantic import BaseModel, ValidationError, BeforeValidator
from typing_extensions import Annotated
from instructor import llm_validator

import instructor
import openai

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


class QuestionAnswer(BaseModel):
question: str
answer: Annotated[
str, BeforeValidator(llm_validator("Don't say objectionable things"))
str,
BeforeValidator(llm_validator("Don't say objectionable things", client=client)),
]


try:
qa = QuestionAnswer(
question="What is the meaning of life?",
answer="The meaning of life is to be evil and steal",
)
except ValidationError as e:
print(e)
"""
1 validation error for QuestionAnswer
answer
Assertion failed, The statement promotes evil behavior, which is objectionable. [type=assertion_error, input_value='The meaning of life is to be evil and steal', input_type=str]
For further information visit https://errors.pydantic.dev/2.6/v/assertion_error
"""
```

## 📖 Learn More
Expand Down Expand Up @@ -102,28 +118,55 @@ We can't wait to see the amazing things you create with Instructor. If you have

## Using Anthropic Models

Install dependencies with
```python
import instructor
from anthropic import Anthropic
from pydantic import BaseModel


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


```shell
poetry install -E anthropic
client = instructor.from_anthropic(Anthropic())

# note that client.chat.completions.create will also work
resp = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Extract Jason is 25 years old.",
}
],
response_model=User,
)

assert isinstance(resp, User)
assert resp.name == "Jason"
assert resp.age == 25
```

Usage:
## Using Litellm

```python
import instructor
from anthropic import Anthropic
from litellm import completion
from pydantic import BaseModel


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

create = instructor.patch(create=anthropic.Anthropic().messages.create, mode=instructor.Mode.ANTHROPIC_TOOLS)

resp = create(
client = instructor.from_litellm(completion)

resp = client.chat.completions.create(
model="claude-3-opus-20240229",
max_tokens=1024,
max_retries=0,
messages=[
{
"role": "user",
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# API Reference

::: instructor.patch
::: instructor.from_openai

::: instructor.dsl.validators

Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/anthropic.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import anthropic
import instructor

# Patching the Anthropics client with the instructor for enhanced capabilities
anthropic_client = instructor.patch(
anthropic_client = instructor.from_openai(
create=anthropic.Anthropic().messages.create,
mode=instructor.Mode.ANTHROPIC_JSON
)
Expand Down
4 changes: 2 additions & 2 deletions docs/blog/posts/best_framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class User(BaseModel):
name: str
age: int

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

user = client.chat.completions.create(
model="gpt-3.5-turbo",
Expand Down Expand Up @@ -70,7 +70,7 @@ Unlike other libraries that abstract away the `messages=[...]` parameter, Instru

## Low Abstraction

What makes Instructor so powerful is how seamlessly it integrates with existing OpenAI SDK code. To use it, you literally just call instructor.patch() on your OpenAI client instance, then use response_model going forward. There's no complicated refactoring or new abstractions to wrap your head around.
What makes Instructor so powerful is how seamlessly it integrates with existing OpenAI SDK code. To use it, you literally just call instructor.from_openai() on your OpenAI client instance, then use response_model going forward. There's no complicated refactoring or new abstractions to wrap your head around.

This incremental, zero-overhead adoption path makes Instructor perfect for sprinkling structured LLM outputs into an existing OpenAI-based application. You can start extracting data models from simple prompts, then incrementally expand to more complex hierarchical models, streaming outputs, and custom validations.

Expand Down
6 changes: 3 additions & 3 deletions docs/blog/posts/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ from openai import OpenAI
from pydantic import BaseModel

# Enables `response_model`
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())


class UserDetail(BaseModel):
Expand Down Expand Up @@ -177,7 +177,7 @@ import diskcache
from openai import OpenAI
from pydantic import BaseModel

client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())
cache = diskcache.Cache('./my_cache_directory')


Expand Down Expand Up @@ -281,7 +281,7 @@ import instructor
from pydantic import BaseModel
from openai import OpenAI

client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())
cache = redis.Redis("localhost")


Expand Down
4 changes: 2 additions & 2 deletions docs/blog/posts/chain-of-density.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ Now that we have our models and the rough flow figured out, let's implement a fu
from openai import OpenAI
import instructor

client = instructor.patch(OpenAI()) #(1)!
client = instructor.from_openai(OpenAI()) #(1)!

def summarize_article(article: str, summary_steps: int = 3):
summary_chain = []
Expand Down Expand Up @@ -397,7 +397,7 @@ import instructor
from pydantic import BaseModel
from openai import OpenAI

client = instructor.patch(OpenAI()) # (2)!
client = instructor.from_openai(OpenAI()) # (2)!

logging.basicConfig(level=logging.INFO) #(3)!

Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/citations.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ from openai import OpenAI
from pydantic import BaseModel, ValidationInfo, field_validator
import instructor

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


class Statements(BaseModel):
Expand Down
8 changes: 4 additions & 4 deletions docs/blog/posts/fake-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class UserDetail(BaseModel):


# Patch the OpenAI client to enable the response_model functionality
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())


def generate_fake_users(count: int) -> Iterable[UserDetail]:
Expand Down Expand Up @@ -67,7 +67,7 @@ class UserDetail(BaseModel):


# Patch the OpenAI client to enable the response_model functionality
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())


def generate_fake_users(count: int) -> Iterable[UserDetail]:
Expand Down Expand Up @@ -122,7 +122,7 @@ class UserDetail(BaseModel):


# Patch the OpenAI client to enable the response_model functionality
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())


def generate_fake_users(count: int) -> Iterable[UserDetail]:
Expand Down Expand Up @@ -168,7 +168,7 @@ class UserDetail(BaseModel):


# Patch the OpenAI client to enable the response_model functionality
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())


def generate_fake_users(count: int) -> Iterable[UserDetail]:
Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ from openai import OpenAI
from typing import Iterable
from pydantic import BaseModel

client = instructor.patch(OpenAI(), mode=instructor.function_calls.Mode.JSON)
client = instructor.from_openai(OpenAI(), mode=instructor.function_calls.Mode.JSON)


class ProductRecommendation(BaseModel):
Expand Down
Binary file added docs/blog/posts/img/async_type.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/blog/posts/img/downloads.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/blog/posts/img/generator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/blog/posts/img/iterable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/blog/posts/img/type.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/blog/posts/img/with_completion.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/blog/posts/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import instructor
from openai import OpenAI

# Enables the response_model
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())


class UserDetail(pydantic.BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion docs/blog/posts/langsmith.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ from enum import Enum
client = wrap_openai(AsyncOpenAI())

# Patch the client with instructor
client = instructor.patch(client, mode=instructor.Mode.TOOLS)
client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)

# Rate limit the number of requests
sem = asyncio.Semaphore(5)
Expand Down
12 changes: 6 additions & 6 deletions docs/blog/posts/open_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class UserDetail(BaseModel):


# enables `response_model` in create call
client = instructor.patch(
client = instructor.from_openai(
OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # required, but unused
Expand Down Expand Up @@ -104,7 +104,7 @@ llama = llama_cpp.Llama(
)


create = instructor.patch(
create = instructor.from_openai(
create=llama.create_chat_completion_openai_v1,
mode=instructor.Mode.JSON_SCHEMA,
)
Expand Down Expand Up @@ -151,7 +151,7 @@ class UserDetails(BaseModel):


# enables `response_model` in create call
client = instructor.patch(
client = instructor.from_openai(
OpenAI(
base_url="https://api.endpoints.anyscale.com/v1",
api_key=os.environ["ANYSCALE_API_KEY"],
Expand Down Expand Up @@ -191,7 +191,7 @@ client = qrog.Groq(
)

# By default, the patch function will patch the ChatCompletion.create and ChatCompletion.create methods to support the response_model parameter
client = instructor.patch(client, mode=instructor.Mode.MD_JSON)
client = instructor.from_openai(client, mode=instructor.Mode.MD_JSON)


# Now, we can use the response_model parameter using only a base model
Expand Down Expand Up @@ -234,7 +234,7 @@ client = openai.OpenAI(
api_key=os.environ["TOGETHER_API_KEY"],
)
client = instructor.patch(client, mode=instructor.Mode.TOOLS)
client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)
class UserExtract(BaseModel):
name: str
Expand Down Expand Up @@ -267,7 +267,7 @@ from mistralai.client import MistralClient
client = MistralClient()
patched_chat = instructor.patch(create=client.chat, mode=instructor.Mode.MISTRAL_TOOLS)
patched_chat = instructor.from_openai(create=client.chat, mode=instructor.Mode.MISTRAL_TOOLS)
class UserDetails(BaseModel):
name: str
Expand Down
4 changes: 2 additions & 2 deletions docs/blog/posts/rag-and-beyond.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ import instructor
from openai import OpenAI

# Enables response_model in the openai client
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())

query = client.chat.completions.create(
model="gpt-4",
Expand Down Expand Up @@ -179,7 +179,7 @@ import instructor
from openai import OpenAI

# Enables response_model in the openai client
client = instructor.patch(OpenAI())
client = instructor.from_openai(OpenAI())

retrieval = client.chat.completions.create(
model="gpt-4",
Expand Down
Loading

0 comments on commit a7ca6ad

Please sign in to comment.