智学文枢:文科分析引擎

文科分析引擎:让抽象知识"情境化"落地

一句话摘要:HumanitiesAnalyzer 以"情境化记忆"为方法论,用 0.3 的采样温度和 8 个完全不同字段的输出 schema,把政史地知识点加工成"典故 + 新闻 + 民俗 + 口诀"的记忆素材,并与理科引擎共享同一条落库流水线。

一、引言

文科学习的痛点,不在"理解不了",而在"记不住"。

"辛亥革命的历史意义"背了三遍还是混,"季风气候成因"理解了但考场上想不起术语。神经科学和教学法都指向同一个结论:孤立的信息记不住,被"挂"在故事、场景、联想上的信息忘不掉 。所以文科私教的第一性原理不是"讲清楚",而是"制造记忆钩子"------每个知识点都要钩住一个典故、一则新闻、一段民俗、一句口诀。

这就是「智学文枢」文科分析引擎的定位:它不输出"分步解析"(那对文科意义不大),而是把知识点翻译成一组"记忆素材"。services/ai/humanities_analyzer.py 与理科引擎(第 32 篇)共享相同的流水线骨架,却在三个维度上刻意分化:温度 (0.1 → 0.3)、输入 (不需要学生答案)、输出 schema(8 个字段完全不同)。

二、核心原理

2.1 文科的"记忆钩子"模型

文科分析要产出的不是"解题路径",而是五类钩子:
#mermaid-svg-tA4l2qqBQFpYMUOC{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-tA4l2qqBQFpYMUOC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tA4l2qqBQFpYMUOC .error-icon{fill:#552222;}#mermaid-svg-tA4l2qqBQFpYMUOC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tA4l2qqBQFpYMUOC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tA4l2qqBQFpYMUOC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tA4l2qqBQFpYMUOC .marker.cross{stroke:#333333;}#mermaid-svg-tA4l2qqBQFpYMUOC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tA4l2qqBQFpYMUOC p{margin:0;}#mermaid-svg-tA4l2qqBQFpYMUOC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC .cluster-label text{fill:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC .cluster-label span{color:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC .cluster-label span p{background-color:transparent;}#mermaid-svg-tA4l2qqBQFpYMUOC .label text,#mermaid-svg-tA4l2qqBQFpYMUOC span{fill:#333;color:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC .node rect,#mermaid-svg-tA4l2qqBQFpYMUOC .node circle,#mermaid-svg-tA4l2qqBQFpYMUOC .node ellipse,#mermaid-svg-tA4l2qqBQFpYMUOC .node polygon,#mermaid-svg-tA4l2qqBQFpYMUOC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tA4l2qqBQFpYMUOC .rough-node .label text,#mermaid-svg-tA4l2qqBQFpYMUOC .node .label text,#mermaid-svg-tA4l2qqBQFpYMUOC .image-shape .label,#mermaid-svg-tA4l2qqBQFpYMUOC .icon-shape .label{text-anchor:middle;}#mermaid-svg-tA4l2qqBQFpYMUOC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tA4l2qqBQFpYMUOC .rough-node .label,#mermaid-svg-tA4l2qqBQFpYMUOC .node .label,#mermaid-svg-tA4l2qqBQFpYMUOC .image-shape .label,#mermaid-svg-tA4l2qqBQFpYMUOC .icon-shape .label{text-align:center;}#mermaid-svg-tA4l2qqBQFpYMUOC .node.clickable{cursor:pointer;}#mermaid-svg-tA4l2qqBQFpYMUOC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tA4l2qqBQFpYMUOC .arrowheadPath{fill:#333333;}#mermaid-svg-tA4l2qqBQFpYMUOC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tA4l2qqBQFpYMUOC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tA4l2qqBQFpYMUOC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tA4l2qqBQFpYMUOC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tA4l2qqBQFpYMUOC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tA4l2qqBQFpYMUOC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tA4l2qqBQFpYMUOC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tA4l2qqBQFpYMUOC .cluster text{fill:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC .cluster span{color:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC 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-tA4l2qqBQFpYMUOC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tA4l2qqBQFpYMUOC rect.text{fill:none;stroke-width:0;}#mermaid-svg-tA4l2qqBQFpYMUOC .icon-shape,#mermaid-svg-tA4l2qqBQFpYMUOC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tA4l2qqBQFpYMUOC .icon-shape p,#mermaid-svg-tA4l2qqBQFpYMUOC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tA4l2qqBQFpYMUOC .icon-shape .label rect,#mermaid-svg-tA4l2qqBQFpYMUOC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tA4l2qqBQFpYMUOC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tA4l2qqBQFpYMUOC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tA4l2qqBQFpYMUOC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 知识点/习题
core_concept 一句话概括
historical_anecdote 历史典故

