OpenMAIC PBL v2 结课报告生成器:evaluator-final 提示词的结构化输出契约与工程实现
【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC
本篇技术指南以 OpenMAIC 仓库中 PBL v2 教学引擎的最终评估系统提示词 evaluator-final.md 为骨架,剖析「整项目完结报告(Completion Report)」是如何由 LLM 生成、解析并持久化的:从严格 JSON 输出契约、四类字段的取值规范,到stars星级校准刻度、what_you_built/what_you_learned的反幻觉写作规则,再到与之配套的 SSE 流式评估 Agent、JSON Tail 解析器和场景化(role-play)变体。读完你将掌握 PBL v2 最终评估的完整链路,并能在自己的多智能体教学或评估系统中复用这套「叙事 + 结构化尾巴」的提示词工程模式。
背景:PBL v2 的三层评估体系与结课报告的位置
在进入提示词本身之前,先厘清它的上下文。OpenMAIC 的 PBL v2(Project-Based Learning,项目式学习)引擎中,Evaluator Agent 有三种评估模式,共享同一套流式模式(见 agents/evaluator.ts 顶部注释):
| 模式 | 触发时机 | 产出 | 驱动 UI |
|---|---|---|---|
runTaskEvaluation | 一个微任务(microtask)带提交完成后 | 短反馈 +{strengths, improvements, score?}JSON 尾巴 | 微任务级反馈 |
runMilestoneEvaluation | 一个里程碑(milestone)的最后一个微任务推进后 | 反思卡片叙事 +{learned, performance, stars}JSON 尾巴 | MilestoneCard 反思卡片 |
runFinalEvaluation | 最后一个里程碑完成后 | 短引言叙事 +{stars, what_you_built, what_you_learned, whats_next}JSON 尾巴 | Completion 结课页 Hero 区 |
本文的主角evaluator-final.md正是runFinalEvaluation的系统提示词。它专门负责为刚完成整个 PBL 项目的学习者撰写结课报告。
为什么需要一个独立 Agent,而不是在 Instructor 上加一个工具?源码注释给出了两条理由:其一,系统提示词基调不同——评估者走「反思 / 报告」语气,与教学语气是冲突的,共用一套提示词会自我打架;其二,输出契约不同——评估者是「叙事 + JSON 尾巴」的固定结构,而 Instructor 是带工具调用的对话式回复。此外,独立的 SSE 调用让前端可以把「导师正在生成阶段反馈…」渲染为独立阶段,而不是神秘的多余 Instructor 回合。
第一要义:这是「页面」而非「聊天气泡」
提示词开头就划定了渲染约束:
This report is rendered as a dedicated page —NOTa chat bubble — so the structured bullets ARE the main content. Your narrative paragraph is just a short, warm intro at the top of that page.
这决定了整份提示词的写作策略:结构化列表才是主体内容,叙事段落只是页面顶部一段简短、温暖的开场白。因此字段规则中反复强调「不要重复列表内容」「保持叙事紧凑」。
在源码层面,这与评估 Agent 的流式策略完全对应。runShared中设定了shouldStreamTokens = false——评估输出是 JSON-only 的,且只有在结构化载荷持久化后才会渲染,因此原始 JSON 不会流式进入聊天(见 agents/evaluator.ts)。也就是说:结课报告的文本流在「确认 JSON 尾巴可解析」之前是不对学习者暴露的,保证学习者永远不会看到一段未成型的 JSON 或半截叙事。
输出契约:唯一的、严格形状的 JSON 对象
提示词规定输出的硬性形状:
{"feedback": "...", "stars": 4.5, "what_you_built": ["...", "...", "..."], "what_you_learned": ["...", "...", "..."], "whats_next": "..."}附带三条输出格式铁律:
- 只输出一个合法的 JSON 对象,JSON 之外不得有任何散文;
- 不要用 markdown 包裹,不要用
```json代码围栏; - 字段名严格如上,不可改名、不可增删。
当然,LLM 在实际推理中并不总是遵守「裸 JSON」的要求。这正是工程侧要兜底的地方——仓库中 eval-tail-parser.ts 的存在意义。它专门处理三类业界常见的「不听话」输出:
- LLM 输出围栏包裹的 JSON、裸 JSON、或「散文 + JSON」混合 → 复用 OpenMAIC 共享的 JSON 修复解析器
parseJsonResponse,通过parseEvaluationTail从多个候选(全文、任意围栏内容、尾部平衡花括号段)中自后向前找到最后一个可解析对象; - 围栏内 JSON 本身畸形 → 同样走共享修复逻辑,只有完全无法恢复对象时才返回
null; - 字段值类型不合法(见下节)→ 通过各
normalize*函数钳制与归一。
而且这是有历史教训的:注释明确写道,这是 v1 仓库(Python 版)评估器踩过的坑——同样的三类失败模式当时迭代修复了很久,PBL v2 一次性全部编码进解析器里。
四个字段的取值规则
feedback:2~3 句的开场叙事
提示词给出的结构是:
- 一句话点出学习者具体做了什么(用学习者自己的话描述的项目标题 + 一句话概括它做什么);
- 一到两句突出一个具体高光时刻(某个恢复过来的报错、某个突然想通的概念、某个提速的阶段)——必须从下方 engagement rollup 取材,不得编造细节;
- 若「Integrative checks (stage synthesis)」部分含有学习者的作答,则优先以此为高光:明确表扬学习者如何跨阶段/跨项目连接了概念,并以记录的问答为根据,简短引用或转述。若没有记录作答,绝不虚构。
同时明确禁止:标题、列表符号、长弧线叙事("from beginning to end...")、结尾号召。
stars:0-5 半星刻度的星级校准
stars是页面以星星图标渲染的视觉评分(不是 "/5" 分母),步进为 0.5。提示词给出了与里程碑卡片一致的校准刻度:
| 星级 | 含义 |
|---|---|
| 5.0 | 自信流畅完成 |
| 4.5 | 大体顺利,有一两个小磕绊 |
| 4.0 | 扎实,遇到预期内的障碍并干净利落地恢复(不确定时的默认值) |
| 3.5 | 明显挣扎,但在提示下最终达成 |
| 3.0 | 大量来回反复 |
| < 3.0 | 出现多个未解决的错误时 |
工程侧的normalizeStars(eval-tail-parser.ts)对这个字段做了完整防御:接受干净数字(4.5、3)、数字字符串("4")、"4.5/5"或"4.5 / 5"形式的分数串(取分子);越界值钳制到[0, 5];NaN/Infinity/ 非数字字符串(如"good"、"4 stars")/null/ 对象 / 数组一律拒绝返回null;最终Math.round(clamped * 2) / 2保证半星步进。解析失败时 UI 干净地隐藏评分,而不是渲染一个错误值。
what_you_built:3-5 条具体成果(名词短语)
要求学习者能一眼认出的名词短语:
- ✓ "一个能猜数字的命令行小游戏"
- ✓ "用户输入名字后会个性化打招呼"
- ✗ "Working Python script"(太抽象)
- ✗ "main.py"(只有文件名)
第一条必须是整个项目,其余为关键功能/能力。工程侧normalizeStringList(eval-tail-parser.ts)过滤非字符串、去空、剔除模板占位符并截断到最多 6 条(persistEvaluation传入6),防止失控的 LLM 撑爆存储。
what_you_learned:3-5 条学习者自己的话——最容易造假、被重点防范的字段
提示词用相当篇幅(原文最大的规则块)强调这是最常被垃圾内容伪造的字段:
- ✗禁止:任何像函数名、snake_case 标签、内部签名的东西。举例:
python_install_verified、if_elif_else_number_comparison、while_break_loop——这些是内部埋点标签,绝不允许出现在这里; - ✗禁止:学习者自己没使用过的行话("Conditional control flow"、"Loop invariants"、"Variable scoping");
- ✓ 允许:"用 if/else 让程序根据输入做出不同反应";
- ✓ 允许:"看到红色报错不再慌张,会逐行读错误信息找出问题"。
具体操作指令是:把 engagement rollup 里concepts_unlocked的内部签名翻译成学习者语言的自然句子,禁止原样粘贴签名。此外,若存在整合性阶段检查(integrative stage-check)的作答,至少一条what_you_learned应承认学习者做出的跨阶段连接。
whats_next:1-2 句、指向具体下一步
禁止泛泛而谈("keep learning!"),要基于刚做完的东西推荐具体的下一个项目或扩展方向。
证据从哪来:user 侧提示词的真实数据装配
系统提示词负责定规则,而user侧则由buildFinalEvalPrompt组装证据(见 eval-prompts.ts)。它拼接了四个数据块:
- 项目信息:
project.title+project.description; - 各里程碑反思卡片(
Per-milestone reflection cards):取每个 milestone 最新的kind === 'milestone'评估,带上strengths(截前 4 条)、stars、以及截断到 280 字符的反馈散文——注释强调要用叙事而非仅 strengths 列表,因为「叙事捕捉了我们希望结课卡片回映的人性化时刻」; - engagement rollup 分析汇总(
formatProjectEngagementRollup):聚合每个里程碑的时长、学习者轮次、错误数(含重复错误)、完成微任务数、closing-check 质量直方图(weak/ok/strong)、去重后的概念集合等,让 LLM 有结构化事实依据,而不是默认生成泛泛的 "great work"; - 整合性检查(
formatProjectSynthesisChecks):从stage_synthesis_check事件(或回退到收尾微任务的 closing_check / 缓存 engagement)中取出核心概念、问题、学习者作答与质量标记,明确标注数据来源。
这正回应了提示词中「从 engagement rollup 取材、不编造细节」的要求——系统把真实遥测以结构化文本喂给模型,模型只能引用已有证据。
从 LLM 输出到持久化:一次「部分成功优于整体失败」的容错设计
评估完成后的落库逻辑在 agents/evaluator.ts 的persistEvaluation中:
- 先用
parseEvaluationTail解析 JSON 尾巴,解析失败是非致命的:仍持久化散文反馈(学习者能看到 LLM 说了什么),只是缺少结构化字段不渲染。注释明言:"partial success beats throwing the whole evaluation away on a malformed JSON tail"; kind === 'final'分支将tail.what_you_built→whatYouBuilt、tail.what_you_learned→whatYouLearned、tail.whats_next→whatsNext,通过normalizeStringList/normalizeOptionalString归一,然后调用addEvaluation写入project.evaluations并追加evaluation_created运行时事件(evaluation.ts);- 类型层面,types.ts 中
PBLEvaluation的whatYouBuilt/whatYouLearned/whatsNext被标注为final-evaluation-only字段,task / milestone 评估保持空值,前端以kind === 'final'为键来决定是否渲染; - 整个评估期间 Evaluator不调用任何工具,仅在最后、JSON 尾巴解析成功后追加一次
project.evaluations——这让流式层保持简单,且解析失败时项目保持原封不动。
值得注意的一点:任务评估(task)使用score(0-100 整数),而里程碑与最终评估使用stars(0-5 半星)。提示词中明确禁止在结课报告里出现"score"/"/100"字段,也禁止使用 task 评估的strengths/improvements形状——三种评估形态刻意不做统一,而是让 UI 依据kind分支渲染。
场景化变体:evaluator-final-scenario.md 与 act_goals
当项目带有scenario(角色扮演/模拟场景)配置时,buildFinalEvalPrompt会切换系统提示词到 evaluator-final-scenario.md,并装配完全不同的证据:场景前提、角色扮演逐字记录(formatScenarioTranscript,保留尾部 6000 字符预算)、以及每幕(act)的目标清单(scenarioActGoalsScaffold)。
这是「技能练习」而非「知识构建」的评估:禁止谈论 concepts/code/artefacts,改为评判对话处理质量,输出契约多出一个act_goals数组。对齐是严格且基于索引的:
- 幕与幕之间按
milestoneId对齐,幕内每个结论按goalIndex对齐(而非数组位置),防止模型重排同幕目标导致结论错挂; normalizeActGoals(completion-stats.ts)要求模型返回的 goals 必须构成[0, N)的完美双射:每个索引恰好出现一次、在范围内、状态合法(achieved/partial/missed);- 任何一处不合规(缺幕、目标数不对、越界/重复/缺失 goalIndex、非法状态)都返回
undefined,宁可让结课页回退到叙事 + 只读目标列表,也绝不展示错标或虚构的记分卡; - 目标文本、技能标签、幕标题永远来自项目数据,LLM 只贡献
status和note,从机制上杜绝它改写或捏造目标。
这一设计是「LLM 输出永远可以被平台严格校验」的极佳范例,普通项目与场景项目的最终评估在提示词、证据、输出契约三层完全隔离,互不污染。
结课报告提示词的工程要点回顾
回到evaluator-final.md本身,这套提示词之所以值得复用,在于它把「写作质量」与「工程可控性」做了清晰分工:
- 叙事与结构化解耦:页面级渲染让列表成为主体,叙事限定 2-3 句,既保证页面信息密度,又避免 LLM 长篇大论稀释重点;
- 反幻觉显式化:「从 rollup 取材」「禁止内部签名」「禁止学习者没说过的话」「没记录就不虚构」全部写成显式规则,并在 user 侧用
formatProjectEngagementRollup/formatProjectSynthesisChecks提供真实证据锚点; - 数值规范提前编码:
stars的 0.5 步进校准、范围钳制、非法值拒绝,在提示词与解析器normalizeStars中双重定义,保证 UI 永远拿到干净值; - 失败降级而非硬失败:JSON 尾巴解析失败时保留散文,学习者不会面对空白页面;
- 规则与代码分离:提示词以 Markdown 文件存放(lib/pbl/v2/prompts/ 目录),由 prompts/loader.ts 读取并做
{{language}}变量插值,改提示词文案无需触碰 TypeScript,人类审阅也只需通读一个文件。
如果你正在为自己的 AI 教学系统设计「项目完结报告」或「阶段反思卡片」功能,直接借鉴这套「系统规则 Markdown + user 证据装配 + JSON 尾巴解析 + 索引对齐校验」的组合,就能同时获得高质量文本与可验证的结构化数据。
【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考