# CSV-AI v2 replaces a LangChain pandas agent with computed statistics

> CSV-AI is a Streamlit dashboard that puts a language model next to pandas on one uploaded CSV. The v2 rewrite dropped LangChain, FAISS retrieval and a self-writing pandas agent, so the model sees less of your file and the numbers on screen come from code you can read.

**Safiullah-Rahu/CSV-AI** — CSV-AI is the ultimate app powered by LangChain, OpenAI, and Streamlit that allows you to unlock hidden insights in your CSV files. With CSV-AI, you can effortlessly interact with, summarize, and analyze your CSV files in one convenient place.

- Repository: https://github.com/Safiullah-Rahu/CSV-AI
- Website: https://csv-ai.streamlit.app/
- Stars: 340 · Forks: 43
- Language: Python
- License: MIT
- Published: 2026-09-15 · Updated: 2026-09-15 · Language: en
- Canonical page: https://hysenlabs.com/projects/safiullah-rahu-csv-ai

## The 278-line app.py became a seven-package app/ tree

Version 1 of this project was one file, a 278-line app.py, that read its settings inline with os.environ and offered a single selectbox labelled functionality. v2 keeps the product idea and splits the code into app/config, app/llm, app/data, app/prompts, app/services, app/ui and app/utils, and adds a tests/ directory that did not exist before. The rule stated for that split is that no Streamlit import appears inside services, which is the reason a FastAPI layer is described as a small adapter rather than a rewrite. ChatService, SummaryService and AnalysisService hold the work; the ui/ subpackage holds the pages, components, theme and session state. Configuration moved to pydantic-settings, and pyproject.toml declares version 2.0.0 with requires-python >=3.10.

That structure pays for itself only if you intend to change the thing, and the specific payoff is testability. The pytest suite named alongside the tree covers the loader, the profiler and the LLM factory, and those three are exactly the modules that carry no UI import. A service you can call from a test is a service you can also call from a FastAPI route later.

## Chat dropped FAISS retrieval for a schema and a sample

In v1 the chat path chunked the CSV, embedded the chunks and queried a FAISS index. v2 replaces that with the file schema plus a smart sample, and the project calls the result cheaper and more accurate. The direction is defensible: an embedding index over tabular rows spends money retrieving text that a column name and a handful of rows usually answer already.

The bill arrives as scope. If the chat context is the schema and a sample, then a value that appears only in one far-off row has no route to a correct answer except the deterministic statistics on the Analyze page. Ask how many rows carry an amount above 500 in the 2019 partition and this is the wrong tool; ask what the amount column looks like and it is the right one. The README does not expose the sample size as a setting, so that sampling policy lives in code you would have to read to change.

## Analyze computes the numbers before the model writes prose

The Analyze page runs deterministic pandas statistics and an LLM analyst narrative side by side, with charts, missingness and correlations beside both. That split is the sharpest decision in v2. The v1 page called create_pandas_dataframe_agent and nothing else, which meant the model wrote pandas code, that code ran, and the figures on screen were whatever the model had asked for.

The alternative worth weighing is v1's own approach: hand the model a pandas agent and let it write and execute code. You get flexibility, because the model can compute something you did not anticipate. You give up reproducibility, because a wrong code path produces wrong numbers that still look computed. v2 makes that trade the other way on purpose. The narrative layer can only narrate statistics the profiler already produced, which is why the numbers survive a hallucinating model and the sentences around them do not.

## DEFAULT_PROVIDER picks the model, and localhost stays in the container

DEFAULT_PROVIDER takes openai, anthropic or ollama, and DEFAULT_MODEL starts at gpt-4o-mini until a session picks something else in the sidebar. OPENAI_API_KEY and ANTHROPIC_API_KEY are each required only for their own provider, so you can leave the one you are not using empty.

The Ollama path is the one that breaks in deployment. OLLAMA_BASE_URL defaults to http://localhost:11434, and docker-compose.yml hands the container nothing but a .env file through env_file plus a plain 8501:8501 port mapping. Inside a container, localhost is the container, so a default value aimed at a laptop-hosted Ollama reaches nothing. The compose file adds no host alias and no extra_hosts entry to bridge that gap. Expect to edit OLLAMA_BASE_URL yourself before the first `docker compose up --build`, and expect the model pull to be your problem too.

## Settings resolve from the environment, then .env, then Streamlit secrets

Configuration resolves in a fixed order: environment variables first, the .env file second, Streamlit secrets last. That chain is what lets one codebase run on a laptop and on Streamlit Community Cloud with keys pasted into Secrets instead. Seven variables are documented, and two of them set the shape of every answer. DEFAULT_TEMPERATURE accepts 0.0 to 1.5 and starts at 0.2, which is a sensible place for statistics that should not drift. DEFAULT_MAX_TOKENS starts at 1024 and is labelled a response length cap, so it is the first value to raise when a reply about a wide table keeps stopping mid-sentence.

tiktoken>=0.7 sits in requirements.txt, and app/utils is where token counting lives, so the cap is counted in real tokens rather than characters. Two reference files ship with the settings: .env.example at the root and .streamlit/secrets.toml.example. Nothing in the documented variable list covers the chat sample size or any retrieval budget, because there is no retrieval stage left to budget.

## From a clone to a dashboard on port 8501

The default branch is named v2, so a plain clone lands on the rewritten tree and there is no branch to check out first.

