This commit is contained in:
LASTA_DEV01\lasta
2026-05-13 19:57:34 +09:00
parent 2e9204243d
commit 9e88f4c7ad
4310 changed files with 48538 additions and 905279 deletions

View File

@@ -1,42 +0,0 @@
import instructor
from pydantic import BaseModel
# Noticed thhat we use JSON not TOOLS mode
client = instructor.from_provider(
"anthropic/claude-3-7-sonnet-latest",
mode=instructor.Mode.JSON,
async_client=False,
)
class Citation(BaseModel):
id: int
url: str
class Response(BaseModel):
citations: list[Citation]
response: str
response_data, completion_details = client.messages.create_with_completion(
messages=[
{
"role": "system",
"content": "You are a helpful assistant that summarizes news articles. Your final response should be only contain a single JSON object returned in your final message to the user. Make sure to provide the exact ids for the citations that support the information you provide in the form of inline citations as [1] [2] [3] which correspond to a unique id you generate for a url that you find in the web search tool which is relevant to your final response.",
},
{
"role": "user",
"content": "What are the latest results for the UFC and who won? Answer this in a concise response that's under 3 sentences.",
},
],
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 3}],
response_model=Response,
)
print("Response:")
print(response_data.response)
print("\nCitations:")
for citation in response_data.citations:
print(f"{citation.id}: {citation.url}")

View File

@@ -1,33 +0,0 @@
from pydantic import BaseModel
import anthropic
import instructor
# Patching the Anthropics client with the instructor for enhanced capabilities
client = instructor.from_anthropic(anthropic.Anthropic())
class Properties(BaseModel):
key: str
value: str
class User(BaseModel):
name: str
age: int
properties: list[Properties]
user = client.messages.create(
model="claude-3-haiku-20240307",
max_tokens=1024,
max_retries=0,
messages=[
{
"role": "user",
"content": "Create a user for a model with a name, age, and properties.",
}
],
response_model=User,
)
print(user.model_dump_json(indent=2))

View File

@@ -1,403 +0,0 @@
"""
Asyncio Benchmarks with Instructor
This script demonstrates and benchmarks different asyncio patterns for LLM processing:
- Sequential processing (baseline)
- asyncio.gather (concurrent, ordered results)
- asyncio.as_completed (concurrent, streaming results)
- Rate-limited processing with semaphores
- Error handling patterns
- Progress tracking
- Batch processing with chunking
Run this script to see performance comparisons and verify all code examples work.
"""
import asyncio
import time
import logging
import instructor
from pydantic import BaseModel, field_validator
from openai import AsyncOpenAI, OpenAI
import os
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Set up the async client with Instructor
client = instructor.from_openai(AsyncOpenAI())
sync_client = instructor.from_openai(OpenAI())
class Person(BaseModel):
name: str
age: int
occupation: str
@field_validator("age")
@classmethod
def validate_age(cls, v):
if v < 0 or v > 150:
raise ValueError(f"Age {v} is invalid")
return v
# Sample dataset
dataset = [
"John Smith is a 30-year-old software engineer",
"Sarah Johnson is a 25-year-old data scientist",
"Mike Davis is a 35-year-old product manager",
"Lisa Wilson is a 28-year-old UX designer",
"Tom Brown is a 32-year-old DevOps engineer",
"Emma Garcia is a 27-year-old frontend developer",
"David Lee is a 33-year-old backend developer",
]
async def extract_person(text: str) -> Person:
"""Extract person information from text using LLM."""
return await client.chat.completions.create(
model="gpt-4o-mini",
response_model=Person,
messages=[{"role": "user", "content": f"Extract person info: {text}"}],
)
# Method 1: Sequential Processing (Baseline)
async def sequential_processing() -> tuple[list[Person], float]:
"""Process items one by one - slowest method."""
start_time = time.time()
persons = []
for text in dataset:
person = await extract_person(text)
persons.append(person)
print(f"Processed: {person.name}")
end_time = time.time()
duration = end_time - start_time
print(f"Sequential processing took: {duration:.2f} seconds")
return persons, duration
# Method 2: asyncio.gather - Concurrent Processing
async def gather_processing() -> tuple[list[Person], float]:
"""Process all items concurrently and return in order."""
start_time = time.time()
# Create tasks for all items
tasks = [extract_person(text) for text in dataset]
# Execute all tasks concurrently
persons = await asyncio.gather(*tasks)
end_time = time.time()
duration = end_time - start_time
print(f"asyncio.gather took: {duration:.2f} seconds")
# Results maintain original order
for person in persons:
print(f"Processed: {person.name}")
return persons, duration
# Method 3: asyncio.as_completed - Streaming Results
async def as_completed_processing() -> tuple[list[Person], float]:
"""Process items concurrently and handle results as they complete."""
start_time = time.time()
persons = []
# Create tasks for all items
tasks = [extract_person(text) for text in dataset]
# Process results as they complete
for task in asyncio.as_completed(tasks):
person = await task
persons.append(person)
print(f"Completed: {person.name}")
end_time = time.time()
duration = end_time - start_time
print(f"asyncio.as_completed took: {duration:.2f} seconds")
return persons, duration
# Method 4: Rate-Limited Processing with Semaphores
async def rate_limited_extract_person(
text: str, semaphore: asyncio.Semaphore
) -> Person:
"""Extract person info with rate limiting."""
async with semaphore:
return await extract_person(text)
async def rate_limited_gather(concurrency_limit: int = 3) -> tuple[list[Person], float]:
"""Process items with controlled concurrency using asyncio.gather."""
start_time = time.time()
# Create semaphore to limit concurrent requests
semaphore = asyncio.Semaphore(concurrency_limit)
# Create rate-limited tasks
tasks = [rate_limited_extract_person(text, semaphore) for text in dataset]
# Execute with rate limiting
persons = await asyncio.gather(*tasks)
end_time = time.time()
duration = end_time - start_time
print(
f"Rate-limited gather (limit={concurrency_limit}) took: {duration:.2f} seconds"
)
return persons, duration
async def rate_limited_as_completed(
concurrency_limit: int = 3,
) -> tuple[list[Person], float]:
"""Process items with controlled concurrency using asyncio.as_completed."""
start_time = time.time()
persons = []
# Create semaphore to limit concurrent requests
semaphore = asyncio.Semaphore(concurrency_limit)
# Create rate-limited tasks
tasks = [rate_limited_extract_person(text, semaphore) for text in dataset]
# Process results as they complete
for task in asyncio.as_completed(tasks):
person = await task
persons.append(person)
print(f"Rate-limited completed: {person.name}")
end_time = time.time()
duration = end_time - start_time
print(
f"Rate-limited as_completed (limit={concurrency_limit}) took: {duration:.2f} seconds"
)
return persons, duration
# Advanced Patterns
async def robust_gather_processing() -> tuple[list[Person], float]:
"""Process items with error handling."""
start_time = time.time()
tasks = [extract_person(text) for text in dataset]
# Execute with error handling
results = await asyncio.gather(*tasks, return_exceptions=True)
persons = []
for i, result in enumerate(results):
if isinstance(result, Exception):
print(f"Error processing item {i}: {result}")
else:
persons.append(result)
end_time = time.time()
duration = end_time - start_time
print(f"Robust gather processing took: {duration:.2f} seconds")
return persons, duration
async def timeout_gather_processing(
timeout_seconds: float = 30.0,
) -> tuple[list[Person], float]:
"""Process items with timeout."""
start_time = time.time()
tasks = [extract_person(text) for text in dataset]
try:
persons = await asyncio.wait_for(
asyncio.gather(*tasks), timeout=timeout_seconds
)
end_time = time.time()
duration = end_time - start_time
print(f"Timeout gather processing took: {duration:.2f} seconds")
return persons, duration
except asyncio.TimeoutError:
end_time = time.time()
duration = end_time - start_time
print(
f"Processing timed out after {timeout_seconds} seconds (took {duration:.2f}s)"
)
return [], duration
async def progress_tracking_processing() -> tuple[list[Person], float]:
"""Process items with progress tracking."""
start_time = time.time()
persons = []
total_items = len(dataset)
completed = 0
tasks = [extract_person(text) for text in dataset]
for task in asyncio.as_completed(tasks):
person = await task
persons.append(person)
completed += 1
print(
f"Progress: {completed}/{total_items} ({completed / total_items * 100:.1f}%)"
)
end_time = time.time()
duration = end_time - start_time
print(f"Progress tracking processing took: {duration:.2f} seconds")
return persons, duration
async def chunked_processing(chunk_size: int = 3) -> tuple[list[Person], float]:
"""Process items in chunks to manage memory and rate limits."""
start_time = time.time()
all_persons = []
# Process in chunks
for i in range(0, len(dataset), chunk_size):
chunk = dataset[i : i + chunk_size]
print(f"Processing chunk {i // chunk_size + 1}")
tasks = [extract_person(text) for text in chunk]
chunk_results = await asyncio.gather(*tasks)
all_persons.extend(chunk_results)
end_time = time.time()
duration = end_time - start_time
print(f"Chunked processing took: {duration:.2f} seconds")
return all_persons, duration
async def benchmark_all_methods():
"""Run all processing methods and compare performance."""
print("=== Python asyncio.gather and asyncio.as_completed Performance Test ===\n")
# Check if OpenAI API key is set
if not os.getenv("OPENAI_API_KEY"):
print("⚠️ OPENAI_API_KEY not set. Using mock responses for demonstration.")
return
# Test different methods
methods = [
("Sequential", sequential_processing),
("asyncio.gather", gather_processing),
("asyncio.as_completed", as_completed_processing),
("Rate-limited gather (3)", lambda: rate_limited_gather(3)),
("Rate-limited as_completed (3)", lambda: rate_limited_as_completed(3)),
("Robust gather", robust_gather_processing),
("Timeout gather", timeout_gather_processing),
("Progress tracking", progress_tracking_processing),
("Chunked processing", chunked_processing),
]
results = {}
for name, method in methods:
print(f"\n{'=' * 50}")
print(f"Testing: {name}")
print("=" * 50)
try:
persons, duration = await method()
results[name] = {
"count": len(persons),
"duration": duration,
"success": True,
}
print(f"✓ Success: {len(persons)} items processed in {duration:.2f}s")
# Show first few results
for person in persons[:3]:
print(f" - {person.name}, {person.age}, {person.occupation}")
if len(persons) > 3:
print(f" ... and {len(persons) - 3} more")
except Exception as e:
results[name] = {
"count": 0,
"duration": 0,
"success": False,
"error": str(e),
}
print(f"✗ Failed: {e}")
# Print summary table
print(f"\n{'=' * 80}")
print("PERFORMANCE SUMMARY")
print("=" * 80)
print(f"{'Method':<25} {'Items':<6} {'Time (s)':<10} {'Speed':<15} {'Status'}")
print("-" * 80)
for name, result in results.items():
if result["success"]:
speed = (
f"{result['count'] / result['duration']:.1f} items/s"
if result["duration"] > 0
else "N/A"
)
status = "✓ Success"
else:
speed = "N/A"
status = "✗ Failed"
print(
f"{name:<25} {result['count']:<6} {result['duration']:<10.2f} {speed:<15} {status}"
)
# Calculate speedup compared to sequential
if "Sequential" in results and results["Sequential"]["success"]:
baseline = results["Sequential"]["duration"]
print(f"\nSpeedup compared to sequential processing:")
for name, result in results.items():
if name != "Sequential" and result["success"] and result["duration"] > 0:
speedup = baseline / result["duration"]
print(f" {name}: {speedup:.1f}x faster")
def sync_example():
"""Show sync version for comparison."""
print("\n" + "=" * 50)
print("Sync Example (for comparison)")
print("=" * 50)
start_time = time.time()
persons = []
for text in dataset[:3]: # Just first 3 for demo
person = sync_client.chat.completions.create(
model="gpt-4o-mini",
response_model=Person,
messages=[{"role": "user", "content": f"Extract person info: {text}"}],
)
persons.append(person)
print(f"Sync processed: {person.name}")
end_time = time.time()
duration = end_time - start_time
print(f"Sync processing (3 items) took: {duration:.2f} seconds")
async def main():
"""Main function to run all examples."""
try:
await benchmark_all_methods()
# Run sync example if API key is available
if os.getenv("OPENAI_API_KEY"):
sync_example()
except KeyboardInterrupt:
print("\n⚠️ Interrupted by user")
except Exception as e:
print(f"❌ Error: {e}")
logger.exception("Unexpected error occurred")
if __name__ == "__main__":
print("🚀 Starting asyncio benchmarks with Instructor...")
print("💡 Make sure to set OPENAI_API_KEY environment variable")
print("⏱️ This will take a few minutes to complete all benchmarks\n")
asyncio.run(main())

View File

@@ -1,149 +0,0 @@
import instructor
from openai import OpenAI
from typing import Optional
from pydantic import BaseModel, Field
from enum import Enum
client = instructor.from_openai(OpenAI())
class PriorityEnum(str, Enum):
high = "High"
medium = "Medium"
low = "Low"
class Subtask(BaseModel):
"""
Correctly resolved subtask from the given transcript
"""
id: int = Field(..., description="Unique identifier for the subtask")
name: str = Field(..., description="Informative title of the subtask")
class Ticket(BaseModel):
"""
Correctly resolved ticket from the given transcript
"""
id: int = Field(..., description="Unique identifier for the ticket")
name: str = Field(..., description="Title of the task")
description: str = Field(..., description="Detailed description of the task")
priority: PriorityEnum = Field(..., description="Priority level")
assignees: list[str] = Field(..., description="List of users assigned to the task")
subtasks: Optional[list[Subtask]] = Field(
None, description="List of subtasks associated with the main task"
)
dependencies: Optional[list[int]] = Field(
None, description="List of ticket IDs that this ticket depends on"
)
class ActionItems(BaseModel):
"""
Correctly resolved set of action items from the given transcript
"""
items: list[Ticket]
def generate(data: str):
return client.chat.completions.create(
model="gpt-4o-mini",
response_model=ActionItems,
messages=[
{
"role": "system",
"content": "The following is a transcript of a meeting between a manager and their team. The manager is assigning tasks to their team members and creating action items for them to complete.",
},
{
"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."""
)
print(prediction.model_dump_json(indent=2))
"""
{
"items": [
{
"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
]
}
]
}
"""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

View File

@@ -1,159 +0,0 @@
#!/usr/bin/env python
"""
Example demonstrating the unified provider interface with string-based initialization.
Creates clients for multiple providers with both sync and async interfaces.
"""
import os
import asyncio
from typing import Any
import instructor
from pydantic import BaseModel, Field
class UserInfo(BaseModel):
"""Simple model to extract user information from text."""
name: str = Field(description="The user's full name")
age: int = Field(description="The user's age in years")
occupation: str = Field(description="The user's job or profession")
async def test_async_client(
client_name: str, client: instructor.AsyncInstructor
) -> dict[str, Any]:
"""Test an async client and return the results."""
print(f"Testing async client: {client_name}")
try:
result = await client.chat.completions.create(
response_model=UserInfo,
messages=[
{
"role": "user",
"content": "John Smith is a 35-year-old software engineer.",
}
],
)
print(f"✅ Async {client_name} result: {result.model_dump()}")
return {"provider": client_name, "success": True, "result": result.model_dump()}
except Exception as e:
print(f"❌ Async {client_name} error: {str(e)}")
return {"provider": client_name, "success": False, "error": str(e)}
def test_sync_client(client_name: str, client: instructor.Instructor) -> dict[str, Any]:
"""Test a sync client and return the results."""
print(f"Testing sync client: {client_name}")
try:
result = client.chat.completions.create(
response_model=UserInfo,
messages=[
{"role": "user", "content": "Jane Doe is a 28-year-old data scientist."}
],
)
print(f"✅ Sync {client_name} result: {result.model_dump()}")
return {"provider": client_name, "success": True, "result": result.model_dump()}
except Exception as e:
print(f"❌ Sync {client_name} error: {str(e)}")
return {"provider": client_name, "success": False, "error": str(e)}
async def main():
"""Create and test multiple clients using the unified provider interface."""
# Collect the test results
sync_results = []
async_results = []
# Test OpenAI clients
if os.environ.get("OPENAI_API_KEY"):
# Sync client
openai_client = instructor.from_provider("openai/gpt-3.5-turbo")
sync_results.append(test_sync_client("OpenAI", openai_client))
# Async client
openai_async = instructor.from_provider(
"openai/gpt-3.5-turbo", async_client=True
)
async_results.append(
asyncio.create_task(test_async_client("OpenAI", openai_async))
)
else:
print("⚠️ OPENAI_API_KEY not set, skipping OpenAI tests")
# Test Anthropic clients
if os.environ.get("ANTHROPIC_API_KEY"):
# Sync client
anthropic_client = instructor.from_provider(
model="anthropic/claude-3-haiku-20240307", max_tokens=400
)
sync_results.append(test_sync_client("Anthropic", anthropic_client))
# Async client
anthropic_async = instructor.from_provider(
model="anthropic/claude-3-haiku-20240307", async_client=True, max_tokens=400
)
async_results.append(
asyncio.create_task(test_async_client("Anthropic", anthropic_async))
)
else:
print("⚠️ ANTHROPIC_API_KEY not set, skipping Anthropic tests")
# Test Cohere clients
if os.environ.get("COHERE_API_KEY"):
# Sync client
cohere_client = instructor.from_provider("cohere/command")
sync_results.append(test_sync_client("Cohere", cohere_client))
# Async client
cohere_async = instructor.from_provider("cohere/command", async_client=True)
async_results.append(
asyncio.create_task(test_async_client("Cohere", cohere_async))
)
else:
print("⚠️ COHERE_API_KEY not set, skipping Cohere tests")
# Test Mistral clients
if os.environ.get("MISTRAL_API_KEY"):
# Sync client
mistral_client = instructor.from_provider("mistral/mistral-small")
sync_results.append(test_sync_client("Mistral", mistral_client))
# Async client
mistral_async = instructor.from_provider(
"mistral/mistral-small", async_client=True
)
async_results.append(
asyncio.create_task(test_async_client("Mistral", mistral_async))
)
else:
print("⚠️ MISTRAL_API_KEY not set, skipping Mistral tests")
# Process async results
if async_results:
completed_tasks = await asyncio.gather(*async_results)
async_results = completed_tasks
# Print summary
print("\n----- Test Results Summary -----")
print("\nSync Clients:")
for result in sync_results:
if result.get("success", False):
print(f"{result['provider']} - Success")
else:
print(
f"{result['provider']} - Failed: {result.get('error', 'Unknown error')}"
)
print("\nAsync Clients:")
for result in async_results:
if result.get("success", False):
print(f"{result['provider']} - Success")
else:
print(
f"{result['provider']} - Failed: {result.get('error', 'Unknown error')}"
)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,133 +0,0 @@
from pydantic import BaseModel, Field
from typing import Literal
from collections.abc import Iterable
from datetime import datetime, timedelta
from openai import OpenAI
import instructor
client = instructor.from_openai(OpenAI())
class DateRange(BaseModel):
explain: str = Field(
...,
description="Explain the date range in the context of the text before generating the date range and the repeat pattern.",
)
repeats: Literal["daily", "weekly", "monthly", None] = Field(
default=None,
description="If the date range repeats, and how often, this way we can generalize the date range to the future., if its special, then we can assume it is a one time event.",
)
days_of_week: list[
Literal[
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
None,
]
] = Field(
...,
description="If the date range repeats, which days of the week does it repeat on.",
)
time_start: datetime = Field(
description="The start of the first time range in the day."
)
time_end: datetime = Field(
description="The end of the first time range in the day."
)
class AvailabilityResponse(BaseModel):
availability: list[DateRange]
def prepare_dates(n=7) -> str:
# Current date and time
now = datetime.now()
acc = ""
# Loop for the next 7 days
for i in range(n):
# Calculate the date for each day
day = now + timedelta(days=i)
# Print the day of the week, date, and time
acc += "\n" + day.strftime("%A, %Y-%m-%d %H:%M:%S")
return acc.strip()
def parse_availability(text: str) -> Iterable[AvailabilityResponse]:
return client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[
{
"role": "system",
"content": "You are a state of the art date range parse designed to correctly extract availabilities.",
},
{
"role": "user",
"content": text,
},
{
"role": "user",
"content": f"To help you understand the dates, here are the next 7 days: {prepare_dates()}",
},
],
response_model=Iterable[AvailabilityResponse],
)
if __name__ == "__main__":
text = """
#1
12/8-12/24
9am - 5pm Monday - Saturday
10am - 5pm Sunday
#2
We are open Friday, after Thanksgiving, and then Saturdays and Sundays 9 a.m. till dusk.``
"""
schedules = parse_availability(text)
for schedule in schedules:
print(schedule.model_dump_json(indent=2))
{
"availability": [
{
"explain": "For the first date range, the availability is from December 8 to December 24, from 9 am to 5 pm on Mondays through Saturdays",
"repeats": "weekly",
"days_of_week": [
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
],
"time_start": "2023-12-08T09:00:00",
"time_end": "2023-12-08T17:00:00",
},
{
"explain": "For the same date range, the availability on Sundays is from 10 am to 5 pm",
"repeats": "weekly",
"days_of_week": ["sunday"],
"time_start": "2023-12-10T10:00:00",
"time_end": "2023-12-10T17:00:00",
},
]
}
{
"availability": [
{
"explain": "The second date range starting from the Friday after Thanksgiving, which is November 24, 2023, and then on Saturdays and Sundays from 9 am until dusk. Assuming 'dusk' means approximately 5 pm, similar to the previous timings.",
"repeats": "weekly",
"days_of_week": ["friday", "saturday", "sunday"],
"time_start": "2023-11-24T09:00:00",
"time_end": "2023-11-24T17:00:00",
}
]
}

View File

@@ -1,141 +0,0 @@
import os
from pydantic import BaseModel, Field
from typing import Literal
from datetime import datetime, timedelta
from openai import OpenAI
import instructor
client = instructor.from_openai(
OpenAI(
base_url="https://api.endpoints.anyscale.com/v1",
api_key=os.environ["ANYSCALE_API_KEY"],
),
mode=instructor.Mode.JSON_SCHEMA,
model="mistralai/Mixtral-8x7B-Instruct-v0.1",
)
class DateRange(BaseModel):
explain: str = Field(
...,
description="Explain the date range in the context of the text before generating the date range and the repeat pattern.",
)
repeats: Literal["daily", "weekly", "monthly", None] = Field(
default=None,
description="If the date range repeats, and how often, this way we can generalize the date range to the future., if its special, then we can assume it is a one time event.",
)
days_of_week: list[
Literal[
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
None,
]
] = Field(
...,
description="If the date range repeats, which days of the week does it repeat on.",
)
time_start: datetime = Field(
description="The start of the first time range in the day."
)
time_end: datetime = Field(
description="The end of the first time range in the day."
)
class AvailabilityResponse(BaseModel):
availability: list[DateRange]
def prepare_dates(n=7) -> str:
# Current date and time
now = datetime.now()
acc = ""
# Loop for the next 7 days
for i in range(n):
# Calculate the date for each day
day = now + timedelta(days=i)
# Print the day of the week, date, and time
acc += "\n" + day.strftime("%A, %Y-%m-%d %H:%M:%S")
return acc.strip()
def parse_availability(text: str):
return client.chat.completions.create_iterable(
max_tokens=10000,
messages=[
{
"role": "system",
"content": "You are a state of the art date range parse designed to correctly extract availabilities.",
},
{
"role": "user",
"content": text,
},
{
"role": "user",
"content": f"To help you understand the dates, here are the next 7 days: {prepare_dates()}",
},
],
response_model=AvailabilityResponse,
max_retries=3,
)
if __name__ == "__main__":
text = """
#1
12/8-12/24
9am - 5pm Monday - Saturday
10am - 5pm Sunday
#2
We are open Friday, after Thanksgiving, and then Saturdays and Sundays 9 a.m. till dusk.``
"""
schedules = parse_availability(text)
for schedule in schedules:
print(schedule.model_dump_json(indent=2))
{
"availability": [
{
"explain": "For the first date range, the availability is from December 8 to December 24, from 9 am to 5 pm on Mondays through Saturdays",
"repeats": "weekly",
"days_of_week": [
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
],
"time_start": "2023-12-08T09:00:00",
"time_end": "2023-12-08T17:00:00",
},
{
"explain": "For the same date range, the availability on Sundays is from 10 am to 5 pm",
"repeats": "weekly",
"days_of_week": ["sunday"],
"time_start": "2023-12-10T10:00:00",
"time_end": "2023-12-10T17:00:00",
},
]
}
{
"availability": [
{
"explain": "The second date range starting from the Friday after Thanksgiving, which is November 24, 2023, and then on Saturdays and Sundays from 9 am until dusk. Assuming 'dusk' means approximately 5 pm, similar to the previous timings.",
"repeats": "weekly",
"days_of_week": ["friday", "saturday", "sunday"],
"time_start": "2023-11-24T09:00:00",
"time_end": "2023-11-24T17:00:00",
}
]
}

View File

@@ -1,98 +0,0 @@
import instructor
import asyncio
from openai import AsyncOpenAI
from pydantic import BaseModel, Field, field_validator
from enum import Enum
client = instructor.from_openai(AsyncOpenAI(), mode=instructor.Mode.TOOLS)
sem = asyncio.Semaphore(5)
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
# Modify the classify function
async def classify(data: str):
async with sem: # some simple rate limiting
return data, await client.chat.completions.create(
model="gpt-4",
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]
resps = []
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 was the book I saw on amazon yesturday?",
"Can you speak german?",
"Do you have access to the meeting transcripts?",
"what are the recent sites I visited?",
"what did I do on Monday?",
"Tell me about todays meeting and how it relates to the email on Monday",
]
asyncio.run(main(questions))

View File

@@ -1,100 +0,0 @@
import json
import instructor
import asyncio
from openai import AsyncOpenAI
from pydantic import BaseModel, Field, field_validator
from enum import Enum
client = AsyncOpenAI()
client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)
sem = asyncio.Semaphore(5)
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
async def classify(data: str):
async with sem: # some simple rate limiting
return data, await client.chat.completions.create(
model="gpt-4",
response_model=QuestionClassification,
max_retries=2,
messages=[
{
"role": "user",
"content": f"Classify the following question: {data}",
},
],
)
async def main(questions: list[str], *, path_to_jsonl: str = None):
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],
}
print(resp)
if path_to_jsonl:
with open(path_to_jsonl, "a") as f:
json_dump = json.dumps(resp)
f.write(json_dump + "\n")
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 was the book I saw on amazon yesturday?",
"Can you speak german?",
"Do you have access to the meeting transcripts?",
"what are the recent sites I visited?",
"what did I do on Monday?",
"Tell me about todays meeting and how it relates to the email on Monday",
]
asyncio.run(main(questions))

View File

@@ -1,103 +0,0 @@
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 enum import Enum
client = wrap_openai(AsyncOpenAI())
client = instructor.from_openai(client, mode=instructor.Mode.TOOLS)
sem = asyncio.Semaphore(5)
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
# Modify the classify function
@traceable(name="classify-question")
async def classify(data: str):
async with sem: # some simple rate limiting
return data, await client.chat.completions.create(
model="gpt-4",
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]
resps = []
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 was the book I saw on amazon yesturday?",
"Can you speak german?",
"Do you have access to the meeting transcripts?",
"what are the recent sites I visited?",
"what did I do on Monday?",
"Tell me about todays meeting and how it relates to the email on Monday",
]
asyncio.run(main(questions))

View File

