<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://www.joshclemm.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.joshclemm.com/" rel="alternate" type="text/html" /><updated>2026-01-10T11:08:52-08:00</updated><id>https://www.joshclemm.com/feed.xml</id><entry><title type="html">Is Ralph Wiggum the Future of Coding?</title><link href="https://www.joshclemm.com/writing/ralph-wiggum-future-of-coding/" rel="alternate" type="text/html" title="Is Ralph Wiggum the Future of Coding?" /><published>2026-01-04T00:00:00-08:00</published><updated>2026-01-04T00:00:00-08:00</updated><id>https://www.joshclemm.com/writing/ralph-wiggum-future-of-coding</id><content type="html" xml:base="https://www.joshclemm.com/writing/ralph-wiggum-future-of-coding/"><![CDATA[<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/ralphs-at-work.jpg" width="100%" />
<br />

</p>

<p>Imagine kicking off a refactor before bed and waking up to code that compiles, passes tests, and is already committed. No babysitting. No prompting “try again”. Just progress.</p>

<p>That sounds like the promise of autonomous AI coding we’ve been hearing for years. The difference is that this version may actually work.</p>

<p>The trick isn’t a new model or a breakthrough in reasoning. It’s something much simpler. People are calling it the <a href="https://ghuntley.com/ralph/">Ralph Wiggum</a> approach, created by <a href="https://ghuntley.com/">Geoffrey Huntley</a>.</p>

<h3 id="why-we-need-this---the-weakness-of-llms">Why We Need This - The Weakness of LLMs</h3>

<p>If you’ve spent time with AI coding agents, you’ve probably hit the same wall I have. The model starts strong, then slowly degrades. It forgets constraints. Reintroduces bugs it already fixed. Works great for new projects but struggles as the codebase grows.</p>

<p>Better agents and models like <a href="https://claude.com/product/claude-code">Claude code</a> and <a href="https://www.anthropic.com/claude/opus">Opus</a> help, but the problem remains.</p>

<p>These issues are related to fundamental limitations of LLMs. In my post <a href="https://joshclemm.com/writing/4-ways-LLMs-fail/">The 4 Ways LLMs fail</a>, I describe how LLMs struggle when given too much context. Benchmarks like <a href="https://github.com/adobe-research/NoLiMa?tab=readme-ov-file#nolima-hard-results">NoLiMa</a> show that accuracy degradation. It doesn’t matter if the input document is too large, you’re calling too many tools, your agent has been looping and adding too much context, or your codebase is growing in size and complexity. It’s all the same issue. And it’s been popularized as <a href="https://research.trychroma.com/context-rot">context rot</a>.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_nolima.jpg" width="500" />
<br />

</p>

<p>This is why the concept of <a href="https://joshclemm.com/writing/rag-leveled-up/">context engineering</a> has burst onto the scene. It’s critical to give an LLM the perfect amount of context to do its job and nothing more (avoid <a href="https://www.youtube.com/watch?v=rmvDxxNubIg">that dumb zone</a>!).</p>

<p>So to code in the most effective way, you need to engineer the context you provide your coding agents to be super precise. You need to keep your tasks tight, crispy defined, and with very clear success criteria (you know…tests).</p>

<p>And if you do that, you can keep running those tasks in a loop until they are complete. And if you have a number of tasks defined? You can have those tasks running while you sleep. That’s the Ralph Wiggum approach.</p>

<h3 id="the-ralph-wiggum-approach">The Ralph Wiggum Approach</h3>
<p>The name comes from the Simpsons character <a href="https://en.wikipedia.org/wiki/Ralph_Wiggum">Ralph Wiggum</a>. He keeps trying, no matter what. That’s kinda the whole philosophy.</p>

<blockquote>
  <p>“In its purest form, Ralph is a Bash loop.”</p>

  <p><cite>Geoffrey Huntley, creator of <a href="https://ghuntley.com/ralph/">Ralph</a></cite></p>
</blockquote>

<p>Wait, are you really just running your coding prompt over and over and over until it’s done? Isn’t that just the <a href="https://www.youtube.com/watch?v=JV-wY5pxXLo">“try again” approach in vibe coding</a>?</p>

<p>Well not exactly. It isn’t just a prompt in a loop. Because if you did that, you’ll still hit those context rot issues. And your prompt might loop forever fixing one issue and introducing another…</p>

<h3 id="what-it-actually-is">What It Actually Is</h3>

<p>The Ralph Wiggum approach requires a <strong>healthy amount of upfront work</strong>. You’ll want to break your work into smaller work items.</p>

<p>Think about the world of your team working on a 2-week sprint. You spend the first day documenting all the tasks that need to get done (and the smaller the task the better). Then each developer grabs a task, works on it, and when done checks it in, and once all tests pass and it’s committed, they move onto the next task.</p>

<p>That’s the right way to Ralph Wiggum.</p>

<p>In practice, it looks something like this:</p>

<ul>
  <li>Give the agent a clear, bounded task</li>
  <li>Define a hard completion signal (tests pass, specific output, etc)</li>
  <li>Let it loop until it succeeds or hits a cap</li>
</ul>

<p>Overall progress also doesn’t live in the ongoing agent conversation. It lives outside the model. You write progress to a file in the filesystem and use git commits. Then, each iteration can reference that info and start over in a clean context window.</p>

<p>A clean and precise context window will guarantee the most success out of your LLM coding agent. That’s why this approach works.</p>

<h3 id="why-this-matters---the-future-of-software-engineering">Why This Matters - The Future of Software Engineering?</h3>

<p>What’s interesting about Ralph isn’t the loop itself. It’s what it represents.</p>

<p>Software engineering isn’t just about the code. There’s a whole lifecycle that starts with understanding your users, figuring out requirements, deciding the UX design, thinking about architecture, finally getting it out to your users, etc.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer_lifecycle.jpg" width="100%" />
<br />

</p>

<p>As AI starts to change how we do coding, it puts pressure on the left side and right side of the “coding” step in the product development lifecycle. And this is especially true as coding becomes more autonomous using techniques like Ralph.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/sdlc-changes.jpg" width="550" />
<br />

</p>

<p>Teams that are deliberate on that left side in defining the right inputs will get the better outputs. Think better PRDs, better specs, and better exit criteria.</p>

<p>And teams that are deliberate on the right side in understanding how to validate the outputs will succeed in delivering more solutions faster. Think robust <a href="https://joshclemm.com/writing/the-premortem-software-engineering-best-practice/">testing cultures</a>, automated and comprehensive QA, and strong CI/CD practices.</p>

<p>Autonomy may not come from bigger prompts or longer context windows (or looping). It will likely come from process, culture, and being deliberate.</p>

<p>The craft is changing. We’re moving more <a href="https://joshclemm.com/writing/think-like-a-farmer/">towards the world of farming</a>. We’re going to need to obsess about the inputs, the architecture, the users, and then understand how to validate if it works. And if it doesn’t, have a learning culture, adjust our inputs, and try again!</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer.jpg" width="350" />
<br />

<i>Is the future engineer more similar with a farmer?</i>
</p>

<h3 id="conclusion">Conclusion</h3>

<p>The Ralph Wiggum approach works because it respects the limits of today’s models instead of fighting them.</p>

<p>Short contexts. Clear goals. Relentless iteration.</p>

<p>Write a <a href="https://www.youtube.com/watch?v=8rABwKRsec4">good spec</a>. Start the loop. Go to sleep.</p>

<p>If you did your job upfront and were deliberate in inputs and tests, you might wake up to working code.</p>

<p>And a very different way of building software.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/ralph-vibe.jpg" width="350" />
<br />

