Skip to main content
Back to Blog
AI/MLCloud ComputingProgramming Languages
13 August 20268 min readUpdated 13 August 2026

Creating a Cost-Effective AI Support Triage API with Serverless Inference

Most AI driven applications initially incorporate a single model embedded within the application. This simplistic approach is fine for a prototype but becomes problematic when a...

Creating a Cost-Effective AI Support Triage API with Serverless Inference

Most AI-driven applications initially incorporate a single model embedded within the application. This simplistic approach is fine for a prototype but becomes problematic when a single endpoint needs to handle diverse and complex tasks. These tasks—such as classification, urgency scoring, generating customer responses, and summarizing long-form content—require distinct models due to varying cost, latency, and quality demands.

Support triage is a prime example of this complexity. For instance, the cost per token remains the same whether processing a simple query like “how do I reset my password?” or a detailed escalation from an enterprise client. You might branch based on ticket types within your application, selecting different models for each, but this leads to having model selection logic embedded in your handler. This setup results in a rigid try/except fallback strategy and necessitates redeployment with every pricing update. Consequently, you might end up using a 70B model for simple tickets, facing delays when the model is slow, and frequently redeploying.

Illustration for: Support triage is a prime exam...

This tutorial demonstrates how to use serverless inference through an inference router to efficiently build a FastAPI support triage endpoint, resolving these issues. By the end, you'll be able to automatically route tasks to the appropriate model, complete with built-in fallback, and without hardcoding model names in your application. This setup will yield a production-ready API that is up to 71% cheaper than using a singular advanced model for everything.

What You’re Building

You'll create a single endpoint, POST /triage, that processes a ticket payload to return:

  • Classification: Categorizes the issue (billing, bug, how-to, account, etc.)
  • Urgency + Sentiment: Provides a severity score and assesses customer mood
  • Drafted Reply: Crafts a short, customer-focused response
  • Escalation Summary: Generates a structured brief for complex tickets

The architecture transitions from:

App → hardcoded model (one model handles every task)

to:

App → Serverless inference via Inference Router → best-fit model per task

The inference router enables this transformation by abstracting model selection from your application.

Serverless Inference and Inference Router

The inference router allows defining tasks and model pools, routing incoming requests to the optimal model based on task definitions and selection policies. A task is a designated job with a description, and a model pool is a set of candidate models that the router can choose from, governed by policies like lowest cost, lowest latency, or a manual hierarchy. Configuration occurs at the router level, meaning your app communicates with the router instead of specific models.

Serverless inference facilitates sending API requests to models without managing infrastructure, allowing quick deployment without handling the components behind an inference endpoint.

Project Setup

To proceed, ensure you have Python 3.10+, an account with serverless inference enabled, and a model access key. A pre-configured project is available, but you can follow along to construct your version and understand the API choices made.

Project structure:

support-triage/
├── main.py
├── sample_tickets.json
├── requirements.txt
└── .env

Clone the repository and install the necessary components:

git clone https://github.com/Jameshskelton/triage_app
cd triage_app
python3 -m venv venv_triage
source venv_triage/bin/activate
pip install -r requirements.txt

Step 1: The Baseline - Direct Model Calls

Initially, developers might hardcode one model to handle all tasks. Below is an example setup using this method:

import os
import re
from openai import OpenAI
from fastapi import FastAPI
from pydantic import BaseModel
from dotenv import load_dotenv

load_dotenv()

client = OpenAI(
    base_url=os.environ["DO_INFERENCE_BASE_URL"],
    api_key=os.environ["DO_MODEL_ACCESS_KEY"],
)

MODEL = "llama3.3-70b-instruct"

app = FastAPI()

class Ticket(BaseModel):
    subject: str
    body: str

def call_model(system: str, user: str) -> str:
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": user},
        ],
    )
    return resp.choices[0].message.content.strip()

