Initial Release
Ask your InterSystems IRIS data questions in plain English (or Portuguese) - no SQL required.
Business users struggle with the IRIS Management Portal and IRIS SQL. IRIS Data Copilot is a simple web portal where
people type what they want to know (“revenue by region over the last 6 months”) and an LLM managed by
InterSystems IRIS AI Hub reads the database catalog, writes the SQL with the right JOINs, runs it on IRIS, fixes
its own mistakes and picks the best chart. With one click the result becomes a real-time dashboard widget or a
reusable SQL view that any tool (ODBC/JDBC, BI, Excel) can query.
| Area | What it does |
|---|---|
| Ask - SQL Query | Natural language → IRIS SQL → execution → chart/table. Multi-turn (“now only the West region”, “split by channel”). Self-correction: when IRIS returns an error or 0 rows, the real error goes back to the LLM, which fixes the query (up to 3 attempts). |
| Ask - AI Analyst | Autonomous %AI.Agent with tools (%AI.ToolSet): lists schemas, describes tables, runs queries, cross-checks results and writes an analysis. The queries the agent ran can be opened as charts. |
| Result actions | Add to dashboard · Create view · Analyze result (AI executive summary) · Refine with AI · CSV export · Open in the SQL editor. |
| Dashboards | Grid of widgets (KPI, bar, line, area, pie, doughnut, scatter, table) with auto-refresh (5 s to 5 min). ✨ Generate with AI: describe the dashboard and the AI creates 5-8 widgets, testing and fixing every query on IRIS. |
| SQL editor | For people who know SQL: run, Explain with AI and Refine with AI (“only delivered orders”). |
| Views | Views created by the portal in the Analytics schema, with definition, author and date. |
| Catalog + Business glossary | Schemas, tables, columns, primary keys and automatically detected relationships, plus an editable business glossary: official definitions (“revenue”, “last month”, “average order value”…) that the AI must follow in every question, dashboard and analysis. |
| History | Audit trail of questions, generated SQL, attempts, time and errors, with “reopen” and “ask again”. |
| Two languages | English and Portuguese (Brazil) for the UI and for every AI answer, switchable at any time. |
flowchart LR
U[Business user] -->|browser| W[Web portal
FastAPI + JS + Chart.js]
W -->|IRIS Native API
sign-in with an IRIS user| F[Copilot.API.Facade]
subgraph IRIS[InterSystems IRIS AI Hub · namespace COPILOT]
F --> A[Copilot.AI.SQLAssistant
NL → SQL + self-correction]
F --> G[Copilot.AI.Analyst
%AI.Agent]
G --> T[Copilot.AI.Tools
%AI.ToolSet]
A --> L[Copilot.Core.LLM
%AI.Provider]
G --> L
A --> S[Copilot.Core.Schema
catalog + relationships + glossary]
A --> Q[Copilot.Core.SQL
read-only guard]
T --> Q
Q --> D[(Your IRIS tables
demo: Core · Sales · Finance · Inventory)]
F --> P[(Dashboards, widgets, glossary,
history, LLM call log)]
M[Copilot.MCP.Service
%AI.MCP.Service] --> T
end
L -->|provider API| LLM[(OpenAI / Anthropic /
OpenAI-compatible endpoint)]
Copilot.API.Facade.Dispatch(action, json).%AI.Provider), never in the browser. Every callCopilot_Data.LLMCall (purpose, model, latency, tokens); today’s usage is shown in the sidebar.Copilot.Core.Schema reads INFORMATION_SCHEMA and the class%DESCRIPTION), relationshipscolumn -> Schema.Table.Column, from FOREIGN KEYs and from object references in persistent classes), sampleTOP instead of LIMIT, %EXACT when grouping text, CAST(... AS DATE) beforeTO_CHAR, IRIS date functions, etc. Whatever slips through is fixed by the self-correction loop using the real IRISCopilot.Core.Isolate), keeping AI-Core/mcp/copilot (%AI.MCP.Service).git clone https://github.com/Andre-Dienes/iris-dashboard-ai.git
cd iris-dashboard-ai
The %AI.* classes only exist in the AI Hub Early Access builds.
iris-container-x64.key).docker load -i iris-community-2027.1.0AI.109.0-docker.tar.gz
Output example: Loaded image: intersystems/iris-community:2027.1.0AI.109.0.
Copy the key file into the keys/ folder of the project:
cp /path/to/iris-container-x64.key keys/
The folder is mounted at /external/keys and the container starts IRIS with -k /external/keys/<file>.key, exactly
like the official docker run ... -k /external/keys/iris-container-x64.key command. *.key files are git-ignored.
Non-Community images without a valid key refuse external connections (“Unable to allocate a license”) and the
portal cannot sign in. EAP keys are temporary: the IRIS log warnsLicense will expire in N days.
cp .env.example .env
Edit .env:
| Variable | Set it to |
|---|---|
IRIS_IMAGE |
The tag printed by docker load in step 2. |
LLM_API_KEY |
Your LLM API key. |
LLM_PROVIDER / LLM_MODEL |
Optional, defaults openai / gpt-4o-mini. |
LLM_AGENT_MODEL |
Optional stronger model for the AI Analyst only (.env.example uses gpt-4o). |
docker compose up -d --build
The build compiles the classes, creates the demo dataset, the sample dashboard and the glossary (about 2 minutes).
Follow the startup with:
docker logs -f copilot-iris
Wait for IRIS Data Copilot ready.
Go to http://localhost:8080 and sign in with _SYSTEM / SYS (demo password of the image).
| Service | Address |
|---|---|
| IRIS Data Copilot portal | http://localhost:8080 |
| API documentation (OpenAPI) | http://localhost:8080/api/docs |
| IRIS superserver | localhost:1972 (namespace COPILOT) |
Questions for the demo dataset:
A fictional US retail chain, created through DDL with foreign keys and English descriptions:
| Schema | Tables |
|---|---|
Core |
Branch (17 stores in 4 US regions), Salesperson (~90), Customer (3,000), Category (12), Product (~100) |
Sales |
SalesOrder (15,000 orders over 24 months, with seasonality and growth), OrderItem (~37,000) |
Finance |
Payment (credit/debit card, digital wallet, bank transfer; approved, declined, refunded) |
Inventory |
StockLevel (per product and branch, with reorder point) |
A simulator (Copilot.Setup.Simulator) inserts an order every 5 s (SIMULATOR_INTERVAL), decreases stock and
moves order statuses forward, so dashboards show live data.
docker compose down
docker compose up -d --build
down stops the stack; the demo database lives inside the image, so the next up starts from a clean dataset.
Use --build after code changes.
The Copilot works on any tables visible in the IRIS namespace it runs in. Pick the option that matches where your
data lives.
| Option | Your data is… | Effort |
|---|---|---|
| A | CSV files or SQL scripts you can load | Low |
| B | Not ready yet; you want an empty install and will create tables yourself | Low |
| C | Already in an IRIS / IRIS for Health instance | Medium |
| D | In PostgreSQL, SQL Server, Oracle, MySQL… | Medium |
Whatever the option, finish with Make the AI understand your data.
Start the stack without the demo data (recommended). In .env:
COPILOT_DEMO=0
SIMULATOR_INTERVAL=0
docker compose up -d --build
To keep the demo and add your data next to it, skip this step.
Create your tables in the COPILOT namespace. Open an IRIS SQL shell:
docker exec -it copilot-iris iris sql IRIS -U COPILOT
and run your DDL, using a schema per business area and declaring primary and foreign keys (the AI uses them
to build JOINs):
CREATE TABLE Clinic.Patient ( PatientID INTEGER NOT NULL PRIMARY KEY %DESCRIPTION 'Patient code', Name VARCHAR(100) %DESCRIPTION 'Full name', BirthDate DATE %DESCRIPTION 'Date of birth', %DESCRIPTION 'Patients registered in the clinic')
CREATE TABLE Clinic.Visit ( VisitID INTEGER NOT NULL PRIMARY KEY, PatientID INTEGER NOT NULL %DESCRIPTION 'Patient seen', VisitDate TIMESTAMP %DESCRIPTION 'Date and time of the visit', Amount NUMERIC(12,2) %DESCRIPTION 'Amount billed in USD', CONSTRAINT FK_Visit_Patient FOREIGN KEY (PatientID) REFERENCES Clinic.Patient (PatientID), %DESCRIPTION 'Patient visits')
Type quit to leave the SQL shell.
Copy your CSV files into the container and load them with LOAD DATA:
docker cp ./data/patients.csv copilot-iris:/tmp/patients.csv
docker exec -it copilot-iris iris sql IRIS -U COPILOT
LOAD DATA FROM FILE '/tmp/patients.csv' INTO Clinic.Patient USING {"from":{"file":{"header":true}}}
For larger or recurring loads, mount a folder as a volume in docker-compose.yml (e.g. ./data:/data:ro) and
load from /data/....
Persist your data. The demo stack keeps the database inside the container, so recreating the container
discards changes. For real data, either put the DDL and LOAD DATA statements in a class method called from
iris/iris.script (so they run at build time), or use IRIS
Durable %SYS
with a mounted volume.
In the portal, open Catalog → ↻ Reload catalog: your tables appear and you can start asking questions.
Set COPILOT_DEMO=0 and SIMULATOR_INTERVAL=0 in .env, run docker compose up -d --build and create/load your
tables as in option A, steps 2-5. No demo tables, sample dashboard or glossary terms are created.
If you already built the stack with the demo, you can remove it without rebuilding:
docker exec -it copilot-iris iris session IRIS -U COPILOT
do ##class(Copilot.Setup.Simulator).Stop()
do ##class(Copilot.Setup.DemoData).DropAll()
do ##class(Copilot.Data.Dashboard).%KillExtent(), ##class(Copilot.Data.Widget).%KillExtent()
do ##class(Copilot.Data.Glossary).%KillExtent()
halt
Use this when your data already lives in an IRIS instance (on-premises, cloud or another container). The instance
must be an AI Hub build (the %AI.* classes are required) and its superserver port (default 1972) must be
reachable from the machine running the portal.
Copy the src/ folder of this repository to the IRIS server (or to a path the IRIS process can read).
In an IRIS terminal, switch to the namespace that contains your data and load the classes:
zn "MYAPP"
do $system.OBJ.LoadDir("/path/to/iris-dashboard-ai/src", "ck", .errors, 1)
do ##class(Copilot.Setup.Installer).Install()
Install() loads no demo data; it registers the MCP tools and prints the schemas the Copilot can see. Only the
Copilot.* package (SQL schemas Copilot_*) and ^Copilot.* globals are added to your namespace.
Configure the LLM inside that IRIS instance (it does not read the portal’s .env). Either set environment
variables for the IRIS process (LLM_API_KEY, LLM_MODEL…) or store them in the namespace:
do ##class(Copilot.Core.Config).Set("LLM_PROVIDER", "openai")
do ##class(Copilot.Core.Config).Set("LLM_API_KEY", "")
do ##class(Copilot.Core.Config).Set("LLM_MODEL", "gpt-4o-mini")
do ##class(Copilot.Core.Config).Set("LLM_AGENT_MODEL", "gpt-4o")
Keys stored this way live in the ^Copilot.Config global of that namespace; protect the database accordingly.
Make sure the %Service_Bindings service is enabled (Management Portal → System Administration → Security →
Services) and that portal users have SQL privileges on your tables.
Run only the web portal, pointing to your instance. In .env:
EXTERNAL_IRIS_HOST=my-iris-server.example.com
EXTERNAL_IRIS_PORT=1972
IRIS_NAMESPACE=MYAPP
Use host.docker.internal as the host when IRIS runs on the same machine as Docker. Then:
docker compose -f docker-compose.external.yml up -d --build
Open http://localhost:8080 and sign in with an IRIS user of that instance.
IRIS can expose tables of other databases through Foreign Tables (IRIS 2023.1+) or SQL Gateway linked
tables, so the Copilot queries them as if they were IRIS tables, without copying the data.
Run the stack with option B (or use your own IRIS, option C).
Create a JDBC (or ODBC) SQL Gateway connection to your database (Management Portal → System Administration →
Configuration → Connectivity → SQL Gateway Connections), e.g. named SalesPG. When the image has no web server,
use an IRIS instance that has the Management Portal or create the connection programmatically
(see SQL Gateway). The JDBC
driver .jar must be available to IRIS.
Create a foreign server and foreign tables in the COPILOT namespace:
CREATE FOREIGN SERVER PG.Server FOREIGN DATA WRAPPER JDBC CONNECTION 'SalesPG'
CREATE FOREIGN TABLE PG.Orders SERVER PG.Server TABLE 'public.orders'
CREATE FOREIGN TABLE PG.Customers SERVER PG.Server TABLE 'public.customers'
See Foreign Tables.
Foreign tables have no foreign keys or descriptions in IRIS, so describe the joins in the glossary (next
section), e.g. term “Orders and customers” → “PG.Orders.customer_id = PG.Customers.id”.
For heavy dashboards, consider copying the data periodically into native IRIS tables (faster, and they can have
keys and descriptions).
These steps matter more than the model you use:
%DESCRIPTION to tables and columns (or /// comments on persistent class properties).-> relationships the AI uses for JOINs.VISIBLE_SCHEMAS=Sales,Finance (allow-list) or HIDDEN_SCHEMAS=Staging,Tmp in .envCopilot.Core.Config.Set(...) in option C), or let each user pick Sources in the top bar. A smallerMAX_SCHEMA_CHARS.SELECT only on what it may see.Settings are read from ^Copilot.Config(key) first, then from the environment (.env → container), then defaults.
Change them at run time with do ##class(Copilot.Core.Config).Set("KEY", "value") in the COPILOT namespace.
| Key | Default | Description |
|---|---|---|
IRIS_IMAGE |
intersystems/iris-community:2027.1.0AI.109.0 |
AI Hub image tag (build time). |
COPILOT_DEMO |
1 |
1 builds the demo dataset; 0 installs empty (build time). |
LLM_PROVIDER |
openai |
AI Hub provider name (openai, anthropic, …). |
LLM_MODEL |
gpt-4o-mini |
Model for SQL generation, explanations, insights and dashboards. |
LLM_AGENT_MODEL |
LLM_MODEL |
Model for the AI Analyst agent (.env.example uses gpt-4o). |
LLM_API_KEY |
API key (falls back to OPENAI_API_KEY / ANTHROPIC_API_KEY). |
|
LLM_BASE_URL |
OpenAI-compatible endpoint (Azure, Ollama, vLLM, LM Studio). | |
COPILOT_LANG |
en |
Default answer language when the UI sends none (en or pt). |
IRIS_NAMESPACE |
COPILOT |
Namespace the portal connects to. |
VISIBLE_SCHEMAS |
Allow-list of schemas the AI may see (comma separated). | |
HIDDEN_SCHEMAS |
Extra schema prefixes to hide (comma separated). | |
VIEW_SCHEMA |
Analytics |
Schema where the portal creates views. |
MAX_ROWS |
1000 |
Maximum rows returned per query. |
MAX_SCHEMA_CHARS |
40000 |
Maximum size of the catalog context sent to the LLM. |
SIMULATOR_INTERVAL |
5 |
Seconds between simulated orders (demo only; 0 disables it). |
WEB_PORT / IRIS_PORT / IRIS_WEB_PORT |
8080 / 1972 / 52773 |
Host ports. |
EXTERNAL_IRIS_HOST / EXTERNAL_IRIS_PORT |
host.docker.internal / 1972 |
IRIS used by docker-compose.external.yml. |
The portal is bilingual (US and Brazil). The EN | PT-BR switch is on the sign-in page and in the top bar; the
choice is saved in the browser, and the first visit follows the browser language. The language applies to:
web/static/i18n.js).AS Revenue / AS Faturamento). The data and catalog can stay in one language: the AI translates1,234.56 and 10/31/2026 in EN; 1.234,56 and 31/10/2026 in PT-BR) and CSVUser-created content (dashboard names, widget titles, glossary terms, views) stays in the language it was written in.
To add a language, add a block to I18N in web/static/i18n.js and a branch in
Copilot.Core.Config.LanguageRule().
Copilot.Core.SQL.Validate accepts only one SELECT/WITH statement, without comments and withoutAnalytics schema.env (git-ignored) or in IRIS config; they never reach the browser. License keys live inkeys/*.key (git-ignored)._SYSTEM/SYS is the default password of the image. Do not expose the ports on a public| Symptom | Cause / fix |
|---|---|
Sign-in fails with Unable to allocate a license |
Non-Community image without a valid key: put the .key file in keys/ and run docker compose up -d. Check docker logs copilot-iris for License will expire. |
Sign-in fails with <SSL Error> |
Old Python driver; the portal pins intersystems-irispython==5.4.0 (rebuild the web image). |
| Sidebar says “LLM not configured” | LLM_API_KEY is empty: set it in .env and run docker compose up -d (recreates the container). |
| Answers use the wrong metric or period | Add or adjust the definition in Catalog → Business glossary. |
| Text results in UPPERCASE | IRIS default collation in GROUP BY; the prompt asks for %EXACT(...). Ask again or refine. |
docker compose up fails pulling the IRIS image |
The AI Hub image is not on a public registry: docker load it and set IRIS_IMAGE to the printed tag. |
| Port already in use | Change WEB_PORT / IRIS_PORT / IRIS_WEB_PORT in .env. |
src/Copilot/
API/Facade.cls single entry point (JSON in/out)
AI/SQLAssistant.cls NL→SQL, self-correction, explain, refine, insights, dashboard generation
AI/Analyst.cls %AI.Agent (AI Analyst)
AI/Tools.cls %AI.ToolSet: ListSchemas, ListTables, DescribeTable, RunQuery
Core/LLM.cls %AI.Provider + LLM call log
Core/Schema.cls catalog, relationships, sample values, today's date + glossary context
Core/SQL.cls read-only validation and execution
Core/Isolate.cls runs AI operations in an isolated process
Core/Config.cls configuration (^Copilot.Config → environment → default) and language rules
Data/ Dashboard, Widget, Glossary, QueryLog, LLMCall
MCP/Service.cls %AI.MCP.Service at /mcp/copilot
Setup/ Installer, DemoData, Simulator
web/
app/main.py FastAPI: sign-in, sessions, /api/call/{action}
app/iris_bridge.py per-user IRIS Native API connection pool
static/ index.html, app.js, i18n.js, styles.css
iris/ Dockerfile, iris.script, entrypoint.sh, dev-load.sh
keys/ put your IRIS license key here (git-ignored)
docker-compose.yml full stack (IRIS + portal)
docker-compose.external.yml portal only, for an existing IRIS instance
With the containers running, ./iris/dev-load.sh copies src/ into the container and recompiles the Copilot
package; for the portal, run docker compose up -d --build web.
| Build | Result |
|---|---|
iris:2027.1.0AI.108.0 + license key |
Validated end to end: portal, NL→SQL with self-correction, AI Analyst (%AI.Agent), dashboard generation, views, live dashboard, EN and PT-BR. |
irishealth-community:2026.3.0AI.136.0 |
Works without a key, including the portal. |
iris-community:2027.1.0AI.109.0 |
Build named in the AI Hub program instructions (same setup, key in keys/); not yet tested here. |