</p>]]></content><author><name></name></author><category term="AI" /><category term="LLM" /><category term="Software Engineering" /><category term="Leadership" /><category term="Engineering Management" /><summary type="html"><![CDATA[Autonomous AI coding doesn't fail because models aren't smart enough. It fails because we give them too much context, vague goals, and no hard definition of success. The Ralph Wiggum approach flips that on its head. Short contexts, brutally clear tasks, hard completion signals, and relentless retries. No magic. No new models. Just a process that actually works.]]></summary></entry><entry><title type="html">The Next AI Breakthrough Is Old-Fashioned Software Engineering</title><link href="https://www.joshclemm.com/writing/next-ai-breakthrough-is-just-engineering/" rel="alternate" type="text/html" title="The Next AI Breakthrough Is Old-Fashioned Software Engineering" /><published>2025-10-22T00:00:00-07:00</published><updated>2025-10-22T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/next-ai-breakthrough-is-just-engineering</id><content type="html" xml:base="https://www.joshclemm.com/writing/next-ai-breakthrough-is-just-engineering/"><![CDATA[<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/reliability_old_timey.jpg" width="100%" />
<br />

</p>

<p>Everyone’s chasing smarter AI. But the real breakthrough won’t be intelligence. It’ll be reliability.</p>

<p>Over the weekend, <a href="https://www.dwarkesh.com/p/andrej-karpathy">Andrej Karpathy’s conversation on the Dwarkesh pod</a> made waves when he said AGI is still at least a decade away.</p>

<p>His reasoning came from experience. Years ago, the first self-driving car demos looked amazing. Cars gliding through cities, reacting to obstacles in real time. I also remember seeing those early <a href="https://www.motortrend.com/news/163-news120817-lexus-rx-450h-google-autonomous-car">GoogleX SUVs</a> near the LinkedIn campus in 2013.</p>

<p>Yet a decade later, the work continues. In fact, I’m writing this <em>inside</em> a Waymo that just told me to get out in the middle of an intersection.</p>

<p>The progress has been less about demos and more about grinding out reliability and predictability.</p>

<p><strong><em>We’re now in the same moment with AI agents.</em></strong></p>

<p>The productivity unlocked in just the last couple of years is undeniable. I have so many go to prompts and agents helping me daily. I’m loving to code because of how fun and easy it has become. But the road from “impressive demo” and “babysitting your agent” to “production-grade reliability” requires hard, unglamorous engineering work.</p>

<p>It’s easy to make something work once. The hard part is making it work every time.</p>

<h2 id="reliability-is-the-real-frontier">Reliability Is the Real Frontier</h2>

<p>While the stakes with AI agents might not match those of autonomous vehicles, the underlying challenge is the same: reliability. Automating parts of your work or life still demands rigor. Companies can’t afford hallucinations in customer support, errors in financial workflows, or security breaches with MCP or AI browsers that get tricked into exposing your data.</p>

<p>Most of today’s limitations trace back to the structural weaknesses of LLMs. Many have written about context rot, security vulnerabilities, or tool misuse. I’ve also explored this in <a href="https://joshclemm.com/writing/4-ways-LLMs-fail/">The 4 Ways LLMs Fail</a> where I break down how these weaknesses create real-world reliability issues.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_overview.jpg" width="80%" />
<br />

<i><a href="https://joshclemm.com/writing/4-ways-LLMs-fail/">The 4 Ways LLMs Fail</a></i>
</p>

<p>AI still remains incredibly useful today for lower-stakes tasks and keeping a human in the loop, but the next wave of innovation will come from teams focused on high-stakes reliability. Building agents that don’t just “work sometimes,” but work nearly all the time.</p>

<p>As Karpathy said, it’s going to be a march of nines: going from 90% to 99.99% requires a lot more engineering effort, testing, and iteration.</p>

<h2 id="borrowing-from-decades-of-software-engineering">Borrowing from Decades of Software Engineering</h2>

<p>The good news is that we already have a playbook. The path to reliable AI will look a lot like the evolution of modern big data, web-scale, and distributed software systems. Many of the same ideas apply:</p>

<ul>
  <li>Evals-driven development - validation loops and metrics for every model and retrieval pipelines, similar to test-driven development</li>
  <li>Agentic orchestration patterns like planning, parallelization, and merging - reminiscent of service oriented call graphs or MapReduce.</li>
  <li>Sub-agent modular design - agents focused on narrow, well-defined tasks, like microservices.</li>
  <li>Context engineering - where we can pull out the most relevant context from memory or underlying search engines</li>
  <li>Query understanding and routing - sending tasks to the right models or tools.</li>
  <li>Reliability engineering - retries, tracing, observability, auto-healing infra.</li>
</ul>

<p>These aren’t new ideas. But they’re being rediscovered and reinvented in the context of AI systems. For example, here’s <a href="https://www.linkedin.com/feed/update/urn:li:activity:7340292821846847488/?lipi=urn%3Ali%3Apage%3Ad_flagship3_pulse_read%3BPspHqfJaQcmJLQDthCyuOA%3D%3D">Anthropic defining a very familiar looking multi-agent architecture</a> or famed ML researcher Andrew Ng introducing a MapReduce-style <a href="https://www.deeplearning.ai/the-batch/issue-316/">parallel agent</a> approach.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/reliability_side_by_side.jpg" width="100%" />
<br />

<i>A resilient service architecture is looking very similar to a resilient multi-agent architecture</i>
</p>

<p>The teams that bring that disciplined software engineering to AI will win the next decade.</p>

<h2 id="the-paradox-of-agent-workflows">The Paradox of Agent Workflows</h2>

<p>AI agents excel when the task is deeply personal or open-ended. Essentially, the long tail of use cases.</p>

<p>But if you want to automate something your users do over and over again, building it the “old-fashioned way” might still be the right choice (today at least). Traditional software can guarantee the nines of reliability that agents don’t have yet.</p>

<p>Karpathy <a href="https://youtu.be/lXUZvyajciY">summed it up perfectly</a>: “A lot of times, the value that I brought to the company was telling them not to use AI.”</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/reliability_dont_use_ai.jpg" width="60%" />
<br />

</p>

<h2 id="the-next-breakthrough-in-ai-software-engineering">The Next Breakthrough in AI? Software Engineering</h2>

<p>We’re in a phase where agents are already unlocking big gains for coding, creative, or custom one-off tasks, especially in lower-stakes situations with clear evals and humans in the loop. But making them trustworthy and enterprise-grade will be a defining challenge of the coming years.</p>

<blockquote>
  <p>Sooner or later, we’ll be back to calling it “software engineering.” And there’s nothing wrong with that.</p>
</blockquote>

<p>The good news is that AI has a lot of momentum and can unlock more and more use cases as it matures. And with increasing amount of autonomy (unlike say self-driving cars). Low-stakes today, medium-stakes tomorrow, highest-stakes in the future.</p>

<p>And the work to address this has already begun.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/reliability_prompt_vs_context.jpg" width="70%" />
<br />

<i><a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">Anthropic</a> describing the different techniques of "engineering"</i>
</p>

<p>“Prompt engineering” started as the first approach in making AI behave more reliably. Then we morphed to “context engineering,” realizing passing the most essential information to LLMs increases accuracy and predictability. Now, we are off to “agent engineering” that thinks about task break-downs, sub-agents, and tight feedback loops and evals to control reliability.</p>

<p>Sooner or later, we’ll be back to calling it “software engineering.” And there’s nothing wrong with that.</p>]]></content><author><name></name></author><category term="AI" /><category term="LLM" /><category term="Software Engineering" /><category term="Architecture" /><summary type="html"><![CDATA[The next AI breakthrough won't be smarter models but reliable ones. Like self-driving cars, progress of AI agents means consistency over demos. The future of AI lies in disciplined software engineering, building agents that work safely and predictably every time, not just sometimes.]]></summary></entry><entry><title type="html">The 4 Ways LLMs Fail</title><link href="https://www.joshclemm.com/writing/4-ways-LLMs-fail/" rel="alternate" type="text/html" title="The 4 Ways LLMs Fail" /><published>2025-09-22T00:00:00-07:00</published><updated>2025-09-22T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/4-ways-LLMs-fail</id><content type="html" xml:base="https://www.joshclemm.com/writing/4-ways-LLMs-fail/"><![CDATA[<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_overview.jpg" width="100%" />
<br />

</p>

<p>Large language models (LLMs) and AI agents that use them often get lauded as magic. But anyone using them in production or serious applications quickly learns they have Achilles’ heels.</p>

<p>What we commonly call <em>hallucinations</em>, <em>workslop</em>, or <em>security vulnerabilities</em> are not random bugs. They tend to cluster into a handful of recurring failure modes. In my experience, you can trace most of these breakdowns to <strong>four core weaknesses</strong> in how LLMs and Agents are built or deployed.</p>

<p>There <em>is</em> a lot of exciting research underway trying to tame these failure modes. But even as new ideas filter into deployed systems, many predictions that “<a href="https://x.com/mustafasuleyman/status/1667184880235446280">hallucinations will disappear soon</a>” are overly optimistic. These weaknesses are deeply structural and continue to require a lot of systems work like <a href="https://www.linkedin.com/pulse/rag-isnt-dead-just-leveled-up-josh-clemm-jxrqc/">context engineering</a> to solve.</p>

<p>Let’s go through those failure models and what to do about them.</p>

<h1 id="the-4-failure-modes">The 4 Failure Modes</h1>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_overview.jpg" width="100%" />
<br />

</p>

<h2 id="1-they-get-lost">1. They Get Lost</h2>

<p>With every new model release, AI labs love to tout their ever-growing context window length. Gemini for example, was the first model to ever support <a href="https://ai.google.dev/gemini-api/docs/long-context">1 million tokens</a>. This created a ton of excitement. You could load the entire Harry Potter books into the prompt itself and ask any question! Because of this, a lot of people thought that retrieving and selecting individual chunks of information (also known as RAG) would be a <a href="https://www.linkedin.com/pulse/rag-isnt-dead-just-leveled-up-josh-clemm-jxrqc/">thing of the past</a>. Everybody was starting to talk about CAG (<a href="https://arxiv.org/abs/2412.15605">Cache Augmented Generation</a>), where you could just throw everything into the context window and you were done.</p>

<p>But with all that hype came serious reality. A lot of research showed how accuracy quickly would degrade as the context window fills up.</p>

<p>Take the <em><a href="https://arxiv.org/abs/2502.05167">NoLiMa benchmark</a></em>: it tests models on a “needle in a haystack” scenario where the relevant fact (the needle) has minimal lexical overlap with the question. As the distractor “haystack” grows, performance degrades sharply. Even modern reasoning models, and the most powerful ones, still degrade. And the newer <a href="https://github.com/adobe-research/NoLiMa?tab=readme-ov-file#nolima-hard-results">NoLiMa-hard benchmark</a> is showing that those are even failing.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_nolima.jpg" width="500" />
<br />

</p>

<p>Now, most users don’t start chats with massive context, but everyone has noticed the drift that creeps in during long, multi-turn sessions. Accuracy degrades, the model loses track, and the common fix is to “start a new chat.” That’s because LLMs struggle to reason over ever-growing context. It’s the same problem. Over time, as more is added, the original goal gets buried. This term has been popularized as <strong><a href="https://research.trychroma.com/context-rot">context rot</a></strong>.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_context_rot.jpg" width="500" />
<br />

<i>Growing context windows can <a href="https://news.ycombinator.com/item?id=44310054">poison their own knowledge</a> leading to context rot</i>
</p>

<p>Context rot is a large reason why very complex agents struggle. They continue to work on their own doing these multi-turn exercises, filling up the context window and drifting from their original goal.</p>

<p><strong>What to do about it:</strong></p>

<ul>
  <li>Be ruthless in context curation. Don’t dump the entire conversation history or every document you retrieved. Select only what’s relevant.</li>
  <li>Use <strong>retrieval-augmented generation (RAG)</strong> or agentic retrieval so the model only fetches the snippets it actually needs at that moment.</li>
  <li>For chat / agentic systems: employ <strong>context summarization and compression</strong> techniques. e.g. generate concise abstracts of prior exchanges, store those instead of full logs. Keep in mind a lot of research is actively working in this area as it’s still very finicky. We’re more or less trying to replicate how the human brain memorizes.</li>
  <li>Adopt <strong>modular / hierarchical agent structure</strong> rather than a “one super agent loop.” Let a planner LLM decide subproblems, then dispatch to sub-agents with narrow tasks and tight contexts. That keeps each agent’s memory and context footprint manageable. Think <a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a> for agents.</li>
</ul>

<h2 id="2-they-are-gullible">2. They Are Gullible</h2>

<p>LLMs are at their core, pattern learners, not truth verifiers. When you feed them context, even wrong or misleading or malicious context, they often treat it as an authority. That means if the retrieval returns incorrect or outdated documents, the LLM will happily repeat them. That also means they are vulnerable to prompt-injection. In short: <strong>garbage in, garbage out</strong> - a common problem in software development.</p>

<p>One of the more notorious examples was with Google’s earliest AI overviews in search. It once recommended <em>adding glue to pizza</em> because a joke Reddit thread made it into the context. Because the LLM treated that context as fact, it repeated the absurdity.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_cheese.jpg" width="400" />
<br />

<i>From Google's now infamous <a href="https://arstechnica.com/information-technology/2024/05/googles-ai-overview-can-give-false-misleading-and-dangerous-answers/">AI Overviews launch</a></i>
</p>

<p>Models have improved tremendously, especially reasoning models that can reflect more on its output. But this still remains to be a challenge overall. LLMs need to understand if a source is reliable, trustworthy, and accurate. It might be able to understand and throw out something that’s wildly off, but subtle mistakes won’t be caught. For example, even GPT-5 will repeat an incorrect fact if provided an incorrect fact.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_gpt5.jpg" width="600" />
<br />

<i>Even GPT-5 repeats plausible but inaccurate information passed to it</i>
</p>

<p>Another dangerous part of this failure mode is <strong>prompt injection</strong> leading to <strong>data exfiltration</strong>. Because LLMs and Agents will follow what they are asked to do, they can be tricked to go off course, extract some sensitive content, and post it publicly. A recent <a href="https://www.codeintegrity.ai/blog/notion">prompt injection attack against Notion</a> where hidden white on white text in a PDF told the agent to gather confidential data from other pages.</p>

<p>The root problem: LLMs lack an intrinsic fact-checker. They default to completion, not verification.</p>

<p><strong>What to do about it:</strong></p>

<ul>
  <li>Invest heavily in <strong>retrieval / search quality</strong>. Rank by trust, recency, authority. Don’t just fetch “something relevant”. Search isn’t a side quest in AI apps. It’s the main quest and you only earn the right to expand once you do it world-class. Read more about <a href="https://www.linkedin.com/pulse/rag-isnt-dead-just-leveled-up-josh-clemm-jxrqc">advanced techniques to improve retrieval</a>.</li>
  <li>Always <strong>cite sources</strong> or return the provenance of snippets. Use prompt engineering to make the model flag “these statements come from document X.” That gives you post hoc auditability. Check out my post about <a href="https://joshclemm.com/writing/ai-search-app-100-lines-of-code/">building an AI answer engine like Perplexity</a> to see how to prompt for citations.</li>
  <li>Use <strong>cross-checking or consensus models</strong>: for important outputs, run the query through two retrieval chains or two LLMs and compare. Divergence is a flag.</li>
  <li>Apply <strong>fact verification or consistency modules</strong> downstream (e.g. specialized verifiers) to validate the answer before presenting it to users.</li>
  <li>Treat all external inputs as <strong>untrusted</strong> and defend with a layered, zero-trust pipeline. Sanitize and filter what goes into the context, restrict what tools the model can touch, and audit and monitor outputs for attempted data exfiltration.</li>
</ul>

<h2 id="3-they-are-overconfident">3. They Are Overconfident</h2>

<p>One of the most dangerous failure modes of LLMs is their inability to say “I don’t know.” Instead, they default to completing the prompt — even if that means confidently fabricating an answer. That’s the essence of hallucination.</p>

<p>OpenAI’s recent article on “<a href="https://openai.com/index/why-language-models-hallucinate/">Why Language Models Hallucinate</a>” suggests most evaluations measure model performance in a way that <strong>encourages guessing</strong> rather than honesty about uncertainty. Research backs this up: studies like <em><a href="https://arxiv.org/html/2404.12452v2">Characterizing LLM Abstention Behavior in Science QA</a></em> and <a href="https://arxiv.org/html/2506.00780v1">ConfuseBench</a> show that even state-of-the-art models struggle to abstain when evidence is missing or ambiguous. Meta’s <a href="https://github.com/facebookresearch/AbstentionBench">Abstention Benchmark</a> confirms the issue persists at scale, with little improvement from larger models.</p>

<p><strong>What to do about it:</strong></p>

<ul>
  <li>Prompt explicitly for uncertainty. Encourage the model to respond with “I’m not certain” or “I don’t know” when evidence is insufficient (though results may vary).</li>
  <li>Clarify capabilities by defining what the model can and cannot do. When asked something out of scope, guide it to suggest alternatives.</li>
  <li>Train for abstention and fine-tune with abstention-aware objectives that treat refusal or uncertainty as valid outcomes.</li>
  <li>Use structured uncertainty checks. <em>ConfuseBench</em> recommends multi-step diagnosis: clarify or re-retrieve if confidence is low. While <em>SteerConf</em> helps align generated confidence with actual uncertainty.</li>
  <li>Improve retrieval quality (like before). Better, more trusted context reduces guessing and increases overall reliability.</li>
</ul>

<h2 id="4-they-get-overloaded-with-tools">4. They Get Overloaded With Tools</h2>

<p>As you build more ambitious systems, you may want your LLM (and more specifically an “Agent”)  to call on many APIs, tools, or functions (search, translation, summarization, databases, code execution, etc.). But more is not always better: giving an LLM access to a large toolbox can overwhelm it. It might choose the wrong tool, misuse them, or simply ignore them, effectively “paralysis by analysis.”</p>

<p>Recent work on tool use calibration highlights exactly this: many LLMs exhibit tool-abuse behavior. They overconfidently misuse tools or issue useless calls. A paper called <a href="https://arxiv.org/abs/2412.12151">SMARTCAL</a> addresses this by letting an LLM self-evaluate tool calls and calibrate use. On benchmark tasks, it reduced tool misuse and boosted reliability somewhat.</p>

<p>There’s been a lot of hype around models that could combine “hundreds of APIs or MCPs” (e.g. the MCP / super-agent approach), but that hype has cooled as we see the complexity cost: too many choices, too many errors, too fragile routing logic.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_mcp.png" width="500" />
<br />

<i>MCP is an exciting standard for LLMs, though its robustness is still in progress</i>
</p>

<p>It’s also critical to check out the tool calling leaderboard, to see which models do better than others. Those might be the ones worth using for agentic applications.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_leaderboard.jpg" width="600" />
<br />

<i><a href="https://gorilla.cs.berkeley.edu/leaderboard.html">Berkeley's tool calling leaderboard</a></i>
</p>

<p><strong>What to do about it:</strong></p>

<ul>
  <li><strong>Limit the number of tools</strong> the agent can choose from. Keep the palette narrow and highly curated.</li>
  <li>Adopt a <strong>planning / tool selection step</strong>: have an LLM (or light planner) decide <em>which tool to call</em> before handing off to the “executor” LLM.</li>
  <li>Use <strong>sub-agents with specialized tools</strong> instead of one all-purpose agent. Each sub-agent has a narrow objective and limited tool set. Think <a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a> for agents.</li>
  <li>Favor <strong>general tools</strong> (e.g. code executor or a general Python sandbox) over many micro-APIs. That way, the model learns to orient its logic around one flexible tool rather than dozens of brittle ones. We follow this approach at <a href="https://dash.dropbox.com/">Dropbox</a>.</li>
  <li>Monitor <strong>tool selection accuracy</strong> as a metric (check the <a href="https://gorilla.cs.berkeley.edu/leaderboard.html">leaderboards on tool-calling correctness</a>). As models scale up, this remains a surprisingly brittle axis.</li>
  <li><strong>Fine-tune the model</strong> to improve the calling of your tools or the growing number of tools.</li>
</ul>

<h1 id="context-engineering-is-the-answer">Context Engineering is the Answer</h1>

<p>In all of the above scenarios, a common theme emerges: <a href="https://www.linkedin.com/pulse/rag-isnt-dead-just-leveled-up-josh-clemm-jxrqc/">context engineering</a>. This is the art of retrieving and providing the right pieces of information and tools at the right time. This goes beyond prompt engineering, where you’re simply iterating on a prompt itself for an LLM, and includes:</p>

<ul>
  <li>Any context you’re providing through retrieval (whether it’s RAG or more of an agentic retrieval solution)</li>
  <li>The past context that you’ve had in a multi-turn session</li>
  <li>Any longer-term memory from previous sessions</li>
  <li>Tool selection</li>
</ul>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_tobi.png" width="500" />
<br />

</p>

<p>Context engineering is the high-status job in AI right now. Deciding what belongs in the context window is the <strong>real secret sauce</strong> to accurate, reliable, and safe LLMs / Agents.</p>

<p>Given how important context engineering is, how do you know if you are context engineering correctly? Measurements and Evals!</p>

<p>Evals are a bit beyond the scope of this doc, but you’ll want to think about these areas:</p>

<p>How would you know if you are selecting the right context from search / retrieval? Check…</p>
<ol>
  <li>Recall: Did we retrieve all the chunks / facts we needed?</li>
  <li>Precision: Are relevant chunks ranked higher than non-relevant chunks?</li>
  <li>Relevancy: What % of retrieved chunks / facts matter?</li>
</ol>

<p>And how would you know if the LLM is using that provided context correctly? Check…</p>

<ol>
  <li>Faithfulness: Is the output contradicting the retrieved chunks / facts?</li>
  <li>Relevancy: Is the response answering the question correctly?</li>
  <li>Citations / Formatting: Is it showing its work or following the structure you expected?</li>
</ol>

<h1 id="conclusion">Conclusion</h1>

<p>LLMs are powerful, but they are far from perfect. It’s tempting to treat them like oracles, but that invites failure. These four failure modes: getting lost, gullibility, overconfidence, and tool overload are predictable and addressable. Your reliability depends not just on how “good” the model is, but how thoughtfully you design the surrounding system.</p>

<p>A useful heuristic: <strong>good context + weak model &gt; bad context + strong model</strong>. Even a simpler model with carefully managed context and rigorous retrieval can outperform a “state-of-the-art” model with sloppy inputs.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/fail_jack.png" width="500" />
<br />

</p>

<p>Most active research today is aimed at these exact weaknesses:</p>

<ul>
  <li>making long contexts and multi-session memory more stable,</li>
  <li>reducing hallucinations and teaching models when <em>not</em> to answer,</li>
  <li>improving factual verification and reasoning,</li>
  <li>and building agentic systems that choose and use tools more effectively.</li>
</ul>

<p>But while research pushes the frontier, real-world success still comes down to <strong>context engineering</strong>. Deciding what belongs in the window, when to prune, which tools to expose, and how to guide the model to reason (or abstain) are the levers that separate brittle prototypes from robust systems.</p>]]></content><author><name></name></author><category term="AI" /><category term="LLM" /><category term="Software Engineering" /><summary type="html"><![CDATA[Large language models (LLMs) and AI agents that use them often get lauded as magic. But anyone using them in production or serious applications quickly learns how often they fail. What we commonly call hallucinations, workslop, or vulnerabilities are not random bugs. They tend to cluster into a handful of recurring failure modes. In my experience, you can trace most of these breakdowns to four core weaknesses in how LLMs and Agents are built or deployed.]]></summary></entry><entry><title type="html">No, RAG Isn’t Dead, It Just Leveled Up As Context Engineering</title><link href="https://www.joshclemm.com/writing/rag-leveled-up/" rel="alternate" type="text/html" title="No, RAG Isn’t Dead, It Just Leveled Up As Context Engineering" /><published>2025-06-02T00:00:00-07:00</published><updated>2025-06-02T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/rag-leveled-up</id><content type="html" xml:base="https://www.joshclemm.com/writing/rag-leveled-up/"><![CDATA[<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_cover.jpg" width="100%" />
<br />

</p>

<p class="note">
      <i>I wrote this post originally as an article published on LinkedIn. View <a href="https://www.linkedin.com/pulse/rag-isnt-dead-just-leveled-up-josh-clemm-jxrqc">the original there</a>.</i>
  </p>

<p>There’s been a lot of recent buzz around whether Retrieval Augmented Generation (RAG) has reached its limits.</p>

<p>Is RAG truly dead or just replaced by new approaches like search agents, MCP, or massive context windows?</p>

<p>All the newer solutions being proposed like agentic search, code-base crawlers, hybrid semantic + lexical rankers, graph databases, or even MCP tool calling are still retrieval-augmented generation under a new name.</p>

<h2 id="what-rag-really-is">What RAG Really Is</h2>

<p>Large Language Models lack a lot of relevant content. First, they are trained with data up to a certain point in time (their knowledge cutoff date). AI search engines like Perplexity (or my own <a href="https://yaddle.ai">yaddle.ai</a>) use <a href="/writing/ai-search-app-100-lines-of-code/">real-time search results</a> to augment the LLM’s knowledge. I even had to do this to generate up-to-date <a href="/writing/generative-player-outlooks/">fantasy football player outlooks</a>.</p>

<p>Second, a lot of applications require proprietary data. This is most common with enterprises that need their company’s data as part of an LLM’s context. That’s what we’re building at Dropbox with <a href="https://dash.dropbox.com/">Dash</a>.</p>

<p>At its core RAG means <strong>helping a language model with context it did not train on</strong>. When you retrieve content from an external source and feed it into the prompt, that’s RAG. It doesn’t prescribe <em>how</em> you fetch the data. It could be through vector search, keyword search, a graph query, retrieving from an MCP server, a searcher agent, or a combination of all of that.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_mrbean.jpg" width="400" />
<br />

<i>MrBeanGPT retrieving some "expert" knowledge</i>
</p>

<h2 id="why-are-some-declaring-rag-dead">Why are some declaring RAG “dead”?</h2>

<p>The first <a href="https://arxiv.org/abs/2005.11401">RAG paper</a> glued together the three simple steps for retrieval augmented generation:</p>

<ol>
  <li>Chunk documents from your corpus</li>
  <li>Embed each chunk into a semantic vector</li>
  <li>Retrieve top-k vectors with nearest-neighbor search and drop them into the model’s input</li>
</ol>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_vector_search.png" width="400" />
<br />

<i>Basic RAG with Vector Search</i>
</p>

<p>Those ingredients were chosen because 2020-era LLMs were small, hallucinated often, and lacked long context. LLMs are quite effective at representing semantic meaning. By transforming chunks of text into vector embeddings, you could query them semantically: “Find me the text snippet whose meaning is most similar to this question.”</p>

<p>But in today’s world, chunking documents and doing semantic lookup from a vector database can be suboptimal for many document types, query types, or for modern corpora like large code bases.</p>

<p>So those that associate “RAG” with <em>just</em> “chunking + semantic search” feel it’s dated.</p>

<p>But those who associate “RAG” with retrieving the best context for an LLM know that RAG is alive and well, just evolved. Any retrieval loop is still the safest way to ground an LLM.</p>

<p>Today, you need to design your retrieval system with the understanding of many factors.</p>

<h2 id="one-size-of-retrieval-does-not-fit-all">One Size of Retrieval Does Not Fit All</h2>

<p>An essential consideration in RAG is “what do you retrieve, why, and from where?” The shape, size, and nature of your knowledge corpus heavily influence your retrieval strategy. For instance, if your corpus is small and specialized, then chunking and embedding might be sufficient. However, if your corpus is large (millions of documents) or extremely varied, you might need more refined retrieval strategies.</p>

<p><strong>Structured vs. Unstructured</strong>: Is your data a set of well-structured tables and knowledge graphs, or is it a corpus of free text (e.g., PDFs, Word docs, HTML)? You’ll likely need different indexing and retrieval approaches for each.</p>

<p><strong>Real-time vs. Static</strong>: If your corpus changes regularly (new product entries, daily news articles, codebase changes), your retrieval pipeline must handle rapid updates.</p>

<p><strong>Complex vs. Straightforward Queries</strong>: For simple question-answering, a single query might suffice. But if you need multi-step reasoning for deep analysis, your retrieval approach might need an iterative or agent-based method</p>

<p><strong>Latency / cost budget</strong>: Complex agents with reasoning cost a lot of tokens and time.</p>

<p>Let’s go through some other options in the retrieval toolbox.</p>

<h2 id="lexical-bm25-retrieval">Lexical (BM25) Retrieval</h2>

<p>Using a lexical keyword index excels for exact terms, codes, and part numbers. A procurement bot that can’t resolve SKUs is useless. Using a ranking function like <a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a> thrives on tight phrases, dates, and proper nouns, with millisecond latency on millions of docs. A classic example is part-catalog search in manufacturing or legal statute lookup where citations must match verbatim. Semantic search may only return similar part numbers etc, frustrating your users.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_semantic_keyword.png" width="600" />
<br />

<i>The range of which solution to pick based on what your users need</i>
</p>

<h2 id="hybrid-retrieval-and-fused-ranking">Hybrid Retrieval and Fused Ranking</h2>

<p>Many real workloads need both precision and fuzziness. The common approach is a hybrid retrieval system that fetches the top-N from both a lexical index and a vector index. Then those combined results are blended in a cross-encoder type re-ranker (like <a href="https://cohere.com/rerank">Cohere’s re-ranker</a>).</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_hybrid_retrieval.png" width="400" />
<br />

<i>Hybrid retrieval gets you the best of both worlds</i>
</p>

<p>E-commerce <a href="https://www.marqo.ai/blog/build-a-multimodal-hybrid-search-system">search</a> is the poster child for these hybrid solutions. You want users to be able to do exact search “size 7” (lexical) matching while understanding “trail running” (semantic).</p>

<p>This technique can be further improved by introducing <a href="https://www.anthropic.com/news/contextual-retrieval">contextual embeddings</a>, as described by Anthropic, to your chunking and embedding strategy.</p>

<h2 id="graph-rag">Graph RAG</h2>
<p>Sometimes, your corpus isn’t a set of documents but rather a knowledge graph or a database with entities and relationships. For example, if you maintain a complex knowledge base about pharmaceuticals, you might have a graph where nodes represent active ingredients, brand names, side effects, drug interactions, and more. Then you can answer queries like “What other conditions might be impacted if the patient is taking Drug A and has a history of heart disease?”</p>

<p>Traversing that graph for relevant information can be more effective than chunk-based retrieval of unstructured text.</p>

<p>In the enterprise world (like we’re doing at <a href="https://dash.dropbox.com/">Dropbox</a>), we have org charts where people are connected to other people, but also connected to groups, documents, projects, and more.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_graph.png" width="500" />
<br />

<i>Graph retrieval might be enough on their own, or part of a hybrid retrieval solution</i>
</p>

<h2 id="agentic-rag">Agentic RAG</h2>

<p>For complex user queries (like deep research) or complex documents (like a code base), the newest generation of retrieval is to use agents that emulate how a human researcher might approach the problem. Instead of a single query that retrieves the top-n results in one shot, an LLM-based agent might:</p>

<ol>
  <li>Start with a broad keyword or semantic search.</li>
  <li>Skim retrieved content.</li>
  <li>Evaluate whether the retrieved results fully answer the query.</li>
  <li>If not satisfied, refine the query, add new keywords, or investigate tangential topics.</li>
  <li>Repeat until it has gathered enough context to produce a thorough answer.</li>
</ol>

<p>This iterative retrieval process can significantly improve correctness and completeness. However, it comes with a trade-off: multiple queries to your database or knowledge store increase latency and cost (especially if you’re using state of the art models).</p>

<p>This approach is still RAG, though the LLM agent itself might invoke a retriever tool to interact with your knowledge base or invoke an <a href="https://www.anthropic.com/news/model-context-protocol">MCP</a> service to retrieve data from an external app.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_agentic.png" width="500" />
<br />

<i>Agent retrievers that fetch content, refine, and evaluate</i>
</p>

<h2 id="multi-agentic-rag">Multi-agentic RAG</h2>
<p>A specialized subset of retrieval scenarios emerges when the knowledge corpus in question is very complex and often has multiple distinct parts to reason upon.</p>

<p>For example, coding assistants / copilots may need a very sophisticated understanding of the structure of a company’s code-base, its dependency graphs, its function names, and any external documentation. If a user asks, “How does function XYZ handle edge cases for user login?” the agent can quickly surface the relevant code snippet, the documentation, or references in other parts of the code.</p>

<p>These retrieval systems are complex and may involve many sub-agents that specialize in retrieving more narrow understanding, which then gets merged back by a main agent (similar to <a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a>).</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_multi_agent.png" width="400" />
<br />

<i>Agent retrievers that fetch content, refine, and evaluate</i>
</p>

<h2 id="cache-augmented-generation-cag">Cache Augmented Generation (CAG)</h2>
<p>Wait a second, LLMs now have massive context windows. Gemini supports <a href="https://ai.google.dev/gemini-api/docs/long-context">over 1M tokens</a>. Why do we even need these complex retrieval systems? Can’t I just load my entire corpus into the context window and have the LLM figure it out? Is <a href="https://arxiv.org/abs/2412.15605">Cache-Augmented Generation all you need</a>?</p>

<p>That’s a very tempting proposition, since the level of effort to add this to your product is very small. But in practice, you will hit four roadblocks.</p>

<h3 id="retrieval-quality">Retrieval quality</h3>

<p>After about 30K tokens, retrieval quality suffers. The <a href="https://arxiv.org/abs/2502.05167">NoLiMa</a> scoring method shows <a href="https://x.com/joshclemm/status/1889131566284304415">active degradation</a>.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_nolima.jpg" width="500" />
<br />

</p>

<p>LLMs are also very impressive at <a href="https://arxiv.org/abs/2407.01437">Needle in the Haystack retrieval</a>, but still can’t quite match the keyword lookup prowess of BM25.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_needle.png" width="500" />
<br />

</p>

<h3 id="freshness">Freshness</h3>

<p>If your documents are changing often, it requires significant re-loading of your data, creating the new for a complex invalidation scheme.</p>

<h3 id="latency">Latency</h3>

<p>LLMs need to search stochastically in their vector (latent) space, which requires inference and time. The methods described above (especially the one-shot approaches) have significantly less latency and are more deterministic.</p>

<h3 id="costs">Costs</h3>

<p>LLMs are very costly for inference, especially these state of the art models that have massive context windows. So a lot of retrievals will result in a lot of inference calls and therefore costs.</p>

<h2 id="evals-evals-evals">Evals, Evals, Evals</h2>

<p>With generative AI, there’s a tendency to want to obsess over the model selection and refine your prompts.</p>

<p>But those activities should come after you’ve nailed your retrieval quality.</p>

<p>Today, LLMs treat any passed-in context (aka RAG) as gospel. If that context contains accurate info, your response will be accurate. If it’s not great, guess what? The <a href="https://arxiv.org/abs/2411.06037">LLM will confidently give you a bad answer</a> (aka a hallucination).</p>

<p>So no matter how fancy or simple your retrieval pipeline is, defining evaluations are crucial. You must know if your system is genuinely improving the quality and accuracy of the model’s responses for your specific use cases.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/rag_evals.jpg" width="400" />
<br />

<i>Your mantra for better retrieval</i>
</p>

<p>Make sure you’ve defined and automated metrics like groundedness, faithfulness, NDCG / MRR, and more.</p>

<p>As advanced retrieval techniques evolve, there’s a temptation to rely solely on marketing claims or general hype about “agent-based retrieval” or “knowledge graphs.” The best guard against that is thorough, data-driven testing. If your system’s performance on your domain-specific tasks actually improves, then the new approach is worth adopting. If not, you might be adding complexity for little gain.</p>

<h2 id="rag-evolves-but-its-not-going-away">RAG Evolves, But It’s Not Going Away</h2>

<p>So, is RAG truly dead? The short answer is no. What’s happening is more of an expansion or refinement of RAG. Techniques are emerging that more closely emulate how humans research or how code is structured. We’re adding iterative search steps, building knowledge graphs, or combining lexical and semantic signals. We’re also experimenting with extremely large context windows. Yet, all these approaches revolve around the same core principle that defines RAG: help a large language model by supplementing it with targeted, external context.</p>

<p>The real challenge is picking the right tool, or combination of tools, for each unique application.</p>

<p>Ultimately, the future of RAG is bright. As LLMs become more powerful, the burden on retrieval might lessen for some tasks (because the model’s baseline knowledge grows), but for domain-specific or time-sensitive tasks, retrieval remains paramount.</p>]]></content><author><name></name></author><category term="AI" /><category term="LLM" /><category term="Software Engineering" /><summary type="html"><![CDATA[There’s been a lot of recent buzz around whether Retrieval Augmented Generation (RAG) has reached its limits. Is RAG truly dead or just replaced by new approaches like search agents, MCP, or massive context windows? That's still retrieval-augmented generation under a new name.]]></summary></entry><entry><title type="html">LLM Routers - The AI Dispatchers You Didn’t Know You Needed</title><link href="https://www.joshclemm.com/writing/llm-routers/" rel="alternate" type="text/html" title="LLM Routers - The AI Dispatchers You Didn’t Know You Needed" /><published>2025-05-15T00:00:00-07:00</published><updated>2025-05-15T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/llm-routers</id><content type="html" xml:base="https://www.joshclemm.com/writing/llm-routers/"><![CDATA[<p>AGI is coming - or so they say. But what exactly is AGI? Definitions differ, yet my favorite describes AGI as the ultimate generalist: imagine an AI that possesses the combined expertise of top human specialists across every conceivable field. And that’s the “G” in AGI (artificial <span style="text-decoration:underline;">general</span> intelligence). Instead of the “jack of all trades, master of none” concept, AGI is the “master of all trades”.</p>

<p>While AGI isn’t here yet, we have many large language models (LLMs) rapidly moving in that direction. However, a closer look at leaderboards such as<a href="https://lmarena.ai/"> Chatbot Arena</a> and <a href="https://artificialanalysis.ai/">standard benchmarks</a> reveals something interesting: <strong>most AI models aren’t generalists. They are actually specialists</strong>.</p>

<blockquote>
  <p>Most AI models aren’t generalists. They are specialists.</p>
</blockquote>

<p>Some excel at real-world coding, others at mathematics, writing, science, research, competitive coding, or questions. Today, there are over <a href="https://huggingface.co/models?pipeline_tag=text-generation&amp;sort=trending">200,000 LLMs</a> open-sourced on Hugging Face. And most are small and focused on specialized tasks.</p>

<p>So if you’re building an AI-powered product designed to handle diverse use cases, choosing just one LLM won’t cut it.</p>

<p>Enter LLM routers - technology that allows you to leverage multiple specialized models, routing each task to the model best suited to handle it. Why settle for one when you can pick exactly the right AI for every task?​</p>

<p>Let’s dive deeper into the world of LLM routing and explore how it works.</p>

<h2 id="understanding-llm-routing">Understanding LLM Routing</h2>

<p>LLM routing operates by evaluating incoming queries and assigning them to the optimal model. Simpler tasks can be handled by smaller, more efficient models. Complex tasks are directed to larger, more capable ones. And specialist tasks can be directed to the best model for the job. This approach ensures that resources are allocated effectively, maintaining high-quality responses without unnecessary expenditure.​</p>

<h3 id="benefits-of-llm-routing">Benefits of LLM Routing</h3>

<ul>
  <li><strong>Cost Reduction</strong>: By directing simpler queries to smaller models, you can significantly reduce operational costs. For instance, routers can cut inferencing costs by <a href="https://lmsys.org/blog/2024-07-01-routellm/">up to 85%</a> by diverting a subset of queries to more efficient models.</li>
  <li><strong>Improved Speed</strong>: Smaller models process queries faster, leading to quicker response times for less complex tasks. This enhances user experience by providing timely answers without compromising quality.​</li>
  <li><strong>Maintained Accuracy</strong>: Complex queries are handled by larger models capable of delivering high-quality responses, ensuring that accuracy is preserved where it matters most.​ Simpler routers can maintain 90% of the accuracy, while more complex ones can push that closer to 98%.</li>
  <li><strong>User Friendliness</strong>: Today’s chatbots require users to do a lot of work and know a lot of technical details (e.g. how to best prompt, what model is the best for my query). Routers allow you to hide that complexity or offer a “<a href="https://x.com/joshclemm/status/1887295854073290802">choose for me</a>” option. Why force your users to make a choice while newer models are introduced every day? Keep things simple and hide those details behind a router.</li>
  <li><strong>Benefit from Market Innovations</strong>. Related to user friendliness, companies that use just one model will be at a big disadvantage since new faster, cheaper, and more powerful models are launched every week. It’s more advantageous to have a router setup, allowing you to more simply swap in new models. <a href="https://www.notdiamond.ai/blog/deepseek-r1-dont-put-all-your-eggs-in-one-llm-basket">Don’t put your eggs in one basket</a>.</li>
</ul>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-pareto.png" width="450" />
<br />

<i>Routers can find a more optimal balance across quality and costs (<a href="https://github.com/lm-sys/RouteLLM">RouteLLM</a>)</i>
</p>

<h2 id="types-of-routers">Types of Routers</h2>

<p>There’s many ways of introducing routing to your flows, ranging from simple to very complex. Let’s walk through each.</p>

<h3 id="simple-routing"><strong>Simple Routing</strong></h3>

<p>This is the most basic version introduced in the early <a href="https://github.com/lm-sys/RouteLLM">RouteLLM paper</a>. Build a router to decide between a smaller LLM (like Llama3 8B, Phi-4-mini, or Claude Haiku) or a larger model (like, GPT 4.5, Claude Sonnet, or Grok 3). Again, the larger state of the art models can do almost anything well, but come at a very steep price.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-simple.png" width="500" />
<br />

<i>Basic router for simple and complex tasks</i>
</p>

<h3 id="specialist-routing"><strong>Specialist Routing</strong></h3>

<p>This method involves training a routing algorithm on benchmark data to predict which model will best handle a given query. By analyzing the strengths and weaknesses of each model, the router can make informed decisions, enhancing both accuracy and cost-efficiency. Hugging Face lists <a href="https://huggingface.co/models?pipeline_tag=text-generation&amp;sort=trending">200,000 LLMs</a> today that are lighter-weight and highly specialized.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-specialist.png" width="500" />
<br />

<i>Route to model tuned for specific task</i>
</p>

<p>An alternative approach to specialist routing was presented in the <a href="https://github.com/lmarena/p2l">Prompt to leaderboard</a> paper. They trained their router using data from <a href="https://lmarena.ai/">Chatbot Arena</a> which has humans decide what answer they prefer between two options. Normally the state of the art (SOTA) models dominate since they are the most capable across all categories. But by leveraging the fact that even the state of the art models differ in their strengths, their approach of routing to the optimal one led to them becoming #1.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router_1.gif" width="500" />
<br />

<i>Router to different company LLMs</i>
</p>

<h3 id="local-routing"><strong>Local Routing</strong></h3>

<p>A variant of simple routing, local routing can take advantage of more and more capable edge devices like your laptop or even mobile phone. With technology like <a href="https://en.wikipedia.org/wiki/Llama.cpp">llama.cpp</a> and <a href="https://ollama.com/">ollama</a>, running LLMs on your laptop or server has never been easier.</p>

<p>The catch is that only the smallest models can reliably run on this hardware (for now). <a href="https://ai.google.dev/gemma/docs/gemma-3n">Google’s Gemma 3n</a> is a great example of how these smaller models are innovating on compute and memory usage. For example, Gemma 3n uses a Matryoshka Transformer (<a href="https://arxiv.org/pdf/2310.07707">MatFormer architecture</a>) that contains nested, smaller models within a single, larger model.</p>

<p>So with local routing, simply send easier queries to your local models and harder queries to more capable cloud-based LLMs. The router itself can run on the device.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-local.png" width="500" />
<br />

<i>Leverage local compute to handle some tasks locally vs. paying cloud costs</i>
</p>

<p>The <a href="https://arxiv.org/html/2404.14618v1">Hybrid LLM</a> paper showed by training a router to handle easy queries locally, they were able to make 40% fewer calls to more expensive cloud-based LLMs.</p>

<h3 id="cascading-routing"><strong>Cascading Routing</strong></h3>

<p>In this approach, queries are first sent to smaller, less expensive models. If the response quality is insufficient, the query is escalated to more powerful models. This can then be repeated if necessary.</p>

<p>This tiered system ensures that only necessary resources are utilized for complex tasks. Though does come at the cost of latency through wasted compute on harder tasks.</p>

<p><a href="https://arxiv.org/pdf/2410.10347">A Unified Approach to Routing and Cascading for LLMs</a> paper introduced an even more advanced version where both cascading and routing is done at each step. This leads to further improvements, cost efficiencies, and helps with overall latency.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-cascade.png" width="450" />
<br />

<i>Cascading routers</i>
</p>

<h3 id="decompose-and-reassemble"><strong>Decompose and Reassemble</strong></h3>

<p>This advanced approach uses a more powerful LLM to break down the user’s query into many, far simpler tasks. Then those tasks can be answered by smaller LLMs, either local or cloud-based, often in parallel. Then, a final remote call merges all the responses together (similar to <a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a>). While not exactly routing like the other techniques, this approach similarly makes use of a variety of models (likely smaller and more specialized) to accomplish the job.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-decompose.png" width="500" />
<br />

<i>Use more powerful model to break down tasks and route to those</i>
</p>

<p>The <a href="https://hazyresearch.stanford.edu/blog/2025-02-24-minions">MinionS Protocol</a> shows this approach achieves 5.7x reduction in remote costs, while maintaining 97.9% of cloud model performance.</p>

<h2 id="how-to-build-a-router">How to Build a Router</h2>

<p>In all our examples, we assumed there was an existing router that “just works” and sends your query to the right place. But how do you build that?</p>

<p>The process begins with data preparation, where a diverse set of user queries is collected and labeled according to the performance of different LLMs in addressing them. For example, queries can be rated on a scale from 1 to 5, indicating how effectively a particular model responds. This labeled dataset serves as the foundation for training a classifier, such as a causal LLM, to predict which model is best suited for a given query. Fine-tuning a classifier involves training it on this labeled data to enhance its ability to accurately route queries.</p>

<p>Techniques like Low-Rank Adaptation (<a href="https://arxiv.org/abs/2106.09685">LoRA</a>) can be employed to efficiently fine-tune your model. After training, the router’s performance is evaluated using benchmark datasets to ensure it effectively balances response quality and cost by directing queries to the appropriate LLMs.</p>

<p>The team behind one of the earliest <a href="https://lmsys.org/blog/2024-07-01-routellm/">LLM routers</a>, trained four routers using Chatbot Arena data:</p>

<ol>
  <li>A similarity-weighted (SW) ranking router that performs a “weighted Elo calculation” based on similarity</li>
  <li>A matrix factorization model that learns a scoring function for how well a model can answer a prompt</li>
  <li>A BERT classifier that predicts which model can provide a better response</li>
  <li>A causal LLM classifier that also predicts which model can provide a better response</li>
</ol>

<h2 id="connection-with-mixture-of-experts">Connection with Mixture of Experts</h2>

<p>Mixture of Experts (MoE) is a <a href="https://huggingface.co/blog/moe">model architecture</a> where one large language model contains multiple specialized sub-models, known as “experts.” The roots come from the 1991 paper <a href="https://www.cs.toronto.edu/~hinton/absps/jjnh91.pdf">Adaptive Mixture of Local Experts</a>, where only a subset of parameters are active in a neural net. It was designed to retain the best quality of an output while improving the overall efficiency and scalability. So, in essence an MoE model is performing routing to generate output tokens <em>within</em> the model itself as compared to routing across very different models.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/router-moe.png" width="500" />
<br />

<i>Traditional vs. Mixture of Experts model architecture</i>
</p>

<p>So while LLM routing chooses the best model for a task, MoE models have a gating network that selects the best “expert” for a given input token.</p>

<h3 id="conclusion"><strong>Conclusion</strong></h3>

<p>AGI may one day be the “master of all trades.” Today isn’t that day. And that’s fine. The winning pattern right now is a team of specialists with a great dispatcher. LLM routing is that dispatcher. It turns a messy, fast-changing model landscape into a system that is cheaper, faster, and just as accurate.</p>

<p>And because new models land weekly, a router isn’t just an optimization—it’s how you stay current without rewiring your product every month.</p>

<p>There also isn’t one “right” router. You can keep it simple (small vs. large), get smart with specialist routing, go hybrid with local models for easy wins, cascade when confidence drops, or decompose big problems and reassemble the answers. Mix and match. Let data, budget, and latency targets decide.</p>]]></content><author><name></name></author><category term="AI" /><category term="LLM" /><category term="Software Engineering" /><summary type="html"><![CDATA[Most AI models aren't generalists—they're specialists. With over 200,000 LLMs available, choosing just one model for your AI product won't cut it. Enter LLM routers - technology that routes each task to the model best suited to handle it. Discover how routers can cut costs by 85%, improve speed, and maintain accuracy while keeping your system current with the latest models.]]></summary></entry><entry><title type="html">Building a Perplexity Clone with 100 lines of code</title><link href="https://www.joshclemm.com/writing/ai-search-app-100-lines-of-code/" rel="alternate" type="text/html" title="Building a Perplexity Clone with 100 lines of code" /><published>2024-04-15T00:00:00-07:00</published><updated>2024-04-15T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/ai-search-app-100-lines-of-code</id><content type="html" xml:base="https://www.joshclemm.com/writing/ai-search-app-100-lines-of-code/"><![CDATA[<p>I’ve been a huge fan of AI search apps like <a href="Perplexity.ai">Perplexity.ai</a>, <a href="Phind.com">Phind.com</a>, and <a href="You.com">You.com</a> and have been a heavy user of them for about a year. If you’re not familiar, these products are an AI-powered question-answering platforms. They use large language models like ChatGPT to answer your questions, but improves on ChatGPT by pulling in accurate and real-time search results to supplement the answer (so no “knowledge cutoff”). Plus, they list citations within the answer itself which builds confidence it’s not hallucinating and allows you to research topics further.</p>

<p>So I wanted to know how it works. And it’s surprisingly simple at its core.</p>

<p>Here’s what I learned in building a version of it in about <a href="https://github.com/joshclemm/ai-search"><strong>100 lines of code</strong></a>.</p>

<p><em>Of course, to build amazing products like Perplexity, Phind, and you.com, you’ll need major investments in a delightful user experience, plenty of extra features, fine-tuned models, custom web-crawling, and more. Ultimately, it’ll be much more than just 100 lines of code!</em></p>

<h2 id="the-basics">The Basics</h2>

<p>To create your own, you need to make just <em>two</em> API calls:</p>

<ol>
  <li>Call a search engine API like Bing, Google, or Brave using the user’s query</li>
  <li>Craft a prompt to an LLM like ChatGPT passing along the search results in the context</li>
</ol>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/search_flow.gif" width="600" />
<br />

<i>Using search results to augment and improve upon an LLM’s answer</i>
</p>

<p>With the basics out of the way, let’s get into the details.</p>

<h2 id="step-1-get-search-results-for-a-user-query">Step 1: Get Search Results for a user query</h2>

<p>The main challenge with LLMs like ChatGPT is that they have knowledge cutoffs (and they occasionally tend to <a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)">hallucinate</a>). It’s because they’re trained on data up to a specific date (eg Sep 2021). So if you want an answer to an up-to-date question or you simply want to research a topic in detail, you’ll need to <em>augment</em> the answer with relevant sources. This technique is known as <a href="https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview">RAG</a> (retrieval augmented generation). And in our case we can simply supply the LLM up-to-date information from search engines like Google or Bing.</p>

<p>To build this yourself, you’ll want to first sign up for an API key from <a href="https://www.microsoft.com/en-us/bing/apis/bing-web-search-api">Bing</a>, Google (via <a href="https://serper.dev/">Serper</a>), <a href="https://brave.com/search/api/">Brave</a>, or others. Bing, Brave, and Serper all offer free usage to get started.</p>

<p>There’s plenty of ways to make a remote call, but here’s a basic way of using cURL / PHP to call Brave’s search API:</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="nv">$params</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">'q'</span> <span class="o">=&gt;</span> <span class="nv">$query</span><span class="p">);</span>
<span class="nv">$ENDPOINT</span> <span class="o">=</span> <span class="s2">"https://api.search.brave.com/res/v1/web/search"</span><span class="p">;</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nv">$ENDPOINT</span> <span class="mf">.</span> <span class="s1">'?'</span> <span class="mf">.</span> <span class="nb">http_build_query</span><span class="p">(</span><span class="nv">$params</span><span class="p">);</span>
<span class="nv">$headers</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
    <span class="s1">'X-Subscription-Token: '</span> <span class="mf">.</span> <span class="nv">$BRAVE_KEY</span><span class="p">,</span>
    <span class="s1">'Accept: application/json'</span>
<span class="p">);</span>
<span class="nv">$curl</span> <span class="o">=</span> <span class="nb">curl_init</span><span class="p">();</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl</span><span class="p">,</span> <span class="no">CURLOPT_HTTPHEADER</span><span class="p">,</span> <span class="nv">$headers</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl</span><span class="p">,</span> <span class="no">CURLOPT_URL</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl</span><span class="p">,</span> <span class="no">CURLOPT_ENCODING</span><span class="p">,</span> <span class="s1">'gzip'</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl</span><span class="p">,</span> <span class="no">CURLOPT_RETURNTRANSFER</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="nv">$response</span> <span class="o">=</span> <span class="nb">curl_exec</span><span class="p">(</span><span class="nv">$curl</span><span class="p">);</span>
<span class="nb">curl_close</span><span class="p">(</span><span class="nv">$curl</span><span class="p">);</span></code></pre></figure>

<p>These APIs will return the URL link, URL title, a description or snippet of the page, and more. After parsing the results, you are ready to construct a <a href="https://en.wikipedia.org/wiki/Prompt_engineering">prompt</a> to ChatGPT or another LLM of your choice.</p>

<p><strong><em>Wait, just the snippet? Is that enough data for this to work?</em></strong></p>

<p>Yes! Search results will typically only show a few hundred characters worth of content related to the user’s query. But it turns out, search engines are highly optimized at finding the relevant part of the page that corresponds to the user’s query. They are tuned at <a href="https://en.wikipedia.org/wiki/Query_understanding">query understanding</a> and often perform <a href="https://queryunderstanding.com/query-rewriting-an-overview-d7916eb94b83">query rewriting</a> or <a href="https://en.wikipedia.org/wiki/Query_expansion">expansion</a> to increase the recall and quality of the search results.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/snippets.png" width="600" />
<br />

<i>There’s a surprising amount of relevant data just within these short snippets</i>
</p>

<p>So while you could scrape the entire page, simply leveraging the great ranking and retrieval those search engines are doing will save you a lot of work. Plus, you’ll be able to respond to the user very quickly by doing this. Scraping the full page content is error-prone and slow, so your users will just be waiting around. And <a href="https://medium.com/@nikhilbd/how-did-google-surpass-all-the-other-search-engines-8a9fddc68631">speed matters</a> in search. Users want their answer not only correct, but delivered quickly.</p>

<h2 id="step-2-decide-the-llm-you-want-to-use">Step 2: Decide the LLM you want to use</h2>

<p>Here, you’ll need to decide which LLM and inference provider to use. For LLMs, each model has unique strengths, different costs, and different speeds. For example, gpt-4 and Claude 3 Opus are very accurate but expensive and slow. If you want to track the best models, here’s a <a href="https://chat.lmsys.org/?leaderboard">ranking leaderboard</a>. When in doubt, I’d recommend using chatgpt-3.5-turbo from OpenAI. It’s good enough, cheap enough, and fast enough to test this out.</p>

<p>Next, you’ll need to sign up for an API key from an LLM provider. There’s a lot of inference providers to choose from right now. For example there’s <a href="https://platform.openai.com/docs/overview">OpenAI</a>, <a href="https://www.anthropic.com/api">Anthropic</a>, <a href="https://www.anyscale.com/">Anyscale</a>, <a href="https://groq.com/">Groq</a>, <a href="https://ai.cloudflare.com/">Cloudflare</a>, <a href="https://docs.perplexity.ai/docs/getting-started">Perplexity</a>, <a href="https://www.lepton.ai/">Lepton</a>, or the big players like AWS, Azure, or Google Cloud. I’ve used many of these with success and they offer a subset of current and popular closed and open source models.</p>

<p>Fortunately, most of these LLM serving providers are compatible with OpenAI’s API format, so switching to another provider / model is only minimal work (or just ask a <a href="https://yaddleai.com/search/?q=Show+the+code+to+call+openAI%27s+API">chatbot</a> to write the code!).</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/open_ai_curl.png" width="600" />
<br />

<i>Test any queries or view code samples on <a href="https://platform.openai.com/playground">OpenAI’s playground</a></i>
</p>

<h2 id="step-3-craft-a-prompt-to-pass-along-the-search-results-in-the-context-window">Step 3: Craft a prompt to pass along the search results in the context window</h2>

<p>When you want to ask an LLM a question, you can provide a lot of additional context. Each model has its own unique limit and some of them are very large. For <a href="https://platform.openai.com/docs/models/continuous-model-upgrades">gpt-4-turbo</a>, you could pass along the entirety of the <a href="https://towardsdatascience.com/de-coded-understanding-context-windows-for-transformer-models-cd1baca6427e">1st Harry Potter book</a> with your question. Google’s super powerful <a href="https://medium.com/google-cloud/googles-gemini-1-5-pro-revolutionizing-ai-with-a-1m-token-context-window-bfea5adfd35f">Gemini 1.5</a> can support a context size of over a million tokens. That’s enough to pass along the entirety of the 7-book Harry Potter series!</p>

<p>Fortunately, passing along the snippets of 8-10 search results is far smaller, allowing you to use many of the faster (and much cheaper) models like gpt-3.5-turbo or mistral-7b.</p>

<p>In my experience, passing along the user question, custom prompt message, and search result snippets are usually under 1K tokens. This is well under even the most basic model’s limits so this should be no problem.</p>

<p>Here’s a sample prompt I’ve been playing around with you can use (Hat-tip to the folks at <a href="https://www.lepton.ai/">Lepton AI</a> who <a href="https://github.com/leptonai/search_with_lepton">open-sourced a similar project</a> which helped me refine my prompt).</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html">You are an assistant written by Josh Clemm. You will be given a question and you will respond with an answer to the question. It must be accurate, high-quality, and expertly written in a positive, interesting, and engaging manner. It must be informative and in the same language as the user question. You will be provided a set of citations for the question. Each will start with a reference number like [citation:x], where x is a number. Always use the related citations and cite the citation at the end of each sentence in the format [citation:x]. If a sentence comes from multiple citations, please list all applicable citations, like [citation:2][citation:3]. Here are the provided citations:</code></pre></figure>

<p>You will then need to dynamically add the sources / citations from the search results. For a user query like “best vegas hotels for families”, the citations would look a bit like this:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html">[citation:1] As for a hotel, I would echo the Mandalay Bay recommendation. There is a wide variety of restaurants, the pool complex is great, the Shark Reef is fun for families

[citation:2] Treasure Island is closest to the center strip and action as well as walking distance. Excalibur and Circus Circus for kids as 2nd floor has games galore.</code></pre></figure>

<p>The language model will then return the answer with citations like the following:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html">When planning a family trip to Las Vegas, there are excellent hotel options ideal for families. Mandalay Bay is a standout choice, offering a wide variety of amenities suitable for all ages. It features numerous offerings like pools, arcades, and a Shark Reef [citation:1]. Other great options include Treasure Island, Excalibur, and Circus Circus [citation:2].</code></pre></figure>

<p>And that’s it, you now have a working up-to-date answer engine with citations!</p>

<h2 id="step-4-add-related-or-follow-up-questions">Step 4: Add Related or Follow Up Questions</h2>

<p>One of the nice features of Perplexity and others is how they suggest follow up questions. Fortunately, this is easy to replicate.</p>

<p>To do this, you can make a second call to your LLM (in parallel) asking for related questions. And don’t forget to pass along those citations in the context again.</p>

<p>Or, you can attempt to construct a prompt so that the LLM answers the question AND comes up with related questions. This saves an API call and some tokens, but it’s a bit challenging getting these LLMs to always answer in a consistent and repeatable format.</p>

<p>Here’s an example prompt that has the LLM do both:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html">You will be given a question. And you will respond with two things.
First, respond with an answer to the question. It must be accurate, high-quality, and expertly written in a positive, interesting, and engaging manner. It must be informative and in the same language as the user question.
Second, respond with 3 related followup questions. First print "==== RELATED ====" verbatim. Then, write the 3 follow up questions in a JSON array format, so it's clear you've started to answer the second part. Do not use markdown. Each related question should be no longer than 15 words. They should be based on user's original question and the citations given in the context. Do not repeat the original question. Make sure to determine the main subject from the user's original question. That subject needs to be in any related question, so the user can ask it standalone.
For both the first and second response, you will be provided a set of citations for the question. Each will start with a reference number like [citation:x], where x is a number. Always use the related citations and cite the citation at the end of each sentence in the format [citation:x]. If a sentence comes from multiple citations, please list all applicable citations, like [citation:2][citation:3].
Here are the provided citations:</code></pre></figure>

<p>And like before, you then need to dynamically add the citations. And doing so still is less than our 100 lines of code.</p>

<p>I’ve <a href="https://github.com/joshclemm/ai-search/blob/main/search_basic.php">open-sourced this more basic version</a> in PHP. Give it a try!</p>

<h2 id="step-5-make-it-look-a-lot-better">Step 5: Make it look a lot better!</h2>

<p>To make this a complete example, we need a usable UI. And of course we’ll now go well beyond our 100 lines of code. I’m not the best web / frontend engineer, so won’t go into these details. But I will share with you the <a href="https://github.com/joshclemm/ai-search">UI I open sourced</a> (I’m using Bootstrap, jquery, and some basic CSS / javascript to make this happen).</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/caesar.gif" width="550" />
<br />

<i>These questions are making me hungry</i>
</p>

<p>The key parts I added to replicate the Perplexity experience are:</p>

<ul>
  <li>The answer <strong>streams</strong> back to the user (improving perception of speed)</li>
  <li>The <strong>citations</strong> are replaced by a nicer in-line UI with a clickable popup for the user to learn more</li>
  <li>The <strong>sources</strong> considered are included after the answer in case the user wants to explore further</li>
  <li>A second call in parallel is made to a search API for <strong>images</strong> to enhance and supplement results</li>
  <li>Markdown and code syntax <strong>highlighting</strong> are used if necessary</li>
</ul>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/api_code.gif" width="550" />
<br />

<i>Markdown and code syntax highlighting support!</i>
</p>

<h2 id="advanced-ways-to-make-it-even-better">Advanced Ways to Make it even Better</h2>

<p><strong>Query Understanding</strong></p>

<p>Search engines today use <a href="https://saturncloud.io/glossary/query-understanding-in-natural-language-processing-nlp/">natural language processing</a> to determine the type of query a user has typed or to identify the entity. And based on that often will show different results. For example, a question like “top news” will retrieve actual news content vs. just returning websites that have news results. A question about a known person will fetch a “knowledge card” for them to supplement any searched results. Queries that are more basic questions (eg starts with “what” or “why”) may trigger an actual answer at the top of the results.</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/sports_card.jpg" width="300" />
<br />

<i>AI search apps don't do well with queries related to up to date sports scores unless manual query understanding is done</i>
</p>

<p>So if we really want to improve upon search engines today, we need to replicate that query understanding and custom response behavior.</p>

<p>To get more specific, let’s consider searching for news. You could check if the user query says “top news”, and if so, instead of making a search API call, you make a news API call. Or you might be regularly fetching and storing news articles, and you just need to fetch those. Then, you simply pass along those results along with your prompt to the LLM. At which point, the LLM just functions to summarize the articles you supplied.</p>

<p><strong>Follow ups and Threads</strong></p>

<p>All AI search apps offer the ability to ask custom follow up questions. In order to be an actual followup, you’ll need to add the original question and answer into the follow up prompt. You would use a very similar technique to adding web sources as done above.</p>

<p>If the user continues to follow up on the followup (eg a full question thread), you may start getting close to the maximum token length of the context window. At this point, the solution gets a bit more complex and beyond the scope of this article. But you essentially have to start storing, <a href="https://learn.microsoft.com/en-us/azure/search/vector-search-how-to-chunk-documents">chunking</a>, and using your own <a href="https://learn.microsoft.com/en-us/azure/search/vector-search-overview">vector search</a> to pass along the most relevant parts of the past thread. You got this!</p>

<p><strong>Pro Search</strong></p>

<p>Let’s say a topic is particularly difficult to answer. Or is more research oriented. Right now, our current solution is optimized for speed (and cost). Within about a second, we can perform both our search and LLM calls and begin to stream back the answer. But users may have some queries that don’t need to be answered quickly.</p>

<p>There’s some ways of replicating that.</p>

<p>First, in your search call, simply fetch more than the default 8-10 results.</p>

<p>Second, you could start workers to parse and scrape each search result, pulling in way more content for your context. Keep in mind this can introduce significant work since you really should chunk the document into relevant summarizations. Search engines like Google use <a href="https://en.wikipedia.org/wiki/Automatic_summarization#Extraction-based_summarization">extractive summarization</a> for their snippets so you are really trying to replicate that work and they have a pretty large head start on you!</p>

<p>Finally, you’ll want to use a more advanced LLM, like <a href="https://openai.com/gpt-4">gpt-4</a> or <a href="https://www.anthropic.com/news/claude-3-family">Claude 3 Opus</a>. Those are trained on significantly more data. And therefore are far more accurate at inference. Plus, they can take in far more context (like those fully scraped results). But they are considerably slower and cost a lot more, so you’ll want to only use this pro or deeper search when appropriate.</p>

<h2 id="improving-answer-quality">Improving Answer Quality</h2>

<p>To further improve quality of your results, there’s a few things to consider. First, you can always utilize the current most powerful general models like Claude 3 Opus and GPT 4 Turbo. Take a look at the <a href="https://chat.lmsys.org/?leaderboard">LLM ranking leaderboard</a> to see who’s the current top model.</p>

<p>Second, you can ask the user to provide the type of question they are asking (or attempt to infer it). Perplexity has focus options like “Writing”, “Academic”, “Math”. And with that focus, you can then utilize a more appropriate LLM for that. Take a look at <a href="https://www.vellum.ai/llm-leaderboard">this leaderboard</a> to determine the best models for specific tests. For example, some models are better at coding, some are better at creative writing, and some are better at general Q&amp;A.</p>

<p>Finally, you can chain a number of LLMs together as Agents. Andrew Ng <a href="https://www.youtube.com/watch?v=sal78ACtGTc">gave a talk on AI agentic workflows</a> where instead of just using one powerful model, you can use a number of more focused and faster / cheaper models together. This technique significantly increases the quality of the final output. For our use case, you could have one agent perform the initial query and a second that checks and scores it for accuracy. This works particularly well for making sure out-of-date sources aren’t part of the final answer.</p>

<h2 id="test-it-out">Test it out</h2>

<p>First, feel free to explore my <a href="https://github.com/joshclemm/ai-search">open source PHP and basic web code</a>. It works well running on some of the most basic web servers (like on this <a href="https://joshclemm.com/answers/">website</a>).</p>

<p>But to make this example a bit more robust, I ended up porting the PHP code over to Javascript and am using <a href="https://www.cloudflare.com/">Cloudflare</a> (with workers and a KV cache) to up the production level a bit.</p>

<p><strong>Go check out that version at <a href="https://yaddleai.com/">https://yaddleai.com</a></strong></p>

<h2 id="final-thoughts">Final thoughts</h2>

<p>These RAG-based AI search apps are fascinating. They are truly a blend of LLM inference and classic search retrieval and ranking. Is the model answering the question itself but using the search snippets? or is the model simply summarizing the already highly relevant snippets?</p>

<p>Daniel Tunkelang offers <a href="https://dtunkelang.medium.com/ai-powered-search-embedding-based-retrieval-and-retrieval-augmented-generation-rag-cabeaba26a8b">a similar take on RAG search apps</a>: “the role that the generative AI model plays is usually secondary to that of retrieval. The name ‘retrieval-augmented generation’ is a bit misleading — it might be better to call it ‘generative-AI-enhanced retrieval’”.</p>

<p>In any case, combining search + LLMs proves to be a powerful and fairly straightforward way to improve upon the classic question / answer framework.</p>

<p>Let me know if you end up building something like this yourself and share any lessons you learned!</p>]]></content><author><name></name></author><category term="AI" /><category term="Open Source" /><category term="LLM" /><category term="Software Engineering" /><category term="Just For Fun" /><summary type="html"><![CDATA[AI search apps like Perplexity.ai are really cool. They use an LLM to answer your questions, but pull in real-time search results to augment the answer (eg RAG) and list citations. I wanted to know how it works and decided to build my own version. Getting it to work is surprisingly simple and took less than a 100 lines of code. Here's how I did it and what I else I did to make it look great.]]></summary></entry><entry><title type="html">A Brief History of Scaling Uber</title><link href="https://www.joshclemm.com/writing/a-brief-history-of-scaling-uber/" rel="alternate" type="text/html" title="A Brief History of Scaling Uber" /><published>2024-01-11T00:00:00-08:00</published><updated>2024-01-11T00:00:00-08:00</updated><id>https://www.joshclemm.com/writing/a-brief-history-of-scaling-uber</id><content type="html" xml:base="https://www.joshclemm.com/writing/a-brief-history-of-scaling-uber/"><![CDATA[<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/scaling_uber.jpg" width="100%" />
<br />

</p>

<p class="note">
      <i>I wrote this post originally as an article published on LinkedIn. View <a href="https://www.linkedin.com/pulse/brief-history-scaling-uber-josh-clemm-dfqgc">the original there</a>.</i>
  </p>

<p>On a cold evening in Paris in 2008, Travis Kalanick and Garrett Camp couldn’t get a cab. That’s when the idea for <a href="https://www.uber.com/newsroom/history/">Uber was born</a>. How great would it be if you could “push a button and get a ride?”</p>

<p>Fast forward to today where Uber is the <a href="https://s23.q4cdn.com/407969754/files/doc_presentations/2022/Uber-Investor-Day-2022.pdf">largest mobility platform</a> in the world. It operates in over 70 countries and 10,500 cities. Uber Eats is the <a href="https://s23.q4cdn.com/407969754/files/doc_presentations/2022/Uber-Investor-Day-2022.pdf">largest food delivery platform</a> ex-China in the world. It operates across 45 countries. We connect millions of driver-partners and merchants with over 130 million customers. We offer dozens of services to <a href="https://www.uber.com/us/en/u/go-get-it/">go anywhere or get anything</a>. We handle billions of database transactions and millions of concurrent users across dozens of apps and thousands of backend services.</p>

<p>
      <b><i>So, how did we get there?</i></b>
  </p>

<p>
      Back in 2009, Uber hired contractors to build the first version of Uber and launched it to friends in San Francisco soon after. The team used the classic <a href="https://en.wikipedia.org/wiki/LAMP_%28software_bundle%29">LAMP stack</a> to build the first version of Uber and the code was written in Spanish.
  </p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/lamp_stack.png" width="500" />
<br />
<i>Original LAMP stack proved out the use case, but couldn’t scale.</i>
</p>

<p>
    The scaling challenges started as more people wanted to use Uber. There were often major concurrency issues, where we’d dispatch two cars to one person or one driver would be matched to two different riders. (Learn more about <a href="https://www.youtube.com/watch?v=fO83dyxa0Vo">Uber’s earliest architecture from founding engineer Curtis Chambers</a>).
</p>

<p>
    But the product caught on. It was time to build the tech foundations from scratch.
</p>

<h2>Setting up for Global Scale</h2>

<p>
    <i>Circa 2011</i>
</p>

<p>
    To architect a better and more scalable solution, we needed to address those concurrency problems. Additionally, we needed a system that can process tons of real-time data. Not only are there requests from riders, but Uber needs to track driver real-time locations in order to match riders as efficiently as possible. Finally, the product was still early and would require a lot of testing and iteration. We needed a solution to solve all these scenarios.
</p>

<p>
Uber adopted <a href="https://nodejs.org/en">Node.js</a> for their real-time needs. And ended up being <a href="https://openjsf.org/wp-content/uploads/sites/50/2017/09/Nodejs-at-Uber.pdf">one of the first major adopters of Node.js</a> in production. Node.js was ideal for a few reasons. First, Node.js handles requests asynchronously (using a non-blocking, single-threaded event loop) and so can process significant amounts of data quickly. Second, Node.js runs on the <a href="https://nodejs.dev/en/learn/">V8 JavaScript engine</a>, so not only is it performant, but it’s excellent for quick iteration.
</p>
<p>
Uber then created a second service built in Python to handle business logic functions like authentication, promotions, and fare calculation.
</p>
<p>
The resulting architecture was two services. One built in Node.js (“<code>dispatch</code>”) connected to MongoDB (later Redis) and the other built in Python (“<code>API</code>”) connected to PostgreSQL. 
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/two_monoliths.png" width="500" />
<br />
<em>Uber’s two monolith architecture allowed the engineering org to begin to scale</em>
</p>

<p>
And to improve the resiliency of Uber’s core dispatching flow, a layer between dispatch and API known as “ON” or Object Node was built to withstand any disruptions within the API service. (<em>Learn more about Uber’s earliest efforts to maintain service reliability in this video <a href="https://www.youtube.com/watch?v=pKpnAX612k0">Designing for Failure: Scaling Uber by Breaking Everything</a></em>).
</p>
<p>
This architecture started to resemble a service oriented architecture. Service oriented architectures can be very powerful. As you carve out services to handle more dedicated functionality, it has a side benefit of allowing easier separation of engineers into dedicated teams. Which then allows for more rapid team scaling.
</p>
<p>
But as the team and number of features grew, the <code>API</code> service was getting bigger and bigger. More and more features were conflicting with one another. Engineering productivity was slowing down. There were huge risks continuously deploying the codebase.
</p>
<p>
It was time to split out <code>API</code> into proper services.
</p>

<h2><strong>From Monolith to Microservices</strong></h2>

<p>
<em>Circa 2013</em>
</p>
<p>
To prepare for our next phase of growth, we decided to <a href="https://www.uber.com/blog/service-oriented-architecture/">adopt a microservice architecture</a>. This design pattern enforces the development of small services dedicated to specific, well-encapsulated domain areas (e.g. rider billing, driver payouts, fraud detection, analytics, city management). Each service can be written in its own language or framework, and can have its own database or lack thereof. However, many backend services utilized Python and many started to adopt <a href="https://www.uber.com/blog/building-tincup-microservice-implementation/">Tornado</a> to provide asynchronous response functionality. By 2014, we had roughly 100 services.
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/api_to_microservices.png" width="500" />
<br />
<em>Uber’s API monolith to microservice migration</em>
</p>

<p>
While microservices can solve many problems, it also introduces <em>significant operational complexity</em>. You must only adopt microservices after <a href="https://joshclemm.com/writing/want-to-move-to-microservices/">understanding the tradeoffs</a> and potentially build or leverage tools to counteract those tradeoffs. And if you don’t consider the operational issues, you will simply create a <a href="https://www.microservices.com/talks/dont-build-a-distributed-monolith/">distributed monolith</a>.
</p>
<p>
Here’s some examples of the issues microservices create and what Uber did to address.
</p>
<ul>

<li>To ensure all services use <strong>standardized service frameworks</strong>, we developed <a href="https://github.com/uber/clay">Clay</a>. This was a Python wrapper on <a href="http://flask.pocoo.org/">Flask</a> to build restful backend services. It gave us consistent monitoring, logging, HTTP requests, consistent deployments, etc.</li>

<li>To <strong>discover and talk to other services</strong> and provide service <strong>resilience</strong> (fault tolerance, rate limiting, circuit breaking), Uber built <a href="https://github.com/uber/tchannel">TChannel</a> over <a href="https://github.com/uber/hyperbahn">Hyperbahn</a>. TChannel as our bi-directional RPC protocol was built in-house mainly to gain better performance and forwarding for our Node and Python services, among other benefits.</li>

<li>To ensure <strong>well-defined RPC interfaces</strong> and stronger contracts across services, Uber used <a href="http://thrift.apache.org/">Apache Thrift</a>.</li>

<li>To prevent <strong>cross-service capability</strong> issues, we use <a href="https://www.uber.com/blog/flipr/">Flipr</a> to feature flag code changes, control rollouts, and many other config-based use cases.</li>

<li>To improve the <strong>observability</strong> of all service metrics, we built <a href="https://www.uber.com/blog/m3/">M3</a>. M3 allows any engineer easy ways to observe the state of their service both offline or through Grafana dashboards. We also leverage <a href="https://www.uber.com/blog/observability-at-scale/">Nagios</a> for alerting at scale.</li>

<li>For <strong>distributed tracing</strong> across services, Uber developed <a href="https://www.uber.com/blog/distributed-tracing/">Merckx</a>. This pulled data from a stream of instrumentation via Kafka. But as each service started to introduce asynchronous patterns, we needed to evolve our solution. We were inspired by Zipkin and ultimately developed <a href="https://github.com/jaegertracing/jaeger">Jaeger</a>, which we still use today.</li>
</ul>
<p>
Over time, we’ve migrated to newer solutions like <a href="https://en.wikipedia.org/wiki/GRPC">gRPC</a> and Protobuf for <a href="https://en.wikipedia.org/wiki/Interface_description_language">interfaces</a>. And many of our services utilize Golang and Java.
</p>
<h2><strong>Scaling the Trip Database</strong></h2>

<p>
<em>Circa 2014</em>
</p>
<p>
While Uber was creating many new backend services, they all continued to use one single PostgreSQL database.
</p>
<p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/postgres_bottleneck.png" width="500" />
<br />
<em>The single PostgreSQL DB became a bottleneck</em>
</p>

<p>
We were hitting some significant issues. First, the performance, scalability, and availability of this DB was struggling. There was only so much memory and CPUs you could throw at it. Second, it was getting very hard for engineers to be productive. Adding new rows, tables, or indices for new features became problematic.
</p>
<p>
And the problem was getting existential. By early 2014, Uber was 6 months away from <a href="https://www.uber.com/blog/mezzanine-codebase-data-migration/">Halloween night</a> - one of the biggest nights of the year. We needed a more scalable solution and needed it fast.
</p>
<p>
When we looked into the data mix of this DB, the majority of storage was related to our trips, which was also growing the fastest.
</p>
<p>







<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/data_usage.jpeg" width="400" />
<br />

<i>The mix of data stored in our single PostgreSQL DB in early 2014</i>
</p>



<p>
We use trip data in order to improve services like Uber Pool, provide rider and driver support, prevent fraud, and develop and test features like suggested pick-ups. So we embarked on developing <a href="https://www.uber.com/blog/schemaless-part-one-mysql-datastore/">Schemaless</a>, our new trip data store. Schemaless is an append-only sparse three dimensional persistent hash map, similar to Google’s Bigtable, and <a href="https://www.uber.com/blog/postgres-to-mysql-migration/">built on top of MySQL</a>. This model lended itself naturally to horizontal scaling by partitioning the rows across multiple shards and supported our rapid development culture. 
</p>
<p>
And we successfully <a href="https://www.uber.com/blog/mezzanine-codebase-data-migration/">migrated all our services</a> that access trip information in time to avert the Halloween peak traffic disaster. <em>(Learn more with this <a href="https://www.youtube.com/watch?v=Dg76cNaeB4s">video from lead engineer Rene Schmidt about our creation of and migration to Schemaless</a>).</em>
</p>
<p>







<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/schemaless_ops.jpeg" width="400" />
<br />

<i>The Schemaless migration operational room - a common look into migrations at scale</i>
</p>



<p>
While we used Schemaless for our trip data store, we started to use <a href="https://cassandra.apache.org/_/index.html">Cassandra</a> as a replacement for our other data needs, including the database that we use for marketplace matching and dispatching.
</p>
<h2><strong>Splitting up Dispatch</strong></h2>


<p>
<em>Circa 2014</em>
</p>
<p>
Among Uber’s original two monoliths, we discussed the evolution of <code>API</code> into hundreds of microservices. But <code>dispatch</code> similarly was doing too much. Not only did it handle matching logic, it was the proxy that routed all other traffic to other microservices within Uber. So we embarked on an exercise to split up dispatch into two areas of cleaner separation.
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/rtapi.png" width="500" />
<br />
<em> Splitting the monolithic dispatch service into a real-time API gateway and an actual dispatch service</em>
</p>


<h3><strong>Extracting Uber’s Mobile Gateway from Dispatch</strong></h3>

<p>
To better handle all the real-time requests from our mobile apps, we created a <a href="https://www.uber.com/blog/gatewayuberapi/">new API gateway layer named RTAPI</a> (“Real-Time API”). And we continued to use Node.js for it. The service was a single repository that was broken up into multiple specialized deployment groups to support our growing businesses.
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/rtapi_details.png" width="500" />
<br />
<em>RTAPI provided a powerful new real-time layer that maintain high development velocity</em>
</p>

<p>
The gateway provided a very flexible development space for writing new code and had access to the hundreds of services within the company. For instance, the first generation of Uber Eats was completely developed within the gateway. As the team's product matured, pieces were moved out of the gateway and into proper services.
</p>
<h3><strong>Rewriting Dispatch for Uber’s Growing Size</strong></h3>


<p>
The original <code>dispatch</code> service was designed for more simplistic transportation (one driver-partner and one rider). There were deep assumptions that Uber only needed to move people and not food or packages. Its state of available driver-partners was sharded by city. And some cities were seeing massive growth of the product.
</p>
<p>
So, <code>dispatch</code> was rewritten into a series of services. The new dispatch system needed to understand much more about the types of vehicles and rider needs.
</p>
<p>
It took on advanced matching optimizations, essentially to solve a version of the <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">traveling salesman problem</a>. Not only did it look at the ETAs of currently available driver-partners, but needed to understand which drivers could be available in the near future. So we had to build a geospatial index to capture this information. We used <a href="https://s2geometry.io/">Google’s S2 library</a> to segment cities into areas of consideration and used the S2 cell ID as the sharding key. <em>(We’ve since updated to and <a href="https://www.uber.com/blog/h3/">open-sourced H3</a>)</em>
</p>


<p align="center" style="font-size:90%;">
<img data-src="/assets/img/dispatch_stack.png" width="500" />
<br />
<em>RTAPI provided a powerful new real-time layer that maintain high development velocity</em>
</p>

<p>
Since these services were still running on Node.js and were stateful, we needed a way to scale as the business grew. So we developed <a href="https://www.uber.com/blog/ringpop-open-source-nodejs-library/">Ringpop</a>, a gossip-protocol based approach to share geospatial and supply positioning for efficient matching.
</p>
<p>
<em>Learn more about <a href="https://www.uber.com/blog/fulfillment-platform-rearchitecture/">the history of our dispatch stack</a> here or watch this <a href="https://www.infoq.com/presentations/uber-market-platform">video on Scaling Uber’s Real-time Market Platform</a>.</em>
</p>
<h2><strong>Mobile App Development at Scale</strong></h2>


<p>
<em>Circa 2016 to present</em>
</p>
<p>
The flagship Uber product could only have existed due to the new mobile paradigm created by the iPhone and Android OS launches in 2007. These modern smartphones contained key capabilities like location-tracking, seamless mapping, payment experiences, on-device sensors, feature-rich user experiences, and so much more.
</p>


<p align="center" style="font-size:90%;">
<img data-src="/assets/img/rider_app.jpeg" width="500" />
<br />
<em>The Uber rider app was critical in defining a scalable mobile architecture</em>
</p>

<p>
So Uber’s mobile apps were always a critical part of our scaling story.
</p>
<p>
As Uber scaled across the globe, there was a need for an ever-growing list of features. Many of these were specific to certain countries like localized payment types, different car product types, detailed airport information, and even some newer bets in the app like Uber Eats and Uber for Business.
</p>
<p>
The mobile app’s repositories slowly hit similar bottlenecks to a backend monolith. Many features and many engineers, all trying to work across a single releasable code base.
</p>
<p>
This led Uber to develop the <a href="https://github.com/uber/RIBs">RIB architecture</a> for mobile, starting with the <a href="https://www.uber.com/blog/new-rider-app-architecture/">rewrite of the main Uber app</a>.
</p>


<p align="center" style="font-size:90%;">
<img data-src="/assets/img/rib.png" width="100%" />
<br />
<em>RIB stands for Router, Interactor, Builder</em>
</p>

<p>
Like the benefits of microservices, RIBs have clear separation of responsibilities. And since each RIB serves a single responsibility, it was easy to separate them and their dependencies into core and optional code. By demanding more stringent review for core code, we were more confident in the availability of our core flows. And this allows simple feature flagging to ensure the app continues to run reliably.
</p>
<p>
And like microservices, RIBs can be owned by different teams and engineers. This allowed our mobile codebases to easily scale to hundreds of engineers.
</p>
<p>
Today, all our apps have adopted RIBs or are migrating towards it. This includes our main <a href="https://www.uber.com/blog/driver-app-ribs-architecture/">Driver</a> app, the Uber Eats apps, and Uber Freight.
</p>
<h2><strong>The Rise of Uber Eats</strong></h2>


<p>
<em>Circa 2017</em>
</p>
<p>
Uber had been experimenting with a number of “Uber for X on-demand” concepts since 2014. And all early signs pointed towards food. So in late 2015 Uber Eats <a href="https://www.uber.com/en-CA/newsroom/ubereatsapp/">launched in Toronto</a>. And followed a similarly fast growth trajectory just like UberX.
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/delivery_charts.jpeg" width="500" />
<br />
<em>Uber Eats business growth compared with Uber rides (<a href="https://s23.q4cdn.com/407969754/files/doc_financials/2020/q3/Quarterly-Earnings-Report-Q32020_Final.pdf">Uber Q3 2020 Earnings</a></em>)
</p>

<p>
To enable this rapid growth, Uber Eats leveraged as much of the existing Uber tech stack as possible, while creating new services and APIs that were unique to food delivery (e.g. e-commerce capabilities like carts, menus, search, browsing).
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/ue_early_arch.png" width="500" />
<br />
<em>A simplified view into early Uber Eats architecture and how it leveraged systems built for original Uber</em>
</p>

<p>
The operations team that needed to tune their cities’ marketplace often got creative and did things that didn’t scale (until the appropriate tech was built).
</p>







<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/uber_eats_canada_laptop.png" width="500" />
<br />

<i>Uber Eats Canada ran scripts to manage active stores and their delivery radius. <a href="https://joshclemm.com/writing/do-things-that-dont-scale/">Learn more about this story </a>.</i>
</p>



<p>
Early Uber Eats was "simple" in that it supported a three-way marketplace of one consumer, one restaurant, and one driver-partner. Uber Eats today (130+ million users, dozens of countries) supports a variety of ordering modes and capabilities and can support 0-N consumers (eg. guest checkout, group ordering), N merchants (eg. multi-restaurant ordering), and 0-N driver partners (eg. large orders, restaurants which supply their own delivery fleet).
</p>
<p>
The history of how Uber Eats evolved probably deserves its own scaling story and I may one day get to it.
</p>
<p>
But for now, to learn more from the earliest days, I highly recommend listening to Uber Eats founder Jason Droege’s recount of <a href="https://open.spotify.com/episode/09i4yZHqVeGuB1u7wcRref">“Building Uber Eats”</a>.
</p>
<h2><strong>Bring on the Standards - Project Ark</strong></h2>


<p>
<em>Circa 2018</em>
</p>
<p>
No scaling story is complete without understanding the context and culture of the company. As Uber continued to expand city by city, local operations folks were hired to ensure their city launch would go successfully. They had tools to ensure their local marketplace would remain healthy and the flywheel would grow. As a result, Uber had a very distributed and decentralized culture. And that helped contribute to Uber’s success in getting to 600 cities by 2018.
</p>
<p>
That culture of decentralization continued within engineering, where one of our earliest cultural values was “Let Builders Build”. This resulted in rapid engineering development that complemented Uber’s success growing across the globe. 
</p>
<p>
But after many years, it resulted in the proliferation of microservices (thousands by 2018), thousands of code repositories, multiple product solutions solving very similar problems, and multiple solutions to common engineering problems. For example, there were different messaging queue solutions, varying database options, communication protocols, and even many choices for programming languages..
</p>
<blockquote>
    "You've got five or six systems that do incentives that are 75 percent similar to one another."
    <br /><br />
    <b>Thuan Pham, Former Uber CTO</b>
</blockquote>
<p>
Developer productivity was hurting.
</p>
<p>
Engineering leadership recognized it was time for more standardization and consistency and formed <a href="https://stanford.edu/dept/gsb-ds/Inkling/Uber_Repaying_Technical_and_Organizational_Debt/index.html#">Project Ark</a>. Project Ark sought to address many aspects of engineering culture that contributes to scaling:
</p>
<ul>

<li>Engineer productivity</li>

<li>Engineer alignment across teams</li>

<li>Duplication</li>

<li>Unmaintained critical systems</li>

<li>Knowledge access and documentation</li>
</ul>

<p>
As a result, we elevated Java and Go as official backend languages to <a href="https://www.uber.com/blog/go-geofence-highest-query-per-second-service/">gain type-safety and better performance</a>. And deprecated the use of Python and Javascript for backend services. We embarked on reducing code repos from 12,000 down to just our main languages (Java, Go, <a href="https://www.uber.com/blog/ios-monorepo/">iOS</a>, <a href="https://www.uber.com/blog/android-engineering-code-monorepo/">Android</a>, and web). We defined more standardized architectural layers where client, presentation, product, and business logic would have clear homes. We introduced abstractions where we could group a number of services together (service “<a href="https://www.uber.com/blog/microservice-architecture/">domains</a>”). And continued to standardize on a series of service libraries to handle <a href="https://github.com/jaegertracing/jaeger">tracing</a>, logging, networking protocols, resiliency patterns, and more.
</p>
<h2><strong>A Modern Gateway for the Modern Uber</strong></h2>


<p>
<em>Circa 2020</em>
</p>
<p>
By 2019, Uber had many business lines with numerous new applications (Uber Eats, Freight, ATG, Elevate, and more). Within each line of business, the teams managed their backend systems and their app. We needed the systems to be vertically independent for fast product development.
</p>
<p>
And our <a href="https://www.uber.com/blog/gatewayuberapi/">current mobile gateway was showing its age</a>. RTAPI had been built years ago and continued to use Node.js and Javascript, a deprecated language. We were also eager to make use of Uber’s newly defined architectural layers as the ad hoc code added to RTAPI over the years was getting messy with view generation and business logic.
</p>
<p>
So we built a new <a href="https://www.uber.com/blog/gatewayuberapi/">Edge Gateway</a> to start standardizing on the following layers:
</p>
<ul>

<li>Edge Layer: <a href="https://www.uber.com/blog/architecture-api-gateway/">API lifecycle management layer</a>. No extraneous logic can be added keeping it clean.</li>

<li>Presentation Layer: microservices that build view generation and aggregation of data from many downstream services.</li>

<li>Product Layer: microservices that provide functional and reusable APIs that describe their product. Can be reused by other teams to compose and build new product experiences.</li>

<li>Domain Layer: microservices that are the leaf node that provides a single refined functionality for a product team.</li>
</ul>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/edge_gateway.png" width="500" />
<br />
</p>

<p>
This evolution set us up well to continue building new products with velocity, yet with the necessary structure to align our 1000s of engineers.
</p>
<h2><strong>Next Generation Fulfillment</strong></h2>


<p>
<em>Circa 2021</em>
</p>
<p>
Throughout the years, Uber has created a world-class platform for matching riders and driver-partners. So our dispatch and fulfillment tech stack is a critical part of Uber’s scaling story.
</p>
<p>
By 2021, Uber sought to power more and more delivery and mobility use cases. The fulfillment stack was showing its age and couldn’t easily support all these new scenarios. For example, we needed to support reservation flows where a driver is confirmed upfront, batching flows with multiple trips offered simultaneously, virtual queue mechanisms at Airports, the three-sided marketplace for Uber Eats, and delivering packages through Uber Direct.
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/fulfillment_products.png" width="500" />
<br />
<em>Some example services the Fulfillment Platform needed to support</em>
</p>

<p>
So we made a bold bet and embarked on a journey to <a href="https://www.uber.com/blog/fulfillment-platform-rearchitecture/">rewrite the Fulfillment Platform from the ground up</a>.
</p>
<p>
To satisfy the requirements of transactional consistency, horizontal scalability, and low operational overhead, we decided to leverage a NewSQL architecture. And opted to use <a href="https://www.uber.com/blog/building-ubers-fulfillment-platform/">Google Cloud Spanner</a> as the primary storage engine.
</p>
<p>
As <a href="https://cloud.google.com/blog/products/databases/announcing-cloud-spanner-price-performance-updates">lead Uber engineer Ankit Srivastava</a> puts it "as we scale and expand our global footprint, Spanner's scalability &amp; low operational cost is invaluable. Prior to integrating Spanner, our data management framework demanded a lot of oversight and operational effort, escalating both complexity and expenditure. The adoption of Spanner streamlined operational costs, improved overall reliability, and strengthened overall value by providing better throughput and performance for the same price.”
</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/uber_gcp.png" width="500" />
<br />
<em>The Uber-GCP network infrastructure</em>
</p>

<h2><strong>What else have we done?</strong></h2>

<p>
Of course, our scaling story is never this simple. There’s a countless number of things we’ve done over the years across all engineering and operations teams, including some of these larger initiatives:
</p>
<p>
Many of our most critical systems have their own rich history and evolution to address scale over the years. This includes our <a href="https://www.uber.com/blog/gatewayuberapi/">API gateway</a>, <a href="https://www.uber.com/blog/fulfillment-platform-rearchitecture/">fulfillment stack</a>, <a href="https://www.uber.com/blog/money-scale-strong-data/">money stack</a>, <a href="https://www.uber.com/blog/gairos-scalability/">real-time data intelligence platform</a>, <a href="https://www.uber.com/blog/visualizing-city-cores-with-h3/">geospatial data platform</a> (where we open-sourced <a href="https://h3geo.org/">H3</a>), and building machine learning at scale through <a href="https://www.uber.com/blog/scaling-michelangelo/">Michelangelo</a>.
</p>
<p>
We’ve introduced various layers of Redis caches. We’ve enabled powerful new frameworks to aid in scalable and reliable systems like <a href="https://www.uber.com/blog/cadence-multi-tenant-task-processing/">Cadence</a> (for writing fault-tolerant, long-running workflows). 
</p>
<p>
We’ve built and leveraged data infrastructure that <a href="https://www.uber.com/blog/uber-big-data-platform/">enables long term growth</a>, like how we <a href="https://www.uber.com/blog/presto-on-apache-kafka-at-uber-scale/">leveraged Presto</a> or <a href="https://www.uber.com/blog/uscs-apache-spark/">scaled Spark</a>. Notably, we built <a href="https://www.uber.com/blog/apache-hudi-graduation/">Apache Hudi</a> to power business critical data pipelines at low latency and high efficiency.
</p>
<p>
And finally, we continue to improve the <a href="https://www.uber.com/blog/compute-cluster-management/">performance of our servers</a> with optimized hardware, advanced <a href="https://www.uber.com/blog/how-we-saved-70k-cores-across-30-mission-critical-services/">memory</a> and <a href="https://www.uber.com/blog/vertical-cpu-scaling/">system tuning</a>, and utilizing newer runtimes.
</p>

<h2><strong>What’s Next? Heading to the Cloud</strong></h2>

<p>
Being a global company, Uber operated out of multiple on-prem data centers from the earliest days. But that introduced a number of challenges.
</p>
<p>
First, our server fleet had grown rapidly (over 250,000 servers) and the tooling and teams were always trying to keep up. Next, we have a large geographical footprint and need to regularly expand into more data centers and availability zones. Finally, with only on-prem machines, we were constantly needing to tune the size of our fleet.
</p>
<p>
We spent the last few years working towards making over <a href="https://www.infoq.com/news/2023/10/uber-up-cloud-microservices/">4000 of our stateless microservices portable</a>. And to ensure our stack would work equally well across cloud and on-prem environments, we embarked on <a href="https://www.uber.com/blog/crane-ubers-next-gen-infrastructure-stack/">Project Crane</a> to solve. This effort set Uber up well for the future. To learn more, watch lead Uber engineer <a href="https://www.youtube.com/watch?v=JQPTlrff8iU">Kurtis Nusbaum’s talk on how Crane</a> solves our scaling problems.
</p>
<p>
We now have plans to migrate a larger portion of our online and offline server fleet <a href="https://www.forbes.com/sites/danielnewman/2023/02/21/uber-goes-big-on-cloud-with-google-and-oracle-as-cloud-architecture-debate-continues/">to the Cloud</a> over the next few years!
</p>
</p></p></p>]]></content><author><name></name></author><category term="Architecture" /><category term="Scaling" /><category term="Microservices" /><category term="External Writing" /><category term="Uber" /><summary type="html"><![CDATA[On a cold evening in Paris in 2008, Travis Kalanick and Garrett Camp couldn't get a cab. Fast forward to today where Uber is the largest mobility platform in the world, in over 70 countries, 10,500 cities, and 130 million users. Here's the story of the key moments in scaling Uber engineering systems.]]></summary></entry><entry><title type="html">Recreating my Site as a 90s Webpage</title><link href="https://www.joshclemm.com/writing/recreating-the-90s-website-look/" rel="alternate" type="text/html" title="Recreating my Site as a 90s Webpage" /><published>2022-12-22T00:00:00-08:00</published><updated>2022-12-22T00:00:00-08:00</updated><id>https://www.joshclemm.com/writing/recreating-the-90s-website-look</id><content type="html" xml:base="https://www.joshclemm.com/writing/recreating-the-90s-website-look/"><![CDATA[<p>Over the past 17 years, I’ve worked as a software engineer and manager, building some of the world’s favorite consumer products like Uber, Uber Eats, and LinkedIn.</p>

