第 9 课的图像生成应用:微软生成式 AI 入门课里请求怎么发、结果怎么取
你大概率是卡在「返回里没有图片链接」
翻到 generative-ai-for-beginners 第 9 课的人,多半不是来学什么是图像生成的,而是想把 images.generate 这一步搬进自己的脚本。真正把人绊住的地方通常有两个:一是不知道该照 aoai-app.py 还是 oai-app.py 抄,两份代码长得很像但客户端构造和取结果的写法不一样;二是照着旧印象去 data[0].url 里拿图片地址,结果拿到 None。
课程 README 在这一点上写得很直接:gpt-image 系列模型把生成结果以 base64 返回在 b64_json 字段里,不是 URL,代码需要自己解码成字节再落盘,没有可下载的图片地址。这一句是这一课的技术核心,后面所有写法差异都是围着它转的。
以下内容来自 09-building-image-applications/ 目录下的 README、python/aoai-app.py、python/oai-app.py 与两个 assignment notebook。该课程持续更新,以仓库最新内容为准。
前置条件:先分清你走哪条路线
课程仓的 00-course-setup/03-providers.md 定了一条命名约定,值得先记住:作业文件名里带 aoai 的需要 Azure OpenAI 的 endpoint 与 key,带 oai 的需要 OpenAI 的 key,另外还有 hf(Hugging Face token)和 githubmodels 两种标记。这一课的 aoai-app.py 与 oai-app.py 就是同一件事的两条路线,不要把两边的配置混着填。
依赖方面,第 9 课目录下的 requirements.txt 内容是 python-dotenv、openai、pillow、requests 四行;而 README 正文里让你自建的那份 requirements.txt 只列了前三行。两处不一致,装第四个也不碍事,但你会发现 oai-app.py 顶部 import requests 和 from requests.exceptions import RequestException 之后,整个文件里没有再用到它们——仓库里没有说明为什么留着这两行,但现在的取图路径确实不经过 HTTP 请求,你自己抄的时候可以不带。
虚拟环境这一步 Windows 侧要留意:README 里给的是 python3 -m venv venv 加一句注释 # Windows: venv\Scripts\activate,而 python/oai-assignment.ipynb 的 Windows 提示写的是 venv\Scripts\activate.bat。两种都能在 cmd 里生效,PowerShell 下则要用 venv\Scripts\Activate.ps1——最后这条是通用做法,不是仓库里的原文。Linux/macOS 侧仓库给的是 source venv/bin/activate。
环境变量还有个坑。根目录 .env.copy 里 AZURE_OPENAI_DEPLOYMENT 的注释写的是「chat completion model deployment name」,而第 9 课 README 让你把它设成图像模型的部署名。也就是说同一个变量在不同课里指向不同部署,你如果拿同一份 .env 跑完第 6、7 课再跑第 9 课,会需要改这一行。密钥本身写 <YOUR_API_KEY> 这类占位即可,.env 在仓库里是被 gitignore 的。
请求怎么发:两条路线的客户端不一样
Azure 侧,python/aoai-app.py 里构造客户端要三样东西:
client = AzureOpenAI(
api_key=os.environ['AZURE_OPENAI_API_KEY'], # this is also the default, it can be omitted
api_version = "2025-04-01-preview",
azure_endpoint=os.environ['AZURE_OPENAI_ENDPOINT']
)
model = os.environ['AZURE_OPENAI_DEPLOYMENT']
注意 api_version 在这里是写死在代码里的字符串,并没有去读 .env.copy 里那个 AZURE_OPENAI_API_VERSION 变量。上面这个值是仓库当前代码里的示例值,随版本可能变动;文件里的注释也自述要「check the Microsoft Foundry docs for the current API version required by your model」。另外 Azure 这条路线传给 model= 的值来自 AZURE_OPENAI_DEPLOYMENT,也就是部署名而不是模型名;README 里那段等价示例把这个变量直接命名成 deployment,两处对照着看就很清楚。
OpenAI 侧,python/oai-app.py 的客户端只要一个 key,并且在构造前先做了一次校验:
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise ValueError("OPENAI_API_KEY environment variable is required. Please set it in your .env file.")
client = OpenAI(api_key=api_key)
这条路线的 model= 直接写模型名,文件里写的是 model="gpt-image-1"——这是仓库示例里的取值,README 的模型说明里把 gpt-image-1 标为 Preview only(在 Microsoft Foundry 上),所以别把它当成默认推荐值照抄进生产代码。
调用本身两边是一致的:client.images.generate(model=..., prompt=..., size='1024x1024', n=1)。size 这个 '1024x1024' 是示例值,README 的代码注释里说明还可以取 1536x1024(横)、1024x1536(竖)或 "auto"。README 另外明确写了一句:图像模型不接受 temperature 参数,那是文本生成的控制项;同一个 prompt 每次调用得到的图不同,想要变化就再调一次,想减少变化就把 prompt 写得更具体。
结果怎么取:同一个字段,两种读法
这是两份文件差别最大的地方。
aoai-app.py 先把返回对象序列化再当字典读:
generation_response = json.loads(result.model_dump_json())
image_b64 = generation_response["data"][0]["b64_json"]
generated_image = base64.b64decode(image_b64)
oai-app.py 则直接走属性访问:
generated_image = base64.b64decode(generation_response.data[0].b64_json)
两条路走到的是同一个字段 data[0].b64_json,区别只在中间那次 model_dump_json() 转换。实际写代码时挑一种就行:属性访问更短,转成 dict 的好处是你可以整包 print 出来看结构——oai-app.py 里恰好就有一行 print(generation_response) 放在取图之前,调不通的时候先把这行打开,比盲猜字段名快。
拿到字节之后两份文件的落盘逻辑一样:用 os.path.join(os.curdir, 'images') 拼目录,不存在就 os.mkdir,然后以 wb 模式写成 generated-image.png,最后 Image.open(image_path).show() 调系统看图程序打开。注意后缀必须是 .png,两个文件的注释都专门提了这一点。
编辑图片走的是另一个方法。README 给的写法是 client.images.edit(model=..., image=..., mask=..., prompt=...),把原图和一张 mask 以二进制方式打开传进去,mask 标记要改动的区域,返回同样是 base64。课程目录 images/ 下的 sunlit_lounge.png 与 mask.png 就是配套素材。
边界:仓库里几处得照实说的地方
create_variation 不是这一课的推荐路径。 README 写明 DALL·E 2 / DALL·E 3 属于 legacy,DALL·E 3 不再接受新部署,create_variation 这类能力只存在于 DALL·E 2。但 oai-app.py 文件末尾仍留着一段:
response = client.images.create_variation(
image=open(image_path, "rb"),
n=1,
size="1024x1024"
)
这段代码在 try/except 块之外、注释 # ---creating variation below--- 之下,而它用到的 image_path 是在 try 里赋值的。也就是说,正文说这个能力属于 legacy、示例文件里却还留着调用,两处放在一起看是对不上的。照抄前先想清楚你的模型是否还支持它。
异常处理两边不对等。 oai-app.py 用 except OpenAIError as err 捕获并打印;aoai-app.py 顶部虽然 from openai import AzureOpenAI, BadRequestError,但那段 except BadRequestError 是被注释掉的,只剩一个 finally: print("completed!")。结果就是 Azure 那份示例出错时异常会直接抛出,而终端上仍会打印一句 completed!。这是仓库现状,不是笔误可以直接忽略的东西——你要是拿它当模板,记得把异常分支补回来。
内容边界靠 metaprompt 兜。 python/aoai-solution.py 演示的做法是先定义一个 disallow_list 字符串,再把它嵌进一段 meta_prompt(里面写明面向儿童、安全、横版这些约束),最后用 f-string 把要画的内容接在 meta_prompt 后面拼成 prompt 传进去。README 对这个做法的说法是:metaprompt 是「接在用户 prompt 前面的一段文本」,用来约束输出;README 也说明这只是其中一层,建议与 Microsoft Foundry 内置的内容过滤配合使用。这是 prompt 层面的约束,不等于服务端保证。
其余没写的就是没写。 关于重试、限流、并发这些,我们在这一课的目录里没有找到相关说明。
以上代码片段均按仓库代码中的接口语义组合,未经实测,以仓库最新代码为准。
怎么验证接对了
按顺序做三步,出错时能一眼定位在哪一层:
- 先验证配置读到了。 不要直接跑生成,先确认对应路线的环境变量非空。
oai-app.py的那段if not api_key: raise ValueError(...)就是这一步的现成写法,Azure 侧用的是os.environ[...],取不到会直接抛KeyError,同样能暴露问题。 - 再验证返回结构。 保留
print(generation_response),确认data[0]里出现的是b64_json而不是url。如果你看到的是 URL 字段,说明连的模型不是这一课讲的这一代。 - 最后验证落盘。 检查当前目录下是否生成了
images/generated-image.png,用看图程序能打开即可。Image.open(...).show()依赖 pillow,如果这一步报错而文件已经写出来了,那问题在显示环节不在接口调用。
另外,这三步都过了之后想加护栏,就照 aoai-solution.py 的顺序把 meta_prompt 拼在用户输入前面,再重跑一次同样的三步。仓库里还有一份 TypeScript 版本,在 typescript/image-generation-app/src/main.ts,它走的是 Azure 这条路线(用 AzureOpenAI 构造客户端),取图那段同样是从 image.b64_json 解 base64 后 fs.writeFileSync,和 Python 侧的 Azure 版本对得上;用 Node 的可以对照着看,但别拿它当 OpenAI 路线的模板。
本文依据 github.com/microsoft/generative-ai-for-beginners 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码,
因此不涉及运行结果、耗时与生成质量的任何描述。
该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。
文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。