githubmodels- 前缀连的其实是 Foundry:微软生成式 AI 入门课第 20 课

2026-08-18

打开 20-mistral/python/githubmodels-assignment.ipynb,第一件该做的事不是找运行按钮,是先看它到底读哪两个环境变量。

因为这个文件名会骗人。

前缀名和实际接入目标已经脱节

notebook 里所有联网的代码格,取凭据的那两行都是这样:

endpoint = os.environ["AZURE_INFERENCE_ENDPOINT"]
token = os.environ["AZURE_INFERENCE_CREDENTIAL"]

前缀写着 githubmodels-,读的却是 AZURE_INFERENCE_*。这不是笔误。00-course-setup/03-providers.md 在列举作业文件名标签时逐字写着:githubmodels 这个标签的含义是「requires Microsoft Foundry Models endpoint, key」,后面跟一句 GitHub Models 将于 2026 年 7 月底退役。同一份文档在配置小节又写了一遍:Microsoft Foundry Models 是它的直接替代者。00-course-setup/02-setup-local.md00-course-setup/README.md、根目录的 .env.copy,以及 06-text-generation-apps/python/githubmodels-app.py 的注释里,都各有一处相同表述。其中 02-setup-local.md00-course-setup/README.md 还特意点名说一并退役的是 GITHUB_TOKEN 这个变量;AGENTS.md 里则直接把 AZURE_INFERENCE_CREDENTIAL 写成「replaces the retiring GITHUB_TOKEN」。

今天是 2026-08-18,那个时间点已经过去了。所以看到 githubmodels- 前缀,别按老印象去配 GITHUB_TOKEN,实际要填的是 Foundry 项目的 endpoint 与 key。.env.copy 里这两行的注释写明它们从 Foundry 项目的 Overview 页取。

还有一点值得先说:第 20 课在 python/ 目录下只有这一个文件,没有 oai-aoai- 的平行版本。前面几课常见的「三条路线各写一份」在这一课不存在,想跑就只有 Microsoft Foundry Models(原 GitHub Models 路线)这一条。

三段代码,只有前两段联网

这个 notebook 的结构是三段:一段 RAG、一段同一提示词换模型跑两次、一段本地 tokenizer 计数。

第一段建了两个 client,但共用同一套凭据:

client = ChatCompletionsClient(
    endpoint=endpoint,
    credential=AzureKeyCredential(token),
)

embed_client = EmbeddingsClient(
        endpoint=endpoint,
        credential=AzureKeyCredential(token)
)

ChatCompletionsClientEmbeddingsClient 都来自 azure.ai.inference,凭据类是 azure.core.credentials.AzureKeyCredential。要注意的是模型名不在 client 上client.complete(...)embed_client.embed(...) 都各自接一个 model= 参数。所以一个 endpoint、一把 key,chat 和 embedding 走的是同一个入口,只靠模型名区分。仓库示例里 chat 侧填的是 "Mistral-large",embedding 侧填的是 "cohere-embed-v3-multilingual"——这两个都只是仓库里当前写着的示例值,不是必须。

RAG 那段的检索部分也很朴素:先按 chunk_size = 2048 做定长切片,把 embed_response.data 里每条 item.embedding 堆成 numpy 数组,用 d = text_embeddings.shape[1] 拿到维度去建 faiss.IndexFlatL2(d),然后 index.search(question_embeddings.reshape(1, -1), k=2) 取回两条最近的切片拼进提示词。2048k=2 同样是仓库里的示例取值。这里没有任何重排、去重、元数据过滤,它就是给你看 RAG 骨架的最小形态。

第二段是把同一段提示词分别发给 "Mistral-small""Mistral-large",代码几乎逐行相同,只有 model_name 那一行不一样。课程正文对这两次调用的差异写了一些描述性说法,本文不转述——我们没有跑过这段代码。

第三段和前两段性质完全不同:它一个字节都不走 endpoint。

from mistral_common.tokens.tokenizers.mistral import MistralTokenizer

model_name = "open-mistral-nemo"
tokenizer = MistralTokenizer.from_model(model_name)

tokenized = tokenizer.encode_chat_completion(
    ChatCompletionRequest(
        tools=[...],
        messages=[UserMessage(content="What's the weather like today in Paris")],
        model=model_name,
    )
)
tokens, text = tokenized.tokens, tokenized.text
print(len(tokens))

这里的 UserMessage 来自 mistral_common.protocol.instruct.messages,和前两段那个来自 azure.ai.inference.modelsUserMessage 是同名不同包的两个类。notebook 里两段各自 import,互不冲突;但如果你把这个 notebook 的代码格拼进同一个脚本,这就是个现成的坑。

顺带说一句数 token 这件事在课程里的两副面孔:第 04 课的 githubmodels-assignment.ipynb 数 token 用的是 tiktoken,写法是 encoding = tiktoken.encoding_for_model("gpt-4o")encoding.encode(text);第 20 课这里换成了 mistral-common。两者不是一个东西的两种写法——tiktoken 那段数的是一段纯文本,encode_chat_completion 数的是把 system/user 消息和工具定义按对话模板拼装之后的完整序列,所以它才需要你把 tools=[...] 一起传进去。想比较「同一份工具定义在不同模型上占多少」,就得走后面这条路,前面那条数不出来。

同一段代码在下一格换成 "mistral-large-latest" 再跑一遍——注意这个模型名和联网那段用的 "Mistral-large" 写法并不一致,因为 MistralTokenizer.from_model() 认的是 Mistral 自己的模型标识,不是 Foundry 上的部署名。工具定义那部分用的是 Tool(function=Function(name=..., description=..., parameters={...})) 的嵌套结构,parameters 是一段 JSON Schema 风格的字典。