```bash
git clone https://github.com/Safiullah-Rahu/CSV-AI.git
cd CSV-AI

python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -r requirements.txt

cp .env.example .env       # then add your API keys
streamlit run streamlit_app.py
```

The `cp .env.example .env` line is the one that decides which provider answers you, because DEFAULT_PROVIDER defaults to openai and a missing OPENAI_API_KEY leaves the chat empty. After the run command, open http://localhost:8501 and upload a CSV.

Developer setup is a separate install, and the test suite needs no flags because pyproject.toml already points at testpaths = ["tests"] with pythonpath = ["."] and addopts = "-ra --strict-markers".

```bash
pip install -r requirements-dev.txt
pytest                     # run tests
ruff check .               # lint
black .                    # format
```

Both tools run at line-length 100 against py310, and ruff selects E, F, I, B, UP, SIM and PL with PLR0913 and PLR2004 ignored. That ignored list is the first thing to read if you add functions with many parameters or compare numbers inline, because the project has already decided those two complaints are noise.

Container deployment is one command, and the compose file reads the same .env:

```bash
docker compose up --build
```

The image behind it is python:3.11-slim, one minor version above the >=3.10 floor that pyproject.toml, ruff, black and mypy all target.

## The healthcheck calls curl; the image installs build-essential

Read the Dockerfile and the compose file side by side and one gap shows up. The Dockerfile's only apt-get install is build-essential, and its HEALTHCHECK line calls curl -fsS against http://localhost:8501/_stcore/health. The compose healthcheck calls the same curl against the same path. Nothing in the image adds curl, so the check that decides whether the container is healthy asks for a binary the build never installs.

The same files set STREAMLIT_SERVER_ENABLE_CORS=false and STREAMLIT_SERVER_HEADLESS=true, and compose publishes 8501:8501 with restart: unless-stopped and no authentication service anywhere in the stack. Anyone who can reach that port reaches the app. On a laptop that is convenient; on a shared host it is a decision to make deliberately. The build ends with COPY . ./ , and whether .env is excluded from that copy by .dockerignore is not stated in the deployment notes, so check it before building on a machine that holds real keys.

## Version 2.0.0, a branch named v2, and no releases

pyproject.toml carries version 2.0.0 and the repository has no GitHub releases, so there is no tag to pin and no changelog to read. The v1-versus-v2 table in the README is the only account of what changed, and it is worth reading in full because it names every removal: langchain.chat_models, langchain.embeddings, load_summarize_chain with map_reduce, the FAISS index and create_pandas_dataframe_agent are all gone from v2. requirements.txt agrees with it, listing the native openai and anthropic SDKs plus requests and no LangChain package at all.

One leftover is worth naming: push-to-github.ps1 still sits in the repository root, a PowerShell helper from the Windows era of the project. Small, but it is the kind of file that tells you which platform the author actually used.

The last push on the repository was on 2026-05-20, and it is not archived. Development dependencies are declared rather than assumed, with a dev extra in pyproject.toml listing pytest, pytest-cov, ruff, black and mypy. The licence is MIT, so forking this into an internal tool carries no copyleft obligation.

## Conclusion

CSV-AI fits an analyst who wants a browser panel over a single CSV and a provider picked in the sidebar. It does not fit a batch pipeline or a service other code has to call, because the UI is Streamlit and no HTTP layer ships yet. Verify two things before you adopt it: the model is given the schema and a smart sample rather than your rows, and DEFAULT_MAX_TOKENS caps every reply at 1024 tokens. Clone the v2 default branch, add a key, and run pytest before you touch a layer.

## FAQ

### What does CSV-AI need before it will answer a question?

Either OPENAI_API_KEY or ANTHROPIC_API_KEY, matching whatever you set DEFAULT_PROVIDER to, which is openai unless you change it. With DEFAULT_PROVIDER set to ollama it needs a model reachable at OLLAMA_BASE_URL, which defaults to http://localhost:11434.

### Does CSV-AI send my whole CSV file to the model?

The chat context is described as the file schema plus a smart sample, not the full table. That replaced v1, which built a FAISS index over CSV chunks. The Analyze page works separately, on pandas statistics computed before the model is asked to narrate them.

### Which model providers does CSV-AI support?

OpenAI, Anthropic Claude and a local Ollama model, selected with DEFAULT_PROVIDER and overridable from the sidebar. The default model is gpt-4o-mini until you pick another one for the session.

### Can I run CSV-AI with Docker?

Yes, with docker compose up --build, which builds a python:3.11-slim image and publishes port 8501. The compose file reads keys from a .env file, and its healthcheck calls curl against the Streamlit health endpoint.

### What did the v2 rewrite remove compared with v1?

LangChain. v1 imported langchain.chat_models, langchain.embeddings, load_summarize_chain and create_pandas_dataframe_agent, all marked deprecated. v2 uses the native openai and anthropic SDKs directly, and requirements.txt lists no LangChain package.

## Sources

- [Issues](https://github.com/Safiullah-Rahu/CSV-AI/issues)
- [License: MIT](https://github.com/Safiullah-Rahu/CSV-AI/blob/v2/LICENSE)
- [Project website](https://csv-ai.streamlit.app/)
- [README](https://github.com/Safiullah-Rahu/CSV-AI/blob/v2/README.md)
- [Safiullah-Rahu/CSV-AI on GitHub](https://github.com/Safiullah-Rahu/CSV-AI)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/safiullah-rahu-csv-ai
