kaxil commented on code in PR #72151:
URL: https://github.com/apache/airflow/pull/72151#discussion_r3968176161


##########
providers/openai/src/airflow/providers/openai/operators/openai.py:
##########
@@ -83,25 +83,34 @@ class OpenAIResponseOperator(BaseOperator):
     """
     Operator that generates a model response using the OpenAI Responses API.
 
-    The operator is synchronous and returns the response's aggregated output 
text. For
-    ``previous_response_id`` chaining, ``background=True`` responses, or 
access to the full
-    structured response, use 
:class:`~airflow.providers.openai.hooks.openai.OpenAIHook` directly.
+    The operator is synchronous and returns the response's aggregated output 
text; the
+    response id is also pushed to XCom (see below), so a downstream task can 
pick it up
+    for ``previous_response_id`` chaining without going through the hook. For
+    ``background=True`` responses, or access to the full structured response, 
use
+    :class:`~airflow.providers.openai.hooks.openai.OpenAIHook` directly.
 
     :param conn_id: The OpenAI connection ID to use.
     :param input_text: The input prompt for the model. This can be a string or 
a structured list of
         input items.
     :param model: The OpenAI model to use.
     :param response_kwargs: Additional keyword arguments to pass to the OpenAI 
``create_response``
         method (for example ``instructions``, ``tools``, ``conversation`` or 
``previous_response_id``).
+        Templated, so values (e.g. ``previous_response_id``) may reference 
upstream XCom.
 
     .. seealso::
         For more information on how to use this operator, take a look at the 
guide:
         :ref:`howto/operator:OpenAIResponseOperator`
         For possible options, see:
         https://platform.openai.com/docs/api-reference/responses/create
+
+    ``execute`` also pushes two XCom keys: ``response_id`` (the response's ID, 
usable as
+    a downstream call's ``previous_response_id``) and ``usage`` (the flattened 
response

Review Comment:
   `ResponseUsage.model_dump()` is nested rather than flattened: on the 
`openai>=2.37.0` floor it returns `{'input_tokens': 5, 'input_tokens_details': 
{'cached_tokens': 1}, 'output_tokens': 7, 'output_tokens_details': 
{'reasoning_tokens': 2}, 'total_tokens': 12}`. Someone who trusts "flattened" 
reaches for `usage["cached_tokens"]` and gets a `KeyError`. The nesting also 
matters for the pricing sentence: cached input tokens bill at a reduced rate, 
so `input_tokens_details.cached_tokens` is exactly the field you need to price 
a run correctly. Showing the literal dict here and in the rst would settle both.



##########
providers/openai/src/airflow/providers/openai/operators/openai.py:
##########
@@ -83,25 +83,34 @@ class OpenAIResponseOperator(BaseOperator):
     """
     Operator that generates a model response using the OpenAI Responses API.
 
-    The operator is synchronous and returns the response's aggregated output 
text. For
-    ``previous_response_id`` chaining, ``background=True`` responses, or 
access to the full
-    structured response, use 
:class:`~airflow.providers.openai.hooks.openai.OpenAIHook` directly.
+    The operator is synchronous and returns the response's aggregated output 
text; the
+    response id is also pushed to XCom (see below), so a downstream task can 
pick it up
+    for ``previous_response_id`` chaining without going through the hook. For
+    ``background=True`` responses, or access to the full structured response, 
use
+    :class:`~airflow.providers.openai.hooks.openai.OpenAIHook` directly.
 
     :param conn_id: The OpenAI connection ID to use.
     :param input_text: The input prompt for the model. This can be a string or 
a structured list of
         input items.
     :param model: The OpenAI model to use.
     :param response_kwargs: Additional keyword arguments to pass to the OpenAI 
``create_response``
         method (for example ``instructions``, ``tools``, ``conversation`` or 
``previous_response_id``).
+        Templated, so values (e.g. ``previous_response_id``) may reference 
upstream XCom.
 
     .. seealso::
         For more information on how to use this operator, take a look at the 
guide:
         :ref:`howto/operator:OpenAIResponseOperator`
         For possible options, see:
         https://platform.openai.com/docs/api-reference/responses/create
