智学文枢:理科分析引擎

理科分析引擎:诊断知识断层的自动化流水线

一句话摘要:ScienceAnalyzer 用 70 行代码把"构建 Prompt → 调用 AI → 解析 JSON → 兜底降级 → 落库"串成一条自动化流水线,让每一道理科错题都能自动产出分步解析、知识断层诊断与补漏建议。

一、引言

理科错题的价值,不在"答案错了",而在"为什么错"。

一道函数题做错,可能是定义域概念没建立、可能是配方法不熟、可能是计算粗心------这三种原因对应的补救措施完全不同。人工诊断一道错题,资深老师也要花几分钟;而一个学生一晚上可能攒下十几道错题,靠人力根本覆盖不过来。第一性原理:错题诊断的本质,是把"题目 + 学生答案 + 正确答案"三份输入,翻译成"知识断层 + 补漏建议"两份输出,中间的分步解析只是让结论可解释、可复核。

「智学文枢」的理科分析引擎 services/ai/science_analyzer.py 就是这个翻译过程的工程化实现。它不解决"模型怎么想"(那是第 29 篇模板的事),也不解决"网络怎么调"(那是第 28 篇客户端的事),它只做一件事:把前面所有组件按正确顺序编排起来,并对每一个失败点给出业务语义的兜底。

二、核心原理

2.1 五段式流水线

analyze(question, grade, config) 的执行路径是一条严格有序的流水线:
#mermaid-svg-CDLHUDFmGXyk8dtI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CDLHUDFmGXyk8dtI .error-icon{fill:#552222;}#mermaid-svg-CDLHUDFmGXyk8dtI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CDLHUDFmGXyk8dtI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CDLHUDFmGXyk8dtI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CDLHUDFmGXyk8dtI .marker.cross{stroke:#333333;}#mermaid-svg-CDLHUDFmGXyk8dtI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CDLHUDFmGXyk8dtI p{margin:0;}#mermaid-svg-CDLHUDFmGXyk8dtI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI .cluster-label text{fill:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI .cluster-label span{color:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI .cluster-label span p{background-color:transparent;}#mermaid-svg-CDLHUDFmGXyk8dtI .label text,#mermaid-svg-CDLHUDFmGXyk8dtI span{fill:#333;color:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI .node rect,#mermaid-svg-CDLHUDFmGXyk8dtI .node circle,#mermaid-svg-CDLHUDFmGXyk8dtI .node ellipse,#mermaid-svg-CDLHUDFmGXyk8dtI .node polygon,#mermaid-svg-CDLHUDFmGXyk8dtI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CDLHUDFmGXyk8dtI .rough-node .label text,#mermaid-svg-CDLHUDFmGXyk8dtI .node .label text,#mermaid-svg-CDLHUDFmGXyk8dtI .image-shape .label,#mermaid-svg-CDLHUDFmGXyk8dtI .icon-shape .label{text-anchor:middle;}#mermaid-svg-CDLHUDFmGXyk8dtI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CDLHUDFmGXyk8dtI .rough-node .label,#mermaid-svg-CDLHUDFmGXyk8dtI .node .label,#mermaid-svg-CDLHUDFmGXyk8dtI .image-shape .label,#mermaid-svg-CDLHUDFmGXyk8dtI .icon-shape .label{text-align:center;}#mermaid-svg-CDLHUDFmGXyk8dtI .node.clickable{cursor:pointer;}#mermaid-svg-CDLHUDFmGXyk8dtI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CDLHUDFmGXyk8dtI .arrowheadPath{fill:#333333;}#mermaid-svg-CDLHUDFmGXyk8dtI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CDLHUDFmGXyk8dtI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CDLHUDFmGXyk8dtI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CDLHUDFmGXyk8dtI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CDLHUDFmGXyk8dtI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CDLHUDFmGXyk8dtI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CDLHUDFmGXyk8dtI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CDLHUDFmGXyk8dtI .cluster text{fill:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI .cluster span{color:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CDLHUDFmGXyk8dtI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CDLHUDFmGXyk8dtI rect.text{fill:none;stroke-width:0;}#mermaid-svg-CDLHUDFmGXyk8dtI .icon-shape,#mermaid-svg-CDLHUDFmGXyk8dtI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CDLHUDFmGXyk8dtI .icon-shape p,#mermaid-svg-CDLHUDFmGXyk8dtI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CDLHUDFmGXyk8dtI .icon-shape .label rect,#mermaid-svg-CDLHUDFmGXyk8dtI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CDLHUDFmGXyk8dtI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CDLHUDFmGXyk8dtI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CDLHUDFmGXyk8dtI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
PromptTemplates 组装消息