<p>Even though I moved to engineering management in 2011, I still love to code and continue to work on <a href="/apps">side projects</a> and even at work.</p>

<p>My first time “coding” was during the early days of the web using HTML and <a href="https://webdevelopmenthistory.com/1995-the-birth-of-javascript/">Javascript</a>. I fondly remember putting together my personal website back in 1996, hosted by flash.net. That site is long gone, but the Internet Archive has a record of a <a href="https://web.archive.org/web/19991010125311/http://clemm.com/josh/">site I created in 1999</a>.</p>

<p>So I decided to create a homage to that past and make <a href="https://joshclemm.com/96.htm">a 90s version of my home page</a>.</p>

<h2 id="inspiration">Inspiration</h2>

<p>I have nostalgia and fondness from those early days of the web. I love that the <a href="https://www.spacejam.com/1996/jam.html">Space Jam movie website</a> still exists and of course, loads super quickly. Modern javascript frameworks are powerful but often overkill.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/space_jam.jpg" width="250" />
<br />

<i>Space Jam 1996</i>
</p>

<p>To inspire the 90s version of my homepage, I looked at the <a href="https://www.webdesignmuseum.org/exhibitions/web-design-in-the-90s">Web Design Museum</a>, the <a href="https://www.spacejam.com/1996/jam.html">Space Jam website</a>, the Yahoo site <a href="https://www.webdesignmuseum.org/gallery/yahoo-1997">from 1997</a>, and my own website memories and on the <a href="https://web.archive.org/web/19991010125311/http://clemm.com/josh/">wayback machine</a>.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/josh_1999.png" width="250" />
<br />

