Stop the token bleed: building token-efficient multi-agent systems
The New Stack

Stop the token bleed: building token-efficient multi-agent systems

Stop the token bleed: building token-efficient multi-agent systems Every engineering team deploying AI agents eventually discovers an uncomfortable truth: the model isn’t the biggest expense. The hidden cost is everything around it: repeated retrievals, duplicate prompts, unnecessary tool calls, oversized context windows, multiple agents reasoning over the same information. Individually, these architectural decisions seem harmless. At production scale, they become a severe tax on latency, infrastructure, and cloud spend. A proof-of-concept agent that answers 50 questions a day can tolerate inefficiencies. An enterprise platform coordinating thousands of requests per minute cannot. This article explores practical techniques for engineering token-efficient AI systems without sacrificing output quality. Rather than focusing solely on prompt compression, we will optimize the entire workflow from routing and retrieval to caching and model selection. Why token optimization is a systems problem Most discussions around token optimization begin and end with prompt engineering. In practice, architecture drives token consumption. Consider a typical multi-agent workflow: User ↓ Intent Agent ↓ Retriever ↓ Research Agent ↓ Planning Agent ↓ Writer Agent ↓ Reviewer Agent ↓ Final Response At each stage, the system might retrieve the same documents, repeat identical instructions, call the same model, and resend the entire conversation history. By the time a response reaches the user, the architecture has processed tens of thousands of unnecessary tokens. β€œImproving efficiency requires redesigning the workflow, not just shortening the prompts.” Improving efficiency requires redesigning the workflow, not just shortening the prompts. Architecture overview A production-ready, token-efficient architecture introduces optimization before every expensive model invocation. User Request β”‚ β–Ό Intent Router β”‚ β–Ό Semantic Cache ───────► Cached Response β”‚ β–Ό Context Budget Manager β”‚ β–Ό Adaptive Retriever β”‚ β–Ό Model Router β”‚ β–Ό LLM β”‚ β–Ό Validated Response β€œThe large language model is no longer the first component. It is the final, most expensive operation.” Notice the critical shift: the large language model is no longer the first component. It is the final, most expensive operation. Step 1: Install modern dependencies Use the latest package structure to avoid deprecated imports and align with the current LangChain ecosystem. Bash pip install \ langchain \ langchain-core \ langchain-openai \ langchain-community \ fastapi \ faiss-cpu \ tiktoken \ rank-bm25 \ pydantic \ python-dotenv Step 2: Configure the model Production systems must configure retries, timeouts, and credentials through the environment. Python import os from langchain_openai import ChatOpenAI api_key = os.getenv("OPENAI_API_KEY") if not api_key: raise ValueError("OPENAI_API_KEY must be configured.") llm = ChatOpenAI( model="gpt-4o-mini", temperature=0, api_key=api_key, timeout=30.0, max_retries=2, ) Setting a low temperature improves consistency, while explicit timeouts and retry limits help the system recover gracefully from transient API failures. Step 3: Route before you generate Not every request requires a large language model. Deterministic logic can often answer simple questions. Routing inexpensive requests away from the LLM yields the most significant cost reduction in production systems. Python def classify_request(question: str) -> str: q = question.lower() if "status" in q: return "metrics" if "runbook" in q: return "retrieval" return "generation" Step 4: Add a semantic cache One of the simplest and most effective optimizations is an exact-match cache, which returns a previously generated response when the same question is asked against the same retrieved documents, avoiding unnecessary model calls. Python import hashlib # Using an exact-match (lexical) cache exact_match_cache = {} def cache_key(question: str, sources: list[str]) -> str: """ Generate a deterministic cache key from the user question and the retrieved document identifiers. """ fingerprint = question + "|" + "|".join(sorted(sources)) return hashlib.sha256(fingerprint.encode()).hexdigest() # Example usage in the pipeline: # key = cache_key(question, source_ids) # if key in semantic_cache: # return semantic_cache[key] Step 5: Budget your context Most retrieval pipelines return far more text than the model actually needs. Instead of stuffing the context window with every retrieved document, establish a strict context budget. Python import tiktoken encoder = tiktoken.encoding_for_model("gpt-4o-mini") MAX_CONTEXT_TOKENS = 2500 def build_context(chunks): context = [] used = 0 for chunk in chunks: tokens = len(encoder.encode(chunk.page_content, disallowed_special=())) if used + tokens > MAX_CONTEXT_TOKENS: break context.append(chunk.page_content) used += tokens return "\n\n".join(context) Step 6: Retrieve once Repeated retrieval is a surprisingly common flaw in multi-agent systems. The rule is simple: retrieve once, reuse everywhere. Python from langchain_core.documents import Document from langchain_community.vectorstores import FAISS from langchain_openai import OpenAIEmbeddings documents = [ Document( page_content="Database latency often follows connection pool exhaustion.", metadata={"source": "db_runbook"}, ), Document( page_content="Node pressure can increase API response times.", metadata={"source": "cluster_runbook"}, ), ] embeddings = OpenAIEmbeddings(api_key=api_key) index = FAISS.from_documents(documents, embeddings) retrieved_docs = index.similarity_search(question, k=4) shared_context = build_context(retrieved_docs) Now, every downstream agent consumes the same optimized context instead of launching its own redundant retrieval pipeline. Step 7: Route models intelligently Large models should solve complex problems. Everything else belongs to a smaller, faster model. Python from langchain_openai import ChatOpenAI small_model = ChatOpenAI(model="gpt-4o-mini", temperature=0, api_key=api_key) large_model = ChatOpenAI(model="gpt-4.1", temperature=0, api_key=api_key) def choose_model(question: str): """Route requests to the most appropriate model based on complexity.""" if len(question) < 200: return small_model return large_model This strategy drastically reduces operational costs without noticeably affecting response quality. Step 8: Estimate tokens before sending Without token telemetry, optimization is just guesswork. Monitoring usage makes efficiency measurable and helps engineers detect cost regressions. Python import tiktoken encoder = tiktoken.encoding_for_model("gpt-4o-mini") def estimate_tokens(messages): """ Estimate input tokens for an OpenAI-style chat payload. Note: This is an estimate, not an exact billing calculation. """ tokens_per_message = 3 tokens_per_name = 1 total = 0 for message in messages: total += tokens_per_message for key, value in message.items(): if isinstance(value, str): total += len(encoder.encode(value)) if key == "name": total += tokens_per_name # Every reply is primed with additional assistant tokens. total += 3 return total Step 9: Validate responses Production systems must return structured outputs to ensure downstream systems receive predictable, well-formed data. Python from pydantic import BaseModel class AgentResponse(BaseModel): answer: str sources: list[str] def validate_response(answer: str, sources: list[str]): """Validate and serialize the agent response using a structured schema.""" response = AgentResponse( answer=answer, sources=sources, ) return response.model_dump() Step 10: Build the optimized pipeline Finally, assemble the architectural components into a single workflow. Notice how failures degrade gracefully instead of crashing the service. Python import logging from langchain_core.prompts import ChatPromptTemplate logger = logging.getLogger(name) def run_pipeline(question: str): """Execute the token-efficient AI workflow with graceful degradation.""" try: route = classify_request(question) # Route deterministic requests away from the LLM. if route == "metrics": return { "answer": "Retrieve metrics directly from the monitoring system.", "sources": [], } # Retrieve context once. docs = index.similarity_search(question, k=4) context = build_context(docs) source_ids = [ doc.metadata.get("source") for doc in docs if doc.metadata.get("source") ] # Check exact-match cache. key = cache_key(question, source_ids) if key in exact_match_cache: return exact_match_cache[key] # Select the most appropriate model. model = choose_model(question) # Keep trusted instructions separate from untrusted user input. prompt_template = ChatPromptTemplate.from_messages( [ ( "system", ( "Answer the user's question using ONLY the provided context. " "If the answer cannot be determined from the context, say so." "\n\nContext:\n{context}" ), ), ("user", "{question}"), ] ) chain = prompt_template | model result = chain.invoke( { "context": context, "question": question, } ) payload = validate_response( answer=result.content, sources=source_ids, ) # Cache validated response. exact_match_cache[key] = payload return payload except Exception: logger.exception("Token-efficient pipeline failed.") # Gracefully degrade instead of crashing. return { "answer": ( "The AI pipeline encountered an error. " "Please continue using the standard operational workflow." ), "sources": [], } What actually reduced token usage? When teams instrument architectures like this, the largest savings rarely come from editing prompts. They come from eliminating unnecessary work. The biggest improvements typically stem from: - Retrieving documents once instead of multiple times. - Caching semantically identical requests. - Routing simple requests away from the LLM. - Limiting context with explicit token budgets. - Selecting the smallest suitable model. These architectural shifts reduce cost and latency while making system behavior significantly easier to reason about. Lessons learned Several core principles consiste

Read on The New Stack ↗ ← Back to News

Comments

No comments yet. Start the discussion.