LEARN · ML FOUNDATIONS & CHATBOTS
Every request to a large language model has a cost. For a small prototype, that cost may seem insignificant. But production AI applications and chatbots can process millions of requests, often with lengthy context windows, repeated questions, multiple model calls, and expensive reasoning.
At that scale, inefficient inference quickly becomes a serious financial problem.
The obvious solution is to switch to a cheaper model. Unfortunately, that approach can introduce a different problem: lower answer quality, more retries, additional escalations, and frustrated users.
A better strategy is to build an intelligent inference layer that determines when a model call is necessary, which model should handle it, and whether an existing verified response can be reused.
Three techniques form the foundation of this approach:
-
Caching: Avoid generating the same answer repeatedly by reusing verified results.
-
Model routing: Select an appropriately capable model according to task complexity, requirements, and budget.
-
Small-model fallbacks: Maintain approved functionality when a stronger model becomes unavailable or too expensive.
These techniques work especially well together. Caching eliminates unnecessary inference, routing reduces the cost of remaining requests, and controlled fallbacks improve resilience.
In this tutorial, we’ll build a runnable inference optimization system using Python 3.14, OpenAI’s Responses API, a modern embedding model from Voyage AI, and SQLite.
We’ll also implement strict cache validation, test semantic matching against dangerous edge cases, simulate model failures, calculate potential savings, and examine a real production deployment that reported more than $12,000 in inference cost reductions.
1. Understand what drives inference cost
Before optimizing model inference, understand what contributes to the bill.
Hosted language model costs typically depend on:
-
Input tokens: System instructions, conversation history, retrieved documents, tool results, and user messages.
-
Output tokens: Generated responses and any billed internal reasoning tokens.
-
Model selection: Different models have substantially different input and output prices.
-
Prompt caching: Some providers charge different rates for new input, cached input reads, and cache writes.
-
Additional operations: Embeddings, tool calls, searches, retries, and supporting infrastructure.
-
Processing tier: Standard, accelerated, and batch processing may have different prices.
For a request without caching or additional operations, the basic cost formula is:
Request cost = (input tokens × input price + output tokens × output price) / 1,000,000
This assumes prices are quoted per million tokens.
Consider a customer support application processing 100,000 requests per day.
Some users ask simple questions about shipment tracking or returns. Others need explanations involving multiple policies, unusual situations, or information unavailable in public documentation.
Sending all these requests to the same powerful model means paying premium rates for tasks that may not need premium capabilities.
An illustrative workload might look like this:
| Request category | Traffic share | Preferred handling |
|---|---|---|
| Repeated public FAQ questions | 35% | Verified cache |
| Simple support questions | 40% | Small model |
| Moderately complex questions | 20% | Stronger model |
| Difficult or ambiguous requests | 5% | Stronger model with validation |
These percentages are hypothetical. They are not measurements from the application we’ll build.
Nevertheless, they illustrate a crucial point: the largest cost reduction may come from avoiding model calls entirely.
Optimize cost per successful task
A cheaper API call does not necessarily mean a cheaper completed workflow.
Suppose Model A costs one unit per attempt and completes a task correctly 95% of the time.
Model B costs 0.2 units but succeeds only 60% of the time.
Model B might still be attractive, but its real cost must include unsuccessful attempts, validation, retries, escalations, and any expensive downstream consequences.
A more useful economic metric is:
Cost per successful task = total inference-related spending / number of successfully completed tasks
For an extraction system, success might mean producing correct structured data that passes domain-specific checks.
For a customer support assistant, it might mean resolving a request accurately without subsequent escalation.
For a coding assistant, it might mean producing a change that passes tests and satisfies the user’s requirements.
The objective is not to minimize the price of an individual model response. It is to minimize the cost of reliably completing useful work.
2. Design a layered inference optimization architecture
An efficient inference layer should make decisions in a predictable order.
A recommended request pipeline looks like this:
-
Determine whether the request is eligible for answer reuse.
-
Check for an exact cache match.
-
Search the semantic cache when reuse is permitted.
-
Route the remaining request to an appropriate model.
-
Apply an explicitly authorized fallback if necessary.
-
Validate the generated response.
-
Cache the response only if validation succeeds.
The distinction between eligibility and similarity is particularly important.
A semantic cache should never assume that two questions deserve the same answer merely because their embeddings are close together.
Layer 1: Exact-match caching
Imagine an online retailer receives the following question hundreds of times:
How do I track my shipment?
If its public tracking instructions haven’t changed, repeatedly generating the same answer wastes computation.
An exact cache stores a response under a key derived from the normalized question and relevant application context.
Normalization can remove harmless differences such as capitalization and repeated whitespace.
Exact caching is particularly useful for:
-
Public FAQ responses.
-
Repeated classifications.
-
Deterministic transformations.
-
Stable documentation explanations.
-
Standardized product information.
A valid exact hit can avoid both generation and embedding costs.
However, exact matching does not recognize different phrasings of the same question.
Layer 2: Semantic caching
Consider these requests:
-
How do I track my shipment?
-
How can I track my shipment?
-
Where can I find my tracking link?
Although the wording differs, the questions may have the same intent.
An embedding model converts text into numerical vectors that capture aspects of semantic meaning.
A cache can compare a new question’s embedding with previously stored embeddings.
One common similarity metric is cosine similarity:
Cosine similarity = dot product(A, B) / (magnitude(A) × magnitude(B))
For unit-normalized embeddings, the formula simplifies to the dot product.
A score closer to 1 indicates stronger directional similarity.
However, semantic similarity is not proof that answers are interchangeable.
Compare:
-
What is your return policy?
-
Can I return an opened item?
-
Can I return a damaged item without a receipt?
All three concern returns. Yet the second and third questions introduce important conditions.
A generic cached answer might not resolve those conditions.
This is why a production semantic cache needs structured eligibility checks, scope isolation, trusted response validation, and carefully evaluated similarity thresholds.
Layer 3: Model routing
When caching cannot resolve a request, the router chooses an appropriate model.
Routing decisions can consider:
-
Task type and complexity.
-
Required tools and modalities.
-
Input and expected output length.
-
Business impact of an incorrect answer.
-
Whether the request requires private or live information.
-
Available latency and spending budgets.
-
Historical evaluation results for each model.
Simple public-policy questions may be suitable for a smaller model.
Requests involving multiple policies, conflicting requirements, or uncertain information may need a stronger model.
In advanced systems, routing is often based on a calibrated classifier or model-performance estimates.
Our tutorial will use a small deterministic routing policy so its behavior can be tested clearly.
Prompt length will be one demonstration signal, not a claim that length reliably measures difficulty.
3. Distinguish application caching from provider prompt caching
Two different mechanisms are frequently described as caching in AI applications.
They should not be confused.
| Property | Application answer cache | Provider prompt cache |
|---|---|---|
| Reuses | Previously completed answer | Previously processed prompt prefix |
| Can match different wording | Yes, with semantic retrieval | Requires an eligible matching prefix |
| Requires new generation on a hit | No | Yes |
| Main benefit | Avoids a generation call | Reduces repeated input processing |
| Correctness responsibility | Application | Newly generated response still needs validation |
| Typical controls | Namespace, revision, TTL, eligibility | Provider-specific caching behavior |
OpenAI’s current prompt caching supports discounted cached input tokens and separately priced cache writes on relevant models.
For example, the current GPT-6 Luna pricing lists cached input reads at 10% of the ordinary input rate, while GPT-6.1 Sol lists them at 5%. Cache writes have separate pricing.
Provider prompt caching still generates a new response. It does not replace application-level answer reuse.
For long-running chatbots, place stable instructions and reusable reference material before user-specific information whenever the provider’s matching rules make that useful.
Avoid inserting changing timestamps or randomly ordered metadata into an otherwise stable prompt prefix.
Do not add meaningless tokens simply to trigger provider caching. The additional input can cost more than the eventual benefit.
4. Choose a current technology stack
Our demonstration application will answer public-policy questions for a fictional online retailer called DemoShop.
It will not access real customer accounts, transactions, or order data.
This intentionally narrow domain gives us a safe environment for studying caching and routing.
Technology choices
| Component | Selection | Purpose |
|---|---|---|
| Runtime | Python 3.14 | Modern application runtime |
| Generation SDK | OpenAI Python SDK 3.31.0 | Responses API |
| Small model | gpt-6-luna |
Low-cost public FAQs |
| Strong model | gpt-6.1-sol |
Complex requests |
| Embedding model | voyage-4-lite |
Semantic similarity |
| Embedding SDK | Voyage AI Python SDK 0.5.0 | Hosted embeddings |
| Database | SQLite | Persistent local cache |
| Testing | unittest |
Offline, mocked behavioral tests |
Why Python 3.14?
Python 3.14 became stable on October 7, 2025.
It provides an appropriately recent runtime for this tutorial and supports our standard-library implementation.
The selected OpenAI SDK version, 3.31.0, was released on October 14, 2026, and lists Python 3.14 compatibility.
Voyage AI SDK 0.5.0 was released on July 10, 2026, and declares support for Python versions below 3.15.
Why Voyage 4 Lite?
Voyage AI introduced its Voyage 4 embedding family on January 15, 2026.
The family includes several models with different retrieval-quality and cost characteristics.
We’ll use voyage-4-lite because it is intended for cost-sensitive embedding workloads.
Its documented properties include:
-
Maximum input context of 32,000 tokens.
-
Default embedding dimension of 1,024.
-
Configurable dimensions of 256, 512, 1,024, and 2,048.
-
Support for floating-point embeddings.
-
Compatibility with the embedding space used by other Voyage 4 family members.
We’ll explicitly request 512-dimensional vectors.
The default is not 512, so specifying the dimension matters.
The current published price for Voyage 4 Lite is $0.02 per million input tokens, before any applicable free-tier allowance or account-specific arrangements.
This example uses OpenAI for generation and Voyage AI for embeddings. A production gateway can abstract both behind provider-neutral interfaces.
5. Create a reproducible Python environment
Create a new project directory using Python 3.14.
On macOS or Linux:
mkdir inference-optimizer
cd inference-optimizer
python3.14 -m venv .venv
source .venv/bin/activate
python --version
The installed interpreter should report Python 3.14.x.
Our project will use two external dependencies.
Create a file named requirements.txt:
openai==3.31.0
voyageai==0.5.0
Install them:
python -m pip install -r requirements.txt
These are explicitly identified release versions, not floating installations that automatically change when the tutorial is rerun.
The version pins cover the direct dependencies. For a production build requiring fully reproducible transitive dependencies, generate and commit a resolved lockfile with hashes as well.
Configure your API credentials:
export OPENAI_API_KEY="your_openai_api_key"
export VOYAGE_API_KEY="your_voyage_api_key"
Replace the placeholders with credentials from the respective providers.
Never commit secret keys to source control. Use secret management or environment-based credential injection in deployed environments.
The project will contain:
inference-optimizer/
├── .venv/
├── requirements.txt
├── app.py
├── test_app.py
└── semantic_cache.sqlite3
The database file will be created automatically when the demonstration runs.
Add the virtual environment, database, and local secrets to your repository’s ignore rules.
6. Define a policy that is safe to cache
Before implementing embeddings, we need an authoritative source of truth.
DemoShop will have three fictional public policies:
| Intent | Authoritative statement |
|---|---|
| Tracking | After dispatch, customers can find a tracking link on the Orders page. |
| Returns | Unused items can be returned within 30 days of delivery through the Orders page. |
| Shipping | Standard shipping is estimated at 3–5 business days after dispatch. |
These statements are the only answers our semantic cache will be permitted to store.
This is an intentionally conservative design.
Why not cache arbitrary model output?
Imagine a model generates:
Your order will arrive tomorrow.
That response might be fluent and plausible, but our public policy does not establish any particular order’s arrival date.
A response being complete, nonempty, or grammatically correct does not establish its factual validity.
Even asking another language model whether an answer is correct would not create a deterministic guarantee.
For this tutorial, we’ll use a stronger rule:
A cacheable response must exactly match the authoritative statement assigned to its verified intent.
If the model produces additional claims, altered conditions, or unsupported wording, the response is rejected for caching.
The application can still return the trusted canonical policy sentence.
This approach deliberately sacrifices stylistic flexibility for verifiable reuse.
It also illustrates a useful architectural lesson: when the correct answer is completely deterministic, production systems should consider serving the canonical answer directly without calling a model at all.
We retain generation in the demonstration so we can exercise real routing, failure handling, and cache-write validation.
For more flexible real-world applications, replace the exact-sentence validator with a properly evaluated claim-level or structured-output validation system.
7. Implement the complete semantic cache and router
Create app.py.
The implementation includes:
-
Exact and semantic cache lookup.
-
Full-string FAQ qualification.
-
Strict canonical answer validation.
-
Policy-version-aware cache keys.
-
Configurable TTLs.
-
Embedding dimension validation.
-
Small and strong model routing.
-
Controlled failure fallback.
-
Budget restrictions.
-
Dependency injection for testing.
-
Real provider integrations.
"""Conservative semantic cache and model router for fictional DemoShop FAQs."""
import hashlib
import json
import math
import os
import re
import sqlite3
import time
from dataclasses import dataclass
SMALL_MODEL = "gpt-6-luna"
STRONG_MODEL = "gpt-6.1-sol"
EMBED_MODEL = "voyage-4-lite" # Launched January 2026.
EMBED_DIMENSIONS = 512
NAMESPACE = "demoshop-public-en"
CACHE_FORMAT = "canonical-answer-v1"
TTL_SECONDS = 3600
SIMILARITY_THRESHOLD = 0.93 # Illustrative, not a universal safe cutoff.
# The only statements that may be stored or replayed from this FAQ cache.
POLICY_FACTS = {
"tracking": "After dispatch, customers can find a tracking link on the Orders page.",
"returns": "Unused items can be returned within 30 days of delivery through the Orders page.",
"shipping": "Standard shipping is estimated at 3-5 business days after dispatch.",
}
REVISION = hashlib.sha256(json.dumps({
"facts": POLICY_FACTS,
"cache_format": CACHE_FORMAT,
"embedding_model": EMBED_MODEL,
"embedding_dimensions": EMBED_DIMENSIONS,
}, sort_keys=True).encode()).hexdigest()[:16]
# Full-string patterns intentionally reject qualifiers and policy exceptions.
FAQ_PATTERNS = {
"tracking": re.compile(
r"(?:how (?:do|can) i track my (?:shipment|package|order)|"
r"how can i find (?:my|the) tracking link|"
r"where (?:do i|can i) find (?:my|the) tracking link)"
),
"returns": re.compile(
r"(?:what is (?:the|your) return policy|"
r"how (?:do|can) i return an (?:item|product)|"
r"please explain (?:the|your) return policy to a new customer)"
),
"shipping": re.compile(
r"(?:what is (?:the|your) (?:shipping policy|standard shipping time)|"
r"how long does standard shipping take|"
r"please explain the standard shipping policy to a new customer "
r"who wants a general overview)"
),
}
INSTRUCTIONS = (
"You are the DemoShop public-policy assistant. These are the only "
"authoritative facts:\n"
+ "\n".join(f"- {key}: {value}" for key, value in POLICY_FACTS.items())
+ "\nFor a question about exactly one public FAQ, output only its exact "
"fact sentence, preserving every character and adding nothing. "
"For other requests, use only the facts above; acknowledge missing "
"information. Never claim to see a personal order or perform an action."
)
def normalize(question):
return " ".join(question.casefold().split())
def faq_intent(question):
if "\n" in question or len(question) > 240:
return None
q = normalize(question).rstrip("?.!").strip()
matches = [name for name, pattern in FAQ_PATTERNS.items()
if pattern.fullmatch(q)]
return matches[0] if len(matches) == 1 else None
def unit_vector(values):
if len(values) != EMBED_DIMENSIONS:
raise ValueError("Unexpected embedding dimensions")
floats = [float(value) for value in values]
if not all(math.isfinite(value) for value in floats):
raise ValueError("Non-finite embedding")
magnitude = math.sqrt(sum(value * value for value in floats))
if magnitude == 0:
raise ValueError("Zero embedding")
return [value / magnitude for value in floats]
def verified_answer(intent, answer):
# Conservative by design: no arbitrary generated prose enters this cache.
return intent in POLICY_FACTS and answer == POLICY_FACTS[intent]
class TemporaryGenerationError(Exception):
"""A model error for which an approved fallback may be attempted."""
class LiveGenerator:
def __init__(self, client):
self.client = client
def generate(self, model, question):
from openai import (APIConnectionError, APITimeoutError,
InternalServerError, RateLimitError)
try:
result = self.client.responses.create(
model=model,
instructions=INSTRUCTIONS,
input=question,
reasoning={"effort": "low" if model == SMALL_MODEL else "medium"},
max_output_tokens=300,
store=False,
)
except (APIConnectionError, APITimeoutError,
InternalServerError, RateLimitError) as exc:
raise TemporaryGenerationError(type(exc).__name__) from exc
if result.status != "completed" or not result.output_text.strip():
raise ValueError("Incomplete or empty generation")
return result.output_text.strip(), result.usage
class VoyageEmbedder:
def __init__(self, client):
self.client = client
def embed(self, question):
result = self.client.embed(
[question], model=EMBED_MODEL, input_type="query",
output_dimension=EMBED_DIMENSIONS, truncation=False,
)
return result.embeddings[0], result.total_tokens
@dataclass
class AnswerResult:
answer: str
path: str
embedding_tokens: int = 0
usage: object = None
class Optimizer:
def __init__(self, db, generator, embedder, *, revision=REVISION,
clock=time.time, ttl=TTL_SECONDS,
similarity=SIMILARITY_THRESHOLD, allow_strong=True):
self.db = db
self.generator = generator
self.embedder = embedder
self.revision = revision
self.clock = clock
self.ttl = ttl
self.similarity = similarity
self.allow_strong = allow_strong
self.db.execute("""
CREATE TABLE IF NOT EXISTS entries (
namespace TEXT NOT NULL,
revision TEXT NOT NULL,
intent TEXT NOT NULL,
normalized TEXT NOT NULL,
vector TEXT NOT NULL,
answer TEXT NOT NULL,
expires_at INTEGER NOT NULL,
PRIMARY KEY (namespace, revision, normalized)
)
""")
self.db.commit()
def exact_lookup(self, intent, normalized):
row = self.db.execute("""
SELECT answer FROM entries
WHERE namespace=? AND revision=? AND intent=?
AND normalized=? AND expires_at>?
""", (NAMESPACE, self.revision, intent, normalized,
int(self.clock()))).fetchone()
return row[0] if row and verified_answer(intent, row[0]) else None
def semantic_lookup(self, intent, vector):
rows = self.db.execute("""
SELECT vector, answer FROM entries
WHERE namespace=? AND revision=? AND intent=? AND expires_at>?
""", (NAMESPACE, self.revision, intent, int(self.clock()))).fetchall()
best_answer, best_score = None, -1.0
for stored, answer in rows:
if not verified_answer(intent, answer):
continue
try:
other = unit_vector(json.loads(stored))
except (ValueError, TypeError):
continue
score = sum(a * b for a, b in zip(vector, other))
if score > best_score:
best_answer, best_score = answer, score
if best_score >= self.similarity:
return best_answer
return None
def cache_put(self, intent, normalized, vector, answer):
if not verified_answer(intent, answer):
raise ValueError("Refusing to cache unverified answer")
with self.db:
self.db.execute("""
INSERT INTO entries
(namespace, revision, intent, normalized,
vector, answer, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(namespace, revision, normalized) DO UPDATE SET
intent=excluded.intent, vector=excluded.vector,
answer=excluded.answer, expires_at=excluded.expires_at
""", (NAMESPACE, self.revision, intent, normalized,
json.dumps(vector), answer, int(self.clock()) + self.ttl))
def answer(self, question):
intent = faq_intent(question)
normalized = normalize(question)
vector, embedding_tokens = None, 0
if intent is not None:
cached = self.exact_lookup(intent, normalized)
if cached is not None:
return AnswerResult(cached, "exact cache")
try:
raw_vector, embedding_tokens = self.embedder.embed(question)
vector = unit_vector(raw_vector)
except Exception:
# Optional cache infrastructure failed; generation still works.
vector, embedding_tokens = None, 0
if vector is not None:
cached = self.semantic_lookup(intent, vector)
if cached is not None:
return AnswerResult(cached, "semantic cache", embedding_tokens)
primary = (SMALL_MODEL if intent and len(question.split()) <= 12
else STRONG_MODEL)
if primary == STRONG_MODEL and not self.allow_strong:
if intent is None:
return AnswerResult(
"This request requires stronger handling or human review.",
"budget guard", embedding_tokens)
primary = SMALL_MODEL
used_model = primary
try:
generated, usage = self.generator.generate(primary, question)
except TemporaryGenerationError:
if primary != STRONG_MODEL or intent is None:
raise
used_model = SMALL_MODEL
generated, usage = self.generator.generate(SMALL_MODEL, question)
if intent is None:
return AnswerResult(generated, used_model, embedding_tokens, usage)
if not verified_answer(intent, generated):
# Grounded, but not cached: generation failed strict verification.
return AnswerResult(POLICY_FACTS[intent],
"canonical policy (uncached)",
embedding_tokens, usage)
if vector is not None:
self.cache_put(intent, normalized, vector, generated)
return AnswerResult(generated, used_model, embedding_tokens, usage)
def main():
import voyageai
from openai import OpenAI
db = sqlite3.connect("semantic_cache.sqlite3")
app = Optimizer(
db, LiveGenerator(OpenAI(timeout=20.0, max_retries=0)),
VoyageEmbedder(voyageai.Client()),
allow_strong=os.getenv("DISABLE_STRONG_MODEL") != "1",
)
questions = [
"How do I track my shipment?",
"How do I track my shipment?",
"How can I track my shipment?",
"Compare standard shipping with the return policy.",
"Please explain the standard shipping policy to a new customer "
"who wants a general overview.",
]
try:
for question in questions:
result = app.answer(question)
print(f"\nQ: {question}\nPath: {result.path}; "
f"embedding tokens: {result.embedding_tokens}")
if result.usage is not None:
print(f"Model input/output tokens: "
f"{result.usage.input_tokens}/"
f"{result.usage.output_tokens}")
print(f"A: {result.answer}")
finally:
db.close()
if __name__ == "__main__":
main()
How the implementation protects cache correctness
Several important controls work together.
First, cache eligibility uses full-string matching.
The FAQ classifier does not simply search for the word “return” or “shipping”.
It requires the entire normalized question to match an approved public-FAQ pattern.
A request about damaged items, opened products, missing receipts, or particular orders therefore cannot enter the generic FAQ semantic cache through a partial keyword match.
These patterns are intentionally restrictive. They prioritize correctness over recall.
They are not general-purpose security filters, and they cannot replace authorization or robust semantic understanding in a broader application.
Second, every stored answer must be independently verifiable.
The verified_answer() function compares a proposed response against the authoritative statement for its intent.
A completed model response is insufficient.
If the model adds an unsupported delivery promise, modifies the return conditions, or produces unexpected content, the application refuses to cache it.
The user instead receives the approved canonical policy statement.
Third, cached responses are validated when read as well as when written.
This provides an additional defense against corrupted or incorrectly inserted cache entries.
Fourth, the revision identifier depends on the authoritative policy and embedding configuration.
Changing a policy statement, cache format, embedding model, or embedding dimensionality changes the revision.
Old entries become inaccessible to the new revision.
Fifth, semantic retrieval occurs only inside the approved intent.
A tracking question cannot retrieve a return-policy answer even if the vectors are unusually similar.
Finally, the cosine threshold is an additional matching condition, not the primary correctness guarantee.
In this example, correctness comes primarily from strict eligibility and canonical answer validation.
8. Run the demonstration
Execute the application:
python app.py
It will create the SQLite cache database and process five questions.
The expected routing behavior is:
| Request | Expected behavior |
|---|---|
| First tracking question | Small model, then validated cache write |
| Identical tracking question | Exact cache hit |
| Reworded tracking question | Semantic hit if its similarity passes the threshold |
| Combined shipping and return question | Strong model, no answer caching |
| Longer approved shipping question | Strong model, with an eligible small-model fallback |
Actual generated wording, embedding scores, and token usage will vary.
A semantic hit for the third question is not guaranteed. If its similarity falls below the demonstration threshold, it will be generated again.
If a model fails the strict canonical-output requirement, the application returns the authoritative policy sentence without writing that generation to the cache.
Simulate a restricted inference budget
Run the application with the stronger model disabled:
DISABLE_STRONG_MODEL=1 python app.py
Eligible public FAQs can use the smaller model.
Requests outside the approved class return an explicit budget-guard response rather than being silently downgraded.
Previously cached, valid public FAQ answers can still be returned.
Reset the demonstration cache
To observe fresh model calls, remove the local demonstration database and rerun:
rm -f semantic_cache.sqlite3
python app.py
This command is appropriate for the disposable tutorial database only.
Do not use it against a production cache.
9. Understand the cache implementation in detail
The application implements several important production concepts in a compact form.
Exact lookup comes before embedding
Exact lookup uses:
-
Cache namespace.
-
Policy revision.
-
Classified intent.
-
Normalized question.
-
Expiration timestamp.
If the entry is valid and unexpired, the application returns it immediately.
No new embedding request is necessary.
This matters because even inexpensive embeddings add network latency and billable operations.
Embeddings are normalized
Voyage AI returns a 512-dimensional embedding because we explicitly configured that output size.
The unit_vector() function validates its length, rejects non-finite values and zero vectors, and normalizes it.
Cosine similarity can then be calculated with a dot product.
We use the same embedding mode for stored questions and lookup questions so comparisons are consistent.
In larger retrieval systems, document and query embeddings may use different input types. Those choices must be tested and versioned.
Similarity alone cannot authorize reuse
Suppose the semantic cache contains a valid answer about returning unused items.
A new question asks whether a damaged item can be returned.
Even if both embeddings achieve high similarity, the new question fails the approved eligibility grammar.
The semantic lookup is not attempted.
This is a stronger safety property than relying on a global similarity threshold.
Cache entries expire
The tutorial uses a one-hour TTL.
Expired entries cannot satisfy exact or semantic lookups.
However, expired rows remain physically stored until they are overwritten or deleted.
Production applications should periodically prune expired entries.
Also remember that TTL is not a substitute for revision-based invalidation.
When a policy changes, correct behavior may require immediately switching revisions instead of waiting for old entries to expire.
Why SQLite is sufficient here
The tutorial scans eligible vectors and computes similarities in Python.
Its semantic lookup work grows approximately linearly with the number of matching cache entries.
For a handful of public FAQs, this is acceptable.
For millions of vectors, consider:
-
PostgreSQL with pgvector.
-
Redis or Valkey vector search.
-
A dedicated vector-search service.
-
Approximate nearest-neighbor indexing.
Regardless of the storage technology, apply tenant, authorization, revision, language, and intent filters before considering vector similarity.
Indexed retrieval is a scalability improvement, not a replacement for validation.
10. Add automated tests for caching, routing, and fallbacks
A useful inference optimization system needs tests that do more than check whether a function returns a string.
We should verify that unsafe requests do not hit the semantic cache, invalid outputs are rejected, and failures follow the intended route.
The following tests use dependency injection and mocked model backends.
They require no API keys and make no network requests.
Create test_app.py:
"""Offline integration-style tests: no keys and no network requests."""
import math
import sqlite3
import unittest
from types import SimpleNamespace
from app import (
Optimizer, POLICY_FACTS, SMALL_MODEL, STRONG_MODEL,
TemporaryGenerationError, faq_intent, normalize,
)
def vector(x, y=0.0):
return [x, y] + [0.0] * 510
class FakeEmbedder:
def __init__(self, vectors=None, broken=False):
self.vectors = vectors or {}
self.broken = broken
self.calls = []
def embed(self, question):
self.calls.append(question)
if self.broken:
raise ConnectionError("Embedding service unavailable")
return self.vectors.get(question, vector(1.0)), 7
class FakeGenerator:
def __init__(self, outputs=None, failing_models=()):
self.outputs = outputs or {}
self.failing_models = set(failing_models)
self.calls = []
def generate(self, model, question):
self.calls.append((model, question))
if model in self.failing_models:
raise TemporaryGenerationError("Simulated outage")
intent = faq_intent(question)
default = POLICY_FACTS[intent] if intent else "Unknown from public policy."
result = self.outputs.get(question, default)
usage = SimpleNamespace(input_tokens=100, output_tokens=30)
return result, usage
class RouterTests(unittest.TestCase):
def setUp(self):
self.db = sqlite3.connect(":memory:")
self.now = [100]
self.generator = FakeGenerator()
self.embedder = FakeEmbedder()
self.app = Optimizer(self.db, self.generator, self.embedder,
clock=lambda: self.now[0], ttl=10)
def tearDown(self):
self.db.close()
def rows(self):
return self.db.execute("SELECT COUNT(*) FROM entries").fetchone()[0]
def test_eligibility_rejects_exceptions_and_personal_context(self):
self.assertEqual(normalize(" TRACK My Order "), "track my order")
self.assertEqual(faq_intent("How do I track my shipment?"), "tracking")
negatives = [
"Can I return an opened item?",
"What is your return policy for damaged items?",
"What is your return policy without a receipt?",
"What is your return policy for sale items?",
"Does standard shipping include weekends?",
"How long does express shipping take?",
"Track order 48712",
"Where is my order now?",
"What is the latest shipping policy?",
"Compare standard shipping with the return policy.",
]
for question in negatives:
with self.subTest(question=question):
self.assertIsNone(faq_intent(question))
def test_exact_hit_skips_embedding_and_generation(self):
q = "How do I track my shipment?"
self.app.answer(q)
second = self.app.answer(q)
self.assertEqual(second.path, "exact cache")
self.assertEqual(second.embedding_tokens, 0)
self.assertEqual(len(self.embedder.calls), 1)
self.assertEqual(len(self.generator.calls), 1)
self.assertEqual(self.rows(), 1)
def test_semantic_hit_for_same_verified_intent(self):
first = "How do I track my shipment?"
second = "How can I track my shipment?"
self.embedder.vectors = {first: vector(1), second: vector(1)}
self.app.answer(first)
result = self.app.answer(second)
self.assertEqual(result.path, "semantic cache")
self.assertEqual(result.answer, POLICY_FACTS["tracking"])
self.assertEqual(len(self.generator.calls), 1)
def test_semantic_rejection_below_threshold(self):
first = "How do I track my shipment?"
second = "How can I track my shipment?"
self.embedder.vectors = {first: vector(1),
second: vector(0.8, math.sqrt(1 - 0.8**2))}
self.app.answer(first)
result = self.app.answer(second)
self.assertEqual(result.path, SMALL_MODEL)
self.assertEqual(len(self.generator.calls), 2)
def test_negative_pair_never_semantically_reused(self):
self.app.answer("What is your return policy?")
result = self.app.answer("What is your return policy for damaged items?")
self.assertEqual(result.path, STRONG_MODEL)
self.assertEqual(len(self.embedder.calls), 1)
def test_expiration_causes_new_generation(self):
q = "How do I track my shipment?"
self.app.answer(q)
self.now[0] = 111
self.assertEqual(self.app.answer(q).path, SMALL_MODEL)
self.assertEqual(len(self.generator.calls), 2)
def test_policy_revision_isolation(self):
q = "How do I track my shipment?"
self.app.answer(q)
revised = Optimizer(self.db, self.generator, self.embedder,
revision="different-policy-revision",
clock=lambda: self.now[0])
self.assertEqual(revised.answer(q).path, SMALL_MODEL)
self.assertEqual(len(self.generator.calls), 2)
def test_embedding_outage_bypasses_cache(self):
self.embedder.broken = True
q = "How do I track my shipment?"
self.assertEqual(self.app.answer(q).path, SMALL_MODEL)
self.assertEqual(self.rows(), 0)
self.assertEqual(self.app.answer(q).path, SMALL_MODEL)
def test_strong_failure_falls_back_only_for_approved_faq(self):
self.generator.failing_models.add(STRONG_MODEL)
q = ("Please explain the standard shipping policy to a new "
"customer who wants a general overview.")
result = self.app.answer(q)
self.assertEqual(result.path, SMALL_MODEL)
self.assertEqual([call[0] for call in self.generator.calls],
[STRONG_MODEL, SMALL_MODEL])
self.assertEqual(result.answer, POLICY_FACTS["shipping"])
self.assertEqual(self.rows(), 1)
def test_no_fallback_for_unapproved_request(self):
self.generator.failing_models.add(STRONG_MODEL)
with self.assertRaises(TemporaryGenerationError):
self.app.answer("Compare standard shipping with return rules.")
self.assertEqual([call[0] for call in self.generator.calls],
[STRONG_MODEL])
self.assertEqual(self.rows(), 0)
def test_budget_guard_rejects_complex_request(self):
restricted = Optimizer(self.db, self.generator, self.embedder,
allow_strong=False)
result = restricted.answer("Compare shipping and returns.")
self.assertEqual(result.path, "budget guard")
self.assertEqual(self.generator.calls, [])
def test_budget_guard_uses_small_for_approved_long_faq(self):
restricted = Optimizer(self.db, self.generator, self.embedder,
allow_strong=False)
result = restricted.answer(
"Please explain the standard shipping policy to a new customer "
"who wants a general overview.")
self.assertEqual(result.path, SMALL_MODEL)
self.assertEqual(self.generator.calls[0][0], SMALL_MODEL)
def test_invalid_generation_is_not_cached(self):
q = "How do I track my shipment?"
self.generator.outputs[q] = "Your order will arrive tomorrow."
result = self.app.answer(q)
self.assertEqual(result.path, "canonical policy (uncached)")
self.assertEqual(result.answer, POLICY_FACTS["tracking"])
self.assertEqual(self.rows(), 0)
with self.assertRaises(ValueError):
self.app.cache_put("tracking", normalize(q), vector(1),
"Your order will arrive tomorrow.")
def test_corrupted_cached_answer_not_reused(self):
q = "How do I track my shipment?"
self.app.answer(q)
self.db.execute("UPDATE entries SET answer='Forged response'")
self.db.commit()
self.assertEqual(self.app.answer(q).path, SMALL_MODEL)
self.assertEqual(len(self.generator.calls), 2)
if __name__ == "__main__":
unittest.main()
Run the tests:
python -m unittest -v
The suite contains 14 tests covering the core application behavior.
The tests were exercised successfully with Python 3.13.5 in the preparation environment; the code uses Python 3.14-compatible standard-library features, but a live Python 3.14 execution and provider API integration test were not available in that environment. The dependency releases and declared Python compatibility were checked against the current package listings.
What the tests establish
| Test scenario | Required outcome |
|---|---|
| Repeated identical request | Exact hit, no second API call |
| Similar approved requests | Semantic reuse when threshold passes |
| Low-similarity pair | Generate instead of reusing |
| Exceptional return question | Bypass generic semantic reuse |
| Expired entry | Generate again |
| Changed policy revision | Old entry inaccessible |
| Embedding outage | Continue through normal generation |
| Strong-model outage on approved FAQ | Attempt small-model fallback |
| Strong-model outage on unapproved request | Propagate failure |
| Strong-model budget restriction | Block complex request |
| Eligible FAQ under budget restriction | Use small model |
| Unsupported generated answer | Reject cache write |
| Corrupted cached response | Reject during lookup |
| Normalization and eligibility rules | Classify only approved requests |
These are behavioral tests, not benchmarks of real semantic similarity.
The fake embedding vectors deliberately force accepted and rejected matches, making tests deterministic.
Production validation should additionally evaluate actual embeddings and model responses against representative, labeled data.
11. Understand why strict response validation matters
One of the most dangerous caching mistakes is treating a successful API response as a verified answer.
A model can return completed output that contains unsupported claims.
Without validation, that output might be cached and served thousands of times.
Our implementation prevents that specific failure mode by requiring an exact match with an approved statement.
For example, the authoritative shipping policy says:
Standard shipping is estimated at 3–5 business days after dispatch.
Now imagine a generated response says:
Standard shipping takes exactly three days and includes weekends.
The original policy does not guarantee either claim.
Our validator rejects the response for caching.
It also rejects otherwise plausible but altered wording. This strictness is deliberate.
Extending validation to less deterministic domains
Exact canonical matching works well when the acceptable answers can be enumerated.
Other applications need more flexible methods.
For structured extraction, consider:
-
JSON Schema validation.
-
Required-field checks.
-
Allowed-value constraints.
-
Cross-field consistency tests.
-
Comparison against source-document values.
For document-grounded question answering, consider:
-
Claim-level source attribution.
-
Verification against retrieved passages.
-
Explicit handling of unavailable information.
-
Independent factual evaluations.
-
Human review for consequential or ambiguous outputs.
A useful intermediate design is to have the model select approved fact identifiers and then construct the final answer from authoritative fact records.
This separates model-driven selection from unrestricted factual generation.
However, selecting the wrong facts can still produce a misleading answer. The chosen identifiers must also be checked against the question and the applicable policy conditions.
Validation is not the same as instruction following
Our prompt asks the model to return a canonical sentence.
That instruction encourages the desired output, but it is not a correctness guarantee.
The validator enforces the rule regardless of whether the model follows the instruction.
This distinction becomes more important when an application processes untrusted documents, customer input, or third-party tool output.
12. Design safe small-model fallback behavior
Fallback strategies generally move in two directions.
Small-to-large escalation
The first approach starts with a smaller model and escalates when the result does not satisfy the task’s requirements.
For example:
-
A small model extracts product attributes.
-
A validator checks required fields and consistency.
-
Invalid results are escalated to a stronger model.
-
The stronger result is evaluated before acceptance.
This can be economical when most requests are easy and failures can be identified reliably.
However, confidence values produced by a language model should not automatically be treated as calibrated probabilities of correctness.
A model claiming 99% confidence does not establish a 99% probability that its response is accurate.
Prefer independently evaluated success predictors, deterministic constraints, and meaningful task-specific checks.
Large-to-small fallback
A different situation occurs when a strong model becomes unavailable.
Perhaps it encounters a timeout, rate limit, or temporary service error.
Using a smaller model may help maintain functionality, but only when reduced capability is acceptable.
Our demonstration permits large-to-small fallback for approved public FAQ questions.
It does not downgrade arbitrary complex requests.
This distinction is essential.
A reliability mechanism should not silently change the application’s quality requirements.
Budget-aware routing
An inference layer may also enforce a spending limit.
For example, a tenant might have exhausted its daily allowance for premium-model inference.
The application can:
-
Use an approved cheaper model.
-
Return a verified static answer when appropriate.
-
Require stronger handling or human review.
-
Reject the request when no safe route is available.
The correct behavior depends on the task.
A hard budget limit is not permission to generate unsupported answers.
Avoid uncontrolled retries
Retry logic can increase cost dramatically.
A network timeout does not prove that the provider performed no computation.
Repeating the same expensive request across multiple models may create additional billed work.
The tutorial disables the OpenAI SDK’s automatic retries and permits only one explicit, narrowly authorized model fallback.
For a production service, additionally use:
-
Overall request deadlines.
-
Per-model concurrency limits.
-
Circuit breakers.
-
Exponential backoff with jitter where appropriate.
-
Bounded fallback attempts.
-
Health-aware routing.
Also test what happens when the fallback model fails.
A fallback policy is incomplete unless it defines the final failure state.
13. Calculate the potential inference savings
Let’s evaluate the economics using current model prices.
As of October 2026, OpenAI’s published Standard text-token rates for our selected generation models are:
| Model | Input per million tokens | Output per million tokens |
|---|---|---|
| GPT-6 Luna | $0.10 | $0.50 |
| GPT-6.1 Sol | $2.00 | $10.00 |
These are ordinary text-token rates for the relevant Standard configuration, not a universal price across processing tiers, long-context adjustments, or regional options.
For this particular pricing comparison, Luna costs one-twentieth as much per ordinary input token and per output token.
That does not mean every workflow costs one-twentieth as much.
Different models can produce different output lengths, internal reasoning token counts, and failure rates.
Calculate cost per generated response
Assume a request uses:
-
1,000 input tokens.
-
200 output tokens, including billed reasoning tokens where applicable.
-
No discounted input reads.
-
No additional tools.
For GPT-6.1 Sol:
Cost = (1,000 × $2.00 + 200 × $10.00) / 1,000,000 = $0.004
For GPT-6 Luna:
Cost = (1,000 × $0.10 + 200 × $0.50) / 1,000,000 = $0.0002
Now assume 100,000 requests per day.
If every request uses the stronger model:
100,000 × $0.004 = $400 per day
Suppose caching and routing produce the illustrative workload distribution introduced earlier.
| Request path | Daily requests | Estimated generation cost |
|---|---|---|
| Valid cache hits | 35,000 | $0 |
| Small model | 40,000 | $8 |
| Strong model | 25,000 | $100 |
| Total | 100,000 | $108 |
Illustrative generation-cost comparison:
| Daily generation cost | |
|---|---|
| Baseline (every request on GPT-6.1 Sol) | $400 |
| Optimized (cache + routing) | $108 |
| Estimated generation savings | $292 per day (73%) |
The savings are before embeddings, infrastructure, retries and validation.
The 73% figure is hypothetical. It is not a measurement from running the tutorial application.
It also assumes that moving a request between models does not change the specified token counts.
Include embedding costs
Semantic caching incurs its own expense.
Our chosen Voyage embedding model is priced at $0.02 per million input tokens.
Suppose the application performs 50,000 semantic lookups daily, each averaging 50 embedding tokens.
Embedding cost = 50,000 × 50 × $0.02 / 1,000,000 = $0.05
That is five cents per day under these assumptions, before any applicable free tier.
In this workload, embeddings are inexpensive relative to generation.
However, additional costs still matter:
-
Vector search and database infrastructure.
-
Network round trips.
-
Storage and retention.
-
Cache maintenance.
-
Validation operations.
-
Failed model attempts.
-
Any extra requests introduced by escalation.
Furthermore, not every semantic lookup creates a useful hit.
A cache with an impressive lookup count may still save little money.
The economically meaningful measurement is net cost avoided per valid cache hit.
Build a more complete cost model
For production accounting, use:
Total cost = generation cost + embedding cost + tool cost + infrastructure cost + validation cost + retry and escalation cost
Then compare total spending with the number of correctly completed tasks.
Record provider usage rather than assuming every model call uses the same input and output lengths.
For OpenAI, token accounting should distinguish ordinary input, cached input reads, cache writes where applicable, and output tokens under the relevant billing rules.
14. The cherry on the cake: a real deployment reported $12,249 in savings
A particularly interesting production result was published by LiteLLM on August 10, 2026.
The company described a deployment of its automatic model router serving more than 450 users across development, staging, and production environments.
The reported measurement window ran from April 15 through August 9, 2026.
LiteLLM production case study (August 2026):
| Reported measure | Value |
|---|---|
| Routed requests | 272,876 |
| Distinct end users | 450+ |
| All-flagship counterfactual spending | $23,985 |
| Routed production spending | $11,736 |
| Estimated savings | $12,249 (51.1% versus the flagship baseline) |
Source: LiteLLM production savings report. Vendor-reported, using USD list-price comparisons.
The surprising detail is that LiteLLM reported 95% of requests were served without using the flagship tier.
That means only approximately one in twenty requests reached the most expensive model tier in the reported configuration.
The savings percentage also increased as the deployment was tuned:
| Period | Reported savings rate |
|---|---|
| May 2026 | 42.9% |
| June 2026 | 50.0% |
| July 2026 | 53.7% |
| August 1–9, 2026 | 60.7% |
The details matter.
LiteLLM calculated the all-flagship comparison by taking recorded production token usage and repricing it at the flagship model’s list rates.
This was a counterfactual billing comparison, not a randomized A/B experiment in which all requests were independently executed through the flagship model.
The report also does not establish equivalent answer quality through an independent controlled experiment.
Actual output token counts might have differed if a flagship model had handled every request.
The vendor’s reported spending figures used its cost map rather than customer invoice totals.
Consequently, the result should not be interpreted as proof that every application can achieve 51% savings without any quality trade-off.
It is evidence that routing can materially alter the economics of a mixed production workload.
The broader lesson is that model capacity should be allocated deliberately.
A large proportion of requests may not require the strongest available model, but the appropriate distribution must be discovered through evaluation.
15. Evaluate semantic caching without sacrificing answer quality
A dangerous inference optimization can appear successful because it reduces spending while increasing incorrect answers.
The evaluation framework therefore needs to measure quality and cost together.
Build a representative evaluation dataset
For our fictional retailer, use questions covering:
-
Standard shipment tracking.
-
Shipping estimates.
-
Public return policies.
-
Multiple-policy comparisons.
-
Questions involving unusual return conditions.
-
Requests for unavailable order status.
-
Ambiguous or contradictory requests.
-
Questions that should explicitly bypass the cache.
Create positive and negative question pairs.
A positive pair consists of two questions known to accept the same authoritative answer.
A negative pair consists of questions that should not reuse the same response.
For example:
| Original question | Candidate question | Reuse decision |
|---|---|---|
| How do I track my shipment? | How can I track my shipment? | Potentially valid |
| What is your return policy? | Can I return an opened item? | Reject |
| What is your return policy? | Can I return a damaged item? | Reject |
| How long does standard shipping take? | Does express shipping include weekends? | Reject |
| How do I track my package? | Where is my order right now? | Reject |
The final four pairs introduce conditions or information requirements not satisfied by the generic answer.
High embedding similarity must not override that distinction.
Measure incorrect accepted cache hits
One useful metric is:
Incorrect-hit rate = incorrect accepted cache matches / all accepted cache matches
This measures the proportion of accepted hits that were wrong.
Separately track:
-
False-positive rate among labeled negative pairs.
-
Recall among genuinely reusable pairs.
-
Exact-hit rate.
-
Semantic-hit rate.
-
Cache bypass rate.
-
Validation rejection rate.
-
Cache-induced latency.
These metrics have different denominators and should not be conflated.
In a high-impact workflow, even a small number of incorrect semantic hits may outweigh the financial savings.
Choose thresholds empirically
Our similarity threshold of 0.93 is illustrative.
It is not a generally safe cutoff for Voyage 4 Lite or every embedding model.
To choose a threshold:
-
Collect representative positive and negative question pairs.
-
Generate embeddings using the actual deployed model and configuration.
-
Calculate similarity scores.
-
Evaluate accepted matches across candidate thresholds.
-
Measure incorrect matches and missed valid reuse opportunities.
-
Select a threshold consistent with the application’s quality requirements.
Repeat this process when embedding models, input preprocessing, supported languages, or application behavior change.
A threshold should be treated as evaluated configuration, not an arbitrary constant copied between projects.
Evaluate the model router
Compare the optimized application against two important baselines:
-
Always use the strongest candidate model.
-
Always use the smallest candidate model.
The first shows the potential quality baseline and cost ceiling.
The second shows whether routing provides benefits beyond choosing the least expensive model for everything.
Track the following operational metrics:
| Metric | Purpose |
|---|---|
| Cost per successful task | Measures economic efficiency |
| Verified task success rate | Protects output quality |
| Exact-cache hit rate | Measures deterministic reuse |
| Semantic-cache hit rate | Measures additional reuse |
| Incorrect-hit rate | Detects unsafe caching |
| Model distribution | Shows routing behavior |
| Escalation rate | Reveals small-model limitations |
| Fallback rate | Tracks availability-related degradation |
| Validation rejection rate | Measures unusable model outputs |
| p50 and p95 latency | Captures user experience |
| Tokens per successful workflow | Detects unnecessary processing |
| Provider cached-token usage | Measures prompt-caching benefit |
Use shadow evaluation before rollout
In shadow mode, production requests continue to receive answers from the existing serving configuration.
A sampled subset is also evaluated using the proposed router and candidate models.
The new outputs are assessed offline against verified answers or appropriate task rubrics.
This can expose quality regressions before users encounter them.
Shadow inference itself incurs costs, so sampling and evaluation budgets matter.
Once the proposed configuration meets its quality requirements, deploy it gradually and maintain a fast rollback mechanism.
16. Production security and reliability considerations
The tutorial is deliberately a constrained prototype.
Production systems need additional safeguards around privacy, security, concurrency, and correctness.
Prevent cross-user and cross-tenant leakage
A shared cache must not serve private content to unauthorized users.
A production cache key may require:
-
Tenant identity.
-
Authenticated permission scope.
-
Policy or source-document revision.
-
Application behavior revision.
-
Language and locale.
-
Tool configuration.
-
Output format.
-
Other context that changes the correct answer.
For personalized queries, it may be safer to disable shared semantic answer caching entirely.
A public FAQ cache should never contain private order details merely because the question superficially resembles a public FAQ.
The demonstration’s fixed namespace is appropriate only because its cached information is fictional, public, and non-personalized.
Treat embeddings as application data
Embedding vectors are not guaranteed to be anonymous.
Depending on the source text and application, vectors can reveal information about their inputs.
Use appropriate access controls, retention policies, and data-handling practices.
Do not assume that embedding sensitive content automatically removes its privacy implications.
Protect against cache poisoning
Cache poisoning occurs when an attacker or faulty component causes incorrect content to enter the cache.
The danger is amplified because subsequent users may receive the poisoned response without generating a new one.
Our strict validator prevents arbitrary generated text from entering the approved FAQ cache.
In broader applications, add:
-
Trusted source verification.
-
Authorization-aware cache operations.
-
Write validation.
-
Quarantine and invalidation mechanisms.
-
Audit logs for cache modifications.
-
Prompt-injection defenses.
-
Independent evaluation of reusable answers.
Regular expressions alone are not a complete security boundary.
Prevent cache stampedes
Imagine a popular cached response expires and thousands of requests arrive simultaneously.
Without coordination, all those requests may miss the cache and call the model.
This is known as a cache stampede.
Common defenses include:
-
Per-key single-flight request deduplication.
-
Distributed locking.
-
Bounded expiration jitter.
-
Request coalescing.
-
Carefully controlled stale-while-revalidate behavior.
Serving stale answers is not appropriate for every domain.
Only allow it where the underlying information can safely remain temporarily outdated.
Avoid making the cache a required dependency
Our application catches embedding failures and continues through generation.
This is useful because a cache should improve performance and cost, not unnecessarily reduce application availability.
In production, narrow exception handling wherever possible and log failure categories for monitoring.
An embedding outage should not silently disable observability.
Similarly, a vector database failure should have an explicit bypass or fail-closed policy depending on the task.
Define complete request deadlines
A production request may involve multiple operations:
-
Cache lookup.
-
Embedding generation.
-
Vector search.
-
Primary model inference.
-
Validation.
-
Fallback inference.
Each consumes part of the available latency budget.
Without an overall deadline, a fallback can continue running long after the original user request should have ended.
A robust router tracks remaining time and avoids starting operations that cannot reasonably complete within it.
Handle provider storage settings carefully
The example uses store=False in the Responses API.
This disables storage of the generated response as a retrievable Responses API object under that setting.
It should not be interpreted as a blanket guarantee about all provider-side processing, logging, or retention.
Review provider data controls and any applicable organizational requirements independently.
17. Advanced optimization: decide whether semantic lookup is worth performing
Semantic caching is frequently implemented as a universal lookup before every model call.
That can be inefficient.
A semantic lookup itself requires resources, including embedding computation, vector search, and network latency.
Suppose a request is highly unusual and unlikely to match anything already cached.
Searching for a reusable response might add cost without producing a benefit.
A more advanced inference layer can estimate whether semantic lookup is economically worthwhile.
A simplified decision formula is:
Expected cache benefit = probability of valid hit × avoided inference cost − lookup cost − expected error cost
Consider two tasks.
Task A: Popular public FAQ
A frequently asked shipping question has:
-
High probability of reuse.
-
Low risk when backed by a canonical policy.
-
Small embedding cost.
-
Predictable answer content.
A semantic cache may be economically attractive.
Task B: Unique document analysis
A user submits a unique document requiring detailed interpretation.
This request has:
-
Low probability of answer reuse.
-
Potentially significant context differences.
-
Greater risk from an incorrect cached match.
-
Possibly expensive preprocessing and embedding.
Semantic lookup may add little value.
The correct strategy might be to bypass the semantic cache and route directly to an appropriate model.
Our implementation makes a basic version of this decision by limiting semantic lookup to narrowly approved FAQ intents.
A more advanced system could learn cache eligibility from historical traffic.
Such a classifier should be evaluated against actual valid-hit probability, false matches, latency, and net savings.
18. Improve routing beyond simple rules
Our demonstration router intentionally uses straightforward logic:
Short approved FAQs use the small model.
Longer approved FAQs can use the stronger model.
Unapproved or more complicated requests go to the stronger model.
This makes testing understandable, but it is not a sophisticated difficulty estimator.
Real routing systems can use more informative signals.
Task-type classification
Separate workloads into categories such as:
-
Text transformation.
-
Information extraction.
-
Public knowledge answering.
-
Multi-document analysis.
-
Code generation.
-
Tool-based workflows.
-
Planning or complex reasoning.
Evaluate model performance separately for each category.
A small model may perform exceptionally well on one category and poorly on another.
Model success predictions
A router may estimate the probability that each candidate model will complete a task successfully.
For a candidate model, an approximate decision objective is:
Expected utility = task value × probability of success − inference cost − expected failure cost
This highlights why the cheapest model should not always win.
If a task is inexpensive to retry, a low-cost first attempt may be attractive.
If a failure can trigger significant downstream consequences, routing directly to a more reliable model may be more economical.
Learned difficulty classifiers
A learned router can use labeled examples of tasks and successful model choices.
However, it introduces its own operational considerations:
-
Classifier inference cost.
-
Additional latency.
-
Training-data quality.
-
Distribution drift.
-
Calibration.
-
Evaluation and monitoring.
A learned router is valuable only when its benefits exceed these costs.
Start with transparent rules where possible and introduce complexity after measuring a clear need.
19. A practical production rollout plan
Deploying caching, routing, and fallback behavior simultaneously makes it difficult to understand which optimization created the observed result.
A staged rollout provides more reliable evidence.
Phase 1: Establish the baseline
Measure existing workloads before changing the architecture.
Capture:
-
Request volume.
-
Token usage by model.
-
Average and tail latency.
-
Total inference spending.
-
Retry and escalation rates.
-
Verified task success.
Segment traffic by task type.
Identify frequently repeated prompts and expensive workflows.
Phase 2: Introduce exact caching
Start with stable, reusable, low-risk tasks.
Exact caching generally has fewer semantic correctness concerns than approximate matching.
Implement revision-aware invalidation and authorization-safe cache keys.
Measure avoided generation calls.
Phase 3: Add small-model routing
Move one well-defined task class to a smaller model.
Compare it with the current strong-model baseline.
Measure success rate, retry costs, validation failures, and latency.
Do not expand routing merely because the smaller model has a lower token price.
Phase 4: Add semantic caching
Build a labeled evaluation set containing both reusable question pairs and dangerous near-matches.
Introduce conservative eligibility rules.
Validate all cache writes.
Deploy with a high precision requirement and monitor incorrect hits.
Expand only when real evaluations justify doing so.
Phase 5: Implement controlled fallbacks
Define which requests may be downgraded.
Test outages, timeouts, budget exhaustion, and multiple simultaneous failures.
Ensure that requests requiring stronger handling cannot silently take an unauthorized route.
Phase 6: Optimize continuously
Reevaluate the system whenever:
-
Model capabilities change.
-
Provider pricing changes.
-
Traffic composition shifts.
-
Policies or reference materials change.
-
New task classes are introduced.
-
Validation requirements evolve.
Treat the inference router as a versioned application component with measurable behavior.
A routing configuration that was economical several months ago may no longer be optimal after a model release or price adjustment.
20. Common inference optimization mistakes
Several predictable mistakes can undermine the value of an otherwise promising optimization project.
| Mistake | Better approach |
|---|---|
| Cache every completed generation | Cache only validated, reusable answers |
| Treat high cosine similarity as proof of correctness | Require eligibility and authoritative validation |
| Use broad keyword intent matching | Qualify requests with structured constraints |
| Use one threshold for every domain | Calibrate thresholds on representative labeled pairs |
| Route only by prompt length | Evaluate task requirements and expected success |
| Downgrade every failed strong-model request | Permit fallbacks only for approved task classes |
| Ignore embedding and infrastructure expenses | Measure net savings |
| Compare only API token prices | Compare cost per successful task |
| Trust model-reported confidence | Use evaluated, independently meaningful signals |
| Share cached answers across tenants | Enforce scope and authorization boundaries |
| Ignore invalidation | Version policies, data, and model behavior |
| Assume provider prompt caching returns previous answers | Distinguish prefix reuse from answer caching |
| Deploy without outage testing | Test fallback, timeout, and budget behavior |
| Optimize without a baseline | Measure before changing the system |
One additional mistake deserves special attention: using a language model for work that can be implemented deterministically.
Our canonical public FAQ example is deliberately simple.
Once an application’s eligibility classifier can identify the correct authoritative answer with sufficient reliability, generating that same fixed sentence with a language model may be unnecessary.
A deterministic lookup could eliminate generation cost entirely.
Semantic caching and routing are valuable architectural tools, but they should not create extra inference where ordinary software logic would be more reliable and less expensive.
21. Bringing everything together
An effective inference optimization system does more than select cheaper models.
It understands the different kinds of work an AI application performs.
Some requests are repeated and can be answered from verified cached results.
Others are simple enough for a small model.
More complex requests benefit from stronger models, additional tools, or greater validation.
Certain tasks should not be automatically downgraded at all.
The architecture built in this tutorial demonstrates how to combine those decisions while preserving important correctness boundaries.
It includes a modern embedding integration, real generation APIs, version-aware storage, strict answer validation, approved fallbacks, and automated behavioral tests.
The most important principles are:
-
Reuse only when correctness is established. High similarity alone is insufficient.
-
Route based on task requirements. Model price is only one part of the decision.
-
Validate before caching. A completed generation is not necessarily a correct generation.
-
Degrade explicitly. Small-model fallback must respect quality and business constraints.
-
Measure complete workflows. Token savings mean little if task success deteriorates.
-
Prefer deterministic solutions when possible. The cheapest reliable inference call is often the one you never make.
The illustrative cost calculation showed how caching and routing could reduce generation spending by 73% under favorable workload assumptions.
The LiteLLM production case study provides a real-world example of a vendor-reported 51.1% reduction against an all-flagship counterfactual.
Neither number should become an automatic expectation.
Your actual result depends on request distribution, model quality, output length, cache eligibility, validation effectiveness, infrastructure overhead, and user requirements.
22. Put it into practice
Start with the runnable application.
Execute its offline test suite, then configure your provider credentials and inspect how the five demonstration requests move through the cache and model router.
Once you understand the behavior, choose a single high-volume, low-risk workflow from your own application.
A strong first candidate might be a public documentation assistant, product FAQ chatbot, or repetitive text-processing service.
Use the following implementation checklist:
- Establish baseline cost per successful task and latency.
- Identify responses that are genuinely safe to reuse.
- Implement exact caching with version-aware invalidation.
- Add a validated small-model route.
- Introduce semantic caching with positive and negative-pair evaluations.
- Enforce cache-write validation.
- Test failures, fallback eligibility, and budget limits.
- Measure net savings without allowing unacceptable quality regressions.
- Roll out gradually and monitor performance.
Your next action: take one repetitive inference workflow, implement the cache-and-router prototype, and measure its cost and task-success rate over a representative evaluation set.
Keep the optimization only if it improves your overall cost per successful task while meeting the application’s quality requirements.
The objective is not to make every request cheap. It is to make expensive inference necessary only when the task genuinely benefits from it.