• AI

AI agent components on Snowflake: Secure external access integration (Perplexity API Example)

Unlock secure, real-time data insights by integrating external APIs in Snowflake. Learn how to leverage Perplexity API with advanced secrets management, network rules, logging, and external access integrations for seamless agentic workflows.

Mika Heino

Written by — Mika Heino, Data Architect

Use case

When exploring agentic workflows, you'll eventually need to access external API endpoints to provide real-time data for your agent. One use case could be to utilize the Perplexity API to retrieve real-time data that is not available in the LLM models provided by Snowflake Cortex. Perplexity AI can be used to answer queries using sources from the web and it’s accessible through API endpoints with credits, which will grant you an API -key, which you want to keep secure.

Within this blog post, I’ll demonstrate how this requirement can be met with Snowflake services.

 

Secrets management

Accessing an API usually requires storing the API user and its key in a secure location. Traditional development in Python means storing the key in OS environment variables or maintaining a .env file loaded using the “python-dotenv” -library. For local deployments and testing purposes, this pattern works, but for production deployments, Docker Secrets and secret management tools from cloud vendors are usually chosen.

For deployments within Snowflake, you can use Snowflake Secrets. Snowflake Secrets use the same RBAC process as other entities created within Snowflake (e.g. grant usage on secret to role <role>) and they require no additional code to maintain. You create and maintain secrets using SQL as seen below in (example 1) and reference them later by the secret name.

Example 1.

CREATE SECRET api_perplexity
  TYPE = password
  USERNAME = 'user' --not passing real values here
  PASSWORD = 'pw'; --not passing real values here

 

 

Network Rules

We don’t want our Snowflake account to be available on the public internet, but we want to access the API endpoint from our Snowflake account. For this, we use network rules, which are the most granular level of objects that you can use to define network identifiers. The above network rules are network policies (for inbound access) and external access integrations (for outbound access) and both of them use network rules to control network traffic in and out of Snowflake service and internal stages. 

In our use, we will reference this network rule on our external access integration for granting access into a given API endpoint as seen in the example 2.

Example 2.

-- Create Network rule
CREATE OR REPLACE NETWORK RULE api_perplexity
  MODE = EGRESS
  TYPE = HOST_PORT
  VALUE_LIST = ('*.perplexity.ai');

 

 

External Access Integration

External access integrations are, as noted in the previous chapter, entities to control outbound access from the Snowflake account. As you can see from the code below (example 3), external access integration builds upon previous components. Within external integration, you define which network rules and secrets are used (you can have a multitude of network rules and secrets) for a given integration. External access integrations are helpful components when you write for example a UDF or procedure that accesses an external location.

What’s not used in example 3. is the possibility to define whether your connection to an external network location uses the public Internet or a private network, such as by using Azure Private Link or AWS PrivateLink.

Example 3.

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION api_perplexity
  ALLOWED_NETWORK_RULES = (api_perplexity)
  ALLOWED_AUTHENTICATION_SECRETS = ('api_perplexity')
  ENABLED = true;

 

 

Logging

As our code will be written in Python and stored within the Snowflake stored procedure, it means that we want to have a better understanding of what happens when we call our procedure. For this, we can leverage logging capabilities. Logging events happen by importing logging and using it as you would normally do (as seen in example 4.)  For a full list of supported languages, check Snowflake documentation.

Example 4.

import logging

def main(session: snowpark.Session, params: str) -> str:
    """
    Snowflake stored procedure that calls the Perplexity API.
    It parses parameters, retrieves secrets, and logs events.
    """
    logging.basicConfig(level=logging.INFO)
    try:
        params_dict = json.loads(params) if params else {}
        logging.info("Parsed parameters")
    except Exception as e:
        logging.error("Error parsing parameters")
        raise e

 

Messages are then stored automatically in the Snowflake event table which you can create without schema as the table inherently possesses a predefined set of columns. After creating the table you define the event table as active, but setting it on an account level (examples 5 and 6). This newly created event table can be queried as a normal table.

Example 5.

CREATE EVENT TABLE <database_name>.<schema_name>.<event_table_name>;
ALTER ACCOUNT SET EVENT_TABLE = <database_name>.<schema_name>.<event_table_name>;

You can change the log-level settings based on your needs.

Example 6.

-- To set log level at session level
ALTER SESSION SET LOG_LEVEL = DEBUG;

-- To set log level at the database level
ALTER DATABASE db1 SET LOG_LEVEL = ERROR;

 

 

Putting it all together

We are creating this API connection as a Snowflake stored procedure which allows us to call API in a similar manner as we would call any Snowflake Cortex function. Wrapping the API call inside a stored procedure allows us to leverage all the previously mentioned components for a secure and loggable connection. A stored procedure allows the code to be reusable in another project without code duplication.

Before heading into the Snowflake part, obtain the Perplexity API Key.

Obtain Perplexity API key

Register for an Account

To access this feature, you need an account on Perplexity website

Configure Billing Details

Enter your billing information to establish a payment method. This is required for generating your API key. Once done, navigate to the API settings page. API settings can be found on the wheel next to your account on the left corner.

Purchase Credits

In the API settings, find the “Available Credits” section and click on “Buy Credits.” Specify the number of credits you’d like to purchase and follow the on-screen instructions to complete the transaction. Note: If you create an Pro account, you’ll be given 5$ of worth of credits out of box

Generate API Key

Go to the “API Keys” section in your account dashboard and click the “Generate API Key” button to create your API key.

Create Snowflake Secret

Use the string obtained in the previous part and add that as a password. As Perplexity does not require a username, give the username as ‘user.

-- Create Secret
CREATE SECRET api_perplexity
  TYPE = password
  USERNAME = 'perplexity_user' --not passing real values here
  PASSWORD = '******'; --not passing real values here

