Get started with a coding agent
Use a coding agent and RAI’s agent skills to build a semantic model from Snowflake data, deploy it to a development schema, and query it to answer business questions. You’ll extend the model with business rules and graph analysis, redeploying your changes before exploring the results.
- Prerequisites
Access to a Snowflake account with the RAI Native App installed.
How do I know if the RAI Native App is installed?
To check if the RAI Native App is installed in your Snowflake account:- Log in to your Snowflake account.
- Run the following SQL command in a worksheet:
SHOW APPLICATIONS;
- Check the output for an application named
RELATIONALAI.- If you see it listed, the RAI Native App is installed.
- If not, you need to install it.
A Snowflake user with the
RAI_DEVELOPERaccount role.How do I check my roles?
To check your roles in Snowflake:- Log in to your Snowflake account.
- Run the following SQL command in a worksheet:
SHOW GRANTS TO USER <your_username>;
- Check the output in the
namecolumn for theRAI_DEVELOPERrole.- If you see it listed, you have the required role.
- If not, contact your Snowflake account administrator to have the role assigned to you. See Grant user access for more information.
Permission to create the tutorial’s source tables and deploy the model to dedicated development schemas.
Install the RAI skills
Section titled “Install the RAI skills”RAI skills give your coding agent instructions for building, querying, and deploying PyRel models. Choose your agent below and install the skills. For other options, see Install RAI agent skills.
In Claude Code, add the RelationalAI marketplace and install the rai plugin:
/plugin marketplace add RelationalAI/rai-agent-skills/plugin install rai@RelationalAIAdd the marketplace, then install the rai plugin:
-
Add the RelationalAI marketplace:
Terminal window codex plugin marketplace add RelationalAI/rai-agent-skills -
Start Codex:
Terminal window codex -
Open the plugin browser and install the
raiplugin from therelationalaimarketplace:Terminal window /plugins
-
In VS Code, add the RelationalAI marketplace to your settings:
"chat.plugins.marketplaces": ["RelationalAI/rai-agent-skills"] -
Then open the Extensions view, search for
@agentPlugins, and install theRaiplugin.
Run this command in your project directory and follow the prompts to install the RAI skills:
npx skills add RelationalAI/rai-agent-skills --skill '*'See the Vercel Skills CLI documentation for more information.
Start a new agent session after installation so the new skills are available.
Check that your agent lists the RAI skills, including /rai-ontology and /rai-deployment.
If they aren’t available, check the installation for your agent in Install RAI agent skills before continuing.
Download the starter project
Section titled “Download the starter project”The starter project contains sample data, model files, a query script, and connection settings:
Directorydata/ Sample CSV files
- …
Directorymodel Starter PyRel semantic model package
__init__.py- schema.py Defines the model’s schema
- sources.py Maps source data to the schema
- load_data.py Creates Snowflake tables from sample data
- pyproject.toml Declares project metadata and dependencies
- queries.py Contains example queries
- raiconfig.yaml Stores the RAI settings for this project
You’ll edit these files as you build the model. First, download the project and install its dependencies.
-
Download and extract the ZIP file.
Copy this prompt:
Create a directory called rai-agent-skills-tutorial. Download https://docs.relational.ai/rai-starter-project.zip into it and extract the archive. Remove only the downloaded ZIP after extraction succeeds. Use the extracted rai-starter-project directory, which contains pyproject.toml, as the project root for the remaining steps.-
Create a new directory and navigate into it:
Terminal window mkdir rai-agent-skills-tutorialcd rai-agent-skills-tutorial -
Download and extract the ZIP:
Terminal window curl -fL -o rai-starter-project.zip https://docs.relational.ai/rai-starter-project.zipunzip rai-starter-project.zip -
Remove the ZIP and open the extracted project.
After extraction succeeds, run:
Terminal window rm rai-starter-project.zipcd rai-starter-project
Confirm that your working directory contains
pyproject.toml,raiconfig.yaml, andmodel/. Use this directory for all remaining commands and agent tasks. If extraction fails or these files are missing, download and extract the ZIP again before continuing. -
-
Set up the project environment.
uvmanages the project’s Python environment and dependencies. Use PyRel 1.38.0 for this tutorial.Copy this prompt:
Install uv if needed, then run uv add "relationalai==1.38.0" in the extracted project root. Confirm that installation succeeds and PyRel 1.38.0 is selected.-
Install
uvif needed.Check whether
uvis installed:Terminal window uv --versionA version number means it’s installed. If the command isn’t found, use the instructions for your operating system:
Use the standalone installer:
Terminal window curl -LsSf https://astral.sh/uv/install.sh | shIn PowerShell, run:
Terminal window powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -
Select PyRel 1.38.0 and install the dependencies.
In the starter project directory, run:
Terminal window uv add "relationalai==1.38.0"This pins PyRel to version 1.38.0 in
pyproject.toml, creates the project environment, and installs the project dependencies.
Confirm that installation succeeds and
pyproject.tomllistsrelationalai==1.38.0. If installation fails, ask your agent to resolve the reported error before continuing. -
Configure your Snowflake connection
Section titled “Configure your Snowflake connection”Connect PyRel to the Snowflake account containing your tutorial data. The starter is configured for a programmatic access token, or PAT, the recommended option for most use cases. Choose the other tab if you need a different authentication method.
-
Gather your Snowflake connection details.
Gather these details before editing the configuration:
Detail Where to find it Account Log in to the Snowflake account that has the RAI Native App installed. Execute the following SQL command in a worksheet to find your account name: SELECT CURRENT_ORGANIZATION_NAME() || '-' || CURRENT_ACCOUNT_NAME() AS account_identifier;User The Snowflake username you will use to connect. This user must have the RAI_DEVELOPER role. Token The programmatic access token (PAT) you use to authenticate with Snowflake. If you don’t have one, ask a Snowflake admin to follow the steps in Configure programmatic access token authentication to create one. Warehouse The name of the Snowflake warehouse you want to use to execute SQL queries. Can be any existing X-SMALLor larger warehouse. -
Edit the
raiconfig.yamlfile.Open
raiconfig.yamland replace the placeholders foraccount,warehouse, anduserwith your Snowflake connection details. Keeprole: RAI_DEVELOPERunless your administrator has specified a different role.Replace the
tokenplaceholder with an environment-variable reference:token: "{{ env_var('SNOWFLAKE_PROGRAMMATIC_ACCESS_TOKEN') }}"Set
SNOWFLAKE_PROGRAMMATIC_ACCESS_TOKENlocally in the environment where you run PyRel before validating the connection. See Configure your connection for secret-handling guidance and the PAT configuration example. Don’t paste your token into agent chat or commit it to source control.
Ask the agent to help you choose and configure authentication:
/rai-setup Help me choose a Snowflake authentication method and configure raiconfig.yaml. Ask only for non-secret connection details. Explain how to supply secrets locally through environment variables or protected files, not chat or source control.See Configure PyRel for configuration options.
After setting up either method, check the connection:
uv run rai connectA success message confirms that PyRel can authenticate and connect to Snowflake. It doesn’t confirm permission to deploy or refresh the model; you’ll check that at deployment. If the connection fails, follow Validate your connection before continuing.
Load the sample data
Section titled “Load the sample data”Use the starter’s load_data.py script to load the sample CSVs into Snowflake tables.
Choose a dedicated source schema, such as RAI_TUTORIAL.ENERGY_GRID, separate from the schemas where you’ll deploy the model.
How do I create a sample database and schema for this tutorial?
Use a dedicated schema for the tutorial’s source data in a database where you have permission to work.
If you need a source schema, ask an administrator to run this SQL using ACCOUNTADMIN or another role with permission to create databases, create schemas, and grant privileges:
CREATE DATABASE IF NOT EXISTS RAI_TUTORIAL;CREATE SCHEMA IF NOT EXISTS RAI_TUTORIAL.ENERGY_GRID;
GRANT USAGE ON DATABASE RAI_TUTORIAL TO ROLE rai_developer;GRANT USAGE ON SCHEMA RAI_TUTORIAL.ENERGY_GRID TO ROLE rai_developer;GRANT CREATE TABLE ON SCHEMA RAI_TUTORIAL.ENERGY_GRID TO ROLE rai_developer;Use RAI_TUTORIAL.ENERGY_GRID as the source schema in the steps below.
These grants allow you to load source data; they don’t establish the privileges required to deploy the model.
See Set the deployment target for deployment role requirements.
What is in the sample data?
The sample data describes Texas electric-grid substations, generators, load zones, and transmission lines.
It also includes requests to connect new data centers, load readings, demand forecasts, maintenance windows, and grid upgrades.
Load the data with your agent or follow the manual steps. After a successful load, remove the local CSVs and loader so the agent uses the Snowflake tables when building the model.
Copy this prompt:
Ask me to confirm a dedicated source schema in DATABASE.SCHEMA form. Warn that the loader replaces matching tutorial tables and overwrites their data.
Run uv run load_data.py <DATABASE.SCHEMA> from the project root. If all sample tables load successfully, remove only this starter project's data/ directory and load_data.py. If loading fails, stop and help me fix the error without deleting files.-
Run the
load_data.pyscript.Check your dedicated source schema name first, since matching tutorial tables will be replaced. From the project root, run this command with that schema, such as
RAI_TUTORIAL.ENERGY_GRID:Terminal window uv run load_data.py <MY_DB>.<MY_SCHEMA> -
Remove the CSV files and the
load_data.pyscript.Confirm that every sample table loaded successfully before deleting anything. Then, from the extracted starter project root, run:
Terminal window rm -rf data load_data.py
If loading fails, keep the local files and resolve the error before continuing. For authentication errors, see Validate your connection. For privilege errors, ask your administrator to check access to the source schema. Rerun the loader only after confirming that it’s safe to replace the tutorial tables.
Start building with /rai-ontology
Section titled “Start building with /rai-ontology”Ask your agent to turn the Snowflake tables into a semantic model. You’ll review the model’s structure here and query its results after deployment.
-
Ask the agent to build the model.
Copy this prompt:
/rai-ontology Build an electric-grid and data-center capacity-planning model from my Snowflake sample tables. Ask me for the source schema in DATABASE.SCHEMA form.Define concepts and relationships in model/schema.py, map the tables in model/sources.py, and load both through model/__init__.py.Keep model imports free of queries, result inspection, and data printing. Prepare a data-center request count query in the root queries.py under an if __name__ == "__main__" guard, but don't run it until after deployment. You may inspect source tables and model schema metadata now.The model should cover substations, load zones, generators, transmission lines, demand, forecasts, upgrades, maintenance, customers, and data-center requests. Source mappings connect those concepts to Snowflake tables using PyRel
Tableobjects and definitions. -
Enter your Snowflake schema.
Provide the source schema you loaded earlier, such as
RAI_TUTORIAL.ENERGY_GRID—not the deployment schema. -
Choose one-shot mode.
If the agent offers a mode choice, choose one-shot mode to generate the starter model in one pass.
For your own projects, guided mode lets you discuss modeling choices with the agent as it works.
-
Review the generated model.
Ask the agent to walk you through these files:
File What to check model/schema.pyDeclares the concepts, properties, and relationships using a PyRel Model.model/sources.pyMaps the intended Snowflake source tables to those concepts. model/__init__.pyLoads both modules without running queries, inspecting results, or printing data. queries.pyContains the count query under an if __name__ == "__main__"guard, so importing it won’t run the query.Ask the agent to correct missing definitions, mappings, or imports before deployment.
The following files illustrate one possible model; your agent’s names and structure may differ. This review checks structure, not deployed results. Don’t run
queries.pyuntil the deployment step below succeeds.Click to expand the sample output from
/rai-ontology"""Semantic model for electric-grid and data-center capacity planning."""from relationalai.semantics import Date, Float, Integer, Model, Stringmodel = Model("EnergyGridCapacityPlanning")LoadZone = model.Concept("LoadZone", identify_by={"id": String})LoadZone.name = model.Property(f"{LoadZone} has {String:name}")LoadZone.peak_demand_mw = model.Property(f"{LoadZone} has {Float:peak_demand_mw}")LoadZone.base_demand_mw = model.Property(f"{LoadZone} has {Float:base_demand_mw}")Customer = model.Concept("Customer", identify_by={"id": String})Customer.name = model.Property(f"{Customer} has {String:name}")Customer.load_zone = model.Property(f"{Customer} is served by {LoadZone:load_zone}")Customer.contracted_demand_mw = model.Property(f"{Customer} has {Float:contracted_demand_mw}")Customer.flexibility_pct = model.Property(f"{Customer} has {Float:flexibility_pct}")Customer.curtailment_cost_per_mwh = model.Property(f"{Customer} has {Float:curtailment_cost_per_mwh}")Substation = model.Concept("Substation", identify_by={"id": String})Substation.name = model.Property(f"{Substation} has {String:name}")Substation.voltage_kv = model.Property(f"{Substation} has {Integer:voltage_kv}")Substation.max_capacity_mw = model.Property(f"{Substation} has {Float:max_capacity_mw}")Substation.current_load_mw = model.Property(f"{Substation} has {Float:current_load_mw}")DataCenterRequest = model.Concept("DataCenterRequest", identify_by={"id": String})DataCenterRequest.name = model.Property(f"{DataCenterRequest} has {String:name}")DataCenterRequest.hyperscaler = model.Property(f"{DataCenterRequest} has {String:hyperscaler}")DataCenterRequest.substation = model.Property(f"{DataCenterRequest} interconnects at {Substation:substation}")DataCenterRequest.requested_mw = model.Property(f"{DataCenterRequest} has {Float:requested_mw}")DataCenterRequest.annual_revenue_per_mw = model.Property(f"{DataCenterRequest} has {Float:annual_revenue_per_mw}")DataCenterRequest.pue = model.Property(f"{DataCenterRequest} has {Float:pue}")DataCenterRequest.low_carbon_requirement_pct = model.Property(f"{DataCenterRequest} has {Float:low_carbon_requirement_pct}")DataCenterRequest.queue_position = model.Property(f"{DataCenterRequest} has {Integer:queue_position}")DataCenterRequest.status = model.Property(f"{DataCenterRequest} has {String:status}")DataCenterRequest.is_ai_workload = model.Relationship(f"{DataCenterRequest} is AI workload")DemandPeriod = model.Concept("DemandPeriod", identify_by={"id": String})DemandPeriod.load_zone = model.Property(f"{DemandPeriod} is for {LoadZone:load_zone}")DemandPeriod.period = model.Property(f"{DemandPeriod} has {Integer:period}")DemandPeriod.demand_mw = model.Property(f"{DemandPeriod} has {Float:demand_mw}")DemandPeriod.price_per_mwh = model.Property(f"{DemandPeriod} has {Float:price_per_mwh}")DemandForecast = model.Concept("DemandForecast", identify_by={"id": String})DemandForecast.substation = model.Property(f"{DemandForecast} is for {Substation:substation}")DemandForecast.forecast_period = model.Property(f"{DemandForecast} has {Integer:forecast_period}")DemandForecast.predicted_load_mw = model.Property(f"{DemandForecast} has {Float:predicted_load_mw}")DemandForecast.confidence = model.Property(f"{DemandForecast} has {Float:confidence}")DemandForecast.includes_dc_growth = model.Relationship(f"{DemandForecast} includes data center growth")Generator = model.Concept("Generator", identify_by={"id": String})Generator.name = model.Property(f"{Generator} has {String:name}")Generator.gen_type = model.Property(f"{Generator} has {String:gen_type}")Generator.substation = model.Property(f"{Generator} connects at {Substation:substation}")Generator.capacity_mw = model.Property(f"{Generator} has {Float:capacity_mw}")Generator.marginal_cost = model.Property(f"{Generator} has {Float:marginal_cost}")Generator.is_renewable = model.Relationship(f"{Generator} is renewable")LoadReading = model.Concept("LoadReading", identify_by={"reading_id": Integer})LoadReading.substation = model.Property(f"{LoadReading} measures {Substation:substation}")LoadReading.reading_date = model.Property(f"{LoadReading} has {Date:reading_date}")LoadReading.load_mw = model.Property(f"{LoadReading} has {Float:load_mw}")LoadReading.temperature_f = model.Property(f"{LoadReading} has {Float:temperature_f}")LoadReading.is_peak_season = model.Relationship(f"{LoadReading} is in peak season")RenewableProfile = model.Concept("RenewableProfile", identify_by={"id": String})RenewableProfile.generator = model.Property(f"{RenewableProfile} is for {Generator:generator}")RenewableProfile.period = model.Property(f"{RenewableProfile} has {Integer:period}")RenewableProfile.capacity_factor = model.Property(f"{RenewableProfile} has {Float:capacity_factor}")SubstationUpgrade = model.Concept("SubstationUpgrade", identify_by={"id": String})SubstationUpgrade.substation = model.Property(f"{SubstationUpgrade} expands {Substation:substation}")SubstationUpgrade.capacity_increase_mw = model.Property(f"{SubstationUpgrade} has {Float:capacity_increase_mw}")SubstationUpgrade.cost_million = model.Property(f"{SubstationUpgrade} has {Float:cost_million}")SubstationUpgrade.lead_time_months = model.Property(f"{SubstationUpgrade} has {Integer:lead_time_months}")SubstationUpgrade.enables_low_carbon = model.Relationship(f"{SubstationUpgrade} enables low carbon service")TransmissionLine = model.Concept("TransmissionLine", identify_by={"id": String})TransmissionLine.from_substation = model.Property(f"{TransmissionLine} originates at {Substation:from_substation}")TransmissionLine.to_substation = model.Property(f"{TransmissionLine} terminates at {Substation:to_substation}")TransmissionLine.capacity_mw = model.Property(f"{TransmissionLine} has {Float:capacity_mw}")TransmissionLine.maintenance_priority = model.Property(f"{TransmissionLine} has {String:maintenance_priority}")TransmissionLine.is_active = model.Relationship(f"{TransmissionLine} is active")MaintenanceWindow = model.Concept("MaintenanceWindow", identify_by={"id": String})MaintenanceWindow.asset_type = model.Property(f"{MaintenanceWindow} has {String:asset_type}")MaintenanceWindow.start_period = model.Property(f"{MaintenanceWindow} has {Integer:start_period}")MaintenanceWindow.end_period = model.Property(f"{MaintenanceWindow} has {Integer:end_period}")MaintenanceWindow.generator = model.Property(f"{MaintenanceWindow} affects {Generator:generator}")MaintenanceWindow.transmission_line = model.Property(f"{MaintenanceWindow} affects {TransmissionLine:transmission_line}")MaintenanceWindow.is_planned = model.Relationship(f"{MaintenanceWindow} is planned")"""Snowflake source bindings for the energy-grid starter ontology."""from .schema import (Customer,DataCenterRequest,DemandForecast,DemandPeriod,Generator,LoadReading,LoadZone,MaintenanceWindow,RenewableProfile,Substation,SubstationUpgrade,TransmissionLine,model,)SNOWFLAKE_NAMESPACE = "TUTORIALS.ENERGY_GRID"class Sources:customers = model.Table(f"{SNOWFLAKE_NAMESPACE}.CUSTOMERS")data_center_requests = model.Table(f"{SNOWFLAKE_NAMESPACE}.DATA_CENTER_REQUESTS")demand_forecasts = model.Table(f"{SNOWFLAKE_NAMESPACE}.DEMAND_FORECASTS")demand_periods = model.Table(f"{SNOWFLAKE_NAMESPACE}.DEMAND_PERIODS")generators = model.Table(f"{SNOWFLAKE_NAMESPACE}.GENERATORS")load_history = model.Table(f"{SNOWFLAKE_NAMESPACE}.LOAD_HISTORY")load_zones = model.Table(f"{SNOWFLAKE_NAMESPACE}.LOAD_ZONES")maintenance_windows = model.Table(f"{SNOWFLAKE_NAMESPACE}.MAINTENANCE_WINDOWS")renewable_profiles = model.Table(f"{SNOWFLAKE_NAMESPACE}.RENEWABLE_PROFILES")substations = model.Table(f"{SNOWFLAKE_NAMESPACE}.SUBSTATIONS")substation_upgrades = model.Table(f"{SNOWFLAKE_NAMESPACE}.SUBSTATION_UPGRADES")transmission_lines = model.Table(f"{SNOWFLAKE_NAMESPACE}.TRANSMISSION_LINES")src = Sourcesmodel.define(LoadZone.new(id=src.load_zones.ID,name=src.load_zones.NAME,peak_demand_mw=src.load_zones.PEAK_DEMAND_MW,base_demand_mw=src.load_zones.BASE_DEMAND_MW,))model.define(Customer.new(id=src.customers.ID,name=src.customers.NAME,load_zone=LoadZone.filter_by(id=src.customers.LOAD_ZONE_ID),contracted_demand_mw=src.customers.CONTRACTED_DEMAND_MW,flexibility_pct=src.customers.FLEXIBILITY_PCT,curtailment_cost_per_mwh=src.customers.CURTAILMENT_COST_PER_MWH,))model.define(Substation.new(id=src.substations.ID,name=src.substations.NAME,voltage_kv=src.substations.VOLTAGE_KV,max_capacity_mw=src.substations.MAX_CAPACITY_MW,current_load_mw=src.substations.CURRENT_LOAD_MW,))model.define(DataCenterRequest.new(id=src.data_center_requests.ID,name=src.data_center_requests.NAME,hyperscaler=src.data_center_requests.HYPERSCALER,substation=Substation.filter_by(id=src.data_center_requests.SUBSTATION_ID),requested_mw=src.data_center_requests.REQUESTED_MW,annual_revenue_per_mw=src.data_center_requests.ANNUAL_REVENUE_PER_MW,pue=src.data_center_requests.PUE,low_carbon_requirement_pct=src.data_center_requests.LOW_CARBON_REQUIREMENT_PCT,queue_position=src.data_center_requests.QUEUE_POSITION,status=src.data_center_requests.STATUS,))model.define(DataCenterRequest.is_ai_workload(DataCenterRequest.filter_by(id=src.data_center_requests.ID))).where(src.data_center_requests.IS_AI_WORKLOAD)model.define(DemandPeriod.new(id=src.demand_periods.ID,load_zone=LoadZone.filter_by(id=src.demand_periods.LOAD_ZONE_ID),period=src.demand_periods.PERIOD,demand_mw=src.demand_periods.DEMAND_MW,price_per_mwh=src.demand_periods.PRICE_PER_MWH,))model.define(DemandForecast.new(id=src.demand_forecasts.ID,substation=Substation.filter_by(id=src.demand_forecasts.SUBSTATION_ID),forecast_period=src.demand_forecasts.FORECAST_PERIOD,predicted_load_mw=src.demand_forecasts.PREDICTED_LOAD_MW,confidence=src.demand_forecasts.CONFIDENCE,))model.define(DemandForecast.includes_dc_growth(DemandForecast.filter_by(id=src.demand_forecasts.ID))).where(src.demand_forecasts.INCLUDES_DC_GROWTH)model.define(Generator.new(id=src.generators.ID,name=src.generators.NAME,gen_type=src.generators.GEN_TYPE,substation=Substation.filter_by(id=src.generators.SUBSTATION_ID),capacity_mw=src.generators.CAPACITY_MW,marginal_cost=src.generators.MARGINAL_COST,))model.define(Generator.is_renewable(Generator.filter_by(id=src.generators.ID))).where(src.generators.IS_RENEWABLE)model.define(LoadReading.new(reading_id=src.load_history.READING_ID,substation=Substation.filter_by(id=src.load_history.SUBSTATION_ID),reading_date=src.load_history.READING_DATE,load_mw=src.load_history.LOAD_MW,temperature_f=src.load_history.TEMPERATURE_F,))model.define(LoadReading.is_peak_season(LoadReading.filter_by(reading_id=src.load_history.READING_ID))).where(src.load_history.IS_PEAK_SEASON)model.define(RenewableProfile.new(id=src.renewable_profiles.ID,generator=Generator.filter_by(id=src.renewable_profiles.GENERATOR_ID),period=src.renewable_profiles.PERIOD,capacity_factor=src.renewable_profiles.CAPACITY_FACTOR,))model.define(SubstationUpgrade.new(id=src.substation_upgrades.ID,substation=Substation.filter_by(id=src.substation_upgrades.SUBSTATION_ID),capacity_increase_mw=src.substation_upgrades.CAPACITY_INCREASE_MW,cost_million=src.substation_upgrades.COST_MILLION,lead_time_months=src.substation_upgrades.LEAD_TIME_MONTHS,))model.define(SubstationUpgrade.enables_low_carbon(SubstationUpgrade.filter_by(id=src.substation_upgrades.ID))).where(src.substation_upgrades.ENABLES_LOW_CARBON)model.define(TransmissionLine.new(id=src.transmission_lines.ID,from_substation=Substation.filter_by(id=src.transmission_lines.FROM_SUBSTATION_ID),to_substation=Substation.filter_by(id=src.transmission_lines.TO_SUBSTATION_ID),capacity_mw=src.transmission_lines.CAPACITY_MW,maintenance_priority=src.transmission_lines.MAINTENANCE_PRIORITY,))model.define(TransmissionLine.is_active(TransmissionLine.filter_by(id=src.transmission_lines.ID))).where(src.transmission_lines.IS_ACTIVE)model.define(MaintenanceWindow.new(id=src.maintenance_windows.ID,asset_type=src.maintenance_windows.ASSET_TYPE,start_period=src.maintenance_windows.START_PERIOD,end_period=src.maintenance_windows.END_PERIOD,))model.define(MaintenanceWindow.generator(Generator.filter_by(id=src.maintenance_windows.ASSET_ID))).where(MaintenanceWindow.filter_by(id=src.maintenance_windows.ID),src.maintenance_windows.ASSET_TYPE == "generator",)model.define(MaintenanceWindow.transmission_line(TransmissionLine.filter_by(id=src.maintenance_windows.ASSET_ID))).where(MaintenanceWindow.filter_by(id=src.maintenance_windows.ID),src.maintenance_windows.ASSET_TYPE == "transmission_line",)model.define(MaintenanceWindow.is_planned(MaintenanceWindow.filter_by(id=src.maintenance_windows.ID))).where(src.maintenance_windows.IS_PLANNED)"""Queries against the energy-grid semantic model."""from relationalai.semantics.std import aggregates as aggsfrom model import DataCenterRequest, modeldef count_data_center_requests() -> int:"""Return the number of data center requests loaded in the model."""dataframe = model.select(aggs.count(DataCenterRequest).alias("data_center_request_count")).to_df()if dataframe.empty:return 0return int(dataframe.iloc[0]["data_center_request_count"])if __name__ == "__main__":print(count_data_center_requests())
Deploy to a development schema with /rai-deployment
Section titled “Deploy to a development schema with /rai-deployment”Your model is defined in Python, but it isn’t ready to query yet. Deploy it to create its outputs in Snowflake. The first refresh populates those outputs using your source data.
-
Choose where to deploy.
Choose a dedicated output schema for this tutorial, such as
RAI_TUTORIAL.ENERGY_GRID_DEV. Don’t use the source schema (RAI_TUTORIAL.ENERGY_GRID) or a production schema. PyRel also uses a separate metadata schema to manage the deployment; by default, this would beRAI_TUTORIAL.ENERGY_GRID_DEV_META.You’ll also need a Snowflake role with permission to deploy and refresh the model. If you’re unsure which role to use, ask your administrator and share Set the deployment target. An administrator can also prepare schemas and tags for you.
-
Ask the agent to deploy.
Copy this prompt:
/rai-deployment Deploy this model to a development schema using PyRel 1.38.0.Ask me for the output schema and deployment role. Keep my connection settings and source mappings. Configure model.path to load the complete model package.Create and activate a dev profile with a default refresh schedule and auto_deploy false. Explain the output schema, metadata schema, role, and schedule, then ask me to confirm before deploying through uv.Use the installed CLI's help for current options. Wait for the initial refresh to succeed before running any model queries.When prompted, provide your chosen schema and role—not credentials. Check the agent’s proposed settings before approving:
- The output and metadata schemas are dedicated to this tutorial and separate from your source data.
- The
devprofile selects these settings for subsequent commands. - The refresh schedule controls how often outputs update.
auto_deploy: falsemeans you’ll explicitly redeploy after changing the model.
Keep your existing authentication method and secret references. In PyRel 1.38.0, the deployment command waits for the first refresh by default. Continue only when both deployment and that refresh succeed.
If either fails, ask the agent to diagnose the reported error before continuing. See Troubleshoot a deployed model for common errors, or Monitor a deployed model if refresh status is unclear.
-
Run the count query.
Now ask the agent to run the query it prepared earlier:
Run the data-center request count query in queries.py using the dev profile. Compare it with the number of requests in the Snowflake source tables that the model should include. Investigate any difference.The counts should match if the model includes every source request. If they don’t, ask the agent to check the source mappings and refresh status before proceeding. This checks the number of requests, not every property or relationship.
Keep the
devprofile active as you explore the model below.
Answer questions with /rai-pyrel
Section titled “Answer questions with /rai-pyrel”Ask your agent a business question in plain language.
The /rai-pyrel skill helps it write a PyRel query against the deployed model.
-
Ask about substation capacity.
Copy this prompt:
/rai-pyrel Which substation has the highest percentage of its capacity in use? What customers would be affected if it went offline?Add the query to queries.py and run it using the dev profile against the deployed model. Keep model definitions unchanged. Explain any part the model cannot answer rather than inventing relationships. -
Check the answer and its limits.
Review the query in
queries.pyand ask the agent to explain how it produced the answer. Check that it:- Identifies the substation with the highest capacity utilization and shows the calculation: current load ÷ maximum capacity × 100.
- Reports affected customers only if the model contains relationships connecting them to the substation.
- Explains any missing relationship instead of presenting an unsupported answer.
If a relationship is missing, ask the agent whether the source tables contain the information needed to model it. Adding that relationship requires a model change and redeployment before querying again. If the query fails, ask the agent to check the error, active
devprofile, and refresh status before retrying.See Query a model with PyRel to learn how the query works.
Plan the reasoning workflow with /rai-discovery
Section titled “Plan the reasoning workflow with /rai-discovery”You’ve queried the model’s existing facts. Next, explore how reasoning could help choose which data-center requests to approve and which grid upgrades to fund.
-
Ask for a plan and handoff prompts.
Copy this prompt:
/rai-discovery How can this model help choose which data-center requests to approve and which upgrades to fund within a budget?Explain how business rules, graph analysis, and prescriptive reasoning could work together. Identify missing data or relationships and the trade-offs of each stage.Give me one short, copyable handoff prompt per stage. Name the skill, files to change, required inputs, and model outputs the next stage will use. Keep model definitions in model/ and queries in separate root scripts.Each handoff must separate defining the model, redeploying with /rai-deployment using the existing dev profile, and querying only after a successful refresh. Include any extra setup needed for that stage. Plan only; don't change files or run optimization. -
Check the recommendation.
Ask the agent to explain what each stage contributes:
Stage Purpose Business rules Calculate available capacity and identify eligible requests. Graph analysis Measure how substations connect to the rest of the network. Prescriptive reasoning Choose approvals and upgrades subject to capacity and budget constraints. Review the assumptions before accepting the plan. For example, eligibility thresholds are business choices, and graph centrality is a measure of network structure—not proof of electrical reliability or a substitute for power-flow analysis. Ask the agent why each proposed measure is useful for your decision.
-
Check for missing information.
If the agent identifies a gap, ask whether the information exists in Snowflake but isn’t mapped, or whether the source data itself is missing. A missing mapping requires a model change and redeployment; missing source data must be supplied before that part of the plan can work.
The skill may label a recommendation
READY. This means the model and data appear suitable for the proposed reasoning, not that deployment permissions or solver setup have been checked. If the plan assumes unavailable information, ask the agent to revise it before proceeding. -
Review the handoffs before using them.
Each handoff prompt should name the skill to invoke, the files to edit, and the outputs it will add. Check that each stage reuses the previous stage’s outputs and keeps queries out of model imports. Deployment must use the existing
devprofile and finish refreshing before result queries run.Use the following sections to review the generated handoffs. If a handoff omits a requirement, ask the agent to revise it before running it. Run one stage at a time so you can check its results before starting the next.
The following sections show the requirements and example prompts for this tutorial’s rules, graph, and prescriptive stages. Use your reviewed handoffs to carry out the plan; the examples help you check or refine them, rather than repeat work you’ve already completed.
Encode business rules with /rai-pyrel
Section titled “Encode business rules with /rai-pyrel”Use the business-rules handoff prompt from your /rai-discovery output to add rules for available capacity, request eligibility, and upgrade comparisons.
-
Review the rules handoff.
Find it in your
/rai-discoveryoutput and check its formulas and thresholds. Treat these as tutorial assumptions, not operational grid-planning recommendations. Your handoff may look like this:/rai-pyrel Add the business rules from our agreed plan to model/rules.py and load them through model/__init__.py. Calculate available substation capacity, identify eligible requests, and compare upgrade efficiency. Preserve existing source mappings.Keep model imports free of queries and data printing. Prepare a summary in the root queries.py, guarded by if __name__ == "__main__". Show me the rules before deployment.After I approve, use /rai-deployment to redeploy with the existing dev settings. Wait for a successful refresh, then run the summary and explain the results, including missing values. -
Run the handoff and approve the changes.
Send your reviewed handoff to the agent. Check that the generated rules match the plan before approving deployment. If deployment or refresh fails, resolve the error before querying; see Troubleshoot a deployed model.
-
Review the summary.
Check a few reported requests, capacity calculations, and upgrade comparisons against the agreed rules and source values. Eligibility is screening, not approval: a request may still need an upgrade. If a rule needs correcting, have the agent fix it, redeploy, and wait for refresh before querying again.
Reveal hidden structure with /rai-graph-analysis
Section titled “Reveal hidden structure with /rai-graph-analysis”Use the graph handoff prompt from your /rai-discovery output to measure how substations connect to the rest of the grid.
Graph reasoning adds scores that later rules and optimization can use.
These scores describe network structure, not electrical reliability or power flow.
-
Review the graph handoff.
Check which substations and transmission lines it includes, how lines are weighted, and which scores it adds. Your handoff may look like this:
/rai-graph-analysis In model/graph.py, define an undirected graph of substations connected by active transmission lines, weighted by line capacity. Bind eigenvector centrality to Substation.centrality_score and define Substation.network_weight as 1 + centrality_score. Load the module through model/__init__.py without running queries.Preserve the existing rules and source mappings. Prepare a centrality report in the root queries.py showing the top ten substations, their available capacity, and pending requests. Explain how isolated substations and missing scores are handled.Show me the model changes. After I approve, use /rai-deployment to redeploy with the existing dev settings. Wait for a successful refresh, then run the report and explain the scores. -
Run the handoff and approve the changes.
Send your reviewed handoff to the agent. Check that graph scores are defined as model properties, not calculated only inside the report. Approve deployment to the existing
devtarget, and resolve any deployment or refresh errors before querying. See Troubleshoot a deployed model if needed. -
Review the graph report.
Ask the agent to explain which substations rank highest and whether pending requests target them. Check that low scores and missing scores are distinguished. Before using the scores in optimization, review why favoring well-connected substations makes sense for the tutorial’s goal. After any model correction, redeploy and wait for refresh before querying again.
Find optimal actions with prescriptive reasoning
Section titled “Find optimal actions with prescriptive reasoning”Use the prescriptive handoff prompt from your /rai-discovery output to choose requests and upgrades under capacity and budget constraints.
Prescriptive reasoning finds decisions that best satisfy the objective you define.
Its recommendations depend on your data, constraints, and assumptions.
-
Review the prescriptive handoff.
Check the eligible requests, budget levels, capacity limits, and objective with the agent. If graph scores influence the objective, ask how that changes the meaning of “best.” Your handoff may look like this:
/rai-prescriptive-problem Use our agreed rules and graph outputs to define binary request-approval and upgrade-funding decisions in model/optimization.py.Compare budgets of $50M, $150M, and $300M using a Scenario concept. Give each scenario its own decisions, budget constraint, and per-substation capacity constraints. Maximize total centrality-weighted expected revenue across these independent scenarios.Configure one Problem with Problem.configure("highs"), not solve(). Publish scenario-specific approvals, funded upgrades, revenue, spend, and weighted objective values as model outputs. Load the module through model/__init__.py without running queries. Prepare a separate portfolio_decision.py report.Show me the formulation and check the deployment refresh credentials. After I approve, use /rai-deployment to redeploy with the existing dev settings and wait for a successful refresh. Then use /rai-prescriptive-results to query solver status and deployed results from the report, not to call solve().Each scenario represents a separate budget choice, not a share of one combined budget. For deployment,
Problem.configure()declares the optimization; the refresh executes it. If the skill proposesproblem.solve()or a Python loop that changes the budget and solves immediately, ask it to revise the handoff before running it. -
Run the handoff and approve the changes.
Send your reviewed handoff to the agent. Check that each scenario’s approved load fits within available capacity plus funded upgrades, and that upgrade spending stays within that scenario’s budget. Approve deployment only after the formulation and refresh credentials are ready. If deployment or refresh fails, resolve the error before reading recommendations.
-
Review the decisions and solver status.
Have the agent run
portfolio_decision.pyusing thedevprofile. Check the solver status before interpreting approvals: a successful refresh alone doesn’t prove the solver found an optimal solution. If the problem is infeasible or the solve is incomplete, ask the agent to explain the status before recommending actions.Compare approved requests, funded upgrades, spending, and expected revenue across scenarios. Keep revenue separate from the graph-weighted objective; a higher weighted score doesn’t necessarily mean higher revenue. Ask what the extra budget buys and which assumptions could change the recommendation. After any model correction, redeploy and wait for refresh before rerunning the report.
Tear down the tutorial deployment
Section titled “Tear down the tutorial deployment”When you’re finished exploring, remove the deployed model’s outputs and refresh tasks. Teardown leaves your local project, Snowflake source tables, and deployment schemas in place.
-
Preview what will be removed.
Copy this prompt:
/rai-deployment Help me tear down this tutorial's deployment. Using the existing dev profile, run uv run rai models teardown without --force to preview the removal.Show me the target schemas and objects to be removed, then ask for confirmation. Only after I approve, run the same command with --force. Keep the source tables, schemas, local project, and configuration. If teardown is blocked, explain why; don't override teardown protections. -
Confirm the target and remove the deployment.
Check that the preview targets only this tutorial’s development model. Approve removal only when the target and listed objects are correct. If teardown fails, ask the agent to resolve the reported error before retrying; see Tear down a model.
-
Check the cleanup result.
Ask the agent to confirm that the listed model outputs and refresh tasks were removed and that your source tables remain. The output and metadata schemas remain, as does
raiconfig.yaml. Don’t run model queries to check cleanup: the deployment is gone. Deploying again with the same configuration recreates it in the same target schemas.
Nice work!
You used a coding agent and discovery handoffs to build a semantic model, deploy changes to Snowflake, and explore the results with business rules, graph analysis, and optimization.