@@ -1,223 +0,0 @@
# Batch API Examples
This directory contains examples and test scripts for Instructor's batch processing capabilities, including both traditional file-based and new in-memory processing.
## Examples
### 1. In-Memory Batch Processing (`in_memory_batch_example.py`)
Demonstrates the new in-memory batch processing feature, perfect for serverless deployments:
```bash
python in_memory_batch_example.py
```
**Key Features:**
- No disk I/O required - ideal for serverless environments
- BytesIO buffers instead of temporary files
- Automatic cleanup - no file management needed
- Security benefits - no temporary files on disk
### 2. Unified Test Script (`run_batch_test.py`)
Tests the unified BatchProcessor with all supported providers: OpenAI, Anthropic, and Google Gemini.
The script creates a batch job to extract structured `User(name: str, age: int)` data from 10 text examples and saves the batch ID for later checking. Since batch jobs can take time to complete, the script returns immediately after creation.
## Unified Test Script (`run_batch_test.py`)
Tests the unified BatchProcessor with any supported provider/model combination.
### Usage
```bash
# Test OpenAI
export OPENAI_API_KEY="your-openai-api-key"
python run_batch_test.py create --model "openai/gpt-4o-mini"
# Test Anthropic
export ANTHROPIC_API_KEY="your-anthropic-api-key"
python run_batch_test.py create --model "anthropic/claude-3-5-sonnet-20241022"
# Test Google (simulation mode)
python run_batch_test.py create --model "google/gemini-2.0-flash-001"
```
### Supported Models
Use the `list-models` command to see all supported models:
```bash
python run_batch_test.py list-models
```
**OpenAI Models:**
- `openai/gpt-4o-mini`
- `openai/gpt-4o`
- `openai/gpt-4-turbo`
**Anthropic Models:**
- `anthropic/claude-3-5-sonnet-20241022`
- `anthropic/claude-3-opus-20240229`
- `anthropic/claude-3-haiku-20240307`
**Google Models:**
- `google/gemini-2.0-flash-001`
- `google/gemini-pro`
- `google/gemini-pro-vision`
### What the Script Does
1. **Creates test messages**: 10 prompts containing user information
2. **Uses BatchProcessor**: Leverages the unified API with provider detection
3. **Generates batch file**: Provider-specific format with JSON schema
4. **Submits batch job**: Actual API call to create the batch
5. **Saves batch ID**: Stores ID in `{provider}_batch_id.txt`
6. **Returns immediately**: No waiting for completion
### API Keys Required
| Provider | Environment Variable | Required |
|----------|---------------------|----------|
| OpenAI | `OPENAI_API_KEY` | Yes |
| Anthropic | `ANTHROPIC_API_KEY` | Yes |
| Google | `GOOGLE_API_KEY` | No (simulation mode) |
### Output Files
Each run creates:
- `{provider}_batch_id.txt` - Contains the batch ID for status checking
- Temporary batch files (automatically cleaned up)
### Test Data
All providers use the same 10 test prompts:
1. "Hi there! My name is Alice and I'm 28 years old. I work as a software engineer."
2. "Hello, I'm Bob, 35 years old, and I love hiking and photography."
3. "This is Sarah speaking. I'm 42 and I'm a graphic designer."
4. "Hey! John here, I'm 29 years old and I teach high school math."
5. "I'm Emma, 33 years old, currently working as a marketing manager."
6. "My name is Michael and I'm 45 years old. I'm a chef at a downtown restaurant."
7. "I'm Lisa, 31 years old, working as a nurse at the local hospital."
8. "This is David, 38 years old, I'm a freelance photographer."
9. "Hello, I'm Jessica, 26 years old, and I'm a data scientist."
10. "I'm Ryan, 41 years old, working in software development for a tech startup."
### Expected Results
Each batch job should extract `User` objects:
```python
class User(BaseModel):
name: str
age: int
```
Expected extractions:
- Alice, 28 | Bob, 35 | Sarah, 42 | John, 29 | Emma, 33
- Michael, 45 | Lisa, 31 | David, 38 | Jessica, 26 | Ryan, 41
## Checking Batch Status
After creating batch jobs, use the CLI to check their status:
```bash
# List all batch jobs for a provider
instructor batch list --model "openai/gpt-4o-mini"
instructor batch list --model "anthropic/claude-3-5-sonnet-20241022"
# Check specific batch status
instructor batch status --batch-id "batch_123" --model "openai/gpt-4o-mini"
# Get results when completed
instructor batch results \
--batch-id "batch_123" \
--output-file "results.jsonl" \
--model "openai/gpt-4o-mini"
```
## Processing Times
- **OpenAI**: Usually completes within a few hours, guaranteed within 24h
- **Anthropic**: Most batches complete in under 1 hour
- **Google**: Varies (simulation only in this test)
## Running Tests for All Providers
```bash
# Test all providers (requires API keys)
python run_batch_test.py create --model "openai/gpt-4o-mini"
python run_batch_test.py create --model "anthropic/claude-3-5-sonnet-20241022"
python run_batch_test.py create --model "google/gemini-2.0-flash-001"
# Check what was created
ls *_batch_id.txt
```
## Troubleshooting
### Common Issues
1. **API Key Not Set**
```
❌ Error: OPENAI_API_KEY environment variable is not set
```
Solution: Set the appropriate environment variable.
2. **Invalid Model Format**
```
❌ Error: Model must be in format 'provider/model-name'
```
Solution: Use the format `provider/model-name`, e.g., `openai/gpt-4o-mini`.
3. **Unsupported Provider**
```
❌ Unsupported provider: xyz
```
Solution: Use `openai`, `anthropic`, or `google` as the provider.
### Provider-Specific Notes
**OpenAI:**
- Requires valid API key with sufficient credits
- Supports both individual and organization accounts
- Rate limits are separate for batch vs regular API
**Anthropic:**
- Uses beta API endpoints (`client.beta.messages.batches`)
- Requires Anthropic API access
- May have different availability by region
**Google:**
- Runs in simulation mode by default
- Full implementation requires Google Cloud Storage setup
- Would need proper GCS authentication for real batch jobs
## Integration with CLI
This test validates that the unified BatchProcessor works correctly, which powers the CLI commands:
```bash
# Create batch using CLI directly
instructor batch create \
--messages-file messages.jsonl \
--model "openai/gpt-4o-mini" \
--response-model "examples.User" \
--output-file batch_requests.jsonl
# Submit the batch
instructor batch create-from-file \
--file-path batch_requests.jsonl \
--model "openai/gpt-4o-mini"
```
## Development
To modify the test:
1. Update `create_test_messages()` to change test data
2. Modify the `User` model if needed
3. Add new providers in the provider detection logic
4. Adjust batch creation functions for new provider-specific behavior
The test demonstrates that the same code works across all providers thanks to the unified BatchProcessor abstraction!

View File

@@ -1,244 +0,0 @@
#!/usr/bin/env python3
"""Example of using in-memory batching for serverless deployments.
This example shows how to create and submit batch requests without writing to disk
"""
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():
"""Demonstrate in-memory batch processing."""
print("In-Memory Batch Processing Example")
print("===================================\n")
# Initialize batch processor
# Note: Use gpt-4o-mini for JSON schema support in batch API
processor = BatchProcessor("openai/gpt-4o-mini", 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",
},
],
]
print("Creating batch requests in memory...")
# 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\n")
# Show the content of the buffer (first 200 chars)
batch_buffer.seek(0)
content_preview = batch_buffer.read(200).decode("utf-8")
print("Buffer content preview:")
print(f"{content_preview}...\n")
# Reset buffer position for submission
batch_buffer.seek(0)
print("Submitting batch job...")
try:
# 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!")
print(f"Batch ID: {batch_id}")
# Poll for completion
print("\nWaiting for batch to complete...")
max_wait_time = 300 # 5 minutes max
start_time = time.time()
status = {}
while time.time() - start_time < max_wait_time:
status = processor.get_batch_status(batch_id)
current_status = status.get("status", "unknown")
# Update status on the same line
print(f"\rCurrent status: {current_status.ljust(20)}", end="")
if current_status in ["completed", "failed", "cancelled", "expired"]:
break
time.sleep(10)
print() # Newline after polling is done
# Use the last fetched status
final_status = status
print(f"\nFinal status: {final_status.get('status', 'unknown')}")
if final_status.get("status") == "completed":
print("\nBatch completed! Retrieving results...")
# Retrieve and process results
results = processor.get_results(batch_id)
print(f"\nResults Summary:")
print(f" Total results: {len(results)}")
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" 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}")
elif final_status.get("status") == "failed":
print("\nBatch failed to complete")
print(" Check your API usage and batch format")
else:
print(f"\nBatch did not complete within {max_wait_time} seconds")
print(f" Current status: {final_status.get('status', 'unknown')}")
print(
" You can check status later with processor.get_batch_status(batch_id)"
)
except Exception as e:
print(f"Error during batch processing: {e}")
print("\nThis is expected if you don't have OpenAI API credentials set up.")
print(
" The important part is that the in-memory buffer was created successfully!"
)
print("\nIn-memory batch processing demo complete!")
print("\nKey benefits of in-memory batching:")
print(" - No disk I/O required - perfect for serverless")
print(" - Faster processing - no file system overhead")
print(" - Better security - no temporary files on disk")
print(" - Cleaner code - no file cleanup required")
def compare_file_vs_memory():
"""Compare file-based vs in-memory batch creation."""
print("\nComparing File-based vs In-Memory Batching")
print("===========================================\n")
processor = BatchProcessor("openai/gpt-4o-mini", User)
messages_list = [
[{"role": "user", "content": "Extract: John, 25, john@example.com"}],
[{"role": "user", "content": "Extract: Jane, 30, jane@example.com"}],
]
# File-based approach (traditional)
print("File-based approach:")
file_path = processor.create_batch_from_messages(
messages_list,
file_path="temp_batch.jsonl", # Specify file path
)
print(f" Created file: {file_path}")
# Clean up the file
import os
if os.path.exists(file_path):
os.remove(file_path)
print(" File cleaned up")
# In-memory approach (new)
print("\nIn-memory approach:")
buffer = processor.create_batch_from_messages(
messages_list,
file_path=None, # No file path = in-memory
)
print(f" Created buffer: {type(buffer).__name__}")
print(f" Buffer size: {len(buffer.getvalue())} bytes")
print(" No cleanup required!")
def demo_polling_logic():
"""Demonstrate how to properly poll for batch completion."""
print("\nBatch Polling Best Practices")
print("============================\n")
print("When working with real batches, follow this pattern:")
print("")
print("```python")
print("import time")
print("")
print("# Submit your batch")
print("batch_id = processor.submit_batch(buffer)")
print("")
print("# Poll for completion")
print("while True:")
print(" status = processor.get_batch_status(batch_id)")
print(" current_status = status.get('status')")
print(" ")
print(" if current_status == 'completed':")
print(" results = processor.get_results(batch_id)")
print(" break")
print(" elif current_status in ['failed', 'cancelled', 'expired']:")
print(" print(f'Batch failed with status: {current_status}')")
print(" break")
print(" else:")
print(" print(f'Status: {current_status}, waiting...')")
print(" time.sleep(10) # Wait 10 seconds before checking again")
print("```")
print("")
print("Typical batch statuses:")
print(" - validating - Checking request format")
print(" - in_progress - Processing requests")
print(" - finalizing - Preparing results")
print(" - completed - Ready for download")
print(" - failed - Something went wrong")
print(" - cancelled - Manually cancelled")
print(" - expired - Took too long to process")
if __name__ == "__main__":
main()
compare_file_vs_memory()

View File

@@ -1,851 +0,0 @@
#!/usr/bin/env python3
"""Unified Batch API Test Script
Test script to verify the unified BatchProcessor works correctly with all supported providers.
Creates a batch job to extract User(name: str, age: int) data from text examples.
Supports:
- OpenAI: openai/gpt-4o-mini, openai/gpt-4o, etc.
- Anthropic: anthropic/claude-3-5-sonnet-20241022, anthropic/claude-3-opus-20240229, etc.
- Google: google/gemini-2.5-flash, google/gemini-pro, etc.
Usage:
# Default (Google Gemini 2.5 Flash)
export GOOGLE_API_KEY="your-key"
python run_batch_test.py
# OpenAI
export OPENAI_API_KEY="your-key"
python run_batch_test.py --model "openai/gpt-4o-mini"
# Anthropic
export ANTHROPIC_API_KEY="your-key"
python run_batch_test.py --model "anthropic/claude-3-5-sonnet-20241022"
# Google with specific model
export GOOGLE_API_KEY="your-key"
python run_batch_test.py --model "google/gemini-2.5-flash"
"""
import os
import sys
from typing import Optional
import typer
from pydantic import BaseModel
# Add parent directory to path for imports
sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))
from instructor.batch import (
BatchProcessor,
BatchStatus,
filter_successful,
filter_errors,
extract_results,
)
app = typer.Typer(help="Unified Batch API Test for all providers")
class User(BaseModel):
name: str
age: int
def create_test_messages() -> list[list[dict]]:
"""Create test message conversations for user extraction"""
test_prompts = [
"Hi there! My name is Alice and I'm 28 years old. I work as a software engineer.",
]
messages_list = []
for prompt in test_prompts:
messages = [
{
"role": "system",
"content": "You are an expert at extracting structured user information from text. Extract the person's name and age.",
},
{"role": "user", "content": prompt},
]
messages_list.append(messages)
return messages_list
def get_expected_results() -> list[User]:
"""Get the expected User objects for validation"""
return [
User(name="Alice", age=28),
]
def check_api_key(provider: str) -> bool:
"""Check if the required API key is set for the provider"""
key_map = {
"openai": "OPENAI_API_KEY",
"anthropic": "ANTHROPIC_API_KEY",
"google": "GOOGLE_API_KEY",
}
required_key = key_map.get(provider)
if not required_key:
return True # Unknown provider, let it fail later
if provider == "google":
# Google is optional since we simulate
if not os.getenv(required_key):
typer.echo(f"Warning: {required_key} not set - will run in simulation mode")
return True
if not os.getenv(required_key):
typer.echo(f"Error: {required_key} environment variable is not set", err=True)
typer.echo(
f"Please set your API key: export {required_key}='your-api-key-here'",
err=True,
)
return False
return True
def create_openai_batch(model: str, messages_list: list[list[dict]]) -> Optional[str]:
"""Create OpenAI batch job using BatchProcessor"""
processor = BatchProcessor(model, User)
# Create batch file
batch_filename = "test_batch.jsonl"
processor.create_batch_from_messages(
file_path=batch_filename,
messages_list=messages_list,
max_tokens=200,
temperature=0.1,
)
try:
typer.echo("Submitting batch job...")
batch_id = processor.submit_batch(
file_path=batch_filename,
metadata={"description": "Unified BatchProcessor test"},
)
return batch_id
finally:
if os.path.exists(batch_filename):
os.remove(batch_filename)
def create_anthropic_batch(
model: str, messages_list: list[list[dict]]
) -> Optional[str]:
"""Create Anthropic batch job using BatchProcessor"""
processor = BatchProcessor(model, User)
# Create batch file
batch_filename = "test_batch.jsonl"
processor.create_batch_from_messages(
file_path=batch_filename,
messages_list=messages_list,
max_tokens=200,
temperature=0.1,
)
try:
typer.echo("Submitting batch job...")
batch_id = processor.submit_batch(file_path=batch_filename)
return batch_id
finally:
if os.path.exists(batch_filename):
os.remove(batch_filename)
def create_google_batch(model: str, messages_list: list[list[dict]]) -> Optional[str]:
"""Create Google batch job using BatchProcessor (inline only)"""
processor = BatchProcessor(model, User)
typer.echo("Submitting Google inline batch...")
batch_id = processor.submit_batch(
messages_list=messages_list,
metadata={"description": "Unified BatchProcessor test"},
use_inline=True,
max_tokens=200,
temperature=0.1,
)
typer.echo(f"Inline batch job created: {batch_id}")
return batch_id
@app.command()
def create(
model: str = typer.Option(
"openai/gpt-4o-mini",
help="Model in format 'provider/model-name' (e.g., 'google/gemini-2.5-flash', 'openai/gpt-4o-mini', 'anthropic/claude-3-5-sonnet-20241022')",
),
save_id: bool = typer.Option(True, help="Save batch ID to file"),
):
"""Create a batch job for the specified model"""
typer.echo(f"Creating Batch Job for {model}")
typer.echo("=" * 50)
# Parse provider from model
try:
provider, model_name = model.split("/", 1)
except ValueError:
typer.echo("Error: Model must be in format 'provider/model-name'", err=True)
typer.echo(
"Examples: 'openai/gpt-4o-mini', 'anthropic/claude-3-5-sonnet-20241022'",
err=True,
)
raise typer.Exit(1) from None
# Check API key
if not check_api_key(provider):
raise typer.Exit(1)
# Create test messages
messages_list = create_test_messages()
typer.echo(f"Created {len(messages_list)} test message conversations")
try:
# Create batch job based on provider
batch_id = None
if provider == "openai":
batch_id = create_openai_batch(model, messages_list)
elif provider == "anthropic":
batch_id = create_anthropic_batch(model, messages_list)
else:
typer.echo(f"Unsupported provider: {provider}", err=True)
raise typer.Exit(1)
if batch_id:
typer.echo(f"Batch job created with ID: {batch_id}")
if save_id:
filename = f"{provider}_batch_id.txt"
with open(filename, "w") as f:
f.write(batch_id)
typer.echo(f"Batch ID saved to {filename}")
# Validate expected results
expected_results = get_expected_results()
typer.echo(f"Expected results validated: {len(expected_results)} users")
for i, user in enumerate(expected_results):
typer.echo(f" {i + 1}. {user.name}, age {user.age}")
# Show how to check status
typer.echo(f"Check status with:")
typer.echo(f" instructor batch list --model {model}")
typer.echo(f"Cost savings: 50% vs regular API")
typer.echo(f"\nSuccess! Batch ID: {batch_id}")
else:
typer.echo("Failed to create batch job", err=True)
raise typer.Exit(1)
except Exception as e:
typer.echo(f"Error creating batch: {e}", err=True)
raise typer.Exit(1) from e
@app.command()
def list_batches():
"""List saved batch IDs for all providers"""
typer.echo("Saved Batch IDs:")
typer.echo("=" * 30)
providers = ["openai", "anthropic"]
found_any = False
for provider in providers:
filename = f"{provider}_batch_id.txt"
if os.path.exists(filename):
with open(filename) as f:
batch_id = f.read().strip()
typer.echo(f"{provider.upper()}: {batch_id}")
found_any = True
if not found_any:
typer.echo("No batch IDs found. Run 'create' command first.")
typer.echo(
"Usage: python run_batch_test.py create --model 'provider/model-name'"
)
else:
typer.echo()
typer.echo(
"To fetch results: python run_batch_test.py fetch --provider <provider>"
)
@app.command()
def fetch(
provider: str = typer.Option(
help="Provider to fetch results from (openai, anthropic, google)"
),
validate: bool = typer.Option(
True, help="Validate extracted data against expected results"
),
poll: bool = typer.Option(
False, help="Poll every 30 seconds until batch completes"
),
max_wait: int = typer.Option(
600, help="Maximum time to wait in seconds (default: 10 minutes)"
),
):
"""Fetch and validate batch results from a provider"""
if provider not in ["openai", "anthropic"]:
typer.echo("Error: Provider must be one of: openai, anthropic", err=True)
raise typer.Exit(1)
# Check if batch ID file exists
filename = f"{provider}_batch_id.txt"
if not os.path.exists(filename):
typer.echo(
f"Error: No batch ID found for {provider}. Run 'create' command first.",
err=True,
)
raise typer.Exit(1)
# Read batch ID
with open(filename) as f:
batch_id = f.read().strip()
typer.echo(f"Fetching results for {provider.upper()} batch: {batch_id}")
typer.echo("=" * 60)
# Check API key
if not check_api_key(provider):
raise typer.Exit(1)
try:
if poll:
results = poll_for_results(provider, batch_id, validate, max_wait)
else:
if provider == "openai":
results = fetch_openai_results(batch_id, validate)
elif provider == "anthropic":
results = fetch_anthropic_results(batch_id, validate)
if results:
typer.echo(f"Successfully fetched and validated {len(results)} results!")
if validate:
# Assert that the results match the expected results
assert validate_results(results, provider.capitalize()), (
f"Test failed: {provider} results do not match expected results."
)
else:
typer.echo("No results available yet or batch still processing")
if not poll:
typer.echo("Use --poll to automatically wait for completion")
except AssertionError as ae:
typer.echo(f"AssertionError: {ae}", err=True)
raise typer.Exit(1) from ae
except Exception as e:
typer.echo(f"Error fetching results: {e}", err=True)
raise typer.Exit(1) from e
@app.command()
def show_results(
provider: str = typer.Option(
help="Provider to show detailed results from (openai, anthropic, google)"
),
):
"""Show detailed parsed Pydantic objects from batch results"""
if provider not in ["openai", "anthropic"]:
typer.echo("Error: Provider must be one of: openai, anthropic", err=True)
raise typer.Exit(1)
# Check if batch ID file exists
filename = f"{provider}_batch_id.txt"
if not os.path.exists(filename):
typer.echo(
f"Error: No batch ID found for {provider}. Run 'create' command first.",
err=True,
)
raise typer.Exit(1)
# Read batch ID
with open(filename) as f:
batch_id = f.read().strip()
typer.echo(f"{provider.upper()} BATCH RESULTS")
typer.echo("=" * 50)
typer.echo(f"Batch ID: {batch_id}")
# Check API key
if not check_api_key(provider):
raise typer.Exit(1)
try:
# Get results using BatchProcessor
if provider == "openai":
processor = BatchProcessor("openai/gpt-4o-mini", User)
elif provider == "anthropic":
processor = BatchProcessor("anthropic/claude-3-5-sonnet-20241022", User)
# Get batch info using list_batches to find our batch
all_batches = processor.list_batches(limit=100)
batch_info = None
for batch in all_batches:
if batch.id == batch_id:
batch_info = batch
break
if not batch_info:
typer.echo(f"Batch {batch_id} not found")
return
typer.echo(f"Status: {batch_info.status.value}")
typer.echo(f"Raw Status: {batch_info.raw_status}")
if batch_info.status != BatchStatus.COMPLETED:
typer.echo(f"Batch not completed yet: {batch_info.status.value}")
return
# Get all results using the new get_results method
all_results = processor.get_results(batch_id)
typer.echo(f"Total results: {len(all_results)}")
# Show each result with detailed info
for i, result in enumerate(all_results):
typer.echo(f"\n--- Result {i + 1} ---")
typer.echo(f"Custom ID: {result.custom_id}")
typer.echo(f"Success: {result.success}")
if result.success:
user = result.result
typer.echo(f"PARSED USER OBJECT:")
typer.echo(f" Type: {type(user)}")
typer.echo(f" Name: {user.name}")
typer.echo(f" Age: {user.age}")
typer.echo(f" JSON: {user.model_dump_json()}")
typer.echo(f" Dict: {user.model_dump()}")
# Test that it's a real Pydantic object
typer.echo(f" Is BaseModel: {isinstance(user, BaseModel)}")
typer.echo(f" Is User: {isinstance(user, User)}")
# Test Pydantic methods
try:
validated = User.model_validate(user.model_dump())
typer.echo(f" Re-validation: Works")
typer.echo(f" Re-validated: {validated}")
except Exception as e:
typer.echo(f" Re-validation: Failed - {e}")
else:
typer.echo(f"ERROR:")
typer.echo(f" Type: {result.error_type}")
typer.echo(f" Message: {result.error_message}")
# Test the utility functions
successful_results = filter_successful(all_results)
error_results = filter_errors(all_results)
extracted_users = extract_results(all_results)
typer.echo(f"\nUTILITY FUNCTIONS:")
typer.echo(f"Successful results: {len(successful_results)}")
typer.echo(f"Error results: {len(error_results)}")
typer.echo(f"Extracted users: {len(extracted_users)}")
if extracted_users:
typer.echo(f"\nEXTRACTED USER OBJECTS:")
for user in extracted_users:
typer.echo(
f"{user.name}, age {user.age} (type: {type(user).__name__})"
)
except Exception as e:
typer.echo(f"Error showing results: {e}", err=True)
raise typer.Exit(1) from e
def poll_for_results(
provider: str, batch_id: str, validate: bool, max_wait: int
) -> list[User]:
"""Poll for batch results until completion or timeout"""
import time
typer.echo(f"Polling {provider.upper()} batch every 30 seconds...")
typer.echo(f"Max wait time: {max_wait} seconds ({max_wait // 60} minutes)")
typer.echo(f"Batch ID: {batch_id}")
typer.echo()
start_time = time.time()
attempt = 1
while time.time() - start_time < max_wait:
typer.echo(f"Attempt {attempt} - Checking batch status...")
try:
if provider == "openai":
status, results = fetch_openai_results_with_status(batch_id, validate)
elif provider == "anthropic":
status, results = fetch_anthropic_results_with_status(
batch_id, validate
)
if status == "completed" or status == "ended":
typer.echo(
f"Batch completed after {int(time.time() - start_time)} seconds!"
)
return results
elif status in ["failed", "expired", "cancelled"]:
typer.echo(f"Batch {status}")
return []
else:
elapsed = int(time.time() - start_time)
remaining = max_wait - elapsed
typer.echo(
f"Status: {status} | Elapsed: {elapsed}s | Remaining: {remaining}s"
)
if remaining > 30:
typer.echo("Waiting 30 seconds before next check...")
time.sleep(30)
else:
typer.echo(f"Waiting {remaining} seconds...")
time.sleep(remaining)
break
except Exception as e:
typer.echo(f"Error during polling: {e}")
time.sleep(30)
attempt += 1
typer.echo(f"Timeout reached after {max_wait} seconds")
return []
def fetch_openai_results_with_status(
batch_id: str, validate: bool
) -> tuple[str, list[User]]:
"""Fetch OpenAI batch results and return status"""
processor = BatchProcessor("openai/gpt-4o-mini", User)
# Get batch info
all_batches = processor.list_batches(limit=100)
batch_info = None
for batch in all_batches:
if batch.id == batch_id:
batch_info = batch
break
if not batch_info:
return "not_found", []
if batch_info.status != BatchStatus.COMPLETED:
return batch_info.raw_status, []
# Get results using the new get_results method
all_results = processor.get_results(batch_id)
successful_results = filter_successful(all_results)
error_results = filter_errors(all_results)
extracted_results = extract_results(all_results)
typer.echo(f"Successful extractions: {len(successful_results)}")
if error_results:
typer.echo(f"Failed extractions: {len(error_results)}")
# Show first few errors for debugging
for error in error_results[:3]:
typer.echo(f" Error ({error.custom_id}): {error.error_message}")
if validate and extracted_results:
validate_results(extracted_results, "OpenAI")
return "completed", extracted_results
def fetch_anthropic_results_with_status(
batch_id: str, validate: bool
) -> tuple[str, list[User]]:
"""Fetch Anthropic batch results and return status"""
processor = BatchProcessor("anthropic/claude-3-5-sonnet-20241022", User)
# Get batch info
all_batches = processor.list_batches(limit=100)
batch_info = None
for batch in all_batches:
if batch.id == batch_id:
batch_info = batch
break
if not batch_info:
return "not_found", []
# Check for various terminal states
if batch_info.status in [
BatchStatus.FAILED,
BatchStatus.CANCELLED,
BatchStatus.EXPIRED,
]:
return batch_info.raw_status, []
if batch_info.status != BatchStatus.COMPLETED:
return batch_info.raw_status, []
# Get results using the new get_results method
all_results = processor.get_results(batch_id)
successful_results = filter_successful(all_results)
error_results = filter_errors(all_results)
extracted_results = extract_results(all_results)
typer.echo(f"Successful extractions: {len(successful_results)}")
if error_results:
typer.echo(f"Failed extractions: {len(error_results)}")
# Show first few errors for debugging
for error in error_results[:3]:
typer.echo(f" Error ({error.custom_id}): {error.error_message}")
if validate and extracted_results:
validate_results(extracted_results, "Anthropic")
return "ended", extracted_results
def fetch_openai_results(batch_id: str, validate: bool) -> list[User]:
"""Fetch OpenAI batch results using BatchProcessor"""
processor = BatchProcessor("openai/gpt-4o-mini", User)
# Get batch info
all_batches = processor.list_batches(limit=100)
batch_info = None
for batch in all_batches:
if batch.id == batch_id:
batch_info = batch
break
if not batch_info:
typer.echo(f"Batch {batch_id} not found")
return []
typer.echo(f"Batch Status: {batch_info.status.value}")
if batch_info.status != BatchStatus.COMPLETED:
typer.echo(
f"Batch is still {batch_info.status.value}. Please wait and try again."
)
return []
# Get results using the new get_results method
all_results = processor.get_results(batch_id)
successful_results = filter_successful(all_results)
error_results = filter_errors(all_results)
extracted_results = extract_results(all_results)
typer.echo(f"Successful extractions: {len(successful_results)}")
if error_results:
typer.echo(f"Failed extractions: {len(error_results)}")
# Show first few errors for debugging
for error in error_results[:3]:
typer.echo(f" Error ({error.custom_id}): {error.error_message}")
if validate and extracted_results:
validate_results(extracted_results, "OpenAI")
return extracted_results
def fetch_anthropic_results(batch_id: str, validate: bool) -> list[User]:
"""Fetch Anthropic batch results using BatchProcessor"""
processor = BatchProcessor("anthropic/claude-3-5-sonnet-20241022", User)
# Get batch info
all_batches = processor.list_batches(limit=100)
batch_info = None
for batch in all_batches:
if batch.id == batch_id:
batch_info = batch
break
if not batch_info:
typer.echo(f"Batch {batch_id} not found")
return []
typer.echo(f"Batch Status: {batch_info.status.value}")
if batch_info.status != BatchStatus.COMPLETED:
typer.echo(
f"Batch is still {batch_info.status.value}. Please wait and try again."
)
return []
# Get results using the new get_results method
all_results = processor.get_results(batch_id)
successful_results = filter_successful(all_results)
error_results = filter_errors(all_results)
extracted_results = extract_results(all_results)
typer.echo(f"Successful extractions: {len(successful_results)}")
if error_results:
typer.echo(f"Failed extractions: {len(error_results)}")
# Show first few errors for debugging
for error in error_results[:3]:
typer.echo(f" Error ({error.custom_id}): {error.error_message}")
if validate and extracted_results:
validate_results(extracted_results, "Anthropic")
return extracted_results
def fetch_google_results(batch_job_name: str, validate: bool) -> list[User]:
"""Fetch Google batch results using BatchProcessor"""
try:
processor = BatchProcessor("google/gemini-2.5-flash", User)
# Get batch info
all_batches = processor.list_batches(limit=100)
batch_info = None
for batch in all_batches:
if batch.id == batch_job_name:
batch_info = batch
break
if not batch_info:
typer.echo(f"Batch {batch_job_name} not found")
return []
typer.echo(f"Batch Status: {batch_info.status.value}")
if batch_info.status != BatchStatus.COMPLETED:
typer.echo(
f"Batch is still {batch_info.status.value}. Please wait and try again."
)
return []
# Get results using the new get_results method
all_results = processor.get_results(batch_job_name)
successful_results = filter_successful(all_results)
error_results = filter_errors(all_results)
extracted_results = extract_results(all_results)
typer.echo(f"Successful extractions: {len(successful_results)}")
if error_results:
typer.echo(f"Failed extractions: {len(error_results)}")
if validate and extracted_results:
validate_results(extracted_results, "Google GenAI")
return extracted_results
except Exception as e:
typer.echo(f"Error fetching Google batch results: {e}")
return []
def validate_results(results: list[User], provider_name: str) -> bool:
"""Validate extracted results against expected results"""
expected_results = get_expected_results()
typer.echo(f"\nValidating {provider_name} Results:")
typer.echo("-" * 40)
if len(results) != len(expected_results):
typer.echo(f"Expected {len(expected_results)} results, got {len(results)}")
return False
# Sort both lists by name for comparison
results_sorted = sorted(results, key=lambda x: x.name)
expected_sorted = sorted(expected_results, key=lambda x: x.name)
all_correct = True
for i, (actual, expected) in enumerate(zip(results_sorted, expected_sorted)):
if actual.name == expected.name and actual.age == expected.age:
typer.echo(f"{i + 1}. {actual.name}, age {actual.age} - CORRECT")
else:
typer.echo(f"{i + 1}. Expected: {expected.name}, age {expected.age}")
typer.echo(f" Got: {actual.name}, age {actual.age}")
all_correct = False
if all_correct:
typer.echo(f"\nAll {provider_name} extractions are correct!")
else:
typer.echo(f"\nSome {provider_name} extractions have errors")
return all_correct
@app.command()
def help():
"""Show all available commands and usage examples"""
typer.echo("Unified Batch API Test Commands")
typer.echo("=" * 40)
typer.echo()
typer.echo("Available Commands:")
typer.echo(" • create - Create a new batch job")
typer.echo(" • list-batches - List all saved batch IDs")
typer.echo(" • fetch - Fetch and validate batch results")
typer.echo(" • show-results - Show detailed parsed Pydantic objects")
typer.echo(" • list-models - Show supported models")
typer.echo(" • help - Show this help message")
typer.echo()
typer.echo("Usage Examples:")
typer.echo(" # Create batch job (default: Google Gemini 2.5 Flash)")
typer.echo(" python run_batch_test.py create")
typer.echo()
typer.echo(" # Create batch job with specific model")
typer.echo(" python run_batch_test.py create --model 'openai/gpt-4o-mini'")
typer.echo()
typer.echo(" # List saved batch IDs")
typer.echo(" python run_batch_test.py list-batches")
typer.echo()
typer.echo(" # Fetch results with validation")
typer.echo(" python run_batch_test.py fetch --provider openai")
typer.echo()
typer.echo(" # Show detailed parsed objects")
typer.echo(" python run_batch_test.py show-results --provider anthropic")
typer.echo()
typer.echo(" # Poll every 30 seconds until batch completes (max 10 minutes)")
typer.echo(" python run_batch_test.py fetch --provider openai --poll")
typer.echo()
typer.echo(" # Poll with custom timeout (20 minutes)")
typer.echo(
" python run_batch_test.py fetch --provider openai --poll --max-wait 1200"
)
typer.echo()
@app.command()
def list_models():
"""List example models for each provider"""
typer.echo("Supported Models by Provider:")
typer.echo()
typer.echo("OpenAI:")
typer.echo(" • openai/gpt-4o-mini")
typer.echo(" • openai/gpt-4o")
typer.echo(" • openai/gpt-4-turbo")
typer.echo()
typer.echo("Anthropic:")
typer.echo(" • anthropic/claude-3-5-sonnet-20241022")
typer.echo(" • anthropic/claude-3-opus-20240229")
typer.echo(" • anthropic/claude-3-haiku-20240307")
typer.echo()
typer.echo("Google:")
typer.echo(" • google/gemini-2.5-flash")
typer.echo(" • google/gemini-2.0-flash-001")
typer.echo(" • google/gemini-pro")
typer.echo()
typer.echo("Usage: python run_batch_test.py create --model 'provider/model-name'")
if __name__ == "__main__":
app()

