凌晨的批处理任务刚跑完,监控面板上的调用次数没有明显变化,API账单却比预估高了一截。

排查日志后,问题通常不在“单价看错了”这么简单:原本应该交给轻量模型的分类任务进入了高规格模型;失败请求被重复执行;长上下文没有裁剪;团队只设置了消费提醒,却没有真正启用硬消费上限。

2026年7月30日,OpenAI调整了GPT-5.6系列部分模型的价格,同时为组织和项目增加了可执行的硬消费上限。

OpenAI API更新日志:
https://developers.openai.com/api/docs/changelog

这意味着开发者可以获得更细的成本控制能力,但前提是应用本身真的记录了每次请求用了多少Token、为什么选择这个模型,以及429错误到底是“请求太快”还是“预算已经耗尽”。

一、先看清GPT-5.6的计费差异

截至2026年8月1日,标准处理模式、短上下文的文本Token价格如下,单位均为每100万Token:

OpenAI API价格页:
https://developers.openai.com/api/docs/pricing

模型 输入 缓存输入 缓存写入 输出
gpt-5.6-sol 5美元 0.5美元 6.25美元 30美元
gpt-5.6-terra 2美元 0.2美元 2.5美元 12美元
gpt-5.6-luna 0.2美元 0.02美元 0.25美元 1.2美元

这张表至少说明三件事。

第一,输出Token通常比输入Token贵。限制回复长度、避免模型重复解释,可能比单纯压缩用户问题更有效。

第二,模型之间的成本差异很大。Sol适合高复杂度任务,但把简单分类、标签提取和格式转换全部交给Sol,会直接放大成本。

第三,价格页还区分短上下文、长上下文、Batch、Flex和Fast mode。不能把一张价格表写死后长期不更新,更不能拿Standard价格估算Fast mode账单。

下面的示例只计算:

  • Standard标准处理;
  • 短上下文;
  • 文本输入和文本输出;
  • 普通输入与缓存输入。

它没有计算缓存写入、内置工具调用、区域处理附加费用和其他模态费用。

二、先做一次手工成本计算

假设一次批量任务累计使用:

输入Token:1,000,000
输出Token:200,000

暂时忽略缓存,三个模型的估算结果为:

Sol:
1 × 5 + 0.2 × 30 = 11美元

Terra:
1 × 2 + 0.2 × 12 = 4.4美元

Luna:
1 × 0.2 + 0.2 × 1.2 = 0.44美元

这并不代表应该把所有任务切到Luna。真正的成本优化目标不是“每次调用最便宜”,而是:

在满足质量要求的前提下,
让每类任务进入成本最低的合适模型。

如果低成本模型导致大量人工返工、重复请求或错误结果进入生产环境,表面上的Token成本下降并没有实际意义。

三、用Python计算单次请求成本

先安装或升级SDK:

python -m pip install -U openai

创建cost_control.py:

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal
from typing import Any


@dataclass(frozen=True)
class TokenPrice:
    input_per_million: Decimal
    cached_input_per_million: Decimal
    output_per_million: Decimal


# Standard处理、短上下文。
# 价格核实日期:2026-08-01。
STANDARD_SHORT_CONTEXT = {
    "gpt-5.6-sol": TokenPrice(
        Decimal("5"),
        Decimal("0.5"),
        Decimal("30"),
    ),
    "gpt-5.6-terra": TokenPrice(
        Decimal("2"),
        Decimal("0.2"),
        Decimal("12"),
    ),
    "gpt-5.6-luna": TokenPrice(
        Decimal("0.2"),
        Decimal("0.02"),
        Decimal("1.2"),
    ),
}


def estimate_text_cost_usd(
    model: str,
    usage: Any,
) -> Decimal:
    price = STANDARD_SHORT_CONTEXT[model]

    input_tokens = int(usage.input_tokens)
    output_tokens = int(usage.output_tokens)

    details = getattr(
        usage,
        "input_tokens_details",
        None,
    )

    cached_tokens = int(
        getattr(details, "cached_tokens", 0) or 0
    )

    uncached_tokens = max(
        input_tokens - cached_tokens,
        0,
    )

    cost = (
        Decimal(uncached_tokens)
        * price.input_per_million
        + Decimal(cached_tokens)
        * price.cached_input_per_million
        + Decimal(output_tokens)
        * price.output_per_million
    ) / Decimal(1_000_000)

    return cost.quantize(Decimal("0.000001"))

Responses API返回的usage对象中包含输入Token、输出Token和总Token等信息。

Responses API参考文档:
https://developers.openai.com/api/reference/python/resources/responses/methods/create/

把成本统计接入真实请求:

import json
import os
from datetime import datetime, timezone

from openai import OpenAI

from cost_control import estimate_text_cost_usd


client = OpenAI()


