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


##########
providers/common/ai/src/airflow/providers/common/ai/operators/llm_schema_compare.py:
##########
@@ -313,8 +324,23 @@ def execute(self, context: Context) -> dict[str, Any]:
         self.log.info("Running LLM schema comparison...")
         result = agent.run_sync(self.prompt, usage_limits=self.usage_limits)
         log_run_summary(self.log, result)
+        output = result.output
+
+        if self.require_approval:
+            severity_counts = Counter(mismatch.severity for mismatch in 
output.mismatches)
+            summary = ", ".join(
+                f"{severity_counts[severity]} {severity}"
+                for severity in ("critical", "warning", "info")
+                if severity_counts[severity]
+            )
+            body = (
+                f"Compatible: {output.compatible}"
+                + (f" (mismatches: {summary})" if summary else "")
+                + f"\n\n```\nPrompt: 
{self.prompt}\n\n{output.model_dump_json(indent=2)}\n```"
+            )
+            self.defer_for_approval(context, output, body=body)  # type: 
ignore[misc]

Review Comment:
   `defer_for_approval` always raises, so on the approval path the `Schema 
comparison result` log two lines below never runs, and `execute_complete` 
doesn't log it either. The result then only exists in the HITL body, never in 
the task log. `LLMSQLOperator` logs the generated SQL before it defers -- 
moving the log above this block would keep the two paths consistent.



##########
providers/common/ai/src/airflow/providers/common/ai/operators/llm_schema_compare.py:
##########
@@ -126,9 +134,9 @@ def __init__(
         system_prompt: str = DEFAULT_SYSTEM_PROMPT,
         **kwargs: Any,
     ) -> None:
-        kwargs.pop("output_type", None)
-        if kwargs.get("require_approval"):
-            raise ValueError("require_approval=True is not supported by 
LLMSchemaCompareOperator.")
+        kwargs["output_type"] = SchemaCompareResult
+        # execute() always returns a dict, so the approval resume path must too
+        kwargs["serialize_output"] = True

Review Comment:
   With `allow_modifications=True` this contract doesn't quite hold. 
`LLMOperator.execute_complete` goes through `rehydrate_pydantic_output`, which 
returns the raw string unchanged when validation fails, so a reviewer edit that 
isn't valid `SchemaCompareResult` JSON lands in XCom as a `str`. I ran the 
cases: `{"compatible": true, "mismatches": []}` (missing `summary`) and `looks 
fine to me` both come back as strings, while the unmodified approve path 
correctly gives a dict. The conditional example in the docs does 
`comparison_result["compatible"]`, which would raise `TypeError: string indices 
must be integers` on that value.
   
   `LLMSQLOperator.execute_complete` overrides and re-validates whenever 
`output != generated_output`; the same guard here (fail the task when the 
edited JSON doesn't parse into `SchemaCompareResult`) would keep the dict 
promise.



##########
providers/common/ai/docs/operators/llm_schema_compare.rst:
##########
@@ -114,6 +114,23 @@ The callable may also return a non-empty 
``Sequence[UserContent]`` for
 multimodal inputs -- see
 :ref:`@task.agent multimodal prompts <howto/operator:agent-multimodal>`.
 
+Human-in-the-Loop Approval
+--------------------------
+
+Set ``require_approval=True`` to pause the task after the comparison and wait
+for a human reviewer to approve the result before it is returned. The review
+body shows the compatibility verdict, a mismatch severity summary, and the
+full result JSON. Rejecting the review, or letting ``approval_timeout``
+expire, fails the task:
+
+.. exampleinclude:: 
/../../ai/src/airflow/providers/common/ai/example_dags/example_llm_schema_compare.py
+    :language: python
+    :start-after: [START howto_operator_llm_schema_compare_approval]
+    :end-before: [END howto_operator_llm_schema_compare_approval]
+
+``approval_timeout``, ``allow_modifications``, and the rest of the approval

Review Comment:
   `llm_branch.rst` spells out that `require_approval=True` needs a string 
prompt, and that a callable returning `Sequence[UserContent]` raises 
`TypeError` before the LLM call. The same constraint applies here 
(`test_execute_rejects_sequence_prompt_with_require_approval` covers it), and 
the paragraph directly above this new section tells readers the callable may 
return a `Sequence[UserContent]`. Worth carrying the same note over.



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