View File

@@ -1,125 +0,0 @@
import functools
import inspect
import instructor
import diskcache
from openai import OpenAI, AsyncOpenAI
from pydantic import BaseModel
client = instructor.from_openai(OpenAI())
aclient = instructor.from_openai(AsyncOpenAI())
class UserDetail(BaseModel):
name: str
age: int
cache = diskcache.Cache("./my_cache_directory")
def instructor_cache(func):
"""Cache a function that returns a Pydantic model"""
return_type = inspect.signature(func).return_annotation
if not issubclass(return_type, BaseModel):
raise ValueError("The return type must be a Pydantic model")
is_async = inspect.iscoroutinefunction(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = f"{func.__name__}-{functools._make_key(args, kwargs, typed=False)}"
# Check if the result is already cached
if (cached := cache.get(key)) is not None:
# Deserialize from JSON based on the return type
if issubclass(return_type, BaseModel):
return return_type.model_validate_json(cached)
# Call the function and cache its result
result = func(*args, **kwargs)
serialized_result = result.model_dump_json()
cache.set(key, serialized_result)
return result
@functools.wraps(func)
async def awrapper(*args, **kwargs):
key = f"{func.__name__}-{functools._make_key(args, kwargs, typed=False)}"
# Check if the result is already cached
if (cached := cache.get(key)) is not None:
# Deserialize from JSON based on the return type
if issubclass(return_type, BaseModel):
return return_type.model_validate_json(cached)
# Call the function and cache its result
result = await func(*args, **kwargs)
serialized_result = result.model_dump_json()
cache.set(key, serialized_result)
return result
return wrapper if not is_async else awrapper
@instructor_cache
def extract(data) -> UserDetail:
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
) # type: ignore
@instructor_cache
async def aextract(data) -> UserDetail:
return await aclient.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
) # type: ignore
def test_extract():
import time
start = time.perf_counter()
model = extract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
start = time.perf_counter()
model = extract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
async def atest_extract():
import time
start = time.perf_counter()
model = await aextract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
start = time.perf_counter()
model = await aextract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
if __name__ == "__main__":
test_extract()
# Time taken: 0.7285366660216823
# Time taken: 9.841693099588156e-05
import asyncio
asyncio.run(atest_extract())

View File

@@ -1,74 +0,0 @@
import redis
import functools
import inspect
import instructor
from pydantic import BaseModel
from openai import OpenAI
client = instructor.from_openai(OpenAI())
cache = redis.Redis("localhost")
def instructor_cache(func):
"""Cache a function that returns a Pydantic model"""
return_type = inspect.signature(func).return_annotation
if not issubclass(return_type, BaseModel):
raise ValueError("The return type must be a Pydantic model")
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = f"{func.__name__}-{functools._make_key(args, kwargs, typed=False)}"
# Check if the result is already cached
if (cached := cache.get(key)) is not None:
# Deserialize from JSON based on the return type
if issubclass(return_type, BaseModel):
return return_type.model_validate_json(cached)
# Call the function and cache its result
result = func(*args, **kwargs)
serialized_result = result.model_dump_json()
cache.set(key, serialized_result)
return result
return wrapper
class UserDetail(BaseModel):
name: str
age: int
@instructor_cache
def extract(data) -> UserDetail:
# Assuming client.chat.completions.create returns a UserDetail instance
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
def test_extract():
import time
start = time.perf_counter()
model = extract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
start = time.perf_counter()
model = extract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
if __name__ == "__main__":
test_extract()
# Time taken: 0.798335583996959
# Time taken: 0.00017016706988215446

View File

@@ -1,44 +0,0 @@
import instructor
from openai import OpenAI
from pydantic import BaseModel
import functools
client = instructor.from_openai(OpenAI())
class UserDetail(BaseModel):
name: str
age: int
@functools.lru_cache
def extract(data):
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
def test_extract():
import time
start = time.perf_counter()
model = extract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
start = time.perf_counter()
model = extract("Extract jason is 25 years old")
assert model.name.lower() == "jason"
assert model.age == 25
print(f"Time taken: {time.perf_counter() - start}")
if __name__ == "__main__":
test_extract()
# Time taken: 0.9267581660533324
# Time taken: 1.2080417945981026e-06

View File

@@ -1,589 +0,0 @@
"""
Comprehensive Caching Example for Instructor
===========================================
This example demonstrates various caching strategies for LLM applications:
1. functools.cache - Simple in-memory caching
2. diskcache - Persistent disk-based caching
3. Redis - Distributed caching
4. Performance benchmarks and cost analysis
5. Advanced patterns: hierarchical caching, monitoring, schema invalidation
Run this example to see real-world performance improvements and cost savings.
"""
import asyncio
import functools
import hashlib
import inspect
import json
import logging
import time
from collections import defaultdict
from typing import Any, Callable, Optional, TypeVar
import instructor
from openai import AsyncOpenAI, OpenAI
from pydantic import BaseModel, Field
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize clients
client = instructor.from_openai(OpenAI())
aclient = instructor.from_openai(AsyncOpenAI())
# Test data
TEST_QUERIES = [
"Extract: Jason is 25 years old and works as a software engineer",
"Extract: Sarah is 30 years old and is a data scientist",
"Extract: Mike is 28 years old and works in marketing",
"Extract: Lisa is 32 years old and is a product manager",
"Extract: Jason is 25 years old and works as a software engineer", # Duplicate for cache hit
]
F = TypeVar("F", bound=Callable[..., Any])
class UserDetail(BaseModel):
"""Enhanced user model with more fields for testing"""
name: str = Field(description="User's full name")
age: int = Field(description="User's age", ge=0, le=150)
occupation: Optional[str] = Field(None, description="User's job title")
class CacheMetrics:
"""Production-ready cache monitoring"""
def __init__(self):
self.hits = 0
self.misses = 0
self.total_time_saved = 0.0
self.error_count = 0
self.hit_rate_by_function: dict[str, dict[str, int]] = defaultdict(
lambda: {"hits": 0, "misses": 0}
)
def record_hit(self, func_name: str, time_saved: float):
self.hits += 1
self.total_time_saved += time_saved
self.hit_rate_by_function[func_name]["hits"] += 1
logger.debug(f"Cache HIT for {func_name}, saved {time_saved:.3f}s")
def record_miss(self, func_name: str):
self.misses += 1
self.hit_rate_by_function[func_name]["misses"] += 1
logger.debug(f"Cache MISS for {func_name}")
def record_error(self, func_name: str, error: str):
self.error_count += 1
logger.warning(f"Cache ERROR in {func_name}: {error}")
@property
def hit_rate(self) -> float:
total = self.hits + self.misses
return self.hits / total if total > 0 else 0.0
def get_stats(self) -> dict[str, Any]:
return {
"hit_rate": f"{self.hit_rate:.2%}",
"total_hits": self.hits,
"total_misses": self.misses,
"error_count": self.error_count,
"time_saved_seconds": f"{self.total_time_saved:.3f}",
"function_stats": dict(self.hit_rate_by_function),
}
def reset(self):
"""Reset all metrics for new test runs"""
self.hits = 0
self.misses = 0
self.total_time_saved = 0.0
self.error_count = 0
self.hit_rate_by_function.clear()
# Global metrics instance
metrics = CacheMetrics()
def smart_cache_key(
func_name: str, args: tuple, kwargs: dict, model_class: type
) -> str:
"""Generate cache key with schema versioning for automatic invalidation"""
# Include model schema in cache key for automatic invalidation
schema_hash = hashlib.md5(
json.dumps(model_class.model_json_schema(), sort_keys=True).encode()
).hexdigest()[:8]
args_hash = hashlib.md5(str((args, kwargs)).encode()).hexdigest()[:8]
return f"{func_name}:{schema_hash}:{args_hash}"
# 1. Simple functools.cache implementation
@functools.lru_cache(maxsize=1000)
def extract_functools(data: str) -> UserDetail:
"""Simple in-memory caching with functools.lru_cache"""
start_time = time.perf_counter()
result = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
# This won't be called on cache hits, so we track metrics differently
return result
def monitored_functools_cache(func: F) -> F:
"""functools.cache with monitoring"""
cached_func = functools.lru_cache(maxsize=1000)(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Check if we'll get a cache hit by calling cache_info
info_before = cached_func.cache_info()
start_time = time.perf_counter()
result = cached_func(*args, **kwargs)
execution_time = time.perf_counter() - start_time
info_after = cached_func.cache_info()
if info_after.hits > info_before.hits:
# We got a cache hit
metrics.record_hit(func.__name__, 0.8) # Assume 800ms saved
else:
# Cache miss
metrics.record_miss(func.__name__)
return result
# Preserve cache_info method
wrapper.cache_info = cached_func.cache_info
wrapper.cache_clear = cached_func.cache_clear
return wrapper
@monitored_functools_cache
def extract_functools_monitored(data: str) -> UserDetail:
"""functools.cache with monitoring"""
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
# 2. Enhanced diskcache implementation
def create_diskcache_decorator(
cache_dir: str = "./cache_directory", ttl: Optional[int] = None
):
"""Factory for diskcache decorator with enhanced features"""
try:
import diskcache
cache = diskcache.Cache(cache_dir)
except ImportError:
logger.warning("diskcache not available, skipping disk cache example")
return lambda func: func
def decorator(func: F) -> F:
return_type = inspect.signature(func).return_annotation
if not (inspect.isclass(return_type) and issubclass(return_type, BaseModel)):
raise ValueError("The return type must be a Pydantic model")
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Generate smart cache key with schema versioning
key = smart_cache_key(func.__name__, args, kwargs, return_type)
try:
# Check if the result is already cached
if (cached := cache.get(key)) is not None:
metrics.record_hit(func.__name__, 0.8) # Assume 800ms saved
return return_type.model_validate_json(cached)
metrics.record_miss(func.__name__)
except Exception as e:
metrics.record_error(func.__name__, str(e))
logger.warning(f"Cache read error: {e}")
# Call the function and cache its result
result = func(*args, **kwargs)
try:
serialized_result = result.model_dump_json()
if ttl:
cache.set(key, serialized_result, expire=ttl)
else:
cache.set(key, serialized_result)
except Exception as e:
metrics.record_error(func.__name__, str(e))
logger.warning(f"Cache write error: {e}")
return result
return wrapper
return decorator
@create_diskcache_decorator(ttl=3600) # 1 hour TTL
def extract_diskcache(data: str) -> UserDetail:
"""Persistent disk-based caching with TTL"""
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
# 3. Enhanced Redis implementation (with fallback)
def create_redis_decorator(
redis_url: str = "redis://localhost:6379",
ttl: int = 3600,
prefix: str = "instructor",
):
"""Factory for Redis decorator with production features"""
try:
import redis
cache = redis.from_url(redis_url, decode_responses=True)
# Test connection
cache.ping()
logger.info("Connected to Redis successfully")
except ImportError as e:
logger.warning(f"Redis not available (ImportError: {e}), using fallback")
return lambda func: func
except Exception as e: # Covers redis.RedisError and other connection issues
logger.warning(f"Redis not available ({e}), using fallback")
return lambda func: func
def decorator(func: F) -> F:
return_type = inspect.signature(func).return_annotation
if not (inspect.isclass(return_type) and issubclass(return_type, BaseModel)):
raise ValueError("The return type must be a Pydantic model")
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Generate cache key with schema versioning
schema_hash = hashlib.md5(
json.dumps(return_type.model_json_schema(), sort_keys=True).encode()
).hexdigest()[:8]
key = f"{prefix}:{func.__name__}:{schema_hash}:{functools._make_key(args, kwargs, typed=False)}"
try:
# Check if the result is already cached
if (cached := cache.get(key)) is not None:
metrics.record_hit(func.__name__, 0.8) # Assume 800ms saved
logger.debug(f"Cache hit for key: {key}")
return return_type.model_validate_json(cached)
metrics.record_miss(func.__name__)
logger.debug(f"Cache miss for key: {key}")
except redis.RedisError as e:
metrics.record_error(func.__name__, str(e))
logger.warning(f"Redis read error: {e}")
# Call the function and cache its result
result = func(*args, **kwargs)
try:
serialized_result = result.model_dump_json()
cache.setex(key, ttl, serialized_result)
logger.debug(f"Cached result for key: {key}")
except redis.RedisError as e:
metrics.record_error(func.__name__, str(e))
logger.warning(f"Redis write error: {e}")
return result
return wrapper
return decorator
@create_redis_decorator(ttl=3600)
def extract_redis(data: str) -> UserDetail:
"""Distributed Redis caching with error handling"""
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
# 4. No cache baseline for comparison
def extract_no_cache(data: str) -> UserDetail:
"""Baseline function without caching"""
metrics.record_miss("extract_no_cache")
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
# 5. Hierarchical caching example
@functools.lru_cache(maxsize=50) # L1: Fast in-memory
def extract_l1(data: str) -> UserDetail:
return extract_l2(data)
@create_diskcache_decorator() # L2: Persistent disk
def extract_l2(data: str) -> UserDetail:
return extract_l3(data)
@create_redis_decorator() # L3: Shared distributed
def extract_l3(data: str) -> UserDetail:
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
def benchmark_caching_strategy(
func: Callable, name: str, queries: list[str]
) -> dict[str, Any]:
"""Benchmark a specific caching strategy"""
logger.info(f"\n=== Benchmarking {name} ===")
# Reset metrics for this test
metrics.reset()
times = []
results = []
for i, query in enumerate(queries):
start_time = time.perf_counter()
try:
result = func(query)
execution_time = time.perf_counter() - start_time
times.append(execution_time)
results.append(result)
logger.info(
f"Query {i + 1}: {execution_time:.3f}s - {result.name}, {result.age}, {result.occupation}"
)
except Exception as e:
logger.error(f"Error in {name}: {e}")
times.append(float("inf"))
results.append(None)
# Calculate statistics
valid_times = [t for t in times if t != float("inf")]
if valid_times:
avg_time = sum(valid_times) / len(valid_times)
total_time = sum(valid_times)
fastest_time = min(valid_times)
slowest_time = max(valid_times)
else:
avg_time = total_time = fastest_time = slowest_time = 0
stats = {
"name": name,
"total_time": total_time,
"avg_time": avg_time,
"fastest_time": fastest_time,
"slowest_time": slowest_time,
"cache_metrics": metrics.get_stats(),
"success_rate": len(valid_times) / len(queries),
}
logger.info(f"Total time: {total_time:.3f}s")
logger.info(f"Average time: {avg_time:.3f}s")
logger.info(f"Cache hit rate: {metrics.hit_rate:.2%}")
return stats
def calculate_cost_savings(baseline_stats: dict, cached_stats: dict) -> dict[str, Any]:
"""Calculate cost savings from caching"""
baseline_time = baseline_stats["total_time"]
cached_time = cached_stats["total_time"]
# Assume $0.002 per API call (rough average)
cost_per_call = 0.002
num_queries = len(TEST_QUERIES)
# Without caching: every call costs money
cost_without_cache = num_queries * cost_per_call
# With caching: only cache misses cost money
cache_misses = cached_stats["cache_metrics"]["total_misses"]
cost_with_cache = cache_misses * cost_per_call
savings = cost_without_cache - cost_with_cache
savings_percent = (
(savings / cost_without_cache) * 100 if cost_without_cache > 0 else 0
)
time_saved = baseline_time - cached_time
time_savings_percent = (
(time_saved / baseline_time) * 100 if baseline_time > 0 else 0
)
return {
"cost_without_cache": cost_without_cache,
"cost_with_cache": cost_with_cache,
"cost_savings": savings,
"cost_savings_percent": savings_percent,
"time_saved": time_saved,
"time_savings_percent": time_savings_percent,
"speed_improvement": (
baseline_time / cached_time if cached_time > 0 else float("inf")
),
}
async def run_async_example():
"""Demonstrate async caching patterns"""
logger.info("\n=== Async Caching Example ===")
# Simple async function with metrics
async def extract_async(data: str) -> UserDetail:
metrics.record_miss("extract_async")
return await aclient.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": data},
],
)
# Run concurrent requests
start_time = time.perf_counter()
tasks = [
extract_async(query) for query in TEST_QUERIES[:3]
] # First 3 to save costs
results = await asyncio.gather(*tasks)
total_time = time.perf_counter() - start_time
logger.info(f"Async processing time: {total_time:.3f}s")
for i, result in enumerate(results):
logger.info(f"Result {i + 1}: {result.name}, {result.age}, {result.occupation}")
def demonstrate_schema_invalidation():
"""Show how cache keys change when model schema changes"""
logger.info("\n=== Schema-Based Cache Invalidation ===")
# Original model
class OriginalUser(BaseModel):
name: str
age: int
# Modified model (different schema)
class ModifiedUser(BaseModel):
name: str
age: int
email: Optional[str] = None # New field
# Generate cache keys for same function args but different models
args = ("test data",)
kwargs = {}
key1 = smart_cache_key("test_func", args, kwargs, OriginalUser)
key2 = smart_cache_key("test_func", args, kwargs, ModifiedUser)
logger.info(f"Original model cache key: {key1}")
logger.info(f"Modified model cache key: {key2}")
logger.info(f"Keys are different: {key1 != key2}")
logger.info("This ensures cache invalidation when model schemas change!")
def main():
"""Run comprehensive caching demonstration"""
logger.info("🚀 Starting Comprehensive Caching Demonstration")
logger.info("=" * 60)
# Run benchmarks for each strategy
strategies = [
(extract_no_cache, "No Cache (Baseline)"),
(extract_functools_monitored, "functools.lru_cache"),
(extract_diskcache, "diskcache"),
(extract_redis, "Redis"),
(extract_l1, "Hierarchical (L1→L2→L3)"),
]
all_stats = {}
for func, name in strategies:
try:
stats = benchmark_caching_strategy(func, name, TEST_QUERIES)
all_stats[name] = stats
except Exception as e:
logger.error(f"Failed to benchmark {name}: {e}")
continue
# Print summary comparison
logger.info("\n" + "=" * 60)
logger.info("📊 PERFORMANCE COMPARISON SUMMARY")
logger.info("=" * 60)
baseline_stats = all_stats.get("No Cache (Baseline)")
if baseline_stats:
for name, stats in all_stats.items():
if name == "No Cache (Baseline)":
continue
logger.info(f"\n{name}:")
logger.info(f" Total time: {stats['total_time']:.3f}s")
logger.info(f" Cache hit rate: {stats['cache_metrics']['hit_rate']}")
# Calculate savings
savings = calculate_cost_savings(baseline_stats, stats)
logger.info(f" Speed improvement: {savings['speed_improvement']:.1f}x")
logger.info(
f" Time saved: {savings['time_saved']:.3f}s ({savings['time_savings_percent']:.1f}%)"
)
logger.info(
f" Cost savings: ${savings['cost_savings']:.4f} ({savings['cost_savings_percent']:.1f}%)"
)
# Additional demonstrations
demonstrate_schema_invalidation()
# Run async example
asyncio.run(run_async_example())
# Print cache info for functools
logger.info(
f"\nfunctools.lru_cache info: {extract_functools_monitored.cache_info()}"
)
logger.info("\n" + "=" * 60)
logger.info("✅ Caching demonstration completed!")
logger.info("💡 Key takeaways:")
logger.info(" - Caching can provide 10x-1000x speed improvements")
logger.info(" - Choose the right strategy based on your needs:")
logger.info(" • functools.cache: Development, single process")
logger.info(" • diskcache: Persistence, moderate performance")
logger.info(" • Redis: Distributed systems, high performance")
logger.info(" • Hierarchical: Best of all worlds")
logger.info(" - Smart cache keys prevent stale data")
logger.info(" - Monitoring helps optimize cache performance")
if __name__ == "__main__":
main()

View File

@@ -1,75 +0,0 @@
# Instructor Caching Prototype
This example demonstrates the new built-in caching functionality in Instructor.
## Files
- `run.py` - Main example showing all caching features (with mock calls for quick testing)
- `run_real.py` - Complete demo with real API calls
- `test_simple.py` - Unit tests for cache components without API calls
- `test_anthropic.py` - Tests with Anthropic provider to verify caching works across providers
## Features Demonstrated
### 1. AutoCache (In-Memory LRU)
```python
from instructor.cache import AutoCache
cache = AutoCache(maxsize=100)
client = instructor.from_openai(OpenAI(), cache=cache)
```
### 2. DiskCache (Persistent)
```python
from instructor.cache import DiskCache
cache = DiskCache(directory=".instructor_cache")
client = instructor.from_openai(OpenAI(), cache=cache)
```
### 3. Cache TTL (Time-to-Live)
```python
client.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
cache_ttl=3600, # 1 hour
)
```
### 4. create_with_completion Support
Both the parsed model and raw completion objects are cached and restored.
## Performance Results
From our tests:
- **156x faster** cache hits vs API calls
- **Identical results** from cache and API
- **Persistent storage** across client instances
- **Automatic cache invalidation** based on:
- Different prompts
- Different models
- Different response schemas
- TTL expiration
## Running the Examples
```bash
# Run the complete demo (requires OpenAI API key)
uv run python run_real.py
# Run unit tests (no API required)
uv run python test_simple.py
# Run pytest tests
uv run pytest tests/test_cache*.py
```
## Key Features
1. **Deterministic caching** - same inputs always produce same cache key
2. **Schema-aware** - changing field descriptions invalidates cache
3. **Multiple backends** - AutoCache (LRU), DiskCache (persistent)
4. **TTL support** - automatic expiration (where supported)
5. **Raw response preservation** - `create_with_completion` works seamlessly
6. **Thread-safe** - all cache implementations are thread-safe

View File

