This is an automated email from the ASF dual-hosted git repository.

shahar1 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 6df6cb32ea5 Add SnowflakeCortexAgentOperator to Snowflake Provider 
(#69939)
6df6cb32ea5 is described below

commit 6df6cb32ea5d2ae262ee342be4532039023e7a0d
Author: SameerMesiah97 <[email protected]>
AuthorDate: Mon Jul 27 13:57:03 2026 +0100

    Add SnowflakeCortexAgentOperator to Snowflake Provider (#69939)
    
    * Introduce SnowflakeCortexAgentOperator to execute Snowflake Cortex Agents
    using SnowflakeCortexAgentHook. Add unit tests, documentation, and an
    example DAG demonstrating how to invoke a Cortex Agent from an Airflow DAG.
    
    * Add templated fields and correct import for BaseOperator.
---
 .../docs/operators/snowflake_cortex_agent.rst      |  66 ++++++++++
 providers/snowflake/provider.yaml                  |   2 +
 .../providers/snowflake/get_provider_info.py       |   2 +
 .../snowflake/operators/snowflake_cortex_agent.py  | 140 +++++++++++++++++++++
 .../snowflake/example_snowflake_cortex_agent.py    |  66 ++++++++++
 .../operators/test_snowflake_cortex_agent.py       | 105 ++++++++++++++++
 6 files changed, 381 insertions(+)

diff --git a/providers/snowflake/docs/operators/snowflake_cortex_agent.rst 
b/providers/snowflake/docs/operators/snowflake_cortex_agent.rst
new file mode 100644
index 00000000000..438519e6870
--- /dev/null
+++ b/providers/snowflake/docs/operators/snowflake_cortex_agent.rst
@@ -0,0 +1,66 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+..   http://www.apache.org/licenses/LICENSE-2.0
+
+.. Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. _howto/operator:SnowflakeCortexAgentOperator:
+
+SnowflakeCortexAgentOperator
+============================
+
+Use the 
:class:`~airflow.providers.snowflake.operators.snowflake_cortex_agent.SnowflakeCortexAgentOperator`
+to execute `Snowflake Cortex Agents 
<https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents>`__.
+
+The operator wraps the Snowflake Cortex Agent Run API and executes an existing
+Cortex Agent. It returns the JSON response payload from the agent, allowing
+responses to be consumed by downstream Airflow tasks through XCom.
+
+Prerequisite Tasks
+^^^^^^^^^^^^^^^^^^
+
+To use this operator, you must do a few things:
+
+  * Install the provider package via **pip**.
+
+    .. code-block:: bash
+
+      pip install 'apache-airflow-providers-snowflake'
+
+    Detailed information is available for :doc:`Installation 
<apache-airflow:installation/index>`.
+
+  * :doc:`Setup a Snowflake Connection </connections/snowflake>`.
+
+  * Create a Snowflake Cortex Agent. See the
+    `Snowflake Cortex Agents documentation 
<https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents>`__.
+
+Using the Operator
+^^^^^^^^^^^^^^^^^^
+
+Use the ``snowflake_conn_id`` argument to specify the connection used. If not
+specified, ``snowflake_default`` will be used.
+
+An example usage of the ``SnowflakeCortexAgentOperator`` is as follows:
+
+.. exampleinclude:: 
/../../snowflake/tests/system/snowflake/example_snowflake_cortex_agent.py
+    :language: python
+    :start-after: [START howto_operator_snowflake_cortex_agent]
+    :end-before: [END howto_operator_snowflake_cortex_agent]
+    :dedent: 4
+
+.. note::
+
+   Parameters passed to the operator take precedence over the corresponding
+   values configured in the Airflow connection metadata, such as ``database``,
+   ``schema`` and ``role``.
diff --git a/providers/snowflake/provider.yaml 
b/providers/snowflake/provider.yaml
index 557f0246feb..0d1742567c9 100644
--- a/providers/snowflake/provider.yaml
+++ b/providers/snowflake/provider.yaml
@@ -120,6 +120,7 @@ integrations:
       - /docs/apache-airflow-providers-snowflake/operators/snowflake.rst
       - /docs/apache-airflow-providers-snowflake/operators/snowpark.rst
       - 
/docs/apache-airflow-providers-snowflake/operators/snowpark_containers.rst
+      - 
/docs/apache-airflow-providers-snowflake/operators/snowflake_cortex_agent.rst
     logo: /docs/integration-logos/Snowflake.png
     tags: [service]
 
@@ -129,6 +130,7 @@ operators:
       - airflow.providers.snowflake.operators.snowflake
       - airflow.providers.snowflake.operators.snowpark
       - airflow.providers.snowflake.operators.snowpark_containers
+      - airflow.providers.snowflake.operators.snowflake_cortex_agent
 
 task-decorators:
   - class-name: airflow.providers.snowflake.decorators.snowpark.snowpark_task
diff --git 
a/providers/snowflake/src/airflow/providers/snowflake/get_provider_info.py 
b/providers/snowflake/src/airflow/providers/snowflake/get_provider_info.py
index 7e20af8004e..8c93b0f9e49 100644
--- a/providers/snowflake/src/airflow/providers/snowflake/get_provider_info.py
+++ b/providers/snowflake/src/airflow/providers/snowflake/get_provider_info.py
@@ -34,6 +34,7 @@ def get_provider_info():
                     
"/docs/apache-airflow-providers-snowflake/operators/snowflake.rst",
                     
"/docs/apache-airflow-providers-snowflake/operators/snowpark.rst",
                     
"/docs/apache-airflow-providers-snowflake/operators/snowpark_containers.rst",
+                    
"/docs/apache-airflow-providers-snowflake/operators/snowflake_cortex_agent.rst",
                 ],
                 "logo": "/docs/integration-logos/Snowflake.png",
                 "tags": ["service"],
@@ -46,6 +47,7 @@ def get_provider_info():
                     "airflow.providers.snowflake.operators.snowflake",
                     "airflow.providers.snowflake.operators.snowpark",
                     
"airflow.providers.snowflake.operators.snowpark_containers",
+                    
"airflow.providers.snowflake.operators.snowflake_cortex_agent",
                 ],
             }
         ],
