Now in Bruin
Inference assets
Ask every row a question. Get a column back.
AI models on your warehouse data, as a step in your pipeline.
Six providers, your keys
| name | description | industry inference |
|---|---|---|
| Lumen Play | We make puzzle games for phones. | gaming |
| Tillpoint | Payment APIs for online stores. | fintech |
| Stride Supply | Online shop for running shoes. | ecommerce |
| Vanroute | Route planning for delivery vans. | other |
| Ledgerleaf | Small loans for freelancers. | fintech |
choicesfintechecommercegamingother
Inference: 5 rows, 5 model calls, 0 cached results
Trusted by forward-thinking teams
Use cases
What you ask. What you get back.
You ask every row
You get a column
So that
“Which industry is this company in?”
industrystring · 4 choices
Revenue by industry, in plain SQL.
“Choose the ticket's primary category.”
categorystring · 3 choices
Count and route tickets by category.
“Overall sentiment from 1 to 5.”
sentiment_scoreinteger · 1 to 5
Chart how each product is reviewed.
“How well this lead fits, from 0 to 100.”
fit_scoreinteger · 0 to 100
Route leads with WHERE fit_score >= 70.
“Accounting category for this expense.”
expense_categorystring · 6 choices
Card spend on your chart of accounts.
“Whether A and B are the same company.”
same_companyboolean
Merge duplicate records before they spread.
In your repo
The input, the model, the question.
One asset in your pipeline. The next step reads its table like any other.
name: stg.companies_enriched
type: inference
connection: warehouse
parameters:
provider: typesafe
model: jev-latest
input_asset: stg.companies
context: |
{{ row.description }}
{{ row.headcount }} people
materialization:
type: table
strategy: merge
columns:
- name: company_id
primary_key: true
- name: industry
type: string
inference:
prompt: Which industry is this company in?
choices:
fintech: Payments, banking, lending
ecommerce: Sells products online
gaming: Makes or publishes games
other: None of the above$ bruin run
- stg.companies
- stg.companies_enrichedinference · 1,000 rows
- reports.rpt_revenue_by_industry
Inference: 1000 rows, 1000 model calls, 0 cached results
Checked
Every answer checked before it lands.
- stg.companies
- stg.companies_enriched
- reports.rpt_revenue_by_industry
$ bruin run
- stg.companies
- stg.companies_enriched1 invalid answer
- reports.rpt_revenue_by_industryheld
Nothing was loaded. Yesterday's table stays as it was.
Typed
Every column is a string, boolean, integer or number. You declare which.
Your list
With choices, the answer is one of your keys.
In range
Numbers stay between your minimum and maximum, checked by Bruin on every provider.
No silent nulls
An invalid answer fails the run. Nothing loads.
Runs
You write the question. Bruin runs the calls.
Run it again, one row edited
$ bruin run
- stg.companies1 row edited
- stg.companies_enriched1 model call
- reports.rpt_revenue_by_industry
Inference: 1000 rows, 1 model calls, 999 cached results
Cached
An unchanged row is never sent twice. Edit one, and only that one is asked again.
In parallel
16 requests at a time by default. Set extract_parallelism to change it.
Retried
Rate limits and server errors, up to 10 attempts, honouring Retry-After.
Per column
Each column can use its own provider and model.
Spend guard
More rows than max_rows? It stops before the first call.
The platform
Part of the Bruin platform.
A step between your SQL models, in the same repo, lineage and run as everything else.
Sources
DatabasesWarehousesApps & APIsFiles & storageStreams & webhooksWeb scrapingMove
Data IngestionFrequently asked
Questions about inference assets.
What is a Bruin inference asset?
A pipeline step that asks an LLM questions about every row of a table and writes the answers as new, typed columns in a destination table. Downstream SQL reads it like any other table.
Which LLM providers and models can I use?
OpenAI, Anthropic, Google Gemini, OpenRouter, OpenCode Zen and TypeSafe, with any of their models that support native structured output. Each column can use its own provider and model.
What does it cost to run?
Inference assets ship in the open-source Bruin CLI, Apache 2.0. Model calls go to your own provider account: one request per row for each provider and model your columns use.
What happens if the model gives an answer that is not in my list?
Bruin checks every answer against its type, choices and bounds before loading. Any invalid answer fails the run and nothing is loaded, so a column never fills with off-list labels or silent nulls.
Does re-running the pipeline call the model for every row again?
No. Bruin caches each answer on disk under ~/.bruin/inference/, so an unchanged run makes no model calls and editing one row asks again for that row only. Set cache: false to bypass it.
How many rows can one run process?
By default an inference asset fails before any model call if the input has more than 1,000 rows, a guard against accidental spend. Raise max_rows when you mean it; up to 16 requests run in parallel by default.
Can the model look things up on the web?
No. The model sees only the row you render into the prompt: Bruin runs no agents, tools or model-written SQL. To classify companies by industry, give it a description, not just a name.
What types can a generated column have?
string, boolean, integer or number. Strings can be limited to a list of choices and numbers to a minimum and maximum; TypeSafe adds levels and thresholds.
Can I use different models for different columns?
Yes. Set provider and model on a column to override the asset default. Columns that share a provider, model and connection share one request per row.
Can the next step in my pipeline use the answers?
Yes. The result is a regular table, so a downstream SQL asset can join, filter and test it, for example WHERE fit_score >= 70.
Where does my data go?
The rows you render are sent to the provider you choose, under its pricing and data-use terms. API keys live as named connections in .bruin.yml, and cached answers are stored unencrypted on the machine that runs Bruin.
Does it support incremental loads?
Inference assets write with merge, which upserts by primary key, or create+replace, which rebuilds the table. Use input_query to pick the rows to process, for example only yesterday's.
How do I try it?
Install the Bruin CLI, add a provider key to .bruin.yml and run the inference example in the Bruin repo. A second run reports cached results and zero model calls.
Your next column is one question away.
Bruin CLI and ingestr are on GitHub.
A demo walks through your own data.

