本地还是云端:微软 AI Agent 入门课第 17 课与第 16 课的取舍

2026-08-18

ai-agents-for-beginners 里第 16 课和第 17 课是背靠背的一对:16-deploying-scalable-agents/ 把 Agent 推到 Microsoft Foundry 的托管服务上,17-creating-local-ai-agents/ 把它拉回一台开发机。两课的 README 互相点名(第 16 课末尾写下一课要「反向走一遍」,第 17 课收尾也写了上一课是把 Agent 往上扩、这一课把它缩到一台工作站),所以很多人会当成一道选择题来问:我该走哪条。

这篇不回答哪条更好。真正会咬到你的是三件更琐碎的事:要先装什么、缺哪个配置会直接崩、伸缩这一层的代码在不在。这三件事两课都白纸黑字写了,可以对照;其余的(速度、成本金额、模型输出质量)我们没有跑过任何一课的代码,一律不比。

维度一:依赖——你先得有账号,还是先得有机器

先看两个 notebook 的第一行安装命令,这是最直白的分界。

第 16 课 16-deploying-scalable-agents/code_samples/16-python-agent-framework.ipynb 里是:

%pip install agent-framework azure-ai-projects azure-identity python-dotenv -q

第 17 课 17-creating-local-ai-agents/code_samples/17-local-agent-foundry-local.ipynb 里是:

%pip install foundry-local-sdk openai chromadb -q

两条命令没有一个包重合。往下看导入就更清楚:第 16 课导入的是 from agent_framework import toolfrom agent_framework.foundry import FoundryChatClientfrom azure.identity import AzureCliCredential;第 17 课导入的是 from foundry_local import FoundryLocalManagerfrom openai import OpenAIimport chromadb

pip 之外的前置条件差得更远。第 16 课 README 的 Prerequisites 一节写明需要一个 Azure 订阅、一个至少部署了一个 chat 模型的 Microsoft Foundry 项目,以及已经 az login 过的 Azure CLI——也就是说,你在敲第一行代码之前,得先有一个开通了服务的云账号。第 17 课的前置条件换成了另一类东西:装好 Microsoft Foundry Local,README 给的安装示例里 Windows 一行是

winget install Microsoft.FoundryLocal      # Windows

macOS 那行 brew install microsoft/foundrylocal/foundrylocal 在 README 里是注释掉的,原文提示按你的操作系统查阅文档。安装之后 README 列的两条命令是 foundry model run qwen2.5-7b-instructfoundry service status。以上均为仓库文档中的原文,未经实测,以仓库最新内容为准。

硬件这一侧只有第 17 课有说法:README 的前置条件里写明 8 GB 内存是一个现实的最低线、16 GB 以上更从容,GPU 或 NPU 有帮助但不是必需。这是仓库文档自己写的前置条件,不是我们量出来的;第 16 课的前置条件那一节我们没有找到对应的本机硬件要求,它列的是订阅、Foundry 项目与 Azure CLI 这三样。

所以依赖这一维的决策路径很短:你缺的是云账号还是缺的是内存。缺账号(企业没开、报销走不通、数据不许出网),第 16 课的 notebook 你连第一个 cell 都过不去;机器紧张,第 17 课的前置条件就先拦住你。

维度二:配置——缺哪个是崩,缺哪个只是降级

这一维最容易踩,因为两课都设计了「缺了也能跑」的路径,但各自把硬性和可选的线划在了不同位置

第 16 课的 notebook 里,两个环境变量是硬的:

endpoint = os.getenv("AZURE_AI_PROJECT_ENDPOINT")
model = os.getenv("AZURE_AI_MODEL_DEPLOYMENT_NAME")

if not endpoint or not model:
    raise ValueError(
        "Missing required environment variables. "
        "Please set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME in your .env file."
    )