和前面几课的代码差在哪

这才是这一课最容易踩的地方。

采样参数。 第 20 课的 client.complete(...) 里老老实实带着 temperature=1.0, top_p=1.0, max_tokens=1000(这几个取值是仓库示例里当前写着的,不是推荐配置)。但第 06 课的 githubmodels-app.py 里,这几个参数都不在了,原地留了一行注释:# Note: gpt-5 reasoning models don't accept temperature/top_p; leave them at their defaults.;第 04 课的同名前缀 notebook 更进一步,把模型名改成了 os.environ.get("AZURE_INFERENCE_CHAT_MODEL", "Llama-3.3-70B-Instruct"),并注上「temperature/top_p need a non-reasoning model」。

为什么第 20 课能留?仓库自己写了:AGENTS.md 里逐字写着「Lessons 20 (Mistral) and 21 (Meta) keep temperature/max_tokens because they target Mistral/Llama models, which support them」,CHANGELOG.md 里另有一句措辞不同、意思相同的说明(仓库文档自述)。06-text-generation-apps/README.md 那一节说得更完整:当前 Foundry 上未废弃的模型是 reasoning 模型(GPT-5 家族、o 系列),不支持 temperature / top_p,也不支持 max_tokens(改用 max_output_tokens,传了会报参数不支持;而 temperature/top_p 在 Mistral、Llama、Phi 这类非 reasoning 模型上仍然有效。

所以结论很具体:第 20 课的写法是绑在 Mistral 模型上的,不是通用模板。 你把这段代码复制走、只把 model_name 换成 GPT-5 家族的某个模型,参数就会打架。

消息怎么构造。 第 20、21 课用 SystemMessage(content=...) / UserMessage(content=...) 对象;第 06 课的 githubmodels-app.py 和第 07 课的 githubmodels-assignment-simple.ipynb 用的是 {"role": "system", "content": ...} 这样的裸字典。两种写法都在仓库里活着,别以为课程统一过。

依赖怎么装。 第 20 课的安装格写的是不带百分号的 pip install faiss-cpupip install mistral-common,而第 21 课的 notebook 里是 %pip install azure-ai-inference 这种 magic 形式。另外第 20 课没有安装 azure-ai-inference 的那一格——它默认你已经按根目录 requirements.txt 装过了(那份清单里有 azure-ai-inference,但没有 faiss-cpumistral-common,所以这两格确实不能跳)。

.env 不会自动生效。 这条最容易在 Windows 上卡住。00-course-setup/02-setup-local.md 教你建 .env 并用 from dotenv import load_dotenv + load_dotenv() 载入;requirements.txt 里也有 python-dotenv。但翻遍这一系列 githubmodels-* 文件,没有一个调 load_dotenv(),它们直接 os.environ[...]——而调 load_dotenv() 的是 oai- / aoai- 那批 notebook。两处放在一起看,结论就是:你光建了 .env 而不在 notebook 里自己加载,os.environ["AZURE_INFERENCE_ENDPOINT"] 会直接抛 KeyError

Windows 侧怎么处理?仓库在建文件这一步给了 cmd 的写法 echo . > .env(Unix 侧是 touch .env)。至于让变量真正进程可见,以下是通用做法、不是该项目官方内容:要么在 notebook 顶部自己补上 load_dotenv(),要么在启动 Jupyter 的那个终端里先设好变量再启动——PowerShell 用 $env:AZURE_INFERENCE_ENDPOINT = "<你的 Foundry 项目 endpoint>",Linux / macOS 用 export。注意顺序:已经开着的 Jupyter 进程不会读到你事后设的变量,得重启内核所在的那个进程。

两处可以顺手改掉的地方

仓库里其实备了工具,只是这个 notebook 没用。

shared/python/env_utils.pyget_required_env(var_name, description=None),缺变量时抛的是带提示信息的 ValueError 而不是干巴巴的 KeyErrorshared/python/api_utils.pymake_safe_request(url, method="GET", timeout=30, retries=3, **kwargs)——而第 20 课那段拉素材文本的代码是裸的 requests.get(url),没有 timeout。这两个函数都在 shared/python/__init__.py__all__ 里导出,也被 tests/ 下的单测和 docs/SECURITY_GUIDELINES.md 引到,但各章课程示例代码里一处都没有接进去。

from shared.python.env_utils import get_required_env
from shared.python.api_utils import make_safe_request

endpoint = get_required_env("AZURE_INFERENCE_ENDPOINT", "Foundry project endpoint")
token = get_required_env("AZURE_INFERENCE_CREDENTIAL", "Foundry Models API key")
text = make_safe_request(source_url).text

以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。timeout=30retries=3 是仓库当前代码里的默认值,随版本可能变动。

最后几句提醒

20-mistral/README.md 和这个 notebook 内容高度重合,但不是逐字一致:notebook 里 Mistral NeMo 那节 markdown 出现了两格、措辞略有出入,README 里只有一处。这类小幅漂移说明两边是分别维护的,真要跑就以 .ipynb 为准。README 与 notebook 里还有一批关于上下文窗口、跑分、价格与响应快慢的描述,本文一律不转述——那些数字会变,而且我们没有跑过任何一段代码。

另外,前两段代码会把你的文档切片和问题发到云端 endpoint,会产生费用也会上传内容,动手前先确认你要喂进去的文本是不是能出内网。这个课程持续更新,上面提到的文件路径、依赖与接口写法都请以仓库最新内容为准。


本文依据 github.com/microsoft/generative-ai-for-beginners 仓库于 2026-08-18 的公开内容整理, 事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码, 因此不涉及运行结果、耗时与生成质量的任何描述。 该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。 文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。

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

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