Back to Blog
AI & Agents

Small Language Models Are Eating Your Agent: A Heterogeneous Routing Playbook

Fine-tuned 7B models now handle most routine agent steps cheaper and more reliably than frontier models. How I route work across SLMs and big models — and the 90% cost cut that follows.

Amit ShrivastavaJuly 3, 20269 min read

A code snippet from this post was tested

Node.js v22.23.1 · Verified July 3, 2026

Logic from this post, adapted into a runnable form and executed by the publishing pipeline.

node verify.mjs

Snippet

const callSLM = async ({ prompt, schema, type }) => {
  // Mock implementation for SLM calls
  if (type === 'extraction') {
    if (prompt.includes("SKU-123") && schema) {
      return { sku: "SKU-123", quantity: 2, color: "blue" };
    }
  } else if (type === 'classification') {
    if (prompt.includes("refund")) {
      return "Refund";
    }
  }
  return `SLM processed: ${prompt} (type: ${type})`;
};

const callMediumModel = async ({ prompt, context }) => {
  // Mock implementation for Medium model calls
  if (prompt.includes("follow up")) {
    return "Could you provide more details?";
  }
  return `Medium model processed: ${prompt}`;
};

const callFrontierModel = async ({ prompt, context, temperature }) => {
  // Mock implementation for Frontier model calls
  if (prompt.includes("creative content")) {
    return "A poetic description of a sunset.";
  }
  return `Frontier model processed: ${prompt}`;
};

class AgentModelRouter {
  async routeAndCall(payload) {
    const { task, input, schema, context } = payload;

    if (task === 'extractProductDetails' || task === 'extractCustomerInfo') {
      const result = await callSLM({ prompt: input, schema, type: 'extraction' });
      return { output: result, modelUsed: 'SLM_EXTRACTOR', costEstimate: 0.0001 };
    }

    if (task === 'classifySupportIntent' || task === 'sentimentAnalysis') {
      const result = await callSLM({ prompt: input, type: 'classification' });
      return { output: result, modelUsed: 'SLM_CLASSIFIER', costEstimate: 0.0002 };
    }

    if (task === 'generateSimpleFollowUp' || task === 'summarizeShortText') {
      const result = await callMediumModel({ prompt: input, context });
      return { output: result, modelUsed: 'MEDIUM_GENERIC', costEstimate: 0.005 };
    }

    if (task === 'orchestrateComplexWorkflow' || task === 'creativeContentGeneration' || task === 'resolveAmbiguity') {
      const result = await callFrontierModel({ prompt: input, context, temperature: 0.7 });
      return { output: result, modelUsed: 'FRONTIER_ORCHESTRATOR', costEstimate: 0.50 };
    }

    console.log(`No specific route for task: ${task}. Falling back to MEDIUM_GENERIC.`);
    const result = await callMediumModel({ prompt: `Given the input: "${input}" and context: "${context}", perform the task: "${task}".` });
    return { output: result, modelUsed: 'MEDIUM_GENERIC', costEstimate: 0.007 };
  }
}

async function runTests() {
  const router = new AgentModelRouter();

  // Test 1: Extract product details (SLM_EXTRACTOR)
  const productPayload = {
    task: 'extractProductDetails',
    input: 'I want 2 blue widgets, SKU-123.',
    schema: { type: 'object', properties: { sku: { type: 'string' }, quantity: { type: 'number' }, color: { type: 'string' } } }
  };
  const productResult = await router.routeAndCall(productPayload);
  console.log('--- Test 1: Product Extraction ---');
  console.log('Input:', productPayload.input);
  console.log('Result:', productResult);

  // Test 2: Classify support intent (SLM_CLASSIFIER)
  const classifyPayload = {
    task: 'classifySupportIntent',
    input: 'I want a refund for my last order.'
  };
  const classifyResult = await router.routeAndCall(classifyPayload);
  console.log('--- Test 2: Intent Classification ---');
  console.log('Input:', classifyPayload.input);
  console.log('Result:', classifyResult);

  // Test 3: Generate simple follow-up (MEDIUM_GENERIC)
  const followUpPayload = {
    task: 'generateSimpleFollowUp',
    input: 'The product is broken.',
    context: 'User reported an issue.'
  };
  const followUpResult = await router.routeAndCall(followUpPayload);
  console.log('--- Test 3: Simple Follow-up ---');
  console.log('Input:', followUpPayload.input);
  console.log('Result:', followUpResult);

  // Test 4: Creative content generation (FRONTIER_ORCHESTRATOR)
  const creativePayload = {
    task: 'creativeContentGeneration',
    input: 'Describe a beautiful sunset.',
    context: ''
  };
  const creativeResult = await router.routeAndCall(creativePayload);
  console.log('--- Test 4: Creative Content ---');
  console.log('Input:', creativePayload.input);
  console.log('Result:', creativeResult);

  // Test 5: Unhandled task (Fallback to MEDIUM_GENERIC)
  const unhandledPayload = {
    task: 'analyzeMarketTrends',
    input: 'What are the current trends in AI?',
    context: 'Recent news articles.'
  };
  const unhandledResult = await router.routeAndCall(unhandledPayload);
  console.log('--- Test 5: Unhandled Task ---');
  console.log('Input:', unhandledPayload.input);
  console.log('Result:', unhandledResult);
}

