07-统计大模型Token用量调用时延与接口成本
如何统计大模型 Token 用量、调用时延与接口成本
系列:Python + FastAPI 大模型应用基础(第 7 篇)
目标:使用 Python 记录模型调用次数、Token 用量、端到端时延、首 Token 时延和估算成本,并明确哪些数据可以计算、哪些数据不能凭空猜测。
1. 为什么“接口能用”之后必须做统计
一个大模型接口上线后,至少要回答以下问题:
- 今天调用了多少次?
- 成功率和失败率是多少?
- 平均响应时间是多少?
- 用户等待首个 Token 需要多久?
- 输入和输出分别消耗多少 Token?
- 每个业务场景花费多少?
- 哪个模型更快,哪个模型更贵?
- 重试是否导致上游调用次数和费用放大?
如果没有数据,只能得到“感觉有点慢”“好像有点贵”这种无法验证的结论。
从第一性原理看,模型观测链路是:
业务请求进入
↓ 记录开始时间
调用外部模型
↓ 记录状态码、首 Token 时间、结束时间
解析模型响应
↓ 读取服务端返回的 Token 用量
结合当前价格配置
↓ 估算本次调用成本
写入结构化指标
2. 需要统计哪些指标
2.1 调用量指标
- 用户请求数;
- 逻辑模型调用数;
- 实际上游 HTTP 请求数;
- 重试次数;
- 成功数和失败数。
一个用户请求可能因为重试产生多次上游 HTTP 请求,所以这几个数字不能混为一谈。
2.2 时延指标
latency_ms:从开始调用到取得完整结果的总时延;ttft_ms:Time To First Token(首 Token 时延);- 连接耗时;
- 模型生成耗时;
- 数据库或其他工具调用耗时。
非流式接口通常只能直接测到总时延。流式接口可以额外记录首 Token 到达时间。
2.3 Token 指标
常见兼容式响应可能包含:
{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 80,
"total_tokens": 200
}
}
为了让企业内部字段更稳定,本文统一转换为:
input_tokens:输入 Token 数;output_tokens:输出 Token 数;total_tokens:总 Token 数。
不同服务商可能使用不同字段,也可能完全不返回用量。不能只根据接口路径推断字段,必须以实际响应和官方文档为准。
2.4 成本指标
最基础的估算公式:
输入成本 = 输入 Token 数 ÷ 1,000,000 × 每百万输入 Token 单价
输出成本 = 输出 Token 数 ÷ 1,000,000 × 每百万输出 Token 单价
总成本 = 输入成本 + 输出成本
但真实计费可能还区分:
- 缓存输入;
- 未缓存输入;
- 批处理;
- 推理 Token;
- 工具调用;
- 图片、音频或视频;
- 不同上下文长度;
- 不同地区和币种。
因此,本文价格全部通过环境变量配置,不写任何具体厂商价格。实际价格必须从当前官方定价页面获取并记录生效日期。
3. 为什么金额必须使用 Decimal
Python 的 float 使用二进制浮点数,某些十进制小数不能被精确表示:
# 结果可能不是人类直觉中的精确 0.3
print(0.1 + 0.2)
成本计算属于金额计算,应该使用 Decimal:
from decimal import Decimal
cost = Decimal("0.1") + Decimal("0.2")
print(cost) # 精确得到 Decimal('0.3')
价格字符串也要直接传给 Decimal,不要先转换成 float。
4. 创建项目
项目结构:
llm_observability/
├── app/
│ ├── __init__.py
│ ├── metrics.py
│ ├── llm_client.py
│ └── main.py
├── data/
└── requirements.txt
本文使用 Python 3.10 及以上版本。
requirements.txt:
fastapi>=0.115,<1
uvicorn[standard]>=0.30,<1
httpx>=0.27,<1
pydantic>=2.7,<3
安装依赖:
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
5. 定义统一用量、价格和指标结构
新建 app/metrics.py:
import asyncio
import json
import logging
from dataclasses import asdict, dataclass
from decimal import Decimal, InvalidOperation
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class TokenUsage:
"""企业内部统一的 Token 用量。"""
input_tokens: int
output_tokens: int
total_tokens: int
def __post_init__(self) -> None:
values = (
self.input_tokens,
self.output_tokens,
self.total_tokens,
)
if any(value < 0 for value in values):
raise ValueError("Token 数不能为负数")
@dataclass(frozen=True)
class ModelPricing:
"""某个模型在某个时间点使用的价格配置。"""
input_per_million: Decimal
output_per_million: Decimal
currency: str
effective_date: str
@classmethod
def from_strings(
cls,
input_per_million: str,
output_per_million: str,
currency: str,
effective_date: str,
) -> "ModelPricing":
"""从字符串创建价格,避免先经过 float 丢失精度。"""
try:
input_price = Decimal(input_per_million)
output_price = Decimal(output_per_million)
except InvalidOperation as exc:
raise ValueError("模型价格不是合法十进制数字") from exc
if input_price < 0 or output_price < 0:
raise ValueError("模型价格不能为负数")
if not currency.strip():
raise ValueError("currency 不能为空")
if not effective_date.strip():
raise ValueError("effective_date 不能为空")
return cls(
input_per_million=input_price,
output_per_million=output_price,
currency=currency.strip().upper(),
effective_date=effective_date.strip(),
)
class CostEstimator:
"""根据已确认的 Token 用量和价格估算成本。"""
TOKENS_PER_MILLION = Decimal("1000000")
def __init__(self, pricing: ModelPricing) -> None:
self.pricing = pricing
def estimate(self, usage: TokenUsage) -> Decimal:
"""返回未强制截断精度的估算成本。"""
input_cost = (
Decimal(usage.input_tokens)
/ self.TOKENS_PER_MILLION
* self.pricing.input_per_million
)
output_cost = (
Decimal(usage.output_tokens)
/ self.TOKENS_PER_MILLION
* self.pricing.output_per_million
)
return input_cost + output_cost
@dataclass(frozen=True)
class CallMetric:
"""一次逻辑模型调用的结构化指标。"""
request_id: str
occurred_at: str
business_scene: str
provider: str
model: str
status: str
status_code: int | None
error_code: str | None
latency_ms: float
ttft_ms: float | None
input_tokens: int | None
output_tokens: int | None
total_tokens: int | None
estimated_cost: str | None
currency: str | None
price_effective_date: str | None
class JsonlMetricRecorder:
"""将指标追加到 JSON Lines 文件,仅用于本地学习。"""
def __init__(self, file_path: Path) -> None:
self.file_path = file_path
self._lock = asyncio.Lock()
def _append_line(self, line: str) -> None:
"""同步文件写入函数,由 asyncio.to_thread 调用。"""
self.file_path.parent.mkdir(parents=True, exist_ok=True)
with self.file_path.open("a", encoding="utf-8") as file:
file.write(line)
file.write("\n")
async def record(self, metric: CallMetric) -> None:
"""把一个指标对象安全地追加为一行 JSON。"""
payload: dict[str, Any] = asdict(metric)
line = json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
)
# 锁只能保护当前 Python 进程内的并发写入
async with self._lock:
await asyncio.to_thread(self._append_line, line)
async def safe_record(self, metric: CallMetric) -> None:
"""指标写入失败时记录错误,但不覆盖原始业务结果。"""
try:
await self.record(metric)
except OSError:
# 观测系统失败不能静默,但也不应让成功的模型回答变失败
logger.exception("模型调用指标写入失败")
为什么价格需要记录生效日期
模型价格可能变化。如果只保存一个成本数字,却不知道使用的是哪一版价格,以后无法审计和重新计算。
至少应该保存:
- 模型完整标识;
- 价格币种;
- 价格生效日期或价格版本;
- 输入和输出单价;
- 原始 Token 用量。
6. 归一化不同字段的 Token 用量
不同兼容接口可能返回 prompt_tokens/completion_tokens,也可能返回 input_tokens/output_tokens。下面只处理这两组明确字段,不根据文本长度猜测。
把下面函数继续写入 app/metrics.py:
def _non_negative_int(value: object) -> int | None:
"""只接受真正的非负整数,拒绝字符串和布尔值。"""
# bool 是 int 的子类,因此必须单独排除
if isinstance(value, bool) or not isinstance(value, int):
return None
if value < 0:
return None
return value
def normalize_usage(response_data: dict[str, Any]) -> TokenUsage | None:
"""把已知字段映射为统一 TokenUsage;无法确认时返回 None。"""
raw_usage = response_data.get("usage")
if not isinstance(raw_usage, dict):
return None
input_tokens = _non_negative_int(raw_usage.get("prompt_tokens"))
if input_tokens is None:
input_tokens = _non_negative_int(raw_usage.get("input_tokens"))
output_tokens = _non_negative_int(
raw_usage.get("completion_tokens")
)
if output_tokens is None:
output_tokens = _non_negative_int(raw_usage.get("output_tokens"))
total_tokens = _non_negative_int(raw_usage.get("total_tokens"))
# 输入或输出缺失时,不能可靠计算基础输入/输出成本
if input_tokens is None or output_tokens is None:
return None
# 如果服务没有返回总量,只做确定性的加法,不猜测其他隐藏用量
if total_tokens is None:
total_tokens = input_tokens + output_tokens
return TokenUsage(
input_tokens=input_tokens,
output_tokens=output_tokens,
total_tokens=total_tokens,
)
如果服务商存在缓存 Token、推理 Token 或其他计费字段,应在 Adapter(适配器)中建立更完整的用量对象,而不是把它们强行塞进基础输入和输出字段。
7. 实现带指标记录的模型客户端
新建 app/llm_client.py:
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any
from uuid import uuid4
import httpx
from app.metrics import (
CallMetric,
CostEstimator,
JsonlMetricRecorder,
TokenUsage,
normalize_usage,
)
class LLMCallError(RuntimeError):
"""模型调用统一异常,包含稳定错误代码。"""
def __init__(self, message: str, error_code: str) -> None:
super().__init__(message)
self.error_code = error_code
@dataclass(frozen=True)
class ChatResult:
"""模型回答及本次请求 ID。"""
request_id: str
content: str
class InstrumentedLLMClient:
"""在模型调用边界统一记录时延、用量和成本。"""
def __init__(
self,
http_client: httpx.AsyncClient,
base_url: str,
api_key: str,
provider: str,
model: str,
cost_estimator: CostEstimator,
recorder: JsonlMetricRecorder,
) -> None:
self.http_client = http_client
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.provider = provider
self.model = model
self.cost_estimator = cost_estimator
self.recorder = recorder
async def chat(
self,
user_message: str,
business_scene: str,
) -> ChatResult:
"""调用模型,并在 finally 中记录成功或失败指标。"""
cleaned_message = user_message.strip()
if not cleaned_message:
raise ValueError("用户问题不能为空")
# request_id 由服务端生成,不能直接信任外部用户提交的值
request_id = str(uuid4())
started_at = datetime.now(timezone.utc)
started_counter = time.perf_counter()
status = "error"
status_code: int | None = None
error_code: str | None = None
usage: TokenUsage | None = None
estimated_cost: str | None = None
try:
response = await self.http_client.post(
f"{self.base_url}/chat/completions",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json={
"model": self.model,
"messages": [
{
"role": "system",
"content": "你是一名严谨的 Python 助手。",
},
{"role": "user", "content": cleaned_message},
],
"temperature": 0.2,
},
)
status_code = response.status_code
if not 200 <= status_code < 300:
error_code = f"UPSTREAM_HTTP_{status_code}"
raise LLMCallError(
f"模型服务返回异常状态码:{status_code}",
error_code=error_code,
)
try:
response_data: dict[str, Any] = response.json()
content = response_data["choices"][0]["message"][
"content"
]
except (ValueError, KeyError, IndexError, TypeError) as exc:
error_code = "INVALID_UPSTREAM_RESPONSE"
raise LLMCallError(
"模型响应结构不正确",
error_code=error_code,
) from exc
if not isinstance(content, str) or not content.strip():
error_code = "EMPTY_MODEL_CONTENT"
raise LLMCallError(
"模型返回了空内容",
error_code=error_code,
)
# 用量缺失不会让正确的模型回答失败,但成本会保持 None
usage = normalize_usage(response_data)
if usage is not None:
cost = self.cost_estimator.estimate(usage)
estimated_cost = str(cost)
status = "success"
return ChatResult(
request_id=request_id,
content=content.strip(),
)
except httpx.TimeoutException as exc:
error_code = "UPSTREAM_TIMEOUT"
raise LLMCallError(
"等待模型服务响应超时",
error_code=error_code,
) from exc
except httpx.HTTPError as exc:
error_code = "UPSTREAM_NETWORK_ERROR"
raise LLMCallError(
"模型服务发生网络异常",
error_code=error_code,
) from exc
except LLMCallError as exc:
# 保留此前设置的错误代码;防止未来分支遗漏赋值
error_code = error_code or exc.error_code
raise
finally:
latency_ms = round(
(time.perf_counter() - started_counter) * 1000,
2,
)
metric = CallMetric(
request_id=request_id,
occurred_at=started_at.isoformat(),
business_scene=business_scene,
provider=self.provider,
model=self.model,
status=status,
status_code=status_code,
error_code=error_code,
latency_ms=latency_ms,
# 当前方法是非流式调用,因此没有 TTFT
ttft_ms=None,
input_tokens=(usage.input_tokens if usage else None),
output_tokens=(usage.output_tokens if usage else None),
total_tokens=(usage.total_tokens if usage else None),
estimated_cost=estimated_cost,
currency=(
self.cost_estimator.pricing.currency
if estimated_cost is not None
else None
),
price_effective_date=(
self.cost_estimator.pricing.effective_date
if estimated_cost is not None
else None
),
)
# 指标写入失败不会覆盖模型调用的原始成功或失败结果
await self.recorder.safe_record(metric)
为什么不记录用户问题和模型全文
指标文件的目的不是保存完整会话。用户问题可能包含客户资料、简历、合同或内部数据。默认只记录必要元数据:
- 请求 ID;
- 模型和业务场景;
- 状态码和错误代码;
- 时延;
- Token 用量;
- 估算成本。
如果必须保存会话内容,应进入专门的数据表,执行用户权限、加密、脱敏和数据保留策略,而不是混入普通监控日志。
8. 使用 FastAPI 组装客户端
新建 app/main.py:
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
import httpx
from fastapi import FastAPI, Request
from pydantic import BaseModel, Field, field_validator
from app.llm_client import InstrumentedLLMClient
from app.metrics import (
CostEstimator,
JsonlMetricRecorder,
ModelPricing,
)
class ChatRequest(BaseModel):
"""聊天接口请求。"""
user_message: str = Field(min_length=1, max_length=4000)
@field_validator("user_message")
@classmethod
def message_must_not_be_blank(cls, value: str) -> str:
cleaned_value = value.strip()
if not cleaned_value:
raise ValueError("user_message 不能为空白字符串")
return cleaned_value
class ChatResponse(BaseModel):
"""对外只返回业务必要数据,不直接返回内部成本。"""
request_id: str
content: str
def required_env(name: str) -> str:
"""读取必需环境变量。"""
value = os.getenv(name, "").strip()
if not value:
raise RuntimeError(f"缺少环境变量:{name}")
return value
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""创建共享连接池和带指标的模型客户端。"""
pricing = ModelPricing.from_strings(
input_per_million=required_env("MODEL_INPUT_PRICE_PER_1M"),
output_per_million=required_env("MODEL_OUTPUT_PRICE_PER_1M"),
currency=required_env("MODEL_PRICE_CURRENCY"),
effective_date=required_env("MODEL_PRICE_EFFECTIVE_DATE"),
)
timeout = httpx.Timeout(
connect=5.0,
read=60.0,
write=10.0,
pool=5.0,
)
http_client = httpx.AsyncClient(timeout=timeout)
recorder = JsonlMetricRecorder(
file_path=Path("data/model_call_metrics.jsonl")
)
app.state.llm_client = InstrumentedLLMClient(
http_client=http_client,
base_url=required_env("LLM_BASE_URL"),
api_key=required_env("LLM_API_KEY"),
provider=required_env("LLM_PROVIDER"),
model=required_env("LLM_MODEL"),
cost_estimator=CostEstimator(pricing),
recorder=recorder,
)
yield
await http_client.aclose()
app = FastAPI(
title="大模型用量与成本统计",
version="1.0.0",
lifespan=lifespan,
)
@app.post("/api/v1/chat", response_model=ChatResponse)
async def chat(
chat_request: ChatRequest,
request: Request,
) -> ChatResponse:
"""调用模型;business_scene 由服务端确定,不能由用户随意伪造。"""
llm_client: InstrumentedLLMClient = request.app.state.llm_client
result = await llm_client.chat(
user_message=chat_request.user_message,
business_scene="python_tutorial",
)
return ChatResponse(
request_id=result.request_id,
content=result.content,
)
设置环境变量:
$env:LLM_BASE_URL = "https://替换为模型服务地址/v1"
$env:LLM_API_KEY = "替换为真实密钥"
$env:LLM_PROVIDER = "替换为服务商内部名称"
$env:LLM_MODEL = "替换为真实模型标识"
# 以下价格必须来自当前官方定价,示例不提供虚构数字
$env:MODEL_INPUT_PRICE_PER_1M = "填入每百万输入Token单价"
$env:MODEL_OUTPUT_PRICE_PER_1M = "填入每百万输出Token单价"
$env:MODEL_PRICE_CURRENCY = "填入币种代码,例如CNY或USD"
$env:MODEL_PRICE_EFFECTIVE_DATE = "填入价格生效日期,例如YYYY-MM-DD"
注意:上面的中文占位符不是合法价格,必须替换后才能启动服务。
9. 如何统计首 Token 时延
流式接口需要记录两个时间点:
开始调用时间 t0
首个非空文本到达时间 t1
流结束时间 t2
TTFT = t1 - t0
总时延 = t2 - t0
核心代码:
import time
from collections.abc import AsyncIterator
async def observe_stream(
upstream: AsyncIterator[str],
) -> AsyncIterator[str]:
"""包装上游文本流,并测量首 Token 和总时延。"""
started_counter = time.perf_counter()
first_token_ms: float | None = None
try:
async for text_piece in upstream:
if text_piece and first_token_ms is None:
first_token_ms = round(
(time.perf_counter() - started_counter) * 1000,
2,
)
yield text_piece
finally:
total_latency_ms = round(
(time.perf_counter() - started_counter) * 1000,
2,
)
# 真实项目应在这里写入 CallMetric
# 即使客户端中途断开,finally 也会执行清理和指标逻辑
print(
{
"ttft_ms": first_token_ms,
"latency_ms": total_latency_ms,
}
)
不能把第一个 SSE 事件到达时间直接当作首 Token 时间,因为第一个事件可能只包含角色、请求 ID 或其他元数据。应该以第一个非空可展示文本为准。
10. 编写可重复验证的成本单元测试
新建 test_metrics.py:
from decimal import Decimal
from app.metrics import (
CostEstimator,
ModelPricing,
TokenUsage,
normalize_usage,
)
def test_cost_estimator_uses_decimal() -> None:
"""验证成本公式使用 Decimal,并得到精确结果。"""
pricing = ModelPricing.from_strings(
input_per_million="2.00",
output_per_million="8.00",
currency="USD",
effective_date="2099-01-01",
)
usage = TokenUsage(
input_tokens=500_000,
output_tokens=250_000,
total_tokens=750_000,
)
cost = CostEstimator(pricing).estimate(usage)
# 输入成本 1,输出成本 2,总成本 3
assert cost == Decimal("3.000")
def test_normalize_compatible_usage() -> None:
"""验证常见兼容字段能够被归一化。"""
usage = normalize_usage(
{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 80,
"total_tokens": 200,
}
}
)
assert usage == TokenUsage(
input_tokens=120,
output_tokens=80,
total_tokens=200,
)
def test_missing_usage_returns_none() -> None:
"""用量缺失时不能通过字符数编造 Token。"""
assert normalize_usage({"choices": []}) is None
安装并运行测试:
.\.venv\Scripts\python.exe -m pip install "pytest>=8,<9"
.\.venv\Scripts\python.exe -m pytest -q
测试价格使用的是人为构造的数学数据,不是任何真实模型的价格。
11. JSONL 指标文件适合什么场景
JSON Lines 每行保存一个独立 JSON 对象,适合本地学习和排查:
{"request_id":"...","model":"...","status":"success","latency_ms":1234.5,"input_tokens":120,"output_tokens":80}
但本文实现只使用进程内锁,存在以下限制:
- 多个 FastAPI 进程不会共享同一把锁;
- 多台服务器无法统一聚合;
- 文件会不断增大;
- 查询和统计效率有限;
- 磁盘故障可能丢失指标。
生产环境通常将结构化日志发送到日志平台,或使用 Metrics(指标)系统和数据库集中统计。
12. 成本为什么只能叫“估算成本”
即使公式正确,本地结果仍可能和最终账单不同:
- 服务商按不同类别 Token 分开计费;
- 缓存命中价格不同;
- 价格在统计期间发生变化;
- 账单存在折扣、套餐或税费;
- 服务商用量字段与本地映射不完整;
- 重试产生了额外请求;
- 流式中断后本地没有收到最终 usage。
因此:
- 本地记录用于监控趋势和预算预警;
- 最终财务结算应以服务商正式账单为准;
- 应定期对账,检查本地统计与账单差异。
13. 对抗性审查:哪些做法容易产生错误结论
13.1 使用字符数代替 Token 数
字符和 Token 不是固定比例,不同语言和 Tokenizer(分词器)结果不同。字符数只能用于非常粗略的容量预警,不能直接作为账单依据。
13.2 把 total_tokens 全部按输入价计算
输入和输出价格可能不同,必须分别计算。如果响应只提供总量而没有输入、输出拆分,本文选择不估算基础成本。
13.3 硬编码网上找到的价格
价格可能已经过期,也可能对应不同模型版本、地区或计费类型。价格配置必须来自当前官方信息,并记录生效日期。
13.4 只统计成功请求
失败请求和重试也可能产生费用。指标必须记录失败状态、状态码、错误代码和实际上游尝试次数。
13.5 使用普通系统时间计算耗时
系统时间可能因为同步发生跳变。持续时间应该使用 time.perf_counter() 或 time.monotonic(),业务发生时间再使用 UTC 时钟。
13.6 监控失败被静默吞掉
本文不让指标写入故障覆盖模型结果,但会记录错误日志。生产系统还要监控“指标系统自身是否正常”,避免长期无数据却无人发现。
13.7 指标标签包含用户原文
不能把 Prompt、客户姓名或任意请求 ID 作为高基数 Metrics 标签,否则会产生隐私和性能问题。完整请求 ID适合进入日志,聚合指标标签应保持有限集合。
14. 下一步应该监控什么
完成基础统计后,可以按以下维度聚合:
- 模型;
- 服务商;
- 业务场景;
- 成功或失败;
- 错误类型;
- 用户或租户,但要避免暴露身份;
- 时间窗口。
常见统计:
- P50、P95、P99 时延;
- 首 Token P95;
- 每分钟请求量;
- 限流率和熔断率;
- 每次成功回答平均成本;
- 每个业务场景每日成本;
- 重试放大倍数。
平均值可能掩盖少量极慢请求,因此不能只看平均时延。
15. 本篇总结
本文完成了模型用量与成本统计的基础闭环:
- 使用
time.perf_counter()测量时延; - 使用首个非空文本计算 TTFT;
- 把外部 Token 字段归一化为内部
TokenUsage; - 使用
Decimal计算金额; - 价格通过配置提供并记录生效日期;
- 用量缺失时不根据字符数编造账单;
- 成功和失败调用都写入结构化指标;
- 指标默认不包含用户问题和模型全文;
- 本地成本只能作为估算,最终以正式账单为准。
下一篇将使用 FastAPI、SQLAlchemy 和 MySQL 持久化多轮对话,并重点处理用户隔离、短事务和模型调用失败后的数据状态。
16. 练习题
- 增加
upstream_attempts字段,统计重试放大倍数; - 为流式接口记录首 Token 时延;
- 分别统计成功和失败请求的 P95 时延;
- 增加缓存输入 Token 字段,但必须以服务商真实字段为依据;
- 设计价格版本表,而不是直接覆盖旧价格;
- 模拟指标文件不可写,确认模型调用的原始结果不被覆盖;
- 对比本地每日估算与服务商正式账单,并记录差异原因。
更多推荐
所有评论(0)