课程实施大纲的第五节是学习资源:教材、参考书目、平台网址。第六节是考核方式与比例。
这两节交给模型写,它能写得毫无破绽:书名像书名,出版社像出版社,网址格式也对,一查,查无此物。考核比例更要命,那是教务要对的账,要拿来定分数。
这是我做智能备课文档生成系统时认清的一件事:模型最危险的输出不是明显的错,而是看起来合理的编造。 明显的错一眼就能挑出来,编得像样的东西会一路混进要交上去的材料里。
在另一个系统教师个人中台里接模型起草会议纪要,也踩过同类的坑。两个系统做下来,攒了 6 条规矩。
规矩一:能核实的事实,照抄,不经模型
大纲第五节、第六节现在只允许从课程标准原样搬运,一个字不改。考核方式那一节最初就是 AI 写的,后来改成照抄:课程标准里那张评价表本身就是答案,连合并单元格一起搬。
课程标准里没有的,只写一行「课程标准中未提供,请教师补充」。宁可留个空让老师补,也不让模型生成一条。 编一个像模像样的比例,比留空更糟。
改完后那轮真实验收,导出的考核表和课程标准里那张逐格一致,四个比例 10/10/30/50 都是原值。
课程目标代码、能力指标代码也归这一条:只能来自已解析的课程标准和人才培养方案,对不上就中止,不降级,不「尽力而为」。
规矩二:别让模型算日期
中台的会议纪要是录音转写后由模型起草的,我确认之后才入库。
问题出在日期上。模型不知道会是哪天开的,看到转写里的「下周三」,会凭空编一个日期,实测编到了前一年。
现在提示词里写死了这条,大意是:
只有转写中出现明确日期时才填写 YYYY-MM-DD;「下周三」「明天」这类相对表述一律返回 null,不要推算。
留空让人填,好过给一个看起来精确的错日期。
规矩三:把输出的形状说死,再配一个 smoke 脚本
纪要的返回结果要过 schema 校验,而且是 strict 模式:少一个键、多一个键、类型不对,整份丢弃。
实测 DeepSeek 会把 discussionPoints(讨论要点)写成 {topic, details} 这样的对象数组,而 schema 要的是字符串数组。内容可能完全正确,整份照样被丢,报错只有一句 MINUTES_INVALID_RESPONSE,光看它很难直接定位原因。
所以提示词里光报字段名不够,每个字段的形状都要说死:字符串还是数组,数组里装字符串还是对象,能不能为空。再配一个专门的 smoke 脚本真调一次模型,让这类问题暴露在配置阶段,而不是上线之后。
规矩四:模型负责的格子,导出前逐项验收
学校的教案模板里,需要 AI 生成的格子用深红色标出来,一共 212 处。这个颜色就是模板和系统之间的契约:导出后回头逐格比对,还保持模板原文的红字格就是漏填,直接中止导出。这道闸门抓住过 60 处真实漏填。成品本身不带红色,写入时把颜色剥掉。
大纲模板用同一套契约,锚点却不一样。大纲模板是整行标红的,「通过本课程学习,同学们能够:」这种固定引导语也是红字,拿颜色当锚点会误判。所以大纲改用占位符文字「【AI 生成…】」认位置,导出前还剩一处就中止,并报出是哪几节。两种锚点在各自的模板里都有效,但不能互换。
这类闸门防的都是「看起来完整的半成品」。有一回拿另一门课的成品大纲当模板导出,文件里混进了那门课的简介和考核方式,学时写的是人家的 32(本课 80),光看版面未必看得出来。
规矩五:长调用拆开,结果存库
大纲的学习进程是一次模型调用,实测 60 到 160 秒;大纲正文是另一次调用,约 30 秒;客户端超时设在 300 秒。两次串在一个请求里,模型稍慢一点就压到天花板上,整个请求作废。
现在生成大纲时不顺带生成正文,正文等到导出时按需生成,生成完存库复用。第一次导出多等约 30 秒,之后秒出,而且每次导出的正文一模一样,老师为了调排版反复导出,正文不会导一次变一次。
顺带一条:超时最早设的是 60 秒,超时了报的却是「模型未返回有效结构化内容」,看上去像模型的错。报错后来按原因拆开,超时就说超时。
规矩六:进度要读实时状态
有一次老师点了生成整门课教案,界面五到十分钟一直停在「0/20 次课已生成 · 生成中」,以为系统死了。其实一直在正常生成,查库时已经有 18 份落库。
根因是进度接口读的「已成功条数」字段,要等整批跑完才写一次。于是整个过程都显示 0/20,最后一瞬跳到 20/20,和卡死看起来一模一样。
改法是让进度接口现场数每一条的状态,不多一次查询;整批结束时写最终值的逻辑保留不动。实时计数和最终持久化是两件事,可以并存。
这条看着和「编造」无关,其实是一回事:界面上显示的数字也得是真的。
把规矩写进 CLAUDE.md,连原因一起写
这些规矩最后都落进了项目的 CLAUDE.md。备课系统那份写得很直白:它比直接问 AI 要一份教案强的地方,全在这几条硬约束上;违反任何一条的改动都是错的,即使测试通过、即使被要求「先跑通再说」,因为这是系统存在的理由。
原因一定要一起写。这些约束在代码里看不出是刻意的,很容易被后来的改动当成「多余的麻烦」删掉。和跨会话记忆那篇一个思路:别指望谁记得,落到文件上。
如果你也在把大模型接进自己的系统,可以把下面这段放进项目的 CLAUDE.md 或 AGENTS.md,按业务改:
本项目接入大模型时遵守以下规则,任何改动不得绕过,即使被要求「先跑通再说」:
- 可被核实的事实(书名、作者、网址、比例、编号代码)只从用户提供的原始材料照抄,不经模型生成;材料里没有的,写「原材料中未提供,请补充」。
- 不让模型推算日期:原文有明确日期才填,相对表述一律留空。
- 模型输出格式写清每个字段的类型和嵌套形状,保留一个真实调用的 smoke 脚本,换模型先跑它。
- 模型负责的位置要有明确标记,导出前逐项检查,有未填项就中止并报出位置。
- 耗时长的模型调用不和其他调用串在同一个请求里,结果存库复用。
- 进度显示读实时状态,不读批次结束后才写入的字段。
每条规则的原因写进代码注释,删改前先读注释。
具体怎么落地,可以翻开源仓库 teaching-design-system-open;只想先看看界面,可以逛静态演示,账号 admin / 000000。
小结
| 规矩 | 防的是什么 | 做法 |
|---|---|---|
| 事实照抄 | 编得像样的书名、比例 | 只从原始材料搬,没有就写「请补充」 |
| 日期留空 | 凭空推算的日期 | 只填明确日期,相对表述返回 null |
| 形状说死 | 内容对、格式错,整份被丢 | 写清字段形状,配 smoke 脚本 |
| 导出闸门 | 看起来完整的半成品 | 标出 AI 负责的位置,漏一处就中止 |
| 长调用拆分 | 串行撞上超时 | 按需生成,存库复用 |
| 进度读实时 | 明明在跑,却像卡死 | 现场数状态,最终值照样落库 |
模型能写的东西越来越多,但能被核实的,就别让它生成;它负责的,就逐项验收,宁可中止,也不交半成品。