可信 Agent 怎么写:微软 AI Agent 入门课第 6 课的系统消息框架

2026-08-18

写 agent 的人多半都碰到过这个场景:三个人各写了一版 system message,谁也说不清哪版更好,改一个字要重新读三百行提示词,新加一个 agent 又得从零抄一遍。ai-agents-for-beginners 第 6 课 06-building-trustworthy-agents/README.md 里,最能直接搬走的不是后面那张威胁清单,而是开头 Safety 一节的 Building a System Message Framework——它给的是一套「用 LLM 批量生产 system message」的流程,配套代码在 06-building-trustworthy-agents/code_samples/06-system-message-framework.ipynb

这篇就沿着这个 README 小节和那个 notebook 走一遍,看清每一格在改什么。

框架的四步:把提示词当产物,而不是当手稿

README 把这个框架拆成四步(Step 1 到 Step 4),核心是把「写提示词」变成「用一段元提示词去生成提示词」。

Step 1 是 meta system message。 README 给的原文是这样一段 plaintext:

You are an expert at creating AI agent assistants. 
You will be provided a company name, role, responsibilities and other
information that you will use to provide a system prompt for.
To create the system prompt, be descriptive as possible and provide a structure that a system using an LLM can better understand the role and responsibilities of the AI assistant. 

注意它的输入契约写得很死:company name、role、responsibilities。这三样后面会原样变成 notebook 里的三个变量,不是随口举例。

Step 2 是 basic prompt,也就是你手写的那一句粗描述。README 的示例是一个 Contoso Travel 的订票 agent,把 lookup available flights、book flights、ask for preferences、cancel、alert 这些任务平铺在一句话里。

Step 3 把前两者拼起来发给 LLM:meta system message 当 system,basic prompt 当输入。README 直接贴出了产出示例,这一段才是「结构化写法」的正题——它产出的不是一段散文,而是一份带固定骨架的文档:Company NameRoleObjectiveKey Responsibilities(下面再按 Flight Lookup / Flight Booking / Customer Preference Inquiry / Flight Cancellation / Flight Monitoring 分条,每条各带若干行动作描述)、Tone and StyleUser Interaction InstructionsAdditional Notes

这个骨架的价值在于它可 diff。散文式提示词改一个词,你只能整段重读;这种分节结构改动落在哪一节一目了然,多个 agent 之间还能按节比对。

Step 4 是迭代。README 自述这套框架的价值在于既方便批量生成,也方便随时间改进,并且明说「很少有一版 system message 第一次就适配完整用例」,做法是改 basic prompt 再跑一遍去对比评估。请留意:这一步在仓库里是人工动作,没有配套代码。

notebook 逐格:请求是怎么发出去的

06-system-message-framework.ipynb 只有四个 code cell,没有 markdown 说明格。

第一格是环境。它 load_dotenv(),然后读两个变量:

endpoint = os.environ["AZURE_OPENAI_ENDPOINT"]
deployment = os.environ["AZURE_OPENAI_DEPLOYMENT"]

这一格的注释值得单独看一眼,原文写明这个示例走 Azure OpenAI 的 Responses API(稳定的 /openai/v1/ 端点),并注明 GitHub Models 已弃用(retiring July 2026)且不支持 Responses API,所以改为直接调 Azure OpenAI。00-course-setup/README.md 里也有对应说明:第 6 课和第 8 课的部分 notebook 之前用的是 GitHub Models。如果你手上还留着老版本的 GitHub Models 写法,这一课是已经换过路线的,别照着旧笔记配。

第二格是鉴权,走的是无密钥路线:

token_provider = get_bearer_token_provider(
    DefaultAzureCredential(),
    "https://cognitiveservices.azure.com/.default",
)

client = OpenAI(
    base_url=f"{endpoint.rstrip('/')}/openai/v1/",
    api_key=token_provider,
)

这里有两处容易踩:一是它把 get_bearer_token_provider 返回的东西直接放进 api_key;二是这一格完全没有读 AZURE_OPENAI_API_KEY 的分支。.env.exampleAZURE_OPENAI_API_KEY 是有的,但 00-course-setup/README.md 的变量表把它标为 Optional——只在你用 key 方式而非 az login / Entra ID 时才需要。也就是说:光在 .env 里填了 key,这个 notebook 的代码路径也不会去用它,仓库文档给的前置动作是先跑 az login

第三格就是那三个变量,与 meta 提示词的输入契约一一对应:

role = "travel agent"
company = "contoso travel"
responsibility = "booking flights"

第四格发请求,meta 提示词进 system,三个变量拼进 user

response = client.responses.create(
    model=deployment,
    input=[
        {"role": "system", "content": """..."""},
        {"role": "user", "content": f"You are {role} at {company} that is responsible for {responsibility}."},
    ],
    # Optional parameters
    temperature=1.0,
    max_output_tokens=1000,
    top_p=1.0,
    store=False,
)

print(response.output_text)

这里有个和 README 对不上的地方,值得知道:README 的 Step 2 basic prompt 是一整句带任务枚举的描述,而 notebook 把它压成了模板句 You are {role} at {company} that is responsible for {responsibility}.,任务枚举没了。两处不一致本身不影响你用,但如果你照 notebook 的模板句去生成,输入信息量比 README 示例少一截,产出自然也会更薄——想复现 README 那份骨架,得把 responsibilities 写细。

那几个「Optional parameters」不是随手填的

