DEV Community

Cover image for A Self-Hostable AI Data Analyst with 4 Agents, Sandboxed Code Execution, and Your Choice of LLM
KibeπŸ”ŒπŸ’»
KibeπŸ”ŒπŸ’»

Posted on Edited on

A Self-Hostable AI Data Analyst with 4 Agents, Sandboxed Code Execution, and Your Choice of LLM

Insight Orchestra

I kept running into the same problem whenever I wanted to explore a dataset.

Open a notebook. Load the data. Fix the encoding. Deal with missing values. Write some pandas code. Try a few charts. Go back and change things when the data doesn't behave as expected.

None of that is particularly difficult, but it takes time.

That was the idea behind Insight Orchestra.

It is an open source, self hosted AI data analyst that lets you connect a file or database and explore the data using natural language.

The main goal was simple: run the analysis on infrastructure I control and let me choose which LLM I want to use.

GitHub logo laban254 / insight-orchestra

Self-hostable AI data analyst: a multi-agent LLM pipeline with natural-language querying and sandboxed Python execution.

Insight Orchestra

Your data, analyzed by a team of AI agents.

Connect a data file or a database and watch specialized agents clean it, form hypotheses,
debate them, and visualize what matters β€” then ask follow-ups in plain English

Website Β· Docs Β· Report a bug

CI CodeQL Latest release License Stars Open in GitHub Codespaces



Insight Orchestra β€” four agents cleaning, hypothesising, debating and visualising a dataset

A real run on the bundled Sales dataset β€” unedited.


What is Insight Orchestra?

Insight Orchestra is an open-source AI data analyst you can self-host β€” think Julius AI or ChatGPT's data analysis, but running on your own hardware, with your choice of LLM, where your data never leaves your machine. Upload a data file β€” CSV, TSV, Excel, JSON, or Parquet β€” or connect a PostgreSQL, MySQL, SQLite, or DuckDB database, and a 4-agent pipeline cleans the data, generates evidence-backed hypotheses, scores them in an LLM-refereed debate, and builds interactive Plotly charts. Then keep asking questions in plain English: an NLQ agent writes…




What you can connect

Insight Orchestra currently works with:

Files

CSV, TSV, Excel, JSON and Parquet.

The CSV/TSV loader also detects encoding and delimiters, so files using semicolons or pipes don't need to be cleaned up manually first.

Databases

PostgreSQL, MySQL, SQLite and DuckDB.

The database workflow can query across tables and use JOINs rather than forcing everything into a single table. BigQuery is also available as an experimental connector.

LLMs

OpenAI, Anthropic, DeepSeek and Ollama.

You can change the provider and model from the UI without restarting the application.

For local models, Ollama lets you run the analysis without sending the data to an external LLM provider.

How the analysis works

I split the analysis into four stages instead of asking one LLM to do everything.

Data
 β”‚
 β–Ό
Data Janitor
 β”‚
 β”œβ”€β”€ duplicates
 β”œβ”€β”€ missing values
 β”œβ”€β”€ outliers
 └── data quality
 β”‚
 β–Ό
Hypothesis Bot
 β”‚
 β”œβ”€β”€ statistics
 β”œβ”€β”€ correlations
 β”œβ”€β”€ distributions
 └── possible findings
 β”‚
 β–Ό
Debate Manager
 β”‚
 β”œβ”€β”€ checks the findings
 β”œβ”€β”€ looks at supporting statistics
 └── selects the findings worth showing
 β”‚
 β–Ό
Viz Whiz
 β”‚
 └── generates relevant charts
 β”‚
 β–Ό
Summary + follow-up questions
Enter fullscreen mode Exit fullscreen mode

Each stage produces structured output for the next stage.

The progress is streamed to the browser using Server Sent Events, so you can see which stage is running instead of staring at a loading screen.

Once the initial analysis is complete, you can continue asking questions about the dataset.

For files, the natural language query is converted into pandas code and executed in a restricted environment.

For databases, the system generates read only SQL and can query across the connected schema.

Why I didn't just use one LLM call

My first version basically did that.

I gave the model the schema, some sample data and instructions to analyse everything.

It worked reasonably well on small, clean datasets.

The problem showed up when something went wrong.

If the final chart was wrong, it was difficult to tell whether the problem came from the data cleaning, the analysis, the generated code or the visualization.

There was also no useful progress information while the request was running.

Breaking the process into separate agents made the system easier to reason about.

The Data Janitor deals with data quality.

The Hypothesis Bot works with the resulting statistics.

The Debate Manager checks those findings against the actual numbers.

The Viz Whiz turns the selected findings into charts.

This also gives the system somewhere to recover when an LLM call fails.

For example, the data cleaning stage does not depend on an LLM. If the LLM provider is unavailable, the analysis can fall back to statistical and heuristic methods, and the response explicitly tells you that it did so.

Running generated Python code safely

This was probably the part that required the most thought.

The natural language query system generates Python code and executes it against the dataset.

Simply doing this would obviously be a bad idea:

