PromptBuilder
On this page
Sits between PromptEngine and the strategy layer: resolves the effective strategy, runs context engineering and guardrail selection, then dispatches to the chosen strategy implementation.
Table of contents
- Overview
- Constructor
build()- Default strategy table
_resolve_strategy(internal)- Thread safety
- Examples
Overview
You will normally never instantiate PromptBuilder directly — PromptEngine creates one internally. It's documented here because it owns the strategy-resolution logic and subsystem wiring that determines what a given (prompt_type, strategy, complexity) combination actually produces.
Constructor
PromptBuilder(
context_manager: Optional[ContextManager] = None,
guardrail_engine: Optional[GuardrailEngine] = None,
) -> None
Defaults to a fresh ContextManager() and GuardrailEngine() if not supplied.
build()
def build(self, request: PromptRequest) -> BuiltPrompt
Internal workflow
- Resolve effective strategy via
_resolve_strategy(request)(see below), then replacerequest.strategywith it usingdataclasses.replace(the originalrequestpassed in is not mutated — a newPromptRequestis created). - Context engineering —
self.context_manager.build(request)→ContextResult(see context_manager). - Memory formatting —
self.context_manager.format_memory(request.memory)→ a plain text block. - Guardrails —
self.guardrail_engine.build(request)→(guardrail_block: str, applied: List[str])(see guardrails). - Strategy dispatch —
get_strategy(effective_strategy)looks up theBaseStrategyimplementation and calls itsbuild(request, context_block, memory_block, guardrail_block, citation_map). - Enrichment — the returned
BuiltPrompt.guardrails_applied,.documents_included,.documents_truncated, and.context_tokens_usedare overwritten with values fromctx_result/applied(the strategy's owndocuments_included— set tolen(request.documents)insideBaseStrategy.build— is replaced here with the more accuratelen(ctx_result.included_docs), i.e. post-dedup/post-budget count). - One
logger.infoline is emitted, then the enrichedBuiltPromptis returned.
Parameters
Returns
A fully-assembled BuiltPrompt.
Raises
No exceptions raised directly by build(). get_strategy() never raises for an unknown PromptStrategy — it logs a warning and falls back to SimpleStrategy (see strategies), so a malformed effective strategy degrades gracefully rather than erroring.
Side effects
One logger.info call. No mutation of shared state beyond what context_manager/guardrail_engine do internally (both are read-only with respect to request).
Default strategy table
_DEFAULT_STRATEGY: Dict[PromptType, PromptStrategy]:
_COMPLEXITY_UPGRADE: Dict[str, PromptStrategy] (only applied when the type default is SIMPLE):
_resolve_strategy (internal)
@staticmethod
def _resolve_strategy(request: PromptRequest) -> PromptStrategy
Logic:
- If
request.strategy != PromptStrategy.SIMPLE, return it as-is (an explicit non-default choice always wins). - Otherwise, look up the type default from the table above.
- If that default is itself
SIMPLE, check_COMPLEXITY_UPGRADEfor the request's complexity and upgrade if a mapping exists. - Return the (possibly upgraded) default.
[!WARNING] You cannot explicitly request
SIMPLEto override a type default Because step 1 only special-cases non-default values, passingstrategy=PromptStrategy.SIMPLEexplicitly is indistinguishable from not passing a strategy at all —PromptBuilderwill still apply the type-based default (e.g. aREASONING-type request always becomesCHAIN_OF_THOUGHT, even if you asked forSIMPLE). If you need a literally simple prompt regardless of type, there's no supported override for this in the current implementation — the closest workaround is to also setprompt_type=PromptType.QA.
Thread safety
Same caveats as PromptEngine — PromptBuilder itself holds no mutable per-call state (only its context_manager/guardrail_engine references, which are themselves effectively stateless for .build() calls), so concurrent use of a single PromptBuilder across threads is safer than concurrent use of PromptEngine, but this is not explicitly declared or tested anywhere in the source.
Examples
Direct usage without PromptEngine (advanced — most users should just use PromptEngine):
from zerda.prompt.builder import PromptBuilder
from zerda.prompt.types import PromptRequest, PromptType
builder = PromptBuilder()
request = PromptRequest(query="Why did the crisis happen?", prompt_type=PromptType.REASONING)
prompt = builder.build(request)
print(prompt.strategy) # PromptStrategy.CHAIN_OF_THOUGHT (type default)
Related APIs
- prompt_engine — the only caller of
PromptBuilderin this codebase - strategies — what
get_strategy()dispatches to - context_manager, guardrails.md — subsystems orchestrated here