Case study · HumanFirst · 2024

Mapping user intent: rebuilding NLU disambiguation while every client stayed in production

The industry was moving toward generative models while our largest accounts still ran intent classification every day. This is how the disambiguation workflow got dramatically better and stayed familiar enough that every client came with it.

Role
UX and Product Designer
Timeframe
Two week cycle, 2024
Tools
Figma
Constraint
Every client stays in production through the change
In a rush?

Watch the 2 minute version

Live prototypeClick an overlapping intent, then move an example between columns.
IntentsPromptsPipeline
Disambiguate modezip_code
3 intents share examples with zip_code. Reassign the confusing ones to sharpen each.
Order_ID· fulfilment
100%
Account_ID· billing
73%
Account_ID· support
14%
Disambiguate

zip_code

Pinned intent
Reassign the examples that overlap with another intent. Pick a close match from the list to compare its examples against zip_code.
Disambiguate by intentCompare zip_code with one overlapping intent and reassign the confusing examples.
Disambiguate all labeled dataReview every labeled example across all intents, ranked by match confidence.
Disambiguating withKNN
Zip_codeOrder_ID100%
ID is 2343523100%
My order ID is 23498%
I don't have my ID number95%
I lost my ID92%
whats my id number89%
my reference id please86%
Order ID: 6184509735
Order ID: 3956431760
Order ID: 7896521711
my order number is 9679250764
Order ID: 4451092
order ref 7890021
Order ID: 5562318
whats my order id
CancelApply changes
The shipped disambiguation workspace, rebuilt here as running code. Pick one of the three overlapping intents on the left, then move an example across the divide to reassign it. Every screen on this page is a faithful recreation, built so you can work the flow yourself.

The workflow I was changing

Before large language models, a conversational assistant worked by classification. Someone typed a message, an intent classifier trained on labeled examples scored it against every intent the assistant knew, and the top prediction decided what happened next.

The interesting part was the failure case. When two intents came back close together, say order_status at 58 percent and track_package at 55 percent, the assistant had no basis for choosing. Explicit disambiguation logic existed to catch those low confidence cases and resolve them.

The fix lived upstream, in the training data. Two intents scored close because their labeled examples overlapped, so someone had to find the overlap and reassign the examples that belonged elsewhere. That review workflow is what I redesigned, and for enterprise clients it ran as a standing operational routine.

How conversational AI worked before LLMs
Utterancewhere is my order
Intent classifiertrained on labeled utterances
Predictions
order_status58%
track_package55%
cancel_order19%
Two close matches need disambiguation
Explicit disambiguation logic resolved the low confidence cases. That is the workflow I redesigned.
User goalMake every intent as clear as possible
The classification loop I was designing inside. The user goal at the end of it, making every intent as clear as possible, is what the disambiguation workflow existed to serve.

What was at stake

By 2024 the industry was moving toward generative models, and HumanFirst had to move with it. The platform's future depended on being credible in that new world.

The pull ran in two directions at once. The revenue funding that move came from enterprise accounts sitting on years of labeled data, trained pipelines, and teams whose weekly routine ran straight through the intent workflow. Those teams had built real fluency in the existing patterns. A migration that asked them to relearn their own daily job was a migration they could refuse.

NLUvsNLG

The shift the company was designing for. The clients paying for it were living on the left side of that line.

Before: the comparison happened in your head

Disambiguation was something you performed, and the interface gave it nowhere to live. You navigated to Intents, opened one, and read its labeled examples in a list.

Open username in the screen below. It holds a run of zip code utterances that were labeled into it by mistake, and nothing on the screen marks them as a problem. There is no signal that username and zip_code are colliding, no ranking of which intent they collide with most, and no score on any single example. A reviewer carried the whole comparison from memory, one intent at a time.

Live prototypeOpen Intents, then select username to see the mislabeled examples.
HEHeavyData_50in…Active NLU engineKNN
importexport
Show
Select all
New intent
New sub-intent
Home

username

Clustering offAnnotate offSort by similarity
My name is Alessandro Phoenix.
I'M Alessandro Phoenix
Zip code is 64955
zip code 62838
zip code: 61800
Zip code: 66011
Zipcode 60616
call me Alex
my username is aphoenix
the name is Phoenix
Add a new training example
Open testing
The starting point, recreated as running code. Open the Intents list, then look inside username. The mislabeled zip code examples are visible only to someone who already knows to look for them.

Three directions

I explored three shapes for the workflow before committing. All three were drawn against the same task: finding which examples inside zip_code belong to another intent, and moving them there. The rail below switches between them, each one running.

Why it was set aside

You could not see which intent each utterance was heading toward, and with many intents sharing a name or a close variation, the right target was easy to miss. The similarity score stayed out of view, so confidence was hard to read at a glance.

HEWorkspace
LabelsPromptsPipeline
Return

Disambiguating

Order_ID
Select allStashDeleteMove stash here
Add a new training example
Utterance
My order id is 102716087352%
ID 1239481950%

Choose an Intent for

Order_ID 52%
Order_status 3%
Order 2%
Cancel
The three candidate directions, live. The third organizes the screen around the decision itself, which is what made it worth the cost of moving people.

The bet

I gave disambiguation its own place in the product, with a mode, a pinned intent, and a surface shaped by the comparison it exists to support.

The core move is the two column compare. On the left sit the examples currently labeled into the pinned intent that look like they belong to the candidate intent. On the right sit the candidate's own examples. Moving an example across the divide reassigns it. The comparison the old workflow asked people to hold in memory now happens on screen.

The migration lived in the details

The surface was a different shape, so every gesture inside it had to feel like something these teams already did. Moving an example between columns reuses the move behavior from the old labeling flow. Hover shows the direction a row will travel before the click commits it. Apply stays quiet until there is a change to apply, so a reviewer can open a conflict, read it, and leave with their data untouched.

Zip_code
ID is 2343523
My order ID is 234
Order_ID
Order ID: 618450
Move to reassign
My order ID is 234
Hover points the way
Apply changes
Apply unlocks on edit
Three of the interaction details, running. Familiar mechanics are the budget a redesign spends. Reusing them is what let a new surface land without a relearning cost.

Browsing a conflict had to be free, and writing to a client's labeled data had to be deliberate. That split is the whole trust model of the screen.On keeping people in control

What changed

Zero

Workflow disruptions through rollout

100%

Client migration to the new workspace

Unblocked

A major client rollout on the platform

These are the outcomes I can stand behind, and they are qualitative. I had no instrumentation on this workflow, so there is no before and after on task time or error rate to show here. The signal we had was the absence of the thing we were most afraid of. No account asked to stay on the previous surface, and the rollout passed without a wave of questions about where the workflow had gone.

What I would measure next

Given another cycle, I would instrument the decision itself. Four things I would put in place:

What I take from this

Evolving a live system is a migration problem before it is a design problem. The first question is what the people already inside the old surface can carry across. How good the new screen is comes second, and it only counts once they arrive.

You can make something dramatically better while keeping it the same. Familiar mechanics are the budget you spend a redesign against. Holding onto the move gesture, the labeling vocabulary, and the navigation model bought the room to change the entire shape of the task.

The best outcome in enterprise work is sometimes that nobody notices. A silent rollout reads as a quiet result on a portfolio page and as a loud one inside the company. This project is the reason I now design the migration path at the same time as the destination.