注明出处
current_news 近期新闻/政策

半年内+来源
folk_custom 民俗/风物

地理学科必填
memory_mnemonic 记忆口诀
exam_frequency 考试频次
common_mistake 易错点
落库 AIAnalysis

analysis_type=humanities

每条钩子都由第 29 篇的 HUMANITIES_USER 模板约束:典故要"注明出处"、新闻要"半年内"、民俗要"当地"------这些约束不是装饰,是为了防止模型编造(出处可查、来源可核)。

2.2 温度 0.3:创造性是刚需

理科压温度是为了"结论稳定",文科抬温度是为了"联想发散":同一个知识点,需要模型在不同次分析里关联不同的典故、给出不同的口诀,才有新鲜感。0.3 是实测平衡点------低于 0.2 时典故容易重复且老套,高于 0.4 时开始出现张冠李戴的史实错误。

2.3 输入差异:没有标准答案的学科

文科分析不接受 student_answer / correct_answer(模板里压根没有这两个占位符),因为政史地题往往没有唯一答案。输入只需要"知识点/习题 + 年级 + 学科"三要素,其余(当前水平/教材版本/考试目标)走第 30 篇的配置合并注入。

三、代码实现

3.1 组装与调用

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

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

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

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

与理科唯一的入参差异是少了 student_answer / correct_answer 两个参数------这印证了模板即数据契约:模板里有什么占位符,引擎就传什么参数,两者必须严丝合缝。

3.2 解析与兜底

python 复制代码
# 源码位置:services/ai/humanities_analyzer.py L39-L51
# 解析JSON
parsed = extract_json(raw_response)
if not parsed:
    parsed = {
        "core_concept": "",
        "historical_anecdote": "",
        "current_news": "",
        "folk_custom": "",
        "memory_mnemonic": "",
        "cross_subject_link": "",
        "exam_frequency": "",
        "common_mistake": "",
    }

注意兜底字典里没有 knowledge_gap="AI返回格式异常,请重试" 这一条------这是与理科引擎的有意差异:文科的"核心概念"字段(core_concept)在 UI 上是大标题位,若填入"格式异常"会显得很突兀;文科格式异常时整体留白,UI 侧用占位样式提示,观感更自然。同一个兜底策略,也要按业务微调。

3.3 字段映射与落库

python 复制代码
# 源码位置:services/ai/humanities_analyzer.py L53-L68
# 构建AIAnalysis对象
analysis = AIAnalysis(
    question_id=question.id,
    analysis_type="humanities",
    raw_response=raw_response,
    step_by_step=[],
    core_concept=parsed.get("core_concept", ""),
    contextual_memory=parsed.get("historical_anecdote", ""),
    memory_mnemonic=parsed.get("memory_mnemonic", ""),
    common_mistake=parsed.get("common_mistake", ""),
    cross_subject_link=parsed.get("cross_subject_link", ""),
    knowledge_gap=parsed.get("exam_frequency", ""),
)

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

映射表里有两个"语义借用":

JSON 字段 AIAnalysis 列 语义说明
historical_anecdote contextual_memory 典故归入"情境记忆"列
exam_frequency knowledge_gap 考试频次借道"知识断层"列
current_news / folk_custom (未映射) 存 raw_response,UI 从原文提取展示

