Skip to content
arbelxez

PÍDELOOrdering agent

The order goes straight to the business WhatsApp at 0%

PÍDELO is a WhatsApp agent that serves the customers of a food business and hands the order to the business itself, with no platform in the middle taking a slice of every plate. Tierra Querida S.A.S. runs the system across 33 locations since April 2026: 63,357 orders processed, COP 3,308 million billed, 97.6% handled by the agent. The agent works through eight tools, knows when to stop and pass the chat to a person, and is traced end to end.

  1. 01

    The cut nobody sees

    A neighborhood food business sells through delivery platforms, and a huge commission comes off every sale. That is not a software fee. It is a percentage of the plate, charged on the final price, order after order. The customer does not belong to the business either: the phone number, the order history and the next purchase all live on the platform side. When the margin is already thin, that subtraction decides whether the place opens next month.

    PÍDELO turns the relationship around. The customer writes to the business WhatsApp, the number already saved in the phone, and an agent answers there. It knows the menu, the opening hours, where the shop is and whatever the business has written down to answer questions. The order lands directly in the register and the business collects the money. Commission per sale is 0%: you pay for the software, not for a slice of every plate you sell.

    The system has served 55,120 customers, and only part of that carries AI. The desktop register and the ESC/POS thermal printer adapter are deterministic on purpose. Once an order is confirmed, nobody wants a model deciding what prints in the kitchen. The intelligence belongs to the conversation. Past that line, ordinary code is in charge and it should stay that way.

  2. 02

    The agent taking orders

    At the center is a tool-calling loop built on the Vercel AI SDK. Every message enters a generateText call with the tool catalog attached and toolChoice on automatic, so the model decides whether to answer, to look something up, or to act. The loop does not wander. The stopping condition pairs a step ceiling with a terminal tool: when the tool that closes the turn fires, the cycle ends right there, however much the model would like to keep going.

    The customer channel carries eight tools, each with a narrow job. prepareOrder assembles the order and leaves it ready to confirm; createOrder actually books it. sendMenuImage sends the menu, sendLocation the address, sendSchedule the opening hours and sendWelcomeMessage opens the conversation with a first-time customer. fetchKnowledgePackage answers a specific question, and escalateToHumanAgent hands the conversation to a person.

    Whatever a tool returns is validated with Zod, so the order arriving at the register has a known shape before it touches the database. The knowledge base is not queried with free text either: the agent picks a topic from a closed list, which leaves it no room to invent an answer. Voice notes are transcribed with Whisper and enter the same loop as text, because a customer ordering by audio is ordering just as seriously.

  3. 03

    Knowing when to step aside

    An agent that cannot give up is a business problem, not a technical curiosity. The one that keeps pushing through what it does not understand ends up taking a wrong order, promising a delivery that will not happen, or arguing with an upset customer on the business's official number. In a real shop that costs money and reputation, and it costs both on the same day.

    So escalateToHumanAgent carries four fixed reasons rather than open judgment: the customer asks for a person, the customer repeats a complaint, the customer shows plain frustration, or the request falls outside what the agent covers. They are written closed like that so the decision is recognizable in the trace and does not depend on the mood of the model that day. When it fires, the turn is over: the tool is terminal and the agent stops talking.

    Designing the exit takes more work than designing the answer. Making a model reply every time is easy. Making it recognize the edge and hand over cleanly, with the whole conversation on the human side and nothing the customer has to repeat, is the hard part. That door is what lets a business owner leave the agent answering without watching it.

  4. 04

    Keeping it standing

    Model providers fail, and they fail at lunch hour. Every model has its own circuit breaker in Redis, with the state machine written as atomic Lua scripts: closed, open and half-open, with a recovery probe and exponential decay on the cooldown. Because it resolves inside Lua, the transition happens in a single operation, so two workers failing at the same instant cannot leave the breaker in an impossible state.

    When a model goes down, the fallback chain moves to the next provider without the conversation noticing. On top of that sits a budget shared across workers, with two buckets: requests per minute and tokens per minute. The pipeline is asynchronous the whole way, from the Meta webhook to SQS, on to BullMQ, the worker, the model, and back out through the Meta API and a WebSocket, with distributed locks, deduplication and a fairness gate between chatbots.

    Even then the model sometimes returns garbage. Before anything reaches the customer there are quality passes: if the text comes back empty, one is synthesized from what the agent just did; if it arrives with markup or written in another alphabet, it gets corrected; if the tool flow breaks, a tool-free recovery still leaves the customer answered. None of that is elegant. All of it is the difference between a demo and a shop that stays open.

  5. 05

    Running it day to day

    Prompts do not live inside the code. They are a versioned CMS with draft, published and archived states, plus variants and dynamic variables. You change the tone of the welcome or fix a rule about the menu and it goes live without a deploy. Nothing is overwritten, so odd behavior traces back to the exact text that caused it, and rolling back is just republishing the wording that worked.

    The whole loop is traced in Langfuse over OpenTelemetry, with separate tracers per channel so customer traffic and admin traffic never blur together. On top of that I keep my own metrics for cost, for tokens and for errors per tool. That last one has earned its keep, because when a tool starts failing more than it should, the problem is almost always the text describing it rather than the model calling it.

    Conversation memory lives in DynamoDB turn by turn, tool calls included, and it rehydrates between retries: if a worker dies halfway through an order, its replacement picks up with the same context. There is also a second channel for the owner, with three read-only tools, active orders, business report and financial breakdown, and an explicit rule against taking orders there. Looking things up and selling are two different permissions.