@@ -1,270 +0,0 @@
"""Demonstrate real caching functionality with actual API calls."""
import time
import instructor
from instructor.cache import AutoCache, DiskCache
from pydantic import BaseModel, Field
from openai import OpenAI
class User(BaseModel):
name: str = Field(description="The user's name")
age: int = Field(description="The user's age")
def test_autocache():
"""Test basic in-memory caching."""
print("\n=== Testing AutoCache (in-memory) ===")
cache = AutoCache(maxsize=100)
client = instructor.from_openai(OpenAI(), cache=cache)
messages = [
{"role": "user", "content": "Generate a user named Alice who is 25 years old"}
]
# First call - hits API
print("First call (hits API)...")
start = time.time()
user1 = client.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
)
api_time = time.time() - start
print(f"Result: {user1}")
print(f"Time: {api_time:.2f}s")
# Second call - from cache
print("\nSecond call (from cache)...")
start = time.time()
user2 = client.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
)
cache_time = time.time() - start
print(f"Result: {user2}")
print(f"Time: {cache_time:.4f}s")
print(f"Speedup: {api_time / cache_time:.0f}x faster")
assert user1.name == user2.name
assert user1.age == user2.age
print("✓ Cache working - identical results")
def test_create_with_completion():
"""Test create_with_completion caching."""
print("\n=== Testing create_with_completion ===")
cache = AutoCache(maxsize=100)
client = instructor.from_openai(OpenAI(), cache=cache)
messages = [
{"role": "user", "content": "What's the weather? Say it's 22C and sunny."}
]
class Weather(BaseModel):
temperature: float
condition: str
# First call
print("First call with completion...")
weather1, completion1 = client.create_with_completion(
model="gpt-3.5-turbo",
messages=messages,
response_model=Weather,
)
print(f"Weather: {weather1}")
print(f"Completion ID: {completion1.id}")
print(f"Tokens used: {completion1.usage.total_tokens}")
# Second call - cached
print("\nSecond call (cached)...")
start = time.time()
weather2, completion2 = client.create_with_completion(
model="gpt-3.5-turbo",
messages=messages,
response_model=Weather,
)
cache_time = time.time() - start
print(f"Weather: {weather2}")
print(f"Completion ID: {completion2.id}")
print(f"Cache time: {cache_time:.4f}s")
assert weather1.temperature == weather2.temperature
assert completion1.id == completion2.id
print("✓ Completion object cached correctly")
def test_diskcache():
"""Test persistent disk caching."""
print("\n=== Testing DiskCache (persistent) ===")
# First client
cache1 = DiskCache(directory=".instructor_cache_demo")
client1 = instructor.from_openai(OpenAI(), cache=cache1)
messages = [{"role": "user", "content": "Create a user named Bob who is 30"}]
print("First client creates user...")
user1 = client1.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
)
print(f"Result: {user1}")
# New client, same cache directory
print("\nNew client with same cache dir...")
cache2 = DiskCache(directory=".instructor_cache_demo")
client2 = instructor.from_openai(OpenAI(), cache=cache2)
start = time.time()
user2 = client2.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
)
cache_time = time.time() - start
print(f"Result: {user2}")
print(f"Time: {cache_time:.4f}s (from disk cache)")
assert user1.name == user2.name
print("✓ Cache persisted across clients")
# Test create_with_completion persistence
print("\nTesting create_with_completion persistence...")
weather_messages = [{"role": "user", "content": "Weather is 25C and cloudy"}]
class Weather(BaseModel):
temperature: float
condition: str
# First call with completion
weather1, completion1 = client1.create_with_completion(
model="gpt-3.5-turbo",
messages=weather_messages,
response_model=Weather,
)
print(f"Weather: {weather1}, Completion ID: {completion1.id}")
# Second call from different client - should get cached completion
weather2, completion2 = client2.create_with_completion(
model="gpt-3.5-turbo",
messages=weather_messages,
response_model=Weather,
)
print(f"Cached: {weather2}, Completion ID: {completion2.id}")
assert weather1.temperature == weather2.temperature
assert completion1.id == completion2.id
print("✓ Raw completion persisted to disk")
# Cleanup
import shutil
shutil.rmtree(".instructor_cache_demo", ignore_errors=True)
def test_cache_ttl():
"""Test cache TTL with DiskCache."""
print("\n=== Testing Cache TTL ===")
cache = DiskCache(directory=".instructor_cache_ttl")
client = instructor.from_openai(OpenAI(), cache=cache)
messages = [{"role": "user", "content": "Create user Charlie age 35"}]
# Set with 2 second TTL
print("Setting cache with 2s TTL...")
user1 = client.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
cache_ttl=2,
)
print(f"Result: {user1}")
# Immediate call - cached
print("\nImmediate call (cached)...")
start = time.time()
user2 = client.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
)
print(f"Time: {time.time() - start:.4f}s")
# Wait for expiry
print("\nWaiting 3s for TTL expiry...")
time.sleep(3)
# Should hit API again
print("After TTL (hits API)...")
start = time.time()
user3 = client.create(
model="gpt-3.5-turbo",
messages=messages,
response_model=User,
)
api_time = time.time() - start
print(f"Time: {api_time:.2f}s")
print("✓ TTL working correctly")
# Cleanup
import shutil
shutil.rmtree(".instructor_cache_ttl", ignore_errors=True)
def test_different_inputs():
"""Show that different inputs use different cache keys."""
print("\n=== Testing Different Cache Keys ===")
cache = AutoCache(maxsize=100)
client = instructor.from_openai(OpenAI(), cache=cache)
# Different prompts
user1 = client.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Create user David age 40"}],
response_model=User,
)
print(f"User 1: {user1}")
user2 = client.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Create user Eve age 45"}],
response_model=User,
)
print(f"User 2: {user2}")
assert user1.name != user2.name or user1.age != user2.age
print("✓ Different prompts = different results")
# Different models
class SimpleUser(BaseModel):
name: str
simple = client.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Create user David age 40"}],
response_model=SimpleUser,
)
print(f"Simple user: {simple}")
print("✓ Different models = different cache keys")
if __name__ == "__main__":
print("Instructor Caching Demo - Real API Calls")
print("=" * 50)
test_autocache()
test_create_with_completion()
test_diskcache()
test_cache_ttl()
test_different_inputs()
print("\n" + "=" * 50)
print("All tests completed! ✨")

View File

@@ -1,31 +0,0 @@
# Introduction
This is a simple example which shows how to perform Chain Of Density summarization using GPT-3.5 and utilise the generated output to fine-tune a 3.5 model for production usage. All of our data referenced in this file is located [here](https://huggingface.co/datasets/ivanleomk/gpt4-chain-of-density) on hugging face
Check out our blog post [here](https://jxnl.github.io/instructor/blog/2023/11/05/implementing-chain-of-density/) where we have a detailed explanation of the code and a [colab notebook](https://colab.research.google.com/drive/1iBkrEh2G5U8yh8RmI8EkWxjLq6zIIuVm?usp=sharing) walking you through how we perform our calculations.
## Instructions
1. First, install all of the required dependencies by running the command below. We recommend using a virtual environment to install these so that it does not affect your system installation.
> We use NLTK to ensure that our summaries are of a certain token length. In order to do so, you'll need to download the `punkt` package to compute the token metrics. You can do so by running the command `nltk.download('punkt')`
```
pip3 install -r requirements.txt
```
2. Download the `test.csv` file and the `summarization.jsonl` file that you want to use for finetuning. We provide one with `20` examples, `50` examples and `100` examples to be used for testing. Let's now run a simple finetuning job with the following command.
> Don't forget to set your `OPENAI_API_KEY` as an environment variable in your shell before running these commands
```
instructor jobs create-from-file summarization.jsonl
```
3. Once the job is complete, you'll end up with a new GPT 3.5 model that's capable of producing high quality summaries with a high entity density. You can run it by simply changing our `finetune.py` file's `instructions.distil` annotator as
```
@instructions.distil(model=<your finetuned model >,mode="dispatch")
def distil_summarization(text: str) -> GeneratedSummary:
// rest of code goes here
```

View File

@@ -1,150 +0,0 @@
from pydantic import BaseModel, Field, field_validator
import instructor
import nltk
from openai import OpenAI
import spacy
client = instructor.from_openai(OpenAI())
nlp = spacy.load("en_core_web_sm")
class InitialSummary(BaseModel):
"""
This is an initial summary which should be long ( 4-5 sentences, ~80 words) yet highly non-specific, containing little information beyond the entities marked as missing. Use overly verbose languages and fillers (Eg. This article discusses) to reach ~80 words.
"""
summary: str = Field(
...,
description="This is a summary of the article provided which is overly verbose and uses fillers. It should be roughly 80 words in length",
)
class RewrittenSummary(BaseModel):
"""
This is a new, denser summary of identical length which covers every entity and detail from the previous summary plus the Missing Entities.
Guidelines
- Make every word count : Rewrite the previous summary to improve flow and make space for additional entities
- Never drop entities from the previous summary. If space cannot be made, add fewer new entities.
- The new summary should be highly dense and concise yet self-contained, eg., easily understood without the Article.
- Make space with fusion, compression, and removal of uninformative phrases like "the article discusses"
- Missing entities can appear anywhere in the new summary
An Entity is a real-world object that's assigned a name - for example, a person, country a product or a book title.
"""
summary: str = Field(
...,
description="This is a new, denser summary of identical length which covers every entity and detail from the previous summary plus the Missing Entities. It should have the same length ( ~ 80 words ) as the previous summary and should be easily understood without the Article",
)
absent: list[str] = Field(
...,
default_factory=list,
description="this is a list of Entities found absent from the new summary that were present in the previous summary",
)
missing: list[str] = Field(
default_factory=list,
description="This is a list of 1-3 informative Entities from the Article that are missing from the new summary which should be included in the next generated summary.",
)
@field_validator("summary")
def min_entity_density(cls, v: str):
# We want to make sure we have a minimum density of 0.12 whenever we do a rewrite. This ensures that the summary quality is always going up
tokens = nltk.word_tokenize(v)
num_tokens = len(tokens)
# Extract Entities
doc = nlp(v)
num_entities = len(doc.ents)
density = num_entities / num_tokens
if density < 0.08:
raise ValueError(
f"The summary of {v} has too few entities. Please regenerate a new summary with more new entities added to it. Remember that new entities can be added at any point of the summary."
)
return v
@field_validator("summary")
def min_length(cls, v: str):
tokens = nltk.word_tokenize(v)
num_tokens = len(tokens)
if num_tokens < 60:
raise ValueError(
"The current summary is too short. Please make sure that you generate a new summary that is around 80 words long."
)
return v
@field_validator("missing")
def has_missing_entities(cls, missing_entities: list[str]):
if len(missing_entities) == 0:
raise ValueError(
"You must identify 1-3 informative Entities from the Article which are missing from the previously generated summary to be used in a new summary"
)
return missing_entities
@field_validator("absent")
def has_no_absent_entities(cls, absent_entities: list[str]):
absent_entity_string = ",".join(absent_entities)
if len(absent_entities) > 0:
print(f"Detected absent entities of {absent_entity_string}")
raise ValueError(
f"Do not omit the following Entities {absent_entity_string} from the new summary"
)
return absent_entities
def summarize_article(article: str, summary_steps: int = 3):
summary_chain = []
# We first generate an initial summary
summary: InitialSummary = client.chat.completions.create(
model="gpt-4-0613",
response_model=InitialSummary,
messages=[
{
"role": "system",
"content": "Write a summary about the article that is long (4-5 sentences) yet highly non-specific. Use overly, verbose language and fillers(eg.,'this article discusses') to reach ~80 words. ",
},
{"role": "user", "content": f"Here is the Article: {article}"},
{
"role": "user",
"content": "The generated summary should be about 80 words.",
},
],
max_retries=2,
)
summary_chain.append(summary.summary)
for _i in range(summary_steps):
new_summary: RewrittenSummary = client.chat.completions.create(
model="gpt-4-0613",
messages=[
{
"role": "system",
"content": f"""
Article: {article}
You are going to generate an increasingly concise,entity-dense summary of the following article.
Perform the following two tasks
- Identify 1-3 informative entities from the following article which is missing from the previous summary
- Write a new denser summary of identical length which covers every entity and detail from the previous summary plus the Missing Entities
Guidelines
- Make every word count: re-write the previous summary to improve flow and make space for additional entities
- Make space with fusion, compression, and removal of uninformative phrases like "the article discusses".
- The summaries should become highly dense and concise yet self-contained, e.g., easily understood without the Article.
- Missing entities can appear anywhere in the new summary
- Never drop entities from the previous summary. If space cannot be made, add fewer new entities.
""",
},
{
"role": "user",
"content": f"Here is the previous summary: {summary_chain[-1]}",
},
],
max_retries=5,
max_tokens=1000,
response_model=RewrittenSummary,
)
summary_chain.append(new_summary.summary)
return summary_chain

View File

@@ -1,51 +0,0 @@
from openai import OpenAI
from chain_of_density import summarize_article
import csv
import logging
import instructor
from pydantic import BaseModel, Field
logging.basicConfig(level=logging.INFO)
client = instructor.from_openai(OpenAI())
instructions = instructor.Instructions(
name="Chain Of Density",
finetune_format="messages",
# log handler is used to save the data to a file
# you can imagine saving it to a database or other storage
# based on your needs!
log_handlers=[logging.FileHandler("generated.jsonl")],
openai_client=client,
)
class GeneratedSummary(BaseModel):
"""
This represents a highly concise summary that includes as many entities as possible from the original source article.
An Entity is a real-world object that's assigned a name - for example, a person, country a product or a book title.
Guidelines
- Make every word count
- The new summary should be highly dense and concise yet self-contained, eg., easily understood without the Article.
- Make space with fusion, compression, and removal of uninformative phrases like "the article discusses"
"""
summary: str = Field(
...,
description="This represents the final summary generated that captures the meaning of the original article which is as concise as possible. ",
)
@instructions.distil
def distil_summarization(text: str) -> GeneratedSummary:
summary_chain: list[str] = summarize_article(text)
return GeneratedSummary(summary=summary_chain[-1])
with open("test.csv") as file:
reader = csv.reader(file)
next(reader) # Skip the header
for article, _summary in reader:
distil_summarization(article)

View File

@@ -1,5 +0,0 @@
openai
pydantic
instructor
nltk
rich

View File

@@ -1,14 +0,0 @@
# https://hub.docker.com/_/python
FROM python:3.10-slim-bullseye
ENV PYTHONUNBUFFERED True
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY . ./
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

View File

@@ -1,73 +0,0 @@
# Citation with Extraction
This repository contains a FastAPI application that uses GPT-4 to answer questions based on a given context and extract relevant facts with correct and exact citations. The extracted facts are returned as JSON events using Server-Sent Events (SSE).
## How it Works
The FastAPI app defines an endpoint `/extract` that accepts a POST request with JSON data containing a `context` and a `query`. The `context` represents the text from which the question is being asked, and the `query` is the question itself.
The app leverages GPT-4, an advanced language model, to generate answers to the questions and extract relevant facts. It ensures that the extracted facts include direct quotes from the given context.
## Example Usage
To use the `/extract` endpoint, send a POST request with `curl` or any HTTP client with the following format:
```bash
curl -X POST -H "Content-Type: application/json" -d '{
"context": "My name is Jason Liu, and I grew up in Toronto Canada but I was born in China.I went to an arts highschool 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.",
"query": "What did the author do in school?"
}' -N http://localhost:8000/extract
```
```sh
data: {'body': 'In school, the author went to an arts high school.', 'spans': [(91, 106)], 'citation': ['arts highschool']}
data: {'body': 'In university, the author studied Computational Mathematics and physics.', 'spans': [(135, 172)], 'citation': ['Computational Mathematics and physics']}
```
Replace `http://localhost:8000` with the actual URL of your FastAPI app if it's running on a different host and port. The API will respond with Server-Sent Events (SSE) containing the extracted facts in real-time.
## Bring your own API key
If you have your own api key but dont want to try deploying it yourself you're welcome to use my
modal isntance here, this code is public and I do not store your key.
```bash
curl -X 'POST' \
'https://jxnl--rag-citation-fastapi-app.modal.run/extract' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <OPENAI_API_KEY>' \
-d '{
"context": "My name is Jason Liu, and I grew up in Toronto Canada but I was born in China.I went to an arts highschool 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.",
"query": "What did the author do in school?"
}'
```
## Requirements
To run this application, ensure you have the following Python packages installed:
```bash
pip install -r requirements.txt
```
## Running the App
To run the FastAPI app, execute the following command:
```bash
uvicorn main:app --reload
```
This will start the server, and the `/extract` endpoint will be available at `http://localhost:8000/extract`.
## Note
Ensure that you have a valid API key for GPT-4 from OpenAI. If you don't have one, you can obtain it from the OpenAI website.
Please use this application responsibly and be mindful of any usage limits or restrictions from OpenAI's API usage policy.
## License
This project is licensed under the [MIT License](LICENSE). Feel free to use, modify, and distribute it as you see fit.

View File

@@ -1,129 +0,0 @@
import instructor
from loguru import logger
from openai import OpenAI
from pydantic import Field, BaseModel, FieldValidationInfo, model_validator
client = instructor.from_openai(OpenAI())
class Fact(BaseModel):
statement: str = Field(
..., description="Body of the sentence, as part of a response"
)
substring_phrase: list[str] = Field(
...,
description="String quote long enough to evaluate the truthfulness of the fact",
)
@model_validator(mode="after")
def validate_sources(self, info: FieldValidationInfo) -> "Fact":
"""
For each substring_phrase, find the span of the substring_phrase in the context.
If the span is not found, remove the substring_phrase from the list.
"""
if info.context is None:
logger.info("No context found, skipping validation")
return self
# Get the context from the info
text_chunks = info.context.get("text_chunk", None)
# Get the spans of the substring_phrase in the context
spans = list(self.get_spans(text_chunks))
logger.info(
f"Found {len(spans)} span(s) for from {len(self.substring_phrase)} citation(s)."
)
# Replace the substring_phrase with the actual substring
self.substring_phrase = [text_chunks[span[0] : span[1]] for span in spans]
return self
def _get_span(self, quote, context, errs=5):
import regex
minor = quote
major = context
errs_ = 0
s = regex.search(f"({minor}){{e<={errs_}}}", major)
while s is None and errs_ <= errs:
errs_ += 1
s = regex.search(f"({minor}){{e<={errs_}}}", major)
if s is not None:
yield from s.spans()
def get_spans(self, context):
for quote in self.substring_phrase:
yield from self._get_span(quote, context)
class QuestionAnswer(instructor.ResponseSchema):
"""
Class representing a question and its answer as a list of facts each one should have a soruce.
each sentence contains a body and a list of sources."""
question: str = Field(..., description="Question that was asked")
answer: list[Fact] = Field(
...,
description="Body of the answer, each fact should be its separate object with a body and a list of sources",
)
@model_validator(mode="after")
def validate_sources(self) -> "QuestionAnswer":
"""
Checks that each fact has some sources, and removes those that do not.
"""
logger.info(f"Validating {len(self.answer)} facts")
self.answer = [fact for fact in self.answer if len(fact.substring_phrase) > 0]
logger.info(f"Found {len(self.answer)} facts with sources")
return self
def ask_ai(question: str, context: str) -> QuestionAnswer:
return client.chat.completions.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}"},
],
validation_context={"text_chunk": context},
)
question = "where did he go to school?"
context = """
My name is Jason Liu, and I grew up in Toronto Canada but I was born in China.I went to an arts highschool 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.
"""
answer = ask_ai(question, context)
print(answer.model_dump_json(indent=2))
"""
2023-09-09 15:48:11.022 | INFO | __main__:validate_sources:35 - Found 1 span(s) for from 1 citation(s).
2023-09-09 15:48:11.023 | INFO | __main__:validate_sources:35 - Found 1 span(s) for from 1 citation(s).
2023-09-09 15:48:11.023 | INFO | __main__:validate_sources:78 - Validating 2 facts
2023-09-09 15:48:11.023 | INFO | __main__:validate_sources:80 - Found 2 facts with sources
{
"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"
]
}
]
}
"""

View File

@@ -1,6 +0,0 @@
import erdantic as erd
from citation_fuzzy_match import QuestionAnswer
diagram = erd.create(QuestionAnswer)
diagram.draw("examples/citation_fuzzy_match/schema.png")

View File

@@ -1,147 +0,0 @@
import json
from collections.abc import Iterable
from fastapi import FastAPI, Request, HTTPException
from fastapi.params import Depends
from instructor import ResponseSchema
from pydantic import BaseModel, Field
from starlette.responses import StreamingResponse
import os
import instructor
import logging
from openai import OpenAI
from instructor.dsl.multitask import MultiTaskBase
client = instructor.from_openai(OpenAI())
logger = logging.getLogger(__name__)
# FastAPI app
app = FastAPI(
title="Citation with Extraction",
)
class Fact(BaseModel):
"""
Class representing single statement.
Each fact has a body and a list of sources.
If there are multiple facts make sure to break them apart such that each one only uses a set of sources that are relevant to it.
"""
fact: str = Field(
...,
description="Body of the sentences, as part of a response, it should read like a sentence that answers the question",
)
substring_quotes: list[str] = Field(
...,
description="Each source should be a direct quote from the context, as a substring of the original content",
)
def _get_span(self, quote, context):
import regex
minor = quote
major = context
errs_ = 0
s = regex.search(f"({minor}){{e<={errs_}}}", major)
while s is None and errs_ <= len(context) * 0.05:
errs_ += 1
s = regex.search(f"({minor}){{e<={errs_}}}", major)
if s is not None:
yield from s.spans()
def get_spans(self, context):
if self.substring_quotes:
for quote in self.substring_quotes:
yield from self._get_span(quote, context)
class QuestionAnswer(ResponseSchema, MultiTaskBase):
"""
Class representing a question and its answer as a list of facts each one should have a source.
each sentence contains a body and a list of sources."""
question: str = Field(..., description="Question that was asked")
tasks: list[Fact] = Field(
...,
description="Body of the answer, each fact should be its separate object with a body and a list of sources",
)
QuestionAnswer.task_type = Fact
class Question(BaseModel):
context: str = Field(..., description="Context to extract answers from")
query: str = Field(..., description="Question to answer")
# Function to extract entities from input text using GPT-3.5
def stream_extract(question: Question) -> Iterable[Fact]:
completion = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
stream=True,
functions=[QuestionAnswer.openai_schema],
function_call={"name": QuestionAnswer.openai_schema["name"]},
messages=[
{
"role": "system",
"content": "You are a world class algorithm to answer questions with correct and exact citations. ",
},
{"role": "user", "content": "Answer question using the following context"},
{"role": "user", "content": f"{question.context}"},
{"role": "user", "content": f"Question: {question.query}"},
{
"role": "user",
"content": "Tips: Make sure to cite your sources, and use the exact words from the context.",
},
],
max_tokens=2000,
)
return QuestionAnswer.from_streaming_response(completion)
def get_api_key(request: Request):
"""
This just gets the API key from the request headers.
but tries to read from the environment variable OPENAI_API_KEY first.
"""
if "OPENAI_API_KEY" in os.environ:
return os.environ["OPENAI_API_KEY"]
auth = request.headers.get("Authorization")
if auth is None:
raise HTTPException(status_code=401, detail="Missing Authorization header")
if auth.startswith("Bearer "):
return auth.replace("Bearer ", "")
return None
# Route to handle SSE events and return users
@app.post("/extract", response_class=StreamingResponse)
async def extract(question: Question, openai_key: str = Depends(get_api_key)):
raise Exception(
"The 'openai.api_key' option isn't read in the client API. You will need to pass it when you instantiate the client, e.g. 'OpenAI(api_key=openai_key)'"
)
facts = stream_extract(question)
async def generate():
for fact in facts:
logger.info(f"Fact: {fact}")
spans = list(fact.get_spans(question.context))
resp = {
"body": fact.fact,
"spans": spans,
"citation": [question.context[a:b] for (a, b) in spans],
}
resp_json = json.dumps(resp)
yield f"data: {resp_json}"
yield "data: [DONE]"
return StreamingResponse(generate(), media_type="text/event-stream")

View File

@@ -1,12 +0,0 @@
from main import app
import modal
stub = modal.Stub("rag-citation")
image = modal.Image.debian_slim().pip_install("fastapi", "instructor>=0.2.1", "regex")
@stub.function(image=image)
@modal.asgi_app()
def fastapi_app():
return app

View File

@@ -1,6 +0,0 @@
fastapi
uvicorn
openai>=1.0.0
pydantic
instructor
regex

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,225 +0,0 @@
from typing import Optional
from openai import OpenAI
from pydantic import (
BaseModel,
Field,
ValidationError,
ValidationInfo,
field_validator,
model_validator,
)
import instructor
client = instructor.from_openai(OpenAI())
"""
Example 1) Simple Substring check that compares a citation to a text chunk
"""
class Statements(BaseModel):
body: str
substring_quote: str
@field_validator("substring_quote")
@classmethod
def substring_quote_exists(cls, v: str, info: ValidationInfo):
context = info.context.get("text_chunks", None)
# Check if the substring_quote is in the text_chunk
# if not, raise an error
for text_chunk in context.values():
if v in text_chunk:
return v
raise ValueError(
f"Could not find substring_quote `{v}` in contexts",
)
class AnswerWithCitaton(BaseModel):
question: str
answer: list[Statements]
try:
AnswerWithCitaton.model_validate(
{
"question": "What is the capital of France?",
"answer": [
{"body": "Paris", "substring_quote": "Paris is the capital of France"},
],
},
context={
"text_chunks": {
1: "Jason is a pirate",
2: "Paris is not the capital of France",
3: "Irrelevant data",
}
},
)
except ValidationError as e:
print(e)
"""
answer.0.substring_quote
Value error, Could not find substring_quote `Paris is the capital of France` in contexts [type=value_error, input_value='Paris is the capital of France', input_type=str]
For further information visit https://errors.pydantic.dev/2.4/v/value_error
"""
"""
Example 2) Using an LLM to verify if a
"""
class Validation(BaseModel):
"""
Verification response from the LLM,
the error message should be detailed if the is_valid is False
but keep it to less than 100 characters, reference specific
attributes that you are comparing, use `...` is the string is too long
"""
is_valid: bool
error_messages: Optional[str] = Field(None, description="Error messages if any")
class Statements(BaseModel):
body: str
substring_quote: str
@model_validator(mode="after")
def substring_quote_exists(self, info: ValidationInfo):
context = info.context.get("text_chunks", None)
resp: Validation = client.chat.completions.create(
response_model=Validation,
messages=[
{
"role": "user",
"content": f"Does the following citation exist in the following context?\n\nCitation: {self.substring_quote}\n\nContext: {context}",
}
],
model="gpt-3.5-turbo",
)
if resp.is_valid:
return self
raise ValueError(resp.error_messages)
class AnswerWithCitaton(BaseModel):
question: str
answer: list[Statements]
resp = AnswerWithCitaton.model_validate(
{
"question": "What is the capital of France?",
"answer": [
{"body": "Paris", "substring_quote": "Paris is the capital of France"},
],
},
context={
"text_chunks": {
1: "Jason is a pirate",
2: "Paris is the capital of France",
3: "Irrelevant data",
}
},
)
# output: notice that there are no errors
print(resp.model_dump_json(indent=2))
{
"question": "What is the capital of France?",
"answer": [{"body": "Paris", "substring_quote": "Paris is the capital of France"}],
}
# Now we change the text chunk to something else, and we get an error
try:
AnswerWithCitaton.model_validate(
{
"question": "What is the capital of France?",
"answer": [
{"body": "Paris", "substring_quote": "Paris is the capital of France"},
],
},
context={
"text_chunks": {
1: "Jason is a pirate",
2: "Paris is not the capital of France",
3: "Irrelevant data",
}
},
)
except ValidationError as e:
print(e)
"""
1 validation error for AnswerWithCitaton
answer.0
Value error, Citation not found in context [type=value_error, input_value={'body': 'Paris', 'substr... the capital of France'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.4/v/value_error
"""
# Example 3) Using an LLM to verify if the citations and the answers are all aligned
# we keep the same model as above for Statements, but we add a new model for the answer
# that also verifies that the citations are aligned with the answers
class AnswerWithCitaton(BaseModel):
question: str
answer: list[Statements]
@model_validator(mode="after")
def validate_answer(self, info: ValidationInfo):
context = info.context.get("text_chunks", None)
resp: Validation = client.chat.completions.create(
response_model=Validation,
messages=[
{
"role": "user",
"content": f"Does the following answers match the question and the context?\n\nQuestion: {self.question}\n\nAnswer: {self.answer}\n\nContext: {context}",
}
],
model="gpt-3.5-turbo",
)
if resp.is_valid:
return self
raise ValueError(resp.error_messages)
"""
Using LLMs for citation verification is inefficient during runtime.
However, we can utilize them to create a dataset consisting only of accurate responses
where citations must be valid (as determined by LLM, fuzzy text search, etc.).
This approach would require an initial investment during data generation to obtain
a finely-tuned model for improved citation.
"""
try:
AnswerWithCitaton.model_validate(
{
"question": "What is the capital of France?",
"answer": [
{"body": "Texas", "substring_quote": "Paris is the capital of France"},
],
},
context={
"text_chunks": {
1: "Jason is a pirate",
2: "Paris is the capital of France",
3: "Irrelevant data",
}
},
)
except ValidationError as e:
print(e)
"""
1 validation error for AnswerWithCitaton
Value error, The answer does not match the question and context [type=value_error, input_value={'question': 'What is the...he capital of France'}]}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.4/v/value_error
"""

