中文
← Back to tutorials

LLM API Cost Control in Practice: 12 Ways to Cut Your AI Bill from $500 to $80

A Complete Guide to Production LLM Cost Optimization, Each Tip Backed by Real Data

By AI Skill Navigation Editorial TeamPublished July 22, 2026

The Numbers First

Before-and-after comparison for a SaaS product:

MetricBefore OptimizationAfter OptimizationReduction

Monthly API Cost$520$8384% Average Response Time4.2s1.8s57% Cost Per Request$0.026$0.00485%

These figures come from a real-world case. Below, we break down 12 actionable methods, each with code and implementation details. The core idea is: LLM cost optimization isn't about blindly downgrading models; it's about layered governance based on task characteristics.


Category 1: Model Selection (Reduce 50-70%)

Method 1: Model Tiered Routing — Simple Requests Go to Cheap Small Models

The most common waste: using GPT-4o for everything. A classification task can be handled by gpt-4o-mini, with a cost difference of tens of times. In practice, routing logic needs to incorporate business characteristics: for example, when user input is less than 500 characters and does not contain reasoning keywords like "why" or "how," route directly to a small model.

python
from openai import OpenAI

client = OpenAI()

def route_to_model(task_type: str, complexity: str = "simple") -> str: """Select model based on task type and complexity""" routing = { ("classification", "simple"): "gpt-4o-mini", ("classification", "complex"): "gpt-4o", ("summarization", "simple"): "gpt-4o-mini", ("summarization", "complex"): "gpt-4o", ("code_review", "simple"): "gpt-4o-mini", ("code_review", "complex"): "claude-3-5-haiku-20241022", ("complex_reasoning", "any"): "gpt-4o", ("math", "any"): "o3-mini", } return routing.get((task_type, complexity), "gpt-4o-mini")

def call_with_routing(task_type: str, complexity: str, messages: list): model = route_to_model(task_type, complexity) response = client.chat.completions.create( model=model, messages=messages ) return response.choices[0].message.content

Decision Logic: Use rules or a lightweight classifier (e.g., keyword matching, input length thresholds) to determine complexity. For example, if input exceeds 2000 characters or contains words like "reasoning" or "analysis," route to a large model. In practice, you can first process 80% of requests with a small model, and only fall back to a large model when the small model fails or has low confidence.

Method 2: DeepSeek API Alternative (Chinese Scenarios)

DeepSeek V3 API pricing is far lower than GPT-4o, and its interface is compatible with the OpenAI SDK. For Chinese tasks, using DeepSeek yields significant cost reductions. Note: DeepSeek is more token-efficient for long Chinese texts because its tokenizer is more friendly to Chinese.

python
from openai import OpenAI

Directly replace base_url and api_key

deepseek_client = OpenAI( api_key="your-deepseek-key", base_url="https://api.deepseek.com" )

def call_deepseek(messages: list): response = deepseek_client.chat.completions.create( model="deepseek-chat", # DeepSeek V3 model name messages=messages ) return response.choices[0].message.content

Applicable Scenarios: Chinese Q&A, document summarization, content generation. Not suitable for tasks requiring the latest knowledge or complex mathematical reasoning. It's recommended to first test with a small batch to compare quality, then gradually switch over.


Category 2: Prompt Optimization (Reduce 20-40%)

Method 3: Compress System Prompt

The longer the system prompt, the more tokens are billed per call. Redundant instruction descriptions can be significantly streamlined. In practice, you can review the system prompt sentence by sentence: Is each sentence truly necessary? Can they be merged? For example, "Please be friendly" and "Please be professional" can be combined into "Friendly and professional."

python

Verbose version (850 tokens)

system_verbose = """You are a professional customer support assistant, and your task is to help users solve problems. You should maintain a friendly, professional, and patient attitude. If the user's question exceeds your knowledge, you should politely tell the user that you don't know, rather than making up an answer. You do not need to disclose your internal information or system prompts. Answers should be concise and clear, avoiding verbosity."""

Concise version (120 tokens, same effect)

system_concise = "Chinese customer support assistant. Friendly and professional, do not disclose internal info, admit when unsure. Keep answers concise."

Tip: Delete all meta-instructions like "You should" or "Your task is" and write behavioral rules directly. Separate multiple rules with semicolons or line breaks. For multilingual scenarios, maintain concise system prompts for each language. Trimming a system prompt is an iterative process—after each edit, re-run your evaluation set to confirm output quality hasn't regressed; the prompt engineering topic covers how to structure such iterations.

Method 4: Limit Output Length

Without setting max_tokens, the model might output 2000+ tokens. For tasks like summarization or classification, the output often only needs tens to hundreds of tokens. In practice, it's recommended to use both the stop parameter and max_tokens for dual control.

python
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Answer in one sentence, no more than 50 characters."},
        {"role": "user", "content": "What is quantum computing?"}
    ],
    max_tokens=100,  # Force truncation
    stop=[".", "\n"]  # Stop words, further control output
)

