用 Gemini 和 WebAuthn 堵住代课漏洞:课堂诚信系统的两种实战设计

AI Summary (BLUF)
本文详细介绍了如何利用Google Cloud和Gemini模型构建两种课堂诚信系统:游戏化Bingo主动出勤验证和零信任考试锁定模式。通过四种验证方式(包括二维码出勤)和防作弊技术,有效解决虚假视频播放、代签和静态屏幕诱饵等问题。
核心洞察
这篇文章解决的是一个很多老师都遇到过但没认真想过的问题:学生把账号密码给同学,让同学帮忙在另一台电脑上播放录好的敲代码视频,屏幕共享看起来一切正常。作者用WebAuthnWeb认证标准,支持生物识别(Face ID/指纹)进行强身份验证。硬件绑定加动态二维码把这条路堵死了。我比较好奇的是那套“两振出局”的非惩罚机制在实际课堂里跑起来会不会有学生钻空子,不过思路本身确实比单纯截屏监控聪明得多。
核心结论
该系统通过 WebAuthn 硬件绑定(一手机对应一学生)和每 10 秒用 HMAC 时间签名动态轮换的投影仪二维码,堵住了"把账号密码给同学+播放预录视频"的代课漏洞和"刷学生证就跑"的逃课漏洞。
课堂 Bingo 支持 4 种验证类型:预定义题库、教师广播画面+字幕 AI 融合、学生实验屏幕检查、WebAuthn 二维码考勤;其中教师广播画面模式对全班 50 名学生仅需一次多模态 Gemini 调用,总成本约 0.00015 美元。
为消除大模型出四选一题时正确答案偏向索引 0 或 1 的统计偏好,系统对每道生成的题目执行确定性 Fisher-Yates一种随机洗牌算法,用于消除选项位置偏差。 置换(
shuffleOptionsAndCorrectIndex),打乱选项顺序并同步更新correctIndex。正确答案
correctIndex仅写入学生在firestore.rules中不可读的classes/{classId}/bingoRecords/{bingoId}路径,推送到学生端的载荷只含question、options和expiresAtMillis,防止学生通过 Chrome DevTools 偷看答案。两振出局状态机将学术理解力与物理在场严格分离:答错(
failed_incorrect)不记振、不扣出勤分钟,只有超时才记振,避免惩罚认真到场但答错的学生。
1. 问题:假视频播放、代课同学和静态屏幕诱饵
在3小时的编程实验课或者上百人的混合课堂上,光靠屏幕共享遥测和传统的门口刷卡签到,有三个经典盲区:
静态屏幕诱饵(挂机)。学生打开VS Code或者课件,开始屏幕共享,然后人走了45分钟,屏幕截图还在不停上传一模一样的画面。
“把密码给同学+播放预录视频”的代课漏洞。更狠的是,一个缺席的学生把账号密码给了坐在实验室里的朋友。那个朋友在旁边一台电脑上登录缺席学生的账号,开始屏幕共享,然后全屏播放一段预先录好的敲代码视频,屏幕采集的画面看起来就像有人在写代码。这时候如果标准网页版的Bingo测验在第二块屏幕上弹出来,这位同学还试图替朋友点答案。
“刷学生证就跑”的逃课漏洞(为什么点名必须随时可验证)。现在很多高校的讲堂门口都装了RFID读卡器或者自助终端,学生早上9点拿实体学生证一刷,或者输个签到码,校园系统就记成“已出席”,然后不想上课的学生扭头就去食堂、闲逛或者直接离校。3小时的课只在开头刷一次卡,这个设计本身就很脆弱。老师需要能在上课期间随时点名,比如讲到一半突击点、或者快下课时点。
反过来,在正式的监考测验或考试中,全屏监控采集、摄像头和麦克风审查、以及防篡改的证据留存必须严格执行。
要同时把这两种模式做好,得解决三个操作层面的难题:
日常教学 vs 高风险监考。普通讲课和实验课上,老师想要的是轻量、互动的理解检查,让学生保持学习投入;正式考试时,监考人员需要严格的全屏采集(displaySurface === 'monitor')、持续的摄像头和音频审查、以及一次写入的云存储锁定。
区分诚实的学习错误和缺勤。如果老师在课上发个随堂测验,学生选错答案就扣考勤分,那学生明明认真坐在座位上,只是对新学的技术内容还没掌握,却因此被惩罚,这说不过去。
消除空转轮询的账单。跑一个Cloud Scheduler定时任务,每60秒扫描50个班级的待处理考勤计时器,一个月下来要烧掉几万次空闲的FirestoreGoogle Cloud的NoSQL文档数据库,用于实时数据同步。读取。
在我们Google Developer Expert系列的第9部分,详细讲了我们怎么设计两套互补的课堂诚信系统:
游戏化“课堂Bingo”主动在场验证(BingoModal.jsx、BingoQuestionBankModal.jsx、BingoResultsView.jsx、LectureQrBingoModal.jsx、bingoFlows.js),包含4种有来源的验证类型(其中2种二维码考勤模式可以随时点名)、一个Gemini 4 ArgonGemini平台上的前沿推理模型,适用于复杂多步推理、代码生成等任务。(gemini-4-argon)题库AI工作室、Fisher-Yates位置偏差消除,以及一个由Google Cloud Tasks全托管的任务队列服务,用于事件驱动的任务调度。(dispatchBingoRetryTask)驱动的、事件驱动的非惩罚性两振出局状态机。
零信任考试模式锁定与法庭级取证档案(ExamReadinessWizard.jsx、storage.rules、getStudentVideoPlaybackUrl.js、processReportJob.js),强制执行全屏采集(displaySurface === 'monitor')、一次写入的云存储元数据锁定(isExam == 'true')、考试窗口期间学生门户的保密屏蔽,以及一键导出正式的Microsoft Word(.docx)和RFC 4180 CSV事件档案。
2. 图1:四种课堂Bingo验证模式与Gemini 4题库工作室
当老师触发一轮课堂Bingo(可以一键手动触发,也可以走交错抖动队列每隔几分钟随机挑战一部分学生,让邻座没法喊出“选B!”,或者在实时屏幕广播时走自动Bingo),functions/ai_flows/bingoFlows.js里的resolveBingoQuestion支持四种不同的验证类型:
类型1:预定义题库(question_bank)。零成本、零AI token,即时从老师精选的题库里抽题做理解检查(可以从Moodle Aiken格式批量导入,或者用Gemini 4 Argon批量生成)。
类型2:多模态教师广播画面+字幕AI融合(teacher_screen)。一次多模态Gemini调用(全班总成本约0.00015美元!),把老师的实时广播画面(liveFrame)和最近5分钟的口语字幕(liveSubtitles)融合起来,外加一条防重复规则。
类型3:个性化学生实验屏幕检查(student_screen)。针对单个学生工作站的定向Gemini视觉评估,验证学生是否在真正写实验代码,同时打击静态窗口诱饵和预录视频播放。
类型4:WebAuthn生物识别二维码考勤(2种二维码模式,随时点名)。专门用来破解“刷学生证就跑”的逃课漏洞。不再依赖门口一次性的刷卡,老师可以在课堂任何时刻通过两种互补的二维码模式触发考勤验证:
模式4A:工作站手机通行密钥二维码(
mobile_passkey)。显示在学生实验电脑的显示器上。学生必须用配对的手机扫描,并通过设备上的Face ID或指纹认证。坐在旁边电脑上的代课同学没法替他点击或验证,因为WebAuthn强制硬件设备绑定(一手机对应一学生)。模式4B:讲堂动态旋转投影仪二维码(
lecture_passkey_qr)。显示在教室或讲堂中央的投影屏幕上。二维码每10秒用HMAC时间签名动态轮换一次。真正坐在讲堂里的学生把手机摄像头对准投影屏幕签到。刷了卡就溜的学生扫不到投影,远程的朋友也没法用转发照片,因为二维码10秒就过期轮换了。
Bingo出题引擎的工程亮点
题库这块,老师课前打开 BingoQuestionBankModal.jsx,输入一个课堂主题(或者自己改系统提示词),选好模型——Gemini 4 Argon、Gemini 3.8 Flash、Gemini 3.5 Flash-Lite 都行——一次生成 1 到 15 道选择题。已有的题库也能直接按 Moodle Aiken 文本格式批量导入。
这里有个坑得单独说:让大模型出四选一的题,它有个很强的统计偏好,正确答案老爱往索引 0(也就是 A)或者索引 1(B)上放。学生做几道就摸出规律了。所以 generateBingoQuestionBank 和 resolveBingoQuestion 生成的每道题,都要过一遍确定性的 Fisher-Yates 置换(shuffleOptionsAndCorrectIndex),把选项顺序打乱,correctIndex 跟着更新。
再说 teacher_screen 模式。resolveBingoQuestion 给全班 50 个学生只发一次多模态 Gemini 调用,整个班加起来成本大概 0.00015 美元。它会把老师直播画面里的当前帧和最近 5 分钟的口头讲解字幕揉在一起送进去,同时注入一条防重复规则,带上最近问过的 5 道题(isDuplicateQuestion 用 80% 的词面 Jaccard 相似度做拒绝),这样老师停在同一张幻灯片上时,Gemini 也不会翻来覆去问同一个问题。
还有个细节容易被忽略:懂点技术的计算机系学生,一看弹出测验,第一反应就是开 Chrome DevTools 翻 Network 或者 Firestore。所以 bingoFlows.js 里 correctIndex 只写进 classes/{classId}/bingoRecords/{bingoId},这个路径在 firestore.rules 里学生根本读不了。推到 studentProperties/{studentUid}.activeBingo 的载荷只有 question、options 和 expiresAtMillis,客户端想偷看答案,没门。
生产代码一:Genkit 结构化 Schema 生成与 Fisher-Yates 位置去偏(bingoFlows.js)
来看 functions/ai_flows/bingoFlows.js 里的 generateBingoQuestionBank 和 shuffleOptionsAndCorrectIndex:
// Source: functions/ai_flows/bingoFlows.js
export async function generateBingoQuestionBank({ topic, count = 5, customPrompt = null, classId = null, model = null }) {
const targetModel = model || AI_MODEL;
const safeCount = Math.min(Math.max(Number(count) || 5, 1), 15);
const prompt = customPrompt
? customPrompt.replace(/\{\{topic\}\}/g, topic || '').replace(/\{\{count\}\}/g, String(safeCount))
: `You are a computer science instructor preparing quick-check multiple choice questions for a lab/lecture.
Topic / Lesson Material: "${topic}".
Generate exactly ${safeCount} multiple-choice questions testing immediate comprehension.
Each question MUST have exactly 4 options and a correctIndex (0, 1, 2, or 3).`;
const { response, modelUsed } = await generateWithResilience({
prompt,
output: {
schema: z.object({
questions: z.array(
z.object({
question: z.string().describe('The question prompt'),
options: z.array(z.string()).length(4).describe('4 multiple choice options'),
correctIndex: z.number().min(0).max(3).describe('Index of the correct option (0-3)'),
})
),
}),
},
}, targetModel);
const shuffledQuestions = response.output.questions.map((q) => {
const { options, correctIndex } = shuffleOptionsAndCorrectIndex(q.options, q.correctIndex);
return { ...q, options, correctIndex };
});
return { questions: shuffledQuestions };
}
export function shuffleOptionsAndCorrectIndex(options, correctIndex) {
const safeCorrectIndex = (typeof correctIndex === 'number' && correctIndex >= 0 && correctIndex < options.length)
? correctIndex
: 0;
const items = options.map((opt, idx) => ({ text: opt, isCorrect: idx === safeCorrectIndex }));
// Unbiased Fisher-Yates shuffle eliminates LLM Option-A bias
for (let i = items.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[items[i], items[j]] = [items[j], items[i]];
}
return {
options: items.map((item) => item.text),
correctIndex: items.findIndex((item) => item.isCorrect),
};
}
驱动游戏化课堂 Bingo 的三个提示词(bingoFlows.js)
下面是 functions/ai_flows/bingoFlows.js 和 admin/prompts/images/ 里三种 AI Bingo 模式实际执行的提示词。
1. 题库生成提示词(Bingo Question Bank Generator.md / generateBingoQuestionBank)
老师在 BingoQuestionBankModal.jsx 里生成 1 到 15 道可复用选择题时用的就是这段。模型可选 gemini-4-argon、gemini-3.8-flash 或 gemini-3.5-flash-lite:
# Bingo Question Bank Generator
You are an instructor preparing quick-check multiple choice questions for a classroom lab or lecture.
Topic / Lesson Material: "{{topic}}".
## Rules & Requirements
1. Generate exactly {{count}} multiple-choice questions testing immediate comprehension.
2. Each question MUST have exactly 4 options and a `correctIndex` (0, 1, 2, or 3).
3. Keep questions concise, practical, and unambiguous.
4. Directly test core principles, code patterns, or command usage from the stated topic.
2. 模式二提示词:一对多教师广播画面 + 5 分钟实时字幕融合 + 防重复规则(resolveBingoQuestion)
讲师在直播中触发 teacher_screen 类型的 Bingo 检查时,resolveBingoQuestion 会把当前 classes/{classId}/screenBroadcast/liveFrame 的画面、最近 5 分钟 classes/{classId}/liveSubtitles/current 的口述内容,以及 classes/{classId}/bingoRecords 里最近问过的 5 道题打包成一次多模态调用,面向全班出题:
You are an invigilator verifying student attention during a live lecture/explanation.
Analyze the instructor's shared screen image alongside their recent spoken commentary/captions:
Recent Instructor Spoken Commentary: "{{captionContext}}"
Formulate a 4-option multiple-choice question testing whether a student was actively following the instructor's lecture explanation.
The question can reference what the instructor just explained verbally, key details visible on the shared screen, or the connection between them.
ONE option MUST be the true detail from the screen or verbal explanation.
THREE options MUST be plausible but incorrect distractors.
Respond with JSON matching the schema:
{
"question": "Which file was the instructor editing?",
"options": ["main.py", "test.js", "app.py", "utils.go"],
"correctIndex": 0,
"observedEvidence": "main.py is open in VS Code with Python code displayed"
}
CRITICAL ANTI-DUPLICATION RULE:
The instructor may be holding or explaining the same slide/window. The following questions were ALREADY recently asked to students:
1. "{{recentQuestion1}}"
2. "{{recentQuestion2}}"
3. "{{recentQuestion3}}"
You MUST NOT repeat or rephrase any of the above questions.
You MUST choose a completely DIFFERENT detail, spoken concept, code line, toolbar item, question angle, or UI element that was NOT covered above.
3. 模式三提示词:学生实验机画面在场验证(Bingo Student Screen Presence Question Generator.md)
上机实验课用,每个学生按自己的节奏在自己的工作站上操作:
# Bingo Student Screen Presence Question Generator
You are a classroom invigilator checking student presence in a computer lab.
Analyze this student's computer screen screenshot.
## Rules & Requirements
1. Formulate a 4-option multiple-choice question testing immediate awareness of their screen state (e.g., active editor file, command in terminal, running app, or lab guide step).
2. ONE option MUST be the true visible detail on the student's screen.
3. THREE options MUST be plausible distractors.
4. Respond with valid JSON matching the schema:
{
"question": "What task or window is currently active on your screen?",
"options": ["Running test suite in terminal", "Reading documentation in browser", "Editing config file", "Debugging breakpoint"],
"correctIndex": 0,
"observedEvidence": "Terminal running pytest is in the foreground"
}
图 2:非惩罚性两振出局状态机(Google Cloud Tasks)+ 三色出勤热力图
高优先级的 BingoModal.jsx(z-index: 2147483647,可选配 Web Audio API 提示音)带着 45 秒倒计时弹到学生屏幕上之后,接下来会发生什么?
我们的两振出局状态机(submitBingoResponse、enqueueBingoRetryTask、handleDispatchBingoRetry,都在 functions/ai_flows/bingoFlows.js 里)把学术理解力和物理在场严格拆开:
- 情况一,答对(
passed):学生拿到基础分 100 分、速度奖励(按剩余时间比例最多 +50 分)、前三名领奖台加成(+50 / +30 / +20 分)。之前累计的振数清零(strikeNumber = 0)。 - 情况二,答错(
failed_incorrect):学生在 45 秒窗口内主动点了选项,工位在场这件事就 100% 验证了。系统立刻给出正确答案反馈,不记振,不扣出勤分钟。 - 情况三,第一振超时(
strikeNumber === 1时的missed_timeout):学生去趟洗手间两分钟怎么办?一次漏检就扣出勤太狠了。submitBingoResponse会设置pendingRetryBingo: true,并向 Google Cloud Tasks(locations/asia-east2/functions/dispatchBingoRetryTask)压入一个延迟任务,等retryDelayMinutes之后再触发(可配 1 到 15 分钟,默认 3 分钟,空闲轮询成本 0 美元)。 - 情况四,连续第二振超时(
strikeNumber >= 2时的missed_timeout):Cloud Task 三分钟后醒来,先做幂等预检(确认班级还在上课、老师没取消这节课),然后派发一次个人专属的第二振补考题。学生只要应答了第二振,哪怕答错,在场状态也算恢复。只有第一振和第二振连续都没应答,后端才计算未验证时长(Δt = t_第二振 - t_第一振),写入一条attendanceAdjustments扣减记录。

