{"slug": "opus-5-5-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql", "title": "Opus 5.5 Made Cache Reads 60% Cheaper. I Redid the Math on My Text-to-SQL Architecture", "summary": "A developer reworked the cost math for Aria, a Go-based text-to-SQL CRM assistant that uses RAG over the database schema, after Anthropic cut Claude Opus 5.5 cache reads to $0.20 per million tokens, 60% below Opus 5. The change narrows the monthly cost gap between retrieving only the top-5 schema docs and sending the full cached semantic layer from 2.1x to 1.2x — roughly $77 versus $92 per month — shifting the design decision from cost to accuracy. The developer also flagged two Opus 5.5 migration issues affecting text-to-SQL setups: forced tool_choice values are now rejected, and append-only conversation history is required when thinking is enabled.", "body_md": "**TL;DR:** Claude Opus 5.5 came out yesterday with cache reads at $0.20 per million tokens, 60% cheaper than Opus 5. I redid the cost math for my text-to-SQL CRM assistant. For one design choice, the cost gap between \"retrieve only the relevant schema\" and \"send the whole schema every time, cached\" shrank from **2.1x to 1.2x**. At that point the choice stops being about cost and becomes about accuracy. The migration guide also has two breaking changes that hit text-to-SQL setups directly.\n\nA few months ago I wrote about [Aria](https://dev.to/rakno/i-built-a-crm-ai-assistant-in-go-from-scratch-no-langchain-no-shortcuts-4jh5), an AI assistant that lets CRM agents ask questions in plain English and get answers from live SQL.\n\nThe core trick is **RAG over the schema, not over the data**. A Python pipeline writes a plain-English description of every table, column and enum value in the CRM (~90 docs in total). When an agent asks a question, I embed the question, run a pgvector search over those docs, and send only the **top 5** to the model along with the question.\n\nHere's why I built it that way:\n\nOpus 5.5's pricing mostly takes away reason #1. That leaves reason #2, which I never actually measured.\n\n|  | Opus 5 | Opus 5.5 | \n|---|---|---|\n| Input | $5 / MTok | $4 / MTok | \n| Output | $25 / MTok | $20 / MTok | \n| 5-min cache write | $6.25 / MTok | $5 / MTok | \n| **Cache read** | **$0.50 / MTok** | **$0.20 / MTok** | \n\nMost of the headlines are about the 20% cut on input and output. For a schema-heavy workload, though, cache reads matter most. They used to cost 0.1x the input price and now cost **0.05x**.\n\nThese are my assumptions. Plug in your own numbers:\n\nI'm only counting the schema part of the prompt, because that's the only part that differs between the two designs.\n\n**Option A: retrieve top 5, no caching** (the retrieved docs change on every question, so there's no stable prefix to cache)\n\n**Option B: send the full semantic layer on every request as a cached prefix**\n\n| Per day | Option A: retrieve | Option B: full + cache | B vs A | \n|---|---|---|---|\n| Opus 5 | $4.40 | $9.25 | **2.1x** | \n| Opus 5.5 | $3.52 | $4.20 | **1.2x** | \n\nPer question on Opus 5.5, that's **$0.0044 vs $0.0053**. Over a 22-working-day month, it's about **$77 vs $92**.\n\nOn Opus 5, \"just give the model everything\" cost twice as much, which made the pgvector step easy to justify. On Opus 5.5, it costs about $15 a month more across the whole team.\n\nAt that price, cost no longer decides the question. Accuracy does.\n\n`last_activity_at`, the SQL is wrong before the model even starts. With the full layer, the model can always see every table.\nWhat it wouldn't replace: my **intent examples**, the question→SQL pairs that get promoted from thumbs-up feedback. That set grows over time, so it still belongs in a retrieval step. The likely end state is a hybrid: **a static cached schema plus retrieved examples.**\n\nI read the [Opus 5.5 migration guide](https://platform.claude.com/docs/en/models/opus-5-5/migration-guide) with Aria in mind. Two changes stood out.\n\n`tool_choice` of type `tool` or `any` is rejected. Many text-to-SQL setups force the model to call their `run_sql` tool so it can't answer from memory. On Opus 5.5, the fix is `tool_choice: auto`, marking the tool as strict, and saying clearly in the prompt when the tool must be used.\n\nMy SQL validator doesn't change. That step never trusted the model anyway: SELECT-only checks, agent-ID injection from the JWT, and a read-only Postgres role. **Moving to a better model shouldn't move your trust boundary.**\n\nThinking is always on in Opus 5.5 (you control it with `effort` instead of turning it off). The guide says to keep conversations append-only, with no edits to `system`, `tools` or earlier messages mid-conversation. For newer accounts, replaying a thinking block after such an edit returns a 400 by default.\n\nThat's a problem if I move my current design over to Opus 5.5 unchanged. I rebuild the system prompt on every turn with a new set of retrieved schema docs. That's an edit to `system` in the middle of a conversation.\n\nSo there are two ways to comply:\n\n`system` and into each new user turn, or\nBoth the pricing and the API now push in the same direction: **put the large, rarely changing context in a static prefix, cache it, and add new context only by appending.**\n\nHere's roughly what the request would look like:\n\n```\n{\n  \"model\": \"claude-opus-5-5\",\n  \"max_tokens\": 1024,\n  \"output_config\": { \"effort\": \"low\" },\n  \"system\": [\n    { \"type\": \"text\", \"text\": \"You write read-only PostgreSQL for a student-housing CRM. Always answer by calling query_crm_database.\" },\n    { \"type\": \"text\", \"text\": \"<full semantic layer: every table, column, enum>\",\n      \"cache_control\": { \"type\": \"ephemeral\" } }\n  ],\n  \"tools\": [{ \"name\": \"query_crm_database\", \"strict\": true, \"...\": \"...\" }],\n  \"tool_choice\": { \"type\": \"auto\" },\n  \"messages\": [\n    { \"role\": \"user\", \"content\": \"<retrieved intent examples>\\n\\nWhich of my leads haven't been contacted in 3 days?\" }\n  ]\n}\n```\n\n(I'd use `effort: low` for the formatting pass and try `medium`, the new default, for SQL generation.)\n\nI haven't switched Aria over yet. This is napkin math and a close read of the docs, not a benchmark. The plan:\n\n`usage` fields (`cache_read_input_tokens` tells you whether caching actually happened).\nIf the full-context version is at least as accurate, I'm removing the pgvector step from the question path. I'll post the numbers when I have them.\n\n**Question for you:** if you're doing text-to-SQL, do you retrieve schema per question or send all of it? And has cheaper caching made you rethink the pieces around RAG? I'd love to hear in the comments.", "url": "https://wpnews.pro/news/opus-5-5-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql", "canonical_source": "https://dev.to/rakno/opus-55-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql-architecture-4jjb", "published_at": "2026-09-23 04:55:42+00:00", "updated_at": "2026-09-23 05:22:43.490629+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "ai-products", "developer-tools", "mlops"], "entities": ["Anthropic", "Claude Opus 5.5", "Claude Opus 5", "Aria", "Go", "pgvector", "Postgres"], "alternates": {"html": "https://wpnews.pro/news/opus-5-5-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql", "markdown": "https://wpnews.pro/news/opus-5-5-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql.md", "text": "https://wpnews.pro/news/opus-5-5-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql.txt", "jsonld": "https://wpnews.pro/news/opus-5-5-made-cache-reads-60-cheaper-i-redid-the-math-on-my-text-to-sql.jsonld"}}