You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by he...@apache.org on 2026/10/15 08:13:01 UTC
(airflow) branch main updated: Let LLMBatchOperator request_params be templated (#74751)
This is an automated email from the ASF dual-hosted git repository.
henry3260 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 fa7c5899a99 Let LLMBatchOperator request_params be templated (#74751)
fa7c5899a99 is described below
commit fa7c5899a9936441180c29912b65aed9d008fbdd
Author: Henry Chen <he...@apache.org>
AuthorDate: Thu Oct 15 16:12:50 2026 +0800
Let LLMBatchOperator request_params be templated (#74751)
``request_params`` carries the same kind of free-form provider body
parameters as ``LLMOperator.agent_params``, which is templated, so a Dag
author who writes a Jinja expression there reasonably expects it to be
rendered. Because the field was missing from ``template_fields``, the
expression reached the provider API verbatim and the request failed there,
with nothing pointing back at the operator parameter as the cause.
---
providers/common/ai/docs/operators/llm_batch.rst | 6 ++++++
.../providers/common/ai/operators/llm_batch.py | 12 +++++++++++-
.../tests/unit/common/ai/operators/test_llm_batch.py | 20 ++++++++++++++++++++
3 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/providers/common/ai/docs/operators/llm_batch.rst b/providers/common/ai/docs/operators/llm_batch.rst
index 18c1b7ac395..90b7745b8ba 100644
--- a/providers/common/ai/docs/operators/llm_batch.rst
+++ b/providers/common/ai/docs/operators/llm_batch.rst
@@ -159,6 +159,12 @@ it means Anthropic Messages keys (``temperature``, ``top_k``, ``metadata``, ``th
The operator does not validate these; an unknown key surfaces as a per-request ``error`` row
from the provider, or as a failed batch when the provider rejects the whole input file.
+``request_params`` is templated: every string inside the dict is rendered, so a value can come
+from a Variable, a param or an upstream XCom. Jinja hands back a string, so a numeric parameter
+written as a template needs ``render_template_as_native_obj=True`` on the Dag. The rendered values
+feed the re-attach fingerprint, so the same rule as ``result_path`` applies: a value that changes
+between attempts of one task instance makes a retry pay for a new batch.
+
.. exampleinclude:: /../../ai/src/airflow/providers/common/ai/example_dags/example_llm_batch.py
:language: python
:start-after: [START howto_operator_llm_batch_provider_params]
diff --git a/providers/common/ai/src/airflow/providers/common/ai/operators/llm_batch.py b/providers/common/ai/src/airflow/providers/common/ai/operators/llm_batch.py
index 9b49d714a1d..816318c2bec 100644
--- a/providers/common/ai/src/airflow/providers/common/ai/operators/llm_batch.py
+++ b/providers/common/ai/src/airflow/providers/common/ai/operators/llm_batch.py
@@ -103,6 +103,9 @@ class LLMBatchOperator(BaseOperator):
conflicting keys. The keys the operator manages (``model``, the
messages, the token cap and the structured-output directive) cannot
be overridden from here.
+ Templated: every string inside the dict is rendered, and Jinja hands back
+ a string, so a numeric parameter written as a template needs
+ ``render_template_as_native_obj=True`` on the Dag.
:param poll_interval: Seconds between status checks. Minimum 30.
:param timeout: Seconds to wait for the batch to reach a terminal state,
measured from submission. Default 24 hours. When a retry re-attaches
@@ -141,7 +144,14 @@ class LLMBatchOperator(BaseOperator):
``"24h"`` is offered by the API today. Ignored by Anthropic.
"""
- template_fields: Sequence[str] = ("requests", "result_path", "system_prompt", "model_id", "llm_conn_id")
+ template_fields: Sequence[str] = (
+ "requests",
+ "result_path",
+ "system_prompt",
+ "model_id",
+ "llm_conn_id",
+ "request_params",
+ )
def __init__(
self,
diff --git a/providers/common/ai/tests/unit/common/ai/operators/test_llm_batch.py b/providers/common/ai/tests/unit/common/ai/operators/test_llm_batch.py
index fbe2a041819..611035a11e8 100644
--- a/providers/common/ai/tests/unit/common/ai/operators/test_llm_batch.py
+++ b/providers/common/ai/tests/unit/common/ai/operators/test_llm_batch.py
@@ -257,6 +257,26 @@ class TestConstructorValidation:
_make_operator(result_path="file:///tmp/x", **kwargs)
+class TestTemplateFields:
+ def test_template_fields(self):
+ assert set(LLMBatchOperator.template_fields) == {
+ "requests",
+ "result_path",
+ "system_prompt",
+ "model_id",
+ "llm_conn_id",
+ "request_params",
+ }
+
+ def test_request_params_values_are_rendered(self):
+ """``request_params`` is the batch counterpart of ``LLMOperator.agent_params``, so it is
+ templated the same way: the nested walk renders each string leaf and leaves it a string,
+ because the keys are provider body parameters whose types the operator does not know."""
+ op = _make_operator(result_path="file:///tmp/x", request_params={"temperature": "{{ params.temp }}"})
+ op.render_template_fields({"params": {"temp": "0.2"}})
+ assert op.request_params == {"temperature": "0.2"}
+
+
class TestNormalizeRequests:
def test_strings_and_dicts_are_accepted(self):
assert LLMBatchOperator._normalize_requests(["a", {"prompt": "b", "params": {"x": 1}}]) == [