Demo · 9 September 2026
TenderLens
TenderLens accepts HTML, PDF, XML, JSON and TXT, maps documents to one contract and splits them into stable chunks. Indexing runs through NATS JetStream. bge-m3 in MWS produces embeddings, pgvector finds nearby fragments, and gpt-oss-120b generates an answer only from retrieved context and returns sources.
Published deployment with real MWS models, pgvector, NATS and a synthetic corpus
01 / The task
Document search with FastAPI and pgvector
The task is to build verifiable search across heterogeneous tender files. The path from source document to answer remains traceable as document → chunk → embedding → hit → citation, so every result can be mapped back to a specific fragment.
Use case: Searching and reviewing a document set.
Work done
- Document normalization, event-driven indexing and chunk storage in PostgreSQL.
- Search and Ask APIs with sources, an explicit no-context response and a relevance threshold.
- An MWS provider for embeddings and Chat Completions, with deployment-ID and vector-dimension validation.
- A public deployment with per-session and global rate limits. The MWS key stays on the server.
02 / Implementation
What sets the implementation apart
Every answer follows one trace: document → chunk → bge-m3 → pgvector → gpt-oss-120b → citation. Replayed events do not duplicate chunks. An unrelated query is rejected before generation. The corpus is synthetic; the queue, indexer, database, vector search and both MWS models run for real.
Screenshots
Screens, forms, errorsScreenshots, diagrams and source evidence retain their original language; captions and descriptions are translated.
03 / Technology
Stack
Shares use Git application-file bytes at pinned revisions. Each file belongs to one subsystem under scripts/generate-stack-share.mjs rules; tests, assets, dependencies and generated files are excluded. Rounded to 0.1%. This describes code composition, not proficiency.
FastAPI / Pydantic
HTTP contracts, validation, Search and Ask.
MWS Model Hub
bge-m3 for embeddings; gpt-oss-120b for context-grounded answers.
NATS JetStream
Event delivery to the indexer and replay.
PostgreSQL / pgvector
Documents, attachments, chunks and cosine search.
SQLAlchemy / Python
Normalization, extraction, chunking and content_hash checks.
04 / Decisions
How the project works
- The seed publishes TenderChangedV1 through NATS; the indexer extracts text and atomically replaces chunks when content_hash changes.
- bge-m3 returns 1024-dimensional embeddings. The indexer stores the model name with every chunk, and PostgreSQL VECTOR(1024) performs cosine search.
- Ask sends only retrieved fragments to gpt-oss-120b. Documents in the prompt are marked as data, not instructions.
- The 0.40 threshold was selected after measuring relevant and unrelated queries. If no suitable chunks exist, the generation endpoint is not called.
- The external crawler is disabled on the public deployment. The corpus is synthetic; the queue, database, vector search and MWS inference run for real.
05 / Verification
Demo workflows
- Find the server document: the first result should mention a 36-month warranty and show the bge-m3 label.
- Switch to Ask: gpt-oss-120b should answer from the retrieved fragments and show its sources.
- Choose No context: when the score is below 0.40, the service returns a refusal without calling the generation model.
- Check corpus state: four documents, 16 chunks and ready on every record.
Check results
- Ten screenshots of version 6c005b6 were published: desktop, mobile, Search, Ask, no-context and validation.
- Four ready documents were reindexed into 16 bge-m3 vectors with dimension 1024.
- In the 9 September 2026 gallery browser check, desktop Search took 298 ms, Ask 1145 ms and no-context 115 ms; mobile Search took 137 ms and Ask 1936 ms. These are single measurements, not a benchmark.
- Relevant test queries produced top cosine scores of 0.604–0.927; two unrelated queries produced 0.278 and 0.370. The production threshold is 0.40.
- A current snapshot of the two TenderLens containers shows about 121.4 MiB RAM combined at idle. Models run in MWS and consume no VPS RAM or GPU.
- At the 9 September 2026 snapshot, about 5.05 GiB RAM and 105.7 GiB disk space were available on the VPS.
Limitations
- Four synthetic documents and 16 chunks form a small demo corpus rather than a real procurement database.
- Model quality was not measured on an independent dataset. The displayed cosine scores cover only the checks run on 9 September 2026.
- The limit is five Search/Ask requests per minute per session and twelve requests per minute for the whole process. User queries are not stored.
- Inference depends on two MWS deployments. When the provider is unavailable, readiness fails and there is no automatic fallback to fake mode.
- Worker health confirms its startup connection; there is no separate stuck-computation detector.
Technical notes
Modules, API, data model, setup and check results.
Open RU textbook →Documentation →Materials ↓