Firefly 接入时最常见的问题是凭据配错了:Client ID 写错、产品没加进项目、令牌和
x-api-key对不上。这篇写一个自检脚本,依次检查环境变量、向 IMS 换令牌、带令牌调用 API,并把每一步的真实报错翻译成具体的修复建议。整个过程不提交生成任务,也就不消耗额度。
先看清成品(下面是在 box 上用错误凭据对真实 Adobe 接口发出的请求)
- IMS 返回
400 invalid_client。- Firefly 返回
401 {"error_code":"401013","message":"Oauth token is not valid"}。- 两个返回体都写进了单元测试。
合规提醒:Firefly Services API 面向企业,需要组织开通 Firefly Services,并在 Adobe Developer Console 创建 OAuth Server-to-Server 凭据。
CLIENT_SECRET和访问令牌只能放在服务器端,不要写进前端代码、仓库或截图。提示词和参考图要遵守 Adobe 的生成式 AI 使用准则,不要生成侵犯他人肖像、版权或商标的内容。在中国境内公开发布生成内容,要遵守《生成式人工智能服务管理暂行办法》,并按《人工智能生成合成内容标识办法》加上“AI 生成”标识。Firefly 按生成额度计费,额度取决于你的合同,本文不列价格。
运行说明:box 上没有有效凭据,所以“鉴权通过”这条分支(返回 400 参数错误)没有实际验证过。它依据的假设是:参数不合法的请求会在校验阶段被拦下,不会生成图片。拿到真实凭据后,建议先在测试项目里确认这一点,再用到生产环境。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
ff_doctor.py |
三步自检,并把报错归类 |
test_doctor.py |
用真实返回体测试分类规则 |
需要准备哪些环境?
Python 3.9 以上,pip install requests pytest,能访问 adobelogin.com 和 adobe.io。
文件结构
49-adobe-firefly-C-src/
├── ff_doctor.py
└── test_doctor.py
第 1 步:代码
"""Firefly 接入自检:依次检查环境变量、IMS 换令牌、带令牌调用 API,
把每一步的真实报错归类成“该怎么修”。不会提交生成任务(不消耗额度)——
第 3 步向 generate-async 发一个故意不合法的请求体(numVariations=0),
令牌无效时得到 401;令牌有效时应被参数校验以 400 拦下,不会生成图片。
(为什么不用 GET /v3/status/<假ID>:实测它在鉴权之前就返回 openresty 的 404,测不出令牌好坏。)"""
import json, os, sys
import requests
IMS = "https://ims-na1.adobelogin.com/ims/token/v3"
API = "https://firefly-api.adobe.io"
SCOPE = "openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis"
UA = "binbho-tutorial-firefly-doctor/1.0"
RULES = [ # (条件, 类别, 建议)。条件看 HTTP 状态码和返回体
(lambda s, b: s == 400 and "invalid_client" in b, "凭据错误", "Client ID 不对,或者项目里没有加 Firefly API;到 Adobe Developer Console 核对"),
(lambda s, b: s == 401 and "invalid_client" in b, "凭据错误", "Client Secret 不对或已轮换"),
(lambda s, b: s == 400 and "invalid_scope" in b, "scope 错误", "scope 用官方文档给的那一串"),
(lambda s, b: s == 401 and "401013" in b, "令牌无效", "令牌过期或不属于这个 Client ID:重新换令牌,x-api-key 要和换令牌用的 Client ID 一致"),
(lambda s, b: s == 403, "无权限", "组织没有开通 Firefly Services,或这个产品配置没有权限"),
(lambda s, b: s == 400, "参数错误", "看返回的 message(如尺寸、numVariations、seeds 个数)"),
(lambda s, b: s == 404, "路径或任务不存在", "检查 URL;同步 v3 接口已于 2025-10-03 下线,改用 *-async"),
(lambda s, b: s == 429, "限流", "按 Retry-After 等待后重试,降低并发"),
(lambda s, b: s >= 500, "服务端错误", "指数退避重试,持续失败看 Adobe 状态页"),
]
def classify(status, body):
for cond, cat, tip in RULES:
if cond(status, body):
return cat, tip
return ("正常", "") if status < 400 else ("未知", body[:200])
def show(step, r):
cat, tip = classify(r.status_code, r.text)
print(f"[{step}] HTTP {r.status_code} → {cat}" + (f":{tip}" if tip else ""))
print(" 返回:", r.text.strip()[:160])
return r.status_code < 400
def main():
cid = os.environ.get("FIREFLY_SERVICES_CLIENT_ID")
sec = os.environ.get("FIREFLY_SERVICES_CLIENT_SECRET")
print("[1] 环境变量:", "CLIENT_ID ✓" if cid else "CLIENT_ID ✗", "CLIENT_SECRET ✓" if sec else "CLIENT_SECRET ✗")
if not (cid and sec):
sys.exit("缺少环境变量")
s = requests.Session(); s.headers["User-Agent"] = UA
r = s.post(IMS, data={"grant_type": "client_credentials", "client_id": cid, "client_secret": sec, "scope": SCOPE}, timeout=30)
token = r.json().get("access_token") if show("2 IMS 换令牌", r) else os.environ.get("FIREFLY_TEST_TOKEN", "invalid")
r = s.post(f"{API}/v3/images/generate-async", timeout=30, json={"prompt": "doctor check", "numVariations": 0},
headers={"x-api-key": cid, "Authorization": f"Bearer {token}", "Content-Type": "application/json"})
show("3 API 鉴权", r)
if r.status_code == 400:
print(" ✓ 鉴权通过(400 说明请求已经过了鉴权、停在参数校验)")
if __name__ == "__main__":
main()
第 2 步:测试
from ff_doctor import classify
# 以下返回体是本文在 box 上用错误凭据真实请求得到的
REAL_IMS = '{"error":"invalid_client","error_description":"invalid client_id parameter"}'
REAL_API = '{"error_code":"401013","message":"Oauth token is not valid"}'
def test_real_bodies():
assert classify(400, REAL_IMS)[0] == "凭据错误"
assert classify(401, REAL_API)[0] == "令牌无效"
def test_others():
assert classify(400, '{"message":"Size must be one of ..."}')[0] == "参数错误"
assert classify(429, "")[0] == "限流"
assert classify(503, "")[0] == "服务端错误"
assert classify(200, "{}")[0] == "正常"
$ python -m pytest -q
2 passed in 0.08s
第 3 步:真实运行
$ FIREFLY_SERVICES_CLIENT_ID=not-a-real-client FIREFLY_SERVICES_CLIENT_SECRET=not-a-real-secret python ff_doctor.py
[1] 环境变量: CLIENT_ID ✓ CLIENT_SECRET ✓
[2 IMS 换令牌] HTTP 400 → 凭据错误:Client ID 不对,或者项目里没有加 Firefly API;到 Adobe Developer Console 核对
返回: {"error":"invalid_client","error_description":"invalid client_id parameter"}
[3 API 鉴权] HTTP 401 → 令牌无效:令牌过期或不属于这个 Client ID:重新换令牌,x-api-key 要和换令牌用的 Client ID 一致
返回: {"error_code":"401013","message":"Oauth token is not valid"}
$ env -u FIREFLY_SERVICES_CLIENT_ID python ff_doctor.py
[1] 环境变量: CLIENT_ID ✗ CLIENT_SECRET ✓
缺少环境变量
检查点:每一步都打印了 HTTP 状态码、类别和原始返回体。凭据填对之后,第 2 步应显示“正常”,第 3 步应显示 400 和“✓ 鉴权通过”。
调试示例
第 3 步最初返回的是 404 网页,而不是 401。 第一版的第 3 步用 GET /v3/status/urn:ff:jobs:doctor-check 来测鉴权,看起来最安全,因为什么都不会生成。但实际跑出来是 404 Not Found,返回的是 openresty 的 HTML 页面。也就是说,网关在检查令牌之前就因为任务不存在而直接返回了,这个请求根本测不出令牌是好是坏。
后来改成向 generate-async 发送 numVariations=0 的请求,这次真实拿到了 401 401013,说明请求确实经过了鉴权这一步。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
invalid_client |
Client ID 或 Secret 错误,或者项目里没有加 Firefly API | 到 Developer Console 核对,必要时重新生成 Secret |
invalid_scope |
scope 自己拼写有误 | 复制官方文档给出的 scope |
| 401013 | 令牌过期,或 x-api-key 和令牌不匹配 | 重新换令牌,两边的 ID 保持一致 |
| 403 | 组织没有开通 Firefly Services | 联系 Adobe 销售或管理员 |
模板规则
- 自检不能提交真实的生成任务。
- 分类规则要用真实的返回体来测试。
- 报错时打印原始返回体,但不要打印 Secret 和令牌。
项目资源
| 用途 | 地址 |
|---|---|
| Firefly API 快速入门(取令牌、generate-async) | https://developer.adobe.com/firefly-services/docs/firefly-api/guides/ |
| 异步 API 使用说明(statusUrl 轮询) | https://developer.adobe.com/firefly-services/docs/firefly-api/guides/how-tos/using-async-apis |
| Seeds 说明 | https://developer.adobe.com/firefly-services/docs/firefly-api/guides/concepts/seeds/ |
| Adobe Developer Console | https://developer.adobe.com/console |
| Adobe IMS 令牌端点(POST) | https://ims-na1.adobelogin.com/ims/token/v3 |
把这个脚本放进部署流程的健康检查里,换凭据或换环境时跑一下,就能马上确认配置有没有问题。