A multi-tenant knowledge base in launch-tools, Launchist's Go platform. Teams upload documents, which are embedded into PostgreSQL with pgvector, and chat with them through streamed, source-grounded answers.
My role
Backend developer. Built the document processing pipeline, the pgvector store, semantic search and the streaming chat with conversation history.
Stack
Go
Echo
LangChain (Go)
PostgreSQL
pgvector
Redis
WebSocket
The problem
A language model doesn't know an organization's own documents and will confidently make things up when asked about them. Keyword search misses questions phrased differently from the source text. And because the platform serves many clients, each organization's knowledge has to stay strictly separate.
Architecture
Ingestion and question answering are separate flows. Uploaded files are parsed, split into chunks, embedded and written to pgvector in batches — one collection per namespace. At question time the query is embedded, the closest chunks are found in the namespaces attached to the chat, combined with conversation history and sent to the selected LLM; the answer streams back to the client over WebSocket. The service is written in Go (Echo) and runs on Google Cloud Run.
Key decisions
01pgvector instead of a separate vector database
Why
Vectors live next to documents, metadata and chat history in one PostgreSQL, so filtering, joins and transactions work as usual and there is one less system to run. A custom LangChain (Go) store adds an HNSW index and metadata filters; schema creation is guarded by PostgreSQL advisory locks, so several Cloud Run instances can start at once safely.
Trade-off
At very large scale a dedicated vector database may offer faster indexing and search.
02Namespaces as the unit of isolation and retrieval
Why
Each namespace is its own vector collection inside an organization. A chat context chooses which namespaces to search, so a team can combine knowledge sources without ever touching another tenant's data.
03Ingestion as its own pipeline with bounded concurrency
Why
Parsing and embedding are slow and rate-limited by the embedding API. Processing files outside the chat path, with a fixed number running in parallel, keeps answers fast and protects both the API quota and instance memory.
Trade-off
A newly uploaded document becomes searchable only after processing finishes.
04Streaming answers with conversation history
Why
Tokens are sent over WebSocket as the model produces them, so users see the answer start immediately. Previous turns are included so follow-up questions like "and what about the second option?" are understood.
Trade-off
Longer prompts mean higher token cost, so history has to be trimmed.
What I built
Multi-tenant model: organizations, projects, namespaces, members and invites
Ingestion of PDF, DOCX, XLSX and archives with bounded concurrency
Custom pgvector store for LangChain (Go) with an HNSW index and metadata filtering
Answers streamed over WebSocket, with conversation history and editable system prompts
Several LLM providers (OpenAI, Anthropic, Gemini) plus customer-supplied keys stored in GCP Secret Manager