<i>My personal links page from 1999</i>
</p>

<h2 id="gifs-and-web-badges">Gifs and Web Badges</h2>

<p>No retro site is complete without blinking text, flashing gifs, and those tiny web badges to mark your personality.</p>

<p>Here’s a few resources I used for that.</p>
<ul>
  <li><a href="https://gifcities.org/?q=">GifCities</a>, the Animated GIF Search Engine hosted by the Internet Archive
<img src="https://joshclemm.com/1996/divider12.gif" height="25" /></li>
  <li><a href="https://99gifshop.neocities.org/">99 Gif Shop</a>, for even more inspiring animated gifs</li>
  <li><a href="https://cyber.dabamos.de/88x31/index.html">The 88x31 GIF Collection</a>, an archive of those famous retro web badges
<br /><img src="https://joshclemm.com/1996/netscapeb.gif" />
<img src="https://joshclemm.com/1996/java_green_button.gif" /></li>
  <li><a href="https://www.cameronsworld.net/">Cameron’s World</a>, an experience of the 90s in itself. Do yourself a favor and give it a click through!</li>
  <li><a href="https://www.free-website-hit-counter.com/">Free website hit counter</a>
<br /><img src="/assets/img/counter.gif" /></li>
</ul>

<h2 id="pixelated-fonts">Pixelated Fonts</h2>