runTests();

Captured output

--- Test 1: Product Extraction ---
Input: I want 2 blue widgets, SKU-123.
Result: {
  output: { sku: 'SKU-123', quantity: 2, color: 'blue' },
  modelUsed: 'SLM_EXTRACTOR',
  costEstimate: 0.0001
}
--- Test 2: Intent Classification ---
Input: I want a refund for my last order.
Result: { output: 'Refund', modelUsed: 'SLM_CLASSIFIER', costEstimate: 0.0002 }
--- Test 3: Simple Follow-up ---
Input: The product is broken.
Result: {
  output: 'Medium model processed: The product is broken.',
  modelUsed: 'MEDIUM_GENERIC',
  costEstimate: 0.005
}
--- Test 4: Creative Content ---
Input: Describe a beautiful sunset.
Result: {
  output: 'Frontier model processed: Describe a beautiful sunset.',
  modelUsed: 'FRONTIER_ORCHESTRATOR',
  costEstimate: 0.5
}
No specific route for task: analyzeMarketTrends. Falling back to MEDIUM_GENERIC.
--- Test 5: Unhandled Task ---
Input: What are the current trends in AI?
Result: {
  output: 'Medium model processed: Given the input: "What are the current trends in AI?" and context: "Recent news articles.", perform the task: "analyzeMarketTrends".',
  modelUsed: 'MEDIUM_GENERIC',
  costEstimate: 0.007
}

Small Language Models Are Eating Your Agent: A Heterogeneous Routing Playbook

It’s an exciting, yet often expensive, time to be building with AI agents. We’re all mesmerized by the capabilities of frontier models like GPT-4, Claude, or Gemini when orchestrating complex tasks. But let’s be honest: are we really getting value for money by sending every single prompt to these behemoths? After more than a decade in software, seeing patterns from web2 to web3 to now AI, I can tell you this: efficiency always wins in the long run.

For the past year, I’ve been heavily invested in building AI-powered agents, and I quickly hit a wall with the operational costs. Sending every intermediate step, every small validation, every data transformation to a $100+/M frontier model felt like using a rocket to swat a fly. That's when I started experimenting. What if we didn’t need a rocket for every fly? What if a perfectly capable, fine-tuned slingshot could do the job cheaper and more reliably for 90% of the flies?

The answer, as I’ve discovered and implemented in production, is a resounding yes. Small Language Models (SLMs) – specifically, fine-tuned 7B models – are not just powerful; they are transformational for agent costs and reliability when deployed strategically. I'm talking about a 90% cost reduction by intelligently routing work between SLMs and larger models. This isn’t theoretical; this is my playbook.

The "All Roads Lead to GPT-4" Fallacy

When you start building agents, especially with frameworks like LangChain or LlamaIndex, the initial instinct is to wire everything up to the most capable model. It reduces cognitive load during development and initially, it works. You prototype quickly. But as your agent scales, your cloud bill balloons, and you start noticing long tail failures where the frontier model hallucinates on a simple task.

Consider these common agent steps:

  • Extraction: Pulling specific entities (names, dates, amounts) from a structured text.
  • Classification: Deciding if an email is a support request or a sales lead.
  • Simple Summarization: Condensing a short paragraph into a sentence.
  • Data Validation: Checking if an output conforms to a JSON schema.
  • Tool Selection: Deciding which function to call based on a user's intent.
  • Rephrasing/Tone Adjustment: Making a sentence more polite or concise.

