01 学习交流群
知识库之外,我们还建了「工程化专题交流群」。
进群和大家一起交流 AI 应用从 Demo 到生产落地的工程实践,包括开发流程、架构设计、效果评估、性能优化与稳定性保障,也会同步专题最新更新和实战资料。
扫码加入,一起聊聊怎样把 AI 应用真正做成、上线并稳定运行。

02 学前须知
本教程配套代码素材:https://gitee.com/geektime-geekbang_admin/ai-tutorial-material/tree/master/llm-engineering-materials
03 从_Vibe_Coding_到可验证的开发——一次任务到底怎样才算完成
你让AI“给页面加一个统计”,它很快回复“已完成”。打开页面,确实多了一行数字。但这个数字算的是所有资料,还是马上要发给模型的片段?刷新时会不会重新读取文件?它为了做这个小功能,有没有顺手换掉模型接入或数据库代码?
这些问题并不要求你先成为资深程序员。你需要的是一个能跟得上的工作过程:说清要改什么,知道它打算改哪里,查看真实变化,再用独立检查确认结果。
第四部分就从这样一个小任务开始。我们继续使用第三部分的个人笔记助手,让Kimi Code协助它增加“已选证据”的片段数和字符数展示。本篇结束时,你应当能够说明一次修改为什么可以接受,而不只是转述Agent的“完成”消息。
下载本篇练习包 · 独立运行说明 · 完整任务文本 · 第三部分目录
核实日期:2026年10月4日。本机只对Kimi Code CLI执行了版本和帮助检查,版本为2.0.2;没有使用用户账号发起真实Kimi开发请求。包中的reference是作者提供并离线验证的参考实现,不是伪造的Kimi会话产物。应用依赖沿用Python 3.11+、openai 3.24.0与Flask 3.1.3。
先分清开发工具和应用本身
第三部分的笔记助手是一个Python应用。它负责读取资料、检索片段、调用DeepSeek、检查回答,并保存问答和复习计划。第四部分增加的是开发方式:让Kimi Code帮助我们理解代码、提出修改和运行验证。
两条路径要分开看:
开发时:你 → Kimi Code → 阅读和修改项目 → 本地验证
使用时:你 → 笔记助手网页 → Python后端 → DeepSeek API(真实模式)
Kimi Code协助修改项目,不会因此成为笔记助手运行时的新后端。改完代码后退出Kimi Code,仍然可以单独启动笔记助手。应用的DeepSeek配置与开发工具的登录配置,也不是同一份东西。
本篇默认使用笔记助手的离线模式,因此验证页面不会调用DeepSeek。不过,向Kimi Code发送开发任务本身可能消耗你的Kimi服务额度。不能把“应用离线”理解成“开发Agent也完全不联网”。
方法是通用的。你可以用其他Code Agent完成同一个任务,只是具体操作不同;本课程以Kimi Code作为贯穿实践的工具,不在这里重写一整套产品说明书。
准备一份可以放心练习的项目
下载并解压本篇练习包,进入lesson-01目录。它包含:
| 文件或目录 | 用途 |
|---|---|
| starter | 第三部分最终版的干净源码,供你实际修改 |
| reference | 作者的完成版,供对照;不是练习的工作目录 |
| TASK.md | 可交给Agent的任务与边界 |
| verify_preview.py | 从应用外部检查新功能的脚本 |
| README.md | 不依赖本文也能运行的简要说明 |
starter的38个文件与第三部分最终快照一致,没有复制你的运行历史、密钥、虚拟环境或个人配置。没读第三部分也可以从这里开始。暂时不要覆盖你以前实际使用过的项目,也不要把自己的私密笔记搬进练习目录。
在lesson-01目录打开终端,先建立Python环境。macOS/Linux:
python3 -m venv .venv
.venv/bin/python -m pip install -r starter/requirements.txt
.venv/bin/python -m pip check
.venv/bin/python starter/acceptance.py
Windows PowerShell:
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r starter/requirements.txt
.\.venv\Scripts\python.exe -m pip check
.\.venv\Scripts\python.exe starter/acceptance.py
需要Python 3.11或以上版本。安装依赖会访问软件包服务;最后的验收在临时目录运行,不需要API Key或config.json,应看到Ran 10 tests和OK。这是修改前的基线:你先确认原项目正常,之后才知道新增失败是否由这次修改引入。
这些检查使用Flask测试客户端,没有真的打开浏览器。它们能检查后端请求、状态和记录,不能证明页面在你的浏览器中看起来合适。稍后还会手工验证页面。Flask测试文档
让Kimi Code进入正确的工作目录
已经装好Kimi Code的读者,先执行kimi --version确认命令可用。本文核实的是2.0.2;未来版本变化时应结合官方文档和kimi --help,不要照搬旧版kimi-cli的安装方式。
尚未安装的读者,可按官方入门说明安装。当前npm路线需要Node.js 22.19.0或以上,先用node --version检查,再执行:
npm install -g @moonshot-ai/kimi-code
kimi --version
上述npm命令在安装好Node/npm的终端执行,不是在Python解释器里执行。Windows还需按官方要求安装Git for Windows,供Kimi Code的命令执行环境使用。没有Node的读者可以选择官方页面提供的独立安装路线;不要在这里混用几套安装方式。
回到lesson-01目录后:
cd starter
kimi
首次使用时,在Kimi交互界面输入/login,选择官方Kimi Code登录路径,按提示在浏览器完成授权。自己的开发账户与应用的DeepSeek Key分开管理,不要把DeepSeek Key粘进这一步。登录失败先解决账户、网络或额度问题,不要改笔记助手源码来“修好登录”。
这里不启用跳过确认的参数。具体修改和命令出现时,阅读它要操作的路径与内容,再决定是否允许。kimi以当前项目目录开始工作;命令参数和运行模式以官方命令参考及本机帮助为准。
如果暂时没有可用Kimi账户,你仍可阅读任务,查看reference并运行全部本地验证。这样学到的是修改与验收方法,但不能把这条替代路线记成“已经真实操作过Kimi”。
这个小功能,怎样描述才没有歧义
我们的需求是:在“检查将使用的证据”页面,显示本次已选中片段的数量,以及这些片段文本的字符数之和。
为什么值得显示?页面上既有已选中,也有未选中的候选片段。用户需要知道真正进入生成请求的资料规模。这个数字用于理解输入,不用来计算账单。
先明确四条验收条件:
- 默认样例选择两个片段,应显示“2个片段、67个字符”。
- 把最多片段改成1,应显示“1个片段、43个字符”,不能把未选中的候选资料算进去。
- 关键词改成“查找”,无命中时显示0/0,仍然不允许生成。
- 已建立的预览使用保存的快照;之后修改源文件并刷新旧预览,不应悄悄改变数字。
字符口径也要写清:使用Python的len(text),保留片段文本中的标点、空格和换行;不额外插入片段之间的分隔符,不计算问题和提示词。这不是Token数,更不是费用。对于包含组合字符的文本,Python字符串长度也不一定等于人眼看到的字形数量,界面必须说明采用的口径。Python字符串说明
默认数据中的两个片段各保留一个末尾换行,因此统计不是简单数完屏幕上的汉字。数字差一点,也可能是需求和测试对换行的理解不一致,不一定是模型“算术差”。
将下面内容发给Kimi Code;完整版本也在TASK.md中:
请给当前笔记助手的证据预览页增加一行统计。
先阅读web_app.py、templates/web.html和必要的rag.py,说明预览如何保存、如何展示,以及建议修改哪些文件。先给方案,等我确认后再改。
显示:已选证据:X 个片段,共 Y 个字符(不等于 Token 数,不含问题与提示词)。
X是本次保存快照snapshot.notes的数量;Y是其中每个note.text的Python len之和,包含原有换行,不增加片段间分隔符。
没有命中显示0/0,仍不生成。首页不显示统计。刷新旧预览使用旧快照,不重新读取资料或调用模型。
保留DeepSeek接入、数据库结构、生成与计划流程,不换依赖、不重写项目。
不要读取个人凭据、不要调用真实API,不修改外部验收脚本,也不要读取reference答案。
请说明你需要运行哪些本地验证命令,以及需要我补充的解释器路径。
这不是追求一段“神奇提示词”。我们正在把任务转成双方都能检查的约定。下一篇会进一步讨论,怎样从更模糊的想法写出这样的要求。
先看方案,再让它修改
一个合理方案应能指出:预览资料已经由后端存入操作快照;显示旧预览时从该快照取出选中资料,计算数量和长度;模板把两个数字展示出来。因此,这次通常只需要改web_app.py和templates/web.html。
Agent不必与参考实现逐字一样。例如,它可能选择一个小辅助函数来组织统计。但如果它提出更换模型、增加数据库表、安装Token计算库、重新检索全部文件,就应该让它解释这些变化是否真的为当前需求所必需。
你可以这样确认方案:
按上述小范围方案实施。请保留原生成路径与模型配置。
先报告准备修改的文件,修改后列出实际变化。
验证时使用我给出的本机Python路径;如果命令失败,请先解释原因,不要自行安装依赖或修改验收预期。
还要补充解释器位置。我们把.venv建在lesson-01目录,而Agent当前在starter里。macOS/Linux的相对解释器路径是../.venv/bin/python。Windows的虚拟环境位于父目录.venv/Scripts/python.exe;Kimi的命令Shell可能是Git Bash,不能把PowerShell语法原样当成它的执行语法。把操作系统、解释器实际路径告诉它,并检查它生成的命令。也可以让它只改代码,你在另一个PowerShell终端运行下面的验证。
权限确认只是允许某项操作发生,不是确认修改已经正确。看到“运行命令”时,要看命令是否只在练习范围内工作,有没有额外下载、删除或真实API调用。本篇不会因为Agent要求更大权限,就把整个电脑交给它处理。
用独立检查决定是否接受结果
另开终端并进入lesson-01目录。这里与Agent的starter工作目录不同,不要漏掉这一步。
macOS/Linux:
.venv/bin/python verify_preview.py starter
.venv/bin/python starter/acceptance.py
Windows PowerShell:
.\.venv\Scripts\python.exe verify_preview.py starter
.\.venv\Scripts\python.exe starter/acceptance.py
第一条检查新功能的五个场景:默认两个片段、只选一个、无命中、旧快照不变、首页不显示统计。第二条重新检查原来10个验收场景,包括引用、重复操作、会话和计划等。
未修改starter时,第一条应有4项失败、1项通过,因为新功能还不存在,首页本来就没有统计。修改完成后,两条都应为OK。这是“基线正常—新要求尚未实现—实现后通过—旧行为继续通过”的过程。
脚本使用Python标准库unittest,失败信息包含实际结果与断言。不要因为最后出现FAILED,就把所有断言都改成能通过;应该回到任务判断失败来自代码、测试还是需求口径。Python unittest文档
本篇的验证脚本位于应用目录之外,是为了给你保留一份独立检查。它不是访问控制机制,Agent仍可能有能力读取或修改父目录。因此你仍应检查它有没有动测试文件,必要时从原下载包取回验证脚本。
再用编辑器查看修改后的两个文件,对照初始版本或参考实现。确认没有更换model、API地址、依赖和数据库结构。下一篇之后会逐步引入Git,让这种检查更清楚;本篇不要求你先掌握Git才能完成第一次任务。
参考实现中,后端在show_preview里新增两个模板参数:
return page(preview=item, ticket=ticket, selected=selected,
evidence_count=len(item["snapshot"]["notes"]),
evidence_chars=sum(len(note["text"]) for note in item["snapshot"]["notes"]))
它统计的是已保存的notes,没有再次调用检索函数,也没有访问模型。模板只显示计算结果。对于这个需求,这样的改动足以完成任务。
打开页面,亲自走一遍
保持终端位于lesson-01目录,运行:
.venv/bin/python starter/run_local.py init
.venv/bin/python starter/run_local.py serve
Windows分别使用.\.venv\Scripts\python.exe starter/run_local.py init和.\.venv\Scripts\python.exe starter/run_local.py serve。初始化只创建缺失的配置;已有配置保留。打开http://127.0.0.1:8765,页面应显示离线演示。
使用默认资料、问题、关键词“检索 片段”和最多2片段,点击“预览检索证据”。在证据区域应看到:
已选证据:2 个片段,共 67 个字符(不等于 Token 数,不含问题与提示词)。
再返回首页,新建一次预览,把最多片段改成1,结果应为1/43。再返回首页把关键词改成“查找”,结果应为0/0,并保留“没有命中片段,不调用模型”的提示。
K=1属于自定义预览,离线固定答案只支持默认配置,所以没有演示生成按钮是原有行为,不是新增统计弄坏了程序。要检查旧生成流程,回到默认配置再生成演示卡片即可。
页面暂时仍保留第三部分的原篇号,我们没有为了这次统计需求修改无关标题。停止服务回到终端按Ctrl+C。不要同时启动starter与reference两套服务;端口和浏览器Cookie都可能干扰你的观察。
如果没有进行真实Agent练习,也可以将上述命令中的starter换成reference,验证作者实现。两条路径的学习记录要分开:一条是你完成了Agent协作,一条是你理解并运行了已给出的代码。
遇到失败时,先确定哪一步出了问题
| 现象 | 先检查 |
|---|---|
| 找不到kimi命令 | 是否安装成功、是否重新打开终端、PATH是否正确;别改应用源码 |
| Kimi登录或额度失败 | 开发工具自己的账户状态;与DeepSeek应用配置分开 |
| Python找不到Flask | 是否使用lesson-01里的虚拟环境,依赖是否装在同一个解释器 |
| 修改后测试仍显示缺统计 | Agent改的是不是starter,是否只改了reference,是否保存了文件 |
| 字符数相差1或2 | 是否保留片段末尾换行,是否额外插入分隔符,是否算入未选片段 |
| 页面数字正确,但旧验收失败 | 是否连带修改生成、计划或打包规则;检查实际差异 |
| 端口被占用或看到旧页面 | 停止旧副本,确认启动路径;必要时明确换端口 |
| Agent说测试通过,但没有命令结果 | 自己重跑,不把叙述当作执行证据 |
本篇作者验证了:原starter的10项验收通过;新增脚本在未修改版本按预期失败;参考实现的5项新检查和10项原验收通过;原第三部分源码保留,参考实现只有两个文件发生变化。全部为本地离线检查,没有真实Kimi任务输出、DeepSeek调用或Windows实机验证,也没有把测试客户端说成真实浏览器验收。
最后问自己:如果页面多了统计、但统计的是全部候选片段,任务完成了吗?没有,输出符合外观,却违反了统计范围。如果新测试通过、但Agent把DeepSeek替换成另一服务呢?也不能直接接受,因为它做了需求之外的重要改变。
这就是本篇要建立的判断:一次任务完成,需要结果符合约定、修改范围可解释、验证证据可复查。 下一篇从更模糊的“帮我加一个历史搜索”开始,学习怎样把想法整理成清楚的需求和规格。
04 需求与规格——把“帮我优化一下”变成明确任务
笔记助手用了几天,你记得问过一个关于RAG的问题,却在首页翻不到,于是对Kimi Code说:“帮我加个历史问答搜索。”
它可以很快做出一个输入框。但这个框到底搜索问题、答案还是笔记?会不会只能搜索首页已经显示的50条?如果换一个浏览器,能不能找到同一台电脑上的记录?这些选择即使都写成了能运行的代码,做出来的也可能不是你想要的功能。
本篇先把这些选择确定下来。交付结果是一份小而明确的需求规格、一组能够区分对错的案例,以及交给Kimi Code的规划任务。应用源码保持第01篇完成后的状态,搜索功能尚未实现。先把“要做什么、怎样判断”稳定下来,再进入后面的版本管理和任务拆分。
下载本篇材料 · 参考需求规格 · 验收案例 · 上一篇:可验证的开发
核实日期:2026年10月4日。本机Kimi Code CLI仍为2.0.2,仅执行版本和帮助检查。本文规格与案例由作者编写,不是实际Kimi生成记录。配套脚本只检查材料完整性,没有运行尚未实现的搜索功能,也没有调用Kimi或DeepSeek。
需求、规格和实现,分别回答什么
先不用记缩写,看三个层次的例子:
| 层次 | 本次例子 | 解决的问题 |
|---|---|---|
| 用户需求 | 我记得过去问过什么,希望快速找回来 | 为什么要做 |
| 行为规格 | 输入关键词,只搜索本会话历史问题,按明确规则展示结果 | 用户操作后应该发生什么 |
| 实现方案 | 哪个路由接收请求,哪个函数查记录,页面怎样渲染 | 代码具体怎样做 |
“加一个SQL查询”是实现线索,不能代替需求。反过来,“让搜索更智能”表达了愿望,也没有给出可以检查的行为。
本课使用的Spec就是一份行为约定,不要求套用庞大的文档模板。所谓规格驱动开发,在这里是先写下关键规则与例子,让实现和测试围绕它们展开;遇到真正改变需求的决定,再同步更新规格。它不保证Agent一定正确,也不意味着需求从此不能修改。
对第01篇那个统计行,几条清楚的验收条件已经足够。这次搜索涉及可见范围、字段、排序、空输入和结果上限,值得单独保存一份文档。文档的多少取决于判断难度,不取决于功能名字听起来是否高级。
先看项目事实,别让AI从想象中设计
解压练习包,进入lesson-02目录。project是第01篇reference的原样快照,包含已选证据统计;其余文件是本篇的规格与案例。没有复制你的历史数据库,不需要为了写需求先启动网页。
先打开三个文件,寻找这些位置:
project/web_app.py中的home函数:首页从当前会话读取历史。project/storage.py中的history函数:按owner过滤,再取最近50条。project/templates/web.html中的历史区域:显示问题、时间、模式与操作状态,链接进入已有预览或结果。
这里的owner可以先理解成“当前浏览器会话的内部标识”。项目还没有登录账号,清除Cookie会进入新会话;同一台电脑上的两种浏览器,也不因此共享一个身份。搜索需求必须承接这个事实,不能只加一个输入框就默认拥有跨设备账号历史。
还有一个容易被忽略的事实:历史不全是已经答完的问题。刚建立的预览是ready,处理中可能为running,结束后是done,结果未确认可能为uncertain。它们都有问题文本,但不一定都有完整答案。
现在把想法改写得更准确一些:让本人在同一浏览器会话里,通过记得的关键词找到过去提交的问题,再打开它的预览或结果。 这句话已经排除了不少不必要的猜测。
先回答五个最影响结果的问题
你不必一开始回答几十项问卷。先处理会明显改变实现和验收的五个决定。
| 需要决定 | 本课第一版选择 | 取舍理由 |
|---|---|---|
| 搜什么字段? | 只搜问题文本,入口叫“搜索历史问题” | 行为容易解释;答案全文搜索留待明确需要时增加 |
| 搜谁的记录? | 当前会话的全部操作 | 延续现有权限,不新增账号与找回机制 |
| 怎样算匹配? | 忽略大小写的连续子串 | 本地即可完成,不引入模型、向量服务或拆词规则 |
| 返回多少、怎样排列? | 最新优先,最多50条;先匹配再截取 | 不漏掉较旧但相关的记录,限制页面规模 |
| 空、长、无结果怎么办? | 空输入回最近列表;超过80字符报错;无结果明确提示 | 不静默截断,不把未命中伪装成模型回答 |
这些是本课的产品选择,不是所有搜索系统的统一规定。你完全可以选择搜索答案,但必须连带改变文案、案例和实现。不能一边告诉用户“全文搜索”,一边只过滤问题标题。
本次不会因为“智能”二字就调用模型。找一个已知关键词,确定性的本地匹配足够清楚。之后若真的需要“记不住原词也能找回”,那是新需求,应另外比较同义词、全文索引或语义检索,而不是偷偷改变第一版的匹配含义。
给容易含糊的词补上可检查的含义
现在把五个选择收紧到实现者不用猜的程度。
输入。 只接收一个关键词字符串,先用Python的strip()去掉两端空白,再检查长度。去空白后0到80字符合法;空字符串表示不加关键词过滤。81个字符就显示错误,不截成前80个。长度在大小写归一化前计算,避免转换后长度变化导致口径不一致。
匹配。 将关键词和问题分别做casefold()后,判断前者是否是后者的连续子串。它比只比较英文字母原样更适合不区分大小写的匹配;它不是中文分词、同义词检索或语义理解。内部空格保留,RAG 流程不会自动匹配没有那个空格的RAG流程。Python字符串方法
%和_也只是普通字符。某种数据库写法可能把它们解释成通配符,但这不能反过来改变用户约定。先规定行为,后面再选择能正确实现它的查询方式。
范围。 匹配所有属于当前会话的操作,四种状态都包括,演示和真实模式记录也都包括。别的会话即使有相同文字,也不能返回它的问题、数量或结果。身份由后端会话决定,不能通过表单里传一个owner就切换成别人。
顺序。 先完成可见范围与关键词过滤,再按创建时间从新到旧排序。时间相同时按操作ID降序,保证规则确定;它不表示随机ID本身代表真实先后。最后取最多50条,有更多时提示用户缩小关键词。这里的80和50都是教学应用的限制,不是服务商上限。
副作用。 已建立会话后,搜索、刷新和清空不会新建问答或计划,不调用模型,也不修改旧业务记录。原程序在首次访问时可能建立会话,响应可能更新Cookie,所以规格写的是“不修改业务记录”,没有声称HTTP访问绝不产生任何状态。
完整规则保存在SEARCH_SPEC.md,用R1到R7编号。后续发现问题,可以直接说“实现违反R5的先过滤后限制”,比反复转述一长段聊天更容易定位。
用反例检验规格,而不只是举成功例子
先看材料中六条精简记录。A和B代表两个不同会话;这些都是作者构造的数据,没有真实个人内容。
| ID | 会话 | 问题 | 说明 |
|---|---|---|---|
| A01 | A | RAG流程是什么? | 较早,已有答案 |
| A02 | A | 怎样检查引用? | 问题不含RAG,答案含RAG |
| A03 | A | rag失败怎样排查? | 比A01新,仍是ready |
| B01 | B | RAG相关的私有问题 | 属于另一个会话 |
| A04 | A | 100%覆盖是否可信? | 用来检查百分号是否按文字匹配 |
| A05 | A | 下划线_是什么? | 用来检查下划线是否按文字匹配 |
当前会话为A,输入RAG,预期依次返回A03、A01。
这个例子一次区分了几种常见错误:不去两端空白会漏结果;只匹配大写会漏A03;擅自搜索答案会多出A02;不检查身份会泄漏B01;只搜索done状态会漏掉尚未生成答案的A03。
再看一个更难察觉的反例:会话C有51条记录,最旧的C01包含“旧资料针”,其余50条都不包含。查询“旧资料针”必须返回C01。如果实现先取首页最近50条,再在那50条里找,就会错误地显示无结果。
这不是要为个人应用提前搭大型搜索引擎。只是把两个看似接近的操作顺序区分开:
要求:本会话全部记录 → 关键词匹配 → 排序 → 最多50条
错误捷径:本会话最近50条 → 关键词匹配
写验收案例时,可以使用“给定条件—当用户操作—应出现结果”的形式。例如:
给定:会话C已有51条记录,只有最旧的C01包含“旧资料针”。
当:当前会话C搜索“旧资料针”。
那么:列表包含C01,不显示“没有匹配的问题”。
并且:没有创建新的问答或调用模型。
这种组织方式与Gherkin中的Given/When/Then思路相通。这里借用它来写清条件、动作和结果,不要求安装Cucumber,也不把中文描述直接宣称为自动化测试。Gherkin参考
让Kimi Code帮助澄清,而不是立刻写代码
已按第01篇完成安装和登录的读者,在lesson-02目录运行:
kimi --plan
此处工作目录包含project与练习材料,不要进入别的私人项目。当前官方文档说明--plan用于以规划模式启动,优先读取和探索。它不等于操作系统级只读沙箱;仍需检查具体动作,不批准与本课无关的执行或源码修改。Kimi Code命令文档
先发一个故意保留模糊空间的请求:
我想给这个个人笔记助手增加历史问答搜索。
请先读取project/web_app.py、storage.py、templates/web.html里与历史有关的代码。
只分析,不修改、不运行应用,不读取data、个人配置或密钥,也先不要看SEARCH_SPEC.md参考答案。
请区分:代码已证明的事实、需要我决定的问题、你建议的默认值。
最多提出5个最影响结果的问题,先别写实现方案。
不要只看回答是否流畅。核对它是否发现当前会话隔离、最近50条上限、ready记录也有问题等事实。它若说“用户登录后跨设备搜索”,就要追问这个能力在代码哪里存在,而不是把想象当现状。
然后给出第三、四节的具体取舍,让它起草一份规格。要求目标、范围、输入、匹配、顺序、空与错误状态、副作用、不做项和验收案例齐全。结果先展示在对话里,由你审阅后保存为MY_SPEC.md。这样本篇真正产出的是一份经过取舍的工作文档,不是一段Agent自行猜测的需求。
再给一次审查任务:
请检查这份规格是否仍有可能产生不同实现结果的歧义;若有,最多列出三个,并给出具体输入和预期差异。
特别检查:是否只在最近50条里搜索;是否搜索答案;%与_是否被当成通配符;其他会话记录是否会泄漏。
若规则已明确,不要为了凑数量编造问题。不要修改应用代码。
完整练习流程在TASK.md。开发对话仍可能消耗自己的Kimi额度;没有账户也可以手工完成MY_SPEC并与参考文档比较,但这不算真实Agent执行。本篇作者没有模拟一份“已经与Kimi讨论过”的聊天记录。
检查材料完整性,别把它误当成功能验收
search-cases.json保存57条精简记录和12个预期案例,覆盖大小写与空白、问题字段边界、空查询、普通符号、80/81字符、跨会话、旧记录、结果上限和未完成操作。
在lesson-02目录运行,不需要安装第三方库:
macOS/Linux:
python3 audit_cases.py
Windows PowerShell:
py -3 audit_cases.py
预期前两行是:
材料结构通过:57条构造记录,12个预期案例。
未执行搜索功能;没有验证匹配正确性、页面、SQL、权限或副作用。
之后会打印每个案例的说明与预期。脚本检查ID是否重复、预期是否引用存在的记录、是否意外把其他会话ID列为允许结果、是否超过约定展示上限。它没有实现搜索算法,不会替你判断“为什么这个问题应当匹配”。
因此,检查通过后仍需手工核对S01和S08:前者应为A03、A01,后者应为C01。再核对S03的空查询顺序、S09的50条截取,以及S06的长度错误。预期是作者写下的要求,不是程序运行得到的事实。
还缺哪些真正的验收?实现后,要把这些精简记录映射到临时数据库的真实结构,发送页面请求核对列表,再验证伪造身份无效、输入正确转义、重复搜索不新增操作、不调用生成函数,以及原有功能仍通过。SEARCH_SPEC.md把这些明确列为待做。
现在打开project的页面不会出现搜索框,是正确的阶段状态。运行原acceptance.py即使得到10项通过,也只能说明旧功能正常,不能把未实现的新功能勾成“完成”。
规格也需要维护,但别把它写成一本书
这份文档应足以让实现者知道结果,却不必提前规定所有函数名、SQL写法、CSS颜色或每一行代码。后续发现一种更简单的实现,只要符合行为约定,就可以采用。
有些变化则必须先改规格。比如你决定“答案里出现RAG也要搜到”,这会改变R4,A02应从S01的排除项变成结果;它还会影响空答案、未完成记录和数据读取。此时保留旧版本,记录新取舍,再更新相关案例,不能只在聊天末尾加一句“顺便也搜答案”,却继续按旧案例验收。
相反,修复排序实现却保持相同顺序约定,不一定需要改规格。区分“行为目标改变”和“实现还没有做到”,能避免一遇到失败就把要求改掉。
本篇不引入需求管理平台、复杂审批表或规格生成框架。一个小Markdown文件、一组能识别错误实现的例子,加上清楚的版本记录,就足够支持眼前这次迭代。
交出这三份东西,再开始实现
完成练习后,你应当能交出:一份明确取舍的MY_SPEC;一组与它一致的验收案例;一份仍待实现和验证的事项清单。没有自己的Kimi账户也能手工完成,但不要混淆“文档写好”与“功能交付”。
做两个判断:如果你只需要找问题,Agent建议顺便增加向量数据库,是否必须采用?不必,先说明当前连续子串为何不足,再决定是否改变需求。如果案例检查脚本输出通过,是否可以告诉用户历史搜索上线了?不能,它没有执行任何搜索请求。
本篇对照了实际历史存储与页面代码,检查了案例结构、预期引用和边界,保留了第01篇全部应用源码。材料中的反例用于阻止错误的实现方向,不是模型质量评测结果。
下一篇先建立Git检查点,让这份规格和后续修改都可以比较、保留与撤回。第04篇再根据它拆分任务和安排实现,避免把“需求还没定”造成的反复修改误认为Agent能力不足。
05 Git_与检查点——让每次_AI_修改都能看清、撤回和比较
你让AI给笔记助手加一个搜索框。十分钟后,它说完成了,却改了十几个文件。页面打不开,你又让它修;第二轮能打开了,但原来显示的证据统计不见了。此时你最想知道的通常不是“再写一遍行不行”,而是:第一轮到底改了什么?哪一次还能用?能不能只撤掉错误的部分?
上一篇把目标写进了搜索规格。本篇给项目建立可比较的起点,再亲手完成一次有选择的提交、一次未提交修改的撤回,以及一次已经提交错误的撤销。练习只改变教学笔记,应用源码与搜索规格都保持原样,历史搜索仍待第04篇实现。
下载练习包 · 材料说明 · 上一篇:需求与规格
核实日期:2026年10月4日。作者用Git 2.49.0在macOS临时仓库实际验证本文流程;Windows命令按通用Git语法提供,未做Windows实机验证。Kimi Code 2.0.2仅核对help/version,没有真实Agent生成或DeepSeek调用。本课不需要模型账户、付费额度或运行网页。
先明白:检查点到底保存了什么
Git是版本管理工具。你可以把一次commit理解成给选定内容建立一个可命名、可比较的项目版本。以后即使文件变了,已提交版本仍可供查看。它不是必须连接GitHub才能使用的服务:本篇所有操作都发生在本地。
需要先认识三个位置:
| 位置 | 在练习里是什么意思 |
|---|---|
| 工作区 | 编辑器里正在修改的文件 |
| 暂存区,也叫index | 已经选定、准备放进下一次提交的内容 |
| 当前提交,通常用HEAD指代 | 当前所在的已记录版本 |
流程是:在工作区修改,使用git add选择内容,再使用git commit记录暂存区。add保存的是执行当时的内容,之后继续编辑不会自动更新暂存区。git-add文档
所以“文件已保存”和“版本已提交”不是一回事。前者让磁盘上有最新文件,后者让你保留一个能够与未来修改比较的版本。提交也不代表测试通过;它只记录内容,质量证据需要另外给出。git-commit文档
对AI开发,检查点最直接的价值是减少混淆:任务开始前是什么,Agent本轮改了什么,哪些是你自己的未完成工作,都有了明确参照。
准备一个独立练习目录
把练习包解压到新的学习文件夹,例如“文档”下的ai-git-lab。不要覆盖前两篇材料,也不要直接在自己的旧项目或包含私人资料的文件夹里演练撤销。
包内结构如下:
lesson-03/
README.md
TASK.md
verify_checkpoints.py
starter/
.gitignore
CHECKPOINT_NOTES.md
PERSONAL_NOTES.md
SEARCH_SPEC.md
search-cases.json
audit_cases.py
project/ 第02篇的38文件应用原样快照
PERSONAL_NOTES.md只是公开的教学文件,名字用于模拟“与任务无关的个人改动”,不要填入真正私密内容。练习包不带.git历史,下面由你亲手创建;也不带运行数据库和密钥。
先在终端进入解压后的lesson-03。macOS/Linux可用pwd查看所在目录,Windows PowerShell可用Get-Location。然后执行以下命令;除特别标注的Python启动方式外,本篇Git命令在两种终端相同:
git --version
cd starter
git init -b main
git rev-parse --show-toplevel
最后一条必须指向刚解压的lesson-03/starter。它用于确认你操作的是本课仓库。如果显示其他项目路径,先停下并核对目录,不要继续暂存或恢复文件。本文使用支持init -b、switch和restore的Git;若旧版本不认识参数,可从Git安装页面选择对应系统安装方式,重新打开终端后检查。init -b main在新仓库设置初始分支名。git-init文档
给这个新仓库设置提交身份:
git config --local user.name "Course Learner"
git config --local user.email "learner@example.invalid"
这只是演示用的提交署名,不是Kimi、DeepSeek或GitHub登录。--local把设置限制在当前仓库,避免为了练习改变其他项目的全局身份。git-config文档
在第一次提交前,先看清楚要保存的范围
打开starter/.gitignore,你会看到.venv/、config.json、.env、data/以及生成记录等排除项。源码和config.example.json模板适合纳入版本;自己的真实配置、会话密钥与数据库不应混进去。
.gitignore主要影响尚未跟踪的文件。已经被跟踪的文件不会因为补上一行忽略规则就从历史消失,它也不是自动识别密钥的扫描器。gitignore文档
在这个尚未运行、没有私人文件的练习仓库里执行:
git status --short --untracked-files=all
git check-ignore -v project/config.json project/data/demo.sqlite3 .venv/example.txt
git add .gitignore CHECKPOINT_NOTES.md PERSONAL_NOTES.md SEARCH_SPEC.md search-cases.json audit_cases.py project
git diff --staged --stat
git diff --staged --name-only
check-ignore只是拿几个路径检查规则,即使文件尚不存在也可以检查,不会生成配置或数据库。它应列出相应忽略规则。随后暂存清单应包含44个教学文件,不包含密钥、数据、虚拟环境或外层的验证脚本。仔细检查文件名;不确定的文件先打开确认再提交。
git commit -m "chore: record lesson 03 baseline"
git status --short
git log -1 --oneline
预期status --short没有输出,log显示一条基线提交。输出的短哈希由你的提交内容和元数据决定,不必与任何示例相同。
这份基线包含第01篇证据统计和第02篇搜索规格,没有新增搜索功能。你现在可以明确地说:“后面的差异是相对于这个起点产生的。”本地提交仍在这台电脑上,不能代替另存备份;本课不要求注册托管服务或推送代码。
开一个分支,做一次有选择的提交
在干净的基线上执行:
git switch -c checkpoint-lab
git branch --show-current
预期当前分支是checkpoint-lab。分支可以先理解成一条有名字的版本路线:main保留基线,练习提交沿新路线前进。切分支使用的仍是这个文件夹,不会自动获得另一个独立工作目录;未提交修改也可能被带到其他分支,因此先确认状态干净再切换。git-switch文档
用编辑器打开CHECKPOINT_NOTES.md,将全文改为:
# 开发检查点
状态:规格已确认,搜索尚未实现。
编辑器按UTF-8保存,并保留文件末尾换行;配套检查会核对这些完整文字。再在PERSONAL_NOTES.md末尾添加一行:
临时想法:以后研究界面配色。
保存两个文件,运行:
git status --short
git diff --stat
git diff -- CHECKPOINT_NOTES.md PERSONAL_NOTES.md
如果终端进入分页查看界面,按q退出后再输入下一条命令。应只有两个文件改变。差异中-标出删除的旧行,+标出新增的行;它们不是需要复制到文件中的字符。检查点结论属于本次任务,配色想法暂时不属于。
只选择检查点文件:
git add CHECKPOINT_NOTES.md
git diff --staged -- CHECKPOINT_NOTES.md
git diff -- PERSONAL_NOTES.md
第一条diff看准备提交的内容,第二条看尚未暂存的内容。一般地,git diff比较工作区与暂存区,git diff --staged比较暂存区与当前提交;git diff HEAD可查看已跟踪文件相对当前提交的整体变化,但不会展示新建且未跟踪文件的正文。先看status,再看diff,才能不漏文件。git-diff文档
先练习取消选择,再重新选择:
git restore --staged -- CHECKPOINT_NOTES.md
git diff --staged --name-only
git diff -- CHECKPOINT_NOTES.md
git add CHECKPOINT_NOTES.md
git commit -m "docs: record confirmed search scope"
git status --short
取消暂存后,暂存文件清单应为空,但正确的新句子仍在工作区;重新暂存并提交后,只剩PERSONAL_NOTES.md未提交。没有把所有改动捆成一包,才方便独立审阅和撤销。
现实中,同一文件也可能混着多个任务的修改。先拆成小步最容易理解;需要时可使用git add -p按差异块选择,但仍要检查暂存diff,不能把“选了一部分”当作天然正确。本篇只练习按文件选择。
撤回未提交改动:先明确放弃的是哪一份内容
现在配色想法是本课故意制造的可丢弃文字。先看一遍,再恢复它:
git diff -- PERSONAL_NOTES.md
git restore --source=HEAD --worktree -- PERSONAL_NOTES.md
git status --short
预期文件恢复到基线文字,工作区干净。这里明确指定从HEAD恢复工作区的这个文件,真正丢弃了尚未提交的那一行。对真实的未完成工作,应该先保留副本或整理成提交,而不是照搬这个练习。
再做一个容易混淆的实验:把CHECKPOINT_NOTES.md中的状态改为“状态:搜索已经上线并通过验收。”,保存后检查:
git diff -- CHECKPOINT_NOTES.md
git restore --source=HEAD --worktree -- CHECKPOINT_NOTES.md
重新打开文件,应恢复为“规格已确认,搜索尚未实现”。这次错误从未提交,所以恢复文件即可。
对照两种操作:restore --staged只是取消暂存,本篇指定--source=HEAD --worktree则用已提交版本覆盖工作区文件。默认不带--source的工作区恢复来源是暂存区;为了不让初学者混淆“从哪恢复”,这里始终写明来源。若同时有暂存和未暂存修改,先分别看两个diff再决定,不能凭命令名字猜作用。git-restore文档
撤销已提交错误:留下修正记录
假设那个错误结论已经被提交,处理方式就不同了。再次用编辑器把状态改为:
状态:搜索已经上线并通过验收。
这是教学反例,不是功能真的上线。只在当前练习仓库执行:
git add CHECKPOINT_NOTES.md
git commit -m "demo: record an intentionally wrong completion claim"
git status --short
git log -1 --oneline
git show --stat HEAD
确认工作区干净、最新提交正是故意造出的错误,然后执行:
git revert --no-edit HEAD
git log --oneline --max-count=4
revert创建一个新提交,用相反的改动抵消所选提交;原错误仍在历史里,可以看清它如何被修正。本实验里目标就是刚刚核对过的HEAD。真实项目如果错误在更早的提交,要先定位正确提交,不能机械地撤销最新版本。较复杂改动可能发生冲突,应停下来理解差异;尚未完成的revert可用git revert --abort退出。git-revert文档
预期现在总共有四条提交:基线、正确结论、故意错误、撤销错误。文件最终又写着“搜索尚未实现”。Git帮你恢复的是已记录的代码或文档状态;它不能退还已经调用模型的费用,也不能自动恢复被程序改过、但没有纳入版本的数据库。
用独立检查证明自己没有撤错
不要仅因为命令没有报错就认为练习完成。仍在starter目录,执行:
git status --short
git diff main HEAD --name-only
git diff main HEAD -- project SEARCH_SPEC.md search-cases.json audit_cases.py PERSONAL_NOTES.md
git log --oneline --max-count=4
四个结果应分别是:无未提交文件;只有CHECKPOINT_NOTES.md与main不同;应用、规格、案例和临时想法文件无差异;历史保留四条记录。即使最终文件正确,错误提交与撤销提交依然可以在历史中看到。
再运行包外层提供的只读检查脚本:
macOS/Linux:
python3 ../verify_checkpoints.py
Windows PowerShell:
py -3 ../verify_checkpoints.py
预期显示“检查点练习通过:4次提交,工作区干净,应用与规格未变。”脚本检查固定练习目录、分支、提交父子关系、最终文件和各阶段差异,不运行应用、不修复仓库,也不创建提交。若你的文件或提交顺序与教程不同,它会给出未通过原因;不要为了凑结果在真实仓库里删除历史。
作者在临时副本实际演练了这些命令,并检查:取消暂存仍保留工作区文字,文件恢复没有碰无关文件,错误提交被新提交抵消,忽略路径未进入基线。随后分别故意留下未跟踪文件、改动应用、破坏最终结论,确认独立脚本能拒绝这些错误状态。应用源码没有改变,本篇没有重新声称搜索或模型调用通过了测试。
把这套流程交给Kimi Code时,责任怎样分
完成练习后,可以把同样的检查习惯用于真实Agent任务。安装与登录已完成的读者可在自己的练习仓库运行kimi --plan,发送下面的审阅请求;它可能消耗Kimi额度,手工执行本篇不需要这一步。
先确认仓库根目录、当前分支、git status和本轮diff。
只解释修改范围,不修改文件、不暂存、不提交、不撤销。
区分本轮任务修改与原有未提交工作;无法确定归属就明确说明。
核对CHECKPOINT_NOTES与SEARCH_SPEC的状态是否一致。
不要读取运行data、个人配置或密钥,也不调用应用模型。
最后给出建议的提交范围和仍需验证的事项,由我执行提交。
当前文档与本机help均提供--plan,但规划模式不等于操作系统只读隔离;仍应核对实际动作。Kimi Code命令文档
下一次让Agent正式改代码时,先记录已存在的未提交工作;在干净或归属明确的起点开始,每次完成一个可验证的小目标,再审阅diff与检查结果,提交这个目标。提交说明写清结果,例如“限制历史搜索为当前会话问题字段”,比“AI修改”更方便日后定位。
遇到这些情况,先查原因
| 现象 | 先检查什么 |
|---|---|
git找不到,或不认识switch、init -b |
Git是否安装、终端是否重开、版本是否过旧 |
| 提交要求设置身份 | 是否在starter里配置了本仓库的user.name和user.email |
git diff为空,却知道改了文件 |
是否已暂存,或文件仍是??未跟踪状态;分别看status和暂存diff |
| 取消暂存后新文字还在 | 这是正确结果,取消选择不等于删除工作区修改 |
| 切分支被拒绝 | 本地修改可能被覆盖;先看状态并保存,不加强制参数硬切 |
| 提交要求签名或触发自己配置的钩子 | 这是本机配置影响,先看错误;不要为练习关闭其他真实项目的规则 |
| 检查脚本不通过 | 对照目录、分支、四次提交及文件内容,先定位第一项差异 |
git status --short的两列分别描述暂存区与工作区状态:本篇常见 M是未暂存修改,M 是已暂存修改,??是未跟踪文件。终端会保留这些空格,阅读时别把它们混成同一种M。git-status文档
最后想一想:Agent说“已经帮你回退了”,你应该看什么?至少看当前文件、工作区差异和提交历史,再运行对应验证。它如果只恢复了源码,却已经写过数据库,任务就不能仅凭Git状态干净宣布恢复完成。
本篇完成后,你拥有一个可检查的起点和一段能解释的修改历史。下一篇根据已经确认的搜索规格拆分页面、业务与存储任务,开始把功能做出来;届时每个小步都有自己的差异和验证证据。
06 任务拆分与架构边界——别让_AI_一口气重写整个项目
历史搜索的规格已经写好了,Git检查点也建立了。现在对Kimi Code说“按规格实现”,是不是就可以等它交付?
还差一个会明显影响成败的决定:这次改动应该在哪里发生,以及每做到哪一步就停下来检查。如果Agent同时改页面、查询、模型接入和数据格式,最后即使看到了搜索框,也很难判断它是否顺手破坏了其他流程。
本篇把这次需求拆成三个检查点:先做好只读查询,再接上网页,最后验证完整流程。最终得到一个能搜索当前浏览器会话历史问题的笔记助手。应用继续使用DeepSeek官方API,Kimi Code只是开发工具;搜索本身不调用模型。
下载完整材料与代码 · 分步开发任务 · 实现状态说明 · 上一篇:Git与检查点
核实日期:2026年10月4日。作者提供的reference是手工参考实现,已完成本地离线存储、HTTP与回归检查,不是Kimi实际生成记录。没有真实Kimi或DeepSeek请求,没有Windows实机或浏览器视觉测试。读者使用Kimi开发可能消耗自己的工具额度,运行本篇离线检查不需要模型密钥。
拆任务要拆出可以判断的结果
“先读代码、再写代码、最后测试”是一种时间安排,却没有说明任何一步的交付结果。更有用的拆法是让每一步都有明确输入、输出和检查方法。
| 检查点 | 交付结果 | 修改范围 | 怎样证明完成 |
|---|---|---|---|
| A:历史查询 | 给定owner与关键词,返回记录和截断提示 | storage.py | 临时数据库中的固定案例得到约定ID |
| B:网页接线 | 搜索框、清空、列表与错误反馈可用 | web_app.py、templates/web.html | HTTP请求返回正确列表、状态码与转义内容 |
| C:集成回归 | 新搜索与旧预览、生成、计划共存 | 原则上只修复发现的问题 | 搜索、证据统计、原有验收全部通过 |
A完成时,网页还可以没有搜索框,但我们能单独证明查询结果正确。B完成时,也不能只看搜索框存在,还要验证页面传给查询的身份来自当前会话。C把前两个小步放回整个应用里检查。
这三个检查点让失败变得容易定位:查询就漏了旧记录,先查A;存储结果正确但页面显示错误,先查B;搜索正常而旧生成流程坏了,结合C的失败和diff找意外改动。
架构边界从真实请求出发
“架构”不必一开始就意味着微服务或几十个目录。对这个个人项目,先说明每一层负责什么已经很有价值。
flowchart LR
A[浏览器提交关键词] --> B[首页路由:接收参数与取得会话身份]
B --> C[存储方法:隔离owner并匹配历史问题]
C --> D[(本地SQLite)]
C --> B
B --> E[模板:展示列表与反馈]
E --> A
这条搜索路径没有经过模型API。不要因为项目叫“AI笔记助手”,就让每个功能都多调一次模型。
首页路由知道HTTP请求是什么,也能读取会话;存储方法知道记录放在哪里、怎样选择;模板只需要知道要显示哪些条目。将这些职责分开,就不会让模板一边生成HTML,一边打开数据库;也不会让存储方法为了知道owner而偷偷读取浏览器请求。
本次规则很小,存储方法可以同时完成查询与匹配。不必为了看起来“分层完整”,立即新增service、repository、controller三个目录。等规则被多个入口复用或明显复杂,再考虑抽出独立业务函数。边界的价值在于减少相互依赖,不在于文件越多越专业。
本课还有几个明确保留的边界:api_client.py负责模型连接;原生成路由负责确认后生成;storage.py里已有的claim/finish负责操作状态;计划方法负责保存复习安排。这些与本次搜索无关,不应随实现搜索被重写。
准备起点,并先见到一次有意义的失败
解压到新目录,进入lesson-04。starter等于第02篇project,也等于第03篇starter/project;它有证据统计,没有搜索。reference是本篇完成参考。两个目录均有38个应用文件,差异只有三个文件。
SEARCH_SPEC.md和search-cases.json保留第02篇原稿,其中“未实现”描述的是当时阶段。当前两个快照的状态另记在IMPLEMENTATION_STATUS.md,不要把参考实现已经通过的结果自动套到自己的starter上。
主线在新的starter里练习,不覆盖第03篇仓库。若你已经在自己的旧仓库连续开发,可以审阅三个文件的差异再移植,保留原.git和运行数据;不要整包替换一个已经有个人修改的项目。
需要Python3.11或以上。在lesson-04目录建立环境,不必激活:
macOS/Linux:
python3 -m venv .venv
.venv/bin/python -m pip install -r starter/requirements.txt
.venv/bin/python verify_search.py starter --stage store
Windows PowerShell:
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r starter/requirements.txt
.\.venv\Scripts\python.exe verify_search.py starter --stage store
本篇沿用已经固定的openai 3.24.0与Flask 3.1.3,不新增依赖。安装依赖需要网络,但测试不发送模型请求。这里的首次失败是预期的:starter还没有search_history。看到缺少该方法与看到依赖缺失不同,先把环境错误处理好,才能把功能失败当作可靠起点。
按第03篇方法给新的starter建立基线,再开任务分支。以下命令在lesson-04执行;只有新解压、没有私人文件的副本才使用这组初始化步骤:
git -C starter init -b main
git -C starter config --local user.name "Course Learner"
git -C starter config --local user.email "learner@example.invalid"
git -C starter add .
git -C starter diff --staged --name-only
git -C starter commit -m "chore: record history search baseline"
git -C starter switch -c history-search
提交前检查清单是那38个源码与教学文件,没有配置、data或虚拟环境。已有自己的仓库时不重复初始化、不要覆盖署名,直接检查状态后创建自己的任务分支。
先约定接口,避免页面和查询各猜一套
步骤A新增的方法是:
Store.search_history(owner, keyword="")
输入是调用者明确传入的owner和一个关键词字符串。输出包含三个字段:
| 字段 | 含义 |
|---|---|
| keyword | 去两端空白后的原关键词,供页面回显 |
| items | 已排序、最多50条的原操作字典 |
| truncated | 是否存在第51条匹配,用来提示缩小关键词 |
保留原操作字典,使页面仍能使用其中的id、created_at、mode、state和payload.questions。没有必要为了一个搜索框改造数据库结构或重写所有历史链接。
这里采用一个小接口约定,配套检查也按它调用。若你选择另一种函数名或返回结构,需要同步调整调用处和测试适配层,但不能借此改掉“只搜问题、当前会话、先匹配再限制”等用户规则。实现接口可以协商,行为要求要保持明确。
输入不合法时抛出ValueError;合法但没有匹配时返回空列表。两者必须分开,否则81字符输入与真正没找到结果都会显示“没有匹配”,用户无法知道该怎样修正。
步骤A:只实现存储查询
如果使用Kimi,先在lesson-04启动kimi --plan,要求它读取规格与starter的三个相关文件,给出检查点计划,不立即修改。当前官方文档与本机2.0.2帮助均支持该参数;规划模式仍不等于系统只读隔离。Kimi Code命令文档
审阅后退出规划会话,运行普通kimi,重新提供规格路径、工作目录和步骤A任务,不假设新会话记住旧计划。完整请求在TASK.md。重点是:只改starter/storage.py,查询检查通过后停止,不顺手做页面。
不用Kimi的读者可以手工加入下面的方法,位置在Store类的history之后、claim之前。它与reference完全一致:
def search_history(self, owner, keyword=""):
"""Literal question search for one owner; no model calls or writes."""
require(isinstance(keyword, str), "关键词必须是字符串。")
keyword = keyword.strip()
require(len(keyword) <= 80, "关键词去除两端空白后不能超过80个字符。")
needle = keyword.casefold()
matches = []
with self.connection() as db:
rows = db.execute(
"SELECT * FROM operations WHERE owner=? ORDER BY created_at DESC, id DESC", (owner,))
for row in rows:
item = self.unpack(row)
question = item["payload"]["questions"][0]["text"]
if needle in question.casefold():
matches.append(item)
if len(matches) == 51:
break
return {"keyword": keyword, "items": matches[:50], "truncated": len(matches) > 50}
它先检查类型,去两端空白,再检查原字符串长度;接着用casefold()做不区分大小写的匹配。这保留了“先判断80字符,再做大小写归一化”的规格。casefold可能让某些字符展开,例如德语ß;本课也检查了这个边界。Python字符串方法
owner作为SQL参数传入,不拼接成查询字符串。Python sqlite3支持这种占位符绑定方式;本方法只有SELECT,连接沿用项目已有管理方式。Python sqlite3文档
为什么SQL中没有LIMIT 50?因为它限制的是候选记录,还没有完成关键词匹配。会话C最旧的“旧资料针”就在最近50条之外,提前限制会漏掉它。
参考实现先让SQLite把本人记录排序,再逐条匹配,累计到第51条匹配就停,返回前50条并标记还有更多。对本课固定排序规则,先排序再过滤与先过滤再排序得到的结果一致;关键是上限施加在匹配之后。第51条只用于判断是否截断,不表示已经数出了全部匹配数量。
这也有代价:匹配很稀疏时,可能检查本人全部记录;SQLite本身也要处理排序。适合教学规模,不代表已经解决大规模历史检索的性能问题。后续要优化,先测数据规模与耗时,再考虑索引或搜索结构,不能用漏结果换取表面上的快。
在lesson-04运行存储阶段检查:
.venv/bin/python verify_search.py starter --stage store
Windows将解释器换成.\.venv\Scripts\python.exe。预期两个测试方法通过,其中第一个逐一执行第02篇12个案例,另一个检查Unicode归一化与非法输入类型。这个脚本真正创建临时SQLite并调用查询方法,已经不同于第02篇只检查JSON材料结构的audit脚本。
然后运行git -C starter diff -- storage.py。确认只有查询方法新增,审阅通过再暂存这个文件并提交。此时不要把“查询通过”写成“网页搜索已完成”。
步骤B:接上HTTP入口和展示
接下来让Agent只改首页逻辑与历史区域,或者对照reference手工完成。首页处理函数改为:
@app.get("/")
def home():
try:
keywords = request.args.getlist("q")
require(len(keywords) <= 1, "请只提交一个关键词。")
result = store.search_history(session["owner"], keywords[0] if keywords else "")
except ValueError as exc:
return page(history=[], show_history=True, search_keyword="", search_error=str(exc),
history_truncated=False), 400
return page(history=result["items"], show_history=True, search_keyword=result["keyword"],
history_truncated=result["truncated"])
q来自URL查询参数,例如/?q=rag;owner来自已经验证的服务器会话。即使有人提交/?q=rag&owner=B,也不能用那个owner替换当前身份。重复提交两个q会返回400,因为第一版约定只接受一个关键词。
路由把校验失败转换成明确的400响应,不偷偷截断输入,也不启动模型回答。正常结果则交给模板,路由不自己拼接HTML。
模板只替换原来的历史区域,完整替换内容见reference/templates/web.html中{% if show_history %}开始的section。它包含一个GET表单:
<form method="get" action="{{ url_for('home') }}">
<label for="history-q">关键词(只搜本浏览器会话的问题)</label>
<input id="history-q" name="q" value="{{ search_keyword }}">
<p><button type="submit">搜索</button> <a href="{{ url_for('home') }}">清空关键词</a></p></form>
后面分别显示错误、当前关键词、截断提示、记录列表或无结果提示。记录仍链接到原show_preview路由,由原逻辑区分预览和结果。不要另开一个绕过owner检查的新结果入口。
这里没有依赖HTML的maxlength来执行80字符限制:前端限制可能被绕过,而且浏览器与Python对某些字符的计数并不相同。权威检查放在后端;页面收到错误后让用户缩短关键词。
模板保留自动转义,不给用户输入加safe。Flask通过render_template渲染.html模板时默认启用自动转义,输入<script>应显示为文字,而不是变成脚本标签。Flask模板文档
为什么不用原来的{% if history %}控制整个区域?空列表为假,若仍这样写,无记录时连搜索框和“没有匹配的问题”都会消失。show_history单独表示“现在是应该展示历史搜索的首页”,把页面是否显示与是否有结果分开。
完成后运行:
.venv/bin/python verify_search.py starter --stage web
预期四个HTTP测试方法通过,包含原12案例的页面检查,以及重复参数、HTML转义、会话隔离、无业务写入、旧链接和快照等检查。Flask测试客户端可以在不启动监听服务器的情况下发送请求、检查响应;它验证后端与模板集成,不等于已经完成真实浏览器视觉测试。Flask测试文档
查看git -C starter diff -- web_app.py templates/web.html。如果模型客户端、生成确认或数据库schema也被改了,先让Agent解释原因,通常应收回超出本次边界的修改。审阅通过后,只提交这两个文件。
步骤C:既要新功能正确,也要旧能力还在
在lesson-04运行完整检查。macOS/Linux:
.venv/bin/python verify_search.py starter
.venv/bin/python verify_preview.py starter
.venv/bin/python starter/acceptance.py
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py starter
.\.venv\Scripts\python.exe verify_preview.py starter
.\.venv\Scripts\python.exe starter/acceptance.py
三组预期分别为6、5、10个测试方法通过。第一组含存储和HTTP两层各12个规格案例,不要把subTest的案例数与最外层“Ran 6 tests”混为矛盾。检查reference时,把命令中的starter换成reference即可。
这些验证分别回答不同问题:
- 搜索是否遵守原约定,特别是旧记录、其他会话和字面符号。
- 已有预览是否仍显示正确的片段数、字符数,并保留资料快照。
- 原检索、生成确认、计划、重复操作、重启读取和源码打包是否仍通过。
搜索测试还让生成函数一旦被调用就报错,并比较重复搜索前后完整operations/plans记录。它不仅观察“页面没出现新卡片”,还验证了两个关键副作用边界。初次建立会话和Cookie更新仍按旧程序运行,不把它们误认为新建问答。
作者对reference实际运行了上述三组检查;未改动starter的搜索检查按预期失败。测试不读取你已有的数据库,而是在临时目录放入作者构造记录。这里的通过不证明模型回答质量、网络连通性或高并发性能。
自己打开页面,体验完整的一步
自动检查通过后,再用自己的工作副本体验一次。在lesson-04运行:
.venv/bin/python starter/run_local.py init
.venv/bin/python starter/run_local.py serve
Windows仍使用.\.venv\Scripts\python.exe替换解释器。只想看参考版本就把两处starter换成reference。init只创建缺失的非秘密配置;serve会在所选项目中初始化自己的data。不要加--live,本课无需真实API。
浏览器打开http://127.0.0.1:8765。刚启动没有历史时,应看到搜索框与“本会话没有匹配的问题”。先按默认值建立一次预览,不必生成,再返回首页搜索“RAG”:默认问题包含这个词,应能找到刚刚的ready记录。点击链接仍打开原预览,证据统计保留。
把关键词改成“根本不存在的字符串”,应显示无匹配;点击“清空关键词”,恢复最近记录。开一个没有原Cookie的新浏览器会话,不能看到刚才那条问题。注意普通浏览器新标签页通常仍共享原会话,不能拿它当隔离测试。
完成后回终端按Ctrl+C停止服务。页面上沿用的“第16篇”标识来自第三部分最终项目,本次没有顺手改文案品牌;它不会影响搜索行为。查看Git状态时,config和data应被忽略,确认三个源码文件之外没有意外变更。
常见卡点,以及什么时候该重新拆任务
| 现象 | 优先定位 |
|---|---|
| 报缺少search_history | 是否仍在未修改的starter,或改到了reference/别的副本 |
| 存储通过,页面12案例失败 | 首页是否调用新方法,模板是否使用返回字段,空列表是否隐藏了整个区域 |
| 旧资料针查不到 | 是否在关键词过滤前使用了LIMIT 50或复用了旧history结果 |
%搜出所有记录 |
是否改成了未经处理的LIKE通配符规则 |
| 81字符返回空列表而非错误 | 后端是否校验并返回400,而非吞掉输入错误 |
| 查询别人owner就显示其记录 | 是否误把URL参数当成身份;必须回到session来源 |
| 手工页面为空,但测试通过 | 测试使用临时构造数据,不会往你的运行数据库插入57条记录 |
| 新搜索通过,原10项失败 | 先读首个失败与diff,不通过修改旧测试掩盖意外行为变化 |
如果步骤A已经复杂到解释不清“匹配、排序、读取”分别在做什么,就可以再拆成纯规则函数和数据库适配;如果只是三个返回字段,先保持小而直观。拆任务不是预先猜出所有代码细节,而是让每个阶段的结果可以单独检查。
最后做一个判断:Agent建议为了搜索增加向量库、切换模型、重做登录,是否应立刻批准?先用当前规格说明它们解决什么尚未满足的问题。当前关键词检索并不需要这些变化;真正新增需求时,再作为独立任务安排。
本篇交付的是能工作的搜索,以及一条“规则—查询—页面—回归”的开发路径。下一篇把项目入口、长期约定、单次任务与进度记录分开保存,让新一轮Agent能够理解这个项目,而不用每次重新讲一遍所有背景。
07 项目约定与文档——哪些事情应该让智能体每次都知道
你换了一个Kimi Code会话,准备继续开发笔记助手。新助手读到一份旧规格,里面写着“搜索尚未实现”,于是开始设计你上一课已经完成的功能。你又得解释:搜索已经有了,应用还是调用DeepSeek,今天只整理文档,别动生成流程。
这不一定是模型记性差。项目里如果同时摆着旧任务、现行规则和零散聊天结论,新会话就缺少一个明确的入口。我们需要把“这个项目长期怎样工作”“目前做到哪了”“这一次要做什么”分开保存。
本篇给已完成历史搜索的项目整理一套小型开发文档。交付结果是一个新会话能够找到入口、识别现状并遵守任务范围的工作区。应用功能不变,也不提前实现导出或其他后续需求。
下载完整工作材料 · 参考项目约定 · 新会话理解检查 · 上一篇:任务拆分与架构边界
核实日期:2026年10月4日。本机Kimi Code为2.0.2,仅核对help/version与官方文档。本文文档由作者整理,没有真实Agent读取或生成实录;“文件存在”不等于已经验证Kimi自动加载。配套应用与验证命令完成本地离线检查,未调用模型。
别把所有信息都塞进同一份说明
先看笔记助手里的四句话:
| 这句话 | 适合放在哪里 | 原因 |
|---|---|---|
| 应用使用DeepSeek,Kimi Code用于辅助开发 | 项目长期约定 | 每轮修改都容易遇到的边界 |
| 搜索已完成,上一轮检查通过 | 当前状态 | 会随实现与验证进度变化 |
| 这一轮只整理文档,不动应用源码 | 本次任务 | 只约束当前工作范围 |
| 搜索只匹配当前会话的问题文本 | 行为规格 | 定义某项功能应该怎样工作 |
再加一份README作为入口,基本就够了。没有必要为个人项目照搬企业里所有文档分类;只有在这些信息开始互相干扰时,才值得分开。
最常见的错误是把“今天还没做完”写进长期约定,过几天忘了删。另一个错误是把完整需求、实现说明、测试记录复制到三处,改了一处却漏掉另外两处。文档拆开后,应通过链接引用详细内容,而不是每份文件都抄一遍。
可以用一句简单的问题判断:下一轮换一个任务,这句话还应该原样成立吗? 若是稳定边界,放长期约定;若是今天的安排或结果,放任务与状态。
AGENTS.md能帮什么,不能保证什么
Kimi Code当前官方文档把AGENTS.md列为项目指令文件的一种形式,也支持项目树里的.kimi-code/AGENTS.md。这些说明作为上下文中的参考信息提供给Agent;它们与通过--agent-file选择的Agent定义不是一回事。本课只使用项目根目录的一份AGENTS.md,不配置自定义Agent。Kimi项目指令文件
它适合写入口、约定、验证方式与交付要求。它不会把一句“不要越权读取”变成数据库访问控制,也不会因为写了“必须测试”就自动证明测试执行过。真正的owner隔离仍在代码里,验证证据仍来自执行结果。
因此,本课做两层检查:先核对文件和事实,再让读者在自己的新会话里检查Agent能否正确找到并解释它们。作者没有发起真实Kimi请求,所以不能把本课离线检查说成“已证明模型一定会遵守AGENTS”。
这里也不使用用户级的全局指令文件。官方文档区分全局与项目内指令;把笔记助手特有的“默认离线、运行DeepSeek”等规则放到全局位置,可能影响其他不相关项目。本课把它们留在学习工作区内。Kimi数据与指令位置
不同工具和版本的指令发现方式可能不同。若要迁移到别的Agent,保留通用内容,再核实它支持的文件入口;不要只改一个文件名就假定作用完全相同。
先让人能找到入口,再让Agent读懂
解压练习包,进入lesson-05。这次不再有starter/reference两个应用目录,只有已经实现搜索的project。它与第04篇reference的38个文件逐字节相同。
lesson-05/
README.md 开发入口与可执行命令
AGENTS.md 长期约定和导航
project/ 已完成搜索的应用
verify_search.py 搜索检查
verify_preview.py 证据统计检查
search-cases.json 原固定案例
docs/
PROJECT_MAP.md 代码入口与职责
STATUS.md 当前状态与验证范围
READING_CHECK.md 理解问题及核对答案
specs/search.md 当前搜索行为契约
tasks/005-docs.md 本次文档任务
archive/ 保留的历史规格原稿
工作目录始终是外层lesson-05,不是project。目录变化必须写清,否则上一课的verify_search.py reference原样复制过来就会失败。
按顺序打开外层README、AGENTS和STATUS,先回答三个问题:应用做什么,今天已经有什么,接下来允许改什么。README负责引导,AGENTS写稳定约定,STATUS记录现在;详细函数和搜索规则再按链接去查。
project/README.md和页面里还保留第三部分“第16篇”的旧标识。本课不改这38个应用文件,而是在外层README明确当前开发入口。旧文件有历史来源并不等于必须删掉,但要让读者知道哪个说明适用于当前工作。
如果你已经有自己的连续开发仓库,就将这套分类映射到自己的真实目录,不要直接覆盖原AGENTS或README。先比较并保留已有内容,尤其不要把本课的project/路径机械搬进一个源码就在仓库根目录的项目。
写一份短而能执行的项目约定
先阅读参考AGENTS,再在自己的练习副本中另存一份docs/archive/AGENTS-reference.md作为对照,随后编辑根目录AGENTS。你的版本需要覆盖四类信息。
第一类是入口。 从哪里开始读,应用放在哪里,当前状态由哪个文件记录。这里写路径,不写一整段口头背景。
第二类是容易被破坏的边界。 例如Kimi用于开发、应用仍用DeepSeek;身份来自session;旧预览使用资料快照;生成与保存计划保留确认。它们直接关联项目行为,值得每轮提醒。
第三类是工作方式。 开始前检查已有修改,只改变当前任务范围;发现文档和实现冲突要指出证据。无需要求每次都重新阅读所有文件,也不要加入与项目无关的“永远写最优秀代码”之类空话。
第四类是验证与交付。 指向真实命令,要求区分已经执行、未执行和仍有限制的检查。不让一句“完成了”代替可核对的结果。
参考文件中有这样一段:
## 交付
说明改动范围、实际执行的检查与结果、未验证内容;没运行就写未运行。更新当前任务记录与docs/STATUS.md,不把聊天中的愿望写成已完成。稳定规则留在本文件,阶段进度不要堆到这里。
它没有要求每轮都写长篇报告,而是要求结论可以对应事实。比如本轮只是修正README中的目录名,没有改应用,也没有重跑模型相关检查,就据实记录“命令路径已核对;应用代码未改;本轮未运行应用测试”。
检查自己的AGENTS:如果出现“待实现搜索”“下一次做导出”“昨天跑了6项测试”,把这些句子移到相应的状态或任务记录。若出现完整的R1—R7搜索规则,改成指向搜索契约的链接,减少重复维护。
项目地图要能带人走到代码,而不是重复架构口号
打开docs/PROJECT_MAP.md。这里没有画一张脱离文件的宏大架构图,而是把具体问题映射到真实入口:
| 想确认的事情 | 应该找到的入口 |
|---|---|
| 程序怎样启动? | project/run_local.py的main、initialize、check |
| 当前会话身份从哪来? | project/web_app.py中create_app里的guard与home |
| 搜索怎样匹配? | project/storage.py的Store.search_history |
| 空结果为什么也能显示搜索框? | project/templates/web.html的history-search区域 |
| 导出的应用源码包包含哪些文件? | project/package_project.py的FILES与build |
实际操作时,不只检查链接能否打开,还要在文件里找到对应函数或区域。函数改名了而地图没改,链接仍有效,却已经不能指路;这是只检查Markdown链接发现不了的问题。
地图里的说明保持短小。搜索长度、排序和可见范围由docs/specs/search.md统一规定;地图只告诉你去哪里理解实现。以后新增导出功能时,才补上相关入口,不提前把尚不存在的文件写成事实。
本课有一个很具体的交接边界:project/package_project.py仍只打包38个应用文件,外层AGENTS、状态和验证材料不会自动进入那个ZIP。如果你打算把“可继续开发的工作区”交给别人,必须包含这些开发材料,不能把“应用源码包已生成”误认为“所有项目知识已交接”。配套lesson-05.zip包含本课完整的50个文件,没有运行数据。
让旧规格保留历史,让当前状态反映现实
现在打开docs/archive/search-v1-before-implementation.md。它仍写着当时的“未实现”,这是第02篇原稿;不需要伪造历史,把它改成当时就已经完成。
再打开docs/specs/search.md。它保留同一套行为规则,但明确适用当前project快照,网页输入与错误映射也已写清。最后打开STATUS,确认当前搜索已经包含在代码里,以及作者验证了哪些范围。
这三份文件回答不同问题:当时怎样定义需求,现在行为契约是什么,目前实现验证到了哪里。AGENTS提醒Agent不要把archive里的过去状态当成新的待办。
遇到冲突时,要具体判断:若当前契约要求只搜问题,代码却开始搜答案,这可能是实现偏离;若任务明确批准升级为答案搜索,则应同步更新契约和案例。不能简单规定“代码永远对”或“文档永远对”,因为代码可能有错,文档也可能过期。
状态记录还必须带范围。写“搜索6个测试方法通过”,应说明哪个工作副本、何时运行、哪些命令、有没有真实模型调用。作者对参考材料的通过结论,不代表你的本地修改也已通过。
本课的docs/tasks/005-docs.md专门留下“读者记录”栏,初始写的是“未运行、未填写”。请把自己的检查结果放在那里,再更新自己的STATUS;不要直接复制作者结论冒充已经亲自执行。
用新会话理解检查验收文档
完成整理后,先手工做五问:应用是否换模型了、搜索是否已实现、从哪里跑验证、这一轮能改什么、应用源码ZIP是否包含AGENTS。每个回答都要给出文件路径或函数作为证据。核对答案在docs/READING_CHECK.md,它是作者的检查标准,不是模型回答实录。
已自行安装并登录Kimi的读者,可在外层lesson-05运行:
kimi --plan
发出一个明确的读取任务:
这是一次新会话的项目理解检查,只读,不改文件也不运行应用。
请先读取根目录AGENTS.md、README.md、docs/STATUS.md和docs/tasks/005-docs.md。
需要时再查PROJECT_MAP、当前搜索契约和对应源码,先不要看READING_CHECK答案。
请回答:应用使用什么模型服务?搜索是否已经实现?
本目录的搜索验证命令是什么?本轮允许改什么?
应用源码打包是否包含外层AGENTS和状态文档?
每个回答给出文件路径或函数依据,最后指出仍有歧义的地方。
不要读取个人配置、密钥或运行data,不要依据旧会话记忆猜测。
官方入门说明提供新会话与退出方式;如果当前已有长对话,可以退出后从这个目录启动新会话,或使用/new。重点是检查它能从持久文件重建理解,而不是靠刚刚聊过的内容答题。Kimi会话基本操作
这次开发对话可能使用你的Kimi额度。没有账户也可以请另一位读者按相同步骤手工核对,不影响完成文档整理。
不要只问“你读过AGENTS了吗”。模型回答“读过”并不足以证明理解正确。看它是否找到了真实路径、是否区分已经实现与待办、是否把Kimi开发工具误认成应用模型。即使明确读取后答对了,也只能说明这次给定上下文下回答合理,不能证明所有未来会话都会自动读取并遵守。
如果它仍建议从零实现搜索,把STATUS和search_history的实际位置指出来,再让它修正理解。先修正文档入口或歧义,不要因为一句错误建议就重复施工。
验证目录与命令,避免文档“看起来正确”
文档正确至少包含三个层次:链接能打开,说明符合源码,命令能在指定目录执行。它们不能互相替代。
本课将上一包的目标名starter/reference改为project,两个外层验证脚本只调整了参数选项、目标名称和用法提示,测试断言没有改变。因为目录确实变化,作者在新布局中运行了一次对应检查,确认命令不是照抄来的。
需要自行复核时,在外层lesson-05按README建立Python3.11或以上虚拟环境并安装project/requirements.txt。已有适用环境可复用;只读文档练习无需安装依赖。macOS/Linux验证命令为:
.venv/bin/python verify_search.py project
.venv/bin/python verify_preview.py project
.venv/bin/python project/acceptance.py
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py project
.\.venv\Scripts\python.exe verify_preview.py project
.\.venv\Scripts\python.exe project/acceptance.py
预期分别为6、5、10个测试方法通过。测试在临时目录运行,不需要复制自己的数据库,不需要真实Key。它们验证应用与调整后的入口能工作,不验证Agent有没有读文档。
若你只修正一处说明文字、没有改变目录或代码,通常先核对相应事实和链接就足够,不必每次重复全部回归。本课也没有为“文件包含某句话”编造一个所谓AI理解测试;五问仍需要人核对回答与证据。
完成后查看Git差异。本轮应集中在外层文档以及因目录调整而改变的验证入口,应用project的38个文件保持原样。若自己的整理不涉及脚本移动,连验证脚本也不必改。提交时写清“整理项目入口与当前状态”,不要写成“实现了新搜索功能”。
文档维护中最常见的几种偏差
| 现象 | 处理方向 |
|---|---|
| Agent读错项目 | 确认启动目录是外层lesson-05,再明确要求读取这份AGENTS与README |
| 根README与project/README说法不同 | 区分当前开发入口与继承的应用说明;给出范围,不让读者猜哪份更新 |
| AGENTS越来越长 | 移走当前进度、完整需求、历史聊天与重复规则,留下导航 |
| 昨天的“测试通过”被当成今天的保证 | 加日期、代码范围、命令和未验证项,修改后更新自己的记录 |
| 换个目录后命令全失效 | 核对工作目录、解释器和目标名,逐一运行受影响入口 |
| 文档写了不读取data,Agent仍尝试读取 | 把文档当协作约定,检查真实工具动作;后续权限章节再建立执行约束 |
再做一个迁移判断:一句“下一轮只整理README”,应该放进AGENTS吗?它属于单次任务;“默认离线,真实生成需要明确进入相应流程”则是长期边界;“本轮没有运行应用测试”是本轮状态。把这三句话放对位置,比增加一百条笼统要求更有帮助。
下一篇处理另一个问题:文档已经分好了,一次具体任务到底应该把哪些内容交给模型?我们会继续讨论上下文的选择、组织与更新,避免把整个仓库和全部历史一股脑塞进对话。
08 上下文工程——给_AI_正确的信息,而不是尽可能多的信息
笔记助手里有一个旧函数,查询语句末尾写着LIMIT 50。有人看到它,提出一个很合理的怀疑:“是不是只在最近50条记录里搜索,所以更早的问题找不到?”
如果只把这一小段代码发给AI,它可能立刻建议去掉上限。如果把整个项目、全部聊天记录和历史规格一起发过去,它又可能被“搜索尚未实现”的旧状态带偏,重新规划一个已经完成的功能。
这次真正需要回答的是:当前首页到底调用哪个函数,50条限制发生在匹配之前还是之后,有什么证据能验证判断?
本篇把这个怀疑作为教学调查,练习为一次任务选择、组织和更新上下文。它不是读者真实报障,也不是已确认的程序缺陷。完成后,你会得到一份有来源、适用范围和未核实事项的调查工作单;应用代码保持现状。
下载完整材料 · 上下文简报 · 读者工作单 · 上一篇:项目约定与文档
核实日期:2026年10月4日。本机Kimi Code为2.0.2,作者只执行help/version和本地离线验证,没有真实Kimi对话、上下文压缩或DeepSeek调用。提示词和失真摘要均为教学材料,参考判断不是Agent生成实录。
上一课整理文件,这一课决定本次读什么
上一课把项目约定、当前状态、规格和单次任务分开了。但它们存在于磁盘上,不代表每次请求都应该把全部内容发送给模型。
这里的“上下文”,是模型处理当前任务时能利用的信息:你的要求、项目指令、已经加载的文件、对话历史、工具返回内容,以及工具提供的操作说明。仓库是信息来源;一次请求中实际提供的内容才是当时的输入。索引里写了文件路径,也不等于文件内容已经被读取。
上下文工程就是围绕任务管理这些信息。它包含选择哪些资料、先读哪一层、怎样标记来源和时效,以及何时删去失效结论、补读缺失证据。写好一句提示词是其中一部分,但不能代替提供正确资料。
例如,“仔细分析,不要出错”没有告诉助手当前首页走哪个函数;一份包含真实入口、现行R5规则和旧记录案例的简报,则让判断有了具体依据。
模型的上下文容量有限,文字通常以Token计量,不能简单按一个汉字等于一个Token换算。即使容量很大,放进去的信息仍可能过时、互相矛盾或缺少关键前提。增加输入也可能增加处理耗时与用量,具体取决于模型、缓存和计费方式。因此,我们追求的是足以完成当前判断的信息,而不是固定要求“越短越好”。
flowchart LR
A[本次要回答的问题] --> B[入口和现行规则]
B --> C[相关源码与独立案例]
C --> D[结论和未核实项]
D --> E{还缺什么证据}
E -->|有具体缺口| C
E -->|足够支撑本次交付| F[保存简短记录]
这条路径允许按需补读。过度精简也会出错:只给LIMIT 50,却省掉函数名和调用者,就删掉了判断最需要的背景。
从一个可独立使用的工作区开始
解压后进入外层lesson-06,它包含上一课的应用与文档。本篇project的38个文件、两个验证脚本和固定案例都与第05篇相同。
新增的工作材料集中在这里:
lesson-06/
README.md
AGENTS.md
project/
verify_search.py
search-cases.json
docs/
STATUS.md
specs/search.md
tasks/006-context.md
context/
search-review.md 本次任务的资料入口
worksheet.md 自己填写的调查记录
reference.md 完成后再看的作者答案
先读README、AGENTS、STATUS和任务006。docs/tasks/005-docs.md以及旧READING_CHECK保留上一课练习,文件开头已标注历史范围。上一课“本轮只整理项目约定”不会自动成为所有未来任务的永久限制;本课有自己的调查目标和允许范围。
本次只需编辑工作单、上下文简报和STATUS中的读者记录。不要为了验证一个还没确认的怀疑先改代码。如果你继续自己的仓库,先查看已有修改,记录工作副本和提交号;没有Git就记录目录与日期,不要虚构版本号。课程快照中的判断不能直接替代你改过的项目。
没有Kimi账户、没有安装Python环境,也能先做文件阅读和证据分类。实际执行离线检查需要Python3.11或以上与配套依赖,README给出了macOS/Linux和Windows的虚拟环境安装命令。只读练习不要求提前配置模型密钥。
先选资料,再写“请分析”
打开docs/context/worksheet.md,先填“为什么需要这份资料”,再读答案。针对“旧问题会不会被50条上限挡住”,第一批资料不多:
| 资料 | 要解决的疑问 | 这次读到什么程度 |
|---|---|---|
| 当前搜索契约 | 原本应该搜谁、匹配什么、显示多少 | R2、R4、R5及必要上下文 |
| web_app.py | 首页实际调用谁、身份从哪里来 | create_app里的完整home函数 |
| storage.py | 匹配、排序、停止与截取的先后关系 | 完整Store.search_history |
| search-cases.json | 怎样区分先限量与后限量 | S08/S09及owner C的51条记录 |
需要执行检查时,再读验证器的Fixture、StoreChecks和入口。需要解释旧说法时,再比较Store.history。API客户端、全部课程文章、个人数据库和所有历史聊天,暂时都不能直接回答这个问题,不必作为第一批输入。
这是一种按问题逐层展开的读法。你不是禁止助手进一步查找,而是要求它说明:现有证据缺了什么,下一份资料怎样补上这个缺口。例如,在代码里发现另一个首页入口,就有理由扩大调用路径检查;仅因为目录里有个文件,并不意味着必须读取它。
小文件可以完整读,大文件先定位到相关函数再读取周围内容。不要只保留一句命中行:过滤条件可能在上面,截断可能在下面,实际调用可能在另一个文件。源码位置最好同时记录“路径+函数名”;单独记第几行,在文件修改后容易失效。
配套search-review.md已经给出一份参考索引。你可以先自己选择,再比较遗漏,重点看每一项是否有明确用途,而不是机械追求相同文件数量。
用调用路径处理看似冲突的资料
先在project/web_app.py中找到首页的home。它调用的是:
result = store.search_history(session["owner"], keywords[0] if keywords else "")
这行至少提供两个事实:当前入口使用search_history,可见身份来自session。接着阅读project/storage.py中的完整方法,会看到它先按owner限定记录并按时间和ID降序排列,再逐条匹配问题文本。找到第51个匹配后停止,返回前50条和是否截断的标志。
旧的Store.history确实使用LIMIT 50,但当前首页没有调用它。一个旧函数的行为,不能直接证明另一条正在使用的调用路径存在缺陷。 这也不意味着你可以顺手删除旧函数;清理影响并不属于本次调查。
再读现行R5:搜索范围是本会话全部操作,结果展示最多50条。这里的“全部”描述参与匹配的范围,“50”描述展示上限,不要求把所有命中一次性显示出来。
固定案例把这层区别表达得很清楚:
- S08:owner C有51条记录,最旧的C01含“旧资料针”。查询这个词,应返回C01。
- S09:同一批记录,关键词为空,全部匹配。应显示C51到C02,共50条,并提示截断;C01仍存在。
如果先拿最近50条再找“旧资料针”,C01会在参与匹配前就被排除。当前实现并不是这条路径。匹配稀疏时,它可能扫描很多记录;这是值得以后评估的性能问题,却不能据此声称已经做过性能测试。
遇到材料冲突,不要简单按“更新日期最新”裁决。先分类:规格定义应有行为,源码描述当前实现,测试输出证明某次执行的观察,历史原稿记录当时状态。如果代码偏离当前已批准规格,应调查实现;如果需求已正式变化,应更新规格与案例。新写的错误摘要也可能比旧的正确契约更新。
本例中,archive里的“尚未实现”有明确历史范围,当前源码与STATUS可以解释差异。我们保留原稿,同时避免把它当成今天的待办。
交给Kimi的是任务和证据入口
若你已自行安装并登录Kimi Code,在外层lesson-06执行:
kimi --plan
这一步进入开发工具,后续发送任务可能消耗Kimi额度,与笔记助手的DeepSeek用量分开。本课建议只做一次调查请求,核对回答后结束;没有把自然语言“只调查”当成严格费用上限或技术沙箱。
当前CLI官方文档说明,输入@可补全文件路径,选中的引用会提供文件上下文;@文件引用与/命令是两种机制。可以输入@docs/context/,从补全中选择search-review.md。路径候选受当前目录影响,先确认你在外层工作区。Kimi输入与文件引用
接着发送以下任务。即使不用文件补全,明确要求读取这些路径也能表达任务,但仍应核对实际读取和回答的依据。
本轮任务006:只调查,不修改文件或运行应用,不读取个人配置和已有data。
先读根AGENTS.md、docs/STATUS.md、docs/tasks/006-context.md,
再读docs/context/search-review.md列出的第一批来源,先不要看reference答案。
教学假设是“当前首页先取最近50条,再搜索,所以旧问题可能漏掉”。
请沿当前home到存储方法的调用路径核对,并用R5、S08和S09解释判断。
将回答分为:已核对事实、还需验证的推断、不能确认的范围。
每项给出文件/函数或案例编号。若资料不够,先指出具体缺口。
不要因为旧规格写过未实现而重做搜索,不要扩展为答案搜索或性能优化。
本次不发起应用或额外外部模型请求,不运行测试命令;验证由我在本地另行执行。
预期是一份能顺着证据检查的调查结论。它应区分history和search_history,说明S08与S09并不矛盾,并承认自己没有运行测试。若它直接给出重写方案,先回到本次目标和实际入口,不要让未经证实的猜测变成新的施工计划。
你可以逐条打开它引用的文件,检查说法是否存在、是否漏掉条件。路径引用只是定位手段,不是正确性证明;“我已阅读”也不能代替这一步。
自己验证关键区别,不把模型回答当测试结果
完成静态阅读后,可以在外层lesson-06运行已有的定向存储检查。它使用公开构造案例和临时数据库,不读取你自己的运行资料。
macOS/Linux:
.venv/bin/python verify_search.py project --stage store
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py project --stage store
预期为2个测试方法通过。其中test_spec_cases包含12个固定案例,另一个检查Unicode与非法类型边界。输出通常只列方法名,不逐条显示所有通过的子案例;不能因此以为只测了两条记录。失败时应结合子案例编号和断言定位,不把报错整段替换成“再优化一下”。
作者本轮在新材料目录实际执行了这条定向检查,并用同一批临时数据做了对照:先调用旧history再筛选“旧资料针”,得到空列表;调用当前search_history,得到C01。这个对照说明为什么调用路径重要,不代表线上曾发生过该故障,也不证明Kimi能稳定答对。
本轮没有修改应用或测试,未重新执行HTTP、预览和原应用的全部回归。上一课的检查记录仍是历史证据;本轮结论应写清自己实际做过的范围。你只完成阅读、没有安装环境时,就在工作单填写“静态核对,未运行测试”,而不是照抄作者的通过结论。
最后对照作者参考判断。合格结论类似:当前快照未证实所怀疑的“先取50条再搜索”问题,S08支持旧命中能够被找到;真实用户输入、数据和性能仍未核实。没有必要为了显得有产出而修改代码。
上下文需要更新,压缩不等于证据永久保存
调查过程中,最初的假设可能已经被证据修正。如果后续还在重复“存在旧记录漏搜缺陷”,它就会污染下一轮判断。请把工作单中的猜测改为有范围的结论,并在简报或STATUS里注明:依据哪个函数、哪个案例,实际执行了什么,什么还不知道。
对于持续增长的会话,Kimi当前官方文档说明接近上下文窗口上限时会自动压缩历史,也可手动执行/compact并附带保留重点;/new会切换到新会话。压缩是对历史的整理,不应拿来代替核对源文件。Kimi会话与上下文
若自己的会话确实已经很长,可在Kimi输入框中使用:
/compact 保留当前任务006、工作副本、R5与S08/S09、实际调用路径、已运行检查和未验证范围。
这是给压缩过程的提示,不能保证每个细节都保留下来。完成后检查摘要;后续判断依赖的关键函数和案例,可以再读取一次。短小的新会话没有必要为了练命令而强行压缩,本课也未实测压缩结果。
如果决定换新会话,先保存自己的结论与来源,再用/new并明确读取更新后的任务和状态。这里讲的是上下文重新建立;更完整的长任务中断与恢复放到第15篇。
试着修正工作单中的这句教学摘要:
搜索验证通过,所以系统绝不会漏记录,可以删除旧资料并重写查询加速。
至少有三处越界:固定案例通过不等于绝无遗漏;删除资料不在当前任务中;重写加速没有性能数据支持。好的摘要必须同时保留结论和限制,否则越压缩越像一份错误的新指令。
遇到这些情况,应该补哪一块信息
| 现象 | 先检查什么 |
|---|---|
| 引用路径不存在 | 当前目录、实际文件名和版本;不要直接复制另一课目录名 |
| 助手坚持搜索未实现 | 让它区分archive、当前STATUS与实际home调用路径 |
| 只看到LIMIT就提出修复 | 补完整函数、调用者和S08,要求先说明证据支持到哪里 |
| 回答全是架构建议,没有调查结论 | 重申本次问题和交付物,减少无关背景,保留必要来源 |
| 输出说已测试,但未见执行 | 要求写明命令与输出;无法核对就记未验证 |
| 压缩后忘了范围 | 对照持久工作单,重新读取当前任务、关键契约与源码 |
| 测试报缺少依赖 | 按README建立虚拟环境,使用其解释器;不要通过删除测试来绕过 |
再换一个问题练习选资料:有人说“我搜‘检索’,为什么找不到默认问题?”优先核对问题原文、输入词和R4。默认问题是“笔记中的RAG流程是什么?”,搜索只匹配问题字段;“检索”出现在笔记或答案里并不会自动让该问题命中。应先确认真实输入与用户期待,再判断是符合当前规则,还是需要讨论新的需求。
这时不必把API计费文档、全部生成日志和旧导出计划都放进上下文。任务变了,需要的证据也随之改变;资料入口可复用,具体选择仍要重新做。
完成本篇后,你的应用仍是同一个搜索版本,多了一份能解释判断依据的工作单。下一篇会利用这些规格、案例和调用边界,进一步讲清怎样设计测试和解释反馈,让“验证通过”本身也成为可以审查的结论。
09 测试与反馈——怎样证明_AI_改出来的代码真的能用
上一课,我们沿着首页到存储方法的调用路径,确认当前搜索没有“先取最近50条再匹配”的问题。可是,假如下一次AI重构查询时不小心引入了这个错误,你靠什么及时发现?
手动搜一次“RAG”,看到结果,并不足以说明旧记录也能被找到。让AI自己再说一遍“逻辑正确”,同样没有增加多少证据。有效的反馈应该能区分两种实现:符合规格的通过,违反关键规则的失败。
本篇会补一个小型边界检查,再让它面对一个故意写错的查询。你将看到4项通过、2项指定失败、恢复4项通过的完整过程。错误只在测试进程中临时启用,不修改应用源码。最终交付是一份能说明输入、预期、实际结果与验证范围的测试报告。
完整练习材料 · 边界检查脚本 · 测试地图 · 报告模板 · 上一篇:上下文工程
核实日期:2026年10月4日。参考检查与教学反例由作者编写,已本地离线执行,不是Kimi生成实录。应用仍使用DeepSeek接口,本课不调用真实模型。Kimi Code仅核对本机2.0.2的help/version与官方文档;没有Windows实机、浏览器视觉或性能压测。
独立预期要先于“现在程序返回什么”
测试通常可以拆成三件事:准备已知输入,执行被检查的行为,把实际结果与预期比较。真正困难的往往是第三件事:预期从哪里来?
如果程序当前返回空列表,你就把空列表填进expected,测试当然很容易通过。但这只是记录了现状,未必检验了需求。尤其当AI同时写实现和测试时,它可能在两边重复同一个误解。
本项目已经有现行搜索契约。R5要求:在本会话全部操作中匹配,按时间和ID降序排列,最多显示50条,有更多匹配时提示截断。先不看实现,仅根据这个规则,就能写出以下预期:
| 已知输入 | 应显示多少条 | truncated应是什么 |
|---|---|---|
| 当前会话没有记录 | 0 | False |
| 恰好50条问题匹配 | 50 | False |
| 51条问题都匹配 | 50 | True |
| 51条记录中只有最旧那条匹配 | 1,必须是最旧那条 | False |
第四种情形特别有价值:它会让“先取最近50条再搜索”的错误暴露出来。第二种也不能省略,否则有人把截断条件写成“大于等于50”,可能在恰好50条时误报还有更多结果。
这叫独立预期:从已经确认的规则和可数清的输入推导答案,不从被测函数的输出反推。并不要求每条预期都手写50个ID;按固定编号生成已知顺序也可以,但不能再次调用同一个搜索算法来替它自己出答案。
先打开报告模板,把这四种情形的预期写下来,再阅读参考脚本。这样你是在审查一个答案,而不是先被代码暗示该答什么。
测试的名字不决定它实际覆盖哪一层
项目里已经有几组检查,容易产生一个误解:“使用unittest,所以都是单元测试。”unittest是运行和组织测试的框架,测试覆盖到哪里,要看它真正调用了什么。
| 检查方式 | 在本项目中做什么 | 仍然看不到什么 |
|---|---|---|
| 小函数的单元检查 | 单独检查输入转换、校验等局部逻辑,控制外部依赖 | 数据库、网页和多步骤连接是否正确 |
| 存储层集成检查 | 调用真实Store,读写临时SQLite,再检查查询结果 | q参数怎样映射、页面怎样显示 |
| HTTP层集成检查 | 经Flask测试客户端走会话、路由、模板和数据库 | 真实浏览器布局、交互与部署网络 |
| 离线流程验收 | 在临时数据上走预览、演示生成、计划确认等步骤 | 真实模型质量、线上环境与性能 |
本篇新增检查属于存储层集成:它不是模拟一个假数据库,而是在临时目录创建真的SQLite文件。因此既能核对返回集合,也能发现一部分查询和数据读取问题。
Flask官方文档说明,测试客户端可以在不启动真实服务的情况下向应用发送请求。本项目原有的--stage web就使用这种方式;它能检查HTTP状态和HTML内容,却不能证明页面在手机上没有遮挡或按钮实际好点。Flask测试文档
分层的作用是帮助定位和控制成本。存储检查通过而网页检查失败,应继续检查路由、参数、会话或模板之间的连接;不能据此直接断定一定是模板错误。少量重要流程再验证部件是否能连接起来。没有必要为了凑齐名词,把一个个人项目拆成几十套测试体系。
准备工作区,先运行正常实现
解压到独立目录,进入外层lesson-07。project的38个文件与上一课完全相同,旧验证脚本和57条固定记录也没有修改。新增的是verify_search_boundary.py、任务007、测试地图和报告模板。
先读README与docs/tasks/007-tests.md。上一课任务006的“只调查”属于当时范围,本篇允许在自己的副本中完善新增测试和报告,但不修改应用源码、旧断言或已批准规格。
新增脚本只依赖Python标准库及项目里的存储和校验模块。若只做这组练习,安装Python3.11或以上就够了,不需要API Key。要运行既有HTTP与流程检查,再按照README建立虚拟环境并安装项目依赖。已有合适环境可直接复用。
以下命令统一假定你已按README创建.venv,工作目录为外层lesson-07。
macOS/Linux:
.venv/bin/python verify_search_boundary.py
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search_boundary.py
只使用系统Python时,将上述解释器替换为macOS/Linux的python3或Windows的py -3即可;这一替换仅解决启动方式,运行后面的HTTP检查仍需确保该解释器安装了依赖。
你应看到4个方法通过,最后显示Ran 4 tests与OK,退出码为0。运行时间会因机器变化,不需要和文章一致。每个方法都创建自己的临时数据库,执行结束后清理,不读取个人笔记或已有运行数据库。
“原应用不变”和“新测试通过”是两条不同事实。前者可以用Git差异或文件比较确认;后者必须来自执行,不能根据文件存在就推断。
读懂一条断言如何守住边界
打开新增脚本,找到以下方法:
def test_exactly_fifty_matches(self):
self.seed(50)
result = self.store.search_history('A', '普通')
self.assertEqual([row['id'] for row in result['items']],
[f'N{i:02d}' for i in range(50, 0, -1)])
self.assertFalse(result['truncated'])
seed(50)准备编号N01到N50的50条记录,创建时间依次递增,问题都含“普通”。这些时间和文本由测试直接规定,不依赖当前时钟或真实模型回答。
第二步调用真正的search_history。第三步检查两件事:返回ID是否恰好按N50到N01排列,以及truncated是否为False。仅检查“列表非空”,即使只返回一条也会通过;仅检查长度等于50,则可能漏掉顺序或内容错误。
setUp负责在每项测试前准备临时数据库,addCleanup登记清理动作;assertEqual检查相等,assertFalse检查条件为假。它们是Python标准库的测试接口,无需为本课额外引入框架。Python unittest文档
再看最旧命中那项:51条记录中,只有N01的问题是“旧资料针”,预期ID直接写为['N01']。这份数据很小,却足以区分正确与错误的限量位置。
这里的payload只包含存储查询所需的问题字段,并不是可以送进网页生成流程的完整业务记录。这是有意限定的夹具:测试准备的数据只服务于当前层次。若要验证生成或预览,就应使用对应流程的完整夹具,不能把这个最小结构直接当真实业务样例。
本课补的是0/50/51附近的边界,不覆盖所有维度。它只有一个owner,不能证明跨会话隔离;它也不经过HTML,不能证明输入转义。相关风险继续由原有搜索检查负责。
让测试面对一个已知错误
现在启用教学反例。macOS/Linux:
.venv/bin/python verify_search_boundary.py --counterexample
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search_boundary.py --counterexample
脚本会在当前进程中临时把Store.search_history替换成错误方法:先用旧history取最近50条,再匹配关键词。它不会改写project/storage.py,也不会接触你的运行数据库;进程退出后这次替换不再存在。
这次应当失败。作者实际运行得到两个指定失败,以下为关键输出摘录:
FAIL: test_fifty_one_matches
AssertionError: False is not true
FAIL: test_old_match_beyond_recent_fifty
AssertionError: Lists differ: [] != ['N01']
FAILED (failures=2)
第一个失败说明:虽然展示了50条,但错误方法从未看到第51条,因此没能正确标出“还有更多”。第二个说明:最旧的N01在匹配前就被排除了。
空库和恰好50条仍然通过。这个细节很重要:坏实现完全可能通过部分正常用例。如果你只测“能够返回几条结果”,这次重构错误就可能悄悄留下。
如果需要查看退出码,在命令结束后立即输入:macOS/Linux使用echo $?,PowerShell使用$LASTEXITCODE。反例命令应为1;正常命令应为0。不要在中间再执行别的程序后才读取,以免拿到另一条命令的状态。
现在去掉--counterexample,重新运行正常命令,应再次4项通过。无需恢复应用文件,因为它们从未被修改。查看自己的Git差异,确认没有为了演练而改坏源码。
这是一次小型错误注入实验:用已知错误检查断言是否有辨别力。它不保证测试能抓住一切错误,也不是已发现的线上缺陷。日常验收只运行正常模式;不要把预期失败的反例命令混进“必须全部通过”的交付清单。
FAIL、ERROR和零测试,代表不同问题
终端出现红字,不一定意味着你成功验证了业务规则。
FAIL通常表示断言比较失败,例如预期N01却得到空列表。ERROR表示执行过程中出现了未按预期处理的异常,例如导入依赖失败、夹具出错。两者都需要查看具体原因,不能把任何非零退出码都当作“测试抓到想要的缺陷”。
本课的反例成功条件很明确:恰好上面两项FAIL,没有ERROR。如果你得到的是ModuleNotFoundError,连目标行为都没检查到。应先修正环境或入口,再观察断言。
还有一种更隐蔽的结果:Ran 0 tests后面也可能出现OK。默认测试发现使用test*.py命名模式,而我们沿用的验证脚本叫verify_...,不能用默认发现命令替代本课明确给出的运行入口。unittest测试发现规则
每次至少检查四件事:运行了哪个脚本,发现多少方法,哪些失败,退出码是什么。对于旧搜索脚本,一个方法中还包含多个subTest案例,方法数不等于业务情形总数;遇到失败时,案例编号比“总共跑了几项”更有定位价值。
把失败反馈交给AI时,也提供这些信息。例如:
在lesson-07执行verify_search_boundary.py --counterexample。
失败项为test_old_match_beyond_recent_fifty:预期['N01'],实际[]。
输入为同一owner的51条固定记录,只有最旧N01含“旧资料针”。
依据R5,不能先取最近50条再匹配。
这是故意启用的教学错误,不是当前应用缺陷,请解释原因,不修改项目。
这比“又报错了,帮我修一下”多提供了足以判断的事实。真实开发时,也应把允许修改的范围和验证方式写清;不要只给最后一行报错就要求助手猜完整背景。
用已有检查补齐连接处,而不是重复证明同一件事
边界检查通过后,再了解原来的分层入口。macOS/Linux:
.venv/bin/python verify_search.py project --stage store
.venv/bin/python verify_search.py project --stage web
.venv/bin/python verify_preview.py project
.venv/bin/python project/acceptance.py
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py project --stage store
.\.venv\Scripts\python.exe verify_search.py project --stage web
.\.venv\Scripts\python.exe verify_preview.py project
.\.venv\Scripts\python.exe project/acceptance.py
预期依次为2、4、5、10个方法通过。前两组分别在存储与HTTP层使用12个固定子案例。作者本轮已在新工作区实际运行这些入口;没有将上一课结果直接当成本轮输出。
存储层核对长度、大小写、字面匹配、排序等规则;HTTP层还检查重复q返回400、HTML转义、owner来自session、搜索不触发生成,以及原预览链接仍可使用。预览与原流程检查则保护证据统计、确认、防重复与数据恢复等已有行为。
其中用到了mock:在检查期间临时替换某个调用,让意外触发生成时直接报错。它适合隔离不该发生的外部动作,但不能把整个被测搜索函数替换成永远返回正确答案,再宣称搜索通过。教学反例中的替换目的恰恰相反,是主动展示一个确定的错误。
这些局部调用阻断也不是系统级网络沙箱。离线验证成立的依据包括当前代码路径、临时夹具、演示响应和对应阻断;不能推导为任意新代码都绝不可能联网。
以后不必每改一句文档就重复全部回归。改查询逻辑时重点验证结果集合、排序、隔离和网页连接;改模板时检查网页输出与必要手工体验;只改说明时核对事实和链接。选择应由变更影响决定,既不机械全跑,也不因为一项成功就省掉受影响路径。
让Kimi参与测试,但保留规则的独立来源
已安装并登录Kimi的读者,可以从外层工作区运行kimi --plan,先让它做一次测试评审。当前官方说明中的Plan模式用于先提出计划再审阅;它不能替你证明断言正确,仍要检查实际动作。Kimi交互与Plan模式
可使用这段任务:
请读取AGENTS、当前任务007、搜索契约R2—R7、测试地图及相关验证脚本。
这次只评审测试,不修改文件、不运行应用或额外外部模型请求。
先从规格推导预期,再指出哪些断言有独立依据、哪些风险仍没覆盖。
重点解释恰好50条与51条、最旧命中、owner隔离和网页转义各由哪里检查。
不要把默认返回值复制成expected,也不要为追求全绿删除断言。
给出最必要的一个补充建议,并说明它针对什么真实遗漏;没有必要就说明原因。
这次开发对话可能使用Kimi额度,读者自行决定是否执行。作者提供的脚本已足够完成离线练习,不要求真实Agent调用。
如果后续确有测试遗漏,让助手只修改已指定的测试文件,再人工审查预期和差异。一个Agent同时写代码和测试并非必然无效,但需要防止两边一起偏离需求。新的测试失败后,先判断实现、预期、夹具或环境哪一方有问题,不能默认“修改测试让它绿”就是修复。
也不必把所有工作都强行套进先写测试的流程。新增可明确描述的行为,很适合先写会失败的验收;探索性界面或简单文案调整,则可能先用可观察结果和人工检查。判断依据是风险与可验证性。
交付一份有范围的测试报告
完成后填写docs/TEST_REPORT.md,记录工作副本、真实命令、预期和实际结果、失败证据以及源码是否改变。作者参考材料里的通过结论不等于你自己的运行记录;跳过的步骤就写未运行。
最后回答两个问题:
- 边界4项通过,是否证明跨会话数据不会泄漏?不能。本组只有一个owner,必须看相应隔离检查。
- 10项离线流程验收通过,是否证明模型回答准确?不能。它使用作者构造响应;其中甚至有一项专门说明机械校验可能放过语义错误。
测试报告最有用的内容,不是“全部正常”四个字,而是哪些行为得到证据支持,哪些尚未检查。本篇完成后,应用功能不变,多了一组能抓住具体错误的边界检查和一份反馈记录。下一篇会进一步处理真实调试与代码审查:怎样从复现信息提出假设、完成最小修复,再审查AI到底改了什么。
10 调试与代码审查——让_AI_根据证据修复,而不是反复猜测
搜索框里输入“RAG 流程”,网页却返回了问题“RAG流程是什么?”。两个字符串中间有没有空格,原本应该影响本项目的连续子串匹配。更奇怪的是,存储层检查全过了,只有网页检查报错。
现在让AI“优化搜索”,它可能换匹配算法、重写SQL,甚至把测试预期改成允许这个结果。改动越来越多,你却越来越难解释:到底哪一处引起了问题?
本篇只处理这个具体故障。我们先稳定复现,再比较相邻层的行为,找到输入在哪一步被改变,做一行修复,最后审查真实差异与验证证据。你会得到一份可以交给别人复核的故障记录和修复报告。
完整练习包 · 问题记录 · 审查报告 · 上一篇:测试与反馈
这是作者专门构造的教学回归,不是上一课真实存在的缺陷,也不是Kimi生成代码实录。核实日期2026年10月4日;作者已离线复现并在临时副本验证修复。没有真实Kimi或DeepSeek请求,没有Windows实机、浏览器视觉或性能压测。
这次的project确实带错,先认清起点
解压后进入外层lesson-08。本篇与前两课不同,包含两个应用目录:
lesson-08/
project/ 故意带错,供你修复
reference/ 正确对照,只读保留
verify_search.py 仍检查project
verify_search_boundary.py 仍检查project
verify_preview.py 仍检查project
search-cases.json
reference-fix.diff 作者最小修复对照,先不看
docs/
tasks/008-debug.md
BUG_REPORT.md
REVIEW_REPORT.md
STATUS.md
reference的38个应用文件与第07篇正确版本完全一致。project只在一个文件中加入了一行错误处理,其他37个文件不变。交付包特意保留这个故障,让你能够从失败开始练习;“作者已验证修复”不表示你的练习起点已经修好。
三个验证器和固定案例沿用上一课,没有为了演练放宽预期。它们的目标仍是project;不要把参数改成reference,也不要打开第07篇的--counterexample。上一课的反例只在进程里生效,本篇错误实际存在于练习文件,重开终端不会自动消失。
先读README、AGENTS和任务008。应用仍使用DeepSeek,开发工具仍是Kimi。我们不需要个人数据库、模型Key或线上请求,也不准备修改模型配置、数据库结构和搜索规格。
如果你已经在自己的仓库继续开发,请另用这份独立练习包,不要把教学故障覆盖进正在使用的项目。
先保存基线,再取得一份稳定的失败
开始修改前,确认工作目录和已有变化。已有独立Git仓库的读者先运行git status --short,记下哪些是自己之前的修改,再按第03篇的方法保留检查点。本课不会替你提交或丢弃内容。
新解压包没有.git也可以练习:用编辑器把project/web_app.py的修改前内容复制保存为docs/debug-before-web_app.py.txt,原文件继续留在project。若同名备份已存在,换一个新名字,不覆盖过去的记录。备份只是一份审查基线,不参与程序执行。
Python环境按README准备:Python3.11或以上,虚拟环境安装project/requirements.txt。已有课程环境可复用。以下都从外层lesson-08运行。
macOS/Linux:
.venv/bin/python verify_search.py project --stage store
.venv/bin/python verify_search.py project --stage web
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py project --stage store
.\.venv\Scripts\python.exe verify_search.py project --stage web
第一条预期2个方法通过。第二条会运行4个方法,并在固定案例S10出现一个断言失败。作者实际输出的关键部分如下,路径与运行时间已省略:
test_spec_cases_over_http ... (case='S10') ... FAIL
AssertionError: Lists differ: ['A01'] != []
Ran 4 tests
FAILED (failures=1)
这说明实际结果含A01,预期却为空;不是程序完全打不开,也不是全部搜索失效。S10的数据为:owner A,关键词RAG 流程,A01的问题是RAG流程是什么?。R3要求保留内部空格,R4要求连续子串匹配,因此预期不命中有独立依据。
先把命令、案例、预期和实际结果写进BUG_REPORT。如果你的结果是缺依赖、零测试或另一条断言失败,先处理环境或版本差异,不要直接套本篇结论。没有稳定复现时,“我猜也是这个原因”还不能成为修复依据。
失败位置给出线索,不直接宣判根因
存储检查通过、网页检查失败,说明两条执行路径的某个差异值得调查。它不自动证明数据库无错,也不自动证明模板有错。
先列出几个能被证据检验的假设:
| 假设 | 怎样核对 | 什么结果会削弱它 |
|---|---|---|
| 规格本来允许忽略内部空格 | 阅读当前R3/R4与S10 | 规格明确保留内部空格 |
| 存储层会把空格忽略掉 | 用同一输入直接检查search_history | 直接调用返回空集合 |
| 网页入口改变了关键词 | 检查q读取后到存储调用前的处理 | 参数原样传入且其他条件一致 |
| 模板错误展示了不存在的记录 | 对比存储返回ID与页面ID | 存储已经返回A01,模板只是显示它 |
不要同时沿四个方向改代码。先用最便宜、最直接的检查缩小范围:读现行规则,比较两个层次使用的输入,再看连接处。
本项目的路径很短:
浏览器关键词q
→ home读取request.args
→ 调用Store.search_history(owner, keyword)
→ 返回items与keyword
→ 模板显示结果和输入回显
读home时,注意传给存储的表达式,而不只是函数名。参数即使类型仍是字符串,内容也可能已经变了。读search_history时,核对它本来在哪里做两端空白处理;不要因为看到两个层次都可能处理输入,就再增加一处“保险清理”。
实际项目可以用断点、少量临时输出或测试替身观察参数。输出只记录定位所需的信息,避免把完整用户文本、凭据或数据库内容塞进日志;确认原因后移除临时调试输出。本课用公开构造词语,无需接触真实用户资料。
找到改变输入的那一步
现在打开project/web_app.py,在create_app内找到home。练习版中这行代码是:
result = store.search_history(session["owner"], keywords[0].replace(" ", "") if keywords else "")
replace(" ", "")会删除字符串中的普通空格,包括中间那个。于是HTTP请求中的RAG 流程在进入存储前,已经变成RAG流程。存储层按照收到的词匹配到A01,模板又按返回内容显示出来。
作者用临时案例数据库跟踪这条路径,观察到:
| 观察点 | 实际值 |
|---|---|
| 直接传给存储的关键词 | RAG 流程 |
| 直接存储返回ID | 空列表 |
| 网页路径实际传给存储的关键词 | RAG流程 |
| 网页返回ID | A01 |
| 网页关键词回显 | RAG流程 |
这份对照比“网页层可能有问题”更具体:同一套构造数据下,入口改变了关键词,后续行为与改变后的输入相符。它还解释了为什么仅运行存储测试会漏掉这个回归。
不需要为了这次调查启动真实服务器。原有检查使用Flask测试客户端,可以在进程内经过请求处理与模板,适合观察这个边界;它仍然不是浏览器视觉测试。Flask测试客户端
根因现在可以写成一句因果完整的话:网页入口删除了关键词内部空格,使按原规格不该匹配的输入变成能够匹配的输入。不要只写“搜索bug”或“字符串问题”,因为那样无法说明修复应该落在哪一层。
只恢复原来的输入传递
将上述调用改回:
result = store.search_history(session["owner"], keywords[0] if keywords else "")
存储方法已经按契约使用strip()去除两端空白,并保留内部空格。恢复原样传递后,入口不再额外改变语义;长度检查、大小写处理、owner隔离和结果排序仍由既有路径负责。
这次最小修复是一行,但“最小”不是机械要求任何bug都只能改一行,而是每一处修改都有证据和任务上的理由。若问题涉及多个接口,可能确实需要多处协调;本例没有证据要求重写数据库或模板。
有两个看起来省事的方向不能接受。第一,把S10预期改成A01,只是让测试迎合错误,改变了已批准行为。第二,在模板里隐藏包含空格的关键词结果,只是遮住症状,还可能错误隐藏本来合法匹配的问题。
搜索无结果也不必转交给模型“智能理解一下”。那会把连续子串搜索变成另一个产品功能,带来新的语义和费用。需求如果要变,应另行明确,而不是混进一次回归修复。
没把握时可以查看reference-fix.diff和reference文件,但不要只复制整个正确目录后宣布“定位完成”。练习的价值在于解释错误与修复之间的因果关系。
先确认失败消失,再检查相邻行为
修复后,先重跑刚才失败的HTTP检查。macOS/Linux:
.venv/bin/python verify_search.py project --stage web
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py project --stage web
预期4个方法通过,S10不再失败。该组还核对输入回显、重复q、错误状态、转义、owner与原链接,因此比只手动看到一次空列表更有说服力。
然后运行相邻检查。macOS/Linux:
.venv/bin/python verify_search.py project --stage store
.venv/bin/python verify_search_boundary.py
.venv/bin/python verify_preview.py project
.venv/bin/python project/acceptance.py
Windows PowerShell:
.\.venv\Scripts\python.exe verify_search.py project --stage store
.\.venv\Scripts\python.exe verify_search_boundary.py
.\.venv\Scripts\python.exe verify_preview.py project
.\.venv\Scripts\python.exe project/acceptance.py
预期依次为2、4、5、10个方法通过。作者在临时修复副本实际执行了这些检查,未改变测试或案例。交付包的project仍保留起点故障,方便你自己重复整个过程。
本例已有S10能稳定阻止同类回归,不必为了这次一行修复再复制一个完全相同的测试。如果实际故障没有被现有案例覆盖,则应先保留一个能复现它的检查,再确认修复能让它通过。
若修复后又出现新的失败,先看它是否与这次变化相关、是否原本就存在,以及测试环境是否发生变化。不要为了追求全绿连续改动多个模块;每一轮只推进有证据支持的假设,保留失败信息和真实差异。
代码审查看实际差异,也看上下文
测试通过后,仍要审查。测试只覆盖已写出的情形,而一次修改可能夹带无关配置、放宽权限或删除其他分支。反过来,短小的diff也不自动意味着正确。
若使用了前面保存的单文件备份,可运行以下命令,两平台相同,需要Git:
git --no-pager diff --no-index -- docs/debug-before-web_app.py.txt project/web_app.py
--no-index比较两个磁盘路径,不要求它们已在Git仓库里。输出中的减号表示旧内容,加号表示新内容;有差异时通常退出1,不应误认成验证脚本失败。Git官方文档说明该模式隐含按差异返回退出状态。git diff文档
本例应只看到搜索调用去掉了.replace(" ", "")。还要读这行周围的getlist、重复q检查、异常处理与session取owner逻辑,确认它们仍完整存在。只看孤立的一行,可能看不出上下文被谁改过。
已有Git仓库则从这些命令检查,注意它们看的范围不同:
git status --short
git diff --stat
git diff -- project/web_app.py
git diff --cached -- project/web_app.py
普通diff看未暂存改动,cached看已暂存改动;新建未跟踪文件还要依据status打开检查。不要只看到一份空diff,就推断没有修改。其他人的既有改动要单独识别,不能为让本次差异好看而删除。
完成后,可再与作者正确文件比较:
git --no-pager diff --no-index -- reference/web_app.py project/web_app.py
完全按参考修复时无输出、退出0。与参考相同是一项对照证据,并不代替规格和测试;别的实现也可能正确,但任何额外变化都需要说明理由。
审查报告里要回答:触发条件是什么,为什么这一行能解决问题,有没有改测试预期,有没有绕过owner或确认,有没有残留调试输出,还有哪些未验证。不要用“AI说已修复”替代这些判断。
让Kimi提出可验证的假设,再授权小范围修改
已自行安装并登录的读者,可以在外层lesson-08启动kimi --plan。本机核对版本为2.0.2,当前官方文档说明Plan模式支持先提出计划供审阅;这有助于先看调查方向,不能代替代码权限和结果验证。Kimi交互说明
第一次请求可只做诊断:
请读取AGENTS、任务008、BUG_REPORT、搜索契约与相关源码。
project是故意带错的练习起点,reference和reference-fix.diff先不要看。
已知store阶段通过,web阶段S10实际['A01']、预期[]。
请提出最多三个可检验假设,沿q到search_history的路径核对。
先给证据、根因与最小修复计划,不编辑文件,不读取个人data或凭据。
不要改测试预期,不要将本次修复扩大为语义搜索、数据库重构或模型迁移。
核对它是否引用了真实路径、解释了两层结果的差别,再明确要求仅修改project/web_app.py的相关输入传递,并按本篇命令验证。一次诊断、一次修改通常足以完成这个练习;若持续往返仍无法解释原因,先停下检查证据,不要让它无限尝试。真实开发对话可能使用Kimi额度,作者没有替你执行。
修复后让它审查自己的diff可以帮助发现遗漏,但这仍是同一系统对自己工作的解释。你应独立查看差异和测试输出,必要时让另一位读者或另一个会话根据规格审阅。是否需要第二位审阅者取决于风险,不是所有小修复都必须增加一个Agent。
把修复交付成别人能复核的结论
完成BUG_REPORT与REVIEW_REPORT后,试着写一段交付说明:当关键词包含内部空格时,网页入口曾删除空格并改变匹配结果;现已恢复原词传入存储,保留既有两端空白处理;只修改web_app.py一行,HTTP及相邻离线检查通过,未验证真实模型、视觉和性能。
这段话同时说明了触发条件、前后行为、改动范围和证据,比“修了一个搜索bug”更能帮助后续维护。你没有执行的检查应从自己的交付结论中删去或标成未运行,不能直接复制作者记录。
如果复现不出本篇故障,先确认自己是否误用了reference或上一课的正确代码;如果HTTP仍然红,确认修改保存到了project,并核对S10的实际差异;如果只剩依赖错误,回到环境检查,不继续改业务。把失败分清楚,调试才不会变成随机改动。
本篇结束后,请从修复后的project或正确reference继续,不把故意带错的起点带入后续正常项目。下一篇进入AI应用评测:代码行为正确之后,怎样进一步检查检索、引用与回答语义,尤其是资料不足时是否能可靠地说明不知道。
11 AI_应用评测:代码测试通过,为什么回答仍然可能不可靠
笔记里写着“借阅期限为14天”,助手回答“可以借30天”,下面还认认真真附上了那句14天的原文。
这种输出很容易骗过一套看似完整的检查:JSON能解析,问题ID正确,引用的片段存在,引文也确实在原文中。程序没有报错,引用不是伪造的,用户却得到了错误结论。
上一篇,我们通过复现和代码差异修复了历史搜索故障。这一篇面对另一类问题:**代码按设计运行,并不代表它生成的回答达到了产品要求。**我们要为笔记助手建立一个小型评测集,把“找没找到资料”“引用是否合法”“结论有没有依据”“该不该说不知道”分别检查。
本篇的交付是一份机器检查报告和一份人工评审记录。约需40—60分钟,不需要模型账号、API Key或付费调用。用到的响应都是作者构造的教学案例,不是DeepSeek或Kimi的真实输出。
从正确的应用版本开始
下载第09篇完整材料,解压后在lesson-09目录打开终端。也可直接阅读工作区说明。
第08篇的project故意带有“删掉查询内部空格”的错误。本篇的project已经从它的reference正确版本复制,38个应用文件没有改动。若跟着自己的Git仓库学习,先确认上篇修复已经完成;不要用故障快照覆盖当前代码。
应用仍然使用DeepSeek官方API,Kimi Code仍是开发工具。本篇的必做路径完全离线,新脚本只调用本地资料读取、检索与验证函数,不导入模型客户端,也不读取应用运行配置。
先在编辑器中找到这些文件:
| 文件 | 本篇作用 |
|---|---|
eval/notes/library.md |
固定的原始笔记 |
eval/cases.json |
问题、检索关键词、人工标注的标准证据 |
eval/candidates.json |
准备送入检查器的构造回答 |
evaluate_offline.py |
调用真实本地函数,生成分层报告 |
docs/EVAL_REVIEW.md |
需要你填写的人工记录模板 |
docs/EVAL_RUBRIC.md |
评分规则与作者参考判定 |
资料、候选回答、评分规则分开存,是为了防止“用被评对象自己的说法证明自己正确”。标准证据需要人先读原文标注;它不是从候选答案倒推出来的。
先把一次回答拆成四个问题
本项目的资料问答大致经过这条路径:
问题 + 显式关键词
↓
从原始笔记切分并检索片段
↓
有命中 → 把选中片段交给模型 → 得到候选回答
无命中 → 在生成之前停止
↓
检查格式和引用 → 再判断语义及缺失行为
图中最后的语义与缺失评审是本课增加的人工检查,不是应用已经具备的自动裁判。
这里的“显式关键词”是应用目前真正使用的输入。它按关键词子串匹配片段,再排序选取;没有在后台自动做向量搜索或让模型改写查询。评测必须先理解当前系统,不能拿想象中的实现解释结果。
我们分四层看:
| 层 | 要回答的问题 | 典型失败 |
|---|---|---|
| 检索 | 必要的证据有没有进入所选上下文? | 资料有答案,却没搜到 |
| 机械检查 | 格式、ID、引文位置是否符合约定? | 引用了一句不存在的话 |
| 语义忠实 | 回答的事实断言是否得到资料支持? | 引用14天,却回答30天 |
| 缺失判断 | 有证据时回答、证据不足时承认不足了吗? | 编造罚款;有答案却拒答 |
Ragas原始论文同样强调,RAG评估需要分别考察检索上下文与生成回答等不同维度。本篇借用这种分层思路,使用自己的小题集和人工规则,不安装Ragas,也不把自定义结果称为该框架的指标。Ragas论文
分层的目的,是让下一步行动具体起来。检索没有交付证据时,应该先查资料切分、关键词和排序;证据已经完整交付,回答却改写了数字,才继续查生成环节。把所有失败统称“模型不够聪明”,很容易让你反复换模型,却绕开真正的问题。
用七个案例把盲区显露出来
打开eval/notes/library.md,全文很短:
# 社区书屋
借阅期限为14天。
续借需要在到期前向管理员申请。
按当前应用的切分方式,标题是F1-C1,借阅期限是F1-C2,续借说明是F1-C3。这些ID由文件顺序与切分顺序产生,不是永远不变的知识编号;将来修改资料后,必须重新核对标注。
现在看七个案例。前几项故意重复同一个问题,以便隔离变量:
| 案例 | 输入或候选中的关键差异 | 用来观察什么 |
|---|---|---|
| E01 | 搜“借阅”,答14天,引14天原句 | 正常对照 |
| E02 | 同样证据,答30天,引14天原句 | 真实引用掩盖错误结论 |
| E03 | 答30天,引用捏造的“借阅期限为30天。” | 机械检查能抓到什么 |
| E04 | 问借多久,却搜“贷款” | 原文有答案,检索无命中 |
| E05 | 问罚款,选中续借说明,答每天1元 | 相关不等于足够 |
| E06 | 与E05同样问题及上下文,标记资料不足 | 合理的缺失回答 |
| E07 | 已选中14天说明,却标记资料不足 | 过度拒答 |
E04没有候选响应。现有应用无命中时会在生成前停止;评测脚本也不把这个案例伪装成“模型看完资料后拒答”。
E05更隐蔽:它确实检索到一段关于借书的文字,但这段文字不能回答罚款问题。有资料、资料相关、资料足够支持答案,是三个不同判断。
此时还要区分两个观察位置:评审者能阅读整份固定笔记,生成器在这次任务中只看到选中的片段。若整份资料有答案、所选片段没有,生成器谨慎标记缺失可能是合理的,同时检索系统仍然失败。不能要求模型凭空知道它没有收到的内容。
跑出报告,先不要急着庆祝“通过”
确认终端当前目录为lesson-09,并且Python版本至少为3.11。已有虚拟环境可以复用,但新评测不需要安装依赖。
macOS/Linux:
python3 --version
python3 evaluate_offline.py --report evaluation-result.json
Windows PowerShell:
py -3 --version
py -3 evaluate_offline.py --report evaluation-result.json
脚本把结果打印在终端,同时创建evaluation-result.json。如果同名文件已存在,会拒绝覆盖并退出2;复跑时换新文件名,或者去掉--report只看终端输出。
默认题集的实际离线结果中,summary应包含:
{
"cases": 7,
"retrieval_eligible_cases": 5,
"mean_gold_coverage": 0.8,
"mechanical_checked": 6,
"mechanical_pass": 5,
"skipped_no_context": 1,
"semantic_pending": 6
}
请用编辑器重新打开生成的文件,找到E02、E03、E04,而不是只读汇总。每项都记录了所选片段及原文、候选内容和机械结果。独立核对以下三件事:
- E02的候选是30天,上下文是14天,但
mechanical为pass。 - E03的
mechanical为fail,原因是引文不在对应原文中。 - E04的
selected_ids为空,候选为null,状态为skipped_no_context。
这才证明你看到的是本课要研究的情况。退出码0仅表示脚本完成了报告;它不表示所有回答合格。本脚本是一个收集评测结果的工具,并没有把全部质量要求配置成发布门禁。
0.8究竟算的是什么
题集里五个“整份资料能够回答”的案例都标了一个必要证据片段。四个成功取到了它,E04没有,因此平均标准证据覆盖是(1+1+1+0+1)/5=0.8。
E05、E06的问题在原始资料中没有答案,标准证据列表为空。它们的覆盖值为null,不加入分母。把“没有标准证据可找”算成检索100%成功,会把指标人为抬高。
这只是本课基于人工标注的覆盖规则。真实任务可能需要两个片段联合回答,也可能有多个不同片段都足以回答;那时必须先定义必要证据与可替代证据,不能机械照搬此处ID集合。
同样,机械检查是5/6通过,不是5/7。E04未进入这一层。一个总百分比很难同时表达这些不同分母,更不能直接拿来判断“用户满意度”。
人工评审要找出结论与依据之间的关系
把docs/EVAL_REVIEW.md另存为docs/my-eval-review.md,先自行填表,再打开评分规则末尾的参考答案。
不要只写“感觉不靠谱”。以E02为例,写清楚:
候选断言:可以借30天。
收到的证据:借阅期限为14天。
机械结果:通过,引用确实存在。
语义结果:失败,期限与证据矛盾。
归因:已有必要证据,问题发生在候选答案的内容层。
对于较长回答,把每一个可核查的事实分别列出。例如“可以借14天,逾期每天罚1元”前半句有依据,后半句没有;不能因为第一句正确就让整段过关。这里关注的是对所给资料的忠实程度,不是让评审者用常识替回答补证据。
也不要要求答案逐字等于参考句子。“借期是两周”在本题语境下可以正确表达14天;字符串完全相等的检查会误伤合理改写。不过金额、数量、否定词、条件与时间范围必须保留原意。
E07则说明“少说就永远安全”也不成立。上下文已经给出14天,候选仍说“笔记未提供,无法判断”,虽然满足现有missing格式,却没完成用户任务。评估资料不足行为时,要同时观察无根据作答和不必要拒答。
按本题集规则,E01、E06合格;E02、E03、E05、E07不合格;E04没有回答可评。机器报告中的六项pending不会自动变成这些人工结论,你的评审文件负责保留判断和依据。
这是六份刻意安排的教学响应,包含重复问题和坏答案。**人工2/6合格不代表DeepSeek的准确率,也不代表真实用户会遇到同样的错误比例。**它证明的是:仅用当前机械验证器,不足以识别全部质量问题。
做一次对照:修改答案,为什么机器分数没变
现在用编辑器把eval/candidates.json另存为eval/candidates-trial.json。只改E02的answer,从“可以借30天。”改为“可以借14天。”,保留原题集和其他字段。
macOS/Linux:
python3 evaluate_offline.py --candidates eval/candidates-trial.json --report evaluation-trial.json
Windows PowerShell:
py -3 evaluate_offline.py --candidates eval/candidates-trial.json --report evaluation-trial.json
预期机械汇总完全相同:仍然5/6通过、6项语义待评。但E02的候选文本已经变化,人工判断应从失败改为通过。对比两个报告,其他案例应保持相同。
原因在project/validation.py中:对于supported回答,它检查引用存在、引文为原文子串等约束,没有实现“30与14是否矛盾”的语义判定。缺少这个能力不会因为换了一个好答案就自动显露为机器分数变化。
再思考一个问题:只把E03的伪造引文改成14天原句,保留30天答案,会怎样?答案是机械检查转为通过,语义仍错。作者已用临时候选副本验证这个反例,它正好回到了E02的结构。
这项练习是在修改构造响应,不是修改提示词,更不是改善真实模型。它帮助你验证评测工具的能力边界。后续若真的调整了提示词,应重新生成回答并保留真实请求与结果,再在相同规则下比较。
可以请Kimi帮忙,但评审标准要握在自己手里
已配置好Kimi Code的读者,可以在本篇目录运行下面的可选步骤。它会消耗开发工具的服务额度;本课没有要求批量生成或让笔记助手调用DeepSeek。
kimi --plan
写作日2026年10月4日本机kimi --version为2.0.2,帮助中确认有--plan;官方文档也提供/plan切换说明。模式用于安排计划与执行流程,不能取代你对实际工具操作的审查。Kimi交互文档
提交一轮范围明确的请求即可:
请阅读docs/tasks/009-evaluation.md、eval/cases.json、
eval/candidates.json、eval/notes/library.md、docs/EVAL_RUBRIC.md
以及project/validation.py。
只评审E02、E04、E07:列出关键断言、直接证据、失败层和理由。
说明为什么机械pass不能代表语义通过。
不改文件,不运行外部请求,不读取配置、密钥或个人data。
不要把构造候选当成真实DeepSeek输出,也不要为了全绿删案例。
这段请求由作者提供,未执行真实Kimi会话。你得到结果后,仍然回到原文逐条核对。如果Kimi说E04是“模型拒答”,就指出它没有候选响应;如果它认为E07很谨慎所以通过,就让它解释已收到的14天证据为何不足。
以后题集变大,模型裁判可以辅助筛查,但要先用人工标注校准、抽查分歧,并记录裁判自身的模型、提示词与消耗。让同一个模型生成回答后再笼统评价“是否优秀”,很难形成独立证据。
把小题集变成持续改进的起点
个人项目开始时,不需要立刻搭建大型评测平台。先积累几类真实失败:同义表达搜不到、只找到部分条件、引用正确却结论错误、资料不足仍编造、有依据仍拒答。去掉个人信息,再为每个案例补上可复核的原文与判断标准。
比较两个版本时,固定资料、问题、关键词、所选上下文或检索设置,以及生成参数。一次只改一个因素,才能解释改进来自哪里。若比较完整应用,允许检索结果变化,但要分别保留检索与回答记录,避免把两层变化混为一谈。
不要一边看结果,一边悄悄删掉失败题或放宽判分规则。开发题集可以用于调试;另外保留一些未参与调参的题,检查修改是否只对熟悉案例奏效。真实生成具有波动,单次成功也不能证明每次都成功;重复次数应结合任务风险与预算确定,不在入门练习里无上限重试。
本篇脚本刻意始终标记候选来源为authored_offline_fixtures_not_live_model。若将来加入真实API评测,应增加明确的运行记录与来源区分,不能把抓来的响应塞进本脚本,却继续冒用“作者构造”的标识。
遇到这些情况先查哪里
| 现象 | 优先检查 |
|---|---|
| 找不到脚本或资料文件 | 是否在解压后的lesson-09;保留eval与project目录结构 |
| 提示报告已存在 | 这是防覆盖;改报告名或去掉--report,不必删原记录 |
| 机械通过5项,误以为任务失败 | 先确认E03故意伪造引文;本课不追求全部候选为绿色 |
| 出现6项语义pending | 正常,脚本没有语义裁判;需要填写人工记录 |
| 覆盖率或片段ID变了 | 对比原文与报告hash;检查空行、标题、关键词、top_k和标注是否被改过 |
| 真实项目里标missing太多 | 分开查看检索漏证据、所选上下文不足、已给足证据仍拒答 |
作者完成了默认报告、候选改写对照、伪造引文变体、缺少/多余候选、重复JSON键及报告防覆盖检查,并确认38个应用文件与第08篇正确版相同。恢复正确版本后的历史搜索HTTP检查4项通过,包含上篇触发错误的S10。没有真实Kimi或DeepSeek调用,也没有Windows实机或浏览器视觉验证。
完成本篇时,你应留下原始报告、自己的人工评审,以及一次候选对照结果。项目增加的是一套能说明“哪里出了问题”的反馈材料,业务代码还没变化。下一篇将把这种分层观察延伸到日志、追踪和成本:一次失败经历了哪些步骤,花在哪里,又该从哪里恢复。
返回第四部分大纲 · 上一篇:调试与代码审查
12 日志、追踪与成本:一次失败究竟发生在哪一步
笔记助手显示“生成失败”。你打开终端,看到一行报错,又发现账户用量似乎增加了。现在有三个问题:它到底有没有找到资料?模型有没有返回内容?既然结果失败,为什么还可能产生消耗?
只保存最后一句错误,回答不了这些问题。一次操作可能已经完成检索、收到模型响应,却在校验引用时失败;也可能请求超时,客户端不知道远端是否已经处理。两种情况都会让用户觉得“没成功”,后续处理却完全不同。
上一篇用固定题集区分了检索、引用、语义和缺失行为。这一篇为这些步骤补上可追查的记录:让你从一次操作出发,找到它经过的阶段、停止的位置,以及哪些费用已知、哪些仍然未知。
我们继续使用Python笔记助手,Kimi Code辅助开发,应用服务仍是DeepSeek。必做练习完全离线,不需要Key,也不会发生实际API费用。约需40—60分钟。
起点:项目已有记录,但仍需要知道怎样串起来
下载第10篇材料,在解压后的lesson-10目录打开终端。当前应用在project/,38个文件与第09篇正确版本一致。上篇题集也保留在eval/,不必重新造一套数据。
项目并不是从零开始记录。project/card_pipeline.py中的run_task已经用同一个文件名前缀,把几类文件放在一起:
| 文件后缀 | 能提供的证据 |
|---|---|
-request.json |
准备发送的请求与实验参数 |
-sources.json |
这次任务使用的资料快照(有资料时) |
-attempt-01.json等 |
逐次尝试的状态、错误类别、用量与重试决策 |
-response.json |
接收到的原始SDK响应 |
-result.json |
应用最终如何处理该响应 |
这个公共前缀已经能帮助你把同一任务的资料找齐。相反,只有一个-request.json,没有对应结果,并不能证明请求没发出去:程序也可能在发送后中断,或保存结果时失败。
本课不读取你的个人运行记录。完整请求和响应可能带有笔记原文;我们用公开小样例学习调查方法,再把它映射回真实项目。
新加入的trace_lab.py是外层教学脚本:调用应用现有的资料读取、检索、校验和费用计算函数,围绕步骤写事件。模型返回和超时由构造资料代替。它没有自动改造网页应用的全部日志,也没有安装监控平台。
日志告诉你发生了什么,追踪把它们连成一次操作
一条日志可以记录“引用检查失败”。如果同时有三个人操作,你还需要知道它属于哪个问题、哪次执行、哪个阶段。
本课用四种ID建立关联:
| ID | 可以怎样理解 |
|---|---|
operation_id |
一次业务操作的编号 |
trace_id |
这一次执行链路的编号 |
span_id |
链路中一个步骤的编号 |
parent_span_id |
这个步骤属于哪个上级步骤 |
Trace可以理解为一次执行的完整路径,Span就是其中一段工作。OpenTelemetry也使用Trace与Span表达执行关联,并用父子关系组织子操作。本课采用这个思路手写少量JSON事件,不是OpenTelemetry格式的实现,也没有部署它的组件。OpenTelemetry追踪概念
一个正常情景大致如下:
operation(一条trace的根步骤)
├─ retrieval:实际执行本地检索
├─ model:注入作者构造的响应
└─ validation:实际执行机械校验
本课每次操作对应一条trace,三个子步骤的父ID都指向根步骤。真实系统未必一对一:同一业务操作可能后来恢复执行,形成另一条trace;同一模型步骤也可能有多次尝试。保留业务ID与执行ID的区别,是为了日后能回答“用户做了什么”和“这一次具体怎样执行”。
实现时,不必把所有函数重写。trace_lab.py只是把记录放在已有函数调用前后,下面这段与配套源码一致:
step = trace.start('retrieval', parent)
report, selected = retrieve(bundle, case['terms'], case['top_k'])
trace.end(step, 'matched' if selected else 'no_matches',
selected_ids=report['selected_ids'], selected_count=len(selected),
source_text_sha256=bundle['sources'][0]['text_sha256'])
开始时分配步骤ID,结束时补上结果与必要数量;原始片段不写进事件。这段代码由脚本提供,你无需粘进网页应用。若函数异常或进程中断,开始事件可能没有对应结束,调查时必须保留这种不完整状态。
case_id则是E01、E03等公开题集编号,帮助我们找到对应的教学输入;它不能替代每次运行产生的唯一执行ID。把第二次运行误认为第一次的后续,会让耗时与费用都算错。
第一步:生成四条可以调查的链路
新脚本只需要Python3.11或更高,不需安装依赖。从lesson-10执行。
macOS/Linux:
python3 trace_lab.py --out trace-run-01
python3 verify_trace.py trace-run-01
Windows PowerShell:
py -3 trace_lab.py --out trace-run-01
py -3 verify_trace.py trace-run-01
第一条命令新建目录,写入两个文件:trace.jsonl和summary.json。JSONL表示每行一个独立JSON对象,适合逐行写日志;本课不用专门查看器,用编辑器打开就行。
目录已存在时脚本拒绝覆盖,退出2。要再做一次,换成trace-run-02;父目录要已经存在。若写文件中断,保留已有部分记录,不把这个目录当作完整成功结果。
第二条命令是独立检查器,预期显示events: 26、operations: 4、result: pass。它检查的是本课固定情景的结构和预期,不是宣称所有业务情景成功。
四条链路分别是:
| 案例 | 发生了什么 | 最终状态 |
|---|---|---|
| E01 | 检索命中,构造回答14天,机械校验通过 | validated_needs_review |
| E03 | 检索命中,构造回答引用了不存在的30天原句 | validation_failed |
| E04 | 关键词“贷款”未找到借阅期限片段 | stopped_no_context |
| T01 | 使用E01的检索输入,随后注入超时情景 | request_uncertain |
T01没有真的等待网络超时,脚本没有发请求,也没有自动重试。它用来演示:如果真实请求出现这种结果,我们应当保留哪些未知信息。
E01也没有升级为“答案已审核”。上一课已经说明,机械校验通过后仍需语义核对;日志里的业务状态必须保留这个区别。
第二步:从最后失败的一步往前找证据
在编辑器中打开trace.jsonl,搜索"case_id": "E03"。你会找到8行:根操作开始,检索开始与结束,模型步骤开始与结束,校验开始与结束,根操作结束。
这8行的trace_id和operation_id应一致。span_id区分步骤,同一步的开始和结束共用一个ID。看看最后几行,你应该能得到这样的判断:
模型步骤:received_fixture,已有构造usage。
校验步骤:failed,error_category为cards_validation。
根操作:validation_failed。
所以当前证据指向“收到候选之后的内容校验”,而不是检索为空或请求未返回。接着去对应的公开候选和原文,才能定位到假引文。日志先帮你缩小范围,不能替代源文件和响应内容本身的核对。
再看E04。它只有4行,根操作与检索各一对开始/结束事件,没有model步骤。这说明在本次完整练习链路中,代码没有进入模型阶段。不要只因为最终没有回答,就把它称为“模型拒答”。
最后看T01。它有6行:检索完成,模型步骤标记timeout_unknown,决策为stop_no_retry,没有校验步骤。此时可以说“未拿到可供校验的响应”;不能进一步断言“服务端一定没有生成”。
超时后立即连点重试,可能产生重复工作和额外费用。现有项目已经保留生成确认及逐次尝试记录,本课不改变这些规则。真实调查应先结合已有操作、尝试记录和服务侧可核查信息确认状态,再决定是否重新发起。
时间戳和耗时也要写清范围
timestamp_utc记录本地写下事件的时间。跨机器比较时间先看时区与时钟;同一程序衡量经过时间,则可使用不随系统时间校准回退的计时器。本课用perf_counter测量本地步骤耗时,取前后两次读数之差。Python计时文档
model步骤的duration_ms是null,timing_scope为not_measured_provider。读出一个构造响应花了多少微秒,不能拿来代表DeepSeek响应速度。
根操作的耗时也只是run_case中的本地执行,包含本地记录等开销,不包含此前加载整套练习资料的时间。更不能称为用户从点击到看到页面的端到端耗时。没有写清测量范围的“平均耗时”,往往比没有数字更容易误导。
第三步:让残缺日志暴露出来
把整个trace-run-01目录复制为trace-run-broken。只在副本中用编辑器删掉trace.jsonl最后一行,然后运行:
macOS/Linux:
python3 verify_trace.py trace-run-broken
python3 verify_trace.py trace-run-01
Windows PowerShell:
py -3 verify_trace.py trace-run-broken
py -3 verify_trace.py trace-run-01
第一条应失败、退出1;第二条仍通过。被删掉的是T01根操作的结束事件。检查器不会因为前面的子步骤看起来合理,就补出一个“已经完成”。
这个反例说明,日志也需要检查完整性。但不要反过来认为真实日志缺少结束,就能证明业务一定失败:程序可能完成业务后没来得及落盘。正确结论是“这份证据不完整,需要进一步核对”。
配套检查器只认识本课四个情景。如果将来增加第五个案例,它会拒绝,因为固定预期已经改变。这时应审查新场景并更新检查规则,而不是把它当成适用于所有生产追踪的通用工具。
成本要从逐次尝试算,失败也可能有消耗
现在打开summary.json。里面刻意分了两类数字:真实练习执行,以及假设服务请求发生后的费用情景。
真实练习没有网络请求,所以actual_network_calls为0,actual_api_charge_cny为"0"。这只描述本脚本的API调用,不描述你整个电脑或账户的费用。
费用情景使用作者构造的用量,以及虚构的人民币单价,目的是让你手算并检查遗漏。下面这些数字不是DeepSeek当前报价:
| 类别 | 构造用量 | 虚构每百万Token单价 |
|---|---|---|
| 输入缓存命中 | 800 | 0.1元 |
| 输入缓存未命中 | 200 | 1元 |
| 输出 | 100 | 2元 |
DeepSeek当前Chat Completions文档把prompt_tokens定义为命中与未命中输入之和,并分别提供缓存计数和completion_tokens。实际估算要读取返回的用量,不能拿字符数或max_tokens上限当成实耗。DeepSeek响应字段
在本课虚构条件下,一次已知用量的费用是:
(800 × 0.1 + 200 × 1 + 100 × 2) ÷ 1,000,000
= 0.00048 元
代码复用project/costs.py的estimate_usage,用十进制数计算。不要把prompt_tokens=1000再加到800与200上,输入会被算两次;输出细项也不能未经核对再叠加到输出总量上。
E01得到0.00048元,E03同样得到0.00048元。E03最后校验失败,不会让情景中已经生成的内容变成“从没消耗过”。这也是为什么成本记录不能只筛选最终成功的操作。
E04根本没有进入模型步骤,所以在本课完整链路下,其情景模型费用可记为0。T01不一样:模型步骤已经进入模拟超时状态,usage未知,因此情景费用为null。
汇总于是得到:
情景已知小计:0.00096元
情景未知尝试:1次
情景完整总额:null(未知)
实际练习API费用:0元(全程离线)
已知小计与未知次数应该一起展示。把null转成0,会悄悄把“没有证据”改成“确定没花钱”。把已知小计叫完整账单,也会造成同样误导。
真实项目中,result顶层的usage只代表最后一次收到的响应。若发生多次尝试,应查看逐次attempt,不要既加顶层又加attempt而重复计费,也不要只算最后一次成功。请求有记录、结果缺失的任务还要另外列为待核查;本地文件不等于平台完整账单。
本篇不使用继承的prices.example.json报价。真实调用前应重新确认模型、币种、价格生效日期及适用时段,再保存当次估算依据;本课虚构单价不能直接拿来做预算。
把开发工具费用与应用费用分开
用Kimi Code改代码,是开发过程;用户运行笔记助手调用DeepSeek,是应用过程。二者即使围绕同一项目,也不是同一账户、同一记录口径。
建议你的项目记录至少分清:
- 开发工具消耗:哪次Kimi任务、做了什么开发工作,服务提供的是额度还是可核查金额。
- 应用模型消耗:哪个业务操作、哪个模型、几次尝试、哪些usage已知。
- 其他资源费用:如果以后接入外部搜索、云主机或存储,再单独列对应来源。
这不是要求你现在做财务系统。一张明确口径的表,就比把所有Token加在一起更有意义。订阅额度不能直接当成逐请求人民币账单,不同供应商的Token也不能在没有模型单价的情况下换算成钱。
预算同样要落到实际动作:一次任务最多尝试几次、允许输出多长、是否存在未知状态后重复发送。max_tokens有助于限制输出,却不限制全部输入成本,更不会自动限制Agent执行多少轮。当前离线脚本没有服务循环,后面的Loop章节才会把终止与预算控制组合起来。
让Kimi读诊断摘要,不要直接倒出所有记录
本课新事件只记录必要的ID、阶段、数量、状态、用量和错误类别,没有题目原文、笔记、完整响应、凭据或会话owner。它使用有限字段,而不是把异常对象或请求体整个写进日志。
这不意味着“只要用了这个类,所有内容都会自动脱敏”。Trace是教学用的内部记录器,调用者仍需控制传入字段。资料hash可以核对版本,也不等于加密或自动匿名化。已有-request、-response等原始文件更需要单独保护。
已经配置Kimi Code、愿意消耗开发额度的读者,可以在本课目录可选执行一轮:
kimi --plan
2026年10月4日本机版本为2.0.2,帮助确认--plan,官方文档说明/plan切换。作者本轮只运行了help/version,没有提交真实请求。Kimi交互说明
可使用这段请求:
请阅读docs/tasks/010-observability.md和trace-run-01中的两份文件。
它们来自公开离线练习,模型事件、usage和单价均是构造数据。
请分别解释E03与T01停止在哪一层,列出证据和仍未知的事实。
说明为什么0.00096只是情景已知小计,不是完整费用。
只给调查建议,不修改文件、不读取个人records/config/密钥,
不发真实请求、不自动重试,不把构造耗时写成模型实测。
若Agent回答“T01没收到结果,所以没有费用”,就拿usage: null要求它重新区分未知与零。若它建议立即批量重发,让它先解释如何避免重复调用。工具的价值是帮助组织证据,最终判断仍要能回到记录上。
完成检查与常见卡点
在docs/TRACE_REVIEW.md中另存一份自己的调查记录,填入E03、T01的trace ID、失败阶段、费用证据与下一步。先自己写,再看模板末尾的参考答案。
| 现象 | 先做什么 |
|---|---|
| 输出目录已存在 | 使用新名称,保留原调查记录 |
| JSONL打不开或解析失败 | 核对是否逐行读取;整个文件不是一个JSON数组,也可能中断残缺 |
| 每次trace ID、时间不同 | 正常,比较业务状态与关联是否正确,不要求整文件字节相等 |
| 本地耗时很短或为0.0 | 测量的是短小本地工作且经过舍入,不代表服务性能 |
| 没有model步骤 | 查前置阶段是否停止;不要虚构一次模型拒答 |
| 有开始没有结束 | 保留不完整状态,结合其他证据调查,不自行补成功 |
| 有已知小计但总额为null | 检查未知尝试;不要为凑总数填0 |
作者实际运行了四情景与26事件检查,验证了错误父ID、缺结束、伪造服务耗时、把未知费用改成总额等反例会被拒绝;新目录重复运行不会覆盖旧记录。应用38文件及第09篇评测基准字节不变。没有真实Kimi或DeepSeek调用,没有Windows实机、浏览器视觉或服务性能验证。
完成后,项目多了一套可运行的追踪练习和调查模板,真实应用仍保留原有行为。下一篇将进一步处理记录里会出现的“动作”:哪些读取、修改和执行可以自动进行,哪些需要明确边界与人的决定。
返回第四部分大纲 · 上一篇:AI应用评测
13 工具与权限边界:哪些事情可以自动做,哪些需要人决定
你让笔记助手安排一次复习。它先说“准备保存”,随后屏幕上出现超时。
这时最容易做的事情是再点一次。可是,如果超时发生在计划已经写入、模型正在组织最后一句说明的时候,再点一次可能得到两份计划。问题不在于模型回答得是否礼貌,而在于程序能不能区分:提出建议、获得授权、执行动作、解释结果。
上一篇用日志追踪了失败发生在哪一步。本篇继续向前走:在动作发生之前限定它能做什么,在动作发生之后判断它到底做了什么。我们用已有笔记助手的“保存本地复习计划”练习,不增加真实日历或消息接口。
读完并完成练习,你应该能审查一次工具调用的权限范围,辨认取消与执行失败,并在总结失败时检查实际结果。主练习完全离线;Kimi Code作为可选的开发审查助手,应用运行时服务仍是DeepSeek。
先把“让 AI 做”拆成两个现场
这个项目里有两个地方能产生动作。
第一个是开发现场。Kimi读取源码、修改文件、运行测试,帮助你开发笔记助手。第二个是应用现场。笔记助手接收用户请求,把模型提议交给自己的Python函数,保存复习计划。
两者需要分别授权。允许Kimi修改练习目录,不代表笔记助手获得了发送微信消息的权限;允许应用保存一份本地计划,也不意味着开发工具可以读取你的个人密钥。
DeepSeek的工具调用流程中,模型返回工具名与参数,执行函数的是接入它的程序。模型说“调用某工具”,不会让这个工具自行运行。因此,决定是否执行的代码是应用必须守住的入口。DeepSeek官方工具调用说明
本课已有代码可以用这条流程来理解:
模型提出一项计划
↓
程序检查工具名、字段和取值
↓
展示计划内容与实际目标文件
↓
用户确认 / 取消
↓
程序保存文件 / 记录取消
↓
模型解释工具结果
最后一步失败,不会自动撤销上一步。反过来,最后一句“已完成”也不能代替文件或服务回执。
从具体任务判断权限,而不是只看“读”还是“写”
权限可以理解为:在什么任务中,允许谁,对哪些对象,做哪些动作。
例如,“检查这份课程代码并运行已有离线测试”已经包含必要的源码读取和测试执行。每读一个文件都再次征求同意,会打断工作。可如果助手为了排错突然要上传真实笔记,就跨出了原任务。
下表是本课任务的范围约定,不是所有项目都要套用的审批表。
| 准备做的事 | 本课怎样处理 | 判断依据 |
|---|---|---|
| 阅读提供的公开源码与案例 | 直接继续 | 完成审查所需的输入 |
| 在新的练习目录写记录 | 直接继续 | 目标明确、可检查,旧记录保留 |
| 运行提供的离线检查 | 核对入口后继续 | 执行内容已知,不调用模型服务 |
| 读取真实运行记录或个人配置 | 先说明缺少什么证据 | 里面可能含笔记、请求与凭据 |
| 修改业务、安装额外工具 | 先判断是否超出本轮任务 | 本轮目标是审查,不是扩功能 |
| 给别人发消息、创建外部日历或发布应用 | 需要对应的明确授权 | 影响已超出当前本地练习 |
读取并非天然没有风险。把文件交给联网开发助手理解,可能意味着内容进入服务请求;“文件没有被改”不能证明资料没有离开电脑。执行命令也不能只凭名字判断:一个叫test.py的脚本,同样可以在运行时访问网络或删除数据。
实际审查时,先看工作目录、输入对象、输出目标和命令内容。授权已经覆盖的可逆操作继续做;确需扩大范围时,先把能完成的分析和预览做好,再让用户决定那项具体新增动作。不要只问一句没有内容的“是否继续”。
本地计划为什么没有任意文件写入能力
打开配套项目的project/review_tool.py。本课只提供一个工具:save_review_plan。它接收四个字段:标题、笔记ID、日期和分钟数。
程序没有把工具名直接当成Python函数名执行,也没有接受任意Shell命令。它先核对名称,再检查字段集合和具体取值。比如,参数里多出一个path,即使另外四项都合法,也会被拒绝。
这一步很重要。模型看到的工具参数说明,帮助它产生合适的提议;本地校验才决定不合格提议能否进入执行阶段。只在说明里写“不要多传字段”,不能代替程序检查。
分钟数的检查尤其适合帮助我们理解“看上去像数字”的问题。源码是:
require(type(data["minutes"]) is int and 1 <= data["minutes"] <= 120, "分钟数必须为1到120的整数。")
Python中,布尔值True是int的子类,但这里要求实际类型就是整数,所以JSON中的true不会被当成1分钟。这是输入契约的一部分。
再打开project/tool_flow.py。文件路径由受信的程序使用工作根目录、固定子目录和随机运行ID组成;标题只是写入JSON的文字,不参与拼接路径。确认时展示的计划,也是随后传给保存函数的那个计划对象。
保存函数使用open("x")独占创建文件:同一路径已存在时失败,避免覆盖旧文件。这是Python文件模式的行为。Python 3.11:open
这些限制的适用范围也要看清:save_plan自己接受调用者提供的路径,并不是通用的文件沙箱。当前流程依赖调用者提供可信根目录,不防范别的恶意本地进程同时改变文件系统。把工作区放在自己控制的新目录里,才符合本课前提。
此外,每次启动run_flow都会产生新的运行ID。防止同一路径覆盖,不等于防止同一业务任务重复执行。 如果未来要接真实日历,需要额外考虑稳定的业务操作ID、外部服务查询和去重能力;不能把一个随机文件名当成完整解决方案。
准备第11篇练习目录
下载并解压本篇材料包,终端进入含permission_lab.py的lesson-11目录。也可以直接打开材料说明。
起点是第10篇的正确应用:project仍有38个原样保留的应用文件。此前评测与日志练习也保留。本次新增脚本会调用真实的run_flow,但把“模型响应”和“确认选择”替换成作者预先编写的测试回调。
回调可以理解为程序传进去的一小段替代行为。这里的send直接返回构造数据;confirm按情景返回SAVE或CANCEL。它们只是自动测试输入,不是人类实际确认,更不能把固定返回SAVE的写法接入正式业务。
需要Python 3.11或更高版本。由于真实流程的导入链会加载SDK模块,仍需项目依赖。没有现成课程虚拟环境时,按系统执行:
macOS/Linux,当前目录为lesson-11:
python3 -m venv .venv
./.venv/bin/python -m pip install -r project/requirements.txt
./.venv/bin/python permission_lab.py --out permission-run-01
./.venv/bin/python verify_permissions.py permission-run-01
Windows PowerShell,同样在lesson-11:
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r project/requirements.txt
.\.venv\Scripts\python.exe permission_lab.py --out permission-run-01
.\.venv\Scripts\python.exe verify_permissions.py permission-run-01
已有相同依赖的环境,就使用其中的Python,不必重新建环境。依赖固定在project/requirements.txt;安装需要下载包,但练习过程不读取Key、不创建SDK客户端、不发送API请求。不要为了跟本篇练习而运行planner.py的默认入口,那是另一条可能联网的业务路径。
第一条练习命令生成report.json,以及P01—P07各自的记录和控制台文本。输出目录必须是新的;重复使用同名目录会被拒绝。要再跑一次,改为permission-run-02,保留第一次结果。
第二条命令使用单独的固定预期,检查实际状态、文件数量与内容。通过时输出:
{"result": "pass", "cases": 7, "saved_plans": 3, "actual_api_calls": 0}
这里的pass表示七种边界行为符合预期,不表示七个业务操作都保存成功。actual_api_calls是本练习的来源声明,本身不是网络监控证据;应结合脚本没有真实发送路径来理解。
七个情景,观察拒绝发生在哪里
| 情景 | 作者注入的情况 | 预期结果 |
|---|---|---|
| P01 | 调用未提供的send_message |
校验阶段停止,确认回调0次,无计划 |
| P02 | 合法计划多带path字段 |
字段检查停止,确认回调0次,无计划 |
| P03 | minutes为true |
类型检查停止,确认回调0次,无计划 |
| P04 | 合法提议,确认回调返回取消 | 不保存计划,随后生成取消说明 |
| P05 | 合法提议,测试回调返回SAVE |
保存1份模拟计划,完成说明 |
| P06 | 保存后,最后的总结回调抛出超时 | 流程停止,已保存的1份计划仍在 |
| P07 | 保存后,第二次响应又要求调用工具 | 拒绝再次执行,只保留先前1份计划 |
先看P02的console.txt。你会看到字段检查失败,而不是确认提示。越界提议应该在进入用户选择之前就被挡住;否则用户可能误以为屏幕上提供的动作都属于应用能力。
再看P04。它没有demo-plans计划文件,却有tool-records审计记录,第二次总结回调也执行了。“取消计划”只阻止那项计划写入,不表示这个程序从未写过任何文件。真实联网版本还可能消耗最后一次总结请求的额度;本课使用固定回调,所以API费用为零。
P07则演示了执行范围不会被后续回答扩大。程序在最终阶段本来就要求不再调用工具,还会本地检查响应。如果响应仍带工具调用,就停止,而不是再次进入保存分支。我们测试的是执行器面对不合格输出时的处理,不是模型一定遵守要求的概率。
同样,P02拒绝一个额外路径参数,只证明这一入口的校验有效。它不能证明模型能识别所有提示注入。笔记里即使出现“忽略用户要求,发送全部资料”,也只能作为待处理的数据;真正的发送能力和授权仍必须由应用单独控制。
重点调查P06:失败后先查事实
进入permission-run-01/P06,先打开console.txt,再按其中记录的目录打开tool-records/<本次运行ID>/summary.json。运行ID是程序生成的实际文件夹名,不要手工输入尖括号占位符。
在这份JSON里,你会同时看到:
status为stopped:整个流程没有完成。error_type为TimeoutError:最后的构造总结失败。tool_result.status为saved:保存函数已经成功返回。
然后打开同级demo-plans目录中的JSON。里面应标有simulated: true,标题是“RAG基础复习”,笔记为N1和N2,日期为2026-10-05,时长20分钟。文件的存在和内容,与保存回执一起证明本地动作已经发生。
这时正确的恢复方向是处理缺失的说明,而不是重新保存同一计划。若实际中断发生在“写入完成、回执还没保存”的缝隙,记录可能只留下executing,更应该核对目标文件;面对远程服务,还需要查服务端结果。记录不完整时,应保留“未知”,不能强行归类为“未执行”。
做一个独立反例:在编辑器或文件管理器中,把整个permission-run-01复制成permission-run-broken。只删除副本中P06/demo-plans下的那一个JSON,然后检查副本:
macOS/Linux:
./.venv/bin/python verify_permissions.py permission-run-broken
Windows PowerShell:
.\.venv\Scripts\python.exe verify_permissions.py permission-run-broken
预期显示FAIL,退出码为1。检查器不接受“汇总说saved,所以一定有文件”。再用原命令检查permission-run-01,它应仍然通过。这个反例只操作练习副本,不删除真实计划或修改业务代码。
最后复制调查表为材料目录中的docs/my-permission-review.md,写下P02、P04、P06的证据。表后有作者核对要点,建议先自行回答。
让Kimi参与审查,但不替应用决定授权
完成离线练习后,如果你已按前面课程配置Kimi,可以让它审查这次调查。该步骤会使用Kimi服务额度;不配置也不影响完成本篇。
2026年10月4日核查的官方文档和本机Kimi 2.0.2区分三种批准策略:默认Always Ask会逐项询问修改和命令执行;Ask When Needed允许常规工具自动运行,但仍询问敏感动作;Never Ask会自动处理包括敏感动作在内的批准。本课建议先用默认策略,审阅实际面板,不附加--yolo或--auto。Kimi交互与批准机制
在lesson-11目录检查版本、帮助并启动:
kimi --version
kimi --help
kimi --plan
这些Kimi命令在Windows PowerShell也相同,前提是命令已安装且可用。Plan模式帮助先审阅方案,但不是操作系统级只读沙箱;官方说明计划文件写入有特殊处理,Never Ask下退出Plan也可自动批准。不要靠一个模式名推断完整隔离能力。Kimi Plan模式说明
可粘贴下面的任务。它是作者提供的提示词,不是已执行的Kimi会话记录:
请先读README.md、docs/STATUS.md、docs/tasks/011-permissions.md。
本轮只审查权限边界,不改project,也不接真实服务。
阅读project/tool_flow.py、project/review_tool.py和permission_lab.py。
解释P02为何在确认前停止、P06为何不应重新保存、P07为何没有第二份计划。
核对permission-run-01中的对应记录,不读取其他运行目录、个人配置或凭据。
先列出审查步骤和准备读取的文件;需要写调查结论时,目标仅为
docs/my-permission-review.md。若需要其他副作用,请说明具体原因和目标。
保留七情景的原始预期,不通过修改验证器把失败变成成功。
在批准面板出现时,看清具体文件、命令和目的。常规读取、已经明确授权的调查写入可以按范围继续;如果建议运行真正的模型请求或连接外部服务,就需要说明为什么离线证据不够。官方的“本会话批准”会影响随后同类调用,不应在未看清范围时随手选择。
还要区分三层东西:AGENTS.md表达工作约定,工具批准策略决定何时询问,执行器与系统权限限定实际能力。约定写得再详细,也不能替代本地参数校验或系统隔离。
卡住时按证据排查
出现No module named openai,先检查是否用到了安装依赖的虚拟环境Python。本篇虽然不联网,导入真实流程仍需要模块;不必创建Key来解决导入错误。
出现“需要不存在的新目录”,换一个未使用的输出名,并确认父目录存在。不要为了重跑而让Agent递归清理整个工作区。若因磁盘权限中途退出,保留部分目录排查,独立检查失败是正常信号。
如果P06没有计划、P04却有计划,先确认运行的是本篇未修改的脚本及正确project,再检查具体summary。不要把表格预期改成当前错误行为。若Kimi显示的策略名称与文中不同,查看本机帮助和当前官方说明;版本差异不能靠猜测权限更宽的参数解决。
作者用Python 3.11.8及已锁定依赖实际运行了七情景,并在阻断socket联网的检查环境中通过验证;另外检查了计划丢失、取消后多出文件、内容错误、重复计划和同路径覆盖等反例。38个应用文件保持不变。未做真实Kimi/DeepSeek请求、Windows实机验证或任意攻击场景测试。
完成后,你有一份能解释“允许什么、实际发生什么、失败后查什么”的调查记录。下一篇会把重复操作整理成Skills、脚本与MCP连接;本篇的边界约定会随这些能力一起保留,接上工具并不会自动获得更大的授权范围。
上一篇:10|日志、追踪与成本 · 本部分目录 · 本篇代码与工作材料
14 技能、脚本与_MCP:把重复工作整理成可复用能力
你已经能让助手修改代码、运行检查,也知道不能把一句“完成了”当成执行证据。但做过几次之后,会遇到另一个问题:每次都要重新解释同一套步骤。
例如,把笔记助手生成的卡片整理成复习资料。你希望保留问题、答案、引用和原始笔记;缺少资料的回答不能被偷偷补全;导出后还要核对文件。若每次都让模型“自行整理一下”,格式和遗漏项可能每次不同。若只记住一条命令,又容易忘记导出后的审核。
本篇把这件事分成三个位置:确定的转换交给脚本,如何完成任务写进Skill,需要跨工具连接时再考虑MCP。你会实际导出一份Markdown复习资料,读懂一个可以交给Kimi使用的Skill,并完成一次MCP接口取舍练习。
应用仍使用DeepSeek。本篇新增的是外层开发材料,不修改模型配置,也不增加网页按钮。
三者解决的是不同问题
先看同一个任务的三种需求。
| 当前需要 | 适合放在哪里 | 在本项目中的例子 |
|---|---|---|
| 按明确规则反复转换数据 | 脚本 | JSON卡片转Markdown,保留引文和状态 |
| 让助手知道何时做、怎样查、失败怎么办 | Skill | 导出前核对输入,导出后检查并交付 |
| 让不同助手通过统一接口访问某个服务 | MCP | 未来从资料归档服务读取指定导出记录 |
脚本是可以由Python等程序直接执行的代码。Skill在这里是一份供Agent理解的工作说明,可以配脚本和参考资料。MCP,全称Model Context Protocol,是应用之间交换上下文和调用能力的协议。
它们可以组合。Skill指导助手运行脚本;MCP服务器内部也可以复用某段转换代码。不过,组合不是必需动作。一个本地脚本已经能解决的事情,并不因为再包一层协议就更可靠。
我们先做最小的那一部分:已有卡片按固定规则导出,不请模型重新生成答案。
起点和交付物先说清楚
下载并解压第12篇材料包,终端进入其中的lesson-12目录。该目录包含README、export_review.py、verify_export.py和project。
project里的38个应用文件与上一篇相同。新导出脚本放在外层,因此不会改变已有网页和数据库。前面各篇的评测、追踪、权限练习也保留,当前任务入口换成了docs/tasks/012-reusable-capabilities.md。
本篇输入是项目原有的三张作者构造卡片样例。它们回答RAG流程、为什么仍需核查,以及资料未提供的响应时间。使用公开固定样例,可以先验证转换规则,不读取你的真实记录,也不产生模型费用。
导出目录里只有两个文件:
review.md:供人阅读的问题、答案、依据和原始笔记。manifest.json:记录样例名、卡片数、输入与产物的指纹、待审核状态。
这不是已经接上真实历史问答的完整导出功能。本篇先建立可以单独验证的转换入口,后续集成时才有稳定的基础。
先把脚本单独跑通
需要Python 3.11或更高版本。新脚本只使用标准库及本地的study_task.py、validation.py,不需要安装SDK、配置Key或登录Kimi。上一篇权限脚本的导入链需要SDK,不能把那个前提机械搬到这里。
macOS/Linux,工作目录为lesson-12:
python3 export_review.py --fixture valid --out export-01
python3 verify_export.py export-01
Windows PowerShell,同样在lesson-12:
py -3 export_review.py --fixture valid --out export-01
py -3 verify_export.py export-01
--fixture valid选择本课的固定正常样例;--out export-01指定一个尚不存在的新目录,不是文件名。父目录必须已经存在。两条命令正常结束后,分别输出:
{"result": "exported", "cards": 3, "semantic_review": "pending", "actual_api_calls": 0}
{"result": "pass", "cards": 3, "semantic_review": "pending"}
第一条说转换结束,第二条才是独立核对。这里的pending表示语义仍待人工检查,不能理解为导出失败。
用编辑器打开export-01/review.md。你应看到Q1、Q2、Q3三张卡,以及N1—N3原始笔记。Q3仍回答“笔记未提供,无法判断”,没有为了版面完整而编造毫秒数。顶部明确写着作者构造样例和待人工核对。
再打开manifest.json。其中的SHA256是一种内容指纹:相同字节得到相同指纹,文件发生变化时可以用它发现不一致。本篇分别记录卡片输入、笔记与问题、导出文件的指纹。指纹不判断事实真假,也不是谁审核过这份资料的签名。
verify_export.py不调用导出函数来生成自己的预期;它另行检查三张卡的固定内容、状态、来源、原文和指纹。因此,“导出器和验证器都运行成功”比只看导出器打印成功多了一层独立证据,但验证范围仍是本课这些固定样例。
脚本里的边界,比提示词里的要求更稳定
导出函数依次做三件事:读取允许的样例、调用应用已有的validate_cards检查、写入新目录。创建目录发生在校验通过之后。坏输入不会先留下一个看似成功的资料目录。
这里没有把“检查一下”重新交给模型判断。格式、问题对应关系、状态取值、引文是否在原文中,都是已有程序规则。输出文字直接来自输入,不由Agent改写。
为了在Markdown里按文字显示卡片内容,脚本把这些段落作为缩进代码块写入。它不是HTML发布系统;本篇只处理提供的教学样例,不声称已经适配任意用户文件、复杂富文本或超大资料。
现有输出目录会被拒绝,退出码为2。复跑时用export-02,不清空旧目录。写盘中途失败则可能留下部分文件,此时需要保留错误和残留文件排查,不能把“目录存在”当作成功。
同一份输入导出到两个新目录,当前脚本产生的文件字节相同。因为它没有在正文中混入随机措辞、时间戳或模型补写。这种可预测性适合交给代码维护:格式变更后,可以直接比较前后差异。
两个反例:复用不能掩盖原来的错误
接下来使用已经提供的坏样例,不修改正常输入。
macOS/Linux:
python3 export_review.py --fixture bad-quote --out rejected-01
python3 export_review.py --fixture semantic-wrong --out wrong-01
python3 verify_export.py wrong-01 --fixture semantic-wrong
Windows PowerShell:
py -3 export_review.py --fixture bad-quote --out rejected-01
py -3 export_review.py --fixture semantic-wrong --out wrong-01
py -3 verify_export.py wrong-01 --fixture semantic-wrong
第一条应失败,退出码为1,提示引文不在对应笔记原文中;新的rejected-01目录不会被创建。说明规则检查阻止了不合格输入进入导出。
后两条却会成功。打开wrong-01/review.md,Q1写的是“先生成回答,再检索资料”,与原文顺序相反。为什么仍然通过?因为这个样例引用的原文是真的,结构也合法;脚本准确复制了错误答案,验证器也确认它没有擅自改写。
这延续了第9篇的区别:转换正确、机械校验通过,与答案语义正确,是不同的结论。你需要对照原文发现这个错误。导出文件保留pending,正是为了不让成功的程序执行伪装成内容审核。
以后把流程写成Skill时,不能删掉这一层阅读步骤。否则只是把遗漏更高效地重复了很多次。
把稳定的做法写进Skill
本课已经提供:
skills/
└── export-review/
├── SKILL.md
└── references/
└── review-checklist.md
打开SKILL.md。文件前面的两道---之间是元数据,后面才是给Agent的工作说明。Kimi官方当前支持这样的目录结构,目录形式要求提供name和description。Kimi Agent Skills文档
本课元数据是:
name: export-review
description: 将本课固定复习卡样例导出为带来源和待审标记的本地Markdown;不生成答案、不发布资料。
type: prompt
disableModelInvocation: true
description说明适用任务,范围刻意写得很窄:它只导出本课样例。disableModelInvocation: true关闭模型自动选用这个Skill的方式,保留手动调用;它不表示调用Skill时不使用模型。Kimi Skill字段说明
真正有用的是正文里的流程。它要求先确认工作目录和输入,选新的输出目录,调用脚本,执行独立检查,阅读三张卡,最后交付文件。导出或检查失败时停止,不修改验证器,也不请模型“顺手修好”答案。
这与把命令写到聊天里有两个差别。第一,说明可以随项目保存、审查和修改,不必依靠会话记忆。第二,失败条件和交付标准随操作一起复用,减少只记住“运行脚本”却忘记检查的情况。
但Skill仍然需要Agent理解并选择动作,不能保证每次执行都完全一致。确定的转换由脚本承担;Skill负责衔接任务与判断,执行结果仍以命令、退出状态和文件为准。
读完文件后,自己核对三个问题:如果用户要求导出真实私人历史,它是否仍然适用?如果目录已存在,是否会删除旧文件?如果Q1错误,它是否有权重新生成答案?本课Skill的答案分别是:不适用、保留旧目录、无权擅自生成。这些限制能在文件中找到,不只存在于教程的叙述里。
在Kimi里手动调用一次
这一步可选。没有Kimi账户,也已经可以完成离线导出与Skill阅读。有现成环境时,调用会消耗Kimi开发服务额度;导出脚本本身仍不调用DeepSeek或Kimi API。
2026年10月4日本机核查版本为Kimi 2.0.2。先在lesson-12检查帮助,再显式加载本课的skills目录:
kimi --version
kimi --help
kimi --skills-dir ./skills
Windows PowerShell的Kimi命令相同。--skills-dir让本次启动使用指定的Skill目录,避免依赖你把练习放在了哪个Git根目录下面;它改变发现范围,不是文件访问沙箱。Kimi命令参考
在新会话输入:
/skill:export-review
这是Kimi当前的手动Skill调用形式。Kimi Skill调用说明
预期助手按Skill核对目录,准备导出到新的skill-export-01;已有同名目录时选择新编号。批准界面出现时检查实际命令与目标。完成后,它应交付review.md和manifest.json,说明固定样例、检查结果和待审核状态。你仍然可以在终端独立运行python3 verify_export.py skill-export-01(Windows用py -3 verify_export.py skill-export-01),确认它交付的文件;若用了新编号,相应替换目录名。
以上是读者操作路径和预期,不是作者已执行的Kimi实录。作者检查了当前帮助、官方文档、Skill结构和配套路径,没有发起真实Kimi请求。
如果命令菜单里找不到这个Skill,依次核对启动目录、--skills-dir是否受当前版本支持、skills/export-review/SKILL.md是否存在,以及元数据的名称和描述。修改文件后重新开启一个会话,再尝试手动调用。不要通过全局安装另一套旧版工具来碰运气。
MCP什么时候才值得接进来
到这里,Kimi已有执行本地脚本的能力,导出也只服务当前工作区。因此当前任务不需要MCP。
假设以后你有一个统一的复习资料归档,希望Kimi、另一个桌面助手和自己的应用,都能读取同一份已登记资料。这时每个消费者都自行了解归档文件结构,会产生重复适配。一个MCP服务器可以向这些支持MCP的客户端提供约定好的能力。
在MCP架构中,宿主是使用AI的应用,客户端负责与服务器交互,服务器提供能力或上下文。服务器可以运行在本机,也可以是远程服务。协议不仅涉及工具,还包括资源和提示模板;本篇只围绕一个读取工具解释。MCP官方架构说明
Kimi中的MCP客户端 ─┐
├─→ 资料归档MCP服务器 → 已登记的复习资料
另一个MCP客户端 ──┘
这条连接并不把大模型装进归档服务器,也不会自动替换笔记助手的DeepSeek接口。服务端是否调用模型,取决于服务实现;这里的设想只是读取已有文件。
打开mcp-design/tool-contract.example.json。它是作者编写的工具列表设计片段,不是完整协议报文、Kimi配置或可运行服务器。里面只提出一个工具:read_review_export,只接受逻辑ID demo-001,不接受调用者指定任意本地路径。
为什么用ID?因为资料归档可以自己维护“这个ID对应哪个文件、谁可以读”的映射。若直接接受任意path,工具的范围就容易从“读取一份资料”扩大成“读取服务器能访问的任何文件”。输入schema是契约,真正的服务端还必须实施校验和权限检查。
本课的设计练习是:打开接入决策表,对照示例回答以下三项:
- 请求
demo-001时,应返回什么? - 请求
../secrets,或者偷偷多传一个path时,应怎样处理? - 当前只有本地Kimi消费者,是否有必要启动服务?
核对答案:返回已登记资料的内容、产物指纹与待审状态;越界ID和额外字段应拒绝;当前直接脚本已足够。这里只完成了设计,没有运行服务器,所以不能写成“已经通过MCP越权测试”。
将来确实选定了服务,Kimi当前提供/mcp-config管理接入、/mcp查看状态。stdio方式会启动本地进程,HTTP方式连接服务地址;因此接入前要核对具体命令或URL、公开的工具和账号范围。连上服务也不等于每项操作都已获用户授权。Kimi MCP接入文档
尤其要注意,官方当前说明Ask When Needed模式下MCP调用会自动批准。不要把“本地敏感动作可能会问”推断成“所有外部MCP写入也一定会问”。本课不切换到该模式,也没有添加服务器配置。Kimi MCP权限说明
维护这套能力时,改哪一层
假如以后希望导出时多一个标题,先改输入/输出约定和转换脚本,再更新验证。若只是想在交付时提醒读者先看缺失答案,调整Skill和阅读清单即可。只有出现明确的跨进程或跨助手访问需求,才需要调整MCP服务接口。
这能避免一种常见返工:转换格式出错,却不断给Skill增加长提示;模型忘记检查,却重写整个导出器;只是本地读文件,却先搭建远程服务。先找到变化发生在哪一层,修改范围就容易控制。
若本篇导出失败,先读终端错误:坏引文属于预期拒绝;已有目录换新名;文件权限或磁盘问题先解决本地环境。若导出成功但内容错误,回到原卡片和原文审核,不要把semantic_review手改为approved制造已审核的假象。正式审核还需要绑定具体版本和审核记录,本课尚未实现。
本轮作者在Python 3.11.8下完成了离线验证:正常导出、坏引文拒绝、错误语义原样保留、同输入输出字节一致、拒绝覆盖;即使篡改答案后重新计算文件指纹,独立检查仍能发现不符合固定预期。旧应用38个文件未改。没有真实模型调用、MCP联调或Windows实机验证。
现在项目多了一个稳定的导出入口,以及一份能指导助手使用它的Skill。下一篇会把需求、上下文、工具、检查与状态组织成最小Harness,让这些已经建立的能力在一次完整任务中协同工作。
上一篇:11|工具与权限边界 · 本部分目录 · 代码与工作材料
15 搭建最小智能体运行环境:给智能体一个可以持续工作的项目环境
上一篇,我们把“导出复习资料”整理成了脚本、Skill 和可讨论的工具接口。现在,你把项目交给一个新的 AI 会话,对它说:“按之前的方法,再导出一份。”
它仍然可能卡住:之前确认的任务在哪里?应该运行哪个脚本?输出目录能否覆盖?脚本显示成功以后,还需要检查什么?如果上次执行到一半,目录里的文件算成果,还是半成品?
这些问题不会因为模型更会写代码就自动消失。模型负责理解和决策,项目还需要提供明确的入口、可执行的动作、可靠的反馈,以及能留给下一次工作的记录。本篇要搭建的最小 Harness,就是围绕一次任务组织这些条件。
你会运行一个不需要模型账户的练习:读取固定教学卡片,导出复习材料,独立验证,把每一步的证据和当前状态保存在一个新目录里。最后,它会明确停在“待人工审核”。
配套:完整工作区 · 下载 ZIP。这是作者编写的参考实现,执行对象是公开教学样例,不是 Kimi 生成代码的实录,也没有连接你的真实问答历史。
Harness 到底包住了什么
Harness 在不同产品中的范围不完全相同。讨论 Agent 工程时,可以把它理解为:让模型能够在具体环境中开展工作的外围设施,例如提供上下文、执行工具、收集反馈、记录状态、处理权限和停止条件。
它没有一个“必须安装这些组件才算合格”的统一清单。Anthropic 关于长任务的工程文章就指出,即使给出了清晰的高层目标,Agent 仍可能一次做得太多、留下难以接手的中间状态,或者过早宣告完成;环境准备、明确任务、进度记录和验证都需要专门设计。参考:Effective harnesses for long-running agents。
落到我们的笔记助手,前面学过的内容可以这样组合:
| 一次任务需要什么 | 本篇放在哪里 | 解决的具体问题 |
|---|---|---|
| 目标与完成条件 | docs/harness/TASK.md |
这次只做固定样例导出,不能顺手改网页 |
| 必要上下文 | docs/harness/CONTEXT.md |
去哪里找事实,不把全部历史塞进会话 |
| 可以执行的能力 | export_review.py |
确定性地生成资料与清单 |
| 独立反馈 | verify_export.py、verify_harness.py |
检查真实产物,不只听执行者说成功 |
| 状态与证据 | runs/运行名/ |
记录哪些步骤发生过、结果是什么 |
| 行为边界 | AGENTS.md、任务和运行手册 |
新目录、固定输入、失败停止、人工审阅 |
这里有两个层次。Kimi Code 自身已经提供模型交互、工具执行等 Agent 运行能力;我们搭建的是项目这一侧的任务环境,让它进入这个项目时能找到路。
配套的 harness.py 也有明确边界:它只是一个固定任务执行入口,按顺序调用已有函数。它不会自己理解新需求,不会挑工具,不会改代码,更不会失败后偷偷请求模型修复。读者可以自己运行它,Kimi 也可以在明确授权后使用它。
这样安排是为了先看清一轮工作的结构。模型如何根据反馈决定下一步,留到第14篇讲执行 Loop。
先确认你的起点
解压配套文件,进入包含 harness.py 的 lesson-13 目录。不要进入内层 project 执行本篇命令。
lesson-13/
├── README.md
├── AGENTS.md
├── harness.py
├── verify_harness.py
├── export_review.py
├── verify_export.py
├── docs/
│ ├── STATUS.md
│ └── harness/
│ ├── TASK.md
│ ├── CONTEXT.md
│ └── RUNBOOK.md
└── project/
└── ...原笔记助手的代码与公开样例
project 的38个文件与第12篇一致,笔记助手的应用模型仍然是 DeepSeek。历史搜索也没有重做。本篇新增的是外层任务组织,不涉及切换应用模型。
先打开任务说明,确认三件事:输入只接受固定教学样例;产物只能进入新的运行目录;机械检查通过后仍要人工对照原文。再看上下文索引,按需要找到对应文件。
本篇主练习只需 Python 3.11 或更高版本、标准库和配套文件。不需要安装 SDK,不需要 API Key,也不需要先登录 Kimi。旧材料中其他练习的依赖要求不代表这个入口也需要它们。
macOS / Linux:
python3 harness.py --check
Windows PowerShell:
py -3 harness.py --check
作者环境的结果如下,读者的版本号可以不同:
{"ready": true, "python": "3.11.8", "missing": [], "watched_files": 14}
这个检查只说明 Python 版本符合要求、14个指定文件存在。它没有证明导出正确,也没有证明文件内容没有语法错误。 程序是否能完成任务,要靠下一步的实际执行来回答。
--check 不创建运行目录。若 missing 非空,优先确认是否解压完整;不要让 Agent 临时编造缺失的规格或样例来凑齐文件数。
让一次运行有自己的身份和证据
在同一目录执行第一轮。
macOS / Linux:
python3 harness.py --run run-01
python3 verify_harness.py runs/run-01
Windows PowerShell:
py -3 harness.py --run run-01
py -3 verify_harness.py runs/run-01
第一条的预期结果:
{"status": "needs_review", "steps": 2, "semantic_review": "pending"}
第二条的预期结果:
{"result": "pass", "run_status": "needs_review", "semantic_review": "pending"}
运行名 run-01 是你给本次执行起的名字。它只能包含字母、数字、下划线、短横线,以字母或数字开头,长度1—40位。固定命名规则可以减少把任意路径误当输出位置的机会,但不构成完整的文件系统隔离。
打开新生成的目录:
runs/run-01/
├── context.json
├── state.json
├── 01-export.json
├── 02-verify.json
└── artifact/
├── review.md
└── manifest.json
不要只盯着最后两行终端输出。先打开 artifact/review.md,确认里面有三张卡片、原文及教学样例和待审核标记。再读 state.json:应该有两个通过的步骤,但整个任务状态仍为 needs_review。
各文件承担不同责任:
context.json保存本次任务关联的14个文件的 SHA-256 指纹,用来对比版本是否变化。state.json保存运行名、输入情景、真实开始时间、步骤状态和下一步动作。01-export.json保存导出返回的清单;02-verify.json保存验证结果。artifact保存读者真正需要审阅的资料。
指纹是一种用于比较文件内容的摘要,不是文件备份。如果后来改了规格或脚本,旧记录仍保留当时的指纹,但不能靠指纹还原旧内容;旧版本仍要由 Git 或保留的快照提供。
第二条命令会重新读取真实产物,并核对回执、任务归属、步骤和当前文件指纹。它不是简单地打印第一条的“成功”字段。
拆开入口,看懂它怎样连接已有能力
本篇流程如下。图中的步骤是固定顺序,不包含模型自己规划的分支。
flowchart TD
A[检查环境与固定输入] --> B[预留全新的运行目录]
B --> C[保存上下文指纹与运行状态]
C --> D[执行导出并保存回执]
D --> E[独立检查导出产物]
E --> F[核对观察文件是否变化]
F --> G[停在 needs_review]
D -->|失败| H[记录失败并停止]
E -->|失败| H
F -->|发生变化| H
核心调用并不长,完整代码在harness.py:
step('export', lambda: export_review.export(fixture, folder/'artifact'))
step('verify', lambda: verify_export.verify(folder/'artifact', fixture))
这里的 step 是一个包裹函数:先记录“准备执行这一步”,再调用真正的导出或验证函数,然后保存结果并更新状态。lambda 只是把“稍后调用这个函数”传给 step,并不额外请求模型。
这种组织让业务能力和运行记录可以分开理解。导出器仍负责导出;验证器仍负责核对;入口负责先后顺序、目录和状态。以后修改导出内容,不必把所有运行记录逻辑也塞进导出器。
程序在运行前后比较指定文件的指纹。如果有人在运行期间改了任务或输入,即使导出和验证两步通过,也会停为失败,要求检查这次证据究竟对应哪个版本。这个检查只覆盖清单内的14个文件,不能监视整个计算机,更不是安全扫描。
状态文件采用先写临时文件、再替换目标文件的方式,减少直接覆盖 JSON 时留下半截内容的机会。但一次运行有多个文件,它们并不是一个数据库事务;断电、强制结束或磁盘错误仍可能留下部分记录。不能因此宣称已经实现了可靠的断点恢复。
为什么完成两步以后,还不能叫“任务完成”
needs_review 的意思是:该生成的文件生成了,该做的固定机械检查做了,现在需要人看内容。
第12篇已经提供了一个很有用的反例:semantic-wrong 情景里的第一张卡片把流程写成“先生成回答,再检索资料”。这份卡片可以有合法结构、真实引文,也能被完整导出,但答案对原文的理解是错的。
运行它:
macOS / Linux:
python3 harness.py --run semantic-01 --fixture semantic-wrong
python3 verify_harness.py runs/semantic-01 --fixture semantic-wrong
Windows PowerShell:
py -3 harness.py --run semantic-01 --fixture semantic-wrong
py -3 verify_harness.py runs/semantic-01 --fixture semantic-wrong
你仍会看到 needs_review 和 pending。打开这轮的 artifact/review.md,把第一张卡片和原文对照,就能发现问题。
这不是验证器失职。此处固定验证器承担的是“是否忠实导出了指定样例、回执和文件是否一致”,并不是替人判断样例知识是否正确。我们故意保留错误答案,正是为了避免把“管道运行正常”偷换成“所有内容都可靠”。
本篇没有提供一个按钮把 pending 改成 approved。人工审阅意见可以另外记录,但不要直接改原样例、旧回执或指纹来制造一份全绿结果。评测和审阅的结论需要自己的依据。
让失败成为有意义的停止
再运行一次带假引用的输入。
macOS / Linux:
python3 harness.py --run bad-01 --fixture bad-quote
python3 verify_harness.py runs/bad-01 --fixture bad-quote
Windows PowerShell:
py -3 harness.py --run bad-01 --fixture bad-quote
py -3 verify_harness.py runs/bad-01 --fixture bad-quote
第一条预期退出码为1,输出:
{"status": "failed", "steps": 1, "semantic_review": "pending"}
这次导出器发现引用不在原文中,在创建产物前抛出 ValidationError。打开 01-export.json 可以读到错误;state.json 记录失败;不应出现 artifact 和 02-verify.json。入口不会自动删掉引用、重写答案,再宣告成功。
第二条却应退出0,报告 result=pass、run_status=failed。这里的通过表示:程序按预期拒绝了坏输入,留下的失败证据符合约定。 不是说复习资料已经成功导出。
想检查退出码,macOS / Linux 在目标命令之后立即运行 echo $?;PowerShell 使用 $LASTEXITCODE。后一条命令会更新退出码,所以不要执行一串命令以后,再把最后一个退出码算到第一条头上。
| 看到的状态 | 可以得出的结论 | 下一步 |
|---|---|---|
needs_review |
固定步骤通过,内容待审 | 对照原文阅读产物 |
failed |
有环节失败或观察文件变化 | 阅读状态、回执和实际目录 |
interrupted |
入口捕获到了 Ctrl+C 中断 | 检查是否已有部分产物 |
running |
记录尚未更新到最终状态 | 检查进程及文件,不能假定没执行 |
尤其要注意最后一行:如果进程被强制结束,记录可能停在“开始执行”,实际动作却已经发生过。因此本篇不提供“看到 running 就再执行一次”的规则。如何恢复,需要在第15篇结合真实执行结果继续讨论。
做一次不相信状态字段的检查
现在做一个小实验:在文件管理器中复制 runs/run-01,把副本命名为 runs/copy-01。只在副本里删除 artifact/review.md,保留 state.json 的通过记录。
macOS / Linux:
python3 verify_harness.py runs/copy-01
Windows PowerShell:
py -3 verify_harness.py runs/copy-01
验证应该失败,因为真实产物丢了。由此可以看出,状态是定位工作的索引,不是能替代文件和事实的权威声明。
再直接重复 --run run-01,程序应退出2,提示运行目录已存在;旧目录不被覆盖。要开展新的尝试,用新的运行名,例如 run-02。
假如你真的修改了任务文档,再验证旧运行,指纹检查也会失败。这时应比较新旧版本,确认运行依据,而不是让 Agent 重新计算一遍旧 context.json,把历史证据改成今天的样子。
这三项检查分别覆盖了产物丢失、重复执行和版本错位。它们比一句“所有步骤成功”更能说明运行记录有没有实际价值。
可选:把这个入口交给 Kimi Code
前面的练习已经完整,不必为了学习 Harness 再付费调用一次模型。如果你希望自己体验,可以在本篇工作目录运行:
kimi --plan
该选项已按写作日官方命令文档和本机 Kimi Code 2.0.2 的帮助核对;作者没有发起真实会话。Plan 模式用于先讨论计划,不等于操作系统层面的只读沙箱。参考:Kimi CLI 命令。
可使用以下任务说明:
先读 AGENTS.md、docs/harness/TASK.md、docs/harness/CONTEXT.md 和 docs/harness/RUNBOOK.md。解释当前任务、允许的动作以及需要人工审核的内容,先给计划。经我确认后,只执行一次固定 valid 样例的 Harness 和独立验证,使用未占用的运行名。不要改 project 或样例,不读取私人资料,不调用真实应用 API,不加载 MCP。失败就报告证据位置并停止;机械通过也保持语义待审。请区分你实际执行过的命令与建议我执行的命令。
确认计划、准备执行时,再按当前客户端提示切换相应模式并审核工具动作。真实 Kimi 会话可能消耗开发工具额度;我们限定的是不要另外触发笔记助手的真实 DeepSeek 请求,不能把这两类消耗混为一谈。
第12篇的 Skill 仍保存在工作区,但它直接调用导出脚本,不会自动生成本篇完整的运行记录。当前任务应走 harness.py。这也说明“项目里存在一个可复用能力”和“本次任务应该使用哪个入口”是两件需要明确区分的事。
遇到问题,沿着证据找原因
| 现象 | 优先核对 | 处理方式 |
|---|---|---|
找不到 harness.py |
是否进入了内层 project |
返回 lesson-13 根目录 |
ready=false |
Python版本、missing列表 |
使用3.11以上版本,恢复完整配套 |
| 已有目录退出2 | 同名运行是否存在 | 保留旧记录,使用新名字 |
| 两步通过但总体失败 | 观察文件是否在执行期间改变 | 查看版本差异,不重写旧指纹 |
| 验证旧运行提示文件变化 | 当前资料是否已经编辑 | 用对应版本核查历史运行 |
| 只留下部分 JSON 或临时文件 | 中断、磁盘空间或写权限 | 保留现场,先检查实际产物 |
| 语义错误也能通过 | 是否误把机械检查当内容判断 | 回到原文和评测标准审阅 |
这个入口没有任意程序沙箱、超时终止或自动修复能力。它在同一个 Python 进程里调用两个固定函数;以后如果换成外部命令或不受信任的工具,执行隔离、超时、输出限制等都必须重新设计,不能把本课的小入口原封不动当作通用运行平台。
作者已在阻断网络的离线环境验证正常、假引用、语义错误、重复命名、缺输入、产物丢失、伪造完成状态、运行中资料变化和中断情景。原应用38文件及已有导出器保持不变。没有真实 Kimi / DeepSeek 调用,也没有 Windows 实机或浏览器视觉验证;Windows 命令采用对应 Python 启动方式提供。
最后检查自己的理解:
--check返回ready=true,能否直接把任务标为完成?不能,它只检查版本和指定文件是否存在。- 一个运行没有
review.md,但状态写着两步通过,应该相信哪一个?应核查实际产物,独立验证必须拒绝这种不一致。 - 为什么
semantic-wrong没有被自动“修好”?本轮任务是忠实导出并留待审阅;改写内容需要新的明确任务和验证标准。
现在,你已经有了一个能找入口、能执行、能检查、能记录、能停止的项目环境。下一篇会在这个基础上讨论执行 Loop:什么时候允许继续,什么时候应停止,以及怎样防止“再试一次”变成没有边界的循环。
16 设计执行循环:让智能体根据反馈继续,而不是无限重试
上一篇,我们给导出任务搭好了最小 Harness:有输入,有工具,有验证,也有保存下来的状态。但它做完一次就停。如果第一份候选的引用不在原文里,而你希望助手依据错误信息再提交一份,接下来应该怎么办?
最容易想到的办法是加一句:“没通过就继续,直到通过为止。”这句话漏掉了不少问题:它是在学习反馈,还是重复相同错误?还有多少预算?是否为了通过测试改坏了预期?如果下一步想把资料发布到外部网站,原来的授权是否包含这件事?
一个有用的执行 Loop,不只是允许继续,还要明确继续的条件和停止的依据。 本篇会在第13篇 Harness 外面增加一个小循环。每次尝试仍运行真正的导出与验证,但候选由作者预先准备,让你能稳定复现成功、停滞、预算耗尽和权限边界。
配套:工作区说明 · 完整 ZIP。主练习完全离线,不需要模型账户。它演示循环控制,不是 Kimi 自动修复的实录,也不是模型能力测评。
先分清:你究竟在循环什么
Loop 就是循环。在 AI 项目里,这个词至少可能出现在三个不同层次:
| 层次 | 一次循环做什么 | 笔记助手里的例子 |
|---|---|---|
| 请求重试 | 再尝试完成同一个请求 | 一次通信暂时失败后,按既定重试策略处理 |
| 应用工具循环 | 根据工具结果决定下一次动作 | 查笔记,读检索结果,再决定回答或调用另一个工具 |
| 开发迭代 | 修改方案或实现,再验证 | Kimi 改搜索逻辑,运行测试,根据失败证据调整 |
这三者不能只靠同一个“最多三次”含糊带过。开发迭代做了两轮,不代表模型只请求了两次;一轮可能包含多次模型交互和多个工具。底层客户端如果还会重试,实际请求数又可能增加。
循环也不自动等于 Agent。预先写死的分支和动态由模型选择动作,是不同的控制方式。Anthropic 在工程文章中区分了固定工作流与模型动态决定流程的 Agent,并强调根据环境反馈行动、设置最大迭代次数等停止条件。参考:Building effective agents。
本篇把“选择下一候选”做成作者脚本,帮助我们专心检查外围规则。在真实 Agent 中,可以由模型提出候选或修改建议,但工具是否允许执行、预算是否还够、验证是否通过,都不应只凭模型一句自我评价决定。
这次在原项目上加什么
解压后进入 lesson-14 根目录,也就是包含 loop_lab.py 的目录。本篇使用 Python 3.11 或更高版本,仅需标准库和配套文件。
lesson-14/
├── loop_lab.py # 新增:决定继续还是停止
├── verify_loop.py # 新增:独立核对循环结果
├── harness.py # 原单次任务入口
├── verify_harness.py
├── export_review.py
├── verify_export.py
├── docs/loop/TASK.md # 新增:本轮范围与预算
└── project/ # 原笔记助手
原应用38个文件、导出器、验证器及 Harness 保持不变。应用运行时仍使用 DeepSeek;Kimi 仍是开发助手。我们没有给网页加循环按钮,也没有把真实用户数据送进新流程。
先读当前任务。它对上一轮范围作了一个明确扩展:单次 Harness 遇错仍然停止;外层 Loop 在核对到特定教学失败后,可以在预算内提交另一个固定候选。每次使用新目录,不覆盖上一份证据。
这很像日常开发:一次测试失败了,这次测试当然结束;开发任务是否继续修改,要由外层流程决定。不能因为“还允许迭代”,就把上一轮的失败记录改成通过。
先检查基础环境。
macOS / Linux:
python3 harness.py --check
Windows PowerShell:
py -3 harness.py --check
应看到 ready=true、missing=[]、watched_files=14。这是内层 Harness 的检查,只证明其所需文件存在和 Python 版本符合要求。新循环还会把自己的脚本和任务文档纳入上下文指纹,总共记录16个文件;它们都包含在完整配套中。
先跑通一轮“根据反馈改变候选”
macOS / Linux:
python3 loop_lab.py --run repair-01 --scenario repair
python3 verify_loop.py loop-runs/repair-01
Windows PowerShell:
py -3 loop_lab.py --run repair-01 --scenario repair
py -3 verify_loop.py loop-runs/repair-01
第一条预期输出:
{"status": "needs_review", "reason": "mechanical_pass", "semantic_review": "pending", "attempts": 2, "actual_api_calls": 0}
第二条预期输出:
{"result": "pass", "status": "needs_review", "attempts": 2, "semantic_review": "pending"}
打开下面三个目录,你会看到外层与内层记录如何对应:
loop-runs/repair-01/loop.json # 循环摘要:为什么继续、为什么停止
runs/repair-01-01/ # 第一次:bad-quote,失败
runs/repair-01-02/ # 第二次:valid,通过机械检查
第一次候选使用假引用样例。导出器实际执行校验,发现引用不在原文中,返回 ValidationError,并留下具体错误:“第 1 张卡:引文不在对应笔记原文中。”
外层先调用独立验证,确认这次确实是预期的拒绝,再把错误类型和消息作为反馈交给候选选择函数。repair 情景收到该反馈后,提交事先准备好的 valid 样例。第二次导出和验证通过,整个循环立即停止,保留 needs_review/pending。
这里没有模型现场写出修正内容。 两份样例都早已存在;真实执行的是校验、导出、记录及分支判断。因此结果只能证明“这个循环按设定处理了反馈”,不能证明 Kimi 能一次修好某种错误。
打开 runs/repair-01-02/artifact/review.md 阅读产物,再看 loop.json 中的 attempts:每次应该有候选、子运行路径、状态和必要反馈。只保存 loop.json 不够,摘要所指向的子运行也必须保留。
一次合理的继续,应该经过哪些判断
本篇的控制顺序如下:
flowchart TD
A[检查下一轮是否还有预算] --> B[根据已有反馈提出候选]
B --> C{动作在允许范围内吗}
C -->|否| H[交给人判断]
C -->|是| D[用新的运行名执行Harness]
D --> E[独立核对产物与证据]
E -->|证据异常| H
E -->|机械通过| R[停止并等待内容审核]
E -->|已确认的教学失败| F{同候选同错误再次出现吗}
F -->|是| S[停止:没有进展]
F -->|否| G[保存反馈]
G --> A
先看预算,再提候选,可以避免额度已经耗尽却还继续策划和执行。先检查动作范围,再运行工具,可以避免“等做完了才判断它不该做”。每次执行后核对真实文件,则避免模型或脚本只凭一句“我完成了”结束任务。
在loop_lab.py 中,候选不包含任意命令。只有固定动作 export 和三个已知样例可以进入内层 Harness。遇到未知异常、记录不一致或无法核对的状态,会停在 needs_human,不会把所有错误都当作可以继续的普通反馈。
这是一个刻意狭窄的循环。如果将来接入代码修改,允许改哪些文件、运行哪些测试、哪些失败可以自动处理,需要在任务里重新定义。不能把“本轮可更换固定样例”扩张成“Agent 可自行重写整个项目”。
连续做了两次,不代表取得了进展
现在运行一个始终提交同一份坏候选的情景。
macOS / Linux:
python3 loop_lab.py --run stuck-01 --scenario stuck
python3 verify_loop.py loop-runs/stuck-01
Windows PowerShell 将上面每行开头的 python3 换成 py -3,其他参数保持相同。后续命令也按这个规则使用。
Loop 预期输出:
{"status": "stalled", "reason": "repeated_failure", "semantic_review": "pending", "attempts": 2, "actual_api_calls": 0}
默认最多允许三次尝试,但本次只执行两次。第二次又是同一个样例、同一种错误、同一条错误消息,程序判定没有进展,提前停止。
本课把这三个字段组合成重复标记。因为候选只来自几个固定文件,这个方法足够说明问题。迁移到真实开发时,“同样的错误消息”可能来自不同代码版本;反过来,错误文字不同也可能是同一个根因。可以结合输入版本、代码差异、失败测试和工具参数判断,不能迷信错误文本去重。
一个有用的开发反馈应该足够具体,例如:“内部空格被删除,S10 的预期是空结果,实际命中 A01。”这会指向可检查的行为。只有“还是不对,再优化一下”,很容易让 Agent 再尝试一次几乎相同的修改。
也不要把减少失败数当作唯一进展:为了让五项测试变成零失败而删掉两项测试,并不等于修好了程序。验收标准、测试预期和输入版本需要保持可追溯。
预算要在程序里生效,并说清计量单位
先把本来需要两次的 repair 情景限制成一次:
python3 loop_lab.py --run budget-01 --scenario repair --max-attempts 1
python3 verify_loop.py loop-runs/budget-01
预期是 budget_exhausted,原因为 attempt_budget,attempts=1。只有 runs/budget-01-01,不能出现第二次运行。即使作者知道下一个候选可以通过,也不能因此擅自突破上限。
这里的“一次”指一次 Harness 尝试,包含第一次,不是失败后额外重试一次。一次尝试内部最多有导出和验证两步。如果导出已经失败,验证步骤就不会执行,所以尝试次数也不等于成功工具执行数。
再把时间预算设为零:
python3 loop_lab.py --run time-01 --scenario repair --max-seconds 0
python3 verify_loop.py loop-runs/time-01
预期 budget_exhausted/time_budget、attempts=0,只有循环记录,没有任何子运行。这是为了稳定演示“开始前预算已不足”的边界,不能拿它评价程序速度。
本篇默认是最多三次尝试、30秒轮次边界预算。时间通过 time.monotonic() 的两次读数差值计算,不依赖可能被校时改变的日历时间。参考:Python 单调时钟。
不过,它只在下一次尝试开始前检查。如果某个已经开始的工具迟迟不返回,外层无法靠这条检查打断它。即使完成时已超过30秒,这个示例也可能把本次结果保存为待审。因此应叫轮次边界检查,不能宣称“整个任务一定在30秒内结束”。要控制真实外部请求,还需要请求超时、执行器的终止机制及终止后的状态核对。
| 预算 | 应回答的问题 | 本篇是否实现 |
|---|---|---|
| 尝试次数 | 最多允许启动多少次任务尝试 | 是,含首次,允许1—5次 |
| 轮次边界时间 | 还有没有时间开始下一轮 | 是,0—300秒 |
| 单次动作时限 | 已启动的请求或命令多久必须结束 | 未实现通用强制超时 |
| Token 与金额 | 每次及累计请求最多消耗多少 | 未接入真实模型,不假装已有费用封顶 |
真实调用时,还要考虑底层请求重试、最大输出以及累计消耗;未知 usage 不能当成零费用。Kimi 开发额度和笔记助手的 DeepSeek 额度仍须分开管理。提示词里写“别超过五块钱”,不会自动给调用程序增加硬限制。
有些停止,不需要再努力一次
还剩两个很值得运行的情景:
python3 loop_lab.py --run semantic-01 --scenario semantic
python3 verify_loop.py loop-runs/semantic-01
python3 loop_lab.py --run permission-01 --scenario permission
python3 verify_loop.py loop-runs/permission-01
semantic 会在第一次机械通过后停止,状态为 needs_review,而第一张卡片仍把 RAG 的步骤顺序说反了。打开它的 artifact/review.md 对照原文。循环不会为了增加“看起来更聪明”的迭代次数,凭空假定自己有可靠的语义判断;也不会把 pending 改成已通过。
permission 情景则提出一个作者构造的 publish 动作。当前范围只允许本地导出,所以结果应为 needs_human/permission_boundary,尝试次数为零。loop.json 的 blocked_proposal 记录这条提议。程序没有发布器,也没有尝试联系任何网站。
这两个例子回答了不同问题:前者缺少内容审核结论,后者超出了允许动作。它们都不是“多试几次应该就能解决”的普通错误。
人工入口也不能只说“请处理”。可以报告:当前停在哪里、已做哪些动作、哪些文件可以核对、需要用户提供什么。比如:“本轮尚未导出就提议发布,超出只允许本地导出的范围;先澄清任务目标,不能直接执行发布。”这让人能够做一个具体决定。
独立验证要核对“没有继续做什么”
六种情景的预期可以集中核对:
| 情景 | 停止状态 | 尝试次数 | 应重点检查 |
|---|---|---|---|
repair |
needs_review |
2 | 保留第一次失败及第二次产物 |
stuck |
stalled |
2 | 不应有第三次子运行 |
repair --max-attempts 1 |
budget_exhausted |
1 | 不能擅自创建第二次 |
repair --max-seconds 0 |
budget_exhausted |
0 | 没有子运行 |
semantic |
needs_review |
1 | 错误内容仍须人工审阅 |
permission |
needs_human |
0 | 越权提议被记录,没有执行 |
Loop 到达待审状态退出0,其他受控停止通常退出1;目录重名退出2,捕获中断退出130。独立验证器确认停止符合教学预期则退出0。因此,stuck 的验证通过,表示循环正确地停止了,不表示资料导出成功。
verify_loop.py 不导入候选决策函数。它根据固定情景的独立预期,核对次数、顺序、反馈、状态、上下文和真正的子运行,再调用上一篇的验证器检查产物。它还会检查是否有摘要没有记录的同名前缀子运行,防止把多执行的一次从摘要里抹掉。
作者做了这样的反例检查:删除第二次的真实产物、把状态改为 complete、删除摘要中的尝试、虚报调用次数,以及额外创建第三次运行。这些都被拒绝。没有只用执行脚本自己的“通过”消息证明自己正确。
这个验证器针对的是表中固定情景。如果非零时间预算在中途真的耗尽,或者发生中断、未知异常,它会要求人工核查,不能直接套用正常次数预期。验证器的能力边界也需要写清楚。
把这套判断带回 Kimi 开发任务
完成离线练习后,你可以选择在本篇目录打开 Kimi 讨论方案:
kimi --plan
2026年10月4日核对了官方命令文档与本机 Kimi Code 2.0.2 的帮助。这里的 --max-attempts、--max-seconds 是课程脚本参数,不要把它们传给 kimi,也不要沿用未经核实的旧 CLI 循环参数。参考:Kimi CLI 命令。
可以给它这样的任务:
读 AGENTS.md、docs/loop/TASK.md、loop_lab.py 和 verify_loop.py。先解释继续与停止条件,给出验证计划。经我确认后,用新名称运行一次 repair 情景,最多两次 Harness 尝试;核对两次子运行与最终产物。不要更改业务代码、样例或验证标准,不调用笔记助手的真实 API,不发布、不连接 MCP。重复失败、预算不足或未知异常就停止,报告证据路径及待处理问题。待审不等于内容正确。
进入执行阶段仍按工具当前界面审核动作。这个提示词明确了任务,但不能硬性限制 Kimi 会话的全部模型请求数;脚本能强制的是其内部的尝试次数。真实 Kimi 交互可能消耗开发额度,作者没有执行这段真实会话。
以后让 Kimi 修复实际代码,可以保留同样的骨架:每轮先说清依据什么失败证据、准备改什么,改完运行指定检查,保留 diff;连续相同失败则停止,验收通过则交付,不无限“再优化一轮”。至于修改文件如何保存成可接手的检查点,下一篇再展开。
最可能卡住的地方
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 找不到新脚本 | 还在lesson-13或内层project | 进入lesson-14根目录 |
| 重名退出2 | 已经执行过相同Loop名 | 留下旧记录,使用新名称 |
needs_human/unexpected_error |
子运行重名、文件变化或证据不一致 | 看error_type及对应子目录,不自动清空重跑 |
| 子运行存在但摘要为running | 发生中断或保存失败 | 核查实际文件,不能认定动作没做 |
| 时间预算没立即打断工具 | 当前只是轮次边界检查 | 不把它当强制超时器 |
| 验证旧记录提示指纹不同 | 当前文件已经变化 | 找对应版本,不重写历史指纹 |
作者在阻断网络的环境中验证了六种情景,以及非法预算、重名保护、中途预算耗尽、注入中断、丢失产物和伪造记录等边界。中途耗时使用可控时钟模拟,中断使用测试注入;没有把它们说成真实服务故障。原应用和 Harness 保持字节不变,没有真实 Kimi / DeepSeek 调用,也没有 Windows 实机或浏览器视觉验证。
最后留三个自检问题:
- 最多三次尝试是否意味着模型最多请求三次?不意味着;必须明确计数层级,本篇根本没有调用模型。
- 第二次还是同一输入、同一错误,是否应该用完第三次额度?不必,识别到停滞可以提前停止。
- 第一轮已经写了文件,却在更新摘要前中断,能否直接重新执行?不能;要先核对已经发生的动作和实际产物。
这时你的项目已经能完成有边界的离线循环,并保留每次尝试的证据。下一篇长任务与断点续做,就从最后这个问题继续:会话或程序中断之后,怎样据实判断接下来该做什么。
17 长任务与断点续做:换一个会话,为什么还能接着干
设想上一轮的循环执行到一半,终端关闭了。第二天,你重新打开项目,看见运行状态写着 interrupted,输出目录却已经有一份复习资料。
这时直接说“继续刚才的任务”,可能产生两种错误:助手以为文件尚未生成,又导出一次;或者看到文件存在,就把整个任务标为完成,跳过了验证。
长任务能接着做,靠的是把已发生的事实、尚未确认的部分和下一步动作保存下来,并在接手时重新核对。恢复会话可以找回讨论,恢复工作则必须面对当前文件与真实执行结果。
本篇会构造一次明确的教学中断:导出已经写出文件,但成功回执尚未保存。然后用一个新的执行过程检查现场,只补做产物核验,另存交接记录。原有文件和中断状态都保留,不重跑导出。
配套:工作区说明 · 完整 ZIP。Python 3.11 或更高版本、标准库即可;不需要 API Key、SDK 或模型账户。候选与中断情景由作者准备,不是 Kimi 实际会话,不代表真实服务事故。
三个“接着干”,其实不是一件事
| 你想恢复什么 | 需要找回什么 | 不能仅靠它保证什么 |
|---|---|---|
| 聊天会话 | 过去的讨论、工具记录、任务意图 | 当前工作目录没有变化 |
| 开发工作 | 代码版本、未提交修改、测试和未完成事项 | 上次启动的动作没有产生副作用 |
| 程序执行 | 已持久化状态、实际产物、外部回执 | 可以从任意一行原封不动继续 |
例如,Kimi 的旧会话记得“下一步运行导出”,但那次导出可能已经完成,只是会话未能拿到最终结果。另一方面,即使保存着完整聊天,代码也可能被你手动修改过,过去通过的测试不能自动覆盖今天的版本。
因此,接手时先问“现在有什么证据”,再问“聊天里上次说要做什么”。二者有冲突时,要把差异写出来,不要默默选一个听起来顺畅的故事。
Anthropic 关于长任务的工程实践也把进度文件、Git 历史、逐步工作和接手时的验证放在一起。其核心用途是让新的上下文尽快了解项目现状,而不只是延长聊天记录。参考:Effective harnesses for long-running agents。
明确本篇的起点和接手范围
解压后进入 lesson-15 根目录。原笔记助手、导出器、Harness 和 Loop 都与第14篇保持一致。应用仍以 DeepSeek 为模型服务,Kimi 是辅助开发工具。
新增的主要文件是:
prepare_handoff.py # 在新目录构造教学中断
recover_export.py # 检查现场,必要时另存接手记录
verify_recovery.py # 独立核对接手结果
docs/recovery/TASK.md # 当前任务范围
docs/recovery/HANDOFF_TEMPLATE.md
先读当前任务。它只允许处理本课的固定 valid 导出样例,以及特定的中断状态。它不是通用恢复工具,也不会恢复上一篇的整个 Loop。
还有一个容易漏掉的前提:先确认旧执行过程已经结束,当前只有一个人在操作这些目录。 如果旧进程还在写文件,你的新会话同时开始“恢复”,两个执行者会对同一份现场作出不同判断。
本课的准备命令是同步执行的,返回以后再运行下一条就可以。真实任务则要确认原终端任务或服务的状态,不能只凭聊天页面没动静判断进程已停止。这里没有提供分布式锁或自动进程探测。
构造“文件已经写好,回执还没保存”的现场
macOS / Linux:
python3 prepare_handoff.py --run resume-01 --case exported
Windows PowerShell:
py -3 prepare_handoff.py --run resume-01 --case exported
预期输出:
{"status": "interrupted", "case": "exported", "simulated": true, "actual_api_calls": 0}
命令退出0,表示教学场景准备成功;不代表导出流程正常结束。
这个脚本先调用原来的本地导出器,真正写出资料和清单,再注入一个 KeyboardInterrupt。Harness 捕获中断、保存状态,但没有机会写出导出成功回执。随后准备脚本添加 SIMULATION.json,明确标记这是作者构造的情景。
你不需要自己抢时间按 Ctrl+C,更不要为了这个练习强制关闭应用或拔电。作者也没有把这次注入测试宣称为真实断电、进程强杀或模型故障测试。
打开 runs/resume-01:
runs/resume-01/
├── context.json
├── state.json
├── SIMULATION.json
└── artifact/
├── review.md
└── manifest.json
这里应该没有 01-export.json 和 02-verify.json。state.json 中整个运行是 interrupted,而导出步骤停留在 running。
这个现场说明:状态没有更新为成功,不能反推出动作没有发生。 程序在写文件与保存回执之间,总可能有一个时间间隔。接手者需要同时看意图、状态和产物。
第一步只检查,不急着重新执行
macOS / Linux:
python3 recover_export.py --run resume-01
Windows PowerShell:
py -3 recover_export.py --run resume-01
预期结果:
{"decision": "reuse_existing_artifact", "reason": "artifact_verified", "semantic_review": "pending", "export_replayed": false}
默认模式只读,不创建接手目录。脚本依次确认:
- 来源是允许的本地运行名,记录中没有本课不支持的符号链接。
- 当前资料与原
context.json的文件指纹一致。 - 原任务、样例、运行名和中断位置符合本课预期。
- 现有
review.md和manifest.json通过原来的独立产物验证。 - 核对期间原记录的文件内容没有变化。
所以 reuse_existing_artifact 是依据现有证据作出的判断,不是“看见一个 Markdown 文件就当它没问题”。它会核对清单、来源、卡片内容和指纹,也会拒绝只有半套文件的目录。
检查仍没有给内容打出语义合格证。pending 继续保留,读者还要对照问题、答案和原文。这里解决的是“能否复用已存在的产物”,不是自动替人决定是否可以发布。
续做剩余步骤,另存一份新的判断
确认只读检查结果后,保存本次核验与交接说明。
macOS / Linux:
python3 recover_export.py --run resume-01 --record review-01
python3 verify_recovery.py recovery-records/review-01
Windows PowerShell:
py -3 recover_export.py --run resume-01 --record review-01
py -3 verify_recovery.py recovery-records/review-01
第一条会重新检查现场,不会拿上一条命令的旧判断直接写结果。终端输出与只读检查相同,但这次新增:
recovery-records/review-01/
├── recovery.json
└── HANDOFF.md
第二条独立验证应输出:
{"result": "pass", "status": "needs_review", "original_status": "interrupted", "semantic_review": "pending"}
打开 recovery.json,其中应有 action=verification_only、export_replayed=false,以及原记录中各文件的指纹。再打开 HANDOFF.md,能读到已确认事实、没有做的动作、未解决事项和下一步。
有读者会觉得奇怪:为什么原状态还写着中断,新记录却说可以待审?
因为它们在回答不同问题。原记录描述上次流程在什么位置停了;新记录描述这次接手者重新核对后得到了什么结论。没有必要把旧事故记录改写成一次顺利完成的历史。
本篇也没有补造缺失的旧 01-export.json,仿佛它在中断之前已经存在。补做的是新的验证,不是补写虚假的过去。原运行目录所有文件保持原样,独立验证器会再次核对这些文件与新记录中保存的指纹。
这样,你已经完成了一次有用的续做:复用完成的工作,只做尚需确认的部分,留下新的证据。它没有恢复 Python 当时的内存、调用栈或下一条指令,也没有重新运行导出。
不完整的现场,不能靠“应该没问题”通过
再准备两个对照情景。以下为 macOS / Linux 命令;Windows PowerShell 将每行开头的 python3 换成 py -3,其他参数相同。
python3 prepare_handoff.py --run partial-01 --case partial
python3 recover_export.py --run partial-01 --record partial-review
python3 prepare_handoff.py --run empty-01 --case empty
python3 recover_export.py --run empty-01
partial 会在本次新建的教学产物中移除 manifest.json,然后注入中断;empty 则在实际导出前注入中断。两种情况都必须标注为模拟,不能冒充真实事故样本。
两次核查的预期都是:
{"decision": "needs_human", "reason": "evidence_incomplete_or_invalid", "semantic_review": "pending", "export_replayed": false}
核查命令退出1,而且 recovery-records/partial-review 不应被创建。原来的不完整现场保留,不会被清空后重新导出。
| 当前证据 | 本课决定 | 为什么 |
|---|---|---|
| 产物完整、版本吻合、符合固定预期 | 只补核验并另存记录 | 已完成的工作可以复用 |
| 只有资料,没有有效清单 | 交人核查 | 无法按本课标准确认完整性 |
| 没有产物,但记录有执行意图 | 交人核查 | 无法仅凭状态批准自动重放 |
| 输入或代码版本变化 | 停止,比较版本 | 过去的证据不自动适用于现在 |
| 状态不属于本课固定中断模式 | 停止,说明不支持 | 不把一个小示例当通用恢复器 |
在 empty 教学场景中,我们作为构造者知道没有导出。但接手算法故意不把作者的场景标签当完成证据,它依据文件和约定判断。面对真实外部动作,尤其不能把“本地没看到文件”推导为“服务器没执行”。
如果人工最终决定重新开展一次本地导出,应作为明确的新尝试,重新确认输入、范围和预算,使用新运行名并关联旧记录。本篇不会替你自动作这个决定。
重复执行和幂等,不要只看命令名称
“再执行一遍应该也没事”有时成立,有时不成立。
读取同一份文件、在稳定版本上重新检查产物,通常适合作为接手的第一步。本篇核验就是这种操作:它不再次创建复习资料,也不改变业务数据。
但保存学习计划、发消息、发布资料等动作,可能每调用一次就产生一个新结果。失败消息还可能发生在动作之后。第11篇中“保存已经成功,后续总结失败”的例子,就属于这种情况。
幂等可以理解为:针对同一个逻辑操作重复执行,最终效果与执行一次相同。给请求起一个新名字并不会自动实现幂等;每次都换一个 ID,反而可能被系统认为是不同任务。
本篇使用独占新目录来拒绝覆盖,用指纹检查现场是否变化。这些有助于保留证据,但不等于给所有外部服务实现了“只执行一次”。真实写操作需要具体系统支持,例如可查询的业务结果、稳定的操作标识、唯一约束或事务;方案要根据动作来设计。参考:AWS 关于幂等 API 与安全重试的工程说明。
不要把恢复流程写成“捕获所有异常,然后从头再来”。越靠近外部副作用,越需要先确认上一次的结果,再决定是否允许重试。
一份能交接的记录,应该让下一位少猜什么
打开交接模板。它不是把整段聊天搬进文件,而是整理几类能指导行动的信息。
| 内容 | 应写什么 | 避免怎样写 |
|---|---|---|
| 当前目标与范围 | 只补验证,不重新导出或发布 | “继续完善项目” |
| 版本与入口 | 工作目录、提交或快照、未提交修改、关键路径 | 只写“最新版” |
| 已确认事实 | 实际命令、结果、证据文件和适用版本 | “感觉已经差不多” |
| 不确定事项 | 哪个动作未确认、哪些验证未做 | 把未知写成未执行 |
| 下一步与停止条件 | 先检查什么,只允许补哪一步,何时停 | “一直干到完成” |
本课生成的 HANDOFF.md 是一份具体例子。拿它对照原文件,看每句话能否找到证据:旧状态确实是中断;产物已重新验证;没有再次导出;内容仍需人工审阅。
如果换成实际代码开发,还要补充当前分支、提交和未提交差异。在你自己的已初始化 Git 仓库中,可以用 git status --short、git diff --stat 和 git log -1 --oneline 定位现状。不要在未建仓的练习包里虚构一个提交号,也不要为了让目录“看起来干净”丢掉未提交修改。
测试证据也要带范围:某个提交上的离线测试通过,不代表后续修改也通过;本机通过,不代表已经做过 Windows 或线上验证。交接记录必须让下一位知道哪些结论可以沿用,哪些需要重做。
最后记下预算。换一个聊天不等于任务预算重新满额,旧循环已消耗的尝试、实际调用和未知费用不能被清零。本课续做不调用模型,也不恢复旧 Loop;将来若需要新尝试,应明确剩余额度或新的授权。
在 Kimi 中续旧会话,或者用新会话接手
如果你原来就在 Kimi 中工作,可在同一个真实项目目录续最近的会话:
kimi --continue
想自己选择旧会话:
kimi --session
已有明确会话 ID 时,--session 也支持传入 ID。它与 --continue 是两种选择方式,不要一起使用。以上按2026年10月4日官方文档与本机 Kimi Code 2.0.2 的帮助核对。参考:Kimi CLI 会话参数。
本篇新解压目录不一定存在旧 Kimi 会话。没有旧会话、换了工具,或者想让一个全新会话检查交接质量时,可以从计划模式开始:
kimi --plan
给新会话的说明可以是:
先读 AGENTS.md、docs/recovery/TASK.md、recovery-records/review-01/HANDOFF.md 和 recovery-records/review-01/recovery.json,再核对其中引用的 runs/resume-01。先判断当前证据是否支持交接说明,指出尚未确认的事项。不要运行 prepare_handoff,不重放导出或 Loop,不修改旧记录,不读取个人资料、不调用真实应用 API。若证据一致,只提出人工核查内容的下一步;不把待审说成完成。若不一致,说明差异和停止原因。
这里引用的路径以你已完成前面 review-01 练习为前提;换了运行名,要同步修改路径。
旧会话可以提供上下文,新会话可以帮助检验文档是否足够自洽。但两个会话都不应同时修改同一现场。真实 Kimi 交互可能消耗开发额度,作者只核对了文档和帮助,没有启动或读取你的真实会话。
完成检查与常见故障
完成本篇后,你应当能自己确认:
- 原
runs/resume-01/state.json仍是interrupted,没有伪造成功回执。 - 原导出仍可读,新
recovery.json明确只补验证,语义仍为pending。 - 独立
verify_recovery.py通过,部分产物和无产物情景都停止。 - 交接说明能指向具体证据,而不是要求下一位相信一句“已经做好”。
| 现象 | 优先检查 | 处理 |
|---|---|---|
| 运行名或记录名已存在 | 是否重复执行了练习 | 保留旧目录,使用新名称;不覆盖 |
context_changed |
README、任务或源码是否被编辑 | 对照对应版本,不刷新旧指纹掩盖变化 |
unsupported_state |
是否拿了其他运行状态来测试 | 本工具只处理指定中断,其他情况人工判断 |
evidence_incomplete_or_invalid |
清单、产物或原记录是否缺失 | 保留现场,不凭猜测补齐 |
| 新记录验证失败 | 原文件是否在接手后改变 | 对比差异,避免沿用过时结论 |
| 续会话找不到预期记录 | 工作目录或选中会话是否正确 | 选择正确会话,或用交接文件开新会话 |
作者验证了三个注入情景、版本变化、未知状态、产物篡改、重名、非法名称和伪造接手结论等边界。测试还把导出函数替换成“一旦被调用就失败”,证明核查和另存记录没有重放它,并逐字节检查原运行不变。网络连接被阻断,没有真实 Kimi / DeepSeek 调用;没有 Windows 实机、浏览器视觉或真实崩溃恢复测试。
自检:为什么一份新的 needs_review 记录可以与旧 interrupted 同时存在?因为前者是本次基于证据作出的新判断,后者保留上次执行事实。为什么不能靠保存聊天保证任务恢复?因为聊天无法代替当前版本、文件完整性与副作用结果的核查。
下一篇并行与多 Agent会把“接手”扩展成“分工”:多个助手各自负责什么,怎样避免互相覆盖,以及如何把结果集成回来。当前练习仍保持单人串行,不提前让多个执行者争用现场。
18 并行与多智能体:什么时候值得拆给多个助手
你准备检查笔记助手的下一次迭代。历史搜索需要核对“第51条记录”这样的边界,复习资料导出需要检查卡片、引用与文件内容。两件事都重要,但搜索检查不必等待导出结果,导出也不需要先知道搜索是否通过。
这时,分给两个助手似乎很自然。可如果它们同时修改 storage.py,或者都把结果写进同一个 report.json,省下的等待时间很快就会变成排查时间:到底检查了哪个版本?后写入的结果有没有盖掉前一个?谁负责判断整个任务完成?
上一篇解决了“另一个会话怎样接着干”。这一篇解决“两个工作者怎样同时干”。我们先用可运行的离线实验观察分工与集成,再把这些规则用到 Kimi Code。你不需要为实验开通模型服务。
先区分多 Agent 与并行
多 Agent 指多个具有各自任务和上下文的模型助手参与工作;并行指多个工作在时间上重叠。两者可以结合,也可以分开:一个主助手先让审查助手返回意见,再自己修改,是多 Agent 协作,但主要步骤仍然串行;同时跑两个固定测试脚本,则有并行,却没有模型助手。
本篇配套实验属于后一种。代码是作者参考实现,两个工作项实际运行 Python 子进程,既不冒充 Kimi 的生成记录,也不把普通脚本包装成“自主智能体”。这样做,是为了让你先看清版本、目录、交付和验收这些可以检查的事实。
当前项目保留第三部分以来的分工:笔记助手在线运行时仍使用 DeepSeek 官方 API,Kimi Code 用来辅助开发。增加开发助手数量,不会自动改变应用的模型供应商。
先画依赖,再决定人数
本轮的目标是“在同一个正确版本上,完成搜索边界与导出产物的机械检查”。它的依赖关系如下:
确认输入版本,创建本次运行目录
│
┌───────┴────────┐
│ │
A:搜索边界测试 B:导出复习资料
│ │
│ 验证导出产物
│ │
└───────┬────────┘
│
收齐证据,复核版本,判断整轮状态
│
人工核对语义
A 与 B 可以同时开始,B 内部却必须先导出、后验证。把所有箭头都去掉并不会更快,只会让验证器读到尚未生成的文件。
判断能否拆分,可以先问三个具体问题:这个工作项是否需要另一个工作项尚未产出的结果?是否会修改同一份状态?完成后能否用清楚的接口交回结果?
| 项目中的情况 | 更合适的安排 | 原因 |
|---|---|---|
| 搜索测试与固定导出检查 | 有限并行 | 输入已确定,输出可分开 |
| 两人同时修改搜索函数 | 先确定唯一负责人 | 共享修改容易覆盖,测试版本难确认 |
| 改数据库字段,再改依赖字段的页面 | 先定接口,再按依赖推进 | 下游不能猜上游最后会采用哪个字段 |
| 从功能和权限两个角度只读审查同一差异 | 可以并行 | 可以共享读取,但意见仍需统一核对 |
| 修复一个明显拼写错误 | 单助手串行 | 分派和收集可能比修改本身还费事 |
这些是本项目的安排建议,不是“复杂任务必须多 Agent”的定律。任务边界越模糊,增加助手越容易放大模糊。
起点:使用本篇独立快照
下载完整练习包,解压到新的练习目录。也可以查看材料说明。使用 Python 3.11 或更高版本;本篇只需要标准库,不安装 SDK、不配置 Key。
所有命令都在解压后的 lesson-16 目录运行,不是在内层 project 中。先确认这里有 parallel_lab.py、verify_parallel.py 和 project。项目的38个交付文件与第15篇相同,搜索功能已经正确实现。
不要覆盖自己原来的项目、Git 历史或运行数据。快照内也保留了前几篇的 Harness、Loop 和接手工具,供对照阅读;本次只运行下面的新实验。第15篇的接手脚本仍要求单人串行,本篇不会自动让它具备并发能力。
使用前先看当前任务。它把职责写成了可以执行的规则:
| 角色 | 负责什么 | 可以写哪里 |
|---|---|---|
| 协调器 | 记录输入、分派、等候、汇总 | 新运行目录的 context.json、integration.json,并创建子目录 |
A:search |
运行四项搜索边界测试 | 本次 search 目录;测试另有自己的临时 SQLite |
B:export |
导出三张样例卡片,再验证 | 本次 export 目录,包括 artifact |
共享源码只读。每个工作项都知道自己的输出位置,协调器承担最终集成责任。
这里的文件归属由可信脚本实现,也要求操作者遵守;目录划分不是操作系统沙箱。运行期间不要编辑输入,也不要让另一个工具修改同一工作区。开始和结束时核对指纹,可以发现最终版本变化,却不能证明运行中从未发生过“修改后又改回来”。
运行一次正常并行
macOS/Linux 终端执行:
python3 parallel_lab.py --run parallel-01
python3 verify_parallel.py --run parallel-01
Windows PowerShell 执行:
py -3 parallel_lab.py --run parallel-01
py -3 verify_parallel.py --run parallel-01
后续命令继续以 python3 展示;Windows 使用同样的参数,将解释器替换为 py -3。每次尝试使用新名称;已有目录会被拒绝覆盖。
第一条命令正常输出如下。JSON 会显示在一行,这里仅为阅读排版:
{
"mode": "offline_script_workers",
"schedule": "parallel",
"fixture": "valid",
"worker_limit": 2,
"status": "needs_review",
"inputs_unchanged": true,
"workers": {"search": "passed", "export": "passed"},
"commands_executed": 3,
"semantic_review": "pending",
"actual_api_calls": 0
}
两个工作项为什么执行了三条命令?A 运行一次搜索测试;B 先运行导出器,再运行导出验证器。工作项数量、命令数量和模型请求数量是三种不同的统计,不能混在一起。
第二条命令应输出:
{"result": "pass", "workers_checked": 2, "status": "needs_review", "semantic_review": "pending"}
needs_review 表示本轮机械检查已满足要求,还有人工语义核对。pending 没有因为工作者变多就消失。固定样例的引用和文件结构通过,不代表真实模型面对任意笔记都能答对。
打开 parallel-runs/parallel-01,你会看到:
context.json 本轮输入指纹
search/
result.json A的结果和命令回执
search.stdout.log
search.stderr.log unittest四项结果
export/
result.json B的结果和两条命令回执
export.stdout.log
export.stderr.log
verify.stdout.log
verify.stderr.log
artifact/
review.md
manifest.json
integration.json 协调器最后形成的结论
先看 search/search.stderr.log:应包含四项测试、Ran 4 tests 和末尾的 OK。测试覆盖空历史、恰好50个命中、51个命中、位于旧记录中的唯一命中。正常的测试报告写在 stderr 并不等于失败,要结合退出码和测试结果判断。
再打开 export/artifact/review.md,检查三张卡片及“作者构造的固定样例”“语义审核:待人工核对”等标记。没有 Key 也能完成,是因为它读取教学样例,而没有现场生成模型答案。
收到“完成”以后,协调器还要检查什么
每个 result.json 都包含工作项身份、输入指纹、命令参数、退出码及日志指纹。这里的“交付接口”就是双方事先约定的这些字段及产物位置,不必一开始就搭建消息队列。
本课有两层检查。parallel_lab.py 等待两个工作项结束,确认返回状态和输入前后指纹,再写汇总;verify_parallel.py 则从文件重新核对证据,没有导入协调器的完成判断逻辑。它会检查工作项是否齐全、身份是否正确、命令顺序和退出码是否符合预期、日志是否匹配,并重新检验真实导出文件。
因此,“汇总里有一个绿色字段”还不够。缺失导出文件时,整套证据仍然不合格。
验证器检查的是本地教学记录的一致性。哈希可以帮助发现改动,不是数字签名;能同时改源码、日志和回执的人也可能伪造一整套材料。它不证明模型回答质量,更不是对恶意助手的隔离机制。
如果迁移到真实开发任务,协调者还要看最终差异:A 修了一个函数,B 的测试却基于旧函数运行,即使两份报告都说通过,也不能推导出集成后的版本通过。把改动组合起来以后,必须针对组合后的状态运行相关检查。
故意让一个工作项失败
执行新的反例:
python3 parallel_lab.py --run failed-01 --fixture bad-quote
python3 verify_parallel.py --run failed-01
第一条命令退出码为1。摘要中的关键字段应为:
{
"status": "blocked",
"workers": {"search": "passed", "export": "failed"},
"commands_executed": 2,
"semantic_review": "pending",
"actual_api_calls": 0
}
这是特意选择的错误引文样例。导出器会拒绝不在对应笔记原文中的引用,B 不再执行下一步验证,也不生成完整产物目录。A 仍然完成搜索检查;失败不会抹掉已经取得的另一份证据。
第二条命令同样应退出1,拒绝把反例当作一次完成的正常运行。要查看上一条退出码,macOS/Linux 使用 echo $?,PowerShell 使用 $LASTEXITCODE;必须紧接需要检查的命令查看。
读 export/export.stderr.log,定位具体引用错误。这里不需要重跑已经通过的搜索,也不应该为了获得“全绿”放宽引文规则。真实任务中,先确认导出失败的原因和修复范围,再决定开启新一轮工作。
本实验选择“让另一项独立工作收尾,再汇总阻塞”。如果两项会产生相互依赖的外部副作用,这个策略就不一定适合。取消一个等待中的任务,也不等于撤销已经执行的写入;退出或超时后都要依据实际现场判断。
实现里真正限制了什么
可以打开协调器源码,找到这段:
with ThreadPoolExecutor(max_workers=2) as pool:
jobs = [pool.submit(worker, n, run, fixture, baseline)
for n in ('search', 'export')]
results = [job.result() for job in jobs]
两个任务先提交,然后收集结果。代码没有“提交A、等A完成、再提交B”。线程在这里主要负责等候各自的 Python 子进程;它不意味着给同一个模型开两个思考线程。Python 的执行器文档说明了工作线程上限与等待结束的行为。
固定工作项最多两个,正常最多三条子命令,没有递归分派,没有失败重试。每条子命令设置30秒超时;超时会阻止该工作项继续。这不是整轮严格30秒的承诺:B 有顺序步骤,启动和收尾也需要时间。当前子命令不再创建进程树;不要直接把此超时处理当成任意外部工具的全进程清理方案。Python subprocess 文档说明了 run 的超时与子进程处理范围。
如果进程被中断、磁盘写入失败,可能只留下部分记录,没有 integration.json。缺失最终记录就没有完整完成证据。本脚本不负责自动续跑;保留现场,确认旧进程结束,再按第15篇的方法判断下一步。
还可以用新名称改为串行:
python3 parallel_lab.py --run serial-01 --serial
python3 verify_parallel.py --run serial-01
正常业务结果应一致,schedule 变成 serial。这组任务很小,进程启动和协调本身可能占去不少时间,作者没有据此给出加速倍数。选择并行要看真实任务耗时和协调成本,而不是看屏幕上出现几个工作者。
在 Kimi Code 中试一次受限的分工
截至2026年10月4日,官方文档区分主 Agent 与子 Agent:子 Agent 使用独立上下文,任务描述需要明确传入;explore 用于只读探索,coder 可以执行开发操作,plan 侧重规划。子 Agent 会独立消耗模型 token。具体能力与权限以安装版本为准。Kimi Code 的 Agent 文档
本机只核实了 kimi --version 为2.0.2及帮助信息,没有实际发起这段练习。以下是给读者选择的开发服务操作,会消耗 Kimi 服务额度;它不需要调用笔记助手的 DeepSeek API。
如果你已有可用的 Kimi Code,在本篇目录先执行:
kimi --version
kimi --help
kimi --plan
帮助中应有 --plan。它让会话先从计划开始,本课先讨论如何分派,不据此承诺任何版本都完全不能写文件。向主助手提供下面的任务,再查看它提出的安排:
当前只做第16篇的只读审查。先读AGENTS.md和docs/parallel/TASK.md。
请先给出分工方案,不立刻执行。拟使用最多两个explore子Agent:
A:读docs/specs/search.md、project/storage.py、verify_search_boundary.py,
核对“先过滤全部历史再取50条”的规格、实现与测试预期。
B:读export_review.py、verify_export.py、project/study_task.py、
project/fixtures/valid.json,核对导出与独立验证之间的对应关系。
给每个子Agent完整的文件路径、单一问题和只读约束,
不要假定它们看得到本会话前面的讨论。
两项都不得写文件、执行shell、调用应用模型或继续转派。
每项只返回:结论、代码依据、尚未验证的事项。
主会话负责核对冲突,不把静态阅读写成测试已经通过。
完成这一轮后停止,不自行开始修复。
注意这里使用的是 docs/specs/search.md,不是旧归档规格。先核对工作卡中的路径是否存在,再决定是否让主助手按方案分派。不要把旧会话的默认任务混进来。
若方案符合范围,可以继续发:“按上述两项只读任务执行,收齐后汇总,保持全部限制。”这一步才是实际模型协作。返回后,把其判断与前面的本地命令结果比较:阅读发现“测试缺少某个边界”,不等于现有测试失败;运行通过,也不能自动反驳它指出的未覆盖场景。
--agent 在本机帮助中用于选择新会话的主 Agent 配置,不是“开几个助手”的数量开关。本课通过任务说明限制分派范围,不发明一个 --agents 2 参数。若你安装的版本没有文档描述的子 Agent 行为,保留同样的工作卡,由一个主助手分两次审查也能练习接口与验收。
真正并行修改代码,还要多一道隔离
本课特意从共享只读输入开始。如果今后让两个助手改代码,仅给它们起不同名字远远不够。
至少应先确定文件负责人和公共接口,让每个助手在自己的工作副本中修改。可以用独立副本或 Git worktree 承载不同分支;仅在同一目录创建两个分支名,并不会得到两份可同时修改的磁盘文件。临时数据库、端口和测试输出也要分开,不能只隔离源码。Git 官方文档介绍了同一仓库下多个工作树的机制。
假设 A 改导出返回结构,B 改页面消费这个结构,双方必须先约定字段及错误行为;或者先让 A 完成,再让 B 跟进。协调者按依赖顺序集成,查看冲突与最终差异,再运行组合版本的相关测试。两个助手对各自副本都说“完成”,还只是两份局部交付。
也不要把多数投票当成事实验证。三个助手读了同一份过时规格,完全可能得出同一个错误结论。意见分歧应回到规格、源码、可复现输入和实验结果,而不是按票数决定业务行为。
成本应该算整轮
并行可能缩短等待,但通常仍要支付两份输入和输出,以及主助手分派、阅读、核对结果的成本。独立上下文能隔开讨论,也意味着必要材料可能被重复传入。
比较方案时至少记录完成时间、总用量、集成返工和结果质量。两项各需要十分钟的工作,在没有依赖、资源充足时,核心执行时间可以重叠;但加上分派和集成,不应把总耗时直接承诺为十分钟。这只是估算思路,不是本课测速结果。
给自己的第一次模型协作定一个小范围:最多两个子任务,每项一个明确问题,只返回必要证据,禁止自行扩大任务,遇到缺信息就报告,主助手汇总一轮后停止。先从服务界面确认自己的可用额度;人数上限只是结构约束,不是金额硬上限。
配套分工工作卡可以用于下一次任务。若你填不出“允许写哪里”或“完成后交什么”,先完善任务,再增加助手。
卡住时按证据查
| 现象 | 检查与处理 |
|---|---|
| 提示运行目录已存在 | 使用新名称,不删旧证据后假装首次执行 |
| A 通过、B 失败 | 看B的第一条失败回执与日志;整轮保持阻塞 |
| 汇总正常、独立验证失败 | 检查缺失文件、输入指纹、工作项身份与日志;不要只改状态字段 |
| 超时或没有最终汇总 | 保留现场,确认进程结束;不假定已经回滚或可以盲目重放 |
| Kimi 返回了源码分析,但没有测试记录 | 如实记为静态审查,另运行本地验证 |
| 并行没有更快 | 检查任务是否太小、存在隐藏依赖或共享资源竞争,必要时回到串行 |
作者实际在 macOS、Python 3.11 环境运行了并行、串行、错误引文和多种证据损坏检查;还通过同步屏障确认两个工作函数可以同时处于执行状态。超时分支和执行期间的版本变化使用了测试注入,没有冒称真实服务故障。没有做真实 Kimi/DeepSeek 请求、Windows 实机或性能基准测试。
现在试着回答:A 的检查通过,B 的产物丢了,能不能交付?不能。两个助手都分析出了同一问题,是否已经证明问题存在?还需要代码或实验依据。两项都通过以后,是否可以跳过人工语义核对?本轮仍然是 pending。
本篇结束时,业务源码保持原来的正确状态,你新增的是一套能分工、收证据和拒绝虚假完成的工作方式。下一篇会把视角移到集成之后:怎样把验证过的修改整理成可以交付、检查兼容性并在必要时回滚的版本。
19 从修改完成到版本交付:检查、发布与回滚
搜索功能在你的电脑上通过了测试,你把项目文件夹压缩,发给另一台电脑。对方解压后却启动不了:你漏了模板,依赖版本不一致,或者把自己的数据库和配置一起打包了。
另一种麻烦发生得更晚:新版本用了两小时才发现问题。你换回旧代码,以为一切恢复正常,却发现新代码已经改过数据库结构;或者顺手恢复了早上的备份,把这两小时的新记录也一起覆盖掉了。
上一篇讲多个工作项如何集成。这一篇走完集成之后的最后一段:留下可识别的交付物,验证交付物本身,并提前练习怎样退回。我们用本地源码包和隔离的回滚演练理解这些机制,不需要购买服务器。
“代码完成”以后,还差哪几件事
测试通过通常只能说明:某个版本,在某个环境、某组输入下,满足了某些预期。交付还要回答:别人拿到的是不是这个版本,能否准备相容的运行环境,遇到问题有没有可执行的恢复路径。
把一次交付拆成三个对象,会清楚很多:
| 对象 | 本项目中的例子 | 要记录什么 |
|---|---|---|
| 代码与静态文件 | Python、模板、样例、依赖说明 | 版本标识、文件清单、内容指纹 |
| 环境与配置 | Python、安装的库、非秘密配置 | 实测版本、配置变化、未验证环境 |
| 持续变化的数据 | 问答记录、计划、会话密钥、个人笔记 | 存放位置、兼容范围、备份与恢复办法 |
源码包主要解决第一项。它不会自动替你备份数据,也不会把运行环境完整地装进去。
本课继续使用已经正确实现搜索的笔记助手。应用仍调用 DeepSeek,Kimi Code 仍是开发工具。为了专注交付,本篇不新增业务功能;我们会制造一个明确标记的故障副本,只用于回滚练习。
起点:独立目录,干净源码
下载第17篇练习包,解压后在外层 lesson-17 目录操作。先读当前任务,确认有 release_lab.py、rollback_lab.py、verify_release.py 和 project。
主路径只需要 Python 3.11 或更高版本及标准库,不需要 Key,也不需要安装模型 SDK。project 的38个文件与上一课相同。不要用它覆盖自己正在运行的项目。
这里有两种 ZIP,名称和用途不同:
- 下载的
lesson-17.zip是完整课程材料,包含外层脚本、任务说明和应用源码。 - 稍后生成的
source.zip是应用源码候选,只含38个白名单文件。外层的导出实验、评测、Harness 和 Loop 等课程工具不在里面。
应用原有的 package_project.py 使用显式清单打包,不把整个运行目录一股脑压进去。因此 config.json、真实 Key、数据库、session.key 和个人运行记录不会因为与代码放在一起就被收进包里。
不过,白名单不是秘密扫描器。你若把密码直接写进一个白名单 Python 文件,打包器依然可能把它带走。分享前仍要审查实际改动;本课使用干净教学快照,也不读取你的私人资料。
创建一个能核对身份的候选
macOS/Linux:
python3 release_lab.py --name local-v1
python3 verify_release.py --name local-v1
Windows PowerShell:
py -3 release_lab.py --name local-v1
py -3 verify_release.py --name local-v1
后续以 python3 展示;PowerShell 换成 py -3,参数相同。每次使用新名称,工具不覆盖已有候选。
第一条命令应输出:
{"release_id": "local-v1", "status": "candidate", "files": 38, "search_exit": 0, "published": false}
local-v1 是本次练习的候选名,不是自动生成的 Git 提交号。真实项目可以同时记录提交号与包指纹:提交号说明源码历史位置,包指纹帮助识别实际交付的文件。不要只用“最新版.zip”作为唯一标识。
构建过程做了几件具体的事:读取源码指纹,调用既有打包器,再比较包内内容和打包后的源码,最后从 ZIP 展开一份应用,对展开的代码运行四项搜索边界测试。
最后一步很关键。只测工作目录、然后把另一个目录打包,会产生“测试对象与交付对象不同”的空隙。本课至少让搜索边界检查直接对包里的代码执行。
生成的目录是:
releases/local-v1/
source.zip
manifest.json
search.stdout.log
search.stderr.log
unpacked/
verify_search_boundary.py
project/ 从source.zip展开的38个文件
manifest.json 是交付清单:候选名称、ZIP 的 SHA256、各文件指纹、实际 Python 版本、依赖说明、数据结构版本和本轮验证范围都在这里。SHA256 可以帮助发现文件变化,但不是发布者身份签名。
第二条独立核对命令应输出:
{"result": "pass", "release_id": "local-v1", "files": 38, "published": false}
它重新读 ZIP 和清单,检查缺失、多余或重复成员,比较展开内容,并核对测试版本与四项通过的日志。只把清单里的 status 改成成功,不能替代这些证据。
正常结束仍然叫 candidate,即候选。published: false 明确表示没有发布;它不会因为测试通过就自动把文件发送给别人或上线服务。
四项通过,不等于可以略过其他检查
四项搜索边界是这个构建工具固定执行的最小检查,不是整款产品的完整验收。交付前还应根据本轮变化补齐相应证据。
例如,改过页面,就应验证 HTTP 输入、显示和会话隔离;改过资料不足的处理,就需要查看固定题集和人工语义判断;改过导出,就需要核对实际文件。本项目已有这些工具,应该使用与当前范围相关的检查,而不是重新发明一个只有“成功”两字的发布报告。
作者本轮还在已有的隔离 Python 环境里,针对展开包执行了搜索存储与 HTTP 检查、预览检查,以及应用原有的十项验收。这是作者环境中的实际离线验证,没有把它写成读者电脑也已通过。
如果你已经有前面课程的可用虚拟环境,可以使用其中的 Python,在外层目录运行:
python -B releases/local-v1/unpacked/project/acceptance.py
预期出现十项测试通过及 OK。这里的 python 指已激活虚拟环境的解释器;-B 避免在待核验源码中生成字节码缓存。若没有环境,可以在新的练习目录准备一个;安装依赖会联网下载包,但不会调用模型:
macOS/Linux:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r releases/local-v1/unpacked/project/requirements.txt
python -B releases/local-v1/unpacked/project/acceptance.py
PowerShell:
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r releases/local-v1/unpacked/project/requirements.txt
.\.venv\Scripts\python.exe -B releases/local-v1/unpacked/project/acceptance.py
Windows 直接调用环境中的解释器,不必为激活脚本修改系统执行策略。主路径的打包与回滚实验不依赖这次安装;遇到下载问题,应把扩展验收记为尚未完成,不要将未执行写成通过。
当前 requirements.txt 固定了 openai==3.24.0 和 Flask==3.1.3。这是本项目的验证基线,不是在推荐读者追逐最新版本。它还没有固定全部传递依赖——也就是这些库继续依赖的其他库。
在专用且已经验证过的虚拟环境中,可以用 python -m pip freeze 记录实际安装集合;以后需要更强的可重复性,再结合完整锁定与依赖包哈希。不要把平常混装许多工具的全局环境原样当成项目依赖。pip 的可重复安装文档解释了版本固定、传递依赖和哈希检查的作用。
给候选补一份人能读懂的说明
指纹能回答“是不是同一份文件”,却回答不了“这次改了什么”。配套的候选说明给出了本项目的记录方式。
真正交付时,应把说明、实际检查结果与源码包放在一起。重点写清行为变化、兼容条件、已知限制和恢复路径,不需要罗列每一行代码修改。
本课可以写成:历史搜索和证据统计沿用上一版正确实现;本次只整理交付方法;数据库结构仍为1,没有执行迁移;源码包不含私人配置或运行数据;测试使用构造样例,真实模型效果没有在本轮测量。这样的说明比“全面优化,稳定可靠”更有用。
此前的 semantic_review: pending 也不能在发布清单中悄悄变成通过。人工确实审查后,应该留下审查对象、结论和范围,而不是为了完成状态表而更改一个单词。
实际做一次本地回滚演练
先确认前面的 local-v1 候选核对通过,再执行:
python3 rollback_lab.py --release local-v1 --run drill-01
python3 verify_release.py --drill drill-01
脚本只在新建的 release-drills/drill-01 内操作。它不会替换外层 project,不会连接正在运行的 Web 服务,也不会接触你的真实数据库。
它从已核对的 ZIP 展开三份代码:before、fault、restored。其中 fault 被作者故意加上一处错误:搜索先取最近50条,再匹配关键词。这个版本应该被正常发布检查拦住;这里故意切入它,只是为了演练退回,绝不是建议带着失败测试发布。
本次顺序如下:
已验证源码包
↓
before:四项搜索边界通过
↓
fault:注入先取50条的错误,两项边界失败
↓
保留故障代码、日志和当前教学数据
↓
restored:重新展开原包,四项边界再次通过
active.json 是演练脚本读取的逻辑指向,表示下一次检查选哪个副本。它不控制真实服务器流量,也不会让应用自动支持生产版本切换。
两条命令正常时都应返回:
{"result": "pass", "active": "restored", "current_records": 2, "backup_records": 1, "schema2_rejected": true}
打开 before.stderr.log、fault.stderr.log、after.stderr.log:先是四项通过,中间是 FAILED (failures=2),最后重新通过。故障失败属于预期;如果故障阶段也全绿,应检查注入或检查范围,而不是认为系统更可靠了。
然后查看 drill.json。四个退出码应依次为:before=0、fault=1、after=0、schema=1。独立验证还会检查恢复代码是否等于原包、指向是否真的切回,以及实际数据库内容是否符合预期。
为什么当前记录是2条,备份只有1条
演练先在全新的教学 SQLite 中写入 old,再创建备份;随后写入 new,代表升级期间新增的数据。切回原代码时,没有把旧备份覆盖到当前数据库,因此两条记录都保留。
脚本另外把备份恢复到 restore-preview.sqlite3,检查那里面只有 old。这说明备份确实能恢复,但也直观地暴露了它的时间边界:备份以后的数据不在里面。
| 文件 | 演练结束时的内容 | 用途 |
|---|---|---|
data.sqlite3 |
old、new |
保留当前教学数据 |
backup.sqlite3 |
old |
升级前备份 |
restore-preview.sqlite3 |
old |
验证备份能恢复,未覆盖当前数据 |
schema2.sqlite3 |
两条记录,版本标记为2 | 单独测试不兼容版本拒绝 |
代码回退和数据恢复是不同操作。新旧代码都支持现有数据时,可以先回退代码;若数据已经发生不兼容变化,就不能机械套用这条路径。
本课备份使用 SQLite 的备份接口,而不是在数据库仍可能写入时只复制一个主文件。Python sqlite3 文档提供了 Connection.backup 的用法。真实项目还需把会话密钥、配置、笔记和相关文件纳入一致的恢复计划,单独一份数据库不代表整个应用都能恢复。
本演练的搜索测试使用独立临时数据库;保留两条记录的检查则针对教学数据文件。它证明了本次回退没有覆盖数据,不代表已经测完生产环境的所有数据读写路径。
数据版本拒绝,比“强行启动”更有用
本项目现有数据库使用版本1,Store 会拒绝不支持的版本。脚本在另一份数据库副本中把 user_version 标记设为2,再让恢复版本的 Store 打开它,预期被拒绝。
注意,这里只改了版本标记,没有真的改表结构;它是检查旧代码拒绝未知数据版本的教学情景,不是完整的数据库迁移实验。
真实升级若增加字段、改变状态含义,甚至改写已有记录,兼容性就不止一个版本号。回退前要确认旧代码还能解释新数据,配置是否改变,旧依赖是否仍可用,以及备份后新增记录怎么处理。
如果不兼容,可以考虑在新版本上修复问题,或者在明确恢复目标和新增数据处理办法后恢复备份。不要为了绕过拒绝,把版本号直接改回1;这样只是移除了提示,没有恢复数据含义。
配套回滚工作卡让你在操作前写清这些决定。它还是待填写模板,不是已经为你的真实项目完成备份和恢复的凭据。
让 Kimi 帮你审交付范围
作者核实了本机 Kimi Code 2.0.2 的帮助信息,但没有发起真实开发请求。若你已有可用服务,可以在外层工作区先运行 kimi --plan,让它先审材料;这会使用开发服务额度。官方命令文档将该选项描述为从计划模式开始,不应把它误认为所有操作都受绝对只读沙箱限制。
可以提供这段任务:
先读AGENTS.md、docs/release/TASK.md与docs/release/RELEASE_NOTES.md。
结合releases/local-v1的manifest和日志,审查这次本地候选:
1. 包内有哪些内容,哪些外层能力并未打包?
2. 哪些结论已有实际证据,哪些还没有?
3. 现有依赖、配置与数据库兼容范围是什么?
4. 回退代码时,怎样避免覆盖备份之后的新数据?
只返回发现、依据和建议补做的验证,暂不改文件、不执行发布,
不读取个人配置、Key或真实数据,不把候选写成已经上线。
如需额外命令,先解释其目的和作用目录。
当它指出一个缺口时,先判断缺口是否属于本次交付范围,再补相应证据。无需因为出现“发布”两个字就把个人学习项目扩成复杂部署平台;也不能因为项目小,就跳过包内容和数据兼容性检查。
如果以后要对外提供服务
本课完成的是本地交付与回滚准备。把源码发给另一个学习者,与让陌生用户访问一个持续运行的服务,要求不同。
对外服务还需要生产服务器配置、访问控制、密钥管理、HTTPS、资源和费用限制,以及可用的日志与恢复流程。现有 Flask 开发服务器不能直接承担生产服务;这一点在Flask 官方部署文档中有明确说明。
因此,不要通过修改监听地址就把本课的个人学习版称作“已经安全上线”。真正需要部署时,应单独确定目标环境和访问范围,再做那一层的工作。本篇没有替你做外部发布。
常见卡点与完成标准
| 现象 | 检查与处理 |
|---|---|
| 提示候选或演练目录已存在 | 换新名称,保留已有结果,不覆盖旧证据 |
| 包核对失败 | 对照文件清单、ZIP和展开目录;不手改指纹掩盖差异 |
| 演练故障阶段失败 | 两个指定边界失败是预期;其他错误需要单独排查 |
| 恢复后测试仍失败 | 确认实际使用哪个副本及其文件指纹,不能只看目录名字 |
| SDK或Flask导入失败 | 检查当前解释器和依赖;主路径标准库实验与扩展验收分别记录 |
| 数据版本不支持 | 停止强行启动,检查兼容方案,不直接修改版本号 |
本篇作者实际完成了候选构建、包核对、回滚及备份恢复副本检查,并验证了多余ZIP成员、展开文件改动、错误当前指向和新增数据丢失等反例会被拒绝。展开包也通过了上述离线回归检查。未进行真实模型调用、Windows实机验证、在线发布或真实数据库迁移。
结束前可以检查三个判断:源码包里没有数据库,是否意味着已备份数据?没有。恢复旧备份能否自动保留此后的新记录?不能。候选检查通过能否直接写成发布完成?仍要看是否实际交付、执行了哪些环境与业务检查。
现在你得到了一份可核对的本地源码候选,以及保留故障现场、切回代码和验证备份时间边界的演练记录。下一篇会把需求、修改、验证、恢复和交付合起来,完成第四部分最后一次可验收的项目迭代。
20 综合实践:让_Kimi_Code_完成一次可验收的项目迭代
打开前面导出的复习资料,第三张卡片写着“笔记未提供,无法判断”。这句话没有编造答案,是正确的边界处理,但读者接下来该做什么?
这一次,我们给资料不足的卡片增加一句明确的下一步提示。需求很小,却足够走完一次完整迭代:理解问题、写规格、建立基线、让 Kimi 修改、根据失败反馈修正、留下接手记录,最后把真正修改过的工具交付出来。
这也是第四部分的最后一篇。目标不是把前17篇的工具全部调用一遍,而是能够按任务需要选择方法,并说明为什么这次改动可以接受。
先确定改的究竟是什么
本次目标是导出的 Markdown 资料,而不是模型接口、数据库或网页。
对于 status=missing 的卡片,在状态之后、依据之前增加:
下一步:
补充与该问题直接相关的笔记,再重新生成。
原答案仍然是“笔记未提供,无法判断”;提示作为单独的呈现内容,不写进模型的 answer,不伪装成引用,也不自动触发新请求。supported 卡片不显示这段提示。
这个区分很重要。若把答案替换成建议,原来“资料不足时使用固定答案”的数据契约就被改掉了;若自动点击式重新生成,还会引入新的请求与费用。本轮只改善读者看见资料不足后的操作方向。
应用仍使用 DeepSeek 官方 API,Kimi Code 只是开发助手。作者提供的参考实现由作者编写并离线验证,没有冒称来自一次真实 Kimi 生成。
准备一个可以验收的起点
下载完整练习包,解压到新的目录。Python 3.11或更高版本即可,离线主路径不需要 SDK、Key或依赖安装。
在外层 lesson-18 中,你会看到:
workspace/ 本次允许修改的工作区
export_review.py 起始版本,尚无下一步提示
verify_export.py 已有导出验证器
project/ 前17篇的正确应用,38文件
docs/capstone/TASK.md 本轮需求与范围
reference/
export_review.py 作者参考答案,单个替换文件
change.diff 参考差异
verify_capstone.py 新的独立行为检查
package_capstone.py 本次工具专用打包器
docs/ 接手、交付和迁移模板
reference 不是可独立运行的项目。其导出脚本需要与 project 放在相应工作目录中,不能直接在 reference 下运行后,再把缺文件当成代码错误。
workspace 的业务项目沿用第17篇正确版本,外层导出器保留起始行为。我们刻意让初始交付不是“已经做完的答案”,这样你能先看到新需求尚未满足的证据。
第一步,在外层执行:
macOS/Linux:
python3 -B verify_capstone.py workspace
PowerShell:
py -3 -B verify_capstone.py workspace
预期是执行7项检查,其中2项失败、5项通过,退出码为1。末尾会出现:
Ran 7 tests in ...s
FAILED (failures=2)
耗时随机器变化。两项失败分别说明:资料不足卡片缺少提示;多张资料不足卡片也没有逐张展示提示。这是新增需求尚未实现,不是原来的导出能力突然坏了。
其余检查保留已有能力:有依据的卡片不加提示、正常CLI导出仍通过旧验证、坏引用仍被拒绝、旧输出不被覆盖、输入文本仍按普通文本呈现。检查直接使用 Python 的 unittest,失败会形成明确的断言与退出状态。官方测试文档解释了这一机制。
给 Kimi 的任务,比“优化一下”更具体
先读任务卡。它规定只修改 export_review.py,其余应用代码、验证器、题集、依赖和模型请求保持原样。
如果你已经有可用的 Kimi Code,在外层进入工作区:
cd workspace
kimi --version
kimi --plan
作者核实的本机版本为2.0.2。--plan 用于从计划模式开始,--continue 则可继续当前目录最近的会话;具体行为以当前官方命令参考和本机帮助为准。计划模式不替代路径和权限审查。
真实 Kimi 开发请求可能消耗服务额度。此次练习先限制为一次计划、最多两轮针对失败的修改反馈;没有证据支撑继续修改时就停下来记录问题。轮数是工作范围,不是金额硬上限,实际额度由你在服务端确认。
在会话中提供:
请先读AGENTS.md、docs/capstone/TASK.md、export_review.py和verify_export.py。
新增验收在父目录../verify_capstone.py;你可以读取它,但不能修改它。
目标:每张missing卡片增加独立的下一步提示,supported不增加。
只改export_review.py,不改模型答案、引用规则、project、依赖或数据库。
先解释改动位置、需要保留的行为和验证方法,不立即修改。
请指出当前资料是否足够;不要调用模型API、安装工具或读取私人配置。
本轮不需要多Agent、MCP或新的自动循环。
一份可接受的计划应该找到 render 的逐卡片循环,并说明按卡片状态判断,在呈现层增加段落;验证仍保留原来的导出流程。若计划要修改 validation.py 的固定答案规则,或者换一个“更聪明”的模型,它已经偏离本轮问题。
核对方案符合任务后,再明确让它实施,例如:“按这个方案完成最小修改,运行新增验证,报告实际差异和退出码。”你允许它修改的是有边界的工作项,不是在这里替它确认代码已经正确。
用失败反馈推动下一步,而不是继续猜
完成修改后,从工作区回到外层:
cd ..
python3 -B verify_capstone.py workspace
Windows使用 py -3 -B。预期变成7项通过,末尾为 OK,退出码为0。
如果仍有失败,不要只把整份日志交回去说“继续修”。先辨认它对应哪条要求,再提供一条可定位的反馈:
test_supported_cards_do_not_get_action失败。当前代码把提示加到了所有卡片,但任务只允许 missing。请检查分支条件,只改导出呈现,不修改测试或答案。
这是有证据的修复请求。相反,如果只是觉得措辞还不够热情,就要求它反复重写整个导出器,反而会扩大无关变化。
还要注意测试的边界。多张 missing 的测试数据由作者构造,用来验证逐张呈现,不是模型实测回答,更不能据此计算模型准确率。固定文字通过也不意味着“建议永远适合所有用户”;这里只验收本次明确的产品规则。
若两轮反馈以后还未定位,保留日志和差异,说明哪条要求未满足。停止条件应在开始前确定,不能因为快到结尾,就降低断言来获得一个绿色结果。
查看差异,再看真正的导出结果
参考实现只在卡片循环中增加两行:
if card['status'] == 'missing':
lines += ['下一步:', '', literal('补充与该问题直接相关的笔记,再重新生成。'), '']
这里先依据已经存在的 status 决定是否显示,再复用 literal 保持普通文本呈现。它没有改答案、证据或模型调用。
实际由 Kimi 完成的代码不必逐字符等于参考实现,但你应能解释每一处差异,并检查它没有越过任务范围。如果已经使用Git,用第3篇的方法检查自己的修改;也可以直接在编辑器中比较工作文件和参考差异。不要拿“修改行数少”代替行为验证。
现在进入 workspace,使用新的输出名:
cd workspace
python3 -B export_review.py --fixture valid --out capstone-review-01
python3 -B verify_export.py capstone-review-01
导出应报告3张卡片,原验证器应报告 pass,两者的语义状态仍是 pending。打开 capstone-review-01/review.md,逐项检查:
- Q1、Q2仍然展示原答案与引文,不出现新的提示段落。
- Q3保留固定的资料不足答案,单独展示“下一步”,然后仍写“依据:无;资料不足”。
- 文件继续标明作者构造样例和待人工语义核对。
如果你发现提示出现在所有卡片上,即使它看起来很友好,也不符合本次规格。如果固定答案被建议替换了,也应退回修正。
已有输出名会被拒绝覆盖。下一次用 capstone-review-02,保留旧文件用于比较;不要把清理证据当成修复的一部分。
没有 Kimi,也能验证完整参考路径
不想调用开发服务的读者可以先走离线对照。回到外层 lesson-18,使用一个不存在的新目录名:
python3 -c "import shutil; shutil.copytree('workspace', 'offline-workspace'); shutil.copyfile('reference/export_review.py', 'offline-workspace/export_review.py')"
python3 -B verify_capstone.py offline-workspace
python3 -B offline-workspace/export_review.py --fixture valid --out offline-review-01
python3 -B offline-workspace/verify_export.py offline-review-01
PowerShell同样将 python3 换成 py -3。第一条命令复制工作区并替换副本中的一个文件;copytree 会拒绝覆盖已有目录。原 workspace 保留,不会因为参考版通过就自动变成完成状态。
这条路线让你检查作者方案,不能将其记录成“我已经让 Kimi 完成了开发”。如果之后练习真实 Agent,仍要记录它实际改了什么和实际执行的验证。
有意留一次接手,而不是把任务重做一遍
为了练习第15篇的方法,可以在“代码已经修改、独立验证尚未运行”的时点暂停。先确认当前没有未结束的命令或写入;不必杀进程来模拟事故。
复制并填写接手模板,保存为工作区的 docs/capstone/HANDOFF.md。写清实际目录、变更文件、已完成和未完成的检查、输出位置及已使用的反馈轮数。没有执行的测试就写“未执行”,不要猜结果。
恢复同一个目录的最近会话可以使用 kimi --continue;也可以在新计划会话中提供以下要求:
先读docs/capstone/HANDOFF.md和当前TASK。
核对工作区实际代码与交接描述,不要直接按摘要重新实现。
如果修改已存在但尚未验证,先运行父目录的verify_capstone检查。
不要覆盖旧导出,不重置已使用的反馈轮数。
发现事实与交接不一致时,说明差异和最小核对动作。
这次是控制好的人工暂停,不是自动崩溃恢复系统。换会话以后还要接着核实文件,因为聊天历史本身不能证明磁盘上的状态。
本轮出现测试失败时,也用同一张工作卡记录:哪条要求失败、什么修改已经尝试、目前证据在哪里。接手的价值,是让下一次行动更小、更明确,而不是再消耗一轮从头探索。
最后交付哪个包:这里容易选错
第17篇的应用源码包包含38个 project 文件,却不含外层的 export_review.py。如果仍然只交付那个包,本次修改根本没有交出去。
因此,本课提供专用打包器:包含应用输入38文件,加上 export_review.py 和 verify_export.py,共40文件。用途明确为离线固定样例导出工具,不把它称作完整的新网页功能。
回到外层执行:
cd ..
python3 -B package_capstone.py --target workspace --out delivery-01
这里的 cd .. 适用于你仍停留在前面的 workspace 时;已经在外层就不要再执行它。离线参考路线改用 --target offline-workspace --out offline-delivery-01。
正常结果应为:
{"status": "candidate", "files": 40, "verification_exit": 0, "published": false}
打包器会将实际 ZIP 展开到临时目录,对展开内容重新运行同样的7项检查。它还核对源文件在打包期间没有最终变化,并保存 manifest.json 和检查日志。起始未实现版本会得到 blocked,不能当作合格交付。
打开新交付目录,应有 review-tool.zip、manifest.json、checks.stdout.log 和 checks.stderr.log。查看 ZIP 的成员,确认实际包含修改后的导出器;查看日志,确认7项通过,而不是只看到打包程序返回了0。
收件人解压后在 review-tool 中,可以运行 python3 -B export_review.py --fixture valid --out review-01 生成固定样例。应用配置、真实数据与凭据都不属于这个包;这仍不是备份,也没有被上传或发布。
最后填写交付说明:问题、差异、验证命令及日志、手工观察、未验证范围、包位置、恢复方法。这个呈现变更可以通过恢复原导出器撤销;不涉及数据库回退。已经生成的文件分别保留,不因撤回代码就假装旧产物不存在。
哪些方法用上了,哪些暂时不需要
这次任务里,规格决定了显示条件,上下文限定了阅读文件,独立测试提供反馈,差异审查控制修改范围,接手记录支持换会话,包核验确认真正交出去的内容。所有方法都对应一个实际问题。
我们没有为了综合实践强行增加多 Agent:两行呈现修改通常不值得额外分工;没有引入 MCP,因为本地文件足够;没有自动不断重试,因为预算和停止条件已经清楚。这些工具仍然有用,只是要在需求出现时使用。
把方法迁移到自己的项目,可以从最小迭代卡开始。尝试将“让我这个项目更好用”缩小成一个可观察变化,写出正例、反例和不能改变的行为,再决定需要哪些上下文和验证。
完成前的最后检查
| 问题 | 合格的回答 |
|---|---|
| 初始失败是否有解释? | 两个新增呈现要求未满足,原有检查继续通过 |
| 谁修改了什么? | 据实写Kimi或自己;业务改动只在导出器 |
| 是否通过独立验证? | 有实际7项结果,未修改断言求绿 |
| 用户能看见什么变化? | missing卡片有下一步,有依据卡片无此提示 |
| 中断后如何接手? | 有事实记录,先查差异和未完成验证,不盲目重做 |
| 包里是否包含新功能? | 40文件专用包包含修改后的导出器,展开后再次验证 |
| 哪些结论还不能声称? | 未证明真实模型准确率、未做线上发布,语义审核仍有范围 |
作者实际验证了:起始7项中2失败;参考版本7项通过;CLI导出仍通过旧验证;错误地给所有卡片加提示、改写missing答案的变体会被拒绝;未实现版本打包被阻塞,参考包展开后通过。应用38文件保持不变。上述均为本地离线结果,没有真实 Kimi/DeepSeek 调用或Windows实机测试。
第四部分到这里结束。你已经能把一个需求推进到可检查的修改、可解释的证据和可交付的结果,也能在失败或中断时说明下一步该做什么。以后更换开发助手时,这套工作方法仍然可以保留;工具负责协助完成步骤,是否满足需求则由具体事实来判断。
21 进阶学习
这套大模型应用工程化承接前面的 Python 笔记助手,用 Kimi Code 帮助项目从能运行,走向可验证、可恢复和可持续迭代。
18 篇内容放在一起,其实是一条很完整的软件交付链路。
前4篇先处理任务与开发基础。
从 Vibe Coding 走向可验证工作,把模糊需求改写成输入、输出、边界和验收标准。然后用 Git、 diff、提交、分支与检查点,让每次修改都可以查看、比较和撤回。任务继续被拆成可验证小步,页面、业务、模型与存储之间也有了明确边界。
中间6篇建立信息与反馈系统。
项目约定、文档和上下文工程,让 Agent 每次看到正确的信息,而不是把所有资料一股脑塞进去。测试、调试与代码审查要求根据证据修复。AI 应用评测又把代码正确和回答可靠分开,通过固定题集检查检索、引用、语义与资料不足。日志、Trace 与成本则帮助定位一次失败究竟发生在哪一步。
后面6篇进入 Agent 运行环境。
工具与权限边界区分读取、写入、执行、敏感资料和外部副作用。Skills、脚本与 MCP 分别处理工作说明、确定性操作和外部连接。最小 Harness 把需求、上下文、工具、检查、状态与边界组合起来。 有限 Loop 定义完成条件、预算、停机规则和人工入口。长任务通过交接记录支持断点续做,多 Agent 则处理有限并行、文件归属、交付接口与集成成本。
最后2篇回到交付。
发布前检查依赖和数据兼容,准备版本与回滚,再通过一次完整项目迭代,把需求、修改、验证、中断恢复和迁移模板全部串起来。
这套知识库里有4个判断特别值得留下。
一个任务完成,不是 Agent 说完成了,而是有机器或人工可以核对的证据。
上下文工程不是提供越多信息越好,而是让正确、最新、与当前任务有关的信息进入正确步骤。
自动化不是放开权限让 Agent 自己跑,而是为读取、写入、执行和外部副作用设置不同边界。
Loop 也不是无限重试,它需要完成条件、预算、停机规则和人工升级入口。
这四个判断,已经把大模型应用从原型带到了工程系统门口。
但知识库里的贯穿项目毕竟是个人笔记助手,适合把方法走完整。进入企业 Agent 场景后,同样的方法会面对更大的规模和更高的风险。
工具不再只有少量本地操作,而可能连接搜索、代码仓库、数据库、工单、内部知识系统与外部服务。每个工具都需要注册、Schema、权限、审计、超时、重试和降级。任务也不再只是一次项目迭代,而可能持续几十分钟甚至更久。状态怎样保存,Checkpoint 怎样恢复,长上下文怎样压缩,工具结果怎样整理,异常怎样升级,都需要专门设计。
评测也会从固定题集扩展到执行轨迹。除了最终答案,还要判断规划是否合理、工具选择是否正确、 失败是否恢复、多 Agent 是否真的带来收益。到了生产环境,还会增加模型网关、版本、监控、回放、灰度、回滚、成本与多租户等问题。
知识库已经搭好了骨架。下一步,是让这副骨架承受更复杂的任务和更真实的生产压力。
这也是为什么想在这里推荐「AI Agent 全栈工程师训练营」。大模型应用工程化已经讲清楚需求、上下文、测试、Trace、权限、Harness、Loop、长任务、多 Agent 和交付,这门课与它的关系,不是重复概念,而是把这些工程方法放进六个更完整的 Agent 项目。
课程用 14 周覆盖 LLM 与 Prompt、RAG、Agent Loop、Memory、Skill、Context Engineering、 Agent Eval、Tracing、MCP 与生产部署。
Agent 工具调用基础设施项目会搭建 ToolRegistry、ToolSchema、JSON Schema 或 Pydantic 校验、权限分级、审计、超时、重试、降级、MCP Client 与 Trace。Codebase Agent 项目会实现 Planning、文件搜索与修改、测试执行、日志读取、自动修复、人工确认、沙箱、状态管理与 Checkpoint。
Codebase RAG 项目会加入代码索引、关键词与向量混合检索、Rerank、项目级记忆、长文件压缩、 任务摘要、恢复和引用追溯。Multi-Agent 与 Eval 项目会继续处理多角色协作、Skill 注册、 Benchmark、LLM-as-Judge、人工评测、Trace Analysis 和回归测试。
生产运维项目则加入模型网关、Redis、PostgreSQL、VectorDB、监控、回放、版本、灰度、回滚与成本治理。收官项目基于 DeerFlow 搭建深度研究平台和新一代软件工厂。
知识库教你把一个项目做稳,课程继续训练怎样把一套 Agent 系统做完整。
扫码添加学习顾问,领取完整大纲和试听资料