少一个就 raise,没有兜底。而 RAG 那一段是软的:notebook 用 USE_AZURE_SEARCH = bool(os.getenv("AZURE_SEARCH_SERVICE_ENDPOINT") and os.getenv("AZURE_SEARCH_API_KEY")) 判断,两个都有就走 _azure_search(),否则退回 _in_memory_search() 这个关键词匹配的内存实现,README 明说这是为了没有 Search 资源也能跑完。索引名走 os.getenv("AZURE_SEARCH_INDEX_NAME", "contoso-policies")contoso-policies 是仓库当前代码里的默认值,随版本可能变动。路由用的两个模型名同样是软的:AZURE_AI_SMALL_MODELAZURE_AI_LARGE_MODEL 没设就都回落到那个必填的 model

第 17 课反过来——它一个必填环境变量都没有。模型别名直接写在代码里(MODEL_ALIAS = "qwen2.5-7b-instruct",这是仓库示例里的取值),端点和密钥由 SDK 自己给:

manager = FoundryLocalManager(MODEL_ALIAS)
model_info = manager.get_model_info(MODEL_ALIAS)

client = OpenAI(
    base_url=manager.endpoint,   # e.g. http://localhost:PORT/v1
    api_key=manager.api_key,     # local placeholder
)

README 特意写了一句:notebook 用 foundry-local-sdk 自动发现端点,所以你不用把端口写死。manager.api_key 在注释里被标为本地占位符——这一课不涉及云端凭据。唯一那个可选变量是 LOCAL_MCP_COMMAND,代码里写的是没设就打印一句提示、跳过 MCP 那一节,README 说明这样其余部分不受影响。

这层差异什么时候咬你:第 16 课的失败会发生在你还没写业务代码的时候,是「配置没到位」这类明确报错;第 17 课的失败点被挪到了配置之外——你没装 Foundry Local、没把模型拉下来,FoundryLocalManager(MODEL_ALIAS) 那一行才会出问题,而这不是一个环境变量能补的。要在团队里推,第 16 课需要发一份 .env 说明,第 17 课需要发一份装机说明。

顺带一提凭据形态也不同:第 16 课是 AzureCliCredential(),也就是复用你本机 az login 的身份;第 16 课 README 讲到生产时写明托管形态应换成带作用域 RBAC 的托管标识,而不是继续用开发者的 az login 令牌。这两句在同一课里,别混着用。

维度三:伸缩——这一层的代码只有一课有

第 16 课把伸缩直接写进了请求处理函数。handle_support_request() 的四步在代码里是按顺序落下来的:先查缓存(response_cache 是个 dict,键由 normalize()re.sub(r"\s+", " ", query.lower().strip()) 归一化),再按复杂度选模型(choose_model()is_simple(),后者先看 query 里有没有 COMPLEX_SIGNALS 里的词,再看词数),然后把整段执行包进 tracer.start_as_current_span("support_request"),并 span.set_attribute("routed.model", chosen_model),最后写回缓存。计数器 route_counters 记录 small / large / cache 三路各走了多少。

notebook 里还有一句值得单独看的注释:# Build one agent per model tier so we can route by cost. The current agent-framework selects the model on the client, so each tier gets its own FoundryChatClient.——模型是在 client 上选的,所以 agent_for() 会按模型名缓存出多个 FoundryChatClient,每个再 client.as_agent(name="ContosoSupportAgent", instructions=..., tools=...)。注释里的「current」是作者自己写的限定,这个结构随框架版本可能变。

围绕伸缩的另外两件东西也只有第 16 课有:一是 @tool(approval_mode="always_require") 装饰在 issue_refund 上、@tool(approval_mode="never_require") 装饰在查询类工具上,人工审批是框架的一个参数而不是你自己写的分支;二是部署后的冒烟测试,tests/lesson-16-smoke-tests.json 是提示词与断言的清单,.github/workflows/smoke-test.yml 里用 azure/login@v2 走 OIDC 登录后调用 JFolberth/ai-smoketest@v1,workflow 头部的注释写明联合身份需要在 Foundry 项目范围上有 Azure AI User 角色,触发方式是 workflow_dispatch 手动跑。

