对话越滚越长:微软 AI Agent 入门课第 12 课的摘要接在哪一步
多轮对话跑久了会撞上同一个坎:每一轮都把完整历史重新发一遍,prompt 部分越滚越大,最后要么被截断要么直接报错。ai-agents-for-beginners 第 12 课给的答案是「摘要工具 + scratchpad」,代码在 12-context-engineering/code_samples/12-chat_summarization.ipynb。
真正让人卡住的是照着做完之后:工具挂上去了,agent 也确实调了,但会话历史看上去一点没短。这篇就沿着这个 notebook 把三件事定位清楚——摘要在哪触发、摘走的是哪一段、摘完的结果以什么形式回到对话里。
现象
典型症状有三种,长得不一样但根子在同一处:
- 加了
summarize_preferences之后,token 用量该涨还是涨,甚至因为多了一次工具往返而更高; - agent 有时候调摘要工具,有时候不调,同样的对话跑两遍行为不一致;
- 摘要”记住”的东西下一次开新会话就没了,
vacation_agent_scratchpad.md那个文件也没被更新过。
怎么确认是这个问题
别猜,四个动作按顺序做一遍就能定位。
第一,看工具函数体。 notebook 里的定义是:
@tool(approval_mode="never_require")
def summarize_preferences(conversation_notes: str) -> str:
"""Summarize accumulated user preferences into a compact format."""
return f"[SUMMARY] User preferences recorded: {conversation_notes}"
函数体只有一个 return。它没有读 session、没有删消息、没有写文件——它把传进来的字符串加了个前缀又还了回去。所以「摘要」这一步在代码里是纯字符串加工,历史缩短不缩短跟它无关。
第二,在 notebook 里搜 open(。 搜不到。datetime 与 pathlib.Path 在导入 cell 里出现过,此后全篇再没用到。同目录下的 vacation_agent_scratchpad.md 有 ## User Preferences 和 ## Completed Tasks 两个小节、条目前面带时间戳,形状很像持久化产物——但它是仓库里的一份示例文件,不是这个 notebook 写出来的。
第三,找触发点在哪。 不在框架里,在自然语言里。挂工具的那个 agent 是这样建的:
summarizing_agent = client.as_agent(
name="SummarizingTravelAgent",
instructions="""...
CONTEXT MANAGEMENT RULES:
1. After gathering several user preferences, call summarize_preferences() to record a compact summary
...""",
tools=[summarize_preferences],
)
「攒够几条偏好之后调一下」是写在 instructions 里的一句话,没有轮次阈值、没有 token 阈值、没有回调。更直白的证据在演示 cell:那条用户消息末尾直接写着 Please record these preferences using your summarization tool.——示例是靠用户开口点名工具来保证它被调到的。上面第二种症状(时调时不调)到这里就解释清楚了。
第四,读 notebook 自己的收尾。 最后一个 markdown cell 的 Next Steps 是一份待办清单,其中和这里直接相关的有两条:把 scratchpad 做成带文件持久化的完整工具,以及在摘要之后加上自动的历史截断。也就是说,「摘完把旧消息丢掉」这一步,仓库自述还没实现。
approval_mode 这个参数的语义在第 2 课的 notebook 里有一句说明:approval_mode="never_require" 表示工具会自动执行、不需要用户确认。它管的是要不要人工放行,不管什么时候调。
三个落点,逐个说清
触发位置:模型侧。约束只有 instructions 里的那条自然语言规则,加上示例对话里用户的明示请求。代码里没有任何按轮数或按 token 判断的分支。
被摘走的范围:由签名决定——summarize_preferences(conversation_notes: str),摘的是模型自己拼进 conversation_notes 的那段文字。代码从头到尾没有读取过 session 对象,所以「哪几轮被摘、哪几轮保留」在这个示例里没有确定性边界,完全取决于模型当次写了什么。
回填形式:工具的返回值 [SUMMARY] User preferences recorded: ... 作为一次工具结果回到同一条会话里。工具结果是往消息序列里加而不是换掉旧消息这件事,仓库在第 4 课把手写循环摊开来写过——04-tool-use/README.md 里那段不走框架的代码是 messages.append({"type": "function_call_output", ...}),然后带着这份更长的 messages 再发一次请求。回到第 12 课:notebook 里既没有出现删除旧消息的代码,也没有出现替换会话内容的代码,摘要还多占了一段——这就是第一种症状的来源。
notebook 的正文里写着「older messages can be safely removed because the summary captures what matters」(旧消息可以安全移除)。把这句话和上面的函数体放在一起看:这是在描述这个模式成立之后的效果,而移除动作本身,代码里没有。两处并不矛盾,但读的时候容易把前者当成后者已经做了。
处置:仓库把这件事拆到了哪几层
第 12 课的 README 在「Managing Context」一节列了六种做法,跟这里直接相关的有三条:
| 做法 | README 的说明要点 |
|---|---|
| Agent Scratchpad | 单次会话内的笔记,明确要求存在于上下文窗口之外——放进文件或运行时对象,需要时再取回 |
| Compressing Context | 窗口接近上限时用 summarization 与 trimming,只留最相关的或移除较早的消息 |
| Runtime State Objects | 为复杂任务建信息容器,逐步存子任务结果,让上下文只连着当前子任务 |
对照着看就清楚了:notebook 实现的是 Compressing Context 里 summarization 的前半段(产出摘要),trimming 那一半和 scratchpad 的「存在窗口之外」这个要求,都还留在你这边。要让历史真的变短,缺的不是这个工具,是摘要之后的移除动作,以及一个真正落在会话之外的存放位置。
vacation_agent_scratchpad.md 的结构可以当作落地时的形状参考:按 ## User Preferences / ## Completed Tasks 分节、条目带时间戳。这是仓库里的示例内容,不是接口规范。
处置后怎么验证
先验语义层。 README 的「Inspecting Context」一节给了可直接落地的记录口径:压缩这一环要记源区间或 trace id、summary id、压缩前后的估算 token 数,以及原始内容有没有被排除在下一次模型调用之外。最后这一项就是判定「摘要有没有真的起作用」的那个开关。这一节同时写明:不要把原始 prompt、工具输出、记忆内容直接落日志,优先记 id、计数、哈希与策略标签。
再验代码层。 仓库自带一个 notebook 批量执行脚本 scripts/validate-notebooks.ps1,用法在 .agents/skills/testing-course-samples/SKILL.md 里。脚本注释里的示例是:
pwsh scripts/validate-notebooks.ps1 -Filter '08-*' -Timeout 600
.PARAMETER Filter 说明这个通配符匹配的是 notebook 的仓库相对路径,把 08-* 换成第 12 课对应的通配符就只跑这一课。它用 nbconvert 无头执行、打印 PASS/FAIL 矩阵,执行副本、逐 notebook 日志与 results.json 写到输出目录(默认是 $env:TEMP\aiab-nbval)。-Timeout 是每个 cell 的超时,-Retries 只对被判定为瞬时的失败重试,ImportError、TypeError 这类确定性错误不重试——这些是仓库当前脚本里的行为与默认值,随版本可能变动。
前置条件那份 skill 文档写得很明确:Python 3.12+ 装 requirements.txt 再加 nbconvert 与 ipykernel,仓库根目录放 .env(至少要有 AZURE_AI_PROJECT_ENDPOINT 和 AZURE_AI_MODEL_DEPLOYMENT_NAME),并完成 az login。
Windows 侧:.ps1 是原生格式,装了 PowerShell 直接 pwsh 起。文档还提示,如果 python 命令被 Windows 应用商店的别名占掉,用 -Python 参数显式指定解释器路径。Linux / macOS 侧:这个脚本仍然要 PowerShell 才能跑,仓库 scripts/ 目录下只有这一个文件,没有提供 shell 版本的等价脚本。
顺带一处不一致值得留意:这个 notebook 的客户端用的是 FoundryChatClient(...) 搭配 DefaultAzureCredential(),而脚本注释与 skill 的前置条件都写「samples authenticate with AzureCliCredential」并要求先 az login。两处措辞不同,配环境时以你实际用的凭据链为准。密钥与 endpoint 请用你自己的占位,不要写进 notebook 提交上去。
什么情况说明不是这个原因
四种情况看着像,其实不是摘要接不上导致的。
报的是 ValueError 且列出了缺失变量名。 那是配置读取 cell 的显式检查:它把 AZURE_AI_PROJECT_ENDPOINT 和 AZURE_AI_MODEL_DEPLOYMENT_NAME 收集起来,缺哪个就把名字拼进异常信息。这一关都没过,压根没走到摘要。
报的是 ImportError / TypeError / 某个参数不认识。 优先怀疑版本口径:notebook 的安装 cell 写的是 %pip install agent-framework azure-ai-projects azure-identity python-dotenv --quiet,不带版本;而仓库 requirements.txt 锁的是 agent-framework-core 的固定版本,注释里逐字写明下一个小版本引入了课程 notebook 用到的 API 的破坏性变更(移除了 ChatMessage 与 HostedWebSearchTool、改了 Message 的构造函数、去掉了 Agent.run() 的 model= 参数),并说明特意直接锁 agent-framework-core 而不走 agent-framework 元包,以免 extras 拉进未锁版本的子包。安装 cell 与 requirements.txt 的口径不一致,这是仓库里两处白纸黑字摆着的事实。这类报错和上下文长度无关。
历史没变长,但 agent 在旧细节里打转、答非所问。 README 把它归为 Context Distraction;如果是你中途改了主意、新旧要求同时留在上下文里导致的前后矛盾,那是 Context Clash,README 给的处置是 pruning 与 offloading,不是再加一个摘要工具。notebook 里改日期那一轮演示的正是这个场景。
工具挂多了之后调用越来越离谱。 这是 Context Confusion,README 的处置是用 RAG 检索工具描述、只把相关工具送进这次调用,和摘要不是一回事。
会话只有几轮就撞窗口上限。 更可能是单条消息或某个工具返回体积过大。README 的 Sandbox Environments 一条讲的就是这种情况:让大块处理发生在沙箱里,只把结果读回上下文。
课程持续更新,上面涉及的文件路径、参数与默认值以仓库最新内容为准。
本文依据 github.com/microsoft/ai-agents-for-beginners 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码,
因此不涉及运行结果、耗时与 Agent 实际表现的任何描述。
该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。
文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。