Ask questions about any GitHub repository. Paste a repo URL, wait for it to be ingested, then chat.
The app uses RAG (Retrieval-Augmented Generation) to answer questions about code. Instead of feeding an entire repo to an LLM, it converts code into vector embeddings - numerical representations that capture semantic meaning - and stores them in a vector database (ChromaDB). When you ask a question, it's embedded the same way and matched against stored vectors using similarity search, so only the most relevant code snippets are sent to the LLM as context.
- Ingest - the backend clones the repo -> splits files into chunks -> embeds them with Ollama -> stores vectors in ChromaDB.
- Query - typing in chat and sending a question -> question gets embedded -> the closest chunks are retrieved -> an LLM generates an answer with source references.
flowchart TD
UI(["React UI :3000"]) -->|HTTP| API["FastAPI :8000"]
subgraph Ingest ["/api/ingest"]
GH["GitHub (clone)"] --> CH["Chunker (split)"]
CH --> EMB["Embedder"]
end
subgraph Query ["/api/query"]
RET["Retriever (RAG)"]
end
API --> Ingest
API --> Query
EMB --> OL["Ollama :11434"]
EMB --> DB[(ChromaDB)]
RET --> OL
RET --> DB
Ingest flow: Clone repo → split files into chunks → embed with Ollama → store vectors in ChromaDB.
Query flow: Embed question → retrieve closest chunks from ChromaDB → send chunks + question to LLM → return answer with source references.
- Backend: FastAPI + Python
- Vector DB: ChromaDB (local, no infra needed)
- LLM + Embeddings: Ollama (
llama3.2for chat,nomic-embed-textfor embeddings) - Frontend: React + Vite
git clone https://github.com/Leon-Rud/git-ask.git
cd git-ask
cp .env.example .env # optional: edit .env to use OpenAI (see below)
docker compose up --buildThis starts everything - backend, frontend, and Ollama. First run pulls the models (~2-4 GB), so it takes a few minutes. After that it's fast.
Open http://localhost:3000.
OpenAI is ~20x faster and gives better answers. Before running docker compose up, open the .env file you just copied and uncomment the OpenAI lines. You'll need an API key from platform.openai.com/api-keys:
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...- Python 3.10+
- Node.js 18+
- Ollama running locally
Pull the required models:
ollama pull llama3.2
ollama pull nomic-embed-textpython3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # edit if needed
uvicorn app.main:app --reload --port 8000cd client
npm install
npm run dev -- --port 3000Open http://localhost:3000.
| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER |
ollama |
ollama or openai |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama server URL |
OLLAMA_MODEL |
llama3.2 |
Chat model (Docker uses llama3.2:1b for speed) |
OLLAMA_EMBED_MODEL |
nomic-embed-text |
Embedding model |
OPENAI_API_KEY |
— | Required if LLM_PROVIDER=openai |
OPENAI_MODEL |
gpt-4o-mini |
OpenAI chat model |
CHROMA_PERSIST_DIR |
./chroma_data |
Where ChromaDB stores data |
| Endpoint | Method | Body / Params | Description |
|---|---|---|---|
/api/ingest |
POST | { repo_url, token? } |
Ingest a repo |
/api/query |
POST | { repo_name, question, n_results? } |
Ask a question |
/api/status/{repo_name} |
GET | — | Check ingestion status |
/health |
GET | — | Health check |
Why ChromaDB? I looked at FAISS, Pinecone, and Weaviate. FAISS doesn't have metadata filtering, which I needed for storing file paths and line numbers with each chunk. Pinecone requires a cloud account and Weaviate needs a separate service to manage. ChromaDB is just pip install and it works - persistent storage, metadata queries, and it runs in-process.
Chunking by function boundaries instead of fixed-size. Fixed-size chunks (e.g. 500 tokens) split functions in half, which makes retrieved context less useful. Splitting on def/class/function boundaries keeps logical units together. For languages without a regex pattern or files that are too big, it falls back to fixed-size chunks of 60 lines.
- Public repos work without a token. For private repos, provide a GitHub Personal Access Token - it's only used for cloning and never stored.
- Re-ingesting a repo replaces the old data.
- Ingestion time depends on repo size and your machine. Large repos can take several minutes.
- Previously analyzed repos are saved in localStorage so you can resume a session without re-ingesting.
