참고소스 수정본

This commit is contained in:
LASTA_DEV01\lasta
2026-05-12 19:40:31 +09:00
parent 0f34a451fc
commit 2e9204243d
8708 changed files with 3259488 additions and 869 deletions

View File

@@ -0,0 +1,148 @@
---
title: Automating Action Item Extraction from Meeting Transcripts
description: Learn to extract actionable items from meeting transcripts using OpenAI's API and Pydantic for efficient project management.
---
# Extracting Action Items from Meeting Transcripts
In this guide, we'll walk through how to extract action items from meeting transcripts using OpenAI's API and Pydantic. This use case is essential for automating project management tasks, such as task assignment and priority setting.
For multi-label classification, we introduce a new enum class and a different Pydantic model to handle multiple labels.
!!! tips "Motivation"
Significant amount of time is dedicated to meetings, where action items are generated as the actionable outcomes of these discussions. Automating the extraction of action items can save time and guarantee that no critical tasks are overlooked.
## Defining the Structures
We'll model a meeting transcript as a collection of **`Ticket`** objects, each representing an action item. Every **`Ticket`** can have multiple **`Subtask`** objects, representing smaller, manageable pieces of the main task.
## Extracting Action Items
To extract action items from a meeting transcript, we use the **`generate`** function. It calls OpenAI's API, processes the text, and returns a set of action items modeled as **`ActionItems`**.
## Evaluation and Testing
To test the **`generate`** function, we provide it with a sample transcript, and then print the JSON representation of the extracted action items.
```python
import instructor
from typing import Iterable, List, Optional
from enum import Enum
from pydantic import BaseModel
class PriorityEnum(str, Enum):
high = "High"
medium = "Medium"
low = "Low"
class Subtask(BaseModel):
"""Correctly resolved subtask from the given transcript"""
id: int
name: str
class Ticket(BaseModel):
"""Correctly resolved ticket from the given transcript"""
id: int
name: str
description: str
priority: PriorityEnum
assignees: List[str]
subtasks: Optional[List[Subtask]]
dependencies: Optional[List[int]]
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
def generate(data: str) -> Iterable[Ticket]:
return client.create(
model="gpt-4",
response_model=Iterable[Ticket],
messages=[
{
"role": "system",
"content": "The following is a transcript of a meeting...",
},
{
"role": "user",
"content": f"Create the action items for the following transcript: {data}",
},
],
)
prediction = generate(
"""
Alice: Hey team, we have several critical tasks we need to tackle for the upcoming release. First, we need to work on improving the authentication system. It's a top priority.
Bob: Got it, Alice. I can take the lead on the authentication improvements. Are there any specific areas you want me to focus on?
Alice: Good question, Bob. We need both a front-end revamp and back-end optimization. So basically, two sub-tasks.
Carol: I can help with the front-end part of the authentication system.
Bob: Great, Carol. I'll handle the back-end optimization then.
Alice: Perfect. Now, after the authentication system is improved, we have to integrate it with our new billing system. That's a medium priority task.
Carol: Is the new billing system already in place?
Alice: No, it's actually another task. So it's a dependency for the integration task. Bob, can you also handle the billing system?
Bob: Sure, but I'll need to complete the back-end optimization of the authentication system first, so it's dependent on that.
Alice: Understood. Lastly, we also need to update our user documentation to reflect all these changes. It's a low-priority task but still important.
Carol: I can take that on once the front-end changes for the authentication system are done. So, it would be dependent on that.
Alice: Sounds like a plan. Let's get these tasks modeled out and get started."""
)
```
## Visualizing the tasks
In order to quickly visualize the data we used code interpreter to create a graphviz export of the json version of the ActionItems array.
![Action items visualization showing extracted tasks with priorities and dependencies](../img/action_items.png)
```json
[
{
"id": 1,
"name": "Improve Authentication System",
"description": "Revamp the front-end and optimize the back-end of the authentication system",
"priority": "High",
"assignees": ["Bob", "Carol"],
"subtasks": [
{
"id": 2,
"name": "Front-end Revamp"
},
{
"id": 3,
"name": "Back-end Optimization"
}
],
"dependencies": []
},
{
"id": 4,
"name": "Integrate Authentication System with Billing System",
"description": "Integrate the improved authentication system with the new billing system",
"priority": "Medium",
"assignees": ["Bob"],
"subtasks": [],
"dependencies": [1]
},
{
"id": 5,
"name": "Update User Documentation",
"description": "Update the user documentation to reflect the changes in the authentication system",
"priority": "Low",
"assignees": ["Carol"],
"subtasks": [],
"dependencies": [2]
}
]
```
In this example, the **`generate`** function successfully identifies and segments the action items, assigning them priorities, assignees, subtasks, and dependencies as discussed in the meeting.
By automating this process, you can ensure that important tasks and details are not lost in the sea of meeting minutes, making project management more efficient and effective.

View File

@@ -0,0 +1,97 @@
---
title: Audio Information Extraction with OpenAI
description: Learn how to extract structured information from audio files using OpenAI's audio capabilities and Instructor for type-safe data extraction.
---
# Audio Information Extraction with OpenAI
This example demonstrates how to use Instructor with OpenAI's audio capabilities to extract structured information from audio files. The example shows how to process audio input and extract specific fields into a Pydantic model.
## Prerequisites
- OpenAI API key with access to GPT-4 audio models
- An audio file in WAV format
- Instructor library installed with OpenAI support
## Code Example
```python
from pydantic import BaseModel
import instructor
from instructor.processing.multimodal import Audio
import base64
# Initialize the OpenAI client with Instructor
client = instructor.from_provider("openai/gpt-5-nano")
# Define the structure for extracted information
class Person(BaseModel):
name: str
age: int
# Read and encode the audio file
with open("./output.wav", "rb") as f:
encoded_string = base64.b64encode(f.read()).decode("utf-8")
# Extract information from the audio
resp = client.create(
model="gpt-4-audio-preview",
response_model=Person,
modalities=["text"],
audio={"voice": "alloy", "format": "wav"},
messages=[
{
"role": "user",
"content": [
"Extract the following information from the audio",
Audio.from_path("./output.wav"),
],
},
],
)
print(resp)
# Example output: Person(name='Jason', age=20)
```
## How It Works
1. First, we import the necessary libraries including the `Audio` class from `instructor.processing.multimodal`.
2. We define a Pydantic model `Person` that specifies the structure of the information we want to extract from the audio:
- `name`: The person's name
- `age`: The person's age
3. The audio file is read and encoded in base64 format.
4. We use OpenAI's audio-capable model to process the audio and extract the specified information:
- The `model` parameter specifies the GPT-4 audio model
- `response_model` tells Instructor to structure the output according to our `Person` model
- `modalities` specifies that we want text output
- The `audio` parameter configures audio-specific settings
- In the message, we use `Audio.from_path()` to include the audio file
5. The response is automatically parsed into our Pydantic model, making the extracted information easily accessible in a structured format.
## Use Cases
This pattern is particularly useful for:
- Transcribing and extracting information from recorded interviews
- Processing voice messages or audio notes
- Automated form filling from voice input
- Voice-based data entry systems
## Tips
- Ensure your audio file is in a supported format (WAV in this example)
- The audio model works best with clear speech and minimal background noise
- Consider the length of the audio file, as there may be model-specific limitations
- Structure your Pydantic model to match the information you expect to extract
## Related Examples
- [Multi-Modal Data with Gemini](multi_modal_gemini.md)
- [Structured Outputs with OpenAI](../integrations/openai.md)

View File

@@ -0,0 +1,155 @@
---
title: Enhancing OpenAI Client with LangSmith and Instructor
description: Discover how to integrate LangSmith with the OpenAI client for improved observability and functionality using instructor.
---
# Seamless Support with Langsmith
Its a common misconception that LangChain's [LangSmith](https://www.langchain.com/langsmith) is only compatible with LangChain's models. In reality, LangSmith is a unified DevOps platform for developing, collaborating, testing, deploying, and monitoring LLM applications. In this blog we will explore how LangSmith can be used to enhance the OpenAI client alongside `instructor`.
First, install the necessary packages:
```bash
pip install -U langsmith
```
## LangSmith
In order to use langsmith, you first need to set your LangSmith API key.
```bash
export LANGCHAIN_API_KEY=<your-api-key>
```
Next, you will need to install the LangSmith SDK:
```bash
pip install -U langsmith
pip install -U instructor
```
In this example we'll use the `wrap_openai` function to wrap the OpenAI client with LangSmith. This will allow us to use LangSmith's observability and monitoring features with the OpenAI client. Then we'll use `instructor` to patch the client with the `TOOLS` mode. This will allow us to use `instructor` to add additional functionality to the client.
```python
import instructor
import asyncio
from langsmith import traceable
from langsmith.wrappers import wrap_openai
from openai import AsyncOpenAI
from pydantic import BaseModel, Field, field_validator
from typing import List
from enum import Enum
# Wrap the OpenAI client with LangSmith
client = wrap_openai(AsyncOpenAI())
# Patch the client with instructor
client = instructor.from_provider("openai/gpt-4o")
# Rate limit the number of requests
sem = asyncio.Semaphore(5)
# Use an Enum to define the types of questions
class QuestionType(Enum):
CONTACT = "CONTACT"
TIMELINE_QUERY = "TIMELINE_QUERY"
DOCUMENT_SEARCH = "DOCUMENT_SEARCH"
COMPARE_CONTRAST = "COMPARE_CONTRAST"
EMAIL = "EMAIL"
PHOTOS = "PHOTOS"
SUMMARY = "SUMMARY"
# You can add more instructions and examples in the description
# or you can put it in the prompt in `messages=[...]`
class QuestionClassification(BaseModel):
"""
Predict the type of question that is being asked.
Here are some tips on how to predict the question type:
CONTACT: Searches for some contact information.
TIMELINE_QUERY: "When did something happen?
DOCUMENT_SEARCH: "Find me a document"
COMPARE_CONTRAST: "Compare and contrast two things"
EMAIL: "Find me an email, search for an email"
PHOTOS: "Find me a photo, search for a photo"
SUMMARY: "Summarize a large amount of data"
"""
# If you want only one classification, just change it to
# `classification: QuestionType` rather than `classifications: List[QuestionType]``
chain_of_thought: str = Field(
..., description="The chain of thought that led to the classification"
)
classification: List[QuestionType] = Field(
description=f"An accuracy and correct prediction predicted class of question. Only allowed types: {[t.value for t in QuestionType]}, should be used",
)
@field_validator("classification", mode="before")
def validate_classification(cls, v):
# sometimes the API returns a single value, just make sure it's a list
if not isinstance(v, list):
v = [v]
return v
@traceable(name="classify-question")
async def classify(data: str) -> QuestionClassification:
"""
Perform multi-label classification on the input text.
Change the prompt to fit your use case.
Args:
data (str): The input text to classify.
"""
async with sem: # some simple rate limiting
return data, await client.create(
model="gpt-4-turbo-preview",
response_model=QuestionClassification,
max_retries=2,
messages=[
{
"role": "user",
"content": f"Classify the following question: {data}",
},
],
)
async def main(questions: List[str]):
tasks = [classify(question) for question in questions]
for task in asyncio.as_completed(tasks):
question, label = await task
resp = {
"question": question,
"classification": [c.value for c in label.classification],
"chain_of_thought": label.chain_of_thought,
}
resps.append(resp)
return resps
if __name__ == "__main__":
import asyncio
questions = [
"What was that ai app that i saw on the news the other day?",
"Can you find the trainline booking email?",
"what did I do on Monday?",
"Tell me about todays meeting and how it relates to the email on Monday",
]
resp = asyncio.run(main(questions))
for r in resp:
print("q:", r["question"])
#> q: what did I do on Monday?
print("c:", r["classification"])
#> c: ['SUMMARY']
```
If you follow what we've done is wrapped the client and proceeded to quickly use asyncio to classify a list of questions. This is a simple example of how you can use LangSmith to enhance the OpenAI client. You can use LangSmith to monitor and observe the client, and use `instructor` to add additional functionality to the client.
To take a look at trace of this run check out this shareable [link](https://smith.langchain.com/public/eaae9f95-3779-4bbb-824d-97aa8a57a4e0/r).

View File

@@ -0,0 +1,342 @@
---
title: In-Memory Batch Processing for Serverless Applications
description: Learn how to use Instructor's in-memory batch processing feature for serverless deployments without disk I/O.
---
## See Also
- [Batch Processing](./batch_job_oai.md) - File-based batch processing
- [Bulk Classification](./bulk_classification.md) - Process multiple classifications
- [from_provider Guide](../concepts/from_provider.md#async-clients) - Async client setup
- [Cost Optimization](./batch_job_oai.md) - Reduce API costs with batch processing
# In-Memory Batch Processing for Serverless
This guide demonstrates how to use Instructor's in-memory batch processing feature, which is perfect for serverless deployments and applications that need to avoid disk I/O.
## Overview
In-memory batch processing allows you to create and submit batch requests without writing to disk, using BytesIO buffers instead of files. This is ideal for:
- **Serverless environments** (AWS Lambda, Google Cloud Functions, Azure Functions)
- **Containerized applications** with read-only file systems
- **Security-sensitive applications** that avoid temporary files
- **High-performance applications** that minimize I/O overhead
## Quick Start
```python
import time
from pydantic import BaseModel
from instructor.batch.processor import BatchProcessor
class User(BaseModel):
"""User model for extraction."""
name: str
age: int
email: str
def main():
# Initialize batch processor
processor = BatchProcessor("openai/gpt-5-nano", User)
# Sample messages for batch processing
messages_list = [
[
{"role": "system", "content": "Extract user information from the text."},
{
"role": "user",
"content": "John Doe is 25 years old and his email is john@example.com",
},
],
[
{"role": "system", "content": "Extract user information from the text."},
{
"role": "user",
"content": "Jane Smith, age 30, can be reached at jane.smith@company.com",
},
],
[
{"role": "system", "content": "Extract user information from the text."},
{
"role": "user",
"content": "Bob Wilson (bob.wilson@email.com) is 28 years old",
},
],
]
# Create batch in memory (no file_path specified)
batch_buffer = processor.create_batch_from_messages(
messages_list,
file_path=None, # This triggers in-memory mode
max_tokens=150,
temperature=0.1,
)
print(f"Created batch buffer: {type(batch_buffer)}")
print(f"Buffer size: {len(batch_buffer.getvalue())} bytes")
# Submit the batch using the in-memory buffer
batch_id = processor.submit_batch(
batch_buffer, metadata={"description": "In-memory batch example"}
)
print(f"Batch submitted successfully! Batch ID: {batch_id}")
# Poll for completion
print("Waiting for batch to complete...")
max_wait_time = 300 # 5 minutes max
start_time = time.time()
while time.time() - start_time < max_wait_time:
status = processor.get_batch_status(batch_id)
current_status = status.get("status", "unknown")
print(f"Current status: {current_status}")
if current_status in ["completed", "failed", "cancelled", "expired"]:
break
time.sleep(10)
# Retrieve and process results
if status.get("status") == "completed":
print("Batch completed! Retrieving results...")
results = processor.get_results(batch_id)
successful_results = [r for r in results if hasattr(r, "result")]
error_results = [r for r in results if hasattr(r, "error_message")]
print(f"Total results: {len(results)}")
print(f"Successful: {len(successful_results)}")
print(f"Errors: {len(error_results)}")
# Show successful extractions
if successful_results:
print("\nExtracted Users:")
for result in successful_results:
user = result.result
print(f" - {user.name}, {user.age} years old, {user.email}")
# Show any errors
if error_results:
print("\nErrors encountered:")
for error in error_results:
print(f" - {error.custom_id}: {error.error_message}")
if __name__ == "__main__":
main()
```
## File vs In-Memory Comparison
### Traditional File-Based Approach
```python
# File-based approach
processor = BatchProcessor("openai/gpt-5-nano", User)
# Creates file on disk
file_path = processor.create_batch_from_messages(
messages_list,
file_path="temp_batch.jsonl", # Specify file path
max_tokens=150,
temperature=0.1,
)
# Submit using file path
batch_id = processor.submit_batch(file_path)
# Remember to clean up
import os
if os.path.exists(file_path):
os.remove(file_path)
```
### New In-Memory Approach
```python
# In-memory approach
processor = BatchProcessor("openai/gpt-5-nano", User)
# Creates BytesIO buffer in memory
buffer = processor.create_batch_from_messages(
messages_list,
file_path=None, # No file path = in-memory
max_tokens=150,
temperature=0.1,
)
# Submit using buffer
batch_id = processor.submit_batch(buffer)
# No cleanup required - buffer is automatically garbage collected
```
## Benefits of In-Memory Processing
### ✅ Perfect for Serverless
```python
# AWS Lambda example
import json
def lambda_handler(event, context):
"""AWS Lambda function using in-memory batch processing."""
# Extract data from event
messages_list = event.get("messages", [])
# Process in memory - no disk I/O
processor = BatchProcessor("openai/gpt-5-nano", User)
buffer = processor.create_batch_from_messages(
messages_list,
file_path=None, # Essential for Lambda
)
batch_id = processor.submit_batch(buffer)
return {
'statusCode': 200,
'body': json.dumps(
{'batch_id': batch_id, 'message': 'Batch submitted successfully'}
),
}
```
### ✅ Memory Efficient
```python
# Check buffer size before submission
buffer = processor.create_batch_from_messages(messages_list, file_path=None)
print(f"Buffer size: {len(buffer.getvalue())} bytes")
print(f"Buffer type: {type(buffer)}")
# Buffer content is accessible
buffer.seek(0)
content_preview = buffer.read(200).decode("utf-8")
print(f"Preview: {content_preview}...")
# Reset for submission
buffer.seek(0)
batch_id = processor.submit_batch(buffer)
```
### ✅ Security Benefits
```python
# No temporary files on disk
# No file permissions to manage
# No cleanup required
# Buffer is automatically garbage collected
processor = BatchProcessor("openai/gpt-5-nano", User)
# This approach leaves no trace on the file system
buffer = processor.create_batch_from_messages(
sensitive_messages,
file_path=None, # Keeps everything in memory
)
batch_id = processor.submit_batch(buffer)
# When buffer goes out of scope, it's automatically cleaned up
```
## Error Handling
```python
try:
# Create batch buffer
buffer = processor.create_batch_from_messages(
messages_list,
file_path=None,
)
# Submit batch
batch_id = processor.submit_batch(buffer)
# Process results
results = processor.get_results(batch_id)
except Exception as e:
print(f"Error during batch processing: {e}")
#> Error during batch processing: name 'processor' is not defined
# No file cleanup needed with in-memory approach
```
## Provider Support
All providers support in-memory batch processing:
### OpenAI
```python
processor = BatchProcessor("openai/gpt-5-nano", User)
buffer = processor.create_batch_from_messages(messages_list, file_path=None)
batch_id = processor.submit_batch(buffer)
```
### Anthropic
```python
processor = BatchProcessor("anthropic/claude-3-5-sonnet-20241022", User)
buffer = processor.create_batch_from_messages(messages_list, file_path=None)
batch_id = processor.submit_batch(buffer)
```
### Google GenAI
```python
processor = BatchProcessor("google/gemini-2.5-flash", User)
buffer = processor.create_batch_from_messages(messages_list, file_path=None)
batch_id = processor.submit_batch(buffer)
```
## Best Practices
1. **Always set `file_path=None`** to enable in-memory mode
2. **Monitor buffer size** for large batches to avoid memory issues
3. **Use appropriate models** that support JSON schema (e.g., gpt-4o-mini)
4. **Handle errors gracefully** - no file cleanup needed
5. **Consider memory limits** in serverless environments
## Limitations
- **Memory usage**: Large batches may consume significant memory
- **No debugging files**: Can't inspect batch files for troubleshooting
- **Temporary storage**: Buffer contents are lost if not submitted immediately
## Troubleshooting
### Buffer Size Issues
```python
# Check buffer size before submission
buffer = processor.create_batch_from_messages(messages_list, file_path=None)
size_mb = len(buffer.getvalue()) / (1024 * 1024)
print(f"Buffer size: {size_mb:.2f} MB")
if size_mb > 100: # Adjust threshold as needed
print("Warning: Large buffer size, consider splitting batch")
```
### Memory Monitoring
```python
import psutil
import os
# Check memory usage
process = psutil.Process(os.getpid())
memory_before = process.memory_info().rss / 1024 / 1024 # MB
buffer = processor.create_batch_from_messages(messages_list, file_path=None)
memory_after = process.memory_info().rss / 1024 / 1024 # MB
print(f"Memory increase: {memory_after - memory_before:.2f} MB")
```
This in-memory approach makes Instructor's batch processing perfect for modern serverless and containerized applications while maintaining the same powerful API and provider support.

View File

@@ -0,0 +1,214 @@
---
title: Generating Synthetic Data with OpenAI's Batch API
description: Learn to use OpenAI's Batch API for large-scale synthetic data generation, focusing on question-answer pairs from the ms-marco dataset.
---
## See Also
- [In-Memory Batch Processing](./batch_in_memory.md) - Serverless batch processing without disk I/O
- [Bulk Classification](./bulk_classification.md) - Process multiple classifications efficiently
- [Cost Optimization](../examples/index.md#api-integration) - Reduce API costs
- [from_provider Guide](../concepts/from_provider.md#async-clients) - Async client setup
# Bulk Generation of Synthetic Data
This tutorial shows how to use `instructor` to generate large quantities of synthetic data at scale using Open AI's new Batch API. In this example, we'll be generating synthetic questions using the `ms-marco` dataset to evaluate RAG retrieval.
??? tips "Why use the batch API?"
There are a few reasons why you might want to use the Batch API
1. Batch Jobs are 50% cheaper than running an inference job on demand ( see Open AI's pricing page [here](https://openai.com/api/pricing/) )
2. Batch Jobs have higher rate limits than normal api calls
3. Batch Jobs support both normal models **and fine-tuned models**
This makes them perfect for non time-sensitive tasks that involve large quantities of data.
## Getting Started
Let's first see how we can generate a Question and Answer Pair using Instructor with a normal OpenAI function call.
```python
from pydantic import BaseModel, Field
client = from_openai(OpenAI())
class QuestionAnswerPair(BaseModel):
"""
This model represents a pair of a question generated from a text chunk, its corresponding answer,
and the chain of thought leading to the answer. The chain of thought provides insight into how the answer
was derived from the question.
"""
chain_of_thought: str = Field(
description="The reasoning process leading to the answer."
)
question: str = Field(description="The generated question from the text chunk.")
answer: str = Field(description="The answer to the generated question.")
def generate_question(chunk: str) -> QuestionAnswerPair:
return client.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a world class AI that excels at generating hypothethical search queries. You're about to be given a text snippet and asked to generate a search query which is specific to the specific text chunk that you'll be given. Make sure to use information from the text chunk.",
},
{"role": "user", "content": f"Here is the text chunk: {chunk}"},
],
response_model=QuestionAnswerPair,
)
text_chunk = """
The Reserve Bank of Australia (RBA) came into being on 14 January 1960 as Australia 's central bank and banknote issuing authority, when the Reserve Bank Act 1959 removed the central banking functions from the Commonwealth Bank. The assets of the bank include the gold and foreign exchange reserves of Australia, which is estimated to have a net worth of A$101 billion. Nearly 94% of the RBA's employees work at its headquarters in Sydney, New South Wales and at the Business Resumption Site.
"""
print(generate_question(text_chunk).model_dump_json(indent=2))
"""
{
"chain_of_thought": "The text discusses the formation of the Reserve Bank of Australia (RBA) and provides key details about its establishment date, the removal of central banking functions from the Commonwealth Bank, its asset worth, and its employee distribution. By focusing on these details, a search query can be framed around the establishment date and purpose of the RBA.",
"question": "When was the Reserve Bank of Australia established and what are its main functions?",
"answer": "The Reserve Bank of Australia was established on 14 January 1960 as Australia's central bank and banknote issuing authority."
}
"""
```
As the number of chunks we'd like to generate these synthetic questions for increases, the cost will grow proportionally.
Let's see how we can use the `BatchJob` object to create a `.jsonl` file which is compatible with the Batch API.
```python hl_lines="9-18 35-40"
from datasets import load_dataset
from instructor.batch import BatchJob
from pydantic import BaseModel, Field
from datasets import load_dataset
dataset = load_dataset("ms_marco", "v1.1", split="train", streaming=True).take(200)
def get_messages(dataset): # (1)!
for row in dataset:
for passage in row['passages']['passage_text']:
yield [
{
"role": "system",
"content": "You are a world class AI that excels at generating hypothethical search queries. You're about to be given a text snippet and asked to generate a search query which is specific to the specific text chunk that you'll be given. Make sure to use information from the text chunk.",
},
{"role": "user", "content": f"Here is the text chunk: {passage}"},
]
class QuestionAnswerPair(BaseModel):
"""
This model represents a pair of a question generated from a text chunk, its corresponding answer,
and the chain of thought leading to the answer. The chain of thought provides insight into how the answer
was derived from the question.
"""
chain_of_thought: str = Field(
description="The reasoning process leading to the answer."
)
question: str = Field(description="The generated question from the text chunk.")
answer: str = Field(description="The answer to the generated question.")
BatchJob.create_from_messages(
messages_batch=get_messages(dataset),
model="gpt-4o",
file_path="./test.jsonl",
response_model=QuestionAnswerPair,
) # (2)!
```
1. We first define a generator which generates a list of messages which we would have made in a normal `openai` api call
2. We then use the `create_from_messages` class method to specify the model and response_model that we want. `instructor` will handle the generation of the openai schema behind the scenes as well as write the output to the file path you specify
Once we've got this new `.jsonl` file, we can then use the new `instructor` cli's `batch` command to create a new batch job.
```bash
> % ls -a | grep test.jsonl
test.jsonl
> % instructor batch create-from-file --file-path test.jsonl
```
This will create a table like what you see below. In my case, my batch job took around 6 minutes to complete and cost me $2.72 to run.
| Batch ID | Created At | Status | Failed | Completed | Total |
| ------------------------------ | ------------------- | ----------- | ------ | --------- | ----- |
| batch_Z8XUudoweH43R9c4sr4wRYub | 2024-07-16 12:45:22 | in_progress | 0 | 483 | 1627 |
Once our batch job is complete, the status will change to `completed`.
??? "Cancelling A Job"
If you'd like to cancel a batch job midway, you can do so too with the instructor `batch` cli command
```bash
instructor batch cancel --batch-id <batch id here>
```
We can then download the file generated by the batch job using the cli command
```bash
instructor batch download-file --download-file-path output.jsonl --batch-id batch_Z8XUudoweH43R9c4sr4wRYub
```
This will then create a `.jsonl` file with the generated content at the path that you specify.
## Parsing the generated response
We can then parse the generated response by using the `.parse_from_file` command provided by the `BatchJob` class.
```python hl_lines="19-21"
from instructor.batch import BatchJob
from pydantic import BaseModel, Field
# <%hide%>
with open("./output.jsonl", "w") as f:
f.write('')
# <%hide%>
class QuestionAnswerPair(BaseModel):
"""
This model represents a pair of a question generated from a text chunk, its corresponding answer,
and the chain of thought leading to the answer. The chain of thought provides insight into how the answer
was derived from the question.
"""
chain_of_thought: str = Field(
description="The reasoning process leading to the answer."
)
question: str = Field(description="The generated question from the text chunk.")
answer: str = Field(description="The answer to the generated question.")
parsed, unparsed = BatchJob.parse_from_file( # (1)!
file_path="./output.jsonl", response_model=QuestionAnswerPair
)
print(len(parsed))
#> 0
print(len(unparsed))
#> 0
# <%hide%>
import os
if os.path.exists("./output.jsonl"):
os.remove("./output.jsonl")
# <%hide%>
```
1. We can then use a generic `Pydantic` schema to parse the generated function calls back
This will then return a list of two elements
- `parsed` is a list of responses that have been succesfully parsed into the `QuestionAnswerPair` Base Model class
- `unparsed` is a second list which contains responses which were not able to be parsed into the `QuestionAnswerPair` Base Model class

View File

@@ -0,0 +1,98 @@
---
title: Building Knowledge Graphs from Text
description: Learn to construct knowledge graphs from textual data using OpenAI's API and Pydantic in this comprehensive tutorial.
---
## See Also
- [Knowledge Graph](./knowledge_graph.md) - Visualize knowledge graphs
- [Entity Resolution](./entity_resolution.md) - Identify and resolve entities
- [Document Segmentation](./document_segmentation.md) - Break down documents for analysis
- [Nested Structures](../learning/patterns/nested_structure.md) - Complex hierarchical models
# Building Knowledge Graphs from Textual Data
In this tutorial, we will explore the process of constructing knowledge graphs from textual data using OpenAI's API and Pydantic. This approach is crucial for efficiently automating the extraction of structured information from unstructured text.
```python
from typing import List
from pydantic import BaseModel, Field
import instructor
class Node(BaseModel):
id: int
label: str
color: str = "blue" # Default color set to blue
class Edge(BaseModel):
source: int
target: int
label: str
color: str = "black" # Default color for edges
class KnowledgeGraph(BaseModel):
nodes: List[Node] = Field(default_factory=list)
edges: List[Edge] = Field(default_factory=list)
# Patch the OpenAI client to add response_model support
client = instructor.from_provider("openai/gpt-5-nano")
def generate_graph(input_text: str) -> KnowledgeGraph:
"""Generates a knowledge graph from the input text."""
return client.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": f"Help me understand the following by describing it as a detailed knowledge graph: {input_text}",
}
],
response_model=KnowledgeGraph,
)
if __name__ == "__main__":
input_text = "Jason is Sarah's friend and he is a doctor"
graph = generate_graph(input_text)
print(graph.model_dump_json(indent=2))
"""
{
"nodes": [
{
"id": 1,
"label": "Jason",
"color": "blue"
},
{
"id": 2,
"label": "Sarah",
"color": "blue"
},
{
"id": 3,
"label": "Doctor",
"color": "blue"
}
],
"edges": [
{
"source": 1,
"target": 2,
"label": "is a friend of",
"color": "black"
},
{
"source": 1,
"target": 3,
"label": "is a",
"color": "black"
}
]
}
"""
```

View File

@@ -0,0 +1,533 @@
---
title: User-Provided Tag Classification Tutorial
description: Learn to classify user-provided tags effectively using async functions and FastAPI for parallel processing.
---
## See Also
- [Batch Processing](./batch_job_oai.md) - Process large datasets efficiently
- [Classification Examples](./classification.md) - More classification patterns
- [FastAPI Integration](../integrations/index.md) - Building APIs with Instructor
- [from_provider Guide](../concepts/from_provider.md#async-clients) - Async client setup
# Bulk Classification from User-Provided Tags.
This tutorial shows how to do classification from user provided tags. This is valuable when you want to provide services that allow users to do some kind of classification.
!!! tips "Motivation"
Imagine allowing the user to upload documents as part of a RAG application. Oftentimes, we might want to allow the user to specify an existing set of tags, give descriptions, and do the classification for them.
## Defining the Structures
One of the easy things to do is to allow users to define a set of tags in some kind of schema and save that in a database. Here's an example of a schema that we might use:
| tag_id | name | instructions |
| ------ | -------- | -------------------- |
| 0 | personal | Personal information |
| 1 | phone | Phone number |
| 2 | email | Email address |
| 3 | address | Address |
| 4 | Other | Other information |
1. **tag_id** - The unique identifier for the tag.
2. **name** - The name of the tag.
3. **instructions** - A description of the tag, which can be used as a prompt to describe the tag.
## Implementing the Classification
In order to do this we'll do a couple of things:
0. We'll use the `instructor` library with async client support.
1. Implement a `Tag` model that will be used to validate the tags from the context. (This will allow us to avoid hallucinating tags that are not in the context.)
2. Helper models for the request and response.
3. An async function to do the classification.
4. A main function to run the classification using the `asyncio.gather` function to run the classification in parallel.
If you want to learn more about how to do bad computations, check out our post on AsyncIO [here](../blog/posts/learn-async.md).
```python
import instructor
client = instructor.from_provider("openai/gpt-4o", async_client=True)
```
First, we'll need to import all of our Pydantic and instructor code and use the AsyncOpenAI client. Then, we'll define the tag model along with the tag instructions to provide input and output.
This is very helpful because once we use something like FastAPI to create endpoints, the Pydantic functions will serve as multiple tools:
1. A description for the developer
2. Type hints for the IDE
3. OpenAPI documentation for the FastAPI endpoint
4. Schema and Response Model for the language model.
```python
from typing import List
from pydantic import BaseModel, ValidationInfo, model_validator
class Tag(BaseModel):
id: int
name: str
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
class TagWithInstructions(Tag):
instructions: str
class TagRequest(BaseModel):
texts: List[str]
tags: List[TagWithInstructions]
class TagResponse(BaseModel):
texts: List[str]
predictions: List[Tag]
```
Let's delve deeper into what the `validate_ids` function does. Notice that its purpose is to extract tags from the context and ensure that each ID and name exists in the set of tags. This approach helps minimize hallucinations. If we mistakenly identify either the ID or the tag, an error will be thrown, and the instructor will prompt the language model to retry until the correct item is successfully extracted.
```python
from pydantic import model_validator, ValidationInfo
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
```
Now, let's implement the function to do the classification. This function will take a single text and a list of tags and return the predicted tag.
```python
# <%hide%>
from typing import List
from pydantic import BaseModel, ValidationInfo, model_validator
class Tag(BaseModel):
id: int
name: str
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
class TagWithInstructions(Tag):
instructions: str
class TagRequest(BaseModel):
texts: List[str]
tags: List[TagWithInstructions]
class TagResponse(BaseModel):
texts: List[str]
predictions: List[Tag]
# <%hide%>
async def tag_single_request(text: str, tags: List[Tag]) -> Tag:
allowed_tags = [(tag.id, tag.name) for tag in tags]
allowed_tags_str = ", ".join([f"`{tag}`" for tag in allowed_tags])
return await client.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a world-class text tagging system.",
},
{"role": "user", "content": f"Describe the following text: `{text}`"},
{
"role": "user",
"content": f"Here are the allowed tags: {allowed_tags_str}",
},
],
response_model=Tag, # Minimizes the hallucination of tags that are not in the allowed tags.
context={"tags": tags},
)
async def tag_request(request: TagRequest) -> TagResponse:
predictions = await asyncio.gather(
*[tag_single_request(text, request.tags) for text in request.texts]
)
return TagResponse(
texts=request.texts,
predictions=predictions,
)
```
Notice that we first define a single async function that makes a prediction of a tag, and we pass it into the validation context in order to minimize hallucinations.
Finally, we'll implement the main function to run the classification using the `asyncio.gather` function to run the classification in parallel.
```python
import asyncio
# <%hide%>
from typing import List
from pydantic import BaseModel, ValidationInfo, model_validator
import instructor
import asyncio
client = instructor.from_provider("openai/gpt-4o-mini", async_client=True)
class Tag(BaseModel):
id: int
name: str
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
class TagWithInstructions(Tag):
instructions: str
class TagRequest(BaseModel):
texts: List[str]
tags: List[TagWithInstructions]
class TagResponse(BaseModel):
texts: List[str]
predictions: List[Tag]
async def tag_single_request(text: str, tags: List[Tag]) -> Tag:
allowed_tags = [(tag.id, tag.name) for tag in tags]
allowed_tags_str = ", ".join([f"`{tag}`" for tag in allowed_tags])
return await client.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a world-class text tagging system.",
},
{"role": "user", "content": f"Describe the following text: `{text}`"},
{
"role": "user",
"content": f"Here are the allowed tags: {allowed_tags_str}",
},
],
response_model=Tag, # Minimizes the hallucination of tags that are not in the allowed tags.
context={"tags": tags},
)
async def tag_request(request: TagRequest) -> TagResponse:
predictions = await asyncio.gather(
*[tag_single_request(text, request.tags) for text in request.texts]
)
return TagResponse(
texts=request.texts,
predictions=predictions,
)
# <%hide%>
tags = [
TagWithInstructions(id=0, name="personal", instructions="Personal information"),
TagWithInstructions(id=1, name="phone", instructions="Phone number"),
TagWithInstructions(id=2, name="email", instructions="Email address"),
TagWithInstructions(id=3, name="address", instructions="Address"),
TagWithInstructions(id=4, name="Other", instructions="Other information"),
]
# Texts will be a range of different questions.
# Such as "How much does it cost?", "What is your privacy policy?", etc.
texts = [
"What is your phone number?",
"What is your email address?",
"What is your address?",
"What is your privacy policy?",
]
# The request will contain the texts and the tags.
request = TagRequest(texts=texts, tags=tags)
# The response will contain the texts, the predicted tags, and the confidence.
response = asyncio.run(tag_request(request))
print(response.model_dump_json(indent=2))
"""
{
"texts": [
"What is your phone number?",
"What is your email address?",
"What is your address?",
"What is your privacy policy?"
],
"predictions": [
{
"id": 1,
"name": "phone"
},
{
"id": 2,
"name": "email"
},
{
"id": 3,
"name": "address"
},
{
"id": 4,
"name": "Other"
}
]
}
"""
```
Which would result in:
```json
{
"texts": [
"What is your phone number?",
"What is your email address?",
"What is your address?",
"What is your privacy policy?"
],
"predictions": [
{
"id": 1,
"name": "phone"
},
{
"id": 2,
"name": "email"
},
{
"id": 3,
"name": "address"
},
{
"id": 4,
"name": "Other"
}
]
}
```
## What happens in production?
If we were to use this in production, we might expect to have some kind of fast API endpoint.
```python
from fastapi import FastAPI
app = FastAPI()
# <%hide%>
from typing import List
from pydantic import BaseModel, ValidationInfo, model_validator
class Tag(BaseModel):
id: int
name: str
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
class TagWithInstructions(Tag):
instructions: str
class TagRequest(BaseModel):
texts: List[str]
tags: List[TagWithInstructions]
class TagResponse(BaseModel):
texts: List[str]
predictions: List[Tag]
# <%hide%>
@app.post("/tag", response_model=TagResponse)
async def tag(request: TagRequest) -> TagResponse:
return await tag_request(request)
```
Since everything is already annotated with Pydantic, this code is very simple to write!
!!! warning "Where do tags come from?"
I just want to call out that here you can also imagine the tag spec IDs and names and instructions for example could come from a database or somewhere else. I'll leave this as an exercise to the reader, but I hope this gives us a clear understanding of how we can do something like user-defined classification.
## Improving the Model
There's a couple things we could do to make this system a little bit more robust.
1. Use confidence score:
```python
# <%hide%>
from typing import List
from pydantic import BaseModel, ValidationInfo, model_validator, Field
class Tag(BaseModel):
id: int
name: str
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
# <%hide%>
class TagWithConfidence(Tag):
confidence: float = Field(
...,
ge=0,
le=1,
description="The confidence of the prediction, 0 is low, 1 is high",
)
```
2. Use multiclass classification:
Notice in the example we use Iterable[Tag] vs Tag. This is because we might want to use a multiclass classification model that returns multiple tag!
```python
import instructor
import instructor
import asyncio
from typing import Iterable
client = instructor.from_openai(
openai.AsyncOpenAI(),
)
# <%hide%>
from typing import List
from pydantic import BaseModel, ValidationInfo, model_validator
class Tag(BaseModel):
id: int
name: str
@model_validator(mode="after")
def validate_ids(self, info: ValidationInfo):
context = info.context
if context:
tags: List[Tag] = context.get("tags")
assert self.id in {
tag.id for tag in tags
}, f"Tag ID {self.id} not found in context"
assert self.name in {
tag.name for tag in tags
}, f"Tag name {self.name} not found in context"
return self
# <%hide%>
tags = [
Tag(id=0, name="personal"),
Tag(id=1, name="phone"),
Tag(id=2, name="email"),
Tag(id=3, name="address"),
Tag(id=4, name="Other"),
]
# Texts will be a range of different questions.
# Such as "How much does it cost?", "What is your privacy policy?", etc.
text = "What is your phone number?"
async def get_tags(text: List[str], tags: List[Tag]) -> List[Tag]:
allowed_tags = [(tag.id, tag.name) for tag in tags]
allowed_tags_str = ", ".join([f"`{tag}`" for tag in allowed_tags])
return await client.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a world-class text tagging system.",
},
{"role": "user", "content": f"Describe the following text: `{text}`"},
{
"role": "user",
"content": f"Here are the allowed tags: {allowed_tags_str}",
},
],
response_model=Iterable[Tag],
context={"tags": tags},
)
tag_results = asyncio.run(get_tags(text, tags))
for tag in tag_results:
print(tag)
#> id=1 name='phone'

