大模型 API 调用避坑:重试、超时、日志与成本控制

作者:AI 爪客
适合读者:正在把大模型能力接入业务系统的后端工程师、AI 应用工程师、Agent 开发者
关键词:LLM API、重试、超时、日志、成本控制、限流、可观测性、Python


摘要

把大模型 API 接进业务系统,真正难的往往不是“调通一次”,而是“稳定、可控、可追踪地调用很多次”。

在本篇文章中,我们围绕一个典型的大模型 API 调用链路,系统梳理四类高频问题:

  1. 重试:网络抖动、服务端 5xx、限流时应该如何重试,哪些错误不应该重试;
  2. 超时:连接超时、读取超时、业务总超时如何分层设置;
  3. 日志:如何记录请求、响应、耗时、错误,同时避免泄露密钥和用户隐私;
  4. 成本控制:如何通过 token 预算、模型分级、缓存、熔断等方式降低不可控开销。

本文不会承诺“零失败”或“成本降低 90%”这类不严谨效果,而是提供一套可落地的工程化模板,帮助你把 LLM API 调用从 Demo 级别推进到生产可用级别。


一、问题背景

很多团队第一次接入大模型 API 时,代码可能长这样:

import requests

resp = requests.post(
    "https://api.example.com/v1/chat/completions",
    headers={"Authorization": "Bearer API_KEY_PREFIX_xxxx"},
    json={
        "model": "example-large-model",
        "messages": [{"role": "user", "content": "帮我总结这段文本"}],
    },
)
print(resp.json()["choices"][0]["message"]["content"])

这段代码适合快速验证 API 是否可用,但不适合直接进入生产环境。原因包括:

  • 没有超时:请求可能长时间卡住,占用线程或协程资源;
  • 没有重试:一次临时网络波动就可能导致用户请求失败;
  • 没有限流处理:遇到 429 后继续猛打,可能加剧失败;
  • 没有结构化日志:线上出问题时无法判断是网络、模型、参数还是业务输入导致;
  • 没有成本边界:长 prompt、大模型、批量任务叠加后,费用容易失控;
  • 密钥硬编码:代码泄露或日志打印时可能造成安全事故。

对于大模型应用来说,API 调用层应该被当成一个基础设施模块,而不是散落在业务代码里的几行 HTTP 请求。


二、最终效果预览

完成本文方案后,你会得到一个相对稳健的 LLM API 调用封装,具备以下能力:

  • 支持连接超时、读取超时、业务总超时;
  • 根据错误类型执行有限次数的指数退避重试;
  • 自动识别 429、5xx、网络异常等可重试场景;
  • 对 400、401、403 等明显不可重试错误快速失败;
  • 输出结构化日志,记录请求 ID、模型名、耗时、token 用量、状态码;
  • 对敏感字段进行脱敏,避免密钥、用户隐私进入日志;
  • 通过 token 预算、模型选择、缓存策略控制成本;
  • 提供提示词模板,方便在业务中统一治理输入输出。

示例调用方式如下:

client = LLMClient(
    base_url="https://api.example.com/v1/chat/completions",
    api_key=os.getenv("LLM_API_KEY"),
    default_model="example-mini-model",
)

result = client.chat(
    messages=[
        {"role": "system", "content": "你是严谨的技术文档助手。"},
        {"role": "user", "content": "请总结一下重试和超时的区别。"},
    ],
    max_tokens=800,
    temperature=0.3,
)

print(result.content)

日志示例:

{
  "event": "llm_api_call_finished",
  "request_id": "req_20250101_001",
  "model": "example-mini-model",
  "status_code": 200,
  "latency_ms": 1842,
  "prompt_tokens": 328,
  "completion_tokens": 219,
  "total_tokens": 547,
  "retry_count": 1,
  "success": true
}

三、技术方案总览

一套生产可用的 LLM API 调用层,建议拆成以下几个模块:

