调 API 这件事,人类新手和 AI 新手犯的错一模一样,但人类新手被报错怼几次就长记性了。
AI 不长记性。它每次都像第一次见到这个接口一样,热情地、自信地、凭"印象"把参数填进去。
你让它对接一个第三方接口,它三秒钟给你写出调用代码,格式漂亮、注释齐全、结构 90 分。你一跑,报错。接下来你能看到这个系列里最经典的一组画面:
user_id 是整数,它传了字符串 "10086",接口回 422
order_no,它凭记忆写成 orderNum,接口回 字段缺失
69.9,接口当成 69.9 分,用户实付 7 毛钱"2026/9/20 下午3点",接口直接解析失败"PAID"/"UNPAID",它传了 "已支付"
每个错单独看都蠢得离谱。但真正让你血压升高的不是这些错,是它面对报错时的反应——它不读错误信息,先猜。
接口说:{"code":422,"detail":"user_id must be a valid integer"},这已经把病因喂到嘴边了。AI 的反应是:
"这个报错可能是参数名拼写有误,建议检查 user_id 是否应为 userId 或 uid。我来尝试修改为驼峰命名……"
它把一个类型错误,诊断成了命名错误,然后带着你的代码朝一个完全错误的方向狂奔。 你不拦着,它能把同一个接口的字段名穷尽式地改一遍——userId、uid、userid、userID——每改一次跑一次,每跑一次错一次,半小时过去了,正确答案(把引号去掉)从头到尾没出现在它的候选里。
像什么呢?AI 调 API 像一个从不看说明书、全靠手感组装家具的人:螺丝孔对不上,他不反思是不是拿错了零件,先怀疑是不是厂家孔打歪了,然后拿着锤子硬砸。 你站在旁边,说明书就摊在桌上第 3 页写着"此孔需用 B 号螺丝",他不看。
我给一个客户做 ERP 对接的小工具:客户那边有套老系统,开放了一套 HTTP 接口,要把我们这边的订单数据每天同步过去。接口文档是一份有点年头的 Swagger(OpenAPI),字段不算多,但类型和命名风格非常不统一——有的字段下划线,有的驼峰,金额有的按元有的按分,这是后面所有坑的温床。
我把文档链接和接口说明给 AI,让它先写一个"创建订单"的调用。
AI 秒回,代码很漂亮:
import requests
def create_order(order):
payload = {
"orderNo": order["order_no"],
"userId": str(order["user_id"]),
"amount": order["amount"], # 69.9
"status": "已支付",
"createTime": order["created_at"], # "2026/9/20 15:00"
"remark": order.get("remark", ""),
}
resp = requests.post(
"https://erp.client.com/api/order/create",
json=payload,
headers={"Authorization": "Bearer xxx"},
)
return resp.json()
我一跑,422 Unprocessable Entity:
{"code":422,"message":"validation failed",
"errors":[
{"field":"order_no","error":"required field missing"},
{"field":"user_id","error":"must be of type integer"},
{"field":"amount","error":"must be integer (unit: cents)"}
]}
我把这个报错原样贴回给它。注意,报错信息已经精确到字段和原因了。
AI 的回复:
"接口返回字段校验失败,orderNo 未被识别。这通常是因为参数命名风格不匹配,第三方系统可能要求驼峰转下划线。我尝试将所有字段调整为下划线风格,并对金额做字符串处理……"
然后它交出第二版:字段名改成了下划线(这步歪打正着对了一半),但:
payload = {
"order_no": order["order_no"],
"user_id": str(order["user_id"]), # 还是字符串!它没动
"amount": str(int(order["amount"])), # 69.9 → "69",直接抹掉小数,还转成字符串
"status": "已支付", # 枚举还是中文
"create_time": "2026-09-20 15:00:00", # 换了格式但还是不带时区、不是ISO
"remark": order.get("remark", ""),
}
它只处理了它"猜"出来的命名问题,对报错里明写的类型问题(integer、单位分)视而不见。user_id 外面那个 str() 它从头到尾没意识到是自己加的、还得自己拆。amount 它的处理方式是 str(int(...))——把 69.9 元先砍成 69 再转字符串,类型、单位、精度三个错叠在一起。
我再跑,还是 422,这次错误少了一个(order_no 过了),但 user_id 和 amount 依然红。
我再次贴上报错。AI 的回复开始有了那种熟悉的"甩锅感":
"user_id 字段持续校验失败,可能该接口实际使用的字段名并非文档所示。建议尝试以下候选:uid、userid、memberId、user_id_str……我先逐个尝试。"
它开始把一个类型问题,当成字段名问题做暴力枚举。 这是这个病最危险的阶段——它已经不看文档、不看报错了,进入"碰运气"模式。如果我放任它试,它能把网络请求发几十次,把对方接口的错误日志刷满,而正确答案始终是那个最简单的操作:把 str() 去掉。
到这里我叫停了。三轮,一个接口,没调通,而且越改越偏。
我做了三件 AI 本该做、但它一件都没做的事:
1. 读报错,按图索骥。 报错说 user_id must be integer——那就是整数,删掉 str(),传 10086 而不是 "10086"。一个引号的事。
2. 读文档的单位说明。 文档在 amount 字段旁边有一行小字"单位:分"。69.9 元 = 6990 分,要做单位换算,并且是整数:
amount_cents = round(float(order["amount"]) * 100)
3. 把每个字段对着文档的类型/枚举逐个核一遍。 status 用英文枚举 "PAID",时间用 ISO8601 带时区:
from datetime import datetime, timezone, timedelta
# "2026-09-20T15:00:00+08:00"
create_time = datetime.fromisoformat(order["created_at"]).astimezone(
timezone(timedelta(hours=8))).isoformat()
改完一次通过。从头到尾,没有一个错误需要"猜"——文档和报错把所有答案都写好了,AI 只是不肯看。
这次之后我改了工作流。再也不让 AI"看一眼文档就直接写调用代码",而是强制它分两步,先固化接口契约:
from pydantic import BaseModel, Field, conint
from enum import Enum
class OrderStatus(str, Enum):
paid = "PAID"
unpaid = "UNPAID"
class CreateOrderRequest(BaseModel):
# 字段名、类型、单位、枚举,全部对着文档显式声明
order_no: str = Field(..., alias="order_no")
user_id: int # 整数,不是字符串
amount: conint(strict=True) # 严格整数,单位:分
status: OrderStatus # 只接受 PAID / UNPAID
create_time: str # ISO8601 带时区
remark: str = ""
class Config:
populate_by_name = True
def to_cents(yuan: float) -> int:
return round(yuan * 100)
然后调用前先过一遍模型校验:
payload = CreateOrderRequest(
order_no=order["order_no"],
user_id=int(order["user_id"]),
amount=to_cents(float(order["amount"])),
status=OrderStatus.paid,
create_time=to_iso8601(order["created_at"]),
remark=order.get("remark", ""),
)
resp = requests.post(url, json=payload.model_dump(by_alias=True))
这一层的意义是:类型错、单位错、枚举错,在请求发出之前就被本地模型拦下,根本到不了服务器。 以前是"写错→发出去→接口 422→AI 瞎猜→再写错"的死循环,现在是"写错→本地直接报错告诉你哪一格不对→当场改"。报错信息也从第三方接口那段含糊的英文,变成精确到字段的"user_id 应为整数"。
1. 它写代码靠的是"概率印象",不是"这份文档"
模型生成 userId 还是 user_id、要不要加引号,依据是什么?是它训练数据里"这种接口通常长什么样"的统计印象——大多数 REST 接口用驼峰、ID 经常是字符串,于是它顺着最常见的模式写。
但你对接的是一个具体的、特定的接口,它的约定恰恰可能是小众的、老旧的、反惯例的。 通用印象和具体契约一旦不一致,错的一定是印象。模型的本能是"调用最可能的样子",而正确的工程动作是"照这份文档的确切定义来"——前者是联想,后者是查表,它默认走前者,因为查表需要你把"表"真正喂给它并强制它用。
2. 报错信息它"看到了",但没有真正进入推理
这是最反直觉的一点。你把 must be of type integer 贴给它,字它都认识,但下一轮它的行为显示它根本没把这条约束用上。
原因在于:对模型来说,报错文本和需求描述是"并列的一段话",它没有"先解析错误→定位字段→只改病因"这个固化的调试程序。 它倾向于把报错当成一个模糊的信号,然后用自己最熟练的假设(命名问题、格式问题)去套。人类调试是"假设←证据"的闭环,AI 默认是"生成一个听起来合理的假设",证据权重经常被它的先验印象盖过。
3. "改名试错"对它来说成本最低
为什么它一上来就猜参数名?因为改个名字、重发一次,对它是最简单的动作:不用理解类型系统、不用读文档、改一个字符串就行。
模型会系统性地偏好"改动小、看起来在努力、不需要深度理解"的动作。 把 "10086" 改成 10086 只需要删两个字符,但这背后要求它理解"我之前加的 str() 是错的、integer 和 string 的区别在这里是致命的"——这个理解链条比改名字长得多,它倾向于绕开。
4. 它不会为"反复失败"感到不好意思
人类工程师连续三次调不通一个接口,会开始怀疑人生、回头重读文档。AI 没有这个反馈机制——它不会因为第五次还报错而下定决心"这次一定先看文档",每一轮对它都是新的、情绪稳定的一次瞎试。 没有你的强制介入,这个循环可以无限持续。
把工作流拆成不可跳过的两步,写进给它的指令:
"第一步,不要写调用代码。逐字段阅读这份接口文档,输出一张表:每个字段的名称、精确类型、是否必填、单位、取值范围/枚举、示例值、约束条件。我确认这张表无误后,第二步你再基于它写代码。"
先产出一份"字段契约表",相当于逼它从"凭印象"切换到"照表抄"。接口调试里 90% 的错,在认真读文档这一步就不会发生。 文档是 Swagger/OpenAPI 的话,直接让它从 schema 部分提取,那里类型定义最权威。
不要让 payload 是一个随手拼的 dict,用 Pydantic(Python)或同类校验模型,把每个字段的类型、单位、枚举显式声明,请求发出前先 .validate() / 构造模型:
payload_model = CreateOrderRequest(**raw_data) # 类型不对这里就炸
resp = requests.post(url, json=payload_model.model_dump())
好处有三:错误在本地提前暴露、报错精确到字段、文档约定变成了代码里不可绕过的强约束。字段名要对齐外部 alias 的,用 Field(alias=...) + by_alias=True,命名风格也一并锁死。
别只把报错丢给它说"改"。强制它按一个结构化流程走:
"处理这个报错前,先输出三部分再改代码:
这一步直接掐死它"跳过分析、上来改名"的本能。好的调试是最小修改、单点验证;坏的调试是广撒网、碰运气。
最危险的不是报错的错,是不报错的错。金额单位(元/分)、时区、字符编码这类问题接口往往照单全收,错了也不吭声。给它立专门规矩:
## 接口对接检查清单(每次必须逐项确认)
- [ ] 金额字段单位:元 or 分?是否做了换算?结果是否为整数?
- [ ] 时间字段:格式是否 ISO8601?是否带时区?
- [ ] ID/数量字段:类型是 integer 还是 string?有没有多余的 str()/int()?
- [ ] 枚举字段:取值是否严格等于文档列表(大小写、中英文)?
- [ ] 字段命名:下划线 or 驼峰?是否与文档逐字一致?
- [ ] 必填字段是否齐全?嵌套结构层级对不对?
让它交付前对照清单自报一遍。把"凭印象最容易猜错的维度"列成清单,就把它的概率弱点变成了确定性检查点。
如果文档带 curl 示例或可运行的样例请求,先让 AI*原样跑通官方示例*(hardcode 示例参数),确认链路、鉴权、字段都对,再一步步把示例值替换成真实变量:
"先用文档里的示例参数,一个字不改,把请求跑通。跑通后我们再逐个字段替换成真实数据,每替换一个验证一次。"
这样一旦报错,你立刻知道是"刚替换的那个字段"的问题,而不是面对一团未知。固定基准、单点变更,是定位一切参数问题的通用方法。
AI 调接口的默认模式是"凭训练印象猜参数"——字段名猜、类型猜、单位猜、枚举猜,猜完一跑 422,它还不读报错,先把类型错误诊断成命名错误,然后穷尽式改名、暴力碰运气,一个引号能解决的问题绕你半小时。治病的关键是把它从"联想模式"强制切到"查表模式":先逐字段读文档、提取契约表,再用数据模型把类型单位枚举焊死在代码里,报错时逼它先解析证据再做最小修改,并对金额/时区这类"不报错的静默错"专项设防。记住,对接接口时文档和报错已经把所有答案写好了——AI 的问题从来不是不够聪明,是太着急表现、不肯先低头看一眼桌上的说明书。