Azure OpenAI 报 404:微软生成式 AI 入门课的 endpoint 与部署名
跑 generative-ai-for-beginners 的 Azure OpenAI 示例,代码本身就二十来行,真正卡人的是 .env 里那三四个变量。资源建好了、key 也贴了,一执行就是找不到路径或者部署不存在——这类问题九成不在代码里,在 endpoint 的形态和部署名的来源上。
先说清一件事:这个仓库没有给错误码对照表,通篇找不到对 HTTP 404 或 DeploymentNotFound 的说明。所以下面的判定动作不是从错误码文档来的,而是从仓库里那几个配置字段的语义倒推的——把「代码怎么拼这个 URL」和「文档说这个值该从哪儿取」放在一起看,错位在哪一眼就能看出来。
一、先把 URL 在本地拼一遍
06-text-generation-apps/python/aoai-app.py 的客户端构造是这样的:
client = OpenAI(
api_key=os.environ['AZURE_OPENAI_API_KEY'],
base_url=f"{os.environ['AZURE_OPENAI_ENDPOINT'].rstrip('/')}/openai/v1/",
)
deployment=os.environ['AZURE_OPENAI_DEPLOYMENT']
注意三处细节。第一,用的是标准的 OpenAI 客户端,不是 AzureOpenAI;第二,base_url 是拿 AZURE_OPENAI_ENDPOINT 做 rstrip('/') 之后再拼 /openai/v1/;第三,AZURE_OPENAI_DEPLOYMENT 单独取出来,后面作为 model= 传给 client.responses.create()。
shared/python/api_utils.py 里的 create_azure_openai_client() 是同一套拼法,函数 docstring 写明它指向 Azure OpenAI 的 v1 endpoint,并且因为用了 v1 endpoint,不需要 api_version。
判定动作很简单:在你自己的脚本里把这个 f-string 单独 print 出来,看拼出来的完整串长什么样。.env.copy 里对这个变量的注释逐字写的是 <add your Foundry resource endpoint here, e.g. https://<resource-name>.openai.azure.com>——它要的是资源根地址,不是你从门户某个页面复制来的、已经带了 /openai/deployments/xxx/chat/completions?api-version=... 的完整调用地址。如果你贴进去的是后者,rstrip('/') 再拼一次 /openai/v1/,出来的就是一条谁也认不出的路径。
rstrip('/') 只处理尾部斜杠这一种情况。它救不了多出来的路径段,也救不了协议头缺失。
二、部署名到底从哪儿取
AZURE_OPENAI_DEPLOYMENT 这个名字容易误导,00-course-setup/03-providers.md 的变量表里把它描述为 text generation 模型的 deployment endpoint,但代码里它就是一个字符串,直接进 model=。
那份文档的「Configure Azure OpenAI: From Microsoft Foundry portal」一节给了取值路径:进 Microsoft Foundry 门户,点侧栏 Deployments 标签看已部署的模型,没有就用 Deploy model 从模型目录里部署一个。然后有一句关键限定:部署名通常与模型名相同,除非你显式改过。文档给的示例是:
AZURE_OPENAI_DEPLOYMENT='gpt-5-mini'
AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT='text-embedding-3-small'
这两个值是仓库里的示例值,课程默认选的模型,不是你那边一定存在的部署。真正会咬人的就是「除非你显式改过」这半句:门户上部署的时候如果给了自定义名字,或者在别人建好的资源上接手,模型名和部署名就分家了。此时把模型名填进去,请求会打到一个不存在的部署上。
再一个错位来源是 embeddings。AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT 是独立变量,文档明说这两个变量分别对应文本生成与向量检索的默认模型。08-building-search-applications/scripts/transcript_enrich_embeddings.py 里这个变量是用 os.getenv() 带默认值读的,没配就会静默落到一个历史默认名上——不会在读取环境变量这一步报错,只会在真正发请求时才暴露。
三、api_version 的错位表现
这是仓库里最容易读岔的一处,因为两种客户端形态在同一个仓库里并存。
.env.copy 里有这一行,注释逐字写着默认已设:
AZURE_OPENAI_API_VERSION='2024-10-21' # Default is set! (current stable GA API version)
这一行里的值是仓库当前 .env.copy 写着的默认值,随版本可能变动,不要当成一个长期有效的常量记下来。
但第 06 课那个 aoai-app.py 从头到尾没有读过它。06-text-generation-apps/README.md 在 Azure 配置一节明确写了:用标准 OpenAI 客户端指向 Azure OpenAI 的 /openai/v1/ endpoint,配合 Responses API,不需要 api_version。06-text-generation-apps/python/aoai-assignment.ipynb 里也有同样的说明:用 v1 endpoint 就不再需要传 api_version。
另一边,08-building-search-applications/scripts/transcript_enrich_embeddings.py 用的是 AzureOpenAI(...),参数是 azure_endpoint= 加 api_version=;09-building-image-applications/python/aoai-app.py 同样是 AzureOpenAI(...),api_version 传的是另一个值,代码上面的注释还提醒你去 Microsoft Foundry 文档查你的模型当前需要哪个 API 版本。
这不是仓库自相矛盾。CHANGELOG.md 的 Deprecated / Notes 一节写明:AzureOpenAI() 在仍然合适的地方是有意保留的,具体点名了 embeddings 与图像生成,因为这两条链路不属于 Responses API 迁移的范围。
把这两处放在一起,错位的表现就清楚了:
- 你照着第 08 或第 09 课的写法建客户端,却拿去调 Responses API 那条链路,
azure_endpoint与base_url是两个不同参数,拼出来的路径自然不同; - 或者你照着第 06 课把
base_url拼好了,又顺手补一个api_version=——OpenAI客户端不吃这个参数; - 又或者你从别处抄来一段带
api_version的老代码,.env里那个默认值恰好还在,于是「变量都配了」的错觉一直维持到发请求为止。
仓库里 .github/skills/azure-openai-to-responses/SKILL.md 是这条迁移路线的完整说明。它写明 AzureOpenAI / AsyncAzureOpenAI 构造器在 openai>=1.108.1 中已被标记为 deprecated,并在「What changes」对照表里逐条给出映射:azure_endpoint=... 换成 base_url=f"{endpoint.rstrip('/')}/openai/v1/",api_version="2024-..." 一行整个删掉,max_tokens 换成 max_output_tokens,seed 直接移除(不支持),resp.choices[0].message.content 换成 resp.output_text。清理动作里还包括把 AZURE_OPENAI_API_VERSION 从 .env 和基础设施配置里删掉。
四、改完怎么验证
SKILL.md 里给了一段 smoke test,原样抄在这里:
import os
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/",
)
resp = client.responses.create(
model=os.environ["AZURE_OPENAI_DEPLOYMENT"],
input="ping",
max_output_tokens=50,
store=False,
)
print(resp.output_text)
这段的价值在于把变量数压到最少:一个 endpoint、一个 key、一个部署名,没有 api_version,没有采样参数。它能过,说明三者的对应关系是通的。
同一份 skill 还给了自查用的检索项,直接在你的项目目录里跑一遍:
rg "AzureOpenAI\(|AsyncAzureOpenAI\("
rg "AZURE_OPENAI_API_VERSION|AZURE_OPENAI_VERSION"
rg "models\.github\.ai|models\.inference\.ai\.azure"
它列出的验收条件里有这么几条:不应再有 AzureOpenAI( / AsyncAzureOpenAI( 的匹配,客户端构造里不应有 api_version,每次调用带 store=False,依赖里 openai>=1.108.1。
配置层面还有个更靠前的检查点。shared/python/api_utils.py 里的 create_azure_openai_client() 在 endpoint 或 key 缺失时会抛 ValueError,tests/test_api_utils.py 的 TestCreateAzureOpenAIClient 用 pytest.raises(ValueError, match="endpoint") 和 match="API key" 分别覆盖了这两种缺失。也就是说,走这个共享工具函数的话,变量没配会在建客户端时就报错;如果你的报错是发生在请求阶段,那基本可以排除「变量为空」这一类。
Windows 这边有两处要单独说。06-text-generation-apps/python/aoai-assignment.ipynb 里明确提示:Windows 下激活虚拟环境用 venv\Scripts\activate,不是 source venv/bin/activate。另外 00-course-setup/03-providers.md 给的复制命令是 cp .env.copy .env,README 里设置环境变量的例子是 export OPENAI_API_KEY='sk-...'——这两条都是 Unix shell 写法,在 Git Bash 里可以直接用,在 PowerShell 里需要换成等价命令。这一句是通用环境常识,不是该课程官方内容,仓库里我们没有找到针对 PowerShell 的对应说明。
五、什么情况说明不是这个原因
以下几种,问题不在 endpoint 与部署名的对应关系上,别顺着这条线继续查:
报的是参数不支持。 06-text-generation-apps/README.md 和 AGENTS.md 都写明:当前 Microsoft Foundry 上未废弃的模型是 reasoning 模型(GPT-5 家族、o 系列),它们不支持 temperature 和 top_p,也不用 max_tokens(改用 max_output_tokens)。README 里的原话是,给 gpt-5-mini 传 temperature 会拿到一个「参数不支持」的错误。这类报错说明请求已经打到部署上了,路由是通的。
你走的其实是另一条 provider 路线。 仓库里同一课常有 oai-*、aoai-*、githubmodels-* 三份示例,配置互不通用。githubmodels-app.py 读的是 AZURE_INFERENCE_CREDENTIAL 与 AZURE_INFERENCE_ENDPOINT,客户端是 azure.ai.inference 的 ChatCompletionsClient,压根不经过 AZURE_OPENAI_ENDPOINT。这条路线的前缀名如今已经名不副实:仓库在多处逐字写明,GitHub Models(连同它的 GITHUB_TOKEN 变量)于 2026 年 7 月底退役,直接替代者是 Microsoft Foundry Models,.env.copy 里对应的两个变量注释也是这么写的。今天这个时间点已经过去了,所以这条路线实际要配的是 Microsoft Foundry Models 的 endpoint 与 key,.env.copy 给的形态示例是 https://<resource-name>.services.ai.azure.com/models——和 Azure OpenAI 那个根地址不是一回事。
你在往 GitHub Models 的老地址上调 Responses API。 SKILL.md 里单独点名:models.github.ai 与 models.inference.ai.azure.com 不支持 Responses API,处置是移除这些代码路径。CHANGELOG.md 也记载 githubmodels-* 系列示例是有意留在 Model Inference API 上的,那套 SDK 用 client.complete(),不走 Responses。这种情况下改 endpoint 拼法不解决问题。
报的是鉴权类错误。 key 与 endpoint 属于不同资源、或者 key 复制时带了空白字符,都跟部署名无关,先把 key 单独核一遍。
最后提醒一句:这个课程仓在持续更新,上面涉及的文件路径、变量名、客户端写法都可能变,以仓库最新内容为准。
本文依据 github.com/microsoft/generative-ai-for-beginners 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码,
因此不涉及运行结果、耗时与生成质量的任何描述。
该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。
文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。