业务代码
  │
  ▼
LLM Service / Agent Orchestrator
  │
  ▼
LLM Client 封装层
  ├── 参数校验
  ├── 超时控制
  ├── 重试策略
  ├── 错误分类
  ├── 日志与追踪
  ├── 成本统计
  └── 敏感信息脱敏
  │
  ▼
大模型 API Provider

核心设计原则:

  1. 失败是常态,不是异常
    网络抖动、限流、服务端临时错误都会发生,调用层必须显式处理。

  2. 重试不是越多越好
    重试会增加延迟和费用,也可能放大下游压力,应限制次数并使用退避策略。

  3. 超时要分层
    HTTP 超时解决连接和读取问题,业务总超时解决整体链路占用问题。

  4. 日志要可观测但不泄密
    应记录排障所需信息,但不要把 API Key、完整用户隐私、原始长文本直接打进日志。

  5. 成本要前置约束
    不要等账单异常后再治理,应在请求前设置 token、模型、并发、缓存等边界。


四、环境准备

本文使用 Python 作为示例语言,HTTP 请求库使用 requests。生产环境也可以替换为 httpxaiohttp 或云厂商 SDK。

1. 安装依赖

pip install requests

如果你使用异步服务,可以考虑:

pip install httpx

本文为了聚焦工程策略,示例采用同步代码。

2. 环境变量配置

不要把实际密钥写进代码。推荐使用环境变量:

export LLM_API_KEY="your-api-key-here"

Windows PowerShell 示例:

$env:LLM_API_KEY="your-api-key-here"

注意:上面的 your-api-key-here 只是占位符,请不要在文章、截图、日志、Git 仓库中暴露实际密钥。

3. 示例目录结构

llm-api-demo/
  ├── llm_client.py
  ├── app.py
  └── README.md

五、核心代码示例

下面给出一个可改造的 LLM Client 示例。它不绑定某个具体厂商,但接口形式参考常见的 Chat Completions 风格。

1. 定义结果对象与异常类型

from dataclasses import dataclass
from typing import Any, Dict, List, Optional


@dataclass
class LLMResult:
    content: str
    model: str
    prompt_tokens: int = 0
    completion_tokens: int = 0
    total_tokens: int = 0
    raw: Optional[Dict[str, Any]] = None


class LLMError(Exception):
    """LLM 调用基础异常。"""


class LLMRetryableError(LLMError):
    """可重试异常,例如网络抖动、429、5xx。"""


class LLMNonRetryableError(LLMError):
    """不可重试异常,例如鉴权失败、参数错误。"""


class LLMBudgetExceededError(LLMError):
    """成本或 token 预算超限。"""

2. 日志脱敏工具

import re
from typing import Any


SENSITIVE_KEYS = {"authorization", "api_key", "apikey", "token", "password", "secret"}


def mask_secret(value: str, keep: int = 4) -> str:
    """保留少量尾字符,其他部分脱敏。"""
    if not value:
        return value
    if len(value) <= keep:
        return "*" * len(value)
    return "*" * (len(value) - keep) + value[-keep:]


def sanitize_for_log(data: Any) -> Any:
    """递归脱敏,避免敏感信息进入日志。"""
    if isinstance(data, dict):
        sanitized = {}
        for key, value in data.items():
            if str(key).lower() in SENSITIVE_KEYS:
                sanitized[key] = mask_secret(str(value))
            else:
                sanitized[key] = sanitize_for_log(value)
        return sanitized

    if isinstance(data, list):
        return [sanitize_for_log(item) for item in data]

    if isinstance(data, str):
        # 简单示例:对疑似 Bearer Token 做脱敏
        data = re.sub(r"Bearer\s+([A-Za-z0-9_\-\.]+)", "Bearer ****", data)
        return data

    return data

3. 成本预算与 token 粗估

