Skip to content

Wizard Config Builder

The WizardConfigBuilder provides a fluent, programmatic API for creating wizard configurations. It complements the YAML-based approach by enabling code-driven wizard generation with built-in validation, serialization, and full roundtrip compatibility with WizardConfigLoader.

Overview

Component Class Purpose
Builder WizardConfigBuilder Fluent API for constructing wizard configs
Config WizardConfig Immutable, validated wizard configuration
Stage StageConfig Configuration for a single wizard stage
Transition TransitionConfig Configuration for a stage-to-stage transition
Intent Detection IntentDetectionConfig Intent detection settings for conversation stages
Context Generation ContextGenerationConfig LLM-generated context variable settings

Quick Start

Minimal Wizard

from dataknobs_bots.config import WizardConfigBuilder

wizard = (
    WizardConfigBuilder("onboarding")
    .add_structured_stage("welcome", "What is your name?", is_start=True)
    .add_end_stage("done", "All set!")
    .add_transition("welcome", "done")
    .build()
)

wizard.to_file("configs/wizards/onboarding.yaml")

Conversation-Start Pattern

For the common single-conversation-stage pattern:

from dataknobs_bots.config import WizardConfigBuilder

wizard = (
    WizardConfigBuilder.conversation_start(
        name="assistant",
        prompt="You are a helpful assistant.",
        tools=["knowledge_search", "web_search"],
        tool_reasoning="react",
        max_tool_iterations=5,
    )
    .build()
)

Multi-Stage Wizard

from dataknobs_bots.config import WizardConfigBuilder

wizard = (
    WizardConfigBuilder("survey")
    .set_version("2.0.0")
    .set_description("Customer feedback survey")
    .set_settings(
        tool_reasoning="react",
        max_tool_iterations=3,
        auto_advance_filled_stages=True,
    )
    .add_conversation_stage(
        name="chat",
        prompt="Welcome! Let's collect your feedback.",
        tools=["knowledge_search"],
        is_start=True,
        suggestions=["Tell me about your experience", "I have a complaint"],
        intent_detection={
            "method": "keyword",
            "intents": [
                {"id": "start_survey", "keywords": ["survey", "feedback"]},
            ],
        },
    )
    .add_structured_stage(
        name="rating",
        prompt="How would you rate our service? (1-5)",
        schema={"type": "object", "properties": {"score": {"type": "integer"}}},
        help_text="Enter a number from 1 to 5.",
        suggestions=["1", "2", "3", "4", "5"],
    )
    .add_end_stage("thanks", "Thank you for your feedback!")
    .add_transition("chat", "rating", condition="data.get('_intent') == 'start_survey'")
    .add_transition("rating", "thanks")
    .build()
)

Builder API

Constructor

WizardConfigBuilder(name: str)

Creates a builder with defaults: version "1.0", empty description, no settings.

Metadata Methods

Method Description
set_version(version) Set the wizard version string
set_description(description) Set a human-readable description

Settings

builder.set_settings(
    tool_reasoning="react",
    max_tool_iterations=3,
    auto_advance_filled_stages=True,
    extraction_scope="current_message",
    conflict_strategy="latest_wins",
    extraction_grounding=True,
    extraction_hints={"enum_normalize": True, "normalize_threshold": 0.7},
    timeout_seconds=300,
    scope_escalation={
        "enabled": True,
        "escalation_scope": "wizard_session",
        "recent_messages_count": 3,
    },
)

Settings are passed through to the wizard runtime. Common keys include tool_reasoning, max_tool_iterations, auto_advance_filled_stages, extraction_scope, conflict_strategy, extraction_grounding, grounding_overlap_threshold, merge_filter, extraction_hints, timeout_seconds, ephemeral_keys, store_trace, verbose, and scope_escalation.

Extraction Scope

The extraction_scope setting controls what context the extraction model receives:

  • "wizard_session" (default): All user messages in the wizard session — comprehensive but heavier.
  • "recent_messages": The last N user messages, controlled by scope_escalation.recent_messages_count (default 3). A middle ground between session and current message.
  • "current_message": Only the user's latest message — fast and focused.

Scope Escalation

When extraction_scope is "current_message", the extraction model may miss fields that were mentioned in earlier turns. Scope escalation automatically retries with a broader scope when required fields are still missing after the initial extraction:

builder.set_settings(
    extraction_scope="current_message",
    scope_escalation={
        "enabled": True,                     # Default: False
        "escalation_scope": "wizard_session", # Or: "recent_messages"
        "recent_messages_count": 3,           # For recent_messages scope
    },
)

Escalation only fires when (a) required fields are missing, (b) the current scope is narrower than the target, and © there is prior conversation history to draw on. The grounding filter protects existing data during escalated re-extraction.

The store_trace and verbose settings propagate to ReAct stages. Both support per-stage overrides (set directly on the stage dict) that take precedence over the wizard-level default.

Use ephemeral_keys to declare data keys that should not be persisted to storage (e.g., per-step display data, intermediate computation results):