exec(generated_code)
Enter fullscreen mode Exit fullscreen mode

Generated code should not be allowed to access the operating system, read arbitrary files or make network requests.

I use RestrictedPython to put some boundaries around the execution environment.

Before execution, the generated code goes through an AST check. Dangerous syntax is rejected before execution.

The execution environment also uses restricted builtins, so things such as open, os, subprocess and unrestricted imports aren't available.

The analysis environment exposes libraries such as pandas, NumPy and Plotly.

If the generated code fails, the error is sent back to the model and it gets another chance to produce valid code.

This isn't a claim that RestrictedPython makes arbitrary code execution completely safe. There are still limitations and attack surfaces to consider. The goal is to reduce the blast radius rather than pretend that generated code is automatically trustworthy.

A few practical details

There are a few things in the project that aren't as visible from the main demo.

The system caches repeated queries for the same dataset and model, so asking the same question again doesn't immediately trigger another LLM request.

Analysis sessions can also be saved and reopened. When reopening a session, the application checks whether the original dataset is still available.

Large datasets are sampled for the main analysis once they pass 250,000 rows, and the UI shows how many rows were actually analysed.

There is also an optional RAG layer. Previous analysis results can be stored in PostgreSQL with pgvector and used when generating new hypotheses.

Authentication

Authentication is intentionally off by default.

If you're running the project locally, adding an account before you can analyse a CSV doesn't really help.

For a shared installation, authentication can be enabled with:

AUTH_ENABLED=true
Enter fullscreen mode Exit fullscreen mode

That enables local accounts, OIDC, API keys, roles and an audit log.

The available roles are admin, member and viewer.

Stack

Part Technology
Backend FastAPI + Python
Frontend Next.js + TypeScript
Agents Google ADK
Real time updates Server Sent Events
State Redis
LLMs OpenAI, Anthropic, DeepSeek, Ollama
Code execution RestrictedPython
Deployment Docker Compose

Running it

You need Docker, Docker Compose v2 and Git.

The setup wizard handles the initial configuration:

curl -fsSL https://raw.githubusercontent.com/laban254/insight-orchestra/main/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

Or:

git clone https://github.com/laban254/insight-orchestra.git
cd insight-orchestra
./setup.sh
Enter fullscreen mode Exit fullscreen mode

You can also run it entirely with a local Ollama model:

./setup.sh --provider ollama -y
Enter fullscreen mode Exit fullscreen mode

Or use a cloud provider:

./setup.sh --provider openai --api-key sk-... -y
Enter fullscreen mode Exit fullscreen mode

After setup, the application runs locally and doesn't require an account by default.

What's next

There are still plenty of things I'd like to improve.

Better database connectors, better handling of large datasets, more reliable generated analysis code and more useful follow up questions are some of the areas I'm interested in.

For now, the project is available on GitHub:

Insight Orchestra

It's MIT licensed and self hostable.

If you try it, I'd be interested to know what you'd want to connect to it first: a particular database, file format or another data source.

Top comments (3)

Collapse
 
dhruonweb profile image
Dhruv Malaviya •

"They upload your data to a cloud you don't control, lock you into one LLM, and charge per seat" is the whole case for self-hosting this, and multi-DB with real JOINs rather than one materialised table is the right call.

One thing worth separating: "runs on my own hardware" and "data stays under my control" aren't the same requirement. A Cube on Krova Cloud (I work there) is a Linux server you administer, not a shared platform , your data doesn't sit in a third party's pipeline, but you're not capped by what a laptop can hold.

For agent-generated code specifically, the isolation matters more than it does for a notebook. Each Cube has its own kernel rather than sharing the host's, so a bad pd.read_csv on hostile Parquet is contained at the hardware boundary. 4 vCPU / 8 GB / 80 GB is $19.42/mo, billed by the minute.

Honest tradeoff: your own hardware is still the stronger privacy posture. This is about scale and containment, not secrecy.

How are you sandboxing the code execution right now , subprocess, container, or something else?

Collapse
 
labank_ profile image
KibeπŸ”ŒπŸ’» •

Good catch on the hardware/data-control distinction. I did collapse those two things.

Sandbox is RestrictedPython, not a subprocess or container. AST check before anything runs, then a stripped builtins context with no os, no open, no network. Fast, no Docker-in-Docker headache. The tradeoff is it's a policy boundary, not a kernel one, so it's "meaningful blast radius reduction" rather than true isolation. For most use cases, that's fine; your Cube model is genuinely stronger for the hostile-Parquet scenario.

Collapse
 
dhruonweb profile image
Dhruv Malaviya •

Two recent escapes worth knowing: CVE-2026-76825 (CVSS 8.4, fixed in 8.4) reached builtins via string.Formatter, bypassing safer_getattr. CVE-2026-55830 (fixed in 8.3) shadowed a guard hook through positional-only parameters.

The awkward part for your case: this class of defence is weaker against an LLM than a human, because the model has read every escape write-up ever published.

Pin RestrictedPython>=8.4. A bare dependency is the failure mode when the package is the boundary.