带配置注入
log_prompts 记录提示词
chat_json 调用AI

temperature=0.1
extract_json 解析
解析成功?
空值字典兜底

knowledge_gap=格式异常请重试
构建 AIAnalysis 对象

字段映射
create_or_update 落库

每一步都有明确职责:组装 (把配置字典翻译成 messages)、留痕 (调用前把完整提示词写入 prompt 日志)、调用 (强制 JSON 模式)、解析 (四级容错)、落库(幂等写入)。

2.2 温度选择:0.1 的确定性追求

理科分析传 temperature=0.1------这是刻意压低的采样温度。理科输出有标准答案、有确定的分步逻辑,我们不希望模型发挥,希望同样的题每次分析结论一致。温度越低,输出越接近贪心解码,可复现性越强;0.3 留给文科(第 33 篇要讲故事、要联想),0.4 留给变式(第 34 篇要创造性出题)。

2.3 字段映射:8 字段 JSON → AIAnalysis ORM

模型输出的 8 个字段并不是一一对应数据库列,中间有一层映射:

JSON 字段 AIAnalysis 列 说明
step_by_step step_by_step 分步解析列表
core_formula core_concept 核心公式/定理
knowledge_gap knowledge_gap 知识断层诊断
remediation_suggestion remediation_suggestion 补漏建议
difficulty_assessment difficulty_assessment 难度判断
common_mistake common_mistake 常见易错点
knowledge_point / student_misunderstanding (未映射) 供 UI 侧展示

core_formula → core_concept 是特意保留的异构映射:数据库列名 core_concept 是文科/理科共用的"核心概念"列,理科填公式、文科填概念,一张表承载两类语义。

三、代码实现

3.1 组装与调用

python 复制代码
# 源码位置:services/ai/science_analyzer.py L16-L39
def analyze(self, question, grade: str = "", config: dict = None) -> AIAnalysis:
    """分析理科习题

    Args:
        question: Question ORM对象
        grade: 学生年级
        config: Prompt配置字典,含学科开关、语气、偏好等

    Returns:
        AIAnalysis ORM对象
    """
    # 构建Prompt(带配置注入)
    messages = PromptTemplates.build_science_prompt_with_config(
        subject=question.subject,
        question_text=question.question_text,
        student_answer=question.student_answer or "",
        correct_answer=question.correct_answer or "",
        grade=grade,
        config=config,
    )

    # 调用AI前,记录完整提示词供核对组成
    log_prompts("AI分析(理科)", messages, temperature=0.1)
    raw_response = self.client.chat_json(messages, temperature=0.1)

两个细节:question.student_answer or "" 保证未作答时模板收到空串而非 None(模板里 {student_answer} 会被空串填上,UI 显示"未作答");log_prompts("AI分析(理科)", ...) 的 scope 参数让日志可按来源过滤------出问题时能分清是"分析"还是"讲解"的提示词组装错了。

3.2 解析与兜底

python 复制代码
# 源码位置:services/ai/science_analyzer.py L41-L53
# 解析JSON
parsed = extract_json(raw_response)
if not parsed:
    parsed = {
        "step_by_step": [],
        "core_formula": "",
        "knowledge_point": "",
        "student_misunderstanding": "",
        "knowledge_gap": "AI返回格式异常,请重试",
        "remediation_suggestion": "",
        "difficulty_assessment": "",
        "common_mistake": "",
    }

这是"优雅降级"的核心:extract_json 返回 None(第 31 篇已尽力)时,不抛异常、不中断 ,而是构造一个全空字典,其中 knowledge_gap 特别填上"AI返回格式异常,请重试"------这条消息会顺着知识断层字段显示在 UI 上,用户一眼知道"该重试了",而分析记录照常落库,不会丢失题目关联。

3.3 对象构建与幂等落库

python 复制代码
# 源码位置:services/ai/science_analyzer.py L55-L69
# 构建AIAnalysis对象
analysis = AIAnalysis(
    question_id=question.id,
    analysis_type="science",
    raw_response=raw_response,
    step_by_step=parsed.get("step_by_step", []),
    core_concept=parsed.get("core_formula", ""),
    knowledge_gap=parsed.get("knowledge_gap", ""),
    remediation_suggestion=parsed.get("remediation_suggestion", ""),
    difficulty_assessment=parsed.get("difficulty_assessment", ""),
    common_mistake=parsed.get("common_mistake", ""),
)