View File

@@ -0,0 +1,331 @@
---
title: Text Classification with OpenAI and Pydantic
description: Learn to implement single-label and multi-label text classification using OpenAI API and Pydantic models in Python.
---
# Text Classification using OpenAI and Pydantic
This tutorial showcases how to implement text classification tasks-specifically, single-label and multi-label classifications-using the OpenAI API and Pydantic models. For complete examples, check out our [single classification](./bulk_classification.md) and [multi-label classification](./bulk_classification.md) examples in the cookbook.
!!! tips "Motivation"
Text classification is a common problem in many NLP applications, such as spam detection or support ticket categorization. The goal is to provide a systematic way to handle these cases using OpenAI's GPT models in combination with Python data structures.
## Single-Label Classification
### Defining the Structures
For single-label classification, we define a Pydantic model with a [Literal](../concepts/prompting.md#literals) field for the possible labels.
!!! note "Literals vs Enums"
We prefer using `Literal` types over `enum` for classification labels. Literals provide better type checking and are more straightforward to use with Pydantic models.
!!! important "Few-Shot Examples"
Including few-shot examples in the model's docstring is crucial for improving the model's classification accuracy. These examples guide the AI in understanding the task and expected outputs.
If you want to learn more prompting tips check out our [prompting guide](../prompting/index.md)
!!! note "Chain of Thought"
Using [Chain of Thought](../concepts/prompting.md#chain-of-thought) has been shown to improve the quality of the predictions by ~ 10%
```python
from pydantic import BaseModel, Field
from typing import Literal
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
class ClassificationResponse(BaseModel):
"""
A few-shot example of text classification:
Examples:
- "Buy cheap watches now!": SPAM
- "Meeting at 3 PM in the conference room": NOT_SPAM
- "You've won a free iPhone! Click here": SPAM
- "Can you pick up some milk on your way home?": NOT_SPAM
- "Increase your followers by 10000 overnight!": SPAM
"""
chain_of_thought: str = Field(
...,
description="The chain of thought that led to the prediction.",
)
label: Literal["SPAM", "NOT_SPAM"] = Field(
...,
description="The predicted class label.",
)
```
### Classifying Text
The function **`classify`** will perform the single-label classification.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import Literal
import instructor
class ClassificationResponse(BaseModel):
"""
A few-shot example of text classification:
Examples:
- "Buy cheap watches now!": SPAM
- "Meeting at 3 PM in the conference room": NOT_SPAM
- "You've won a free iPhone! Click here": SPAM
- "Can you pick up some milk on your way home?": NOT_SPAM
- "Increase your followers by 10000 overnight!": SPAM
"""
chain_of_thought: str = Field(
...,
description="The chain of thought that led to the prediction.",
)
label: Literal["SPAM", "NOT_SPAM"] = Field(
...,
description="The predicted class label.",
)
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
# <%hide%>
def classify(data: str) -> ClassificationResponse:
"""Perform single-label classification on the input text."""
return client.create(
model="gpt-4o-mini",
response_model=ClassificationResponse,
messages=[
{
"role": "user",
"content": f"Classify the following text: <text>{data}</text>",
},
],
)
```
### Testing and Evaluation
Let's run examples to see if it correctly identifies spam and non-spam messages.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import Literal
import instructor
client = instructor.from_provider("openai/gpt-5-nano")
class ClassificationResponse(BaseModel):
"""
A few-shot example of text classification:
Examples:
- "Buy cheap watches now!": SPAM
- "Meeting at 3 PM in the conference room": NOT_SPAM
- "You've won a free iPhone! Click here": SPAM
- "Can you pick up some milk on your way home?": NOT_SPAM
- "Increase your followers by 10000 overnight!": SPAM
"""
chain_of_thought: str = Field(
...,
description="The chain of thought that led to the prediction.",
)
label: Literal["SPAM", "NOT_SPAM"] = Field(
...,
description="The predicted class label.",
)
def classify(data: str) -> ClassificationResponse:
"""Perform single-label classification on the input text."""
return client.create(
model="gpt-4o-mini",
response_model=ClassificationResponse,
messages=[
{
"role": "user",
"content": f"Classify the following text: <text>{data}</text>",
},
],
)
# <%hide%>
if __name__ == "__main__":
for text, label in [
("Hey Jason! You're awesome", "NOT_SPAM"),
("I am a nigerian prince and I need your help.", "SPAM"),
]:
prediction = classify(text)
assert prediction.label == label
print(f"Text: {text}, Predicted Label: {prediction.label}")
#> Text: Hey Jason! You're awesome, Predicted Label: NOT_SPAM
#> Text: I am a nigerian prince and I need your help., Predicted Label: SPAM
```
## Multi-Label Classification
### Defining the Structures
For multi-label classification, we'll update our approach to use Literals instead of enums, and include few-shot examples in the model's docstring.
```python
from typing import List
from pydantic import BaseModel, Field
from typing import Literal
class MultiClassPrediction(BaseModel):
"""
Class for a multi-class label prediction.
Examples:
- "My account is locked": ["TECH_ISSUE"]
- "I can't access my billing info": ["TECH_ISSUE", "BILLING"]
- "When do you close for holidays?": ["GENERAL_QUERY"]
- "My payment didn't go through and now I can't log in": ["BILLING", "TECH_ISSUE"]
"""
chain_of_thought: str = Field(
...,
description="The chain of thought that led to the prediction.",
)
class_labels: List[Literal["TECH_ISSUE", "BILLING", "GENERAL_QUERY"]] = Field(
...,
description="The predicted class labels for the support ticket.",
)
```
### Classifying Text
The function **`multi_classify`** is responsible for multi-label classification.
```python
# <%hide%>
from typing import List
from pydantic import BaseModel, Field
from typing import Literal
class MultiClassPrediction(BaseModel):
"""
Class for a multi-class label prediction.
Examples:
- "My account is locked": ["TECH_ISSUE"]
- "I can't access my billing info": ["TECH_ISSUE", "BILLING"]
- "When do you close for holidays?": ["GENERAL_QUERY"]
- "My payment didn't go through and now I can't log in": ["BILLING", "TECH_ISSUE"]
"""
chain_of_thought: str = Field(
...,
description="The chain of thought that led to the prediction.",
)
class_labels: List[Literal["TECH_ISSUE", "BILLING", "GENERAL_QUERY"]] = Field(
...,
description="The predicted class labels for the support ticket.",
)
# <%hide%>
import instructor
client = instructor.from_provider("openai/gpt-5-nano")
def multi_classify(data: str) -> MultiClassPrediction:
"""Perform multi-label classification on the input text."""
return client.create(
model="gpt-4o-mini",
response_model=MultiClassPrediction,
messages=[
{
"role": "user",
"content": f"Classify the following support ticket: <ticket>{data}</ticket>",
},
],
)
```
### Testing and Evaluation
Finally, we test the multi-label classification function using a sample support ticket.
```python
# <%hide%>
from typing import List
from pydantic import BaseModel, Field
from typing import Literal
import instructor
class MultiClassPrediction(BaseModel):
"""
Class for a multi-class label prediction.
Examples:
- "My account is locked": ["TECH_ISSUE"]
- "I can't access my billing info": ["TECH_ISSUE", "BILLING"]
- "When do you close for holidays?": ["GENERAL_QUERY"]
- "My payment didn't go through and now I can't log in": ["BILLING", "TECH_ISSUE"]
"""
chain_of_thought: str = Field(
...,
description="The chain of thought that led to the prediction.",
)
class_labels: List[Literal["TECH_ISSUE", "BILLING", "GENERAL_QUERY"]] = Field(
...,
description="The predicted class labels for the support ticket.",
)
client = instructor.from_provider("openai/gpt-5-nano")
def multi_classify(data: str) -> MultiClassPrediction:
"""Perform multi-label classification on the input text."""
return client.create(
model="gpt-4o-mini",
response_model=MultiClassPrediction,
messages=[
{
"role": "user",
"content": f"Classify the following support ticket: <ticket>{data}</ticket>",
},
],
)
# <%hide%>
# Test multi-label classification
ticket = "My account is locked and I can't access my billing info."
prediction = multi_classify(ticket)
assert "TECH_ISSUE" in prediction.class_labels
assert "BILLING" in prediction.class_labels
print(f"Ticket: {ticket}")
#> Ticket: My account is locked and I can't access my billing info.
print(f"Predicted Labels: {prediction.class_labels}")
#> Predicted Labels: ['TECH_ISSUE', 'BILLING']
```
By using Literals and including few-shot examples, we've improved both the single-label and multi-label classification implementations. These changes enhance type safety and provide better guidance for the AI model, potentially leading to more accurate classifications.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,248 @@
---
title: "Document Segmentation with LLMs: A Comprehensive Guide"
description: Learn effective document segmentation techniques using Cohere's LLM, enhancing comprehension of complex texts.
---
## See Also
- [Knowledge Graph](./knowledge_graph.md) - Build knowledge graphs from documents
- [Entity Resolution](./entity_resolution.md) - Identify and disambiguate entities
- [List Extraction](../learning/patterns/list_extraction.md) - Extract multiple objects
- [Nested Structures](../learning/patterns/nested_structure.md) - Complex hierarchical models
# Document Segmentation
In this guide, we demonstrate how to do document segmentation using structured output from an LLM. We'll be using [command-a](https://docs.cohere.com/docs/command-a) - one of Cohere's latest LLMs with 256k context length and testing the approach on an article explaining the Transformer architecture. Same approach to document segmentation can be applied to any other domain where we need to break down a complex long document into smaller chunks.
!!! tips "Motivation"
Sometimes we need a way to split the document into meaningful parts that center around a single key concept/idea. Simple length-based / rule-based text-splitters are not reliable enough. Consider the cases where documents contain code snippets or math equations - we don't want to split those on `'\n\n'` or have to write extensive rules for different types of documents. It turns out that LLMs with sufficiently long context length are well suited for this task.
## Defining the Data Structures
First, we need to define a **`Section`** class for each of the document's segments. **`StructuredDocument`** class will then encapsulate a list of these sections.
Note that in order to avoid LLM regenerating the content of each section, we can simply enumerate each line of the input document and then ask LLM to segment it by providing start-end line numbers for each section.
```python
from pydantic import BaseModel, Field
from typing import List
class Section(BaseModel):
title: str = Field(description="main topic of this section of the document")
start_index: int = Field(description="line number where the section begins")
end_index: int = Field(description="line number where the section ends")
class StructuredDocument(BaseModel):
"""obtains meaningful sections, each centered around a single concept/topic"""
sections: List[Section] = Field(description="a list of sections of the document")
```
## Document Preprocessing
Preprocess the input `document` by prepending each line with its number.
```python
def doc_with_lines(document):
document_lines = document.split("\n")
document_with_line_numbers = ""
line2text = {}
for i, line in enumerate(document_lines):
document_with_line_numbers += f"[{i}] {line}\n"
line2text[i] = line
return document_with_line_numbers, line2text
```
## Segmentation
Next use a Cohere client to extract `StructuredDocument` from the preprocessed doc.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import List
class Section(BaseModel):
title: str = Field(description="main topic of this section of the document")
start_index: int = Field(description="line number where the section begins")
end_index: int = Field(description="line number where the section ends")
class StructuredDocument(BaseModel):
"""obtains meaningful sections, each centered around a single concept/topic"""
sections: List[Section] = Field(description="a list of sections of the document")
# <%hide%>
import instructor
# Apply the patch to the cohere client
# enables response_model keyword
client = instructor.from_provider("cohere/command-r-plus")
system_prompt = f"""\
You are a world class educator working on organizing your lecture notes.
Read the document below and extract a StructuredDocument object from it where each section of the document is centered around a single concept/topic that can be taught in one lesson.
Each line of the document is marked with its line number in square brackets (e.g. [1], [2], [3], etc). Use the line numbers to indicate section start and end.
"""
def get_structured_document(document_with_line_numbers) -> StructuredDocument:
return client.create(
model="command-a-03-2025",
response_model=StructuredDocument,
messages=[
{
"role": "system",
"content": system_prompt,
},
{
"role": "user",
"content": document_with_line_numbers,
},
],
) # type: ignore
```
Next, we need to get back the section text based on the start/end indices and our `line2text` dict from the preprocessing step.
```python
def get_sections_text(structured_doc, line2text):
segments = []
for s in structured_doc.sections:
contents = []
for line_id in range(s.start_index, s.end_index):
contents.append(line2text.get(line_id, ''))
segments.append(
{
"title": s.title,
"content": "\n".join(contents),
"start": s.start_index,
"end": s.end_index,
}
)
return segments
```
## Example
Here's an example of using these classes and functions to segment a tutorial on Transformers from [Sebastian Raschka](https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html). We can use `trafilatura` package to scrape the web page content of the article.
```python
from trafilatura import fetch_url, extract
# <%hide%>
import instructor
from pydantic import BaseModel, Field
from typing import List
def doc_with_lines(document):
document_lines = document.split("\n")
document_with_line_numbers = ""
line2text = {}
for i, line in enumerate(document_lines):
document_with_line_numbers += f"[{i}] {line}\n"
line2text[i] = line
return document_with_line_numbers, line2text
client = instructor.from_provider("cohere/command-r-plus")
system_prompt = f"""\
You are a world class educator working on organizing your lecture notes.
Read the document below and extract a StructuredDocument object from it where each section of the document is centered around a single concept/topic that can be taught in one lesson.
Each line of the document is marked with its line number in square brackets (e.g. [1], [2], [3], etc). Use the line numbers to indicate section start and end.
"""
class Section(BaseModel):
title: str = Field(description="main topic of this section of the document")
start_index: int = Field(description="line number where the section begins")
end_index: int = Field(description="line number where the section ends")
class StructuredDocument(BaseModel):
"""obtains meaningful sections, each centered around a single concept/topic"""
sections: List[Section] = Field(description="a list of sections of the document")
def get_structured_document(document_with_line_numbers) -> StructuredDocument:
return client.create(
model="command-a-03-2025",
response_model=StructuredDocument,
messages=[
{
"role": "system",
"content": system_prompt,
},
{
"role": "user",
"content": document_with_line_numbers,
},
],
) # type: ignore
def get_sections_text(structured_doc, line2text):
segments = []
for s in structured_doc.sections:
contents = []
for line_id in range(s.start_index, s.end_index):
contents.append(line2text.get(line_id, ''))
segments.append(
{
"title": s.title,
"content": "\n".join(contents),
"start": s.start_index,
"end": s.end_index,
}
)
return segments
# <%hide%>
url = 'https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html'
downloaded = fetch_url(url)
document = extract(downloaded)
document_with_line_numbers, line2text = doc_with_lines(document)
structured_doc = get_structured_document(document_with_line_numbers)
segments = get_sections_text(structured_doc, line2text)
```
```
print(segments[5]['title'])
"""
Introduction to Multi-Head Attention
"""
print(segments[5]['content'])
"""
Multi-Head Attention
In the very first figure, at the top of this article, we saw that transformers use a module called multi-head attention. How does that relate to the self-attention mechanism (scaled-dot product attention) we walked through above?
In the scaled dot-product attention, the input sequence was transformed using three matrices representing the query, key, and value. These three matrices can be considered as a single attention head in the context of multi-head attention. The figure below summarizes this single attention head we covered previously:
As its name implies, multi-head attention involves multiple such heads, each consisting of query, key, and value matrices. This concept is similar to the use of multiple kernels in convolutional neural networks.
To illustrate this in code, suppose we have 3 attention heads, so we now extend the \(d' \times d\) dimensional weight matrices so \(3 \times d' \times d\):
In:
h = 3
multihead_W_query = torch.nn.Parameter(torch.rand(h, d_q, d))
multihead_W_key = torch.nn.Parameter(torch.rand(h, d_k, d))
multihead_W_value = torch.nn.Parameter(torch.rand(h, d_v, d))
Consequently, each query element is now \(3 \times d_q\) dimensional, where \(d_q=24\) (here, lets keep the focus on the 3rd element corresponding to index position 2):
In:
multihead_query_2 = multihead_W_query.matmul(x_2)
print(multihead_query_2.shape)
Out:
torch.Size([3, 24])
"""
```

View File

@@ -0,0 +1,316 @@
---
title: Entity Resolution and Visualization for Legal Documents
description: Learn how to extract, resolve, and visualize entities from legal contracts for better understanding and analysis.
---
## See Also
- [Knowledge Graph](./knowledge_graph.md) - Build knowledge graphs from entities
- [Building Knowledge Graphs](./building_knowledge_graphs.md) - Advanced graph construction
- [Document Segmentation](./document_segmentation.md) - Break down documents for analysis
- [Response Models](../concepts/models.md) - Working with complex data structures
# Entity Resolution and Visualization for Legal Documents
In this guide, we demonstrate how to extract and resolve entities from a sample legal contract. Then, we visualize these entities and their dependencies as an entity graph. This approach can be invaluable for legal tech applications, aiding in the understanding of complex documents.
!!! tips "Motivation"
Legal contracts are full of intricate details and interconnected clauses. Automatically extracting and visualizing these elements can make it easier to understand the document's overall structure and terms.
## Defining the Data Structures
The **`Entity`** and **`Property`** classes model extracted entities and their attributes. **`DocumentExtraction`** encapsulates a list of these entities.
```python
from pydantic import BaseModel, Field
from typing import List
class Property(BaseModel):
key: str
value: str
resolved_absolute_value: str
class Entity(BaseModel):
id: int = Field(
...,
description="Unique identifier for the entity, used for deduplication, design a scheme allows multiple entities",
)
subquote_string: List[str] = Field(
...,
description="Correctly resolved value of the entity, if the entity is a reference to another entity, this should be the id of the referenced entity, include a few more words before and after the value to allow for some context to be used in the resolution",
)
entity_title: str
properties: List[Property] = Field(
..., description="List of properties of the entity"
)
dependencies: List[int] = Field(
...,
description="List of entity ids that this entity depends or relies on to resolve it",
)
class DocumentExtraction(BaseModel):
entities: List[Entity] = Field(
...,
description="Body of the answer, each fact should be a separate object with a body and a list of sources",
)
```
## Entity Extraction and Resolution
The **`ask_ai`** function utilizes OpenAI's API to extract and resolve entities from the input content.
```python
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
# <%hide%>
from pydantic import BaseModel, Field
from typing import List
class Property(BaseModel):
key: str
value: str
resolved_absolute_value: str
class Entity(BaseModel):
id: int = Field(
...,
description="Unique identifier for the entity, used for deduplication, design a scheme allows multiple entities",
)
subquote_string: List[str] = Field(
...,
description="Correctly resolved value of the entity, if the entity is a reference to another entity, this should be the id of the referenced entity, include a few more words before and after the value to allow for some context to be used in the resolution",
)
entity_title: str
properties: List[Property] = Field(
..., description="List of properties of the entity"
)
dependencies: List[int] = Field(
...,
description="List of entity ids that this entity depends or relies on to resolve it",
)
class DocumentExtraction(BaseModel):
entities: List[Entity] = Field(
...,
description="Body of the answer, each fact should be a separate object with a body and a list of sources",
)
# <%hide%>
def ask_ai(content) -> DocumentExtraction:
return client.create(
model="gpt-4",
response_model=DocumentExtraction,
messages=[
{
"role": "system",
"content": "Extract and resolve a list of entities from the following document:",
},
{
"role": "user",
"content": content,
},
],
) # type: ignore
```
## Graph Visualization
**`generate_graph`** takes the extracted entities and visualizes them using Graphviz. It creates nodes for each entity and edges for their dependencies.
```python
from graphviz import Digraph
# <%hide%>
from pydantic import BaseModel, Field
from typing import List
class Property(BaseModel):
key: str
value: str
resolved_absolute_value: str
class Entity(BaseModel):
id: int = Field(
...,
description="Unique identifier for the entity, used for deduplication, design a scheme allows multiple entities",
)
subquote_string: List[str] = Field(
...,
description="Correctly resolved value of the entity, if the entity is a reference to another entity, this should be the id of the referenced entity, include a few more words before and after the value to allow for some context to be used in the resolution",
)
entity_title: str
properties: List[Property] = Field(
..., description="List of properties of the entity"
)
dependencies: List[int] = Field(
...,
description="List of entity ids that this entity depends or relies on to resolve it",
)
class DocumentExtraction(BaseModel):
entities: List[Entity] = Field(
...,
description="Body of the answer, each fact should be a separate object with a body and a list of sources",
)
# <%hide%>
def generate_html_label(entity: Entity) -> str:
rows = [
f"<tr><td>{prop.key}</td><td>{prop.resolved_absolute_value}</td></tr>"
for prop in entity.properties
]
table_rows = "".join(rows)
return f"<<table border='0' cellborder='1' cellspacing='0'><tr><td colspan='2'><b>{entity.entity_title}</b></td></tr>{table_rows}</table>>"
def generate_graph(data: DocumentExtraction):
dot = Digraph(comment="Entity Graph", node_attr={"shape": "plaintext"})
for entity in data.entities:
label = generate_html_label(entity)
dot.node(str(entity.id), label)
for entity in data.entities:
for dep_id in entity.dependencies:
dot.edge(str(entity.id), str(dep_id))
dot.render("entity.gv", view=True)
```
## Execution
Finally, execute the code to visualize the entity graph for the sample legal contract.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import List
from graphviz import Digraph
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
class Property(BaseModel):
key: str
value: str
resolved_absolute_value: str
class Entity(BaseModel):
id: int = Field(
...,
description="Unique identifier for the entity, used for deduplication, design a scheme allows multiple entities",
)
subquote_string: List[str] = Field(
...,
description="Correctly resolved value of the entity, if the entity is a reference to another entity, this should be the id of the referenced entity, include a few more words before and after the value to allow for some context to be used in the resolution",
)
entity_title: str
properties: List[Property] = Field(
..., description="List of properties of the entity"
)
dependencies: List[int] = Field(
...,
description="List of entity ids that this entity depends or relies on to resolve it",
)
class DocumentExtraction(BaseModel):
entities: List[Entity] = Field(
...,
description="Body of the answer, each fact should be a separate object with a body and a list of sources",
)
def ask_ai(content) -> DocumentExtraction:
return client.create(
model="gpt-4",
response_model=DocumentExtraction,
messages=[
{
"role": "system",
"content": "Extract and resolve a list of entities from the following document:",
},
{
"role": "user",
"content": content,
},
],
) # type: ignore
def generate_html_label(entity: Entity) -> str:
rows = [
f"<tr><td>{prop.key}</td><td>{prop.resolved_absolute_value}</td></tr>"
for prop in entity.properties
]
table_rows = "".join(rows)
return f"<<table border='0' cellborder='1' cellspacing='0'><tr><td colspan='2'><b>{entity.entity_title}</b></td></tr>{table_rows}</table>>"
def generate_graph(data: DocumentExtraction):
dot = Digraph(comment="Entity Graph", node_attr={"shape": "plaintext"})
for entity in data.entities:
label = generate_html_label(entity)
dot.node(str(entity.id), label)
for entity in data.entities:
for dep_id in entity.dependencies:
dot.edge(str(entity.id), str(dep_id))
dot.render("entity.gv", view=True)
# <%hide%>
content = """
Sample Legal Contract
Agreement Contract
This Agreement is made and entered into on 2020-01-01 by and between Company A ("the Client") and Company B ("the Service Provider").
Article 1: Scope of Work
The Service Provider will deliver the software product to the Client 30 days after the agreement date.
Article 2: Payment Terms
The total payment for the service is $50,000.
An initial payment of $10,000 will be made within 7 days of the the signed date.
The final payment will be due 45 days after [SignDate].
Article 3: Confidentiality
The parties agree not to disclose any confidential information received from the other party for 3 months after the final payment date.
Article 4: Termination
The contract can be terminated with a 30-day notice, unless there are outstanding obligations that must be fulfilled after the [DeliveryDate].
""" # Your legal contract here
model = ask_ai(content)
generate_graph(model)
```
This will produce a graphical representation of the entities and their dependencies, stored as "entity.gv".
![Entity Graph visualization showing relationships between legal document entities](entity_resolution.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

View File

@@ -0,0 +1,204 @@
---
title: Citation Validation with Instructor - Prevent Hallucinations
description: Validate AI-generated answers with contextual citations using Instructor. Ensure every statement is backed by source quotes to prevent hallucinations.
---
# Example: Answering Questions with Validated Citations
For the full code example, check out [examples/citation_fuzzy_match.py](https://github.com/jxnl/instructor/blob/main/examples/citation_with_extraction/citation_fuzzy_match.py)
## Overview
This example shows how to use Instructor with validators to not only add citations to answers generated but also prevent hallucinations by ensuring that every statement made by the LLM is backed up by a direct quote from the context provided, and that those quotes exist!
Two Python classes, `Fact` and `QuestionAnswer`, are defined to encapsulate the information of individual facts and the entire answer, respectively.
## Data Structures
### The `Fact` Class
The `Fact` class encapsulates a single statement or fact. It contains two fields:
- `fact`: A string representing the body of the fact or statement.
- `substring_quote`: A list of strings. Each string is a direct quote from the context that supports the `fact`.
#### Validation Method: `validate_sources`
This method validates the sources (`substring_quote`) in the context. It utilizes regex to find the span of each substring quote in the given context. If the span is not found, the quote is removed from the list.
```python hl_lines="6 8-13"
from pydantic import Field, BaseModel, model_validator, ValidationInfo
from typing import List
class Fact(BaseModel):
fact: str = Field(...)
substring_quote: List[str] = Field(...)
@model_validator(mode="after")
def validate_sources(self, info: ValidationInfo) -> "Fact":
text_chunks = info.context.get("text_chunk", None)
spans = list(self.get_spans(text_chunks))
self.substring_quote = [text_chunks[span[0] : span[1]] for span in spans]
return self
def get_spans(self, context):
for quote in self.substring_quote:
yield from self._get_span(quote, context)
def _get_span(self, quote, context):
for match in re.finditer(re.escape(quote), context):
yield match.span()
```
### The `QuestionAnswer` Class
This class encapsulates the question and its corresponding answer. It contains two fields:
- `question`: The question asked.
- `answer`: A list of `Fact` objects that make up the answer.
#### Validation Method: `validate_sources`
This method checks that each `Fact` object in the `answer` list has at least one valid source. If a `Fact` object has no valid sources, it is removed from the `answer` list.
```python hl_lines="5-8"
from pydantic import BaseModel, Field, model_validator
from typing import List
# <%hide%>
from pydantic import ValidationInfo
class Fact(BaseModel):
fact: str = Field(...)
substring_quote: List[str] = Field(...)
@model_validator(mode="after")
def validate_sources(self, info: ValidationInfo) -> "Fact":
text_chunks = info.context.get("text_chunk", None)
spans = list(self.get_spans(text_chunks))
self.substring_quote = [text_chunks[span[0] : span[1]] for span in spans]
return self
def get_spans(self, context):
for quote in self.substring_quote:
yield from self._get_span(quote, context)
def _get_span(self, quote, context):
for match in re.finditer(re.escape(quote), context):
yield match.span()
# <%hide%>
class QuestionAnswer(BaseModel):
question: str = Field(...)
answer: List[Fact] = Field(...)
@model_validator(mode="after")
def validate_sources(self) -> "QuestionAnswer":
self.answer = [fact for fact in self.answer if len(fact.substring_quote) > 0]
return self
```
## Function to Ask AI a Question
### The `ask_ai` Function
This function takes a string `question` and a string `context` and returns a `QuestionAnswer` object. It uses the OpenAI API to fetch the answer and then validates the sources using the defined classes.
To understand the validation context work from pydantic check out [pydantic's docs](https://docs.pydantic.dev/usage/validators/#model-validators)
```python hl_lines="5 6 14"
import instructor
# Apply the patch to the OpenAI client
# enables response_model, context keyword
client = instructor.from_provider("openai/gpt-5-nano")
# <%hide%>
from pydantic import ValidationInfo, BaseModel, Field, model_validator
from typing import List
class Fact(BaseModel):
fact: str = Field(...)
substring_quote: List[str] = Field(...)
@model_validator(mode="after")
def validate_sources(self, info: ValidationInfo) -> "Fact":
text_chunks = info.context.get("text_chunk", None)
spans = list(self.get_spans(text_chunks))
self.substring_quote = [text_chunks[span[0] : span[1]] for span in spans]
return self
def get_spans(self, context):
for quote in self.substring_quote:
yield from self._get_span(quote, context)
def _get_span(self, quote, context):
for match in re.finditer(re.escape(quote), context):
yield match.span()
class QuestionAnswer(BaseModel):
question: str = Field(...)
answer: List[Fact] = Field(...)
@model_validator(mode="after")
def validate_sources(self) -> "QuestionAnswer":
self.answer = [fact for fact in self.answer if len(fact.substring_quote) > 0]
return self
# <%hide%>
def ask_ai(question: str, context: str) -> QuestionAnswer:
return client.create(
model="gpt-4o-mini",
temperature=0,
response_model=QuestionAnswer,
messages=[
{
"role": "system",
"content": "You are a world class algorithm to answer questions with correct and exact citations.",
},
{"role": "user", "content": f"{context}"},
{"role": "user", "content": f"Question: {question}"},
],
context={"text_chunk": context},
)
```
## Example
Here's an example of using these classes and functions to ask a question and validate the answer.
```python
question = "What did the author do during college?"
context = """
My name is Jason Liu, and I grew up in Toronto Canada but I was born in China.
I went to an arts high school but in university I studied Computational Mathematics and physics.
As part of coop I worked at many companies including Stitchfix, Facebook.
I also started the Data Science club at the University of Waterloo and I was the president of the club for 2 years.
"""
```
The output would be a `QuestionAnswer` object containing validated facts and their sources.
```python
{
"question": "where did he go to school?",
"answer": [
{
"statement": "Jason Liu went to an arts highschool.",
"substring_phrase": ["arts highschool"],
},
{
"statement": "Jason Liu studied Computational Mathematics and physics in university.",
"substring_phrase": ["university"],
},
],
}
```
This ensures that every piece of information in the answer has been validated against the context.

View File

@@ -0,0 +1,72 @@
---
title: Few-Shot Learning with Examples - Pydantic Models
description: Enhance Pydantic models with practical examples for few-shot learning. Improve LLM understanding with example-driven JSON schemas.
---
# How should I include examples?
To enhance the clarity and usability of your model and prompt, incorporating examples directly into the JSON schema extra of your Pydantic model is highly recommended. This approach not only streamlines the integration of practical examples but also ensures that they are easily accessible and understandable within the context of your model's schema.
```python
import instructor
from typing import Iterable
from pydantic import BaseModel, ConfigDict
client = instructor.from_provider("openai/gpt-5-nano")
class SyntheticQA(BaseModel):
question: str
answer: str
model_config = ConfigDict(
json_schema_extra={
"examples": [
{"question": "What is the capital of France?", "answer": "Paris"},
{
"question": "What is the largest planet in our solar system?",
"answer": "Jupiter",
},
{
"question": "Who wrote 'To Kill a Mockingbird'?",
"answer": "Harper Lee",
},
{
"question": "What element does 'O' represent on the periodic table?",
"answer": "Oxygen",
},
]
}
)
def get_synthetic_data() -> Iterable[SyntheticQA]:
return client.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Generate synthetic examples"},
{
"role": "user",
"content": "Generate the exact examples you see in the examples of this prompt. ",
},
],
response_model=Iterable[SyntheticQA],
) # type: ignore
if __name__ == "__main__":
for example in get_synthetic_data():
print(example)
#> question='What is the capital of France?' answer='Paris'
#> question='What is the largest planet in our solar system?' answer='Jupiter'
#> question="Who wrote 'To Kill a Mockingbird'?" answer='Harper Lee'
"""
question="What element does 'O' represent on the periodic table?" answer='Oxygen'
"""
"""
question="What element does 'O' represent on the periodic table?" answer='Oxygen'
"""
"""
question="What element does 'O' represent on the periodic table?" answer='Oxygen'
"""
```

View File

@@ -0,0 +1,101 @@
---
title: Contact Information Extraction - Lead Generation Automation
description: Automate customer lead extraction from text using Instructor. Extract names, phone numbers, and contact details with automatic validation.
---
# Customer Information Extraction
In this guide, we'll walk through how to extract customer lead information using OpenAI's API and Pydantic. This use case is essential for seamlessly automating the process of extracting specific information from a context.
## Motivation
You could potentially integrate this into a chatbot to extract relevant user information from user messages. With the use of machine learning driven validation it would reduce the need for a human to verify the information.
## Defining the Structure
We'll model a customer lead as a Lead object, including attributes for the name and phone number. We'll use a Pydantic PhoneNumber type to validate the phone numbers entered and provide a Field to give the model more information on correctly populating the object.
## Extracting Lead Information
To extract lead information, we create the `parse_lead_from_message` function which integrates Instructor. It calls OpenAI's API, processes the text, and returns the extracted lead information as a Lead object.
## Evaluating Lead Extraction
To showcase the `parse_lead_from_message` function we can provide sample user messages that may be obtained from a dialogue with a chatbot assistant. Also take note of the response model being set as `Iterable[Lead]` this allows for multiple leads being extracted from the same message.
```python
import instructor
from pydantic import BaseModel, Field
from pydantic_extra_types.phone_numbers import PhoneNumber
from typing import Iterable
class Lead(BaseModel):
name: str
phone_number: PhoneNumber = Field(
description="Needs to be a phone number with a country code. If none, assume +1"
)
# Can define some function here to send Lead information to a database using an API
client = instructor.from_provider("openai/gpt-5-nano")
def parse_lead_from_message(user_message: str):
return client.create(
model="gpt-4-turbo-preview",
response_model=Iterable[Lead],
messages=[
{
"role": "system",
"content": "You are a data extraction system that extracts a user's name and phone number from a message.",
},
{
"role": "user",
"content": f"Extract the user's lead information from this user's message: {user_message}",
},
],
)
if __name__ == "__main__":
lead = parse_lead_from_message(
"Yes, that would be great if someone can reach out my name is Patrick King 9175554587"
)
assert all(isinstance(item, Lead) for item in lead)
for item in lead:
print(item.model_dump_json(indent=2))
"""
{
"name": "Patrick King",
"phone_number": "tel:+1-917-555-4587"
}
"""
# Invalid phone number example:
try:
lead2 = parse_lead_from_message(
"Yes, that would be great if someone can reach out my name is Patrick King 9172234"
)
assert all(isinstance(item, Lead) for item in lead2)
for item in lead2:
print(item.model_dump_json(indent=2))
"""
{
"name": "Patrick King",
"phone_number": "tel:+1-917-223-4999"
}
"""
except Exception as e:
print("ERROR:", e)
"""
ERROR:
1 validation error for IterableLead
tasks.0.phone_number
value is not a valid phone number [type=value_error, input_value='+19172234', input_type=str]
"""
```
In this example, the `parse_lead_from_message` function successfully extracts lead information from a user message, demonstrating how automation can enhance the efficiency of collecting accurate customer details. It also shows how the function successfully catches that the phone number is invalid so functionality can be implemented for the user to get prompted again to give a correct phone number.

View File

@@ -0,0 +1,281 @@
---
title: Extracting Competitor Data from Slides Using AI
description: Learn how to extract competitor data from presentation slides, leveraging AI for comprehensive information gathering.
---
# Data extraction from slides
In this guide, we demonstrate how to extract data from slides.
!!! tips "Motivation"
When we want to translate key information from slides into structured data, simply isolating the text and running extraction might not be enough. Sometimes the important data is in the images on the slides, so we should consider including them in our extraction pipeline.
## Defining the necessary Data Structures
Let's say we want to extract the competitors from various presentations and categorize them according to their respective industries.
Our data model will have `Industry` which will be a list of `Competitor`'s for a specific industry, and `Competition` which will aggregate the competitors for all the industries.
```python
from pydantic import BaseModel, Field
from typing import Optional, List
class Competitor(BaseModel):
name: str
features: Optional[List[str]]
# Define models
class Industry(BaseModel):
"""
Represents competitors from a specific industry extracted from an image using AI.
"""
name: str = Field(description="The name of the industry")
competitor_list: List[Competitor] = Field(
description="A list of competitors for this industry"
)
class Competition(BaseModel):
"""
This class serves as a structured representation of
competitors and their qualities.
"""
industry_list: List[Industry] = Field(
description="A list of industries and their competitors"
)
```
## Competitors extraction
To extract competitors from slides we will define a function which will read images from urls and extract the relevant information from them.
```python
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
# <%hide%>
from pydantic import BaseModel, Field
from typing import Optional, List
class Competitor(BaseModel):
name: str
features: Optional[List[str]]
# Define models
class Industry(BaseModel):
"""
Represents competitors from a specific industry extracted from an image using AI.
"""
name: str = Field(description="The name of the industry")
competitor_list: List[Competitor] = Field(
description="A list of competitors for this industry"
)
class Competition(BaseModel):
"""
This class serves as a structured representation of
competitors and their qualities.
"""
industry_list: List[Industry] = Field(
description="A list of industries and their competitors"
)
# <%hide%>
# Define functions
def read_images(image_urls: List[str]) -> Competition:
"""
Given a list of image URLs, identify the competitors in the images.
"""
return client.create(
model="gpt-4o-mini",
response_model=Competition,
max_tokens=2048,
temperature=0,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Identify competitors and generate key features for each competitor.",
},
*[
{"type": "image_url", "image_url": {"url": url}}
for url in image_urls
],
],
}
],
)
```
## Execution
Finally, we will run the previous function with a few sample slides to see the data extractor in action.
As we can see, our model extracted the relevant information for each competitor regardless of how this information was formatted in the original presentations.
```python
# <%hide%>
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
from pydantic import BaseModel, Field
from typing import Optional, List
class Competitor(BaseModel):
name: str
features: Optional[List[str]]
# Define models
class Industry(BaseModel):
"""
Represents competitors from a specific industry extracted from an image using AI.
"""
name: str = Field(description="The name of the industry")
competitor_list: List[Competitor] = Field(
description="A list of competitors for this industry"
)
class Competition(BaseModel):
"""
This class serves as a structured representation of
competitors and their qualities.
"""
industry_list: List[Industry] = Field(
description="A list of industries and their competitors"
)
# Define functions
def read_images(image_urls: List[str]) -> Competition:
"""
Given a list of image URLs, identify the competitors in the images.
"""
return client.create(
model="gpt-4o-mini",
response_model=Competition,
max_tokens=2048,
temperature=0,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Identify competitors and generate key features for each competitor.",
},
*[
{"type": "image_url", "image_url": {"url": url}}
for url in image_urls
],
],
}
],
)
# <%hide%>
url = [
'https://miro.medium.com/v2/resize:fit:1276/0*h1Rsv-fZWzQUyOkt',
]
model = read_images(url)
print(model.model_dump_json(indent=2))
"""
{
"industry_list": [
{
"name": "Accommodation Booking",
"competitor_list": [
{
"name": "CouchSurfing",
"features": [
"Free accommodation",
"Community-driven",
"Cultural exchange"
]
},
{
"name": "Craigslist",
"features": [
"Local listings",
"Variety of options",
"User-generated content"
]
},
{
"name": "BedandBreakfast.com",
"features": [
"Specialized in B&Bs",
"Personalized service",
"Local experiences"
]
},
{
"name": "AirBed & Breakfast (Airbnb)",
"features": [
"Wide range of accommodations",
"User reviews",
"Instant booking"
]
},
{
"name": "Hostels.com",
"features": [
"Budget-friendly hostels",
"Global reach",
"User ratings"
]
},
{
"name": "RentDigs.com",
"features": [
"Rental listings",
"Long-term stays",
"User-friendly interface"
]
},
{
"name": "VRBO",
"features": [
"Vacation rentals",
"Family-friendly options",
"Direct owner contact"
]
},
{
"name": "Hotels.com",
"features": [
"Wide selection of hotels",
"Rewards program",
"Price match guarantee"
]
}
]
}
]
}
"""
```

View File

@@ -0,0 +1,179 @@
---
title: Receipt Data Extraction with GPT-4 Vision - Expense Tracking
description: Extract and validate receipt data from images using GPT-4 Vision and Instructor. Automate expense tracking with structured receipt parsing.
---
# Extracting Receipt Data using GPT-4 and Python
This post demonstrates how to use Python's Pydantic library and OpenAI's GPT-4 model to extract receipt data from images and validate the total amount. This method is particularly useful for automating expense tracking and financial analysis tasks.
## Defining the Item and Receipt Classes
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
quantity: int
class Receipt(BaseModel):
items: list[Item]
total: float
```
## Validating the Total Amount
To ensure the accuracy of the extracted data, we use Pydantic's `model_validator` decorator to define a custom validation function, `check_total`. This function calculates the sum of item prices and compares it to the extracted total amount. If there's a discrepancy, it raises a `ValueError`.
```python
from pydantic import model_validator
@model_validator(mode="after")
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 self
```
## Extracting Receipt Data from Images
The `extract_receipt` function uses OpenAI's GPT-4 model to process an image URL and extract receipt data. We utilize the `instructor` library to configure the OpenAI client for this purpose.
```python
import instructor
# <%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_provider("openai/gpt-5-nano")
def extract(url: str) -> Receipt:
return client.create(
model="gpt-4",
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.",
},
],
}
],
)
```
## Practical Examples
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
# <%hide%>
from pydantic import BaseModel, model_validator
import instructor
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 = round(sum(item.price * item.quantity for item in items), 2)
if calculated_total != total:
raise ValueError(
f"Total {total} does not match the sum of item prices {calculated_total}"
)
return values
client = instructor.from_provider("openai/gpt-5-nano")
def extract(url: str) -> Receipt:
return client.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)
"""
items=[Item(name='Lorem ipsum', price=9.2, quantity=1), Item(name='Lorem ipsum dolor sit', price=19.2, quantity=1), Item(name='Lorem ipsum dolor sit amet', price=15.0, quantity=1), Item(name='Lorem ipsum', price=15.0, quantity=1), Item(name='Lorem ipsum', price=15.0, quantity=1), Item(name='Lorem ipsum dolor sit', price=15.0, quantity=1), Item(name='Lorem ipsum', price=19.2, quantity=1)] total=107.6
"""
```
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.

View File

@@ -0,0 +1,307 @@
---
title: Extracting Tables from Images using GPT-Vision
description: Learn how to use Python and GPT-Vision to extract and convert tables from images into markdown for data analysis.
---
## See Also
- [Vision Processing](./tables_from_vision.md) - More vision-based table extraction
- [Multi-Modal Processing](./multi_modal_gemini.md) - Using Gemini for vision tasks
- [Image Processing Examples](./index.md#vision-processing) - More vision examples
- [Raw Response](../concepts/raw_response.md) - Access original LLM responses
# Extracting Tables using GPT-Vision
This post demonstrates how to use Python's type annotations and OpenAI's new vision model to extract tables from images and convert them into markdown format. This method is particularly useful for data analysis and automation tasks.
The full code is available on [GitHub](https://github.com/jxnl/instructor/blob/main/examples/vision/run_table.py)
## Building the Custom Type for Markdown Tables
First, we define a custom type, `MarkdownDataFrame`, to handle pandas DataFrames formatted in markdown. This type uses Python's `Annotated` and `InstanceOf` types, along with decorators `BeforeValidator` and `PlainSerializer`, to process and serialize the data.
```python
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",
}
),
]
```
## Defining the Table Class
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
```
## Extracting Tables from Images
The `extract_table` function uses OpenAI's vision model to process an image URL and extract tables in markdown format. We utilize the `instructor` library to patch the OpenAI client for this purpose.
```python
import instructor
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 separate",
}
),
]
class Table(BaseModel):
caption: str
dataframe: MarkdownDataFrame
# <%hide%>
# Use MD_JSON mode since the vision model does not support any special structured output mode
client = instructor.from_provider("openai/gpt-4o-mini", mode=instructor.Mode.MD_JSON)
def extract_table(url: str) -> Iterable[Table]:
return client.create(
model="gpt-4o-mini",
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}},
],
}
],
)
```
## Practical Example
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 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 separate",
}
),
]
class Table(BaseModel):
caption: str
dataframe: MarkdownDataFrame
client = instructor.from_provider("openai/gpt-5-nano")
def extract_table(url: str) -> Iterable[Table]:
return client.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.dataframe)
"""
Android App ... Category
Android Rank ...
1 Google One ... Social networking
2 Disney+ ... Entertainment
3 TikTok - Videos, Music & LIVE ... Entertainment
4 Candy Crush Saga ... Entertainment
5 Tinder: Dating, Chat & Friends ... Games
6 Coin Master ... Entertainment
7 Roblox ... Dating
8 Bumble - Dating & Make Friends ... Games
9 Royal Match ... Business
10 Spotify: Music and Podcasts ... Education
[10 rows x 5 columns]
"""
```
??? Note "Expand to see the output"
![Top 10 Grossing Apps in October 2023 for Ireland - Table extraction example showing structured data from image](https://a.storyblok.com/f/47007/2400x2000/bf383abc3c/231031_uk-ireland-in-three-charts_table_v01_b.png)
### Top 10 Grossing Apps in October 2023 (Ireland) for Android Platforms
| Rank | App Name | Category |
|------|----------------------------------|--------------------|
| 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 |
### Top 10 Grossing Apps in October 2023 (Ireland) for iOS Platforms
| Rank | App Name | Category |
|------|----------------------------------|--------------------|
| 1 | Tinder: Dating, Chat & Friends | Social networking |
| 2 | Disney+ | Entertainment |
| 3 | YouTube: Watch, Listen, Stream | Entertainment |
| 4 | Audible: Audio Entertainment | Entertainment |
| 5 | Candy Crush Saga | Games |
| 6 | TikTok - Videos, Music & LIVE | Entertainment |
| 7 | Bumble - Dating & Make Friends | Dating |
| 8 | Roblox | Games |
| 9 | LinkedIn: Job Search & News | Business |
| 10 | Duolingo - Language Lessons | Education |

View File

@@ -0,0 +1,63 @@
---
title: Groq AI Integration - Fast Structured Outputs
description: Use Groq AI with Instructor for fast structured outputs. Leverage Groq's high-speed inference for real-time structured data extraction.
---
# Structured Outputs using Groq
Instead of using openai or antrophic you can now also use groq for inference by using from_groq.
The examples are using mixtral-8x7b model.
## GroqCloud API
To use groq you need to obtain a groq API key.
Goto [groqcloud](https://console.groq.com) and login. Select API Keys from the left menu and then select Create API key to create a new key.
## Use example
Some pip packages need to be installed to use the example:
```
pip install instructor groq pydantic openai anthropic
```
You need to export the groq API key:
```
export GROQ_API_KEY=<your-api-key>
```
An example:
```python
from pydantic import BaseModel, Field
from typing import List
import instructor
class Character(BaseModel):
name: str
fact: List[str] = Field(..., description="A list of facts about the subject")
# Use from_provider for simplified setup
client = instructor.from_provider("groq/mixtral-8x7b-32768", mode=instructor.Mode.TOOLS)
resp = client.create(
model="mixtral-8x7b-32768",
messages=[
{
"role": "user",
"content": "Tell me about the company Tesla",
}
],
response_model=Character,
)
print(resp.model_dump_json(indent=2))
"""
{
"name": "Tesla",
"fact": [
"electric vehicle manufacturer",
"solar panel producer",
"based in Palo Alto, California",
"founded in 2003 by Elon Musk"
]
}
"""
```
You can find another example called groq_example2.py under examples/groq of this repository.

View File

@@ -0,0 +1,389 @@
---
title: Automatically Generate Advertising Copy from Product Images Using GPT-4 Vision
description: Learn how to use GPT-4 Vision API to create engaging advertising copy from product images, ideal for e-commerce and marketing teams.
---
# Use Vision API to detect products and generate advertising copy
This post demonstrates how to use GPT-4 Vision API and the Chat API to automatically generate advertising copy from product images. This method can be useful for marketing and advertising teams, as well as for e-commerce platforms.
The full code is available on [GitHub](https://www.github.com/jxnl/instructor/tree/main/examples/vision/image_to_ad_copy.py).
## Building the models
### Product
For the `Product` model, we define a class that represents a product extracted from an image and store the name, key features, and description. The product attributes are dynamically determined based on the content of the image.
Note that it is easy to add [Validators](https://jxnl.github.io/instructor/concepts/reask_validation/) and other Pydantic features to the model to ensure that the data is valid and consistent.
```python
from pydantic import BaseModel, Field
from typing import List, Optional
class Product(BaseModel):
"""
Represents a product extracted from an image using AI.
The product attributes are dynamically determined based on the content
of the image and the AI's interpretation. This class serves as a structured
representation of the identified product characteristics.
"""
name: str = Field(
description="A generic name for the product.", example="Headphones"
)
key_features: Optional[List[str]] = Field(
description="A list of key features of the product that stand out.",
default=None,
)
description: Optional[str] = Field(
description="A description of the product.",
default=None,
)
# Can be customized and automatically generated
def generate_prompt(self):
prompt = f"Product: {self.name}\n"
if self.description:
prompt += f"Description: {self.description}\n"
if self.key_features:
prompt += f"Key Features: {', '.join(self.key_features)}\n"
return prompt
```
### Identified Product
We also define a class that represents a list of products identified in the images. We also add an error flag and message to indicate if there was an error in the processing of the image.
```python
from pydantic import BaseModel, Field
from typing import Optional, List
# <%hide%>
class Product(BaseModel):
"""
Represents a product extracted from an image using AI.
The product attributes are dynamically determined based on the content
of the image and the AI's interpretation. This class serves as a structured
representation of the identified product characteristics.
"""
name: str = Field(
description="A generic name for the product.", example="Headphones"
)
key_features: Optional[List[str]] = Field(
description="A list of key features of the product that stand out.",
default=None,
)
description: Optional[str] = Field(
description="A description of the product.",
default=None,
)
# Can be customized and automatically generated
def generate_prompt(self):
prompt = f"Product: {self.name}\n"
if self.description:
prompt += f"Description: {self.description}\n"
if self.key_features:
prompt += f"Key Features: {', '.join(self.key_features)}\n"
return prompt
# <%hide%>
class IdentifiedProduct(BaseModel):
"""
Represents a list of products identified in the images.
"""
products: Optional[List[Product]] = Field(
description="A list of products identified by the AI.",
example=[
Product(
name="Headphones",
description="Wireless headphones with noise cancellation.",
key_features=["Wireless", "Noise Cancellation"],
)
],
default=None,
)
error: bool = Field(default=False)
message: Optional[str] = Field(default=None)
```
### Advertising Copy
Finally, the `AdCopy` models stores the output in a structured format with a headline and the text.
```python
from pydantic import BaseModel, Field
class AdCopy(BaseModel):
"""
Represents a generated ad copy.
"""
headline: str = Field(
description="A short, catchy, and memorable headline for the given product. The headline should invoke curiosity and interest in the product.",
)
ad_copy: str = Field(
description="A long-form advertisement copy for the given product. This will be used in campaigns to promote the product with a persuasive message and a call-to-action with the objective of driving sales.",
)
name: str = Field(description="The name of the product being advertised.")
```
## Calling the API
### Product Detection
The `read_images` function uses OpenAI's vision model to process a list of image URLs and identify products in each of them. We utilize the `instructor` library to patch the OpenAI client for this purpose.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import Optional, List
class Product(BaseModel):
"""
Represents a product extracted from an image using AI.
The product attributes are dynamically determined based on the content
of the image and the AI's interpretation. This class serves as a structured
representation of the identified product characteristics.
"""
name: str = Field(
description="A generic name for the product.", example="Headphones"
)
key_features: Optional[List[str]] = Field(
description="A list of key features of the product that stand out.",
default=None,
)
description: Optional[str] = Field(
description="A description of the product.",
default=None,
)
# Can be customized and automatically generated
def generate_prompt(self):
prompt = f"Product: {self.name}\n"
if self.description:
prompt += f"Description: {self.description}\n"
if self.key_features:
prompt += f"Key Features: {', '.join(self.key_features)}\n"
return prompt
class IdentifiedProduct(BaseModel):
"""
Represents a list of products identified in the images.
"""
products: Optional[List[Product]] = Field(
description="A list of products identified by the AI.",
example=[
Product(
name="Headphones",
description="Wireless headphones with noise cancellation.",
key_features=["Wireless", "Noise Cancellation"],
)
],
default=None,
)
error: bool = Field(default=False)
message: Optional[str] = Field(default=None)
# <%hide%>
def read_images(image_urls: list[str]) -> IdentifiedProduct:
"""
Given a list of image URLs, identify the products in the images.
"""
logger.info(f"Identifying products in images... {len(image_urls)} images")
return client_image.create(
response_model=IdentifiedProduct,
max_tokens=1024, # can be changed
temperature=0,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Identify products using the given images and generate key features for each product.",
},
*[
{"type": "image_url", "image_url": {"url": url}}
for url in image_urls
],
],
}
],
)
```
This gives us a list of products identified in all the images.
### Generate advertising copy
Then, we can use the `generate_ad_copy` function to generate advertising copy for each of the products identified in the images.
Two clients are defined for the two different models. This is because the `gpt-4-vision-preview` model is not compatible with the `gpt-4-1106-preview` model in terms of their response format.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import List, Optional
class Product(BaseModel):
"""
Represents a product extracted from an image using AI.
The product attributes are dynamically determined based on the content
of the image and the AI's interpretation. This class serves as a structured
representation of the identified product characteristics.
"""
name: str = Field(
description="A generic name for the product.", example="Headphones"
)
key_features: Optional[List[str]] = Field(
description="A list of key features of the product that stand out.",
default=None,
)
description: Optional[str] = Field(
description="A description of the product.",
default=None,
)
# Can be customized and automatically generated
def generate_prompt(self):
prompt = f"Product: {self.name}\n"
if self.description:
prompt += f"Description: {self.description}\n"
if self.key_features:
prompt += f"Key Features: {', '.join(self.key_features)}\n"
return prompt
class AdCopy(BaseModel):
"""
Represents a generated ad copy.
"""
headline: str = Field(
description="A short, catchy, and memorable headline for the given product. The headline should invoke curiosity and interest in the product.",
)
ad_copy: str = Field(
description="A long-form advertisement copy for the given product. This will be used in campaigns to promote the product with a persuasive message and a call-to-action with the objective of driving sales.",
)
name: str = Field(description="The name of the product being advertised.")
# <%hide%>
def generate_ad_copy(product: Product) -> AdCopy:
"""
Given a product, generate an ad copy for the product.
"""
logger.info(f"Generating ad copy for product: {product.name}")
return client_copy.create(
response_model=AdCopy,
temperature=0.3,
messages=[
{
"role": "system",
"content": "You are an expert marketing assistant for all products. Your task is to generate an advertisement copy for a product using the name, description, and key features.",
},
{"role": "user", "content": product.generate_prompt()},
],
)
```
### Putting it all together
Finally, we can put it all together in a single function that takes a list of image URLs and generates advertising copy for the products identified in the images. Please refer to the [full code](https://www.github.com/jxnl/instructor/tree/main/examples/vision/image_to_ad_copy.py) for the complete implementation.
## Input file
The input file is currently a list of image URLs, but this trivial to change to any required format.
```plaintext
https://contents.mediadecathlon.com/p1279823/9a1c59ad97a4084a346c014740ae4d3ff860ea70b485ee65f34017ff5e9ae5f7/recreational-ice-skates-fit-50-black.jpg?format=auto
https://contents.mediadecathlon.com/p1279822/a730505231dbd6747c14ee93e8f89e824d3fa2a5b885ec26de8d7feb5626638a/recreational-ice-skates-fit-50-black.jpg?format=auto
https://contents.mediadecathlon.com/p2329893/1ed75517602a5e00245b89ab6a1c6be6d8968a5a227c932b10599f857f3ed4cd/mens-hiking-leather-boots-sh-100-x-warm.jpg?format=auto
https://contents.mediadecathlon.com/p2047870/8712c55568dd9928c83b19c6a4067bf161811a469433dc89244f0ff96a50e3e9/men-s-winter-hiking-boots-sh-100-x-warm-grey.jpg?format=auto
```
??? Note "Expand to see the output"
![Recreational ice skates product image for ad copy generation](https://contents.mediadecathlon.com/p1279823/9a1c59ad97a4084a346c014740ae4d3ff860ea70b485ee65f34017ff5e9ae5f7/recreational-ice-skates-fit-50-black.jpg?format=auto)
![Men's hiking leather boots product image for ad copy generation](https://contents.mediadecathlon.com/p2329893/1ed75517602a5e00245b89ab6a1c6be6d8968a5a227c932b10599f857f3ed4cd/mens-hiking-leather-boots-sh-100-x-warm.jpg?format=auto)
```json
{
"products":
[
{
"name": "Ice Skates",
"key_features": [
"Lace-up closure",
"Durable blade",
"Ankle support"
],
"description": "A pair of ice skates with lace-up closure for secure fit, durable blade for ice skating, and reinforced ankle support."
},
{
"name": "Hiking Boots",
"key_features": [
"High-top design",
"Rugged outsole",
"Water-resistant"
],
"description": "Sturdy hiking boots featuring a high-top design for ankle support, rugged outsole for grip on uneven terrain, and water-resistant construction."
},
{
"name": "Winter Boots",
"key_features": [
"Insulated lining",
"Waterproof lower",
"Slip-resistant sole"
],
"description": "Warm winter boots with insulated lining for cold weather, waterproof lower section to keep feet dry, and a slip-resistant sole for stability."
}
],
"ad_copies": [
{
"headline": "Glide with Confidence - Discover the Perfect Ice Skates!",
"ad_copy": "Step onto the ice with poise and precision with our premium Ice Skates. Designed for both beginners and seasoned skaters, these skates offer a perfect blend of comfort and performance. The lace-up closure ensures a snug fit that keeps you stable as you carve through the ice. With a durable blade that withstands the test of time, you can focus on perfecting your moves rather than worrying about your equipment. The reinforced ankle support provides the necessary protection and aids in preventing injuries, allowing you to skate with peace of mind. Whether you're practicing your spins, jumps, or simply enjoying a leisurely glide across the rink, our Ice Skates are the ideal companion for your ice adventures. Lace up and get ready to experience the thrill of ice skating like never before!",
"name": "Ice Skates"
},
{
"headline": "Conquer Every Trail with Confidence!",
"ad_copy": "Embark on your next adventure with our top-of-the-line Hiking Boots! Designed for the trail-blazing spirits, these boots boast a high-top design that provides unparalleled ankle support to keep you steady on any path. The rugged outsole ensures a firm grip on the most uneven terrains, while the water-resistant construction keeps your feet dry as you traverse through streams and muddy trails. Whether you're a seasoned hiker or just starting out, our Hiking Boots are the perfect companion for your outdoor escapades. Lace up and step into the wild with confidence - your journey awaits!",
"name": "Hiking Boots"
},
{
"headline": "Conquer the Cold with Comfort!",
"ad_copy": "Step into the season with confidence in our Winter Boots, the ultimate ally against the chill. Designed for those who don't let the cold dictate their moves, these boots feature an insulated lining that wraps your feet in a warm embrace, ensuring that the biting cold is a worry of the past. But warmth isn't their only virtue. With a waterproof lower section, your feet will remain dry and cozy, come rain, snow, or slush. And let's not forget the slip-resistant sole that stands between you and the treacherous ice, offering stability and peace of mind with every step you take. Whether you're braving a blizzard or just nipping out for a coffee, our Winter Boots are your trusty companions, keeping you warm, dry, and upright. Don't let winter slow you down. Lace up and embrace the elements!",
"name": "Winter Boots"
}
]
}
```

View File

@@ -0,0 +1,143 @@
---
title: Instructor Cookbook Collection
description: Practical examples and recipes for solving real-world problems with structured outputs
---
# Instructor Cookbooks
<div class="grid cards" markdown>
- :material-text-box-multiple: **Text Processing**
Extract structured information from text documents
[:octicons-arrow-right-16: View Recipes](#text-processing)
- :material-image: **Multi-Modal**
Work with images and other media types
[:octicons-arrow-right-16: View Recipes](#multi-modal-examples)
- :material-database: **Data Tools**
Integrate with databases and data processing tools
[:octicons-arrow-right-16: View Recipes](#data-tools)
- :material-server: **Deployment**
Options for local and cloud deployment
[:octicons-arrow-right-16: View Recipes](#deployment-options)
</div>
Our cookbooks demonstrate how to use Instructor to solve real-world problems with structured outputs. Each example includes complete code and explanations to help you implement similar solutions in your own projects.
## Text Processing
### Classification Examples
| Example | Description | Use Case |
|---------|-------------|----------|
| [Single Classification](single_classification.md) | Basic classification with a single category | Content categorization |
| [Multiple Classification](multiple_classification.md) | Handling multiple classification categories | Multi-label document tagging |
| [Enum-Based Classification](classification.md) | Using Python enums for structured classification | Standardized taxonomies |
| [Batch Classification](bulk_classification.md) | Process multiple items efficiently | High-volume text processing |
| [Batch Classification with LangSmith](batch_classification_langsmith.md) | Using LangSmith for batch processing | Performance monitoring |
| [Local Classification](local_classification.md) | Classification without external APIs | Offline processing |
### Information Extraction
| Example | Description | Use Case |
|---------|-------------|----------|
| [Entity Resolution](entity_resolution.md) | Identify and disambiguate entities | Name standardization |
| [Contact Information](extract_contact_info.md) | Extract structured contact details | CRM data entry |
| [PII Sanitization](pii.md) | Detect and redact sensitive information | Privacy compliance |
| [Citation Extraction](exact_citations.md) | Accurately extract formatted citations | Academic research |
| [Action Items](action_items.md) | Extract tasks from text | Meeting follow-ups |
| [Search Query Processing](search.md) | Structure complex search queries | Search enhancement |
### Document Processing
| Example | Description | Use Case |
|---------|-------------|----------|
| [Document Segmentation](document_segmentation.md) | Divide documents into meaningful sections | Long-form content analysis |
| [Planning and Tasks](planning-tasks.md) | Break down complex queries into subtasks | Project management |
| [Knowledge Graph Generation](knowledge_graph.md) | Create relationship graphs from text | Information visualization |
| [Knowledge Graph Building](../examples/building_knowledge_graphs.md) | Build and query knowledge graphs | Semantic data modeling |
| [Chain of Density](../tutorials/6-chain-of-density.ipynb) | Implement iterative summarization | Content distillation |
## Multi-Modal Examples
### Vision Processing
| Example | Description | Use Case |
|---------|-------------|----------|
| [Table Extraction](tables_from_vision.md) | Convert image tables to structured data | Data entry automation |
| [Table Extraction with GPT-4](extracting_tables.md) | Advanced table extraction | Complex table processing |
| [Receipt Information](extracting_receipts.md) | Extract data from receipt images | Expense management |
| [Slide Content Extraction](extract_slides.md) | Convert slides to structured text | Presentation analysis |
| [Image to Ad Copy](image_to_ad_copy.md) | Generate ad text from images | Marketing automation |
| [YouTube Clip Analysis](youtube_clips.md) | Extract info from video clips | Content moderation |
### Multi-Modal Processing
| Example | Description | Use Case |
|---------|-------------|----------|
| [Gemini Multi-Modal](multi_modal_gemini.md) | Process text, images, and other data | Mixed-media analysis |
## Data Tools
### Database Integration
| Example | Description | Use Case |
|---------|-------------|----------|
| [SQLModel Integration](sqlmodel.md) | Store AI-generated data in SQL databases | Persistent storage |
| [Pandas DataFrame](pandas_df.md) | Work with structured data in Pandas | Data analysis |
### Streaming and Processing
| Example | Description | Use Case |
|---------|-------------|----------|
| [Partial Response Streaming](partial_streaming.md) | Stream partial results in real-time | Interactive applications |
| [Self-Critique and Correction](self_critique.md) | Implement self-assessment | Quality improvement |
### API Integration
| Example | Description | Use Case |
|---------|-------------|----------|
| [Content Moderation](moderation.md) | Implement content filtering | Trust & safety |
| [Cost Optimization with Batch API](batch_job_oai.md) | Reduce API costs | Production efficiency |
| [Few-Shot Learning](examples.md) | Use contextual examples in prompts | Performance tuning |
### Observability & Tracing
| Example | Description | Use Case |
|---------|-------------|----------|
| [Langfuse Tracing](tracing_with_langfuse.md) | Open-source LLM engineering | Observability & Debugging
## Deployment Options
### Model Providers
| Example | Description | Use Case |
|---------|-------------|----------|
| [Groq Cloud API](groq.md) | High-performance inference | Low-latency applications |
| [Mistral/Mixtral Models](mistral.md) | Open-source model integration | Cost-effective deployment |
| [IBM watsonx.ai](watsonx.md) | Enterprise AI platform | Business applications |
### Local Deployment
| Example | Description | Use Case |
|---------|-------------|----------|
| [Ollama Integration](ollama.md) | Local open-source models | Privacy-focused applications |
## Stay Updated
Subscribe to our newsletter for updates on new features and usage tips:
<iframe src="https://embeds.beehiiv.com/2faf420d-8480-4b6e-8d6f-9c5a105f917a?slim=true" data-test-id="beehiiv-embed" height="52" frameborder="0" scrolling="no" style="margin: 0; border-radius: 0px !important; background-color: transparent;"></iframe>
Looking for more structured learning? Check out our [Tutorial series](../tutorials/index.md) for step-by-step guides.

View File

@@ -0,0 +1,419 @@
---
title: 'Visualizing Knowledge Graphs: A Guide to Complex Topics'
description: Learn how to create and update knowledge graphs using Python, OpenAI's API, Pydantic, and Graphviz for enhanced understanding of complex subjects.
---
# Visualizing Knowledge Graphs for Complex Topics
In this guide, you'll discover how to visualise a detailed knowledge graph when dealing with complex topics. We'll then move on to iteratively updating our knowledge graph with new information through a series of sequential api calls using only the Instructor library, Pydantic and Graphviz to visualise our graph.
!!! tips "Motivation"
Knowledge graphs offer a visually appealing and coherent way to understand complicated topics like quantum mechanics. By generating these graphs automatically, you can accelerate the learning process and make it easier to digest complex information.
## Defining the Structures
Let's model a knowledge graph with **`Node`** and **`Edge`** objects. **`Node`** objects represent key concepts or entities, while **`Edge`** objects indicate the relationships between them.
```python
from pydantic import BaseModel, Field
from typing import List
class Node(BaseModel, frozen=True):
id: int
label: str
color: str
class Edge(BaseModel, frozen=True):
source: int
target: int
label: str
color: str = "black"
class KnowledgeGraph(BaseModel):
nodes: List[Node] = Field(..., default_factory=list)
edges: List[Edge] = Field(..., default_factory=list)
```
## Generating Knowledge Graphs
The **`generate_graph`** function leverages OpenAI's API to generate a knowledge graph based on the input query.
```python hl_lines="8"
import instructor
# <%hide%>
from pydantic import BaseModel, Field
from typing import List
class Node(BaseModel, frozen=True):
id: int
label: str
color: str
class Edge(BaseModel, frozen=True):
source: int
target: int
label: str
color: str = "black"
class KnowledgeGraph(BaseModel):
nodes: List[Node] = Field(..., default_factory=list)
edges: List[Edge] = Field(..., default_factory=list)
# <%hide%>
# Adds response_model to ChatCompletion
# Allows the return of Pydantic model rather than raw JSON
client = instructor.from_provider("openai/gpt-5-nano")
def generate_graph(input) -> KnowledgeGraph:
return client.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": f"Help me understand the following by describing it as a detailed knowledge graph: {input}",
}
],
response_model=KnowledgeGraph,
) # type: ignore
```
## Visualizing the Graph
The **`visualize_knowledge_graph`** function uses the Graphviz library to render the generated knowledge graph.
```python
from graphviz import Digraph
# <%hide%>
from pydantic import BaseModel, Field
from typing import List
import instructor
class Node(BaseModel, frozen=True):
id: int
label: str
color: str
class Edge(BaseModel, frozen=True):
source: int
target: int
label: str
color: str = "black"
class KnowledgeGraph(BaseModel):
nodes: List[Node] = Field(..., default_factory=list)
edges: List[Edge] = Field(..., default_factory=list)
client = instructor.from_provider("openai/gpt-5-nano")
def generate_graph(input) -> KnowledgeGraph:
return client.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": f"Help me understand the following by describing it as a detailed knowledge graph: {input}",
}
],
response_model=KnowledgeGraph,
) # type: ignore
# <%hide%>
def visualize_knowledge_graph(kg: KnowledgeGraph):
dot = Digraph(comment="Knowledge Graph")
# Add nodes
for node in kg.nodes:
dot.node(str(node.id), node.label, color=node.color)
# Add edges
for edge in kg.edges:
dot.edge(str(edge.source), str(edge.target), label=edge.label, color=edge.color)
# Render the graph
dot.render("knowledge_graph.gv", view=True)
graph = generate_graph("Teach me about quantum mechanics")
visualize_knowledge_graph(graph)
```
![Knowledge Graph visualization showing interconnected concepts and relationships](knowledge_graph.png)
This will produce a visual representation of the knowledge graph, stored as "knowledge_graph.gv". You can open this file to explore the key concepts and their relationships in quantum mechanics.
## Iterative Updates
Now that we've seen how to generate a knowledge graph from a single input, let's see how we can iteratively update our knowledge graph with new information, or when information does not fit into a single prompt.
Let's take an easy example where we want to visualise the combined knowledge graph that the following sentences represent.
```python
text_chunks = [
"Jason knows a lot about quantum mechanics. He is a physicist. He is a professor",
"Professors are smart.",
"Sarah knows Jason and is a student of his.",
"Sarah is a student at the University of Toronto. and UofT is in Canada",
]
```
### Updating Our Data Model
To support our new iterative approach, we need to update our data model. We can do this by adding helper methods `update` and `draw` to our Pydantic models. These methods will simplify our code and allow us to easily visualize the knowledge graph.
In the `KnowledgeGraph` class, we have migrated the code from the `visualize_knowledge_graph` method and added new lists for nodes and edges.
```python
from pydantic import BaseModel, Field
from typing import List, Optional
class Node(BaseModel, frozen=True):
id: int
label: str
color: str
class Edge(BaseModel, frozen=True):
source: int
target: int
label: str
color: str = "black"
class KnowledgeGraph(BaseModel):
nodes: Optional[List[Node]] = Field(..., default_factory=list)
edges: Optional[List[Edge]] = Field(..., default_factory=list)
def update(self, other: "KnowledgeGraph") -> "KnowledgeGraph":
"""Updates the current graph with the other graph, deduplicating nodes and edges."""
return KnowledgeGraph(
nodes=list(set(self.nodes + other.nodes)),
edges=list(set(self.edges + other.edges)),
)
def draw(self, prefix: str = None):
dot = Digraph(comment="Knowledge Graph")
for node in self.nodes: # (1)!
dot.node(str(node.id), node.label, color=node.color)
for edge in self.edges: # (2)!
dot.edge(
str(edge.source), str(edge.target), label=edge.label, color=edge.color
)
dot.render(prefix, format="png", view=True)
```
1. We iterate through all the nodes in our graph and add them to the graph
2. We iterate through all the edges in our graph and add them to the graph
We can modify our `generate_graph` function to now take in a list of strings. At each step, it'll extract out the key insights from the sentences in the form of edges and nodes like we've seen before. We can then combine these new edges and nodes with our existing knowledge graph through iterative updates to our graph before arriving at our final result.
```python hl_lines="2 21-25 31-32"
from typing import List
# <%hide%>
from pydantic import BaseModel, Field
from typing import List, Optional
class Node(BaseModel, frozen=True):
id: int
label: str
color: str
class Edge(BaseModel, frozen=True):
source: int
target: int
label: str
color: str = "black"
class KnowledgeGraph(BaseModel):
nodes: Optional[List[Node]] = Field(..., default_factory=list)
edges: Optional[List[Edge]] = Field(..., default_factory=list)
def update(self, other: "KnowledgeGraph") -> "KnowledgeGraph":
"""Updates the current graph with the other graph, deduplicating nodes and edges."""
return KnowledgeGraph(
nodes=list(set(self.nodes + other.nodes)),
edges=list(set(self.edges + other.edges)),
)
def draw(self, prefix: str = None):
dot = Digraph(comment="Knowledge Graph")
for node in self.nodes: # (1)!
dot.node(str(node.id), node.label, color=node.color)
for edge in self.edges: # (2)!
dot.edge(
str(edge.source), str(edge.target), label=edge.label, color=edge.color
)
dot.render(prefix, format="png", view=True)
# <%hide%>
def generate_graph(input: List[str]) -> KnowledgeGraph:
cur_state = KnowledgeGraph() # (1)!
num_iterations = len(input)
for i, inp in enumerate(input):
new_updates = client.create(
model="gpt-3.5-turbo-16k",
messages=[
{
"role": "system",
"content": """You are an iterative knowledge graph builder.
You are given the current state of the graph, and you must append the nodes and edges
to it Do not procide any duplcates and try to reuse nodes as much as possible.""",
},
{
"role": "user",
"content": f"""Extract any new nodes and edges from the following:
# Part {i}/{num_iterations} of the input:
{inp}""",
},
{
"role": "user",
"content": f"""Here is the current state of the graph:
{cur_state.model_dump_json(indent=2)}""",
}, # (2)!
],
response_model=KnowledgeGraph,
) # type: ignore
# Update the current state
cur_state = cur_state.update(new_updates) # (3)!
cur_state.draw(prefix=f"iteration_{i}")
return cur_state
```
1. We first initialise an empty `KnowledgeGraph`. In this state, it has zero nodes and edges
2. We then add in the current state of the graph into the prompt so that the model knows what new information needs to be added
3. We then update the nodes and edges of our graph with the information that our model has returned before visualizing the new changes
Once we've done this, we can now run this new `generate_graph` function with the following two lines.
```python
# <%hide%>
from pydantic import BaseModel, Field
from typing import List, Optional
import instructor
from graphviz import Digraph
class Node(BaseModel, frozen=True):
id: int
label: str
color: str
class Edge(BaseModel, frozen=True):
source: int
target: int
label: str
color: str = "black"
class KnowledgeGraph(BaseModel):
nodes: Optional[List[Node]] = Field(..., default_factory=list)
edges: Optional[List[Edge]] = Field(..., default_factory=list)
def update(self, other: "KnowledgeGraph") -> "KnowledgeGraph":
"""Updates the current graph with the other graph, deduplicating nodes and edges."""
return KnowledgeGraph(
nodes=list(set(self.nodes + other.nodes)),
edges=list(set(self.edges + other.edges)),
)
def draw(self, prefix: str = None):
dot = Digraph(comment="Knowledge Graph")
for node in self.nodes: # (1)!
dot.node(str(node.id), node.label, color=node.color)
for edge in self.edges: # (2)!
dot.edge(
str(edge.source), str(edge.target), label=edge.label, color=edge.color
)
dot.render(prefix, format="png", view=True)
client = instructor.from_provider("openai/gpt-5-nano")
def generate_graph(input: List[str]) -> KnowledgeGraph:
cur_state = KnowledgeGraph() # (1)!
num_iterations = len(input)
for i, inp in enumerate(input):
new_updates = client.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": """You are an iterative knowledge graph builder.
You are given the current state of the graph, and you must append the nodes and edges
to it Do not procide any duplcates and try to reuse nodes as much as possible.""",
},
{
"role": "user",
"content": f"""Extract any new nodes and edges from the following:
# Part {i}/{num_iterations} of the input:
{inp}""",
},
{
"role": "user",
"content": f"""Here is the current state of the graph:
{cur_state.model_dump_json(indent=2)}""",
}, # (2)!
],
response_model=KnowledgeGraph,
) # type: ignore
# Update the current state
cur_state = cur_state.update(new_updates) # (3)!
cur_state.draw(prefix=f"iteration_{i}")
return cur_state
# <%hide%>
text_chunks = [
"Jason knows a lot about quantum mechanics. He is a physicist. He is a professor",
"Professors are smart.",
"Sarah knows Jason and is a student of his.",
"Sarah is a student at the University of Toronto. and UofT is in Canada",
]
graph: KnowledgeGraph = generate_graph(text_chunks)
graph.draw(prefix="final")
```
## Conclusion
We've seen how we can use `Instructor` to obtain structured outputs from the OpenAI LLM API but you could use that for any of the other open-source models that the library is compatible with. If you enjoy the content or want to try out `Instructor` check out the [github](https://github.com/jxnl/instructor) and don't forget to give us a star!

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

View File

@@ -0,0 +1,133 @@
---
title: Classifying Confidential Data with Local AI Models
description: Learn to classify private documents securely using Llama-cpp-python with instructor while maintaining data privacy and local infrastructure.
---
# Leveraging Local Models for Classifying Private Data
In this article, we'll show you how to use Llama-cpp-python with instructor for classification. This is a perfect use-case for users who want to ensure that confidential documents are handled securely without ever leaving your own infrastructure.
## Setup
Let's start by installing the required libraries in your local python environment. This might take a while since we'll need to build and compile `llama-cpp` for your specific environment.
```bash
pip install instructor pydantic
```
Next, we'll install `llama-cpp-python` which is a python package that allows us to use llama-cpp with our python scripts.
For this tutorial, we'll be using `Mistral-7B-Instruct-v0.2-GGUF` by `TheBloke` to do our function calls. This will require around 6GB of RAM and a GPU.
We can install the package by running the following command
```bash
CMAKE_ARGS="-DGGML_CUDA=on" pip install llama-cpp-python
```
!!! note "Don't have a GPU?"
If you don't have a GPU, we recommend using the `Qwen2-0.5B-Instruct` model instead and compiling llama-cpp-python to use `OpenBLAS`. This allows you to run the program using your CPU instead.
You can compile `llama-cpp-python` with `OpenBLAS` support by running the command
```bash
CMAKE_ARGS="-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS" pip install llama-cpp-python
```
## Using `LLama-cpp-python`
Here's an example of how to implement a system for handling confidential document queries using local models:
```python hl_lines="7-12 14-16 43-52"
from llama_cpp import Llama # type: ignore
import instructor
from pydantic import BaseModel
from enum import Enum
from typing import Optional
llm = Llama.from_pretrained( # type: ignore
repo_id="TheBloke/Mistral-7B-Instruct-v0.2-GGUF", # (1)!
filename="*Q4_K_M.gguf",
verbose=False, # (2)!
n_gpu_layers=-1, # (3)!
)
create = instructor.patch(
create=llm.create_chat_completion_openai_v1, # type: ignore # (4)!
)
# Define query types for document-related inquiries
class QueryType(str, Enum):
DOCUMENT_CONTENT = "document_content"
LAST_MODIFIED = "last_modified"
ACCESS_PERMISSIONS = "access_permissions"
RELATED_DOCUMENTS = "related_documents"
# Define the structure for query responses
class QueryResponse(BaseModel):
query_type: QueryType
response: str
additional_info: Optional[str] = None
def process_confidential_query(query: str) -> QueryResponse:
prompt = f"""Analyze the following confidential document query and provide an appropriate response:
Query: {query}
Determine the type of query (document content, last modified, access permissions, or related documents),
provide a response, and include a confidence score and any additional relevant information.
Remember, you're handling confidential data, so be cautious about specific details.
"""
return create(
response_model=QueryResponse, # (5)!
messages=[
{
"role": "system",
"content": "You are a secure AI assistant trained to handle confidential document queries.",
},
{"role": "user", "content": prompt},
],
)
# Sample confidential document queries
confidential_queries = [
"What are the key findings in the Q4 financial report?",
"Who last accessed the merger proposal document?",
"What are the access permissions for the new product roadmap?",
"Are there any documents related to Project X's budget forecast?",
"When was the board meeting minutes document last updated?",
]
# Process each query and print the results
for query in confidential_queries:
response: QueryResponse = process_confidential_query(query)
print(f"{query} : {response.query_type}")
"""
#> What are the key findings in the Q4 financial report? : document_content
#> Who last accessed the merger proposal document? : access_permissions
#> What are the access permissions for the new product roadmap? : access_permissions
#> Are there any documents related to Project X's budget forecast? : document_content
#> When was the board meeting minutes document last updated? : last_modified
"""
```
1. We load in the model from Hugging Face and cache it locally. This makes it quick and easy for us to experiment with different model configurations and types.
2. We can set `verbose` to be `True` to log out all of the output from `llama.cpp`. This helps if you're trying to debug specific issues
3. If you have a GPU with limited memory, set `n_gpu` to a lower number (Eg. 10 ). We've set it here to `-1` so that all of the model layers are loaded on the GPU by default.
4. Now make sure to patch the client with the `create_chat_completion_openai_v1` api which is OpenAI compatible
5. Pass in the response model as a parameter just like any other inference client we support
## Conclusion
`instructor` provides a robust solution for organizations needing to handle confidential document queries locally. By processing these queries on your own hardware, you can leverage advanced AI capabilities while maintaining the highest standards of data privacy and security.
But this goes far beyond just simple confidential documents, using local models unlocks a whole new world of interesting use-cases, fine-tuned specialist models and more!

View File

@@ -0,0 +1,50 @@
---
title: Using MistralAI for Structured Outputs
description: Learn how to use MistralAI models for inference, including setup, API key generation, and example code.
---
# Structured Outputs using Mistral
You can use MistralAI models for inference with Instructor using `from_provider`.
The examples use `mistral-large-latest`.
## MistralAI API
To use mistral you need to obtain a mistral API key.
Goto [mistralai](https://mistral.ai/) click on Build Now and login. Select API Keys from the left menu and then select
Create API key to create a new key.
## Use example
Some pip packages need to be installed to use the example:
```
pip install instructor mistralai pydantic
```
You need to export the mistral API key:
```
export MISTRAL_API_KEY=<your-api-key>
```
An example:
```python
import instructor
from pydantic import BaseModel
class UserDetails(BaseModel):
name: str
age: int
# Using from_provider (recommended)
client = instructor.from_provider("mistral/mistral-large-latest")
resp = client.create(
response_model=UserDetails,
messages=[{"role": "user", "content": "Jason is 10"}],
temperature=0,
)
print(resp)
#> name='Jason' age=10
# output: UserDetails(name='Jason', age=10)
```

View File

@@ -0,0 +1,58 @@
---
title: OpenAI Moderation Example for Content Compliance
description: Learn how to use OpenAI's moderation endpoint to filter harmful content and ensure compliance with usage policies.
---
# OpenAI Moderation
This example uses OpenAI's moderation endpoint to check content compliance with OpenAI's usage policies. It can identify and filter harmful content that violates the policies.
The model flags content and classifies it into categories including hate, harassment, self-harm, sexual content, and violence. Each category has subcategories for detailed classification.
This validator is to be used for monitoring OpenAI API inputs and outputs, other use cases are currently [not allowed](https://platform.openai.com/docs/guides/moderation/overview).
## Incorporating OpenAI moderation validator
The following code defines a function to validate content using OpenAI's Moderation endpoint. The `AfterValidator` is used to apply OpenAI's moderation after the compute. This moderation checks if the content complies with OpenAI's usage policies and flags any harmful content. Here's how it works:
1. Generate the OpenAI client and patch it with the `instructor`. Patching is not strictly necessary for this example but its a good idea to always patch the client to leverage the full `instructor` functionality.
2. Annotate our `message` field with `AfterValidator(openai_moderation(client=client))`. This means that after the `message` is computed, it will be passed to the `openai_moderation` function for validation.
```python
import instructor
from instructor import openai_moderation
from typing_extensions import Annotated
from pydantic import BaseModel, AfterValidator
client = instructor.from_provider("openai/gpt-5-nano")
class Response(BaseModel):
message: Annotated[str, AfterValidator(openai_moderation(client=client))]
try:
Response(message="I want to make them suffer the consequences")
except Exception as e:
print(e)
"""
1 validation error for Response
message
Value error, `I want to make them suffer the consequences` was flagged for violence [type=value_error, input_value='I want to make them suffer the consequences', input_type=str]
For further information visit https://errors.pydantic.dev/2.9/v/value_error
"""
try:
Response(message="I want to hurt myself.")
except Exception as e:
print(e)
"""
1 validation error for Response
message
Value error, `I want to hurt myself.` was flagged for self_harm, self_harm_intent, self-harm, self-harm/intent [type=value_error, input_value='I want to hurt myself.', input_type=str]
For further information visit https://errors.pydantic.dev/2.9/v/value_error
"""
```

View File

@@ -0,0 +1,195 @@
---
title: Utilizing Gemini for Multi-Modal Data Processing with Audio Files
description: Learn how to use Gemini with Google Generative AI to process audio files efficiently in multi-modal applications.
---
# Using Gemini with Multi Modal Data
This tutorial shows how to use `instructor` with `google-generativeai` to work with multi-modal data. In this example, we'll demonstrate three ways to work with audio files.
We'll be using this [recording](https://storage.googleapis.com/generativeai-downloads/data/State_of_the_Union_Address_30_January_1961.mp3) that's taken from the [Google Generative AI cookbook](https://github.com/google-gemini/cookbook/blob/main/quickstarts/Audio.ipynb).
## Normal Message
The first way to work with audio files is to upload the entire audio file and pass it into the LLM as a normal message. This is the easiest way to get started and doesn't require any special setup.
```python
# <%hide%>
import requests
from pydub import AudioSegment
# Download the audio file
url = "https://storage.googleapis.com/generativeai-downloads/data/State_of_the_Union_Address_30_January_1961.mp3"
response = requests.get(url)
# Save the audio file locally
with open("sample.mp3", "wb") as file:
file.write(response.content)
sound = AudioSegment.from_mp3("sample.mp3") # (2)!
sound = sound[:60000]
sound.export(
"sample.mp3", format="mp3"
) # Save the processed audio segment as sample.mp3
# <%hide>
import instructor
import google.generativeai as genai
from pydantic import BaseModel
client = instructor.from_provider("google/gemini-2.5-flash"),
mode=instructor.Mode.JSON, # (1)!
)
mp3_file = genai.upload_file("./sample.mp3") # (2)!
class Description(BaseModel):
description: str
resp = client.create(
response_model=Description,
messages=[
{
"role": "user",
"content": "Summarize what's happening in this audio file and who the main speaker is",
},
{
"role": "user",
"content": mp3_file, # (3)!
},
],
)
print(resp)
"""
description = 'The main speaker is President John F. Kennedy, giving his State of the Union address to a joint session of Congress. He is speaking in the House of Representatives in Washington, D.C. on January 30th, 1961. He is thanking the members of Congress for their knowledge and inspiration.'
"""
```
1. Make sure to set the mode to `Mode.JSON` (replaces deprecated `GEMINI_JSON`), this is important because Tool Calling doesn't work with multi-modal inputs.
2. Use `genai.upload_file` to upload your file. If you've already uploaded the file, you can get it by using `genai.get_file`
3. Pass in the file object as any normal user message
## Inline Audio Segment
!!! note "Maximum File Size"
When uploading and working with audio, there is a maximum file size that we can upload to the api as an inline segment. You'll know when this error is thrown below.
```
google.api_core.exceptions.InvalidArgument: 400 Request payload size exceeds the limit: 20971520 bytes. Please upload your files with the File API instead.`f = genai.upload_file(path); m.generate_content(['tell me about this file:', f])`
```
When it comes to video files, we recommend using the file.upload method as shown in the example above.
Secondly, we can also pass in a audio segment as a normal message as an inline object as shown below. This requires you to install the `pydub` library in order to do so.
```python
import instructor
import google.generativeai as genai
from pydantic import BaseModel
from pydub import AudioSegment
client = instructor.from_provider("google/gemini-2.5-flash"),
mode=instructor.Mode.JSON, # (1)!
)
sound = AudioSegment.from_mp3("sample.mp3") # (2)!
sound = sound[:60000]
class Transcription(BaseModel):
summary: str
exact_transcription: str
resp = client.create(
response_model=Transcription,
messages=[
{
"role": "user",
"content": "Please transcribe this recording",
},
{
"role": "user",
"content": {
"mime_type": "audio/mp3",
"data": sound.export().read(), # (3)!
},
},
],
)
print(resp)
"""
summary='President addresses the joint session of Congress, reflecting on his first time taking the oath of federal office and the knowledge and inspiration gained.' exact_transcription="The President's state of the union address to a joint session of the Congress from the rostrum of the House of Representatives, Washington D.C. January 30th 1961 Speaker, Mr Vice President members of the Congress It is a pleasure to return from whence I came You are among my oldest friends in Washington And this house is my oldest home It was here it was here more than 14 years ago that I first took the oath of federal office It was here for 14 years that I gained both knowledge and inspiration from members of both"
"""
#> summary='President delivers a speech to a joint session of Congress,
#> highlighting his history in the House of Representatives and thanking
#> the members of Congress for their guidance.',
# >
#> exact_transcription="The President's State of the Union address to a
#> joint session of the Congress from the rostrum of the House of
#> Representatives, Washington DC, January 30th 1961. Mr. Speaker, Mr.
#> Vice-President, members of the Congress, it is a pleasure to return
#> from whence I came. You are among my oldest friends in Washington,
#> and this house is my oldest home. It was here that I first took the
#> oath of federal office. It was here for 14 years that I gained both
#> knowledge and inspiration from members of both"
```
1. Make sure to set the mode to `Mode.JSON` (replaces deprecated `GEMINI_JSON`), this is important because Tool Calling doesn't work with multi-modal inputs.
2. Use `AudioSegment.from_mp3` to load your audio file.
3. Pass in the audio data as bytes to the `data` field using the content as a dictionary with the right content `mime_type` and `data` as bytes
## Lists of Content
We also support passing in these as a single list as per the documentation for `google-generativeai`. Here's how to do so with a audio segment snippet from the same recording.
Note that the list can contain normal user messages as well as file objects. It's incredibly flexible.
```python
import instructor
import google.generativeai as genai
from pydantic import BaseModel
client = instructor.from_provider("google/gemini-2.5-flash"),
mode=instructor.Mode.JSON, # (1)!
)
mp3_file = genai.upload_file("./sample.mp3") # (2)!
class Description(BaseModel):
description: str
content = [
"Summarize what's happening in this audio file and who the main speaker is",
mp3_file, # (3)!
]
resp = client.create(
response_model=Description,
messages=[
{
"role": "user",
"content": content,
}
],
)
print(resp)
"""
description = 'President John F. Kennedy delivers his State of the Union address to the Congress on January 30, 1961. The speech was delivered at the rostrum of the House of Representatives in Washington, D.C.'
"""
```
1. Make sure to set the mode to `Mode.JSON` (replaces deprecated `GEMINI_JSON`), this is important because Tool Calling doesn't work with multi-modal inputs.
2. Upload the file using `genai.upload_file` or get the file using `genai.get_file`
3. Pass in the content as a list containing the normal user message and the file object.

View File

@@ -0,0 +1,65 @@
---
title: Multi-Label Classification - Support Ticket Categorization
description: Implement multi-label classification with Instructor for support tickets. Assign multiple categories like ACCOUNT, BILLING, and GENERAL_QUERY simultaneously.
---
For multi-label classification, we introduce a new enum class and a different Pydantic model to handle multiple labels.
```python
import instructor
from typing import List, Literal
from pydantic import BaseModel, Field
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
LABELS = Literal["ACCOUNT", "BILLING", "GENERAL_QUERY"]
class MultiClassPrediction(BaseModel):
"""
A few-shot example of multi-label classification:
Examples:
- "My account is locked and I can't access my billing info.": ACCOUNT, BILLING
- "I need help with my subscription.": ACCOUNT
- "How do I change my payment method?": BILLING
- "Can you tell me the status of my order?": BILLING
- "I have a question about the product features.": GENERAL_QUERY
"""
labels: List[LABELS] = Field(
...,
description="Only select the labels that apply to the support ticket.",
)
def multi_classify(data: str) -> MultiClassPrediction:
return client.create(
model="gpt-4o-mini",
response_model=MultiClassPrediction,
messages=[
{
"role": "system",
"content": f"You are a support agent at a tech company. Only select the labels that apply to the support ticket.",
},
{
"role": "user",
"content": f"Classify the following support ticket: <text>{data}</text>",
},
],
) # type: ignore
if __name__ == "__main__":
ticket = "My account is locked and I can't access my billing info."
prediction = multi_classify(ticket)
assert {"ACCOUNT", "BILLING"} == {label for label in prediction.labels}
print("input:", ticket)
#> input: My account is locked and I can't access my billing info.
print("labels:", LABELS)
#> labels: typing.Literal['ACCOUNT', 'BILLING', 'GENERAL_QUERY']
print("prediction:", prediction)
#> prediction: labels=['ACCOUNT', 'BILLING']
```

View File

@@ -0,0 +1,119 @@
---
title: Harnessing Structured Outputs with Ollama and Instructor
description: Discover how to utilize Ollama's Instructor library for structured outputs in LLM applications using Pydantic models.
---
## See Also
- [Ollama Integration](../integrations/ollama.md) - Complete Ollama setup guide
- [Open Source Models](./open_source.md) - More open-source model examples
- [Local Deployment](./index.md#local-deployment) - Local model deployment options
- [Response Models](../concepts/models.md) - Working with Pydantic models
# Structured Outputs with Ollama
Open-source Large Language Models (LLMs) are rapidly gaining popularity in the AI community. With the recent release of Ollama's OpenAI compatibility layer, it has become possible to obtain structured outputs using JSON schema from these open-source models. This development opens up exciting possibilities for developers and researchers alike.
In this blog post, we'll explore how to effectively utilize the Instructor library with Ollama to harness the power of structured outputs with [Pydantic models](../concepts/models.md). We'll cover everything from setup to implementation, providing you with practical insights and code examples.
## Why use Instructor?
Instructor offers several key benefits:
- :material-code-tags: **Simple API with Full Prompt Control**: Instructor provides a straightforward API that gives you complete ownership and control over your prompts. This allows for fine-tuned customization and optimization of your LLM interactions. [:octicons-arrow-right-16: Explore Concepts](../concepts/models.md)
- :material-refresh: **Reasking and Validation**: Automatically reask the model when validation fails, ensuring high-quality outputs. Leverage Pydantic's validation for robust error handling. [:octicons-arrow-right-16: Learn about Reasking](../concepts/reask_validation.md)
- :material-repeat-variant: **Streaming Support**: Stream partial results and iterables with ease, allowing for real-time processing and improved responsiveness in your applications. [:octicons-arrow-right-16: Learn about Streaming](../concepts/partial.md)
- :material-code-braces: **Powered by Type Hints**: Leverage Pydantic for schema validation, prompting control, less code, and IDE integration. [:octicons-arrow-right-16: Learn more](https://docs.pydantic.dev/)
- :material-lightning-bolt: **Simplified LLM Interactions**: Support for various LLM providers including OpenAI, Anthropic, Google, Vertex AI, Mistral/Mixtral, Anyscale, Ollama, llama-cpp-python, Cohere, and LiteLLM. [:octicons-arrow-right-16: See Examples](../examples/index.md)
For more details on these features, check out the [Concepts](../concepts/models.md) section of the documentation.
## Patching
Instructor's [patch](../concepts/patching.md) enhances an openai api with the following features:
- `response_model` in `create` calls that returns a pydantic model
- `max_retries` in `create` calls that retries the call if it fails by using a backoff strategy
!!! note "Learn More"
To learn more, please refer to the [docs](../index.md). To understand the benefits of using Pydantic with Instructor, visit the tips and tricks section of the [why use Pydantic](../why.md) page.
## Ollama
Start by downloading [Ollama](https://ollama.ai/download), and then pull a model such as Llama 3 or Mistral.
!!! tip "Make sure you update your `ollama` to the latest version!"
```
ollama pull llama3
```
```python
import instructor
from pydantic import BaseModel, Field
from typing import List
class Character(BaseModel):
name: str
age: int
fact: List[str] = Field(..., description="A list of facts about the character")
# Use from_provider with base_url for Ollama
client = instructor.from_provider(
"ollama/llama3",
base_url="http://localhost:11434/v1",
mode=instructor.Mode.JSON,
)
resp = client.create(
model="llama3",
messages=[
{
"role": "user",
"content": "Tell me about the Harry Potter",
}
],
response_model=Character,
)
print(resp.model_dump_json(indent=2))
"""
{
"name": "Harry James Potter",
"age": 37,
"fact": [
"He is the chosen one.",
"He has a lightning-shaped scar on his forehead.",
"He is the son of James and Lily Potter.",
"He attended Hogwarts School of Witchcraft and Wizardry.",
"He is a skilled wizard and sorcerer.",
"He fought against Lord Voldemort and his followers.",
"He has a pet owl named Snowy."
]
}
"""
```
This example demonstrates how to use Instructor with Ollama, a local LLM server, to generate structured outputs. By leveraging Instructor's capabilities, we can easily extract structured information from the LLM's responses, making it simpler to work with the generated data in our applications.
## Further Reading
To explore more about Instructor and its various applications, consider checking out the following resources:
1. [Why use Instructor?](../why.md) - Learn about the benefits and use cases of Instructor.
2. [Concepts](../concepts/models.md) - Dive deeper into the core concepts of Instructor, including models, retrying, and validation.
3. [Examples](../examples/index.md) - Explore our comprehensive collection of examples and integrations with various LLM providers.
4. [Tutorials](../tutorials/1-introduction.ipynb) - Step-by-step tutorials to help you get started with Instructor.
5. [Learn Prompting](../prompting/index.md) - Techniques and strategies for effective prompt engineering with Instructor.
By exploring these resources, you'll gain a comprehensive understanding of Instructor's capabilities and how to leverage them in your projects.

View File

@@ -0,0 +1,17 @@
---
title: Open Source Model Providers for Chat API
description: Explore tested open source models compatible with the OpenAI chat API, including OpenRouter, Perplexity, and RunPod LLMs.
---
# Instructor with open source models
Instructor works with Open source model providers that support the [OpenAI API chat endpoint](https://platform.openai.com/docs/api-reference/chat)
See examples README [here](https://github.com/jxnl/instructor/tree/main/examples/open_source_examples)
# Currently tested open source model providers
- [OpenRouter](https://openrouter.ai/)
- [Perplexity](https://www.perplexity.ai/)
- [RunPod TheBloke LLMs](https://github.com/TheBlokeAI/dockerLLM/blob/main/README_Runpod_LocalLLMsUI.md) **
** This utilizes text-generation-webui w/ Openai plugin under the hood.

View File

@@ -0,0 +1,139 @@
---
title: Extracting DataFrames from Markdown using Pandas
description: Learn how to extract and convert Markdown tables directly into Pandas DataFrames in Python.
---
# Extracting directly to a DataFrame
In this example we'll show you how to extract directly to a `pandas.DataFrame`
```python
from io import StringIO
from typing import Annotated, Any
from pydantic import (
BaseModel,
BeforeValidator,
PlainSerializer,
InstanceOf,
WithJsonSchema,
)
import pandas as pd
import instructor
import instructor
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_provider("openai/gpt-5-nano")
def extract_df(data: str) -> pd.DataFrame:
return client.create(
model="gpt-3.5-turbo",
response_model=MarkdownDataFrame,
messages=[
{
"role": "system",
"content": "You are a data extraction system, table of writing perfectly formatted markdown tables.",
},
{
"role": "user",
"content": f"Extract the data into a table: {data}",
},
],
)
class Table(BaseModel):
title: str
data: MarkdownDataFrame
def extract_table(data: str) -> Table:
return client.create(
model="gpt-3.5-turbo",
response_model=Table,
messages=[
{
"role": "system",
"content": "You are a data extraction system, table of writing perfectly formatted markdown tables.",
},
{
"role": "user",
"content": f"Extract the data into a table: {data}",
},
],
)
if __name__ == "__main__":
df = extract_df(
"""Create a table of the last 5 presidents of the United States,
including their party and the years they served."""
)
assert isinstance(df, pd.DataFrame)
print(df)
"""
Party Years Served
President
Joe Biden Democrat 2021 - Present
Donald Trump Republican 2017 - 2021
Barack Obama Democrat 2009 - 2017
George W. Bush Republican 2001 - 2009
Bill Clinton Democrat 1993 - 2001
"""
table = extract_table(
"""Create a table of the last 5 presidents of the United States,
including their party and the years they served."""
)
assert isinstance(table, Table)
assert isinstance(table.data, pd.DataFrame)
print(table.title)
#> Last 5 Presidents of the United States
print(table.data)
"""
Party Years Served
President
Joe Biden Democratic 2021-2025
Donald Trump Republican 2017-2021
Barack Obama Democratic 2009-2017
George W. Bush Republican 2001-2009
Bill Clinton Democratic 1993-2001
"""
```
Notice that you can extract both the raw `MarkdownDataFrame` or a more complex structure like `Table` which includes a title and the data as a DataFrame. You can even request `Iterable[Table]` to get multiple tables in a single response!

View File

@@ -0,0 +1,68 @@
---
title: Partial Response Streaming - Field-Level Updates
description: Stream partial responses with Instructor for real-time UI updates. Get incremental snapshots of response models as fields are generated.
---
# Streaming Partial Responses
Field level streaming provides incremental snapshots of the current state of the response model that are immediately useable. This approach is particularly relevant in contexts like rendering UI components.
Instructor supports this pattern by making use of `Partial[T]`. This lets us dynamically create a new class that treats all of the original model's fields as `Optional`.
```python
import instructor
from pydantic import BaseModel
from typing import List
client = instructor.from_provider("openai/gpt-5-nano")
text_block = """
In our recent online meeting, participants from various backgrounds joined to discuss the upcoming tech conference. The names and contact details of the participants were as follows:
- Name: John Doe, Email: johndoe@email.com, Twitter: @TechGuru44
- Name: Jane Smith, Email: janesmith@email.com, Twitter: @DigitalDiva88
- Name: Alex Johnson, Email: alexj@email.com, Twitter: @CodeMaster2023
During the meeting, we agreed on several key points. The conference will be held on March 15th, 2024, at the Grand Tech Arena located at 4521 Innovation Drive. Dr. Emily Johnson, a renowned AI researcher, will be our keynote speaker.
The budget for the event is set at $50,000, covering venue costs, speaker fees, and promotional activities. Each participant is expected to contribute an article to the conference blog by February 20th.
A follow-up meetingis scheduled for January 25th at 3 PM GMT to finalize the agenda and confirm the list of speakers.
"""
class User(BaseModel):
name: str
email: str
twitter: str
class MeetingInfo(BaseModel):
users: List[User]
date: str
location: str
budget: int
deadline: str
PartialMeetingInfo = instructor.Partial[MeetingInfo]
extraction_stream = client.create(
model="gpt-4",
response_model=PartialMeetingInfo,
messages=[
{
"role": "user",
"content": f"Get the information about the meeting and the users {text_block}",
},
],
stream=True,
) # type: ignore
from rich.console import Console
console = Console()
for extraction in extraction_stream:
obj = extraction.model_dump()
console.clear()
console.print(obj)
```

View File

@@ -0,0 +1,221 @@
---
title: Extracting and Scrubbing PII Data with OpenAI
description: Learn to extract and sanitize Personally Identifiable Information (PII) from documents using OpenAI's ChatCompletion model and Python.
---
# PII Data Extraction and Scrubbing
## Overview
This example demonstrates the usage of OpenAI's ChatCompletion model for the extraction and scrubbing of Personally Identifiable Information (PII) from a document. The code defines Pydantic models to manage the PII data and offers methods for both extraction and sanitation.
## Defining the Structures
First, Pydantic models are defined to represent the PII data and the overall structure for PII data extraction.
```python
from typing import List
from pydantic import BaseModel
# Define Schemas for PII data
class Data(BaseModel):
index: int
data_type: str
pii_value: str
class PIIDataExtraction(BaseModel):
"""
Extracted PII data from a document, all data_types should try to have consistent property names
"""
private_data: List[Data]
def scrub_data(self, content: str) -> str:
"""
Iterates over the private data and replaces the value with a placeholder in the form of
<{data_type}_{i}>
"""
for i, data in enumerate(self.private_data):
content = content.replace(data.pii_value, f"<{data.data_type}_{i}>")
return content
```
## Extracting PII Data
The OpenAI API is utilized to extract PII information from a given document.
```python
import instructor
# <%hide%>
from typing import List
from pydantic import BaseModel
# Define Schemas for PII data
class Data(BaseModel):
index: int
data_type: str
pii_value: str
class PIIDataExtraction(BaseModel):
"""
Extracted PII data from a document, all data_types should try to have consistent property names
"""
private_data: List[Data]
def scrub_data(self, content: str) -> str:
"""
Iterates over the private data and replaces the value with a placeholder in the form of
<{data_type}_{i}>
"""
for i, data in enumerate(self.private_data):
content = content.replace(data.pii_value, f"<{data.data_type}_{i}>")
return content
# <%hide%>
client = instructor.from_provider("openai/gpt-5-nano")
EXAMPLE_DOCUMENT = """
# Fake Document with PII for Testing PII Scrubbing Model
# (The content here)
"""
pii_data = client.create(
model="gpt-4o-mini",
response_model=PIIDataExtraction,
messages=[
{
"role": "system",
"content": "You are a world class PII scrubbing model, Extract the PII data from the following document",
},
{
"role": "user",
"content": EXAMPLE_DOCUMENT,
},
],
) # type: ignore
print("Extracted PII Data:")
#> Extracted PII Data:
print(pii_data.model_dump_json())
"""
{"private_data":[{"index":1,"data_type":"Name","pii_value":"John Doe"},{"index":2,"data_type":"Email","pii_value":"john.doe@example.com"},{"index":3,"data_type":"Phone","pii_value":"+1234567890"},{"index":4,"data_type":"Address","pii_value":"1234 Elm Street, Springfield, IL 62704"},{"index":5,"data_type":"SSN","pii_value":"123-45-6789"}]}
"""
```
### Output of Extracted PII Data
```json
{
"private_data": [
{
"index": 0,
"data_type": "date",
"pii_value": "01/02/1980"
},
{
"index": 1,
"data_type": "ssn",
"pii_value": "123-45-6789"
},
{
"index": 2,
"data_type": "email",
"pii_value": "john.doe@email.com"
},
{
"index": 3,
"data_type": "phone",
"pii_value": "555-123-4567"
},
{
"index": 4,
"data_type": "address",
"pii_value": "123 Main St, Springfield, IL, 62704"
}
]
}
```
## Scrubbing PII Data
After extracting the PII data, the `scrub_data` method is used to sanitize the document.
```python
# <%hide%>
from typing import List
from pydantic import BaseModel
# Define Schemas for PII data
class Data(BaseModel):
index: int
data_type: str
pii_value: str
class PIIDataExtraction(BaseModel):
"""
Extracted PII data from a document, all data_types should try to have consistent property names
"""
private_data: List[Data]
def scrub_data(self, content: str) -> str:
"""
Iterates over the private data and replaces the value with a placeholder in the form of
<{data_type}_{i}>
"""
for i, data in enumerate(self.private_data):
content = content.replace(data.pii_value, f"<{data.data_type}_{i}>")
return content
pii_data = PIIDataExtraction(
private_data=[
{"index": 0, "data_type": "date", "pii_value": "01/02/1980"},
{"index": 1, "data_type": "ssn", "pii_value": "123-45-6789"},
{"index": 2, "data_type": "email", "pii_value": "john.doe@email.com"},
{"index": 3, "data_type": "phone", "pii_value": "555-123-4567"},
{
"index": 4,
"data_type": "address",
"pii_value": "123 Main St, Springfield, IL, 62704",
},
]
)
EXAMPLE_DOCUMENT = """
# Fake Document with PII for Testing PII Scrubbing Model
# He was born on 01/02/1980. His social security number is 123-45-6789. He has been using the email address john.doe@email.com for years, and he can always be reached at 555-123-4567.
"""
# <%hide%>
print("Scrubbed Document:")
#> Scrubbed Document:
print(pii_data.scrub_data(EXAMPLE_DOCUMENT))
"""
# Fake Document with PII for Testing PII Scrubbing Model
# He was born on <date_0>. His social security number is <ssn_1>. He has been using the email address <email_2> for years, and he can always be reached at <phone_3>.
"""
```
### Output of Scrubbed Document
```plaintext
# Fake Document with PII for Testing PII Scrubbing Model
## Personal Story
John Doe was born on <date_0>. His social security number is <ssn_1>. He has been using the email address <email_2> for years, and he can always be reached at <phone_3>.
## Residence
John currently resides at <address_4>. He's been living there for about 5 years now.
```

View File

@@ -0,0 +1,196 @@
---
title: Query Planning with Instructor - Complex Task Decomposition
description: Plan and execute complex query plans using Instructor. Break down complex questions into sub-questions with dependencies for systematic information gathering.
---
# Planning and Executing a Query Plan
This example demonstrates how to use the OpenAI Function Call ChatCompletion model to plan and execute a query plan in a question-answering system. By breaking down a complex question into smaller sub-questions with defined dependencies using [lists](../concepts/lists.md), the system can systematically gather the necessary information to answer the main question similar to [knowledge graph extraction](../examples/knowledge_graph.md).
!!! tips "Motivation"
The goal of this example is to showcase how query planning can be used to handle complex questions, facilitate iterative information gathering, automate workflows, and optimize processes. By leveraging the OpenAI Function Call model, you can design and execute a structured plan to find answers effectively.
**Use Cases:**
* Complex question answering
* Iterative information gathering
* Workflow automation
* Process optimization
With the OpenAI Function Call model, you can customize the planning process and integrate it into your specific application to meet your unique requirements.
## Defining the Structures
Let's define the necessary Pydantic models to represent the query plan and the queries.
```python
from typing import List, Literal
from pydantic import Field, BaseModel
class Query(BaseModel):
"""Class representing a single question in a query plan."""
id: int = Field(..., description="Unique id of the query")
question: str = Field(
...,
description="Question asked using a question answering system",
)
dependencies: List[int] = Field(
default_factory=list,
description="List of sub questions that need to be answered before asking this question",
)
node_type: Literal["SINGLE", "MERGE_MULTIPLE_RESPONSES"] = Field(
default="SINGLE",
description="Type of question, either a single question or a multi-question merge",
)
class QueryPlan(BaseModel):
"""Container class representing a tree of questions to ask a question answering system."""
query_graph: List[Query] = Field(
..., description="The query graph representing the plan"
)
def _dependencies(self, ids: List[int]) -> List[Query]:
"""Returns the dependencies of a query given their ids."""
return [q for q in self.query_graph if q.id in ids]
```
!!! warning "Graph Generation"
Notice that this example produces a flat list of items with dependencies that resemble a graph, while pydantic allows for recursive definitions, it's much easier and less confusing for the model to generate flat schemas rather than recursive schemas. If you want to see a recursive example, see [recursive schemas](recursive.md)
## Planning a Query Plan
Now, let's demonstrate how to plan and execute a query plan using the defined models and the OpenAI API.
```python
import instructor
# <%hide%>
from typing import List, Literal
from pydantic import Field, BaseModel
class Query(BaseModel):
"""Class representing a single question in a query plan."""
id: int = Field(..., description="Unique id of the query")
question: str = Field(
...,
description="Question asked using a question answering system",
)
dependencies: List[int] = Field(
default_factory=list,
description="List of sub questions that need to be answered before asking this question",
)
node_type: Literal["SINGLE", "MERGE_MULTIPLE_RESPONSES"] = Field(
default="SINGLE",
description="Type of question, either a single question or a multi-question merge",
)
class QueryPlan(BaseModel):
"""Container class representing a tree of questions to ask a question answering system."""
query_graph: List[Query] = Field(
..., description="The query graph representing the plan"
)
def _dependencies(self, ids: List[int]) -> List[Query]:
"""Returns the dependencies of a query given their ids."""
return [q for q in self.query_graph if q.id in ids]
# <%hide%>
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
def query_planner(question: str) -> QueryPlan:
PLANNING_MODEL = "gpt-4o-mini"
messages = [
{
"role": "system",
"content": "You are a world class query planning algorithm capable ofbreaking apart questions into its dependency queries such that the answers can be used to inform the parent question. Do not answer the questions, simply provide a correct compute graph with good specific questions to ask and relevant dependencies. Before you call the function, think step-by-step to get a better understanding of the problem.",
},
{
"role": "user",
"content": f"Consider: {question}\nGenerate the correct query plan.",
},
]
root = client.create(
model=PLANNING_MODEL,
temperature=0,
response_model=QueryPlan,
messages=messages,
max_tokens=1000,
)
return root
```
```
plan = query_planner(
"What is the difference in populations of Canada and the Jason's home country?"
)
plan.model_dump()
```
!!! warning "No RAG"
While we build the query plan in this example, we do not propose a method to actually answer the question. You can implement your own answer function that perhaps makes a retrieval and calls openai for retrieval augmented generation. That step would also make use of function calls but goes beyond the scope of this example.
```python
{
"query_graph": [
{
"dependencies": [],
"id": 1,
"node_type": "SINGLE",
"question": "Identify Jason's home country",
},
{
"dependencies": [],
"id": 2,
"node_type": "SINGLE",
"question": "Find the population of Canada",
},
{
"dependencies": [1],
"id": 3,
"node_type": "SINGLE",
"question": "Find the population of Jason's home country",
},
{
"dependencies": [2, 3],
"id": 4,
"node_type": "SINGLE",
"question": "Calculate the difference in populations between Canada and Jasons home country",
},
]
}
```
In the above code, we define a `query_planner` function that takes a question as input and generates a query plan using the OpenAI API.
## Conclusion
In this example, we demonstrated how to use the OpenAI Function Call `ChatCompletion` model to plan a query using a question-answering system. We defined the necessary structures using Pydantic and created a query planner function that generates a structured plan for answering complex questions.
The query planner breaks down the main question into smaller, manageable sub-questions, establishing dependencies between them. This approach allows for a systematic and organized way to tackle multi-step queries.
For more advanced implementations and variations of this concept, you can explore:
1. [Query planning and execution example](https://github.com/jxnl/instructor/blob/main/examples/query_planner_execution/query_planner_execution.py)
2. [Task planning with topological sort](https://github.com/jxnl/instructor/blob/main/examples/task_planner/task_planner_topological_sort.py)
These examples provide additional insights into how you can leverage structured outputs for complex query planning and task management.
Feel free to adapt this code to your specific use cases and explore the possibilities of using OpenAI Function Calls to plan and structure complex workflows in your applications.

View File

@@ -0,0 +1,141 @@
---
title: Working with Recursive Schemas in Instructor
description: Learn how to effectively implement and use recursive Pydantic models for handling nested and hierarchical data structures.
---
## See Also
- [Nested Structures](../learning/patterns/nested_structure.md) - Complex hierarchical models
- [Knowledge Graph](./knowledge_graph.md) - Build knowledge graphs
- [Response Models](../concepts/models.md) - Working with complex data structures
- [Types](../concepts/types.md) - Working with different data types
# Recursive Schema Implementation Guide
This guide demonstrates how to work with recursive schemas in Instructor using Pydantic models. While flat schemas are often simpler to work with, some use cases require recursive structures to represent hierarchical data effectively.
!!! tips "Motivation"
Recursive schemas are particularly useful when dealing with:
* Nested organizational structures
* File system hierarchies
* Comment threads with replies
* Task dependencies with subtasks
* Abstract syntax trees
## Defining a Recursive Schema
Here's an example of how to define a recursive Pydantic model:
```python
from typing import List, Optional
from pydantic import BaseModel, Field
class RecursiveNode(BaseModel):
"""A node that can contain child nodes of the same type."""
name: str = Field(..., description="Name of the node")
value: Optional[str] = Field(
None, description="Optional value associated with the node"
)
children: List["RecursiveNode"] = Field(
default_factory=list, description="List of child nodes"
)
# Required for recursive Pydantic models
RecursiveNode.model_rebuild()
```
## Example Usage
Let's see how to use this recursive schema with Instructor:
```python
import instructor
client = instructor.from_provider("openai/gpt-5-nano")
def parse_hierarchy(text: str) -> RecursiveNode:
"""Parse text into a hierarchical structure."""
return client.create(
model="gpt-4",
messages=[
{
"role": "system",
"content": "You are an expert at parsing text into hierarchical structures.",
},
{
"role": "user",
"content": f"Parse this text into a hierarchical structure: {text}",
},
],
response_model=RecursiveNode,
)
# Example usage
hierarchy = parse_hierarchy(
"""
Company: Acme Corp
- Department: Engineering
- Team: Frontend
- Project: Website Redesign
- Project: Mobile App
- Team: Backend
- Project: API v2
- Project: Database Migration
- Department: Marketing
- Team: Digital
- Project: Social Media Campaign
- Team: Brand
- Project: Logo Refresh
"""
)
```
## Validation and Best Practices
When working with recursive schemas:
1. Always call `model_rebuild()` after defining the model
2. Consider adding validation for maximum depth to prevent infinite recursion
3. Use type hints properly to maintain code clarity
4. Consider implementing custom validators for specific business rules
```python
from pydantic import model_validator
class RecursiveNodeWithDepth(RecursiveNode):
@model_validator(mode='after')
def validate_depth(self) -> "RecursiveNodeWithDepth":
def check_depth(node: "RecursiveNodeWithDepth", current_depth: int = 0) -> int:
if current_depth > 10: # Maximum allowed depth
raise ValueError("Maximum depth exceeded")
return max(
[check_depth(child, current_depth + 1) for child in node.children],
default=current_depth,
)
check_depth(self)
return self
```
## Performance Considerations
While recursive schemas are powerful, they can be more challenging for language models to handle correctly. Consider these tips:
1. Keep structures as shallow as possible
2. Use clear naming conventions
3. Provide good examples in your prompts
4. Consider breaking very large structures into smaller chunks
## Conclusion
Recursive schemas provide a powerful way to handle hierarchical data structures in your applications. While they require more careful handling than flat schemas, they can be invaluable for certain use cases.
For more examples of working with complex data structures, check out:
1. [Query Planning with Dependencies](planning-tasks.md)
2. [Knowledge Graph Generation](knowledge_graph.md)

View File

@@ -0,0 +1,56 @@
---
title: Search Query Segmentation with Instructor - Multi-Task Extraction
description: Segment complex search queries into actionable tasks using Instructor. Break down user queries into parallel executable tasks with structured outputs.
---
# Example: Segmenting Search Queries
In this example, we will demonstrate how to leverage the `MultiTask` and `enum.Enum` features of OpenAI Function Call to segment search queries. We will define the necessary structures using Pydantic and demonstrate how segment queries into multiple sub queries and execute them in parallel with `asyncio`.
!!! tips "Motivation"
Extracting a list of tasks from text is a common use case for leveraging language models. This pattern can be applied to various applications, such as virtual assistants like Siri or Alexa, where understanding user intent and breaking down requests into actionable tasks is crucial. In this example, we will demonstrate how to use OpenAI Function Call to segment search queries and execute them in parallel.
## Structure of the Data
The `Search` class is a Pydantic model that defines the structure of the search query. It has three fields: `title`, `query`, and `type`. The `title` field is the title of the request, the `query` field is the query to search for relevant content, and the `type` field is the type of search. The `execute` method is used to execute the search query.
```python
import instructor
from typing import Iterable, Literal
from pydantic import BaseModel, Field
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
class Search(BaseModel):
query: str = Field(..., description="Query to search for relevant content")
type: Literal["web", "image", "video"] = Field(..., description="Type of search")
async def execute(self):
print(
f"Searching for `{self.title}` with query `{self.query}` using `{self.type}`"
)
def segment(data: str) -> Search:
return client.create(
model="gpt-4o-mini",
response_model=Iterable[Search],
messages=[
{
"role": "user",
"content": f"Consider the data below: '\n{data}' and segment it into multiple search queries",
},
],
max_tokens=1000,
)
for search in segment("Search for a picture of a cat and a video of a dog"):
print(search.model_dump_json())
#> {"query":"picture of a cat","type":"image"}
#> {"query":"video of a dog","type":"video"}
```

View File

@@ -0,0 +1,165 @@
---
title: Implementing Self-Correction with LLM Validator
description: Learn how to use llm_validator for self-healing in NLP applications and improve response accuracy with validation errors.
---
# Self-Correction with `llm_validator`
## Introduction
This guide demonstrates how to use `llm_validator` for implementing self-healing. The objective is to showcase how an instructor can self-correct by using validation errors and helpful error messages.
```python
from pydantic import BaseModel
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-4.1-mini")
class QuestionAnswer(BaseModel):
question: str
answer: str
question = "What is the meaning of life?"
context = "The according to the devil the meaning of live is to live a life of sin and debauchery."
qa: QuestionAnswer = client.create(
response_model=QuestionAnswer,
messages=[
{
"role": "system",
"content": "You are a system that answers questions based on the context. answer exactly what the question asks using the context.",
},
{
"role": "user",
"content": f"using the context: {context}\n\nAnswer the following question: {question}",
},
],
)
```
### Output Before Validation
While it calls out the objectionable content, it doesn't provide any details on how to correct it.
```json
{
"question": "What is the meaning of life?",
"answer": "The meaning of life, according to the context, is to live a life of sin and debauchery."
}
```
## Adding Custom Validation
By adding a validator to the `answer` field, we can try to catch the issue and correct it.
Lets integrate `llm_validator` into the model and see the error message. Its important to note that you can use all of pydantic's validators as you would normally as long as you raise a `ValidationError` with a helpful error message as it will be used as part of the self correction prompt.
```python
from pydantic import BaseModel, BeforeValidator
from typing_extensions import Annotated
from instructor import llm_validator
import instructor
client = instructor.from_provider("openai/gpt-4.1-mini")
class QuestionAnswerNoEvil(BaseModel):
question: str
answer: Annotated[
str,
BeforeValidator(
llm_validator(
"don't say objectionable things", client=client, allow_override=True
)
),
]
try:
qa: QuestionAnswerNoEvil = client.create(
response_model=QuestionAnswerNoEvil,
messages=[
{
"role": "system",
"content": "You are a system that answers questions based on the context. answer exactly what the question asks using the context.",
},
{
"role": "user",
"content": f"using the context: {context}\n\nAnswer the following question: {question}",
},
],
)
except Exception as e:
print(e)
#> name 'context' is not defined
```
### Output After Validation
Now, we throw validation error that its objectionable and provide a helpful error message.
```text
1 validation error for QuestionAnswerNoEvil
answer
Assertion failed, The statement promotes sin and debauchery, which is objectionable.
```
## Retrying with Corrections
By adding the `max_retries` parameter, we can retry the request with corrections. and use the error message to correct the output.
```python
# <%hide%>
import instructor
from pydantic import BaseModel, BeforeValidator
from typing_extensions import Annotated
from instructor import llm_validator
question = "What is the meaning of life?"
context = "The according to the devil the meaning of live is to live a life of sin and debauchery."
client = instructor.from_provider("openai/gpt-4.1-mini")
class QuestionAnswerNoEvil(BaseModel):
question: str
answer: Annotated[
str,
BeforeValidator(
llm_validator(
"don't say objectionable things", client=client, allow_override=True
)
),
]
# <%hide%>
qa: QuestionAnswerNoEvil = client.create(
response_model=QuestionAnswerNoEvil,
messages=[
{
"role": "system",
"content": "You are a system that answers questions based on the context. answer exactly what the question asks using the context.",
},
{
"role": "user",
"content": f"using the context: {context}\n\nAnswer the following question: {question}",
},
],
)
```
### Final Output
Now, we get a valid response that is not objectionable!
```json
{
"question": "What is the meaning of life?",
"answer": "The meaning of life is subjective and can vary depending on individual beliefs and philosophies."
}
```

View File

@@ -0,0 +1,62 @@
---
title: Single-Label Text Classification - SPAM Detection Example
description: Implement single-label text classification with Instructor. Classify text as SPAM or NOT_SPAM with chain-of-thought reasoning.
---
# Single-Label Classification
This example demonstrates how to perform single-label classification using the OpenAI API. The example uses the `gpt-3.5-turbo` model to classify text as either `SPAM` or `NOT_SPAM`.
```python
from pydantic import BaseModel, Field
from typing import Literal
import instructor
# Apply the patch to the OpenAI client
# enables response_model keyword
client = instructor.from_provider("openai/gpt-5-nano")
class ClassificationResponse(BaseModel):
"""
A few-shot example of text classification:
Examples:
- "Buy cheap watches now!": SPAM
- "Meeting at 3 PM in the conference room": NOT_SPAM
- "You've won a free iPhone! Click here": SPAM
- "Can you pick up some milk on your way home?": NOT_SPAM
- "Increase your followers by 10000 overnight!": SPAM
"""
label: Literal["SPAM", "NOT_SPAM"] = Field(
...,
description="The predicted class label.",
)
def classify(data: str) -> ClassificationResponse:
"""Perform single-label classification on the input text."""
return client.create(
model="gpt-4o-mini",
response_model=ClassificationResponse,
messages=[
{
"role": "user",
"content": f"Classify the following text: <text>{data}</text>",
},
],
)
if __name__ == "__main__":
for text, label in [
("Hey Jason! You're awesome", "NOT_SPAM"),
("I am a nigerian prince and I need your help.", "SPAM"),
]:
prediction = classify(text)
assert prediction.label == label
print(f"Text: {text}, Predicted Label: {prediction.label}")
#> Text: Hey Jason! You're awesome, Predicted Label: NOT_SPAM
#> Text: I am a nigerian prince and I need your help., Predicted Label: SPAM
```

View File

@@ -0,0 +1,644 @@
---
title: SQLModel with Instructor - Complete Guide to AI-Powered Database Operations
description: Master SQLModel integration with Instructor for AI-powered database operations, FastAPI APIs, and production-ready applications. Learn advanced patterns, performance optimization, and best practices.
keywords: SQLModel, Instructor AI, Python ORM, FastAPI integration, database automation, AI data generation, Pydantic models, SQLAlchemy, OpenAI GPT, structured data extraction
---
# SQLModel with Instructor: Complete Integration Guide
[SQLModel](https://sqlmodel.tiangolo.com/) is a modern Python library that combines the power of SQLAlchemy's database operations with Pydantic's data validation. Created by Sebastian Ramirez (the creator of FastAPI), SQLModel provides a unified approach to database modeling and API development.
When integrated with Instructor, SQLModel becomes a powerful tool for AI-driven database operations, allowing you to generate structured data directly from language models and seamlessly store it in your database.
## Why SQLModel + Instructor?
The combination of SQLModel and Instructor offers several key advantages:
- **Single Model Definition**: Write one model that works for database tables, API schemas, and AI data generation
- **Type Safety**: Full type checking and editor support throughout your application
- **AI-Powered Data Generation**: Generate realistic database records using large language models
- **FastAPI Integration**: Seamless API development with automatic documentation
- **Production Ready**: Built on proven technologies (SQLAlchemy + Pydantic)
## Quick Start Example
Here's a simple example to get you started:
```python
import instructor
from typing import Optional
from uuid import UUID, uuid4
from pydantic.json_schema import SkipJsonSchema
from sqlmodel import Field, SQLModel, create_engine, Session
# Initialize the Instructor client
client = instructor.from_provider("openai/gpt-5-nano")
class Hero(SQLModel, instructor.OpenAISchema, table=True):
id: SkipJsonSchema[UUID] = Field(default_factory=lambda: uuid4(), primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
power_level: Optional[int] = Field(default=None, ge=1, le=100)
# Generate AI-powered data
def create_hero() -> Hero:
return client.create(
model="gpt-4",
response_model=Hero,
messages=[
{
"role": "user",
"content": "Create a superhero with a power level between 1-100",
},
],
)
# Database setup and insertion
engine = create_engine("sqlite:///heroes.db")
SQLModel.metadata.create_all(engine)
hero = create_hero()
with Session(engine) as session:
session.add(hero)
session.commit()
print(f"Created hero: {hero.name} with power level {hero.power_level}")
```
# Core Concepts and Best Practices
## Model Definition Strategies
### Using SkipJsonSchema for Auto-Generated Fields
The `SkipJsonSchema` annotation is crucial for fields that should be generated by your application rather than the AI:
```python
from pydantic.json_schema import SkipJsonSchema
from sqlmodel import Field, SQLModel
import instructor
from uuid import UUID, uuid4
from datetime import datetime
class Product(SQLModel, instructor.OpenAISchema, table=True):
# Auto-generated fields excluded from AI generation
id: SkipJsonSchema[UUID] = Field(default_factory=uuid4, primary_key=True)
created_at: SkipJsonSchema[datetime] = Field(default_factory=datetime.utcnow)
updated_at: SkipJsonSchema[datetime] = Field(default_factory=datetime.utcnow)
# AI-generated fields
name: str = Field(description="Product name")
description: str = Field(description="Detailed product description")
price: float = Field(gt=0, description="Product price in USD")
category: str = Field(description="Product category")
```
### Field Validation and Constraints
SQLModel supports Pydantic's validation features, ensuring data quality:
```python
from typing import Optional
from sqlmodel import Field, SQLModel
import instructor
from pydantic import validator
class Customer(SQLModel, instructor.OpenAISchema, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(min_length=2, max_length=100)
email: str = Field(regex=r'^[\w\.-]+@[\w\.-]+\.\w+$')
age: Optional[int] = Field(default=None, ge=18, le=120)
credit_score: Optional[int] = Field(default=None, ge=300, le=850)
@validator('email')
def validate_email_domain(cls, v):
allowed_domains = ['gmail.com', 'yahoo.com', 'outlook.com']
domain = v.split('@')[1]
if domain not in allowed_domains:
raise ValueError(f'Email domain must be one of {allowed_domains}')
return v
```
## Advanced Integration Patterns
### Relationship Modeling with AI Generation
SQLModel supports relationships between tables, which can be populated using AI:
```python
from typing import List, Optional
from sqlmodel import Field, SQLModel, Relationship
import instructor
client = instructor.from_provider("openai/gpt-5-nano")
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
city: str
# Relationship to heroes
heroes: List["Hero"] = Relationship(back_populates="team")
class Hero(SQLModel, instructor.OpenAISchema, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
# Foreign key to team
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
team: Optional[Team] = Relationship(back_populates="heroes")
def create_hero_for_team(team_name: str) -> Hero:
return client.create(
model="gpt-4",
response_model=Hero,
messages=[
{"role": "user", "content": f"Create a superhero for the {team_name} team"},
],
)
```
### Bulk Data Generation
Generate multiple records efficiently:
```python
from typing import List
import instructor
from sqlmodel import Session
client = instructor.from_provider("openai/gpt-5-nano")
def create_hero_team(team_size: int = 5) -> List[Hero]:
return client.create(
model="gpt-4",
response_model=List[Hero],
messages=[
{
"role": "user",
"content": f"Create a team of {team_size} diverse superheroes",
},
],
)
# Bulk insert
heroes = create_hero_team(10)
with Session(engine) as session:
for hero in heroes:
session.add(hero)
session.commit()
print(f"Created {len(heroes)} heroes")
```
# FastAPI Integration
## Building Production APIs
SQLModel's tight integration with FastAPI makes it perfect for building production APIs:
```python
from fastapi import FastAPI, HTTPException, Depends
from sqlmodel import Session, select
from typing import List
import instructor
app = FastAPI(title="Hero Management API")
client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
def get_session():
with Session(engine) as session:
yield session
session_dep = Depends(get_session)
# Create hero endpoint
@app.post("/heroes/", response_model=Hero)
async def create_hero_endpoint(prompt: str, session: Session = session_dep):
hero = await client.create(
model="gpt-4",
response_model=Hero,
messages=[
{"role": "user", "content": f"Create a superhero: {prompt}"},
],
)
session.add(hero)
session.commit()
session.refresh(hero)
return hero
# List heroes endpoint
@app.get("/heroes/", response_model=List[Hero])
def list_heroes(limit: int = 10, offset: int = 0, session: Session = session_dep):
statement = select(Hero).offset(offset).limit(limit)
heroes = session.exec(statement).all()
return heroes
# Get specific hero
@app.get("/heroes/{hero_id}", response_model=Hero)
def get_hero(hero_id: int, session: Session = session_dep):
hero = session.get(Hero, hero_id)
if not hero:
raise HTTPException(status_code=404, detail="Hero not found")
return hero
```
## API Response Models
Create specialized models for different API operations:
```python
from sqlmodel import SQLModel
from typing import Optional
# Base model for database
class HeroBase(SQLModel):
name: str
secret_name: str
age: Optional[int] = None
# Database model
class Hero(HeroBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
# API models
class HeroCreate(HeroBase):
pass
class HeroRead(HeroBase):
id: int
class HeroUpdate(SQLModel):
name: Optional[str] = None
secret_name: Optional[str] = None
age: Optional[int] = None
```
# Performance Optimization
## Database Connection Management
Optimize database connections for production:
```python
from sqlmodel import create_engine
from sqlalchemy.pool import QueuePool
# Production database configuration
engine = create_engine(
"postgresql://user:password@localhost/dbname",
poolclass=QueuePool,
pool_size=20,
max_overflow=0,
pool_pre_ping=True,
echo=False, # Set to True for debugging
)
```
## Efficient AI Data Generation
Optimize AI calls for better performance:
```python
import asyncio
from typing import List
import instructor
client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
async def create_heroes_batch(prompts: List[str]) -> List[Hero]:
"""Generate multiple heroes concurrently"""
tasks = []
for prompt in prompts:
task = client.create(
model="gpt-4",
response_model=Hero,
messages=[{"role": "user", "content": prompt}],
)
tasks.append(task)
return await asyncio.gather(*tasks)
# Usage
prompts = [
"Create a fire-based superhero",
"Create a water-based superhero",
"Create an earth-based superhero",
]
heroes = await create_heroes_batch(prompts)
```
# Testing Strategies
## Unit Testing with SQLModel
Test your models and AI integration:
```python
import pytest
from sqlmodel import Session, SQLModel, create_engine
from sqlalchemy.pool import StaticPool
@pytest.fixture
def session():
engine = create_engine(
"sqlite://",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
def test_hero_creation(session):
hero = Hero(name="Test Hero", secret_name="Test Identity", age=25)
session.add(hero)
session.commit()
assert hero.id is not None
assert hero.name == "Test Hero"
@pytest.mark.asyncio
async def test_ai_hero_generation():
# Mock the AI response for testing
mock_hero = Hero(name="AI Hero", secret_name="AI Identity", age=30)
# Test the generated hero meets requirements
assert len(mock_hero.name) > 0
assert len(mock_hero.secret_name) > 0
assert mock_hero.age is None or mock_hero.age > 0
```
## Integration Testing
Test the full stack including AI generation:
```python
from fastapi.testclient import TestClient
client = TestClient(app)
def test_create_hero_endpoint():
response = client.post("/heroes/", params={"prompt": "Create a test superhero"})
assert response.status_code == 200
hero_data = response.json()
assert "name" in hero_data
assert "secret_name" in hero_data
def test_list_heroes():
response = client.get("/heroes/")
assert response.status_code == 200
heroes = response.json()
assert isinstance(heroes, list)
```
# Production Deployment
## Environment Configuration
Set up proper configuration for different environments:
```python
from pydantic import BaseSettings
from sqlmodel import create_engine
class Settings(BaseSettings):
database_url: str = "sqlite:///./app.db"
openai_api_key: str
debug: bool = False
class Config:
env_file = ".env"
settings = Settings()
engine = create_engine(settings.database_url)
```
## Error Handling and Logging
Implement robust error handling:
```python
import logging
from fastapi import HTTPException
import instructor
logger = logging.getLogger(__name__)
client = instructor.from_provider("openai/gpt-5-nano")
async def safe_create_hero(prompt: str) -> Hero:
try:
hero = await client.create(
model="gpt-4",
response_model=Hero,
messages=[{"role": "user", "content": prompt}],
max_retries=3,
)
logger.info(f"Successfully created hero: {hero.name}")
return hero
except Exception as e:
logger.error(f"Failed to create hero: {str(e)}")
raise HTTPException(
status_code=500, detail="Failed to generate hero data"
) from e
```
# Advanced Use Cases
## Data Migration and Seeding
Use AI to generate realistic seed data:
```python
from sqlmodel import Session
import instructor
client = instructor.from_provider("openai/gpt-5-nano")
def seed_database():
"""Generate realistic seed data for development"""
engine = create_engine("sqlite:///seed.db")
SQLModel.metadata.create_all(engine)
# Generate diverse heroes
hero_types = [
"tech-based superhero",
"magic-based superhero",
"strength-based superhero",
"speed-based superhero",
"psychic superhero",
]
with Session(engine) as session:
for hero_type in hero_types:
for _ in range(5): # 5 heroes of each type
hero = client.create(
model="gpt-4",
response_model=Hero,
messages=[
{"role": "user", "content": f"Create a unique {hero_type}"}
],
)
session.add(hero)
session.commit()
print("Database seeded successfully!")
if __name__ == "__main__":
seed_database()
```
## Real-time Data Processing
Combine SQLModel with streaming for real-time applications:
```python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import instructor
import json
app = FastAPI()
client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
@app.post("/heroes/stream")
async def stream_hero_creation(prompts: List[str]):
async def generate_heroes():
for prompt in prompts:
try:
hero = await client.create(
model="gpt-4",
response_model=Hero,
messages=[{"role": "user", "content": prompt}],
)
# Save to database
with Session(engine) as session:
session.add(hero)
session.commit()
session.refresh(hero)
yield f"data: {hero.model_dump_json()}\n\n"
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
return StreamingResponse(generate_heroes(), media_type="text/plain")
```
# Troubleshooting Common Issues
## Model Inheritance Issues
When using both SQLModel and instructor.OpenAISchema:
```python
# Correct way to inherit from both
class Hero(SQLModel, instructor.OpenAISchema, table=True):
__table_args__ = {'extend_existing': True} # Prevents table conflicts
# ... model fields
```
## JSON Schema Conflicts
Handle conflicts between database and AI schema requirements:
```python
from pydantic import Field
from pydantic.json_schema import SkipJsonSchema
class Hero(SQLModel, instructor.OpenAISchema, table=True):
# Database-only fields
id: SkipJsonSchema[int] = Field(default=None, primary_key=True)
created_at: SkipJsonSchema[datetime] = Field(default_factory=datetime.utcnow)
# AI-generated fields with database constraints
name: str = Field(description="Hero name for AI", max_length=100) # DB constraint
power_level: int = Field(description="Power level 1-100", ge=1, le=100)
```
## Performance Monitoring
Monitor AI generation performance:
```python
import time
from functools import wraps
def monitor_ai_calls(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
result = await func(*args, **kwargs)
duration = time.time() - start_time
logger.info(f"AI call took {duration:.2f} seconds")
return result
return wrapper
@monitor_ai_calls
async def create_hero(prompt: str) -> Hero:
return await client.create(
model="gpt-4",
response_model=Hero,
messages=[{"role": "user", "content": prompt}],
)
```
# Conclusion
SQLModel with Instructor provides a powerful foundation for building AI-powered applications with robust database integration. The combination offers:
- **Developer Productivity**: Single model definition for multiple use cases
- **Type Safety**: Full type checking and validation
- **AI Integration**: Seamless integration with language models
- **Production Ready**: Built on proven, scalable technologies
- **FastAPI Compatible**: Perfect for modern API development
By following the patterns and best practices outlined in this guide, you can build sophisticated applications that leverage AI for data generation while maintaining data integrity and performance.
## Next Steps
- Explore the [FastAPI integration guide](../concepts/fastapi.md) for advanced API patterns
- Check out [validation techniques](../concepts/validation.md) for robust data handling
- Learn about [streaming responses](partial_streaming.md) for real-time applications
![Database screenshot showing AI-generated hero records stored in SQLite database](db.png)
*Example of AI-generated hero data stored in SQLite database*

View File

@@ -0,0 +1,128 @@
---
title: Extracting Tables from Images Using OpenAI GPT-4
description: Learn how to convert images into markdown tables using OpenAI's GPT-4 Vision model for data extraction and analysis.
---
# Extracting Tables from Images with OpenAI's GPT-4 Vision Model
First, we define a custom type, `MarkdownDataFrame`, to handle pandas DataFrames formatted in markdown. This type uses Python's `Annotated` and `InstanceOf` types, along with decorators `BeforeValidator` and `PlainSerializer`, to process and serialize the data.
## Defining the Table Class
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!
This requires additional dependencies `pip install pandas tabulate`.
```python
from io import StringIO
from typing import Annotated, Any, List
from pydantic import (
BaseModel,
BeforeValidator,
PlainSerializer,
InstanceOf,
WithJsonSchema,
)
import instructor
import pandas as pd
from rich.console import Console
console = Console()
client = instructor.from_provider("openai/gpt-4o", mode=instructor.Mode.TOOLS)
def md_to_df(data: Any) -> Any:
if isinstance(data, str):
return (
pd.read_csv(
StringIO(data), # Get rid of whitespaces
sep="|",
index_col=1,
)
.dropna(axis=1, how="all")
.iloc[1:]
.map(lambda x: x.strip())
) # type: ignore
return data
MarkdownDataFrame = Annotated[
InstanceOf[pd.DataFrame],
BeforeValidator(md_to_df),
PlainSerializer(lambda x: x.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
class MultipleTables(BaseModel):
tables: List[Table]
example = MultipleTables(
tables=[
Table(
caption="This is a caption",
dataframe=pd.DataFrame(
{
"Chart A": [10, 40],
"Chart B": [20, 50],
"Chart C": [30, 60],
}
),
)
]
)
def extract(url: str) -> MultipleTables:
return client.create(
model="gpt-4-turbo",
max_tokens=4000,
response_model=MultipleTables,
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": url},
},
{
"type": "text",
"text": """
First, analyze the image to determine the most appropriate headers for the tables.
Generate a descriptive h1 for the overall image, followed by a brief summary of the data it contains.
For each identified table, create an informative h2 title and a concise description of its contents.
Finally, output the markdown representation of each table.
Make sure to escape the markdown table properly, and make sure to include the caption and the dataframe.
including escaping all the newlines and quotes. Only return a markdown table in dataframe, nothing else.
""",
},
],
}
],
)
urls = [
"https://a.storyblok.com/f/47007/2400x1260/f816b031cb/uk-ireland-in-three-charts_chart_a.png/m/2880x0",
"https://a.storyblok.com/f/47007/2400x2000/bf383abc3c/231031_uk-ireland-in-three-charts_table_v01_b.png/m/2880x0",
]
for url in urls:
for table in extract(url).tables:
console.print(table.caption, "\n", table.dataframe)
```

View File

@@ -0,0 +1,274 @@
---
title: Observability & Tracing with Langfuse
description: Learn how to trace and monitor Instructor API calls using Langfuse for comprehensive observability in your LLM applications.
---
# Observability & Tracing with Langfuse
**What is Langfuse?**
> **What is Langfuse?** [Langfuse](https://langfuse.com) ([GitHub](https://github.com/langfuse/langfuse)) is an open source LLM engineering platform that helps teams trace API calls, monitor performance, and debug issues in their AI applications.
![Instructor Trace in Langfuse showing structured output monitoring and observability](https://langfuse.com/images/docs/instructor-trace.png)
This cookbook shows how to use Langfuse to trace and monitor model calls made with the Instructor library.
## Setup
> **Note** : Before continuing with this section, make sure that you've signed up for an account with [Langfuse](https://langfuse.com). You'll need your private and public key to start tracing with Langfuse.
First, let's start by installing the necessary dependencies.
```python
pip install langfuse instructor
```
It is easy to use instructor with Langfuse. We use the [Langfuse OpenAI Integration](https://langfuse.com/docs/integrations/openai) and simply patch the client with instructor. This works with both synchronous and asynchronous clients.
### Langfuse-Instructor integration with synchronous OpenAI client
```python
import instructor
from langfuse.openai import openai
from pydantic import BaseModel
import os
# Set your API keys Here
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-..."
os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com"
os.environ["OPENAI_API_KEY] = "sk-..."
# Patch Langfuse wrapper of synchronous OpenAI client with instructor
client = instructor.from_provider("openai/gpt-5-nano")
class WeatherDetail(BaseModel):
city: str
temperature: int
# Run synchronous OpenAI client
weather_info = client.create(
model="gpt-4o",
response_model=WeatherDetail,
messages=[
{"role": "user", "content": "The weather in Paris is 18 degrees Celsius."},
],
)
print(weather_info.model_dump_json(indent=2))
"""
{
"city": "Paris",
"temperature": 18
}
"""
```
Once we've run this request succesfully, we'll see that we have a trace avaliable in the Langfuse dashboard for you to look at.
### Langfuse-Instructor integration with asychnronous OpenAI client
```python
import instructor
from langfuse.openai import openai
from pydantic import BaseModel
import os
import asyncio
# Set your API keys Here
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-"
os.environ["LANGFUSE_SECRET_KEY"] = "sk-"
os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com"
os.environ["OPENAI_API_KEY] = "sk-..."
# Patch Langfuse wrapper of synchronous OpenAI client with instructor
client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
class WeatherDetail(BaseModel):
city: str
temperature: int
async def main():
# Run synchronous OpenAI client
weather_info = await client.create(
model="gpt-4o",
response_model=WeatherDetail,
messages=[
{"role": "user", "content": "The weather in Paris is 18 degrees Celsius."},
],
)
print(weather_info.model_dump_json(indent=2))
"""
{
"city": "Paris",
"temperature": 18
}
"""
asyncio.run(main())
```
Here's a [public link](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/0da3f599-b807-4e14-9888-cf68fa53d976?timestamp=2025-03-31T16:12:40.076Z&display=details) to the trace that we generated which you can view in Langfuse.
## Example
In this example, we first classify customer feedback into categories like `PRAISE`, `SUGGESTION`, `BUG` and `QUESTION`, and further scores the relevance of each feedback to the business on a scale of 0.0 to 1.0. In this case, we use the asynchronous OpenAI client `AsyncOpenAI` to classify and evaluate the feedback.
```python
from enum import Enum
import asyncio
import instructor
from langfuse import Langfuse
from langfuse.openai import AsyncOpenAI
from langfuse.decorators import langfuse_context, observe
from pydantic import BaseModel, Field, field_validator
import os
# Set your API keys Here
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-..."
os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com"
os.environ["OPENAI_API_KEY] = "sk-..."
client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
# Initialize Langfuse (needed for scoring)
langfuse = Langfuse()
# Rate limit the number of requests
sem = asyncio.Semaphore(5)
# Define feedback categories
class FeedbackType(Enum):
PRAISE = "PRAISE"
SUGGESTION = "SUGGESTION"
BUG = "BUG"
QUESTION = "QUESTION"
# Model for feedback classification
class FeedbackClassification(BaseModel):
feedback_text: str = Field(...)
classification: list[FeedbackType] = Field(
description="Predicted categories for the feedback"
)
relevance_score: float = Field(
default=0.0,
description="Score of the query evaluating its relevance to the business between 0.0 and 1.0",
)
# Make sure feedback type is list
@field_validator("classification", mode="before")
def validate_classification(cls, v):
if not isinstance(v, list):
v = [v]
return v
@observe() # Langfuse decorator to automatically log spans to Langfuse
async def classify_feedback(feedback: str):
"""
Classify customer feedback into categories and evaluate relevance.
"""
async with sem: # simple rate limiting
response = await client.create(
model="gpt-4o",
response_model=FeedbackClassification,
max_retries=2,
messages=[
{
"role": "user",
"content": f"Classify and score this feedback: {feedback}",
},
],
)
# Retrieve observation_id of current span
observation_id = langfuse_context.get_current_observation_id()
return feedback, response, observation_id
def score_relevance(trace_id: str, observation_id: str, relevance_score: float):
"""
Score the relevance of a feedback query in Langfuse given the observation_id.
"""
langfuse.score(
trace_id=trace_id,
observation_id=observation_id,
name="feedback-relevance",
value=relevance_score,
)
@observe() # Langfuse decorator to automatically log trace to Langfuse
async def main(feedbacks: list[str]):
tasks = [classify_feedback(feedback) for feedback in feedbacks]
results = []
for task in asyncio.as_completed(tasks):
feedback, classification, observation_id = await task
result = {
"feedback": feedback,
"classification": [c.value for c in classification.classification],
"relevance_score": classification.relevance_score,
}
results.append(result)
# Retrieve trace_id of current trace
trace_id = langfuse_context.get_current_trace_id()
# Score the relevance of the feedback in Langfuse
score_relevance(trace_id, observation_id, classification.relevance_score)
# Flush observations to Langfuse
langfuse_context.flush()
return results
feedback_messages = [
"The chat bot on your website does not work.",
"Your customer service is exceptional!",
"Could you add more features to your app?",
"I have a question about my recent order.",
]
feedback_classifications = asyncio.run(main(feedback_messages))
for classification in feedback_classifications:
print(f"Feedback: {classification['feedback']}")
print(f"Classification: {classification['classification']}")
print(f"Relevance Score: {classification['relevance_score']}")
"""
Feedback: I have a question about my recent order.
Classification: ['QUESTION']
Relevance Score: 0.0
Feedback: Could you add more features to your app?
Classification: ['SUGGESTION']
Relevance Score: 0.0
Feedback: The chat bot on your website does not work.
Classification: ['BUG']
Relevance Score: 0.9
Feedback: Your customer service is exceptional!
Classification: ['PRAISE']
Relevance Score: 0.9
"""
```
We can see that with Langfuse, we were able to generate these different completions and view them with our own UI. Click here to see the [public trace](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/ba27e7b1-e23e-4f50-87de-420cf038190f?timestamp=2025-03-31T16:12:57.041Z&display=details) for the 5 completions that we generated.

View File

@@ -0,0 +1,47 @@
---
title: Working with Decimal Types in Instructor
description: Learn how to use Python Decimal types for precise financial calculations and numeric data extraction with Instructor.
---
## See Also
- [Types](../concepts/types.md) - Working with different data types
- [Fields](../concepts/fields.md) - Customizing field validation
- [Field Validation](../learning/patterns/field_validation.md) - Field-level validation patterns
- [Validation](../concepts/validation.md) - Core validation concepts
# Using Decimals
Extract precise decimal values for financial calculations using Python's `Decimal` type.
```python
from decimal import Decimal
from pydantic import BaseModel, field_validator
import instructor
class Receipt(BaseModel):
item: str
price: Decimal
@field_validator('price', mode='before')
@classmethod
def parse_price(cls, v):
if isinstance(v, str):
return Decimal(v)
return v
client = instructor.from_provider("openai/gpt-4.1-mini")
receipt = client.create(
messages=[{"role": "user", "content": "Coffee costs $4.99"}],
response_model=Receipt,
)
print(f"Item: {receipt.item}")
print(f"Price: {receipt.price}") # Decimal('4.99')
print(f"Type: {type(receipt.price)}") # <class 'decimal.Decimal'>
```
The `field_validator` ensures string values from LLM responses are properly converted to Decimal objects for precise financial calculations.

View File

@@ -0,0 +1,73 @@
---
title: IBM watsonx.ai Integration - Enterprise LLM Inference
description: Use IBM watsonx.ai with Instructor through LiteLLM for enterprise-grade structured outputs. Setup, authentication, and production examples.
---
# Structured Outputs with IBM watsonx.ai
You can use IBM watsonx.ai for inference using [LiteLLM](https://docs.litellm.ai/docs/providers/watsonx).
## Prerequisites
- IBM Cloud Account
- API Key from IBM Cloud IAM: https://cloud.ibm.com/iam/apikeys
- Project ID (from watsonx.ai instance URL: https://dataplatform.cloud.ibm.com/projects/<WATSONX_PROJECT_ID>/)
## Install
```bash
poetry install instructor --with litellm
```
## Example
```python
import os
import litellm
from litellm import completion
from pydantic import BaseModel, Field
import instructor
from instructor import Mode
litellm.drop_params = True # watsonx.ai doesn't support `json_mode`
os.environ["WATSONX_URL"] = "https://us-south.ml.cloud.ibm.com"
os.environ["WATSONX_API_KEY"] = ""
os.environ["WATSONX_PROJECT_ID"] = ""
# Additional options: https://docs.litellm.ai/docs/providers/watsonx
class Company(BaseModel):
name: str = Field(description="name of the company")
year_founded: int = Field(description="year the company was founded")
client = instructor.from_litellm(completion, mode=Mode.JSON)
resp = client.create(
model="watsonx/meta-llama/llama-3-8b-instruct",
max_tokens=1024,
messages=[
{
"role": "user",
"content": """\
Given the following text, create a Company object:
IBM was founded in 1911 as the Computing-Tabulating-Recording Company (CTR), a holding company of manufacturers of record-keeping and measuring systems.
""",
}
],
project_id=os.environ["WATSONX_PROJECT_ID"],
response_model=Company,
)
print(resp.model_dump_json(indent=2))
"""
{
"name": "IBM",
"year_founded": 1911
}
"""
```

View File

@@ -0,0 +1,128 @@
---
title: Generating YouTube Clips from Transcripts Using Instructor
description: Learn to create concise YouTube clips from video transcripts with `instructor` and OpenAI, enhancing your content engagement.
---
# Generating YouTube Clips from Transcripts
This guide demonstrates how to generate concise, informative clips from YouTube video transcripts using the `instructor` library. By leveraging the power of OpenAI's models, we can extract meaningful segments from a video's transcript, which can then be recut into smaller, standalone videos. This process involves identifying key moments within a transcript and summarizing them into clips with specific titles and descriptions.
First, install the necessary packages:
```bash
pip install youtube_transcript_api instructor rich
```
![YouTube clip streaming demonstration showing real-time video segment extraction](../img/youtube.gif)
```python
from youtube_transcript_api import YouTubeTranscriptApi
from pydantic import BaseModel, Field
from typing import List, Generator, Iterable
import instructor
import instructor
client = instructor.from_provider("openai/gpt-5-nano")
def extract_video_id(url: str) -> str | None:
import re
match = re.search(r"v=([a-zA-Z0-9_-]+)", url)
if match:
return match.group(1)
class TranscriptSegment(BaseModel):
source_id: int
start: float
text: str
def get_transcript_with_timing(
video_id: str,
) -> Generator[TranscriptSegment, None, None]:
"""
Fetches the transcript of a YouTube video along with the start and end times
for each text segment, and returns them as a list of Pydantic models.
"""
transcript = YouTubeTranscriptApi.get_transcript(video_id)
for ii, segment in enumerate(transcript):
yield TranscriptSegment(
source_id=ii, start=segment["start"], text=segment["text"]
)
class YoutubeClip(BaseModel):
title: str = Field(description="Specific and informative title for the clip.")
description: str = Field(
description="A detailed description of the clip, including notable quotes or phrases."
)
start: float
end: float
class YoutubeClips(BaseModel):
clips: List[YoutubeClip]
def yield_clips(segments: Iterable[TranscriptSegment]) -> Iterable[YoutubeClips]:
return client.create(
model="gpt-4-turbo-preview",
stream=True,
messages=[
{
"role": "system",
"content": """You are given a sequence of YouTube transcripts and your job
is to return notable clips that can be recut as smaller videos. Give very
specific titles and descriptions. Make sure the length of clips is proportional
to the length of the video. Note that this is a transcript and so there might
be spelling errors. Note that and correct any spellings. Use the context to
make sure you're spelling things correctly.""",
},
{
"role": "user",
"content": f"Let's use the following transcript segments.\n{segments}",
},
],
response_model=instructor.Partial[YoutubeClips],
context={"segments": segments},
) # type: ignore
# Example usage
if __name__ == "__main__":
from rich.table import Table
from rich.console import Console
from rich.prompt import Prompt
console = Console()
url = Prompt.ask("Enter a YouTube URL")
with console.status("[bold green]Processing YouTube URL...") as status:
video_id = extract_video_id(url)
if video_id is None:
raise ValueError("Invalid YouTube video URL")
transcript = list(get_transcript_with_timing(video_id))
status.update("[bold green]Generating clips...")
for clip in yield_clips(transcript):
console.clear()
table = Table(title="Extracted YouTube Clips", padding=(0, 1))
table.add_column("Title", style="cyan")
table.add_column("Description", style="magenta")
table.add_column("Start", justify="right", style="green")
table.add_column("End", justify="right", style="green")
for youtube_clip in clip.clips or []:
table.add_row(
youtube_clip.title,
youtube_clip.description,
str(youtube_clip.start),
str(youtube_clip.end),
)
console.print(table)
```