Back to portfolio
Case Study Side project

NetWho — AI CRM for a personal network.

A Telegram-native assistant for keeping a personal network live. Ask it in natural language — who's working on what, who I should ping about X, what's been happening with whom — and it answers from its own retrieval layer over contacts, notes, and parsed news. Extended to multi-tenant — private and open communities plug their own contact sets in with per-tenant RLS isolation.

RoleSolo · built end-to-end
ScopeBot · RAG · multi-tenant layer
SurfaceTelegram bot
StatusLive demo

01Context

Live in Telegram — question in, grounded answer out.

I know a lot of people. I forget what they're up to. A notebook doesn't search; a CRM is too much; a flat list of contacts is where data goes to die. What I wanted was a conversational front door — I type a question, it answers with the right people and the right context.

NetWho is that. It lives in Telegram, so I already have it open. Under the hood it's a retrieval-augmented assistant: every contact becomes an embedded record, every news event gets parsed and linked, and the reasoning loop ranks what matches the question.

02Architecture

aiogram 3.x bot on the edge. Every contact is parsed into a Pydantic v2 schema and stored with its embedding in pgvector. News items get pulled in via Jina AI's reader API, embedded, and linked back to contacts by entity overlap. Queries run a semantic retrieval + reranking loop before generating a grounded answer with source references.

03What was built

Structured contact ingestion— schema

Every contact lands through a Pydantic v2 schema — name, aliases, roles, companies, tags, notes. Validation happens at the boundary, not deep inside. Bad input gets a useful error, not a 500.

Semantic retrieval— pgvector

Each contact and note gets embedded and stored in pgvector. Queries retrieve top-k by cosine similarity over the same embedding space, so a question about "the guy working on LLM evals" hits the right record without exact name match.

News enrichment via Jina AI— freshness

Instead of one-shot contact records, NetWho pulls fresh news via Jina's reader API, extracts entities, and links items back to existing contacts. So "what's new with Anna?" answers against this week, not last year's notes.

Reranking reasoning loop— relevance

Raw vector search is rarely the right answer. A rerank pass scores retrieved candidates against the actual question phrasing before composing the reply, so the model sees a tight context window of the most relevant few, not a bag of similar-looking records.

Grounded answers with sources— trust

Every reply references the underlying records it drew from — so the bot's answer is auditable. No hallucinated contacts, no invented jobs.

04Outcome

Shipped solo, live on Telegram as @netwho_bot. Used daily for my own network management; every change merges to master and rolls out from there.

Takeaway

Personal RAG is the cleanest way to learn production RAG: the dataset is small enough to understand end-to-end, but every reasoning failure is something you can feel. Once the single-user shape held up, the same schema → embeddings → rerank → grounded-answer pipeline got lifted into a multi-tenant layer — private and open communities bring their own contacts, per-tenant RLS keeps the boundaries clean.

05Stack

Bot
aiogram 3.x Telegram Bot API async handlers
Validation
Pydantic v2 structured schemas
Retrieval
RAG pgvector reranking grounded generation
External
Jina AI reader OpenRouter LLM embedding models
Data
Supabase PostgreSQL