Clarify Tool

Ask focused clarifying questions when agents need user input to proceed

Clarify enables agents to pause mid-task and ask focused questions instead of guessing, improving decision accuracy and user control.

python
from praisonaiagents import Agent

agent = Agent(
    name="assistant",
    instructions="Ask clarifying questions when the request is ambiguous",
)

agent.start("Help me set up monitoring")

The user gives a vague request; the agent uses clarify before acting.

Diagram

Clarify Flow

🤖 Agent Task

âť“ Clarify Question

📱 User Reply

âś… Continue Task

Behavior by Surface

clarify behaves differently on each surface.

Diagram

clarify behavior

Surface

đź’» CLI / TUI

đź’¬ Bot / Gateway

📦 Headless / no TTY

Reads answer from terminal
via create_cli_clarify_handler

Delivers question to user
as this turn’s reply
choices render as reply buttons

Returns best-judgment fallback
agent proceeds without blocking

SurfaceHow the user sees itWho wires it
CLI / TUI (praison "...", praisonai tui launch)Numbered menu or free-text prompt in the terminal; Ctrl+C / EOF returns "skip"Auto-wired by the CLI interactive toolset (see Interactive Tools)
Bot / Gateway (Telegram, Discord, Slack, etc.)The agent ends its turn by restating the question; choices render as tappable reply buttons; the tap or typed reply arrives as the next turn’s inputAuto-wired by BotSessionManager on every turn
Headless (no TTY, no bound handler, batch/CI)Returns "No interactive channel available. Please proceed with your best judgment for: <question>" so scripted runs never blockFallback, nothing to wire

Quick Start

  1. Enable Clarify
    python
    from praisonaiagents import Agent
    from praisonaiagents.tools.clarify import clarify
    
    agent = Agent(
        name="Writer",
        instructions="Write code. If requirements are ambiguous, ask clarifying questions.",
        tools=[clarify],
    )
    
    agent.start("Build me a web scraper")
    # Agent may call: clarify(question="Which language?", choices=["python", "rust"])
  2. With Custom Handler
    python
    from praisonaiagents import Agent
    from praisonaiagents.tools.clarify import clarify, create_cli_clarify_handler
    
    # Setup CLI handler for interactive questions
    handler = create_cli_clarify_handler()
    
    agent = Agent(
        name="Researcher",
        instructions="Research topics. Ask for clarification when needed.",
        tools=[clarify],
        ctx={"clarify_handler": handler}
    )

How It Works

Diagram
UserChannelClarifyAgentUserChannelClarifyAgentclarify(question, choices)Show question + optionsDisplay promptProvide answerReturn responseContinue with answer
ComponentPurposeBehavior
ClarifyToolCore tool implementationPauses execution for user input
ClarifyHandlerChannel-specific behaviorRoutes questions to CLI/bot/UI
Context IntegrationRuntime handler resolutionUses ctx[‘clarify_handler’] if available

Channel Integration

CLI Usage

python
from praisonaiagents.tools.clarify import create_cli_clarify_handler

handler = create_cli_clarify_handler()
# Shows: 🤔 Which language?
# Choices:
#   1. python
#   2. rust
# Your choice (number or text): 1
# Returns: "python"

Bot Usage

On a bot turn clarify is already wired. The bot session binds a turn-scoped handler automatically, so no handler setup is needed.

When the agent calls clarify(...):

  1. The question and choices are recorded for this turn’s reply.
  2. The agent is told to end its turn by restating the question instead of guessing.
  3. If the agent did not emit its own MessagePresentation, the choices are attached as MessagePresentation.question(question, choices) and render as native reply buttons.
  4. The user’s tap or typed reply arrives as the next turn’s input.
python
from praisonaiagents import Agent
from praisonaiagents.tools.clarify import clarify

agent = Agent(
    name="Deployer",
    instructions="If the user doesn't say which environment, ask with clarify before deploying.",
    tools=[clarify],
)
# On a bot session, a call like
#   clarify(question="Which environment?", choices=["staging", "production"])
# ends this turn restating the question. The two choices render as tappable
# buttons; the tap becomes the next turn's input.
Diagram
ClarifyAgentBot (praisonai-bot)UserClarifyAgentBot (praisonai-bot)User“deploy it”turn 1 (binds clarify handler for this turn)clarify(“Which environment?”, [“staging”,”production”])“Your question has been delivered… do not guess… end your turn”“Which environment? staging / production”text reply + reply buttons [staging] [production]taps [staging]turn 2 input = “staging”proceeds with deploy to staging

Custom Surfaces: set_clarify_handler

Bind a handler for one turn on any surface you own, such as a web UI or a custom chat adapter.

python
from praisonaiagents.tools.clarify import (
    ClarifyHandler,
    set_clarify_handler,
    reset_clarify_handler,
)

async def my_turn(user_input):
    def ask(question, choices=None):
        # Deliver the question through your UI; return the answer as a string.
        return deliver_to_my_ui(question, choices)

    token = set_clarify_handler(ClarifyHandler(ask_callback=ask))
    try:
        return await run_agent(user_input)
    finally:
        reset_clarify_handler(token)
