How this guide is structured. Each phase ends with something that works. Phase 0 is a complete minimal system in under 10 minutes. Phases 1–3 are progressive upgrades. You never need to redo what came before.
All code in this cookbook is real. Base URL is https://api.hydradb.com. Get your API key at app.hydradb.com.
Prerequisites
Required knowledge: Python basics, REST APIs, environment variablesRequired tools:
- HydraDB API key
- Python 3.11 or 3.12 (
python --version) - OpenAI API key (for generating answers in Phase 3)
pip install hydradb-sdk openai
What You’ll Build
By the end of this cookbook, you’ll be able to:- Upload any codebase file into HydraDB and get grounded answers to “why was this built this way?”
- Progressively ingest GitHub files, PRs, Slack threads, and RFCs across four phases
- Build a FastAPI backend that answers developer questions using
search_docs+ GPT-4o - Add a VS Code extension trigger to query HydraDB from inside your editor
Phase 0 - Minimal Working System · 5–10 minutes
The only goal is to see a real search response from a real file you uploaded. No multi-source ingestion, no backend server, no VS Code extension - just the four API calls that prove the pipeline works end-to-end. Goal: You will run one search query and see actual results.⚠️ Do Phase 0 first, even if you plan to skip ahead. Every later phase assumes the database exists and indexing works. Running Phase 0 takes under 10 minutes and eliminates the most common failure modes.
Prerequisites
You need three things before writing any code:- A HydraDB API key. Email [email protected] or book a demo at hydradb.com. You’ll receive a key that starts with
hdb_. - Python 3.10+. Run
python3 --versionto check. - The HydraDB SDK. Run
pip install hydradb-sdk python-dotenv.
config.py in the project root. This is the canonical configuration used by every script in this guide:
❌ Authorization header must be exact. Authorization: Bearer hdb_abc123 - capital B, one space, no quotes. Anything else returns 401.
Step 1 - Create a Database
What: Registers your top-level namespace. Database creation is asynchronous - infrastructure provisions in the background. Do not upload files until polling confirms it is ready. Endpoint:POST /databases - Async, poll /databases/status before ingesting
401 - API key wrong or missing Bearer prefix. Confirm .env has HYDRA_DB_API_KEY=hdb_abc123 with no surrounding quotes.
Step 2 - Upload One File
What: Uploads a single file using multipart form-data to/context/ingest - the recommended beginner path. HydraDB handles chunking, embedding, and graph-node creation automatically. The returned id is what you use in Step 3 to verify indexing.
💡 Two ingestion modes exist. This step uses multipart file upload viaFirst, create a sample document:/context/ingest- the tested beginner path. An advanced JSON body mode (used in Phases 1–2) supports structured IDs and explicit graphrelations. Use file upload here first.
id depends on HydraDB’s internal file registration. Copy the value returned and use it in Step 3.
If it fails:
404 Database does not exist- Step 1 not complete, ordatabasemismatch.400 / missing files- Confirm the field name isdocuments(notfile), and thatContent-Typeis NOT manually set in headers.
Step 3 - Verify Indexing
What: Polls/context/status using the id returned in Step 2. HydraDB returns a statuses array; you read statuses[0].indexing_status. Querying before this reaches "completed" returns empty results with no error - the most common beginner confusion.
Replace YOUR_ID_HERE with the id from Step 2:
Step 4 - Run Your First Search Query
What: Sends a POST to/query and receives a chunks array. Each chunk has a chunk_content field - this is the text you will later pass to GPT-4o as context. This is the end-to-end proof that database, ingestion, indexing, and retrieval all work.
Validated first search - minimal working request:
chunks: [] - Indexing is not yet complete. Wait 30 seconds and retry. Re-run Step 3 to confirm indexing_status: completed.
✅ Phase 0 complete. The sameWhat you just built: You now have a working retrieval system. HydraDB can store your content, index it, and return the most relevant chunks - each with a/queryendpoint - with the same minimal three-field body - is what every later phase uses. You’ll only add parameters, not change the structure. Thechunksarray andchunk_contentfield are now your canonical search response objects.
chunk_content text field and a relevancy_score - for a question. The missing layer is the reasoning backend that takes those chunks and turns them into a readable, cited answer.
Phase 1 - Improve Retrieval · 15–20 minutes
Ingest multiple files with structured metadata, use collections to organise content, and tune search parameters. At the end of this phase, search queries return more relevant chunks across many documents. Goal: 50+ files indexed with metadata, scoped search working.How Retrieval Works
Before writing more ingestion code, it’s worth understanding what HydraDB actually returns and how to interpret it. This mental model applies to every phase. Chunks vs. sources: When HydraDB indexes a document, it splits the content into overlapping chunks. Each chunk is embedded independently and stored as a node in the context graph. When you call/query, you get back a chunks array. Iterate this array and read chunk_content from each item - that is the text you pass directly to your LLM as context.
The response also includes a sources array - a deduplicated list of the original documents that contributed chunks. Use sources for citation labels; use chunks for the actual LLM context.
Anatomy of a search chunk - field reference:
How to interpret the
relevancy_score: Scores are relative within a single response - they indicate ranked relevance for your specific query, not absolute confidence.
How graph context changes search: When you add
"graph_context": true to your search request, HydraDB walks the explicit relations.ids edges you set at ingestion time for every high-scoring chunk. A source file chunk can pull in the PR that last changed it. That PR can pull in the RFC it referenced. This multi-hop traversal is what makes “why” questions answerable.
💡 Always safe to add to Phase 0. "graph_context": true has no downside with a single file and no relations - it returns the same result. Turn it on now.
P1 · Step 1 - Batch Upload with Explicit IDs (JSON Ingestion)
What: Switches to JSON body ingestion via/context/ingest with app_knowledge field - the advanced ingestion path. This gives you full control over IDs, timestamps, collections, metadata, and (in Phase 2) explicit graph relations. The {repo_name}/{relative_path} ID convention is required for Phase 2 graph linking.
⚠️ Advanced path - stricter validation. JSON ingestion requires well-formed payloads withid,type, andcontent.texton every item. Malformed requests are rejected outright. Use the file upload path (Phase 0 Step 2) if you just want to get content indexed quickly. Use JSON ingestion when you need stable IDs and graph relations.
P1 · Step 2 - Metadata and Collections
What:collections are labels you define for scoping search. additional_metadata fields are arbitrary key-value pairs used for filtering and citation labels. No extra API calls needed - these fields go in the ingestion payload.
P1 · Step 3 - Tuned Search with Graph Context
Phase 2 - Multi-source Context · 20–30 minutes
Connect GitHub source files to their pull requests, Slack decision threads, and internal wikis. Use explicitrelations.ids to guarantee graph edges. When a developer asks “why?”, the answer now travels across all four source types in a single query.
Goal: A “why” question returns code + PR + Slack + RFC in one response.
Pipeline: Source files → Pull requests → Slack threads → Wikis & RFCs → Graph built
💡 Explicit vs. automatic graph edges. Setting relations.ids guarantees a graph link every time. HydraDB may also try to extract relationships from content, but this is best-effort. Always use explicit relations for links that matter.
Why Each Source Type Matters
A developer asking “why does the auth middleware skip token validation for internal IPs?” cannot be answered by any single context. Here is what each source type contributes:
A normal code search tool answers what a function does. It is weak at answering why it exists because the rationale almost never lives in the code file alone. HydraDB becomes powerful when you index all the places where engineering intent is recorded:
- Code tells you what is running now.
- Pull requests tell you why the code changed and what alternatives were discussed.
- Slack threads capture informal trade-offs, approvals, and urgency.
- RFCs and ADRs record the formal decision and long-term architectural reasoning.
💡 Adaptation checklist. Pick a stable ID scheme first, choose the minimum set of high-signal sources second, then expand. Good IDs and a small number of trustworthy sources beat a huge but messy index every time.
P2 · Step 1 - Ingest GitHub Source Files with Consistent IDs
What: Ingests source files using the{repo_name}/{relative_path} ID convention. These IDs are what PR ingestion will reference in relations.ids. The IDs must match exactly - myrepo/auth/middleware.py, not auth/middleware.py.
Use ingest_directory() from Phase 1 Step 1. If you already ran Phase 1, your files are already indexed.
P2 · Step 2 - Ingest Pull Requests with Explicit Graph Relations
What: Fetches merged PRs and turns each into one document: title + description + review comments + changed-file list. Therelations.ids field guarantees graph edges form between the PR and every source file it changed.
Before: A GitHub token with repo scope. Create at github.com/settings/tokens → New classic token → check repo → add to .env as GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO.
P2 · Step 3 - Ingest Slack Threads
What: Groups messages bythread_ts so each conversation thread becomes one document. Architecture channels, post-mortems, and decision threads are the highest-value sources - these capture approvals that never get written up anywhere else.
Before: A Slack export ZIP. Go to your-workspace.slack.com/services/export (requires admin), export, and unzip. Structure: {export_dir}/{channel_name}/{YYYY-MM-DD}.json.
P2 · Step 4 - Ingest Wikis & RFCs
What: Ingests Architecture Decision Records, RFCs, Confluence pages, and Notion wikis. Tag each withdoc_type so search can be scoped to decisions-only when that is what a question requires.
P2 · Step 5 - Run a Multi-source Graph Query
✅ Phase 2 complete. A “why” question now returns multi-hop retrieved context. Phase 3 wraps this in a production FastAPI server with your own GPT-4o answer generation layer.
Phase 3 - Backend & Answer Generation · 25–35 minutes
Build a production FastAPI server. HydraDB is the memory layer - it retrieves rankedchunks via search_docs(). GPT-4o is the reasoning layer - it reads those chunks and writes a grounded, cited answer. The two layers are kept strictly separate so they can be debugged and improved independently.
Goal: POST /chat and POST /ask → HydraDB chunks → GPT-4o → answer.
Backend Architecture
The flow through the backend is:- Question -
POST /chator/ask - Search -
search_docs()→ chunks - Context block -
chunk_contentassembled - GPT-4o - writes grounded answer
- Response - answer + citations
config.py- Loads.env, exposesHYDRA_DB_API_KEY,DATABASE_ID,client(HydraDB SDK instance),OPENAI_API_KEY, and search defaults. Every other file imports from here only.hydra_client.py-search_docs()is the HydraDB retrieval path. It returns chunks, sources, and graph context; prompt formatting and OpenAI calls stay inanswer.py. Imported by →search.py,app.py.search.py-recall_context()callssearch_docs()and returns the raw payload.build_context_block()extractschunk_contentfrom each chunk and assembles a formatted context string for the LLM. Imported by →app.py.answer.py- Takes the context block fromsearch.py, formats the system + user prompt, and calls the OpenAI streaming API. Contains all anti-hallucination rules. Never calls HydraDB. Imported by →app.py.app.py- Two endpoints:POST /chatstreams NDJSON token-by-token - ideal for a web or IDE frontend.POST /askreturns a complete JSON response withanswer,sources, andchunks- easier for Postman testing. Both use the samerecall_context→build_context_block→stream_answerpipeline.
.env:
backend/config.py
Centralises all credentials. UseHYDRA_DB_API_KEY as the variable name for your HydraDB key. All other backend files import from here.
backend/hydra_client.py
search_docs() is the HydraDB retrieval path. It returns ranked chunks and source metadata; it does not format prompts or decide routing.
backend/search.py
recall_context() calls search_docs() and returns the raw HydraDB payload. build_context_block() reads the chunks array, extracts each chunk_content field, and assembles them into a single formatted string that is passed to the LLM. The sources array from the payload is used separately by app.py for citation output.
💡 Production upgrade: score filtering.build_context_blockis intentionally simple for the first backend. For production, add arelevancy_scorefilter to drop low-quality chunks before building the context string - the same pattern used in Phase 1’sbuild_context()helper.
backend/answer.py
Takes the context block string fromsearch.py and turns it into a grounded GPT-4o answer. This is the only file that touches OpenAI. If the context string is empty, it returns a safe fallback without calling the model at all - this is the primary hallucination guard.
backend/app.py
The FastAPI server exposes two endpoints.POST /chat streams NDJSON - best for a web frontend or VS Code extension. POST /ask returns a complete JSON response - easier for Postman testing and best for API consumers that don’t stream. Both use the same recall_context → build_context_block → stream_answer pipeline.
💡 Want WHY_SIGNALS routing? Once this basic backend is working, add a question classifier that adjustssearch_docs()parameters for “why” questions, such as loweralpha, highermax_results, andgraph_context: true. Keep final answer generation in your app layer.
Answer Generation Flow
Understanding the exact data path prevents debugging confusion.build_context_block() does the following in order:
- Read the
chunksarray from the search payload./queryreturns{"chunks": [...], "sources": [...]}- always iteratechunks, neverresults. - Extract
chunk_contentfrom each chunk object. This is the raw text of the chunk - a few hundred tokens of the original document. It is the only required field. Skip any chunk wherechunk_contentis absent or empty. - Join all
chunk_contentvalues with double newlines to form the context body. - Append the sources list from
recall_payload.sourcesin- title (id)format so the model can cite them. - Return the assembled string to
stream_answer(). This becomes the user message body inside the OpenAI prompt.
Avoiding Hallucination
LLMs will confidently invent details about your codebase if you don’t actively prevent it. The system prompt inanswer.py contains specific rules for each failure mode:
⚠️ Do not raise temperature above 0.2 for this use case. Higher temperature increases creativity, which is the opposite of what you want for accurate, cited answers about specific code.
Running the Server
/chat endpoint:
/ask endpoint (easier for Postman):
✅ Phase 3 complete. You have a production FastAPI backend. HydraDB is the memory layer -search_docs()retrieves ranked chunks. GPT-4o is the reasoning layer - it reads those chunks viastream_answer()and writes a grounded answer. The two layers are intentionally separate so you can debug and improve each independently.
Phase 4 - Productization · Coming next
Ship the assistant to your team. Build the VS Code extension sidebar, set up a daily incremental sync cron job, wire a GitHub Actions workflow for on-push indexing, and monitor search quality over time. Goal: Team using it daily with a fresh index on every merge. Detailed implementation coming in the next revision. Everything in Phase 4 is packaging around a working Phase 3 server. Planned components: VS Code extension (TypeScript), Webview chat UI, Active-file context injection, Daily cron sync, GitHub Actions on push to main, Incremental re-index, Search quality monitoring.If Your Retrieval Quality Is Weak
- Fix your IDs and metadata. Weak retrieval often starts upstream. Use stable IDs, clear titles, accurate timestamps, and useful metadata like
doc_type,repo, andchannel. - Add explicit relations. If “why” answers feel shallow, it usually means the assistant sees the code but not the documents around it. Add
relations.idsbetween code, PRs, Slack, and RFCs. - Improve source formatting. A PR with only a title is weak. A PR with title, description, changed files, reviews, and inline comments is strong. Rich documents produce better chunks.
- Add relevancy_score filtering. The production upgrade to
build_context_blockis to drop chunks whererelevancy_scoreis below your threshold before assembling the context string.
How to Extend Beyond the First Version
A good progression: Phase 0 search in the terminal → FastAPI/ask in Postman → FastAPI /chat with a simple web frontend → VS Code sidebar → streaming, incremental sync, caching, usage analytics.
Troubleshooting
401 - Not Authenticated
Cause: The Authorization header is missing or malformed. Most common: theBearer prefix is absent, quotes surround the key in .env, or there is a leading/trailing space in the key value.
Fix: Run python3 -c "from config import HYDRA_DB_API_KEY; print(repr(HYDRA_DB_API_KEY))". The output should be 'hdb_abc123' - no surrounding spaces. Your header must be exactly Authorization: Bearer hdb_abc123.
404 - Database Does Not Exist
Cause: You are attempting to upload or query before the database was created, or thedatabase in your request does not match the one you created. Database IDs are case-sensitive.
Fix: Run python3 phase0/create_tenant.py and wait for ”✓ Database ready.” before proceeding. Confirm DATABASE_ID in config.py exactly matches what you passed to create_tenant.
404 - Not Found (on search or ingestion endpoint)
Cause: The endpoint path has a typo, extra slash, or missing segment. Also occurs when querying acollection that has never had data written to it.
Fix: Correct paths: /context/ingest (file upload or JSON batch with app_knowledge), /query, /context/status. If using collection, confirm at least one batch was successfully uploaded to that collection first.
400 - Provide at least one of: documents or app_knowledge
Cause: For file upload: the field name is wrong (file instead of documents), or Content-Type: application/json was manually set (which breaks multipart). For JSON batch: the array is empty or objects are missing required fields.
Fix: For file upload: use files={"documents": (filename, f, "text/markdown")} and data={"database": DATABASE_ID}. Do NOT set Content-Type manually. For JSON batch: confirm each item has id, type, and content.text.
Empty Search - chunks: []
Cause: Indexing is still in progress, orrelevancy_score filtering in build_context_block is too aggressive, or the query does not semantically match any ingested content.
Fix: Step 1 - Run verify_file() and confirm "indexing_status": "completed" in the statuses array. Step 2 - Run phase0/search.py directly and print the raw response - check that the chunks key is present and non-empty before any filtering. Step 3 - Check the relevancy_score values on returned chunks.
429 - OpenAI insufficient_quota
Cause: Your OpenAI account has run out of credits or hit its usage limit. This is an OpenAI-side error - HydraDB retrieval may be working perfectly. The error appears instream_answer() inside answer.py, not in the HydraDB search step.
Fix: Add billing or upgrade your OpenAI plan at platform.openai.com. While debugging, replace stream_answer in answer.py with this temporary debug fallback - it returns the raw retrieved context without calling OpenAI:
💡 General debugging pattern. Addprint("STATUS CODE:", resp.status_code)andprint("RESPONSE:", resp.text[:500])immediately after everyrequests.post()call. See the full error body before doing anything else.
Production Notes
- Batch size and rate limits. The JSON batch upload endpoint accepts a maximum of 20 source objects per request. Always sleep 1 second between batches. For large repos (1,000+ files), expect ingestion to take 10–30 minutes total.
- Indexing delays. Indexing is async. Never rely on a fixed sleep; always poll
/context/statusuntil thestatusesarray shows"indexing_status": "completed". - Consistency and upserts. HydraDB upserts by
id- re-uploading replaces the existing document. There is a brief window where search may return stale chunks. Run verify on new IDs before marking a deployment complete. - LLM context window. The basic
build_context_blockassembles all chunk text. For production, add arelevancy_scorefilter and a character cap (e.g. 12,000 chars) to prevent exceeding GPT-4o’s context window. - Graph edge consistency. Graph edges only form if both sides exist and are indexed. Always ingest source files first, then PRs. Re-ingest affected PRs if you add new files after initial ingestion.
- API key security. The HydraDB API key grants full access to all database data. Never commit it to git. Use environment secrets in production. Rotate immediately if you suspect exposure.
- OpenAI token costs. Each question sends context + system prompt + question to GPT-4o. For a 50-engineer team asking 200 questions/day, budget $2–4/day. Use
gpt-4o-minifor factual lookups to reduce cost. - Search quality monitoring. Log the
relevancy_scorevalue of each chunk included in a response. If median scores are falling over time, new content may be diluting the index - re-ingest with better metadata and collections tagging. Also confirm thechunksarray is non-empty before sending context to GPT-4o.
API Reference
All endpoints used in this cookbook. Base URL:https://api.hydradb.com. Header: Authorization: Bearer YOUR_API_KEY
Create Database
POST/databases - Async, poll /databases/status before ingesting
Upload File - Beginner Path
POST/context/ingest - Multipart form-data · field name "files" · database as form field
Upload Sources - JSON Body (Advanced)
POST/context/ingest - Max 20 items · body field app_knowledge (JSON string) · supports relations
Verify Indexing
GET/context/status - Returns statuses[] array · poll until "completed"
Search - Validated Minimal Request
POST/query - Primary verified path · collection optional · returns chunks[] and sources[]
Multi-hop Search
POST/query - Use for multi-hop retrieval, then generate the answer in your app layer
Benchmarks
1,200 developer questions across three engineering teams (18–80 engineers, codebases 150k–2.2M lines) compared against naive vector search using identical ingested content.ℹ️ The 22% accuracy on “why” questions isn’t a tuning problem - it’s structural. Embedding a source file and the RFC that motivated it produces similar-but-unlinked vectors. Without explicit graph edges, the RFC never surfaces when the source file matches. HydraDB makes the connection explicit: every node linked to your query gets traversed, not just nodes that look semantically similar.