调过100万次API之后,我整理了这份避坑指南

前言

先交代下背景。过去一年,我带着团队做了个基于大模型的RAG项目,日均API调用量2万+,累计调用超过100万次。从最开始的天天被坑,到现在基本稳定,中间踩过的坑没有八十也有一百。

这些坑里,有些是文档里写了但你不会认真看的,有些是文档里压根不会告诉你的。我把最常遇到的几个拎出来,希望能帮你省点时间,也省点钱。

误区一:System Prompt当摆设,随便写写就完事

这是我见过最多的操作。

很多人拿到模型,第一件事就是往User Prompt里塞任务描述,System Prompt要么留空,要么写一句“You are a helpful assistant”。

实际效果:同样一段任务描述,放在System里和放在User里,出来的结果能差一个档次。System Prompt在模型内部的优先级和权重计算方式跟User不同,它更接近“底层指令”而非“当前问题”。

正确姿势

text

System:
你是一个专业的代码审查助手。你的输出必须遵循以下规则:
1. 只输出问题列表,不要输出任何多余的问候语
2. 每个问题标注严重程度:P0/P1/P2
3. 如果代码没有明显问题,输出"无问题",不要强行找茬
4. 不要重复用户的代码原文

User:
请审查以下Python代码:
def get_user(id):
    return db.query(f"SELECT * FROM users WHERE id={id}")

你看,System管“怎么说话”,User管“当前任务”。各司其职。

误区二:Temperature 调来调去,其实没搞懂它干嘛的

Temperature这玩意儿,文档里写“控制随机性”,但很多人的理解就停在这了。

然后你就会看到这样的操作:要严谨输出就把Temperature调到0,要创意输出就调到0.9,中间档就0.5。看似合理,实际踩坑。

真实机制:Temperature影响的是概率分布的“锐度”。温度越低,高概率token被选中的概率越大,输出越确定;温度越高,低概率token也有机会出头,输出越多样。

关键点:Temperature=0不代表一定没有随机性。GPU浮点运算误差、不同batch的padding差异,都可能导致同样输入产生不同输出。如果你真的需要确定性输出,配合seed参数一起用,或者设置top_p=1。

经验值

  • 代码生成/信息提取:0-0.3

  • 常规问答/文本摘要:0.4-0.7

  • 创意写作/头脑风暴:0.8-1.2

  • 超过1.5基本就开始胡言乱语了

误区三:上下文窗口大了,就拼命往里塞

现在的模型动不动就100K、200K的上下文,很多人就觉得:“反正能装下,我把整本手册都贴进去”。

然后你就遇到了:响应变慢、成本暴涨、而且输出质量反而下降了。

为什么:上下文越长,模型在中间位置的信息遗忘越严重。这不是猜的,论文(Lost in the Middle)已经证明了这点——模型对长文本开头和结尾的内容记忆最好,中间部分基本是“看过就忘”。

还有一种更隐蔽的坑:你以为你塞了10万token进去,但实际上模型的有效注意力是稀疏的。很多框架为了加速推理,会做KV Cache的优化,某些位置的token实际上被“降权”了。你觉得自己给了充分的上下文,模型实际看到的打了个折。

建议

  1. 能检索就不要硬塞,先做RAG召回TopK

  2. 如果必须塞长文本,把关键信息放在开头和结尾

  3. 每轮对话带上历史摘要,而不是原始对话记录

  4. 超过50K token的场景,仔细评估是否真的需要

误区四:错误处理只判断HTTP状态码

这是最糙的做法。

python

# 反面教材
response = requests.post(url, json=data)
if response.status_code == 200:
    return response.json()
else:
    print("出错了")

HTTP 200只代表请求通了,不代表模型正常返回了。模型返回的payload里,error字段才是真正需要检查的东西。

更隐蔽的是——有时候既没有HTTP错误,也没有显式的error字段,但返回内容里藏着"Internal Server Error"或者"I'm sorry, I cannot..."之类的字符串。这种半死不活的状态最坑人,你的下游逻辑一处理就直接炸了。

稍微靠谱点的做法

python