FunctionSignaturePurpose
set_clarify_handler(handler: Optional[ClarifyHandler]) -> contextvars.TokenBind a handler for the current context
reset_clarify_handler(token: contextvars.Token) -> NoneRestore the previous binding
  • The binding is turn-scoped (a ContextVar), so concurrent asyncio tasks never see each other’s handlers.
  • An explicitly configured handler (ClarifyTool(handler=...) or ctx["clarify_handler"]) still wins. The bound handler is used only by the shared default clarify instance.
  • Always call reset_clarify_handler(token) in a finally block.

Custom Context Handler

python
from praisonaiagents import Agent
from praisonaiagents.tools.clarify import clarify

async def my_clarify_handler(question, choices):
    # Custom UI/web interface
    return await show_dialog(question, choices)

agent = Agent(
    name="Assistant",
    tools=[clarify],
    ctx={"clarify_handler": my_clarify_handler}
)

Handler Resolution

The tool resolves the handler in this order:

Diagram
graph TD
    A[clarify called] --> B{ctx['clarify_handler']?}
    B -->|yes| USE[Use that handler]
    B -->|no| C{Explicit handler on ClarifyTool?}
    C -->|yes| USE
    C -->|no| D{set_clarify_handler bound<br/>in this ContextVar?}
    D -->|yes| USE
    D -->|no| F[Default ClarifyHandler<br/>best-judgment fallback string]
    USE --> R[Return user's answer]

    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff
    classDef fallback fill:#F59E0B,stroke:#7C90A0,color:#fff

    class A,B,C,D process
    class USE,R result
    class F fallback
  1. Context handler: kwargs["ctx"]["clarify_handler"] (highest priority)
  2. Explicit tool handler: ClarifyTool(handler=...)
  3. Bound handler: set with set_clarify_handler(...), used only when the tool has no explicit handler
  4. Default: ClarifyHandler() returns the best-judgment fallback string

Configuration Options

Tool Schema

python
# LLM sees this tool signature:
{
    "name": "clarify",
    "description": "Ask the user a focused clarifying question when genuinely ambiguous. Use sparingly - only when you cannot proceed without their input.",
    "parameters": {
        "type": "object",
        "properties": {
            "question": {
                "type": "string",
                "description": "The clarifying question to ask"
            },
            "choices": {
                "type": "array", 
                "items": {"type": "string"},
                "description": "Optional list of predefined answer choices"
            }
        },
        "required": ["question"]
    }
}

Bot Auto-Approval

python
from praisonaiagents import BotConfig

config = BotConfig(
    default_tools=["clarify"],  # Included by default
    auto_approve_tools=True     # No approval prompt for clarify
)

The clarify tool is included in the bot’s default auto-approve list, so it won’t require manual approval in bot environments.


Common Patterns

Progressive Clarification

python
from praisonaiagents import Agent
from praisonaiagents.tools.clarify import clarify

agent = Agent(
    name="CodeWriter",
    instructions="""
    Write code based on user requests. Use clarify for:
    1. Language/framework choice when unspecified
    2. Feature priorities when scope is broad
    3. Architecture decisions when requirements are complex
    """,
    tools=[clarify]
)

# Example interaction:
# User: "Build a REST API"
# Agent: clarify("Which language?", ["python", "node.js", "go"])
# User: "python"  
# Agent: clarify("Which framework?", ["fastapi", "flask", "django"])

Context-Aware Questions

python
async def smart_clarify_handler(question, choices):
    """Handler that considers conversation context"""
    # Check previous messages for hints
    context = get_conversation_context()

    if "python" in context and "web" in question.lower():
        # Auto-suggest based on context
        return "fastapi"

    return await show_user_dialog(question, choices)

Fallback Behavior

The "No interactive channel" string is returned only when no handler is configured and none is bound, which means a truly headless run.

python
result = await clarify("Which database?", ["postgres", "mysql"])
# Headless: "No interactive channel available. Please proceed with your best judgment for: Which database?"
# CLI with create_cli_clarify_handler() in ctx: the user is prompted.
# Bot turn: "Your question has been delivered to the user. Do not guess an answer: end your turn now by restating the question and its choices. Their answer will arrive as their next message."

if "no interactive channel" in result.lower():
    database = "postgres"  # headless default

Best Practices

Use Sparingly

Only call clarify when you genuinely cannot proceed without user input. Don’t ask for preferences that have reasonable defaults.

Good: “Which API endpoint format?” when building an API

Bad: “Should I use descriptive variable names?” (obvious default)

Provide Clear Choices

When possible, offer specific choices rather than open-ended questions.

Good: choices=["fastapi", "flask", "django"]

Bad: "What Python web framework should I use?" (no choices)

Handle Fallbacks Gracefully

For headless runs, check if the response indicates no interactive channel and proceed with a sensible default.

python
response = await clarify("Pick a color", ["blue", "red"])
if "no interactive channel" in response.lower():
    color = "blue"  # Use default
else:
    color = response
Context-Aware Questions

Frame questions with enough context for users to make informed decisions.

Good: “Which authentication method for your user API?”

Bad: “Which auth?” (unclear context)


Related