This is an automated email from the ASF dual-hosted git repository.
kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 134b7332319 Add support for TypeSafe Jev classifier models (#73363)
134b7332319 is described below
commit 134b7332319d78f81facc66b085f764e84aa5bcd
Author: Kaxil Naik <[email protected]>
AuthorDate: Mon Sep 21 00:31:55 2026 +0100
Add support for TypeSafe Jev classifier models (#73363)
A classifier model answers typed questions -- pick one of these options, a
bounded score, a yes/no probability -- and refuses anything that would need
prose. pydantic-ai 2.45.0 added ``TypeSafeModel`` for TypeSafe's Jev, and
its
provider takes exactly the ``api_key`` and ``base_url`` that
``PydanticAIHook`` already passes, so no provider code changes: setting
``model_id`` or the connection's Model field is the whole integration.
Adds the opt-in ``typesafe`` extra, a guide covering when a classifier
model is
the right choice and where it fits among the existing operators, and an
example
Dag with a branch and a confidence-gated classification.
The extra pulls ``typesafe-sdk``, which is pre-1.0, so it stays opt-in
rather
than a base dependency. The temporary per-package ``exclude-newer`` pins are
needed because 2.45.0 was published inside the rolling four-day window.
---
docs/spelling_wordlist.txt | 1 +
providers/common/ai/README.rst | 1 +
providers/common/ai/docs/classifier_models.rst | 189 +++++++++++++++
providers/common/ai/docs/examples.rst | 4 +
providers/common/ai/docs/index.rst | 10 +-
providers/common/ai/pyproject.toml | 10 +
.../ai/example_dags/example_classifier_model.py | 134 +++++++++++
pyproject.toml | 8 +
uv.lock | 259 +++++++++++----------
9 files changed, 494 insertions(+), 122 deletions(-)
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index f719c597bf7..67308f30971 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -918,6 +918,7 @@ jdk
jenkins
JenkinsRequest
Jetson
+Jev
Jiajie
Jinja
jinja
diff --git a/providers/common/ai/README.rst b/providers/common/ai/README.rst
index d799a5a0d31..b7b02d9ddb2 100644
--- a/providers/common/ai/README.rst
+++ b/providers/common/ai/README.rst
@@ -90,6 +90,7 @@ Extra Dependencies
``google`` ``pydantic-ai-slim[google]>=2.23.0``
``openai`` ``pydantic-ai-slim[openai]>=2.23.0``
``mcp`` ``pydantic-ai-slim[mcp]>=2.23.0``
+``typesafe`` ``typesafe-sdk>=0.6.0``
``code-mode`` ``pydantic-ai-harness[codemode]>=0.3.0``
``shields`` ``pydantic-ai-shields>=0.3.4``
``skills`` ``apache-airflow-providers-git>=0.4.0``,
``pydantic-ai-skills>=1.2.0``
diff --git a/providers/common/ai/docs/classifier_models.rst
b/providers/common/ai/docs/classifier_models.rst
new file mode 100644
index 00000000000..f53e36f6b2a
--- /dev/null
+++ b/providers/common/ai/docs/classifier_models.rst
@@ -0,0 +1,189 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ .. http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+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
+:class:`~airflow.providers.common.ai.hooks.pydantic_ai.PydanticAIHook` as
every other
+model, so a model id is the whole integration.
+
+Setup
+-----
+
+1. Install the extra:
+
+ .. code-block:: bash
+
+ pip install 'apache-airflow-providers-common-ai[typesafe]'
+
+ The extra installs the TypeSafe SDK; the model adapter itself is part of
pydantic-ai
+ from 2.45.0, so make sure ``pydantic-ai-slim>=2.45.0`` is installed as
well. The
+ provider does not raise its own floor to that release yet, because
pydantic-ai's
+ ``openai`` extra needs openai 3.x while other Airflow providers still pin
openai 2.x.
+
+2. Create a connection (``Admin > Connections``):
+
+ - **Connection Id**: ``jev_default``
+ - **Connection Type**: ``Pydantic AI``
+ - **Password**: your TypeSafe API key
+ - **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
+------------------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 15 55
+
+ * - Surface
+ - Fits
+ - Why
+ * -
:class:`~airflow.providers.common.ai.operators.llm_branch.LLMBranchOperator`
+ - 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
+ ``model_id`` is the only change, as long as there are two or more
downstream tasks
+ -- a one-option pick is refused. The other caveat is below: the
operator cannot see
+ the confidence, so it will branch on an unsure answer without telling
you.
+ * - :class:`~airflow.providers.common.ai.operators.llm.LLMOperator` /
+ :class:`~airflow.providers.common.ai.operators.agent.AgentOperator`
with a typed
+ ``output_type``
+ - Yes
+ - A ``Literal``, ``Enum``, ``bool`` or bounded number works. Describe the
field, which
+ becomes the question, and describe each option, which is what tells
them apart. An
+ option with no description is read from its name alone.
+ * - :doc:`LLMRetryPolicy <retry_policies>`
+ - No
+ - Three of ``ErrorClassification``'s four fields are shapes a classifier
model
+ refuses: ``category`` and ``reasoning`` are bare ``str``, and
+ ``suggested_delay_seconds`` is an unbounded ``int``. Only
``should_retry`` fits.
+ Making the class answerable would need a ``Literal`` category, a rubric
delay, and
+ no explanation at all.
+ * - 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 ``ToolCallProposed`` after the request, which
is a
+ ``ModelAPIError`` rather than a refusal, so ``FallbackModel`` hands
those requests to
+ a text model behind it and only they cost a full call.
+
+Reading the confidence
+----------------------
+
+The confidence lives in ``provider_details`` on the model response, which no
operator
+exposes on XCom by default -- ``AgentOperator`` carries it inside the
``message_history``
+transcript when that is enabled. 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.
+
+This matters most for branching, because a branch is taken inside the operator
before any
+downstream task can look at it. Given two repair paths that both fit the
evidence, a
+classifier model splits its probability between them, and the branch is then
decided by
+whichever side of a near-even split came out ahead. The confidence reports
that faithfully;
+it is just not somewhere Airflow can act on yet, so a near-tie routes on a
margin the
+operator cannot show you. Treat ``LLMBranchOperator`` on a classifier model as
suited to
+branches whose options are genuinely distinct, and put anything closer than
that behind a
+task that reads the confidence itself:
+
+.. code-block:: python
+
+ from airflow.providers.common.ai.hooks.pydantic_ai import PydanticAIHook
+ from airflow.providers.common.compat.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
<https://github.com/apache/airflow/blob/providers-common-ai/|version|/providers/common/ai/src/airflow/providers/common/ai/example_dags/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
<https://pydantic.dev/docs/ai/models/typesafe/>`__ 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.
diff --git a/providers/common/ai/docs/examples.rst
b/providers/common/ai/docs/examples.rst
index 536e3f1b46a..f45a49d627a 100644
--- a/providers/common/ai/docs/examples.rst
+++ b/providers/common/ai/docs/examples.rst
@@ -144,6 +144,10 @@ Reliability
- Failing over to another vendor inside one task attempt, and drilling
the chain
without waiting for an outage. Source:
`example_llm_fallback.py
<https://github.com/apache/airflow/blob/providers-common-ai/|version|/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_llm_fallback.py>`__.
+ * - :doc:`classifier_models`
+ - Routing a failure with a model that answers typed questions instead of
writing
+ text, and escalating when its confidence is low. Source:
+ `example_classifier_model.py
<https://github.com/apache/airflow/blob/providers-common-ai/|version|/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_classifier_model.py>`__.
.. toctree::
:hidden:
diff --git a/providers/common/ai/docs/index.rst
b/providers/common/ai/docs/index.rst
index 329cba65e79..29b6c9e211e 100644
--- a/providers/common/ai/docs/index.rst
+++ b/providers/common/ai/docs/index.rst
@@ -117,10 +117,12 @@ Choosing extras
The provider's extras split into a few groups:
-* **Model providers** — ``openai``, ``anthropic``, ``google``, ``bedrock``:
pick the one
- matching your ``llm_conn_id`` connection. Each extra name mirrors the
identically named
+* **Model providers** — ``openai``, ``anthropic``, ``google``, ``bedrock``,
``typesafe``:
+ pick the one matching your ``llm_conn_id`` connection. ``typesafe`` differs
from the rest
+ in kind: it installs a classifier model that answers typed questions and
cannot write
+ text (see :doc:`classifier_models`). Each extra name mirrors the identically
named
``pydantic-ai-slim`` optional dependency group; pydantic-ai supports more
model providers
- than these four, each under its own extra name, so check the
+ than these, each under its own extra name, so check the
`pydantic-ai install docs <https://ai.pydantic.dev/install/#slim-install>`__
for the full list.
* **Agent tooling** — ``mcp``, ``skills``, ``code-mode``, ``shields``: MCP
servers, Agent
Skills, code-mode tool execution, and shield capabilities (input/output
guards, tool
@@ -163,6 +165,7 @@ See the Optional dependencies table below for the exact
package each extra insta
Provider fallback <provider_fallback>
Retry Policies <retry_policies>
Self-hosted models <self_hosted_models>
+ Classifier models <classifier_models>
HITL Review <hitl_review>
Observability <observability>
@@ -273,6 +276,7 @@ Extra Dependencies
``bedrock`` ``pydantic-ai-slim[bedrock]>=2.23.0``
``google`` ``pydantic-ai-slim[google]>=2.23.0``
``openai`` ``pydantic-ai-slim[openai]>=2.23.0``
+``typesafe`` ``typesafe-sdk>=0.6.0``
``mcp`` ``pydantic-ai-slim[mcp]>=2.23.0``
``code-mode`` ``pydantic-ai-harness[codemode]>=0.3.0``
``shields`` ``pydantic-ai-shields>=0.3.4``
diff --git a/providers/common/ai/pyproject.toml
b/providers/common/ai/pyproject.toml
index a27011e7f69..ef66ea5b0ee 100644
--- a/providers/common/ai/pyproject.toml
+++ b/providers/common/ai/pyproject.toml
@@ -82,6 +82,16 @@ dependencies = [
"bedrock" = ["pydantic-ai-slim[bedrock]>=2.23.0"]
"google" = ["pydantic-ai-slim[google]>=2.23.0"]
"openai" = ["pydantic-ai-slim[openai]>=2.23.0"]
+# TypeSafe Jev: answers typed questions (pick one of N, a bounded score, a
yes/no
+# probability) and cannot emit text, so it fits a branch or a classification
but not a
+# summary. It arrives through pydantic-ai's own ``typesafe:`` provider, so the
hook needs
+# no changes -- setting ``model_id`` or the connection's Model field is the
integration.
+# The adapter itself ships inside pydantic-ai-slim from 2.45.0
(pydantic/pydantic-ai#8450);
+# this extra only adds the pre-1.0 typesafe-sdk it talks to, which is why it
is opt-in. It
+# deliberately does not raise the pydantic-ai-slim floor: from 2.31 the
``openai`` extra
+# needs openai>=3.8 while llama-index-llms-openai and the Google provider's
litellm need
+# openai<3, so a higher floor makes the workspace unsatisfiable. The guide
says 2.45+.
+"typesafe" = ["typesafe-sdk>=0.6.0"]
"mcp" = ["pydantic-ai-slim[mcp]>=2.23.0"]
# Code mode: collapse tool calls into a single `run_code` tool that the model
# drives by writing Python, executed in the Monty sandbox (pydantic-monty).
diff --git
a/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_classifier_model.py
b/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_classifier_model.py
new file mode 100644
index 00000000000..c108a3cacae
--- /dev/null
+++
b/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_classifier_model.py
@@ -0,0 +1,134 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Example DAG using a classifier model to route an incident.
+
+A classifier model answers typed questions and cannot write text, so it suits
a branch
+(pick one of these task ids) and a classification (pick one of these labels),
and nothing
+that produces prose. See the "Classifier models" guide in this provider's
documentation.
+
+Prerequisites:
+ - ``pip install 'apache-airflow-providers-common-ai[typesafe]'``
+ - Connection ``jev_default`` with ``conn_type='pydanticai'``,
``password=<API key>``,
+ ``extra='{"model": "typesafe:jev-1.13.0"}'``
+"""
+
+from __future__ import annotations
+
+from typing import Literal
+
+from airflow.providers.common.ai.hooks.pydantic_ai import PydanticAIHook
+from airflow.providers.common.ai.operators.llm_branch import LLMBranchOperator
+from airflow.providers.common.compat.sdk import dag, task
+
+# Clear-cut: the error names the missing permission, so one remediation fits
and the others
+# do not. Branch options a classifier model can separate are the ones to give
it.
+PERMISSION_FAILURE = (
+ "Task export_report failed: botocore.exceptions.ClientError: An error
occurred "
+ "(AccessDenied) when calling the PutObject operation: the task role "
+ "airflow-worker lacks s3:PutObject on arn:aws:s3:::reports-prod/daily/."
+)
+
+# Answerable but not decisively: a classifier model lands on ``resource`` here
with roughly
+# two thirds of the probability, which is enough to be worth recording and not
enough to act
+# on unattended. That is the case the confidence gate below exists for.
+UNDERDETERMINED_INCIDENT = (
+ "Task load_orders failed: psycopg2.OperationalError: connection refused to
"
+ "warehouse.internal:5432. Two other Dags writing to the same warehouse
failed in the "
+ "last ten minutes. A Terraform apply touching the database security group
merged "
+ "four hours ago."
+)
+
+# Acting automatically deserves a higher bar than flagging for review, so the
two use
+# different thresholds rather than one shared number.
+ACT_ABOVE = 0.8
+REVIEW_ABOVE = 0.5
+
+
+# [START howto_classifier_model_branch]
+@dag(tags=["example", "classifier"])
+def example_classifier_model_branch():
+ """Route the incident. The model id is the only thing that makes this a
classifier."""
+ route = LLMBranchOperator(
+ task_id="route_failure",
+ prompt=PERMISSION_FAILURE,
+ llm_conn_id="jev_default",
+ model_id="typesafe:jev-1.13.0",
+ system_prompt="Pick the remediation that addresses the cause, not the
symptom.",
+ )
+
+ @task
+ def grant_bucket_write():
+ return "Adding s3:PutObject for the task role"
+
+ @task
+ def restore_deleted_bucket():
+ return "Recreating the destination bucket"
+
+ @task
+ def wait_and_retry():
+ return "Treating this as transient"
+
+ route >> [grant_bucket_write(), restore_deleted_bucket(), wait_and_retry()]
+
+
+# [END howto_classifier_model_branch]
+
+example_classifier_model_branch()
+
+
+# [START howto_classifier_model_confidence]
+@dag(tags=["example", "classifier"])
+def example_classifier_model_confidence():
+ """Classify a failure and escalate when the model says it does not know.
+
+ The branch Dag above cannot do this: ``LLMBranchOperator`` takes the
branch inside the
+ operator, before any task can read the confidence.
+ """
+
+ @task
+ def classify(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)
+ # Confidence is reported per output field; a bare output type lands
under
+ # "response". A bounded ``float`` output would report none at all --
there the
+ # probability is the answer -- so this ``or 0.0`` would read as no
confidence
+ # rather than as a missing one. No operator surfaces this on XCom by
default.
+ details = result.response.provider_details or {}
+ return {
+ "category": result.output,
+ "confidence": (details.get("confidence") or {}).get("response"),
+ }
+
+ @task
+ def act(classification: dict) -> str:
+ confidence = classification["confidence"] or 0.0
+ if confidence >= ACT_ABOVE:
+ return f"Remediating {classification['category']} automatically"
+ if confidence >= REVIEW_ABOVE:
+ return f"Filing {classification['category']} for review"
+ return "Paging a human: the classification was a coin flip"
+
+ act(classify(UNDERDETERMINED_INCIDENT))
+
+
+# [END howto_classifier_model_confidence]
+
+example_classifier_model_confidence()
diff --git a/pyproject.toml b/pyproject.toml
index 59c0b15f2dc..14af9aa3600 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1602,6 +1602,12 @@ pydantic-ai-skills = "2026-07-16T00:00:00Z"
# past that release so 1.164.0 resolves while any later release stays
quarantined.
# Remove once the rolling window advances past 2026-08-12.
google-cloud-aiplatform = "2026-08-13T00:00:00Z"
+# Temporary: typesafe-sdk>=0.6.0 (required by the
apache-airflow-providers-common-ai[typesafe]
+# extra) first shipped on 2026-09-18, inside the "4 days" exclude-newer
window. This pins the
+# per-package cutoff just past that release so it resolves while any later
release stays
+# quarantined.
+# Remove once the rolling window advances past 2026-09-18.
+typesafe-sdk = "2026-09-19T00:00:00Z"
[tool.uv.pip]
# Synchroonize with scripts/ci/prek/upgrade_important_versions.py
@@ -1756,6 +1762,8 @@ apache-airflow-task-sdk-integration-tests = false
pydantic-ai-skills = "2026-07-16T00:00:00Z"
# Temporary: see the google-cloud-aiplatform note in
`[tool.uv.exclude-newer-package]`.
google-cloud-aiplatform = "2026-08-13T00:00:00Z"
+# Temporary: see the typesafe-sdk note in `[tool.uv.exclude-newer-package]`.
+typesafe-sdk = "2026-09-19T00:00:00Z"
[tool.uv.sources]
# These names must match the names as defined in the pyproject.toml of the
workspace items,
diff --git a/uv.lock b/uv.lock
index 157eb691173..07a45a3b29f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -29,147 +29,148 @@ exclude-newer = "0001-01-01T00:00:00Z" # This has no
effect and is included for
exclude-newer-span = "P4D"
[options.exclude-newer-package]
-apache-airflow-providers-informatica = false
-apache-airflow-providers-clickhousedb = false
-apache-airflow-providers-amazon = false
-apache-airflow-providers-elasticsearch = false
-apache-airflow-providers-duckdb = false
+apache-airflow = false
+apache-airflow-breeze = false
+apache-airflow-core = false
+apache-airflow-ctl = false
+apache-airflow-ctl-tests = false
+apache-airflow-dev = false
+apache-airflow-devel-common = false
+apache-airflow-docker-stack = false
apache-airflow-docker-tests = false
-apache-airflow-providers-microsoft-winrm = false
-apache-airflow-providers = false
-apache-airflow-providers-fab = false
-apache-airflow-providers-openlineage = false
-apache-airflow-providers-sftp = false
apache-airflow-e2e-tests = false
-apache-airflow-shared-logging = false
-apache-airflow-providers-common-dataquality = false
+apache-airflow-helm-chart = false
+apache-airflow-kubernetes-tests = false
+apache-airflow-mypy = false
+apache-airflow-providers = false
+apache-airflow-providers-airbyte = false
+apache-airflow-providers-akeyless = false
+apache-airflow-providers-alibaba = false
+apache-airflow-providers-amazon = false
+apache-airflow-providers-anthropic = false
+apache-airflow-providers-apache-arrow = false
+apache-airflow-providers-apache-beam = false
+apache-airflow-providers-apache-cassandra = false
apache-airflow-providers-apache-drill = false
-apache-airflow-providers-pgvector = false
-apache-airflow-providers-imap = false
-apache-airflow-providers-qdrant = false
-apache-airflow-providers-edge3 = false
-apache-airflow-providers-neo4j = false
-apache-airflow-providers-discord = false
-apache-airflow-providers-opensearch = false
-apache-airflow-providers-samba = false
-apache-airflow-providers-arangodb = false
+apache-airflow-providers-apache-druid = false
+apache-airflow-providers-apache-flink = false
+apache-airflow-providers-apache-hdfs = false
+apache-airflow-providers-apache-hive = false
+apache-airflow-providers-apache-iceberg = false
+apache-airflow-providers-apache-impala = false
+apache-airflow-providers-apache-kafka = false
+apache-airflow-providers-apache-kylin = false
+apache-airflow-providers-apache-livy = false
+apache-airflow-providers-apache-pig = false
+apache-airflow-providers-apache-pinot = false
apache-airflow-providers-apache-spark = false
-apache-airflow-providers-ftp = false
-apache-airflow-providers-jenkins = false
-apache-airflow-shared-listeners = false
-apache-airflow-shared-providers-discovery = false
-apache-airflow-providers-telegram = false
+apache-airflow-providers-apache-tinkerpop = false
+apache-airflow-providers-apprise = false
+apache-airflow-providers-arangodb = false
+apache-airflow-providers-asana = false
+apache-airflow-providers-atlassian-jira = false
apache-airflow-providers-celery = false
-apache-airflow-providers-docker = false
-apache-airflow-providers-sendgrid = false
+apache-airflow-providers-clickhousedb = false
+apache-airflow-providers-cloudant = false
+apache-airflow-providers-cncf-kubernetes = false
+apache-airflow-providers-cohere = false
apache-airflow-providers-common-ai = false
-apache-airflow = false
-apache-airflow-shared-observability = false
-apache-airflow-dev = false
+apache-airflow-providers-common-compat = false
+apache-airflow-providers-common-dataquality = false
+apache-airflow-providers-common-io = false
+apache-airflow-providers-common-messaging = false
+apache-airflow-providers-common-sql = false
+apache-airflow-providers-databricks = false
+apache-airflow-providers-datadog = false
apache-airflow-providers-dbt-cloud = false
+apache-airflow-providers-dingding = false
+apache-airflow-providers-discord = false
+apache-airflow-providers-docker = false
+apache-airflow-providers-duckdb = false
+apache-airflow-providers-edge3 = false
+apache-airflow-providers-elasticsearch = false
+apache-airflow-providers-exasol = false
+apache-airflow-providers-fab = false
+apache-airflow-providers-facebook = false
+apache-airflow-providers-ftp = false
+apache-airflow-providers-git = false
+apache-airflow-providers-github = false
+apache-airflow-providers-google = false
+apache-airflow-providers-grpc = false
+apache-airflow-providers-hashicorp = false
+apache-airflow-providers-http = false
+apache-airflow-providers-ibm-db2 = false
+apache-airflow-providers-ibm-mq = false
+apache-airflow-providers-imap = false
+apache-airflow-providers-influxdb = false
+apache-airflow-providers-informatica = false
+apache-airflow-providers-jdbc = false
+apache-airflow-providers-jenkins = false
+apache-airflow-providers-keycloak = false
+apache-airflow-providers-microsoft-azure = false
+apache-airflow-providers-microsoft-mssql = false
+apache-airflow-providers-microsoft-psrp = false
+apache-airflow-providers-microsoft-winrm = false
+apache-airflow-providers-mongo = false
+apache-airflow-providers-mysql = false
+apache-airflow-providers-neo4j = false
+apache-airflow-providers-odbc = false
+apache-airflow-providers-openai = false
apache-airflow-providers-openfaas = false
-apache-airflow-devel-common = false
-apache-airflow-providers-apache-cassandra = false
-apache-airflow-providers-asana = false
+apache-airflow-providers-openlineage = false
+apache-airflow-providers-opensearch = false
+apache-airflow-providers-opsgenie = false
apache-airflow-providers-oracle = false
-apache-airflow-providers-mysql = false
-apache-airflow-providers-alibaba = false
-apache-airflow-providers-microsoft-mssql = false
-apache-airflow-providers-teradata = false
-apache-airflow-providers-jdbc = false
-apache-airflow-helm-chart = false
-apache-airflow-providers-anthropic = false
-apache-airflow-providers-common-io = false
-apache-airflow-providers-cohere = false
+apache-airflow-providers-pagerduty = false
+apache-airflow-providers-papermill = false
+apache-airflow-providers-pgvector = false
apache-airflow-providers-pinecone = false
-apache-airflow-providers-segment = false
+apache-airflow-providers-postgres = false
+apache-airflow-providers-presto = false
+apache-airflow-providers-qdrant = false
apache-airflow-providers-redis = false
-apache-airflow-shared-dagnode = false
-apache-airflow-providers-apache-pinot = false
-apache-airflow-providers-weaviate = false
-google-cloud-aiplatform = "2026-08-13T00:00:00Z"
-apache-airflow-providers-akeyless = false
apache-airflow-providers-salesforce = false
+apache-airflow-providers-samba = false
+apache-airflow-providers-segment = false
+apache-airflow-providers-sendgrid = false
+apache-airflow-providers-sftp = false
+apache-airflow-providers-singularity = false
+apache-airflow-providers-slack = false
+apache-airflow-providers-smtp = false
+apache-airflow-providers-snowflake = false
+apache-airflow-providers-sqlite = false
apache-airflow-providers-ssh = false
-apache-airflow-providers-papermill = false
-apache-airflow-providers-google = false
-pydantic-ai-skills = "2026-07-16T00:00:00Z"
-apache-airflow-providers-apache-beam = false
-apache-airflow-providers-microsoft-psrp = false
+apache-airflow-providers-standard = false
+apache-airflow-providers-tableau = false
+apache-airflow-providers-telegram = false
+apache-airflow-providers-teradata = false
+apache-airflow-providers-trino = false
apache-airflow-providers-vertica = false
-apache-airflow-providers-apache-hdfs = false
-apache-airflow-shared-template-rendering = false
-apache-airflow-mypy = false
-apache-airflow-providers-http = false
-apache-airflow-providers-slack = false
apache-airflow-providers-vespa = false
-apache-airflow-providers-databricks = false
-apache-airflow-shared-state = false
-apache-airflow-docker-stack = false
-apache-airflow-providers-sqlite = false
-apache-airflow-providers-ibm-mq = false
-apache-airflow-shared-module-loading = false
+apache-airflow-providers-weaviate = false
apache-airflow-providers-yandex = false
-apache-airflow-shared-serialization = false
-apache-airflow-scripts = false
-apache-airflow-providers-exasol = false
-apache-airflow-providers-mongo = false
-apache-airflow-providers-apache-arrow = false
-apache-airflow-providers-apprise = false
-apache-airflow-providers-apache-impala = false
-apache-airflow-ctl = false
-apache-airflow-providers-github = false
-apache-airflow-providers-snowflake = false
+apache-airflow-providers-ydb = false
apache-airflow-providers-zendesk = false
-apache-airflow-providers-presto = false
-apache-airflow-providers-airbyte = false
-apache-airflow-providers-apache-hive = false
-apache-airflow-kubernetes-tests = false
-apache-airflow-providers-grpc = false
-apache-airflow-providers-apache-druid = false
-apache-airflow-providers-apache-flink = false
-apache-airflow-providers-cncf-kubernetes = false
-apache-airflow-providers-apache-pig = false
-apache-airflow-providers-apache-tinkerpop = false
-apache-airflow-shared-timezones = false
-apache-airflow-providers-apache-iceberg = false
-apache-airflow-breeze = false
-apache-airflow-providers-opsgenie = false
-apache-airflow-providers-apache-livy = false
-apache-airflow-core = false
-apache-airflow-providers-hashicorp = false
-apache-airflow-providers-pagerduty = false
-apache-airflow-providers-datadog = false
-apache-airflow-providers-apache-kafka = false
-apache-airflow-providers-influxdb = false
-apache-airflow-providers-keycloak = false
-apache-airflow-providers-trino = false
-apache-airflow-providers-common-messaging = false
-apache-airflow-providers-standard = false
-apache-airflow-providers-singularity = false
-apache-airflow-providers-common-compat = false
-apache-airflow-ctl-tests = false
-apache-airflow-providers-tableau = false
-apache-airflow-providers-ibm-db2 = false
-apache-airflow-providers-common-sql = false
+apache-airflow-scripts = false
apache-airflow-shared-configuration = false
-apache-airflow-providers-facebook = false
-apache-airflow-providers-ydb = false
-apache-airflow-providers-microsoft-azure = false
+apache-airflow-shared-dagnode = false
+apache-airflow-shared-listeners = false
+apache-airflow-shared-logging = false
+apache-airflow-shared-module-loading = false
+apache-airflow-shared-observability = false
apache-airflow-shared-plugins-manager = false
+apache-airflow-shared-providers-discovery = false
apache-airflow-shared-secrets-backend = false
apache-airflow-shared-secrets-masker = false
-apache-airflow-providers-git = false
+apache-airflow-shared-serialization = false
+apache-airflow-shared-state = false
+apache-airflow-shared-template-rendering = false
+apache-airflow-shared-timezones = false
apache-airflow-task-sdk = false
-apache-airflow-providers-atlassian-jira = false
-apache-airflow-providers-odbc = false
-apache-airflow-providers-postgres = false
-apache-airflow-providers-openai = false
apache-airflow-task-sdk-integration-tests = false
-apache-airflow-providers-smtp = false
-apache-airflow-providers-dingding = false
-apache-airflow-providers-cloudant = false
-apache-airflow-providers-apache-kylin = false
+google-cloud-aiplatform = "2026-08-13T00:00:00Z"
+pydantic-ai-skills = "2026-07-16T00:00:00Z"
+typesafe-sdk = "2026-09-19T00:00:00Z"
[manifest]
members = [
@@ -4656,6 +4657,9 @@ sql = [
{ name = "apache-airflow-providers-common-sql" },
{ name = "sqlglot" },
]
+typesafe = [
+ { name = "typesafe-sdk" },
+]
[package.dev-dependencies]
dev = [
@@ -4708,8 +4712,9 @@ requires-dist = [
{ name = "pypdf", marker = "extra == 'pdf'", specifier = ">=4.0.0" },
{ name = "python-docx", marker = "extra == 'docx'", specifier = ">=1.0.0"
},
{ name = "sqlglot", marker = "extra == 'sql'", specifier = ">=30.0.0" },
+ { name = "typesafe-sdk", marker = "extra == 'typesafe'", specifier =
">=0.6.0" },
]
-provides-extras = ["anthropic", "bedrock", "google", "openai", "mcp",
"code-mode", "shields", "skills", "avro", "parquet", "sql", "common-sql",
"langchain", "llamaindex", "pdf", "docx", "git"]
+provides-extras = ["anthropic", "bedrock", "google", "openai", "typesafe",
"mcp", "code-mode", "shields", "skills", "avro", "parquet", "sql",
"common-sql", "langchain", "llamaindex", "pdf", "docx", "git"]
[package.metadata.requires-dev]
dev = [
@@ -25424,6 +25429,22 @@ wheels = [
{ url =
"https://files.pythonhosted.org/packages/91/25/489751806bf5c95e4007f8e17409199c54d31e49ffbea07c5729b1286c8e/types_toml-0.10.8.20260518-py3-none-any.whl",
hash =
"sha256:0e564ab05f6fde62a315b3b5a9b6624fda569399795d30a37e64705a70459303", size
= 9669, upload-time = "2026-05-18T06:02:15.86Z" },
]
+[[package]]
+name = "typesafe-sdk"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "httpx2" },
+ { name = "pydantic" },
+ { name = "pydantic-core" },
+ { name = "tenacity" },
+ { name = "typing-extensions" },
+]
+sdist = { url =
"https://files.pythonhosted.org/packages/28/e2/ac317772d4d5cfabf3cecdeacd83cc838523e240bb4e1cf256837e6005bb/typesafe_sdk-0.7.0.tar.gz",
hash =
"sha256:930d42fd73cfed6f25bccae488ce0f30f6043eaedd8682ef59734e79ab94a876", size
= 22235, upload-time = "2026-09-18T09:12:30.846Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/d3/2a/16f4163e6d8827ef80b2ed7a400c80688e1ebf571c10f143f792bdafc654/typesafe_sdk-0.7.0-py3-none-any.whl",
hash =
"sha256:c6d2257c4b04b8d4d81cff2b55489b4188795fd44fb40b9cef26ff243b2d1080", size
= 35352, upload-time = "2026-09-18T09:12:29.625Z" },
+]
+
[[package]]
name = "typing-extensions"
version = "4.16.0"