+
+    ``execute`` also pushes two XCom keys: ``response_id`` (the response's ID, 
usable as
+    a downstream call's ``previous_response_id``) and ``usage`` (the flattened 
response
+    usage, or ``None`` when the API omits it). ``usage`` reports token counts 
only --
+    the OpenAI response carries no cost field, so pricing a run means 
multiplying these
+    counts by your own per-token rate.
     """
 
-    template_fields: Sequence[str] = ("input_text",)
+    template_fields: Sequence[str] = ("input_text", "response_kwargs")

Review Comment:
   `OpenAIResponseOperator` shipped in 1.8.0, so widening this tuple changes 
behaviour for Dags that already pass `response_kwargs`. The templater recurses 
into every string in the dict and `template_undefined` defaults to 
`StrictUndefined` on both the 2.11 floor and 3.x, so `instructions="Fill in 
Hello {{ customer_name }}"` now hard-fails, and `instructions="Refer to the run 
as {{ ds }}"` silently sends a different prompt to a paid API on a run that 
still goes green. Not asking to drop the templating, it is what makes the 
chaining work, but could it get a `.. note::` at the top of 
`providers/openai/docs/changelog.rst` and a mention of `{% raw %}` as the 
escape in the howto? #69506 added that kind of note in this provider and the 
release manager folded it into 1.8.2.



##########
providers/openai/src/airflow/providers/openai/operators/openai.py:
##########
@@ -83,25 +83,34 @@ class OpenAIResponseOperator(BaseOperator):
     """
     Operator that generates a model response using the OpenAI Responses API.
 
-    The operator is synchronous and returns the response's aggregated output 
text. For
-    ``previous_response_id`` chaining, ``background=True`` responses, or 
access to the full
-    structured response, use 
:class:`~airflow.providers.openai.hooks.openai.OpenAIHook` directly.
+    The operator is synchronous and returns the response's aggregated output 
text; the
+    response id is also pushed to XCom (see below), so a downstream task can 
pick it up
+    for ``previous_response_id`` chaining without going through the hook. For
+    ``background=True`` responses, or access to the full structured response, 
use
+    :class:`~airflow.providers.openai.hooks.openai.OpenAIHook` directly.
 
     :param conn_id: The OpenAI connection ID to use.
     :param input_text: The input prompt for the model. This can be a string or 
a structured list of
         input items.
     :param model: The OpenAI model to use.
     :param response_kwargs: Additional keyword arguments to pass to the OpenAI 
``create_response``
         method (for example ``instructions``, ``tools``, ``conversation`` or 
``previous_response_id``).
+        Templated, so values (e.g. ``previous_response_id``) may reference 
upstream XCom.

Review Comment:
   One knock-on worth a line in the PR body: rendered template fields are 
persisted to `rendered_task_instance_fields` on every run, so the full 
`instructions` text and any tool schemas now land in the metadata DB per run, 
and that write is not gated by `do_xcom_push`. A dedicated 
`previous_response_id` parameter in `template_fields` would give the same 
chaining without templating the whole prompt payload. Was the broader field a 
deliberate call so `tools` and `instructions` can be templated too?



##########
providers/openai/tests/unit/openai/operators/test_openai.py:
##########
@@ -89,20 +90,84 @@ def test_openai_response_operator_execute():
         response_kwargs={"instructions": "Be concise.", 
"previous_response_id": "resp_prev"},
     )
     mock_hook_instance = Mock(spec=OpenAIHook)
-    mock_hook_instance.create_response.return_value = Mock(
-        spec=Response, output_text="haiku text", id="resp_123", 
status="completed"
+    mock_usage = Mock(spec=ResponseUsage)
+    mock_usage.model_dump.return_value = {"input_tokens": 5, "output_tokens": 
7}

Review Comment:
   `ResponseUsage` is a plain pydantic value object, so building a real one 
here instead of stubbing `model_dump` would pin the actual nested shape and 
stop the docstring and the test agreeing on a shape the SDK never returns. 
While you are in this file, the templating test below only exercises 
`response_kwargs`: reverting `template_fields` to drop `input_text` still 
passes every test in the file.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to