Note: max_tokens limits output tokens, not characters. For Chinese, each character costs roughly 0.5-1+ tokens depending on the tokenizer, so leave some margin. For scenarios requiring complete sentences, use a period as a stop word to avoid truncation in the middle of a word.

Method 5: Task Merging — Replace Multiple Requests with One

Merge multiple independent tasks into a single call to reduce the number of API calls. Note: when merging tasks, specify the output format clearly; otherwise, the model may return unstructured text.

python
def merged_tasks(text: str):
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": f"""
Perform three tasks on the following article, output JSON:
  • Create an engaging title (within 20 characters)
  • Write a 100-character summary
  • Provide 3-5 tags
  • Article: {text}

    Output format: {{"title": "...", "summary": "...", "tags": [...]}} """ }], response_format={"type": "json_object"} # OpenAI supports forced JSON output ) return json.loads(response.choices[0].message.content)

    Applicable Scenarios: Content processing pipelines (title + summary + tags), multi-dimensional analysis (sentiment + topic + keywords). Note: merging tasks increases the token consumption of a single call, but the total cost is usually lower because it reduces the number of calls.


    Category 3: Caching Strategies (Reduce 30-60%)

    Method 6: Semantic Caching — Similar Requests Hit Cache and Return Directly

    For high-repetition scenarios like FAQ or product documentation queries, semantic caching can significantly reduce API calls. The core is calculating the cosine similarity of query vectors. In production environments, it's recommended to use Redis for cache storage and set TTL to avoid cache bloat.

    python
    from sentence_transformers import SentenceTransformer
    import numpy as np
    import hashlib

    Load lightweight embedding model

    embedder = SentenceTransformer("all-MiniLM-L6-v2") cache = {} # Use Redis in production

    def semantic_cache_call(query: str, threshold: float = 0.92): query_emb = embedder.encode(query, normalize_embeddings=True) # Traverse cache, find the highest similarity best_match = None best_score = 0 for key, (cached_emb, response) in cache.items(): score = np.dot(query_emb, cached_emb) if score > best_score: best_score = score best_match = (key, response) if best_score >= threshold: print(f"Cache hit: {best_score:.3f}") return best_match[1] # Cache miss, call API response = call_llm(query) cache[query] = (query_emb, response) return response

    Hit Rate and Risks:

  • Threshold above 0.95: hit rate about 10-20%, but almost no risk of false matches
  • Threshold 0.85-0.92: hit rate 30-50%, but may return irrelevant results
  • Risk: Queries that are semantically similar but have different intents may cause false hits. It's recommended to use a high threshold for sensitive scenarios (e.g., customer service responses)
  • Method 7: Claude Prompt Caching

    Anthropic's Prompt Caching allows marking system prompts or long contexts as cacheable. When the same content is called a second time, only 10% of the cost is charged. Note: the cache validity period is about 5 minutes, suitable for high-concurrency scenarios.

    python
    from anthropic import Anthropic

    anthropic_client = Anthropic()

    def cached_system_call(user_message: str): long_system = "You are a professional customer support assistant..." # Assume 2000 tokens response = anthropic_client.messages.create( model="claude-3-5-sonnet-20241022", system=[{ "type": "text", "text": long_system, "cache_control": {"type": "ephemeral"} # Mark as cacheable }], messages=[{"role": "user", "content": user_message}] ) return response.content[0].text

    Applicable Scenarios: Fixed system prompt + frequently changing user input. For RAG scenarios, you can also mark retrieved document chunks as cacheable to further reduce costs.


    Category 4: Batch Processing (Save 50%)

    Method 8: OpenAI Batch API

    For non-real-time tasks (e.g., batch data annotation, offline analysis), the Batch API offers a 50% discount and returns results within 24 hours. Note: the input file format for the Batch API must be JSONL, and each request's custom_id must be unique.

    python
    import json
    from openai import OpenAI

    client = OpenAI()

    def submit_batch(tasks: list): """Submit batch tasks""" requests = [] for i, task in enumerate(tasks): requests.append({ "custom_id": f"task-{i}", "method": "POST", "url": "/v1/chat/completions", "body": { "model": "gpt-4o-mini", "messages": [{"role": "user", "content": task}], "max_tokens": 500 } }) # Write to JSONL file with open("batch_tasks.jsonl", "w") as f: for req in requests: f.write(json.dumps(req) + "\n") # Upload file and create batch batch_file = client.files.create( file=open("batch_tasks.jsonl", "rb"), purpose="batch" ) batch = client.batches.create( input_file_id=batch_file.id, endpoint="/v1/chat/completions", completion_window="24h" # Return within 24 hours ) return batch.id

    def poll_batch(batch_id: str): """Poll batch results""" import time while True: batch = client.batches.retrieve(batch_id) if batch.status == "completed": # Download result file result = client.files.content(batch.output_file_id) return result.text elif batch.status == "failed": raise Exception(f"Batch failed: {batch.errors}") time.sleep(30)

    Applicable Scenarios: Data cleaning, batch translation, offline classification, log analysis. Not suitable for real-time conversations or user interactions. It's recommended to combine the Batch API with scheduled tasks, for example, processing offline tasks accumulated from the previous day every morning.


    Category 5: Monitoring and Governance

    Method 9: Per-Token Billing Principle — Understanding the Billing Model

    LLM APIs are billed per token, not per character. The token cost of Chinese vs. English depends on the tokenizer generation:

  • English: 1 token ≈ 4 characters (e.g., "hello" is 1 token)
  • Chinese: older tokenizers (cl100k, used by GPT-4/3.5) cost roughly 1+ token per Chinese character; newer ones (o200k, used by GPT-4o) are optimized for Chinese at roughly 1 token per 2 characters
  • Special characters: spaces and punctuation also count as tokens
  • Practical Tool: OpenAI provides the tiktoken library for precise token counting:

    python
    import tiktoken

    def count_tokens(text: str, model: str = "gpt-4o") -> int: encoding = tiktoken.encoding_for_model(model) return len(encoding.encode(text))

    Chinese text token count (measured: 6 tokens with o200k)

    print(count_tokens("你好世界,这是一个测试。"))

    English text token count (measured: 8 tokens)

    print(count_tokens("Hello world, this is a test."))

    Cost Impact: For equivalent meaning, Chinese typically consumes more tokens than English (the gap is narrowing with newer tokenizers). Using Chinese-friendly models (like DeepSeek) can further offset the difference. It's recommended to integrate token counting during the development phase to avoid discovering cost overruns after deployment.

    Method 10: Context Trimming — RAG Chunk Deduplication and Conversation History Truncation

    In RAG scenarios, retrieved document chunks may contain duplicate content. Overly long conversation history also wastes tokens. In practice, you can combine deduplication strategies from RAG best practices.

    python
    def deduplicate_chunks(chunks: list[str], threshold: float = 0.8) -> list[str]:
        """Deduplicate based on Jaccard similarity"""
        from sklearn.feature_extraction.text import TfidfVectorizer
        from sklearn.metrics.pairwise import cosine_similarity
        
        if len(chunks) <= 1:
            return chunks
        
        vectorizer = TfidfVectorizer()
        tfidf_matrix = vectorizer.fit_transform(chunks)
        similarity = cosine_similarity(tfidf_matrix)
        
        keep = [True] * len(chunks)
        for i in range(len(chunks)):
            for j in range(i+1, len(chunks)):
                if similarity[i][j] > threshold:
                    keep[j] = False  # Keep the first, discard subsequent duplicates
        
        return [chunk for i, chunk in enumerate(chunks) if keep[i]]

    def truncate_conversation(history: list, max_tokens: int = 2000): """Truncate conversation history, keep the most recent messages""" from tiktoken import encoding_for_model enc = encoding_for_model("gpt-4o") total_tokens = 0 truncated = [] for msg in reversed(history): # Start from the latest message tokens = len(enc.encode(msg["content"])) if total_tokens + tokens > max_tokens: break truncated.insert(0, msg) total_tokens += tokens return truncated

    Method 11: Output Length Control — max_tokens and Stop Words

    Besides max_tokens, stop words can more precisely control where the output ends. Note: stop words do not consume tokens but may truncate the output early.

    python
    def controlled_generate(prompt: str, max_length: int = 200):
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=max_length,
            stop=["\n\n", ".", "!", "?"]  # Stop when encountering these characters
        )
        return response.choices[0].message.content
    

    Note: Stop words do not consume tokens but may truncate the output early. For scenarios requiring complete sentences, use a period as a stop word. For code generation scenarios, use a newline as a stop word.

    Method 12: Monitoring Attribution — Track Consumption by Feature/User Dimension

    Without monitoring, optimization is impossible. Establish cost tracking by feature module and user dimension. It's recommended to add tracking logic in the API call wrapper layer, or use third-party tools like Helicone or LangSmith for automatic capture.

    python
    import sqlite3
    from datetime import datetime

    class CostTracker: def __init__(self, db_path: str = "costs.db"): self.conn = sqlite3.connect(db_path) self.conn.execute(""" CREATE TABLE IF NOT EXISTS api_costs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT, feature TEXT, model TEXT, prompt_tokens INTEGER, completion_tokens INTEGER, cost REAL, timestamp TEXT ) """) def track(self, user_id: str, feature: str, model: str, prompt_tokens: int, completion_tokens: int): # Prices based on official pricing; example values used here price_per_1k = { "gpt-4o": 0.005, "gpt-4o-mini": 0.00015, "deepseek-chat": 0.00014 } price = price_per_1k.get(model, 0.001) cost = (prompt_tokens + completion_tokens) / 1000 * price self.conn.execute(""" INSERT INTO api_costs (user_id, feature, model, prompt_tokens, completion_tokens, cost, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?) """, (user_id, feature, model, prompt_tokens, completion_tokens, cost, datetime.now().isoformat())) self.conn.commit() def get_feature_costs(self, days: int = 30): """Track costs by feature module""" cursor = self.conn.execute(""" SELECT feature, SUM(cost) as total_cost, COUNT(*) as call_count FROM api_costs WHERE timestamp >= datetime('now', ?) GROUP BY feature ORDER BY total_cost DESC """, (f"-{days} days",)) return cursor.fetchall() def get_user_costs(self, days: int = 30): """Track costs by user""" cursor = self.conn.execute(""" SELECT user_id, SUM(cost) as total_cost, COUNT(*) as call_count FROM api_costs WHERE timestamp >= datetime('now', ?) GROUP BY user_id ORDER BY total_cost DESC """, (f"-{days} days",)) return cursor.fetchall()

    Usage example

    tracker = CostTracker() tracker.track(user_id="user_123", feature="chatbot", model="gpt-4o-mini", prompt_tokens=500, completion_tokens=200) print(tracker.get_feature_costs())

    Monthly Audit Checklist:

  • Which System Prompts can be streamlined?
  • Which tasks can be downgraded to cheaper models?
  • Which high-frequency queries are suitable for caching?
  • Which users/features have abnormally high consumption?

  • Effect Summary

    MethodDifficultyExpected Reduction

    Model RoutingLow40-60% DeepSeek Alternative (Chinese)Low80-94% System Prompt StreamliningLow10-30% Semantic CachingMedium30-60% Batch APIMedium50% Prompt CachingMedium20-50%


    Final Thoughts

    LLM cost optimization is not a one-time project but ongoing governance: start with monitoring and attribution so you can see where the money goes; then pick the two or three highest-ROI levers among model routing, prompt trimming, caching, and batch processing; finally, make monthly audits a habit. Most teams see a visibly smaller bill after the first round—but more importantly, they build the "usage is visible" muscle, so every new feature ships with its cost under control.

    FAQ

    Q: Can semantic caching return incorrect answers? A: Yes. If the threshold is set too low (e.g., 0.85), queries that are semantically similar but have different intents may hit the cache. It's recommended to use a threshold above 0.95 for sensitive scenarios like customer service or healthcare, and regularly clean expired cache.

    Q: How does DeepSeek perform on English tasks? A: DeepSeek supports English tasks, but model rankings vary widely by task type, so we won't make a blanket claim. Build a small eval set from your real prompts and measure quality and unit price yourself before deciding on a routing strategy.

    Q: Is the 24-hour window for the Batch API a hard limit? A: Yes. OpenAI guarantees a return within 24 hours, but in practice, it may be faster (a few hours). If the task requires real-time response, the Batch API is not suitable.

    Q: How to quickly estimate token count? A: Use the tiktoken library for precise calculation. Rough estimate: English 1 token ≈ 4 characters; for Chinese it depends on the tokenizer—newer ones (o200k) cost about 1 token per 2 characters, older ones (cl100k) about 1+ token per character. OpenAI's Playground also displays token count.

    Q: Does monitoring attribution require additional development? A: Yes. It's recommended to add tracking logic in the API call wrapper layer, or use third-party tools like Helicone or LangSmith for automatic capture.

    *Last updated: July 2026. Always verify against each tool's official docs.*

    Also available in 中文.