Every token in context is memory the model reasons over
More tokens = more compute = more latency
Agentic loops multiply token spend non-linearly
Output tokens cost 2–5× more than input tokens
Design and governance work before any request arrives
Intercept and route before spending any LLM tokens
Assemble only the context the model actually needs
Control what the model generates and how much
Minimise cost of tool calls and their results
Govern the full agentic loop across all iterations
| Group | Stage | What it does | Est. Saving | Agnostic |
|---|---|---|---|---|
| G1 Prompt Design | Before Request | Compress prompts; one-role-one-agent; lazy-load edge-case instructions | 15–40% input | ✅ |
| G2 Template Registry | Before Request | Git-backed registry with per-template token budgets enforced in CI/CD | Prevents bloat | ✅ |
| G3 Knowledge Strategy | Before Request | Preprocess documents at ingestion; decide RAG vs. fine-tune per domain | Up to 100% retrieval | ⚠️ |
| G4 Rules-Based Bypass | At the Gate | Skip LLM entirely for deterministic requests | 30–90% LLM calls | ✅ |
| G5 Response Caching | At the Gate | L1 exact + L2 semantic cache; idempotent step cache for retries | 40–70% API calls | ✅ |
| G6 Model Routing | At the Gate | Route to cheapest capable model; escalate on low confidence only | 60–80% per-query cost | ✅ |
| G7 Retrieval Optimisation | Into the LLM | Small chunks + reranker + hybrid dense/sparse → inject only top-1 or top-2 | 50–80% context | ✅ |
| G8 Tool Loading | Into the LLM | Inject only tools relevant to the current step; prune unused tools weekly | 20–70% sys prompt | ✅ |
| G9 Context Schema | Into the LLM | Compact typed fields for inter-step state; suppress reasoning from handoffs | 20–70% handoff tokens | ✅ |
| G10 Memory Management | Into the LLM | Sliding window + rolling summary; externalise state; retrieve skills on-demand | 30–70% multi-turn | ✅ |
| G11 Output Control | Inside the LLM | Always set max_tokens; use JSON schema instead of prose for machine outputs | 30–60% output | ⚠️ |
| G12 Reasoning Budget | Inside the LLM | Constrain chain-of-thought via prompt instructions or provider budget parameters | 50–90% reasoning | ⚠️ |
| G13 Batch & TOON | Inside the LLM | Batch similar items into one call; use TOON compact schema for repetitive data | 20–98% bulk calls | ✅ |
| G14 Tool Minimisation | After Response | Combine tool calls, parallelise in handler; project to only the fields needed | 30–90% tool spend | ✅ |
| G15 Server-Side Compute | After Response | Filter, sort, aggregate in your tool/MCP handler — never in the LLM context | 40–80% data-heavy | ✅ |
| G16 Agent Architecture | Across the Loop | Decompose monoliths into specialised sub-agents; async for I/O-heavy workflows | 20–60% per-agent | ✅ |
| G17 Loop Control | Across the Loop | Hard iteration limits + stop conditions; pass remaining_budget to downstream agents | 10–30% iterative | ✅ |
| G18 Observability | Across the Loop | Instrument every LLM call; track ET metric + turn efficiency; alert on overruns | 15–40% indirect | ✅ |
All the tools listed below are based on internet search/publicly available information
I have NOT experimented/verified all the TOOLS listed below, ensure you do the due diligence before you implement any of these tools in your architecture
Every token in every system prompt is billed on every call. Three compounding levers:
Remove filler phrases ("As an AI…", "Please note…"), collapse whitespace, eliminate boilerplate. For automated compression: LLMLingua-2 (Microsoft Research, MIT) runs as a Docker HTTP sidecar — call via HTTP from Java or Go before any LLM request. Reduces prompts 50–80% preserving semantic accuracy. Deploy in your AI Gateway, not individual service logic. Compress static prompts at build time; dynamic prompts at runtime. Do not compress user inputs.
Each example adds 50–200 tokens per call. Audit all prompts with 3+ examples; replace with precise task description + JSON output schema. A/B test on 200 representative samples. If accuracy drops, add back one example only. ⚠️ Cheaper model tiers may still need 1–2 examples — test each tier separately.
Three failure modes inflate system prompts fleet-wide:
Establish a layered composition model: base → role → task → dynamic context. Each layer independently versioned and token-budgeted (G2).
All other groups fix token waste reactively. This group prevents it by making token budgets a first-class property of every prompt template, enforced in CI/CD.
Each template stored in Git (YAML/JSON) with mandatory fields:
template_id: customer-support-classifier version: 3.2.1 owner: platform-team token_budget: system_prompt_max: 400 total_input_max: 800 output_max: 150 current_token_counts: system_prompt: 387 # measured at build time
CI/CD enforcement: pre-commit hook renders each template with representative inputs, counts tokens, fails the pipeline if budget exceeded. Token count history tracked per version — regressions visible in PR diff. Increasing a budget requires documented justification. Templates not accessed in 30 days flagged for deprecation.
Count tokens using your provider's tokenizer endpoint. For Go: tiktoken-go gives reasonable BPE approximations for GPT-family models only. ⚠️ tiktoken and tiktoken-go are OpenAI-specific — they will produce incorrect counts for Gemini and other non-BPE models. For a provider-agnostic fallback, use a character-based estimate (~4 chars ≈ 1 token) as a ceiling check before calling the provider endpoint.
Strip content that tokenises expensively but carries no value: HTML tags, headers/footers, legal boilerplate, base64 data. Process at ingestion — cost paid once, not on every retrieval.
Raw doc → Extract text → Strip boilerplate → Collapse whitespace
→ Tables to CSV → If >4,000t → summarise with cheap model
→ Store clean text in vector DB
Apache Tika: ⚠️ Java Maven dependency. Unstructured OSS, Docling, MarkItDown: Python-based, available as Docker HTTP sidecars for Java/Go.
Every RAG call injects 400–3,000 retrieval tokens per query. For stable, high-frequency domain knowledge (product taxonomy, internal policies, support playbooks), fine-tuning bakes knowledge into weights — reduced retrieval tokens per call permanently, though Fine-tuning is not a reliable replacement for knowledge retrieval and can lead to stale knowledge.
| Knowledge | Approach | Token impact |
|---|---|---|
| Changes frequently | RAG | Retrieval cost per call |
| Stable, large (>1M tokens) | RAG + hybrid search (G7) | Optimise precision |
| Stable, moderate size, high frequency | Fine-tune | Eliminate retrieval entirely |
| Stable, small, critical accuracy | Fine-tune + RAG fallback | Minimal retrieval |
Break-even: for a domain receiving 1M queries/month with 1,000 retrieval tokens per cache-miss query, even a 50% cache hit rate from G5 still yields 500M retrieval tokens/month saved via fine-tuning. ⚠️ Re-train when knowledge changes. ⚠️ Keep RAG fallback for out-of-distribution queries.
Not every request needs an LLM. Route deterministic requests — order lookups, eligibility checks, CRUD, FAQ matches, structured data queries — directly to backend services before the LLM call. Zero tokens spent.
Audit LLM call logs first — categorise by type, identify what is deterministic. Do not over-specify rules — incorrectly bypassing the LLM costs more in quality than it saves in tokens.
For Java: Apache Camel ⚠️ for custom routing logic.
31% of enterprise LLM queries are semantically similar to previous ones.
Tiered strategy: L1 → L2 → L3 (LLM call + store result). Normalise before hashing. TTL: FAQ answers → days; real-time data → no cache.
Individual workflow steps treated as pure functions — results memoised by hash(step_name + step_inputs + template_version). On retry/re-run, unchanged steps skipped — no LLM call. High value for: workflows that fail partway, evaluation pipelines, batch pipelines sharing upstream steps.
Temporal provides deterministic workflow replay: completed Activity results are stored in workflow history and reused during replay, avoiding re-execution of already completed Activities. Activities themselves use at-least-once execution semantics and should be designed to be idempotent when side effects are involved.
For Java: Jedis/Lettuce for Redis. For Go: go-redis.
Two complementary strategies — use both:
Classifier categorises each request before any model call → cheapest capable model.
| Tier | Example models | Relative cost | Typical traffic |
|---|---|---|---|
| Simple | Gemini Flash Lite · GPT-4o mini · Claude Haiku | ~1× | ~70% |
| Medium | Gemini Flash · GPT-4o · Claude Sonnet | ~5–15× | ~20% |
| Complex | Gemini Pro · GPT-4.5 · Claude Opus | ~50–150× | ~10% |
RouteLLM (ICLR 2025): achieves 95% of GPT-4 quality routing only 14–26% of traffic to frontier models, though these results are benchmark-dependent and workload-specific
Send every request to cheap model first. If it returns {answer, confidence ≥ 0.88} → return immediately. Escalate only on low confidence. In practice 70–80% of queries never need escalation.
⚠️ 0.88 is a starting point only — model-generated confidence is not statistically calibrated. Validate against ground truth on your own workload before deploying; the right threshold varies significantly by task type.
Start with word-count + keyword heuristic classifier (zero latency). LiteLLM acts as provider-agnostic proxy — routing logic lives in the proxy, your code calls one endpoint.
Naive RAG retrieves top-5 large chunks (~10,000 context tokens/call). Optimised RAG delivers top-1 or top-2 (~400–800 tokens). Two compounding levers:
| Strategy | Weakness |
|---|---|
| Dense only (ANN/embedding) | Misses exact keywords → false positives in context |
| Sparse only (BM25/TF-IDF) | Misses paraphrases → over-fetches |
| Hybrid RRF | Higher precision → fewer chunks pass threshold |
RRF score: 1/(k + rank_dense) + 1/(k + rank_sparse) where k=60. Start with alpha=0.5, tune on your dataset.
<=> with tsvector in SQL — no new infra if Postgres is runningalphaPre-compute all embeddings and sparse indexes at ingestion — never at query time. For Java: LangChain4j ⚠️ for orchestration.
All tool definitions injected into every system prompt by default. A large tool registry adds 2,000–5,000 tokens to every single LLM call. Inject only tools relevant to the current step.
⚠️ Requires tools tagged with intent/category metadata in advance.
The architect's choice of how information flows between steps and agents is made once but affects every token for the system's lifetime. Prose accumulates 3–5× larger than structured context.
PROSE (~40–60 tokens)
"Customer John Smith called about order 1234 placed March 15th, shipped 7 days ago without confirmation. Requested escalation."
STRUCTURED (~14–18 tokens)
cust=C99|order=1234 |placed=2026-03-15 |status=shipped|days=7 |action=escalate
next_action upstream to reduce downstream re-derivation (saves an extra LLM call)Design LangGraph TypedDict collaboratively before implementation — state field proliferation is hard to prune retroactively. Use Instructor to enforce typed LLM output at message boundaries.
Three compounding patterns preventing context from growing unbounded:
A 20-turn conversation accumulates 15,000+ tokens/call. Fix: keep last 4–8 turns verbatim; summarise older turns with cheap model once; prepend as a single context message. One summarisation call recovers in 2–3 subsequent calls of avoided history tokens.
Naive agents embed full state in system prompt every call. By step 7 of a 10-step workflow: 5,000+ tokens/call. Fix: store state externally; inject only instruction + minimal_context for the current step.
[Orchestrator] <--> [State Store (Redis / any KV)]
│
[LLM API] — sees only current-step context (200–500 tokens)
Knowledge-heavy agents embed all procedures in the system prompt permanently (1,000–3,000+ tokens/call). Fix: index skills as RAG chunks with metadata tags; retrieve only relevant skills using hybrid retrieval (G7).
Before: [Role: 500t] + [All skills: 2,000t] + [Tools: 800t] = 3,300 tokens/call After: [Role: 500t] + [Retrieved skills: ~300t] + [Tools: 800t] = ~1,600 tokens/call
Skills: atomic chunks (one skill = one chunk), tagged with category and conditions. Version in Git. Store history/state in Redis — never in-process memory.
Output tokens cost 2–5× more than input (model/provider dependent). Two always-on levers:
max_tokens — mandatory on every call. Without it, models return 3–10× more than needed. Start at 2× expected output, tune down.| Provider | Structured output feature |
|---|---|
| Gemini / Vertex AI | response_mime_type: application/json + response_schema |
| OpenAI | response_format: json_schema (GPT-4o+) |
| Anthropic Claude | Tool use pattern + Instructor |
| All providers | max_tokens / max_output_tokens — always set |
Extended reasoning models generate large reasoning volumes billed at output token rates — often dwarfing the actual response.
| Provider | Parameter |
|---|---|
| OpenAI o1/o3 | reasoning_effort: low/medium/high + max_completion_tokens |
| Gemini 2.5 Flash Thinking | thinking_config or thinking_budget in Vertex AI |
| Anthropic Claude | thinking: {budget_tokens: N} |
Apply only where reasoning is not needed in the output. ⚠️ Over-constraining hurts accuracy on complex tasks — validate quality trade-offs on your workload. Measure reasoning vs. output split separately in observability (G18).
Shared prompt overhead paid once per batch instead of once per item. Instead of 50 calls each paying for the same system prompt, send one: "Classify each of the following 50 tickets. Return a JSON array indexed by position."
Use message queue (Kafka) to accumulate items; flush on size or time threshold. Cap batch size by token budget: items × avg_size + shared_prefix. For Java: CompletableFuture. For Go: goroutines with semaphore.
For repetitive structured data with uniform schema, verbose JSON is extremely expensive — every key name, brace, and quote is tokenised.
// Standard JSON (~85 tokens for 2 rows)
[{"order_id": 1001, "status": "shipped", "region": "APAC"},
{"order_id": 1002, "status": "pending", "region": "EMEA"}]
// TOON (~12 tokens for 2 data rows)
orders: id|status|region
1001|shipped|APAC
1002|pending|EMEA
// TOON with code substitution (~8 tokens per data row)
orders(id,s,r): 1001,S,A|1002,P,E
# s: S=shipped P=pending r: A=APAC E=EMEA
Schema definition is a one-time cost in the system prompt, amortised across thousands of data rows. For a batch of 500 rows, the ~20-token schema overhead adds ~0.04 tokens per row. Apply TOON only to the LLM-facing portion when the downstream system needs standard JSON.
⚠️ Requires uniform structure across items.
Two distinct compounding levers — reduce number of calls and size of each result:
Each tool call = full LLM round-trip (output tokens + result input tokens + follow-up). Three lookups = 3× the tokens.
For Java: CompletableFuture.allOf() inside tool implementations. For Go: sync.WaitGroup.
If a tool returns 50 fields but agent uses 3, the other 47 are waste on every subsequent turn.
[1001, 1002, 1003] vs [{"id": 1001, ...}]For Java: ToolResponseProjector class with field whitelist. For Go: middleware wrapper stripping unexposed keys. Use G18 observability to measure average tool result token size — high-average tools are top targets.
A common expensive pattern: loading raw data into the LLM context for filtering, sorting, or aggregation. Code execution costs nothing in tokens; in-context data processing is extremely expensive.
| ❌ Do NOT do (in-context) | ✅ Do instead (server-side) |
|---|---|
| Pass 500 log lines → find errors | Regex in your service; return matching lines only |
| Pass full customer list → filter by region | WHERE region = 'X' in DB; pass only results |
| Pass raw API response → sort by date | Sort in tool handler before returning |
| Pass 200 results → pick top 5 | Cross-encoder rerank; return top 5 |
| Pass 40-field JSON → extract 3 fields | Extract in code; pass only those 3 |
MCP servers are the ideal location: filter, sort, project data in the tool handler so the LLM never receives raw unprocessed data. Audit current tool handlers for raw dataset dumps. Compounds with G14.
For Java/Go: filtering/sorting in tool handler methods or DB queries — never delegate to the LLM.
A monolithic agent carries all tools, instructions, and domain knowledge — the system prompt is maximally large. Decompose into specialised sub-agents: each carries only context relevant to its function. Additional benefits: each sub-agent uses the cheapest model tier for its complexity (G6); failures isolated; agents independently testable. ⚠️ Start with 2–3 sub-agents for highest-token tasks before decomposing everything. NOTE: Multi-agent systems can reduce context per agent, but they can also increase total token usage due to handoffs, orchestration overhead, and duplicated context. The benefit is workload-dependent.
In a synchronous ReAct agent, the full accumulated context is held open and re-sent while waiting for tool I/O.
Synchronous: Agent holds [full context] → calls tool → waits → injects result → LLM call
Async: Agent emits event → suspends (context discarded, zero cost during I/O)
Tool executes → fires result → Agent resumes with [minimal state + result only]
On async resume, the agent loads only current-step context from the external state store (G10) — 200–500 tokens vs. 5,000+ in the synchronous version.
Temporal is the most mature tool for durable async workflows: each Activity receives only its specific inputs on resume, not the full workflow history. ⚠️ Async adds operational complexity (distributed state, event ordering, idempotency) — apply to high-token, high-latency workflows first.
Without explicit bounds, agents accumulate tokens at each iteration until context window exhaustion.
LangGraph: recursion_limit in graph config. AutoGen: max_turns. LangChain AgentExecutor: max_iterations.
In multi-agent pipelines, downstream agents default to unconstrained verbosity without a budget. Pass remaining_budget as an explicit field in every inter-agent message (as part of the G9 state schema):
{"task": "...", "token_budget_remaining": 800, "output_format": "compact"}
Budget-aware output instruction in each agent's system prompt: "If token_budget_remaining < 500, respond only with required JSON fields — no explanation."
In LangGraph: add remaining_budget to TypedDict state; decrement at each node using completion_tokens from API response. Implement in the orchestration layer, not individual agent code.
"Optimization without measurement is guesswork."
prompt_tokens + completion_tokens splitNormalised cost across tiers:
OpenLLMetry — lowest effort, wraps existing LLM SDK via OpenTelemetry auto-instrumentation. Langfuse — adds prompt versioning, eval scoring, team dashboards. Emit token-usage.jsonl per workflow run for post-hoc analysis. Set per-feature and per-team daily budgets in Prometheus; alert on overrun.
When a loop pathology is detected, the framework must act — not just alert. Implement a configurable circuit breaker per workflow: on trigger (turn count > 2× baseline, or repeated identical call fingerprint detected via `hash(step_name + input)` appearing ≥ 3 times in a sliding window of 5 calls), execute one of: `partial_return` (halt and return accumulated result with a `forced_stop` flag), `human_interrupt` (halt and emit to a webhook/queue for human decision), or `hard_kill` (halt and discard). Configure the action per workflow type in your orchestration layer — interactive agents default to `partial_return`; high-stakes workflows to `human_interrupt`; batch pipelines to `hard_kill`.
| Layer | Recommended Approach | Group |
|---|---|---|
| Prompt registry | Git + YAML with token budgets + Promptfoo in CI | G2 |
| Document ingestion | Unstructured OSS / Apache Tika Docker sidecar at ingestion | G3 |
| AI Gateway / proxy | LiteLLM as provider-agnostic proxy | G6 |
| Rules bypass | LiteLLM / Bifrost Apache Camel / LangGraph conditional routing | G4 |
| Response cache L1 | Redis OSS — hash-based exact match | G5 |
| Response cache L2 | GPTCache + pgvector — semantic similarity | G5 |
| Step cache | Redis with hash(step_name + inputs + version) | G5 |
| Model routing | RouteLLM + LiteLLM rules | G6 |
| Vector / RAG store | pgvector or Qdrant — hybrid dense+sparse | G7 |
| Reranker | Cohere Rerank or cross-encoder in Haystack | G7 |
| Tool registry | Intent-tagged manifest; inject per step | G8 |
| Agent state store | Redis / any KV — externalised per step | G10 |
| Agent memory | Mem0 + Redis — skills retrieved on-demand | G10 |
| Agent orchestration | LangGraph — typed state schema; per-node tool scoping | G9 · G8 |
| Async execution | Temporal OSS for long-running / I/O-heavy workflows | G16 |
| MCP tool handlers | Server-side filtering/sorting/projection | G15 |
| Observability | Langfuse OSS + OpenLLMetry — ET metric + turn efficiency | G18 |
| Token governance | Prometheus + Grafana — per-feature and per-team budget alerts | G18 |