diff --git 
a/providers/snowflake/src/airflow/providers/snowflake/operators/snowflake_cortex_agent.py
 
b/providers/snowflake/src/airflow/providers/snowflake/operators/snowflake_cortex_agent.py
new file mode 100644
index 00000000000..f00cde01a9a
--- /dev/null
+++ 
b/providers/snowflake/src/airflow/providers/snowflake/operators/snowflake_cortex_agent.py
@@ -0,0 +1,140 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from collections.abc import Sequence
+from functools import cached_property
+from typing import TYPE_CHECKING, Any
+
+from airflow.providers.common.compat.sdk import BaseOperator
+from airflow.providers.snowflake.hooks.snowflake_cortex_agent import 
SnowflakeCortexAgentHook
+
+if TYPE_CHECKING:
+    from airflow.providers.common.compat.sdk import Context
+
+
+class SnowflakeCortexAgentOperator(BaseOperator):
+    """
+    Execute a Snowflake Cortex Agent.
+
+    :param database: Database containing the Cortex Agent.
+    :param schema: Schema containing the Cortex Agent.
+    :param agent_name: Name of the Cortex Agent.
+    :param messages: Conversation messages to send to the agent.
+    :param thread_id: Existing conversation thread identifier. Optional.
+        Defaults to ``None``.
+    :param parent_message_id: Parent message identifier within the specified
+        thread. Required when ``thread_id`` is provided. Defaults to ``None``.
+    :param tool_choice: Tool selection configuration. Optional. Defaults to
+        ``None``.
+    :param models: Model configuration. Optional. Defaults to ``None``.
+    :param instructions: Agent instruction overrides. Optional. Defaults to
+        ``None``.
+    :param orchestration: Orchestration configuration. Optional. Defaults to
+        ``None``.
+    :param tools: Additional tools available to the agent. Optional. Defaults
+        to ``None``.
+    :param tool_resources: Configuration for tools specified in ``tools``.
+        Optional. Defaults to ``None``.
+    :param timeout: Maximum time in seconds to wait for the request to
+        complete. Defaults to ``600``.
+    :param snowflake_conn_id: Snowflake connection ID. Defaults to
+        ``snowflake_default``.
+    """
+
+    template_fields: Sequence[str] = (
+        "database",
+        "schema",
+        "agent_name",
+        "messages",
+    )
+
+    template_fields_renderers = {
+        "messages": "json",
+    }
+
+    ui_color = "#29B5E8"
+
+    def __init__(
+        self,
+        *,
+        database: str,
+        schema: str,
+        agent_name: str,
+        messages: list[dict[str, Any]],
+        thread_id: int | None = None,
+        parent_message_id: int | None = None,
+        tool_choice: dict[str, Any] | None = None,
+        models: dict[str, Any] | None = None,
+        instructions: dict[str, Any] | None = None,
+        orchestration: dict[str, Any] | None = None,
+        tools: list[dict[str, Any]] | None = None,
+        tool_resources: dict[str, Any] | None = None,
+        timeout: int | None = 600,
+        snowflake_conn_id: str = "snowflake_default",
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+
+        self.database = database
+        self.schema = schema
+        self.agent_name = agent_name
+        self.messages = messages
+        self.thread_id = thread_id
+        self.parent_message_id = parent_message_id
+        self.tool_choice = tool_choice
+        self.models = models
+        self.instructions = instructions
+        self.orchestration = orchestration
+        self.tools = tools
+        self.tool_resources = tool_resources
+        self.timeout = timeout
+        self.snowflake_conn_id = snowflake_conn_id
+
+    @cached_property
+    def hook(self) -> SnowflakeCortexAgentHook:
+        """Return the Snowflake Cortex Agent hook."""
+        return SnowflakeCortexAgentHook(
+            snowflake_conn_id=self.snowflake_conn_id,
+        )
+
+    def execute(self, context: Context) -> dict[str, Any]:
+        """Execute the Snowflake Cortex Agent."""
+        self.log.info(
+            "Executing Snowflake Cortex Agent '%s.%s.%s'.",
+            self.database,
+            self.schema,
+            self.agent_name,
+        )
+
+        return self.hook.run_agent(
+            database=self.database,
+            schema=self.schema,
+            agent_name=self.agent_name,
+            messages=self.messages,
+            thread_id=self.thread_id,
+            parent_message_id=self.parent_message_id,
+            tool_choice=self.tool_choice,
+            models=self.models,
+            instructions=self.instructions,
+            orchestration=self.orchestration,
+            tools=self.tools,
+            tool_resources=self.tool_resources,
+            timeout=self.timeout,
+        )
diff --git 
a/providers/snowflake/tests/system/snowflake/example_snowflake_cortex_agent.py 
b/providers/snowflake/tests/system/snowflake/example_snowflake_cortex_agent.py
new file mode 100644
index 00000000000..35a93bfadfc
--- /dev/null
+++ 
b/providers/snowflake/tests/system/snowflake/example_snowflake_cortex_agent.py
@@ -0,0 +1,66 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Example use of SnowflakeCortexAgentOperator.
+"""
+
+from __future__ import annotations
+
+from datetime import datetime
+
+from airflow import DAG
+from airflow.providers.snowflake.operators.snowflake_cortex_agent import (
+    SnowflakeCortexAgentOperator,
+)
+
+SNOWFLAKE_CONN_ID = "my_snowflake_conn"
+DAG_ID = "example_snowflake_cortex_agent"
+
+with DAG(
+    DAG_ID,
+    start_date=datetime(2024, 1, 1),
+    schedule="@once",
+    default_args={"snowflake_conn_id": SNOWFLAKE_CONN_ID},
+    tags=["example"],
+    catchup=False,
+) as dag:
+    # [START howto_operator_snowflake_cortex_agent]
+    run_agent = SnowflakeCortexAgentOperator(
+        task_id="run_agent",
+        database="DEFAULT_DATABASE",
+        schema="DEFAULT_SCHEMA",
+        agent_name="default_agent",
+        messages=[
+            {
+                "role": "user",
+                "content": [
+                    {
+                        "type": "text",
+                        "text": "What can you help me with?",
+                    }
+                ],
+            }
+        ],
+    )
+    # [END howto_operator_snowflake_cortex_agent]
+
+
+from tests_common.test_utils.system_tests import get_test_run  # noqa: E402
+
+# Needed to run the example DAG with pytest (see: 
contributing-docs/testing/system_tests.rst)
+test_run = get_test_run(dag)
diff --git 
a/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_cortex_agent.py
 
b/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_cortex_agent.py
new file mode 100644
index 00000000000..130a2878f52
--- /dev/null
+++ 
b/providers/snowflake/tests/unit/snowflake/operators/test_snowflake_cortex_agent.py
@@ -0,0 +1,105 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+from airflow.models.dag import DAG
+from airflow.providers.snowflake.operators.snowflake_cortex_agent import (
+    SnowflakeCortexAgentOperator,
+)
+from airflow.utils import timezone
+
+TASK_ID = "run_agent"
+CONN_ID = "snowflake_default"
+
+
+class TestSnowflakeCortexAgentOperator:
+    @mock.patch(
+        
"airflow.providers.snowflake.operators.snowflake_cortex_agent.SnowflakeCortexAgentHook.run_agent"
+    )
+    def test_execute(self, mock_run_agent):
+        """Test that the operator delegates execution to the hook."""
+        response = {"content": [{"type": "text", "text": "Hello"}]}
+        mock_run_agent.return_value = response
+
+        operator = SnowflakeCortexAgentOperator(
+            task_id=TASK_ID,
+            snowflake_conn_id=CONN_ID,
+            database="MY_DATABASE",
+            schema="MY_SCHEMA",
+            agent_name="my_agent",
+            messages=[
+                {
+                    "role": "user",
+                    "content": "Hello",
+                }
+            ],
+        )
+
+        result = operator.execute(context={})
+
+        mock_run_agent.assert_called_once_with(
+            database="MY_DATABASE",
+            schema="MY_SCHEMA",
+            agent_name="my_agent",
+            messages=[
+                {
+                    "role": "user",
+                    "content": "Hello",
+                }
+            ],
+            thread_id=None,
+            parent_message_id=None,
+            tool_choice=None,
+            models=None,
+            instructions=None,
+            orchestration=None,
+            tools=None,
+            tool_resources=None,
+            timeout=600,
+        )
+
+        assert result == response
+
+    def test_template_fields(self):
+        dag = DAG(
+            dag_id="test_template_fields",
+            start_date=timezone.datetime(2024, 1, 1),
+        )
+
+        operator = SnowflakeCortexAgentOperator(
+            task_id=TASK_ID,
+            dag=dag,
+            database="{{ var.value.database }}",
+            schema="{{ params.schema }}",
+            agent_name="{{ dag_run.conf['agent_name'] }}",
+            messages=[
+                {
+                    "role": "user",
+                    "content": "{{ ds }}",
+                }
+            ],
+        )
+
+        assert operator.template_fields == (
+            "database",
+            "schema",
+            "agent_name",
+            "messages",
+        )

Reply via email to