不同模型的 token 计算方式略有差异。生产环境建议使用对应厂商或模型的 tokenizer。这里先给一个保守的粗估函数,用于请求前的预算保护。

def estimate_tokens_by_chars(text: str) -> int:
    """
    粗略估算 token 数。
    中文、英文、符号混合场景下只能作为预算前置判断,不能替代精确 tokenizer。
    """
    if not text:
        return 0
    return max(1, len text // 2)

上面代码里有一个常见笔误:len text 是非法语法。正确版本如下:

def estimate_tokens_by_chars(text: str) -> int:
    """
    粗略估算 token 数。
    中文、英文、符号混合场景下只能作为预算前置判断,不能替代精确 tokenizer。
    """
    if not text:
        return 0
    return max(1, len(text) // 2)


def estimate_messages_tokens(messages: List[Dict[str, str]]) -> int:
    total = 0
    for msg in messages:
        total += estimate_tokens_by_chars(msg.get("role", ""))
        total += estimate_tokens_by_chars(msg.get("content", ""))
        total += 4  # 每条消息的结构开销,示例值
    return total

提醒:这里故意保留并纠正了一个“真实开发中经常出现的小错误”,便于大家在复制代码时注意语法检查。

4. 错误分类策略

import requests


def is_retryable_status(status_code: int) -> bool:
    """判断 HTTP 状态码是否适合重试。"""
    if status_code == 429:
        return True
    if 500 <= status_code < 600:
        return True
    return False


def is_non_retryable_status(status_code: int) -> bool:
    """判断 HTTP 状态码是否不适合重试。"""
    return status_code in {400, 401, 403, 404, 422}


def parse_retry_after(headers: Dict[str, str]) -> Optional[float]:
    """解析 Retry-After,单位秒。"""
    value = headers.get("Retry-After") or headers.get("retry-after")
    if not value:
        return None
    try:
        return max(0.0, float(value))
    except ValueError:
        return None

错误处理建议:

类型 示例 是否重试 说明
网络连接失败 DNS 抖动、连接重置 可短暂重试
读取超时 服务端响应过慢 视情况 若请求可能已被服务端处理,要注意幂等性
429 限流 尊重 Retry-After
5xx 服务端临时异常 使用指数退避
400 参数错误 修正请求参数
401 密钥错误 检查 API Key
403 权限不足 检查模型权限或账号权限
404 接口或模型不存在 检查 URL、模型名
422 请求语义不合法 检查 messages、max_tokens 等

5. 带重试、超时、日志、预算的 Client

import json
import logging
import os
import random
import time
import uuid
from typing import Tuple

import requests

logger = logging.getLogger("llm_client")
logging.basicConfig(level=logging.INFO, format="%(message)s")


class LLMClient:
    def __init__(
        self,
        base_url: str,
        api_key: str,
        default_model: str,
        connect_timeout: float = 3.0,
        read_timeout: float = 30.0,
        max_retries: int = 3,
        max_prompt_tokens: int = 6000,
        max_completion_tokens: int = 1500,
    ):
        if not api_key:
            raise ValueError("api_key is required. Please set LLM_API_KEY in environment variables.")

        self.base_url = base_url
        self.api_key = api_key
        self.default_model = default_model
        self.timeout: Tuple[float, float] = (connect_timeout, read_timeout)
        self.max_retries = max_retries
        self.max_prompt_tokens = max_prompt_tokens
        self.max_completion_tokens = max_completion_tokens

    def _log(self, event: str, **kwargs):
        payload = {"event": event, **kwargs}
        logger.info(json.dumps(sanitize_for_log(payload), ensure_ascii=False))

    def _build_headers(self) -> Dict[str, str]:
        return {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }

    def _check_budget(self, messages: List[Dict[str, str]], max_tokens: int):
        estimated_prompt_tokens = estimate_messages_tokens(messages)

        if estimated_prompt_tokens > self.max_prompt_tokens:
            raise LLMBudgetExceededError(
                f"estimated prompt tokens exceed budget: "
                f"{estimated_prompt_tokens} > {self.max_prompt_tokens}"
            )

        if max_tokens > self.max_completion_tokens:
            raise LLMBudgetExceededError(
                f"max_tokens exceed completion budget: "
                f"{max_tokens} > {self.max_completion_tokens}"
            )

    def _sleep_before_retry(self, attempt: int, retry_after: Optional[float] = None):
        if retry_after is not None:
            sleep_seconds = min(retry_after, 30.0)
        else:
            # 指数退避 + 随机抖动,避免多个实例同时重试
            base = min(2 ** attempt, 16)
            jitter = random.uniform(0, 0.5)
            sleep_seconds = base + jitter

        time.sleep(sleep_seconds)

    def chat(
        self,
        messages: List[Dict[str, str]],
        model: Optional[str] = None,
        max_tokens: int = 800,
        temperature: float = 0.3,
    ) -> LLMResult:
        request_id = f"req_{uuid.uuid4().hex[:12]}"
        model = model or self.default_model

        self._check_budget(messages, max_tokens)

        payload = {
            "model": model,
            "messages": messages,
            "max_tokens": max_tokens,
            "temperature": temperature,
        }

        headers = self._build_headers()
        start_time = time.time()
        retry_count = 0

        self._log(
            "llm_api_call_started",
            request_id=request_id,
            model=model,
            estimated_prompt_tokens=estimate_messages_tokens(messages),
            max_tokens=max_tokens,
        )

        last_error = None

        for attempt in range(self.max_retries + 1):
            try:
                resp = requests.post(
                    self.base_url,
                    headers=headers,
                    json=payload,
                    timeout=self.timeout,
                )

                latency_ms = int((time.time() - start_time) * 1000)

                if resp.status_code == 200:
                    data = resp.json()
                    choice = data.get("choices", [{}])[0]
                    message = choice.get("message", {})
                    content = message.get("content", "")
                    usage = data.get("usage", {})

                    self._log(
                        "llm_api_call_finished",
                        request_id=request_id,
                        model=model,
                        status_code=resp.status_code,
                        latency_ms=latency_ms,
                        prompt_tokens=usage.get("prompt_tokens", 0),
                        completion_tokens=usage.get("completion_tokens", 0),
                        total_tokens=usage.get("total_tokens", 0),
                        retry_count=retry_count,
                        success=True,
                    )

                    return LLMResult(
                        content=content,
                        model=model,
                        prompt_tokens=usage.get("prompt_tokens", 0),
                        completion_tokens=usage.get("completion_tokens", 0),
                        total_tokens=usage.get("total_tokens", 0),
                        raw=data,
                    )

                if is_non_retryable_status(resp.status_code):
                    self._log(
                        "llm_api_call_failed_non_retryable",
                        request_id=request_id,
                        model=model,
                        status_code=resp.status_code,
                        latency_ms=latency_ms,
                        response_text=resp.text[:500],
                        success=False,
                    )
                    raise LLMNonRetryableError(
                        f"non-retryable status={resp.status_code}, body={resp.text[:300]}"
                    )

                if is_retryable_status(resp.status_code):
                    last_error = LLMRetryableError(
                        f"retryable status={resp.status_code}, body={resp.text[:300]}"
                    )

                    if attempt < self.max_retries:
                        retry_count += 1
                        retry_after = parse_retry_after(resp.headers)
                        self._log(
                            "llm_api_call_retrying",
                            request_id=request_id,
                            model=model,
                            status_code=resp.status_code,
                            attempt=attempt + 1,
                            retry_after=retry_after,
                        )
                        self._sleep_before_retry(attempt, retry_after)
                        continue

                    raise last_error

                raise LLMError(f"unexpected status={resp.status_code}, body={resp.text[:300]}")

            except (requests.ConnectionError, requests.Timeout) as exc:
                last_error = exc
                if attempt < self.max_retries:
                    retry_count += 1
                    self._log(
                        "llm_api_call_retrying_after_network_error",
                        request_id=request_id,
                        model=model,
                        attempt=attempt + 1,
                        error_type=type(exc).__name__,
                    )
                    self._sleep_before_retry(attempt)
                    continue
                raise LLMRetryableError(f"network error after retries: {exc}") from exc

        raise LLMError(f"llm call failed: {last_error}")

6. 使用示例

import os


if __name__ == "__main__":
    client = LLMClient(
        base_url="https://api.example.com/v1/chat/completions",
        api_key=os.getenv("LLM_API_KEY"),
        default_model="example-mini-model",
    )

    result = client.chat(
        messages=[
            {"role": "system", "content": "你是严谨、简洁的技术文档助手。"},
            {"role": "user", "content": "解释一下 LLM API 调用中重试和超时的区别。"},
        ],
        max_tokens=600,
        temperature=0.2,
    )

    print(result.content)

生产建议:如果你的业务运行在 FastAPI、Celery、Airflow、Agent 框架中,可以把 LLMClient 注册为单例或依赖注入对象,避免到处重复创建配置。


六、提示词模板

API 调用稳定性解决的是“能不能稳定拿到结果”,提示词模板解决的是“结果是否容易被业务消费”。二者需要配合。

1. 通用技术总结模板

你是一名严谨的技术文档助手,请基于用户输入生成结构化总结。

要求:
1. 不编造用户未提供的信息;
2. 如果信息不足,请明确说明“不确定”;
3. 输出使用 Markdown;
4. 先给结论,再给依据;
5. 控制在 {max_words} 字以内。

用户输入:
{user_content}

2. JSON 输出模板

你是一个只输出 JSON 的信息抽取助手。

请从用户输入中抽取以下字段:
- title: 标题,字符串
- summary: 摘要,字符串
- risks: 风险列表,字符串数组
- actions: 建议动作列表,字符串数组

输出要求:
1. 只输出合法 JSON,不要输出 Markdown;
2. 字段缺失时使用空字符串或空数组;
3. 不要编造不存在的事实。

用户输入:
{user_content}

3. 成本友好的分阶段提示词

当输入很长时,不建议直接把所有内容塞进最强模型。可以拆成两阶段:

阶段一:低成本模型做预处理

请从以下文本中提取与“{topic}”相关的信息。

要求:
1. 删除重复和无关内容;
2. 保留关键事实、数字、时间、实体;
3. 不做扩写;
4. 输出不超过 {max_words} 字。

文本:
{long_text}

阶段二:高能力模型做最终生成

你是一名资深技术作者,请基于整理后的材料撰写一段专业说明。

要求:
1. 逻辑清晰;
2. 不夸大效果;
3. 对不确定信息明确标注;
4. 使用 Markdown 小标题和列表。

整理后的材料:
{compressed_context}

4. 提示词版本管理建议

建议为每个提示词模板增加版本号:

prompt_name: technical_summary
prompt_version: v1.2.0

日志中记录 prompt_nameprompt_version,便于排查:

  • 某次结果异常是不是提示词变更导致;
  • A/B 测试中哪个版本效果更稳定;
  • 回滚时应该恢复到哪个模板版本。

七、常见错误与排查

1. 忘记设置超时

现象:

  • 请求偶发卡死;
  • Web 服务线程被占满;
  • 用户侧一直转圈,没有明确错误。

错误示例:

requests.post(url, headers=headers, json=payload)

建议修复:

requests.post(url, headers=headers, json=payload, timeout=(3, 30))

含义:

  • 3 秒连接超时;
  • 30 秒读取超时。

如果业务整体要求 20 秒内返回,还需要在上层增加业务总超时,而不是只依赖 HTTP read timeout。

2. 对所有错误无脑重试

现象:

  • 401 鉴权失败仍然重试;
  • 400 参数错误重复请求;
  • 错误没有恢复,反而增加费用和延迟。

建议:

  • 429、5xx、网络异常可以重试;
  • 400、401、403、404、422 通常不应重试;
  • 重试次数建议从 2~3 次开始,根据业务 SLA 调整。

3. 重试没有退避

错误示例:

for i in range(3):
    requests.post(url, json=payload)

这会导致失败后立刻连续请求,可能加剧限流。

建议:

使用指数退避:

sleep_seconds = min(2 ** attempt, 16) + random.uniform(0, 0.5)
time.sleep(sleep_seconds)

如果服务端返回 Retry-After,应优先尊重该值。

4. 日志中打印完整请求头

危险示例:

logger.info(headers)

可能把 Authorization: Bearer xxx 打进日志系统。

建议:

  • 请求头只记录必要字段;
  • Authorization、token、secret 必须脱敏;
  • 对用户输入做长度截断和隐私过滤。

5. 没有记录 token 用量

现象:

  • 账单上涨但不知道来自哪个业务;
  • 无法比较不同模型和提示词版本的成本;
  • 无法给团队或客户做成本归因。

建议日志字段:

{
  "business": "doc_summary",
  "model": "example-mini-model",
  "prompt_tokens": 1200,
  "completion_tokens": 350,
  "total_tokens": 1550,
  "request_id": "req_xxx"
}

6. 忽略流式输出的异常处理

如果使用 streaming,异常可能发生在响应开始之后。例如:

  • 前几个 chunk 正常返回;
  • 中途网络断开;
  • 用户只看到半截答案。

建议:

  • 对流式输出增加结束标记;
  • 前端识别“生成中断”状态;
  • 后端记录 partial response;
  • 必要时提供“重新生成”或“继续生成”能力。

7. 粗估 token 与真实 token 差异较大

粗估函数只适合做前置保护。如果业务对 token 成本敏感,应:

  • 使用模型对应 tokenizer;
  • 在日志中记录服务端返回的真实 usage;
  • 定期比较估算值与真实值偏差;
  • 为不同语言、不同模板设置不同安全系数。

八、优化方向

1. 引入缓存

对于重复输入或可复用结果,可以增加缓存:

  • 完全相同的 prompt 走精确缓存;
  • FAQ、分类、标签等任务适合缓存;
  • 高实时性、强个性化任务要谨慎缓存。

缓存 key 示例:

import hashlib
import json


def build_cache_key(model: str, messages: list, temperature: float) -> str:
    raw = json.dumps(
        {"model": model, "messages": messages, "temperature": temperature},
        ensure_ascii=False,
        sort_keys=True,
    )
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()

注意:如果提示词中包含用户隐私,缓存系统也要满足数据安全要求。

2. 模型分级路由

不是所有任务都需要调用最强模型。

可以按任务复杂度分层:

任务类型 推荐策略
文本分类、标签提取 小模型优先
简单摘要、格式转换 中小模型优先
长文推理、复杂代码分析 高能力模型
高风险决策 模型输出 + 人工审核

示例策略:

def choose_model(task_type: str, input_tokens: int) -> str:
    if task_type in {"classification", "tagging"}:
        return "example-mini-model"
    if input_tokens > 8000:
        return "example-long-context-model"
    return "example-standard-model"

3. 请求队列与并发控制

批量任务中,如果同时发起大量请求,很容易触发限流。

建议:

  • 使用队列削峰;
  • 为不同模型设置并发上限;
  • 429 后动态降低并发;
  • 对低优先级任务延迟执行。

4. 熔断与降级

当某个模型或供应商持续失败时,可以进入熔断状态:

  • 短时间内失败率超过阈值,暂停调用;
  • 切换到备用模型;
  • 返回可解释的降级结果;
  • 通知运维或业务负责人。

伪代码:

if recent_failure_rate(model) > 0.5 and recent_request_count(model) > 20:
    open_circuit(model, ttl_seconds=60)

5. 成本看板

建议把以下指标接入监控系统:

  • 每日请求量;
  • 每日 token 总量;
  • 不同业务线 token 占比;
  • 不同模型成本占比;
  • 平均响应时延;
  • P95 / P99 延迟;
  • 失败率;
  • 重试次数;
  • 429 次数;
  • 单用户或单租户成本。

这些指标能帮助你从“感觉贵”变成“知道哪里贵、为什么贵、怎么优化”。

6. 幂等性设计

对于会产生副作用的任务,例如:

  • 自动发消息;
  • 创建工单;
  • 写数据库;
  • 触发外部流程;

不要简单地在整个业务流程外层重试。应把 LLM 生成和副作用执行拆开,并增加幂等键:

request_id + business_id + action_type

确保同一个动作不会因为重试执行多次。


九、资产复制区

这一节把常用片段集中放在一起,方便复制到项目中改造。

1. 推荐日志字段

{
  "event": "llm_api_call_finished",
  "request_id": "req_xxx",
  "trace_id": "trace_xxx",
  "business": "doc_summary",
  "prompt_name": "technical_summary",
  "prompt_version": "v1.0.0",
  "model": "example-mini-model",
  "status_code": 200,
  "latency_ms": 1200,
  "prompt_tokens": 800,
  "completion_tokens": 300,
  "total_tokens": 1100,
  "retry_count": 1,
  "success": true,
  "error_type": null
}

2. 推荐重试配置

RETRY_CONFIG = {
    "max_retries": 3,
    "retryable_status_codes": [429, 500, 502, 503, 504],
    "connect_timeout": 3,
    "read_timeout": 30,
    "max_retry_sleep_seconds": 30,
}

3. 不应进入日志的字段

DO_NOT_LOG_FIELDS = [
    "Authorization",
    "api_key",
    "token",
    "password",
    "secret",
    "raw_user_private_text",
]

4. 成本控制检查清单

[ ] 是否设置 max_tokens?
[ ] 是否对输入长度做上限?
[ ] 是否记录 prompt_tokens / completion_tokens?
[ ] 是否区分不同业务线的 token 用量?
[ ] 是否为简单任务选择低成本模型?
[ ] 是否对重复请求使用缓存?
[ ] 是否设置用户级或租户级预算?
[ ] 是否对批量任务设置并发上限?
[ ] 是否有异常账单告警?

5. 排查问题时的最小信息包

request_id:
trace_id:
business:
model:
prompt_name:
prompt_version:
status_code:
latency_ms:
retry_count:
prompt_tokens:
completion_tokens:
error_type:
error_message_sanitized:

6. 安全提醒文案

请勿在代码、日志、截图、Issue、博客文章中暴露真实 API Key。
如果怀疑密钥泄露,请立即在服务商控制台禁用旧密钥并生成新密钥。

十、结尾互动

大模型 API 调用的工程化治理,本质上是在平衡四件事:

  • 稳定性:失败时能否恢复;
  • 响应速度:重试和超时是否拖慢用户体验;
  • 可观测性:线上问题能否快速定位;
  • 成本边界:规模上来后费用是否可控。

如果你正在把 LLM 能力接入自己的业务系统,建议先不要急着追求复杂 Agent 架构,而是先把 API 调用层打磨扎实。一个可靠的调用层,往往能减少大量线上排障成本。

欢迎在评论区交流:

  1. 你们线上最常见的 LLM API 调用问题是什么?
  2. 你更关心超时、重试、日志,还是成本控制?
  3. 是否需要我继续整理一篇《大模型 API 流式输出与前端打字机效果避坑》?

如果这篇文章对你有帮助,可以收藏备用,也欢迎分享给正在做 AI 应用落地的同事。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