资讯动态

基于AIGC的在线编程评测系统:架构、核心实现与部署避坑指南

发布时间:2026/10/26 17:46:08 来源:尧图企业网站定制
简介基于AIGC的在线编程题目评测系统是一套面向编程学习者、教育者及竞赛选手的开源级解决方案重点解决传统在线判题系统中题目同质化、反馈滞后与个性化缺失等问题。平台整合AI生成内容技术支持按用户技能水平动态生成编程题目覆盖基础语法到复杂算法代码自动评测不仅校验结果还检查编码风格与性能指标实时反馈帮助学习者快速修正错误学习路径推荐则依据历史表现定制进阶路线。压缩包共61个文件以46个Java源码文件为核心辅以XML配置、YAML环境配置、SQL初始化脚本、Maven封装器及说明文档整体仅81KB轻量易部署适合快速搭建与二次开发。目前已有53人学习下载。资源附赠说明文件与配套文档目录结构清晰有助于理解系统在认证授权、数据缓存、异步消息处理等模块的实现细节可作为相关课程设计或毕业设计的参考原型。1. 基于AIGC的在线编程评测系统出题、评测、反馈一条链路打通“录题一小时做题五分钟评卷再看一小时”这是大多数自建OJ的人绕不过去的坎。基于AIGC的在线编程题目评测系统把编程练习平台最重的三件事——题目生成、代码自动评测、实时反馈——串成一条自动链路。它面向两类人一是想给学员搭建练习平台的高校老师或培训机构二是接手了类似项目、需要快速看懂代码结构和部署方案的开发者。系统内集成的AIGC出题、代码自动评测、学习路径推荐等模块解决的是“从出题到批改都有人工介入”的痛点。这篇拆解会按模块展开技术选型、核心实现、参数配置和部署时常见的坑都会覆盖照着复现一遍比直接啃源码效率高得多。2. 系统架构与技术选型六个模块和核心依赖怎么配2.1 模块划分为什么评测和出题必须拆成两个服务先讲整体架构。这套系统按业务边界拆成六个模块题目服务、评测服务、用户认证、学习路径推荐、数据缓存、异步消息。第一次看代码的人最容易问的问题是为什么题目生成和代码评测不放在同一个服务里原因有两个。第一两个请求的耗时模型完全不同。题目生成要调用AIGC接口一次完整的生成流程包括构造Prompt、请求大模型、解析返回的JSON、校验测试用例正常情况20秒打底模型负载高的时候可能到60秒。而代码评测是高频操作用户提交代码后要求是2到5秒内给出判定结果。如果这两个接口放同一个业务链路里后台线程池会被出题请求占满评测按时反馈就不现实。常见的做法是把题目生成接口设计成异步任务提交后立刻返回任务ID生成完成后再通知前端评测接口虽然由用户提交触发但真正执行评测的也是异步消费者。第二两者的扩缩容策略不同。出题服务是CPU密集型和外部API密集型瓶颈在AIGC接口的速率限制和响应时间评测服务是计算密集型和IO密集型瓶颈在沙箱的并发数和磁盘IO。两者要监控的指标完全不一样拆开之后可以各自独立扩缩容也方便故障隔离——AIGC接口挂了不至于影响已经开始的评测任务。整个系统的调用链路大概是这样用户通过前端提交题目生成请求题目服务构造Prompt调用AIGC接口拿到题目和测试用例后写入MySQL并把题目ID放入RabbitMQ队列提交代码时评测服务接收请求先查Redis缓存里有没有这道题的测试用例没有就从MySQL读然后调度Docker沙箱执行编译和测试最后通过WebSocket把每一条测试用例的判定结果推给前端。提示如果只是个人练手Demo可以不分这么细。但只要你打算把它部署到公网供多人使用拆服务和引入消息队列这一步不能省不然后面加功能、扩并发都会很痛苦。2.2 技术栈选型Spring Boot、Redis、RabbitMQ、Docker的配合关系技术栈基本是Java后端最常见的组合Spring Boot 2.7JDK 11、MySQL 8.x、Redis 6.x、RabbitMQ 3.x、Docker 20.x、Maven 3.8。前端用的是Vue 2 Element UI前后端分离通过Nginx做反向代理。各组件分工Spring Boot提供REST API、WebSocket端点、定时任务调度管理MySQL存用户、题目、提交记录、学习路径元数据Redis做五类缓存——热点题目、测试用例、用户会话、做题计数、排行榜RabbitMQ承载两类异步消息——题目生成任务、评测任务Docker评测沙箱编译执行用户代码Nginx静态资源服务、WebSocket反向代理、限流这套组合的选型理由很直接Spring Boot生态里WebSocket、JWT、MyBatis-Plus的整合都是现成的Redis可以同时解决缓存和限流RabbitMQ的消息确认机制比简单Redis队列可靠Docker沙箱则是有评测功能的OJ绕不开的隔离方案。如果你问为什么不用Netty或者Go写评测服务答案是这套系统整体是Java技术栈评测模块用Docker隔离之后Java和Go在性能上的差距远小于维护成本上的差距保持单一技术栈才是长期迭代最划算的选择。2.3 数据库设计与缓存落地三道缓存各自防的是什么核心表有五张user用户、problem题目、problem_test_case测试用例、submission提交记录、learning_path学习路径。比较关键的设计是problem表里有一个generation_source字段标记这道题是AIGC生成还是人工录入方便后续统计AIGC题目的正确率。没有这个字段你会发现后期想评估AIGC出题质量时无从下手。测试用例单独拆一张表不在problem表里存一个大JSON。原因很实际评测时只需要按题目ID批量读取用例数据而且测试用例经常要单独修正——AIGC生成的边界用例偶尔不准确拆成表可以直接改单条不用把整个题目的大JSON拉出来反序列化再写回去。submission表要建联合索引(problem_id, status)因为“某道题的正确率”是统计报表里最常用的查询。缓存设计上我一般会在三层做题目列表缓存存题目ID、标题、难度、标签这些列表展示字段key为problem:list:page:{page}:{size}过期时间5分钟。这里只缓列表不缓详情避免大对象反复进出Redis。测试用例缓存key为testcase:{problemId}存JSON序列化后的测试用例集合过期时间12小时。这道缓存直接影响评测速度命中后不用每次评测都查MySQL。提交记录缓存只缓存最近一个小时的提交用于实时反馈页面的展示和统计类接口。缓存要防两个经典问题缓存穿透和缓存雪崩。穿透用布隆过滤器挡把所有合法题目ID放进去请求来的时候先判断ID是否存在不存在直接返回空不打数据库雪崩通过给缓存过期时间加一个随机偏移量来解决比如基础5分钟实际过期时间在5到7分钟之间随机取防止大量缓存同时失效打到数据库。配套的还有一个兜底方案Redis故障时接口层加一个本地缓存二级缓存能扛住短时间的Redis抖动。3. AIGC题目生成与代码自动评测核心链路怎么落地3.1 题目生成的Prompt模板与参数设定AIGC出题是本系统最特殊的一块核心是Prompt模板和参数控制。代码包里提供的Prompt模板是这样的private String buildPrompt(String topic, Integer difficulty, String language) { String jsonFormat {\title\:\题目标题\,\description\:\题目描述\, \inputFormat\:\输入格式\,\outputFormat\:\输出格式\, \samples\:[{\input\:\样例输入\,\output\:\样例输出\}], \testCases\:[{\input\:\用例输入\,\output\:\用例输出\, \type\:\normal|edge|large\}],\tags\:[\标签1\,\标签2\]}; return 你是一位竞赛编程出题专家。请生成一道 difficulty 难度的编程题 知识点 topic 语言 language 。\n 要求题目描述清晰有完整的输入输出格式说明 至少包含5组测试用例其中至少1组边界用例1组大数据用例。\n 严格按以下JSON格式输出不要输出任何额外文字\n jsonFormat; }这段代码做的事情很明确把知识点、难度、语言三个参数拼进Prompt并且用一段JSON示例约束模型的输出结构。参数说明difficulty建议取值easy/medium/hard对应难度标记language用于让出题模型知道目标编程语言影响题目描述里样例代码的偏向性。jsonFormat这个字符串里的转义很关键如果引号漏了一个模型返回的JSON就会带额外字符解析阶段直接失败。调用AIGC接口时我常用的参数是temperature设0.7到0.9之间。出题需要一定随机性太低了会反复生成雷同题目太高了容易跑题。max_tokens设1024到2048。一个题目带5组测试用例1024够用但如果你要求模型额外输出题目解析建议2048。top_p设0.9配合temperature使用稳定输出格式。生成结果回来后不能直接入库必须先做三道校验第一是否能解析成合法JSON第二samples和testCases数组是否非空第三测试用例的input和output是否都非空。模型吐出来的测试用例缺output的情况我见过很多次直接入库的话后面整个评测服务都会跟着翻车。校验不通过就重新生成一次重试两次仍失败就把题目标记为failed并转人工。这里重试的体会是超过两次几乎不会成功继续重试既浪费额度又拖长等待时间。3.2 评测沙箱编译执行与资源限制的实现评测沙箱是本系统里安全要求最高的模块。用户提交的代码要在隔离环境里运行绝不能在宿主机上直接执行。常见的做法是Docker容器加资源限制启动一个预装的编译运行镜像用docker run命令执行编译和测试。核心执行逻辑docker run --rm \ --network none \ --memory 256m \ --cpus 1 \ --pids-limit 64 \ -v /tmp/judge/$TASK_ID:/workspace \ -w /workspace \ --timeout 5s \ judge-image:1.0 \ /bin/sh -c gcc main.c -o main ./main input.txt命令里的参数逐一说明--network none表示容器无网络防止用户代码外联--memory 256m限制内存256MB超过直接OOM杀掉--pids-limit 64限制最大进程数防止fork炸弹--timeout 5s是最外层超时保护编译加运行一共5秒挂载目录把待判代码和输入数据交进去工作目录切到/workspace。这个镜像里预装了gcc、python3、openjdk-11等运行时按题目要求的语言选择对应的编译命令。这里有一个反复会被提到的点不要直接在容器里跑用户程序之后又用宿主机上的time命令看时间。受容器隔离和系统调度影响宿主机上看到的时间会偏大而且不可复现。正确做法是在容器内部测时间或者在评测程序里用clock_gettime采集进程自己的CPU时间。代码包里我用的方案是在容器内写一个wrapper脚本在脚本里用ulimit -t 2限制CPU时间再运行用户程序。这样限制的是CPU时间而不是墙钟时间对多核机器上的竞赛题评测更公平。评测流程分两步先把测试用例逐个从Redis或MySQL读出来写入input.txt文件然后给每个测试用例启动一个容器执行把返回码、输出内容、运行时间收集起来最后拿标准输出和expected output做比对注意要忽略行尾空格和最后空行这个规则可以在系统配置里开关。比对策略要区分两种字符串完全匹配适合精确输出类题目特判评测适合浮点数误差或答案顺序无关的题目代码包里两种都实现了。3.3 实时反馈链路WebSocket推送评测状态评测过程用户要实时看到进展这是体验的核心。前端提交代码后后端把评测任务交给RabbitMQ同时建立WebSocket连接前端根据任务ID订阅评测状态变化。WebSocket配置要点Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws/judge) .setAllowedOriginPatterns(*) .withSockJS(); } Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker(/topic/judge); registry.setApplicationDestinationPrefixes(/app); } }这个配置的意思是前端连接/ws/judge端点建立WebSocket连接enableSimpleBroker指定/topic/judge为服务端主动推送的前缀setApplicationDestinationPrefixes指定客户端发消息时的前缀。评测状态推送时topic按任务ID做动态拼接比如/topic/judge/{taskId}。动态topic的好处是不同用户提交的任务互不干扰A用户能看到B用户的评测状态这种串号问题不会出现。推送的状态包括QUEUED排队中、COMPILING编译中、RUNNING运行中、FINISHED完成。前端收到FINISHED后展示每个测试用例的通过情况和运行时间。RUNNING状态里还会附带当前正在执行第几条用例这样用户能直观看到5组用例一组一组在跑体验上比干等要好很多。状态机里QUEUED到COMPILING的切换由消费者在真正开始编译前推送如果队列积压严重用户会一直停在QUEUED这时候前端要显示排队序否则用户以为系统卡死了。提示如果部署环境是Nginx反代WebSocket很多项目会在这里翻车。Nginx必须开启Upgrade和Connection请求头转发否则浏览器始终连不上/ws端点表现为前端一直停留在“排队中”。这个细节在第5章避坑里会再展开。4. 用户认证、学习路径推荐与异步消息外围功能的关键实现4.1 JWT认证与权限控制实现用户认证用的是JWT Spring Security。实现上核心是自定义一个JwtAuthenticationFilter每次请求头带着Authorization: Bearer token过滤器校验签名和过期时间通过后把用户信息塞进SecurityContext。JWT的载荷里建议只放userId、username、role三个字段不要放其他业务数据。原因很简单JWT是服务端签名的一旦签发不能主动失效放进去的字段越多用户信息变更后越容易读到脏数据。比如用户改了昵称token里还是旧昵称前端展示就会出问题。如果你的系统要支持踢人下线JWT就不够用了得引入Redis维护一个token黑名单但这套系统没做这块属于可以接受的取舍。token过期时间按角色区分普通用户12小时管理员8小时。登录接口返回accessToken的同时把用户的基本信息和角色一起返回避免前端为了拿角色信息再调一次/user/info接口。这里有个经验token刷新机制不要做太复杂短过期加重新登录比做refresh_token流程在运维上省心得多。权限控制上给AIGC出题接口单独开一个角色teacher_admin。管理员可以调用出题接口普通用户只能做题不能出题。控制通过Spring Security的PreAuthorize注解实现PreAuthorize(hasAnyRole(ADMIN, TEACHER)) PostMapping(/api/problem/aigc/generate) public ResultLong generateProblem(RequestBody GenerateRequest request) { // 校验知识点、难度、语言参数 // 构造Prompt并提交到RabbitMQ异步队列 // 返回任务ID前端轮询或通过WebSocket等待结果 }PreAuthorize的hasAnyRole会在方法调用前做权限校验不匹配直接返回403。这里要注意的是Spring Security默认的角色前缀是ROLE_所以数据库里存的角色字段必须是ROLE_ADMIN这种格式或者在配置里手动指定前缀否则权限判断永远不通过。这个问题在本地跑通、部署到生产环境后才暴露的现象是管理员账号登录成功但调出题接口一直返回403日志里也没有明显报错。4.2 学习路径推荐基于做题记录的标签权重算法学习路径推荐是本系统里最不炫技但最实用的一块。设计思路是每道题有若干标签如“动态规划”“双指针”“二分查找”用户在系统里的每一条提交记录被判定为AC后认为用户掌握了该题的知识点标签。推荐的算法逻辑用伪代码描述def recommend_next(user_id, top_n3): # 1. 取出用户最近已AC题目的标签分布 tags get_ac_tags(user_id) # 2. 对每个标签维护一个掌握度得分正确次数越多得分越高 scores {tag: count_ac(tag) for tag in tags} # 3. 从题目库中找包含这些标签但用户未做过的题 candidates find_problems_by_tags(scores.keys(), exclude_doneuser_id) # 4. 按标签匹配度和难度梯度排序优先推荐难度比用户平均AC难度高一级的题 candidates.sort(keylambda p: ( -sum(scores.get(t, 0) for t in p.tags), abs(p.difficulty - (user_avg_difficulty 1)) )) return candidates[:top_n]逻辑说明第一步统计用户已掌握的知识点第二步用AC次数近似掌握度只统计AC的题目WA的不计入因为WA可能是一次性失误不代表完全没掌握第三步排除已做过的题防止推荐重复内容第四步排序的key同时考虑标签匹配度负号表示得分越高越靠前和难度梯度推荐比平均难度高一级的题用abs保证不过度跳跃。用户平均难度这个值从提交记录里实时算缓存10分钟避免每次推荐都全表扫提交记录。这个算法的实现成本低但效果比随机出题好很多。代码包里的learning_path_scheduler是个定时任务每天早上6点根据前一天新增的提交记录重新计算所有活跃用户的推荐结果并写入Rediskey为path:recommend:{userId}过期时间24小时。用户每次进首页推荐接口直接读缓存响应时间在10毫秒以内。夜间计算的好处是推荐过程本身不占用在线接口的CPU资源白天用户访问时只读缓存体验稳定。当然这是一个能用的朴素版本。想要更准需要引入IRT或者协同过滤但那种方案要维护用户-题目评分矩阵数据量不大时收益有限。我建议先跑朴素版本积累一段时间真实做题数据后再考虑升级。等到题库有几千道、用户有几百个的时候再迁移到协同过滤也不迟数据都在submission表里迁移成本不高。4.3 异步消息处理评测任务的RabbitMQ流转RabbitMQ在本系统里承担两个队列problem_generate_queue题目生成任务、judge_queue评测任务。这里重点讲judge_queue因为它是评测流程的发动机。评测任务流转前端提交代码后评测Controller把任务封装成JudgeTask对象含submissionId、problemId、language、code然后发送到judge_queue接口立刻返回“已提交等待评测”。真正的编译、测试、判定全部由消费者在另一个进程里完成消费完之后通过WebSocket推送结果。发送端代码大致如下Autowired private RabbitTemplate rabbitTemplate; public void submitJudgeTask(JudgeTask task) { rabbitTemplate.convertAndSend( judge.exchange, judge.routing.key, task, message - { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); message.getMessageProperties().setExpiration(60000); return message; } ); }convertAndSend的参数顺序是交换机、路由键、消息体。setExpiration(60000)给消息设置60秒TTL意思是这条评测消息如果60秒内没被消费就自动丢弃。这个TTL必须设因为AIGC出题任务可以允许排队几分钟但用户提交的代码评测任务等不了那么久超时的消息留着只会造成积压堆积。PERSISTENT投递模式也要设否则RabbitMQ在持久化消息时不会把这条消息落盘服务重启后消息就丢了。消费者挂了消息会重回队列但如果服务重启且没有持久化未消费的消息全部消失用户那边就永远停在“排队中”。消费者端处理要点RabbitListener(queues judge.queue) public void onJudgeTask(JudgeTask task, Channel channel, Message message) throws IOException { try { judgeService.execute(task); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); } }消费者的逻辑是执行评测成功就basicAck确认失败就basicNack且requeue参数传false。这里的false意味着消息不会重新入队而是进入死信队列避免一条坏消息无限重试拖垮整个消费者线程。死信队列我单独建了judge.dead.queue方便事后排查失败原因。死信队列里的消息要人工定期处理比如重新提交或者标记失败不能只积压不处理否则会掩盖系统性问题。我一般写一个定时任务统计死信队列堆积量超过阈值就告警。5. 部署与避坑指南最容易翻车的五个环节5.1 沙箱逃逸与权限配置现象评测容器里用户代码能访问宿主机上的敏感文件或者能向外发起网络请求。原因docker run只加了--network none还挡不住挂载卷里的敏感信息泄漏而且如果Docker守护进程本身是以root身份跑的容器内的进程在某些配置下有权限利用漏洞逃逸到宿主机。我见过真实案例用户在代码里执行cat /etc/hostname直接拿到了宿主机的主机名。解决评测容器统一加--read-only参数把根文件系统设为只读需要的临时目录单独挂载tmpfs容器内运行用户程序时用nobody用户身份而不是容器默认的root方法是在Dockerfile里加USER nobody或者在docker run加--user 10001:10001。Docker服务本身建议配置不让前端直接操作宿主机Docker socket把socket文件权限收紧或者用rootless模式。这个环节没有绝对安全只能层层加限制降低风险。还有一个我常用的习惯评测完立刻删除容器不留容器复用避免多个用户代码共享同一个残留容器状态。5.2 Redis缓存穿透与雪崩现象某个不存在的题目ID的请求大量打到MySQL或者缓存同一时间全部过期数据库连接被打满接口响应从10毫秒变成2秒。原因穿透是因为异常请求拿了一个不存在的ID缓存查不到就去数据库查数据库也没有于是每次都打库缓存形同虚设雪崩是因为所有缓存的过期时间设成了同一个值比如固定5分钟那同一时刻全量过期数据库瞬间被淹没。解决穿透用布隆过滤器挡在前面把所有合法题目ID提前存入过滤器查询前先判断雪崩给过期时间加随机偏移我实际用的代码是int baseExpire 300 ThreadLocalRandom.current().nextInt(120); redisTemplate.expire(key, Duration.ofSeconds(baseExpire));这个方案把过期时间从固定300秒变成300到420秒随机大量key不会再同一时刻集体过期。如果你的场景里Redis挂了还要在接口层加一个本地缓存兜底防止Redis故障时直接打到MySQL。另外要注意的是布隆过滤器本身的误判率如果误判率太高可以把bit数组长度设大一点一个常用公式是预计元素数量乘以20误判率能控制在1%以下。5.3 异步消息丢失消息确认现象RabbitMQ消费者偶发丢失评测消息表现是用户提交代码后永远停在“排队中”日志里没有任何异常。原因消费者处理消息时异常退出或者代码里调用basicAck之前进程崩溃消息没有被确认RabbitMQ默认会自动重回队列但如果消费者代码里catch异常后忘了nack消息就会一直留在unacked状态最终看起来就是消息“丢失”了。解决开启手动确认模式spring.rabbitmq.listener.simple.acknowledge-modemanual并且在finally里对确认或拒绝做兜底。我习惯在消费者入口包一个大的try-catch-finally捕获到异常时记录消息头和堆栈然后basicNack且requeuefalse把坏消息送死信队列。发送端要检查日志里有没有“channel error; protocol method”报错这类报错通常表示交换机或队列名配错。这里还有一个容易忽略的点RabbitMQ默认的消费者线程并发数很小如果评测任务量大要调大concurrency配置否则即使消息没丢消费速度也跟不上积压到TTL过期消息同样会被丢弃。5.4 AIGC接口超时与重试策略现象题目生成任务偶尔失败用户点击生成后迟迟收不到题目后台日志出现ConnectTimeoutException或者ReadTimeoutException。原因AIGC大模型接口在高负载时响应很慢默认的连接超时和读取超时可以设置5秒这种情况基本必挂。生成一次题目需要模型思考并输出完整JSON耗时经常超过10秒高峰期甚至30秒。解决连接超时和读取超时分开设置。连接超时3秒读取超时60秒生成任务放在RabbitMQ队列里由专门消费者处理消费者内部再做一次重试第一次失败后等5秒重试第二次失败后等15秒重试两次都失败就放弃并标记题目生成失败。重试之间加延时的目的是避开接口限流窗口。重试次数不能太多AIGC接口是按调用次数计费的重试3次失败后应该走人工录入兜底路径而不是无限重试。这里的教训是不要把AIGC调用逻辑和业务逻辑写在同一个同步方法里一旦接口超时整个请求线程会被拖住其他用户的操作也会排队。5.5 WebSocket连接断开与重连现象用户提交代码后前端偶尔收不到评测状态推送刷新页面后又能收到或者长时间挂机后推送中断。原因WebSocket连接被Nginx空闲超时断开或者浏览器后台休眠时连接被释放前端又没有做重连处理。默认情况下Nginx的proxy_read_timeout是60秒一条评测任务如果排队超过60秒推送还没发出去连接就断了。解决Nginx配置里对/ws/路径设置更长的超时并显式开启Upgrade转发location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }前端也要做断线重连推荐在SockJS的onclose回调里加一个指数退避重连逻辑重连后根据任务ID重新订阅topic。指数退避的意思是第一次断开后1秒重连第二次2秒第三次4秒最大间隔30秒。从实际测试情况看只配Nginx不配前端重连长任务仍然会中断只配前端不配Nginx确实能收到但阈值还是60秒。两端都要改缺一个都算没解决。还有一点如果服务端有负载均衡多实例部署时WebSocket连接要配置sticky session否则重连后落到另一台实例上订阅关系就丢了。6. 进阶评测结果可信度的验证与校准技巧系统上线后最容易被质疑的一个问题AIGC生成的题目和测试用例质量可靠吗评测结果会不会误判这一部分把我反复调整过的验证方法列出来你可以直接套用。第一对生成的题目做覆盖度统计。拿AIGC出题接口生成100道“动态规划”难度为medium的题统计测试用例里normal、edge、large三种类型的分布。理想的分布是6:3:1。如果edge用例太少说明Prompt里“至少1组边界用例”的约束没起作用需要把边界用例的示例写得更具体比如“包含数组长度为0或1的边界情况”。这套统计在代码包里是个定时任务每天把新生成题目的覆盖度写入一张统计表dashboard上直接看趋势。第二对评测判定做预检比对。方法很简单每道题准备一份标准AC代码由出题人生成或者从题解库取和一份标准WA代码分别提交预期结果必须分别是AC和WA。如果AC代码被判了WA优先查输出对比是否对行尾空格敏感如果WA代码判了AC优先查测试用例是否有边界遗漏。这个校验要在题目入库时就执行而不是等用户发现问题。代码包里的judge_precheck工具支持批量跑一次可以校验几十道题。第三跟踪全站AIGC题目的通过率。打一个指标aigc_problem_avg_pass_rate。如果这个值低于人工录入题目平均通过率15个百分点以上说明生成题目的难度标注或者描述清晰度有问题应该调低难度或者加长描述。admin_dashboard里有一张报表专门统计这个对比。生成题目里通过率低于10%的系统会自动标记为“疑似问题题目”推送到人工复核队列。实际经验是这些验证最好做成定时任务每周自动把新生成题目的质量指标汇总一次。从那以后我每次接类似的项目都会先把题目通过率和评测误判率这两个数字盯住至少观察两周确认没有异常波动才敢把AIGC出题接口放量开放。这套系统的完整工程源码、SQL初始化脚本和部署文档都整理在项目包里按第2到第5章的路径走一遍就能跑起来。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