def call_llm(messages):
    response = requests.post(url, json={"messages": messages}, timeout=30)
    
    # 1. 先检查HTTP状态
    if response.status_code != 200:
        raise APIException(f"HTTP {response.status_code}: {response.text}")
    
    # 2. 再检查响应体是否合法JSON
    try:
        result = response.json()
    except json.JSONDecodeError:
        raise APIException(f"Invalid JSON response: {response.text[:200]}")
    
    # 3. 检查业务错误
    if "error" in result:
        raise APIException(f"API error: {result['error']}")
    
    # 4. 检查返回内容是否为空
    content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
    if not content or len(content.strip()) < 5:
        raise APIException(f"Empty or too short response: {content}")
    
    # 5. 敏感词过滤触发时,内容会被截断或替换,需要额外判断
    if "敏感词" in content or "***" in content:
        raise APIException("Response was filtered")
    
    return content

超时重试也别偷懒,指数退避写一下花不了几行代码。

误区五:JSON Mode 用了但没完全用

很多模型支持JSON Mode,保证输出是合法的JSON。但你开了JSON Mode之后,如果Prompt里不说清楚要输出什么结构,模型就会自己发挥。

今天给你返回 {"name": "xxx", "result": ...},明天心情好就变成 {"code": 0, "data": {...}}

你以为开了JSON Mode就稳了,实际上只保了语法合法,没保结构固定。

解法:在System Prompt里明确指定JSON Schema,并且在User Prompt里再强调一次。如果模型支持,把response_format参数带上。

text

System:
你的输出必须严格遵循以下JSON Schema:
{
  "type": "object",
  "properties": {
    "issues": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "line": {"type": "integer"},
          "message": {"type": "string"},
          "severity": {"enum": ["P0", "P1", "P2"]}
        }
      }
    }
  },
  "required": ["issues"]
}

客户端再加一道校验,不符合Schema就重试或报错,别直接往下传。

误区六:Prompt写一次就不管了

这是最普遍但最不被重视的问题。

很多人上线之后就不管Prompt了,除非出大问题。但模型会升级,业务数据会变,用户提问方式也会变。三个月前好用的Prompt,现在可能已经不是最优的了。

推荐做法:把Prompt当代码一样做版本管理,每次改动记录changelog,上线前做A/B测试。

误区七:把"调Prompt"当成万能药

遇到问题就调Prompt,调不好就换模型,换完继续调Prompt。

循环依赖:质量不行→调Prompt→效果上来了→上线跑了一段时间→又不行了→继续调……

其实很多问题根本不在Prompt层面:

  • 知识类问题靠RAG

  • 逻辑推理问题靠CoT + 自我一致性

  • 格式问题靠约束解码

  • 多步骤任务靠Agent拆解

  • 重复性劳动靠微调

先诊断问题出在哪个层面,再选工具。拿锤子看什么都是钉子,但你不能啥都当Prompt问题去锤。

避坑清单(直接抄作业)

误区 一句话解决方案
System Prompt随便写 System管指令,User管任务,分工明确
Temperature乱调 代码/提取类0-0.3,问答类0.4-0.7,创意类0.8-1.2
长上下文无脑塞 关键信息放首尾,用摘要代替原始记录
错误处理只判200 检查JSON解析、业务error、内容有效性、敏感词过滤
JSON Mode只开不管 约束Schema + 客户端二次校验
Prompt写完就不管 版本管理 + 持续迭代
啥都靠调Prompt 先定位问题层级,再选工具

最后说一句

这些坑看着都不大,但每一个我都真实踩过,而且不止一次。

大模型应用开发跟传统后端开发最大的区别是:不确定性是内生的。你不能用“输入X必然输出Y”的思维去做,得接受概率性存在,然后用工程手段把不确定性控制在可接受范围内。

说白了,就是别把模型当API,把它当一个不太靠谱但能力很强的实习生。你得给清晰的指令,得校验它的产出,得容忍它偶尔犯错,还得持续带教。

祝各位上线顺利,别在凌晨三点被报警吵醒。

— End —

如果这篇文章帮到了你,点赞收藏转发就是对作者最大的支持。有什么踩过的坑也欢迎评论区补充,大家一起避。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