Skip to content
Learn Agentic AI10 min read0 views

Contextual Follow-Up Questions: Building Agents That Ask Smart Clarifying Questions

Design AI agents that identify information gaps and generate contextually relevant clarifying questions to improve response accuracy without frustrating users.

The Art of Asking the Right Question

The difference between a helpful assistant and an annoying one often comes down to questions. A great agent asks precisely the right question at the right time — one that fills a genuine information gap and moves the conversation forward. A poor agent asks too many questions, asks obvious ones, or asks things the user already answered.

Contextual follow-up questions are dynamically generated based on what the agent already knows, what it still needs, and the specific task being performed.

Modeling Information Gaps

Start by defining what information is needed for each task and tracking what has been gathered so far.

from dataclasses import dataclass, field
from typing import Optional
from enum import Enum


class GapPriority(Enum):
    BLOCKING = "blocking"    # Cannot proceed without this
    IMPORTANT = "important"  # Significantly improves outcome
    OPTIONAL = "optional"    # Nice to have


@dataclass
class InformationGap:
    field_name: str
    description: str
    priority: GapPriority
    question_template: str
    context_hints: list[str] = field(default_factory=list)
    max_asks: int = 2
    times_asked: int = 0

    def can_ask(self) -> bool:
        return self.times_asked < self.max_asks


@dataclass
class TaskRequirements:
    task_name: str
    gaps: list[InformationGap]
    known_info: dict = field(default_factory=dict)

    def blocking_gaps(self) -> list[InformationGap]:
        return [
            g for g in self.gaps
            if g.field_name not in self.known_info
            and g.priority == GapPriority.BLOCKING
            and g.can_ask()
        ]

    def important_gaps(self) -> list[InformationGap]:
        return [
            g for g in self.gaps
            if g.field_name not in self.known_info
            and g.priority == GapPriority.IMPORTANT
            and g.can_ask()
        ]

    def completion_ratio(self) -> float:
        total = len(self.gaps)
        filled = sum(
            1 for g in self.gaps if g.field_name in self.known_info
        )
        return filled / total if total > 0 else 1.0

Smart Question Generation

Questions should incorporate context from what the agent already knows to demonstrate it has been listening and to avoid redundant requests.

class QuestionGenerator:
    def __init__(self):
        self.conversation_context: dict = {}

    def generate(
        self, gap: InformationGap, known_info: dict
    ) -> str:
        question = gap.question_template

        # Inject known context into the question
        for key, value in known_info.items():
            placeholder = "{" + key + "}"
            if placeholder in question:
                question = question.replace(placeholder, str(value))

        # Add contextual hints based on known information
        hints = self._select_hints(gap, known_info)
        if hints:
            question += f" ({hints})"

        gap.times_asked += 1
        return question

    def _select_hints(
        self, gap: InformationGap, known_info: dict
    ) -> Optional[str]:
        relevant_hints = []
        for hint in gap.context_hints:
            # Hints reference known info keys
            for key in known_info:
                if key in hint:
                    filled = hint.replace(
                        f"{{{key}}}", str(known_info[key])
                    )
                    relevant_hints.append(filled)
        return "; ".join(relevant_hints) if relevant_hints else None

The Clarification Controller

The controller decides when to ask, what to ask, and when to stop asking and proceed with available information.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

class ClarificationController:
    def __init__(
        self,
        max_questions_per_turn: int = 1,
        proceed_threshold: float = 0.7,
    ):
        self.generator = QuestionGenerator()
        self.max_per_turn = max_questions_per_turn
        self.proceed_threshold = proceed_threshold
        self.questions_this_session = 0
        self.max_session_questions = 5

    def should_ask(self, requirements: TaskRequirements) -> bool:
        if self.questions_this_session >= self.max_session_questions:
            return False

        # Always ask if blocking gaps exist
        if requirements.blocking_gaps():
            return True

        # Ask important gaps only if below threshold
        if requirements.completion_ratio() < self.proceed_threshold:
            return bool(requirements.important_gaps())

        return False

    def get_questions(
        self, requirements: TaskRequirements
    ) -> list[str]:
        questions = []

        # Blocking gaps first
        for gap in requirements.blocking_gaps():
            if len(questions) >= self.max_per_turn:
                break
            q = self.generator.generate(gap, requirements.known_info)
            questions.append(q)
            self.questions_this_session += 1

        # Then important gaps if room
        if len(questions) < self.max_per_turn:
            for gap in requirements.important_gaps():
                if len(questions) >= self.max_per_turn:
                    break
                q = self.generator.generate(
                    gap, requirements.known_info
                )
                questions.append(q)
                self.questions_this_session += 1

        return questions

Example: Travel Booking Agent

travel_task = TaskRequirements(
    task_name="book_flight",
    gaps=[
        InformationGap(
            "destination", "Where the user wants to fly",
            GapPriority.BLOCKING,
            "Where would you like to fly to?",
        ),
        InformationGap(
            "departure_date", "When to depart",
            GapPriority.BLOCKING,
            "When would you like to depart for {destination}?",
            context_hints=["popular travel period for {destination}"],
        ),
        InformationGap(
            "return_date", "When to return",
            GapPriority.IMPORTANT,
            "When would you like to return from {destination}?",
        ),
        InformationGap(
            "cabin_class", "Preferred cabin class",
            GapPriority.OPTIONAL,
            "Any preference on cabin class?",
        ),
    ],
)

controller = ClarificationController(max_questions_per_turn=1)

# User says: "I want to fly to Tokyo"
travel_task.known_info["destination"] = "Tokyo"

if controller.should_ask(travel_task):
    questions = controller.get_questions(travel_task)
    for q in questions:
        print(q)
    # Output: "When would you like to depart for Tokyo?"

The question naturally incorporates the already-known destination, making it feel like a real conversation rather than an interrogation.

FAQ

How many clarifying questions should an agent ask before proceeding?

Limit clarifying questions to one per turn and five per session. After that, proceed with defaults or partial information and let the user refine. Research shows that more than two consecutive questions causes significant user drop-off, so interleave questions with partial answers when possible.

How do you handle users who refuse to answer a clarifying question?

If a user ignores a blocking question, rephrase it once with different wording. If they ignore it again, explain why the information is needed and offer alternatives. For example: "I need the date to search flights. Would you like me to show options for the next week instead?" Providing a default path prevents dead ends.

Should agents ask optional questions at all?

Ask optional questions only when the conversation is flowing well and the user seems engaged. If the user is giving terse responses or showing impatience signals, skip optional gaps and use sensible defaults. The agent should track engagement signals like response length and response time to calibrate.


#FollowUpQuestions #Clarification #DialogFlow #ConversationalAI #Python #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.