Do these truly require a frontier model’s advanced reasoning capabilities every single time? Often, the answer is no. A fine-tuned, smaller model excels at these narrow, repetitive tasks.

The Heterogeneous Routing Strategy: Smart Delegation

My core philosophy for optimizing agent performance and cost is heterogeneous routing. It's about building a robust decision layer that intelligently delegates tasks to the most appropriate model for the job.

Here’s how I break it down:

1. Task Definition & Prioritization

Before you can route, you need to understand your agent's core capabilities and the individual steps it performs. My first step is always to enumerate every distinct "atomic" task my agent is designed to execute.

  • extractProductDetails: Needs to extract SKU, quantity, color from an order confirmation.
  • classifySupportIntent: Determines if a user message is about "Refund," "Technical Issue," or "Feature Request."
  • generateFollowUpQuestion: Creates a clarifying question if initial user input is ambiguous.
  • orchestrateComplexWorkflow: This is the big one, often requiring broad reasoning.

For each task, I ask:

  • What's the complexity?
  • How critical is accuracy?
  • Can this task be framed as a classification, extraction, or simple generation?
  • What's the expected input and output format?

2. Model Selection: The Right Tool for the Job

This is where the "heterogeneous" part comes in. My typical stack includes:

  • Fine-tuned SLMs (e.g., Llama 2 7B, Mistral 7B): Best for highly specific, repetitive tasks that can be framed as classification, entity extraction, or constrained generation. They are incredibly fast and cheap, especially when self-hosted or run on optimized inference platforms.
  • Open-source "Medium" Models (e.g., Mixtral 8x7B, Llama 3 8B): Good for slightly more complex reasoning, summarization of longer texts, or tasks where an SLM might struggle with generalization, but a frontier model is still overkill.
  • Frontier Models (e.g., GPT-4-Turbo, Claude 3 Opus): Reserved for truly novel, complex, or open-ended reasoning, orchestration of multi-step processes, or scenarios where maximum robustness and creativity are paramount.

Implementation: The Routing Layer

This is where the rubber meets the road. I typically implement a routing layer as a separate service or a dedicated module within my agent's codebase. It's essentially a sophisticated if/else or a configurable router.

Here's a simplified TypeScript example of a ModelRouter interface and its implementation:

// interfaces/router.ts
export type ModelType = 'SLM_EXTRACTOR' | 'SLM_CLASSIFIER' | 'MEDIUM_GENERIC' | 'FRONTIER_ORCHESTRATOR';

export interface PromptPayload {
  task: string; // e.g., "extractProductDetails", "classifySupportIntent"
  input: string;
  schema?: any; // For structured outputs
  context?: string;
}

export interface ModelResponse {
  output: string | object;
  modelUsed: ModelType;
  costEstimate: number;
}

export interface ModelRouter {
  routeAndCall(payload: PromptPayload): Promise<ModelResponse>;
}

// services/modelRouter.ts
import { ModelRouter, PromptPayload, ModelResponse, ModelType } from '../interfaces/router';
import { callSLM, callMediumModel, callFrontierModel } from './modelClients'; // Assume these exist

export class AgentModelRouter implements ModelRouter {
  async routeAndCall(payload: PromptPayload): Promise<ModelResponse> {
    const { task, input, schema, context } = payload;

    if (task === 'extractProductDetails' || task === 'extractCustomerInfo') {
      // These are specific extraction tasks, perfect for a fine-tuned SLM
      const result = await callSLM({ prompt: input, schema, type: 'extraction' });
      return { output: result, modelUsed: 'SLM_EXTRACTOR', costEstimate: 0.0001 };
    }

    if (task === 'classifySupportIntent' || task === 'sentimentAnalysis') {
      // Classification tasks, also ideal for SLMs
      const result = await callSLM({ prompt: input, type: 'classification' });
      return { output: result, modelUsed: 'SLM_CLASSIFIER', costEstimate: 0.0002 };
    }

    if (task === 'generateSimpleFollowUp' || task === 'summarizeShortText') {
      // Simple generation or summarization
      const result = await callMediumModel({ prompt: input, context });
      return { output: result, modelUsed: 'MEDIUM_GENERIC', costEstimate: 0.005 };
    }

    if (task === 'orchestrateComplexWorkflow' || task === 'creativeContentGeneration' || task === 'resolveAmbiguity') {
      // Complex reasoning, requiring frontier model
      const result = await callFrontierModel({ prompt: input, context, temperature: 0.7 });
      return { output: result, modelUsed: 'FRONTIER_ORCHESTRATOR', costEstimate: 0.50 };
    }

    // Fallback for unhandled tasks, or use a default model
    console.warn(`No specific route for task: ${task}. Falling back to MEDIUM_GENERIC.`);
    const result = await callMediumModel({ prompt: `Given the input: "${input}" and context: "${context}", perform the task: "${task}".` });
    return { output: result, modelUsed: 'MEDIUM_GENERIC', costEstimate: 0.007 };
  }
}

