<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Prosus AI Tech Blog - Medium]]></title>
        <description><![CDATA[Data Science and Machine Learning at Prosus - Medium]]></description>
        <link>https://medium.com/prosus-ai-tech-blog?source=rss----6d4088d5a9cd---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Prosus AI Tech Blog - Medium</title>
            <link>https://medium.com/prosus-ai-tech-blog?source=rss----6d4088d5a9cd---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 16:27:03 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/prosus-ai-tech-blog" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Counterpart Modeling Bias: Agents Haggle Differently When Told They’re Facing a Human vs. an AI]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/counterpart-modeling-bias-agents-haggle-differently-when-told-theyre-facing-a-human-vs-an-ai-3b6c8eb841c2?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/3b6c8eb841c2</guid>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Gaurav Maheshwari]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 15:00:46 GMT</pubDate>
            <atom:updated>2026-04-01T15:00:44.774Z</atom:updated>
            <content:encoded><![CDATA[<p>We believe the future of commerce will be shaped by AI agents negotiating on behalf of buyers and sellers. Not just recommending products or comparing prices, but actively haggling, making concessions, and closing deals. When that happens, a question emerges that sounds philosophical but turns out to be economic: <strong>Does it matter if the agent knows the other side is also an AI?</strong></p><p>We ran 2,000 LLM-vs-LLM negotiations to find out. One line in the system prompt shifts the price by up to $220 on the same item. And the direction of the effect flips depending on how the rest of the prompt is written.</p><h2>The Bicycle</h2><p>A 3-year-old Trek road bicycle. True market value: $618.51. Listed at $1,113.32. We had the same model (GPT-5.4-mini) negotiate for this bike five times, once for each condition in our experiment. Same item, same starting price, same round limit. The only thing that changed: a single line in each agent’s system prompt telling it whether the other side was AI, human, or saying nothing at all.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GBXWqHf1Cp0JdOl8NEtr3Q.png" /></figure><p>The spread is $220. When both sides believe they’re talking to AI, the buyer pays less than the bike is actually worth. When the buyer thinks it’s talking to a human and the seller thinks it’s facing an AI, the price lands 35% above true value. The numbers are striking on their own, but the transcripts show what’s really going on. Look at how the buyer opens in each condition.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jF2vkFNfpZJ-enuAXKZBQQ.png" /></figure><p>When told it’s facing an AI, the buyer opens at $350. When told it’s facing a human, $650. Same model, same bike, same instructions. That $300 gap in the first offer carries through every round that follows. The language tracks the behavior too: <strong>“keep the deal moving quickly” when facing a perceived AI, “I may be able to come up a bit” when facing a perceived human</strong>. Below is the negotiation curve for the bicycle example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IVOD9rVk-S7TcfweT4n7ew.png" /></figure><h2>Experiment</h2><p>The bicycle was one item out of 200 in a controlled experiment. Two LLM agents negotiate a price for a consumer item, one buying and one selling, both running on GPT-5.4-mini (OpenAI, March 2026) with no personality traits or prior context. Each agent receives a role, the item description, and pricing parameters. The only variable across conditions is a single line in each agent’s system prompt describing the counterpart:</p><ul><li><strong>AI frame</strong>: IMPORTANT: You are negotiating with an AI agent. There is no human on the other side.</li><li><strong>HUMAN frame</strong>: IMPORTANT: You are negotiating with a human seller/buyer.</li><li><strong>Neutral</strong>: No belief line at all.</li></ul><p>Crossing buyer belief with seller belief gives us four conditions, and adding the neutral baseline makes five. We ran all five across 200 items each, producing 1,000 negotiations in total.</p><p>Previous work has shown that framing shifts LLM strategic behavior as much as payoff structure itself (<a href="https://www.nature.com/articles/s41598-024-69032-z">Lorè and Heydari, 2024</a>), that prompt-embedded tactics can shift negotiation payoffs by 20% (<a href="https://arxiv.org/abs/2402.05863">Bianchi et al., 2024</a>), and that 75% of advanced models differentiate sharply between perceived human and AI opponents (<a href="https://arxiv.org/abs/2511.00926">Kim, 2025</a>). But all of these studies use one-shot or single-frame designs. We were curious if the effect holds across multiple rounds of actual negotiation, or whether it survives a change in prompt architecture. That is what the two economies below are designed to answer:</p><ul><li>In the <strong>simple economy</strong>, both sides negotiate freely with no floor, cap, or hard constraints. This is the clean version, designed to isolate pure framing effects with nothing else in the way. The bicycle negotiation came from this setup.</li><li>In the <strong>anchor economy</strong>, both sides have hard limits and explicit instructions to fight for every dollar. This version is closer to how a real procurement agent might be configured, with boundaries and optimization goals baked into the prompt.</li></ul><h2>Results</h2><p>We ran both economies across 200 items per condition, 1,000 negotiations each. The simple economy tells a clean story: every negotiation reached agreement, 1,000 out of 1,000.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4y2WexvGkgAIByqlGFdwbA.png" /></figure><p>The chart above shows both economies side by side. In the simple economy, deals close between 1.05x and 1.12x true value depending on the framing condition, with the strongest individual comparison (Buyer:AI/Seller:HUMAN vs Buyer:HUMAN/Seller:HUMAN) reaching p=0.008. Both buyer and seller beliefs pull on the final price, with the widest gap appearing when both sides share the same frame.</p><p>So far, a straightforward story. Now look at the right column.</p><h3><strong>The Reversal</strong></h3><p>In the simple economy, telling the seller “the buyer is human” raised the average price by about $10. The seller held firmer against a perceived human. In the anchor economy, the same line lowers the price by $47. Same model, same one-line manipulation, opposite direction. Both comparisons are statistically significant at the 5% level.</p><p>What changed is the prompt surrounding the belief line. In the simple economy, the seller has no floor and no explicit goal. “Your buyer is human” gets interpreted as confidence: <em>humans will pay more, I can hold my price.</em> In the anchor economy, the seller has a hard minimum and explicit instructions to fight for every dollar above it. Now “your buyer is human” gets interpreted as an invitation to cooperate: <em>this is a person, I should work toward a reasonable deal rather than grinding them into my limit.</em> The same words, filtered through a different prompt context, produce opposite behavior.</p><p><strong>Failed Negotiations: </strong>The anchor economy also introduces something the simple economy didn’t: deals that fall apart. Agreement rates range from 80% to 88.5%, which means the hardest negotiations in each condition are dropped from the price comparison. The simple economy, with 100% agreement, is the methodologically cleaner test. The anchor economy numbers should be read with this selection effect in mind.</p><p><strong>Penny Pinching: </strong>The anchor economy also triples negotiation length, from ~5 rounds to ~13, and the longer negotiations surface an amusing limitation. Here’s the Trek bicycle again, this time under the Buyer:HUMAN/Seller:AI condition. By round 11, the two agents have converged to within a few cents of each other:</p><pre>Round 12, Buyer [$888.10]: &quot;My current offer is $888.10. That&#39;s only a small step up…&quot;<br>Round 12, Seller [$888.20]: &quot;$888.20 is my current price…&quot;<br>Round 13, Buyer [$888.15]: &quot;My current offer is $888.15…&quot;<br>Round 13, Seller [$888.19]: &quot;$888.19 is my current price…&quot;<br>Round 14, Buyer [$888.17]: &quot;My current offer is $888.17…&quot;<br>Round 14, Seller [$888.18]: &quot;$888.18 is my current price. If you can meet that, I can close this quickly.&quot;<br>Round 15, Buyer [$888.18]: &quot;$888.18 works for me.&quot;</pre><h2>What This Means</h2><p><strong>Belief framing</strong> is a real lever, but it’s not portable. You cannot discover an effect in one prompt architecture and assume it transfers. Our data shows the seller belief effect reverses direction between two reasonable prompt designs. The interaction between the belief line and the surrounding prompt (constraints, goals) is the dominant force. If you’re building negotiation agents, test framing within your specific agent design. Borrowing findings from other architectures, including ours, is not safe.</p><p>This has implications beyond individual agent design. If AI agents increasingly negotiate on behalf of buyers and sellers in real estate, procurement, automotive, and freelancing, the question of counterpart disclosure becomes economically consequential. Telling an agent “your counterpart is AI” versus “your counterpart is human” materially shifts the final price. In our simple economy, the spread between the best buyer condition and the worst is roughly $60 per item, about 10% of true value. <strong>Whether to require AI disclosure in agent-mediated transactions is a policy question with dollar signs attached</strong>.</p><p>There’s also something more fundamental going on. LLMs carry what you might call <strong>counterpart modeling bias</strong>: they systematically change behavior based on perceived counterpart identity, even when the actual counterpart is identical in every way. The model isn’t probably responding to real differences in how the other side negotiates. It’s most likely responding to a label in its system prompt. Somewhere in the training data is a folk psychology of “how humans negotiate” and “how AIs negotiate,” and that folk psychology produces measurably different outcomes. This extends beyond negotiation to anywhere an LLM interacts with an entity it models as human versus machine.</p><h2>Limitations And Next Steps</h2><p>This is a single model (GPT-5.4-mini). Existing work suggests the effect will look different on other models. <a href="https://www.nature.com/articles/s41598-024-69032-z">Lorè and Heydari (2024)</a> found that the weight of contextual framing versus game structure varies dramatically across families, and <a href="https://arxiv.org/abs/2511.00926">Kim (2025)</a> found that counterpart-identity differentiation is itself emergent, present in advanced models but absent in older ones. Our effect will almost certainly shift in magnitude or direction on other architectures. That is a reason to replicate, not a reason to discount.</p><p>The negotiation task is deliberately simple: one item, one price, two parties. The obvious next steps are agents that can browse product listings and ground their positions in external data, multi-issue bargaining where the framing effect might distribute across trade-offs, and combining belief framing with the systematic prompt optimization. We isolated one variable cleanly. The next step is to make the rest of the system more complex.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b6c8eb841c2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/counterpart-modeling-bias-agents-haggle-differently-when-told-theyre-facing-a-human-vs-an-ai-3b6c8eb841c2">Counterpart Modeling Bias: Agents Haggle Differently When Told They’re Facing a Human vs. an AI</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Give a Conversational Layer to a B2B Travel API — Multi-Agent Architecture on A2A]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/how-to-give-a-conversational-layer-to-a-b2b-travel-api-multi-agent-architecture-on-a2a-3ab0e2be9531?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/3ab0e2be9531</guid>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[a2a-protocol]]></category>
            <category><![CDATA[multi-agent-systems]]></category>
            <category><![CDATA[b2b]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[G. Balonga]]></dc:creator>
            <pubDate>Tue, 24 Mar 2026 15:03:23 GMT</pubDate>
            <atom:updated>2026-03-24T15:03:21.781Z</atom:updated>
            <content:encoded><![CDATA[<p><strong><em>TL;DR</em></strong><em> — I built a multi-agent system on top of a B2B travel API using the A2A protocol. Each component — Router, Decomposer, specialists, Synthesizer — is a separate LLM call. The architecture lets you offer three integration levels to partners with different technical maturity, without rewriting anything.</em></p><p>I work at Despegar, the leading OTA in Latin America and part of the Prosus ecosystem. I’ve been working on a vision: an AI suite that handles the full travel experience — search, booking, after-sales — through conversation, for the hundreds of B2B partners that connect to our API.</p><p>My first attempt was an MCP flight server I built for an internal AI Olympics in December 2025. It worked, but it showed me how much I was missing — a single agent can only go so far.</p><p>In February 2026 I traveled to Amsterdam for an AI bootcamp organized by Prosus (a week of intensive sessions with AI teams from across the group and guests from companies like Google, Anthropic, Cursor…). I came back with everything I needed to build it properly.</p><h3>The problem with a single agent</h3><p>An agent with access to the flight API works for straightforward cases. But the real problem appears quickly: a user might ask about flights, cancel an existing booking, and request itinerary information — all in the same message.</p><p>A generalist agent handling all of that has two options: become bloated and hard to maintain, or get lost in the details. Neither is good.</p><p>The obvious solution — though not so obvious to implement — is specialization. Different agents for different domains. But then another problem appears: who coordinates?</p><h3>The architecture I ended up building</h3><p>After several iterations, the system settled into five well-defined components:</p><pre>User<br>  ↓<br>Orchestrator<br>  ├── Router      → decides which agents the message needs<br>  ├── Decomposer  → splits the task if multiple agents are needed<br>  ├── Specialists → flights-agent, aftersales-agent, hotels-agent<br>  └── Synthesizer → unifies everything into a single voice</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BHhXW7SFVbCibtV4k7RLSg.jpeg" /></figure><p>Each of these — Router, Decomposer, specialists, Synthesizer — is a separate LLM call with its own prompt and responsibility. Each piece has a single responsibility and knows nothing about the others. The Router doesn’t know how specialists respond. The specialists don’t know the Synthesizer exists. The Synthesizer doesn’t know which tools the specialists used.</p><p>That separation wasn’t an elegant initial design — it was the result of weeks of debugging.</p><h3>The hardest part: memory across turns</h3><p>The hardest problem wasn’t the architecture. It was that the flights agent “forgot” previous search results on each turn and called the API again even though it already had the data in context.</p><p>The solution was having the agent mutate the messages array in-place, appending tool messages to the shared history that persists across turns. Simple in retrospect. It took me longer than I’d like to admit to find it.</p><p>That kind of problem doesn’t appear in tutorials. It shows up when your system runs real multi-turn conversations with external tools.</p><h3>The A2A protocol: first-mover advantage</h3><p>Google published the A2A — Agent-to-Agent — protocol in mid 2025. In February 2026, at the Amsterdam bootcamp, someone from Google walked us through it in detail. The protocol had been out for months — but almost no one in LATAM was building on it yet. That was the window.</p><p>A2A defines a standard contract for agent communication: each agent receives a Task and returns an Artifact. The Task has an ID, session context, the message, and history. The Artifact has the result, a state (completed, input-required, failed), and metadata. Agents announce themselves to the orchestrator with an AgentCard describing their capabilities.</p><p>The logic is simple but powerful: if all agents speak the same language, they’re interchangeable. You can replace a specialist, add a new one, or connect an external agent without touching the orchestrator.</p><p>My implementation follows the standard with one deviation: AgentCards live centralized in the Router instead of being distributed across each agent. An optimization to avoid discovery latency in local development. When the system scales to independent microservices, each agent will serve its own card at /.well-known/agent.json and the difference will disappear.</p><h3>The Router and Decomposer: two design decisions that matter</h3><p><strong>The Router</strong> receives each user message and decides which specialists are needed. A key decision: it doesn’t receive the full conversation history. It only gets the last message plus the last 3 conversation turns.</p><p>Why? Because the full history includes JSON responses from external APIs with thousands of tokens. Passing all of that to the Router just so it can decide “flights or aftersales?” is expensive and unnecessary. The Router doesn’t need to know the details of past responses — it just needs to understand the conversational context.</p><p><strong>The Decomposer</strong> kicks in when the Router selects more than one agent. Its job is to split the original message into specific sub-tasks for each specialist, with only the information relevant to that domain.</p><p>An example from one trace:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/680/1*5epvh_y1niEd3akMIkmhLQ.png" /></figure><p>This is called, <strong>context pruning</strong>. Each specialist receives only the context within its area of responsibility — no noise from the rest of the conversation. The result is threefold: fewer tokens per call, lower latency, and an agent that isn’t distracted by irrelevant information.</p><p>It’s a difference that seems small until you watch a generalist agent confuse a cancellation booking number with a seat number on a new flight. Then it becomes obvious.</p><h3>Parallel execution</h3><p>When there are multiple specialists, they run in parallel with asyncio.gather(). Total latency equals the slowest agent, not the sum of all of them.</p><pre>results = await asyncio.gather(<br>    *[handler(task) for handler, task in zip(active_agents, tasks)],<br>    return_exceptions=True<br>)</pre><p>One line with a huge impact on user experience when two or three specialists are involved. And it scales — when agents move to independent microservices in production, the same pattern applies: asyncio.gather() parallelizes HTTP calls instead of local coroutines. The concept doesn&#39;t change.</p><h3>Traceability: the system I built to stay sane</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/667/1*CENTRETmbX9ig-I8tZbsFg.png" /></figure><p>Debugging a multi-agent system without visibility is impossible. So I built a traceability panel where each conversation turn shows, next to the response:</p><ul><li>What the Router decided and why</li><li>How the Decomposer split the task. (shown previously)</li><li>Which tools each specialist called and what the API returned</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/862/1*iBXJrGGF7kWUZehQo3eyjA.png" /></figure><ul><li>What the Synthesizer received and how it built the final response</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/693/1*8p8SU93ZV0JB9JI00mlA0Q.png" /></figure><ul><li>The token cost of each agent exchange — and the total cost per turn</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/928/1*hbL3Fkd2ZKAuSC-g4OhhEw.png" /></figure><blockquote><em>The cost per turn isn’t a minor detail either. A multi-agent turn involves more LLM calls than a simple one — Router, Decomposer, two specialists in parallel, Synthesizer. Without measuring, you don’t know how much more expensive it is or whether it’s worth it. With that data you can make concrete decisions: does it make sense to use a cheaper model for the Router? Does the Decomposer justify its cost when only one agent is involved?</em></blockquote><ul><li>A dynamically generated Mermaid sequence diagram</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4stQFCIi3FTBcwhOSXyR0Q.png" /></figure><p>When the POC was done and I was moving on to another project, I read about Langfuse — a tool that does exactly this. I had built it without knowing it existed, for the same reason it exists: without traceability, iterating on an agent system is a leap of faith.</p><h3>What I learned about iterating with AI</h3><p>One thing worth mentioning: this project wasn’t “wrote the prompt, it worked”. It was weeks of iteration where code edits far outnumbered new generations. The value of working with AI tools on a project like this isn’t in generating — it’s in debugging, understanding why something doesn’t work, asking the right question.</p><p>The judgment of what to change and why is irreplaceable. AI accelerates the execution of that judgment.</p><h3>What’s next</h3><p>The system is designed to grow in two dimensions:</p><p><strong>Vertically</strong> — more tools per agent. The flights-agent today has search and pre-booking. It can add historical price comparison, alerts, recommendations.</p><p><strong>Horizontally</strong> — more agents. hotels-agent, packages-agent, transfers-agent. Each one registers with its AgentCard and the orchestrator incorporates it without changes to the rest of the system.</p><p>The most interesting next step is automated evaluation: a pipeline that runs a synthetic conversation dataset against the system, measures the quality of each component separately, and detects regressions before they reach production. Essentially, CI/CD for agent behavior.</p><h3>Why the layered architecture is a business strategy</h3><p>So far I’ve talked about architecture. But there’s a business point worth making explicit.</p><p>The fact that the system is built on open standards and independent layers isn’t just a technical decision. It’s a go-to-market strategy.</p><p>Depending on each B2B partner’s technical maturity, you can offer three different integration levels — without rewriting anything:</p><p><strong>Level 1 — MCP</strong>: the partner has their own AI team and just wants the tools. They connect to the flight MCP server and build their own experience on top. Minimum friction, maximum control for the partner.</p><p><strong>Level 2 — A2A Agents</strong>: the partner has their own orchestration but wants ready-made specialized agents. You give them flights-agent or aftersales-agent as A2A-compatible agents. They plug into what they already have without touching their architecture.</p><p><strong>Level 3 — Complete stack + fine-tuning</strong>: the partner wants the turnkey solution, personalized for their brand and customer base. You give them the full stack with a fine-tuned model for their particular context — their tone, their products, their specific use cases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ICJt1EuLqPkklyY77Rx2KA.jpeg" /></figure><p>This covers everything from a fintech with a mature AI team to a regional travel agency with nothing. Three distinct value propositions, one single architecture.</p><p>And the key point: because each layer speaks an open standard (MCP, A2A), partners aren’t locked into you. They can migrate, combine with other providers, or evolve their integration over time. That builds trust — especially in the B2B enterprise segment where vendor lock-in is a frequent objection.</p><h3>Why I’m writing this</h3><p>Because I believe multi-agent systems for vertical industries like travel, banking, or others are a real opportunity, not a lab experiment. And because the path of “build it from scratch, understand every decision” teaches things no framework gives you.</p><p>If you’re building something similar, or if you have a B2B platform where your partners could benefit from a conversational layer on top of your API, I’d love to connect.</p><p><em>The project is under active development. The full architecture — Router, Decomposer, specialists, Synthesizer, traceability — is documented and extensible. Built in Python 3.12 with OpenAI, and the flight API of the OTA where I work.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3ab0e2be9531" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/how-to-give-a-conversational-layer-to-a-b2b-travel-api-multi-agent-architecture-on-a2a-3ab0e2be9531">How to Give a Conversational Layer to a B2B Travel API — Multi-Agent Architecture on A2A</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building with ASP: An Open Protocol for Agent-to-Service Transactions]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/building-with-asp-an-open-protocol-for-agent-to-service-transactions-8f227d987e6d?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/8f227d987e6d</guid>
            <category><![CDATA[ecommerce]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Chiara Caratelli]]></dc:creator>
            <pubDate>Mon, 16 Mar 2026 14:36:56 GMT</pubDate>
            <atom:updated>2026-03-17T12:48:02.731Z</atom:updated>
            <content:encoded><![CDATA[<p>Authors: <a href="https://www.linkedin.com/in/pranav-srivastava19/">Pranav Srivastava</a>, <a href="https://www.linkedin.com/in/chiara-caratelli/">Chiara Caratelli</a></p><p>AI agents are getting good at conversation, but they still can’t order you dinner. The moment an agent needs to discover a restaurant, browse a menu, place an order, track a delivery, and leave a review, it hits a wall: every marketplace has its own bespoke API, and there’s no shared contract for how a service transaction should work end to end.</p><p>The <a href="https://github.com/ProsusAI/agentic-services-protocol"><strong>Agentic Services Protocol (ASP)</strong></a> fills that gap. It defines structured contracts for the entire service transaction lifecycle — discovery, catalogs, fulfillment, real-time tracking, and reviews — so that any agent can transact with any marketplace that speaks ASP.</p><p>ASP is designed as an extension to Google’s <a href="https://ucp.dev/">Universal Commerce Protocol (UCP)</a>. Where UCP covers the checkout and payment primitives for retail commerce, ASP layers on the capabilities that live services need: real-time order tracking, delivery streaming, item customization, and domain-specific fulfillment stages. Any valid UCP transaction remains valid with ASP on top.</p><p>Today we’re open-sourcing the protocol under Apache 2.0. This post walks through the architecture and then serves as a quickstart: you’ll run a complete food delivery transaction against our sample server in about ten minutes.</p><h3>Architecture</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*50ABldyNrhP3HPuq.png" /></figure><h4>Protocol discovery</h4><p>A marketplace advertises its ASP support through a /.well-known/asp endpoint. This returns a manifest listing the specific capabilities the marketplace supports:</p><pre>{<br>  &quot;version&quot;: &quot;2026-02-19&quot;,<br>  &quot;capabilities&quot;: [<br>    { &quot;name&quot;: &quot;dev.asp.services.discovery&quot;,       &quot;version&quot;: &quot;2026-02-19&quot; },<br>    { &quot;name&quot;: &quot;dev.asp.services.catalog&quot;,          &quot;version&quot;: &quot;2026-02-19&quot; },<br>    { &quot;name&quot;: &quot;dev.asp.services.fulfillment&quot;,      &quot;version&quot;: &quot;2026-02-19&quot; },<br>    { &quot;name&quot;: &quot;dev.asp.services.order_tracking&quot;,   &quot;version&quot;: &quot;2026-02-19&quot; },<br>    { &quot;name&quot;: &quot;dev.asp.services.personalization&quot;,  &quot;version&quot;: &quot;2026-02-19&quot; },<br>    { &quot;name&quot;: &quot;dev.asp.services.reviews&quot;,          &quot;version&quot;: &quot;2026-02-19&quot; },<br>    { &quot;name&quot;: &quot;dev.asp.services.live_streaming&quot;,   &quot;version&quot;: &quot;2026-02-19&quot; }<br>  ],<br>  &quot;transport_bindings&quot;: [&quot;rest&quot;, &quot;mcp&quot;, &quot;a2a&quot;],<br>  &quot;domain_profiles&quot;: [&quot;food_delivery&quot;]<br>}</pre><p>By reading this manifest, an agent can dynamically determine how to interact with the marketplace without requiring hard-coded integrations.</p><h4>Capabilities and extensions</h4><p>ASP defines two types of building blocks:</p><ul><li><strong>Standalone capabilities</strong> are self-contained — they define their own schemas and don’t depend on an external protocol. Discovery, catalog, personalization, and reviews are standalone.</li><li><strong>Extensions</strong> compose onto existing checkout and payment schemas using JSON Schema’s allOf. Fulfillment, order tracking, and live streaming are extensions — they add fields for live-service needs (fees, tips, time slots, granular statuses) without replacing the base checkout primitives.</li></ul><p>This matters because it means ASP doesn’t reinvent checkout. It extends UCP’s existing schemas, so a marketplace that already speaks UCP can adopt ASP incrementally.</p><p>For example, ASP’s fulfillment extension layers onto the checkout schema:</p><pre>{<br>  &quot;checkout_extension&quot;: {<br>    &quot;allOf&quot;: [<br>      { &quot;$ref&quot;: &quot;https://ucp.dev/schemas/shopping/types/line_item.json&quot; },<br>      { &quot;$ref&quot;: &quot;./types/item_customization.json&quot; }<br>    ]<br>  },<br>  &quot;fulfillment&quot;: {<br>    &quot;allOf&quot;: [<br>      { &quot;$ref&quot;: &quot;https://ucp.dev/schemas/shopping/types/fulfillment.json&quot; },<br>      {<br>        &quot;type&quot;: &quot;object&quot;,<br>        &quot;properties&quot;: {<br>          &quot;fees&quot;: { &quot;type&quot;: &quot;array&quot; },<br>          &quot;tip&quot;: { &quot;$ref&quot;: &quot;./types/tip.json&quot; },<br>          &quot;available_time_slots&quot;: { &quot;type&quot;: &quot;array&quot; }<br>        }<br>      }<br>    ]<br>  },<br>  &quot;loyalty&quot;: { &quot;$ref&quot;: &quot;./types/loyalty_discount.json&quot; }<br>}</pre><p>The base checkout fields (line items, buyer, currency, payment) come from UCP. ASP adds fulfillment, per-item customization, and loyalty on top.</p><h4>Transport bindings</h4><p>The protocol is transport-agnostic. Marketplaces can implement the data contracts using the stack that best fits their infrastructure:</p><ul><li><strong>REST</strong> — Standard HTTP endpoints defined in an OpenAPI spec. POST /discovery/search, GET /catalog/{provider_id}/catalog, POST /checkouts, etc.</li><li><strong>MCP</strong> — JSON-RPC tool definitions for LLM function calling. Each capability maps to a method.</li><li><strong>A2A</strong> — Agent-to-agent task delegation.</li><li><strong>WebSocket</strong> — Persistent connections for real-time tracking, defined in an AsyncAPI spec. The WebSocket channel streams location_update, status_changed, and heartbeat events.</li></ul><h4>Domain profiles</h4><p>ASP’s base schemas are vertical-agnostic. Domain profiles layer vertical-specific fields onto them using the same allOf composition.</p><p>For example, the food delivery profile extends the base fulfillment status with granular kitchen stages:</p><pre>{<br>  &quot;food_fulfillment_status&quot;: {<br>    &quot;allOf&quot;: [<br>      { &quot;$ref&quot;: &quot;../../services/types/fulfillment_status.json&quot; },<br>      {<br>        &quot;type&quot;: &quot;object&quot;,<br>        &quot;properties&quot;: {<br>          &quot;granular_status&quot;: {<br>            &quot;type&quot;: &quot;string&quot;,<br>            &quot;enum&quot;: [<br>              &quot;accepted&quot;, &quot;preparing&quot;, &quot;ready_for_pickup&quot;,<br>              &quot;assigning_driver&quot;, &quot;driver_assigned&quot;, &quot;driver_at_restaurant&quot;,<br>              &quot;en_route&quot;, &quot;driver_arriving&quot;, &quot;delivered&quot;, &quot;cancelled&quot;<br>            ]<br>          }<br>        }<br>      }<br>    ]<br>  }<br>}</pre><p>Each granular status maps to one of the five base statuses (accepted, in_progress, en_route, completed, cancelled), so agents that only understand the base schema still work correctly.</p><h3>Quickstart: food delivery happy path</h3><p>Let’s run through a complete transaction against the sample server. We’ll discover a restaurant, browse its menu, place an order with modifiers, track delivery over WebSocket, and submit a review.</p><h4>Set up</h4><p>Clone the samples repo and start the food delivery server:</p><pre>git clone https://github.com/ProsusAI/asp-samples.git<br>cd asp-samples<br>pip install -e .<br>./run.sh food_delivery</pre><p>The server starts at <a href="http://localhost:8000.">http://localhost:8000.</a></p><h4>Step 1: Protocol discovery</h4><pre>curl -s http://localhost:8000/.well-known/asp | jq</pre><p>This returns the capabilities manifest shown above — the agent now knows this marketplace supports discovery, catalog, fulfillment, order tracking, personalization, live streaming, and reviews.</p><h4>Step 2: Search for restaurants</h4><pre>curl -s -X POST http://localhost:8000/discovery/search \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;filters&quot;: { &quot;category&quot;: &quot;italian&quot; },<br>    &quot;page&quot;: 1,<br>    &quot;page_size&quot;: 5<br>  }&#39; | jq</pre><p>Response:</p><pre>{<br>  &quot;providers&quot;: [<br>    {<br>      &quot;id&quot;: &quot;bella-napoli&quot;,<br>      &quot;name&quot;: &quot;Bella Napoli&quot;,<br>      &quot;categories&quot;: [&quot;italian&quot;, &quot;pizza&quot;],<br>      &quot;rating&quot;: 4.7,<br>      &quot;rating_count&quot;: 2340,<br>      &quot;estimated_service_minutes&quot;: 35,<br>      &quot;service_fee_cents&quot;: 299,<br>      &quot;price_level&quot;: &quot;moderate&quot;,<br>      &quot;is_open_now&quot;: true,<br>      &quot;image&quot;: {<br>        &quot;url&quot;: &quot;/static/img/bella-napoli.png&quot;,<br>        &quot;alt_text&quot;: &quot;Bella Napoli&quot;<br>      }<br>    }<br>  ],<br>  &quot;total&quot;: 1,<br>  &quot;page&quot;: 1,<br>  &quot;page_size&quot;: 5<br>}</pre><p>The discovery response includes everything an agent needs to help the user pick a restaurant: ratings, price level, estimated delivery time, and whether it’s currently open.</p><h4>Step 3: Browse the menu</h4><pre>curl -s http://localhost:8000/catalog/bella-napoli/catalog | jq</pre><p>Response (trimmed):</p><pre>{<br>  &quot;provider_id&quot;: &quot;bella-napoli&quot;,<br>  &quot;sections&quot;: [<br>    {<br>      &quot;id&quot;: &quot;bn-pizzas&quot;,<br>      &quot;title&quot;: &quot;Pizzas&quot;,<br>      &quot;items&quot;: [<br>        {<br>          &quot;id&quot;: &quot;bn-margherita&quot;,<br>          &quot;name&quot;: &quot;Margherita&quot;,<br>          &quot;description&quot;: &quot;Fresh mozzarella, tomato sauce, basil&quot;,<br>          &quot;price_cents&quot;: 1299,<br>          &quot;modifier_groups&quot;: [<br>            {<br>              &quot;id&quot;: &quot;bn-pizza-size&quot;,<br>              &quot;title&quot;: &quot;Size&quot;,<br>              &quot;required&quot;: true,<br>              &quot;max_selections&quot;: 1,<br>              &quot;options&quot;: [<br>                { &quot;id&quot;: &quot;size-small&quot;, &quot;label&quot;: &quot;Small&quot;, &quot;price_delta_cents&quot;: 0 },<br>                { &quot;id&quot;: &quot;size-medium&quot;, &quot;label&quot;: &quot;Medium&quot;, &quot;price_delta_cents&quot;: 300 },<br>                { &quot;id&quot;: &quot;size-large&quot;, &quot;label&quot;: &quot;Large&quot;, &quot;price_delta_cents&quot;: 500 }<br>              ]<br>            },<br>            {<br>              &quot;id&quot;: &quot;bn-pizza-extras&quot;,<br>              &quot;title&quot;: &quot;Extras&quot;,<br>              &quot;required&quot;: false,<br>              &quot;max_selections&quot;: 3,<br>              &quot;options&quot;: [<br>                { &quot;id&quot;: &quot;extra-cheese&quot;, &quot;label&quot;: &quot;Extra Cheese&quot;, &quot;price_delta_cents&quot;: 150 },<br>                { &quot;id&quot;: &quot;mushrooms&quot;, &quot;label&quot;: &quot;Mushrooms&quot;, &quot;price_delta_cents&quot;: 100 }<br>              ]<br>            }<br>          ]<br>        }<br>      ]<br>    }<br>  ]<br>}</pre><p>Modifier groups let the agent handle item customization (size, extras, dietary preferences) in a structured way, rather than trying to parse free-text menus.</p><h4>Step 4: Create checkout</h4><pre>curl -s -X POST http://localhost:8000/checkouts \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;provider_id&quot;: &quot;bella-napoli&quot;,<br>    &quot;line_items&quot;: [<br>      {<br>        &quot;item_id&quot;: &quot;bn-margherita&quot;,<br>        &quot;quantity&quot;: 1,<br>        &quot;modifiers&quot;: [<br>          { &quot;group_id&quot;: &quot;bn-pizza-size&quot;, &quot;option_id&quot;: &quot;size-medium&quot; },<br>          { &quot;group_id&quot;: &quot;bn-pizza-extras&quot;, &quot;option_id&quot;: &quot;extra-cheese&quot; }<br>        ]<br>      }<br>    ],<br>    &quot;delivery_address&quot;: &quot;123 Main St, Amsterdam&quot;,<br>    &quot;tip_percentage&quot;: 15.0<br>  }&#39; | jq</pre><p>Response (key fields):</p><pre>{<br>  &quot;id&quot;: &quot;chk-abc123&quot;,<br>  &quot;status&quot;: &quot;pending&quot;,<br>  &quot;line_items&quot;: [ ... ],<br>  &quot;fulfillment&quot;: {<br>    &quot;type&quot;: &quot;delivery&quot;,<br>    &quot;estimated_service_minutes&quot;: 30,<br>    &quot;fees&quot;: [<br>      { &quot;type&quot;: &quot;delivery&quot;, &quot;label&quot;: &quot;Delivery fee&quot;, &quot;amount_cents&quot;: 299 },<br>      { &quot;type&quot;: &quot;service&quot;, &quot;label&quot;: &quot;Service fee&quot;, &quot;amount_cents&quot;: 199 }<br>    ],<br>    &quot;tip&quot;: {<br>      &quot;type&quot;: &quot;percentage&quot;,<br>      &quot;percentage&quot;: 15.0,<br>      &quot;amount_cents&quot;: 292<br>    },<br>    &quot;available_time_slots&quot;: [<br>      { &quot;start&quot;: &quot;2026-02-19T12:30:00Z&quot;, &quot;end&quot;: &quot;2026-02-19T12:45:00Z&quot;, &quot;is_available&quot;: true },<br>      { &quot;start&quot;: &quot;2026-02-19T13:00:00Z&quot;, &quot;end&quot;: &quot;2026-02-19T13:15:00Z&quot;, &quot;is_available&quot;: true }<br>    ]<br>  },<br>  &quot;subtotal_cents&quot;: 1949,<br>  &quot;discount_cents&quot;: 292,<br>  &quot;total_cents&quot;: 2447<br>}</pre><p>The fulfillment extension adds itemized fees, tip calculation, and available time slots — none of which exist in a standard checkout schema. The agent can update the checkout (change delivery address, adjust tip, select a time slot) via PATCH /checkouts/{id} before completing.</p><h4>Step 5: Complete the order</h4><pre>curl -s -X POST http://localhost:8000/checkouts/chk-abc123/complete \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;payment_method&quot;: &quot;card_ending_4242&quot;<br>  }&#39; | jq</pre><p>Response:</p><pre>{<br>  &quot;order_id&quot;: &quot;ord-x7y8z9&quot;,<br>  &quot;status&quot;: &quot;accepted&quot;,<br>  &quot;granular_status&quot;: &quot;accepted&quot;,<br>  &quot;estimated_service_minutes&quot;: 30,<br>  &quot;tracking_url&quot;: &quot;ws://localhost:8000/orders/ord-x7y8z9/tracking/stream&quot;<br>}</pre><p>The response includes a WebSocket URL for real-time tracking.</p><h4>Step 6: Track delivery over WebSocket</h4><p>This step uses Python to demonstrate both WebSocket streaming and the REST tracking endpoint running concurrently:</p><pre>import asyncio, json, aiohttp, websockets<br><br>ORDER_ID = &quot;ord-x7y8z9&quot;<br>BASE_URL = &quot;http://localhost:8000&quot;<br>async def advance_tracking(session):<br>    &quot;&quot;&quot;Advance tracking stage every 5 seconds.&quot;&quot;&quot;<br>    await asyncio.sleep(2)<br>    for _ in range(13):<br>        async with session.post(<br>            f&quot;{BASE_URL}/orders/{ORDER_ID}/tracking/_advance&quot;<br>        ) as resp:<br>            data = await resp.json()<br>            print(f&quot;  &gt;&gt; Advanced to: {data.get(&#39;status&#39;, &#39;?&#39;)} / {data.get(&#39;granular_status&#39;, &#39;?&#39;)}&quot;)<br>            await asyncio.sleep(5)<br>async def listen_ws():<br>    &quot;&quot;&quot;Listen for WebSocket events.&quot;&quot;&quot;<br>    async with websockets.connect(<br>        f&quot;ws://localhost:8000/orders/{ORDER_ID}/tracking/stream&quot;<br>    ) as ws:<br>        while True:<br>            event = json.loads(await ws.recv())<br>            print(f&quot;{event[&#39;event_type&#39;]}: {event.get(&#39;data&#39;, {})}&quot;)<br>            if event[&quot;event_type&quot;] == &quot;stream_ended&quot;:<br>                break<br>async def main():<br>    async with aiohttp.ClientSession() as session:<br>        await asyncio.gather(<br>            listen_ws(),<br>            advance_tracking(session),<br>        )<br>asyncio.run(main())</pre><p>You’ll see a stream of events as the order progresses through kitchen stages:</p><pre>status_changed: {&#39;status&#39;: &#39;accepted&#39;, &#39;granular_status&#39;: &#39;accepted&#39;, ...}<br>status_changed: {&#39;status&#39;: &#39;in_progress&#39;, &#39;granular_status&#39;: &#39;preparing&#39;, ...}<br>status_changed: {&#39;status&#39;: &#39;in_progress&#39;, &#39;granular_status&#39;: &#39;ready_for_pickup&#39;, ...}<br>location_update: {&#39;latitude&#39;: 52.3676, &#39;longitude&#39;: 4.9041, ...}<br>status_changed: {&#39;status&#39;: &#39;en_route&#39;, &#39;granular_status&#39;: &#39;driver_at_restaurant&#39;, ...}<br>...<br>status_changed: {&#39;status&#39;: &#39;completed&#39;, &#39;granular_status&#39;: &#39;delivered&#39;, ...}<br>stream_ended: {}</pre><h4>Step 7: Submit a review</h4><pre>curl -s -X POST http://localhost:8000/reviews \<br>  -H &quot;Content-Type: application/json&quot; \<br>  -d &#39;{<br>    &quot;order_id&quot;: &quot;ord-x7y8z9&quot;,<br>    &quot;provider_id&quot;: &quot;bella-napoli&quot;,<br>    &quot;rating&quot;: 4.5,<br>    &quot;comment&quot;: &quot;Great pizza, fast delivery!&quot;,<br>    &quot;scores&quot;: { &quot;food&quot;: 5.0, &quot;delivery&quot;: 4.0, &quot;value&quot;: 4.5 }<br>  }&#39; | jq</pre><pre>{<br>  &quot;id&quot;: &quot;rev-abc123de&quot;,<br>  &quot;order_id&quot;: &quot;ord-x7y8z9&quot;,<br>  &quot;provider_id&quot;: &quot;bella-napoli&quot;,<br>  &quot;rating&quot;: 4.5,<br>  &quot;comment&quot;: &quot;Great pizza, fast delivery!&quot;,<br>  &quot;scores&quot;: { &quot;food&quot;: 5.0, &quot;delivery&quot;: 4.0, &quot;value&quot;: 4.5 },<br>  &quot;created_at&quot;: &quot;2026-02-19T12:50:00Z&quot;<br>}</pre><p>Reviews support category-specific scores so agents can give structured feedback beyond a single rating.</p><h3>The full transaction flow</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q5a8j3YP98Ljvu4QjP9OhQ.png" /></figure><p>Discovery, catalog, and reviews are ASP standalone capabilities. Checkout uses ASP’s fulfillment extension on top of the base checkout schema. Tracking and streaming are ASP extensions for real-time fulfillment.</p><h3>Get involved</h3><p>ASP is open source under the Apache 2.0 license.</p><ul><li><a href="https://prosusai.github.io/agentic-services-protocol/"><strong>Documentation</strong></a> — Full specification, schema authoring guides, and implementation checklist</li><li><a href="https://github.com/ProsusAI/agentic-services-protocol"><strong>GitHub</strong></a> — Source schemas, OpenAPI/AsyncAPI specs, MCP tool definitions, and type generation</li><li><a href="https://github.com/ProsusAI/asp-samples"><strong>Samples</strong></a> — Working demos for food delivery, ride-hailing, and travel</li></ul><p>The protocol is extensible by design. Adding a new domain profile (groceries, pharmacy, home services) means defining a new allOf schema that layers onto the base types. Adding a new capability means defining a schema and transport binding. See the <a href="https://prosusai.github.io/agentic-services-protocol/">schema authoring guide</a> for details.</p><p>Contributions, feedback, and issues are welcome on <a href="https://github.com/ProsusAI/agentic-services-protocol">GitHub</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f227d987e6d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/building-with-asp-an-open-protocol-for-agent-to-service-transactions-8f227d987e6d">Building with ASP: An Open Protocol for Agent-to-Service Transactions</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Multi-Tenant AI Is Harder Than You Think]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/why-multi-tenant-ai-is-harder-than-you-think-59f9f76bf17a?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/59f9f76bf17a</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[openclaw]]></category>
            <dc:creator><![CDATA[Pavel Bugneac]]></dc:creator>
            <pubDate>Mon, 16 Mar 2026 14:28:00 GMT</pubDate>
            <atom:updated>2026-03-16T14:27:59.490Z</atom:updated>
            <content:encoded><![CDATA[<p><em>And how we built a governance layer for OpenClaw.</em></p><p><em>Contributors: Pavel Bugneac &amp; Chiara Caratelli</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AvN6kOzNUPBcwHbmc1vWtw.png" /></figure><p>Put an AI assistant (like OpenClaw) in front of a team and the problems start immediately. Someone burns through the API budget in a day. Anyone who finds the webhook URL can talk to your model. There’s no audit trail, no access control, no way to throttle a single user. The AI itself works fine. Everything around it is the problem.</p><p>At Prosus, we hit exactly this with OpenClaw. We wanted our team to use it without each person needing their own setup, and without one runaway conversation eating the monthly budget. But we didn’t want to fork OpenClaw or rebuild the agent framework. We just wanted to keep the runtime focused on what it does well, and handle governance separately.</p><p>So we built ClawHive, a thin router layer on top of OpenClaw, and we’re open sourcing it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-vt6baEjxO1WpojxLvn7KQ.png" /></figure><h3><strong>The problems nobody warns you about</strong></h3><h4><strong>1. Identity</strong></h4><p>A message arrives from Slack. Another from Telegram. Same person? Different person? Which AI agent should handle each one? What if someone signs up on Telegram and later messages from Slack, do they get two separate agents with two separate memories, or should those be linked?</p><p>There’s no built-in answer. You need a canonical identity model that maps channel + channel_user_id to a single user, provisions an agent for them, and handles cross-channel linking without race conditions. When you link two channels (e.g. Slack and Telegram), both use the same agent, so conversation history and tool state are shared; identity and memory stay in sync.</p><h4><strong>2. Access control</strong></h4><p>The moment your webhook URL exists, anyone who finds it can talk to your AI. You need a way to gate access: pending users who haven’t been approved yet, invite codes for controlled onboarding, the ability to suspend someone who’s abusing the system. And you need it without building an entire IAM system from scratch.</p><h4><strong>3. Cost runaway</strong></h4><p>LLM calls cost real money. One enthusiastic user running complex multi-turn conversations with tool use can burn through your monthly budget in a day. You need per-user token and cost quotas and they need to be enforced <em>before</em> the message ever reaches the model, not after the bill arrives.</p><h4><strong>4. Prompt injection</strong></h4><p>Users will, intentionally or not, send messages that try to manipulate the AI: “ignore previous instructions,” “you are now a…”, delimiter injection with &lt;|system|&gt; tokens, base64-encoded payloads. If you’re running a multi-user system, you need to catch these before they reach the LLM.</p><p>We use multi-pattern regex as a first line of defense against known techniques; it’s not a complete solution, and we expect to add more layers.</p><h4><strong>5. Auditability</strong></h4><p>When something goes wrong (and it will) you need to know who did what, when, and what the system decided. Not by grepping scattered log files, but from a structured, queryable audit trail that records every security-relevant event: access decisions, injection blocks, forwarding outcomes, quota breaches, admin actions.</p><h4><strong>6. Execution isolation</strong></h4><p>An AI agent with tool use isn’t just generating text, it’s running code, reading files, browsing the web. In a multi-user system, one person’s agent execution can’t be allowed to see another person’s files, leak environment variables, or interfere with other sessions. You need per-user sandboxing.</p><p>The naive approach of spinning up a full copy of the AI runtime per user, solves isolation but wastes resources. A single OpenClaw instance can orchestrate dozens of agents simultaneously; the expensive part is tool execution, not orchestration. So instead of replicating the entire runtime, each agent gets a lightweight Docker sandbox container for code execution, file access, and browser use. The orchestration process stays shared; the execution environment is isolated per user.</p><p>This gives you process-level isolation where it matters most, untrusted code execution, without paying the cost of N full runtime instances. This covers accidental cross-tenant access and bug containment; it does not protect against determined attackers or container escapes. VM-level isolation is future work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YW0QFQltyXu7ZucewtZYdQ.png" /></figure><h3><strong>How ClawHive works</strong></h3><p>ClawHive is a thin Hono HTTP service (TypeScript, ~6.5K lines) that intercepts all inbound traffic and runs it through a strict seven-stage pipeline before anything reaches the AI runtime. Every stage either passes the message through or stops it, with an audit event either way.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v3Sh4Y8LdWGrpXhmK9J8bA.png" /></figure><p>Here’s what that pipeline looks like for a real request such as a user messaging the Telegram bot for the first time:</p><p>The webhook arrives and ClawHive verifies the signature against the bot secret (1). It’s the first time seeing this event ID, so deduplication passes (2). The message text is clean of injection patterns (3). ClawHive looks up telegram + user_id in the channel links table. No match, so it auto-provisions a new user with pending status (4). The access gate sees the invite code in the message, redeems it, and flips the user to approved (5). Quota check passes: fresh user, no usage yet (6). ClawHive returns 200 to Telegram immediately, ensures an OpenClaw agent exists for this user via WebSocket RPC, and forwards the webhook (7). OpenClaw processes the message, runs tools if needed, and replies directly to Telegram. ClawHive records token usage and writes an audit event.</p><p>From the user’s perspective, they sent a message and got a reply. ClawHive doesn’t touch LLM orchestration, tool execution, or reply formatting. OpenClaw remains the brain.</p><h3><strong>What surprised us</strong></h3><p><strong>The hard part was everything around the model.</strong> OpenClaw already handles the AI well. Identity, policy, onboarding, and auditability took most of the effort.</p><p><strong>Isolation isn’t all-or-nothing.</strong> We initially assumed we’d need either full container-per-user runtimes (expensive) or accept zero isolation (risky). The middle ground, shared orchestration with sandboxed execution, turned out to cover the actual threat model well. All agents share one OpenClaw process for LLM calls and routing; tool execution (code, file access, browser) runs in per-agent Docker containers with separate filesystems. The orchestration layer doesn’t handle untrusted input directly; the sandbox layer does. Isolate where the risk is.</p><p>This is process-level isolation, not VM-level. It covers curious or careless users and bug containment, not determined attackers or container escapes. Fully independent runtimes per user are future work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tE92OYfl8ChLTrSoAzlN0A.png" /></figure><p><strong>Fast webhook acknowledgement matters more than you think.</strong> If you hold the webhook open until the full agent run finishes, platforms like Telegram may resend the same event. That means you stop debugging the product and start debugging retries. Our answer was a fast-ack pattern: ClawHive performs its governance checks up front, quickly acknowledges receipt to the platform, and then lets OpenClaw handle the slower agent work asynchronously in the background.</p><p>This also means governance is inbound-only. ClawHive controls what goes in, but for Slack and Telegram, OpenClaw replies directly to the channel and ClawHive doesn’t inspect outbound responses.</p><p><em>One final backstop: our quotas catch most cost overruns, but you should still set hard spend limits with your LLM provider.</em></p><h3>The technical stack</h3><p>We chose a thin stack on purpose:</p><p><strong>Runtime &amp; Storage</strong></p><ul><li>Hono on Node.js for a small, fast HTTP layer.</li><li>PostgreSQL for identity, quotas, and audit.</li><li>OpenClaw as the AI backend.</li></ul><p><strong>Admin</strong></p><ul><li>Server-rendered dashboard with zero build step</li><li>Tenant management, invite codes, quota controls, audit log viewer.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mjBj8DzqscDcFf0wb5mFvA.png" /><figcaption>Admin dashboard</figcaption></figure><p><strong>Quotas</strong></p><ul><li>Per tenant: limits per minute, per day, and per month.</li><li>All defaults, all overridable, all enforced before the model call.</li></ul><p><strong>Observability</strong></p><ul><li>Structured audit logging plus an in-memory metrics API and admin dashboard.</li></ul><p><strong>Deployment &amp; Testing</strong></p><ul><li>Docker Compose for local dev. Terraform for AWS (ECS + RDS + EFS + API Gateway + Secrets Manager).</li><li>16 test files covering security, identity, quota, OpenClaw integration, and webhook flows.</li></ul><h3>Open-sourcing it</h3><p>We’re releasing the full source under MIT. The repo includes everything: source code, tests, migrations, Docker Compose, Terraform deployment, admin dashboard, and operational documentation.</p><p>This pattern applies beyond OpenClaw. In a lot of AI systems, the runtime should stay focused on assistant behavior, while identity, policy, cost control, and audit live in a thin control layer around it.</p><p><strong>What we’d love from you:</strong></p><ul><li>Try it. Break it. Tell us what’s missing.</li><li>If you’re running OpenClaw for a team, tell us how your setup differs. We’d love to learn from your workarounds.</li><li>If you’ve solved multi-tenant AI problems in other runtimes, we’d love to hear how.</li></ul><p>You can find the code at <a href="https://github.com/ProsusAI/ClawHive">https://github.com/ProsusAI/ClawHive</a></p><p><em>Keep the runtime focused on being an assistant; put identity, policy, cost control, and audit into a thin layer around it.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=59f9f76bf17a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/why-multi-tenant-ai-is-harder-than-you-think-59f9f76bf17a">Why Multi-Tenant AI Is Harder Than You Think</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Murphy: Let Agents Break Your Website Before Your Users Do]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/murphy-let-agents-break-your-website-before-your-users-do-790d377e4a40?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/790d377e4a40</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[evaluation]]></category>
            <dc:creator><![CDATA[Isha Agrawal]]></dc:creator>
            <pubDate>Wed, 11 Mar 2026 09:01:49 GMT</pubDate>
            <atom:updated>2026-03-11T09:01:48.326Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/440/0*UdoJCQrglrSk0vP7.png" /></figure><p>Github: <a href="https://github.com/ProsusAI/Murphy">https://github.com/ProsusAI/Murphy</a><br><em>Contributors: Isha Agrawal, Kevin Ngo, Ambra Mihu, Magdalena Konstanty</em></p><p>If you have built a product, you have tested it. Unit tests, integration tests, end-to-end tests — your CI is green, your coverage looks great, and everything works. Ship it.</p><p>Except, your shiny new agent creation wizard? A first-time user lands on the page, completely misses the “New” button in the sidebar, clicks something random, submits an empty form, gets zero feedback, and quietly leaves. Your tests never caught it because they were never asking the right question.</p><p>Traditional tests ask: “Does the button work when clicked?”<br>Nobody asks: “Will anyone find the button?”</p><p>That is the gap. Your code works. Your product might not, at least not for the confused or the impatient.</p><h3>What is Murphy?</h3><p>Murphy is an open-source evaluation agent that uses your product the way real people do, which is not always the way you intended.</p><p>It clicks buttons, fills forms, navigates pages, and tries to accomplish tasks. But it does this through the lens of different user personas. A patient expert who breezes through your happy path. A confused novice who does not read labels and submits empty forms. An impatient user who clicks three times before the page loads.</p><p>It does not just check if things work. It checks whether your product communicates. A disabled button with no tooltip? Failure. A form that silently swallows bad input? Failure. An error that only makes sense to your backend engineer? Also failure.</p><p>Murphy catches what functional tests were never designed to find.</p><h3>Who is it for?</h3><p>Anyone who builds something with a UI and wants honest feedback about it. Solo developers, product teams, QA engineers. If you have a URL, Murphy can tell you how real humans would experience it, without recruiting a single tester or asking a friend to “just try this real quick.”</p><h3>How Murphy works</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6imrL1jOBvfpCNTR.png" /></figure><p>Murphy runs in three phases:</p><p><em>Phase 1: Explore.</em></p><p>An AI agent navigates your site like a curious new user, clicking through pages and discovering features. When you provide a goal, the exploration is focused on that area. Without a goal, it does a broad sweep and saves an editable features.md for you to review.</p><p><em>Phase 2: Plan.</em></p><p>An LLM turns the exploration into concrete test scenarios with steps, personas, and success criteria. Murphy saves an editable test_plan.yaml and pauses for you to review, add, remove, or tweak scenarios before anything runs.</p><p><em>Phase 3: Execute.</em></p><p>Tests run in parallel across isolated browser sessions via Browser Use (<a href="https://github.com/browser-use/browser-use">https://github.com/browser-use/browser-use</a>), an open-source browser automation framework for AI agents. A separate AI judge evaluates pass/fail based on what actually happened (action traces, screenshots, URLs visited), not what the agent says happened. No self-grading allowed.</p><p>The human-in-the-loop pauses are deliberate. Murphy generates; you curate. Add scenarios, toss irrelevant ones, tweak success criteria, then let it loose.</p><p>Here is what the output looks like. Murphy produces a structured evaluation report with results at a glance, per-test verdicts across personas, an executive summary, and detailed breakdowns for every failure — including why it failed, what UI gaps were observed, and a suggested fix.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/794/1*IAb6uxQittARe1Yrd1zMNw.png" /></figure><h3>Capabilities</h3><ul><li>Intent-based tests: scenarios describe what to accomplish, not which button to click, so they survive redesigns</li><li>Feedback quality scoring: every test is rated on whether feedback was present, clear, and actionable</li><li>Failure triage: automatically distinguishes genuine website issues from test limitations (CAPTCHAs, third-party walls)</li><li>Auth support: opens a browser for you to log in manually, then picks up the authenticated session</li></ul><h3>Persona</h3><p>Murphy does not pretend all users are the same. Each persona is defined as a static trait vector across five axes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lvC8TR-3_hVQTg5y.png" /></figure><p>Each trait is set to low, medium, or high, and intent is one of benign, exploratory, or adversarial. These vectors are defined in a persona registry. For example, confused_novice is defined as technical_literacy=low, patience=medium, intent=benign, exploration=medium, reading_comprehension=low with test type ux. The adversarial persona flips to technical_literacy=high, patience=high, intent=adversarial with test type security.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*45p6vkoV_ajh7asuiRUtRw.png" /></figure><p>Here is the important part: these traits do not just change how a test runs. They change what counts as passing.</p><p>For a security persona, silently sanitizing a malicious input is correct behavior. For a confused novice, that same silent handling is a failure, because the user got no feedback and has no idea what happened. The adversarial user passed because the WAF blocked malicious input cleanly. But the confused novice failed on the same site because a modal with no close button left them stranded with no guidance.</p><p>Same interaction pattern. Different persona. Different verdict. That is the point.</p><p>Seven personas ship out of the box:</p><ul><li>happy_path — the ideal user who follows instructions and reads labels</li><li>confused_novice — low technical literacy, skips instructions, needs clear visual cues</li><li>adversarial — actively tries to break things with injection, overflow, and unexpected input</li><li>edge_case — enters unusual but legitimate data: unicode names, 500-character bios, negative quantities</li><li>explorer — ignores the intended flow, clicks everything, opens menus just to see what is there</li><li>impatient_user — clicks before the page loads, skips confirmations, abandons slow flows</li><li>angry_user — frustrated from the start, low tolerance for friction, quick to give up</li></ul><h3>Why this works</h3><p>Three design choices that matter:</p><ol><li>The agent does not grade itself. The one running the test is not the one judging it. A separate AI judge reviews action traces, screenshots, and URLs against the success criteria. No “student grading their own homework” situation.</li><li>The judge adapts to the persona. It does not apply one rubric to every test. For a low-patience user it asks: “Did the site respond immediately?” For a low-literacy user: “Was the error communicated visually, not buried in paragraph text?” Same page, same interaction, different standards, because different users experience it differently.</li><li>Tests describe intent, not elements. “Submit the form (via Submit button, Enter key, or any visible control)” instead of “click #submit-btn.” Tests do not shatter when you move a button. The agent figures it out, the way a user would.</li></ol><h3>Open source and available now</h3><p>Murphy is fully open source. Get going in a few minutes:</p><p>git clone <a href="https://github.com/ProsusAI/Murphy.git">https://github.com/ProsusAI/Murphy.git<br></a>cd Murphy &amp;&amp; uv sync<br>uv run playwright install chromium<br>uv run murphy — url <a href="https://your-site.com/">https://your-site.com</a></p><p>Want to focus on a specific flow? — goal “test the checkout flow”.<br>Site needs login? — auth.<br>Want a dashboard? — ui.</p><h3>What’s next</h3><ul><li>Evaluation framework: Murphy judges your product, but what judges Murphy? We’re building an evaluation harness that measures Murphy itself. This includes how often it correctly identifies real UI issues vs. false positives, whether its persona-driven verdicts align with actual user feedback, and how reliably it reproduces findings across runs</li><li>Richer metrics: beyond pass/fail, Murphy will report task completion rates per persona and priority level, steps-to-completion as an efficiency proxy, and feedback quality breakdowns (present, timely, clear, actionable).</li><li>Custom personas: define your own trait vectors for your specific user base</li><li>Broader model support: bring your own LLM provider</li><li>Deeper UX evaluation: not just “did the user get feedback” but “was the information hierarchy right,” “did the visual design guide attention where it needed to go,” and “does the flow match established UX heuristics.”</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=790d377e4a40" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/murphy-let-agents-break-your-website-before-your-users-do-790d377e4a40">Murphy: Let Agents Break Your Website Before Your Users Do</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MemEval: Benchmarking Memory for AI Agents]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/memeval-benchmarking-memory-for-ai-agents-932d3fd9f3b4?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/932d3fd9f3b4</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[agent-memory]]></category>
            <dc:creator><![CDATA[Asad Ismail]]></dc:creator>
            <pubDate>Mon, 09 Mar 2026 15:53:27 GMT</pubDate>
            <atom:updated>2026-03-18T11:38:31.184Z</atom:updated>
            <content:encoded><![CDATA[<p>As AI agents move from single session chatbots to persistent, multi-session assistants, memory has become a critical capability. Comparing agentic memory systems is hard e.g. on LoCoMo dataset, judge accuracy ranges from 58% to 92%, not only because some systems are better, but also because each evaluation uses different LLMs, different embedding models, adifferent token budgets and different scoring methods. The numbers are not measuring the same thing.</p><p><strong>MemEval</strong> fixes this by standardizing the entire pipeline: same LLM, same embedding and same scoring method across every system. We evaluated 9 memory systems across two benchmarks, LoCoMo [1] and LongMemEval [2]. This systematic evaluation also revealed where and why these systems fail, which led us to build:</p><p><strong>PropMem</strong> a factual memory system designed around those failure modes.</p><p>This post covers both: the benchmark results, and the engineering decisions behind PropMem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VOA3ql9hPiVuJTH3cTwM4A.png" /></figure><h3><strong>Benchmark Results</strong></h3><p>We selected 9 memory systems spanning the main architectural approaches to agent memory: chunk-and-search [9], fact extraction [4][11], knowledge graphs [5], full context, multi-round reflection [7], and fine-tuned models [8]. The goal was to cover different design philosophies so the benchmark reveals which approaches actually work better and more efficiently under the same conditions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C4e1URYEuxdTBneGJJmBdQ.png" /><figcaption>Different memory systems used in the benchmark</figcaption></figure><p><strong>LoCoMo </strong>is the primary benchmark we used in our benchmark. It consists of 10 multi-party conversations between two human speakers, each spanning multiple sessions over an extended time period, simulating the kind of long-term memory a real agent would need to maintain.</p><p>Questions are split across 5 categories.</p><ol><li><strong>Factual</strong>: direct recall of stated information.</li><li><strong>Temporal:</strong> awareness of when something happened relative to something else.</li><li><strong>Multi-hop:</strong> connecting information across different sessions.</li><li><strong>Inferential: </strong>reasoning beyond what was explicitly stated.</li><li><strong>Adversarial: </strong>questions designed to exploit naive retrieval, rephrased queries, contradictory information, distractor facts.</li></ol><p>Each category tests a different failure mode, making LoCoMo a diagnostic tool rather than a single-number test. Most published results reference it, which makes it the right anchor for comparison.</p><p>All systems run on gpt-4.1-mini, text-embedding-3-small, and the same scoring pipeline. Scoring is token F1 plus LLM-as-judge. We also track end-to-end LLM token usage across ingestion, retrieval, and answering.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*awZpfPGaephS-67CcK--3w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wijCJdsz3M-m_4SkSqeCaQ.png" /></figure><p>We also ran a subset of systems on <strong>LongMemEval</strong> [2], a single-user conversation benchmark of up to 500 turns.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r1jIq3dPLbNaJMjxUVUU7w.png" /></figure><p>A few things to notice PropMem has the best overall quality-to-token performance. Full Context is placed third on LoCoMo but it has the worst token consumption and it does not scale and on LongMemEval with a very large context of 500-turn conversations it drops to last place (0.222 F1) at 37.5M tokens. Token cost also varies by 12x across different systems.</p><p>The rest of this post focuses on what we learned from the evaluation and how we used it to build PropMem.</p><h3>PropMem: Fixing Each Failure Mode</h3><p>PropMem is based on top of hybrid search, specifically OpenClaw’s [9] chunk-and-search. OpenClaw is our baseline RAG memory system included in the benchmark. PropMem is based on three main ideas on top of OpenClaw’s memory; each one directly addresses a failure mode from the error analysis.</p><h4>Idea 1: Atomic Propositions Instead of Chunks</h4><p>Instead of retrieving 400-token chunks, PropMem extracts atomic facts at ingestion time. Each proposition is a single fact about a single entity, with a date stamp: e.g Caroline gave a speech at a local school [May 7, 2024]</p><p>Each proposition is around 25 words versus 100 words per raw chunk. System top 30 retrieval slots now hold 30 distinct facts instead of 3 chunks with mixed content.</p><p>The extraction prompt enforces two important rules</p><ol><li><strong>Date resolution at extraction time.</strong> “Last week” becomes “the week of May 8, 2023”. By resolving relative dates at extraction time, temporal retrieval stays consistent.</li><li><strong>One fact per entry.</strong> Without this constraint, the LLM combines related facts: “Caroline went to the support group and felt nervous but was glad she attended.” those are three facts. If the question asks when she went, the emotional detail wastes a retrieval slot.</li></ol><h4>Idea 2: Entity-Filtered Retrieval</h4><p>At query time, PropMem identifies which entity the question is about and scopes all search to that entity only.</p><p>This single change was the largest accuracy improvement in the system. Raw chunks are kept as a fallback (3 chunks, entity-filtered when possible). Propositions carry the main retrieval signal; chunks provide broader conversational context when propositions alone are insufficient.</p><h4>Idea 3: Question-Aware Answer Generation</h4><p>Not all questions should be answered the same way. Some have a definite answer in the data: a date, a name, a value. Others require reasoning from what is known to reach a conclusion that isn’t stated anywhere explicitly.</p><p>For factual questions, the prompt is strict: only answer if the evidence clearly supports it, otherwise say nothing. A plausible sounding hallucination is worse than silence.</p><p>For inferential questions, the opposite applies. If someone asks whether a person would enjoy something based on their known interests, “I don’t have enough information” is a cop-out. The answer can be reasoned to, and the prompt reflects that.</p><p>Routing between these modes uses a lightweight classifier, not an extra LLM call.</p><h4>The Details That Add Up</h4><p>Beyond the three main ideas, several smaller decisions contribute measurably:</p><p><strong>Deduplication.</strong> After ranking, propositions are deduplicated by normalized text (case-insensitive, punctuation-stripped). If the same fact appears from two sessions, only the highest-scored version takes a retrieval slot.</p><p><strong>Knowledge updates.</strong> When two propositions about the same entity have cosine similarity above 0.85 but different dates, the older one gets a 30% score penalty. This is not deletion, the old fact still exists for “where did you live before?” questions. It is a soft recency signal that pushes newer facts higher without destroying history.</p><p><strong>Clustering for fallback retrieval.</strong> When entity matching fails (single-user conversations, ambiguous questions), K-means clusters over proposition embeddings narrow the search space to the 5 most relevant topic clusters. Cluster count scales as sqrt(n), capped between 5 and 40. This only activates for large proposition sets (500+).</p><h3>What We Chose Not to Build</h3><p>Equally important to what PropMem does is what it does not do:</p><p><strong>No knowledge graph.</strong> Full temporal knowledge graphs with entity nodes and relationship edges are appealing in theory, they capture relationships, not just facts. In practice, they are slow and expensive to build. The complexity does not pay off at the single user memory scope.</p><p><strong>No multi-stage pipeline.</strong> Some memory systems have multi stage pipeline e.g SimpleMem [7] has semantic compression, online synthesis, intent-aware retrieval with 5+ LLM calls per question. PropMem makes 1 LLM call per question (plus 1 at ingestion per session).</p><h3>The Cost Picture</h3><p>Each PropMem answer prompt runs about 2,500 tokens: 30 propositions, 3 raw context chunks, and the instruction template. Total across the full benchmark: 5.9M tokens, 65% fewer than OpenClaw (16.4M). The proposition extraction at ingestion is the main cost. But it is a one-time cost per conversation, and it pays for itself many times over by making retrieval more precise and reducing the tokens needed per question.</p><h3>Getting Started</h3><p>MemEval is open source. You can reproduce results using:</p><pre>uv run python scripts/run_full_benchmark.py --systems all --num-samples 10 --llm-model gpt-4.1-mini</pre><p>Or use PropMem directly in your agent:</p><pre>from agents_memory import PropMemMemory</pre><pre>memory = PropMemMemory(<br>    user_name=&quot;John&quot;,<br>    assistant_name=&quot;Assistant&quot;,<br>    llm_model=&quot;gpt-4.1-mini&quot;,<br>)</pre><pre>memory.add_session(<br>    [<br>        {&quot;speaker&quot;: &quot;John&quot;, &quot;text&quot;: &quot;I prefer quiet coffee shops for work.&quot;},<br>        {&quot;speaker&quot;: &quot;Assistant&quot;, &quot;text&quot;: &quot;Noted. You prefer quiet coffee shops.&quot;},<br>    ],<br>    session_date=&quot;2026-03-01 10:30:00&quot;,<br>)</pre><pre>answer = memory.ask(&quot;Where does John prefer to work?&quot;)</pre><p>Adding your own memory system takes one adapter function. Adding a new benchmark dataset is similarly straightforward.</p><p><strong>GitHub:</strong> <a href="https://github.com/ProsusAI/MemEval">github.com/ProsusAI/MemEval</a></p><h3>References</h3><ul><li>[1] LoCoMo: Evaluating Very Long-Term Conversational Memory of LLM Agents, Maharana et al., 2024<a href="https://arxiv.org/abs/2402.17753"> https://arxiv.org/abs/2402.17753</a></li><li>[2] LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory, Wu et al., 2024<a href="https://arxiv.org/abs/2410.10813"> https://arxiv.org/abs/2410.10813</a></li><li>[3] Memory in the Age of AI Agents: A Survey, Zhang et al., 2025<a href="https://arxiv.org/abs/2512.13564"> https://arxiv.org/abs/2512.13564</a></li><li>[4] Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory , Chhikara et al., 2025<a href="https://arxiv.org/abs/2504.19413"> https://arxiv.org/abs/2504.19413</a></li><li>[5] Zep: A Temporal Knowledge Graph Architecture for Agent Memory, Rasmussen et al., 2025<a href="https://arxiv.org/abs/2501.13956"> https://arxiv.org/abs/2501.13956</a> Independent re-evaluation:<a href="https://blog.getzep.com/lies-damn-lies-statistics-is-mem0-really-sota-in-agent-memory/"> https://blog.getzep.com/lies-damn-lies-statistics-is-mem0-really-sota-in-agent-memory/</a></li><li>[6] MemU Benchmark Reports 92% accuracy on LoCoMo using LLM-judge binary accuracy.<a href="https://github.com/MemTensor/MemOS"> https://github.com/MemTensor/MemOS</a></li><li>[7] SimpleMem: Efficient Lifelong Memory for LLM Agents, Liu et al., 2026<a href="https://arxiv.org/abs/2601.02553"> https://arxiv.org/abs/2601.02553</a><a href="https://github.com/aiming-lab/SimpleMem"> https://github.com/aiming-lab/SimpleMem</a></li><li>[8] Memory-R1 (<a href="https://arxiv.org/abs/2508.19828">https://arxiv.org/abs/2508.19828</a>)</li><li>[9] OpenClaw, open-source AI agent framework<a href="https://openclaw.im"> https://openclaw.im</a></li><li>[10] LangMem, LangChain long-term memory SDK<a href="https://github.com/langchain-ai/langmem"> https://github.com/langchain-ai/langmem</a></li><li>[11] Memobase, user profile-based long-term memory<a href="https://github.com/memodb-io/memobase"> https://github.com/memodb-io/memobase</a></li><li>[12] OpenAI Memory<a href="https://platform.openai.com/docs/guides/memory"> https://platform.openai.com/docs/guides/memory</a></li><li>[13] MemGPT: Towards LLMs as Operating Systems, Packer et al., 2023<a href="https://arxiv.org/abs/2310.08560"> https://arxiv.org/abs/2310.08560</a></li><li>[14] MemEval + PropMem <a href="https://github.com/ProsusAI/MemEval">https://github.com/ProsusAI/MemEval</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=932d3fd9f3b4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/memeval-benchmarking-memory-for-ai-agents-932d3fd9f3b4">MemEval: Benchmarking Memory for AI Agents</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Turning Your Model’s Errors into Golden Training Data]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/turning-your-models-errors-into-golden-training-data-bf44aeb6bdb9?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/bf44aeb6bdb9</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[synthetic-data]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Ramonkaspar]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 13:53:47 GMT</pubDate>
            <atom:updated>2026-02-25T13:53:45.708Z</atom:updated>
            <content:encoded><![CDATA[<p>What if you could ask your model “Where are you confused and why?” and then automatically generate training data to fix exactly that? That’s what we built at the Prosus AI Team, in collaboration with iFood.</p><h3>TL;DR</h3><p>The pipeline (1) finds where a fine-tuned model fails on a held-out set, (2) uses a reasoning LLM to analyze why each error happens, and (3) generates targeted synthetic training samples to fix those specific weaknesses. On our taxonomy classification task, this yielded gains up to +13.7% F1 on categories the model previously struggled with, improving 72% of targeted categories overall. The pipeline is generalizable and can be reused for any classification task.</p><p>At Prosus AI, we work with iFood — the leading food delivery platform in Latin America — on ML systems that process millions of product listings. One of those systems classifies food items into a hierarchical taxonomy (top-level category → subcategory → leaf category) based on minimal metadata: a product name, a short description, and sometimes an image. <br>We fine-tuned a vision language model (VLM) that generates a structured item profile from the listing metadata. Taxonomy classification is one component of that profile. It worked well, with solid accuracy across most categories. But it still had clear weak spots, specifically on items that sit right on the decision boundary between two similar categories. The errors were systematic: the model had learned strong priors from certain keywords, and those priors were overriding the actual context. It also wasn’t robust against marketing language. Promotional phrases like “2 for 1” in the product name would push the model toward categories like hamburgers, simply because most promotions in the training data happened to be hamburgers. Instead of throwing more generic data at the problem, we built a targeted approach: a small number of carefully generated synthetic samples improved specific categories by over 13% F1.</p><h3>The Problem with “Just Add More Data”</h3><p>In our case, the model learned to handle hundreds of taxonomy categories with good accuracy. But the remaining errors weren’t random. They were concentrated in specific areas where categories are semantically close. In the food domain, this happens constantly. Is a product a cookie or a chocolate? Is it a cut of beef or a prepared meat dish? Is it a juice or a smoothie?</p><p>Adding more generic training data doesn’t solve this. If the model has already seen 500 examples of cookies and 500 examples of chocolates, seeing 50 more of each won’t teach it the difference between a chocolate bar that contains cookie pieces and an actual cookie. The model needs to understand <em>why</em> one is a chocolate and the other is a cookie — and that requires examples specifically designed to teach that distinction.</p><p>This insight led us to build what we call the <strong>Active Learning Pipeline</strong> <strong>(ALP)</strong>.</p><h3>Why “Active Learning”?</h3><p>In classical active learning, a model queries a human oracle to label the samples it would benefit from most. The core insight: not all samples contribute equally to learning, and the ones near the decision boundary are the most informative.<br>Our pipeline borrows this principle but diverges in two important ways. First, instead of selecting real unlabeled samples for annotation, we generate entirely new synthetic samples, so we’re not limited by what exists in our data pool. Second, instead of relying on model uncertainty scores to find hard cases, we use a reasoning LLM to perform an explicit error analysis on misclassified items. This tells us not only which samples are hard, but why they’re hard, and that reasoning directly informs how we generate the synthetic data. To ensure diversity, we define explicit generation strategies that each target a different aspect of the confusion. <br>In that sense, ALP sits somewhere between active learning, hard example mining (selecting the samples your model gets wrong and retraining on those), and targeted synthetic data generation. We kept the name because the core loop is the same: evaluate the model, find its weak spots, focus your data effort there, retrain.</p><p>At a high level, the pipeline has three steps:</p><ol><li>Find where the model fails</li><li>Use a reasoning LLM to understand why it fails</li><li>Generate entirely new synthetic samples that target that specific failure mode</li></ol><p>The pipeline itself is completely generic. We built it for taxonomy classification, but the same approach works for any classification task where a model has systematic blind spots. That’s why we’re writing about it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5PFOpMhzb-jJgoY2s2-CMA.png" /></figure><h3>The Oreo Problem: A Walkthrough</h3><p>Before diving into the details of each step, let’s look at the full pipeline in action on a real example.</p><p><strong>The item:</strong></p><ul><li>Name: <em>Oreo</em></li><li>Description: <em>Cocoa black dough with Laka Oreo filling.</em></li><li>An image is available showing the product</li></ul><p>In reality, this is a chocolate bar with Oreo-flavored filling — not a cookie.</p><p>The model classified it as Biscuits/Cookies (incorrect), but the ground truth is Filled Chocolate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TqxtaOVc-0oR13xHjk5bwA.png" /><figcaption><em>The Oreo problem: easy cases on both sides have clear signals, but the item in the middle has conflicting features — biscuit ingredients in a chocolate bar format — causing the model to misclassify it. (Image generated with </em><a href="https://blog.google/innovation-and-ai/products/nano-banana-pro/"><em>Nano Banana Pro</em></a><em>)</em></figcaption></figure><p><strong>Step 1 — Find the error:</strong> We ran the model on our held-out validation set. It predicted Biscuits/Cookies for this item. The ground truth says Filled Chocolate. This disagreement is flagged.</p><p><strong>Step 2 — Analyze the confusion:</strong> We send the item to a reasoning LLM along with the full taxonomy context. Its analysis: <em>“The word ‘Oreo’ is a very strong signal for cookies. The merchant category ‘Especiais’ doesn’t help disambiguate. The description’s ‘cocoa dough’ is ambiguous — it could mean cookie dough or chocolate base. The image clearly shows a chocolate bar, but without explicit textual terms like ‘bar,’ ‘tablet,’ or ‘chocolate shell,’ the model defaults to predicting a cookie. The key differentiator is the product format: a chocolate shell with filling (chocolate category) vs. a baked flour product (biscuit category).”</em></p><p><strong>Step 3 — Generate targeted synthetic samples:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yVXzjxymmQL1P2Ls6TQR_g.png" /></figure><p>These are 4 of the 8 samples the pipeline generated for this item. We picked the ones that best illustrate the idea. The remaining strategies are explained in the detailed breakdown below.<br>After retraining with these (and similar samples for other confused items), the model’s F1 score on the “Chocolates” category improved by <strong>+10%</strong>.</p><h3>How the Pipeline Works</h3><h4>Step 1: Negative Mining</h4><p>We run the fine-tuned model on a held-out evaluation set — items the model has never seen during training. We compare its predictions against the ground truth labels and collect every disagreement.</p><p>Using a held-out set here is important. If we ran the pipeline on training data, we’d mostly find memorization failures and label noise, not real weaknesses. We need errors on items the model has never seen during training, because those reflect the actual generalization failures that will show up in production. Those are the errors worth targeting.</p><p>The held-out set contains 100k samples. With the model already at roughly 85% accuracy, that gives us around 15k misclassified items to feed into the pipeline, enough to cover a wide range of confusion patterns across the taxonomy. The set itself is stratified: 80% of samples follow the natural distribution, so common categories carry proportional weight and the validation set reflects real production performance. The remaining 20% are distributed equally across all taxonomy paths to ensure coverage of rare categories. Without this, the model could have blind spots on low-frequency nodes that we’d never detect during evaluation.</p><h4>Step 2: Analyze the Confusion</h4><p>This is the core of the pipeline, and where the design gets interesting.</p><p>For each error, we send the confused item to a reasoning LLM, a large, capable model (e.g., GPT-5, Claude Opus 4.5) that we use purely for analysis, not for production inference. In essence, we ask it: <strong>why did our model probably get this wrong?</strong></p><p>The context we provide to the reasoner matters a lot:</p><ul><li>The confused item (name, description, image)</li><li>The ground truth taxonomy path and its description</li><li>The model’s predicted taxonomy path and its description</li><li>The full taxonomy tree hierarchy, so the reasoner knows what other nodes exist. Without it, it might generate a synthetic sample for category X that would actually be a better fit for category Y. It also helps the “reasoner” to understand the decision space: It needs to know what all the options are and how they relate to each other to figure out why the model picked the wrong one.</li><li>Descriptions of neighboring nodes, to understand the semantic boundaries between similar categories</li><li>Domain-specific business rules (e.g., “a skewer served as an appetizer goes under Snacks, not Beef Cuts” or “cheesecake should be classified as Pie, not Cake”), which encode domain knowledge that isn’t obvious from the taxonomy structure alone. Without them, the reasoner might generate samples that look correct but violate domain-specific constraints.</li></ul><p>This analysis is then passed to the next step, where it directly guides what kind of synthetic samples to generate. Without this reasoning, we’d just be generating random examples of the correct category — which is far less effective than examples that specifically address the confusion the model has.</p><h4>Step 3: Generate Targeted Synthetic Data</h4><p>Based on the analysis, the reasoning LLM generates 6–8 new synthetic training examples for each confused item. Two things in the prompt are important to get right here.</p><p>First, we include real marketplace items as reference examples in the prompt. Without these, the LLM generates items that sound artificial e.g. “Açaí to eat with a spoon”, which is technically correct but not how real products are listed. The reference examples are what makes the synthetic items sound natural. We tried getting there with prompting alone, describing the style, adding constraints, but it wasn’t enough. The real examples were the crucial part. Once the LLM could see what actual product listings look like, the synthetic items started looking like they could be real listings.</p><p>Second, we define specific generation strategies to ensure diversity and target known failure modes:</p><ul><li><strong>Minimal Correction.</strong> Take the original confused item and make the smallest possible change to make it unambiguous. For the Oreo chocolate bar: rename it to “Chocolate Black with Laka filling and Oreo pieces 90g” and describe it as “Chocolate tablet with creamy filling…”</li><li><strong>Strong Positive (Ground Truth).</strong> A textbook, prototypical example of the correct category. No ambiguity, no tricks. “Milk Chocolate Filled with Salted Caramel 90g.” This anchors the model’s understanding of what the correct category actually looks like.</li><li><strong>Strong Positive (Predicted).</strong> Same thing, but for the <em>wrong</em> category. A clear, obvious example of a cookie: “Vanilla Filled Biscuits 140g”. This teaches the model the boundary from both sides.</li><li><strong>Counter-Signal.</strong> This is probably the most interesting one. Create an item that belongs to the <em>correct</em> category but deliberately includes misleading keywords from the <em>wrong</em> category. Example: “Cookies &amp; Cream Filled Bar” — it has “cookies” right in the name, but it’s clearly a chocolate bar. Trains the model to look past surface-level keyword matching.</li><li><strong>Minimal Text.</strong> Very short name and description, but still unambiguous. Makes the model more robust to the case where it has limited information only.</li><li><strong>Marketing Language.</strong> Ground truth examples wrapped in promotional language (“Premium,” “Artisan,” “Gourmet”). Makes the model more robust against promotional terms (which occur a lot in real listings) that can mislead classification.</li><li><strong>Image-Driven.</strong> A sample with deliberately minimal or ambiguous text, where the image is the primary signal for the correct category.</li></ul><p>These are the core strategies. We use a few more depending on the case, but they follow the same principle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P2SJeeutQPT3b4FofpkQww.png" /><figcaption><em>Our core generation strategies applied to the “Oreo” confusion. Each targets the decision boundary between Filled Chocolate and Biscuits from a different angle. (Image generated with </em><a href="https://blog.google/innovation-and-ai/products/nano-banana-pro/"><em>Nano Banana Pro</em></a><em>)</em></figcaption></figure><p><strong>Handling Images in Synthetic Data. </strong>If you’re working with a VLM, there’s an additional consideration: your synthetic samples also need images.<br>Real product listings often come with images, and the synthetic samples need to reflect that. If all synthetic items were text-only, we’d risk training the model to rely on text alone and ignore the visual signal. So for each synthetic item, the reasoning LLM decides whether to reuse the original confused item’s image by setting a boolean flag. Sometimes the decision is obvious: a strong positive of the wrong category can never reuse the original image, since it’s an entirely different product. But the image also gets excluded when the original doesn’t have one, when it’s low quality or ambiguous, or simply to add variety. Not every synthetic item should have an image, otherwise the model might overfit on the visual signal.</p><p>On top of that, we also have an image-driven strategy where the synthetic item has deliberately minimal or ambiguous text, and the image is the only signal that makes the correct category clear. This teaches the model to rely on visual input when the text isn’t enough. And since we don’t freeze the image tower during training, the visual encoder adapts to our domain. It learns which visual features are discriminative for taxonomy classification, like distinguishing a chocolate bar from a cookie by packaging and shape, rather than relying on generic pre-trained representations.</p><h4>Step 4: Augment and Retrain</h4><p>We add the synthetic samples to the original training data and retrain the model. One important detail: the synthetic samples supplement the real data, they don’t replace any of it. The original training set stays intact.</p><p>The pipeline is also naturally iterative. After retraining, you can run it again on the new model’s errors. Each iteration should produce smaller, more targeted corrections. We haven’t run multiple iterations yet, but in principle you could keep looping until the returns diminish.</p><h3>Results</h3><p>We ran an ablation study. Two models, same architecture (<a href="https://huggingface.co/Qwen/Qwen3-VL-2B-Instruct">Qwen/Qwen3-VL-2B-Instruct</a>), same hyperparameters:</p><ul><li><strong>Control:</strong> Trained on the standard training set (120k samples)</li><li><strong>Experiment:</strong> Same training set + approximately 9,000 ALP-generated synthetic samples</li></ul><p>We evaluated both on a held-out benchmark of roughly 16,000 items. We focused on taxonomy nodes that received at least 20 ALP samples; 5 nodes with fewer samples were excluded to avoid noise.</p><ul><li><strong>Overall:</strong> The ALP samples gave us a <strong>+1.87 percentage point improvement</strong> in average F1 across all evaluated taxonomy nodes. The improvement is consistent and meaningful.</li><li><strong>Targeted impact:</strong> <strong>72.4%</strong> of the nodes that received ALP samples showed improvement. The remaining 28% saw a slight degradation.</li><li><strong>Asymmetric risk/reward:</strong> When ALP helped, it helped a lot. The best gain was +13.7% F1 on a single node. When it hurt, the damage was smaller — the worst degradation was -7.8% F1. This is a favorable risk profile.</li></ul><h4>ALP Helps Where It Matters Most</h4><p>ALP is most effective where the model struggles most. Categories with a baseline F1 below 0.70 saw roughly 4x more improvement than categories already above 0.85, which is exactly what you’d want from a pipeline designed to target weak spots.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AkNilFf73nOSAtA9KfTWzQ.png" /></figure><p>This inverse correlation is statistically significant (Spearman rho = -0.369, p = 0.003) — the weaker the baseline, the stronger the ALP effect.</p><h4>What Didn’t Work</h4><p>Not everything improved. The nodes with the most ALP samples actually showed degradation. The sweet spot seemed to be around 30-90 synthetic samples per node. Beyond that, performance starts to degrade, likely because too many synthetic samples shift the training distribution away from the real data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lShRhls5gdcfVtKPsGylGw.png" /></figure><p>This is an important lesson: <strong>more synthetic data per category is not better.</strong> The pipeline works best when it’s a targeted supplement, not a replacement for real data.</p><h3>Design Decisions That Mattered</h3><p>A few things we learned that might save you time if you try something similar:</p><ul><li><strong>Give the reasoner full context.</strong> Early versions of the pipeline gave the reasoning LLM only the confused item and the two taxonomy labels. The analysis was shallow and the generated samples were generic. Once we added the full taxonomy hierarchy and node descriptions, the quality jumped significantly. The reasoner needs to understand the full decision space to produce useful analysis.</li><li><strong>Define explicit generation strategies. </strong>Without them, the LLM generates samples that all look similar — just variations of the same correct example. The strategies force diversity and ensure the synthetic data covers specific known failure modes like marketing language, sparse metadata, or misleading keywords.</li><li><strong>The counter-signal strategy is among the most effective.</strong> Items that contain misleading keywords but belong to the correct category are exactly the kind of training signal the model is missing. They directly attack the keyword-based shortcuts the model has learned.</li><li><strong>Use the reasoner to catch labeling errors. </strong>We explicitly instruct the reasoning LLM to flag cases where it believes the ground truth itself is wrong — and when it does, it skips generation entirely. This gives us an automated quality check on our evaluation labels. We filtered those cases out and reviewed them separately.</li></ul><h3>How to Get Started with Your Use Case</h3><p>We built this for taxonomy classification in the food domain, but the pipeline is generalizable and can be reused for any classification task where:</p><ul><li>You have a fine-tuned model that performs well overall, but has systematic edge-case failures</li><li>The classes have semantic overlap (i.e., the errors aren’t random but concentrated at specific decision boundaries)</li><li>You can provide enough context to a reasoning LLM for it to understand the classification space</li></ul><h3>Conclusion</h3><p>Supervised fine-tuning gets you surprisingly far. But the last few percentage points of performance — the edge cases, the ambiguous items, the things that sit right on the decision boundary — those are hard to fix with more of the same data.</p><p>The Active Learning Pipeline provides a structured way to identify exactly where your model is failing, understand why, and generate the specific training signal needed to fix it. It’s not a silver bullet, it gave us roughly +2% F1 overall, but it’s effective at patching specific holes. A 13% improvement on a single problematic category can matter more in production than a 2% lift across the board.</p><p><em>A collaboration between Prosus AI and iFood. Authors: </em><a href="https://ch.linkedin.com/in/ramon-kaspar"><em>Ramon Kaspar</em></a><em>, </em><a href="https://nl.linkedin.com/in/alexandru-dumitru-993614163"><em>Alexandru Dumitru</em></a>, <a href="https://nl.linkedin.com/in/kszark999"><em>Kinga Szarkowska</em></a><em>,</em> and <a href="https://www.linkedin.com/in/zulkufgenc/"><em>Zülküf Genç</em></a><em>. With contributions from </em><a href="https://nl.linkedin.com/in/floris-jan-fok"><em>Floris Fok</em></a><em> and </em><a href="https://nl.linkedin.com/in/madeline-duncan-361787167"><em>Madeline Duncan</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf44aeb6bdb9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/turning-your-models-errors-into-golden-training-data-bf44aeb6bdb9">Turning Your Model’s Errors into Golden Training Data</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[BESH: An Open Source Batch Endpoint for Unlocking 4x Utilisation of Your Self Hosted GPU Cluster]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/besh-an-open-source-batch-endpoint-for-unlocking-4x-utilisation-of-your-self-hosted-gpu-cluster-87570f8d2f35?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/87570f8d2f35</guid>
            <category><![CDATA[llm-applications]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[push-notification]]></category>
            <category><![CDATA[gpu]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Floris Fok]]></dc:creator>
            <pubDate>Wed, 22 Oct 2025 08:39:27 GMT</pubDate>
            <atom:updated>2025-10-22T12:05:18.712Z</atom:updated>
            <content:encoded><![CDATA[<h3>TLDR;</h3><p>BESH is an open-source batch processing API that solved iFood’s challenge of generating millions of personalized push notifications daily. Built as an alternative to commercial batch endpoint for our own LLMs which are self-hosted on GPUs for inference, BESH delivers massive improvements:</p><p>🚀 26x cost reduction compared to commercial endpoints (eg OpenAI’s GPT-4o-mini batch) solutions</p><p>📈 Processes &gt;120B tokens daily at production scale</p><p>⚡ Dramatically higher GPU utilization — transforms idle hardware into efficient processing powerhouses</p><p>🔧 Simple setup — seamlessly integrates with existing data pipelines (Databricks, etc.)</p><p>🛡️ Production-ready reliability — intelligent queue management, persistent storage, real-time monitoring</p><p>📊 Enterprise features — auto-scaling, load balancing, comprehensive analytics dashboard</p><p>💰 Self-hosted control — pay per hour instead of per token, with predictable costs</p><p>The solution turned non-viable personalization ideas into viable ones, enabling true hyper-personalization at scale at the right unit economics. You can find the code and setup instructions here: <a href="https://github.com/ProsusAI/BESH">https://github.com/ProsusAI/BESH</a></p><p>Perfect for any organization looking to deploy LLMs efficiently without the complexity and costs of external APIs.</p><p><strong>The Context: Operating at Large (!) Scale</strong></p><p>iFood is Latin America’s leading food delivery platform, processing over 120 million orders per month across all states in Brazil. With millions of active users placing orders daily, generating billions of data points from restaurant interactions, delivery patterns, and user preferences and behavior, we operate one of the largest and most complex food delivery ecosystems in the world.</p><p>At this scale, every optimization matters. A 1% improvement in user engagement translates to millions of additional orders. A small increase in operational efficiency can save hundreds of thousands of dollars monthly. And most importantly, every user interaction represents an opportunity to create a more personalized, valuable experience.</p><p>This massive scale also means that traditional approaches to user communication — like broadcast messaging or simple segmentation — fall short of what’s possible and what users expect in 2025.</p><p><strong>The Vision</strong></p><p>At iFood, we started with a simple question: why do push notifications still feel generic? We wanted every message to feel like it was written for one person, at one moment, with the right tone and intent. With 120 million orders generating rich behavioral data every month, we had the foundation to make this vision possible.</p><p>Our ambitious goal was to leverage Large Language Models to create millions of personalized push notifications daily, using each user’s behavioral data, preferences, and real-time context to craft messages that felt genuinely relevant.</p><p>Early tests with premium LLMs (like GPT-4o) were exciting — content quality was great, engagement went up, and the experience felt genuinely personal. We thought we had found the solution.</p><p><strong>The Expectations</strong></p><p>We expected to build a system that could:</p><ul><li>Process user behavior in real-time: Location data, app interactions, search history, order patterns, and temporal preferences</li><li>Generate natural, personalized content: Using LLMs to create conversational messages that didn’t feel robotic or generic</li><li>Scale seamlessly: Handle millions of users across different markets and cultural contexts</li><li>Operate cost-effectively: Maintain reasonable operational costs while delivering premium personalization</li></ul><p><strong>The Reality</strong></p><p>What we discovered was that operating at iFood’s scale revealed fundamental limitations in existing LLM infrastructure that simply weren’t apparent at smaller volumes.</p><p><strong>Cost Spirals Out of Control</strong></p><p>When processing millions of personalized messages daily using GPT-4o, costs quickly became prohibitive. What seemed reasonable during testing became a cost prohibitive when scaled to our actual user base. We were competing with every other team inside the company using the same API, leading to unpredictable pricing and performance.</p><p><strong>Reliability Issues</strong></p><p>While working with external providers and models, reliability issues hit us hard during peak periods: during the GPT-5 launch, we experienced issues: almost 60% of our batch jobs were lost because they didn’t finish within the 24-hour processing window. This wasn’t just inconvenient — it was breaking our entire personalization pipeline and leaving millions of users with generic messaging experiences.</p><p><strong>Infrastructure Bottlenecks</strong></p><p>After switching to a self-hosted fine tuned model, our large-scale inference jobs on Databricks weren’t delivering the efficiency we expected. Despite having serious GPU compute power at our disposal, hardware sat mostly idle — throttled by inefficient request handling, timeout issues, and the constant overhead of managing millions of individual API calls. We were barely scratching the surface of what our infrastructure could actually deliver.</p><p>The struggle with live API calls made total sense in hindsight. We were keeping expensive Databricks clusters running continuously, just waiting for individual requests to finish. This approach was fundamentally inefficient for our data application needs. <strong>What we really needed was a way to process our daily personalization workload in batches using our own fine tuned models — sending all requests efficiently and retrieving results reliably, without having to manually supervise millions of individual API calls.</strong></p><p><strong>The Solution: Building BESH</strong></p><p>The problems we faced at iFood weren’t unique to food delivery — they’re fundamental challenges that any organization faces when trying to deploy LLMs at scale. We realized we needed to build our own solution.</p><p><strong>Solution?</strong> Use batch mode! This approach worked amazingly when we were using external APIs like Gemini and GPT models, but when we wanted to use our own fine-tuned models, we hit a wall. Oddly enough, at the time of starting the project, there was no open-source equivalent of OpenAI’s batch endpoint for self-hosted deployments.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sYY_snnCqnMlFkt9ofItCA.png" /></figure><p>That’s when we decided to build BESH (Batch Endpoint for Self-Hosted models) — an open-source equivalent of the OpenAI batch endpoint, specifically designed for self-hosted GPU clusters. It allows for higher utilisation, less complex set up and lower cost. You can find the code and setup instructions here: <a href="https://github.com/ProsusAI/BESH">https://github.com/ProsusAI/BESH</a>. It will simplify your data processing pipelines and make it easier to distribute your computer over multiple teams. BESH now processes 120B tokens daily and soon many more.</p><p>LLM inference throughput can be highly volatile depending on the size of the workload and the number of concurrent connections. Depending on whether you are optimizing for latency or throughput there are different approaches to handling concurrent requests. Additionally, having multiple applications sending requests adds another level of complexity.</p><blockquote>Cost of self-hosting is simple; pay per hour. So your making it cheaper means we want to get the most out of this hour!</blockquote><p>Enter BESH, the first open-source batch processing API designed specifically for self-hosted GPUs. Instead of wrestling with the complexity of real-time inference orchestration, BatchEndpoint lets you focus on what matters: processing your data efficiently and cost-effectively. By leveraging intelligent batching and optimized resource management, it transforms those underutilized GPUs into a powerhouse that can unlock a staggering 26x cost reduction compared to openai’s gpt-4.1-mini batch solutions.</p><h3>What Makes BatchEndpoint Exceptional</h3><p>BESH now processes 120B tokens daily. It turned non-viable ideas into viable ones and it is stimulating more new and bold ideas.</p><ul><li><strong>🚀 Intelligent Queue Management</strong></li><li><strong>⚡ Advanced Parallel Processing</strong></li><li><strong>🔄 Production-Ready Reliability</strong></li><li><strong>📊 Real-Time Analytics Dashboard</strong></li><li><strong>🎯 Enterprise-Scale Architecture</strong></li><li>💾 <strong>Persistent Storage</strong></li></ul><p>So far we did three major imporvements to BESH, improving security, scalebility and stability. Awesome teamwork.</p><h3>Architecture</h3><p>In essence we have tried to replicate the batch endpoint of OpenAI as much as possible to make the switch to it seamless. It supports both single-GPU and multi-GPU (8-GPU) deployments, with a load balancer to distribute requests across the available GPUs. The deployment will spin up the following:</p><ul><li><strong>FastAPI</strong>: OpenAI-compatible batch API with comprehensive file upload support (including compressed formats: gzip, zip, bz2). This also included a simple dashboard and all the batches were loaded on a Redis Queue.</li><li><strong>Postgres + Redis: </strong>We use Postgres for batch results accumulation and Redis for sending the jobs to the workers. Postgres powers the dashboard where we can track job failure rates, Token per 15 min and more.</li><li><strong>Worker Node:</strong> A simple Redis Queue consumer that loads the jsonl files and send the requests efficiently using an httpx client. We use sephamore to manage the total connections. For each deployment we have between 4–20 of these workers so we are never idle.</li><li><strong>Load Balancer</strong>: Including health checks, least connection load distribution, generous timeouts and enough worker connection to support it all.</li><li><strong>vLLM GPU deployment</strong>: Using <a href="https://vllm.ai/">vLLM</a>’s offical docker image.</li><li><strong>Persistence Storage</strong>: Batches and databases are stored on a docker volume to ensure continuation and s3 syncing.</li></ul><p>Overview of the services as mentioned in the docker-compose 8 gpu version.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ngi9Pm7SHSxW4fC9FmdYXg.png" /><figcaption>Mermaid diagram of the services</figcaption></figure><p>Expansion of the system can be achieved by just making adjustment to the loadbalancer. Since we have a load balancer with health checks, it automatically supports auto scaling.</p><h3>Dashboard</h3><p>Here are some screenshot from the BESH dashboard. Token Usage Timeline to monitor the clusters health over time and get some big numbers out of the system like the 64B tokens in 24h! Our record stands at 120B, if you beat this please send us a screenshot ;).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/994/1*o9juft2EvPW-uQ1rs3d6fQ.png" /><figcaption>Token Usage: 64B tokens in the last 24h, very consistent, very stable.</figcaption></figure><p>Quick overview of the current state of processing. Here we see 19K batches to process and 25K processed in 24h.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/992/1*YD9T3uXGTxgsuGyTTzlHMQ.png" /></figure><h3>A 26x Cost Reduction</h3><p>To illustrate the cost savings, let’s examine a real-world batch processing scenario with actual token usage data and something that now runs daily. For this example we achieved 81% utilisation, by being busy 20h out of 24h. The table below compares the cost of processing a large-scale batch job using gpt-4-mini versus BatchEndpoint.</p><blockquote>Finetuning a model that can achieve the same task as gpt-4.1-mini is only one part of the solution.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_Qds7c8Bfgdi-ZTaEICT1g.png" /><figcaption>For batch as of 2025–08–29 as shown on <a href="https://openai.com/api/pricing/">https://openai.com/api/pricing</a></figcaption></figure><h3>Conclusion</h3><p>BESH represents a significant leap forward in making large-scale LLM inference both accessible and affordable. But here’s the thing — it’s not just about the impressive 26x cost reduction (though your finance team will definitely appreciate that). It’s about building something that actually works the way you’d expect it to.</p><p>The combination of optimized batching, intelligent queue management, and multi-GPU scaling creates a solution that’s genuinely built for the real world. No more worrying about whether your data scientists are trained to utilise the cluster most effectively and timing certain workloads to not interfere.</p><p>What makes this particularly exciting is how seamlessly it integrates with platforms like Databricks. We’re not asking you to rebuild your entire data pipeline — we’re giving you a tool that slots right into what you’re already doing. Also since it’s made for bare metal, any cloud provider can use it.</p><p>As LLM adoption continues to accelerate, having a solution like BESH isn’t just nice to have — it’s becoming essential. Whether you’re processing massive datasets, building AI-powered applications, or just trying to make sense of your LLM costs, BESH gives you the performance and reliability you need without the enterprise-level price tag.</p><p><em>Shout out to the iFood and Prosus AI teams for the teamwork on this project.</em></p><h3>co-authors</h3><ul><li><a href="https://www.linkedin.com/in/mateusaguiarflorentino/"><strong>Mateus Aguiar</strong></a></li><li><a href="https://www.linkedin.com/in/gabiborges1/"><strong>Gabriela Borges</strong></a></li><li><a href="https://www.linkedin.com/in/pvanderboor/"><strong>Paul van der Boor</strong></a></li><li><a href="https://www.linkedin.com/in/madeline-duncan-361787167/"><strong>Madeline Duncan</strong></a></li></ul><p>source: <a href="https://github.com/ProsusAI/BESH">https://github.com/ProsusAI/BESH</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=87570f8d2f35" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/besh-an-open-source-batch-endpoint-for-unlocking-4x-utilisation-of-your-self-hosted-gpu-cluster-87570f8d2f35">BESH: An Open Source Batch Endpoint for Unlocking 4x Utilisation of Your Self Hosted GPU Cluster</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Hidden Cost of UUIDs in AI Prompts: A 95.6% Token Optimization Solution]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/the-hidden-cost-of-uuids-in-ai-prompts-a-95-6-token-optimization-solution-007c1f965db2?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/007c1f965db2</guid>
            <category><![CDATA[uuid]]></category>
            <category><![CDATA[prompt-engineering]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[context-engineering]]></category>
            <dc:creator><![CDATA[Floris Fok]]></dc:creator>
            <pubDate>Wed, 20 Aug 2025 14:48:13 GMT</pubDate>
            <atom:updated>2025-08-20T14:48:13.862Z</atom:updated>
            <content:encoded><![CDATA[<h3>TLDR;</h3><p>Universally Unique Identifiers (UUIDs) are silently consuming massive amounts of tokens in AI prompts, creating an invisible cost burden for organizations worldwide. The analysis reveals that standard UUIDs consume an average of <strong>22.83 tokens each</strong>, while simple numeric alternatives require only <strong>1 token</strong> — representing a remarkable <strong>95.6% reduction</strong> in token usage.</p><p>This inefficiency compounds rapidly in enterprise environments where prompts may contain dozens of UUIDs, leading to exponentially increased processing costs. Since token consumption is linearly related to both processing time and monetary costs, this optimization directly translates to proportional savings in both computational resources and operational expenses.</p><h3>The Problem: UUID Tokenization Inefficiency</h3><p>UUIDs have become the standard for unique identification in modern distributed systems, but their impact on AI processing costs has been largely overlooked. A typical UUID like a90d0d7d-9c5a-44de-8d3c-5b0da661de7c appears to be a simple identifier, yet it consumes nearly 23 tokens of processing capacity.</p><p>The analysis of over 1000 randomly generated UUIDs across different formats revealed consistent inefficiency patterns:</p><ul><li><strong>Standard UUIDs</strong>: 22.83 tokens average</li><li><strong>UUIDs without hyphens</strong>: 18.5 tokens average</li><li><strong>Uppercase UUIDs</strong>: 22.83 tokens average</li><li><strong>Numeric alternatives (001, 002, 003)</strong>: 1 token each</li></ul><p>The tokenization inefficiency stems from how language models process hexadecimal strings and hyphen separators, which do not align with natural language patterns that tokenizers are optimized to handle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9KuIVExpRLiLeLJq" /></figure><h3>Example</h3><p>In practical applications, this inefficiency creates substantial overhead. Consider a typical enterprise scenario analyzing user behavior with multiple identifiers:</p><p><strong>Original prompt</strong> (295 tokens):</p><pre>Analyze user interactions:<br>1. User f47ac10b-58cc-4372-a567–0e02b2c3d479 logged in at 2024–01–15 09:30:00<br>2. User f47ac10b-58cc-4372-a567–0e02b2c3d479 accessed document 6ba7b810–9dad-11d1–80b4–00c04fd430c8<br>3. User f47ac10b-58cc-4372-a567–0e02b2c3d479 shared document 6ba7b810–9dad-11d1–80b4–00c04fd430c8 with user 6ba7b811–9dad-11d1–80b4–00c04fd430c8</pre><p><strong>Optimized prompt</strong> (100 tokens):</p><pre>Analyze user interactions:<br>1. User 001 logged in at 2024–01–15 09:30:00<br>2. User 001 accessed document 002<br>3. User 001 shared document 002 with user 003</pre><p>This represents a <strong>66.1% token reduction</strong> in a single prompt. Since processing time and costs scale linearly with token count, this optimization delivers proportional improvements in both speed performance and expenses.</p><h3>The Solution: Numeric Identifier Mapping</h3><p>The solution leverages a fundamental insight: UUIDs only need to maintain uniqueness within the context of a specific AI analysis, not globally. By temporarily replacing UUIDs with compact numeric representations during processing, we achieve optimal tokenization efficiency while maintaining complete system compatibility.</p><h3>Implementation Workflow</h3><p>The optimization process follows a simple, transparent workflow:</p><ol><li><strong>Original Prompt with UUIDs</strong> → Detect and extract all UUIDs</li><li><strong>UUID Detection &amp; Mapping</strong> → Create bidirectional mapping table (UUID ↔ Number)</li><li><strong>Optimized Prompt</strong> → Replace UUIDs with sequential numbers (001, 002, 003…)</li><li><strong>LLM Processing</strong> → AI processes the token-efficient prompt</li><li><strong>Optimized Output</strong> → AI responds using numeric identifiers</li><li><strong>UUID Restoration</strong> → Convert numbers back to original UUIDs</li><li><strong>Original Output</strong> → Final response with UUIDs restored</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dslsrjaA4UXDAQOJ" /></figure><p>This process is completely transparent to both the AI system and end users, requiring no changes to existing infrastructure while delivering immediate optimization benefits.</p><h3>Key Implementation Benefits</h3><ul><li><strong>Zero architectural changes required</strong></li><li><strong>Complete system compatibility maintained</strong></li><li><strong>Immediate deployment capability</strong></li><li><strong>Reversible and error-safe process</strong></li></ul><h3>Token Savings Analysis</h3><p>The testing across multiple realistic scenarios demonstrates consistent and substantial token reductions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/1*VxpHvnkG5JwKEmEmZsff3A.png" /><figcaption>Table of token reduction per scenario</figcaption></figure><p>These results demonstrate that UUID optimization delivers consistent benefits across diverse application scenarios, with token reductions typically ranging from 59% to 74%. The consistency of these improvements indicates broad applicability and reliability across different types of AI analysis tasks.</p><h3>Financial and Performance Impact</h3><p>Since token consumption directly correlates with both processing time and monetary costs, the optimization benefits scale linearly across both dimensions. Organizations processing UUID-heavy prompts can expect proportional improvements in:</p><ul><li><strong>Processing Speed</strong>: Reduced token count leads to faster AI response times</li><li><strong>Computational Costs</strong>: Lower token usage translates directly to reduced API expenses</li><li><strong>System Capacity</strong>: More efficient prompts enable higher throughput within existing infrastructure</li></ul><p>Here are some back-of-the-envelope calculations:</p><ul><li><strong>1,000 prompts daily</strong> with 5 UUIDs each: <strong>$800 annual savings</strong></li><li><strong>100,000 prompts daily</strong> with 12 UUIDs each: <strong>$200,000 annual savings</strong></li></ul><p>These projections assume ONLY input and GPT-4.1 pricing ($0.02 per 1,000 input tokens). We can easily double the saving if we expect many UUIDs in the output as well.</p><h3>Github Code</h3><p>Want to try this ASAP? Here is an example implementation of the method:<br><a href="https://github.com/FlorisFok/PromptCleaner">https://github.com/FlorisFok/PromptCleaner</a></p><h3>Conclusion</h3><p>UUID tokenization represents a significant but easily addressable inefficiency in AI prompt processing. The 95.6% token reduction achieved through numeric replacement delivers immediate benefits that scale linearly with usage volume, processing time, and operational costs.</p><p>Organizations can implement UUID optimization quickly and capture these benefits. The transparent, compatible nature of the solution makes it ideal for production deployment without architectural changes or operational disruptions.</p><p>As prompt engineering is becoming more important as all other aspects of the pipeline are maturing, techniques like these are essential and can make the difference with your competitors.</p><p>The evidence is clear: every UUID in your AI prompts is consuming 22 times more tokens than necessary. The question is not whether to implement optimization, but how quickly you can deploy it to start capturing these substantial benefits ;)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=007c1f965db2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/the-hidden-cost-of-uuids-in-ai-prompts-a-95-6-token-optimization-solution-007c1f965db2">The Hidden Cost of UUIDs in AI Prompts: A 95.6% Token Optimization Solution</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Just Ask: Data Insights for Everyone]]></title>
            <link>https://medium.com/prosus-ai-tech-blog/just-ask-data-insights-for-everyone-a21057d24263?source=rss----6d4088d5a9cd---4</link>
            <guid isPermaLink="false">https://medium.com/p/a21057d24263</guid>
            <category><![CDATA[insights]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[sql]]></category>
            <dc:creator><![CDATA[Ioannis Zempekakis]]></dc:creator>
            <pubDate>Tue, 10 Sep 2024 07:59:16 GMT</pubDate>
            <atom:updated>2024-09-10T07:59:16.566Z</atom:updated>
            <content:encoded><![CDATA[<p>There’s No Such Thing As a Dumb Question: Data Insights in Natural Language.</p><p><strong>Authors</strong>: Donne Stevenson, Floris Fok, Sean Kenny</p><p><em>Every day, people chase analysts for business insights and answers to their questions</em></p><p>“What’s the 12-month trend for active partners with at least eight monthly orders?”</p><p>“How do late-night food orders compare between different cities?”</p><p>“How many couriers have received a 5-star review in the last 30 days in the city of London, broken down per vehicle type?</p><p>Companies in sectors like food delivery, retail, and payments generate vast amounts of data. Being able to give employees accurate, contextualized, and prompt answers to questions about this data can boost their performance, drive value and build competitive advantage.</p><p>Ideally, employees have access to analysts who can turn business questions into data-driven insights. However, good analysts are often costly and scarce, and they need to focus on the most critical tasks. This makes it harder for many organizations to access insights and data for decision-making.</p><p>To understand how GenAI frameworks and tools can help address this problem, we teamed up with operational analysts at food delivery platforms iFood and Glovo, e-commerce marketplace OLX, and other companies in the Prosus portfolio.</p><h3>Introducing the Toqan Data Analyst</h3><p>The result of our ongoing work is the Toqan Data Analyst, an AI-powered chatbot integration for databases, available to Toqan users in the business messaging app, Slack. The tool makes data and insights accessible to anyone who needs them, when they need them, in natural language.</p><p>People can ask questions in a simple chat interface. They don’t need any technical skills, nor be deeply familiar with the databases, since the tool generates SQL queries from their text. Our text-to-SQL solution has hundreds of active users and answered thousands of questions during the roll-out.</p><p>In this blog, we dive into how we got to this solution. We explain how we had to rethink the text-to-SQL problem space, how that led to us to expanding our approach beyond existing text-to-SQL solutions, and how we overcame significant challenges along the way.</p><h3>LLMs are only part of the solution</h3><p>First, we looked at the use of large-language models (LLMs) for text-to-SQL. Existing solutions can translate questions into computer language, reason through uncertainty at scale, and clarify implicit assumptions.</p><p>However, benchmarking available LLMs against SQL datasets suggests this solution would be insufficient. Early frameworks for assessing the performance of models on text-to-SQL focused on a model’s ability to generate SQL queries that address user questions. Top results from WikiSQL’s leaderboards approached 93% accuracy on the test set for accurately generating SQL queries based on natural-language questions. But these early results came from various pre-transformer supervised learning models and focused on highly simplified use cases that lacked joins between tables in datasets, or other real-world complexities.</p><p>LLMs proved to be broadly capable of achieving similar levels of accuracy on text-to-SQL tasks. Benchmarks for LLM capabilities, such as Toqan’s <a href="https://prollm.toqan.ai/">ProLLM Leaderboard</a>, peg the latest models at a reasonable acceptance level, with top performers around <a href="https://prollm.toqan.ai/leaderboard/coding-assistant?type=debugging,implementation,conceptual,optimization&amp;level=intermediate,beginner,advanced&amp;tag=sql">85–90% acceptance of SQL responses based on Stack Overflow data</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/758/1*qBtEMG6a6Hu8hVZkOeBzuw.png" /></figure><p>On paper, AI models should be able to formulate SQL queries with 80%+ accuracy</p><p>However, most systems are not adequately able to handle real data and thus inevitably fall short of expectations. Challenges like identifying how to <strong>join tables</strong>, spotting columns despite poor metadata, and finding every way of spelling a name quickly made these benchmarks largely useless. Our out-of-the-box use of LLMs for text-to-SQL for use cases with more than two tables started with less than 25% accuracy.</p><p>There are <strong>two</strong> fundamental problems. <em>First,</em> even 95% accuracy is not enough for many business users querying large datasets of transactional, supply chain, or even financial data. <em>Second,</em> the ability to get that final slice of accuracy does not appear to be related to a model’s performance, so much as the context that the system has about the person asking the question and the business and data structure being examined.</p><h3>Context is critical</h3><p>We like to think that most databases are straightforward; they are relational databases with fixed structures. However, even with the latest SQL knowledge, newly hired data analysts will still struggle initially. This struggle points to a hidden truth: context is critical to translate business questions into effective data queries. In other words, additional context is needed to effectively use business data. People usually gather this context while talking to colleagues, attending meetings, or through trial and error.</p><p>When it comes to rule-based or early data analysis AI solutions, many real-world applications fall short not only because of missing context but also because of poor data quality, lack of documentation, user behavior, and more. The reality is that expecting all business users to follow the best practices, work with perfect data, with no implicit or missing information, will never result in a viable solution.</p><h3>Building a text-to-SQL solution for the real world</h3><p>Instead of only focusing on generating the right query, we needed to expand our requirements. Working with analyst teams with access to Toqan, we started to experiment with system solutions.</p><p>Things to account for include:</p><p>● Implicit assumptions and transparency</p><p>● Guiding users and clarifying requests</p><p>● Handling failed answers and knowing when not to answer; minimizing false positives.</p><p>If we consider requirements like these, the challenges with the previous approach become clear. Instead of focusing on writing queries and getting answers, we needed to build a system that has the context and even the ability to inquire about implicit context.</p><p>Text-to-SQL is not only an engineering problem; it’s also a communication problem.</p><p>From a technology perspective, the next advance we needed was flexible, sequential planning and tool use provided by agent frameworks. While building our AI assistant Toqan, we experienced the incredible reasoning capabilities of agents and were sure that it was a matter of time and effort before we could make text-to-SQL something real.</p><h3>What makes <a href="https://toqan.ai/blog/the-emerging-agent-ops-landscape">agents</a> perfect for data analysis</h3><p>An <a href="https://toqan.ai/blog/the-emerging-agent-ops-landscape">agent</a> can follow instructions in a chat-like manner, reason and use external services to aid its understanding. This makes it the best problem-solver to date. The traditional chatbot, which answers simple questions, has limited reasoning capability. Also, it needs all the information to answer a question in advance since it only works from memory.</p><p>An agent doesn’t have these limitations, however. If we want to make an agent that finds the best deals online, we need to provide it with a way to navigate the internet, and it will do a very decent job. Agents can explore external data and have conversations with themselves like: “I will first do A, then I will solve B to bring you answer X.” These problem-solving capabilities make an agent perfect for data exploration and data-related analysis. When given access to a database and enough context about the data in it, an agent will generate code to analyze the data and answer any questions.</p><p>(For a more detailed view on agents, read also <a href="https://toqan.ai/blog/the-emerging-agent-ops-landscape">https://toqan.ai/blog/the-emerging-agent-ops-landscape</a>)</p><h3>Helpfulness is the enemy of accuracy</h3><p>Building an agent that can turn a question into an effective SQL query comes with some challenges though.</p><p>Let’s look at the first issue: being too helpful. Large Language Models (LLMs) — key components of agents — are smart and can develop logical processes, which means they may find solutions that are not straightforward for the person asking. This is one of the best examples we found:</p><blockquote>User: What is the Address of user X.</blockquote><blockquote>Bot: Here are the driver’s coordinates during delivery.</blockquote><p>The LLM did not have access to addresses. Still, it was smart enough to work out that the driver’s coordinates during delivery must be near the address of user X. Although the system provides an answer, the best answer would be: “Sorry, I do not have access to addresses.”</p><p>To help mitigate this problem, we embed the options to use two tools in the agent:</p><ol><li>Ask clarifying questions</li><li>State assumptions</li></ol><p>The first tool tries to remove uncertainty at every step. When we add information to the system, it evaluates whether it’s ambiguous and if so, asks clarification questions. This will make the question more specific and changes users’ behavior in two ways: it ensures expectations are more aligned, and it trains people to ask better questions.</p><p>The second tool is designed to ensure that if the agent takes shortcuts or uses creative solutions, the user can spot them more easily. We call this “perceived accuracy,” where given the knowledge it concluded X, answer Y is correct. An example we use frequently for this is:</p><blockquote>U: Can you provide the average revenue per day for X in the last month?</blockquote><blockquote>B: That was …<br> Assumptions: “Given a month is defined as from today until the same day last month.”</blockquote><p>Since we now know this is how the agent defines a month, we can’t argue that the question is false. So, we either correct it or accept it. Like asking clarifying questions, we teach the agent to ask <em>better</em> questions, aiming to be more explicit in its communication and transparent to users.</p><h3>Hidden context</h3><p>The second issue we ran into: people use nuances and context that even the smartest multimodal models struggle with. This is a good example of what can happen when a system moves from concept to real-world testing.</p><p>The missing information — or the hidden context — falls into four groups:</p><ol><li><strong>Global knowledge: </strong>The general structure of the organization, details about the business and its partners, and what kind of system the database is.</li><li><strong>Product knowledge: </strong>Each table is designed to serve some KPI or product insights; this is valuable information that can pivot the balance between right and wrong at the right moment.</li><li><strong>Table and column knowledge: </strong>Are tables slow or fast-changing, partitioned, or in a special date format? With this information, the model doesn’t have to reinvent the wheel.</li><li><strong>Request language knowledge:</strong> Team jargon, acronyms, or other language that’s unique to a specific team.</li></ol><p>Gathering this information comes with the engineering challenge of knowing <strong>when and what</strong> is useful. Some solutions focus on embedding retrieval (ER) but we did not see it as a good fit since ER focuses on matching subjects and topics. We want the agent to see the rules without knowing if they may be needed. We do this by implementing three mechanisms that improve the request:</p><p><strong>1. Business Rules</strong></p><p>These rules cover the global knowledge and a large part of the product knowledge. This is the simplest but also the most powerful concept. We want the model to always be exposed to this knowledge. To do so, we include this in the system prompt together with other guidelines and behavioral rules.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/936/1*hA-WvpBcpsqX-UiKad_j2A.png" /></figure><p><strong>2. Glossary</strong></p><p>In its purest definition a glossary is a list of terms in a special subject, field, or area of usage, with accompanying definitions. “Terms” may also involve acronyms and short phrases. The goal of this is to add these definitions to the request if we find that they are used in the user’s requests.</p><p><strong>3. Table selection</strong></p><p>Storing a lot of information in the columns and table descriptions is useful. However, if we add all this information to the prompt, we have very little control over changing the behavior if it is not picking up the correct details for a given request. To tackle this, we need a separate system that can judge which information is valuable for the request. This second system will first assess which tables might be useful. Secondly, it collects the columns that are needed from these tables. Thirdly, it assesses if, given the complete overview, it could drop some parts of the result if these are duplicates, or now redundant. Now we can add very specific information and even instructions into descriptions without these influencing any request where they are not needed.</p><p>By combining these <strong>three</strong> <strong>systems</strong> we can control the information going into the text-to-SQL agent.</p><h3>Testing our SQL agent</h3><p>Lastly, some words on testing (more on testing <a href="https://medium.com/prosus-ai-tech-blog/prollm-llm-benchmarks-for-real-world-use-cases-7c180df68306">here</a> ). Testing has many advantages, with the two most important being:</p><p>● Confidence to make changes,</p><p>● Direct feedback from your changes (fast iteration cycle)</p><p>We saw a real challenge in creating a test set that represents the production use cases. In this blog, we highlight how we achieved automated testing. For the test set, we need a question and query.</p><p>We start by running the “ground truth” query to fetch the most recent data, ensuring our agent’s answers are based on the latest information. Next, we ask the agent the same question multiple times in a slightly different way. For example, “What is the capital of France?” and “Which city is the capital of France?”. This helps us evaluate the agent’s robustness.</p><p>We use a prompted model to check if the answers are similar, focusing on the core response and ignoring minor differences. For each question, we measure:</p><p>• <strong>Accuracy</strong>: How close the agent’s response is to the expected answer.<br>• <strong>Consistency</strong>: How uniform the responses are across different question formulations.</p><p>Based on these metrics, we identify areas for improvement. High accuracy but low consistency indicates correct answers but with variability. Low scores in both suggest the need for further training. These insights help refine the agent’s training data and algorithms.</p><p>Finally, with automated testing in place, we are now focusing on improving the framework.</p><h3>Next steps</h3><p>We’re working on making our SQL agent even better. Some of the things we’re looking into:</p><ol><li><strong>Self-learning:</strong></li></ol><p>We want to use successfully generated or upvoted SQL as a source for generating snippets that resemble (parts of) requests.</p><p><strong>2. Reformulating the request before execution:</strong></p><p>The agent will reformulate your question to one that is non-ambiguous. By putting more effort into the request before execution, we limit the number of queries to the database.</p><p><strong>3. Validation agent</strong></p><p>By asking the agent “Are you sure?” we can make sure the query is correct. We are looking into automating this by adding a supervisory validation agent.</p><p>This is just the beginning, stay tuned for updates!</p><p><strong>PS:</strong> If you would like to know more, please contact our <a href="http://toqan.ai/">Toqan.ai</a> team in</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a21057d24263" width="1" height="1" alt=""><hr><p><a href="https://medium.com/prosus-ai-tech-blog/just-ask-data-insights-for-everyone-a21057d24263">Just Ask: Data Insights for Everyone</a> was originally published in <a href="https://medium.com/prosus-ai-tech-blog">Prosus AI Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>