Part 5: Debugging Dialogflow Overlapping User Intents

A chatbot can work perfectly with a dozen intents and become surprisingly unpredictable as its vocabulary grows. Add order tracking, delivery estimates, and shipping information, and suddenly “When will my order arrive?” has three plausible destinations.
That is the real challenge of overlapping intents in Dialogflow: several intents appear to describe the same user request, but your application expects different behavior from each. The result may be an irrelevant answer, an unnecessary question, or a webhook that starts the wrong workflow.
In Part 5, we will work through a repeatable debugging process: reproduce the mismatch, inspect the available evidence, clarify intent boundaries, and test the repair before releasing it.
This guide uses Dialogflow terminology and APIs available in 2019; tools and interfaces may have changed since then.
What overlapping intents actually means
An intent represents what a user wants to accomplish. Overlap occurs when two or more intents compete for similar language in the same conversational state.
Consider these three intents:
| Intent | Intended purpose | Example training phrase |
| --- | --- | --- |
| Order.Track | Check an existing order | “Where is my order?” |
| Shipping.Estimate | Explain delivery times before purchase | “How long does shipping take?” |
| Shipping.Policy | Explain shipping options and restrictions | “Tell me about shipping” |
Now imagine adding “When will it arrive?” to both Order.Track and Shipping.Estimate. Without conversational context, neither interpretation is necessarily wrong. The design is asking Dialogflow to infer something the user has not supplied.
Overlap is not the same as a duplicate intent ID. Separate intents have separate identifiers and can still compete semantically. Conversely, a correctly matched intent can produce a bad answer because its parameters or fulfillment logic are wrong.
There is also no standard Dialogflow folder named overlapping_intent_bugs, nor a built-in main_script that resolves these problems. Any such files would belong to a particular project's custom tooling. Reliable debugging starts with observable requests and responses, not an assumed diagnostic script.
1. Capture a reproducible failure
Start with one concrete mismatch rather than changing the entire agent.
In the Dialogflow console, open the agent and enter the problematic utterance in the test console. Record:
- The exact text, including punctuation and spelling.
- The intent you expected and the intent actually matched.
- Any preceding messages required to reproduce the problem.
- Active contexts and relevant parameter values.
- The agent language and the environment being tested.
- The response shown to the user.
For example:
Previous message: I placed an order yesterday.
Current message: When will it arrive?
Expected intent: Order.Track
Actual intent: Shipping.Estimate
Test with and without conversation history
Repeat the utterance in a fresh session, then repeat the original conversation sequence in another session.
If the result changes, conversational state is probably involved. An unexpected context may be making an intent eligible, or an expected context may have expired before the user replies.
Do not treat grammatical correctness as proof that an utterance should match a particular intent. Natural-language classification depends on meaning, training examples, and state, not merely well-formed sentences.
2. Separate classification errors from fulfillment errors
Before editing training phrases, establish which layer failed.
Dialogflow's API response includes a queryResult object. Useful fields include:
intent.displayName: the matched intent.intentDetectionConfidence: confidence associated with that match.parameters: extracted values.allRequiredParamsPresent: whether required parameters have been collected.outputContexts: contexts returned for the session.fulfillmentText: a text response, when available.
Use these fields to distinguish several common cases:
| Observation | Likely investigation | | --- | --- | | Wrong intent matched | Training phrases, intent boundaries, contexts | | Right intent, wrong parameter | Entity annotations and entity definitions | | Right intent, missing required value | Parameter prompts and slot filling | | Right intent and parameters, wrong answer | Webhook routing or response logic |
Confidence is useful evidence, but it is not a guarantee of correctness. A confidently wrong result still needs investigation. The standard detection response also does not provide a complete ranking of competing intents, so it cannot directly tell you every alternative the classifier considered.
3. Audit the competing intents together
Open the expected intent and the incorrectly matched intent side by side. Compare their purpose, training phrases, parameters, contexts, and fulfillment behavior.
Write a one-sentence boundary
For each intent, complete this sentence:
Use this intent when the user wants to ___, but not when they want to ___.
For Order.Track, the boundary might be checking an existing purchase rather than estimating delivery before purchase.
If you cannot explain the distinction without referring to an internal department or backend service, the split may not belong in the conversational model. Users express goals, not your application architecture.
Look for exact and semantic duplicates
Exact duplicates are easy to spot, but paraphrases can be equally troublesome:
Order.Track: When does my package arrive?
Shipping.Estimate: When will my package get here?
These examples express essentially the same request. Moving a single word around will not create a useful boundary.
Review short, generic examples especially carefully: “shipping,” “help,” “status,” and “change it” depend heavily on context. Also inspect entity annotations. If the only distinguishing information is absorbed into a broad entity value, the remaining phrase may provide little evidence about the user's goal.
4. Choose the right structural repair
Not every overlap should be solved by adding more examples. Choose a repair that matches the underlying ambiguity.
Merge intents that represent the same goal
If two intents perform the same conversational job and return the same kind of answer, consider merging them.
For example, separate intents for “track my package” and “check my order status” may be unnecessary. One intent can collect an order identifier and let fulfillment retrieve the appropriate status.
Before deleting or renaming an intent, check webhook mappings, integrations, and tests that depend on its name or identifier.
Use contexts for state-dependent meanings
Contexts help distinguish language that only makes sense after a particular question.
An order-tracking conversation could set an output context named awaiting_order_number. An intent that accepts the user's order number can require that context as an input context. This prevents a bare number from triggering that handler throughout the conversation.
Configure context lifespans deliberately. A lifespan that is too short loses useful state; one that is too long leaves unrelated intents eligible later. Verify the contexts returned during a full exchange, rather than assuming a configured context remains active indefinitely.
Contexts should represent conversation state, not compensate for two globally ambiguous intent definitions.
Ask a clarifying question when the information is missing
Sometimes “When will it arrive?” really is ambiguous.
Instead of forcing a guess, ask whether the user is checking an existing order or asking about delivery before purchasing. Route the answer using short-lived contexts associated with that clarification.
This adds a turn, but it avoids sending a user into the wrong workflow. For consequential actions such as cancellation, classification should never substitute for explicit confirmation.
5. Refine training phrases without overfitting
Once the boundaries are clear, adjust the examples.
Use a small review cycle:
- Remove or relocate phrases that contradict an intent's purpose.
- Add natural variations that express the distinguishing goal.
- Keep genuinely ambiguous phrases in a clarification path where appropriate.
- Save the changes and allow training to finish.
- Test both the original failure and neighboring requests.
For tracking, useful examples might mention an existing purchase: “I ordered last week; where is it?” For estimates, examples might establish a pre-purchase situation: “If I buy today, when would it arrive?”
Avoid adding dozens of nearly identical phrases just to make one intent win. That can conceal a design problem and create regressions elsewhere. Keep some evaluation utterances out of the training set so that your tests measure generalization, not just recognition of supplied examples.
6. Automate a small regression suite
Manual testing finds the problem; repeatable testing helps keep it fixed.
The following Python example calls the Dialogflow v2 detectIntent endpoint. It uses a new session for each case so these checks measure standalone requests without inherited contexts.
Install requests, set GOOGLE_CLOUD_PROJECT and GOOGLE_OAUTH_ACCESS_TOKEN, and use a short-lived OAuth access token for an identity authorized to detect intents in the project. The token needs an appropriate scope, such as cloud-platform; it is not an API key.
import os
import uuid
import requests
project = os.environ["GOOGLE_CLOUD_PROJECT"]
token = os.environ["GOOGLE_OAUTH_ACCESS_TOKEN"]
cases = [
("Where is the order I placed yesterday?", "Order.Track"),
("How long is delivery if I buy today?", "Shipping.Estimate"),
("Which countries do you ship to?", "Shipping.Policy"),
]
failures = 0
for text, expected in cases:
session = uuid.uuid4().hex
url = (
"https://dialogflow.googleapis.com/v2/projects/"
f"{project}/agent/sessions/{session}:detectIntent"
)
response = requests.post(
url,
headers={"Authorization": f"Bearer {token}"},
json={
"queryInput": {
"text": {"text": text, "languageCode": "en"}
}
},
timeout=30,
)
response.raise_for_status()
result = response.json()["queryResult"]
actual = result.get("intent", {}).get("displayName", "")
confidence = result.get("intentDetectionConfidence", 0.0)
passed = actual == expected
failures += int(not passed)
print(
f"{'PASS' if passed else 'FAIL'}: {text!r} "
f"expected={expected} actual={actual} "
f"confidence={confidence:.3f}"
)
raise SystemExit(1 if failures else 0)
These names are examples; replace them with your agent's intent names. This endpoint targets the default agent session path, so confirm that you are testing the configuration you intend to release.
For conversation tests, reuse a session ID within each scenario and send messages in order. Assert parameters and contexts as well as intent names. Use a separate session for the next scenario.
Run tests against safe fulfillment dependencies: detection requests can invoke webhooks, and a test should not cancel a real order.
7. Check regressions before deployment
A repair is incomplete until nearby intents still work. Include tests for:
- Clear examples of each competing intent.
- Ambiguous requests and their clarification replies.
- Short answers within an active conversation.
- Unrelated requests that should reach fallback handling.
- Changes of topic and expired contexts.
Avoid raising the agent's classification threshold as a first response to overlap. A higher threshold may send more uncertain requests to fallback without resolving the underlying boundary problem.
After deployment, review mismatches using appropriately protected conversation records. Remove sensitive values before sharing debugging examples, and turn confirmed failures into regression cases.
Takeaway
Debugging overlapping intents is a modeling and testing task, not an ID-cleanup exercise. Reproduce the conversation, identify the failing layer, clarify intent boundaries, and use contexts or clarification when language alone is insufficient. Then verify the fix against both isolated utterances and complete conversations.
Conversation (0)
No comments yet. Start the conversation.
Join the conversation
Create a free member account to comment.