Documents
The Documents page holds the retrieval corpus. Uploaded files are parsed into chunks, each chunk is embedded, and a request that asks for retrieval has the best-matching chunks pasted into its system prompt before it reaches a provider.
Retrieval is core capability, not an entitlement — it is included at every licence tier.
The pipeline
Upload → pending → processing → ready │ └──→ error- The upload creates a document record in
pendingand enqueues the file. - A worker parses it, splits it into chunks, embeds each chunk, and stores them.
- On success the document becomes
readyand carries its chunk count. - On failure it becomes
errorwith the worker’s reason, shown verbatim on the row and in the drawer.
Only ready documents are retrievable. A document that is still processing
is invisible to retrieval, and a request augmented from this corpus will answer
as though it were never uploaded. The page states the count of non-ready
documents above the table rather than leaving you to notice.
The worker is not optional. Without it, uploads sit in pending
indefinitely.
The original file is not kept. Only the extracted chunks are stored, and there is no download endpoint.
File types
| Type | Detected from |
|---|---|
pdf | application/pdf |
markdown | text/markdown, or a .md filename |
code | A source-file extension — .ts, .py, .go, .rs, .java, .sql, .vue and around thirty others |
text | Everything else |
The maximum upload is 50 MB. PDFs are parsed; everything else is decoded as UTF-8. There is no format allow-list, so a binary file that is not a PDF will be accepted and decoded into nonsense — check the chunk preview in the drawer after uploading anything unusual.
You can override the detected type on upload.
Chunking
Prose (pdf, text, markdown) | Code | |
|---|---|---|
| Split on | Blank lines, accumulating paragraphs | Lines, preferring function and class boundaries |
| Target size | ~2048 characters | ~26 lines, hard-split at double that |
| Overlap | ~200 characters carried into the next chunk | — |
| Metadata | Start line | Start and end line |
The overlap exists so a passage that straddles a boundary is still wholly present in one chunk.
Token counts on a chunk are an estimate — roughly four characters per token — not a real tokenisation.
Embeddings — read this before you deploy
Document embeddings are produced by Ollama, not by the local ONNX model the semantic cache uses.
| Variable | Default |
|---|---|
OLLAMA_URL | http://localhost:11434 |
RAG_EMBEDDING_MODEL | nomic-embed-text |
The same endpoint embeds the query at request time. So retrieval has a runtime dependency the rest of the gateway does not, and the two embedding stacks are not interchangeable — their vectors are different sizes and are not comparable.
If Ollama is unreachable, a document still completes. The failed embedding
is caught, the chunk is stored with an empty vector, and the document reaches
ready with a full chunk count. Retrieval skips chunks with no vector, so the
document reads as retrievable and is reachable by nothing.
Nothing in the console distinguishes that state from a healthy one. Verify retrieval works after any change to the Ollama deployment — the Try a retrieval panel below is the fastest way to do it.
Trying a retrieval
The Try a retrieval panel runs the same code path a live request uses: a query, a chunk count (3 / 5 / 10 / 20) and a minimum match (0.50 loose, 0.70 default, 0.80 strict, 0.90 near-identical). Results show each hit’s score, its document and chunk index, and the chunk text.
Use it as the check that a corpus is actually live, not just uploaded.
Retrieval at request time
Retrieval runs only when a request asks for it — rag: true, or an options
object { documentIds?, topK?, threshold? }. Requests that do not ask for it
pay nothing.
- The query is the last user message only. An empty one skips retrieval.
- Defaults are top 5 chunks at a 0.7 minimum score.
- A routing configuration with
enableRag: falseskips retrieval for requests addressed to it. - Every eligible chunk is scored, streamed in batches so memory stays bounded by the requested chunk count.
- A 15-second deadline applies. Exceeding it fails the retrieval rather than returning what it had — a truncated scan returns confident answers that are simply not the best ones, which is worse than an honest failure.
Winning chunks are formatted and appended to the existing system message, or prepended as one if there is none:
[Document 1] (relevance: 87.3%)<chunk content>
---
[Document 2] (relevance: 81.0%)…A retrieval failure never blocks the request. It is logged, and the request proceeds unaugmented. The caller sees a normal answer with no indication that retrieval did not happen — another reason to check the corpus deliberately rather than inferring health from successful completions.
The injected chunks are not recorded on the request log, so after the fact there is no way to see which chunks shaped a given completion.
Scoping
The document list is scoped: an organization sees its own documents and any uploaded with no organization set, which is how shared reference material is published to the whole deployment.
Retrieval currently resolves across the deployment. Do not use the corpus as a boundary for material that must not be visible between organizations; upload only what every user of the deployment may see.
Deleting
Deleting a document removes its chunks and their embeddings first, then the document itself. Anything a request was answering from it stops being retrievable immediately. Requests keep succeeding — they answer without it.
Endpoints
| Method | Path | Permission |
|---|---|---|
GET | /api/admin/documents | documents:read |
POST | /api/admin/documents | documents:create |
GET | /api/admin/documents/:id | documents:read |
GET | /api/admin/documents/:id/chunks | documents:read |
POST | /api/admin/documents/search | documents:read |
DELETE | /api/admin/documents/:id | documents:delete |
POST /documents is multipart/form-data with the file in a field named
file, plus optional name and type.
POST /documents/search takes { query, topK?, threshold? } and returns the
matching chunks with their scores. topK is passed through unclamped — ask for
a sane number.
The list and chunk endpoints return a nested envelope unlike the rest of the admin API:
{ "data": { "items": [], "total": 0, "page": 1, "limit": 20, "totalPages": 0 } }documents:* is granted to super-admin and deployment-admin only.
Related
- Cache — the other, unrelated embedding stack
- Vector Stores — the caller-facing retrieval API
- Routing —
enableRagon a routing configuration