第 17 课这一层是空的。它的 README 里确实有一张 Hybrid Cloud-and-Local Patterns 表,写「敏感或离线走本地 SLM、需要多跳推理的非敏感任务走云端模型」,并且明说这是第 16 课模型路由思路的延伸;但第 17 课的 notebook 里没有任何路由代码。那份 notebook 的循环是手写的 run_agent(user_query: str, max_iterations: int = 5):调 client.chat.completions.create(...) 带上 TOOLS_SCHEMA,没有 tool_calls 就返回,有就逐个查 TOOL_IMPL 执行再把结果以 role: "tool" 追加回去,跑满次数就返回 "Stopped: reached max tool-calling iterations."max_iterations5 是仓库当前代码里的默认值,随版本可能变动。

也就是说:伸缩这一维没法对称地比。第 16 课有实现,第 17 课只有文字设计建议,对照到这里就该停——我们不去替第 17 课推断「本地能扛多少并发」,仓库里没有这个说法。

两处 README 与 notebook 对不上的地方

翻这两课时有两个地方值得你留个心眼,都是把仓库里两处原文摆在一起就能看到的。

第一处,评估门的阈值。 第 16 课 README 正文里的示例签名是 async def evaluation_gate(agent, test_cases, threshold: float = 0.8),里面判定单条用例通过的写法是 if score_response(result.text, case["expected"]) >= 0.8。而 notebook 里的实现签名是 async def evaluation_gate(test_cases: list[dict], threshold: float = 0.8)(没有 agent 参数,直接用全局的 support_agent),单条用例的判定写的是 if s >= 0.5。两处的 threshold 都是 0.8,但单条用例的及格线一处是 0.8、一处是 0.5。照 README 抄进 CI,和照 notebook 抄进 CI,放行标准不是一回事。这是我们把两处原文并排看出来的差异,不去猜哪个是作者的本意。

第二处,「换个 base_url 就行」这句话的适用范围。 第 17 课 README 写明 Foundry Local 暴露 OpenAI 兼容端点,「OpenAI SDK 和 Microsoft Agent Framework 的 OpenAI client 只要改 base_url 就能工作」。但同一课的 notebook 里没有导入 agent_framework,它用的是 OpenAI SDK 加一段手写的工具调用循环。所以第 16 课那套 @tool 装饰器、as_agent()approval_modeget_tracer() 的写法,在第 17 课的示例代码里是没有对应演示的。你想把第 16 课的 Agent 原样挪到本地,README 给了这条路,但仓库里没有把它走完的样例——这一点你得自己验。

倒推:从你的处境到该翻哪一课

  • 数据不许出网、或者你要在没有网的环境里演示 → 第 17 课。它的整条链路(embedding、Chroma 向量库、检索、SLM)README 都写明在本机,read_file / analyze_code 这类工具还带一个 _safe_path() 沙箱检查,把路径限死在 PROJECT_ROOT 内。
  • 你要交付的是「多人同时用、要能查、要能回滚」的东西 → 第 16 课。缓存、路由、trace 属性、评估门、冒烟测试这五样在它的代码与工作流文件里都能翻到实体,第 17 课没有对应物。
  • 你只是想先把工具调用循环搞明白 → 第 17 课的 run_agent() 更适合读,它没有框架挡在中间;第 16 课的循环被 Agent Framework 收走了,你看到的是 await agent_for(chosen_model).run(prompt) 一行。
  • 你关心的是速度、月账单、答得准不准 → 这三样我们不比。第 17 课 README 的作业第 4 步恰好让你自己给三次回答计时,并判断这个延迟对你的工作流可不可接受——课程把这件事留给了你的机器,我们也不替你回答。

最后一句提醒:第 17 课 README 关于本地 MCP 的那段写得很直接——MCP server 跑在本机不等于安全,它以你的用户权限运行,要把它的作用范围限在一个项目目录而不是整个用户目录,并且把它的输出当作需要校验的输入。第 16 课那侧的说法是把 MCP server 当作不受信任的边界:固定版本、给受限身份、校验输出、不要把密钥交给它。这两句在两课里各说一遍,方向是一致的。


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

本文对照的是同一项目内的两种用法,依据均为上述仓库内容,不对两种用法做优劣排名, 选型结论只在仓库文档写明的能力边界内成立。

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

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