Add ClassifierRetryPolicy and keep LLMRetryPolicy as the text-model policy it was - #73501
Merged
Merged
Conversation
kaxil
force-pushed
the
retry-policy-compat-followups
branch
from
September 21, 2026 19:29
a4862a5 to
7682d71
Compare
kaxil
force-pushed
the
retry-policy-compat-followups
branch
from
September 21, 2026 19:36
7682d71 to
76750a5
Compare
…olicy it was apache#73450 folded a classifier-model mode into LLMRetryPolicy, which changed what a 0.9.0 Dag does on a text model (the model no longer chose the retry or the delay, custom instructions that named their own categories were mapped onto the defaults, ErrorClassification disappeared) and left arguments on the class that mean nothing without a classifier. A back-compat check of the released 0.9.0 provider against main found those and a few smaller drifts. Retry policies are now one class per layer of a ladder from hardcoded to reasoning. Fallback rules (ExceptionRetryPolicy, or ``fallback_rules`` on either policy) are the floor. ``ClassifierRetryPolicy`` is the middle: the model names one of ``categories`` and the ``ErrorCategory`` table decides retry, delay and how sure the model has to be, tuned by descriptions and a confidence bar; it is the policy for a classifier model such as TypeSafe's Jev. ``LLMRetryPolicy`` is the top and is the 0.9.0 policy unchanged, with no new arguments. ``on_uncertain`` chains them: a classifier policy names a policy, typically an LLMRetryPolicy on a text model, to consult when it is unsure or unreachable, and whatever that decides nothing about falls to the rules. Smaller drifts fixed on the classifier path: a ``fallback_rules`` entry with ``action=DEFAULT`` keeps its delay and reason; an answer missing from the table and a subclass ``_classify`` returning a non-decision log what happened and fall back. ``LLMOperator._push_decision`` no longer raises when the context's ``task_instance`` is a plain dict. The changelog describes the branching changes (``decision`` XCom on every run, sorted option order, ``branches`` template field) and the ``decision`` keyword ``execute_complete`` grew, which breaks a subclass override with the old signature at resume. The branch guide gets the "add an option for none of these" guidance the retry guide has.
kaxil
force-pushed
the
retry-policy-compat-followups
branch
from
September 21, 2026 19:49
76750a5 to
5de5258
Compare
…ack rules always run Only a RETRY or FAIL from the on_uncertain policy ends the chain. A DEFAULT, whatever reason it carries, hands back to the ClassifierRetryPolicy's own fallback_rules, so an outer PermissionError -> FAIL rule still holds when a nested classifier or an LLM policy has nothing to add. Previously a DEFAULT with a reason counted as a decision, which a ClassifierRetryPolicy used as on_uncertain always produced, so the outer rules never ran. A decision without a reason no longer renders as "escalated (...); None". on_uncertain no longer requires min_confidence: a classifier outage escalates without a bar. The "not supported by this model" hint is worded as a hint, since text models whose profile lacks structured output raise the same words. ErrorClassification keeps its 0.9.0 docstring, which pydantic sends to the model as the schema description, and the class docstring concatenation survives python -OO. The changelog note on execute_complete names the two operators that pass decision on resume; the additive ClassifierRetryPolicy note is dropped.
The policy behind a classifier is consulted on a low-confidence answer, a missing
confidence and a model outage alike, and it can be plain rules rather than a stronger
model, so "fallback" describes it and "uncertain" does not. The operators' DecisionPolicy
keeps on_uncertain, where the value is an action ("review" or "fail"), not a policy; using
one name for two different kinds of value in the same package invited confusion.
The name has not shipped in a release.
vatsrahul1001
approved these changes
Sep 22, 2026
kaxil
marked this pull request as ready for review
September 22, 2026 07:04
kaxil
added a commit
to astronomer/airflow
that referenced
this pull request
Sep 22, 2026
The page has covered ClassifierRetryPolicy as well as LLMRetryPolicy since apache#73501, so a title naming only the LLM policy undersells it. Rename the page title and the two sidebar references to "Retry policies".
kaxil
added a commit
that referenced
this pull request
Sep 22, 2026
The page has covered ClassifierRetryPolicy as well as LLMRetryPolicy since #73501, so a title naming only the LLM policy undersells it. Rename the page title and the two sidebar references to "Retry policies".
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #73368 and #73450 from a backward-compatibility check of the released 0.9.0 provider against main. Every 0.9.0-style
LLMRetryPolicyandLLMBranchOperatorpattern from that release's docs was run against main's code with the model mocked and each decision compared with the one 0.9.0 produced.Summary
#73450 folded a classifier-model mode into
LLMRetryPolicy. Two things were wrong with that. It changed what 0.9.0 Dags do on a text model: the model stopped choosing the retry and the delay, custominstructionsthat named their own categories (the 0.9.0 guide's documented way to customise the policy) had those names silently mapped onto the seven defaults, andErrorClassificationwas gone. And it leftcategories,min_confidenceand a confidence gate on a class most users run against a text model, where none of them mean anything.Retry policies are now one class per layer of a ladder from fully hardcoded to fully model-driven, and the guide is organised that way:
ExceptionRetryPolicy, orfallback_ruleson either policy belowRetryRulematches on the exception type, then the task's ownretriesandretry_delay. No model.ClassifierRetryPolicy(new)categories; the table says retry or fail, the delay, and how sure the model has to be. A classifier model such as Jev runs here; a text model can too.LLMRetryPolicy(unchanged)instructions, and explains itself.LLMRetryPolicyis the 0.9.0 policy and grows no arguments. Verified decision for decision against the 0.9.0 module for every pattern in the 0.9.0 docs, including the Snowflake example and a prompt that invents a category.ErrorClassificationis back,fallback_rulesapply verbatim,retry_reasonkeeps its<category>: <reasoning>shape. Pointed at a classifier model it fails over with a log line namingClassifierRetryPolicy.ClassifierRetryPolicycarries the classifier layer from LetLLMRetryPolicyrun on a classifier model with author-defined categories #73450:categories(defaultDEFAULT_CATEGORIES, the same seven with the same split and delays),min_confidence, the per-category bar, the generatedretry_reasonline.fallback_policychains the layers. AClassifierRetryPolicynames aRetryPolicyto consult when its answer is under the bar, reports no confidence, or the classifier call fails; typically anLLMRetryPolicyon a text model. Its RETRY or FAIL is used with the reason prefixed by why the classifier's answer was not (escalated (below_threshold); rate_limit: 429 with Retry-After). Only its RETRY or FAIL ends the chain: a DEFAULT from it, whatever reason it carries, means it decided nothing and the outerfallback_rulesapply, then the task's own retry behaviour. So Jev under 0.8 goes to Claude, and if Claude is unreachable the rules decide, including when the inner policy is another classifier. Withoutmin_confidencethe classifier's answer is always acted on andfallback_policyis consulted only when the classifier call fails. The example Dag has the chain.The parse-time warning added in #73450 goes away with the behaviour it warned about.
Live run of the chain
Airflow 3.3, pydantic-ai 2.46.0,
typesafe:jev-1.13.0as the classifier,anthropic:claude-sonnet-5through the Astro gateway asfallback_policy, a ruleRuntimeError -> RETRY 7sas the floor. Two failures, one clear (000606 ... Warehouse 'ANALYTICS_WH' is suspended) and one vague (Upstream job reported status ERROR with no message), through three policies.permanentcarries its own bar of 0.95.warehouse_suspendedat 1.00, retry 30spermanentat 0.94, under its bar, so the rule: retry 7sfallback_policy=LLMRetryPolicy(claude)transient, retry 30s, reasonescalated (below_threshold); transient: The error indicates an upstream job failed with a generic ERROR status...conn_id no_such_connection isn't defined, then the rule: retry 7s, reasonclassifier answer not applied (below_threshold); ruleOne thing the run showed about the bar: on the first attempt the vague message scored
permanentat exactly 0.90 against a 0.90 bar and the classifier acted alone; the same message a minute later scored 0.89. The docs already say to set the bar from observed values and that it reduces wrong actions rather than eliminating them; this is what that looks like.Smaller drifts the check found, fixed here
fallback_rulesentry withaction=RetryAction.DEFAULTlost its delay and reason; a matched rule is now kept whatever its action.categoriessurfaced as a swallowedKeyErrortraceback. It now logs what happened and takes the fallback path.LLMOperator._push_decisionraisedAttributeErrorwhen the context'stask_instancewas a plain dict, a shape older tests and hand-built runners use. It now warns and skips the push, as it already did when the key was missing.Docs
The retry guide opens with the ladder and describes each class under it, with an "Escalating to an LLM" section for the chain. The changelog note #73450 added for the retry policy is gone, since
LLMRetryPolicybehaves as it did in 0.9.0 and the new names are additive. Two notes are added for the branching changes: thedecisionXCom on every run, sorted option order andbranchesas a template field, and thedecisionkeywordexecute_completegrew on four operators.LLMOperatorandLLMBranchOperatorpass it on resume, so a subclass override of either with the old three-argument signature raises there; the SQL and schema-compare operators accept the keyword but do not pass it yet. The branch guide gets the same "add an option for none of these" guidance the retry guide has.