Use this file to discover all available pages before exploring further.
This cookbook demonstrates how to use the AI_COMPLETE function to enrich records with AI-generated structured attributes directly within NQL. The entire inference pipeline runs inside your Snowflake data plane—your data never leaves your infrastructure.
AI_COMPLETE is available on Snowflake data planes only.
AI_COMPLETE runs through Snowflake Cortex, so the customer Snowflake account must grant Cortex access to the Narrative application and allow cross-region model invocation. A Snowflake account admin runs these statements once:
-- Allow Cortex to route requests to models hosted in any regionALTER ACCOUNT SET CORTEX_ENABLED_CROSS_REGION = 'ANY_REGION';-- Grant the Cortex role to the Narrative applicationGRANT DATABASE ROLE snowflake.cortex_user TO APPLICATION <narrative_application_name>;
Replace <narrative_application_name> with the name of the Narrative Snowflake Native App installed in the account (for example, NARRATIVE_DATA_COLLABORATION_HMI).
The GRANT DATABASE ROLE ... TO APPLICATION statement is a temporary workaround required by current Snowflake Native App permissioning. Snowflake may change how Native Apps access Cortex in the future, at which point this setup step may change. Contact Narrative support if AI_COMPLETE queries begin failing with permission errors.
CORTEX_ENABLED_CROSS_REGION = 'ANY_REGION' lets Cortex invoke models that are hosted outside the account’s home region. Narrative recommends ANY_REGION because model availability varies by region. If your compliance posture requires region pinning, consult Snowflake’s cross-region inference documentation for the available alternatives.
To confirm the grant succeeded, run a minimal query with a literal prompt:
CREATE MATERIALIZED VIEW "test_ai_complete" ASWITH test_prompt AS (SELECT 'respond with ok' AS prompt)SELECT AI_COMPLETE( 'openai-gpt-5-nano', test_prompt.prompt, '{}', '{"type": "json", "schema": {"type": "object", "properties": {"reply": {"type": "string"}}, "required": ["reply"], "additionalProperties": false}}', TRUE ) AS model_responseFROM test_prompt
If this query fails with a privileges error, re-run the grant statements above.
Before writing NQL, design the JSON Schema that describes the structured attributes you want the model to produce. This schema constrains the model’s output, guaranteeing predictable, typed results.
{ "type": "json", "schema": { "type": "object", "properties": { "category": { "type": "string", "description": "The primary issue category", "enum": [ "billing", "technical", "account_access", "feature_request", "general_inquiry" ] }, "priority": { "type": "string", "description": "Urgency level based on the issue description", "enum": ["low", "medium", "high", "critical"] }, "sentiment": { "type": "number", "description": "Customer sentiment from -1 (very negative) to 1 (very positive)" }, "product_mentioned": { "type": "string", "description": "The specific product or feature referenced, or 'none' if not applicable" } }, "required": ["category", "priority", "sentiment", "product_mentioned"], "additionalProperties": false }}
Key schema design decisions:
Decision
Why
enum on category and priority
Restricts output to known values, preventing free-form drift
number for sentiment
Enables numeric filtering and aggregation downstream
additionalProperties: false
Prevents the model from adding unexpected fields
description on each field
Guides the model’s interpretation of what each field means
Keep schemas focused on the fields your downstream queries will actually use. Every additional field increases token usage and inference cost.
AI_COMPLETE requires its prompt parameter to be a column reference, not a string literal. Use a Common Table Expression (CTE) to assemble prompts from your source columns.
WITH prompts AS ( SELECT company_data."support_tickets".ticket_id, company_data."support_tickets".customer_id, company_data."support_tickets".created_at, 'Analyze the following customer support ticket and extract structured attributes.' || ' Ticket subject: ' || company_data."support_tickets".subject || ' Ticket body: ' || company_data."support_tickets".body AS prompt_text FROM company_data."support_tickets" WHERE company_data."support_tickets".created_at > CURRENT_TIMESTAMP - INTERVAL '7' DAY)SELECT prompts.ticket_id, prompts.prompt_textFROM prompts
Tips for building effective prompts:
Prefix each field with a label (e.g., ' Ticket subject: ') so the model can distinguish between data elements
Filter rows before calling AI_COMPLETE to avoid unnecessary inference costs
Use COALESCE to handle nulls: COALESCE(company_data."support_tickets".subject, 'No subject')
Use PARSE_JSON and path notation to extract individual fields from the AI response into typed columns.
SELECT enriched.ticket_id, enriched.customer_id, enriched.created_at, CAST( PARSE_JSON(enriched.ai_response)['structured_output'][0]['raw_message']['category'] AS STRING ) AS category, CAST( PARSE_JSON(enriched.ai_response)['structured_output'][0]['raw_message']['priority'] AS STRING ) AS priority, CAST( PARSE_JSON(enriched.ai_response)['structured_output'][0]['raw_message']['sentiment'] AS DOUBLE ) AS sentiment, CAST( PARSE_JSON(enriched.ai_response)['structured_output'][0]['raw_message']['product_mentioned'] AS STRING ) AS product_mentionedFROM enriched
The path ['structured_output'][0]['raw_message'] navigates to the model’s schema-validated output. Each field within raw_message corresponds to a property in your response_format schema.