@app.post("/triage")
def triage(ticket: Ticket):
    text = f"Subject: {ticket.subject}\n\n{ticket.body}"

    category = call_model(
        "Classify this support ticket into one of: billing, bug, how-to, account, other. Reply with one word.",
        text,
    )
    urgency = call_model(
        "Score urgency from 1 (low) to 5 (critical) and note sentiment. Reply as 'score: N, sentiment: X'.",
        text,
    )
    reply = call_model(
        "Write a short, professional reply to this customer. Maximum 4 sentences.",
        text,
    )
    summary = call_model(
        "Summarize this ticket for a human agent. Include the problem, what's been tried, and recommended next steps.",
        text,
    )

![Illustration for: category = callmodel(
        ...](https://storage.googleapis.com/xfinit-blogs-scraper-assets-664708921442/blog-assets/images/eaad7938-eec6-479e-991d-0603489331e5.jpg)

    return {
        "category": category,
        "urgency": urgency,
        "reply": reply,
        "escalation_summary": summary,
    }

Run it with:

uvicorn main:app --reload

Test it with simple and complex tickets to observe the responses.

Step 2: Configure the Inference Router

In the control panel, navigate to the Inference Router and create a new router with appropriate tasks and model pools. Ensure each task has a clear description and that each pool includes fallback models.

Step 3: Refactor the App to Use the Router

Replace hardcoded model references with the router ID and pass the task name through requests. This setup allows the router to select models dynamically:

ROUTER = "your-router-id"

def parse_urgency(urgency_text: str) -> int:
    match = re.search(r"score:\s*(\d)", urgency_text, re.IGNORECASE)
    return int(match.group(1)) if match else 3

def call_router(task: str, system: str, user: str) -> dict:
    resp = client.chat.completions.create(
        model=ROUTER,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": user},
        ],
        extra_body={"task": task},
    )
    return {
        "content": resp.choices[0].message.content.strip(),
        "served_by": resp.model,
    }

@app.post("/triage")
def triage(ticket: Ticket):
    text = f"Subject: {ticket.subject}\n\n{ticket.body}"

    category = call_router(
        "classify_ticket",
        "Classify this support ticket into one of: billing, bug, how-to, account, other. Reply with one word.",
        text,
    )
    urgency = call_router(
        "urgency_detection",
        "Score urgency from 1 (low) to 5 (critical) and note sentiment. Reply as 'score: N, sentiment: X'.",
        text,
    )
    reply = call_router(
        "draft_customer_reply",
        "Write a short, professional reply to this customer. Maximum 4 sentences.",
        text,
    )

![Illustration for: category = callrouter(
       ...](https://storage.googleapis.com/xfinit-blogs-scraper-assets-664708921442/blog-assets/images/15e5e54a-6e13-4185-84f4-36f559bce3f8.jpg)

    urgency_score = parse_urgency(urgency["content"])
    summary = None
    if urgency_score >= 4:
        summary = call_router(
            "escalate_complex_issue",
            "Summarize this ticket for a human agent. Include the problem, what's been tried, and recommended next steps.",
            text,
        )

    return {
        "category": category["content"],
        "urgency": urgency["content"],
        "urgency_score": urgency_score,
        "reply": reply["content"],
        "escalation_summary": summary["content"] if summary else None,
        "routing": {
            "classify_ticket": category["served_by"],
            "urgency_detection": urgency["served_by"],
            "draft_customer_reply": reply["served_by"],
            "escalate_complex_issue": summary["served_by"] if summary else None,
        },
    }

Step 4: Run Mixed Tickets Through the Router

Test with a variety of ticket complexities to see the router's efficiency in selecting appropriate models. You can use sample_tickets.json to run tests.

What This Actually Saves You

Evaluate cost savings by comparing the router-based approach with hardcoded models. Using serverless inference rates, the router offers significant savings by efficiently routing tasks to the most suitable models.

Production Checklist

Before deploying, ensure logging, task evaluation, fallback models, and regular updates for routing rules to optimize performance.

Closing Thoughts

The final setup simplifies the application by moving model selection logic to the router. This flexible and efficient approach allows for easy adjustments without code changes. Future enhancements might include streaming replies, integrating summaries into ticketing systems, or deploying additional routers for other workflows.