
这篇继续聊SpringAI在线考试系统的成绩管理。上一篇把基础架构、题库、组卷和在线考试流程讲完了成绩这块只留了个尾巴这次把成绩管理的完整实现方案补上。如果你正好在做在线考试系统或者准备接入SpringAI做数据分析和结果生成这篇可以当作一份能直接参考的落地笔记。成绩管理在考试系统里属于那种平时没人提、考试一结束就全员盯着的模块。学生看分数老师看及格率教务处看成绩分布领导看分析报告。不同角色看同一批数据诉求完全不一样。单纯把数据库里的分数捞出来列表展示根本撑不起管理这两个字。所以这个功能的设计重点不在于查分数而在于怎么把分数变成有用的信息。1. 成绩管理功能的设计思路先理清场景。在线考试系统的成绩管理通常要覆盖四个环节分数采集、成绩汇总、异常处理、结果呈现。分数采集在考试结束时就已经完成了大部分客观题系统自动判分主观题可以走人工批改也可以接入AI辅助评分。真正需要单独开发的是后面三部分。成绩汇总看起来简单实际坑很多。一次考试的数据来自不同来源单选题、多选题的得分是逐题累加的填空题有部分匹配的情况主观题可能有多位老师分别打分需要取平均或按权重合并考试中有人断线重连、有人提前交卷、有人违纪被标记。消息提醒Redis 缓存降低重复查询压力定时任务比如考试结束自动触发成绩计算。并发场景要考虑。在线考试系统的成绩计算往往是考试结束瞬时流量大批学生在同一时间点交卷。此时如果每个学生的成绩都实时计算加缓存刷新数据库压力集中在某几秒。我的做法是延迟计算加结果缓存考试结束后不立即算成绩先保存原始答题记录用一个异步任务在几分钟内完成成绩汇总和缓存写入。这样既能缓解瞬时压力也让教师端看到的成绩数据是稳定的。在线考试系统的成绩管理核心是把数据整理好、把异常处理掉、把分析呈现清楚。技术复杂度不算高关键是细节多。下面从数据模型开始拆。2.1 一张成绩表的边界哪些字段该放哪些不该放成绩表的设计容易走两个极端要么只存一个总分要么把跟成绩相关的所有字段全塞进去。我的建议是成绩表只存结果型数据明细型数据放独立的明细表。推荐的最小表结构大约长这样CREATE TABLE exam_score ( id BIGINT PRIMARY KEY AUTO_INCREMENT, exam_id BIGINT NOT NULL COMMENT 考试ID, student_id BIGINT NOT NULL COMMENT 学生ID, total_score DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT 总分, objective_score DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT 客观题得分, subjective_score DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT 主观题得分, rank_no INT NULL COMMENT 排名, score_status TINYINT NOT NULL DEFAULT 0 COMMENT 0-正常 1-待复核 2-缺考 3-违纪, calculator_version VARCHAR(16) NULL COMMENT 计算器版本号, answer_snapshot_url VARCHAR(255) NULL COMMENT 答题快照地址, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, UNIQUE KEY uk_exam_student (exam_id, student_id) ) COMMENT 考试成绩表;总分可以放但客观题分和主观题分建议分开存。原因很实在成绩出来后学生和老师最常问的问题是我客观题错了几道主观题是不是压分了。字段分开查询和统计分析都不用从试卷明细表里二次聚合。实际上字段里我还习惯保存答题快照的URL。这就是在线考试系统特有的需求了。学生申诉我明明提交了怎么显示缺考时需要回溯考试过程中的答题数据。如果把答题明细放Redis或消息队列里成绩表里存一个快照地址排查效率会高很多。不建议在成绩表里存太多跟成绩本身关系不大的东西比如考试开始时间、交卷时间、考试时长。这些字段应该放在考试记录表里。CREATE TABLE exam_session ( id BIGINT PRIMARY KEY AUTO_INCREMENT, exam_id BIGINT NOT NULL, student_id BIGINT NOT NULL, start_time DATETIME NULL, submit_time DATETIME NULL, client_ip VARCHAR(45) NULL, device_info VARCHAR(255) NULL, session_status TINYINT NOT NULL DEFAULT 0 COMMENT 0-进行中 1-已交卷 2-超时强制提交 3-异常中断, UNIQUE KEY uk_exam_student (exam_id, student_id) ) COMMENT 考试会话表;成绩管理和考试会话表是关联关系但职责边界要清楚成绩表负责结果计算会话表负责过程追踪。join查询在需要的时候去做但不要在表设计阶段就合并在一起。这个分离的思路业务上有个实际好处允许人工复核。某次考试结束后如果系统判定某学生有作弊嫌疑管理员可以修改成绩表的状态字段而考试会话记录保持原样留作证据。2.2 成绩明细与阅卷记录的联动除了总分表还需要一张成绩明细表。主观题评分往往需要记录每题得分。一个作文题可以是20分满分学生拿了16分这16分是哪个维度扣的一个简答题回答提到3个采分点分别得了多少分。这些信息不落到明细表里AI辅助评分的意义就大打折扣。CREATE TABLE score_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, score_id BIGINT NOT NULL, question_id BIGINT NOT NULL, question_type TINYINT NOT NULL COMMENT 1-单选 2-多选 3-填空 4-简答, max_score DECIMAL(5,2) NOT NULL, earned_score DECIMAL(5,2) NOT NULL DEFAULT 0, reviewer_type TINYINT NOT NULL DEFAULT 0 COMMENT 0-系统 1-教师 2-AI, review_note VARCHAR(500) NULL COMMENT 评分说明, UNIQUE KEY uk_score_question (score_id, question_id) ) COMMENT 成绩明细表;系统的8个字段只能存关键信息明细表负责支撑可追溯性。主观题如果启用了AI辅助评分reviewer_type字段会标记为2同时可以在review_note里存AI的评分依据。比如回答涵盖条件判断、循环结构、异常处理三个采分点逻辑完整扣2分。这样人工复核时只要看一眼评分备注就能判断AI评分是否合理。客观题部分不需要在score_detail里存太多东西。选择题错了就是错了得分是固定值。如果你需要分析具体哪个知识点失分率高可以在score_detail里冗余一个知识点ID。这个字段是为了给Stats分析用的。假设一道题涉及Java集合框架这个知识点20个学生做错了15个那后续教学重点一下就清晰了。3. SpringAI在成绩管理里的真正位置SpringAI是Java生态里接入大模型能力的一套框架。它提供了统一的接口让应用可以对接不同的大模型服务比如OpenAI格式的接口、通义千问、DeepSeek等。在这套在线考试系统里SpringAI被用在三个地方AI辅助阅卷、成绩分析报告生成、学生个人学情反馈。这里提一下为什么用SpringAI而不是直接调大模型API。直接调API也能实现但SpringAI把提示词管理、模型切换、流式输出、结构化输出这些通用能力做成了抽象层。比如我用提示词模板管理可以把阅卷的评分标准、学科的采分点单独维护在配置中心而不是硬编码在业务代码里。模型厂商更换时只改配置不改代码。这个自由度和可维护性在项目中期体现得非常明显。成绩管理模块的核心不是AI替你做决定而是AI帮你看数据、写评语、生成解释。这可以算是AI辅助决策最务实的用法。3.1 哪些环节真的适合用AI哪些不适合先说结论主观题自动评分、成绩分析报告、个性化评语这三个场景适合客观题判分、成绩排名、总分计算这三个环节不要用AI。客观题判分是确定性逻辑A答案就是A答案答案比对用代码完成比任何大模型都可靠。总分计算是精确算术出错代价太大。成绩排名是排序算法数据库一条SQL搞定。这些环节硬塞AI不仅没有提升效率反而引入了概率性错误的可能。AI助力的主观题评分适合初评辅助设计思路务实AI对一个简答题答案给出参考打分和建议但最终分数需要老师确认。这样核心诉求清晰给老师省量不是替代老师。一个踩过的坑主观题评分时把参考答案、评分标准、学生答案一次性全部丢给提示词要求AI返回分数。结果AI经常被带偏大幅度给学生加分。原因是大模型容易把参考答案当作标准答案然后用相似度去匹配导致回答稍微沾边就给高分。后来的解决方式是分层提示先让AI提取学生答案的采分点再让AI对照采分点逐条评价最后才让AI计算分数。一层一层套AI的注意力就不会跑偏了。3.2 SpringAI结构化输出的处理方案AI返回的不应该是一段自然语言而应该是结构化的对象。用SpringAI的时候可以把输出期望定义成一个Java类。public record SubjectiveScoreResult( ListString pointsCovered, ListString pointsMissing, String overallComment, BigDecimal suggestedScore, String confidenceLevel ) {}定义使用Java record。SpringAI通过提示词中的输出JSON格式说明结合函数回调把模型输出映射成这个类。实际开发中我会用SpringAI的StructuredOutputConverter让模型返回JSON后再反序列化。提示词里写清楚请针对以下主观题答案进行评分 题目{questionContent} 参考答案要点{answerKey} 评分标准{rubric} 学生答案{studentAnswer} 请只返回JSON对象不要包含任何其他文字 { pointsCovered: [覆盖的采分点列表], pointsMissing: [缺失的采分点列表], overallComment: 一句话总评, suggestedScore: 建议分数数字, confidenceLevel: high / medium / low }这个方案有一个容易出问题的地方。如果你不明确限制只返回JSON模型可能会在JSON前后加解释性文字导致JSON解析失败。SpringAI对大多数模型做了输出格式约束但不同类型的模型对JSON Only的遵循程度不一样实测中有的模型偶尔会返回Markdown代码块包裹的JSON。所以我在解析失败时增加了一层容错先按纯JSON解析失败则用正则提取JSON子串。3.3 AI分析报告生成时提示词设计的原则成绩分析报告生成是SpringAI最被称赞的功能模块。考完试以后老师需要一份包含平均分、及格率、分数段分布的班级成绩分析。传统系统能导出数据但把数据转成文字报告需要教务人工完成。SpringAI让数据到文字这一步自动了。提示词设计的原则先给统计数值再给分析要求。不要问AI你觉得这次考试怎么样它会给你一堆正确的废话。要给数据、给上下文、给输出格式。我的提示词模板大致是你是经验丰富的中学数学教师请根据以下统计数据生成班级考试分析报告语气客观专业。 考试名称{examName} 班级人数{totalStudents} 参考人数{participants} 平均分{averageScore} 最高分{maxScore} 最低分{minScore} 及格率{passRate}% 分数段分布{scoreDistribution} 失分率最高的三个知识点{weakKnowledgePoints} 请在报告中包含 1. 整体成绩概况不超过100字 2. 主要问题分析结合失分知识点展开 3. 针对薄弱环节的教学建议 4. 重点关注学生的数据不超过50字注意分数段分布这块不应该只给一个60-70分有10人的死数据而是给一个自然语言描述。AI模型处理自然语言的能力要远远好于处理原始JSON数据的稳定性和可读性。当然如果你用SpringAI的structured output来生成报告结构也能处理但报告这种东西的语义灵活性更重要我用自然语言拼接的方式反而效果更稳定。4. 核心功能实现汇总、查询、导出与AI分析成绩管理的后端接口按照用途分四大类成绩汇总、成绩查询、成绩导出、AI分析。下面直接给出实现方案和关键代码。4.1 成绩汇总的实现异步任务加状态机考试结束那一刻系统需要把每个学生的答题记录变成成绩。这个动作我建议用异步任务完成。触发时机有三个定时轮询考试结束时间超过5分钟后触发、交卷事件触发最后一名学生交卷时触发、手动触发管理员强制结束考试时触发。汇总逻辑的核心代码结构Service public class ScoreCalculationService { private final ExamSessionRepository sessionRepository; private final AnswerRecordRepository answerRepository; private final ScoreRepository scoreRepository; private final ScoreCommandService commandService; Async(scoreExecutor) public void calculateExamScore(Long examId) { // 1. 防止重复计算 if (calculateLock(examId)) { throw new IllegalStateException(考试成绩计算任务已在进行中); } // 2. 拉取所有已完成考试的会话 ListExamSession sessions sessionRepository.findFinishedSessions(examId); // 3. 逐学生计算 for (ExamSession session : sessions) { try { calculateOneStudent(examId, session); } catch (Exception ex) { log.error(成绩计算失败 studentId{}, examId{}, session.getStudentId(), examId, ex); // 记录失败任务后续可以补偿重试 commandService.markScoreFailed(examId, session.getStudentId()); } } } }为什么强调防重复计算和逐学生try-catch因为批量计算里一条数据异常会导致整个事务回滚其他学生的成绩也全没了。改成逐条处理异常单条失败只标记失败状态整体任务继续推进。至少这个实践让系统在真实考试中经受住了几百人同时交卷的冲击。工欲善其事必先利其器。成绩计算任务用异步线程池执行时线程池参数也要合理。我用的参数如下spring: task: execution: pool: max-size: 8 core-size: 2 queue-capacity: 100 thread-name-prefix: score-worker-考试结束20分钟后成绩还没出来等待的用户情绪肯定不会好。所以成绩计算的耗时一定要快核心逻辑不能做过度复杂的循环查询。批量拉取所有答题记录到内存用Map按学生分组再逐个计算。数据库不复杂查询性能就会好很多。4.2 成绩查询接口分页、筛选、排序一个都不能少成绩查询接口是给教师端和学生端共用的。接口设计遵循统一返回结构查询条件用参数对象接收。RestController RequestMapping(/api/scores) public class ScoreController { private final ScoreQueryService scoreQueryService; GetMapping(/exam/{examId}) public ResultPageResultScoreVO queryScores( PathVariable Long examId, RequestParam(required false) String keyword, RequestParam(required false) Integer minScore, RequestParam(required false) Integer maxScore, RequestParam(required false) Integer scoreStatus, RequestParam(defaultValue 1) int page, RequestParam(defaultValue 20) int size) { ScoreQuery query ScoreQuery.builder() .examId(examId) .keyword(keyword) .minScore(minScore) .maxScore(maxScore) .scoreStatus(scoreStatus) .page(page) .size(size) .build(); return Result.success(scoreQueryService.queryScores(query)); } }分页查询这里有个容易被忽略的细节。如果系统里考试结束后的成绩是异步计算的那么教师在成绩还没生成完毕时点查询会查到一个空列表。为了避免这个误解查询接口里可以加一个状态字段表明成绩计算中还是成绩已完成。我在ScoreVO里加了一个examScoreStatus字段这样前端就知道该显示暂无数据还是正在计算请稍后刷新。筛选条件里的minScore和maxScore是两个独立参数单独过滤时用。比如我想查80分以上的学生只用minScore80。如果想查60到70之间的学生就两个参数都传。实现用MyBatis-Plus的LambdaQueryWrapper就可以了不需要单独写SQL。分页排序的默认规则一定要清楚按总分从高到低排列总分相同的按用时从短到长排列。排名可以单独算但下游依赖排名做筛选。排名计算用窗口函数比程序里逐条比较靠谱。MySQL 8.0以上的窗口函数写法SELECT student_id, total_score, RANK() OVER (PARTITION BY exam_id ORDER BY total_score DESC) AS rank_no FROM exam_score WHERE exam_id #{examId}这个排名是按考试分区的同一个学生在不同考试的排名互不影响。RANK()和DENSE_RANK()的差别要注意RANK在有并列成绩时会出现并列第一名后直接跳to第三名的情况。要不要跳号取决于业务需求。我个人倾向于用RANK因为成绩单上两个并列第二名比一个第三名更直观。4.3 成绩导出用Excel模板而非直接生成全表成绩导出功能教师端几乎每天都会用到。大部分在线考试系统的导出方案是用EasyExcel或POI把数据写到Excel文件里。实现不难关键是导出的内容结构。我的方案是分三个SheetSheet1-成绩汇总学号、姓名、班级、客观题得分、主观题得分、总分、排名、状态。Sheet2-题目明细题号、题型、满分、得分、知识点、是否答对。Sheet3-成绩分析平均分、最高分、最低分、及格率、分数段分布、知识点失分率。每个Sheet对应一个不同的维度教师拿到一个Excel文件就能完成大部分统计上报工作。实际写代码时Excel导出需要异步生成。考试结束后几千个学生同时点击导出同步生成Excel文件会造成长时间接口等待。我的队列方案是导出请求先落库返回一个导出任务ID后端异步生成文件生成完毕后通过WebSocket推送通知前端下载。文件生成临时存储48小时后清理。这套方案实测很稳不会有Excel文件积压的问题。生成Excel的代码片段public void exportScoreReport(Long examId, OutputStream outputStream) { ExcelWriter writer EasyExcel.write(outputStream).build(); // 成绩汇总 WriteSheet summarySheet EasyExcel.writerSheet(0, 成绩汇总) .head(ScoreSummaryRow.class) .build(); writer.write(scoreQueryService.listSummaryRows(examId), summarySheet); // 题目明细 WriteSheet detailSheet EasyExcel.writerSheet(1, 题目明细) .head(ScoreDetailRow.class) .build(); writer.write(scoreQueryService.listDetailRows(examId), detailSheet); // 成绩分析 WriteSheet analysisSheet EasyExcel.writerSheet(2, 成绩分析) .head(ScoreAnalysisRow.class) .build(); writer.write(scoreAnalysisService.buildAnalysisRows(examId), analysisSheet); writer.finish(); }单元格样式无需过度定制但有几列需要用格式化。分数列保留两位小数知识点失分率显示为百分比。表格样式加一个自动列宽。导出需求优先级是内容完整其次才是美观。4.4 SpringAI成绩分析接口从统计到文字的转换成绩分析接口是该系统的特色功能。调用SpringAI代码时业务侧先做统计拿到一个统计数据对象然后把这个对象传给AI提示词生成报告。这样设计能确保AI分析是基于准确数据的而不是模型自己猜出来的。核心代码Service public class ScoreAnalysisAiService { private final ChatClient chatClient; private final PromptTemplateFactory templateFactory; public ExamAnalysisReport generateAnalysisReport(Long examId) { ScoreStatistics stats scoreStatisticsService.collectStatistics(examId); Prompt prompt templateFactory.createAnalysisPrompt(stats); String aiText chatClient.call(prompt); ExamAnalysisReport report ExamAnalysisReport.builder() .examId(examId) .statistics(stats) .aiGeneratedContent(aiText) .createdAt(LocalDateTime.now()) .build(); return report; } }AI生成的报告先存数据库然后前端渲染。流程上要避免每次打开页面都重复调用大模型接口这既浪费token也增加延迟。成绩分析报告一旦生成除非统计结果变化否则不重新生成。我加了个manual button重新生成如果老师认为AI报告质量不高可以手动触发一次更新。AI报告生成还可以做进一步优化。报告里有一段重点关注学生可以自动列出排名靠后、成绩波动大的学生并附上AI生成的简要评语。这部分需要在提示词里传入学生历史成绩数据。实际效果还不错教师反馈说这比手工翻成绩单方便很多。5. 前端展示与交互设计要点后端功能再完善前端展示不好成绩管理体验还是差。在线考试系统的用户包括学生、教师、管理员三者的界面诉求完全不同。5.1 学生端成绩页分数不是重点错题才是学生考完最关心的不是分数是我错在哪。成绩页除了展示总分一定要展示每道题的得分情况尤其是主观题的得分点和得分理由。页面结构大概是这样顶部总分卡片展示成绩和排名用进度条展示客观题和主观题得分占比。下面按题型分tab单选题列表展示题目、选项、正确答案、我的答案、是否正确。简答题展示参考答案、我的答案、AI或教师评分、评分说明。这里的交互细节很多。学生点查看详情时展开一张答题卡所有题目缩略图排列。答对的题绿色背景答错的红色背景半对的黄色背景。一眼扫过去薄弱题型立刻清晰。提交时间早于考试结束时间的学生会有一个提前交卷标签。这对学生来说代表高效对老师来说则可能代表敷衍。前端不做价值判断只展示客观信息。5.2 教师端成绩分析页图表不是装饰是决策依据教师端成绩分析页是数据展示最复杂的页面。核心模块包括总体指标卡平均分、最高分、最低分、及格率、参考人数、标准差。分数段分布图柱状图展示各分数段人数。知识点失分率雷达图或水平条形图展示各知识点的失分比例。成绩趋势对比多次考试的平均分变化。这里说一个细节图表选择要克制。分数段分布适合柱状图知识点失分率适合横向条形图多次考试对比适合折线图。不要为了炫技术搞太多复杂的图表类型信息密度高且一眼看懂才是关键。前端实现用ECharts就足够但ECharts的配置项比较灵活建议封装一个组件来统一处理数据和option的转换。比如分数段分布后端返回一个List 前端把它转换成xAxis的分数段标签和yAxis的人数。接口层面直接返回图表友好的数据结构会减少前端的逻辑处理。5.3 AI分析报告的展示保留机器生成的痕迹AI生成的成绩分析报告展示时最好加一个AI生成仅供参考的提示。这既是对教师使用AI结果的提醒也明确了责任边界。AI报告生成后教师可以手动编辑或删除但原报告保留历史记录。实际操作中AI报告是教师认可的但个别措辞偏主观教师会手动润色后发布给学生。报告展示组件用Markdown渲染即可。SpringAI生成的报告是自然语言文本老师编辑后以富文本保存。后端保存一篇JSON包含原始报告、人工修改稿、状态字段。这样改版有迹可循方便审计。6. 常见问题与排查实录成绩管理功能开发过程中遇到的问题不少挑几个有代表性的记录一下。6.1 并发交卷时成绩计算出现重复记录现象考试结束瞬间同一学生的成绩记录出现了两条总分一致但ID不同。原因交卷事件触发成绩计算同时定时任务也检测到考试结束两个任务同时执行了同一学生的成绩写入逻辑。数据库没有做唯一索引校验。解决办法在exam_score表上加了UNIQUE KEY uk_exam_student (exam_id, student_id)并在代码里增加了任务级别的互斥锁。分布式环境可以用Redis的SetNX或者数据库锁表。单机部署时用并发控制即可。实际排查中还需要注意成绩计算任务里不要用先删除后插入的逻辑因为这会影响成绩记录的历史审计。用INSERT ON DUPLICATE KEY UPDATE或者先查后改保持幂等。6.2 AI主观题评分出现高置信度低准确性现象AI给一道完全答非所问的题打了8分confidenceLevel却是high。原因提示词里的评分标准描述太笼统没有明确答非所问的扣分规则。模型的主观判断和人类评分预期存在偏差。解决办法评分标准细化到具体情景比如完全偏离题目要求得分不超过满分的10%。增加降级机制如果AI给分与教师抽检结果偏差大于阈值自动标记为待人工复核。设置抽检规则每批次AI评分的主观题按10%比例抽检交给老师复核。AI辅助评分的核心目标是减少老师的工作量而不是完全替代。降级机制是重要的兜底手段。6.3 成绩导出Excel中文文件名乱码现象下载的Excel文件名为_成绩汇总.xlsx打开后中文乱码。原因HTTP响应头里的Content-Disposition默认只支持ASCII编码中文文件名需要URL编码。解决办法String fileName URLEncoder.encode(成绩汇总.xlsx, UTF-8); response.setHeader(Content-Disposition, attachment; filename\ fileName \);前端拿到文件名后再解码显示。这个坑几乎每个导出功能都会遇到记下来下次直接用这个方案。6.4 AI报告生成耗时太长导致接口超时现象生成成绩分析报告时AI接口有时候需要30秒以上前端接口6000ms超时导致生成失败。解决办法AI报告生成改为异步流程。前端点击生成报告后端先返回任务已开始然后通过WebSocket通知前端报告生成状态。这样用户不会在页面上干等体验会好很多。AI接口响应慢时还可以通过超时重试或者降低生成报告的字数来缩短响应时间。7. 写在后面成绩管理的一点体会成绩管理模块做完之后我对在线考试系统这个项目的理解深了一层。这个系统不只是一个在线答题工具它需要对考试数据做完整闭环管理。答题结束只是起点成绩计算、分析、反馈才是把考试的价值释放出来的关键。SpringAI在这个闭环里的角色是文字生成器和辅助评阅者。它本身不承担计算职责但帮两个角色节省了大量重复工作老师不需要再逐题手工批改主观题不需要再写长篇大段的考试成绩分析报告。教务管理的工作效率提升明显这个价值可以直接量化。最后分享一个小的调优技巧。AI生成报告里的重点关注学生建议最初是直接让AI基于学生列表选人结果AI总喜欢选分数最低的。后来改为先在代码里算出成绩波动最大和较上次退步明显的学生列表再让AI对这些学生逐个生成描述。AI不负责挑人只负责写人话。这个思路也可以推广到其他AI的业务场景让代码做确定性的事情让AI做非确定性的表达。边界分清楚系统的稳定性和AI的智能感就都有了。