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


##########
providers/common/ai/tests/unit/common/ai/conftest.py:
##########
@@ -104,6 +106,46 @@ def _make(output, *, cost=None):
     return _make
 
 
[email protected]
+def make_reporting_agent(make_mock_run_result):
+    """Factory fixture creating a mock agent whose run spends a known usage, 
as ``(agent, expected_pushes)``.
+
+    ``expected_pushes`` maps each XCom key the run must report (``usage``, 
``run_id``, and the
+    model name unless ``model_name`` is ``None``) to its value, for a task 
instance with id ``ti-1``.
+    """
+
+    def _make(output, *, model_name):
+        result = make_mock_run_result(output)
+        result.run_id = "ti-1"

Review Comment:
   Since `result.run_id` is fixed to `"ti-1"` here and `_run_sync` ignores the 
`run_id=` it's called with, the four new subclass XCom tests still pass if 
`_run_llm` stops forwarding `run_id` (or forwards the wrong key). Same for the 
identity stamp: `instrument` is a bare MagicMock, so 
`stamp_identity_on_agent_spans` is a no-op, and moving the stamp back into 
`LLMOperator.execute` would keep every subclass test green. Could `_run_sync` 
set `result.run_id = kwargs["run_id"]`, and one subclass test patch 
`stamp_identity_on_agent_spans` (autospec) and assert it got the agent?



##########
providers/common/ai/tests/unit/common/ai/plugins/test_model_panel.py:
##########
@@ -70,8 +78,16 @@ def 
test_applies_to_scopes_to_operators_that_publish_model_name(self):
         operator_names = app["applies_to"]["operator_name"]
         assert set(operator_names) == {

Review Comment:
   This catches renames. A new `LLMOperator` subclass, though, would publish 
the model name through `_run_llm` and still not get the tab, and this test 
wouldn't notice. Could it build the expected set from the in-tree `LLMOperator` 
subclasses (recursively, plus `AgentOperator`) instead of a hand list?



##########
providers/common/ai/src/airflow/providers/common/ai/operators/llm.py:
##########
@@ -497,6 +462,60 @@ def _run_llm(
         user_prompt: Any,
         *,
         usage_limits: UsageLimits | None,
+    ) -> AgentRunResult[Any]:
+        """
+        Run the agent and publish the run's ``usage``, ``run_id`` and model 
name on XCom.
+
+        Every ``execute`` of this operator and its subclasses runs the agent 
through here, so they
+        all report the same run metadata.
+        """
+        # A per-attempt key (the task-instance id on Airflow 3, which is 
regenerated on each
+        # retry; dag/run/task/map/try on Airflow 2) ties this run to its GenAI 
trace, the same
+        # join key AgentOperator uses. A hand-built context with no task 
instance runs untracked.
+        ti = self._resolve_task_instance(context)
+        run_kwargs: dict[str, Any] = {}
+        if ti is not None:
+            stamp_identity_on_agent_spans(agent, 
build_run_identity_attributes(ti))
+            run_kwargs["run_id"] = make_task_instance_run_key(ti)
+
+        attempt_usage = RunUsage()
+        try:
+            result = self._run_agent_within_budget(
+                context, agent, user_prompt, usage_limits=usage_limits, 
run_usage=attempt_usage, **run_kwargs
+            )
+        except BaseException:
+            # A run that raises partway through (e.g. a cost_limit-triggered 
UsageLimitExceeded)
+            # still reports what it spent: the capped run is the one cost 
tracking most needs.
+            try:
+                log_run_usage(self.log, attempt_usage, outcome="failed")
+            except Exception:
+                self.log.warning("Failed to log partial usage for the failed 
run", exc_info=True)
+            try:
+                self._push_xcom(context, "usage", 
format_usage_for_xcom(attempt_usage))
+            except Exception:
+                self.log.warning("Failed to push usage XCom for the failed 
run", exc_info=True)
+            if ti is not None:
+                try:
+                    self._push_xcom(context, "run_id", run_kwargs["run_id"])
+                except Exception:
+                    self.log.warning("Failed to push run_id XCom for the 
failed run", exc_info=True)
+            raise
+
+        self._push_xcom(context, "usage", format_usage_for_xcom(attempt_usage))
+        if ti is not None:
+            self._push_xcom(context, "run_id", result.run_id)
+        model_name = ModelConfidence.from_result(result).model
+        if model_name is not None:
+            self._push_xcom(context, MODEL_NAME_XCOM_KEY, model_name)
+        return result
+
+    def _run_agent_within_budget(
+        self,
+        context: Context,
+        agent: Agent[Any, Any],
+        user_prompt: Any,
+        *,
+        usage_limits: UsageLimits | None,
         run_usage: RunUsage | None = None,

Review Comment:
   `_run_llm` is the only caller now and always passes `run_usage`, so this 
could be a required `run_usage: RunUsage` and the two `if run_usage is not 
None` guards (and the "when given" in the docstring) can go.



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