Building Millisecond-Level Identity Resolution for AI Agents with M|BOX and Python

Build a deterministic CRM identity resolution tool for OpenAI Agents with the M|BOX Python SDK, balancing fault tolerance, scoring, and anti-hallucination.
Based on a YouTube tutorial, this article fully reproduces how to build a deterministic customer identity resolution tool for an OpenAI Agent using the M|BOX Python SDK. The core idea is to encapsulate identity matching into a plain Python function resolveCustomer, with M|BOX building an index on four fields—first name, last name, company, city—and performing field-level scoring, while the LLM only handles conversation and follow-up questions. The tutorial demonstrates indexing 10,000 records in under 0.2 seconds, single queries in just milliseconds, and Agent-workflow-friendly designs such as excluding unknown fields via empty strings, returning multiple candidates, and auto-converting docstrings to JSON schema. Three real-world scenarios validate matching despite spelling differences, the Agent proactively asking follow-ups on record conflicts, and returning zero candidates to refuse hallucination when no such person exists. The conclusion is separation of responsibilities: deterministic tools handle matching, conversational models handle guidance.
Large language models are great at understanding user intent, but they have never seen your internal CRM system. When a caller says "I'm Elizabeth Turner from Orion," a standard LLM can only guess who she is. Based on a YouTube tutorial, this article fully reproduces how to build a deterministic customer identity resolution tool for an OpenAI Agent using the M|BOX Python SDK—tolerating real-world typos and abbreviations, while tracking search performance, and enabling the Agent to proactively ask follow-up questions when records conflict and refuse to hallucinate when no match exists.
Why LLMs Need External Identity Resolution
The core capability of an LLM is language understanding, not precise data retrieval. Letting the model answer CRM queries "from memory" inevitably leads to hallucination or guesswork. The tutorial's approach: encapsulate identity resolution logic into a deterministic Python tool for the Agent to call. The model handles conversation and follow-up questions; M|BOX handles matching and scoring—clear separation of responsibilities.
This architecture must solve two hidden challenges. First, the dataset contains two completely different Robert Hayes—one at Vultra International and another at Lumio Solutions—indistinguishable by name alone. Second, Elizabeth Turner is spelled with a Z in the database, while callers often spell it with an S. The tutorial therefore requires searching across four fields: first name, last name, company, and city.
Deterministic Entity Resolution means that given a fixed input and fixed rules, the system always produces the same, reproducible results when matching identity records, without relying on probabilistic sampling or the randomness of approximate nearest neighbors. In contrast, LLM generation itself involves temperature sampling, and vector retrieval may return approximate rather than exact neighbors due to index structure. In the CRM identity resolution scenario, determinism means: the same set of name, company, and city inputs should yield the same candidate list and scores regardless of how many times it is called or in which session; when below a threshold, the system explicitly returns "no match" rather than "the most similar record." This property makes the Agent's downstream decisions (whether to ask follow-up questions, whether to reveal an email) auditable and testable, and allows field-level scores to be written directly into prompts as hard conditions.
Building the Index: 10,000 Records in Under 0.2 Seconds
The tutorial loads 10,000 customer records with fields including customer ID, name, company, city, and contact information. It uses tableindexer.createindex to build an index on these four fields and time.perfcounter to record elapsed time. The result: M|BOX indexes all 10,000 records in less than one-fifth of a second—what the tutorial calls "sub-millisecond indexing," meaning adding deterministic entity resolution to an Agent pipeline adds virtually no startup overhead.
Calling index.describe reveals how M|BOX generates a schema for each column: single-word attributes like first name, last name, and city become term fields; multi-word names like company become phrase fields; and customer ID and email are classified as non-searchable—they cannot be queried directly but are returned with every match result.

Term fields and phrase fields are two basic classifications for text columns in the M|BOX index schema. Term fields target single tokens (e.g., first name, last name, city), comparing token by token during matching, which is suitable for tolerating spelling differences and abbreviations; phrase fields target multi-word names (e.g., company), considering word order and overall phrase structure during matching to avoid incorrectly joining generic words like "International" and "Solutions" across companies. Customer ID and email are classified as non-searchable fields because, as identifiers, they have no semantic value for fuzzy matching, but they are returned as payload with candidate records for the Agent to display or use in subsequent operations. Understanding this field-type distinction helps when designing an index to decide which columns participate in scoring and which are merely passed through.
Writing the resolveCustomer Tool Function
The tutorial defines a plain Python function resolveCustomer that accepts four parameters—first name, last name, company, and city—internally calls index.match to fetch the top three candidates with their field-level scores, and records millisecond-level search time. The function returns each candidate's customer ID, name, company, city, email, total score, and field-level breakdown.
This function has several designs tailored to the Agent workflow. Type hints and docstrings are functional code—the Agent SDK parses them directly to build the tool definition. Passing an empty string means the field is unknown, and M|BOX excludes it from total score calculation, so a caller who doesn't provide a city isn't penalized. Returning up to three candidates rather than one gives the Agent a basis for judgment when two records are tied. Field-level scores replace opaque vector similarity, letting the model clearly know which attributes match and which have typos.
First, test directly with misspelled data: search for Catherine Vos at Kestrel Manufacturing, with both the last name and company name intentionally misspelled, and city left empty. Within milliseconds it matches Kestrel, with Catherine Vos scoring 76 overall and a clear breakdown: first name a perfect 100, company still 90 despite a missing letter, last name only 39 because a three-letter word is missing one letter, and city counted as 0 but not penalized for being empty.

