
这个项目的名字很直白A Dual-Dimensional LLM Framework for Automated Item Incidental Content Similarity Analysis in Large-Scale Assessments。简单翻译过来就是一套面向大规模测评场景、用 LLM 自动化分析“题目附带内容相似度”的双维度框架。它要解决的核心问题不是“这道题做对没有”而是“这道题的附带材料跟其他题目、外部素材之间有没有重复、改写、拼凑的嫌疑”。在题库建设、试卷命制、出版内容合规审查这些环节里这类相似度分析是真正的痛点。一套大规模考试试卷里阅读文章、题干情境、选项干扰项都有可能是从历年题库或外部出版物中复用、改编过来的。人工抽审只能覆盖很小比例字符串精确匹配又识别不了“同义改写”“段落重组”“跨材料拼凑”这类常见手法。这个框架的切入点是把相似度拆成显式文本相似和隐式语义相似两个维度先批量召回候选再用 LLM 给出复核结论。下面直接进入正题从框架设计、技术选型、环境部署、核心代码、API 接入、批量任务和性能观察几个方面展开。看完之后你可以照着搭出一套能跑通、能接 API、能处理批量文件的题目素材相似度分析系统。1. 核心能力速览能力项说明项目类型教育测评领域 NLP 分析框架基于 LLM 的自动化相似度审查核心思路显式文本相似度 隐式语义向量相似度双维度融合判定输入内容题目附带内容包括阅读材料、题干、选项、情境材料等主要功能素材去重、题目查重、改写识别、跨材料拼凑检测、版权风险提示依赖模型Embedding 模型 本地 LLM 或云端 LLM API硬件门槛Embedding 阶段可 CPU 运行加入本地 LLM 复核建议配置独立显卡具体显存取决于模型规模启动方式Python 脚本、FastAPI 服务、批量目录处理是否支持 API支持接口设计可直接接入内部系统是否支持批量任务支持按目录批量处理或按接口批量提交适合读者教育测评机构、题库建设团队、内容审核、NLP 工程开发人员这个项目本身是一套研究性质的双维度分析框架不是打包好的一键客户端。要落地需要自己组合模型、向量库和接口服务。难度不高关键是理解两个维度分别解决什么问题。2. 这个框架要解决什么问题2.1 题目附带内容指什么从项目标题来看“Item Incidental Content”指的是题目正文之外的辅助内容。在大规模测评里一道题的构成通常不只是题干本身还包括阅读篇章、图表说明、实验情境、选项段落等。这些内容被计入“附带内容”是因为它们承载了题目要考查的信息背景也是命题质量审查和版权审查的重点对象。举例来说一道阅读理解题里的英文短文可能来自某本出版物一道数学应用题里的情境素材可能与另一场考试的材料高度相似一道多选题的干扰项可能是从旧题里换了个说法拼出来的。这些都属于题目附带内容的相似度问题也是这个框架要自动识别的对象。2.2 传统查重方案为什么不够传统文本相似度方法主要分两类一类是词面匹配比如编辑距离、Jaccard 相似度、最长公共子序列、SimHash另一类是统计向量化比如 TF-IDF、BM25。它们的问题是只停留在“字面相同”的层。字面相同的题目好查难的是这些情况同义改写把主动句改成被动句把关键词换成近义词字面相似度很低但语义等同。段落重组把一篇材料切成几段打乱顺序重新拼接n-gram 匹配基本失效。跨材料拼凑从三篇不同文章里各取一段组成新材料单独跟任何一篇比都不像但整体语义密度很高。跨语言或格式转换中文素材翻译成英文或者从 PDF 复制后格式混乱。这些场景单靠传统文本相似度算法无法覆盖。于是需要引入 LLM 和语义向量把“这段文本表达的是什么意思”纳入比较。2.3 双维度融合的思路只用语义向量也会误报。两篇材料讨论同一个话题比如都是“全球变暖”语义向量可能很接近但实际内容来源并不相同。如果只按向量余弦相似度判定会产生大量误报。所以这个框架采用“双维度”融合的思路第一维度显式文本相似度捕捉字面上的复制、改写、局部拼接。第二维度隐式语义向量相似度捕捉语义层面的等效表达、观点复现、段落信息重复。两个维度不是二选一而是互补。字面相似度高而语义相似度低可能是“复制后插入大量干扰内容”字面相似度低而语义相似度高可能是“高质量改写”两个维度都高则高度疑似重复或素材复用。把所有情况合并成一个打分体系再交给 LLM 做最终复核比单纯用某一个维度的判定可靠得多。3. 双维度框架整体设计3.1 第一维度显式文本相似度显式文本相似度的目标是识别“看起来就很像”的文本对。这一层适合用经典算法实现速度快、可解释性强。常用方法包括编辑距离或 Levenshtein 距离衡量字符级差异。difflib.SequenceMatcher计算最长匹配块比例适合检测局部复制。Jaccard 相似度基于分词后的集合交集。SimHash适合大规模去重场景可以先做候选集收缩。在实际实现里我会先用difflib取得一个大致的相似度分数再对匹配块的长度进行统计。如果文本对之间存在大量连续匹配片段说明存在明显的复制拼接行为。import difflib from typing import Tuple def lexical_similarity(text_a: str, text_b: str) - float: sm difflib.SequenceMatcher(None, text_a, text_b) return sm.ratio() def longest_match_ratio(text_a: str, text_b: str) - float: sm difflib.SequenceMatcher(None, text_a, text_b) matching_blocks sm.get_matching_blocks() total sum(block.size for block in matching_blocks) base_len max(len(text_a), len(text_b), 1) return total / base_len这个维度的特点是“严查字面”。它不会放过直接复制和低水平改写但对高水平的语义复述无能为力。因此需要第二个维度补位。3.2 第二维度隐式语义向量相似度语义向量相似度是把文本用 Embedding 模型编码成向量再计算余弦相似度。这一步解决的是“表面不同实际同源”的问题。实际落地时可以用本地 Embedding 模型也可以用云端 API。从数据保密角度考虑本地部署更稳妥。适合做中英文题目素材的嵌入模型包括 BGE-M3、bge-large-zh、gte-large 等这类模型对长文本、中英混合内容支持都不错。from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-m3) texts [ 这是一段阅读理解材料的第一段内容。, This is the first paragraph of a reading comprehension passage. ] embeddings model.encode(texts, normalize_embeddingsTrue)编码完成后计算两个向量之间的余弦相似度即可。关键点在于Embedding 模型通常有最大输入长度限制长阅读材料需要切块。切块后可以用段级向量均值或取最大相似度作为整篇材料的语义相似度。这部分对最终效果影响很大后面第 6 节会专门给出代码。3.3 融合判定与三级流水线双维度框架的完整工作流程建议按“召回、精排、复核”三级结构组织。第一级候选召回。大规模测评的题库可能有数万道题不能对全部题目两两计算相似度那样计算量是 O(n²)。正确做法是先对所有题目附带内容做向量化建立向量索引。查询时用 FAISS 或 Milvus 做近似最近邻检索把相似度较高的 Top-K 条作为候选。第二级精排。对候选对同时计算显式文本相似度和语义向量相似度得到一个两维特征向量。再根据业务规则或者一个简单的加权公式输出融合分数。第三级LLM 复核。对融合分数位于敏感区间的文本对把两段内容拼成一个 Prompt交给 LLM 判断“是否存在素材复用、改写、拼凑”并输出判定理由。LLM 能理解语义和上下文可以显著降低误报率。输入材料 - 预处理与题目切块 - Embedding 向量化 - ANN 召回 Top-K - 双维度精排打分 - LLM 复核判定 - 输出相似度报告这条流水线既能处理批量任务也能独立成接口服务。整个框架的核心实现就是把各个阶段的代码串起来。4. 技术选型与工具链4.1 文本处理与预处理题目附带内容通常来自 Word、PDF、Excel 或纯文本原始数据里往往混有格式符、特殊字符、图片注记。预处理阶段要解决几件事统一换行和空格去掉全角空白。去除页眉页脚、题目编号、章节名这类与内容无关的信息。按题目 ID 切分把一整份试卷拆成“题号 附带内容”的结构。对中文内容做分词对英文内容做小写化为后续计算做准备。预处理的质量直接影响相似度计算的准确性。建议在预处理之后把切分结果保存成 JSON 或 CSV 中间文件方便后续反复调试。import json import re from pathlib import Path from typing import List, Dict def normalize_text(text: str) - str: text text.replace(\u3000, ).replace(\r, ) text re.sub(r\s, , text) return text.strip().lower() def parse_items(raw_text: str) - List[Dict[str, str]]: blocks re.split(r\n(?Q\d|\d[.、]), raw_text) results [] for block in blocks: block normalize_text(block) if len(block) 10: continue results.append({id: fitem_{len(results)}, content: block}) return results def load_corpus(filepath: str) - List[Dict[str, str]]: raw Path(filepath).read_text(encodingutf-8) items parse_items(raw) with open(corpus.json, w, encodingutf-8) as f: json.dump(items, f, ensure_asciiFalse, indent2) return items4.2 嵌入模型选型Embedding 模型决定了语义维度的上限。选择时主要看三点是否支持中英文混合文本。最大输入长度是否够长。本地推理的显存和内存消耗。BGE-M3 是最常被推荐的选项之一支持中英文、长文本效果和性能比较均衡。如果只处理中文题目bge-large-zh 也是一个不错的选择。如果使用云端 API可以选择 text-embedding-3-small 这类服务但需要评估题目素材是否允许传送到外部服务。4.3 向量检索与索引数万道题目的向量检索不能靠暴力循环。常用方案FAISSMeta 开源的向量检索库轻量级适合单机批量分析。Chroma接口简单适合中小规模项目快速搭建。Milvus分布式向量数据库适合大规模生产环境。对这个框架来说如果数据量在十万条以下FAISS 足够。重点是把全量向量导入索引查询时设置相似度阈值过滤低分结果。import faiss import numpy as np def build_index(vectors: np.ndarray): dim vectors.shape[1] index faiss.IndexFlatIP(dim) index.add(vectors) return index def search_candidates(index, query_vector: np.ndarray, top_k: int 10): scores, indices index.search(query_vector.reshape(1, -1), top_k) return scores[0], indices[0]4.4 LLM 复核层处理模糊判定时需要 LLM 阅读两段文本给出结构化结论。复核层可以部署本地模型也可以调用企业内部已建好的 LLM 网关。对中文内容来说Qwen、GLM、DeepSeek 这一系列模型都支持 OpenAI 兼容接口接入成本很低。推荐使用 7B 到 14B 规模的模型既保证判定质量又不会太吃显存。如果只有 CPU 机器也可以先用小参数模型或者直接调用云端 API前提是数据脱敏和合规审批通过。5. 环境准备与本地部署5.1 硬件与运行环境先明确一条原则Embedding 阶段和 LLM 复核阶段对硬件的要求不一样。只跑 Embedding普通 CPU 机器即可内存建议 16GB 以上适合先跑通流程。加入本地 LLM 复核如果用 7B 级别模型建议配置至少 12GB 显存的独立显卡显存不足时可以使用量化版本或把部分层卸载到 CPU。如果使用云端 API本机只需要能联网硬件门槛大幅降低。操作系统以 Linux 或 macOS 最佳Windows 也可以运行但显存管理和一些原生依赖需要额外配置。5.2 Python 环境与依赖建议使用 Python 3.9 到 3.11 版本。新建虚拟环境后安装以下依赖python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install -U pip pip install sentence-transformers pip install faiss-cpu # 有 GPU 可换成 faiss-gpu pip install fastapi uvicorn pip install openai # 用于调用 OpenAI 兼容接口 pip install python-multipart pip install jieba pip install scikit-learn安装完成后先跑一句简单的模型加载测试确认环境没有缺包。from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-m3) vectors model.encode([测试文本], normalize_embeddingsTrue) print(vectors.shape)5.3 模型文件准备Embedding 模型会自动下载到本地缓存目录。如果服务器无法直接访问模型仓库可以提前在有网的机器上下载好再复制到离线环境的~/.cache/huggingface或指定目录。LLM 模型建议用 vLLM 或 Ollama 启动一个兼容 OpenAI 接口的服务。这里以 OpenAI 兼容接口为例启动后记住base_url和模型名后续代码只需要替换两个参数。# vLLM 启动示例实际参数需要按模型版本调整 python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --host 127.0.0.1 \ --port 80005.4 启动服务与验证如果只是本地跑分析脚本直接把函数串起来执行即可。如果要提供接口就启动 FastAPI 服务然后在浏览器或 curl 里验证。uvicorn api_server:app --host 0.0.0.0 --port 7860启动后用下面的命令快速验证接口是否可用curl -X POST http://127.0.0.1:7860/compare \ -H Content-Type: application/json \ -d {text_a: 这是一段阅读材料, text_b: 这是一段类似的阅读材料}端口冲突时改掉--port参数即可。服务进程不要用nohup裸跑建议接 systemd 或 supervisor 做进程托管至少在脚本里加日志输出。6. 核心实现代码6.1 文本预处理与题目切分第一步是把原始试卷文件转换成“题目 ID 附带内容”的结构。这里用正则粗切实际项目里可以根据不同试卷格式定制切分规则。import re from typing import List, Dict def split_items(raw_text: str) - List[Dict[str, str]]: # 按题号切分支持 1. 1、 Q1 等常见格式 parts re.split(r\n(?(?:\d[.、]|Q\d)), raw_text) items [] for part in parts: lines part.strip().split(\n) if not lines: continue item_id re.match(r(\d[.、]|Q\d), lines[0]) content .join(lines[1:]).strip() if content: items.append({id: item_id.group(0) if item_id else fitem_{len(items)}, content: content}) return items6.2 语义向量构建对切分后的题目内容批量编码。注意长文本要切块否则会被模型截断。from sentence_transformers import SentenceTransformer EMBEDDING_MODEL SentenceTransformer(BAAI/bge-m3) MAX_CHARS 500 def embed_text(text: str): if len(text) MAX_CHARS: return EMBEDDING_MODEL.encode([text], normalize_embeddingsTrue)[0] chunks [text[i:i MAX_CHARS] for i in range(0, len(text), MAX_CHARS)] chunk_vecs EMBEDDING_MODEL.encode(chunks, normalize_embeddingsTrue) return chunk_vecs.mean(axis0)切块后取平均向量是一种简单但有效的策略。如果材料本身段落结构清晰也可以按段落切块再逐段做向量召回最后取最大值作为相似度。6.3 双维度相似度计算把显式文本相似度和语义相似度合并成一个分数。融合方式可以简单加权也可以用规则分组。import difflib import numpy as np def cosine_sim(a: np.ndarray, b: np.ndarray) - float: return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) 1e-9)) def dual_dimension_score(text_a: str, text_b: str, vec_a: np.ndarray, vec_b: np.ndarray) - dict: lexical difflib.SequenceMatcher(None, text_a, text_b).ratio() semantic cosine_sim(vec_a, vec_b) fused 0.4 * lexical 0.6 * semantic return { lexical_score: round(lexical, 4), semantic_score: round(semantic, 4), fused_score: round(fused, 4) }融合权重需要根据业务数据校准。如果误报偏高就增加 lexical 的权重如果漏报偏高就增加 semantic 的权重。不要把阈值固定死要观察实际分数分布。6.4 LLM 复核调用对融合分数处于敏感区间的内容调用 LLM 做最终判定。这里使用 OpenAI 兼容接口方便对接本地 vLLM 或云端服务。import openai client openai.OpenAI( base_urlhttp://127.0.0.1:8000/v1, api_keyEMPTY ) def llm_review(text_a: str, text_b: str) - str: prompt f你是一名试卷素材审查专家。请判断以下两段文本是否存在素材复用、改写或拼凑关系。 材料 A {text_a[:800]} 材料 B {text_b[:800]} 请输出 JSON包含两个字段 - verdict: same / rewrite / partial / different - reason: 简要说明判断理由 resp client.chat.completions.create( modelQwen/Qwen2.5-7B-Instruct, messages[{role: user, content: prompt}], temperature0.1, max_tokens256 ) return resp.choices[0].message.content这里只取前 800 字去复核是为了控制 Token 消耗。实际使用中可以按段落抽取最相似片段把片段交给 LLM而不是把整篇长文都塞进去。7. 接口 API 与批量任务7.1 FastAPI 服务示例如果要把双维度分析能力开放给题库系统或内容管理平台可以封装成 API 服务。下面的代码实现两个接口/compare用于单对文本对比/analyze用于单篇文本检索全库相似内容。from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI() class CompareRequest(BaseModel): text_a: str text_b: str class AnalyzeRequest(BaseModel): text: str top_k: int 10 app.post(/compare) def compare(req: CompareRequest): vec_a embed_text(req.text_a) vec_b embed_text(req.text_b) result dual_dimension_score(req.text_a, req.text_b, vec_a, vec_b) return result app.post(/analyze) def analyze(req: AnalyzeRequest): vec embed_text(req.text) scores, indices search_candidates(index, vec, req.top_k) results [] for score, idx in zip(scores, indices): item corpus[idx] results.append({ id: item[id], content: item[content][:200], semantic_score: round(float(score), 4) }) return {results: results}这里省略了index和corpus的全局初始化代码实际部署时要提前加载。接口启动后用下面的 Python 脚本测试import requests response requests.post( http://127.0.0.1:7860/compare, json{ text_a: 这是第一段阅读材料, text_b: 这是第二段阅读材料 }, timeout30 ) print(response.json())7.2 批量目录处理脚本大规模分析场景下更适合用批量脚本处理目录文件。输入目录放待分析材料输出目录生成 JSON 报告。import json from pathlib import Path input_dir Path(./data/input) output_dir Path(./data/output) output_dir.mkdir(parentsTrue, exist_okTrue) def batch_analyze(): report [] files list(input_dir.glob(*.txt)) for file in files: content file.read_text(encodingutf-8) vec embed_text(content) scores, indices search_candidates(index, vec, top_k5) file_result {file: file.name, candidates: []} for score, idx in zip(scores, indices): item corpus[idx] file_result[candidates].append({ id: item[id], score: round(float(score), 4) }) report.append(file_result) with open(output_dir / report.json, w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2) batch_analyze()批量任务最关键的是日志和断点续跑。建议每个文件处理成功后把结果单独写一个result_xxx.json最后再合并汇总。这样中途失败无需重新计算全部内容。7.3 任务与日志建议在 API 和批量任务中建议做好三件事每个请求或文件记录开始时间、结束时间、处理状态。失败任务设置重试机制Embedding 调用或 LLM 调用偶尔会有超时。LLM 复核结果要缓存避免同一对文本重复调用模型产生额外成本。创建一张简单的结果表字段包括item_id_a、item_id_b、lexical_score、semantic_score、fused_score、llm_verdict、created_at。后续做阈值调整和人工抽检都很方便。8. 性能观察与资源占用8.1 关键性能指标这套框架的性能瓶颈通常有三个位置Embedding 编码阶段。向量索引查询阶段。LLM 复核阶段。Embedding 阶段的执行速度取决于模型大小和是否使用 GPU批量编码比单条循环快很多所以要尽量减少逐条调用。向量索引阶段在万级规模下耗时很低但索引构建需要一次性完成。LLM 复核是成本最高的部分建议只对融合分数超过阈值的文本对调用。8.2 显存与内存观察本地运行 LLM 时用nvidia-smi查看显存占用。不同量化级别、不同上下文长度的显存占用差异非常大不能直接引用别人的数值代替本地测试。如果显存不足可以按优先级调整改用 4bit 或 8bit 量化版本。缩短输入文本只取相似片段。降低 batch size分批推理。用 CPU 跑小参数模型速度慢但稳定。Embedding 模型通常占用的显存或内存较少CPU 也能跑完万级向量化任务。如果数据量到十万级以上建议先把向量缓存到磁盘避免每次启动重复计算。8.3 优化手段从工程角度看优化点集中在几个地方向量结果落盘缓存。同一批题目向量算一次之后直接加载。索引使用 IVF 或其他近似检索方式降低查询耗时。文本对去重不重复计算已经判断过的组合。LLM 复核加流控避免同时打爆 GPU。长文本优先做片段级相似度定位再做整篇融合。观察资源时应该记下三个数字全量向量化的耗时、构建索引的耗时、单次 LLM 复核的耗时。调优后再对比能明显看到瓶颈在哪。9. 常见问题与排查方法问题现象可能原因排查方式解决方案模型加载失败模型文件缺失或路径错误检查模型缓存目录确认网络可访问重新下载或手动指定模型路径显存不足模型参数过大或 batch size 过高查看nvidia-smi当前占用换量化模型减小 batch size接口返回超时LLM 复核阶段 token 生成过长查看服务日志和耗时分布限制 max_tokens减少复核文本长度相似度误报高阈值设置不合理输出分数分布统计误报样本调整权重和阈值结合 LLM 复核长文本结果不准确Embedding 截断导致信息丢失检查切块策略和长度统计分段编码取均值或片段级比对批量任务卡住某个文件异常或进程崩溃查看任务日志定位失败文件加入 try-except 和重试机制向量召回结果为空阈值设置过高或索引未构建检查索引是否加载查看查询阈值降低阈值或重新构建索引端口冲突服务被其他进程占用lsof -i :7860查看占用情况更换端口或关闭旧进程整套流程最容易出现的问题不在代码本身而在数据预处理和阈值设置。建议第一批跑出来的结果不要直接采信先人工抽查 50 对文本确认判定是否符合预期再调整参数。10. 合规边界与内容安全题目附带内容相似度分析处理的是考试材料、出版物片段、题库内容这些数据通常涉及版权和保密要求。以下几个边界必须注意。数据授权与保密。大规模测评材料在正式发布前属于敏感数据不能随意上传到外部服务。优先使用本地部署的 Embedding 模型和 LLM。如果必须使用云端 API需要经过数据安全评估并确认数据传输链路加密。隐私与个人信息。如果题目素材中包含考生作答数据、学生画像等个人信息必须在分析前完成脱敏。相似度分析系统只应该处理去标识化后的文本内容。版权合规。从公开出版物中提取的段落用于相似度比对和查重是合理的但批量复制、长期存储或对外展示需要获得授权。分析报告里的素材来源信息宜只做内部使用。使用范围限制。API 服务要设置访问控制避免未授权调用。批量任务结束后及时清理临时文件中间数据按保密等级管理。11. 最佳实践建议根据实际项目落地经验总结几条可复用的建议。先跑小规模验证集。准备 100 到 200 道题人工标注出“重复、改写、拼凑、正常”四类结果。用这批数据校准双维度权重和阈值再全量运行。保留最小可运行配置。把模型路径、阈值、权重、LLM 接口地址写进配置文件不要硬编码在代码里。这样不同环境切换成本低。建立输入、中间结果、输出目录。原始试卷放input切分后的题目内容和向量缓存放intermediate报告放output。跑完一段就落盘一段避免内存和进程失效导致数据丢失。LLM 复核结果必须有格式校验。调用 LLM 输出 JSON 时可能会遇到格式不完整的情况。要加一个解析后校验逻辑解析失败就重试一次或标记为待人工复核。阈值不是一次定死。不同学科、不同题型附带内容风格差异很大。理科题干的重复度高但正常相似也高文科材料的语义相近更常见。建议按科目分别设置阈值。合规与审核前置。部署前确认数据来源合法、授权清晰。分析结果用于辅助审查不能直接当作侵权或抄袭的最终结论涉及外部版权的内容应提交人工复核。12. 总结与下一步这个双维度 LLM 框架最值得尝试的点是把“字面相似”和“语义相似”分开计算再通过流水线召回和 LLM 复核降低误报。这种设计比单用 Embedding 或单用字符串匹配都更适合大规模测评场景。第一次验证时建议先准备一批已知存在重复或改写的题目跑一遍完整流程重点看几件事语义向量能否召回被改写的材料融合分数是否能把不同关系区分开LLM 复核在模糊样本上是否稳定。这三个点验证通过再扩大到全量题库。最容易踩的坑是长文本直接进 Embedding 模型被截断以及阈值设得太死导致误报或漏报。解决方案也不复杂长文本先分段阈值用数据分布去校准。再往下扩展可以加两条路线。一条是接入 OCR把扫描版试卷和旧书素材纳入分析范围另一条是增加溯源能力把相似片段定位到具体段落输出类似“第 3 段与另一篇材料第 2 段存在连续匹配”的报告。这两条路都属于同一套框架的延伸核心的双维度判定逻辑不需要大改。如果你的业务里也面临题库查重、内容审核、素材版权风险识别这类问题用这个框架先跑一个最小原型比直接上人工审核要快得多。