首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >不调大模型,用规则引擎做一个可离线、可插拔的"课题申报书体检器"

不调大模型,用规则引擎做一个可离线、可插拔的"课题申报书体检器"

原创
作者头像
用户12801448
修改于 2026-10-23 11:38:55
修改于 2026-10-23 11:38:55
470
举报

本文是参加 1024 造物挑战的作品技术复盘。主角不是"某个工具",而是它的实现:如何在浏览器里,用一个单文件、零依赖、可离线的规则引擎,完成一套"可插拔评分标准 + 反刷分评分"的体系。

在线体验(公开可打开):https://proposal-checker.app.workbuddy.host/?sharecode=F4HFIUYZ2H

一、选型:为什么不用大模型

需求来自一个真实场景——教师对"课题申报书"做立项前自查。这类材料有三个硬约束:

  1. 涉密:未立项的申报书,老师普遍不敢上传到任何在线服务;
  2. 要可解释:用户需要知道"为什么扣这分、该改哪一条",黑盒评分不可信;
  3. 要可复现、零成本:同一份稿子每次结果必须一致,且要能跑一万次不花钱。

这三条正好指向"离线规则引擎"而不是"调大模型 API"。于是技术路线定为:纯前端单文件 HTML,零依赖,全部计算在浏览器完成,没有任何网络请求。

代价也很明确:没有语义理解,只能做"结构化 + 启发式"检测。这是主动取舍,不是能力缺失。

二、整体架构:三层

标准解析层:把任意"评审标准"解析成可执行的评分树(可插拔)

评分引擎:证据句判定 + 加权打分 + 反刷分约束 + 逻辑一致性检查

渲染/导出层:报告渲染、意见书文本生成、本地存档对比

核心设计目标是"尺子可换":内置的默认标准只是示例,用户上传自己单位的标准,评分逻辑立刻切换。这让工具从"一把固定尺子"变成"一个可复用的评审模板引擎"。

三、关键实现一:可插拔的"标准"怎么解析

评分标准被抽象成两层结构(维度 -> 子项):

代码语言:js
复制
{ dim: "内容与设计", max: 40, subs: [
    { name: "研究内容分解科学、重点难点明确", w: 12, kws: [...], binary: false }
]}

支持两种输入格式:精确格式 维度名 | 子项名 | 权重;自由文本(自动识别维度标题、剥离序号、抓"N分"作权重)。

解析后最关键的一步是权重归一化到 100:

代码语言:js
复制
const all = dims.flatMap(d => d.sub);
const allHaveW = all.every(s => s.w != null && s.w > 0);
const factor = allHaveW ? (100 / all.reduce((a, s) => a + s.w, 0)) : 1;
let w = s.w != null ? s.w * factor : (100 / all.length);

子项的匹配关键词是自动派生的:命中通用概念同义词库 + 从条目名里切 2–6 字片段。

代码语言:js
复制
const CONCEPT = {
  "目标": ["研究目标","目标","旨在","目的","总目标","拟"],
  "方法": ["思路","方法","技术路线","问卷","访谈","案例","实证","扎根"],
  "创新": ["创新","突破","前沿","首次","新视角","填补","重构"]
};
function deriveKw(name){
  const kws = new Set();
  for(const key in CONCEPT) if(name.includes(key)) CONCEPT[key].forEach(k => kws.add(k));
  name.split(/[::,,。、;;()()\s]+/).filter(t => t.length>=2 && t.length<=6).forEach(t => kws.add(t));
  return [...kws];
}

四、关键实现二:反刷分——朴素关键词命中的致命漏洞

第一版评分就是最朴素的"关键词命中数 / 目标数"。上线前自测发现严重漏洞:把"创新、目标、内容、方法、成果、价值"这些词在一句话里各写一遍,分数能刷到 80+;纯堆词不展开也能拿 60 分。也就是说,一个"看起来什么都有、实际什么都没写"的稿子能骗过评分器。

解决方案是引入"证据句"判定——关键词必须落在有实质展开的句子里才算有效命中:

代码语言:js
复制
const ACTION = ["构建","建立","设计","验证","实证","检验","比较","测量","分析",
                "界定","提出","采用","运用","基于","结合","梳理","评价","明确",
                "形成","开展","实施","调查","访谈","问卷","案例","纳入","设置"];

function isEvidence(s){
  return s.length >= 22 && ACTION.some(a => s.includes(a));  // 够长 + 有实义动作
}

