Azure OpenAI 报 404:微软生成式 AI 入门课的 endpoint 与部署名

2026-08-18

跑 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_ENDPOINTrstrip('/') 之后再拼 /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_version06-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_endpointbase_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_tokensseed 直接移除(不支持),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 缺失时会抛 ValueErrortests/test_api_utils.pyTestCreateAzureOpenAIClientpytest.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.mdAGENTS.md 都写明:当前 Microsoft Foundry 上未废弃的模型是 reasoning 模型(GPT-5 家族、o 系列),它们不支持 temperaturetop_p,也不用 max_tokens(改用 max_output_tokens)。README 里的原话是,给 gpt-5-minitemperature 会拿到一个「参数不支持」的错误。这类报错说明请求已经打到部署上了,路由是通的。

你走的其实是另一条 provider 路线。 仓库里同一课常有 oai-*aoai-*githubmodels-* 三份示例,配置互不通用。githubmodels-app.py 读的是 AZURE_INFERENCE_CREDENTIALAZURE_INFERENCE_ENDPOINT,客户端是 azure.ai.inferenceChatCompletionsClient,压根不经过 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.aimodels.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 的公开内容整理, 事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码, 因此不涉及运行结果、耗时与生成质量的任何描述。 该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。 文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。

安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。