接入统一绘图 API 时,团队最容易撞上的问题不是模型效果,而是参数口径不一致。Stable Diffusion 3.5 的请求体与自家统一规范里的字段名、取值范围、默认规则往往对不上,导致 prompt 传过去了但采样参数空转,或者种子类型错误直接报 422。下面给出一张可复制的映射表模板和转换逻辑,让接入工作从“对着文档猜”变成“对着代码改”。
本文提供统一绘图 API 到 Stable Diffusion 3.5 的请求参数映射表模板、Python 转换骨架、校验规则与错误映射结构。适用于已有统一 API 规范、正在接入 SD3.5 的团队。操作动作是:先比对字段,再套用转换函数,最后验证边界。注意:SD3.5 部署方式不同,字段名可能有差异,需以实际网关或推理服务为准。
先画统一绘图 API 与 SD3.5 的字段差异
统一绘图 API 通常按业务语言定义参数,SD3.5 推理服务则按模型实现定义参数。直接透传不是不行,但会让调用方被迫了解 SD3.5 的内部细节。建议先列一张映射表,把字段分成三类:直接映射、名称转换、值域转换。下面是一张可参考的模板,字段名需要结合你的网关实际配置确认。
| 统一 API 字段 | SD3.5 常见字段 | 映射类型与处理建议 |
|---|---|---|
| prompt | prompt | 直接映射 |
| negative_prompt | negative_prompt | 直接映射 |
| width / height | width / height | 名称相同,值域不同,SD3.5 通常要求是 16 的倍数 |
| steps | num_inference_steps | 名称转换,需做范围校验 |
| seed | seed | 类型强转 int,SD3.5 不支持浮点种子 |
| cfg_scale | guidance_scale | 名称转换,建议保留 1-2 位小数 |
| scheduler | scheduler | 值域转换,统一 API 的枚举值与 SD3.5 不完全一致 |
| batch_size | num_images_per_prompt | 名称转换,需确认 SD3.5 服务是否允许一次生成多张 |
注意:这只是一个起点。如果你的统一 API 里还有 sampler、clip_skip、controlnet 等字段,映射前先确认 SD3.5 部署是否开放了这些参数。未开放的字段不要放进请求体,否则可能被忽略或报错。
写一个参数转换函数兼容不同调用方
映射表最终要落到代码上。建议在网关层写一个独立的转换函数,输入统一定义的请求字典,输出 SD3.5 格式的请求体。这样前端和上游系统只对准统一 API,不感知 SD3.5 的字段名。
def convert_unified_to_sd35(uni: dict) -> dict:
# 基线映射:统一字段名 -> SD3.5 字段名
name_map = {
'prompt': 'prompt',
'negative_prompt': 'negative_prompt',
'steps': 'num_inference_steps',
'cfg_scale': 'guidance_scale',
'scheduler': 'scheduler',
'batch_size': 'num_images_per_prompt',
}
sd35 = {}
for unified_key, sd_key in name_map.items():
if unified_key in uni:
sd35[sd_key] = uni[unified_key]
# 直接映射的宽高和种子
if 'width' in uni:
sd35['width'] = int(uni['width'])
if 'height' in uni:
sd35['height'] = int(uni['height'])
if 'seed' in uni:
sd35['seed'] = int(uni['seed'])
return sd35这个函数只做名称和类型转换,不负责校验。配合第三段的校验逻辑使用,可以避免把非法参数直接传给 SD3.5 推理服务。
映射后如何校验参数合法性与默认值
SD3.5 对参数范围比较敏感,比如 steps 太小可能出图质量差,太大又拖慢响应。统一 API 的调用方不一定了解这些边界,所以建议在转换后、请求发出前做一次校验,并填充默认值。
DEFAULTS = {'steps': 30, 'guidance_scale': 4.5, 'num_images_per_prompt': 1}
def sanitize_sd35_params(p: dict) -> dict:
for k, v in DEFAULTS.items():
p.setdefault(k, v)
# 宽高校验:按常见池化倍数,限制为 16 的整数倍
for dim in ('width', 'height'):
if dim in p:
try:
p[dim] = int(p[dim])
except (TypeError, ValueError):
raise ValueError(f'{dim} 必须是整数')
if p[dim] % 16 != 0:
# 若你的 SD3.5 服务允许非 16 倍,可移除或改为警告
p[dim] = (p[dim] // 16) * 16 # 向下取整到 16 的倍数
# steps 范围:示例 1-50,可按模型能力调整
if p.get('steps') is not None:
p['steps'] = max(1, min(int(p['steps']), 50))
return p校验逻辑放在网关层比较合适。这样入口统一收口,错误类型也更容易归类。如果 SD3.5 服务自身对范围有严格要求,比如 steps 必须大于 4,需要在上面的范围上再收紧。
将 SD3.5 返回错误映射为统一错误结构
SD3.5 返回的错误格式不一定和你的统一 API 一致,可能是 HTTP 状态码加原始字符串,也可能带内部字段。为了让前端和上游系统拿到一致的异常信息,建议在网关层捕获所有 SD3.5 异常,并转换成统一 JSON 错误结构。
UNIFIED_ERROR = {
'code': '55040', # 自定义业务码,这里只是示例
'message': 'SD3.5 请求失败',
'details': {},
'request_id': '',
}
def map_sd35_error(e: Exception, request_id: str) -> dict:
err = UNIFIED_ERROR.copy()
err['request_id'] = request_id
if hasattr(e, 'status_code'):
status = e.status_code
if status in (400, 422):
err['code'] = '55041' # 参数错误
err['message'] = 'SD3.5 参数校验未通过'
elif status == 504:
err['code'] = '55042' # 超时
err['message'] = 'SD3.5 推理服务超时'
else:
err['code'] = '55043'
err['message'] = f'SD3.5 服务返回异常状态 {status}'
else:
err['code'] = '55044'
err['message'] = f'SD3.5 调用异常: {str(e)}'
err['details'] = {'source': 'sd35'}
return err这个结构里的 code 只是占位示例,你可以按自己的错误码体系替换。关键是所有 SD3.5 相关的错误都统一走这一层,不要让原始堆栈直接透出到业务响应里。