builder.set_settings(
    ephemeral_keys=["_dedup_result", "_review_summary", "_batch_passed_count"],
)

Extraction Grounding Settings

Key Type Default Description
extraction_grounding bool True Enable schema-driven grounding checks. When enabled, extracted values must be grounded in the user's message before they can overwrite existing data.
grounding_overlap_threshold float 0.5 Minimum word-overlap ratio for string grounding (0.0--1.0).
merge_filter str \| None None Dotted import path to a custom MergeFilter class. Composes with the built-in grounding check (grounding runs first, then the custom filter).
skip_builtin_grounding bool False When True and a merge_filter is set, bypass the built-in grounding check entirely — only the custom filter runs.
extraction_hints dict {} Class-level extraction hints. enum_normalize (default true): normalize extracted enum values to canonical entries. normalize_threshold (default 0.7): fuzzy match threshold. reject_unmatched (default true): reject enum values that don't match any entry. boolean_recovery (default true): enable signal-word recovery for boolean fields that extraction fails to fill; only active when "boolean_recovery" is in the recovery pipeline.

See Extraction Grounding and Enum Normalization for full documentation.

Stage Methods

add_conversation_stage()

Adds a mode: conversation stage for free-form chat with optional tool access and intent detection.

builder.add_conversation_stage(
    name="chat",
    prompt="Let's discuss your project.",
    tools=["knowledge_search"],
    is_start=True,
    suggestions=["Tell me more", "What options do I have?"],
    intent_detection={
        "method": "keyword",
        "intents": [
            {"id": "switch_topic", "keywords": ["change", "different"]},
        ],
    },
)

Parameters:

Parameter Type Description
name str Stage identifier
prompt str User-facing prompt
tools list[str] \| None Available tool names
is_start bool Whether this is the start stage
suggestions list[str] \| None Quick-reply suggestions
intent_detection dict \| None Intent detection config (method + intents)
**kwargs Any Additional StageConfig fields

add_structured_stage()

Adds a data-collection stage with optional JSON Schema validation.

builder.add_structured_stage(
    name="collect_name",
    prompt="What is your full name?",
    schema={"type": "object", "properties": {"name": {"type": "string"}}},
    is_start=True,
    can_skip=True,
    skip_default="Anonymous",
    reasoning="react",
    max_iterations=5,
    response_template="Got it, {{ name }}!",
    help_text="Enter your first and last name.",
    context_generation={"variables": {"greeting": "Generate a greeting for {{ name }}"}},
)

Parameters:

Parameter Type Description
name str Stage identifier
prompt str User-facing prompt
schema dict \| None JSON Schema for input validation
tools list[str] \| None Available tool names
is_start bool Whether this is the start stage
is_end bool Whether this is an end stage
can_skip bool Whether the user can skip
skip_default Any Default value when skipped
suggestions list[str] \| None Quick-reply suggestions
response_template str \| None Template-driven response (bypasses LLM)
help_text str \| None Help message
reasoning str \| None Reasoning mode: "single" or "react"
max_iterations int \| None Max iterations for ReAct reasoning
context_generation dict \| None LLM context generation config
**kwargs Any Additional StageConfig fields

add_end_stage()

Adds a terminal stage (sets is_end=True automatically).

builder.add_end_stage("done", "Thank you! Your session is complete.")

add_stage()

Adds a pre-built StageConfig directly for advanced use cases.

from dataknobs_bots.config import StageConfig

stage = StageConfig(
    name="custom",
    prompt="Custom stage with full control.",
    is_start=True,
    llm_assist=True,
    llm_assist_prompt="Help the user with their query.",
)
builder.add_stage(stage)

Transitions

builder.add_transition(
    from_stage="chat",
    to_stage="quiz",
    condition="data.get('_intent') == 'quiz'",
    transform="transform_chat_to_quiz",
    priority=10,
    derive={"quiz_topic": {"from_field": "selected_topic"}},
    metadata={"description": "User wants to start a quiz"},
)

Transitions are stored separately and attached to their source stage at build time. This means stages and transitions can be added in any order.

Parameters:

Parameter Type Description
from_stage str Source stage name
to_stage str Target stage name
condition str \| None Python expression evaluated with data in scope
transform str \| list[str] \| None Transform function name(s)
priority int \| None Evaluation priority
derive dict \| None Data derivation rules
metadata dict \| None Custom metadata

Intent Detection

Intent detection can be configured inline on conversation stages or added separately:

# Method 1: Inline (via add_conversation_stage)
builder.add_conversation_stage(
    name="chat",
    prompt="...",
    intent_detection={
        "method": "keyword",
        "intents": [{"id": "quiz", "keywords": ["quiz", "test"]}],
    },
)

# Method 2: Separate (via add_intent_detection)
builder.add_intent_detection(
    stage="chat",
    method="llm",
    intents=[{"id": "quiz", "description": "User wants to take a quiz"}],
)

Global Tasks

builder.add_global_task(
    task_id="collect_name",
    description="Get the user's name",
    required=True,
    completed_by="field",
    field_name="user_name",
)