<p>Things were looking good, except that the font rendering itself looked much too modern and smooth. “Old” fonts like Times New Roman render much nicer today using high resolution screens and <a href="https://en.wikipedia.org/wiki/Font_rasterization">antialiasing</a>.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/modern_fonts.jpg" width="500" />
<br />

<i>We're not tricking anyone with these super crisp and smooth fonts</i>
</p>

<p>We needed to actually create a retro-looking version of Times New Roman. For this, I found a great resource that looked into creating <a href="https://web.archive.org/web/20230407020457/https://vistaserv.net/blog/90s-fonts-modern-browsers">convincing-looking 90s fonts in modern browsers</a>. After bringing over those font packages, the site really took on that retro feel.</p>

<h2 id="retro-looking-images">Retro Looking Images</h2>

<p>The final piece of my 90s homepage conversion was to make sure the pictures had that retro look. With today’s fast internet speeds and high resolution monitors, pictures look fantastic. But in the 90s, monitors were older and speed was a luxury. So images were a fraction of the size and often were pixelated.</p>

<p><em>So how should I pixelate my pictures?</em></p>

<p>We can emulate the older look by adding random noise, a technique known as <a href="https://en.wikipedia.org/wiki/Dither">Dithering</a>. For this project, I found the <a href="https://doodad.dev/dither-me-this/">Dither Me This</a> tool using the “RBG Dither” option to really work nicely.</p>

