The demos are immaculate. The model drafts a product description in twelve seconds, answers a support query with footnoted citations, or summarizes a dense legal brief into three crisp paragraphs. The room nods. Someone mentions pilot timelines. Another asks about cost per query—handwaved, because tokens are cheap now, right? Six weeks later, the thing is hemorrhaging money, the accuracy has gone feral, and the infrastructure team is paging you at 2 AM because the retrieval layer is timing out under real load.
This pattern repeats with metronomic regularity. Not because teams are careless. Because production LLM systems are fundamentally unlike the single-threaded notebook demos that sold the project. The demo is a lie of omission—it shows you the happy path, the case where the stars align: clean input, relevant context, a model that happens to know the answer. Production is the unmarked minefield that begins the moment you accept arbitrary user input and promise reliability.
The Demo Trap: Where Clean Inference Meets Messy Reality
A demo typically involves one API call. You craft a prompt, hit an endpoint, stream back some tokens. The illusion is that this is the application. It isn't. What you've built is a request router sitting atop a rickety distributed system you didn't design and probably can't see.
Consider the actual data flow when a user asks your customer service bot a question. First, input sanitization—you need to detect prompt injection attempts, strip PII if you're in a regulated industry, and maybe redact credentials someone pasted by mistake.
Then intent classification: is this a refund question, a technical issue, or someone trying to jailbreak the system by asking it to ignore prior instructions? That's often a separate classifier or a lightweight LLM call. Next comes retrieval: you query a vector database for relevant knowledge chunks, maybe cross-reference a structured SQL table for account details, and pull the three most recent support tickets. Each of these is a network hop with its own latency budget and failure modes.
Now, you assemble context. Chunking strategy matters here—did you split documents on sentence boundaries, fixed token windows, or semantic sections? If your chunks are too small, the model lacks coherence. Too large and you waste tokens on irrelevant preamble, blowing your context budget before the user's question even arrives. You serialize this into a prompt, often with a system message, a few-shot example, the retrieved docs, and the user query. That's another 2,000 tokens before inference starts.
The LLM call itself is the part everyone focuses on. It's also the part you control least. You send a request, wait 800 milliseconds (or three seconds if the provider is under load), and parse a response. The model might return JSON. Might return markdown. Might return an apology for something you didn't ask about because the prompt leaked signal from a prior turn you thought you'd isolated. You need a parser that fails gracefully when the model invents a new schema.
Then, policy enforcement. Did the model hallucinate a refund policy that doesn't exist? You need guardrails—another LLM call, or a rules engine that checks output against ground truth. Logging comes next: you have to persist the raw prompt, the completion, the retrieval results, the latency breakdown, and any user feedback for later debugging. Formatting and delivery are last—rendering markdown, localizing timestamps, and injecting disclaimers.
Each of these stages is a microservice boundary in disguise. Traditional software crashes with stack traces when something breaks. LLMs don't crash. They confabulate. They return a coherent-sounding answer synthesized from fragments of misaligned context and pretraining artifacts. The user sees a response. You see a 200 status code. Nobody knows the answer is wrong until someone escalates three days later or a regulator sends a letter.
The Retrieval Problem No One Wants to Talk About
Here's the inconvenient truth: most "LLM failures" are retrieval failures wearing a model's voice.
You cannot stuff all your knowledge into a prompt. Even the new million-token context windows are mirages for real applications—cost and latency scale quadratically with context length, and model attention degrades over long sequences, no matter what the benchmarks claim. So you use RAG. You embed your documents, store vectors, and retrieve the top-k chunks at query time. This works in the demo because you've indexed ten handpicked PDFs and tested five curated questions.
Production is a different organism. Your knowledge base is now 40,000 documents, half of them outdated, a quarter mislabeled, 10% in formats your embedding model wasn't trained on. Users ask questions that span multiple documents. They use jargon; your chunking strategy is fragmented across boundaries. They reference version 2.3 of a policy when your vector search returns the deprecated 1.8 text because it has higher lexical overlap.
The model isn't hallucinating out of malice. It's synthesizing from what you gave it. If your retrieval is wrong, the answer will be fluent and wrong. This is worse than a null result because it looks authoritative. You tune the model. You adjust the temperature. You write increasingly elaborate system prompts begging it to say "I don't know." None of this addresses the root cause: the retrieval pipeline is returning garbage.
Building robust retrieval is unglamorous infrastructure work. It means chunking the same document three ways—semantic sections, sliding windows, and sentence clusters—and indexing all of them. It means a hybrid search that combines dense embeddings with BM25 keyword matching because some queries are lexical ("contract clause 4.7.2") and others are conceptual ("situations where we'd owe penalties"). It means metadata filters so you can scope retrieval to the correct product line, jurisdiction, or version without polluting the vector space.
It also means feedback loops. You need humans to label whether the retrieved chunks were relevant. You need to A/B test chunking strategies and measure precision@k on real queries, not synthetic ones you invented. You need to version your embeddings and reindex when the model changes because switching from text-embedding-ada-002 to text-embedding-3-large isn't a drop-in upgrade—the vector spaces aren't compatible.
Most teams underinvest here. They treat retrieval as a solved problem because vendors sell managed vector databases with uptime SLAs. But reliability isn't accuracy. You can have a database that never goes down and still serve a completely irrelevant context. That's not a database failure. It's a you failure, because you didn't build the scaffolding to measure and improve what matters.
Cost: The Budget Ambush
In the demo, you spend twelve cents. In production, you're on track to spend twelve thousand dollars a month, and nobody can explain why.
Token costs are a tax on carelessness. Every time you re-embed the same chunk because you're not caching, you're burning money. Every verbose system prompt that restates instructions the model already learned in pretraining is a waste. Every retry loop that doesn't exponentially backoff or cap attempts can spiral into a runaway cost event when your upstream service hiccups.
The worst part? Costs are largely invisible until the bill arrives. You don't get an alert when someone writes a prompt that accidentally balloons to 8,000 tokens. You don't see that one malformed query is triggering fifteen retries because your error handling is naive. Cloud vendors love this—every inefficiency is margin.
Careful builders instrument costs per request. You log token counts for every LLM call, every embedding, every retrieval. You set budget thresholds and kill requests that exceed them. You cache aggressively: embeddings at the chunk level, LLM responses for common queries, even partial prompt templates if your application has a predictable structure.
You also challenge the assumption that every task needs your flagship model. Does a binary yes/no classification really need GPT-4? Or can you route it to a fine-tuned gpt-4o-mini for one-tenth the cost? Can you precompute summaries overnight instead of generating them on demand? The objective isn't cheaper models—it's fewer, more intentional model invocations. Every call you don't make costs zero dollars and has zero latency.
Observability: Debugging Probabilistic Systems
Traditional software is deterministic. Given the same input, you get the same output. Logs and stack traces point you at the broken line. LLM systems are probabilistic. The same prompt can yield different outputs. Bugs are statistical, not binary—95% of queries work, but you can't predict which 5% will fail or why.
This breaks conventional debugging. You can't just reproduce the issue locally because the model's sampled output might be different the second time. You can't trust user reports because they'll describe the symptom ("it gave a wrong answer") but not the context that caused it.
You need forensic-level observability. Log the exact prompt sent, not your template. Log the raw completion, not the parsed version. Log retrieval scores and which chunks were selected. Log the model config: temperature, top-p, and the specific model version (because providers silently upgrade backend models). Log user feedback if they downvote a response.
Version your prompts like code. When you change a system message, tag it. When accuracy drops next week, you can bisect: was it a prompt change, a retrieval degradation, or a model update upstream? Without versioning, you're flying blind.
Build dashboards that show the metrics that matter: retrieval precision, average response latency, token cost per query, and rate of guardrail interventions. Surface outliers—the queries that took 7 seconds instead of 1, the ones that hit fallback logic, the ones where the model refused to answer. Those outliers are your debugging surface.
Safety and Guardrails: The Unglamorous Moat
Demos skip safety because it kills the vibe. Production can't.
Users will try to extract your system prompt. They'll inject instructions that override your rules. They'll ask the model to generate harmful content, leak training data, or just spam nonsense to probe boundaries. Some of this is malicious. Much of it is curiosity or accident. All of it needs mitigation.
Input validation is first. Reject overly long inputs, strip markdown that could confuse parsing, detect and block obvious injection patterns. This is a shallow defense—sophisticated attacks will evade regex—but it stops the script kiddies.
Guardrails come next. Run a secondary check on model outputs before serving them. Is the response PII-free? Does it contradict known facts in your database? Did it refuse a legitimate request because your system prompt was too restrictive? Some teams use a second LLM call here—expensive but effective. Others build rules engines for known failure modes.
Rate limiting is safety posing as scalability. It's not just about avoiding overload; it's about stopping abuse. One user shouldn't be able to monopolize your token budget or flood your logs.
Content filtering matters even for internal tools. A model trained on the internet has seen things you don't want it repeating in a corporate Slack channel. Vendors provide moderation endpoints. Use them, or build your own classifier if you have domain-specific risk (medical disinfo, financial advice, etc.).
None of this is exciting. It's the tax you pay for reliability. The difference between a prototype and a product.
What Changes on Monday Morning
You don't need to rebuild everything at once. Start with instrumentation. If you can't trace which prompt, context, and model version produced a given response, stop and fix that. Observability is the foundation for everything else.
Next, audit your retrieval. Manually review the chunks being returned for twenty random queries. Are they actually relevant? If not, you have a data problem, not a model problem. Improve chunking, metadata, or search strategy before you touch the LLM.
Then address cost. Set budget alerts. Log token usage per request. Cache everything you can. Question whether every task needs the expensive model.
Finally, build feedback loops. Let users report bad outputs. Review them weekly. Cluster failure modes. You'll find patterns—certain question types always fail, specific documents confuse retrieval, and edge cases your prompt didn't cover. Fix the top three. Repeat.
The trap is thinking you're building an AI product. You're not. You're building a distributed system that happens to have a language model in the critical path. The model is the least debuggable component. So, you armor everything around it: input validation, retrieval accuracy, output guardrails, cost controls, observability.
Do that, and the demo becomes a product. Skip it, and you'll be paged at 2 AM, staring at logs that tell you nothing, wondering why the model that worked last week is now confidently inventing refund policies that don't exist.
The system worked in the demo because you controlled every variable. Production is the variable you didn't anticipate showing up all at once. Build for that.