def ask_model(
    model: str,
    user_text: str,
) -> str:
    response = client.responses.create(
        model=model,
        input=[
            {
                "role": "user",
                "content": user_text,
            }
        ],
    )

    estimated_cost = estimate_text_cost_usd(
        model,
        response.usage,
    )

    log_record = {
        "time": datetime.now(
            timezone.utc
        ).isoformat(),
        "response_id": response.id,
        "model": model,
        "input_tokens": (
            response.usage.input_tokens
        ),
        "output_tokens": (
            response.usage.output_tokens
        ),
        "total_tokens": (
            response.usage.total_tokens
        ),
        "estimated_cost_usd": str(
            estimated_cost
        ),
    }

    print(
        json.dumps(
            log_record,
            ensure_ascii=False,
        )
    )

    return response.output_text


result = ask_model(
    model=os.getenv(
        "OPENAI_MODEL",
        "gpt-5.6-terra",
    ),
    user_text="把这条工单分成技术、退款或建议。",
)

print(result)

日志示例:

{
  "time": "2026-08-01T08:30:00+00:00",
  "response_id": "resp_xxx",
  "model": "gpt-5.6-terra",
  "input_tokens": 128,
  "output_tokens": 22,
  "total_tokens": 150,
  "estimated_cost_usd": "0.000520"
}

这里记录的是应用侧估算值,适合做实时趋势判断,不能替代官方账单。

生产环境还应该增加:

project_id
business_task
user_id或匿名业务标识
service_tier
request_duration_ms
retry_count
success
error_code

不要记录完整API Key、用户密码、验证码和未经脱敏的业务数据。

四、模型路由比“全量换模型”更实用

可以先把任务分成三档:

任务级别 示例 推荐起点
high 复杂代码审查、关键方案推理、长链工具调用 Sol
normal 客服回复、内容整理、常规代码解释 Terra
bulk 分类、标签、字段提取、简单改写 Luna

下面是一个简化路由器:

from dataclasses import dataclass


@dataclass(frozen=True)
class BudgetState:
    spent_usd: float
    soft_limit_usd: float


class SoftBudgetExceeded(RuntimeError):
    pass


def choose_model(
    task_level: str,
    budget: BudgetState,
) -> str:
    if budget.soft_limit_usd <= 0:
        raise ValueError(
            "soft_limit_usd必须大于0"
        )

    usage_ratio = (
        budget.spent_usd
        / budget.soft_limit_usd
    )

    # 达到应用侧软预算后,
    # 停止非必要任务,而不是无限降级。
    if usage_ratio >= 1:
        raise SoftBudgetExceeded(
            "应用软预算已耗尽"
        )

    # 剩余预算低于10%时,
    # 只允许批量轻任务继续执行。
    if usage_ratio >= 0.9:
        if task_level == "bulk":
            return "gpt-5.6-luna"

        raise SoftBudgetExceeded(
            "预算进入保护区,暂停非批量任务"
        )

    routes = {
        "high": "gpt-5.6-sol",
        "normal": "gpt-5.6-terra",
        "bulk": "gpt-5.6-luna",
    }

    try:
        return routes[task_level]
    except KeyError as exc:
        raise ValueError(
            f"未知任务级别:{task_level}"
        ) from exc

这段规则只是工程起点,正式路由应该由回归测试决定。

例如,先准备100条带标准答案的分类数据,分别交给Terra和Luna执行,然后比较:

字段完整率
分类准确率
JSON合法率
平均Token
平均延迟
单条成本
人工返工率

只有在质量指标达到要求后,才能把对应任务切到更便宜的模型。
请添加图片描述

五、建立三层预算防线

单靠程序里的成本估算并不够,建议建立三层控制。

第一层:应用软预算

应用每天或每小时统计已使用成本。接近预算时:

  • 停止低优先级批处理;
  • 降低非关键任务的并发量;
  • 缩短非必要输出;
  • 将合适的轻任务路由到Luna;
  • 发送内部告警。

软预算的优点是不会突然中断核心业务。

第二层:消费提醒

消费提醒达到阈值后只发送通知,不会停止API流量。

可以设置多级阈值,例如:

50%:观察趋势
75%:检查异常流量
90%:暂停非关键任务

提醒必须留出处理时间。如果只在99%时通知,团队很可能来不及定位问题。

第三层:组织或项目硬消费上限

OpenAI现在支持组织级和项目级硬消费上限:

  • 组织硬上限影响组织下的全部项目;
  • 项目硬上限只影响对应项目;
  • 达到组织硬上限会返回organization_spend_limit_exceeded;
  • 达到项目硬上限会返回project_spend_limit_exceeded。

在控制台设置项目硬上限的步骤为:

  1. 打开项目设置;
  2. 进入Limits;
  3. 找到Spend;
  4. 编辑Monthly spend limit;
  5. 开启Enforce a hard limit;
  6. 保存设置。

官方同时说明,硬上限的执行并非瞬间完成,状态传播期间可能继续产生少量费用,因此最终记录金额可能略高于配置值。

OpenAI消费上限说明:
https://developers.openai.com/api/docs/guides/spend-limits

硬上限应该被理解为最后一道保险,而不是日常路由工具。

如果业务每月预算为500美元,可以采用类似结构:

应用软预算:400美元
提醒阈值:250 / 350 / 400美元
项目硬上限:450美元
组织硬上限:根据全部项目统一规划

