Classifier models¶
Every other model in this provider writes text. A classifier model does not: you give it some text and a typed question, and it answers with a value from a set you named in advance, plus a confidence. Ask it for a string and the request is refused before it leaves your process.
TypeSafe’s Jev is the one pydantic-ai supports, as the
typesafe: provider. Nothing in this provider is specific to it – it arrives through the same
PydanticAIHook as every other
model, so a model id is the whole integration.
Setup¶
Install the extra:
pip install 'apache-airflow-providers-common-ai[typesafe]'
The extra installs the TypeSafe SDK. The
typesafe:model adapter is part of pydantic-ai itself frompydantic-ai-slim2.45.0, which is newer than the floor the provider’s other extras set, so check that release or later is installed.Create a connection (
Admin > Connections):Connection Id:
jev_defaultConnection Type:
Pydantic AIPassword: your TypeSafe API key, from your TypeSafe account
Extra:
{"model": "typesafe:jev-1.13.0"}
Leave Host empty unless you are pointing at a proxy; the provider defaults to TypeSafe’s own endpoint.
Pin the version rather than using jev-latest. A threshold you tuned against one
release is not guaranteed to mean the same thing after the next one, and jev-latest
moves under you.
When a classifier model is the right choice¶
All four of these have to hold.
The answer is a label, a number, or a pick, not prose. A bool, a Literal or
Enum of strings, a bounded float, a whole-number rubric, or a list of picks. A
str field is refused, so anything that writes a summary, a query, a migration, or a
reply to a person is out.
You can name the options up front, and there are at most 255. Downstream task ids, error categories, severity levels, environments, a repository list. If the set is open, or is discovered at run time and could grow past the cap, this is the wrong tool. The cap is per question, so tools attached to an agent get their own 255, with the output type as one option in that question.
The decision is on a path where latency or cost is the constraint. One decision per Dag run rarely justifies changing models. One per row, per file, or per retrieved document does, and so does a branch a scheduler is waiting on.
You want a number to branch on, not a sentence to trust. A pick, a rubric, and a
yes/no each come back with a confidence, so “act automatically above 0.8, ask a human below
it” becomes something you can write down. A bounded float is the exception: there the
probability is the answer, so nothing separate is reported and you gate on the value
itself. If you would not do anything different with a confidence of 0.55 than with 0.95,
that is a sign a general-purpose model is fine here.
And one case where the answer is neither: if a deterministic rule already sorts the input correctly, use the rule. A classifier model is cheap, not free, and a rule you can read is worth more than a probability you have to calibrate.
Where it fits in this provider¶
Surface |
Fits |
Why |
|---|---|---|
Yes, with a caveat |
The downstream task ids are already presented to the model as a constrained set of
choices, which is exactly the shape a classifier model answers. Setting
|
|
|
Yes |
A |
Yes |
The model names one of the policy’s |
|
Agents with toolsets |
Partly |
Which tool the text calls for is itself a pick, so a classifier model can make it.
What it cannot write is a tool’s arguments. A tool taking none it calls itself; one
taking arguments raises |
Reading the confidence¶
The confidence lives in provider_details on the model response. It is reported per
output field, and a bare output type is reported under "response". A bounded float
field reports none at all, because there the probability is the answer.
Two surfaces act on it for you. LLMBranchOperator
and LLMOperator take a
decision_policy whose min_confidence sends an unsure answer to a person, or fails
the task, before anything downstream runs on it, and record the confidence, the
probabilities and the bar in the decision XCom (see Branch on an answer: LLMBranchOperator).
ClassifierRetryPolicy takes the same min_confidence and hands an
unsure answer to fallback_policy, then its deterministic fallback rules. In the branch operator and the retry policy, a
per-option bar lets the choice whose wrong pick costs most demand more certainty than the rest.
Outside those, read it yourself. AgentOperator carries it inside the message_history
transcript when that is enabled, and a hook-level call has it on the result:
from airflow.providers.common.ai.hooks.pydantic_ai import PydanticAIHook
from airflow.sdk import task
from typing import Literal
@task
def triage(log_line: str) -> dict:
agent = PydanticAIHook(llm_conn_id="jev_default").create_agent(
output_type=Literal["transient", "resource", "permanent"],
instructions="Classify why this Airflow task failed.",
)
result = agent.run_sync(log_line)
details = result.response.provider_details or {}
confidence = (details.get("confidence") or {}).get("response")
return {"category": result.output, "confidence": confidence}
Then branch on the returned confidence in a downstream task, so an unsure answer escalates instead of acting. Use a higher bar for acting automatically than for flagging something for review: collapsing both into one number is the easier thing to tune and the wrong shape for the decision.
Worked example¶
example_classifier_model.py
has both halves: a branch whose only classifier-specific line is model_id, and a
classification that escalates when the confidence is low.
What it answers badly¶
Read pydantic-ai’s model page and TypeSafe’s own documentation before you trust a number from one of these models. Two of its failure modes matter more than the rest in a Dag:
The text is treated as data, not as hostile. An injected instruction, a misleading framing, or an argument for its own answer can move the result. A guard built on a classifier model belongs alongside deterministic checks, not instead of them.
Option order is part of what the model sees. Reordering a
Literal’s members can change the answer, so a threshold measured against one ordering is measured against that ordering only.