
At Tekst, we work with three different kinds of AI models to turn unstructured business data into structured information.
Classification models take a piece of unstructured data and determine which category it belongs to.
Extraction models take unstructured text or attachments and extract specific pieces of information from them.
But we kept running into a third problem that didn't fit neatly into either category:
How do you map something we've extracted back to the data that actually exists in a customer's systems?
Imagine an email comes in and we classify it as a new order. The customer attaches a purchase order containing four order lines:
The problem is that these order lines are just text.
We need to turn each one into an actual product from the customer's product catalogue.
If the customer has ten products, one approach is to give those ten products directly to the extraction model. An LLM is surprisingly good at understanding fuzzy descriptions and can often map the text directly to the right product.
But what happens when the customer has 100 products?
You could instead create a classification model where every product is a classification label, and classify each extracted order line against the product catalogue. That works too.
But what happens when the customer has 100,000 products?
Neither approach scales particularly well.
For a long time, we solved this by building custom fuzzy-matching logic in code for each customer and each use case. It worked okay. But "okay" isn't good enough when the result feeds directly into automation, and maintaining a growing collection of customer-specific matching logic quickly becomes a problem of its own.
So we built something that was missing from our model toolbox:
Matching models.
A matching model takes an unstructured value and resolves it to the corresponding record in a customer's own master data, whether that's a product, customer, location, supplier, or something else.
And unlike simply doing a fuzzy search, it can learn how that particular business actually identifies its data.
At its core, a matching model consists of two things:
The master data could be almost anything:
The matching agent is responsible for taking some unstructured input and finding the record that represents it.
There are a few properties we care about when building such a system:
For now, we'll leave speed out of the equation. There are interesting trade-offs there as well, but they are less fundamental to the matching problem itself.
A basic matching agent is actually surprisingly simple to build.
Give an agent access to your master data and allow it to generate search queries. The query itself can take many forms depending on how the data is indexed.
For example, the agent could generate:
The agent runs its query and gets back the top N results.
It can then make one of two decisions:
PICK: one of the returned records is the correct match.
SEARCH AGAIN: the results aren't good enough, so generate a new search query using the information from the records it just saw.
That gives us a simple iterative loop:
Generate query → Search → Inspect results → Pick or search again
And this actually works surprisingly well, but there are two fundamental problems.
Imagine a catalogue with 100,000 products.
Now imagine that 10,000 of those products are very similar to each other.
The agent searches for a particular product and gets back a record that looks like an excellent match.
It has a product name that is almost identical. The description looks right. The other attributes seem plausible.
So the agent picks it.
The problem is that the agent only knows about the records it has seen.
It has no reliable way of knowing that there might be another record somewhere in those 100,000 products that is an even better match.
This is a fundamental problem with an iterative search-and-pick approach.
Finding a good candidate does not tell you that you have found the best candidate.
And the larger and more similar the dataset becomes, the more important this distinction gets.
You can try to solve this by making the agent search more, but that introduces another problem: how much searching is enough?
There is no obvious answer.
The second problem is even more fundamental.
Suppose the input is:
"Apple iPhone 19 Pro Max 4TB"
But the customer's product catalogue contains no such product.
The matching agent searches.
It finds some iPhones.
It searches again.
It finds more iPhones.
It can keep searching, but at what point can it confidently conclude:
This product does not exist in the catalogue.
There is no obvious stopping condition.
The only truly reliable way to prove that a record does not exist is, in the general case, to examine every relevant record in the dataset.
For a dataset of 100,000 products, that is obviously undesirable.
It can drive up the cost of the matching operation, and it creates another practical problem: we need some kind of maximum search depth.
Eventually we have to say:
"We've searched 20 times. Let's stop."
But what does 20 mean? Why not 10? Why not 50?
And what happens when the correct answer would have been found on search 21?
An arbitrary search limit is not a particularly satisfying definition of correctness.
Ideally, the system should be able to finish cleanly:
Match found. or: No match exists.
rather than: Search depth exceeded.
These two problems, knowing that we found the best match and knowing that no match exists, are at the heart of building a reliable matching system.
The problems above point to a fundamental issue with simply taking a user's input and asking an AI agent:
"Find the best match for this input."
Consider an order line: 24 blue 10cm by 10cm boxes
If we give this directly to an agent and ask it to find the best matching product, the agent has to make a decision every time it finds a plausible candidate:
"Is this good enough, or should I keep searching?"
That decision is inherently difficult.
Instead, we changed the way our matching agent approaches the problem.
Rather than asking the agent to find the best match, we first ask it to break the input down into several search angles that might independently lead us to a valid record.
For our example, that could produce three searches:
At first glance, this might look like an unnecessary extra step. We're still ultimately trying to find the same product, so why not just search for it directly?
The difference is that these searches have fundamentally different semantics.
Each search is no longer asking: "Is this the best match?"
Instead, each search asks a much more constrained question:
"Find a record that satisfies these specific characteristics."
The first search is looking specifically for a blue box with dimensions of 10cm by 10cm.
If it finds nothing, it does not need to decide whether it should start looking for something slightly less specific.
It simply returns no results.
That's because another search angle is already responsible for that alternative:
"Search for a blue box without dimensions."
And another is responsible for a different relaxation:
"Search for a 10cm by 10cm box without color."
This changes the fundamental behavior of the matching agent.
In the original approach, the agent is continuously making a global decision:
"Is the match I have now better than anything I might find if I keep searching?"
In the new approach, we create multiple independent subtasks, each with a clearly defined scope:
"Search for records matching exactly these characteristics."
There is no need for the individual search agent to reason about what it should relax, what it should try next, or whether the current result is globally optimal.
That responsibility has been moved up one level.
The matching system decides which search angles are worth exploring. Each individual search then executes a much narrower task.
This is the foundation of our matching agent.
Instead of having one agent endlessly search for the "best" answer, we create multiple constrained searches that explore different interpretations of the input.
The result is not just more searches.
It is a fundamentally different way of structuring the problem: we replace an open-ended search problem with a collection of bounded, well-defined search problems.
There is something we have been ignoring throughout this entire blog:
The LLM doesn't actually know everything it needs to know to make the correct match.
Whether we use one generic search agent or our search-angle approach, we are still relying on an LLM to understand what is important about the data it is searching.
And surprisingly often, it does.
LLMs are remarkably good at making assumptions about unfamiliar data. They can infer relationships between product names, recognize common abbreviations, understand that two descriptions probably refer to the same thing, and generally make a lot of reasonable assumptions without being explicitly told how the business works.
But there is a limit.
Some knowledge is simply not present in the input or the model's general training data.
Consider synonyms that are specific to a particular product category.
Maybe your customers call a particular product type a "box", while your catalogue calls it a "container". Or perhaps your company has internal terminology where "short", "STD", and "S" all refer to the same product variant.
A generic LLM cannot reliably know these relationships.
The same applies to matching criteria.
Imagine that a business has a 10% tolerance on dimensions. If a customer asks for a 10cm × 10cm product and the catalogue contains a 10cm × 11cm version, that might be considered an acceptable match for this particular business.
But there is nothing inherently correct about that assumption.
Another business might require an exact dimensional match.
The LLM cannot know which policy applies unless we tell it.
This is why our matching models don't actually consist of two elements.
They consist of three:
The domain knowledge is what allows us to encode things that a generic model simply cannot know.
For example, suppose we're matching products based on dimensions.
A customer might have a rule that says: "If the exact dimensions are not available, a CTS (cut-to-size) variant is also an acceptable match."
That single piece of domain knowledge changes how we should search.
If the input asks for a specific dimension, we now know that we should always search for at least two things:
The search agent doesn't have to independently figure out that CTS is relevant.
The domain knowledge tells it that this is a requirement.
Once those searches have completed, we can then make the matching decision using the results we found and the same domain knowledge.
Perhaps the exact-dimension product exists. Perhaps only the CTS variant exists. Perhaps both exist.
The correct decision depends on the business rules.
This distinction is important.
The search agent is responsible for finding candidates. Domain knowledge is responsible for defining what those candidates mean.
Domain knowledge doesn't necessarily mean writing hundreds of deterministic rules.
Some knowledge can be very specific: "CTS is an acceptable substitute when the exact dimensions are unavailable."
Other knowledge can be more general: "Dimensions are important for this product category, but color is not."
Or: "Prefer the same manufacturer, but a different manufacturer is acceptable if no exact match exists."
This gives the matching model additional context without turning it into a giant collection of hard-coded business logic.
And again, this is where LLMs are surprisingly useful.
We don't need to explicitly encode every possible interpretation of every product description. The model can still use its general understanding of language and the data.
We only need to provide the knowledge that is specific to this business and cannot reasonably be inferred.
That combination is what makes the system useful:
This post has become rather long and rather non-technical :) so I’ll end it here. There are endless interesting questions that arise from the way we use LLMs to solve these types of business problems. This has implications for how we can, or cannot, calculate the “confidence” of a match, and even for what confidence actually means in this context.
But we’ll keep that for another time.
Discover the impact of AI on your enterprise. We're here to help you get started.