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


##########
providers/common/ai/src/airflow/providers/common/ai/operators/llm.py:
##########
@@ -307,7 +311,10 @@ def execute(self, context: Context) -> Any:
         log_run_summary(self.log, result)
         output = result.output
 
+        self._push_xcom(context, "usage", format_usage_for_xcom(result.usage))

Review Comment:
   This only runs once `run_agent_sync` returns, so a run that raises (say 
`UsageLimitExceeded` from a `cost_limit`) pushes no `usage` at all. 
`AgentOperator` passes its own `RunUsage` into `run_sync` and pushes the 
partial usage from `_report_failed_run`, and its docstring promises that to 
`all_done` tasks and failure callbacks. `run_agent_sync` already forwards 
`**run_kwargs`, so could this pass `usage=RunUsage()` and push it in an 
`except` before re-raising? Otherwise the cost-capped failure, the run where 
cost matters most, is the one the Model tab can't show. If you'd rather keep it 
success-only, the docs line below should say so.



##########
providers/common/ai/tests/unit/common/ai/operators/test_llm.py:
##########
@@ -403,7 +404,46 @@ def 
test_hand_built_context_skips_the_decision_push_with_a_warning(
             output = op.execute(context)
 
         assert Summary.model_validate(output).text == "t"
-        assert "the decision record was not pushed to XCom" in caplog.text
+        assert "'decision' was not pushed to XCom" in caplog.text
+
+    @patch("airflow.providers.common.ai.operators.llm.PydanticAIHook", 
autospec=True)
+    def test_resolved_model_name_pushed_to_xcom(self, mock_hook_cls, 
make_mock_run_result):
+        """The model that actually answered is exposed on its own namespaced 
XCom key."""
+        mock_agent = MagicMock(spec=["run_sync"])
+        mock_agent.run_sync.return_value = self._result(make_mock_run_result, 
Summary(text="t"), None)
+        mock_hook_cls.get_hook.return_value.create_agent.return_value = 
mock_agent
+        op = LLMOperator(task_id="t", prompt="p", llm_conn_id="c", 
output_type=Summary)
+        context = MagicMock(spec=dict)
+
+        op.execute(context)
+
+        pushes = {
+            c.kwargs["key"]: c.kwargs["value"] for c in 
context["task_instance"].xcom_push.call_args_list
+        }
+        assert pushes[MODEL_NAME_XCOM_KEY] == "jev-1.13.0"
+
+    @patch("airflow.providers.common.ai.operators.llm.PydanticAIHook", 
autospec=True)
+    def test_usage_pushed_to_xcom(self, mock_hook_cls, make_mock_run_result):
+        """Token usage/cost is exposed on the usage XCom key, the same shape 
AgentOperator uses."""
+        mock_agent = MagicMock(spec=["run_sync"])
+        mock_agent.run_sync.return_value = self._result(make_mock_run_result, 
Summary(text="t"), None)
+        mock_hook_cls.get_hook.return_value.create_agent.return_value = 
mock_agent
+        op = LLMOperator(task_id="t", prompt="p", llm_conn_id="c", 
output_type=Summary)
+        context = MagicMock(spec=dict)
+
+        op.execute(context)
+
+        pushes = {
+            c.kwargs["key"]: c.kwargs["value"] for c in 
context["task_instance"].xcom_push.call_args_list
+        }
+        assert pushes["usage"] == {

Review Comment:
   The fixture's usage is all zeros with `cost=None`, so this only pins the key 
and `requests: 1`. Passing a cost to `make_mock_run_result` and asserting the 
stringified value (like `test_usage_cost_is_stringified_on_xcom` in 
test_agent.py) would catch more. The setup is identical to 
`test_resolved_model_name_pushed_to_xcom` above, so the two could also be one 
test.



##########
providers/common/ai/docs/operators/llm.rst:
##########
@@ -284,3 +284,6 @@ After each LLM call, the operator logs a summary with model 
name, token usage,
 and request count at INFO level. At DEBUG level, the LLM output is also logged
 (truncated to 500 characters). See :ref:`AgentOperator logging 
<howto/operator:agent>`
 for details on the log format.
+
+The same request/token/cost counts are also pushed to XCom under the ``usage``

Review Comment:
   The Scope bullet in `observability.rst` still says the `run_id` / `usage` 
XComs come only from `AgentOperator` and `@task.agent`. With this PR 
`LLMOperator` and `@task.llm` push `usage` too, so that sentence needs updating 
(`run_id` stays agent-only). The `LLMOperator` class docstring could also 
mention the `usage` key the way `AgentOperator`'s does.



-- 
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