View File

@@ -1,176 +0,0 @@
# pip install openai instructor
from pydantic import BaseModel, field_validator, Field
import openai
import instructor
from tqdm import tqdm
client = instructor.from_openai(openai.OpenAI())
classes = {
"11-0000": "Management",
"13-0000": "Business and Financial Operations",
"15-0000": "Computer and Mathematical",
"17-0000": "Architecture and Engineering",
"19-0000": "Life, Physical, and Social Science",
"21-0000": "Community and Social Service",
"23-0000": "Legal",
"25-0000": "Education Instruction and Library",
"27-0000": "Arts, Design, Entertainment, Sports and Media",
"29-0000": "Healthcare Practitioners and Technical",
"31-0000": "Healthcare Support",
"33-0000": "Protective Service",
"35-0000": "Food Preparation and Serving",
"37-0000": "Building and Grounds Cleaning and Maintenance",
"39-0000": "Personal Care and Service",
"41-0000": "Sales and Related",
"43-0000": "Office and Administrative Support",
"45-0000": "Farming, Fishing and Forestry",
"47-0000": "Construction and Extraction",
"49-0000": "Installation, Maintenance, and Repair",
"51-0000": "Production Occupations",
"53-0000": "Transportation and Material Moving",
"55-0000": "Military Specific",
"99-0000": "Other",
}
class SOCCode(BaseModel):
reasoning: str = Field(
default=None,
description="Step-by-step reasoning to get the correct classification",
)
code: str
@field_validator("code")
def validate_code(cls, v):
if v not in classes:
raise ValueError(f"Invalid SOC code, {v}")
return v
def classify_job(description: str) -> SOCCode:
response = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=SOCCode,
max_retries=3,
messages=[
{
"role": "system",
"content": f"You are an expert at classifying job descriptions into Standard Occupational Classification (SOC) codes. from the following list: {classes}",
},
{
"role": "user",
"content": f"Classify this job description into the most appropriate SOC code: {description}",
},
],
)
return response
if __name__ == "__main__":
# gpt-3.5-turbo: 16/20
# gpt-3.5-turbo (COT): 18/20
# gpt-4-turbo: 20/20
job_descriptions = [
(
"Develop and design complex software applications for various industries, including finance, healthcare, and e-commerce",
"15-0000", # Computer and Mathematical Occupations
),
(
"Provide comprehensive technical support and troubleshooting for enterprise-level software products, ensuring seamless user experience",
"15-0000", # Computer and Mathematical Occupations
),
(
"Teach a diverse range of subjects to elementary school students, fostering their intellectual and social development",
"25-0000", # Education, Training, and Library Occupations
),
(
"Conduct cutting-edge research in various academic fields at a renowned university, contributing to the advancement of knowledge",
"25-0000", # Education, Training, and Library Occupations
),
(
"Design visually appealing and strategically effective logos, branding, and marketing materials for clients across different industries",
"27-0000", # Arts, Design, Entertainment, Sports, and Media Occupations
),
(
"Perform as part of a professional musical group, entertaining audiences and showcasing artistic talent",
"27-0000", # Arts, Design, Entertainment, Sports, and Media Occupations
),
(
"Diagnose and treat a wide range of injuries and medical conditions, providing comprehensive healthcare services to patients",
"29-0000", # Healthcare Practitioners and Technical Occupations
),
(
"Assist doctors and nurses in delivering high-quality patient care, ensuring the smooth operation of healthcare facilities",
"31-0000", # Healthcare Support Occupations
),
(
"Patrol assigned areas to enforce laws and ordinances, maintaining public safety and order in the community",
"33-0000", # Protective Service Occupations
),
(
"Prepare and serve a diverse menu of delectable meals in a fast-paced restaurant environment",
"35-0000", # Food Preparation and Serving Related Occupations
),
(
"Maintain the cleanliness and upkeep of various buildings and facilities, ensuring a safe and presentable environment",
"37-0000", # Building and Grounds Cleaning and Maintenance Occupations
),
(
"Provide a range of beauty services, such as haircuts, styling, and manicures, to help clients look and feel their best",
"39-0000", # Personal Care and Service Occupations
),
(
"Engage with customers in a retail setting, providing excellent service and assisting them in finding the products they need",
"41-0000", # Sales and Related Occupations
),
(
"Perform a variety of clerical duties in an office environment, supporting the overall operations of the organization",
"43-0000", # Office and Administrative Support Occupations
),
(
"Cultivate and harvest a wide range of crops, contributing to the production of food and other agricultural products",
"45-0000", # Farming, Fishing, and Forestry Occupations
),
(
"Construct and build various structures, including residential, commercial, and infrastructure projects",
"47-0000", # Construction and Extraction Occupations
),
(
"Repair and maintain a diverse range of mechanical equipment, ensuring their proper functioning and longevity",
"49-0000", # Installation, Maintenance, and Repair Occupations
),
(
"Operate specialized machinery and equipment in a manufacturing setting to produce high-quality goods",
"51-0000", # Production Occupations
),
(
"Transport freight and goods across different regions, ensuring timely and efficient delivery",
"53-0000", # Transportation and Material Moving Occupations
),
(
"Serve in the armed forces, protecting the nation and its citizens through various military operations and duties",
"55-0000", # Military Specific Occupations
),
]
correct = 0
errors = []
for description, expected_code in tqdm(job_descriptions):
try:
predicted_code = None
result = classify_job(description)
predicted_code = result.code
assert result.code == expected_code, (
f"Expected {expected_code}, got {result.code} for description: {description}"
)
correct += 1
except Exception as e:
errors.append(
f"Got {classes.get(predicted_code, 'Unknown')} expected {classes.get(expected_code, 'Unknown')}"
)
print(f"{correct} out of {len(job_descriptions)} tests passed!")
for error in errors:
print(error)

View File

@@ -1,41 +0,0 @@
import enum
import instructor
from openai import OpenAI
from pydantic import BaseModel
client = instructor.from_openai(OpenAI())
# Define new Enum class for multiple labels
class MultiLabels(str, enum.Enum):
BILLING = "billing"
GENERAL_QUERY = "general_query"
HARDWARE = "hardware"
# Adjust the prediction model to accommodate a list of labels
class MultiClassPrediction(BaseModel):
predicted_labels: list[MultiLabels]
# Modify the classify function
def multi_classify(data: str) -> MultiClassPrediction:
return client.chat.completions.create(
model="gpt-4o-mini",
response_model=MultiClassPrediction,
messages=[
{
"role": "user",
"content": f"Classify the following support ticket: {data}",
},
],
) # type: ignore
# Example using a support ticket
ticket = (
"My account is locked and I can't access my billing info. Phone is also broken."
)
prediction = multi_classify(ticket)
print(prediction)

View File

@@ -1,37 +0,0 @@
import enum
import instructor
from openai import OpenAI
from pydantic import BaseModel
client = instructor.from_openai(OpenAI())
class Labels(str, enum.Enum):
SPAM = "spam"
NOT_SPAM = "not_spam"
class SinglePrediction(BaseModel):
"""
Correct class label for the given text
"""
class_label: Labels
def classify(data: str) -> SinglePrediction:
return client.chat.completions.create(
model="gpt-4o-mini",
response_model=SinglePrediction,
messages=[
{
"role": "user",
"content": f"Classify the following text: {data}",
},
],
) # type: ignore
prediction = classify("Hello there I'm a nigerian prince and I want to give you money")
assert prediction.class_label == Labels.SPAM

View File

@@ -1,121 +0,0 @@
import json
import datetime
from pathlib import Path
from jinja2 import Template
import re
from datamodel_code_generator import InputFileType, generate
from pydantic import BaseModel
APP_TEMPLATE_STR = '''# generated by instructor-codegen:
# timestamp: {{timestamp}}
# task_name: {{task_name}}
# api_path: {{api_path}}
# json_schema_path: {{json_schema_path}}
from fastapi import FastAPI
from pydantic import BaseModel
from jinja2 import Template
from models import {{title}}
import openai
import instructor
instructor.from_openai()
app = FastAPI()
class TemplateVariables(BaseModel):
{% for var in jinja_vars %}
{{var.strip()}}: str
{% endfor %}
class RequestSchema(BaseModel):
template_variables: TemplateVariables
model: str
temperature: int
PROMPT_TEMPLATE = Template("""{{prompt_template}}""".strip())
@app.post("{{api_path}}", response_model={{title}})
async def {{task_name}}(input: RequestSchema) -> {{title}}:
rendered_prompt = PROMPT_TEMPLATE.render(**input.template_variables.model_dump())
return await openai.ChatCompletion.acreate(
model=input.model,
temperature=input.temperature,
response_model={{title}},
messages=[
{"role": "user", "content": rendered_prompt}
]
) # type: ignore
'''
class TemplateVariables(BaseModel):
biography: str
def load_json_schema(json_schema_path: str) -> dict:
try:
with open(json_schema_path) as f:
return json.load(f)
except Exception as e:
raise ValueError(f"Failed to load JSON schema: {e}") from e
def generate_pydantic_model(json_schema_path: str):
input_path = Path(json_schema_path)
output_path = Path("./models.py")
generate(
input_=input_path, input_file_type=InputFileType.JsonSchema, output=output_path
)
def extract_jinja_vars(prompt_template: str) -> list:
return re.findall(r"\{\{(.*?)\}\}", prompt_template)
def render_app_template(template_str: str, **kwargs) -> str:
app_template = Template(template_str)
return app_template.render(**kwargs)
def create_app(
api_path: str, task_name: str, json_schema_path: str, prompt_template: str
) -> str:
if not api_path.startswith("/"):
api_path = "/" + api_path
schema = load_json_schema(json_schema_path)
title = schema["title"]
generate_pydantic_model(json_schema_path)
jinja_vars = extract_jinja_vars(prompt_template)
return render_app_template(
APP_TEMPLATE_STR,
timestamp=datetime.datetime.now().isoformat(),
task_name=task_name,
api_path=api_path,
json_schema_path=json_schema_path,
title=title,
jinja_vars=jinja_vars,
prompt_template=prompt_template,
)
if __name__ == "__main__":
try:
fastapi_code = create_app(
api_path="/api/v1/extract_person",
task_name="extract_person",
json_schema_path="./input.json",
prompt_template="Extract the person from the following: {{biography}}",
)
with open("./run.py", "w") as f:
f.write(fastapi_code)
print("FastAPI application generated and saved to './run.py'")
except Exception as e:
print(f"An error occurred: {e}")

View File

@@ -1,30 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "ExtractPerson",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer"
},
"phoneNumbers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["home", "work", "mobile"]
},
"number": {
"type": "string"
}
},
"required": ["type", "number"]
}
}
},
"required": ["name", "age", "phoneNumbers"]
}

View File

@@ -1,26 +0,0 @@
# generated by datamodel-codegen:
# filename: input.json
# timestamp: 2023-09-10T00:33:42+00:00
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel
class Type(Enum):
home = "home"
work = "work"
mobile = "mobile"
class PhoneNumber(BaseModel):
type: Type
number: str
class ExtractPerson(BaseModel):
name: str
age: int
phoneNumbers: list[PhoneNumber]

View File

@@ -1,35 +0,0 @@
# FastAPI Code Generator
## Overview
Generates FastAPI application code from API path, task name, JSON schema path, and Jinja2 prompt template. Also creates a `models.py` file for Pydantic models.
## Dependencies
- FastAPI
- Pydantic
- Jinja2
- datamodel-code-generator
## Functions
### `create_app(api_path: str, task_name: str, json_schema_path: str, prompt_template: str) -> str`
Main function to generate FastAPI application code.
## Usage
Run the script with required parameters.
Example:
```python
fastapi_code = create_app(
api_path="/api/v1/extract_person",
task_name="extract_person",
json_schema_path="./input.json",
prompt_template="Extract the person from the following: {{biography}}",
)
```
Outputs FastAPI application code to `./run.py` and a Pydantic model to `./models.py`.

View File

@@ -1,43 +0,0 @@
# This file was generated by instructor
# timestamp: 2023-09-09T20:33:42.572627
# task_name: extract_person
# api_path: /api/v1/extract_person
# json_schema_path: ./input.json
import instructor
from fastapi import FastAPI
from pydantic import BaseModel
from jinja2 import Template
from models import ExtractPerson
from openai import AsyncOpenAI
aclient = instructor.apatch(AsyncOpenAI())
app = FastAPI()
class TemplateVariables(BaseModel):
biography: str
class RequestSchema(BaseModel):
template_variables: TemplateVariables
model: str
temperature: int
PROMPT_TEMPLATE = Template(
"""Extract the person from the following: {{biography}}""".strip()
)
@app.post("/api/v1/extract_person", response_model=ExtractPerson)
async def extract_person(input: RequestSchema) -> ExtractPerson:
rendered_prompt = PROMPT_TEMPLATE.render(**input.template_variables.model_dump())
return await aclient.chat.completions.create(
model=input.model,
temperature=input.temperature,
response_model=ExtractPerson,
messages=[{"role": "user", "content": rendered_prompt}],
) # type: ignore

View File

@@ -1,59 +0,0 @@
import cohere
import instructor
from pydantic import BaseModel, Field
# Patching the Cohere client with the instructor for enhanced capabilities
client = instructor.from_cohere(
cohere.ClientV2(),
max_tokens=1000,
model="command-a-03-2025",
)
class Person(BaseModel):
name: str = Field(description="name of the person")
country_of_origin: str = Field(description="country of origin of the person")
class Group(BaseModel):
group_name: str = Field(description="name of the group")
members: list[Person] = Field(description="list of members in the group")
task = """\
Given the following text, create a Group object for 'The Beatles' band
Text:
The Beatles were an English rock band formed in Liverpool in 1960. With a line-up comprising John Lennon, Paul McCartney, George Harrison and Ringo Starr, they are regarded as the most influential band of all time. The group were integral to the development of 1960s counterculture and popular music's recognition as an art form.
"""
group = client.messages.create(
response_model=Group,
messages=[{"role": "user", "content": task}],
temperature=0,
)
print(group.model_dump_json(indent=2))
"""
{
"group_name": "The Beatles",
"members": [
{
"name": "John Lennon",
"country_of_origin": "England"
},
{
"name": "Paul McCartney",
"country_of_origin": "England"
},
{
"name": "George Harrison",
"country_of_origin": "England"
},
{
"name": "Ringo Starr",
"country_of_origin": "England"
}
]
}
"""

View File

@@ -1,87 +0,0 @@
from enum import Enum
from pydantic import BaseModel, Field
import instructor
from openai import OpenAI
client = instructor.from_openai(OpenAI())
class CRMSource(Enum):
personal = "personal"
business = "business"
work_contacts = "work_contacts"
all = "all"
class CRMSearch(BaseModel):
"""A CRM search query
The search description is a natural language description of the search query
the backend will use semantic search so use a range of phrases to describe the search
"""
source: CRMSource
city_location: str = Field(
..., description="City location used to match the desired customer profile"
)
search_description: str = Field(
..., description="Search query used to match the desired customer profile"
)
class CRMSearchQuery(BaseModel):
"""
A set of CRM queries to be executed against a CRM system,
for large locations decompose into multiple queries of smaller locations
"""
queries: list[CRMSearch]
def query_crm(query: str) -> CRMSearchQuery:
queries = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=CRMSearchQuery,
messages=[
{
"role": "system",
"content": """
You are a world class CRM search career generator.
You will take the user query and decompose it into a set of CRM queries queries.
""",
},
{"role": "user", "content": query},
],
)
return queries
if __name__ == "__main__":
query = "find me all the pottery businesses in San Francisco and my friends in the east coast big cities"
print(query_crm(query).model_dump_json(indent=2))
"""
{
"queries": [
{
"source": "business",
"city_location": "San Francisco",
"search_description": "pottery businesses"
},
{
"source": "personal",
"city_location": "New York",
"search_description": "friends in New York"
},
{
"source": "personal",
"city_location": "Boston",
"search_description": "friends in Boston"
},
{
"source": "personal",
"city_location": "Philadelphia",
"search_description": "friends in Philadelphia"
}
]
}
"""

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env python3
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
if __name__ == "__main__":
client = instructor.from_provider("openai/gpt-4.1-mini")
receipt = client.chat.completions.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'>
# Test precision
total = receipt.price * 2
print(f"Total for 2 items: {total}") # Decimal('9.98')

View File

