本地AI错题纠正小程序:端侧推理与错题闭环实战

「本地AI错题纠正小程序」指的是把 OCR、知识点匹配、错因诊断这些 AI 能力部署在本地环境(手机端或内网服务器),由小程序负责采集与交互的一类应用。相比直接把题目图片丢给公有云大模型,本地化方案的核心诉求通常有三个:数据不出内网、弱网可用、推理成本可控。本文按工程实现顺序,讲清楚这类小程序的技术选型、链路设计和落地时要避开的坑。

一、先明确「本地」到底落在哪一层

很多团队一开始就把「本地 AI」理解成「模型跑在手机里」,结果在真机上撞墙。实际上可落地的方案有三档,需要按场景选:

  1. **纯端侧**:模型通过 WASM 或小程序原生推理框架运行在手机内。优点是零网络依赖、隐私;缺点是小程序容器对算力、内存、包体积限制严格,只适合做轻量 OCR、手写数字识别、简单分类这类小模型。

  2. **内网服务器推理**:模型部署在学校或机构的本地服务器,小程序通过内网 HTTPS 调用。这是目前常见、工程上稳的形态,可以跑 7B 级别量化模型或完整的 OCR + 向量检索链路。

  3. **端云混合**:端侧做预处理和缓存,复杂推理回落到内网服务。

结论是:**错题纠正的「纠正」环节放在内网服务,「采集」环节放在端侧**,这个分工在成本与体验之间平衡。

二、整体架构与技术选型

参考同类多端教育应用的通用做法,用户端用 uniapp(Vue 语法)一套代码适配小程序、H5、公众号和 App;服务端用 Spring Boot + MyBatis Plus + MySQL;管理后台用 Vue + Element UI。AI 推理服务单独拆成一个独立的进程或容器,与业务服务通过内网 HTTP 通信,这样模型迭代不需要重启业务服务。

分层如下:

  • **采集层(小程序)**:拍照/相册、裁剪、透视校正、压缩、题目框选。

  • **识别层(内网)**:OCR 提取题干文本,公式单独走公式识别模型。

  • **理解层(内网)**:文本向量化 → 向量库检索相似题与知识点 → 错因分类模型输出错因标签。

  • **反馈层(小程序)**:按提示等级逐步给出引导,而不是直接抛答案。

  • **沉淀层(MySQL + 本地缓存)**:错题本、复习计划、掌握度曲线。

向量检索建议先用轻量方案起步,比如 FAISS 或 PostgreSQL 的 pgvector,题目量在十万级以内完全够用,不必一上来就上独立向量数据库。

三、错题纠正链路的代码实现

1. 小程序端采集与上传

拍照后不要直接上传原图,先做压缩和区域裁剪,能显著降低内网带宽压力:

javascript 复制代码
```javascript
// uniapp:拍照 + 压缩 + 上传
uni.chooseImage({
  count: 1,
  sizeType: ['compressed'],
  success: async (res) => {
    const path = res.tempFilePaths[0];
    // 压缩到长边 1600px,质量 80,足够 OCR 使用
    const compressed = await new Promise((resolve) => {
      uni.compressImage({ src: path, quality: 80, success: (r) => resolve(r.tempFilePath) });
    });
    uni.uploadFile({
      url: `${BASE_URL}/api/mistake/recognize`,
      filePath: compressed,
      name: 'file',
      header: { Authorization: `Bearer ${token}` },
      success: (r) => handleResult(JSON.parse(r.data))
    });
  }
});
```

2. 服务端识别与错因诊断接口

业务侧只暴露一个入口,内部串联 OCR、检索、诊断三步,避免小程序端多次往返:

java 复制代码
```java
@RestController
@RequestMapping("/api/mistake")
public class MistakeController {

    private final OcrClient ocrClient;          // 内网 OCR 服务
    private final VectorSearchClient vectorClient; // 内网向量检索
    private final DiagnoseClient diagnoseClient;   // 错因分类模型

    @PostMapping("/recognize")
    public Result<MistakeVO> recognize(@RequestParam MultipartFile file,
                                       @RequestParam Long studentId) {
        String imageUrl = storageService.save(file);

        // 1. 题干识别
        OcrResult ocr = ocrClient.recognize(imageUrl);

        // 2. 知识点与相似题召回
        List<Float> embedding = vectorClient.embed(ocr.getText());
        List<SimilarQuestion> similar = vectorClient.search(embedding, 5);

        // 3. 错因诊断:概念不清 / 计算失误 / 审题偏差
        DiagnoseResult diagnose = diagnoseClient.classify(
                ocr.getText(), similar, studentId);

        return Result.ok(MistakeVO.of(ocr, similar, diagnose, imageUrl));
    }
}
```

3. 分步提示的设计

错题纠正容易做错的地方是「一上来就给答案」。建议把反馈拆成三级提示,逐级释放:

  • L1:指出考查的知识点,不给任何解题步骤;

  • L2:给出步的思路,留出后续推理空间;

  • L3:给出完整解析与同类题推荐。

等级由学生点击次数或答题正确率驱动,这个策略写在服务端,方便后续按学段调整。

