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 d46d97fa8d5 Note the unparseable-output case in OpenAIResponseOperator
XCom docs (#74519)
d46d97fa8d5 is described below
commit d46d97fa8d5082e73dea68610b514aadc6346419
Author: Yash jain <[email protected]>
AuthorDate: Thu Nov 5 20:21:08 2026 +0530
Note the unparseable-output case in OpenAIResponseOperator XCom docs
(#74519)
The class docstring said a structured response that fails the task still
records its id and token usage. That does not hold when the SDK cannot
parse the output, because it raises before returning a response object.
The test for a rejected response now also covers an incomplete response,
which pins that both keys are pushed before the status check.
---
.../airflow/providers/openai/operators/openai.py | 4 ++-
.../tests/unit/openai/operators/test_openai.py | 29 ++++++++++++++++++----
2 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/providers/openai/src/airflow/providers/openai/operators/openai.py
b/providers/openai/src/airflow/providers/openai/operators/openai.py
index f761442bc37..202d311867e 100644
--- a/providers/openai/src/airflow/providers/openai/operators/openai.py
+++ b/providers/openai/src/airflow/providers/openai/operators/openai.py
@@ -205,7 +205,9 @@ class OpenAIResponseOperator(BaseOperator):
only reflects the current attempt rather than a silently under-reported
total across
retries. With ``text_format`` set, both keys are pushed before the
structured output is
checked, so a response that then fails the task still records its id and
token usage.
- Both XCom pushes are skipped when ``do_xcom_push=False``.
+ The exception is output the SDK cannot parse, such as JSON cut off
mid-object: it raises
+ before returning a response object, so there is no id or usage to push and
neither key
+ is recorded. Both XCom pushes are skipped when ``do_xcom_push=False``.
"""
template_fields: Sequence[str] = (
diff --git a/providers/openai/tests/unit/openai/operators/test_openai.py
b/providers/openai/tests/unit/openai/operators/test_openai.py
index dafb984ffc8..39381a117f5 100644
--- a/providers/openai/tests/unit/openai/operators/test_openai.py
+++ b/providers/openai/tests/unit/openai/operators/test_openai.py
@@ -918,20 +918,39 @@ class TestOpenAIResponseOperatorStructuredOutput:
key="usage", value={**usage.model_dump(mode="json"),
"try_number": 2}
)
- def test_rejected_response_still_records_id_and_usage(self):
+ @pytest.mark.parametrize(
+ ("response_fields", "expected_error"),
+ [
+ pytest.param(
+ {"refusal": "I cannot help with that request."},
+ "did not return a structured output",
+ id="refusal",
+ ),
+ pytest.param(
+ {
+ "output_parsed": _StructuredPerson(name="Alice"),
+ "status": "incomplete",
+ "incomplete_details":
IncompleteDetails(reason="max_output_tokens"),
+ },
+ "did not complete",
+ id="incomplete",
+ ),
+ ],
+ )
+ def test_rejected_response_still_records_id_and_usage(self,
response_fields, expected_error):
# The API call behind a rejected response was still billed, so its id
and token usage
- # are pushed before the structured output is checked.
+ # are pushed before the status and the structured output are checked.
operator, hook = self._operator()
usage = _build_usage()
hook.parse_response.return_value = _build_parsed_response(
- response_id="resp_refused", refusal="I cannot help with that
request.", usage=usage
+ response_id="resp_rejected", usage=usage, **response_fields
)
context = _build_execute_context()
- with pytest.raises(ValueError, match="did not return a structured
output"):
+ with pytest.raises(ValueError, match=expected_error):
operator.execute(context)
- context["ti"].xcom_push.assert_any_call(key="response_id",
value="resp_refused")
+ context["ti"].xcom_push.assert_any_call(key="response_id",
value="resp_rejected")
context["ti"].xcom_push.assert_any_call(
key="usage", value={**usage.model_dump(mode="json"), "try_number":
1}
)