具体金额要根据业务流量和容错能力确定,不能机械套用。

六、429错误不能全部指数退避

429至少可能代表以下几类问题:

429原因 是否重试 处理方式
请求速率过高 可以有限重试 读取Retry-After或指数退避
credit_balance_exhausted 不重试 检查余额
organization_spend_limit_exceeded 不重试 检查组织硬上限
project_spend_limit_exceeded 不重试 检查项目硬上限
organization_usage_limit_exceeded 不重试 检查获批用量上限

官方明确指出,账单、消费上限或额度类错误不会因为重试而恢复。只有临时速率限制或部分服务器错误适合有限重试。

OpenAI错误码说明:
https://developers.openai.com/api/docs/guides/error-codes

创建错误判断函数:

NON_RETRYABLE_429_CODES = {
    "credit_balance_exhausted",
    "organization_spend_limit_exceeded",
    "project_spend_limit_exceeded",
    "organization_usage_limit_exceeded",
}


def should_retry(
    status_code: int,
    error_code: str | None,
) -> bool:
    if status_code == 429:
        return (
            error_code
            not in NON_RETRYABLE_429_CODES
        )

    return status_code in {
        500,
        502,
        503,
        504,
    }

对应测试:

import unittest


class RetryPolicyTest(unittest.TestCase):
    def test_rate_limit_can_retry(self):
        self.assertTrue(
            should_retry(
                429,
                "rate_limit_exceeded",
            )
        )

    def test_spend_limit_stops_retry(self):
        self.assertFalse(
            should_retry(
                429,
                "project_spend_limit_exceeded",
            )
        )

    def test_server_error_can_retry(self):
        self.assertTrue(
            should_retry(503, None)
        )


if __name__ == "__main__":
    unittest.main()

运行:

python -m unittest -v

预期输出:

test_rate_limit_can_retry ... ok
test_spend_limit_stops_retry ... ok
test_server_error_can_retry ... ok

遇到项目消费上限后再从Sol切换到Luna,通常不能解决问题,因为同一个项目硬上限影响的是该项目的API流量,而不是某一个模型。

正确做法是停止自动重试,向维护人员发送明确告警:

项目:customer-service-prod
错误:project_spend_limit_exceeded
动作:已停止自动重试
建议:检查当前用量、异常流量与项目消费上限

请添加图片描述

七、避免成本失控的几个工程细节

  1. 限制输出长度

对于分类和字段抽取任务,不需要让模型解释完整推理过程。应该明确要求只返回所需字段,并设置合理的输出上限。

  1. 对重复前缀使用缓存

长而稳定的系统指令、工具定义和公共上下文更适合缓存。日志中要分别记录缓存输入Token与普通输入Token,否则成本估算会偏高。

  1. 避免失败任务被队列无限重放

队列消费失败后,应设置最大重试次数并使用死信队列。账单类429必须直接停止,不应重新入队。

  1. 给每个项目使用独立预算

测试环境、内部工具和生产业务如果共用一个项目,很难判断成本来自哪里。项目拆分后,硬上限、API Key和使用记录也更容易管理。

  1. 价格配置不能散落在代码中

生产项目可以把价格表放入独立配置文件,并记录:

model
service_tier
context_type
input_price
cached_input_price
cache_write_price
output_price
effective_date

每次价格调整后发布新版本,而不是直接覆盖旧价格。这样历史账单仍能按当时价格回算。

八、API成本与会员订阅要分开理解

这里讨论的是开发者API调用成本,不等同于ChatGPT Plus、Claude Pro、Gemini Advanced等会员订阅。团队如果还需要长期使用这些AI工具,可以把gpt328作为第三方AI会员充值平台了解;使用前应看清套餐说明、账号要求、到账说明和售后规则。真正控制API成本,仍然要依靠Token统计、模型路由、预算告警和项目限额。

九、上线前检查清单

  • 已确认当前使用的是Standard、Batch、Flex还是Fast mode;
  • 已区分短上下文与长上下文价格;
  • 每次请求记录模型、Token、延迟和业务类型;
  • 成本计算已区分普通输入与缓存输入;
  • Sol、Terra、Luna根据任务质量测试进行路由;
  • 设置了应用侧软预算;
  • 设置了多级消费提醒;
  • 生产项目已启用合理的硬消费上限;
  • 429错误会读取具体error.code;
  • 速率限制采用有限重试;
  • 消费上限和额度错误不会自动重试;
  • 队列任务设置最大重试次数与死信队列;
  • 成本日志不包含API Key和未脱敏数据;
  • 价格配置带有核实日期和版本。

GPT-5.6价格调整确实给高并发、批处理和常规业务带来了更大的模型选择空间,但“单价下降”不等于“账单自动下降”。

稳定的成本控制应该形成一条完整链路:

请求分类
→ 选择合适模型
→ 记录Token
→ 计算请求成本
→ 累计软预算
→ 触发多级提醒
→ 硬上限兜底
→ 按错误码决定是否重试

当这条链路真正进入项目后,团队才能解释每一笔成本来自哪里,也能在流量异常、任务配置错误或重试风暴出现时及时止损。

更多推荐