Published Apr 2024

Cookbook recipes to get up and running with Spice.ai quickly ๐
spice add spiceai/cookbook-generative-viz-recipespice connect spiceai/cookbook-generative-viz-recipedependencies:
- spiceai/cookbook-generative-viz-recipeSample sales data including order details, product information, and customer data
version: v2
kind: Spicepod
name: generative-visualizations
runtime:
task_history:
captured_output: truncated
datasets:
- from: s3://spiceai-demo-datasets/cleaned_sales_data.parquet
name: sales
description: Sample sales data including order details, product information, and customer data
params:
file_format: parquet
acceleration:
enabled: true
engine: cayenne
models:
- name: visualisation_and_sql
from: openai:gpt-6-luna
params:
# gpt-6-luna accepts function tools on chat completions only with reasoning_effort none.
reasoning_effort: none
openai_api_key: ${ secrets:SPICE_OPENAI_API_KEY }
tools: sql, list_datasets, table_schema
system_prompt: |
Your job is to make a chart.js scripts and the SQL query needed for the given question a user asks. Use the `list_datasets` and `table_schema` tools to ensure the SQL query is correct. Before you return the SQL query, make sure it works by running `EXPLAIN <query>`.
`chart_js_html` must be a complete, self-contained HTML document that reads its rows from the `window.__DATA__` global, e.g. `const rows = window.__DATA__ || [];`. The caller runs your SQL query and defines `window.__DATA__` before your script runs, so do not inline the results yourself and do not fetch data over the network.
Spice formats timestamps with strftime specifiers: `to_char(ts, '%Y-%m')` and `date_format(ts, '%Y-%m')` both yield `2003-01`. Postgres-style patterns such as `'YYYY-MM'` do not error โ they are returned verbatim as that literal string, giving every row an identical label. Prefer `EXTRACT` with `LPAD`, and always check a formatted column's actual values with the `sql` tool before returning the query.
Keep the chart configuration simple and dependency-free. Load chart.js and nothing else. Put pre-formatted strings in `data.labels` and plain numbers in `data.datasets[].data`. Never use `type: 'time'` for an axis, never load a date adapter plugin, and never set `parsing: false` โ a time axis fed `Date` objects under `parsing: false` silently renders an empty chart, because chart.js only accepts numeric timestamps once parsing is disabled.
response_format:
type: json_schema
json_schema:
name: visualisation_and_sql
schema:
type: object
properties:
sql:
type: string
chart_js_html:
type: string
additionalProperties: false
required:
- sql
- chart_js_html
strict: true
- name: summary_maker
from: openai:gpt-6-luna
params:
openai_api_key: ${ secrets:SPICE_OPENAI_API_KEY }
system_prompt: |
You are a data analyst. Your job is to sumarise the trends in the data provided. You will receive the initial question the user provided, the query another analyst ran, and the data it returned from the system.
<examples>
<example-1>
<input>{
"user_question": "How has per month sales trended over the last year?".
"sql_query": "SELECT month, COUNT(*) as sales_count FROM spice.public.sales GROUP BY month ORDER BY month",
"data": [{"month":1,"sales_count":229},{"month":2,"sales_count":224},{"month":3,"sales_count":212},{"month":4,"sales_count":178},{"month":5,"sales_count":252},{"month":6,"sales_count":131},{"month":7,"sales_count":141},{"month":8,"sales_count":191},{"month":9,"sales_count":171},{"month":10,"sales_count":317},{"month":11,"sales_count":597},{"month":12,"sales_count":180}]
}<input/>
<output>- Sales have been varied between months, with a maximum of 597 in November and low of 131 in June.\n- Broadly the middle months of the year received lower sales except for december (130) which appears to be a lull from the high of November (597). <output/>
<example-1/>
<examples/>
Works with v2.0+
This recipe demonstrates how to build an AI-powered data analyst that generates SQL queries and interactive Chart.js visualizations from natural language questions.
sql, list_datasets, table_schema) to enable LLMs to explore and query dataRequired:
Install Spice CLI - Follow the Getting Started guide (opens in a new tab) if you haven't already.
Clone this repository:
git clone https://github.com/spiceai/cookbook.git # Skip if already cloned
cd cookbook/generative-visualisations
Configure your environment:
Create a .env file with your OpenAI API key:
echo "SPICE_OPENAI_API_KEY=your_openai_api_key_here" > .env
Get an API key from OpenAI Platform (opens in a new tab)
Set up Python environment:
This recipe uses uv (opens in a new tab) for dependency management. If you don't have it installed:
curl -LsSf https://astral.sh/uv/install.sh | sh
Then install dependencies (this will create the virtual environment automatically):
uv sync
This recipe uses two AI models working together:
visualisation_and_sql (GPT-6 Luna) - Takes a natural language question and:
list_datasets tooltable_schema toolsql toolsummary_maker (GPT-6 Luna) - Takes the query results and:
Start the Spice runtime in your terminal:
spice run
You should see output indicating that Spice is loading datasets and models:
INFO runtime::init::dataset: Dataset sales initializing...
INFO runtime::init::model: Loading model [visualisation_and_sql] from openai:gpt-6-luna...
INFO runtime::init::model: Model [visualisation_and_sql] deployed, ready for inferencing
INFO runtime::init::model: Loading model [summary_maker] from openai:gpt-6-luna...
INFO runtime::init::model: Model [summary_maker] deployed, ready for inferencing
INFO runtime::init::dataset: Dataset sales registered (s3://spiceai-demo-datasets/cleaned_sales_data.parquet), acceleration (cayenne), results cache enabled. duration_ms=0
INFO runtime_table::accelerated::refresh_task: Loading data for dataset sales
INFO runtime_table::accelerated::refresh_task: Loaded 2,823 rows (1010.18 kiB) for dataset sales in 687ms.
Keep this terminal open. Open a new terminal for the next steps.
In a new terminal, run the main script via uv:
uv run main.py "How has per month sales trended?"
The script will:
visualisation_and_sql modelsummary_maker model for analysisPass --output-html to write a self-contained HTML file. The script runs the
generated SQL and embeds the results in the page, so the chart renders offline
with no further wiring:
uv run main.py "How has sales changed over time?" --output-html chart.html
Then open it in a browser (open chart.html on macOS, xdg-open chart.html on Linux).
Without --output-html, the script prints the Chart.js HTML to stdout alongside
the SQL, the query results, and the summary. That output is a human-readable
report โ don't redirect it straight into a .html file, as the section headers
and the trailing sections are not valid HTML.
Try these sample questions:
# Sales trends
uv run main.py "How has per month sales trended?"
uv run main.py "What are the top 5 products by total sales?"
uv run main.py "Show me quarterly revenue breakdown"
# Product analysis
uv run main.py "Which product lines have the highest sales?"
uv run main.py "Compare sales between different countries"
For the question "How has per month sales trended?", you'll get:
Generated SQL:
SELECT "year", "month", SUM("sales") as total_sales
FROM spice.public.sales
GROUP BY "year", "month"
ORDER BY "year", "month";
Chart.js Visualization:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<canvas id="salesTrendChart" width="600" height="400"></canvas>
<script>
// The model reads rows from `window.__DATA__`, which `--output-html`
// defines from the query results before this script runs.
const rows = window.__DATA__ || [];
// Chart configuration with line chart showing monthly sales trends
...
</script>
</body>
</html>
AI Summary:
Sales show a strong seasonal pattern with peaks in November. The data from 2003-2005 shows consistent growth year-over-year, with November typically exceeding $1M in sales.
| Flag | Description |
|---|---|
--output-html FILE | Write a self-contained HTML file with the chart and query results embedded |
--no-summary | Skip the summary_maker step |
The spicepod.yaml configures:
You can modify the system prompts in spicepod.yaml to:
Error: Could not connect to the Spice API server
spice run) in another terminalError: SPICE_OPENAI_API_KEY not set
.env file exists and contains a valid OpenAI API keygenerative-visualisations directoryEvery x-axis label is identical (e.g. all YYYY-MM)
to_char(ts, '%Y-%m') is correct
while the Postgres-style to_char(ts, 'YYYY-MM') is returned verbatim as a literal
string rather than erroring. Re-run the question, or ask for the label built with
EXTRACT and LPAD.The chart renders but the plot area is empty
parsing: false on a type: 'time' axis. Chart.js only
accepts numeric timestamps once parsing is disabled, so Date objects make the axis
silently fall back to the current month, placing the data off-scale.SQL query errors
Published Apr 2024
Published Sep 2024
Published Sep 2024
Published Sep 2024