ragweld Documentation
-
Tri-brid Retrieval
Parallel Vector (pgvector), Sparse (PostgreSQL FTS/BM25), and Graph (Neo4j) search fused with configurable strategies.
-
MLOps Engineering Platform
Synthetic data, training studios, eval drilldowns, tracing, routing, and ops controls built into one workflow.
-
Pydantic Is The Law
All configuration and API shapes live in
server/models/tribrid_config_model.py. Everything else derives from it. -
PostgreSQL Backbone
Chunk storage, embeddings, pgvector indexing, and FTS in one database.
-
Knowledge Graph
Neo4j stores entities/relationships and supports traversal for cross-file context.
-
API-First
FastAPI endpoints for indexing, retrieval, graph, models, health, metrics, and training.
-
Operational Safety
Field constraints, readiness gates, metrics, and cost-aware model selection.
Get started Configuration Config reference API
Naming: ragweld vs tribrid
The repo/product is ragweld. Many internal names still say tribrid (config keys, module names, older docs). Treat tribrid as stable internal naming; don’t mass-rename it.
Read This First
ragweld is strictly Pydantic-first. If a field or feature is not in server/models/tribrid_config_model.py, it does not exist. Add it there, regenerate TypeScript types, then build the rest.
Terminology — corpus vs repo_id
The API still accepts repo_id for legacy reasons. Treat it as the corpus identifier. Pydantic models use AliasChoices("repo_id", "corpus_id") and serialize as corpus_id.
Security
Keep .env out of version control. Restrict database access. Use strong passwords for PostgreSQL and Neo4j. Rotate API keys regularly.
What ragweld does as an MLOps Engineering Platform
| Feature | Description | Status |
|---|---|---|
| Vector Search | Dense similarity via pgvector in PostgreSQL | ✅ Active |
| Sparse Search | PostgreSQL FTS/BM25 for exact terms, identifiers | ✅ Active |
| Graph Search | Neo4j traversal to follow entities/relations | ✅ Active |
| Fusion | Weighted/reciprocal-rank fusion of sources | ✅ Active |
| Reranker | Optional cloud/learning reranking | ✅ Active |
| Synthetic Data Lab | Recipe-driven generation for eval datasets, semantic cards, and triplets | ✅ Active |
| Training Studios | LoRA training workbenches for reranker and agent model | ✅ Active |
| Eval + Drilldown | Run comparisons and per-query diagnostics for regressions | ✅ Active |
| Tracing + Grafana | Local traces plus embedded observability dashboards | ✅ Active |
| Routing + Model Catalog | Local/cloud model routing with catalog refresh and custom models | ✅ Active |
Product Screenshots
These screenshots are taken from recent production UI surfaces and map to the same workflows documented throughout this site.
API routing first, MCP layered per channel
- API docs: API
- MCP docs: MCP integration
Indexing guardrails and model-mismatch detection
- Indexing guide: Indexing a corpus
- Config reference: Indexing config
Learning studios and live training telemetry
- Training reference: Training config
- Workflow guide: Reranker how-to
Graph explorer and retrieval inspection
- Retrieval overview: Retrieval concepts
- Graph controls: Graph search config
Recall gating and chat memory controls
- Chat reference: Chat config
- Caching reference: Semantic cache config
End-to-End Retrieval Flow
flowchart LR
Q["Query"] --> V["Vector Search (pgvector)"]
Q --> S["Sparse Search (PostgreSQL FTS)"]
Q --> G["Graph Search (Neo4j)"]
V --> F["Fusion Layer"]
S --> F
G --> F
F --> R["Reranker (optional)"]
R --> O["Results"]
F --> O Quickstart — Run, Index, Search
- Configure environment (.env)
- Launch services with Docker Compose
- Regenerate TypeScript types from Pydantic
- Index a corpus
- Search via API
- Tune fusion weights and confidence thresholds
- Enable reranking if needed
Use Ctrl+C to stop local uvicorn or Docker tail sessions.
import httpx, subprocess
BASE = "http://127.0.0.1:8012/api"
# 1) Generate TS types from Pydantic (required for UI) (1)!
subprocess.check_call(["uv", "run", "scripts/generate_types.py"]) # (1) Types derive from Pydantic
# 2) Trigger indexing of a corpus (2)!
req = {
"corpus_id": "tribrid", # (3)! repo_id alias is also accepted
"repo_path": "/path/to/your/codebase",
"force_reindex": False,
}
httpx.post(f"{BASE}/index", json=req).raise_for_status()
# 3) Poll status (4)!
status = httpx.get(f"{BASE}/index/tribrid/status").json()
print(status)
# 4) Search (parallel vector/sparse/graph -> fusion -> optional rerank) (5)!
payload = {
"corpus_id": "tribrid",
"query": "How does the chunker split Python files?",
"top_k": 8,
}
res = httpx.post(f"{BASE}/search", json=payload).json()
for m in res.get("matches", []):
print(m["file_path"], m["score"]) # fused score
- Pydantic → generated types is a hard contract
- Start an indexing job for your corpus
- Inputs accept
repo_idbut serialize ascorpus_id - Poll index status to show progress in UI
- Search runs vector/sparse/graph in parallel, fuses, then optionally reranks
BASE=http://127.0.0.1:8012/api
# Start indexing (1)!
curl -sS -X POST "$BASE/index" \
-H 'Content-Type: application/json' \
-d '{
"corpus_id": "tribrid",
"repo_path": "/path/to/your/codebase",
"force_reindex": false
}'
# Status (2)!
curl -sS "$BASE/index/tribrid/status" | jq .
# Search (3)!
curl -sS -X POST "$BASE/search" \
-H 'Content-Type: application/json' \
-d '{
"corpus_id": "tribrid",
"query": "How does the chunker split Python files?",
"top_k": 8
}' | jq '.matches[] | {file_path, score}'
- Kick off corpus indexing
- Verify progress and current file
- Run tri-brid retrieval
// Ensure ./web/src/types/generated.ts exists (generated by Python) (1)!
import type { IndexRequest, SearchRequest, SearchResponse } from "./web/src/types/generated";
async function indexAndSearch() {
const base = "http://127.0.0.1:8012/api";
const indexReq: IndexRequest = {
corpus_id: "tribrid", // (2)! repo_id alias also accepted server-side
repo_path: "/path/to/your/codebase",
force_reindex: false,
};
await fetch(`${base}/index`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(indexReq),
});
const searchReq: SearchRequest = {
corpus_id: "tribrid",
query: "chunker split Python",
top_k: 8,
} as any;
const r = await fetch(`${base}/search`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(searchReq),
});
const data: SearchResponse = await r.json(); // (3)!
console.log(data.matches.map(m => [m.file_path, m.score]));
}
- Import only generated API types
- Scoped by
corpus_id(alias of legacyrepo_id) - Response includes fused matches and timings
Architecture Overview
flowchart TB
subgraph Client
U["User / UI / API Client"]
end
U --> A["FastAPI"]
A --> V["VectorRetriever\n(Postgres+pgvector)"]
A --> S["SparseRetriever\n(Postgres FTS)"]
A --> G["GraphRetriever\n(Neo4j)"]
V --> F["Fusion"]
S --> F
G --> F
F --> R["Reranker\n(optional)"]
R --> O["Final Results"]
F --> O
subgraph Storage
P["PostgreSQL"]
N["Neo4j"]
end
V <--> P
S <--> P
G <--> N Advanced Topics
- Fusion math: weighted linear combination and Reciprocal Rank Fusion with configurable
fusion.rrf_k. - Retrieval cache: cache keys include
corpus_id,query, and a hash of the retrieval config subset. - Failure isolation: vector, sparse, and graph legs are resilient; a failure in one leg degrades gracefully.