Field-level Scoring means the match result provides not only a total score but also similarity scores for each field such as first name, last name, company, and city. Compared with overall vector similarity, field-level scoring breaks down "why it matched" into readable components: for example, the last name scores only 39 because a three-letter word is missing one letter, the company name still scores 90 despite a missing letter, and the city counts as 0 but doesn't reduce the total when not provided. This granularity lets the Agent determine whether a conflict stems from spelling noise or genuine ambiguity, and lets human debuggers locate which field is dragging down the score. In prompts, field breakdowns can also serve as conditions—for example, requiring "must ask follow-up questions when the last name score is below 50."
Handing the Tool to the Agent: Setting Safety Guardrails with Prompts
The tutorial wraps resolveCustomer using the SDK's function tool, and the SDK automatically generates a complete JSON schema from the docstring—each parameter becomes a string property, with descriptions taken directly from the docstring's args block. This is exactly why the model knows to "pass an empty string for unmentioned information."
The Agent's system instructions require: always call resolveCustomer, never guess a customer ID, and pause and ask for more details when the top score is below 80 or the gap between two candidates is less than 10 points. The model's temperature is set to 0 to ensure deterministic reasoning. The tutorial emphasizes that matching rules are written in natural language in the prompt, identity resolution logic runs deterministically in M|BOX, and the model decides what to ask and how to converse—but never decides whether a record matches.

Validation Across Three Real-World Scenarios
Spelling difference scenario: Ask about Elizabeth Turner at Orion Systems (with an S spelling) in Frankfurt. The Agent calls the tool with Elizabeth, Turner, Orion, Frankfurt and successfully matches Elizabeth Turner, spelled with a Z in the database, customer ID c17505, score 92. M|BOX search takes about 3 milliseconds, while the entire Agent run takes several seconds—almost all the time spent waiting for the model to generate tokens.
Record conflict scenario: Ask for Robert Hayes's email without providing a company or city. Both Robert Hayes records return with a score of 100. The Agent follows the guardrail instructions, withholds the email, explains that one is at Vultra International and one at Lumio Solutions, and requests clarification. The user then adds "he works at Lumio" and passes result.to_input_list() to preserve conversation context; the search narrows to a single candidate, customer ID C18851, with a score of 97—since Lumio is only part of Lumio Solutions—and only then does the Agent reveal the email. The tutorial notes that guessing here could leak one customer's privacy to another.

No-such-person scenario: Ask about Laura Chen at Brightwave Corporation. The tool returns zero candidates, and the Agent doesn't hallucinate but explicitly reports that Laura Chen is not in the CRM. Brightwave is indeed a real company in the CRM, but there is no one named Laura Chen, and no record scores high enough. The tutorial specifically contrasts vector nearest-neighbor search—which always returns something—whereas M|BOX never forces a match when the data isn't close enough, and this deterministic honesty carries directly through to the Agent.
Architectural Insights
The value of this approach isn't in a particular API call but in the division of responsibilities: M|BOX handles deterministic matching, and the model handles conversational guidance. Field-level scoring provides explainability, so the Agent gets not just records but also the reasoning for "why it matched"; and mathematical rules like score-gap thresholds can be written directly into prompts to become hard guardrails for the Agent. For any scenario requiring internal data to be connected to an Agent, this combination of "deterministic tools + conversational model" is worth emulating.
Related articles

How Crashlytics Eliminates Android Memory and Responsiveness Issues
Through the Chroma Clip case study, this article explains how Firebase Crashlytics uses Application Exit Info, Profiling Manager, GWP ASAN, and ARM MTE to pinpoint ANRs, OOMs, memory limit terminations, and native memory corruption—solving the challenge of stack traces that don't point to the real culprit.

Googlebook's First Developer Case Studies: How Notability and Logitech Built Desktop-Class Android Experiences
Notability and Logitech share first-hand experience building for Googlebook: using Jetpack Compose, Navigation 3, and Jetpack Ink for desktop-class Android, plus how the peripheral customization API bridges hardware and OS.

Google Play Multi-Device Publishing Guide: Modular Architecture and Review Pitfalls
Complete guide to Google Play multi-device publishing: why modular App Bundles beat a single bundle, track exceptions for Wear OS and Android Auto, how three-tier testing channels pre-validate reviews, and common rejection reasons for TV, XR, cars, and Wear OS with avoidance tips.