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}}]) == [