那一格里 # Optional parameters 下面的几个参数,写的都是仓库示例里的取值,但它们各有出处,改之前最好知道:

  • store=False。仓库 .agents/skills/azure-openai-to-responses/references/cheat-sheet.md 里写明,用 previous_response_id 做多轮串联需要 store=True(默认),会话会存在服务端;手动维护 input 数组这条路则不需要服务端存储,配 store=False。放在「可信 Agent」这一课里,这就是一个明摆着的数据边界选择。
  • max_output_tokens。同一份 cheat-sheet 与 references/troubleshooting.md 都写明:reasoning 模型的内部 reasoning token 也会计入这个上限,按老习惯设一个很小的值,症状是 output_text 空掉或者输出被截断。notebook 里那个具体取值只是仓库示例里的写法,别当成推荐配置抄走。
  • temperature=1.0。同一目录下的 SKILL.mdreferences/troubleshooting.md 写明:GPT-5 与 o 系列模型的 temperature 必须省略或设为 1,给别的值会报参数错误。而 .env.exampleAZURE_OPENAI_DEPLOYMENT 的示例值恰好是 gpt-5-mini。所以这个 1.0 是跟着模型走的,不是随手写的中间值——照着老习惯调低去「让输出更稳定」,按仓库自己的排错表就是一条报错。
  • top_p=1.0。这一条要和上一条一起看:SKILL.md 的 O-series 一节写明 top_p 在 o 系列模型上不受支持、应当移除。notebook 把它显式写在了参数里,所以你要是把 deployment 换成 o 系列,按仓库文档这一行就该删掉,而不是照抄。两处说法我们只并列,不替作者解释它为什么留在示例里。

以上参数含义均来自仓库文档,未经实测,以仓库最新代码为准。另外 troubleshooting.md 还提到 output_text 是 SDK 的便捷属性,直接打 REST 的话原始 JSON 里没有这个字段,文本在 output[0].content[0].text——你要把生成的 system message 落盘做版本管理时会用到这一点。

「校验」这一环,仓库里在哪

按这一课的名字,你可能期待 notebook 里有一格拿产出去做安全检查。没有。 这个 notebook 的最后一个动作就是 print(response.output_text),既没有断言,也没有对生成的 system message 做过滤或评分。README 威胁一节讲的那些缓解手段(输入校验与过滤、限制对话轮数、按需最小授权、在 Docker 容器这类受限环境里执行、失败回退与重试)在这个 notebook 里都没有对应代码。

真正带判定逻辑的是同一课的另一个 notebook code_samples/06-human-in-the-loop.ipynb:它自己的 markdown 说明写明 README 那段 APPROVE / REJECT 片段属于 post-action(agent 已经跑完才问人),而它实现的是 pre-action gate,代码里有 classify_risk() 做低/中/高三档分级、gate_action() 做闸口、log_decision() 把每次判定按 JSONL 追加成审计日志。它的 classify_risk 注释还明说未识别的动作默认落到 medium 而不是 low。这两个 notebook 的分工要分清:一个负责产出提示词,一个负责运行期的人工闸口。

另一层「校验」是 notebook 本身能不能跑通,这一层仓库是有工具的:scripts/validate-notebooks.ps1,说明在 .agents/skills/testing-course-samples/SKILL.md。它用 nbconvert 把每个 Python notebook 无头执行一遍,打出 PASS/FAIL 矩阵,并把执行后的副本、逐 notebook 日志和 results.json 写到输出目录。

pwsh scripts/validate-notebooks.ps1 -List
pwsh scripts/validate-notebooks.ps1 -Filter '08-*' -Timeout 600

上面两条是仓库文档里的原文示例,未经实测;-Filter 的语义按文档是对 notebook 的仓库相对路径做通配,换成本课的目录前缀即可,具体以仓库最新内容为准。

对 Windows 用户有两处仓库明写的细节:默认输出目录写作 $env:TEMP\aiab-nbval-Python 参数的文档说明就是给「python 不在 PATH(例如 Windows Store 别名)」这种情况用的。另外文档提到,人工介入那格挂住时会以 StdinNotImplementedError 的形式暴露,而不是一直卡着,这靠的是按格计时的 -Timeout(文档写的默认值是 300 秒,这是仓库当前文档里的默认值,随版本可能变动)。

前置条件方面,00-course-setup/README.md 要求 Python 3.12+,虚拟环境激活在 zsh/bash 下是 source venv/bin/activate,Windows 命令提示符下是 venv\Scripts\activate;跑校验脚本还要额外装 nbconvert 与 ipykernel;.env 至少要有 AZURE_OPENAI_ENDPOINTAZURE_OPENAI_DEPLOYMENT,并先完成 az login

拿走之后怎么用

如果只搬一件事,我会搬 Step 1 那段 meta system message 和它的三变量契约:把它存成仓库里的一个文件,把 role / company / responsibility 做成一张表,产出的分节文档按 agent 落成独立文件进版本库。这样每次调整都变成一次可 diff 的改动,而不是又一版谁也说不清差在哪的提示词。

至于校验,仓库给的现状要认清:生成端没有自动校验,评估靠 Step 4 的人工迭代;运行期的把关代码在人工介入那个 notebook 里;notebook 能否跑通则交给那个 PowerShell 脚本。这三层是分开的,别指望其中一个覆盖另外两个。这套课程持续更新,上面涉及的文件路径、变量名与参数写法都可能随版本变动,动手前请以仓库最新内容为准。


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

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

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