@@ -0,0 +1,10 @@
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(540, b=677, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 540,\n \"b\": 677,\n \"result\": 1217\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(798, b=534, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 798,\n \"b\": 534,\n \"result\": 1332\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(608, b=669, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 608,\n \"b\": 669,\n \"result\": 1277\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(982, b=768, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 982,\n \"b\": 768,\n \"result\": 1750\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(994, b=682, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 994,\n \"b\": 682,\n \"result\": 1676\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(467, b=754, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 467,\n \"b\": 754,\n \"result\": 1221\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(497, b=364, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 497,\n \"b\": 364,\n \"result\": 861\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(840, b=821, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 840,\n \"b\": 821,\n \"result\": 1661\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(646, b=835, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 646,\n \"b\": 835,\n \"result\": 1481\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(926, b=196, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 926,\n \"b\": 196,\n \"result\": 1122\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}

View File

@@ -1,4 +0,0 @@
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(540, b=677, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 540,\n \"b\": 677,\n \"result\": 1217\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(798, b=534, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 798,\n \"b\": 534,\n \"result\": 1332\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(608, b=669, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 608,\n \"b\": 669,\n \"result\": 1277\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}
{"messages": [{"role": "system", "content": "Predict the results of this function:\n\ndef fn(a: int, b: int, c: str) -> __main__.Multiply\n\"\"\"\n_summary_\n\nArgs:\n a (int): _description_\n b (int): _description_\n c (str): _description_\n\nReturns:\n Response: _description_\n\"\"\""}, {"role": "user", "content": "Return `fn(982, b=768, c=\"hello\")`"}, {"role": "assistant", "function_call": {"name": "Multiply", "arguments": "{\n \"a\": 982,\n \"b\": 768,\n \"result\": 1750\n}"}}], "functions": [{"name": "Multiply", "description": "Correctly extracted `Multiply` with all the required parameters with correct types", "parameters": {"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}, "result": {"description": "The result of the multiplication", "type": "integer"}}, "required": ["a", "b", "result"], "type": "object"}}]}

View File

@@ -1,47 +0,0 @@
# What to Expect
This script demonstrates how to use the `Instructor` library for fine-tuning a Python function that performs three-digit multiplication. It uses Pydantic for type validation and logging features to generate a fine-tuning dataset.
## How to Run
### Prerequisites
- Python 3.9
- `Instructor` library
### Steps
1. **Install Dependencies**
If you haven't already installed the required libraries, you can do so using pip:
```
pip install instructor pydantic
```
2. **Set Up Logging**
The script uses Python's built-in `logging` module to log the fine-tuning process. Ensure you have write permissions in the directory where the log file `math_finetunes.jsonl` will be saved.
3. **Run the Script**
Navigate to the directory containing `script.py` and run it:
```
python three_digit_mul.py
```
This will execute the script, running the function ten times with random three-digit numbers for multiplication. The function outputs and logs are saved in `math_finetunes.jsonl`.
4. **Fine-Tuning**
Once you have the log file, you can run a fine-tuning job using the following `Instructor` CLI command:
```
instructor jobs create-from-file math_finetunes.jsonl
```
Wait for the fine-tuning job to complete.
If you have validation date you can run:
```
instructor jobs create-from-file math_finetunes.jsonl --n-epochs 4 --validation-file math_finetunes_val.jsonl
```
### Output
That's it! You've successfully run the script and can now proceed to fine-tune your model.
### Dispatch
Once you have the model you can replace the model in `three_digit_mul_dispatch.py` with the model you just fine-tuned and run the script again. This time, the script will use the fine-tuned model to predict the output of the function.

View File

@@ -1,72 +0,0 @@
import logging
from pydantic import BaseModel, Field
from instructor import Instructions
logging.basicConfig(level=logging.INFO)
# Usage
instructions = Instructions(
name="three_digit_multiply",
finetune_format="messages",
log_handlers=[
logging.FileHandler("math_finetunes.jsonl"),
],
)
class Multiply(BaseModel):
a: int
b: int
result: int = Field(..., description="The result of the multiplication")
@instructions.distil
def fn(a: int, b: int) -> Multiply:
"""Return the result of multiplying a and b together"""
resp = a * b
return Multiply(a=a, b=b, result=resp)
if __name__ == "__main__":
import random
log_lines = {
"messages": [
{
"role": "system",
"content": 'Predict the results of this function:\n\ndef fn(a: int, b: int) -> __main__.Multiply\n"""\nReturn the result of multiplying a and b together\n"""',
},
{"role": "user", "content": "Return `fn(169, b=166)`"},
{
"role": "assistant",
"function_call": {
"name": "Multiply",
"arguments": '{\n "a": 169,\n "b": 166,\n "result": 28054\n}',
},
},
],
"functions": [
{
"name": "Multiply",
"description": "Correctly extracted `Multiply` with all the required parameters with correct types",
"parameters": {
"properties": {
"a": {"title": "A", "type": "integer"},
"b": {"title": "B", "type": "integer"},
"result": {
"description": "The result of the multiplication",
"title": "Result",
"type": "integer",
},
},
"required": ["a", "b", "result"],
"type": "object",
},
}
],
}
for _ in range(10):
a = random.randint(100, 999)
b = random.randint(100, 999)
print("returning", fn(a, b=b))

View File

@@ -1,51 +0,0 @@
import logging
from pydantic import BaseModel, Field
from instructor import Instructions
import instructor
from openai import OpenAI
client = instructor.from_openai(OpenAI())
logging.basicConfig(level=logging.INFO)
# Usage
instructions = Instructions(
name="three_digit_multiply",
finetune_format="messages",
include_code_body=True,
log_handlers=[
logging.FileHandler("math_finetunes.jsonl"),
],
openai_client=client,
)
class Multiply(BaseModel):
a: int
b: int
result: int = Field(..., description="The result of the multiplication")
@instructions.distil(mode="dispatch", model="ft:gpt-3.5-turbo-0125:personal::9i1JeuxJ")
def fn(a: int, b: int) -> Multiply:
"""Return the result of the multiplication as an integer"""
resp = a * b
return Multiply(a=a, b=b, result=resp)
if __name__ == "__main__":
import random
for _ in range(5):
a = random.randint(100, 999)
b = random.randint(100, 999)
result = fn(a, b)
print(f"{a} * {b} = {result.result}, expected {a * b}")
"""
972 * 508 = 493056, expected 493776
145 * 369 = 53505, expected 53505
940 * 440 = 413600, expected 413600
114 * 213 = 24282, expected 24282
259 * 650 = 168350, expected 168350
"""

View File

@@ -1,146 +0,0 @@
from collections import Counter, defaultdict
from enum import Enum
from typing import Any, Union
import numpy as np
import json
from pydantic import ValidationError
from pprint import pprint
import models as m
class Status(Enum):
IS_JSON = "_is_json_"
IS_VALID = "_is_valid_"
VALIDATION_ERROR = "_validation_error_"
class StreamingAccumulatorManager:
def __init__(self):
self.accumulator = defaultdict(StreamingAccumulator)
def validate_string(self, json_string: str, index: int) -> None:
try:
obj = json.loads(json_string)
self.accumulator[Status.IS_JSON.value].update(index, True)
try:
# Replace this line with your validation logic
obj = m.MultiSearch.model_validate(obj)
self.update(index, obj.model_dump())
self.accumulator[Status.IS_VALID.value].update(index, True)
except ValidationError as e:
self.accumulator[Status.IS_VALID.value].update(index, False)
self.process_validation_error(e, index)
except json.JSONDecodeError:
self.accumulator[Status.IS_JSON.value].update(index, False)
def process_validation_error(self, error, index):
for err in error.errors():
path = (
"$."
+ ".".join(
[str(x) if not isinstance(x, int) else "[*]" for x in err["loc"]]
)
+ "."
+ err["type"]
)
self.accumulator[Status.VALIDATION_ERROR.value].update(index, path)
def update(self, index, data: Any, path: str = "$") -> None:
if isinstance(data, dict):
for key, value in data.items():
new_path = f"{path}.{key}"
self.update(index, value, new_path)
elif isinstance(data, list):
new_path = f"{path}[*]"
for value in data:
self.update(index, value, new_path)
length_path = f"{path}.length"
self.accumulator[length_path].update(index, len(data))
elif isinstance(data, Enum):
enum_path = f"{path}.enum"
self.accumulator[enum_path].update(index, data.value)
else:
self.accumulator[path].update(index, data)
def summarize(self) -> dict[str, dict]:
return {k: v.summarize(key_name=k) for k, v in self.accumulator.items()}
class StreamingAccumulator:
def __init__(self):
self.counter = Counter()
self.min = float("inf")
self.max = float("-inf")
self.sum = 0
self.squared_sum = 0
self.unique_values = set()
self.missing_values = 0
self.str_min_length = float("inf")
self.str_max_length = float("-inf")
self.str_sum_length = 0
self.str_squared_sum_length = 0
self.value = []
self.str_length = []
self.reverse_lookup = defaultdict(list)
def update(self, index: Any, value: Any) -> None:
if isinstance(value, (int, str, bool)):
self.counter[value] += 1
self.unique_values.add(value)
self.value.append(value)
self.reverse_lookup[value].append(index)
if value is None or value == "":
self.missing_values += 1
return
if isinstance(value, (int, float)):
self.min = min(self.min, value)
self.max = max(self.max, value)
self.sum += value
self.squared_sum += value**2
if isinstance(value, str):
str_len = len(value)
self.str_length.append(str_len)
self.str_min_length = min(self.str_min_length, str_len)
self.str_max_length = max(self.str_max_length, str_len)
self.str_sum_length += str_len
self.str_squared_sum_length += str_len**2
def summarize(self, key_name=None) -> dict[str, Union[int, float, dict]]:
if key_name is None:
key_name = ""
n = sum(self.counter.values())
summaries = {}
summaries["counter"] = self.counter
summaries["unique_count"] = len(self.unique_values)
summaries["missing_values"] = self.missing_values
summaries["_reverse_lookup"] = dict(self.reverse_lookup)
if n > 0:
if all(isinstance(value, (bool)) for value in self.unique_values):
summaries["mean"] = self.sum / n
return summaries
if all(isinstance(value, (int, float)) for value in self.unique_values):
summaries["min"] = self.min
summaries["max"] = self.max
summaries["mean"] = self.sum / n
summaries["std"] = np.sqrt(self.squared_sum / n - (self.sum / n) ** 2)
return summaries
if all(isinstance(value, str) for value in self.unique_values):
summaries["str_min_length"] = self.str_min_length
summaries["str_max_length"] = self.str_max_length
summaries["str_mean_length"] = self.str_sum_length / n
summaries["str_std_length"] = np.sqrt(
self.str_squared_sum_length / n - (self.str_sum_length / n) ** 2
)
return summaries
return summaries
if __name__ == "__main__":
eval_manager = StreamingAccumulatorManager()
with open("test.jsonl") as f:
lines = f.readlines()
for ii, line in enumerate(lines):
eval_manager.validate_string(line, ii)
pprint(eval_manager.summarize())

View File

@@ -1,24 +0,0 @@
from typing import Optional
from pydantic import BaseModel, Field
from enum import Enum
class SourceType(str, Enum):
CRM = "CRM"
WEB = "WEB"
EMAIL = "EMAIL"
SOCIAL_MEDIA = "SOCIAL_MEDIA"
OTHER = "OTHER"
class Search(BaseModel):
query: str
source_type: SourceType
results_limit: Optional[int] = Field(10)
is_priority: Optional[bool] = None
tags: Optional[list[str]] = None
class MultiSearch(BaseModel):
queries: list[Search]
user_id: Optional[str]

View File

@@ -1,224 +0,0 @@
from collections import Counter
stats_dict = {
"$.queries.length": {
"_reverse_lookup": {
1: [0, 1, 8, 9, 10, 13, 14, 15],
2: [7, 11, 16],
3: [12, 17],
},
"counter": Counter({1: 8, 2: 3, 3: 2}),
"max": 3,
"mean": 1.5384615384615385,
"min": 1,
"missing_values": 0,
"std": 0.7457969011409735,
"unique_count": 3,
},
"$.queries[*].is_priority": {
"_reverse_lookup": {False: [13], True: [1, 9, 14, 17]},
"counter": Counter({True: 4, False: 1}),
"mean": 0.8,
"missing_values": 15,
"unique_count": 2,
},
"$.queries[*].query": {
"_reverse_lookup": {
"customer churn": [1],
"customer feedback": [15],
"customer satisfaction": [11],
"email campaigns": [12],
"email open rates": [17],
"email outreach": [10],
"marketing strategies": [14],
"new products": [16],
"product sales": [11],
"revenue 2022": [9],
"revenue streams": [16],
"sales Q1": [0, 7, 8, 13],
"sales Q2": [7],
"social impact": [12],
"social trends": [17],
"web traffic": [12],
"website analytics": [17],
},
"counter": Counter(
{
"sales Q1": 4,
"customer churn": 1,
"sales Q2": 1,
"revenue 2022": 1,
"email outreach": 1,
"product sales": 1,
"customer satisfaction": 1,
"social impact": 1,
"email campaigns": 1,
"web traffic": 1,
"marketing strategies": 1,
"customer feedback": 1,
"revenue streams": 1,
"new products": 1,
"social trends": 1,
"email open rates": 1,
"website analytics": 1,
}
),
"missing_values": 0,
"str_max_length": 21,
"str_mean_length": 13.15,
"str_min_length": 8,
"str_std_length": 3.8376425054973518,
"unique_count": 17,
},
"$.queries[*].results_limit": {
"_reverse_lookup": {
5: [17],
10: [0, 1, 7, 7, 8, 9, 10, 11, 11, 12, 12, 12, 13, 15, 16, 16, 17, 17],
15: [14],
},
"counter": Counter({10: 18, 15: 1, 5: 1}),
"max": 15,
"mean": 10.0,
"min": 5,
"missing_values": 0,
"std": 1.5811388300841898,
"unique_count": 3,
},
"$.queries[*].source_type.enum": {
"_reverse_lookup": {
"CRM": [0, 7, 8, 11, 13, 16],
"EMAIL": [10, 11, 12, 15, 17],
"SOCIAL_MEDIA": [12, 17],
"WEB": [1, 7, 9, 12, 14, 16, 17],
},
"counter": Counter({"WEB": 7, "CRM": 6, "EMAIL": 5, "SOCIAL_MEDIA": 2}),
"missing_values": 0,
"str_max_length": 12,
"str_mean_length": 4.4,
"str_min_length": 3,
"str_std_length": 2.672077843177477,
"unique_count": 4,
},
"$.queries[*].tags": {
"_reverse_lookup": {},
"counter": Counter(),
"missing_values": 16,
"unique_count": 0,
},
"$.queries[*].tags.length": {
"_reverse_lookup": {1: [15, 17], 2: [10, 14]},
"counter": Counter({2: 2, 1: 2}),
"max": 2,
"mean": 1.5,
"min": 1,
"missing_values": 0,
"std": 0.5,
"unique_count": 2,
},
"$.queries[*].tags[*]": {
"_reverse_lookup": {
"2022": [10],
"2023": [14],
"analytics": [17],
"feedback": [15],
"outreach": [10],
"strategy": [14],
},
"counter": Counter(
{
"outreach": 1,
"2022": 1,
"strategy": 1,
"2023": 1,
"feedback": 1,
"analytics": 1,
}
),
"missing_values": 0,
"str_max_length": 9,
"str_mean_length": 6.833333333333333,
"str_min_length": 4,
"str_std_length": 2.034425935955618,
"unique_count": 6,
},
"$.user_id": {
"_reverse_lookup": {
"user_1": [0],
"user_10": [10],
"user_11": [11],
"user_12": [12],
"user_13": [13],
"user_14": [14],
"user_15": [15],
"user_16": [16],
"user_17": [17],
"user_2": [1],
"user_7": [7],
"user_8": [8],
"user_9": [9],
},
"counter": Counter(
{
"user_1": 1,
"user_2": 1,
"user_7": 1,
"user_8": 1,
"user_9": 1,
"user_10": 1,
"user_11": 1,
"user_12": 1,
"user_13": 1,
"user_14": 1,
"user_15": 1,
"user_16": 1,
"user_17": 1,
}
),
"missing_values": 0,
"str_max_length": 7,
"str_mean_length": 6.615384615384615,
"str_min_length": 6,
"str_std_length": 0.48650425541052295,
"unique_count": 13,
},
"_is_json_": {
"_reverse_lookup": {
False: [2, 4],
True: [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
},
"counter": Counter({True: 16, False: 2}),
"mean": 0.8888888888888888,
"missing_values": 0,
"unique_count": 2,
},
"_is_valid_": {
"_reverse_lookup": {
False: [3, 5, 6],
True: [0, 1, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
},
"counter": Counter({True: 13, False: 3}),
"mean": 0.8125,
"missing_values": 0,
"unique_count": 2,
},
"_validation_error_": {
"_reverse_lookup": {
"$.queries.[*].is_priority.bool_parsing": [6],
"$.queries.[*].source_type.enum": [3],
"$.user_id.missing": [5],
},
"counter": Counter(
{
"$.queries.[*].source_type.enum": 1,
"$.user_id.missing": 1,
"$.queries.[*].is_priority.bool_parsing": 1,
}
),
"missing_values": 0,
"str_max_length": 38,
"str_mean_length": 28.333333333333332,
"str_min_length": 17,
"str_std_length": 8.653836657164781,
"unique_count": 3,
},
}

View File

@@ -1,62 +0,0 @@
import streamlit as st
from stats_dict import stats_dict
# Sample data
query_data = {i: line.strip() for i, line in enumerate(open("test.jsonl"))}
# Initialize selected keys
selected_keys = {}
# Function to get lines
def get_lines(stats_key, keys):
indices = []
for key in keys:
indices.extend(stats_dict[stats_key]["_reverse_lookup"][key])
return "\n".join([query_data[i] for i in indices])
# Function to render dropdown and button
def render_dropdown_and_button(stats_key):
st.subheader(f"Stats for `{stats_key}`")
st.json(stats_dict[stats_key]["counter"])
st.json(
{k: v for k, v in stats_dict[stats_key].items() if isinstance(v, (int, float))}
)
st.subheader("Histogram")
st.bar_chart(stats_dict[stats_key]["counter"], use_container_width=True)
options = list(stats_dict[stats_key]["counter"].keys())
selected_keys[stats_key] = st.multiselect(
f"View samples with {stats_key}",
options,
default=selected_keys.get(stats_key, []),
)
st.code(get_lines(stats_key, selected_keys[stats_key]))
# Sidebar for navigation
st.sidebar.title("Navigation")
page = st.sidebar.selectbox(
"Select a page:",
["Validation Stats", "Individual Path Views"],
)
# Main Streamlit App
st.title("Structured Output Evaluation")
# Validation Stats
if page == "Validation Stats":
st.header("Validation Stats")
for key in [k for k in stats_dict.keys() if k.startswith("_")]:
render_dropdown_and_button(key)
# Individual Path Views
elif page == "Individual Path Views":
st.header("Individual Path Views")
path = st.selectbox(
"Choose a path:",
[key for key in stats_dict.keys() if not key.startswith("_")],
)
if "counter" in stats_dict[path]:
render_dropdown_and_button(path)

View File

@@ -1,18 +0,0 @@
{"queries": [{"query": "sales Q1", "source_type": "CRM"}], "user_id": "user_1"}
{"queries": [{"query": "customer churn", "source_type": "WEB", "is_priority": true}], "user_id": "user_2", "total_queries": 1}
{"queries": ["query": "email campaigns", "source_type": "EMAIL"}, {"query": "social ads", "source_type": "SOCIAL_MEDIA"}], "user_id": "user_3", "total_queries": 2}
{"queries": [{"query": "sales Q2", "source_type": "INVALID_ENUM"}], "user_id": "user_4"}
{queries: [{"query": "sales Q3", "source_type": "CRM"}], "user_id": "user_5"}
{"queries": [{"query": "sales Q4", "source_type": "CRM", "timestamp": "2023-09-10T12:00:00Z"}], "total_queries": 1}
{"queries": [{"query": "customer retention", "source_type": "EMAIL", "is_priority": "should_be_bool"}], "user_id": "user_6"}
{"queries": [{"query": "sales Q1", "source_type": "CRM"}, {"query": "sales Q2", "source_type": "WEB"}], "user_id": "user_7", "total_queries": 2}
{"queries": [{"query": "sales Q1", "source_type": "CRM", "timestamp": "2023-09-10T12:00:00Z"}], "user_id": "user_8", "total_queries": 1}
{"queries": [{"query": "revenue 2022", "source_type": "WEB", "results_limit": 10, "is_priority": true}], "user_id": "user_9", "total_queries": 1}
{"queries": [{"query": "email outreach", "source_type": "EMAIL", "tags": ["outreach", "2022"]}], "user_id": "user_10", "total_queries": 1}
{"queries": [{"query": "product sales", "source_type": "CRM"}, {"query": "customer satisfaction", "source_type": "EMAIL"}], "user_id": "user_11", "total_queries": 2}
{"queries": [{"query": "social impact", "source_type": "SOCIAL_MEDIA"}, {"query": "email campaigns", "source_type": "EMAIL"}, {"query": "web traffic", "source_type": "WEB"}], "user_id": "user_12", "total_queries": 3}
{"queries": [{"query": "sales Q1", "source_type": "CRM", "is_priority": false}], "user_id": "user_13", "total_queries": 1}
{"queries": [{"query": "marketing strategies", "source_type": "WEB", "results_limit": 15, "is_priority": true, "tags": ["strategy", "2023"]}], "user_id": "user_14", "total_queries": 1}
{"queries": [{"query": "customer feedback", "source_type": "EMAIL", "tags": ["feedback"]}], "user_id": "user_15", "total_queries": 1}
{"queries": [{"query": "revenue streams", "source_type": "CRM"}, {"query": "new products", "source_type": "WEB"}], "user_id": "user_16", "total_queries": 2}
{"queries": [{"query": "social trends", "source_type": "SOCIAL_MEDIA", "is_priority": true}, {"query": "email open rates", "source_type": "EMAIL", "results_limit": 5}, {"query": "website analytics", "source_type": "WEB", "tags": ["analytics"]}], "user_id": "user_17", "total_queries": 3}

View File

@@ -1,152 +0,0 @@
from openai import OpenAI
from io import StringIO
from typing import Annotated, Any
from pydantic import (
BaseModel,
BeforeValidator,
PlainSerializer,
InstanceOf,
WithJsonSchema,
)
import instructor
import pandas as pd
from rich.console import Console
console = Console()
client = instructor.from_openai(
client=OpenAI(),
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 separate""",
}
),
]
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.chat.completions.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)
"""
Growth in app installations and sessions across different app categories in Q3 2022 compared to Q2 2022 for Ireland and U.K.
Install Growth (%) Session Growth (%)
Category
Education 7 6
Games 13 3
Social 4 -3
Utilities 6 -0.4
Top 10 Grossing Android Apps in Ireland, October 2023
App Name Category
Rank
1 Google One Productivity
2 Disney+ Entertainment
3 TikTok - Videos, Music & LIVE Entertainment
4 Candy Crush Saga Games
5 Tinder: Dating, Chat & Friends Social networking
6 Coin Master Games
7 Roblox Games
8 Bumble - Dating & Make Friends Dating
9 Royal Match Games
10 Spotify: Music and Podcasts Music & Audio
Top 10 Grossing iOS Apps in Ireland, October 2023
App Name Category
Rank
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

@@ -1,126 +0,0 @@
from openai import OpenAI
from io import StringIO
from typing import Annotated, Any
from pydantic import (
BaseModel,
BeforeValidator,
PlainSerializer,
InstanceOf,
WithJsonSchema,
)
import instructor
import pandas as pd
from langsmith.wrappers import wrap_openai
from langsmith import traceable
client = wrap_openai(OpenAI())
client = instructor.from_openai(
client, mode=instructor.processing.function_calls.Mode.MD_JSON
)
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())
)
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 separate""",
}
),
]
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],
}
),
)
]
)
@traceable(name="extract-table")
def extract(url: str) -> MultipleTables:
tables = client.chat.completions.create(
model="gpt-4-vision-preview",
max_tokens=4000,
response_model=MultipleTables,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": f"Describe this data accurately as a table in markdown format. {example.model_dump_json(indent=2)}",
},
{
"type": "image_url",
"image_url": {"url": url},
},
{
"type": "text",
"text": """
First take a moment to reason about the best set of headers for the tables.
Write a good h1 for the image above. Then follow up with a short description of the what the data is about.
Then for each table you identified, write a h2 tag that is a descriptive title of the table.
Then follow up with a short description of the what the data is about.
Lastly, produce the markdown table for each table you identified.
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.
""",
},
],
}
],
)
return tables.model_dump()
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:
tables = extract(url)
print(tables)

View File

@@ -1,89 +0,0 @@
from openai import OpenAI
from pydantic import BaseModel, Field
from rich.console import Console
import instructor
console = Console()
client = instructor.from_openai(
client=OpenAI(),
mode=instructor.Mode.TOOLS,
)
class People(BaseModel):
id: str
name: str
role: str
reports: list[str] = Field(
default_factory=list, description="People who report to this person"
)
manages: list[str] = Field(
default_factory=list, description="People who this person manages"
)
class Organization(BaseModel):
people: list[People]
def extract(url: str):
return client.chat.completions.create_partial(
model="gpt-4-turbo",
max_tokens=4000,
response_model=Organization,
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": url},
},
{
"type": "text",
"text": """
Analyze the organizational chart image and extract the relevant information to reconstruct the hierarchy.
Create a list of People objects, where each person has the following attributes:
- id: A unique identifier for the person
- name: The person's name
- role: The person's role or position in the organization
- reports: A list of IDs of people who report directly to this person
- manages: A list of IDs of people who this person manages
Ensure that the relationships between people are accurately captured in the reports and manages attributes.
Return the list of People objects as the people attribute of an Organization object.
""",
},
],
}
],
)
console.print(
extract(
"https://www.mindmanager.com/static/mm/images/features/org-chart/hierarchical-chart.png"
)
)
"""
Organization(
people=[
People(id='A1', name='Adele Morana', role='Founder, Chairman & CEO', reports=[], manages=['B1', 'C1', 'D1']),
People(id='B1', name='Winston Cole', role='COO', reports=['A1'], manages=['E1']),
People(id='C1', name='Marcus Kim', role='CFO', reports=['A1'], manages=['F1']),
People(id='D1', name='Karin Ludovicicus', role='CPO', reports=['A1'], manages=['G1']),
People(id='E1', name='Lea Erastos', role='Chief Business Officer', reports=['B1'], manages=['H1', 'I1']),
People(id='F1', name='John McKinley', role='Chief Accounting Officer', reports=['C1'], manages=[]),
People(id='G1', name='Ayda Williams', role='VP, Global Customer & Business Marketing', reports=['D1'], manages=['J1', 'K1']),
People(id='H1', name='Zahida Mahtab', role='VP, Global Affairs & Communication', reports=['E1'], manages=[]),
People(id='I1', name='Adelaide Zhu', role='VP, Central Services', reports=['E1'], manages=[]),
People(id='J1', name='Gabriel Drummond', role='VP, Investor Relations', reports=['G1'], manages=[]),
People(id='K1', name='Nicholas Brambilla', role='VP, Company Brand', reports=['G1'], manages=[]),
People(id='L1', name='Felice Vasili', role='VP Finance', reports=['C1'], manages=[]),
People(id='M1', name='Sandra Herminius', role='VP, Product Marketing', reports=['D1'], manages=[])
]
)
"""

View File

@@ -1,115 +0,0 @@
from openai import OpenAI
from io import StringIO
from typing import Annotated, Any
from pydantic import (
BaseModel,
BeforeValidator,
PlainSerializer,
InstanceOf,
WithJsonSchema,
)
import instructor
import pandas as pd
from rich.console import Console
console = Console()
client = instructor.from_openai(
client=OpenAI(),
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 separate""",
}
),
]
class Table(BaseModel):
caption: str
dataframe: MarkdownDataFrame
def extract(url: str):
return client.chat.completions.create(
model="gpt-4-turbo",
max_tokens=4000,
response_model=Table,
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": url},
},
{
"type": "text",
"text": """
Analyze the organizational chart image and extract the relevant information to reconstruct the hierarchy.
Create a list of People objects, where each person has the following attributes:
- id: A unique identifier for the person
- name: The person's name
- role: The person's role or position in the organization
- manager_name: The name of the person who manages this person
- manager_role: The role of the person who manages this person
Ensure that the relationships between people are accurately captured in the reports and manages attributes.
Return the list of People objects as the people attribute of an Organization object.
""",
},
],
}
],
)
print(
extract(
"https://www.mindmanager.com/static/mm/images/features/org-chart/hierarchical-chart.png"
).model_dump()["dataframe"]
)
"""
| id | name | role | manager_name | manager_role |
|-------:|:-------------------|:-----------------------------------------|:------------------|:-----------------------------|
| 1 | Adele Morana | Founder, Chairman & CEO | | |
| 2 | Winston Cole | COO | Adele Morana | Founder, Chairman & CEO |
| 3 | Marcus Kim | CFO | Adele Morana | Founder, Chairman & CEO |
| 4 | Karin Ludovicus | CPO | Adele Morana | Founder, Chairman & CEO |
| 5 | Lea Erastos | Chief Business Officer | Winston Cole | COO |
| 6 | John McKinley | Chief Accounting Officer | Winston Cole | COO |
| 7 | Zahida Mahtab | VP, Global Affairs & Communication | Winston Cole | COO |
| 8 | Adelaide Zhu | VP, Central Services | Winston Cole | COO |
| 9 | Gabriel Drummond | VP, Investor Relations | Marcus Kim | CFO |
| 10 | Felicie Vasili | VP, Finance | Marcus Kim | CFO |
| 11 | Ayda Williams | VP, Global Customer & Business Marketing | Karin Ludovicius | CPO |
| 12 | Nicholas Brambilla | VP, Company Brand | Karin Ludovicius | CPO |
| 13 | Sandra Herminius | VP, Product Marketing | Karin Ludovicius | CPO |
"""

View File

@@ -1,65 +0,0 @@
from pydantic import BaseModel, model_validator
from openai import OpenAI
import instructor
client = instructor.from_openai(
client=OpenAI(),
mode=instructor.Mode.TOOLS,
)
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
def extract(url: str) -> Receipt:
return client.chat.completions.create(
model="gpt-4o",
max_tokens=4000,
response_model=Receipt,
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": url},
},
{
"type": "text",
"text": "Analyze the image and return the items in the receipt and the total amount.",
},
],
}
],
)
# URLs of images containing receipts. Exhibits the use of the model validator to check the total amount.
urls = [
"https://templates.mediamodifier.com/645124ff36ed2f5227cbf871/supermarket-receipt-template.jpg",
"https://ocr.space/Content/Images/receipt-ocr-original.jpg",
]
for url in urls:
receipt = extract(url)
print(receipt)

View File

@@ -1,106 +0,0 @@
from pydantic import BaseModel
from openai import OpenAI
import instructor
client = OpenAI()
client = instructor.from_openai(client)
class User(BaseModel):
name: str
email: str
class MeetingInfo(BaseModel):
user: User
date: str
location: str
budget: int
deadline: str
data = """
Jason Liu jason@gmail.com
Meeting Date: 2024-01-01
Meeting Location: 1234 Main St
Meeting Budget: $1000
Meeting Deadline: 2024-01-31
"""
stream1 = client.chat.completions.create_partial(
model="gpt-4",
response_model=MeetingInfo,
messages=[
{
"role": "user",
"content": f"Get the information about the meeting and the users {data}",
},
],
stream=True,
) # type: ignore
for message in stream1:
print(message)
"""
ser={} date=None location=None budget=None deadline=None
user={} date=None location=None budget=None deadline=None
user={} date=None location=None budget=None deadline=None
user={} date=None location=None budget=None deadline=None
user=PartialUser(name=None, email=None) date=None location=None budget=None deadline=None
user=PartialUser(name=None, email=None) date=None location=None budget=None deadline=None
user=PartialUser(name=None, email=None) date=None location=None budget=None deadline=None
user=PartialUser(name=None, email=None) date=None location=None budget=None deadline=None
user=PartialUser(name=None, email=None) date=None location=None budget=None deadline=None
user=PartialUser(name=None, email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email=None) date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date=None location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location=None budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=None deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=100 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline=None
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline='2024-01-31'
user=PartialUser(name='Jason Liu', email='jason@gmail.com') date='2024-01-01' location='1234 Main St' budget=1000 deadline='2024-01-31'
"""

View File

@@ -1,118 +0,0 @@
from pydantic import BaseModel
import instructor
from openai import OpenAI
client = instructor.from_openai(OpenAI())
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):
"""
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
EXAMPLE_DOCUMENT = """
# Fake Document with PII for Testing PII Scrubbing Model
## Personal Story
John Doe 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.
## Residence
John currently resides at 123 Main St, Springfield, IL, 62704. He's been living there for about 5 years now.
## Career
At the moment, John is employed at Company A. He started his role as a Software Engineer in January 2015 and has been with the company since then.
"""
# Define the PII Scrubbing Model
pii_data: PIIDataExtraction = client.chat.completions.create(
model="gpt-3.5-turbo",
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:")
print(pii_data.model_dump_json(indent=2))
"""
{
"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"
}
]
}
"""
# Scrub the PII Data from the document
print("Scrubbed Document:")
print(pii_data.scrub_data(EXAMPLE_DOCUMENT))
"""
# Fake Document with PII for Testing PII Scrubbing Model
## Personal Story
John Doe was born on <date_of_birth_0>. His social security number is <social_security_number_1>. He has been using the email address <email_address_2> for years, and he can always be reached at <phone_number_3>.
## Residence
John currently resides at <address_4>. He's been living there for about 5 years now.
## Career
At the moment, John is employed at <employment_5>. He started his role as a <job_title_6> in <employment_start_date_7> and has been with the company since then.
"""

View File

@@ -1,42 +0,0 @@
from fastapi import FastAPI
from instructor import ResponseSchema
import instructor.dsl as dsl
from pydantic import BaseModel, Field
app = FastAPI(title="Example Application using instructor")
class SearchRequest(BaseModel):
body: str
class SearchQuery(ResponseSchema):
title: str = Field(..., description="Question that the query answers")
query: str = Field(
...,
description="Detailed, comprehensive, and specific query to be used for semantic search",
)
SearchResponse = dsl.MultiTask(
subtask_class=SearchQuery,
description="Correctly segmented set of search queries",
)
@app.post("/search", response_model=SearchResponse)
async def search(request: SearchRequest):
task = (
dsl.ChatCompletion(name="Segmenting Search requests example")
| dsl.SystemTask(task="Segment search results")
| dsl.TaggedMessage(content=request.body, tag="query")
| dsl.TipsMessage(
tips=[
"Expand query to contain multiple forms of the same word (SSO -> Single Sign On)",
"Use the title to explain what the query should return, but use the query to complete the search",
"The query should be detailed, specific, and cast a wide net when possible",
]
)
| SearchRequest
)
return await task.acreate()

View File

@@ -1,49 +0,0 @@
from instructor import ResponseSchema, dsl
from pydantic import Field
import json
class SearchQuery(ResponseSchema):
query: str = Field(
...,
description="Detailed, comprehensive, and specific query to be used for semantic search",
)
SearchResponse = dsl.MultiTask(
subtask_class=SearchQuery,
description="Correctly segmented set of search queries",
)
task = (
dsl.ChatCompletion(name="Segmenting Search requests example")
| dsl.SystemTask(task="Segment search results")
| dsl.TaggedMessage(
content="can you send me the data about the video investment and the one about spot the dog?",
tag="query",
)
| dsl.TipsMessage(
tips=[
"Expand query to contain multiple forms of the same word (SSO -> Single Sign On)",
"Use the title to explain what the query should return, but use the query to complete the search",
"The query should be detailed, specific, and cast a wide net when possible",
]
)
| SearchResponse
)
print(json.dumps(task.kwargs, indent=1))
"""
{
"tasks": [
{
"query": "data about video investment"
},
{
"query": "data about spot the dog"
}
]
}
"""

View File

@@ -1,24 +0,0 @@
from __future__ import annotations
from openai import OpenAI
import instructor
client = instructor.from_openai(OpenAI())
def fizzbuzz_gpt(n) -> list[int | str]:
return client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=list[int | str],
messages=[
{
"role": "user",
"content": f"Return the first {n} numbers in fizzbuzz",
},
],
) # type: ignore
if __name__ == "__main__":
print(fizzbuzz_gpt(n=15))
# > [1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz', 11, 'Fizz', 13, 14, 'FizzBuzz']

View File

@@ -1,83 +0,0 @@
--- readme.md
+++ readme.md
@@ -1,9 +1,9 @@
# FastAPI App
-This is a FastAPI app that provides some basic math functions.
+This is a Flask app that provides some basic math functions.
## Usage
To use this app, follow the instructions below:
1. Install the required dependencies by running `pip install -r requirements.txt`.
-2. Start the app by running `uvicorn main:app --reload`.
+2. Start the app by running `flask run`.
3. Open your browser and navigate to `http://localhost:5000/docs` to access the Swagger UI documentation.
## Example
To perform a basic math operation, you can use the following curl command:
```bash
-curl -X POST -H "Content-Type: application/json" -d '{"operation": "add", "operands": [2, 3]}' http://localhost:8000/calculate
+curl -X POST -H "Content-Type: application/json" -d '{"operation": "add", "operands": [2, 3]}' http://localhost:5000/calculate
```
--- main.py
+++ main.py
@@ -1,29 +1,29 @@
-from fastapi import FastAPI
-from pydantic import BaseModel
+from flask import Flask, request, jsonify
-app = FastAPI()
+app = Flask(__name__)
-class Operation(BaseModel):
- operation: str
- operands: list
+@app.route('/calculate', methods=['POST'])
+def calculate():
+ data = request.get_json()
+ operation = data.get('operation')
+ operands = data.get('operands')
-@app.post('/calculate')
-async def calculate(operation: Operation):
- if operation.operation == 'add':
- result = sum(operation.operands)
- elif operation.operation == 'subtract':
- result = operation.operands[0] - sum(operation.operands[1:])
- elif operation.operation == 'multiply':
+ if operation == 'add':
+ result = sum(operands)
+ elif operation == 'subtract':
+ result = operands[0] - sum(operands[1:])
+ elif operation == 'multiply':
result = 1
- for operand in operation.operands:
+ for operand in operands:
result *= operand
- elif operation.operation == 'divide':
- result = operation.operands[0]
- for operand in operation.operands[1:]:
+ elif operation == 'divide':
+ result = operands[0]
+ for operand in operands[1:]:
result /= operand
else:
result = None
- return {'result': result}
+ return jsonify({'result': result})
--- requirements.txt
+++ requirements.txt
@@ -1,3 +1,2 @@
-fastapi
-uvicorn
-pydantic
+flask
+flask-cors

View File

@@ -1,139 +0,0 @@
import instructor
from openai import OpenAI
from pydantic import Field
from instructor import ResponseSchema
client = instructor.from_openai(OpenAI())
class File(ResponseSchema):
"""
Correctly named file with contents.
"""
file_name: str = Field(
..., description="The name of the file including the extension"
)
body: str = Field(..., description="Correct contents of a file")
def save(self):
with open(self.file_name, "w") as f:
f.write(self.body)
class Program(ResponseSchema):
"""
Set of files that represent a complete and correct program
"""
files: list[File] = Field(..., description="List of files")
def develop(data: str) -> Program:
completion = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0.1,
functions=[Program.openai_schema],
function_call={"name": Program.openai_schema["name"]},
messages=[
{
"role": "system",
"content": "You are a world class programming AI capable of writing correct python scripts and modules. You will name files correct, include __init__.py files and write correct python code. with correct imports.",
},
{
"role": "user",
"content": data,
},
],
max_tokens=1000,
)
return Program.from_response(completion)
if __name__ == "__main__":
program = develop(
"""
Create a fastapi app with a readme.md file and a main.py file with
some basic math functions. the datamodels should use pydantic and
the main.py should use fastapi. the readme.md should have a title
and a description. The readme should contain some helpful infromation
and a curl example"""
)
for file in program.files:
print(file.file_name)
print("-")
print(file.body)
print("\n\n\n")
"""
readme.md
-
# FastAPI App
This is a FastAPI app that provides some basic math functions.
## Usage
To use this app, follow the instructions below:
1. Install the required dependencies by running `pip install -r requirements.txt`.
2. Start the app by running `uvicorn main:app --reload`.
3. Open your browser and navigate to `http://localhost:8000/docs` to access the Swagger UI documentation.
## Example
To perform a basic math operation, you can use the following curl command:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"operation": "add", "operands": [2, 3]}' http://localhost:8000/calculate
```
main.py
-
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Operation(BaseModel):
operation: str
operands: list
@app.post('/calculate')
async def calculate(operation: Operation):
if operation.operation == 'add':
result = sum(operation.operands)
elif operation.operation == 'subtract':
result = operation.operands[0] - sum(operation.operands[1:])
elif operation.operation == 'multiply':
result = 1
for operand in operation.operands:
result *= operand
elif operation.operation == 'divide':
result = operation.operands[0]
for operand in operation.operands[1:]:
result /= operand
else:
result = None
return {'result': result}
requirements.txt
-
fastapi
uvicorn
pydantic
"""
with open("program.json", "w") as f:
f.write(Program.parse_obj(program).json())

View File

@@ -1 +0,0 @@
{"files": [{"file_name": "readme.md", "body": "# FastAPI App\n\nThis is a FastAPI app that provides some basic math functions.\n\n## Usage\n\nTo use this app, follow the instructions below:\n\n1. Install the required dependencies by running `pip install -r requirements.txt`.\n2. Start the app by running `uvicorn main:app --reload`.\n3. Open your browser and navigate to `http://localhost:8000/docs` to access the Swagger UI documentation.\n\n## Example\n\nTo perform a basic math operation, you can use the following curl command:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" -d '{\"operation\": \"add\", \"operands\": [2, 3]}' http://localhost:8000/calculate\n```\n"}, {"file_name": "main.py", "body": "from fastapi import FastAPI\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\n\nclass Operation(BaseModel):\n operation: str\n operands: list\n\n\n@app.post('/calculate')\nasync def calculate(operation: Operation):\n if operation.operation == 'add':\n result = sum(operation.operands)\n elif operation.operation == 'subtract':\n result = operation.operands[0] - sum(operation.operands[1:])\n elif operation.operation == 'multiply':\n result = 1\n for operand in operation.operands:\n result *= operand\n elif operation.operation == 'divide':\n result = operation.operands[0]\n for operand in operation.operands[1:]:\n result /= operand\n else:\n result = None\n return {'result': result}\n"}, {"file_name": "requirements.txt", "body": "fastapi\nuvicorn\npydantic"}]}

