可以先做一次最小请求来把返回结构看清楚,但要接受它的边界:一次最小请求只能验证“成功时返回长什么样、失败时错误长什么样”这两件事,验证不了并发、限流、长文本、多图这类行为。接入 Inkling 这类接口前,最省事的顺序是:把已知的接入信息列清楚,用一张小图加一句明确提问发出去,把响应体原样打印,再用三次故意失败的请求补齐错误形态,最后才动手写封装。这样即使后面参数有调整,改动也只落在封装函数一处。
能用一次最小请求看清返回结构,但要限定目标:先确认凭证形式、请求方式、字段名和大小限制,再用「一张小图 + 一句明确提问」发出最小请求并原样打印响应体,随后用缺字段、超图、短超时三种错误输入补齐失败形态。凡是不确定的字段名一律标为待确认,不靠猜写进代码。注意最小请求只能反映单次成功与失败的返回,无法代表并发、限流或长内容表现,上线前仍需按实际环境另行确认。
先确认手上已有的接入信息有哪些
动手前先把信息分两栏:能直接看到的、必须先问清楚的。常见的坑是把字段名当成“大概是 image 吧”就写进去,等封装写完才发现叫法不对,整层解析都要推倒。建议在项目里先建一个 notes 文件,逐项填写,缺失的写“待确认”,不要自行假定。
- 凭证形式:是放在请求头(如
Authorization: Bearer <token>)、查询参数还是单独字段;凭证是否区分环境。待确认就写待确认。 - 请求方式:POST JSON 还是 multipart/form-data;图像走 URL 还是 base64 还是文件流。
- 字段名:图像字段叫什么、文本提问字段叫什么、是否有可选的模型或版本参数。以实际文档为准,占位符不要直接当正式值。
- 大小限制:单图体积上限、边长或分辨率上限、文本长度上限、请求体总大小上限。
- 返回约定:成功时业务数据在顶层还是嵌套一层,是否恒有
id或request_id这类请求标识。
这几项里,凭证形式和请求方式决定代码骨架怎么写,字段名和大小限制决定参数怎么传,返回约定决定解析怎么写。前两项不清楚就没法发最小请求,后两项可以先发出去看返回再补。
构造一次最小请求并把返回原样打印
最小请求的要点是成本低、信息量大:一张很小的图(比如自己画一张几十像素的纯色块),一句只有一个明确答案的提问,比如“图里是什么颜色”。字段名用占位符并加注释,真正跑之前替换成确认过的名字。第一版代码不要写解析,先完整打印状态码、响应头和响应体文本,看清楚了再写取值逻辑。
import json, time, uuid
import requests
# 以下字段名、路径、鉴权方式均为占位符,以实际文档为准
ENDPOINT = "https://<api-host>/<path>"
TOKEN = "<your-token>"
IMG_FIELD = "<image_field_name>" # 待确认
TEXT_FIELD = "<text_field_name>" # 待确认
req_id = str(uuid.uuid4())
with open("tiny.png", "rb") as f:
img_bytes = f.read()
resp = requests.post(
ENDPOINT,
headers={"Authorization": f"Bearer {TOKEN}",
"X-Request-Id": req_id}, # 头名待确认
files={IMG_FIELD: ("tiny.png", img_bytes, "image/png")},
data={TEXT_FIELD: "图里是什么颜色"},
timeout=(5, 20), # (连接, 读取) 秒
)
print("status:", resp.status_code)
print("headers:", dict(resp.headers))
print("body:", resp.text) # 先原样打印,暂不解析
拿到返回体后再逐字段解析:把 resp.text 存成文件对比更稳妥,能看清键的顺序、嵌套层级和空值写法。若返回是流式或多段内容,先确认是否需要用 resp.iter_lines() 读取,不要默认按 JSON 一次性解析。
用错误输入看错误返回长什么样
成功返回只告诉你怎么取值,失败返回才决定封装里要捕获什么。建议分别造三次,每次都把状态码、响应体原文、请求标识记到同一个表里(比如放到 error-shapes.md 或日志文件),后续写异常分支时直接对照。
- 缺必填字段:把图像字段整个删掉,或把文本字段置空,观察是返回 4xx 还是 200 带错误字段。
- 图像过大:本地生成一张明显超过限制的图(比如边长远超上限),不要用真实业务图片,避免无谓传输。
- 请求超时:把
timeout设成极小值(如0.001),看客户端抛的是什么异常,与“服务端返回超时错误”区分开——这两类在封装里要分开处理。
记录时统一用同一套字段:场景、状态码、错误提示文本、是否有错误码字段、耗时。缺必填字段与图像过大通常会给出不同的提示文本,能帮助判断是参数问题还是内容问题;客户端本地超时则根本没有状态码,只有异常类型。
按观察结果写封装与超时重试
把上面的观察收敛进一个函数,业务代码只调它。输入输出约定写死:输入是图像字节、提问文本、超时秒数、最大重试次数;输出是 (是否成功, 解析后的数据, 错误类型, 请求标识, 耗时毫秒)。超时与重试次数不要散落在调用处,放到模块级配置常量里,方便按环境调整。
CONNECT_TIMEOUT = 5
READ_TIMEOUT = 20
MAX_RETRIES = 2 # 仅对连接失败和可重试状态码生效
def call_inkling(image_bytes, prompt, req_id=None):
# 返回: (ok, data, err_kind, req_id, elapsed_ms)
...
日志必须记三项以上:请求标识(自己生成的 uuid 或服务端返回的 id)、耗时毫秒、状态(状态码加错误类型)。重试只对连接异常和明确的临时性状态码生效,参数错误这类 4xx 不重试,否则只是把同一个错误重复打几遍。日志里不要打印完整凭证,图像内容建议只记体积和哈希前缀。
用两条固定样本做回归
封装改完要有办法确认行为没变。准备两条固定样本:一条是一张小图配一句固定提问,另一条是一句文本提问配同一张小图(若接口支持纯文本,可换成明显的非法输入)。为每条样本记录预期输出摘要:成功时包含哪些键、关键字段的类型、是否有请求标识;失败时预期的状态码区间和错误类型。摘要写进仓库里的一个文本文件,与代码同版本管理。
每次改动封装后,重跑这两条样本,把新返回体的结构(键名、层级、类型)与摘要逐项比对,不看具体文本内容只看结构。若发现多了或少了字段,先确认是接口变化还是自己解析漏了,再决定改哪边。回归通过后再合并,这样后续参数调整只影响封装函数一处,业务代码不用跟着动。