From 12d686d21d2ef7bb2643d2ccd570db56d4c67488 Mon Sep 17 00:00:00 2001 From: Matt Senick <32623708+Matts52@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:18:28 -0700 Subject: [PATCH] feat: add web_search_tool config option to cortex_agent materialization (closes #11) Adds a boolean `web_search_tool` config key (default false) that, when true, appends `{ TYPE = WEB_SEARCH_TOOL, NAME = 'web_search' }` to a DDL-level TOOLS clause in the generated CREATE OR REPLACE AGENT statement. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 45 ++++++++++++++++--- .../macros/assert_agents_exist.sql | 2 +- .../models/agent_with_web_search.sql | 16 +++++++ macros/relations/cortex_agent/create.sql | 11 +++++ 4 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 integration_tests/models/agent_with_web_search.sql diff --git a/README.md b/README.md index 76e4b4e..f8e5bec 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,40 @@ $$ > managed elsewhere in your project. This makes the agent a proper downstream > node in your lineage graph. +### Enabling web search + +Add `web_search_tool = true` to the config block to give the agent access to +Snowflake's built-in web search capability: + +```sql +{{ + config( + materialized = 'cortex_agent', + web_search_tool = true + ) +}} +models: + orchestration: claude-4-sonnet +instructions: + response: "Be concise." + orchestration: "Use web search to answer questions about current events." +``` + +This emits a DDL-level `TOOLS` clause before `FROM SPECIFICATION`: + +```sql +create or replace agent MY_DB.MY_SCHEMA.MY_AGENT +tools = ( + { TYPE = WEB_SEARCH_TOOL, NAME = 'web_search' } +) +from specification +$$ +... +$$ +``` + +Omitting the config key (or setting it to `false`) produces no `TOOLS` clause. + ### 2. Raw DDL mode (`raw_ddl=true`) The body is **everything that follows `CREATE OR REPLACE AGENT `** — a @@ -246,11 +280,12 @@ as usual. The model `alias` becomes the skill folder name on the stage. ## `cortex_agent` configuration reference -| Config | Mode | Type | Description | -|-------------|-----------------|----------------|-------------| -| `comment` | specification | string | Sets the agent-level `COMMENT` clause. Single quotes are escaped automatically. | -| `profile` | specification | dict or string | Sets the `PROFILE` clause. A dict is serialized to JSON for you (`display_name`, `avatar`, `color`); a string is used verbatim. | -| `raw_ddl` | both | bool (default `false`) | When `true`, the model body is treated as raw DDL appended after `CREATE OR REPLACE AGENT `, and `comment` / `profile` configs are ignored. | +| Config | Mode | Type | Description | +|-------------------|-----------------|----------------|-------------| +| `comment` | specification | string | Sets the agent-level `COMMENT` clause. Single quotes are escaped automatically. | +| `profile` | specification | dict or string | Sets the `PROFILE` clause. A dict is serialized to JSON for you (`display_name`, `avatar`, `color`); a string is used verbatim. | +| `web_search_tool` | specification | bool (default `false`) | When `true`, adds `{ TYPE = WEB_SEARCH_TOOL, NAME = 'web_search' }` to the DDL-level `TOOLS = (...)` clause, enabling live web search for the agent. | +| `raw_ddl` | both | bool (default `false`) | When `true`, the model body is treated as raw DDL appended after `CREATE OR REPLACE AGENT `, and `comment` / `profile` / `web_search_tool` configs are ignored. | Standard dbt configs (`database`, `schema`, `alias`, `tags`, `pre_hook`, `post_hook`, `grants`, `enabled`, …) all work as usual. The agent is created diff --git a/integration_tests/macros/assert_agents_exist.sql b/integration_tests/macros/assert_agents_exist.sql index fef1ef7..405df6a 100644 --- a/integration_tests/macros/assert_agents_exist.sql +++ b/integration_tests/macros/assert_agents_exist.sql @@ -13,7 +13,7 @@ -#} {% macro assert_agents_exist() %} {%- if execute -%} - {%- set expected = ['agent_minimal', 'agent_with_semantic_view', 'agent_raw_ddl', 'agent_with_skill'] -%} + {%- set expected = ['agent_minimal', 'agent_with_semantic_view', 'agent_raw_ddl', 'agent_with_skill', 'agent_with_web_search'] -%} {%- for model_name in expected -%} {%- set rel = ref(model_name) -%} {%- do run_query("show agents like '" ~ rel.identifier ~ "' in schema " ~ rel.database ~ "." ~ rel.schema) -%} diff --git a/integration_tests/models/agent_with_web_search.sql b/integration_tests/models/agent_with_web_search.sql new file mode 100644 index 0000000..44042da --- /dev/null +++ b/integration_tests/models/agent_with_web_search.sql @@ -0,0 +1,16 @@ +{{ + config( + materialized = 'cortex_agent', + comment = 'Agent with web search enabled (integration test)', + web_search_tool = true + ) +}} +models: + orchestration: claude-4-sonnet +orchestration: + budget: + seconds: 30 + tokens: 16000 +instructions: + response: "Be concise." + orchestration: "Use web search to answer questions about current events." diff --git a/macros/relations/cortex_agent/create.sql b/macros/relations/cortex_agent/create.sql index 297c5b8..f64ced8 100644 --- a/macros/relations/cortex_agent/create.sql +++ b/macros/relations/cortex_agent/create.sql @@ -62,6 +62,12 @@ {%- set comment = config.get('comment', default=none) -%} {%- set profile = config.get('profile', default=none) -%} + {%- set web_search_tool = config.get('web_search_tool', default=false) -%} + + {%- set tools_list = [] -%} + {%- if web_search_tool -%} + {%- do tools_list.append("{ TYPE = WEB_SEARCH_TOOL, NAME = 'web_search' }") -%} + {%- endif -%} create or replace agent {{ relation }} {%- if comment is not none %} @@ -70,6 +76,11 @@ {%- if profile is not none %} profile = {{ dbt_cortex_agent.cortex_agent_render_profile(profile) }} {%- endif %} + {%- if tools_list | length > 0 %} + tools = ( + {{ tools_list | join(',\n ') }} + ) + {%- endif %} from specification $${{ '\n' }}{{ sql }}{{ '\n' }}$$