View File

@@ -1,191 +0,0 @@
import instructor
from openai import OpenAI
from pydantic import Field, parse_file_as
from instructor import ResponseSchema
from generate import Program
client = instructor.from_openai(OpenAI())
class Diff(ResponseSchema):
"""
Changes that must be correctly made in a program's code repository defined as a
complete diff (Unified Format) file which will be used to `patch` the repository.
Example:
--- /path/to/original timestamp
+++ /path/to/new timestamp
@@ -1,3 +1,9 @@
+This is an important
+notice! It should
+therefore be located at
+the beginning of this
+document!
+
This part of the
document has stayed the
same from version to
@@ -8,13 +14,8 @@
compress the size of the
changes.
-This paragraph contains
-text that is outdated.
-It will be deleted in the
-near future.
-
It is important to spell
-check this dokument. On
+check this document. On
the other hand, a
misspelled word isn't
the end of the world.
@@ -22,3 +23,7 @@
this paragraph needs to
be changed. Things can
be added after it.
+
+This paragraph contains
+important new additions
+to this document.
"""
diff: str = Field(
...,
description=(
"Changes in a code repository correctly represented in 'diff' format, "
"correctly escaped so it could be used in a JSON"
),
)
def refactor(new_requirements: str, program: Program) -> Diff:
program_description = "\n".join(
[f"{code.file_name}\n[[[\n{code.body}\n]]]\n" for code in program.files]
)
completion = client.chat.completions.create(
model="gpt-4",
temperature=0,
functions=[Diff.openai_schema],
function_call={"name": Diff.openai_schema["name"]},
messages=[
{
"role": "system",
"content": "You are a world class programming AI capable of refactor "
"existing python repositories. You will name files correct, include "
"__init__.py files and write correct python code, with correct imports. "
"You'll deliver your changes in valid 'diff' format so that they could "
"be applied using the 'patch' command. "
"Make sure you put the correct line numbers, "
"and that all lines that must be changed are correctly marked.",
},
{
"role": "user",
"content": new_requirements,
},
{
"role": "user",
"content": program_description,
},
],
max_tokens=1000,
)
return Diff.from_response(completion)
if __name__ == "__main__":
program = parse_file_as(path="program.json", type_=Program)
changes = refactor(
new_requirements="Refactor this code to use flask instead.",
program=program,
)
print(changes.diff)
"""
--- readme.md
+++ readme.md
@@ -1,9 +1,9 @@
# FastAPI App
-This is a FastAPI app that provides some basic math functions.
+This is a Flask app that provides some basic math functions.
## Usage
To use this app, follow the instructions below:
1. Install the required dependencies by running `pip install -r requirements.txt`.
-2. Start the app by running `uvicorn main:app --reload`.
+2. Start the app by running `flask run`.
3. Open your browser and navigate to `http://localhost:5000/docs` to access the Swagger UI documentation.
## Example
To perform a basic math operation, you can use the following curl command:
```bash
-curl -X POST -H "Content-Type: application/json" -d '{"operation": "add", "operands": [2, 3]}' http://localhost:8000/calculate
+curl -X POST -H "Content-Type: application/json" -d '{"operation": "add", "operands": [2, 3]}' http://localhost:5000/calculate
```
--- main.py
+++ main.py
@@ -1,29 +1,29 @@
-from fastapi import FastAPI
-from pydantic import BaseModel
+from flask import Flask, request, jsonify
-app = FastAPI()
+app = Flask(__name__)
-class Operation(BaseModel):
- operation: str
- operands: list
+@app.route('/calculate', methods=['POST'])
+def calculate():
+ data = request.get_json()
+ operation = data.get('operation')
+ operands = data.get('operands')
-@app.post('/calculate')
-async def calculate(operation: Operation):
- if operation.operation == 'add':
- result = sum(operation.operands)
- elif operation.operation == 'subtract':
- result = operation.operands[0] - sum(operation.operands[1:])
- elif operation.operation == 'multiply':
+ if operation == 'add':
+ result = sum(operands)
+ elif operation == 'subtract':
+ result = operands[0] - sum(operands[1:])
+ elif operation == 'multiply':
result = 1
- for operand in operation.operands:
+ for operand in operands:
result *= operand
- elif operation.operation == 'divide':
- result = operation.operands[0]
- for operand in operation.operands[1:]:
+ elif operation == 'divide':
+ result = operands[0]
+ for operand in operands[1:]:
result /= operand
else:
result = None
- return {'result': result}
+ return jsonify({'result': result})
--- requirements.txt
+++ requirements.txt
@@ -1,3 +1,2 @@
-fastapi
-uvicorn
-pydantic
+flask
+flask-cors
"""
with open("changes.diff", "w") as f:
f.write(changes.diff)

View File

@@ -1,44 +0,0 @@
import os
from pydantic import BaseModel, Field
from groq import Groq
import instructor
class Character(BaseModel):
name: str
fact: list[str] = Field(..., description="A list of facts about the subject")
client = Groq(
api_key=os.environ.get("GROQ_API_KEY"),
)
client = instructor.from_groq(client, mode=instructor.Mode.TOOLS)
resp = client.chat.completions.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": [
"An American electric vehicle and clean energy company.",
"Co-founded by Elon Musk, JB Straubel, Martin Eberhard, Marc Tarpenning, and Ian Wright in 2003.",
"Headquartered in Austin, Texas.",
"Produces electric vehicles, energy storage solutions, and more recently, solar energy products.",
"Known for its premium electric vehicles, such as the Model S, Model 3, Model X, and Model Y.",
"One of the world's most valuable car manufacturers by market capitalization.",
"Tesla's CEO, Elon Musk, is also the CEO of SpaceX, Neuralink, and The Boring Company.",
"Tesla operates the world's largest global network of electric vehicle supercharging stations.",
"The company aims to accelerate the world's transition to sustainable transport and energy through innovative technologies and products."
]
}
"""

View File

@@ -1,36 +0,0 @@
import os
from pydantic import BaseModel
from groq import Groq
import instructor
client = Groq(
api_key=os.environ.get("GROQ_API_KEY"),
)
client = instructor.from_groq(client, mode=instructor.Mode.TOOLS)
class UserExtract(BaseModel):
name: str
age: int
user: UserExtract = client.chat.completions.create(
model="mixtral-8x7b-32768",
response_model=UserExtract,
messages=[
{"role": "user", "content": "Extract jason is 25 years old"},
],
)
assert isinstance(user, UserExtract), "Should be instance of UserExtract"
assert user.name.lower() == "jason"
assert user.age == 25
print(user.model_dump_json(indent=2))
"""
{
"name": "jason",
"age": 25
}
"""

View File

@@ -1,61 +0,0 @@
# Instructor Hooks Example
This example demonstrates how to use the Hooks system in the Instructor library to monitor, log, and debug your LLM interactions.
## What are Hooks?
Hooks provide a powerful mechanism for intercepting and handling events during the completion and parsing process. They allow you to add custom behavior, logging, or error handling at various stages of the API interaction.
The Instructor library supports several predefined hooks:
- `completion:kwargs`: Emitted when completion arguments are provided
- `completion:response`: Emitted when a completion response is received
- `completion:error`: Emitted when an error occurs during completion
- `completion:last_attempt`: Emitted when the last retry attempt is made
- `parse:error`: Emitted when an error occurs during response parsing
## What This Example Shows
This example demonstrates:
1. **Basic Hook Registration**: How to register handlers for different hook events
2. **Multiple Handlers**: How to register multiple handlers for the same event
3. **Statistics Collection**: How to collect and track API usage statistics
4. **Error Handling**: How to catch and process different types of errors
5. **Hook Cleanup**: How to remove hooks when they're no longer needed
## Usage Examples
The code demonstrates three scenarios:
1. **Successful Extraction**: A basic example that works correctly
2. **Parse Error**: An example that triggers a validation error
3. **Multiple Hooks**: Shows how to attach multiple handlers to the same event
## How to Run the Example
```bash
# Navigate to the hooks example directory
cd examples/hooks
# Run the example
python run.py
```
## Expected Output
The example will print detailed information about each request, including:
- 🔍 Request details (model, prompt)
- 📏 Approximate input token count
- 📊 Token usage statistics
- ✅ Successful responses
- ⚠️ Parse errors
- ❌ Completion errors
- 🔄 Retry attempt notifications
At the end, it will print a summary of the statistics collected.
## Learn More
For more information about hooks in Instructor, see the [hooks documentation](https://instructor-ai.github.io/instructor/concepts/hooks/).

View File

@@ -1,204 +0,0 @@
"""
This example demonstrates how to use hooks in Instructor for monitoring,
logging, and debugging your LLM interactions.
Hooks allow you to attach handlers to events that occur during the completion
and parsing process. This can be useful for:
- Logging API requests and responses
- Debugging parsing errors
- Collecting statistics about API usage
- Adding custom error handling
"""
import instructor
import openai
import pydantic
class User(pydantic.BaseModel):
"""A simple user model with validation."""
name: str
age: int
@pydantic.field_validator("age")
def validate_age(cls, v: int) -> int:
if v < 0:
raise ValueError("Age must be non-negative")
return v
class CompletionStats:
"""A simple class to collect statistics about completions."""
def __init__(self):
self.total_completions = 0
self.errors = 0
self.successful = 0
self.tokens_used = 0
def report(self):
"""Print a report of the statistics."""
print("\n--- Completion Statistics ---")
print(f"Total completions: {self.total_completions}")
print(f"Successful: {self.successful}")
print(f"Errors: {self.errors}")
print(f"Total tokens used: {self.tokens_used}")
def main():
# Initialize the OpenAI client with Instructor
client = instructor.from_openai(openai.OpenAI())
# Create a statistics collector
stats = CompletionStats()
# Define hook handlers
def log_completion_kwargs(_, **kwargs):
"""Handler for completion:kwargs hook."""
stats.total_completions += 1
print(
f"\n🔍 Sending completion request using model: {kwargs.get('model', 'unknown')}"
)
if "messages" in kwargs:
for msg in kwargs["messages"]:
if msg.get("role") == "user":
print(f"📝 User prompt: {msg.get('content')}")
def log_completion_response(response):
"""Handler for completion:response hook."""
stats.successful += 1
# Extract token usage if available
if hasattr(response, "usage") and response.usage:
token_usage = response.usage.total_tokens
stats.tokens_used += token_usage
print(f"📊 Token usage: {token_usage}")
print(f"✅ Received completion response")
def log_completion_error(error):
"""Handler for completion:error hook."""
stats.errors += 1
print(f"❌ Completion error: {type(error).__name__}: {str(error)}")
def log_parse_error(error):
"""Handler for parse:error hook."""
stats.errors += 1
print(f"⚠️ Parse error: {type(error).__name__}: {str(error)}")
# Register the hooks
client.on("completion:kwargs", log_completion_kwargs)
client.on("completion:response", log_completion_response)
client.on("completion:error", log_completion_error)
client.on(
"completion:last_attempt", lambda _: print(f"🔄 Last retry attempt failed")
)
client.on("parse:error", log_parse_error)
# Example 1: Successful extraction
try:
print("\n--- Example 1: Successful Extraction ---")
user = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Extract: John is 30 years old."}],
response_model=User,
)
print(f"Result: {user}")
except Exception as e:
print(f"Main exception: {e}")
# Example 2: Parse error (validation fails)
try:
print("\n--- Example 2: Parse Error (Age Validation) ---")
user = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Extract: Alice is -5 years old."}],
response_model=User,
)
print(f"Result: {user}")
except Exception as e:
print(f"Main exception: {e}")
# Example 3: Multiple hooks for the same event
print("\n--- Example 3: Multiple Hooks ---")
# Add another hook for completion:kwargs that counts message tokens
def count_input_tokens(_, **kwargs):
"""Handler for counting approximate tokens in input messages."""
if "messages" in kwargs:
total_chars = sum(len(msg.get("content", "")) for msg in kwargs["messages"])
# Rough approximation of tokens (not accurate)
approx_tokens = total_chars / 4
print(f"📏 Approximate input tokens: {approx_tokens:.0f}")
# Register the additional hook
client.on("completion:kwargs", count_input_tokens)
try:
user = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Extract: Bob is 25 years old."}],
response_model=User,
)
print(f"Result: {user}")
except Exception as e:
print(f"Main exception: {e}")
# Print the final statistics
stats.report()
# Clean up hooks
print("\n--- Cleaning Up Hooks ---")
client.clear()
print("All hooks cleared")
if __name__ == "__main__":
main()
"""
--- Example 1: Successful Extraction ---
🔍 Sending completion request using model: gpt-3.5-turbo
📝 User prompt: Extract: John is 30 years old.
📊 Token usage: 82
✅ Received completion response
Result: name='John' age=30
--- Example 2: Parse Error (Age Validation) ---
🔍 Sending completion request using model: gpt-3.5-turbo
📝 User prompt: Extract: Alice is -5 years old.
📊 Token usage: 82
✅ Received completion response
⚠️ Parse error: ValidationError: 1 validation error for User
age
Value error, Age must be non-negative [type=value_error, input_value=-5, input_type=int]
For further information visit https://errors.pydantic.dev/2.9/v/value_error
🔍 Sending completion request using model: gpt-3.5-turbo
📝 User prompt: Extract: Alice is -5 years old.
📊 Token usage: 170
✅ Received completion response
Result: name='Alice' age=5
--- Example 3: Multiple Hooks ---
🔍 Sending completion request using model: gpt-3.5-turbo
📝 User prompt: Extract: Bob is 25 years old.
📏 Approximate input tokens: 7
📊 Token usage: 82
✅ Received completion response
Result: name='Bob' age=25
--- Completion Statistics ---
Total completions: 4
Successful: 4
Errors: 1
Total tokens used: 416
--- Cleaning Up Hooks ---
All hooks cleared
"""

View File

@@ -1,56 +0,0 @@
import time
from collections.abc import Iterable
from openai import OpenAI
from pydantic import BaseModel
import instructor
client = instructor.from_openai(OpenAI())
class User(BaseModel):
name: str
job: str
age: int
def stream_extract(input: str) -> Iterable[User]:
return client.chat.completions.create_iterable(
model="gpt-4o",
temperature=0.1,
stream=True,
response_model=User,
messages=[
{
"role": "system",
"content": "You are a perfect entity extraction system",
},
{
"role": "user",
"content": (
f"Consider the data below:\n{input}"
"Correctly segment it into entitites"
"Make sure the JSON is correct"
),
},
],
max_tokens=1000,
)
start = time.time()
for user in stream_extract(
input="Create 5 characters from the book Three Body Problem"
):
delay = round(time.time() - start, 1)
print(f"{delay} s: User({user})")
"""
0.8 s: User(name='Ye Wenjie' job='Astrophysicist' age=60)
1.1 s: User(name='Wang Miao' job='Nanomaterials Researcher' age=40)
1.7 s: User(name='Shi Qiang' job='Detective' age=50)
1.9 s: User(name='Ding Yi' job='Theoretical Physicist' age=45)
1.9 s: User(name='Chang Weisi' job='Military Strategist' age=55)
"""
# Notice that the first one would return at 5s bu the last one returned in 10s!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

View File

@@ -1,58 +0,0 @@
import instructor
from graphviz import Digraph
from pydantic import BaseModel, Field
from openai import OpenAI
client = instructor.from_openai(OpenAI())
class Node(BaseModel):
id: int
label: str
color: str
class Edge(BaseModel):
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)
def generate_graph(input) -> KnowledgeGraph:
return client.chat.completions.create(
model="gpt-3.5-turbo-16k",
messages=[
{
"role": "user",
"content": f"Help me understand following by describing as a detailed knowledge graph: {input}",
}
],
response_model=KnowledgeGraph,
) # type: ignore
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: KnowledgeGraph = generate_graph("Teach me about quantum mechanics")
visualize_knowledge_graph(graph)

View File

@@ -1,103 +0,0 @@
from openai import OpenAI
import instructor
from graphviz import Digraph
from typing import Optional
from pydantic import BaseModel, Field
client = instructor.from_openai(OpenAI())
class Node(BaseModel):
id: int
label: str
color: str
def __hash__(self) -> int:
return hash((id, self.label))
class Edge(BaseModel):
source: int
target: int
label: str
color: str = "black"
def __hash__(self) -> int:
return hash((self.source, self.target, self.label))
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")
# Add nodes
for node in self.nodes:
dot.node(str(node.id), node.label, color=node.color)
# Add edges
for edge in self.edges:
dot.edge(
str(edge.source), str(edge.target), label=edge.label, color=edge.color
)
dot.render(prefix, format="png", view=True)
def generate_graph(input: list[str]) -> KnowledgeGraph:
cur_state = KnowledgeGraph()
num_iterations = len(input)
for i, inp in enumerate(input):
new_updates = client.chat.completions.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)}""",
},
],
response_model=KnowledgeGraph,
) # type: ignore
# Update the current state
cur_state = cur_state.update(new_updates)
cur_state.draw(prefix=f"iteration_{i}")
return cur_state
# here we assume that we have to process the text in chunks
# one at a time since they may not fit in the prompt otherwise
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")

View File

@@ -1,126 +0,0 @@
import time
import asyncio
import instructor
from pydantic import BaseModel
from openai import AsyncOpenAI
client = instructor.apatch(AsyncOpenAI())
class Timer:
def __init__(self, name):
self.name = name
self.start = None
self.end = None
async def __aenter__(self):
self.start = time.time()
async def __aexit__(self, *args, **kwargs):
self.end = time.time()
print(f"{self.name} took {(self.end - self.start):.2f} seconds")
class Person(BaseModel):
name: str
age: int
async def extract_person(text: str) -> Person:
return await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": text},
],
response_model=Person,
)
async def main():
"""We'll use this to run the example. and time how long each one takes!
0. for loop
1. asyncio.gather
2. asyncio.as_completed
"""
dataset = [
"My name is John and I am 20 years old",
"My name is Mary and I am 21 years old",
"My name is Bob and I am 22 years old",
"My name is Alice and I am 23 years old",
"My name is Jane and I am 24 years old",
"My name is Joe and I am 25 years old",
"My name is Jill and I am 26 years old",
]
"""
This is the simplest way to run multiple async functions in series.
It will wait for each function to complete before continuing.
"""
async with Timer("for loop"):
persons = []
for text in dataset:
person = await extract_person(text)
persons.append(person)
print("for loop:", persons)
"""
This is the simplest way to run multiple async functions in parallel.
It will wait for all of the functions to complete before continuing.
"""
async with Timer("asyncio.gather"):
tasks_get_persons = [extract_person(text) for text in dataset]
all_person = await asyncio.gather(*tasks_get_persons)
print("asyncio.gather:", all_person)
"""
This is a bit more complicated, but it allows us to process each
person as soon as they are ready. This is useful if you have a
large dataset and want to start processing the results as soon
as they are ready.
"""
async with Timer("asyncio.as_completed"):
all_persons = []
tasks_get_persons = [extract_person(text) for text in dataset]
for person in asyncio.as_completed(tasks_get_persons):
all_persons.append(await person)
print("asyncio.as_copmleted:", all_persons)
"""
If we want to rate limit our requests, we can use the
semaphore to limit the number of concurrent requests.
"""
# Create a semaphore that will only allow 2 concurrent requests
sem = asyncio.Semaphore(2)
async def rate_limited_extract_person(text: str) -> Person:
async with sem:
return await extract_person(text)
async with Timer("asyncio.gather (rate limited)"):
tasks_get_persons = [rate_limited_extract_person(text) for text in dataset]
resp = await asyncio.gather(*tasks_get_persons)
print("asyncio.gather (rate limited):", resp)
async with Timer("asyncio.as_completed (rate limited)"):
all_persons = []
tasks_get_persons = [rate_limited_extract_person(text) for text in dataset]
for person in asyncio.as_completed(tasks_get_persons):
all_persons.append(await person)
print("asyncio.as_completed (rate limited):", all_persons)
if __name__ == "__main__":
asyncio.run(main())
"""
for loop took 6.17 seconds
asyncio.gather took 1.11 seconds
asyncio.as_completed took 0.87 seconds
asyncio.gather (rate limited) took 3.04 seconds
asyncio.as_completed (rate limited) took 3.26 seconds
"""

View File

@@ -1,96 +0,0 @@
import instructor
import openai
from pydantic import BaseModel, Field
client = instructor.from_openai(openai.OpenAI())
class Judgment(BaseModel):
thought: str = Field(
description="The step-by-step reasoning process used to analyze the question and text"
)
justification: str = Field(
description="Explanation for the similarity judgment, detailing key factors that led to the conclusion"
)
similarity: bool = Field(
description="Boolean judgment indicating whether the question and text are similar or relevant (True) or not (False)"
)
prompt = """
You are tasked with comparing a question and a piece of text to determine if they are relevant to each other or similar in some way. Your goal is to analyze the content, context, and potential connections between the two.
To determine if the question and text are relevant or similar, please follow these steps:
1. Carefully read and understand both the question and the text.
2. Identify the main topic, keywords, and concepts in the question.
3. Analyze the text for any mention of these topics, keywords, or concepts.
4. Consider any potential indirect connections or implications that might link the question and text.
5. Evaluate the overall context and purpose of both the question and the text.
As you go through this process, please use a chain of thought approach. Write out your reasoning for each step inside <thought> tags.
After your analysis, provide a boolean judgment on whether the question and text are similar or relevant to each other. Use "true" if they are similar or relevant, and "false" if they are not.
Before giving your final judgment, provide a justification for your decision. Explain the key factors that led to your conclusion.
"""
def judge_relevance(question: str, text: str) -> Judgment:
return client.chat.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": prompt},
{
"role": "user",
"content": """
Here is the question:
<question>
{{question}}
</question>
Here is the text:
<text>
{{text}}
</text>
""",
},
],
response_model=Judgment,
context={"question": question, "text": text},
)
if __name__ == "__main__":
test_pairs = [
{
"question": "What are the main causes of climate change?",
"text": "Global warming is primarily caused by human activities, such as burning fossil fuels, deforestation, and industrial processes. These activities release greenhouse gases into the atmosphere, trapping heat and leading to a rise in global temperatures.",
"is_similar": True,
},
{
"question": "How does photosynthesis work?",
"text": "Photosynthesis is the process by which plants use sunlight, water, and carbon dioxide to produce oxygen and energy in the form of sugar. It occurs in the chloroplasts of plant cells and is essential for life on Earth.",
"is_similar": True,
},
{
"question": "What are the benefits of regular exercise?",
"text": "The Eiffel Tower, located in Paris, France, was completed in 1889. It stands 324 meters tall and was originally built as the entrance arch for the 1889 World's Fair.",
"is_similar": False,
},
{
"question": "How do vaccines work?",
"text": "The process of baking bread involves mixing flour, water, yeast, and salt to form a dough. The dough is then kneaded, left to rise, shaped, and finally baked in an oven.",
"is_similar": False,
},
]
score = 0
for pair in test_pairs:
result = judge_relevance(pair["question"], pair["text"])
if result.similarity == pair["is_similar"]:
score += 1
print(f"Score: {score}/{len(test_pairs)}")

View File

@@ -1,11 +0,0 @@
# Instructions
1. Create a virtual environment and install all of the packages inside `requirements.txt`
2. Run the server using
```
uvicorn server:app --reload
```
3. Open up the documentation at `http://127.0.0.1:8000/docs` to start experimenting with fastapi! You can print out the streaming example using `test.py`.

View File

@@ -1,7 +0,0 @@
pydantic==2.7.1
openai==1.24.1
instructor==1.0.3
logfire==0.28.0
fastapi==0.110.3
uvicorn[standard]
logfire[fastapi]

View File

@@ -1,84 +0,0 @@
from pydantic import BaseModel
from fastapi import FastAPI
from openai import AsyncOpenAI
import instructor
import logfire
import asyncio
from collections.abc import Iterable
from fastapi.responses import StreamingResponse
class UserData(BaseModel):
query: str
class MultipleUserData(BaseModel):
queries: list[str]
class UserDetail(BaseModel):
name: str
age: int
app = FastAPI()
openai_client = AsyncOpenAI()
logfire.configure(pydantic_plugin=logfire.PydanticPlugin(record="all"))
logfire.instrument_fastapi(app)
logfire.instrument_openai(openai_client)
client = instructor.from_openai(openai_client)
@app.post("/user", response_model=UserDetail)
async def endpoint_function(data: UserData) -> UserDetail:
user_detail = await client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": f"Extract: `{data.query}`"},
],
)
logfire.info("/User returning", value=user_detail)
return user_detail
@app.post("/many-users", response_model=list[UserDetail])
async def extract_many_users(data: MultipleUserData):
async def extract_user(query: str):
user_detail = await client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": f"Extract: `{query}`"},
],
)
logfire.info("/User returning", value=user_detail)
return user_detail
coros = [extract_user(query) for query in data.queries]
return await asyncio.gather(*coros)
@app.post("/extract", response_class=StreamingResponse)
async def extract(data: UserData):
supressed_client = AsyncOpenAI()
logfire.instrument_openai(supressed_client, suppress_other_instrumentation=False)
client = instructor.from_openai(supressed_client)
users = await client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=Iterable[UserDetail],
stream=True,
messages=[
{"role": "user", "content": data.query},
],
)
async def generate():
with logfire.span("Generating User Response Objects"):
async for user in users:
resp_json = user.model_dump_json()
logfire.info("Returning user object", value=resp_json)
yield resp_json
return StreamingResponse(generate(), media_type="text/event-stream")

