Personal
$10/month
- 50M tokens a week
- 20 requests per minute
- For one person
Support emails arrive as run-on sentences. Your code wants a customer, an order number and a category. Write the record once as a Pydantic class, send each message to Pareto and get back an object that already passed validation. Pareto is Unbiased’s model: it selects frontier and open models for each request and blends their outputs into one answer.
Tested live with Pydantic AI 2.58.0 and openai 3.35.150M tokens a week
Each of these talks to Pareto with a base URL, an API key and the model string pareto. Pick the one you already use.
Both versions below send the same three messages and print the same Ticket records. Pick Pydantic AI if you want validation retries and agents later; pick the OpenAI SDK if you want the fewest moving parts. Either way the schema travels as a tool call, which is the structured-output path Pareto supports.
Choose a plan, then create a key in the Unbiased platform. New accounts are reviewed and approved by a person before the key works.
Every field your code needs, with real types. Use Literal for a fixed set of labels and | None for anything a message might leave out, so the model has an honest answer when the text is silent.
Base URL https://api.unbiased.ai/v1, your Unbiased key, and the model name pareto, typed by hand: Unbiased does not list models at /v1/models.
Pydantic AI does this by default. With the OpenAI SDK, wrap the class in pydantic_function_tool and force the call with tool_choice. A JSON schema in response_format gets an HTTP 400 from Pareto.
One message, one request, one validated object. Write model_dump_json() to a file, a queue or a database row.
pareto. Both recipes turned three messy support messages into validated Ticket records with the right names, order number, amount and category, 3 runs out of 3 each. Run record.Agent(model, output_type=Ticket) turns the class into a tool the model must call, checks the arguments against the class, and returns a Ticket.
pip install "pydantic-ai-slim[openai]"PYDANTIC_AI_NO_BANNER=1 to hide the startup bannerimport os
from typing import Literal
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider
class Ticket(BaseModel):
customer: str
order_id: str | None
category: Literal['shipping', 'billing', 'bug', 'other']
amount: float | None
urgent: bool
model = OpenAIChatModel(
'pareto',
provider=OpenAIProvider(
base_url='https://api.unbiased.ai/v1',
api_key=os.environ['UNBIASED_API_KEY'],
),
)
agent = Agent(
model,
output_type=Ticket,
instructions='Extract one support ticket. Use null for anything the message does not say.',
)
inbox = [
"hi its Priya Nair, order #A-10442 never showed up, tracking says delivered?? need it by friday for my sisters wedding",
"Charged twice for my annual plan ($96 each). pls refund one. Tom Okafor",
"app crashes every time i export a pdf on ipad. not urgent, just annoying. cheers, M. Lindqvist",
]
for text in inbox:
ticket = agent.run_sync(text).output
print(ticket.model_dump_json())pip install "pydantic-ai-slim[openai]" export UNBIASED_API_KEY=your-unbiased-api-key python tickets.py
pydantic_function_tool(Ticket) sends the class as a function tool, tool_choice makes the model call it, and chat.completions.parse hands back the arguments as a Ticket.
pip install openaiValidationError you can catch and retryimport os
from typing import Literal
from openai import OpenAI, pydantic_function_tool
from pydantic import BaseModel
class Ticket(BaseModel):
customer: str
order_id: str | None
category: Literal['shipping', 'billing', 'bug', 'other']
amount: float | None
urgent: bool
client = OpenAI(
base_url='https://api.unbiased.ai/v1',
api_key=os.environ['UNBIASED_API_KEY'],
)
inbox = [
"hi its Priya Nair, order #A-10442 never showed up, tracking says delivered?? need it by friday for my sisters wedding",
"Charged twice for my annual plan ($96 each). pls refund one. Tom Okafor",
"app crashes every time i export a pdf on ipad. not urgent, just annoying. cheers, M. Lindqvist",
]
for text in inbox:
completion = client.chat.completions.parse(
model='pareto',
messages=[
{'role': 'system', 'content': 'Extract one support ticket. Use null for anything the message does not say.'},
{'role': 'user', 'content': text},
],
tools=[pydantic_function_tool(Ticket)],
tool_choice={'type': 'function', 'function': {'name': 'Ticket'}},
)
ticket = completion.choices[0].message.tool_calls[0].function.parsed_arguments
print(ticket.model_dump_json())pip install openai export UNBIASED_API_KEY=your-unbiased-api-key python tickets_openai.py
{"customer":"Priya Nair","order_id":"A-10442","category":"shipping","amount":null,"urgent":true}
{"customer":"Tom Okafor","order_id":null,"category":"billing","amount":96.0,"urgent":false}
{"customer":"M. Lindqvist","order_id":null,"category":"bug","amount":null,"urgent":false}Extraction is short in, short out, so a record is a few hundred tokens. These are the counts the API reported in our runs, not an estimate for your messages: longer text and bigger classes cost more.
Records a week assume every record is as large as our largest (694 tokens) and ignore the request rate; Team cost uses our largest record’s split of 303 input and 391 output tokens at the published rates. Your messages and classes will differ. Running this for a business is production use, which needs Team or a written agreement. Run record.
Pick a monthly allowance, or pay per token on Team.
$10/month
$100/month
Pay as you go
Subscriptions are for one person and are not unlimited. New API accounts are reviewed by a person before access turns on, and commercial or production use needs a separate written agreement. Rates and limits are on Pricing.
Because that is what works on Pareto today. In our test on October 20, 2026, a JSON schema sent in response_format came back as HTTP 400 unsupported_parameter, both from the OpenAI SDK’s parse(response_format=Ticket) and from Pydantic AI’s NativeOutput. Tool calls worked, and so did Pydantic AI’s PromptedOutput. Format compatibility is not a promise that every feature works, so tell us if one doesn’t.
Pydantic AI sends the validation error back to the model and asks again, then raises if the retries run out. With the OpenAI SDK, parse raises a Pydantic ValidationError, so wrap the call and retry or set the message aside for a person. None of our 6 runs needed a retry.
Facts written in the text came back the same in every run we made: names, order numbers, amounts and categories. Judgment calls can move: in 1 of the 12 runs we saved, the double charge came back as urgent. If a field needs a rule, put the rule in the instructions, for example “urgent only if the customer names a deadline”.
Each record is one request, so your plan’s request rate sets the pace: 20 requests per minute on Personal. Each of our 18 measured records took between 0.9 and 2.5 seconds. Requests over your plan’s limits return HTTP 429, so back off and retry.
No. The class names say OpenAI because that is the request format. With base_url and api_key set, requests go to Unbiased with your Unbiased key, and your Unbiased plan pays for them.
Requests over your plan’s limits return an HTTP 429 error. Personal includes 50M tokens a week and 20 requests per minute. Personal Max raises that to 600M a week and 60 requests per minute for $100/month. Team uses prepaid pay-as-you-go credits with no weekly token cap.
No. You send every request to one model string, pareto. Pareto selects frontier and open models for each request and blends their outputs into one answer.
Personal and Personal Max are not our zero data retention tier: prompts and responses may be kept up to 30 days, as section 5(c) of the Terms explains. For work that must not be kept, ask about the Zero Data Retention API on the Private AI page.
Same key, same model string, different job. See every use case. Or find your tool on Works with.
Product names belong to their owners. Unbiased is not affiliated with or endorsed by the makers of the tools on this page.
One class, one loop and an API key. Pareto starts at $10 a month for 50M tokens a week.