Skip to content
Learn Agentic AI10 min read0 views

Disambiguation Patterns: Helping Users Choose When Multiple Options Match

Master disambiguation design patterns for conversational AI agents that present options clearly, narrow choices with follow-up criteria, and guide users to the right selection.

Why Disambiguation Matters

When a user asks "Show me the Python course," your agent might find three matching courses. Guessing which one the user wants creates frustration. Effective disambiguation presents options in a ranked, digestible format and uses follow-up questions to narrow the choice set naturally.

Disambiguation is the process of resolving ambiguity when a user query matches multiple entities, intents, or actions. Good disambiguation feels like a helpful assistant narrowing options, not a database dumping search results.

Modeling Disambiguation Candidates

from dataclasses import dataclass
from typing import Optional


@dataclass
class Candidate:
    id: str
    title: str
    description: str
    score: float  # relevance score 0.0-1.0
    differentiator: str  # what makes this option unique

    def summary(self) -> str:
        return f"{self.title} - {self.differentiator}"


@dataclass
class DisambiguationResult:
    resolved: bool
    selected: Optional[Candidate] = None
    candidates: list[Candidate] = None
    follow_up_question: Optional[str] = None

The differentiator field is critical. It tells users why each option is distinct, preventing them from staring at a list of near-identical items.

The Disambiguation Engine

The engine decides whether to auto-resolve (one clear winner), present ranked options, or ask narrowing questions based on the score distribution.

class DisambiguationEngine:
    def __init__(
        self,
        auto_resolve_threshold: float = 0.85,
        max_options: int = 5,
        score_gap_threshold: float = 0.3,
    ):
        self.auto_resolve_threshold = auto_resolve_threshold
        self.max_options = max_options
        self.score_gap_threshold = score_gap_threshold

    def resolve(
        self, candidates: list[Candidate]
    ) -> DisambiguationResult:
        if not candidates:
            return DisambiguationResult(
                resolved=False,
                follow_up_question="I could not find any matches. "
                "Could you rephrase your request?",
            )

        ranked = sorted(candidates, key=lambda c: c.score, reverse=True)

        # Auto-resolve when the top candidate is dominant
        if ranked[0].score >= self.auto_resolve_threshold:
            if len(ranked) == 1 or (
                ranked[0].score - ranked[1].score
                >= self.score_gap_threshold
            ):
                return DisambiguationResult(
                    resolved=True, selected=ranked[0]
                )

        # Present top options
        top = ranked[: self.max_options]
        return DisambiguationResult(
            resolved=False,
            candidates=top,
            follow_up_question=self._build_question(top),
        )

    def _build_question(self, options: list[Candidate]) -> str:
        lines = ["I found several matches. Which one did you mean?"]
        for i, opt in enumerate(options, 1):
            lines.append(f"  {i}. {opt.summary()}")
        lines.append("You can pick a number or describe what you need.")
        return "\n".join(lines)

Narrowing with Follow-Up Criteria

When options are too similar, ask a targeted narrowing question instead of listing everything.

See AI Voice Agents Handle Real Calls

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

class NarrowingStrategy:
    def __init__(self, attribute_extractors: dict):
        self.extractors = attribute_extractors

    def find_distinguishing_attribute(
        self, candidates: list[Candidate]
    ) -> Optional[str]:
        """Find an attribute that splits candidates into groups."""
        for attr_name, extractor in self.extractors.items():
            values = set()
            for c in candidates:
                val = extractor(c)
                if val:
                    values.add(val)
            if 1 < len(values) <= 4:
                return attr_name
        return None

    def generate_narrowing_question(
        self, attr_name: str, candidates: list[Candidate]
    ) -> str:
        values = set()
        for c in candidates:
            val = self.extractors[attr_name](c)
            if val:
                values.add(val)
        options = ", ".join(sorted(values))
        return (
            f"To narrow it down, are you looking for a specific "
            f"{attr_name}? Options: {options}"
        )

Putting It Together

engine = DisambiguationEngine()

candidates = [
    Candidate("py101", "Python Basics", "Intro course",
              0.72, "For beginners, 4 weeks"),
    Candidate("py201", "Python for Data Science", "Intermediate",
              0.70, "Pandas and NumPy focus, 6 weeks"),
    Candidate("py301", "Advanced Python Patterns", "Expert-level",
              0.68, "Design patterns, 8 weeks"),
]

result = engine.resolve(candidates)
if not result.resolved:
    print(result.follow_up_question)
    # Presents numbered list with differentiators

The engine automatically adapts: a single strong match is auto-resolved, close scores trigger a list, and when candidates are nearly identical, narrowing questions provide a better experience than an overwhelming option dump.

FAQ

How many disambiguation options should you present?

Keep it between two and five. Research on choice overload shows that more than five options increases cognitive load and reduces satisfaction. If you have more matches, show the top five and offer a "show more" option, or ask a narrowing question to reduce the set first.

When should the agent auto-resolve instead of asking?

Auto-resolve when the top candidate's relevance score is significantly higher than the runner-up and exceeds a confidence threshold. A good rule of thumb is auto-resolve when the gap is at least 0.3 and the top score is above 0.85. Always log auto-resolutions so you can tune thresholds from real usage data.

How do you handle users who pick none of the presented options?

Treat "none of these" as a signal to broaden the search or ask an open-ended clarification question. Never loop back with the same options. Instead, ask what specifically they are looking for, or offer to start a fresh search with different keywords.


#Disambiguation #ConversationalAI #DialogDesign #UXPatterns #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.