3. Proactive Output Validation

This is crucial for reliability. Even with fine-tuned SLMs, you need guardrails. For tasks requiring structured output (like JSON), always validate the output against a schema. If the SLM fails to conform, you have options:

  • Retry with SLM: Sometimes a slight re-prompt or second attempt works.
  • Escalate to a larger model: Send the original prompt and the SLM's (failed) output to a medium or frontier model with instructions to fix or re-do. This is still cheaper than sending everything to the big model initially.

The Agent Process Flow with Heterogeneous Routing

Here’s a visualization of how an agent might use this routing strategy:

graph TD
    A[User Input] --> B{Agent Controller};
    B --> C{Identify Initial Task};
    C --> D{Model Router};
    D -- Task Type 1 (e.g., Extraction) --> E[SLM (Fine-tuned)];
    D -- Task Type 2 (e.g., Simple Gen) --> F[Medium Model];
    D -- Task Type 3 (e.g., Complex Reasoning) --> G[Frontier Model];
    E --> H{Output Validation};
    F --> I{Output Validation};
    G --> J{Output Validation};
    H -- Valid --> K[Integrate & Continue];
    I -- Valid --> K;
    J -- Valid --> K;
    H -- Invalid --> D;
    I -- Invalid --> D;
    J -- Invalid --> K;
    K --> L[Respond to User / Perform Action];

Note: The invalid path from H & I going back to D might indicate a re-routing to a more capable model for correction, or a retry. From J, an invalid output might lead to internal logging and potential user communication about an error, as this is the "final frontier" model.

The Fine-Tuning Advantage

You might wonder, why not just prompt-engineer a 7B model? While impressive, prompt engineering has its limits. For truly reliable performance on specific tasks, fine-tuning is king. It bakes the desired behavior directly into the model weights, making it:

  • More reliable: Fewer hallucinations or deviations from specified formats.
  • Faster: Doesn't need lengthy, complex prompts or in-context examples.
  • Cheaper: Smaller models are inherently cheaper to run, and fine-tuning makes them efficient for specific tasks, reducing the need for frontier models.

I've found that even a few hundred well-curated examples can dramatically improve an SLM's performance for tasks like specific entity extraction or intent classification. Platforms like Hugging Face, Google Cloud's Vertex AI, or AWS Bedrock make fine-tuning surprisingly accessible.

A 90% Cost Cut: My Real-World Experience

This isn't hyperbole. Across several agent projects, by meticulously identifying which tasks could be handled by SLMs and routing them appropriately, I’ve seen my inference costs plummet by over 90%.

Here’s a breakdown of where the savings typically come from:

  • Eliminating redundant frontier model calls: Most internal verification, data reformatting, or simple decision-making steps no longer hit the most expensive endpoint.
  • Faster execution: SLMs infer much quicker, reducing latency and operational overhead.
  • Increased reliability: Fine-tuned SLMs, for their specific tasks, often outperform generic frontier models because they don't have to "think" as broadly. This means fewer retries or manual interventions.

The Road Ahead

The landscape of LLMs is evolving rapidly. We're seeing increasingly capable SLMs, open-source models catching up to proprietary ones, and more accessible fine-tuning tools. The "Small Language Models Are Eating Your Agent" trend is only going to accelerate. Those who adapt now, by intelligently segregating and routing their agent's workload, will gain a significant competitive advantage in terms of cost, speed, and reliability.

Don't let the allure of the "bigger is always better" mentality blind you to the power of precise, targeted interventions. Start small, identify your agent's most frequent and simplest tasks, and empower SLMs to handle them. Your wallet (and your agent's users) will thank you.


Connect with me:

I'd love to hear your thoughts on this strategy or discuss your own experiences building AI agents. Let's connect on LinkedIn: https://www.linkedin.com/in/amit-shrivastava or X: https://x.com/amit5214.

Small Language Models
Model Routing
AI Agents
Cost Optimization