View File

@@ -1,13 +0,0 @@
import requests
response = requests.post(
"http://127.0.0.1:3000/extract",
json={
"query": "Alice and Bob are best friends. They are currently 32 and 43 respectively. "
},
stream=True,
)
for chunk in response.iter_content(chunk_size=1024):
if chunk:
print(str(chunk, encoding="utf-8"), end="\n")

View File

@@ -1,52 +0,0 @@
import enum
from pydantic import BaseModel
from openai import OpenAI
import instructor
import logfire
class Labels(str, enum.Enum):
"""Enumeration for single-label text classification."""
SPAM = "spam"
NOT_SPAM = "not_spam"
class SinglePrediction(BaseModel):
"""
Class for a single class label prediction.
"""
class_label: Labels
openai_client = OpenAI()
logfire.configure(pydantic_plugin=logfire.PydanticPlugin(record="all"))
logfire.instrument_openai(openai_client)
client = instructor.from_openai(openai_client)
@logfire.instrument("classification", extract_args=True)
def classify(data: str) -> SinglePrediction:
"""Perform single-label classification on the input text."""
return client.chat.completions.create(
model="gpt-4o-mini",
response_model=SinglePrediction,
messages=[
{
"role": "user",
"content": f"Classify the following text: {data}",
},
],
)
if __name__ == "__main__":
emails = [
"Hello there I'm a Nigerian prince and I want to give you money",
"Meeting with Thomas has been set at Friday next week",
"Here are some weekly product updates from our marketing team",
]
for email in emails:
classify(email)

View File

@@ -1,79 +0,0 @@
import instructor
from io import StringIO
from typing import Annotated, Any
from collections.abc import Iterable
from pydantic import (
BeforeValidator,
InstanceOf,
WithJsonSchema,
BaseModel,
)
import pandas as pd
from openai import OpenAI
import logfire
openai_client = OpenAI()
logfire.configure(pydantic_plugin=logfire.PydanticPlugin(record="all"))
logfire.instrument_openai(openai_client)
client = instructor.from_openai(openai_client, mode=instructor.Mode.MD_JSON)
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),
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
@logfire.instrument("extract-table", extract_args=True)
def extract_table_from_image(url: str) -> Iterable[Table]:
return client.chat.completions.create(
model="gpt-4-vision-preview",
response_model=Iterable[Table],
max_tokens=1800,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Extract out a table from the image. Only extract out the total number of skiiers.",
},
{"type": "image_url", "image_url": {"url": url}},
],
}
],
)
url = "https://cdn.statcdn.com/Infographic/images/normal/16330.jpeg"
tables = extract_table_from_image(url)
for table in tables:
print(table.caption, end="\n")
print(table.dataframe.to_markdown())

View File

@@ -1,4 +0,0 @@
pydantic==2.7.1
openai==1.24.1
instructor==1.0.3
logfire==0.28.0

View File

@@ -1,33 +0,0 @@
from typing import Annotated
from pydantic import BaseModel, ValidationError
from pydantic.functional_validators import AfterValidator
from instructor import llm_validator
import logfire
import instructor
from openai import OpenAI
openai_client = OpenAI()
logfire.configure(pydantic_plugin=logfire.PydanticPlugin(record="all"))
logfire.instrument_openai(openai_client)
client = instructor.from_openai(openai_client)
class Statement(BaseModel):
message: Annotated[
str,
AfterValidator(
llm_validator("Don't allow any objectionable content", client=client)
),
]
messages = [
"I think we should always treat violence as the best solution",
"There are some great pastries down the road at this bakery I know",
]
for message in messages:
try:
Statement(message=message)
except ValidationError as e:
print(e)

View File

@@ -1,51 +0,0 @@
import instructor
import openai
import logging
from pydantic import BaseModel
# Set logging to DEBUG
logging.basicConfig(level=logging.DEBUG)
client = instructor.from_openai(openai.OpenAI())
class UserDetail(BaseModel):
name: str
age: int
user = client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=UserDetail,
messages=[
{"role": "user", "content": "Extract Jason is 25 years old"},
],
) # type: ignore
"""
DEBUG:httpx:load_ssl_context verify=True cert=None trust_env=True http2=False
DEBUG:httpx:load_verify_locations cafile='/Users/jasonliu/dev/instructor/.venv/lib/python3.11/site-packages/certifi/cacert.pem'
DEBUG:instructor:Patching `client.chat.completions.create` with mode=<Mode.TOOLS: 'tool_call'>
DEBUG:instructor:max_retries: 1
DEBUG:openai._base_client:Request options: {'method': 'post', 'url': '/chat/completions', 'files': None, 'json_data': {'messages': [{'role': 'user', 'content': 'Extract Jason is 25 years old'}], 'model': 'gpt-3.5-turbo', 'function_call': {'name': 'UserDetail'}, 'functions': [{'name': 'UserDetail', 'description': 'Correctly extracted `UserDetail` with all the required parameters with correct types', 'parameters': {'properties': {'name': {'title': 'Name', 'type': 'string'}, 'age': {'title': 'Age', 'type': 'integer'}}, 'required': ['age', 'name'], 'type': 'object'}}]}}
DEBUG:httpcore.connection:connect_tcp.started host='api.openai.com' port=443 local_address=None timeout=5.0 socket_options=None
DEBUG:httpcore.connection:connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x105062c90>
DEBUG:httpcore.connection:start_tls.started ssl_context=<ssl.SSLContext object at 0x100748680> server_hostname='api.openai.com' timeout=5.0
DEBUG:httpcore.connection:start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x101caa150>
DEBUG:httpcore.http11:send_request_headers.started request=<Request [b'POST']>
DEBUG:httpcore.http11:send_request_headers.complete
DEBUG:httpcore.http11:send_request_body.started request=<Request [b'POST']>
DEBUG:httpcore.http11:send_request_body.complete
DEBUG:httpcore.http11:receive_response_headers.started request=<Request [b'POST']>
DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'OK', [(b'Date', b'Mon, 12 Feb 2024 14:55:45 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'access-control-allow-origin', b'*'), (b'Cache-Control', b'no-cache, must-revalidate'), (b'openai-model', b'gpt-3.5-turbo-0613'), (b'openai-organization', b'scribe-ai'), (b'openai-processing-ms', b'483'), (b'openai-version', b'2020-10-01'), (b'strict-transport-security', b'max-age=15724800; includeSubDomains'), (b'x-ratelimit-limit-requests', b'10000'), (b'x-ratelimit-limit-tokens', b'2000000'), (b'x-ratelimit-remaining-requests', b'9999'), (b'x-ratelimit-remaining-tokens', b'1999975'), (b'x-ratelimit-reset-requests', b'6ms'), (b'x-ratelimit-reset-tokens', b'0s'), (b'x-request-id', b'req_f0fa476897ae165fc50fa90b7968595b'), (b'CF-Cache-Status', b'DYNAMIC'), (b'Set-Cookie', b'__cf_bm=e2_yCrwo4frh6Oq4ZufCEhNJ4lSGJ2.MMtk45X8lrMM-1707749745-1-AfWk8CyACc7aZo6GpCI82FBfI/wmPEFZLNO/Cr3eavTW3xKVFCS7G9jvwYTFLXjJr0cttYsXeLAnjwipw18R0Vo=; path=/; expires=Mon, 12-Feb-24 15:25:45 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), (b'Set-Cookie', b'_cfuvid=PyVVCGSMxTg1p.woYvHVVC9E3n69faOs5FOxaDdjXOM-1707749745711-0-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), (b'Server', b'cloudflare'), (b'CF-RAY', b'8545aca30c1fa22f-YYZ'), (b'Content-Encoding', b'gzip'), (b'alt-svc', b'h3=":443"; ma=86400')])
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
DEBUG:httpcore.http11:receive_response_body.started request=<Request [b'POST']>
DEBUG:httpcore.http11:receive_response_body.complete
DEBUG:httpcore.http11:response_closed.started
DEBUG:httpcore.http11:response_closed.complete
DEBUG:openai._base_client:HTTP Request: POST https://api.openai.com/v1/chat/completions "200 OK"
DEBUG:httpcore.connection:close.started
DEBUG:httpcore.connection:close.complete
"""

View File

@@ -1,99 +0,0 @@
from pydantic import BaseModel
from instructor import patch
from openai import AsyncOpenAI
from langdetect import detect
docs = map(
lambda x: x.strip(),
"""
Լեզվական մոդելները վերջին տարիներին դարձել են ավելի հարուստ և կատարյալ, հնարավորություն ընձեռելով ստեղծել սահուն և բնական տեքստեր, ինչպես նաև գերազանց արդյունքներ ցուցաբերել մեքենայական թարգմանության, հարցերի պատասխանման և ստեղծագործ տեքստերի ստեղծման նման տարբեր առաջադրանքներում։ Այս մոդելները մշակվում են հսկայական տեքստային տվյալների հիման վրա և կարող են բռնել բնական լեզվի կառուցվածքն ու նրբությունները՝ հեղափոխություն առաջացնելով համակարգիչների և մարդկանց միջև հաղորդակցության ոլորտում։
---
Mga modelo ng wika ay naging mas sopistikado sa nagdaang mga taon, na nagbibigay-daan sa pagbuo ng mga natural at madaling basahing teksto, at nagpapakita ng mahusay na pagganap sa iba't ibang gawain tulad ng awtomatikong pagsasalin, pagsagot sa mga tanong, at pagbuo ng malikhain na teksto. Ang mga modelo na ito ay sinanay sa napakalaking mga dataset ng teksto at kayang hulihin ang istruktura at mga nuances ng natural na wika. Ang mga pagpapabuti sa mga modelo ng wika ay maaaring magdulot ng rebolusyon sa komunikasyon sa pagitan ng mga computer at tao, at inaasahan ang higit pang pag-unlad sa hinaharap.
---
Ngaahi motuʻa lea kuo nau hoko ʻo fakaʻofoʻofa ange ʻi he ngaahi taʻu fakamuimui ni, ʻo fakafaingofuaʻi e fakatupu ʻo e ngaahi konga tohi ʻoku lelei mo fakanatula pea ʻoku nau fakahaaʻi ʻa e ngaahi ola lelei ʻi he ngaahi ngāue kehekehe ʻo hangē ko e liliu fakaʻētita, tali fehuʻi, mo e fakatupu ʻo e konga tohi fakaʻatamai. Ko e ako ʻa e ngaahi motuʻa ni ʻi he ngaahi seti ʻo e fakamatala tohi lahi pea ʻoku nau malava ʻo puke ʻa e fakafuofua mo e ngaahi meʻa iiki ʻo e lea fakanatula. ʻE lava ke fakatupu ʻe he ngaahi fakaleleiʻi ki he ngaahi motuʻa lea ha liliu lahi ʻi he fetu'utaki ʻi he vahaʻa ʻo e ngaahi komipiuta mo e kakai, pea ʻoku ʻamanaki ʻe toe fakalakalaka ange ia ʻi he kahaʻu.
---
Dil modelleri son yıllarda daha da gelişti, akıcı ve doğal metinler üretmeyi mümkün kılıyor ve makine çevirisi, soru cevaplama ve yaratıcı metin oluşturma gibi çeşitli görevlerde mükemmel performans gösteriyor. Bu modeller, devasa metin veri setlerinde eğitilir ve doğal dilin yapısını ve nüanslarını yakalayabilir. Dil modellerindeki iyileştirmeler, bilgisayarlar ve insanlar arasındaki iletişimde devrim yaratabilir ve gelecekte daha da ilerleme bekleniyor.
---
Mô hình ngôn ngữ đã trở nên tinh vi hơn trong những năm gần đây, cho phép tạo ra các văn bản trôi chảy và tự nhiên, đồng thời thể hiện hiệu suất xuất sắc trong các nhiệm vụ khác nhau như dịch máy, trả lời câu hỏi và tạo văn bản sáng tạo. Các mô hình này được huấn luyện trên các tập dữ liệu văn bản khổng lồ và có thể nắm bắt cấu trúc và sắc thái của ngôn ngữ tự nhiên. Những cải tiến trong mô hình ngôn ngữ có thể mang lại cuộc cách mạng trong giao tiếp giữa máy tính và con người, và người ta kỳ vọng sẽ có những tiến bộ hơn nữa trong tương lai.
---
Les modèles de langage sont devenus de plus en plus sophistiqués ces dernières années, permettant de générer des textes fluides et naturels, et de performer dans une variété de tâches telles que la traduction automatique, la réponse aux questions et la génération de texte créatif. Entraînés sur d'immenses ensembles de données textuelles, ces modèles sont capables de capturer la structure et les nuances du langage naturel, ouvrant la voie à une révolution dans la communication entre les ordinateurs et les humains.
---
近年来,语言模型变得越来越复杂,能够生成流畅自然的文本,并在机器翻译、问答和创意文本生成等各种任务中表现出色。这些模型在海量文本数据集上训练,可以捕捉自然语言的结构和细微差别。语言模型的改进有望彻底改变计算机和人类之间的交流方式,未来有望实现更大的突破。
---
In den letzten Jahren sind Sprachmodelle immer ausgefeilter geworden und können flüssige, natürlich klingende Texte generieren und in verschiedenen Aufgaben wie maschineller Übersetzung, Beantwortung von Fragen und Generierung kreativer Texte hervorragende Leistungen erbringen. Diese Modelle werden auf riesigen Textdatensätzen trainiert und können die Struktur und Nuancen natürlicher Sprache erfassen, was zu einer Revolution in der Kommunikation zwischen Computern und Menschen führen könnte.
---
पिछले कुछ वर्षों में भाषा मॉडल बहुत अधिक परिष्कृत हो गए हैं, जो प्राकृतिक और प्रवाहमय पाठ उत्पन्न कर सकते हैं, और मशीन अनुवाद, प्रश्नोत्तर, और रचनात्मक पाठ उत्पादन जैसे विभिन्न कार्यों में उत्कृष्ट प्रदर्शन कर सकते हैं। ये मॉडल विशाल पाठ डेटासेट पर प्रशिक्षित होते हैं और प्राकृतिक भाषा की संरचना और बारीकियों को समझ सकते हैं। भाषा मॉडल में सुधार कंप्यूटर और मानव के बीच संवाद में क्रांति ला सकता है, और भविष्य में और प्रगति की उम्मीद है।
---
近年、言語モデルは非常に洗練され、自然で流暢なテキストを生成できるようになり、機械翻訳、質問応答、クリエイティブなテキスト生成など、様々なタスクで優れたパフォーマンスを発揮しています。これらのモデルは膨大なテキストデータセットで学習され、自然言語の構造とニュアンスを捉えることができます。言語モデルの改善により、コンピューターと人間のコミュニケーションに革命が起こる可能性があり、将来のさらなる進歩が期待されています。
""".split("---"),
)
# Patch the OpenAI client to enable response_model
client = patch(AsyncOpenAI())
class GeneratedSummary(BaseModel):
summary: str
async def summarize_text(text: str):
response = await client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=GeneratedSummary,
messages=[
{
"role": "system",
"content": "Generate a concise summary in the language of the article. ",
},
{
"role": "user",
"content": f"Summarize the following text in a concise way:\n{text}",
},
],
) # type: ignore
return response.summary, text
if __name__ == "__main__":
import asyncio
async def main():
results = await asyncio.gather(*[summarize_text(doc) for doc in docs])
for summary, doc in results:
source_lang = detect(doc)
target_lang = detect(summary)
print(
f"Source: {source_lang}, Summary: {target_lang}, Match: {source_lang == target_lang}"
)
asyncio.run(main())
"""
Source: et, Summary: en, Match: False
Source: tl, Summary: tl, Match: True
Source: sw, Summary: en, Match: False
Source: tr, Summary: tr, Match: True
Source: vi, Summary: en, Match: False
Source: fr, Summary: fr, Match: True
Source: zh-cn, Summary: en, Match: False
Source: de, Summary: de, Match: True
Source: hi, Summary: en, Match: False
Source: ja, Summary: en, Match: False
"""

View File

@@ -1,102 +0,0 @@
from pydantic import BaseModel, Field
from instructor import patch
from openai import AsyncOpenAI
from langdetect import detect
docs = map(
lambda x: x.strip(),
"""
Լեզվական մոդելները վերջին տարիներին դարձել են ավելի հարուստ և կատարյալ, հնարավորություն ընձեռելով ստեղծել սահուն և բնական տեքստեր, ինչպես նաև գերազանց արդյունքներ ցուցաբերել մեքենայական թարգմանության, հարցերի պատասխանման և ստեղծագործ տեքստերի ստեղծման նման տարբեր առաջադրանքներում։ Այս մոդելները մշակվում են հսկայական տեքստային տվյալների հիման վրա և կարող են բռնել բնական լեզվի կառուցվածքն ու նրբությունները՝ հեղափոխություն առաջացնելով համակարգիչների և մարդկանց միջև հաղորդակցության ոլորտում։
---
Mga modelo ng wika ay naging mas sopistikado sa nagdaang mga taon, na nagbibigay-daan sa pagbuo ng mga natural at madaling basahing teksto, at nagpapakita ng mahusay na pagganap sa iba't ibang gawain tulad ng awtomatikong pagsasalin, pagsagot sa mga tanong, at pagbuo ng malikhain na teksto. Ang mga modelo na ito ay sinanay sa napakalaking mga dataset ng teksto at kayang hulihin ang istruktura at mga nuances ng natural na wika. Ang mga pagpapabuti sa mga modelo ng wika ay maaaring magdulot ng rebolusyon sa komunikasyon sa pagitan ng mga computer at tao, at inaasahan ang higit pang pag-unlad sa hinaharap.
---
Ngaahi motuʻa lea kuo nau hoko ʻo fakaʻofoʻofa ange ʻi he ngaahi taʻu fakamuimui ni, ʻo fakafaingofuaʻi e fakatupu ʻo e ngaahi konga tohi ʻoku lelei mo fakanatula pea ʻoku nau fakahaaʻi ʻa e ngaahi ola lelei ʻi he ngaahi ngāue kehekehe ʻo hangē ko e liliu fakaʻētita, tali fehuʻi, mo e fakatupu ʻo e konga tohi fakaʻatamai. Ko e ako ʻa e ngaahi motuʻa ni ʻi he ngaahi seti ʻo e fakamatala tohi lahi pea ʻoku nau malava ʻo puke ʻa e fakafuofua mo e ngaahi meʻa iiki ʻo e lea fakanatula. ʻE lava ke fakatupu ʻe he ngaahi fakaleleiʻi ki he ngaahi motuʻa lea ha liliu lahi ʻi he fetu'utaki ʻi he vahaʻa ʻo e ngaahi komipiuta mo e kakai, pea ʻoku ʻamanaki ʻe toe fakalakalaka ange ia ʻi he kahaʻu.
---
Dil modelleri son yıllarda daha da gelişti, akıcı ve doğal metinler üretmeyi mümkün kılıyor ve makine çevirisi, soru cevaplama ve yaratıcı metin oluşturma gibi çeşitli görevlerde mükemmel performans gösteriyor. Bu modeller, devasa metin veri setlerinde eğitilir ve doğal dilin yapısını ve nüanslarını yakalayabilir. Dil modellerindeki iyileştirmeler, bilgisayarlar ve insanlar arasındaki iletişimde devrim yaratabilir ve gelecekte daha da ilerleme bekleniyor.
---
Mô hình ngôn ngữ đã trở nên tinh vi hơn trong những năm gần đây, cho phép tạo ra các văn bản trôi chảy và tự nhiên, đồng thời thể hiện hiệu suất xuất sắc trong các nhiệm vụ khác nhau như dịch máy, trả lời câu hỏi và tạo văn bản sáng tạo. Các mô hình này được huấn luyện trên các tập dữ liệu văn bản khổng lồ và có thể nắm bắt cấu trúc và sắc thái của ngôn ngữ tự nhiên. Những cải tiến trong mô hình ngôn ngữ có thể mang lại cuộc cách mạng trong giao tiếp giữa máy tính và con người, và người ta kỳ vọng sẽ có những tiến bộ hơn nữa trong tương lai.
---
Les modèles de langage sont devenus de plus en plus sophistiqués ces dernières années, permettant de générer des textes fluides et naturels, et de performer dans une variété de tâches telles que la traduction automatique, la réponse aux questions et la génération de texte créatif. Entraînés sur d'immenses ensembles de données textuelles, ces modèles sont capables de capturer la structure et les nuances du langage naturel, ouvrant la voie à une révolution dans la communication entre les ordinateurs et les humains.
---
近年来,语言模型变得越来越复杂,能够生成流畅自然的文本,并在机器翻译、问答和创意文本生成等各种任务中表现出色。这些模型在海量文本数据集上训练,可以捕捉自然语言的结构和细微差别。语言模型的改进有望彻底改变计算机和人类之间的交流方式,未来有望实现更大的突破。
---
In den letzten Jahren sind Sprachmodelle immer ausgefeilter geworden und können flüssige, natürlich klingende Texte generieren und in verschiedenen Aufgaben wie maschineller Übersetzung, Beantwortung von Fragen und Generierung kreativer Texte hervorragende Leistungen erbringen. Diese Modelle werden auf riesigen Textdatensätzen trainiert und können die Struktur und Nuancen natürlicher Sprache erfassen, was zu einer Revolution in der Kommunikation zwischen Computern und Menschen führen könnte.
---
पिछले कुछ वर्षों में भाषा मॉडल बहुत अधिक परिष्कृत हो गए हैं, जो प्राकृतिक और प्रवाहमय पाठ उत्पन्न कर सकते हैं, और मशीन अनुवाद, प्रश्नोत्तर, और रचनात्मक पाठ उत्पादन जैसे विभिन्न कार्यों में उत्कृष्ट प्रदर्शन कर सकते हैं। ये मॉडल विशाल पाठ डेटासेट पर प्रशिक्षित होते हैं और प्राकृतिक भाषा की संरचना और बारीकियों को समझ सकते हैं। भाषा मॉडल में सुधार कंप्यूटर और मानव के बीच संवाद में क्रांति ला सकता है, और भविष्य में और प्रगति की उम्मीद है।
---
近年、言語モデルは非常に洗練され、自然で流暢なテキストを生成できるようになり、機械翻訳、質問応答、クリエイティブなテキスト生成など、様々なタスクで優れたパフォーマンスを発揮しています。これらのモデルは膨大なテキストデータセットで学習され、自然言語の構造とニュアンスを捉えることができます。言語モデルの改善により、コンピューターと人間のコミュニケーションに革命が起こる可能性があり、将来のさらなる進歩が期待されています。
""".split("---"),
)
# Patch the OpenAI client to enable response_model
client = patch(AsyncOpenAI())
class GeneratedSummary(BaseModel):
detected_language: str = Field(
description="The language code of the original article. The summary must be generated in this same language.",
)
summary: str
async def summarize_text(text: str):
response = await client.chat.completions.create(
model="gpt-3.5-turbo",
response_model=GeneratedSummary,
messages=[
{
"role": "system",
"content": "Generate a concise summary in the language of the article. ",
},
{
"role": "user",
"content": f"Summarize the following text in a concise way:\n{text}",
},
],
) # type: ignore
return response.detected_language, response.summary, text
if __name__ == "__main__":
import asyncio
async def main():
results = await asyncio.gather(*[summarize_text(doc) for doc in docs])
for lang, summary, doc in results:
source_lang = detect(doc)
target_lang = detect(summary)
print(
f"Source: {source_lang}, Summary: {target_lang}, Match: {source_lang == target_lang}, Detected: {lang}"
)
asyncio.run(main())
"""
Source: et, Summary: et, Match: True, Detected: hy
Source: tl, Summary: tl, Match: True, Detected: tl
Source: sw, Summary: sw, Match: True, Detected: to
Source: tr, Summary: tr, Match: True, Detected: tr
Source: vi, Summary: vi, Match: True, Detected: vi
Source: fr, Summary: fr, Match: True, Detected: fr
Source: zh-cn, Summary: zh-cn, Match: True, Detected: zh
Source: de, Summary: de, Match: True, Detected: de
Source: hi, Summary: hi, Match: True, Detected: hi
Source: ja, Summary: ja, Match: True, Detected: ja
"""

View File

@@ -1,28 +0,0 @@
from pydantic import BaseModel
from mistralai.client import MistralClient
from instructor import from_mistral
from instructor.mode import Mode
import os
class UserDetails(BaseModel):
name: str
age: int
# enables `response_model` in chat call
client = MistralClient(api_key=os.environ.get("MISTRAL_API_KEY"))
instructor_client = from_mistral(
client=client,
model="mistral-large-latest",
mode=Mode.TOOLS,
max_tokens=1000,
)
resp = instructor_client.messages.create(
response_model=UserDetails,
messages=[{"role": "user", "content": "Jason is 10"}],
temperature=0,
)
print(resp)

View File

@@ -1,116 +0,0 @@
import instructor
import enum
from typing import Optional
from pydantic import BaseModel, Field
from openai import OpenAI
client = instructor.from_openai(OpenAI())
class Action(enum.Enum):
CREATE = "create_task"
DELETE = "close_task"
UPDATE = "update_task"
class Projects(enum.Enum):
FRONTLINE_QA_AI = "frontline_qa_ai"
FUTURE_OF_PROGRAMMING = "future_of_programming"
PERSONAL_SITE = "personal_site"
NORDIC_HAMSTRING_CURLS = "nordic_hamstring_curls"
class Buckets(enum.Enum):
FINANCE = "finance"
PURVIEW_OPERATIONS = "purview_operations"
TASKBOT = "taskbot"
CHECKBOT = "checkbot"
NIGHT_HACKING = "night_hacking"
TICKLER = "tickler"
class TaskAction(BaseModel):
id: int
method: Action = Field(
description="Method of creating and closing a task: to close a task, only an ID is required"
)
waiting_on: Optional[list[int]] = Field(
None, description="IDs of tasks that this task is waiting on"
)
name: Optional[str] = Field(None, description="Name of the task")
notes: Optional[str] = Field(None, description="Notes about the task")
bucket: Optional[Buckets] = Field(
None, description="Bucket of the task, to set, or update"
)
project: Optional[Projects] = Field(
None, description="Project of the task, to set, or update"
)
class Response(BaseModel):
text: str = Field(description="The text of the response")
task_action: Optional[list[TaskAction]] = Field(
description="The action to take on the task"
)
initial_messages = [
{
"role": "system",
"content": "You are an AI assistant. have the ability to create, update, and close tasks.",
},
{
"role": "assistant",
"content": """
The task is below. When assisting the user, reference the details from this task.
[BEGIN TASK]
id: 23
Name: Create 10 new GIFs
Description: Create 10 new GIFs for the Taskbot page on the user's personal site. They should be similar to the existing GIFs, but with different use cases.
Projects: Personal site
Buckets: Taskbot
Updates:
[BEGIN UPDATE]
**User Update - September 01, 2023 03:58:00 PM EDT**
The user plans to create the GIFs in the background as they work through their daily tasks. They aim to produce about one to two GIFs per day. If this plan doesn't work, they will reconsider their strategy.
[END UPDATE]
[END TASK]
""",
},
{"role": "assistant", "content": "What's up with this task?"},
{
"role": "user",
"content": "Change it to 20, then make a new task for when its done make 20 more that moves.",
},
]
response: Response = client.chat.completions.create(
messages=initial_messages, response_model=Response, model="gpt-4"
) # type: ignore
print(response.model_dump_json(indent=2))
{
"text": "Updating task to create 20 GIFs and creating a new task to create an additional 20 animated GIFs after the initial task is done.",
"task_action": [
{
"id": 23,
"method": "update_task",
"waiting_on": None,
"name": "Create 20 new GIFs",
"notes": "The user increased the number of GIFs from 10 to 20. They plan to create these as they work through their daily tasks, creating about one to two GIFs per day. If this plan doesn't work, they will reconsider their strategy.",
"bucket": "taskbot",
"project": "personal_site",
},
{
"id": 24,
"method": "create_task",
"waiting_on": [23],
"name": "Create 20 new animated GIFs",
"notes": "The task will be initiated once the task with id 23 is completed.",
"bucket": "taskbot",
"project": "personal_site",
},
],
}

View File

@@ -1,6 +0,0 @@
import erdantic as erd
from segment_search_queries import MultiSearch
diagram = erd.create(MultiSearch)
diagram.draw("examples/segment_search_queries/schema.png")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More