Architecture
The app is the top layer of a four-component stack.
Component
Type
Role
RAG Knowledge Base
ODC App
Screens, entities, orchestration. Customise this layer.
Semantic Engine V2 Library
ODC Library
Serialisation between ODC and External Logic.
SemanticEngineV2
External Logic
PDF extraction, chunking, embeddings, cosine similarity.
BM25Engine
Tokenization, Porter stemming, BM25 scoring.
Entities
Document — tracks ingested files. Key attributes: Id, FileName, UploadedAt, Status (Processing, Active, or Failed).
DocumentChunk — stores text chunks, embeddings, and BM25 index state. Attributes: Id, DocumentId, PageNumber (1-based), ChunkIndex (0-based), ChunkText, VectorJson, ChunkHash, TokenCount (Integer, null until IndexChunks runs — new in this release).
BM25Term — one row per unique Porter-stemmed term. Attributes: Id, Term (unique index), DocumentFrequency (populated by RecomputeBM25Stats timer, not at ingestion time).
BM25Posting — one row per (term, chunk) pair. Inverted index for query time. Attributes: Id, BM25TermId, DocumentChunkId, TermFrequency.
BM25Stats — singleton holding TotalChunks and TotalTokenCount for BM25 length normalisation. Refreshed by RecomputeBM25Stats timer. Must contain a row before any hybrid search is valid.
QueryEmbeddingCache — stores cached query vectors and search results. One record per unique query hash and embedding model. Key attributes: QueryHash (lookup key), CachedQueryVector (always stored), CachedResult (stored only when IsCacheResult is True), CacheMaxAgeMinutes (per-record TTL), HitCount (cache hit counter).
Screens
File Upload: upload a PDF, extract and chunk the text, generate embeddings, and build the BM25 index in one workflow. Drag-and-drop file upload included. Default chunk size is 1000 characters with 150-character overlap.
Performance note: ingestion runs synchronously on screen. Very large files can hit ODC's execution time limit. For large documents, run the ingestion logic in a background Timer or ODC Workflow instead.
Semantic Search: type a natural language query and retrieve matching chunks ranked by score with page references. Toggle between semantic and hybrid search from the same screen. Includes a switch to enable or disable result caching at runtime. When caching is on, the query vector and search result are stored for repeat queries. When off, the query vector is still cached but results are always fetched fresh.
Documents: inspect ingested documents and their processing status.
Vectors: inspect stored chunks, hash values, vector entries, and BM25 token detail per chunk. Select the Index Chunk (BM25) tab to view stemmed tokens and their frequencies for any indexed chunk. Useful for verifying what has been ingested and confirming the pipeline ran correctly.
How It Works
Document Ingestion: user uploads a PDF, text is extracted by page, split into overlapping chunks, embedded via the API endpoint, and stored as Document and DocumentChunk records. BM25Engine.IndexChunk is called per chunk: TokenCount is written back to DocumentChunk, and BM25Term and BM25Posting rows are created. Document status is set to Active or Failed.
Semantic Search: query text is hashed and checked against QueryEmbeddingCache. Cache hit with result: cached result returned immediately. Cache hit without result: stored vector used for cosine similarity, no embedding call. Cache miss: query embedded via API, cosine similarity runs against DocumentChunk. Results below the minimum score threshold are excluded. Query vector always stored; result stored only when result caching is enabled.
Hybrid Search: HybridSearchWithBM25 runs the vector leg via SearchVectorWithCache (TopK x 2 candidates), then the BM25 leg via BM25Engine.TokenizeText and BM25Engine.ScoreQuery. Both ranked lists are fused using Reciprocal Rank Fusion (RRF, k=60). A minimum score filter is applied. The top TopK results are returned as a SearchResult list, identical in structure to semantic search output.
Site Properties
Configure these in the ODC Portal under your application's runtime settings before running any operations.
Site Property
Default
Description
EmbeddingAPIKey
(none)
Mandatory. Set before running ingestion or search.
EmbeddingEndpoint
https://api.openai.com/v1/embeddings
Embedding API URL.
EmbeddingModel
text-embedding-3-small
Embedding model to use.
EmbeddingIsAzure
False
Set to True for Azure OpenAI endpoints.
DefaultCacheMaxAgeMinutes
1440
Default cache TTL in minutes.
K1
1.2
BM25 term frequency saturation.
B
0.75
BM25 length normalisation.
TokenizerVersion
v2-stemmed-porter
Tokenizer version used to build the current index. Must match BM25Engine.
DisplayMinSemanticScore
0.5
Min cosine similarity to display (semantic).
DisplayMinHybridScore
0.025
Min RRF score to display (hybrid).
SearchMinSemanticScore
0.3
Min cosine similarity for semantic pipeline.
SearchMinHybridScore
0.015
Min RRF score for hybrid pipeline.
Setup
Limitations