> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synthefy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Snowflake

> Run Nori on your Snowflake data directly from SQL, with no data export and no training.

Call Nori from Snowflake SQL to predict numeric targets on your tables. Nori is
**in-context**: you pass some labeled rows as examples and the rows you want
scored, and it returns one prediction per row in a single forward pass, with no
training or fine-tuning.

<CardGroup cols={2}>
  <Card title="Hosted connector" icon="bolt" href="#hosted-connector">
    A SQL function that calls the hosted Nori endpoint. Fastest to set up, and
    works today.
  </Card>

  <Card title="Native App (in-account)" icon="shield-halved" href="#native-app-in-account">
    Runs Nori inside your Snowflake account via Snowpark Container Services.
    Your data never leaves the account. Coming to Snowflake Marketplace.
  </Card>
</CardGroup>

***

## Hosted connector

A Snowflake external-access function calls the hosted Nori endpoint. The model
runs on Synthefy's cloud; Snowflake sends the rows and gets predictions back.

<Note>
  The rows you pass are sent to the hosted endpoint for scoring. If your
  workload requires data to stay inside your Snowflake account, use the [Native
  App](#native-app-in-account) instead.
</Note>

**Prerequisites**

<Steps>
  <Step title="ACCOUNTADMIN role">
    Needed to create the external access integration (`CREATE INTEGRATION`).
  </Step>

  <Step title="A Nori API key">
    See [API key](/setup/api_key).
  </Step>

  <Step title="A warehouse">
    Any warehouse, to run the function.
  </Step>
</Steps>

### Set up (once)

Run each step in a Snowsight worksheet as `ACCOUNTADMIN`.

<Steps>
  <Step title="Create a namespace">
    ```sql Namespace theme={null}
    CREATE DATABASE IF NOT EXISTS NORI;
    CREATE SCHEMA   IF NOT EXISTS NORI.PUBLIC;
    ```
  </Step>

  <Step title="Add the egress rule and your API key">
    Replace `<YOUR_NORI_API_KEY>` with your key (highlighted line).

    ```sql Network rule + secret {6} theme={null}
    CREATE OR REPLACE NETWORK RULE NORI.PUBLIC.nori_api_network_rule
      MODE = EGRESS TYPE = HOST_PORT VALUE_LIST = ('inference.baseten.co:443');

    CREATE OR REPLACE SECRET NORI.PUBLIC.nori_api_key
      TYPE = GENERIC_STRING
      SECRET_STRING = '<YOUR_NORI_API_KEY>';
    ```
  </Step>

  <Step title="Create the external access integration">
    ```sql Integration theme={null}
    CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION nori_api_integration
      ALLOWED_NETWORK_RULES          = (NORI.PUBLIC.nori_api_network_rule)
      ALLOWED_AUTHENTICATION_SECRETS = (NORI.PUBLIC.nori_api_key)
      ENABLED = TRUE;
    ```
  </Step>

  <Step title="Create the prediction function">
    Uses only the Python standard library, so there is nothing to install.

    ```sql nori_predict theme={null}
    CREATE OR REPLACE FUNCTION NORI.PUBLIC.nori_predict(x_train ARRAY, y_train ARRAY, x_test ARRAY)
    RETURNS ARRAY
    LANGUAGE PYTHON
    RUNTIME_VERSION = '3.11'
    HANDLER = 'predict'
    EXTERNAL_ACCESS_INTEGRATIONS = (nori_api_integration)
    SECRETS = ('api_key' = NORI.PUBLIC.nori_api_key)
    AS
    $$
    import json, _snowflake, urllib.request

    def predict(x_train, y_train, x_test):
        api_key = _snowflake.get_generic_secret_string("api_key")
        body = json.dumps({
            "model": "synthefy/nori-30m", "task": "regression",
            "X_train": x_train, "y_train": y_train, "X_test": x_test,
        }).encode()
        req = urllib.request.Request(
            "https://inference.baseten.co/predict", data=body,
            headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
            method="POST")
        with urllib.request.urlopen(req, timeout=300) as r:
            return json.loads(r.read().decode()).get("predictions")
    $$;
    ```
  </Step>
</Steps>

### Predict

<Tabs>
  <Tab title="Smoke test">
    Returns two numbers, confirming the connector works end to end.

    ```sql Smoke test theme={null}
    SELECT NORI.PUBLIC.nori_predict(
      [[0.0,1.0],[1.0,0.0],[0.5,0.5],[0.2,0.8]]::ARRAY,  -- X_train
      [0.1,0.9,0.5,0.3]::ARRAY,                           -- y_train
      [[0.3,0.7],[0.8,0.2]]::ARRAY                        -- X_test
    );
    -- [ 0.37, 0.72 ]
    ```
  </Tab>

  <Tab title="Predict from a table">
    Rows with a `NULL` target are scored from the rows where it's set. Aggregate
    `x_train` and `y_train` with the **same** `ORDER BY` so features and targets
    stay aligned.

    <Tip>Set a current schema first (`USE SCHEMA <db>.<schema>;`) or fully-qualify your table names, or you'll hit "does not have a current schema".</Tip>

    ```sql Table prediction theme={null}
    WITH ctx AS (   -- labeled rows
      SELECT ARRAY_AGG(ARRAY_CONSTRUCT(feat1, feat2, feat3)) WITHIN GROUP (ORDER BY id) AS x_train,
             ARRAY_AGG(target)                               WITHIN GROUP (ORDER BY id) AS y_train
      FROM my_table WHERE target IS NOT NULL
    ),
    qry AS (        -- rows to score
      SELECT ARRAY_AGG(ARRAY_CONSTRUCT(feat1, feat2, feat3)) WITHIN GROUP (ORDER BY id) AS x_test,
             ARRAY_AGG(id)                                   WITHIN GROUP (ORDER BY id) AS ids
      FROM my_table WHERE target IS NULL
    ),
    res AS (
      SELECT NORI.PUBLIC.nori_predict(ctx.x_train, ctx.y_train, qry.x_test) AS preds, qry.ids AS ids
      FROM ctx, qry
    )
    SELECT res.ids[f.index]::NUMBER AS id, f.value::FLOAT AS prediction
    FROM res, LATERAL FLATTEN(input => res.preds) f
    ORDER BY id;
    ```
  </Tab>
</Tabs>

<Accordion title="Full setup script (copy-paste)">
  ```sql setup.sql theme={null}
  CREATE DATABASE IF NOT EXISTS NORI;
  CREATE SCHEMA   IF NOT EXISTS NORI.PUBLIC;

  CREATE OR REPLACE NETWORK RULE NORI.PUBLIC.nori_api_network_rule
    MODE = EGRESS TYPE = HOST_PORT VALUE_LIST = ('inference.baseten.co:443');

  CREATE OR REPLACE SECRET NORI.PUBLIC.nori_api_key
    TYPE = GENERIC_STRING SECRET_STRING = '<YOUR_NORI_API_KEY>';

  CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION nori_api_integration
    ALLOWED_NETWORK_RULES          = (NORI.PUBLIC.nori_api_network_rule)
    ALLOWED_AUTHENTICATION_SECRETS = (NORI.PUBLIC.nori_api_key)
    ENABLED = TRUE;

  CREATE OR REPLACE FUNCTION NORI.PUBLIC.nori_predict(x_train ARRAY, y_train ARRAY, x_test ARRAY)
  RETURNS ARRAY
  LANGUAGE PYTHON
  RUNTIME_VERSION = '3.11'
  HANDLER = 'predict'
  EXTERNAL_ACCESS_INTEGRATIONS = (nori_api_integration)
  SECRETS = ('api_key' = NORI.PUBLIC.nori_api_key)
  AS
  $$
  import json, _snowflake, urllib.request

  def predict(x_train, y_train, x_test):
      api_key = _snowflake.get_generic_secret_string("api_key")
      body = json.dumps({
          "model": "synthefy/nori-30m", "task": "regression",
          "X_train": x_train, "y_train": y_train, "X_test": x_test,
      }).encode()
      req = urllib.request.Request(
          "https://inference.baseten.co/predict", data=body,
          headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
          method="POST")
      with urllib.request.urlopen(req, timeout=300) as r:
          return json.loads(r.read().decode()).get("predictions")
  $$;
  ```
</Accordion>

***

## Native App (in-account)

For workloads that require data to **stay inside your Snowflake account**, Nori
is also available as a **Snowflake Native App** that runs the model in-account
using **Snowpark Container Services**, so nothing is sent to Synthefy.

<Note>
  The in-account Native App is rolling out via the Snowflake Marketplace.
  Contact [support@synthefy.com](mailto:support@synthefy.com) for early access.
</Note>

### Start the app (once, after install)

Grant the privileges the app requested, then start its service:

```sql Start theme={null}
GRANT CREATE COMPUTE POOL ON ACCOUNT TO APPLICATION nori_app;
GRANT BIND SERVICE ENDPOINT ON ACCOUNT TO APPLICATION nori_app;
CALL nori_app.core.start_service();
```

Wait until the service is READY (the first start pulls the image and boots the
model):

```sql Status theme={null}
SELECT SYSTEM$GET_SERVICE_STATUS('nori_app.svc.nori_service');
```

### Predict

Same signature as the hosted connector, but everything runs in your account:

```sql Smoke test theme={null}
SELECT nori_app.svc.nori_predict_svc(
  [[0.0,1.0],[1.0,0.0],[0.5,0.5],[0.2,0.8]]::ARRAY,
  [0.1,0.9,0.5,0.3]::ARRAY,
  [[0.3,0.7],[0.8,0.2]]::ARRAY
);
-- [ 0.37, 0.72 ]
```

<Accordion title="Try it end to end (creates a demo table)">
  Fully qualified, so it runs without setting a current schema.

  ```sql Demo theme={null}
  CREATE OR REPLACE TABLE NORI.PUBLIC.demo (id INT, feat1 FLOAT, feat2 FLOAT, target FLOAT);
  INSERT INTO NORI.PUBLIC.demo VALUES
    (1, 0.0, 1.0, 0.1), (2, 1.0, 0.0, 0.9), (3, 0.5, 0.5, 0.5),
    (4, 0.2, 0.8, 0.3), (5, 0.3, 0.7, NULL), (6, 0.8, 0.2, NULL);

  WITH ctx AS (
    SELECT ARRAY_AGG(ARRAY_CONSTRUCT(feat1, feat2)) WITHIN GROUP (ORDER BY id) AS x_train,
           ARRAY_AGG(target)                        WITHIN GROUP (ORDER BY id) AS y_train
    FROM NORI.PUBLIC.demo WHERE target IS NOT NULL
  ),
  qry AS (
    SELECT ARRAY_AGG(ARRAY_CONSTRUCT(feat1, feat2)) WITHIN GROUP (ORDER BY id) AS x_test,
           ARRAY_AGG(id)                            WITHIN GROUP (ORDER BY id) AS ids
    FROM NORI.PUBLIC.demo WHERE target IS NULL
  ),
  res AS (
    SELECT nori_app.svc.nori_predict_svc(ctx.x_train, ctx.y_train, qry.x_test) AS preds, qry.ids AS ids
    FROM ctx, qry
  )
  SELECT ids[f.index]::NUMBER AS id, f.value::FLOAT AS prediction
  FROM res, LATERAL FLATTEN(input => res.preds) f
  ORDER BY id;
  -- id 5 -> ~0.37, id 6 -> ~0.72
  ```
</Accordion>

## Good to know

<Note>
  **Numeric features only.** Encode categorical columns (one-hot or ordinal)
  before calling.
</Note>

<Warning>
  **Cold start (hosted connector).** The first call after an idle period is slow
  while the model warms up; subsequent calls are fast. See the timings in the
  [Nori Quickstart](/nori/quickstart#api).
</Warning>

<Note>
  **Size & alignment.** Snowflake `ARRAY` values cap at 16 MB, so sample the
  labeled rows for very large context tables. Always aggregate `x_train` and
  `y_train` with the same `ORDER BY`.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Amazon SageMaker" icon="aws" href="/nori/sagemaker">
    Deploy Nori as a SageMaker endpoint in your own AWS account.
  </Card>

  <Card title="Nori Quickstart" icon="rocket" href="/nori/quickstart">
    The model sizes and the request/response contract.
  </Card>
</CardGroup>
