我为什么做这个
用 AI 写完一个功能,再让它解释一遍,听起来往往都能懂。但如果换成自己回答“这里为什么这样写”“出错会怎么样”,可能就卡住了。我想试着把这一步做成工具:拿着真实项目里的代码提问,让回答的人发现自己哪里还没弄明白。
项目最早叫 Project Mastery,后来往项目答辩收了一步,再缩到学生简历里的一句项目描述。下面按开发顺序说说它怎么变成现在这个样子。P0 到 P8 是当时的阶段编号,留着方便对照仓库。
P0:先把最小的一套搭起来
我一开始先搭了前后端入口,约定项目、代码片段、问题和回答怎么表示。那时还没什么状态要保存,也没有任务要排队,就先没上数据库、缓存和消息队列。
前端用 Next.js,后端用 FastAPI。数据格式交给 Pydantic 检查:需要哪些字段、值的类型对不对,尽量在进入下一步之前就确认好。基础的数据对象也和 Web 接口、模型调用分开,方便单独测试。
还有一个小约定:没评估过就留空,不能记成零分。否则“还没问到”和“回答得不好”会被混在一起。
P1:先确认代码是真的,再拿它提问
这一步我先做仓库扫描。要围绕代码问问题,至少得知道这段代码来自哪个文件、哪一版、哪几行,不能让模型自己补一个看起来合理的出处。
扫描后会留下文件内容和版本信息。要引用某几行时,程序先核对文件是不是这次扫描得到的,再从保存的原文里截取。需要重新验证时,就回到本地路径再扫一遍,检查文件有没有改过。
仓库里的符号链接也单独处理,避免扫描绕到项目目录外面。Windows 上类似的路径跳转同样会被检查。
源码里负责这些事的主要是 repository/。其中的 EvidenceFactory 可以理解为取代码片段的统一入口:它负责核对来源,而不是只给一段文本贴上文件名。
P2:从哪里开始问,先筛一遍
我接着做的是找值得问的地方。仓库里有业务逻辑,也有很多简单声明和重复内容,全部丢给模型不太合适。
这一阶段会读取 Python 的语法树和 TypeScript 的语法结构,从中整理知识点和候选位置,再排序、去重,降低琐碎代码和测试代码的优先级。选出来的还只是“可以从这里出题”,正式问题留给下一步。
相关代码在 discovery/。这一步的重点是缩小范围,让后面的问题有具体落点。
P3:模型负责出题,程序负责核对
我把出题接到模型上时,给它的内容限定在选好的代码片段里。先找代码、再让模型围绕代码问,比让它自由挑一个话题更容易核查。
模型返回后,程序会检查格式、引用和版本是否对应。生成前、尝试修正前、返回结果前也会检查代码依据。格式不合要求时最多修正一次,避免一直重试。
出题时还会准备评分要点。这些内容留在内部,展示给答题人的只有问题和需要看的代码,免得还没回答就先看到了评分提示。
模型调用放在单独的接口后面。业务只管“拿这些材料出题”,具体请求哪个模型、怎样发 HTTP 请求,由另一层处理。这样换模型时不用再写一遍出题流程。开发时也能用固定返回值走通页面,不必每次都调用真实模型。
P4:把回答拆开看,再算结果
接下来我做回答评估。我希望反馈能说清楚哪一点答到了、哪一点还缺依据,而不是只给一个总分。
模型按评分要点判断回答,程序再按既定权重汇总。如果回答里有关键误解,也会按规则限制得分。模型提交的是每一项的判断,总分和权重由本地代码处理。
这部分在 evaluation/,复用前面的模型接口。它沿用出题时的代码版本和评分要点,方便回头查某条反馈是怎么来的。
P5:追问要有目的,也要能停下来
第一轮回答不够清楚时,我加了追问。但继续问什么、什么时候结束,需要有明确规则,否则很容易变成长聊天。
这一版默认最多三个追问。程序决定需要澄清哪一点、是否继续,模型负责把问题表达出来。后续评估只更新正在追问的那一项,原来的权重保留,不把几轮总分简单平均。
每一轮的问题、回答、评估和下一步决定都会留下来。调试时可以顺着记录回看,知道为什么又问了一轮,或者为什么到这里就结束了。
P6:讲完以后,再试一次
到了这一步,我想让用户看到反馈后还有机会补一下,再试着回答。于是把找薄弱点、生成讲解和生成重试题分开处理。
讲解围绕前面已经确认的问题,最多安排两个小目标。重试继续对应同一个评分点和代码版本,评估仍然用原来的逻辑。
我也加了对照抄讲解的检查和得分限制。最多两次重试,用完就停。这些记录能说明这次练习发生了什么,至于是不是真的学会了,还需要后面的真实用户验证。
P7:把流程接成一个能操作的演示
我在这一阶段把分析、提问、学习和报告接到页面上,先用预置样本跑完整个过程。
浏览器负责展示和收集回答;扫描、模型调用和分数汇总放在后端。演示默认使用模拟结果,配置好真实模型后才切换。样本代码只被读取,不会在服务里执行。
会话先放在内存里,每个会话有单独标识、过期时间和删除入口,同时限制项目大小、题数和容量。这适合当时的本地单用户演示,重启后也就没有那次会话了。
报告会回到已有的代码依据和回答记录,只整理这次问到了什么、回答如何。同一个知识点有父子关系时,也要避免重复计分。
P8-R:从练习收拢到项目答辩
后来我把重点收到了项目答辩:让用户带来项目,围绕选中的代码解释设计、分析问题,再看回答里暴露出的疑问。
这一阶段复用了前面的扫描、出题、评估和重试代码,增加了答辩会话、追问策略和报告。出题措辞与单项判断交给模型,继续澄清、换方向还是结束,由程序决定。
输入也从预置样本扩展到文件清单和 ZIP。上传前后会检查路径、过滤敏感文件,并限制请求大小和解压后的大小。上传内容放进各自的临时目录,会话结束或到期后清理。
C++ 在这里主要用词法方式找候选位置,分析能力比完整的语义图有限。报告也围绕实际选中的代码展开,不把这一小段答辩写成对整个仓库的判断。
P8-S:再缩到简历里的一句话
我又把入口缩小了一次:先拿简历里的一句项目描述,找它对应的代码,再围绕它问。这样用户一进来就有一件具体的事可以做。
这条流程继续使用之前扫仓库、取代码、出题和评估的实现。新增的部分主要是把简历描述对应到代码,以及管理这次答辩和整理报告。
这个阶段一次处理一条描述、一份仓库快照,最多收五次回答。记录中的默认预算还限制了 24 次模型请求和 49,152 个输出 token;这些是上限,实际用量另记,模型没返回用量时就留空。前面的“三次追问”“两次重试”属于各自阶段的规则,不能都当成同一条入口的设置。
自带模型密钥和使用服务端配置,走的是同一套问答逻辑,区别在密钥从哪里来。密钥留在服务端调用这一层,不放进报告,页面也不记住它。
当时的记录里还遇到了官方 API 返回 401,真实模型验证、人工数据和同模型对照实验都没做完,所以我没有把这个阶段当成效果已经验证的产品。
后来补过账号,现在先把作品展示好
开发后期我补过登录、模型配置和额度管理,所以旧文档里会同时出现“没有数据库”和“用了 SQLite”。它们说的是不同阶段,放在一起看容易误会。
最初是内存演示;后来加了本地账号、密码哈希和会话;再往后,账号与额度记录放进 SQLite,答辩过程仍然主要留在内存里。模型配置可以变化,出题、评估和报告继续复用同一套实现。
现在这个网站改成了个人作品集,用静态页面展示项目、截图和记录。需要模型的交互流程留在本地演示,账号和额度那部分也就不再作为访客入口了。
测到了什么,还缺什么
我给后端写了接口、数据格式和代码来源检查,也测了文件被修改、引用失效等情况。前端会跑类型检查和构建,确认页面能生成。
项目页里的 600 项通过、1 项跳过来自 9 月 12 日的验收记录,这次没重跑,真实用户效果还需要另外测。
下一步我想找人实际试用,看看反馈是否准确,再和同一个模型直接问答的结果比较。
这也是我接下来更想弄明白的事:问完几轮以后,用户有没有发现一个自己原本没意识到的问题。