资讯动态

多智能体情感分析在教育评价系统中的应用与架构实践

发布时间:2026/10/9 11:49:57 来源:尧图企业网站定制
简介这是一套面向在线教育平台评价场景的多智能体情感分析系统基于CrewAI框架实现多个专业化角色协同分析学习者评论并输出课程质量评估结果。系统内置登录鉴权、任务选择、数据文件上传、情感分析、结果对话与历史记录查看等模块默认对接DeepSeek智能体也支持切换不同智能体模型适合教育技术研究者、自然语言处理学习者以及在线课程运营人员参考。资源共五十三个文件、约四点四兆以网页模板、样式脚本、后端代码、配置文件和图片图表为主另有示例数据、数据库文件与工程配置便于直接运行和二次开发。从压缩包目录可见前端页面、后端逻辑、数据导入脚本与说明文档齐全能够帮助读者理清多智能体任务分配、情感分析流水线及可视化展示的完整流程。已有七十二人学习下载适合希望快速上手智能体评价系统搭建的开发者。1. 教育平台评价系统为什么情感分析要拆给多个智能体某教育平台上线了匿名评价入口三千条评语涌进来之后运营同学当晚就没睡成觉“课太难了”和“难得好课”同时命中“难”这个关键词被自动判成负面“平台卡成PPT”既不骂脏话也不含常见情绪词被老老实实归入中性。这类评价系统如果只做一个情感极性分类器注定翻车——因为情感分析在教育场景里真正要回答的不是“好评还是差评”而是“谁在说话、在说哪个对象、情绪到底有多强、下一步该处理什么”。我拆过一套多智能体教育平台评价系统思路是把情感分析拆成多个智能体分工一个负责判情绪一个负责抓对象一个负责提建议最后由仲裁逻辑合并结果。语言模型负责出判断规则负责兜底既有容错性也不至于变成黑匣子。这套做法对课程评价、师生反馈、在线教学平台运营都有直接参考价值下面把架构、参数和踩坑一次讲透。2. 整体架构与数据流五个角色的职责边界2.1 数据链路从评语提交到看板展示整套评价系统不是“一个情感模型”而是一条清晰的数据链路评语从客户端进来先经过清洗和匿名化再并行分发给不同智能体各自产出结构化结果最后由汇总服务合并、仲裁、落库最终呈现在评价看板上。我一般会把链路分成五段每段都有明确的输入输出评语输入 - 清洗与匿名化 - 智能体任务分发 - 结果仲裁与合并 - 存储与看板第一段卡得最狠。匿名化不能只做“把学号打码”评语里常出现“王老师”“李老师”这类称呼如果不做实体脱敏后续统计和导出都有合规风险。常见做法是维护一个称呼词典凡是匹配到“X老师”“X同学”统一替换成“该老师”“该同学”。第二段的清洗策略要以“保留语义”为前提不能简单删停用词因为情感判断恰恰依赖那些口语词。分发阶段才是多智能体真正起作用的地方。同一个标准化评语文本会同时发给情感判别智能体、主题归因智能体和建议提取智能体三者彼此独立、互不干扰。每个智能体返回 JSON统一格式、统一置信度字段仲裁层拿到后再合并成一条完整评价记录。2.2 角色拆分谁来判断情绪、谁负责归因多智能体在这里不是“一个模型跑三次”这么敷衍而是真正把任务边界切开。我在这个项目里定了五个角色职责不能混。智能体角色输入输出典型判断逻辑情感判别智能体清洗后的评语文本极性、情感分数、情绪标签列表分析整体语气、情绪强度和隐含态度主题归因智能体评语文本加候选主题列表对象列表及对应情感先抽取评价对象再判断每个对象的情感建议提取智能体评语文本建议句原文或空从“希望”“建议”“能不能”等句式里抓建议异常识别智能体评语文本加历史统计是否异常、异常类型重复内容、刷屏词、超长文本识别汇总仲裁智能体上述四个结果最终评价记录去重、投票、置信度加权、冲突消解情感判别智能体管“情绪方向”它的输出是polarity和emotions。主题归因智能体管“在说谁”很多评语同时包含课程难度和平台卡顿两个对象如果只给一个整句极性统计必然失真。建议提取智能体是为了把“改进项”从评价里抽出来直接变成工单这一步看起来简单实际过滤掉的无效内容很多。异常识别智能体负责防作弊比如连续十条“老师很好”重复提交必须先拦下来不能进入统计。五个角色之间不存在上下级仲裁器只在最终汇总时介入。这种扁平结构的好处是单个角色可以独立替换模型、单独调参不会牵一发动全身。2.3 存储设计评价结果如何回填平台多智能体的价值要落到数据上落库结构没设计好后面做趋势分析会非常痛苦。我建议按评价事实、结构化结果、人工复核状态三块来存核心表可以这样建CREATE TABLE review_result ( review_id VARCHAR(64) PRIMARY KEY, course_id VARCHAR(32), teacher_id VARCHAR(32), raw_text TEXT, clean_text TEXT, polarity VARCHAR(16), -- positive / negative / neutral sentiment_score FLOAT, -- 0~1越接近1越正面 emotions JSON, -- [satisfied,confused] aspect_tags JSON, -- [{aspect:teaching,polarity:negative}] suggestion_text TEXT, confidence FLOAT, status TINYINT, -- 0待复核 1已复核 2已忽略 created_at DATETIME, reviewed_at DATETIME );这里的emotions和aspect_tags都用 JSON好处是模型输出的标签数量不固定不用频繁改表结构。confidence字段非常关键它是仲裁阶段算出来的最终置信度低于阈值的记录会进入人工复核队列而不是直接对用户发布。看板层不要直接读这张原始表建议提前聚合出三张统计表按课程维度的情感分布、按老师维度的负面主题占比、按周粒度的情感趋势。聚合口径最好在写入时就定死避免看板查了十秒还没出来运营侧早就没耐心了。提示清洗后的clean_text一定要单独存不要覆盖raw_text。很多事故都是因为后来想复现误判结果原始文本已经被洗没了。3. 情感分析核心模块语言模型出分数、规则兜底防翻车3.1 用语言模型做结构化情感输出情感分析模块是整个系统里被问得最多的部分。早期方案大多用关键词词典或统计分类器但在“课太难了”和“难得好课”这种场景里识别率非常差。后来切到语言模型之后准确率才有明显提升但语言模型不能直接输出一段散文再交给下游必须用结构化约束把它变成严格 JSON。我先给一个最精简的调用示意模型服务用你自己部署的或内部网关都行关键是输出格式import json def llm_chat(messages, temperature0.1, max_tokens300): # 调用内部模型服务的封装函数这里省略传输细节 response model_service.chat(messages, temperaturetemperature, max_tokensmax_tokens) return response[content] def analyze_sentiment(review_text): schema_hint ( 返回严格JSON不要输出任何其他文字。 格式{polarity:positive/negative/neutral, score:0.0~1.0, emotions:[satisfied,happy,confused,angry], reason:一句话说明判断依据} ) prompt f{schema_hint}\n评语内容{review_text} raw llm_chat([ {role: system, content: 你是教学评价领域的情感分析助手只做判断、不写评价。}, {role: user, content: prompt} ], temperature0.1, max_tokens300) try: result json.loads(raw.strip()) except json.JSONDecodeError: # 遇到模型输出不标准的情况强制走规则兜底 result rule_based_fallback(review_text) return result这里的温度参数必须压到 0.1 左右不是玄学是为了让多次调用结果尽量稳定。max_tokens给 300 足够因为只输出 JSON 而不是一段话给太多反而容易让模型自由发挥、夹带解释性文字。system角色里我加了“只做判断、不写评价”能有效减少模型输出里附带“这句评语表达了……”之类的前缀。如果模型偶尔返回非 JSON直接把它判定为失败走规则兜底而不是尝试二次解析。因为失败通常是生成序列跑偏了再去拿同一个文本重试往往还是失败的浪费延迟不说还可能把错误结果写入数据库。3.2 用规则修正口语化评语语言模型再强在教育评语这种口语化严重的文本上也会飘。最常见的三类问题是双否定结构、程度词叠加、表情符号和网络梗。规则层不需要覆盖所有语法只需要兜住模型最不稳定的那几个点。NEGATION_WORDS {不, 没, 无, 别, 难, 非, 未, 莫} DOUBLE_NEG_PATTERNS [ (不是, 不好), (没有, 不), (不能, 不), ] def rule_based_fallback(text): polarity neutral score 0.5 # 双重否定检测整体语义会被拉回中性或偏正 for first, second in DOUBLE_NEG_PATTERNS: if first in text and second in text: polarity positive if 难得好 not in text else neutral score 0.6 return {polarity: polarity, score: score, source: rule_fallback} # 口语程度词修正 intensifiers {巨: 0.2, 非常: 0.15, 极其: 0.2, 有点: -0.1, 一般般: -0.15} base_polarity positive if 好 in text or 赞 in text else negative return {polarity: base_polarity, score: score, source: rule_fallback}这段代码只是示意真正落地时键值对会维护得很长。双重否定之所以必须用规则是因为语言模型对“我不是说老师不好”这种句子经常理解成负面人读出来其实是中偏正。这里的判断顺序也重要先查双重否定、再做普通极性匹配否则“不是”会被首轮判成负面。表情符号也得单独处理。评语里“老师讲得不错”后面跟一个笑脸文本内容本身不含情绪词但人一眼就知道是正面。见过不少项目栽在这上面因为预处理阶段把表情符号直接删掉了。正确姿势是转成语义等价词笑脸转成“开心”哭脸转成“难过”再喂给模型。规则层不是技术的倒退它是在给模型减负把稳定可枚举的语言现象先消化掉。3.3 关键参数怎么选温度、长度和批量参数选择这块非常看场景。教育评价情感分析属于“低创造、高稳定性”任务所有生成式参数都要往保守方向调。温度建议固定为 0.1有些团队觉得 0 更稳但实测 0 时部分模型服务端仍会有浮点采样的微小波动0.1 反而能避开某些服务的固定坏点。top_p 需要配合调常见做法是 0.8 到 0.9 之间太高会引入无关词汇太低会使输出结构僵硬。最大长度不用给大评语本身不超过两三百字JSON 输出控制在 200 token 以内足够给长了模型容易在结尾补充废话。批量推理要特别小心。很多人图省事把一千条评语拼进一个 prompt 让模型一次处理性能确实上去了但结果非常不稳定。模型在长上下文里的格式遵循能力会衰减比如第 20 条之后开始丢字段、混序。我一般控制每个 batch 不超过 32 条并且每条之间用编号分隔解析时按编号切分而不是按换行切分这样能减少漏项。如果要用 GPU 推理显存和 batch size 是直接相关的。一个几百 MB 的中等规模模型16G 显存下 batch 开到 8 比较稳开到 16 就可能 OOM。优先保证推理速度稳定的办法不是堆 batch而是把输入文本截断到 128 个 token评语超过这个长度后截掉后半段因为情感判断主要依赖前半段的语气和态度词结尾大多是客套话截掉对准确率影响不大。4. 多智能体协同投票仲裁与结果一致性4.1 三个角色Prompt的设计多智能体的效果好坏八成取决于 Prompt 角色的边界划分和格式约束。情感判别、主题归因、建议提取三个角色的输出目标完全不同Prompt 不能套同一个模板。下面给出我自己常用的角色 Prompt 设计SENTIMENT_ROLE { name: 情感判别员, system_prompt: ( 你是教学评价情感判别员。你的任务只有一项判断评语的整体情感方向。 不要抽取评价对象不要输出建议只回答情感极性和强度。 输出必须严格遵循JSON格式。 ) } ASPECT_ROLE { name: 主题归因员, system_prompt: ( 你是教学评价主题归因员。你的任务是找出这条评语在评价哪些对象 对象限定为教学、内容、平台、互动、作业、环境。 每个对象都要单独给出情感方向允许同时出现多个对象 输出必须严格遵循JSON格式。 ) } SUGGESTION_ROLE { name: 建议提取员, system_prompt: ( 你是教学评价建议提取员。只提取评语中明确的改进建议 没有建议就返回空数组。不要使用评语中的负面情绪 输出必须严格遵循JSON格式。 ) }三个角色之间有个微妙的地方情感判别员和主题归因员的输出可能冲突比如整体极性是负面但“平台美观度”这个对象却是正面的。这种冲突不能丢给下游必须留到仲裁层处理。角色隔离的另一个好处是模型不会被“既要又要”的复杂指令搞糊涂实测拆开之后每个角色的字段完整率都更高了。每个角色的输出示例最好在 prompt 里给一到两条 few-shot。教育评语特有的表达比如“老师水”“作业巨多”“课件很精美”一定要放在示例里模型见过这类口语后输出质量完全两样。4.2 仲裁机制从多数投票到置信度加权仲裁层最容易犯的错是“谁票多听谁的”。教育评语场景里情绪复杂的句子往往所有角色都拿不准这时投票投出来的结果也没有意义。我的做法分三档意见完全一致直接采信不一致但存在多数派按置信度加权完全对立则标注为待人工复核。from collections import Counter def arbitrate(results): results: list[dict], 每个元素包含 polarity, score, confidence polarities [r[polarity] for r in results] counter Counter(polarities) if len(counter) 1: return results[0] # 有明确多数派时用置信度加权后比较 majority_p, majority_count counter.most_common(1)[0] if majority_count len(results) * 0.6: weighted {} for r in results: weighted[r[polarity]] weighted.get(r[polarity], 0) r[confidence] * r[score] final_polarity max(weighted, keyweighted.get) else: # 完全分裂不做自动判断 return {polarity: unknown, need_review: True} avg_score sum(r[score] for r in results if r[polarity] final_polarity) / majority_count return { polarity: final_polarity, score: round(avg_score, 2), need_review: False, confidence: counter[final_polarity] / len(results), }这段仲裁逻辑里有个关键点need_review字段。分裂场景宁可多进人工复核也不要硬给结论。置信度加权比简单多数票更适合教育评语因为负面评语的表达往往更含蓄比如“老师人很好就是讲课重点不清晰”两个角色可能因为侧重点不同给出相反极性直接投票会丢掉信息。为了达到投票效果同一个评语一般让情感判别智能体跑三次再汇总。如果部署资源有限可以把 temperature 从 0.1 稍调到 0.3 做三次独立采样再走仲裁效果可以逼近三个不同模型投票。4.3 效果评估一致性系数与人工抽检多智能体系统上线前必须做一致性评估否则没人敢信它的输出。我常用两个指标跟人工标注的准确率一致性以及多智能体重复调用的稳定性。def cohens_kappa(y1, y2, k): # y1: 人工标注列表, y2: 系统输出列表, k: 类别数 n len(y1) matrix [[0] * k for _ in range(k)] for a, b in zip(y1, y2): matrix[a][b] 1 p0 sum(matrix[i][i] for i in range(k)) / n row_sums [sum(row) for row in matrix] col_sums [sum(matrix[i][j] for i in range(k)) for j in range(k)] pe sum(row_sums[i] * col_sums[i] for i in range(k)) / (n * n) return (p0 - pe) / (1 - pe) if pe ! 1 else 1.0Cohens Kappa 值高于 0.7 说明系统与人工标注达到强一致低于 0.5 就要回去调 Prompt 或规则层。人工抽检不需要全部标注每次随机抽 200 条就够了但抽样时要保证正负样本都有不能全抽正面评语否则算出来的 Kappa 会虚高。稳定性评估更简单取 100 条高复杂度评语每条调用系统三次计算三次结果一致的占比。我见过的项目里这个值通常在 85% 到 95% 之间低于 85% 说明温度或者 prompt 设计有问题需要降温。把一致性测出来的 badcase 喂回 few-shot 示例是目前成本最低的优化手段。提示一致性评估一定要在真实语料上做不要拿人工编的测试集糊弄。真实评语里的口语连招和句式残缺是测试集里永远造不出来的。5. 避坑手册六次真实事故与处理记录5.1 重复评语刷屏导致数据失真现象某门课程一周内收到 400 条评语其中 180 条是同一句话“老师超级棒推荐”正面占比从 62% 飙到 89%。原因清洗阶段没有做文本去重同源评语被多次提交。解决在存储层加唯一键。计算评语文本的哈希值对原始文本先做归一化去空格、统一全半角、小写化再算 MD5 存入review_dedup_hash字段。同一哈希值的评语只在统计里保留第一条后续全部标记为status2即已忽略。如果同一用户对同一门课有多条不同评语也要在业务层限制提交次数。5.2 模型输出格式不稳定导致解析失败现象语言模型在连续调用中出现两次把polarity值返回成“正面”而不是规范的positive解析层直接抛异常。原因模型在低温度下仍然可能受上下文影响改变措辞尤其是提示词里没有给出合法的枚举值范围。解决解析层不信任任何自由文本。先把返回内容strip再用正则提取 JSON 大括号区域解析完后要做枚举校验polarity不在合法值列表内就走规则兜底。规范一点的 prompt 里应该写成“polarity只能是positive、negative、neutral三者之一”这句话能减少一半的格式问题。5.3 一条评语里多个对象只统计了第一个现象评语“课程内容很实用但平台播放器经常卡顿希望优化一下”被标成了“正面”负面信息完全丢失。原因情感判别智能体只输出整体极性没做评价对象拆分导致负面对象“平台播放器”消失在聚合统计里。解决这条最致命但不是模型问题是任务拆分不彻底。必须强制主题归因智能体先抽取对象列表再给每个对象独立输出极性。聚合统计时按aspect_tags展开而不是用整句极性。平台方最想看的其实是“负面对象排行榜”这个榜只有拆对象才能做出来。5.4 双重否定语料被大量误判现象“我不是说老师不好”被标为负面复核时发现有超过 150 条此类误判。原因模型没有理解“不是……不好”的转折逻辑尤其是上下文里出现了负面词“不好”时直接把极性压到负面。解决规则层单独加双重否定检测命中后强制改为中性或微偏正面。同时把这些语料补进情感判别智能体的 few-shot 示例里。最直接的做法是在 prompt 里加一句“注意‘不是…不好’等双重否定结构应视为中立或正面表达”。5.5 长评语截断把核心结论删掉了现象一条 500 字的评语末尾写着“总之不推荐”但预处理时截断到前 256 个 token模型判成了中性。原因截断位置只按长度切没有考虑语义完整性。解决改成按句子边界截断当累计长度接近上限时以句号、感叹号、问号作为结束标志。更稳妥的方案是保留评语的后半段因为中文评语里“总结性结论”经常出现在句尾。我现在的做法是前 128 个 token 取开头后 128 个 token 取结尾中间丢掉这样的情感信息保留度比只取开头高很多。5.6 只看平均分导致极端情绪被稀释现象某门课 10% 的差评情绪很激烈但合并到 90% 的中好评后平均分数仍然好看运营漏掉了真实问题。原因聚合看板只呈现平均分没有看情绪分布。解决看板上增加“负面占比”和“高情绪强度负面条数”两个指标。平均分是位置指标情绪分布是离散指标两个都要看。同时给负面占比设置阈值超过 15% 就触发告警把对应评语列表推送给人审队列。这个告警规则可以在数据库端实现每天晚上跑一次任务即可。6. 上线前必须做的三件验证用可控实验证明系统可信6.1 构建黄金标注集多智能体系统让人不放心的点在于它看起来什么都能说但没人保证说得对。所以上线前第一件事不是调模型而是先做一个黄金标注集。我一般从历史真实评语里抽 300 条覆盖课程难度、平台卡顿、师资评价、作业负荷、正面夸奖五类每一条都由两组人独立标注情感极性和评价对象出现冲突时由第三方裁决最终形成一个“标准答案”。这个黄金集不参与任何模型训练只做评估。它要固定版本号每次模型或 Prompt 有更新都必须先在黄金集上跑一遍算准确率和 Kappa 系数。没有这个基线后面所有的调优都是在黑匣子里乱撞。6.2 三路对照实验黄金集准备好了之后我习惯做一次三路对照纯规则关键词、单个语言模型、多智能体投票仲裁。对比表每次跑出来都会有差异但整体趋势基本稳定方案准确率负面召回率对象级准确率稳定性纯规则关键词61%42%35%100%单语言模型78%70%62%88%多智能体投票仲裁83%78%74%93%单语言模型稳定性反而低于多智能体是因为单模型在复杂评语上经常“自信地错”三次调用可能给出三个不同极性多智能体虽然每次单独调用也有漂移但仲裁把分裂结果标成待复核宁可不答也不硬答。这套对照实验的结论很重要多智能体的价值不完全在提准确率更关键的是把低置信度的样本分离出来让系统知道自己不知道。6.3 漂移监测与每日质检上线后系统不是一劳永逸的。我踩过一个坑模型服务端悄悄升级了版本输出的风格略微变化导致连续一周的负面检出率下跌了 8%但没有任何报错平台运营还以为是课程质量变好了。从那以后我每天凌晨定时跑一遍黄金集计算当天的准确率和分布漂移指标一旦负面占比或调用一致性跌出阈值就当问题处理先回滚模型版本再排查输入数据。日常监测不需要太重的任务流一个定时脚本加一张结果表就够了。每次跑完把准确率、召回率、调用稳定性三个值写入model_daily_check表查询最近七天的曲线就能定位异常发生的时间点。教育评价系统的情感分析是一个偏冷门但业务价值很直接的场景把架构拆开、把流程做稳、把评估做严它就能从“看着能用”变成“真的敢用”。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