首页 / 资讯中心 / 文章详情

用大模型API实现课堂话语智能分类的完整实践

用大模型API实现课堂话语智能分类的完整实践 ★ FEATURED ARTICLE
前阵子接手了一个课堂观察数据分析的小项目教研组拿到的是一堆课堂录音转写文本希望能快速知道一节课里教师提问了多少次、提问类型分布如何、学生回答情况怎么样、教师反馈方式有哪些。传统做法是人肉对着编码表一句句打标签二十分钟的课堂片段能让人盯到眼冒金星。后来我基于通义千问 API 做了个课堂话语智能分类分析工具把这段手工活交给大模型整体流程跑通之后效果超出预期今天把完整实现思路和踩坑过程整理出来。这个工具适合谁呢如果你手头有课堂转写文本需要做师生互动分析、教研观察记录、教学行为量化研究或者只是想试试大模型 API 怎么落地到具体行业场景这篇内容都能直接参考。核心思路不复杂把转写文本按发言切成片段构造分类 Prompt让大模型逐条返回结构化 JSON最后汇总成统计报表。下面从需求拆解到代码实现再到参数调优和常见问题完整过一遍。1. 项目背景与方案选型为什么用大模型 API 做课堂话语分类1.1 传统课堂话语分析的痛点课堂话语分析在教育领域是个很成熟的研究方向核心是把课堂上的师生语言行为归类到不同维度比如提问、回答、反馈、管理、讲解等等。教研员拿着编码表一边听录音一边给每句话打标签一节课四十五分钟转写出来大概五六千字人工编码通常要花两三个小时甚至更久而且不同人对同一句话的理解可能不一致编码一致性很难保证。之前我也想用规则或者小模型来做这件事。比如写关键词字典去匹配“对不对”“为什么”“谁来回答”这类句式但课堂语言太灵活了同一个意思可以有几十种表达方式规则覆盖永远追不上真实场景。后来试过微调小型预训练模型光是标注训练数据就要几百上千条还得准备 GPU 环境折腾了两周准确率还是上不去。这些都是成本也是我最终转向大模型 API 的直接原因。1.2 大模型 API 在分类任务上的优势与取舍选择大模型 API 而不是自建模型理由其实很实际。课堂话语分类本质上是一个文本分类任务但它的难点在于上下文依赖很强一句话到底是“追问”还是“讲解”往往要看前面几句的互动节奏。大模型经过海量数据训练对意图判断、语用理解的把握比小模型强不少尤其适合这种需要“阅读理解”的分类场景。使用通义千问 API 这类商用模型服务省去了模型部署和运维的麻烦按量付费成本可控。结构上我考虑过两条路一种是逐条调用简单粗暴另一种是把整个对话段落一次丢给模型让它输出多行结果。前者速度慢上下文缺失后者容易超过上下文窗口而且分类边界容易糊。最终我采用的方案是“带上下文的逐条分类”每次调用时携带当前句之前最近三到五句上下文既保留互动脉络又不会让单次请求体积失控。这个设计在后面代码里会详细展开。2. 分类体系设计定维度比写代码更重要2.1 分类维度怎么定才能既实用又不混乱工具的核心不只是 API 调用更重要的是分类体系。我参考了课堂互动分析里常用的行为编码思路结合教研组的实际需求设计了一套两级分类体系。一级维度分六类教师提问开放式教师提问封闭式学生回答正确/完整学生回答部分正确/不完整学生回答错误/无关教师反馈肯定/鼓励教师反馈否定/纠错教师反馈追问/引导教师讲解/解释课堂组织与管理其他无关话语二级维度用来区分提问和反馈的具体子类型这样既便于统计整体结构也能在后续做更细的教学行为分析。之所以没有把分类维度设计得更细比如再去区分“记忆类提问”“理解类提问”“应用类提问”这种按认知层次划分的方式是因为当前这版工具的核心目标是帮助教研员快速完成基础编码分类粒度太细模型区分难度大、人工核对成本也高反而不利于落地。先把粗粒度分类跑稳再谈精细化。2.2 分类定义与 Few-shot 示例的编写技巧分类体系写进 Prompt 时光给类别名是不够的模型需要看到每个类别的典型说法。我在每个类别后面都配了两三个课堂场景示例示例尽量贴近真实课堂口语而不是课本里的标准句型。比如“教师开放式提问”的示例我写的是“你觉得这篇课文里哪个情节最打动你”和“关于光合作用你们还有什么疑问”“教师封闭式提问”则用“这个单词读作什么”“对不对”“是不是选B”这类有确定答案的句子。Few-shot 示例的质量直接决定分类效果这一步一定不能省。Prompt 里我还会一次性告诉模型输出格式要求它返回 JSON包含 utterance、speaker、category、confidence、reason 五个字段。confidence 是模型对自身判断的置信度reason 是一句话的判断依据这俩字段在后续人工抽检和结果解释时非常有用。3. 工具架构与数据流从录音转写到统计报表3.1 整体模块划分整个工具的流程可以拆成五个环节原始文本预处理、发言片段切分、Prompt 构造与 API 调用、结果解析、统计汇总与导出。原始文本一般来自两类场景一是已经有带时间戳的 SRT 字幕文件二是 ASR 转写出来的纯文本两种情况都需要先清洗格式去掉特殊符号和重复片段再按发言者切分。如果转写文本本身没有标注发言者也不用慌我在 Prompt 里让模型额外输出 speaker 字段自动判断当前句是教师还是学生。虽然多了一个分类任务但实测下来准确率还可以毕竟教师语言和学生语言在措辞上差异比较明显。3.2 带上下文的逐条分类设计这是整个工具里最关键的一个设计决策。最开始我用最简单的“单句分类”直接把当前句丢给模型结果“教师追问”和“教师讲解”经常搞混因为追问往往依赖前一句学生的回答。后来改成带上下文模式在 Prompt 中拼入当前句前面最近的几段话语并明确告诉模型“只需要对最后待分类的那句话做判断前面的内容仅作为背景参考”。上下文长度我试过三段、五段、七段最终选取五段作为默认值。太少语义补不全太多会引入干扰信息而且 token 消耗明显上升。每条请求加上固定系统提示词和 Few-shot 示例大约在五百到七百 token 左右一节四十五分钟课堂的转写数据分三百到四百次请求就能覆盖成本完全在可接受范围内。3.3 输出结果的结构化设计为了让分类结果能直接进统计环节所有返回内容都强制走 JSON 格式。每个返回对象长这样{ utterance: 你觉得这个实验结果说明了什么, speaker: teacher, category: teacher_question_open, confidence: 0.96, reason: 教师面向全班提出开放性思考题没有固定答案 }解析时我会把模型输出先尝试用 json.loads 处理失败则用正则提取大括号内的内容再做一次解析。这里有一个很重要的防御性编程习惯大模型输出是概率性的任何一次调用都可能返回非法 JSON容错逻辑必须做到位。4. 代码实现细节从调用千问 API 到生成 Excel 报告4.1 环境准备与依赖安装开发环境用的是 Python 3.9核心依赖只有四个dashscope通义千问官方 SDK、pandas数据处理、openpyxlExcel 写入、tiktoken可选用于估算 token 数辅助控制上下文长度。安装命令非常简单pip install dashscope pandas openpyxl tiktokenAPI Key 不要写死在代码里建议通过环境变量注入我一般习惯在项目根目录创建一个.env文件然后用os.getenv(DASHSCOPE_API_KEY)读取。这样既安全也方便多台机器部署时不改代码。4.2 文本预处理与发言切片下面这段代码负责把 SRT 字幕文件解析成结构化片段如果输入已经是纯文本就走简单按空行切分的逻辑。SRT 的格式规律是序号、时间轴、字幕内容依次排列我按空行拆分后逐段提取时间轴和文本def parse_srt(srt_path): with open(srt_path, encodingutf-8) as f: content f.read().strip() blocks content.split(\n\n) fragments [] for block in blocks: lines block.strip().split(\n) if len(lines) 2: continue time_line lines[1] start, end time_line.split( -- )[:2] text .join(lines[2:]).strip() if text: fragments.append({ start: start, end: end, text: text }) return fragments如果转写文本里已经包含了说话人标签比如“教师请同学们思考一下”我会用正则把标签摘出来单独存放。如果没有任何标签就默认 speaker 字段交给模型去判断。切片的核心原则是保持完整语义一个片段最少是一句完整的话不要因为标点符号硬拆成半句。4.3 Prompt 模板与调用函数Prompt 模板是整个工具的灵魂。我把它拆成了三部分系统角色设定、Few-shot 分类示例、当前待分类片段。系统角色设定是这样的你是一名课堂话语分析专家。请根据给定的分类体系对课堂转写文本中最后一条话语进行分类。 你只需要判断最后一条待分类话语前面的对话内容仅作为背景信息。 分类体系如下 - teacher_question_open: 教师提出没有唯一答案的开放性提问 - teacher_question_closed: 教师提出有明确答案的封闭性提问 - student_response_correct: 学生回答正确且完整 - student_response_partial: 学生回答部分正确或表达不完整 - student_response_incorrect: 学生回答错误或答非所问 - teacher_feedback_positive: 教师对学生的回答给予肯定、鼓励 - teacher_feedback_negative: 教师指出错误或表示否定 - teacher_feedback_followup: 教师基于学生回答进行追问或引导 - teacher_explanation: 教师讲解知识点或解释概念 - classroom_management: 教师进行课堂组织、纪律管理、任务布置 - other: 其他类型 请以 JSON 格式输出字段为 {utterance: 原话, speaker: teacher 或 student, category: 分类标签, confidence: 0到1之间的小数, reason: 判断理由}Few-shot 示例我直接放在系统提示词最后每个类别给一个真实感强的课堂例句帮助模型对齐判断标准。调用函数写成通用接口方便后续替换模型名称import os import json import time import dashscope from dashscope import Generation dashscope.api_key os.getenv(DASHSCOPE_API_KEY) MODEL_NAME qwen-plus def classify_utterance(context_list, target_utterance): system_prompt build_system_prompt() user_prompt 课堂对话片段\n \n.join(context_list) \ \n\n待分类话语 target_utterance messages [ {role: system, content: system_prompt}, {role: user, content: user_prompt} ] for attempt in range(3): try: resp Generation.call( modelMODEL_NAME, messagesmessages, temperature0.1, max_tokens500, result_formatmessage ) if resp.status_code 200: content resp.output.choices[0].message.content return parse_json_response(content) elif resp.status_code in [429, 500, 502, 503]: time.sleep(2 ** attempt) except Exception: time.sleep(2 ** attempt) return None这里有个细节值得说明temperature 我固定设置为 0.1不是 0。虽然分类任务通常建议低温但完全设为 0 在某些情况下会让模型陷入重复输出同一结果的陷阱稍微保留一点随机性反而更稳定。你也可以在后续版本里测试 temperature0 的效果不同模型版本表现会有差异。4.4 批量处理与结果汇总批量处理时我加了两个保险机制一是每次请求之间休眠 0.2 秒避免触发限流二是用字典做结果缓存同一句话只调用一次 API防止重复请求浪费 token。所有分类结果存进列表最后用 pandas 转成 DataFrame直接输出 Excel。def process_transcript(fragments, window_size5): results [] cache {} for i, frag in enumerate(fragments): text frag[text] if not text: continue key hashlib.md5(text.encode(utf-8)).hexdigest() if key in cache: result cache[key] else: left max(0, i - window_size) context_lines [f[text] for f in fragments[left:i]] result classify_utterance(context_lines, text) cache[key] result time.sleep(0.2) if result: result[start] frag.get(start, ) result[end] frag.get(end, ) result[index] i results.append(result) return results统计维度我做了一张透视表横向是分类类别纵向是发言者值里同时输出次数和占比。这样教研员一眼就能看出一节课里教师提问结构中开放式和封闭式的比例也能快速定位学生回答类型分布和教师反馈方式偏好。def export_report(results, output_path): df pd.DataFrame(results) summary df.groupby([speaker, category]).size().reset_index(namecount) total len(df) summary[ratio] summary[count] / total with pd.ExcelWriter(output_path, engineopenpyxl) as writer: df.to_excel(writer, sheet_name明细, indexFalse) summary.to_excel(writer, sheet_name汇总, indexFalse)报表里我还会单独加一个“人工复核抽样”工作表用 confidence 排序把模型置信度最低的十条单独列出来方便人工重点检查。这个操作看着不起眼实际使用中非常管用。5. 参数调优与成本控制实测数据说话5.1 关键参数对比与推荐值我把调试过程中试过的几组关键参数列出来方便你直接参考参数尝试范围推荐值说明temperature0 到 0.70.1温度过高分类标签会跳变过低可能重复输出max_tokens200 到 800500JSON 结果加 reason 字段时 500 足够上下文窗口3 / 5 / 7 条5太少语义不足太多引入干扰且成本上升请求间隔0 / 0.1 / 0.2 秒0.2 秒稳定避免限流一节课数据耗时十分钟左右重试次数1 / 2 / 33超过 3 次基本是接口或网络问题再试意义不大有个参数我没放进表里但实际挺重要就是每次请求的 prompt 输出格式约束。在 system prompt 里明确写“只输出 JSON不要附加任何解释文字”能显著降低解析失败的次数。如果你的账号支持更严格的 JSON Mode可以在调用时显式开启解析成功率还能再高一点。5.2 成本测算与缓存优化成本是项目能否长期跑下去的关键。我拿一节真实的转写课例做过测算大约五千字按千问系列模型输入加输出合计约六千 token 估算单节课的 API 费用大概在几毛到一块钱之间。如果是几十节课的教学研究项目总成本控制在几十块钱以内比人工编码的时间成本低两三个数量级。成本优化的第一原则是减少无效请求。我的做法有两条一是先做本地预筛选纯噪声句、单字应答这类明显不需要分类的内容直接跳过二是结果缓存同一句话只调一次 API整个项目重复跑分析时能省掉大量重复费用。缓存我用一个简单的文件缓存把 utterance 的 MD5 值映射到分类结果下次遇到相同的句子直接从本地读。还有一个容易被忽略的优化点上下文内容如果很长可以只保留每条发言的前半部分。课堂口语里经常有口头禅和重复表达截断到一定长度对分类结果影响不大但能减少 token 消耗。6. 常见问题与避坑实录我踩过的那些坑6.1 非法 JSON 与解析失败排查问题中最常见的就是模型输出了一堆解释文字或者 JSON 里带了反引号和多余内容。我的兜底解析函数写了三层先直接 json.loads失败就提取第一个大括号之前的 JSON 对象再失败就用正则把 category 字段里的双引号内容抠出来。虽然看起来不优雅但确实能把解析成功率拉到接近百分之百。6.2 分类边界混淆的典型场景分类不准主要集中在三对关系上。第一是“开放式提问”和“封闭式提问”比如“你们觉得呢”和“你们觉得是A还是B”后者带有选项倾向容易被误判。第二是“学生部分正确”和“学生回答错误”这类边界本身模糊人工编码时也常争执我会在 Prompt 示例里刻意补充这两类的对照例句。第三是“教师追问”和“教师讲解”追问通常紧跟学生回答且语气带有引导性质这时上下文窗口就发挥作用了。遇到分类结果不理想时推荐把它加入 Prompt 的 Few-shot 示例中让模型在下一次调用时参考。这个迭代方法实测效率非常高比单纯调 temperature 管用得多。6.3 限流、超时与长文本截断批量处理大量片段时限流几乎必然遇到。我的处理思路是固定的指数退避重试第一次失败等两秒第二次等四秒第三次等八秒。如果第三次还失败就跳过这条并把索引记录下来最后统一补跑。补跑机制看起来增加了代码量但它能让整个批处理流程真正做到无人值守课堂数据量大时非常关键。长文本截断主要是针对单句话特别长的情况。有些学生回答能一口气说两百多字加上五条上下文可能超窗口。我的办法是按句子边界截断待分类原文保留前一百五十字同时在生成 Prompt 时加一句“如果待分类话语过长以开头部分为主要判断依据”。7. 一些使用心得与后续扩展方向整个工具从开发到跑通大概花了两周时间其中三分之一的时间在研究 Prompt三分之一在处理容错真正写 API 调用逻辑的时间和前两者相比反而很少。这是我反复想强调的一点这类大模型 API 应用难点不在 API 本身而在任务拆解和工程容错。我个人的经验是不要一开始就追求百分之百的分类准确率先让工具帮教研员省掉百分之八十的人工编码时间剩下的边界案例留给人脑处理这才是合适的落地方案。分类结果出来后建议做两件事一是定期抽取百分之十的结果做人工复核监控模型的分类稳定性二是把置信度低的样本收集起来回填进少样本示例里形成正循环。这个工具后续的空间还挺大。比如可以把分类结果按时间轴展开生成课堂话语热力图直观看到教师提问密度、学生回应分布甚至能辅助做新手教师和资深教师的提问策略对比。我目前正准备把“分类结果 时间戳”做成 JSON 单文件方便第三方可视化脚本直接调用省去在 Excel 里翻找明细的麻烦。如果你也在做类似的课堂分析工具从这套基础框架入手应该能少走不少弯路。
阅读完成 · 觉得有帮助?
咨询建站