FixFirst Edge turns your manuals, parts catalog, and incident history into a multimodal search that runs on the technician's laptop. Photo, error code, voice note — one query, local retrieval, auditable answers. No cloud. No LLM hallucinations. No waiting for signal.
A technician standing next to a broken conveyor typically has four things to search — the manual, the incident log, the parts catalog, and their own memory. On a plant floor with weak WiFi, none of that is fast. Cloud-based AI assistants fail the second the signal drops and hallucinate answers that can't be traced back to a specific page.
FixFirst Edge flips it. One search bar. One local database. Every answer points to a specific manual page, a specific prior incident, and a specific part number. Auditable by design.
"E04 motor overload." Rare tokens like codes get caught by the BM25 side of the hybrid fusion.
CLIP embeds the uploaded image locally on the same machine. Searched against every image vector in the same collection.
faster-whisper transcribes locally. The transcript is re-embedded and searched through transcript retrieval plus a dedicated voice-note vector lane.
Any query can be filtered by machine type, model, fault code, or severity — keyword-indexed on Actian's side.
FixFirst Edge is built on features that don't show up in ordinary vector stores. Each one earns its place in the request path.
One collection holds three embedding spaces: text_vec (384d, bge-small), image_vec (512d, CLIP-ViT-B-32), audio_text_vec (384d). A single document can carry any subset and be retrievable through any modality.
vectors_config={
"text_vec": VectorParams(size=384, …),
"image_vec": VectorParams(size=512, …),
"audio_text_vec": VectorParams(size=384, …),
}
Six metadata fields are keyword-indexed: doc_type, machine_type, model_no, fault_code, severity, part_no. Filters are applied in-engine, not after the fact.
builder = FilterBuilder()
builder.must(Field("doc_type").eq("manual"))
builder.must(Field("model_no").eq("CX-200"))
Text queries run Actian dense ANN and app-side BM25-style payload scoring in parallel, then merge with reciprocal rank fusion. Codes like "E04" score high in BM25; phrases like "motor tripped on overload" score high in the dense branch. RRF covers both.
score = Σ 1 / (60 + rank)
┌─────────────────────────┐ ┌─────────────────────────────────┐
│ Next.js 14 (frontend) │ HTTP │ FastAPI (backend) │
│ UploadZone, SearchBar, │ ─────►│ /api/ingest/* │
│ FilterPanel, │ │ /api/search/* │
│ DiagnosePanel, │ │ /api/diagnose │
│ OfflineBanner │ │ /api/incident/save │
└─────────────────────────┘ └──────────────┬──────────────────┘
│
┌───────────────────────────────────┼──────────────────────────────────┐
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────┐
│ pipelines/ │ │ services/ │ │ db.py │
│ text_embedder │ │ ingest_service │ │ init_collection │
│ image_embedder │ │ search_service │ │ upsert │
│ audio_transcriber │ │ diagnose_service │ │ search_text / │
│ pdf_chunker │ │ │ │ image / audio / │
│ csv_loader │ │ │ │ hybrid (RRF) │
└──────────┬──────────┘ └────────────┬────────────┘ └──────────┬──────────┘
│ │ │
▼ ▼ ▼
bge-small, CLIP-ViT-B-32, builds metadata, gRPC → Actian VectorAI DB
whisper tiny.en (CPU) calls embedders + db :50051, collection: incidents
docker run -d --name vectoraidb -p 50051:50051 \ --restart unless-stopped williamimoh/actian-vectorai-db:latest
cd backend && python -m venv .venv && source .venv/bin/activate pip install -r requirements.txt
cp data/fixtures/*.csv data/raw/ # drop your own PDFs into data/raw/manuals/, images into data/raw/images/
uvicorn app.main:app --host 127.0.0.1 --port 8000 & PYTHONPATH=. python scripts/bulk_ingest.py
cd frontend && npm install && npm run dev # open http://localhost:3000