Skip to content
Learn Agentic AI
Learn Agentic AI10 min read8 views

Multi-Intent Detection: Handling Users Who Ask Multiple Things in One Message

Learn how to detect and handle multiple intents in a single user message, including intent splitting, parallel processing, and delivering coherent ordered responses.

The Single-Intent Assumption Problem

Most conversational AI systems assume each user message contains exactly one intent. But users naturally combine requests: "Check my balance and transfer $200 to savings." That single message carries two distinct intents — a balance inquiry and a fund transfer. Agents that only detect one intent frustrate users by ignoring part of their request.

Multi-intent detection identifies all intents within a message, separates them, processes each one, and delivers a coherent combined response.

Intent Segmentation

The first step is splitting a compound message into individual intent segments. Coordinating conjunctions ("and," "also," "then") and punctuation are natural delimiters.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus<br/>classify"]
    PLAN["Plan and tool<br/>selection"]
    AGENT["Agent loop<br/>LLM plus tools"]
    GUARD{"Guardrails<br/>and policy"}
    EXEC["Execute and<br/>verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus<br/>next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
import re
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class IntentSegment:
    text: str
    intent: Optional[str] = None
    confidence: float = 0.0
    entities: dict = field(default_factory=dict)
    response: Optional[str] = None
    order: int = 0

class IntentSplitter:
    def __init__(self):
        self.split_patterns = [
            r"and(?:s+also)?",
            r"then",
            r"also",
            r"plus",
            r"[;.](?=s)",
            r"after that",
        ]
        self.combined_pattern = "|".join(
            f"({p})" for p in self.split_patterns
        )

    def split(self, message: str) -> list[IntentSegment]:
        segments = re.split(
            self.combined_pattern, message, flags=re.IGNORECASE
        )
        # Filter out None values and delimiter matches
        cleaned = [
            s.strip() for s in segments
            if s and s.strip() and not re.match(
                self.combined_pattern, s.strip(), re.IGNORECASE
            )
        ]

        if not cleaned:
            return [IntentSegment(text=message, order=0)]

        return [
            IntentSegment(text=seg, order=i)
            for i, seg in enumerate(cleaned)
            if len(seg) > 2  # Skip very short fragments
        ]

Intent Classification Pipeline

After splitting, classify each segment independently. This example uses a keyword-based classifier, but in production you would use a trained model or LLM.

class IntentClassifier:
    def __init__(self):
        self.intent_patterns = {
            "check_balance": {
                "keywords": ["balance", "how much", "account"],
                "base_confidence": 0.8,
            },
            "transfer": {
                "keywords": ["transfer", "send", "move"],
                "base_confidence": 0.8,
            },
            "pay_bill": {
                "keywords": ["pay", "bill", "payment"],
                "base_confidence": 0.75,
            },
            "order_status": {
                "keywords": ["order", "tracking", "shipment", "delivery"],
                "base_confidence": 0.8,
            },
        }

    def classify(self, segment: IntentSegment) -> IntentSegment:
        text_lower = segment.text.lower()
        best_intent = None
        best_score = 0.0

        for intent, config in self.intent_patterns.items():
            matches = sum(
                1 for kw in config["keywords"] if kw in text_lower
            )
            if matches > 0:
                score = config["base_confidence"] * (
                    matches / len(config["keywords"])
                )
                if score > best_score:
                    best_score = score
                    best_intent = intent

        segment.intent = best_intent or "unknown"
        segment.confidence = best_score
        return segment

Parallel Processing and Ordered Response

Process intents in parallel when they are independent, but maintain the user's original ordering in the response.

import asyncio
from typing import Callable

class MultiIntentProcessor:
    def __init__(self):
        self.splitter = IntentSplitter()
        self.classifier = IntentClassifier()
        self.handlers: dict[str, Callable] = {}

    def register_handler(self, intent: str, handler: Callable):
        self.handlers[intent] = handler

    async def process(self, user_message: str) -> str:
        segments = self.splitter.split(user_message)

        # Classify all segments
        classified = [self.classifier.classify(seg) for seg in segments]

        # Process independent intents concurrently
        tasks = []
        for seg in classified:
            handler = self.handlers.get(seg.intent)
            if handler:
                tasks.append(self._execute(seg, handler))
            else:
                seg.response = f"I'm not sure how to help with: {seg.text}"
                tasks.append(asyncio.sleep(0))  # no-op placeholder

        await asyncio.gather(*tasks)

        # Combine responses in original order
        responses = sorted(classified, key=lambda s: s.order)
        parts = [s.response for s in responses if s.response]
        return "\n\n".join(parts)

    async def _execute(self, segment: IntentSegment, handler: Callable):
        try:
            segment.response = await handler(segment)
        except Exception as e:
            segment.response = (
                f"I encountered an issue processing '{segment.text}': {e}"
            )

Wiring Up Handlers

async def handle_balance(segment: IntentSegment) -> str:
    # Simulated balance check
    return "Your current balance is $2,450.00."

async def handle_transfer(segment: IntentSegment) -> str:
    return "Transfer of $200 to savings has been initiated."

processor = MultiIntentProcessor()
processor.register_handler("check_balance", handle_balance)
processor.register_handler("transfer", handle_transfer)

# Usage
result = asyncio.run(
    processor.process("Check my balance and transfer $200 to savings")
)
print(result)
# Your current balance is $2,450.00.
#
# Transfer of $200 to savings has been initiated.

Handling Intent Dependencies

Some compound requests have implicit dependencies. "Check my balance and transfer everything to savings" requires the balance result before the transfer can execute. Detect these dependencies and process them sequentially.

class DependencyResolver:
    def __init__(self):
        self.dependency_rules = {
            ("check_balance", "transfer"): self._check_transfer_dep,
        }

    def _check_transfer_dep(self, segments: list[IntentSegment]) -> bool:
        transfer_seg = next(
            (s for s in segments if s.intent == "transfer"), None
        )
        if transfer_seg and "everything" in transfer_seg.text.lower():
            return True  # Transfer depends on balance result
        return False

    def has_dependency(self, segments: list[IntentSegment]) -> bool:
        intents = tuple(s.intent for s in segments)
        for rule_key, checker in self.dependency_rules.items():
            if all(i in intents for i in rule_key):
                if checker(segments):
                    return True
        return False

FAQ

How do you avoid splitting single intents that use coordinating conjunctions?

Not every "and" separates intents. "Search for flights to Paris and London" is a single search intent with two destinations. Use syntactic analysis to distinguish coordinated arguments from coordinated clauses. Train your splitter on labeled examples from your domain, and when in doubt, keep the message whole and let the classifier handle multi-entity extraction within one intent.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

What if the intents conflict with each other?

Conflicting intents like "cancel my order and add expedited shipping" should be flagged before processing. Build a conflict matrix of intent pairs that are mutually exclusive. When detected, ask the user to clarify which action they prefer rather than executing one and silently dropping the other.

How do you handle more than three intents in one message?

Messages with four or more intents are rare but happen. Process them all, but present the responses with clear visual separation — numbered items or headers for each. If processing all would exceed a time budget, acknowledge the full list and process them in batches, confirming each before continuing.


#MultiIntent #NLU #IntentDetection #ConversationalAI #Python #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

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

Related Articles You May Like

Agentic AI

Building Your First Agent with the OpenAI Agents SDK in 2026: A Hands-On Walkthrough

Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.

Agentic AI

Voice Agent Quality Metrics in 2026: WER, Latency, Grounding, and the Ones Most Teams Miss

The full metric set for evaluating production voice agents — STT word error rate, end-to-end latency budgets, RAG grounding, prosody, and the metrics that actually correlate with retention.

Agentic AI

Building OpenAI Realtime Voice Agents with an Eval Pipeline (2026)

Build a working voice agent with the OpenAI Realtime API + Agents SDK, then bolt on an eval pipeline that catches barge-in failures, hallucinated grounding, and latency regressions.

Agentic AI

Multilingual Chat Agents in 2026: The 57-Language Gap and How to Close It

Amazon's MASSIVE-Agents research shows top models hit 57% on English vs 6.8% on Amharic. Here is what 50+ language chat agents actually need.

Agentic AI

Smolagents: Hugging Face's Code-First Agent Framework Reviewed

Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.

AI Strategy

Enterprise CIO Guide: ElevenLabs Conversational AI 2.0 — Voice Agents Get Real Tools

Enterprise CIO Guide perspective on ElevenLabs Conversational 2.0 ships native MCP tool use, sub-second turn-taking, and a redesigned dashboard that makes voice agents feel like real software.