生产代码 2:事件驱动的两振 Cloud Tasks 调度与 AFK 时长扣减(bingoFlows.js)
下面是 functions/ai_flows/bingoFlows.js 里两振状态迁移和确定性 Cloud Task 去重的实际逻辑:
双振出局:缺勤判定与考勤修正
代码接着处理三种判定结果。passed 最简单,清零振数、取消待重试标记。failed_incorrect 表示学生在座位上但答错了,这种情况不动考勤,只更新时间戳记录。
missed_timeout 才是真正触发缺勤逻辑的分支。第一次超时,系统不直接扣考勤,而是安排一次延迟重试,默认三分钟后。这里用 Google Cloud Tasks 做延迟队列,空闲时不产生轮询费用。重试任务里带上班级、学生和上一次的 Bingo ID,方便第二次判定时追溯。
第二次超时,缺勤坐实。系统会去查第一次 Bingo 的签发时间,算出两次之间的时间窗口,按分钟向上取整,最少扣一分钟。然后往 attendanceAdjustments 写一条记录,带上起止时间、扣除分钟数、原因,以及两次 Bingo 的 ID。这条记录在 AttendanceView.jsx 里渲染成红色的位掩码代码 2。
五分钟位掩码:考勤的可视化编码
AttendanceView.jsx 把每节课的每一分钟编码成五种状态之一。
绿色(1、3、4)代表已验证的在场,来源可以是屏幕共享、Passkey 验证或教师手动覆盖。红色(2)是被作废的 AFK 窗口,来自连续两次 Bingo 超时,鼠标悬停能看到具体时间戳,旁边有一个一键恢复按钮,教师点一下就把 excused 设为 true。灰色(0)是离线或断连。
红色状态的设计意图是给教师留一个纠错出口。系统自动判定,但最终裁量权在人手里。
零信任考试模式:四层纵深防御
日常课堂用 Bingo 保持活跃、不惩罚,正式实操考试则切换到另一套逻辑。当课程进入预设的 examPeriods 时间窗,或者 lessonType 标记为 exam,四层防御自动激活,覆盖浏览器端、Cloud Run 的 FFmpeg 处理、Cloud Storage 安全规则,以及签名 URL 代理。
写一次锁定:考试元数据的存储层防护
考试期间,学生不能删除已上传的截图或视频,也不能事后回放自己的考试录像把题目泄露给其他班级。
这条约束在两个独立边界上执行。第一层是 Cloud Storage 规则:学生对 screenshots/ 和 irregularities/ 只有写权限,大小分别限制在 2 MB 和 5 MB,删除权限为零。videos/{classId}/{videoId} 路径下,只要 resource.metadata.isExam == 'true',学生端的读取请求一律拒绝。
第二层是签名 URL 代理 getStudentVideoPlaybackUrl.js,学生拿不到直连存储的地址,所有回放请求都经过服务端鉴权后再签发临时链接。两层各管各的,任何一层被绕过,另一层仍然拦得住。
先看存储层的规则。storage.rules 里对 /videos/{classId}/{videoId} 的读权限做了两层判断:老师可以读班上任何视频,学生只能读属于自己的、且不是考试的视频。注意最后那个条件,isExam 字段不存在或者不等于 'true' 才放行,等于把考试视频默认锁死。删除权限只给老师。
但光靠存储规则还不够。假设视频是在老师后来才往班级设置里补了 examPeriods 时间段之前就编译好的,元数据上没打考试标记,存储规则就拦不住。所以服务端还有一个签 URL 的中转函数 getStudentVideoPlaybackUrl.js,里面的 evaluateStudentRecordingsAccess 会同时检查 jobData.isExam 标志和 isTimestampInExamPeriods(jobData.startTime, examPeriods, timeZone)。两个时间戳都比对,startTime 和 createdAt 各查一次。
// Source: functions/media_processing/getStudentVideoPlaybackUrl.js
export function evaluateStudentRecordingsAccess({
examPeriods = [],
policy = 'always_enabled',
releaseDate = null,
jobData = {},
timeZone = 'Asia/Hong_Kong',
}) {
// Recordings for exam/test periods or stamped as exam are strictly confidential and not shared
const isExam = Boolean(
jobData.isExam ||
jobData.lessonType === 'exam' ||
isTimestampInExamPeriods(jobData.startTime, examPeriods, timeZone) ||
isTimestampInExamPeriods(jobData.createdAt, examPeriods, timeZone)
);
if (isExam) {
return {
allowed: false,
reason: 'Screen recordings for exam or test periods are confidential and not shared with students to protect assessment questions.',
};
}
// Also enforces 'disabled' and 'delayed_release' policies
return { allowed: true };
}
只要命中考试判定,直接拒绝,返回的理由写得很明确:考试时段的录屏属于保密内容,不发给学生,保护试题。函数注释里还提了一句,disabled 和 delayed_release 这两种策略也在这里执行,只是代码片段省略了。
生产代码之四:可上学术委员会的 Word 文档与 RFC 4180 CSV 取证卷宗编译器(processReportJob.js)
学术诚信委员会开听证会的时候,没人愿意盯着一堆数据库里的 JSON 日志看。非技术背景的委员根本没法审。
老师在 IrregularitiesView.jsx 里点开 IncidentDossierExportModal.jsx,按下那个「导出正式卷宗(.docx)」的按钮,背后跑的是 processReportJob.js。这个 Cloud Function 配了 1 vCPU、2 GiB 内存、540 秒超时。它把该班级所有限定时间范围内的 irregularities、audio 转写文本(Edge Whisper 加 Gemini 二次校验过的)、还有 screenshots 全部查出来,给每个学生算一个风险等级:HIGH RISK、MODERATE RISK、NORMAL / CLEAN。然后用 docx 和 csv-stringify 两个库,编译出一份可下载的 Word 卷宗,里面嵌了 ImageRun 证据截图、3D 头部姿态和视线遥测表格,还有逐字语音转写。
// Source: functions/media_processing/processReportJob.js
export const processReportJob = onDocumentCreated(
{
document: 'reportJobs/{jobId}',
region: FUNCTION_REGION,
cpu: 1,
memory: '2GiB',
timeoutSeconds: 540,
},
async (event) => {
// ... Fetches classData, studentProperties, time-scoped irregularities, audioTranscripts & screenshots
const studentReports = targetStudentIds.map((uid) => {
const student = studentMap[uid] || { id: uid };
const studentIrregs = irregularities.filter((ir) => ir.studentUid === uid || ir.userId === uid);
const studentAudios = audioTranscripts.filter((a) => a.studentUid === uid);
const studentScreens = screenshots.filter((s) => s.studentUid === uid);
const highSeverityCount = studentIrregs.filter((i) => i.severity === 'high' || i.type === 'multiple_faces').length;
const totalIncidents = studentIrregs.length;
const riskLevel = highSeverityCount > 2 ? 'HIGH RISK' : totalIncidents > 3 ? 'MODERATE RISK' : 'NORMAL / CLEAN';
return { student, irregularities: studentIrregs, audios: studentAudios, screenshots: studentScreens, totalIncidents, highSeverityCount, riskLevel };
});
风险分级用的是两个阈值:高危事件超过 2 次就标 HIGH RISK,总事件超过 3 次标 MODERATE RISK,其余算干净。高危的定义是 severity === 'high' 或者类型为 multiple_faces,画面里出现多张脸这种事在考试场景下基本没有解释空间。
三个零信任考试模式的监考提示词,喂给 .docx 卷宗
考试进行中,只要一级浏览器边缘侦查器或老师手动触发的云端扫描发现视觉或声音异常,admin/prompts/ 下这三个提示词就会上场评估证据,调用 Genkit 工具(recordIrregularity、recordAudioIrregularity、recordAudioAudit)把数据填进 .docx 取证卷宗。
1. 屏幕异常与工具调用监考器(admin/prompts/images/AI invigilator.md)
**你是一名 AI 监考员。你的任务是分析学生屏幕,通过工具采取适当行动,然后在最终文本摘要中报告你做了什么。**
## 指导原则
* **轻微分心:** 短暂看向别处,非考试应用可见但未使用。
* **严重分心:** 主动使用其他应用、玩手机、与人交谈、考试期间未经授权的通信或使用 AI 工具。
## 行动与响应协议
1. **分析屏幕。**
2. **如果没有问题,你的最终答案必须是这段确切的文字:** "所有学生都专注且在正轨上。"
3. **如果有问题:**
* 首先,并行调用所有必要的工具。你**必须**调用 `sendMessageToStudent` 和 `sendMessageToTeacher`。如果问题是**严重分心**,你还**必须**调用 `recordIrregularity`。
* 然后,作为最终答案,你**必须**提供问题和你所采取行动的详细摘要。
## 输出规范
* 在任何输出中提及学生时,你**必须**使用 'email (uid)' 的格式。
2. 云端兜底人脸与视线监考器(admin/prompts/images/Cloud Fallback Face & Gaze Invigilator.md)
# 云端兜底人脸与视线监考器
分析这张学生 {{studentEmail}}(UID: {{studentUid}},班级: {{classId}})的教室监考摄像头照片。
判断学生人脸是否存在以及视线方向。
## 检测规则
1. 画面中有人脸吗?如果看不到脸,状态为 'no_face'。
2. 有多张脸吗?如果画面中超过 1 人,状态为 'multiple_faces'。
3. 学生是否面朝前方/正对电脑屏幕?如果头部或视线明显偏离(左、右、看其他设备、看向考试区域之外),状态为 'looking_away'。
4. 如果学生正常坐着面对屏幕/作业,状态为 'normal'。
## 输出格式
仅以如下精确结构的合法 JSON 响应:
{
"faceStatus": "normal" | "looking_away" | "no_face" | "multiple_faces",
"confidence": 0.95,
"reason": "简要说明"
}
3. 实时滚动音频串通与耳语监考器(admin/prompts/audios/AI Voice Invigilator (Live Rolling Audio).md)
# AI 语音监考器(实时滚动音频)
你是一名专家级 AI 教室监考员,正在分析考试期间的学生实时音频流。
## 上下文
- 班级 ID: {{classId}}
- 学生 UID: {{studentUid}}
- 学生邮箱: {{studentEmail}}
- 音频时长: 滚动 20–45 秒窗口
## 音频转录
"""
{{transcript}}
"""
## 指令
1. 分析转录内容,查找未经授权的说话、考试串通、念题、交换答案,或对第三方/设备耳语。
2. 如果检测到多人讨论、未经授权的交谈或念答案:
- 调用 `recordAudioIrregularity` 工具,传入 `title`、`message`、`riskLevel`('low' | 'medium' | 'high')、`speakerCount` 和 `transcriptSnippet`。
3. 调用 `recordAudioAudit` 工具,传入 `verdict`('clean_exam' | 'suspicious_collaboration' | 'whisper_detected' | 'background_noise' | 'inconclusive')、`speakerCount`、`summary` 和 `transcript`。
第 10 部分预告
到这里,整个教学与多模态 AI 技术栈已经全部走了一遍——从浏览器边缘 AI(Gemma 4 E2B、MediaPipe、LiteRT Whisper)到云端广播、Gemini 4 Argon 视频评分、Capstone 移动端督学,再到零信任考试。
第 10 部分(系列大结局),我们掀开引擎盖看第二天机构运营:怎么用实时 Google Cloud Billing SKU 同步(syncGeminiPricing)管住 AI 和存储预算,9 类事件驱动的存储配额执行(storageQuota.js),4 阶段零孤儿级联班级清除(onClassDocDeleted),以及一条命令搞定的 Terraform 基础设施即代码(./setup-new-project.sh)。
常见问题(FAQ)
课堂Bingo的四种验证模式分别是什么?
预定义题库、教师广播画面加字幕AI融合、个性化学生实验屏幕检查,以及WebAuthn生物识别二维码考勤(含工作站手机通行密钥和讲堂动态旋转投影仪两种二维码模式)。
为什么二维码考勤能防止刷学生证就跑和代签?
手机通行密钥二维码强制一手机对应一学生,需Face ID或指纹认证,代课同学无法代验;投影仪二维码每10秒用HMAC时间签名轮换,离场学生扫不到,转发照片也会过期。
Gemini出题为什么还要做Fisher-Yates打乱?
大模型出四选一题时正确答案偏爱索引0或1,学生几道就能摸出规律。每道题经确定性Fisher-Yates置换打乱选项并同步更新correctIndex,消除位置偏差。
版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。
文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。
若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。