<p>You then get photos that look like this:
<img src="https://joshclemm.com/1996/dithered-josh.png" width="400" /></p>

<h2 id="were-done">We’re Done!</h2>

<p>Now, go check out <a href="https://joshclemm.com/96.htm">the 90s version of joshclemm.com</a>. I’d love to see you create your own retro site one day too. Thanks!</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/josh_96_preview.jpeg" width="80%" />
<br />

</p>]]></content><author><name></name></author><category term="Web Development" /><category term="Just For Fun" /><summary type="html"><![CDATA[I revisited the look of early web design by recreating my home page with a 90s theme. Drawing inspiration from iconic sites like Space Jam, I incorporated classic elements such as gif badges, pixelated fonts, and dithered images to capture the era's distinct look.]]></summary></entry><entry><title type="html">Using AI Language Models to Generate Fantasy Football Player Outlooks</title><link href="https://www.joshclemm.com/writing/generative-player-outlooks/" rel="alternate" type="text/html" title="Using AI Language Models to Generate Fantasy Football Player Outlooks" /><published>2022-08-04T00:00:00-07:00</published><updated>2022-08-04T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/generative-player-outlooks</id><content type="html" xml:base="https://www.joshclemm.com/writing/generative-player-outlooks/"><![CDATA[<p>I’ve been heads-down on <a href="https://draftpunk.net">Draft Punk</a>, my fantasy football drafting companion app, and this offseason I wanted the player pages to have season outlook writeups. The same that all the best analysts and beat writers create with context, nuance, and a clear “what to do” at draft time.</p>

<p>I already had a ton of data about the upcoming season. I had player info, past statistics, team schedules, team news, player news, and depth charts. And I generate <a href="/writing/fantasy-projections/">player projections</a> using wisdom of the crowd, player tiers, automatic sleepers/busts, draft recommendations, and even use <a href="https://en.wikipedia.org/wiki/Markov_chain">Markov chains</a> to predict availability odds.</p>

<p>But I needed a compelling narrative to stitch that all together. So I went exploring large language models.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/dp22_outlook_crop.jpg" width="300" />
<br />

</p>

<h1 id="the-language-models-i-tried">The language models I tried</h1>

<p>I found OpenAI and their GPT-3 models to be pretty far along on the research and capability of large language models. We had explored a project at Uber Eats to use conversational AI to receive food orders from phone calls, so I was familiar with the technology and wanted to put it to use.</p>

<ul>
  <li>
    <p><strong>text-davinci-002</strong> 
Best long-form writer I tested. Good at following directions, keeping a consistent voice, and weaving facts into paragraphs. Slower and pricier, but the output was closest to “publishable.” (<a href="https://openai.com/research/instruction-following">InstructGPT background</a>)</p>
  </li>
  <li><strong>text-curie-001</strong>
Fast and cheap. Great for shorter tasks—headlines, bullet points, tier labels, and re-phrasing. It struggled with longer narratives without heavy scaffolding. (<a href="https://platform.openai.com/docs/models">OpenAI API models overview</a>)</li>
  <li>
    <p><strong>text-babbage-001 / text-ada-001</strong>
Useful workhorses for lightweight tasks: classifying news blurbs as relevant/irrelevant, tagging injury notes, quick sentiment on coach quotes. (<a href="https://platform.openai.com/docs/api-reference/introduction">API reference</a>)</p>
  </li>
  <li><strong>Embeddings (text-similarity-* and text-search-* families)</strong>
Critical for grounding. I used these to index player projections, depth charts, recent news, schedule difficulty, team tendencies, and my Markov availability odds so the generator could pull the right facts before it wrote anything. (<a href="https://platform.openai.com/docs/guides/embeddings">Embeddings guide</a>)</li>
</ul>

<p><em>You’ll see later, I ended up finding generating embeddings and fetching relevant chunks were sometimes overkill. I could simply pass various player info straight into the language model and got pretty good results</em></p>

<h1 id="what-i-tried-in-the-apis">What I tried in the APIs</h1>

<p>I did nearly everything through the <a href="https://platform.openai.com/docs/api-reference/completions">Completions API</a> and <a href="https://platform.openai.com/docs/api-reference/embeddings">Embeddings API</a>. A few patterns emerged.</p>

<h2 id="1-retrieval-augmented-generation-rag-to-keep-it-honest">1. Retrieval-augmented generation (RAG) to keep it honest</h2>
<p>LLMs are great at writing; they’re less great at being right by default. I built a tiny retrieval layer:</p>

<ol>
  <li>Chunk my data into bite-sized passages per player (projections deltas, role notes, schedule flags, depth chart context, news summaries).</li>
  <li>Embed each chunk (<code class="language-plaintext highlighter-rouge">text-similarity-ada-001</code> was the budget pick; <code class="language-plaintext highlighter-rouge">*-curie-001</code> gave a small relevance bump).</li>
  <li>At write time, embed the prompt/question, grab the top-k chunks via <a href="https://en.wikipedia.org/wiki/Cosine_similarity">cosine similarity</a>, and stuff those facts into the prompt before generating.</li>
</ol>

<p>This kept the model focused on <em>my</em> numbers and <em>today’s</em> situation. If you want the deeper theory, start with Retrieval-Augmented Generation by Lewis et al. (<a href="https://arxiv.org/abs/2005.11401">paper</a>).</p>

<p>I did find the effort generating vector embeddings was overkill at times. Later I found simply passing along the formatted text in the prompt template fit within the context window, generated as good results as before, and frankly simplified the setup for this project.</p>

<p>The original <a href="https://arxiv.org/abs/2005.11401">Retrieval-Augmented Generation paper</a> seemed to suggest vectors were critical for this technique. But given we are working with text in the prompt (which then gets encoded in the language model), I tried it and it was pretty good. My input context size was small enough to work. So I could see how trying to apply RAG on a larger data set would need that pre-processing.</p>

<h2 id="2-prompt-scaffolding-templates--facts">2. Prompt scaffolding (templates + facts)</h2>
<p>I learned quickly that “write a blurb about DK Metcalf” yields creative fiction. The fix was structure and specifics.</p>

<p><strong>Example Prompt template:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You are a fantasy football analyst writing a 180–220 word season outlook report for the 2022 NFL season.
Voice: clear, direct, helpful. Avoid cliches.

FACTS (verbatim, may include numbers):
- Team: SEA. Role: X. Depth chart: Y.
- Projection (PPR): 85-1,050-9, WR12 consensus (range WR8–WR16).
- Average Draft Position (ADP): 67
- Schedule: Weeks 1–4 tough vs perimeter CBs; weeks 14–16 favorable.
- News (last 14d): hamstring tightness, limited two practices, no setback.

Constraints:
- Do not invent facts. If unknown, say "unclear as of June 2022."
- Cite at least three of the FACTS directly.
- End with a concise draft plan: "Round/price | Risk | Ceiling | Team fit".

Write the report.
</code></pre></div></div>

<p><strong>Completions params I leaned on:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">temperature: 0.3–0.5</code> (steadier, less flowery)</li>
  <li><code class="language-plaintext highlighter-rouge">top_p: 0.9</code> (aka nucleus sampling; see Holtzman et al. <a href="https://arxiv.org/abs/1904.09751">paper</a>)</li>
  <li><code class="language-plaintext highlighter-rouge">max_tokens: 220–320</code> per section</li>
  <li><code class="language-plaintext highlighter-rouge">stop: ["\n\n--", "END"]</code> to prevent run-ons</li>
  <li><code class="language-plaintext highlighter-rouge">presence_penalty</code> small nudge to avoid repetition for multi-paragraph outputs<br />
For parameter details, see the <a href="https://platform.openai.com/docs/api-reference/parameters">OpenAI API reference</a>.</li>
</ul>

<h2 id="3-light-post-processing-and-ranking">3. Light post-processing and ranking</h2>
<p>I occasionally generated <strong>two variants</strong> (e.g., one risk-forward, one opportunity-forward) and scored them against a checklist: number of facts used, coverage of projection range, mention of schedule, explicit draft guidance. The highest-scoring one shipped.</p>

<h1 id="combining-generative-text-with-pre-determined-blocks">Combining generative text with pre-determined blocks</h1>

<p>I was still struggling a bit controlling the quality and output of the generated reports. I did end up blending some pre-written sections with the AI response:</p>

<ul>
  <li><strong>“Usage &amp; Role”</strong> – partially templated using depth chart and team tendency tags.</li>
  <li><strong>“Injury/Availability”</strong> – templated sentence starters fed with any practice/injury notes.</li>
  <li><strong>“Draft Plan”</strong> – templated rubric with round, risk, ceiling filled by the model but constrained to allowed phrases.</li>
</ul>

<p>This hybrid kept tone consistent and let the model spend its creativity where it adds value: transitions, nuance, and connecting dots.</p>

<h1 id="what-worked-what-didnt">What worked, what didn’t</h1>

<p><strong>Worked</strong></p>
<ul>
  <li>Retrieval before generation: huge drop in hallucinations.</li>
  <li>Low temperature + explicit constraints: more concise, fewer cute metaphors.</li>
  <li>Shorter sections composed together: better than one giant 600-token ask.</li>
  <li>Curie/ada for classification and filtering tasks around the pipeline (cheap and fast).</li>
</ul>

<p><strong>Didn’t</strong></p>
<ul>
  <li>Letting the model summarize <em>and</em> opine <em>and</em> recommend in one pass.</li>
  <li>Overly clever prompts. Plain-language instructions beat flowery, lengthy prompts.</li>
  <li>Trusting the model with “latest news” without pinning a date (“as of June 2022”) and quoting the source snippet.</li>
</ul>

<h1 id="lessons-im-taking-forward">Lessons I’m taking forward</h1>

<ul>
  <li><strong>Ground first, generate second.</strong> Retrieval isn’t optional if you care about accuracy.</li>
  <li><strong>Shape the page, not just the paragraph.</strong> Break the problem into labeled sections with clear constraints.</li>
  <li><strong>Use the right model for the job.</strong> Davinci for narrative, Curie/Ada for the plumbing.</li>
  <li><strong>Keep it dated.</strong> “As of June 2022” prevents the model from time-traveling your facts.</li>
  <li><strong>Expect to iterate.</strong> Prompt tuning is important. Small changes (tone, constraints, stop tokens) mattered more than fancy tricks.</li>
</ul>

<p>I’m encouraged by how fast the field is moving. Even in my short tests I saw clear improvements across OpenAI’s releases. This technique will only get better and more accurate.</p>

<h1 id="give-it-a-try">Give it a Try</h1>

<p>Each player profile now blends my projections, schedule context, team/role signals, and fresh notes into a scouting report that reads like something you’d expect from a seasoned analyst.</p>

<p>If you want actionable, AI-assisted analysis with some of the most accurate projections in the industry, give <a href="https://draftpunk.net"><strong>Draft Punk</strong></a> a spin. Set your league settings, browse the player pages, and see how the reports line up with your gut. I think you’ll draft with more confidence. And have more fun doing it.</p>

<p align="center" style="font-size:90%;">
<img data-src="/assets/img/dp22_tablet.png" width="600" />
<br />

</p>]]></content><author><name></name></author><category term="AI" /><category term="LLM" /><category term="Draft Punk" /><category term="Android" /><category term="Fantasy Football" /><summary type="html"><![CDATA[I explored using OpenAI's gpt-3 language models to generate fantasy football player outlooks for my Draft Punk app. Through experimentation with different models, retrieval-augmented generation, and prompt engineering, I learned how to create compelling, fact-based player narratives that rival professional analyst writeups.]]></summary></entry><entry><title type="html">Think Like a Farmer</title><link href="https://www.joshclemm.com/writing/think-like-a-farmer/" rel="alternate" type="text/html" title="Think Like a Farmer" /><published>2021-09-12T00:00:00-07:00</published><updated>2021-09-12T00:00:00-07:00</updated><id>https://www.joshclemm.com/writing/think-like-a-farmer</id><content type="html" xml:base="https://www.joshclemm.com/writing/think-like-a-farmer/"><![CDATA[<p><img class="imground right" data-src="/assets/img/farmer.jpg" width="40%" /></p>

<p>You don’t get better crops by yelling at the plants. So why do software engineering managers try and get better team velocity by “yelling” at their team?</p>

<p>It’s all too common to see managers take a page out of that “pressure” playbook. Any of these look familiar?</p>
<ul>
  <li>Aggressive deadlines</li>
  <li>Tracking individual diffs or PR outputs</li>
  <li>Think adopting Agile is the silver bullet</li>
  <li>Assuming your team isn’t competent</li>
  <li>Blaming individuals or other teams</li>
  <li>Thinking one more coding tool is all that’s missing</li>
</ul>

<blockquote>
  <p>The real secret is in <strong>how you prepare the ground</strong>.</p>
</blockquote>

<p>A manager and team that obsesses with that preparation will win every time. And a lot of that work happens well before it’s time to “code.”</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer_lifecycle.jpg" width="100%" />
<br />

</p>

<h2 id="applying-farming-to-management">Applying Farming to Management</h2>

<p><em>Let’s go through each aspect of “farming” and how it applies to engineering leadership</em></p>

<h3 id="dont-shout-at-the-crops">Don’t shout at the crops</h3>

<p>Pressure tactics are among the <a href="https://www.sciencedirect.com/science/article/abs/pii/S1048984316301655">least effective methods</a> for gaining commitment. Your employees may do the task but they aren’t bought in. It can be used in emergencies for quick, short-term actions, but over-reliance will harm leadership effectiveness.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer-boss-vs-leader.png" width="200" />
<br />

<i>Are you the boss farmer or leader farmer?</i>
</p>

<p>Don’t jump to conclusions when progress stalls. Delays are rarely from lack of effort. Instead, it’s likely unclear requirements, shifting scope, or tech debt.</p>

<h3 id="dont-blame-the-crop">Don’t blame the crop</h3>

<p>Getting instant results is rare. Velocity improves with clarity, tooling, and team trust.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/perf_problems_flowchart.jpg" width="400" />
<br />

<i>Be deliberate about <a href="https://joshclemm.com/writing/analyzing-performance-problems/">finding the reasons</a> behind a problem</i>
</p>

<p>Often leaders expect instant dramatic results, but engineering / product development has ramp-up time: building team trust, setting up CI/CD tooling, cleaning up tech debt, improving flow.</p>

<h3 id="dont-uproot-crops-too-early">Don’t uproot crops too early</h3>

<p>Too much thrash kills overall velocity. Sprinting in a zig-zag will make less overall progress than clear focus on a solid plan.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer_zigzag.jpg" width="80%" />
<br />

</p>

<p>So you need to pick a direction, give the team enough runway, then evaluate. Disciplined “pivot vs persevere” decisions rather than knee-jerk changes.</p>

<p>The research backs this up. Google’s <a href="https://rework.withgoogle.com/intl/en/guides/managers-research-behind-great-managers">long-term study on the best managers</a> noted that great managers set a crisp direction, make hard calls, and then empower the team to execute without micromanaging.</p>

<h3 id="choose-the-best-plants-for-the-soil">Choose the best plants for the soil</h3>

<p>Match the right team, tech stack, and process to the problem space. 0-&gt;1 startups and enterprise-scale infra need different solutions. One-size-fits-all tools rarely work everywhere. Consider what part of the s-curve your team, org, or company is one and tailor the team, talent, and process as appropriate.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer-s-curve-talent.jpg" width="80%" />
<br />

<i>Different phases of the s-curve have very different needs.</i>
</p>

<p>Other considerations? Growth teams need a lean process and rapid experimentation. Platform or Infra teams need more structured process, metrics around reliability. Product teams need a healthy mix of them all. You wouldn’t start a basketball team of all centers would you?</p>

<h3 id="irrigate-and-fertilize">Irrigate and fertilize</h3>

<p>Invest in your teams with mentorship, better tools, and clear strategy. Provide support and direction to keep your org healthy and productive. Focus on building a <a href="https://joshclemm.com/writing/good-team-now-or-great-team-later/">great team later vs. a good team today</a>.</p>

<p><a href="https://rework.withgoogle.com/intl/en/guides/managers-research-behind-great-managers">Top managers</a> focus on upskilling their teams, focus on career development, and creating healthy working environments.</p>

<h3 id="remove-weeds">Remove weeds</h3>

<p>Leaders unblock their team, get rid of bad processes, unclear roles, or unnecessary meetings. Weeds choke growth just like layers and layers of bureaucracy.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/perf_dilbert.jpg" width="80%" />
<br />

<i>Even you as the manager might the biggest blocker</i>
</p>

<h3 id="remember-you-will-have-good-seasons-and-bad-seasons">Remember you will have good seasons and bad seasons</h3>

<p>Build a culture of resilience. Build slack into your plans, and stay flexible as markets change.</p>

<p>Keeping a team at high stress throughout the year leads to burnout and a reduced ability to react to unplanned events. At the same time, keeping a team too calm can lead to complacency and also leaves them ill-equipped to respond when something unexpected happens.</p>

<p align="center" style="font-size:90%;">
<img class="imground" data-src="/assets/img/farmer-resilience.jpg" width="80%" />
<br />

</p>

<p>The key to fostering resilience is routine and predictability, paired with <em>intentional</em> doses of stress: moments of challenge where the team is pushed. When those moments are spaced and purposeful, the next challenge is absorbed better. This “sine-wave” approach keeps your team on their toes while avoiding burnout and sustained low performance.</p>

<h2 id="want-better-team-performance-start-with-yourself">Want Better Team Performance? Start with Yourself</h2>

<p>So while you can focus on better coding tools, better sprint processes, and track team output, if you want to truly unlock better performance, start with yourself.</p>

<p>When you <a href="https://joshclemm.com/writing/analyzing-performance-problems/">analyze the reasons for poor individual or team performance</a>, you’ll often find you have most of the answers.</p>

<p>Are you…</p>
<ul>
  <li>Obsessing about your business strategy?</li>
  <li>Understanding your customers?</li>
  <li>Thinking about org design?</li>
  <li>Ensuring your coaching and hiring the right people?</li>
  <li>Unblocking and aligning your team?</li>
  <li>Ensuring the team is creating a good extensible architecture?</li>
  <li>Thinking about practical processes for your team / phase / industry?</li>
</ul>

<p>Nail those inputs and your team’s output will zip!</p>]]></content><author><name></name></author><category term="Leadership" /><category term="Engineering Management" /><summary type="html"><![CDATA[You don't grow crops by yelling at plants and you don't grow team velocity by pressuring engineers. Great managers prepare the ground. Give clarity, tools, and trust. Focus on support, not blame. Remove blockers, nurture growth, match people to problems, and improve yourself first.]]></summary></entry></feed>