builder.add_global_task(
    task_id="search_kb",
    description="Search the knowledge base",
    required=False,
    depends_on=["collect_name"],
    completed_by="tool",
    tool_name="knowledge_search",
)

Validation

The builder validates the configuration at build time and provides both errors and warnings.

Errors (block build)

Condition Error
No stages "Wizard must have at least one stage"
No start stage "Wizard must have exactly one start stage"
Multiple start stages "Wizard has multiple start stages: [...]"
Duplicate stage names "Duplicate stage name: '...'"
Transition to unknown stage "Stage '...' has transition to unknown stage '...'"
Transition from unknown stage "Transition from unknown stage '...' to '...'"
Intent detection on unknown stage "Intent detection references unknown stage '...'"
Invalid reasoning value "Stage '...' has invalid reasoning value '...'"
Invalid mode value "Stage '...' has invalid mode value '...'"

Warnings (logged, don't block build)

Condition Warning
max_iterations without reasoning "Stage '...' sets max_iterations but has no reasoning mode"
End stage with transitions "End stage '...' has transitions that will never be followed"
Unreachable stages "Stage '...' is not reachable from the start stage"

Pre-Build Validation

Use validate() to check without building:

result = builder.validate()
if not result.valid:
    for error in result.errors:
        print(f"Error: {error}")
for warning in result.warnings:
    print(f"Warning: {warning}")

Serialization

To Dict

config = builder.build()
config_dict = config.to_dict()
# config_dict is compatible with WizardConfigLoader.load_from_dict()

To YAML

yaml_str = config.to_yaml()

To File

config.to_file("configs/wizards/my-wizard.yaml")
# Creates parent directories if they don't exist

Roundtrip

Existing wizard YAML files can be loaded into a builder, modified, and re-exported:

# Load from file
builder = WizardConfigBuilder.from_file("configs/wizards/existing.yaml")

# Modify
builder.add_structured_stage("new_stage", "New question?")
builder.add_transition("existing_stage", "new_stage")

# Rebuild
wizard = builder.build()
wizard.to_file("configs/wizards/updated.yaml")

Or from a dict:

builder = WizardConfigBuilder.from_dict(existing_config_dict)

Integration with DynaBotConfigBuilder

Use set_reasoning_wizard() on DynaBotConfigBuilder to combine bot and wizard configs:

from dataknobs_bots.config import DynaBotConfigBuilder, WizardConfigBuilder

# Build the wizard config
wizard = (
    WizardConfigBuilder("assistant")
    .add_conversation_stage("chat", "How can I help?", is_start=True)
    .build()
)

# Write it to disk
wizard.to_file("configs/wizards/assistant.yaml")

# Reference it from the bot config
bot_config = (
    DynaBotConfigBuilder()
    .set_llm("ollama", model="llama3.2")
    .set_conversation_storage("memory")
    .set_reasoning_wizard(wizard)  # Uses wizard.name as the config path
    .build()
)

You can also pass a file path string directly:

bot_config = (
    DynaBotConfigBuilder()
    .set_llm("ollama", model="llama3.2")
    .set_conversation_storage("memory")
    .set_reasoning_wizard("configs/wizards/assistant.yaml")
    .build()
)

StageConfig Reference

All fields available on StageConfig:

Field Type Default Description
name str (required) Stage identifier
prompt str (required) User-facing prompt
is_start bool False Whether this is the start stage
is_end bool False Whether this is an end stage
can_skip bool False Whether the user can skip this stage
skip_default Any None Default value if skipped
can_go_back bool True Whether the user can go back
auto_advance bool \| None None Auto-advance past this stage. true overrides global to enable, false overrides global to disable, absent/None defers to auto_advance_filled_stages. See Message Stages
confirm_first_render bool True Whether to pause for confirmation on first render. Set to false to skip and evaluate transitions immediately
confirm_on_new_data bool False Re-render confirmation when schema values change
label str \| None None Display label
suggestions tuple[str, ...] () Quick-reply suggestions
help_text str \| None None Help message
schema dict \| None None JSON Schema for validation
transitions tuple[TransitionConfig, ...] () Stage transitions
tools tuple[str, ...] () Available tool names
reasoning str \| None None "single" or "react"
max_iterations int \| None None Max ReAct iterations
extraction_model str \| None None Model for extraction
response_template str \| None None Template-driven response
llm_assist bool False Enable LLM-assisted responses
llm_assist_prompt str \| None None Custom LLM assist prompt
context_generation ContextGenerationConfig \| None None LLM context generation
mode str \| None None "conversation" for chat stages
intent_detection IntentDetectionConfig \| None None Intent detection settings
tasks tuple[dict, ...] () Stage-level tasks
capture_mode str \| None None Extraction control: "auto", "verbatim", or "extract"

TransitionConfig Reference

Field Type Default Description
target str (required) Target stage name
condition str \| None None Python expression with data in scope
transform str \| list[str] \| None None Transform function name(s)
priority int \| None None Evaluation priority
derive dict \| None None Data derivation rules
metadata dict \| None None Custom metadata
subflow dict \| None None Subflow configuration