Skip to content
Learn Agentic AI10 min read0 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.

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.

See AI Voice Agents Handle Real Calls

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

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.

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 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.