Architecture

On this page

Overview

zerda.context is built around one orchestrator, ContextEngine, which wires together six pluggable strategy components plus two cross-cutting concerns (caching, adaptive learning). Every component implements an abstract interface defined in strategy.py, which means any stage can be swapped out for a custom implementation without touching the engine's orchestration logic.

The run() pipeline, step by step

This is the exact sequence executed by ContextEngine.run() (see api/engine), in source order:

Stage responsibilities

#StageComponentWhat it does
0Cache lookupContextCacheReturns immediately on a hit, skipping the rest of the pipeline. Cache key is an MD5 hash of the lower-cased query + sorted filters.
1History enrichmentConversationHistoryIf multi_turn is enabled and use_history=True, prefixes the query with a summary of the last n Q/A turns.
2Query analysisQueryAnalyzerStrategy (default: RuleBasedQueryAnalyzer)Detects language, intent, complexity, keywords, entities, question type, and a suggested context budget.
3Dynamic budgetAdaptiveLearningSystemBlends the analyzer's budget suggestion with rolling historical usage statistics.
4RetrievalHybridRetrieverRuns vector, keyword, and metadata retrieval in parallel (logically), merges by content hash, and applies Reciprocal Rank Fusion. Vector retrieval may be backed by a native zerda.vector_database backend, an external LangChain-style adapter, or in-memory embeddings — see VectorRetriever.
5FilteringChunkFilterStrategy (default: ChunkFilter)Removes noise patterns, out-of-range lengths, and exact duplicates (by SHA-256 content hash).
6RankingChunkRankingStrategy (default: CompositeRanker)Recomputes a composite score per chunk from five weighted factors and truncates to max_chunks_to_rank.
7CompositionContextComposerStrategy (default: SmartContextComposer)Formats chunks with a template, greedily packs them under the budget, and truncates safely at a word boundary if needed.
8GuardingContextGuardStrategy (default: ContextGuard)Validates the composed context isn't empty/too short, warns if too long, checks for source markers, and optionally checks semantic relevance.
9Cache storeContextCacheStores the result, but only if the guard passed.
10History recordingConversationHistoryAppends the turn (capped at max_turns, default 10).
11Adaptive recordingAdaptiveLearningSystemRecords response time, context length, chunk count, and guard outcome into rolling windows; every 20 queries, runs an auto-tune pass that just emits log warnings/info — it does not currently mutate any config values itself.

[!NOTE] What AdaptiveLearningSystem._auto_tune actually does Reading the implementation directly: _auto_tune() only logs a warning if the guard failure rate exceeds 30%, and logs an info message if average response time exceeds 2000ms suggesting the caller enable caching. It does not automatically change any ContextEngineConfig value. The budget adjustment that does take effect is in suggest_budget(), which is called every run(), independent of _auto_tune().

Component substitution points

Every stage's default implementation can be replaced by passing an alternative to ContextEngine.__init__:

Python · 11 lines
engine = ContextEngine(
    config         = my_config,
    query_analyzer = MyLLMAnalyzer(),       # implements QueryAnalyzerStrategy
    retriever      = MyVectorStoreRetriever(),  # a HybridRetriever instance, or any object exposing .retrieve()
    chunk_filter   = MyCustomFilter(),       # implements ChunkFilterStrategy
    ranker         = MyCustomRanker(),       # implements ChunkRankingStrategy
    composer       = MyCustomComposer(),     # implements ContextComposerStrategy
    guard          = MyHallucinationGuard(), # implements ContextGuardStrategy
    budget_allocator = MyAllocator(),        # implements BudgetAllocationStrategy
    length_counter   = make_token_counter(my_tokenizer),  # swap chars for tokens
)

This is taken verbatim from the "advanced use case" example in the ContextEngine class docstring.

Class relationships

[!NOTE] VectorDatabaseBase / DocumentChunk come from sibling packages VectorDatabaseBase (and its ChromaVectorDatabase/FAISSVectorDatabase/PineconeVectorDatabase subclasses) live in zerda.vector_database; DocumentChunk/ChunkMetadata live in zerda.chunks. context.retriever only imports them under TYPE_CHECKING, so importing zerda.context never requires either package (or their optional deps) to be installed — only actually using vector_db= does. See api/retriever.md.

Data flow types

Multi-turn conversation flow

ConversationHistory (see api/models) is created automatically by ContextEngine.__init__ when config.multi_turn is True (the default), with a fresh session_id of the form session_{unix_timestamp}. Each call to run() with record_turn=True (the default) appends a ConversationTurn. When use_history=True (the default) and there is prior history, the next query is rewritten before analysis as:

Text · 5 lines
[Previous conversation:
Q: <previous query>
A: <previous answer>]

Current question: <current query>

This enriched string is what gets analyzed and retrieved against — but note the original query (not the enriched one) is what gets recorded back into history and returned in ContextResult.query_analysis.original_query is actually the enriched string, since analysis = self._analyzer.analyze(effective_query). See the implementation note in api/engine for the exact distinction.

Where the code diverges from typical RAG-library architecture

  • No LLM calls anywhere. This library never calls an LLM. Summarization (summarize_long_chunks in ComposerConfig) is a configuration flag that exists but, on inspection of pipeline.py, is not read or acted upon anywhere in SmartContextComposer. It is effectively a no-op flag today.
  • No persistence. ContextCache is in-process memory only (a plain dict + deque); restarting the process clears it.
  • No async support. Every method in the package is synchronous; there is no async def anywhere in the source.

Related pages