代入评分,并加一条全局硬约束:

代码语言:js
复制
function subScore(text, sub){
  const sents = splitSentences(text);           // 按 。!?\n;; 切句
  const sentsLow = sents.map(s => s.toLowerCase());

  let validHits = 0;                            // 只统计"有效命中"
  for(const k of sub.kws){
    const kl = k.toLowerCase();
    if(sentsLow.some((sl, i) => sl.includes(kl) && isEvidence(sents[i]))) validHits++;
  }
  const evCount = sents.filter(s => isEvidence(s)).length;   // 全文证据句总数

  const raw = Math.min(validHits / Math.min(sub.kws.length, 3), 1);
  let full = raw >= 0.7;

  // 全局约束:全文证据句不足 2 句 —— 任何维度都不许判"达标"
  if(!sub.binary && evCount < 2) full = false;

  const frac = full ? 1 : 0.3 + 0.45 * raw;
  return { score: Math.round(sub.w * frac) };
}

实测效果(同一份作弊稿前后对比):

样本

旧版(朴素命中)

新版(证据句约束)

结构完整的正常稿

87

87(不误伤)

一句话堆满所有关键词

80+

48

纯堆词、无任何展开

60

22

正常稿不误伤,作弊稿被压回原形——这是这次实现里最值钱的一处改动。

五、关键实现三:.docx 本地解析(零网络请求)

.docx 本质是个 zip。要在浏览器里离线读出文字且不引入任何库:从文件尾部倒序找 EOCD 签名 0x06054b50,拿到中央目录偏移与条目数;遍历中央目录取压缩方式、偏移、文件名;用浏览器原生的 DecompressionStream("deflate-raw") 解压;最后正则提取 <w:t> 里的文本。

代码语言:js
复制
async function inflate(raw, method){
  if(method === 0) return raw;                        // 未压缩
  if(method === 8){
    const ds = new DecompressionStream("deflate-raw");
    const stream = new Response(raw).body.pipeThrough(ds);
    return await new Response(stream).arrayBuffer();
  }
  throw new Error("不支持的压缩方式");
}

const dv = new DataView(buf);
let eocd = -1;
for(let i = buf.byteLength - 22; i >= 0; i--){
  if(dv.getUint32(i, true) === 0x06054b50){ eocd = i; break; }
}
const cdOffset = dv.getUint32(eocd + 16, true);

全程在本地内存完成,申报书内容一个字节都不出浏览器——这正是这个场景能成立的前提。

六、逻辑一致性检查

除逐项打分外,还做了一层"跨段体检":用关键词共现 + 互斥判定抓四类硬伤——研究方法是否只说"问卷访谈"而无对象/工具;创新点是否只列未展开;研究目标与研究内容是否对应;预期成果与研究目标是否对应。另有参考文献时效性信号(正则抽取年份,判"偏少 / 近 3 年不足 / 达标")。

七、诚实地说清局限

  1. 无语义理解:判断的是"该写的板块有没有、关键信号在不在",读不出学术深度与逻辑质量——得分高只代表结构齐全;
  2. 启发式精度有限:自定义标准的关键词是自动派生的,精度低于手工调校的默认标准;
  3. 准确率未做大规模验证:目前只跑了自造的三档样本(完整/中等/空白)。下一步该做的是找 3–5 份真实申报书,与专家打分做一致性对比,把数据写进文档。

八、小结

这个作品的工程判断可以浓缩成一句话:不是不会用大模型,是刻意不用——涉密场景要可解释、要可复现、要零成本,所以把领域标准固化成规则引擎,跑在用户自己的浏览器里。而"可插拔标准"这个设计,让它从"一个工具"变成"一个模板引擎":任何单位上传自己的评审表,评分逻辑立刻切换。

在线体验(公开可打开):https://proposal-checker.app.workbuddy.host/?sharecode=F4HFIUYZ2H

#WorkBuddy #1024程序员节 #前端 #架构设计 #规则引擎 #教育科研

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、选型:为什么不用大模型
  • 二、整体架构:三层
  • 三、关键实现一:可插拔的"标准"怎么解析
  • 四、关键实现二:反刷分——朴素关键词命中的致命漏洞
  • 五、关键实现三:.docx 本地解析(零网络请求)
  • 六、逻辑一致性检查
  • 七、诚实地说清局限
  • 八、小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档