Create Network rule

For this network rule, we define .perplexity.ai as the value list

-- Create a Network rule
CREATE OR REPLACE NETWORK RULE api_perplexity
  MODE = EGRESS
  TYPE = HOST_PORT
  VALUE_LIST = ('*.perplexity.ai');


Create External Access Integration

Create a external access integration that uses the rule and secret created above

-- Create EAI
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION api_perplexity
  ALLOWED_NETWORK_RULES = (api_perplexity)
  ALLOWED_AUTHENTICATION_SECRETS = ('api_perplexity')
  ENABLED = true;

 

Create Stored Procedure

Putting this all together is rather simple. Snowflake Snowpark Stored Procedure uses Anaconda packages and because of that, we can inform, that "requests"-package is needed during installation.
We have the option to load third-party packages, but for this use case, those are unnecessary. Other important notions are the mention of EXTERNAL_ACCESS_INTEGRATION which defines what access the stored procedure is allowed to use. SECRETS define what secrets to use for the Stored Procedure. With this method, we can later say that _snowflake.get_username_password('cred').password contains our password to Perplexity API.

EXECUTE AS Caller or Owner?

The last part of the procedure is the EXECUTE AS notion, which is related to Snowflake internals as this stored procedure uses Snowflake RBAC. This means that if we do not execute the procedure as the owner, we need to grant usage to the caller. Creating any Snowflake objects with the code also follows the rights of the execute as. If I execute this procedure as ACCOUNTADMIN and create a table within the code, the table will be owned by ACCOUNTADMIN.

Breakdown of rights

Caller’s Rights

Owner’s Rights

Runs with the privileges of the caller.

Runs with the privileges of the owner.

Inherit the current warehouse of the caller.

Inherit the current warehouse of the caller.

Use the database and schema that the caller is currently using.

Use the database and schema that the stored procedure is created in, not the database and schema that the caller is currently using.

 

CREATE OR REPLACE PROCEDURE AI_AGENT.PUBLIC.PERPLEXITY(PARAMS VARCHAR(16777216))
RETURNS VARCHAR(16777216)
LANGUAGE PYTHON
RUNTIME_VERSION = '3.11'
PACKAGES = ('snowflake-snowpark-python', 'requests')
EXTERNAL_ACCESS_INTEGRATIONS = (api_perplexity)
SECRETS = ('cred' = api_perplexity)
HANDLER = 'main'
EXECUTE AS CALLER
AS
$$
import json
import requests
import logging
import traceback
from datetime import datetime

import snowflake.snowpark as snowpark
import _snowflake

def main(session: snowpark.Session, params: str) -> str:
    """
    Snowflake stored procedure that calls the Perplexity API.
    It retrieves the API key from Snowflake secrets, sends a request
    to the Perplexity API, and returns the content of the first message
    from the response.
    """
    logging.basicConfig(level=logging.INFO)
    try:
        # 1) Parse input parameters (if provided)
        params_dict = json.loads(params) if params else {}
        model = params_dict.get("model", "llama-3.1-sonar-small-128k-online")
        user_message = params_dict.get("user_message", "This is a placeholder question?")
        logging.info("Parsed parameters successfully.")
        
        # 2) Retrieve the API key from Snowflake secrets
        api_key = _snowflake.get_username_password('cred').password
        logging.info("Retrieved API key from secrets.")
        
        # 3) Build the request to Perplexity API
        url = "https://api.perplexity.ai/chat/completions"
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        payload = {
            "model": model,
            "messages": [{"role": "user", "content": user_message}],
            "temperature": 0.2,
            "max_tokens": 100
        }
        logging.info("Making API call to Perplexity API")
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        response_json = response.json()
        logging.info("API call successful.")
        # 4) Extract the content from the first choice message
        result_content = response_json.get('choices', [{}])[0].get('message', {}).get('content', '')
        return json.dumps({"result": result_content})
    except requests.HTTPError as http_err:
        logging.error("HTTP error occurred: %s", str(http_err))
        traceback_str = ''.join(traceback.format_tb(http_err.__traceback__))
        return json.dumps({"error": f"HTTP_ERROR: {str(http_err)}", "traceback": traceback_str})
    except Exception as e:
        logging.error("An error occurred: %s", str(e))
        traceback_str = ''.join(traceback.format_tb(e.__traceback__))
        return json.dumps({"error": f"ERROR: {str(e)}", "traceback": traceback_str})
$$;

 

 

Testing the result

Testing the function happens by calling the procedure with a given user message. The result gives an accurate answer referencing a blog video released on the Recordly site. 

This call can now be made anywhere as long as the caller is either the owner of the procedure or has been granted usage for the procedure.

CALL AI_AGENT.PUBLIC.PERPLEXITY('{"user_message": "What has AI expert Rasmus Toivanen recently published on the field of GenAI?"}');

{"result": "Rasmus Toivanen, an AI expert, has recently explored the question of whether the GenAI market is consolidating. His work, published on Recordly, discusses the potential reduction in the number of vendors in the GenAI market due to the increasing resources required to develop large language models (LLMs) and the possibility of smaller, dedicated models addressing current challenges more effectively while reducing energy consumption[1]. Additionally, his profile on Hugging Face indicates his involvement in various AI and ML projects,"}

 

 

Conclusion

You should now have an understanding of how you can create external access from Snowflake. Obviously, this is not the only pattern on how to solve external access integrations, as Snowflake Containers Services introduces a variety of Docker-based solutions. This solution though works inside Snowflake and can be leveraged on code deployed into Snowflake Container Services or Snowflake Notebooks.

Latest from the Blog

Check out more articles