跟微软生成式 AI 入门课写第一个文本生成应用:oai-app.py 逐段拆开
克隆下 generative-ai-for-beginners 之后,第一个真正要动手跑的地方是 06-text-generation-apps/。打开它的 python/ 目录,你会看到一串文件名长得很像的脚本:oai-app.py、aoai-app.py、githubmodels-app.py,后面还各自跟着 -recipe、-history-bot、-study-buddy 的变体。新手最常见的卡点不是不会写代码,而是随手打开了一个跟自己手上凭证对不上的文件,然后对着一串环境变量报错发呆。
这篇只走一条线:06-text-generation-apps/python/oai-app.py,也就是 OpenAI 那条路线。文件很短,但每一行背后都有一个能踩的坑。
先分清你在哪条路线上
课程仓把 provider 写在了文件名前缀里。00-course-setup/03-providers.md 给了一张对照:aoai 需要 Azure OpenAI 的 endpoint 和 key,oai 那一行写的是 OpenAI 的 endpoint 和 key(但 .env.copy 的 OpenAI 段里实际只列了一个键,下面会说到),hf 需要 Hugging Face token,githubmodels 需要 Microsoft Foundry Models 的 endpoint 和 key。同一份文档里还写明,GitHub Models 正在退役,Microsoft Foundry Models 是它的直接替代。
这个前缀不是装饰。同一课的 githubmodels-app.py 里用的根本不是 openai 这个库,而是 azure.ai.inference 的 ChatCompletionsClient,调用方法叫 client.complete(...);而 oai-app.py 和 aoai-app.py 用的是 openai 的 OpenAI 客户端和 client.responses.create(...)。三条线的 import、客户端类、调用方法、环境变量全都不一样,混着抄必然报错。认准前缀,然后从头到尾只读那一个文件。
前置条件
第 06 课 README 的练习环节给的步骤是建虚拟环境再装库:
python -m venv venv
source venv/bin/activate
pip install openai
README 在这里专门补了一条 Windows 说明:Windows 下把 source venv/bin/activate 换成 venv\Scripts\activate。这条别跳,Git Bash 里习惯性敲 source 的人会在这里愣一下。
依赖上有个小不一致值得先知道:README 正文只写了 pip install openai,但 06-text-generation-apps/python/requirements.txt 里同时钉了 openai 和 python-dotenv 两个包,且都锁到了具体版本。oai-app.py 开头第三行就是 from dotenv import load_dotenv,只按 README 那句装是不够的——按目录里的 requirements.txt 装才对得上。仓库还在持续更新,具体依赖以仓库最新内容为准。
凭证这边,仓库根目录有一个 .env.copy 模板,00-course-setup/03-providers.md 给的动作是把它复制成 .env:
cp .env.copy .env
同一份文档写明 .env 是被 gitignore 掉的。模板里 OpenAI 那条路线只需要一个键:OPENAI_API_KEY。Windows 侧仓库没有给对应的复制命令说明;PowerShell 里 cp 是 Copy-Item 的别名、CMD 里则要用 copy——这一句是通用做法,不是该项目官方内容,照做前请自行确认。
逐段拆 oai-app.py
客户端初始化
from openai import OpenAI
import os
from dotenv import load_dotenv
# load environment variables from .env file
load_dotenv()
# configure OpenAI service client
client = OpenAI()
这里有两处值得停一下。
第一处,OpenAI() 是无参构造,文件里没有把 key 传进去。仓库对这个写法没有再多做解释,但可以把三处并起来看:.env.copy 里 OpenAI 路线的键名是 OPENAI_API_KEY;README 在”定位 API key”那一节明确要求把这个环境变量设成你的 key;而 README 后面”Improve your setup”一节给出的写法是显式传参:
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
两种写法在仓库里同时存在,指向的都是 OPENAI_API_KEY 这个键。你要是排查”key 没生效”,先确认 .env 里的键名一个字母都没写错。
第二处,oai-app.py 里 import os 之后并没有用到 os——上面那段显式写法里 os 才有用武之地。这不影响运行,但你照抄时不必疑惑自己漏了什么。
模型标识:一个残留的变量名
deployment = "gpt-5-mini"
变量名叫 deployment,这是 Azure 那边的说法。README 的练习环节有一条 note 说得很清楚:如果你用的是纯 OpenAI 而不是 Azure,客户端不传 base_url,并且传的是模型名而不是 deployment 名。也就是说,在 oai-app.py 这条线上,这个变量装的是模型名,名字只是从 Azure 版本抄过来没改。
同一目录下的 oai-app-recipe.py 和 oai-study-buddy.py 更明显——它们的注释写着 # configure Azure OpenAI service client,但代码是 client = OpenAI(),走的是 OpenAI 路线。注释和代码不一致时以代码为准,这几处属于同源文件复制留下的痕迹。
至于 "gpt-5-mini",它只是仓库示例里当前填的那个值,不是必须。换成你账号下可用的模型名即可。
请求:input 与消息结构
prompt = "Complete the following: Once upon a time there was a"
# make a request using the Responses API
response = client.responses.create(model=deployment, input=prompt, store=False)
课程走的是 Responses API,入口是 client.responses.create。这里的 input 直接收了一个字符串。
消息结构上,仓库里有一处需要看清。README 的”Multi-turn conversations”小节文字上说,多轮时在 input 里给一个消息列表;但紧跟其后的那段示例代码,传的仍然是 input="Hello world" 这样一个字符串,并没有演示列表形态。真正把列表形态写出来的,是 .github/skills/azure-openai-to-responses/references/cheat-sheet.md,它的多轮示例里 input 是一个 {"role": ..., "content": ...} 的数组,每轮把上一次的 response.output_text 以 assistant 角色 append 回去,再连同新的用户消息一起发下一次。两处摆在一起就清楚了:单轮传字符串、多轮传消息数组,README 的例子只覆盖了前者。
store=False 这个参数三条 OpenAI 系的示例都带着。仓库里对它的说明在 .github/skills/azure-openai-to-responses/SKILL.md 的迁移清单里:每次请求都设 store=False,对话状态由客户端自己维护;同一份文件同时写明,用 previous_response_id 做多轮的那条路子需要 store=True。两者是同一个开关的两面,选了 store=False 就得自己拼消息数组。
参数:max_output_tokens 与不能乱传的 temperature
oai-app.py 本身没传别的参数,但同目录的 oai-app-recipe.py 加了 max_output_tokens。这个参数名同样出自上面那份 SKILL.md 的迁移映射:老的 max_tokens 在 Responses API 里改叫 max_output_tokens。
这里也有一处不一致:README 正文讲购物清单那一步时,写的是”这次我们把 max_output_tokens 设成 1200”,而 oai-app-recipe.py 里两处实际写的是 600。都是仓库里的示例值,照抄哪个都能跑通逻辑,但别以为文档和代码是逐字对应的。
输出取值路径
# print response
print(response.output_text)
Responses API 的文本结果在 response.output_text 上。SKILL.md 的迁移映射里给了对照:老写法 resp.choices[0].message.content 一律换成 resp.output_text,并把”零匹配 choices[0]”列为迁移的验收条件之一。你如果是从旧教程转过来的,手指头会习惯性去敲 choices[0],这就是要改的地方。
另外,output_text 不保证有内容。oai-app-recipe.py 里的写法是先取出来再判空:
old_prompt_result = response.output_text
if not old_prompt_result:
print("No response received.")
else:
print(old_prompt_result)
这是仓库代码自己做的防护,值得抄进你的第一个脚本里。
边界:仓库里写明不支持的那些
- reasoning 模型不吃
temperature。 第 06 课 README 明确写着:Microsoft Foundry 上当前非弃用的模型是 reasoning 模型(GPT-5 家族、o 系列),它们不支持temperature和top_p,也不支持max_tokens(要用max_output_tokens);把temperature发给gpt-5-mini会拿到 “parameter not supported” 的错误。但同一篇 README 末尾的 Challenge 又让你把 temperature 依次设成 0、0.5、1 去试——这两处在仓库里是白纸黑字打架的。README 自己给的出路是:想试采样参数,就换一个仍支持采样控制的模型走 Foundry Models 那条线。 - GitHub Models 那条线不支持 Responses API。 SKILL.md 的”Model compatibility”一节写明
models.github.ai、models.inference.ai.azure.com不支持 Responses API,要求把这些代码路径移除。所以别拿githubmodels-*的凭证去套responses.create。 AzureOpenAI/AsyncAzureOpenAI构造器已标 deprecated。 同一份 SKILL.md 写明这两个构造器被弃用,改用OpenAI客户端。这也是为什么aoai-app.py现在用的也是OpenAI(...)而不是AzureOpenAI(...)——不过 Azure 那条线的具体配置不在本文范围,要走那条线请直接读aoai-app.py。- 数据边界。 这段代码会把你的 prompt 发到云端服务,密钥放
.env只是防止提交进 git,不等于数据不出本机。这一点请自行评估。
怎么验证配对了
第一步验证凭证有没有被读进环境变量。可以先不发请求,只确认键存在:
import os
from dotenv import load_dotenv
load_dotenv()
print("OPENAI_API_KEY" in os.environ)
以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。注意只打印布尔值,别把 key 本身打出来。
第二步,直接执行 oai-app.py。这个文件有个方便的地方:README 练习那一节给了预期输出的样子,而 oai-app.py 文件末尾把同样两行作为注释留在了源码里——上面那句 prompt 是 “Complete the following: Once upon a time there was a”,注释里留的是它被补全后的样子。你拿到的续写内容不会和注释逐字相同(生成本来就有随机性),但如果你看到的是一段自然的续写而不是 traceback,这条链路就通了。
第三步,如果报的是鉴权类错误,回头查三个点:.env 是不是真的从 .env.copy 复制并填了值、键名是不是 OPENAI_API_KEY、脚本里 load_dotenv() 是不是在构造 OpenAI() 之前调用的。oai-app.py 里这个顺序是先 load_dotenv() 后 OpenAI(),抄的时候别把两行调换。
把这二十来行读透,后面 oai-history-bot.py、oai-study-buddy.py 那几个变体就没有新东西了——它们的差别只在 prompt 怎么拼:一个用 input() 收人物设定和问题,一个用三段式格式约束回答结构,客户端初始化、responses.create、output_text 这条主干一模一样。
本文依据 github.com/microsoft/generative-ai-for-beginners 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码,
因此不涉及运行结果、耗时与生成质量的任何描述。
该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。
文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。