Azure OpenAI 迁到 Responses API:微软生成式 AI 入门课的迁移 skill
手上有一份两年前写的 Python 脚本,开头是 AzureOpenAI(api_version=..., azure_endpoint=...),中间是 client.chat.completions.create(messages=...),结尾是 resp.choices[0].message.content。现在要换一个新模型,发现它只走 Responses API,于是你开始逐行改——改到一半会发现,不是把方法名换掉就完事:参数改了名、工具 schema 从嵌套变扁平、流式事件的形状整个换了。
generative-ai-for-beginners 这个课程仓在做这件事的时候,把迁移规则本身也提交进了仓库,路径是 .github/skills/azure-openai-to-responses/,里面只有两个文件:SKILL.md 和 references/cheat-sheet.md。仓库的 CHANGELOG.md 里写明这个 skill 是「用来指导 API 迁移的」。这篇就拆这两个文件。
它是怎么被触发的
SKILL.md 开头是一段 YAML frontmatter,字段是 name、description、license、source。name 的值是 azure-openai-to-responses,license 写的是 MIT,source 指向 Azure-Samples/azure-openai-to-responses——也就是说这份 skill 不是课程仓原创,是从另一个仓库装进来的,正文里也写了一句「Installed from」。
触发靠的是 description 这一段。它不是一句人话简介,而是三段拼起来的:先用一句话说清做什么(把 Python 应用从 Azure OpenAI Chat Completions 迁到 Responses API),然后是一串 USE FOR: 关键词,把用户可能说出口的各种说法都铺进去——migrate to responses API、switch from chat completions、upgrade openai SDK、gpt-5 migration、AzureOpenAI to OpenAI client 之类;最后是 DO NOT USE FOR:,明确排掉几类场景。
排掉的那几类值得单独看一眼,因为它决定了你这份代码到底该不该套:从零写新应用(直接用 Responses 起手,不需要迁移)、Node/TypeScript/C#/Java/Go 的迁移(description 里逐字写了 this skill is Python-only)、Azure 基础设施搭建、模型部署。
frontmatter 之外,正文里还有一节 ## Triggers,用列表又写了一遍激活条件,其中一条是「修复 AzureOpenAI 构造函数或 api_version 相关的弃用告警」。也就是说描述里的关键词负责被检索到,Triggers 一节负责让读到全文的人自己判断适不适用,两层是冗余的。
还有一句约束写在正文最上方的引用块里:这份指引标着 AUTHORITATIVE GUIDANCE — FOLLOW EXACTLY,并且明说 Do not improvise parameter mappings or invent API shapes(不要即兴发挥参数映射,不要编造 API 形状)。这句话是给执行者的硬约束,本身也说明了这类迁移最容易出的事故是什么。
前置条件
Step 0 那一节写明:AzureOpenAI / AsyncAzureOpenAI 这两个构造函数在 openai>=1.108.1 中已经是 deprecated,验收清单里也要求 requirements 中写 openai>=1.108.1。
这里有个可以自己核的地方:课程仓自己的 requirements.txt 里那一行是 openai>=1.12.0。两处白纸黑字放在一起,下限并不一致——skill 是从外部仓库装进来的通用迁移规则,课程仓的依赖清单是另一份文件。你套用它的验收清单之前,先确认自己项目里装的是哪一个。(这两个值都是仓库当前文件里的内容,随版本可能变动。)
除此之外:走 API key 路线只需要 openai;走 EntraID 路线(cheat-sheet.md 里标注为 recommended)还要 azure.identity,用到的是 DefaultAzureCredential 和 get_bearer_token_provider 两个符号。
还有一条前置是关于 provider 的。SKILL.md 的 Model compatibility 一节写得很直接:GitHub Models(models.github.ai、models.inference.ai.azure.com)不支持 Responses API,要求把这些代码路径删掉,换成 Azure OpenAI、OpenAI 或兼容的本地端点。同一节还写了模型侧的分档:GPT-4o 与 GPT-4 支持 Responses 的基础文本、chat、streaming、tools,但不是全部特性;更新的模型(gpt-4.1+、gpt-5.x)是完整支持。这段是原文口径,我不往上加码。
客户端那一步在改什么
Step 0 给了 before/after 两段代码。改之前:
from openai import AzureOpenAI
client = AzureOpenAI(
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_API_KEY"],
)
改之后:
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AZURE_OPENAI_API_KEY"],
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
)
三处变化:类从 AzureOpenAI 换成 OpenAI;azure_endpoint 换成 base_url,并且要在原 endpoint 后面拼 /openai/v1/(rstrip('/') 是为了避免尾斜杠拼出双斜杠);api_version 整个删掉——SKILL.md 的 Why migrate 一节自述的理由是 /openai/v1/ 这个新端点不需要 api_version,并且在 OpenAI 与 Azure OpenAI 上写法一致。
EntraID 路线的差别只在一个参数:旧写法的 azure_ad_token_provider=... 变成 api_key=token_provider,也就是把 token provider 直接当 api_key 传进去。异步就是把 OpenAI 换成 AsyncOpenAI。
收尾动作 Step 0 也列了:从 .env 和基础设施里删掉 AZURE_OPENAI_API_VERSION / AZURE_OPENAI_VERSION,把 AZURE_OPENAI_CLIENT_ID 改名成 AZURE_CLIENT_ID。顺带说一句,课程仓的 .env.copy 里 AZURE_OPENAI_API_VERSION 这一行目前还在,并带着一个默认值——这两处是并存的,改自己项目时按自己的调用点判断。
cheat-sheet 里的新旧对应
references/cheat-sheet.md 是完整的 before/after 代码集,SKILL.md 正文里用相对链接 ./references/cheat-sheet.md 挂上去。挑几处改起来最容易漏的:
取结果:resp.choices[0].message.content → resp.output_text。验收清单里那条「choices[0] 零匹配」就是查这个。
参数改名:max_tokens → max_output_tokens,映射表里标了 Azure 上最小值是 16;seed 直接删除(表里标的是 not supported);response_format 不再是顶层参数,改成 text={"format": {...}},结构化输出的 format 里放 "type": "json_schema"、"name"、"strict"、"schema" 四个键。这些阈值与键名都是仓库当前文档里的内容,随版本可能变动。
工具:旧的嵌套写法 {"type":"function","function":{...}} 变成扁平的 {"type":"function","name":...,"description":...,"parameters":{...}}。工具结果的回传变化更大——旧的 {"role":"tool","tool_call_id":...} 换成 {"type":"function_call_output","call_id":...,"output":...},注意字段名从 tool_call_id 变成了 call_id。回合衔接上,cheat-sheet 的示例是先 messages.extend(response.output) 把模型输出的 function_call 条目原样追加回去,再逐个追加 function_call_output。
这一节结尾有一句独立警告,值得单独拎出来:openai.pydantic_function_tool() 生成的仍然是旧的嵌套格式,与 responses.create() 不兼容,要求手写 tool schema。如果你原来是靠这个辅助函数生成 schema 的,光改调用方法不会让它变对。
多模态:content[].type 从 "text" 变成 "input_text";图片从 {"type":"image_url","image_url":{"url":"..."}} 变成 {"type":"input_image","image_url":"..."}——image_url 从一个对象塌成了一个扁平字符串。
流式:旧写法读 chunk.choices[0].delta.content;新写法是遍历事件,判断 event.type == "response.output_text.delta" 后取 event.delta,另有一个 response.completed 事件。
多轮:cheat-sheet 的 Multi-turn 示例只演示了一条路——自己维护一个 messages 数组,每轮把 response.output_text 追加成 assistant 消息再整个传给 input。另一条路写在 SKILL.md 的 Step 2 里:用 previous_response_id,括号里跟着前提 requires store=True。而同一节的上一条又要求「每个请求都设 store=False」,后面括号标的是 client-managed state。这两条同时摆在那儿,选 previous_response_id 就意味着你走的不是 cheat-sheet 演示的那条路径,写的时候心里得有数。
推理模型:max_completion_tokens → max_output_tokens(文档建议设高一些)、reasoning_effort → reasoning={"effort": ...}(effort 取值保持 low/medium/high)、temperature 与 top_p 都删掉。表里对 temperature 的说明是 GPT-5 会直接拒绝,o 系列只接受 1;要让输出有变化,文档给的做法是换一个非推理模型,它举的例子是通过 Foundry Models 用 Llama-3.3-70B-Instruct——这是仓库里的示例值,不是推荐清单。
以上代码片段均原样抄自仓库文件,把它们拼进自己的调用时,以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。
边界:这套规则没覆盖到哪儿
SKILL.md 的验收清单里有一条是「AzureOpenAI( / AsyncAzureOpenAI( 零匹配」。但在课程仓自己身上,这条并不成立:08-building-search-applications/scripts/transcript_enrich_embeddings.py 和 09-building-image-applications/python/aoai-app.py 里,AzureOpenAI( 仍然在,并且仍然带着 api_version 参数。对照 CHANGELOG.md 里 Responses API 迁移那条记录,它列出的课次不包含 08 和 09。
这不是漏改。CHANGELOG.md 的「Deprecated / Notes」一节自己写明了:AzureOpenAI() 在仍然合适的地方是有意保留的(embeddings 与图像生成),因为这些工作流不属于 Responses API 迁移的范围。同一节还写了另一条同类保留:走 azure-ai-inference 的那些 githubmodels-* 示例与第 19、20、21 课留在 Model Inference API 上,仓库自述该 API 不支持 Responses。
对你的意义是:跑这份验收清单之前,先把调用点按性质分开——chat 类的调用面归 Responses,embeddings 与图像生成不在这条线上,别一并「迁」了。
另外两条边界前面已经提过,这里归拢一下:这份 skill 只适用于 Python,其它语言的迁移它自己在 DO NOT USE FOR 里排除了;GitHub Models 端点不在支持范围内。至于迁移过程中的兼容层、灰度切换、回滚方案——仓库里没有找到相关说明。
怎么验证改对了
Step 1 给了一组检测命令,用的是 rg(ripgrep)。原文那组比下面长,这里原样摘出其中几条:
rg "chat\.completions\.create" # legacy API calls
rg "AzureOpenAI\(|AsyncAzureOpenAI\(" # deprecated constructors
rg "choices\[0\]\.message\.content" # response access
rg "max_tokens\b" # rename to max_output_tokens
rg "['\"]seed['\"]" # remove entirely
Linux/macOS 上包管理器一般都有 ripgrep。Windows 侧要注意:PowerShell 与 cmd 都不自带 rg,需要自己装;上面这些正则里的引号在 PowerShell 里的转义规则和 bash 不同,建议在 Git Bash 或 WSL 里跑。仓库里没有给 Windows 的等价命令。
另有一段 smoke test 代码放在 Model compatibility 那一节里,位置在 Step 0 之前,那一节的标题直接写着 CHECK FIRST,正文要求「迁移之前先确认部署的模型支持 Responses API」。它用 client.responses.create(model=..., input="ping", max_output_tokens=50, store=False) 打一发最小请求,用来确认你部署的那个模型到底支不支持 Responses API(其中的 50 是仓库示例里的值)。这段要真连云端,会产生费用也会把内容发出去,跑之前先确认密钥和数据边界。
还有一处现成的参照样板在仓库里:shared/python/api_utils.py 的 create_azure_openai_client() 已经是迁移后的形态,函数体最后 return OpenAI(api_key=_api_key, base_url=f"{_endpoint.rstrip('/')}/openai/v1/"),它的 docstring 里明写了「因为用的是 v1 endpoint,所以不需要 api_version」。对应的测试在 tests/test_api_utils.py 的 TestCreateAzureOpenAIClient 类里,覆盖的是缺 endpoint 和缺 key 时抛 ValueError 这两种情况——它验的是参数校验,不是 API 形状,别指望它替你把迁移正确性兜住。验收清单里对测试的要求是另一条:mock 改用 kwargs.get("input"),快照换成 Responses 的形状。
本文依据 github.com/microsoft/generative-ai-for-beginners 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码,
因此不涉及运行结果、耗时与生成质量的任何描述。
该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。
文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。
许可条款请以仓库 LICENSE 原文与你所在组织的要求为准,本文不构成法律意见。