exam_frequency → knowledge_gap 是最"抽象"的一处复用:数据库没有"频次"列,而 knowledge_gap 是通用文本列,把"高频/中频/低频"存进去,UI 层按 analysis_type 决定如何解读。step_by_step=[] 硬编码空列表,避免 ORM 列出现 None 破坏 JSON 列约束。

四、设计权衡 / 踩坑记录

1. 为什么文科要 8 个字段、理科也要 8 个,却不共用 schema? 第一版试图用一套"通用分析字段"兼容两科,结果是理科嫌字段太虚、文科嫌字段太硬。拆成两套 schema 后,各自字段贴合各自方法论(理科:步骤/公式/断层;文科:典故/新闻/口诀),代价是数据库列出现语义复用(knowledge_gap 存频次)------宁可列名抽象,不可字段错位。

2. 典故出处怎么防编造? 模板要求"注明出处",但模型仍可能编一个看似真实的出处。工程上无法完全杜绝,只能三管齐下:模板约束(注明出处)、温度压制(0.3 防过度发散)、UI 标注(典故/新闻区块标"AI生成,请核对")。提示词只能降概率,不能消风险,产品层必须给用户"核对"的预期。

3. 为什么 step_by_step=[] 而不是 None? SQLite 的 JSON 列存 None 会变成 SQL NULL,而 UI 侧代码普遍按列表处理 step_by_step(如 len(steps)、for s in steps),NULL 会直接炸。硬编码 [] 保证类型恒为 list------兜底值要按"下游消费方式"选择,而不是按"字段本义"选择。

4. 温度 0.3 是玄学还是工程? 0.3 不是拍脑袋:实测了 0.1/0.2/0.3/0.5 四档,0.1 时典故重复率高且过于严肃(像百科词条)、0.5 时开始出现史实张冠李戴、0.3 在"发散度"与"准确性"之间交点上。这个值写在 chat_json(messages, temperature=0.3) 里,同时被 log_prompts 记录,方便日后按样本复盘调参。

五、小结

维度 理科引擎 文科引擎
温度 0.1(求稳定) 0.3(求发散)
输入 题目+学生答案+正确答案 知识点/习题
核心产出 分步解析、断层诊断 典故、新闻、民俗、口诀
兜底策略 断层列提示"格式异常请重试" 全字段留白
数据库列 core_concept 存公式 core_concept 存概念、knowledge_gap 存频次

HumanitiesAnalyzer 与 ScienceAnalyzer 一母同胞、各走各路:骨架完全一致(组装→留痕→调用→解析→落库),业务语义完全分化。这种"共享架构、分化语义"的模式,正是 AI 服务层后续所有引擎(变式/教学/跟进)的标配------下一篇的变式引擎将第一次引入"数组输出 + 显式重试",让我们看看骨架如何再进化。

相关推荐
包小黑1 小时前
Python 转 EXE (实用工具)
开发语言·python
全栈练习生1 小时前
DeepSeek 稀疏注意力
python·ai
我命由我123451 小时前
6-3.Python 异常处理 - 异常处理进阶(else 块与 finally 块、异常传递、自定义异常)
开发语言·前端·数据库·后端·python·学习·java-ee
mjhcsp2 小时前
Code date creation v8.0 竞赛数据生成与对拍实战指南
算法·ai·深度优先·cdc
FYKJ_20102 小时前
springboot高考志愿填报推荐系统14332-计算机课程设计、毕业设计
javascript·spring boot·python·架构·django·课程设计·高考
卷无止境2 小时前
长篇写作 Skill 横向盘点:从单文件指令到多智能体流水线
后端·python
外收内放2 小时前
FastAPI猜字谜实战(8):收官
开发语言·python·学习·fastapi
fensnote2 小时前
一句话,让 AI 把一块 ESP8266 从头调到能上网
ai·esp8266·nodemcu·workbuddy
沉下心来学鲁班2 小时前
具身智能实战:让机器鸭爬梯子——双策略交接与接触力控制的深度解读
开发语言·人工智能·python·机器人