四、错题数据模型与复习调度

错题本不是「收藏夹」,它的价值在于调度。表结构建议把「题目」和「复习记录」拆开,一张表存题目,另一张表存每次复习的结果:

java 复制代码
```sql
CREATE TABLE mistake_item (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  student_id    BIGINT NOT NULL,
  subject       VARCHAR(32),
  knowledge_tag VARCHAR(64),
  stem_text     TEXT,
  image_url     VARCHAR(255),
  error_type    VARCHAR(32),
  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
  KEY idx_student_subject (student_id, subject)
);

CREATE TABLE review_log (
  id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  item_id     BIGINT NOT NULL,
  quality     TINYINT,          -- 0~5,本次回忆质量
  ease        DECIMAL(4,2),     -- 难度因子
  interval_d  INT,              -- 下次间隔(天)
  next_time   DATETIME,
  KEY idx_item (item_id)
);
```

调度算法可以直接用 SM-2 的简化版,实现很短,但对复习节奏的改善很明显:

java 复制代码
```java
public class Sm2Scheduler {

    public ReviewLog schedule(ReviewLog last, int quality) {
        double ease = last == null ? 2.5 : last.getEase();
        int interval = last == null ? 1 : last.getIntervalD();

        // 难度因子更新,做上下限保护
        ease = ease + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));
        ease = Math.max(1.3, Math.min(2.8, ease));

        if (quality < 3) {
            interval = 1;                 // 回忆失败,明天重来
        } else if (interval == 1) {
            interval = 3;
        } else {
            interval = (int) Math.round(interval * ease);
        }

        ReviewLog log = new ReviewLog();
        log.setQuality(quality);
        log.setEase(ease);
        log.setIntervalD(interval);
        log.setNextTime(LocalDateTime.now().plusDays(interval));
        return log;
    }
}
```

五、落地时的几个工程坑

**OCR 是准确率瓶颈,不是模型。** 手写体、竖式计算、带图的几何题,识别失败率远高于印刷体。建议在上传前引导用户框选题干区域,并在识别结果页提供「手动修正文本」入口,修正后的文本回写向量库,等于持续做数据回流。

**小程序包体积与 WASM。** 如果确实要做端侧推理,模型体积控制在几 MB 级别,且要准备降级路径------部分低端机不支持相关能力时自动切到内网服务。

**推理服务要限流。** 内网服务器算力有限,OCR 和诊断模型要做队列与并发上限,否则一次班级规模的集中提交会把服务打满。建议按学生维度做令牌桶限流。

**冷启动缓存。** 常用知识点向量和小模型可以常驻内存,避免每次请求重新加载。

**隐私边界要写进代码。** 如果题目图片只在本地留存,存储路径要做定期清理策略,并在接口层做数据脱敏,这既是合规要求,也是本地化方案的立身之本。

六、FAQ

**Q:本地AI错题纠正小程序一定要用大模型吗?**

不一定。错因分类用微调后的小模型(如 BERT 级别的分类模型)效果往往更稳定,也更省算力。大模型更适合用来生成解析文本和变式题。

**Q:题目量不大,还需要向量数据库吗?**

十万题以内,pgvector 或 FAISS 足够,检索延迟通常在毫秒级。数据量再上一个量级再考虑独立向量库。

**Q:小程序端能直接跑 OCR 吗?**

可以做轻量场景,但受包体积和机型限制,兼容性成本高。更稳的做法是端侧做图像预处理,识别放在内网服务。

**Q:怎么衡量纠正效果?**

建议同时看三个指标:同类题复现正确率、复习计划完成率、错题二次出错间隔。只看单次答题正确率容易被蒙对的情况误导。

**Q:能不能不联网使用?**

采集和复习调度可以离线,但推理环节需要内网可达。若要完全离线,只能把模型下沉到端侧,功能范围要做相应收敛。

相关推荐
吠品44 分钟前
Python 写入 Excel 的两种主流方案实际用法总结
c语言·开发语言·算法
Wang's Blog1 小时前
Java 服务器: Linux-yum在线安装与lrzsz文件传输
java·服务器
邪修king1 小时前
Re:Linux 系统篇(三十一):库的制作与原理Chapter2:静态链接与程序加载 —— 从磁盘 ELF 到运行中进程的完整旅程
android·linux·运维·开发语言
geovindu1 小时前
rust: Factory Method Pattern
开发语言·设计模式·rust·工厂方法模式·创建型模式
eybk1 小时前
用Kivy制作手机相片分类局域网传送工具,还能传输数据库文件
开发语言·python
Java内核笔记1 小时前
Spring Boot 4 与 Spring AI 2.0 深度集成:ChatClient、Advisor 链与 MCP(源码级实战)
java·后端
可爱的小小小狼1 小时前
【无标题】
java·算法
用户3126874877201 小时前
Java SPI 到底怎么实现动态扩展的?从 ServiceLoader 到 Dubbo SPI 全链路拆解
java
新时代农民工~1 小时前
【双机高可用部署方案-前后端部署】
java·nginx·springboot