# 存入数据库(create_or_update)
return self.repo.create_or_update(analysis)

parsed.get(key, []) / parsed.get(key, "") 的默认值选择很讲究:列表字段给 []、字符串字段给 ""------即使模型漏了某个字段,ORM 也能拿到类型正确的值,不会因 None 触发 SQLite 约束或 UI 渲染异常。create_or_update 是幂等写:同一道题重复分析只更新不新增,避免历史记录无限膨胀。

四、设计权衡 / 踩坑记录

1. 为什么 temperature 用 0.1 而不是默认 0.3? 踩坑经历:首版理科分析用默认温度,同一道题连续分析两次,模型一次写"用配方法"、一次写"用求根公式",补漏建议跟着漂移。降到 0.1 后结论基本稳定。理科要"准",温度宁低勿高;代价是解题步骤可能略显套路化,但诊断场景"稳定"优先于"灵活"。

2. 为什么不把兜底逻辑放进 json_parser? 曾想让 extract_json 解析失败时直接返回"空结构",后来否决:理科空结构(8 字段空串)与文科空结构(8 字段完全不同)不一致,解析器不该知道业务字段;把兜底放回引擎,各引擎按自己的 schema 兜底,解析器保持纯工具化------通用层与业务层各管各的边界。

3. core_formula → core_concept 的异构映射是妥协。 最初想给数据库加 core_formula 列,但 AIAnalysis 表还要给文科用(文科的 core_concept 是"核心概念一句话"),加列会让文科行空着一大半。复用同一列、按学科类型解释语义,是"一张表承载双 schema"的折中,代价是查询时需按 analysis_type 区分列含义。

4. 重试放哪一层? 理科分析只在变式引擎(第 34 篇)做了显式重试,理科分析本身不重试------因为 chat_json 内部已对 400 降级(第 28 篇),再叠加业务重试会放大延迟与成本;"格式异常请重试"的兜底消息 + UI 重试按钮,把决策权交给用户。变式不重试会"没东西可存",分析不重试只是"少一次诊断",两者的失败代价不同,重试策略自然不同。

五、小结

ScienceAnalyzer 是五个引擎中最"规整"的一个:输入一个 Question ORM 对象,输出一条落库的 AIAnalysis 记录,中间五段流水线清晰可读。它的设计基调可以总结为三句话:

  1. 组装靠模板 :提示词细节全部下沉到 PromptTemplates,引擎只传参;
  2. 失败不中断:解析失败用空值字典兜底,让"重试"成为可见的 UI 动作而非后台崩溃;
  3. 写库要幂等 :create_or_update 保证重复分析只更新不重复。

与它形成对照的文科引擎(第 33 篇)将展示同一套流水线在"情境化记忆"业务下的变体------字段不同、温度不同、但骨架完全一致。这就是统一 AI 服务层架构的收益:骨架收敛一次,业务各自生长。

相关推荐
青少儿编程课堂2 小时前
2026蓝桥杯青少组图形化真题:校园飞机大战解析(全国青少年信息素养·电子学会图形化考级)
c++·python·算法·bfs·信息学竞赛
ShiMetaPi2 小时前
182X 模型转换详解:从 PC 模型到板端 NPU 运行全流程
ai·边缘计算·rk3588·rk1820·rk1828
方知我2 小时前
01FastAPI基础入门
人工智能·python
物联网老王2 小时前
第05篇-Agent 连不上工具?MCP 就是那根“USB-C”
开发语言·人工智能·ai·aigc·ai编程
DreamLife☼3 小时前
Agent与大语言模型的集成
windows·python·语言模型
Maydaycxc4 小时前
gentic RPA 深度解析|AI Agent 与 RPA 混合架构、落地痛点、选型要点
人工智能·ai·自动化·rpa
znnnk4 小时前
【AI技术】从 Vibe Coding 到 Harness:以 DeepSeek Harness 为例看 AI 项目的工程化维护
人工智能·ai·deepseek·ai工程化·harness·项目维护
BK贝壳8 小时前
亿级用户签到位图选型复盘:用 bool-hybrid-array 扛冷启动打卡,也把它和 Roaring/Redis 的边界测清楚了
数据结构·python·性能优化·numpy
2601_966949659 小时前
如何根据研究的起止时间截取分钟级行情?Python 实战与 QuantDash 数据处理指南
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash