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

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

Python · 4 lines
PromptBuilder(
    context_manager:  Optional[ContextManager]  = None,
    guardrail_engine: Optional[GuardrailEngine] = None,
) -> None

Defaults to a fresh ContextManager() and GuardrailEngine() if not supplied.

build()

Python
def build(self, request: PromptRequest) -> BuiltPrompt

Internal workflow

  1. Resolve effective strategy via _resolve_strategy(request) (see below), then replace request.strategy with it using dataclasses.replace (the original request passed in is not mutated — a new PromptRequest is created).
  2. Context engineering — self.context_manager.build(request) → ContextResult (see context_manager).
  3. Memory formatting — self.context_manager.format_memory(request.memory) → a plain text block.
  4. Guardrails — self.guardrail_engine.build(request) → (guardrail_block: str, applied: List[str]) (see guardrails).
  5. Strategy dispatch — get_strategy(effective_strategy) looks up the BaseStrategy implementation and calls its build(request, context_block, memory_block, guardrail_block, citation_map).
  6. Enrichment — the returned BuiltPrompt.guardrails_applied, .documents_included, .documents_truncated, and .context_tokens_used are overwritten with values from ctx_result/applied (the strategy's own documents_included — set to len(request.documents) inside BaseStrategy.build — is replaced here with the more accurate len(ctx_result.included_docs), i.e. post-dedup/post-budget count).
  7. One logger.info line is emitted, then the enriched BuiltPrompt is returned.

Parameters

NameTypeNotes
requestPromptRequestRequired. See promptrequest

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]:

PromptTypeDefault PromptStrategy
QASIMPLE
CONVERSATIONALSIMPLE
REASONINGCHAIN_OF_THOUGHT
AGENTREACT
TOOL_USEREACT
SAFETYSIMPLE
SUMMARIZATIONCHAIN_OF_THOUGHT
EXTRACTIONSIMPLE
COMPARISONMULTI_HOP

_COMPLEXITY_UPGRADE: Dict[str, PromptStrategy] (only applied when the type default is SIMPLE):

complexity.valueUpgrades to
"complex"CHAIN_OF_THOUGHT
"expert"MULTI_HOP

_resolve_strategy (internal)

Python · 2 lines
@staticmethod
def _resolve_strategy(request: PromptRequest) -> PromptStrategy

Logic:

  1. If request.strategy != PromptStrategy.SIMPLE, return it as-is (an explicit non-default choice always wins).
  2. Otherwise, look up the type default from the table above.
  3. If that default is itself SIMPLE, check _COMPLEXITY_UPGRADE for the request's complexity and upgrade if a mapping exists.
  4. Return the (possibly upgraded) default.

[!WARNING] You cannot explicitly request SIMPLE to override a type default Because step 1 only special-cases non-default values, passing strategy=PromptStrategy.SIMPLE explicitly is indistinguishable from not passing a strategy at all — PromptBuilder will still apply the type-based default (e.g. a REASONING-type request always becomes CHAIN_OF_THOUGHT, even if you asked for SIMPLE). 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 set prompt_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):

Python · 7 lines
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 pages