go-ms-rag
Document ingestion and evidence-bounded answers through a staged Go retrieval service.
- Interface
- HTTP API
- Language
- Go
- Vector index
- Pinecone
- Database
- PostgreSQL
- Model provider
- AWS Bedrock
Illustrative simulation with fixed example data. No live model or service is called.
Problem
A question about a document collection needs evidence, not just a fluent answer. Retrieval-augmented generation (RAG) finds relevant passages first, then uses them to answer. go-ms-rag exposes that workflow as an HTTP API for raw text and uploaded documents.
How it works
Ingestion extracts and normalizes text, splits it into passages, creates numeric representations called embeddings, and stores them. Queries retrieve candidate passages, apply the caller’s document allowlist, and only then generate an answer.
No passages means a fixed response, zero tokens and no model call.
All components
- Ingest request
Raw text or uploaded documents enter the service.
- Extract and normalize
Extract file text, remove HTML and normalize whitespace.
- Chunk and classify
Overlapping passages receive kind and quality labels.
- Embed
Numeric text representations support similarity search.
- Persist
Write vectors and store document, passage and fingerprint records.
- Relational database
PostgreSQL stores documents, passages and fingerprints.
- Vector index
Pinecone stores per-document and default namespaces; queries currently use the default.
- Object storage
Uploaded originals go to S3 when configured.
- Decompose
Names and multi-need words supply a heuristic routing signal.
- Route
Explicit mode, probe results and query signals select LOCAL or GLOBAL.
- Retrieve
Retrieve from the default namespace; GLOBAL may widen sparse results.
- Document allowlist
Filter retrieved passages before they reach synthesis.
- Synthesize
Select bounded context and answer from the available excerpts.
- Early exit
No passages means a fixed response, zero tokens and no model call.
Ingest a document
Raw text or an uploaded file enters a bounded asynchronous queue. Text is extracted and normalized, split into classified passages, embedded and persisted.
- Documents
- Extract + normalize
- Sentence-aware passages
- Embeddings
- Persist
- Two vector writes
More detail Illustrative examples
Asynchronous queue202 processing
An asynchronous queue accepts documents, builds overlapping sentence-aware passages and embeddings, then writes vectors twice, metadata to Postgres and uploaded originals to object storage when configured.
Read query signals
The service matches known names and checks words that suggest multiple needs. These are deterministic rules, not a language-model call.
- Question
- Word check
- Named-entity match
- Routing
More detail Illustrative examples
No decomposition model call
Word cues and named-entity matching feed routing without a model call; a word cue creates a synthetic count of two only when no named entity matches.
Choose a retrieval mode
An explicit mode takes precedence. Otherwise the service uses the probe, multi-need count, names and score threshold to choose LOCAL or GLOBAL.
- Explicit mode?
- Probe failed?
- Several needs?
- Named entity?
- Probe score ≥ 0.70?
- Otherwise
More detail Illustrative examples
- Explicit mode?Requested mode
- Probe failed?GLOBAL
- Several needs?GLOBAL
- Named entity?LOCAL
- Probe score ≥ 0.70?LOCAL
- OtherwiseGLOBAL
Explicit mode takes priority, followed by probe failure, multiple needs, a named entity and finally the inclusive 0.70 similarity threshold.
Apply the document allowlist
Retrieved passages are filtered before synthesis. An omitted list allows all documents; an empty list allows none.
- Omitted / null
- Only document A
- Empty list
More detail Illustrative examples
After retrieval and before synthesis, an omitted allowlist preserves every passage, a document list preserves only matches, and an empty list preserves none.
Answer from bounded evidence
The service selects passages using a relative score floor and a fixed context budget. With no passages, it returns a fixed answer without calling a model.
- Retrieved passages
- Relative score floor
- Synthesis
- Early exit
More detail Illustrative examples
No relevant information was found to answer this question.
No synthesis callOnly sufficiently strong excerpts enter the capped context; no remaining passage produces the fixed response instead of a synthesis call.
Details
Chunking
A document becomes overlapping passages so adjacent context is not lost at every boundary. The service also classifies each passage by kind and quality.
Illustrative simulation with fixed example data. No live model or service is called.
Routing rule
LOCAL and GLOBAL are routing labels, not separate document-security boundaries. Both currently query the default namespace; GLOBAL can widen a search when fewer than three results are returned.
Illustrative simulation with fixed example data. No live model or service is called.
Allowlist before generation
The allowlist is applied after retrieval and before synthesis. An omitted list does not restrict documents; an empty list permits none. Without passages, the service returns a fixed no-evidence answer without calling a model.
Illustrative simulation with fixed example data. No live model or service is called.
Decisions and tradeoffs
Filter before generation
The allowlist is enforced before retrieved text reaches the synthesis context. The document boundary is separate from the model prompt.
- Retrieved passages
- Access gate
- Prompt
More detail Illustrative examples
The accepted order keeps denied document text out of the prompt, unlike the rejected alternative of filtering after synthesis.
Stop when evidence is absent
Empty evidence produces a fixed response and zero token usage. The service does not ask a model to fill the gap.
- No passages
- Early exit
- Fixed response
More detail Illustrative examples
No relevant information was found to answer this question.
No synthesis callStopping on zero evidence avoids asking the synthesizer for an unsupported answer and returns the exact fixed response with zero token usage.
Write vectors to two namespaces
Ingestion writes each vector to its document namespace and to the default namespace. This maintains both layouts, while the current query path uses the default one.
- One vector
- Document namespace
- Default namespace
- Two vector writes
More detail Illustrative examples
A document namespace and the default namespace require more vector writes and storage in exchange for global retrieval and per-document deletion.
Use inspectable query rules
Name matching and multi-need expressions avoid an extra model call. They also limit decomposition to the patterns those rules recognize.
- Word + entity checks
- Synthetic count: 2
- Model-based decomposition
- Can miss or over-trigger
More detail Illustrative examples
“Where do the stand and its case go?”
One intended needCue found: “and” → Counted: 2Word and entity checks avoid the latency and cost of a decomposition call, but a simple conjunction can overstate the number of information needs.
Keep a local test path
Fake mode substitutes deterministic providers and an in-memory vector store. PostgreSQL remains required.
- Deterministic hash
- In-memory vectors
- Fake synthesizer
- Offline checks
- Postgres still required
More detail Illustrative examples
Fake mode substitutes deterministic hash embeddings, in-memory vectors and a fake synthesizer for offline checks, while Postgres is still required.
Limitations
LOCAL still uses the default namespace
LOCAL and GLOBAL currently query the default vector namespace. LOCAL does not isolate a per-document namespace; the allowlist remains a separate filter.
- LOCAL / GLOBAL
- Default namespace
- Widened search
More detail Illustrative examples
Both modes currently query the default namespace; GLOBAL alone adds a widened search when fewer than three passages return.
Widening is a bounded fallback
When GLOBAL returns fewer than three results, the service widens the search to a limit of 20 and merges duplicates. This is not a reranker; there is no reranker in the current path.
- Initial search
- Search limit: 20
- Merge + deduplicate
- Unique results
More detail Illustrative examples
The widened search merges and deduplicates results up to the requested limit, but ranking still uses vector similarity rather than a reranker.
Heuristics can miss query intent
Multi-need detection recognizes a fixed set of expressions. Context score floors, passage counts and truncation can also leave out retrieved material.
- Counted: 2
- Counted: 1
More detail Illustrative examples
“Where do the stand and its case go?”
One intended need“Explain loan renewals; describe workshop access.”
Two intended needsA word-based rule can count one intent as several needs or miss several needs that do not use its cue words.
Answer text does not embed citations
The response includes source metadata with scored passages. The generated answer text itself does not embed source citations.
- Answer text
- Source metadata
More detail Illustrative examples
Return the stand clean before the workshop closes.
No inline citationsThe response carries document and passage identities in scored_chunks metadata, not inline citations attached to the answer’s individual claims.