双维度LLM框架实现大规模试题附带内容相似度自动审查

发布时间:2026/8/30 15:32:50
双维度LLM框架实现大规模试题附带内容相似度自动审查 大规模教育测评中的试题质量审查一直是个耗时耗力的环节尤其是“附带内容相似度”这类容易影响试题公平性的隐性风险传统人工审查成本极高而且不同审查者之间的一致性很难保证。本文围绕一个双维度 LLM 分析框架的落地实现展开完整讲解如何利用语义嵌入初筛与大模型细粒度判断两阶段流水线自动化完成大规模试题附带内容的相似度分析与风险标记。内容覆盖核心概念、框架设计、文本预处理、Embedding 相似度计算、LLM 结构化判断提示词、批量任务编排、成本优化和质量评估附带可运行的 Python 示例代码与工程化建议。1. 背景与核心概念先来理解一个场景。大型教育测评项目例如国际学生评估项目 PISA、各国学业质量监测、入学考试等的题库往往包含成千上万道题目每道题目除了考查的核心知识点之外还包含大量“附带内容”。这里说的“附带内容”指什么简单来说就是题目中与测量目标无关、但考生在作答时会接触到的信息。比如一道数学题里提到的故事情境、一张图表上的国家名称、一篇阅读理解中的人物文化背景、一道科学题里使用的日常用品品牌等。这些内容本身不参与核心能力的测量却可能对部分考生产生额外影响。例如如果某道题使用了特定国家的货币单位那么熟悉该货币体系的考生就可能获得隐性优势。附带内容的相似度分析就是在大规模题库中识别那些在“非核心测量维度”上存在相似甚至雷同的题目。这种相似如果不被发现可能会带来两个问题测试公平性问题考生如果在练习阶段接触过高度相似的附带内容正式考试时可能因为“见过”而产生虚假的熟悉感削弱测量的真实性。题库污染问题当多道题目在附带内容层面高度相似时它们之间的统计独立性会下降影响等值与连接设计的准确性。在传统流程中这种分析完全依赖命题专家和审查人员人工完成。几百上千道题目逐对比较工作量巨大而且不同专家的判断标准难以统一出现漏检几乎是必然的。于是借助大语言模型构建一个自动化的双维度分析框架就成为一个值得深入探索的工程方案。所谓“双维度”是指这个框架从两个互补的维度对题目附带内容进行相似度判断维度一语义嵌入相似度检索。利用预训练文本嵌入模型将题目附带内容转换为向量通过余弦相似度等指标做大规模初筛。这一维度计算效率高、可覆盖全量题目的两两比对但判断颗粒度较粗无法解释“为什么相似”。维度二LLM 细粒度判断。针对初筛得到的候选相似对让大语言模型结合教育测量领域的判断标准对附带内容的结构、功能、潜在公平性影响进行细粒度分析输出结构化的判断结论。这一维度精度高、可解释性强但推理成本较高无法直接对全量组合逐一执行。两个维度级联工作正好弥补彼此的不足。初筛维度负责“大海捞针”LLM 维度负责“验证定性”。整个过程既控制了算力成本又保证了判断结果的可靠性。这也是本文要实现的 LLM Framework 的核心设计思想。2. 整体框架设计2.1 双维度流水线架构整个框架由四个核心模块组成。我们用一个清晰的流程来描述。第一步数据准备与预处理。从题库系统中导出试题原始数据解析题目文本并通过规则或人工标注的方式将附带内容情境描述、图表说明、文化背景等从题目主体中提取出来。这一步的输出是结构化字段题目编号、知识点标签、附带内容文本、附带内容类型。第二步语义嵌入初筛。将附带内容文本送入嵌入模型生成向量表示再对所有题目向量做两两相似度计算。这里可以根据需要选择 Top-K 策略输出超过预设阈值的高相似候选集。假设题库有 5000 道题两两组合约 1250 万对如果全部交给 LLM 判断成本和时间都是灾难但嵌入向量计算只需分钟级能够快速压缩到几百对候选。第三步LLM 细粒度判断。将候选题目对连同附带内容字段构建为结构化提示词调用大语言模型按照预设的判断框架输出相似度等级、相似维度、潜在公平性风险和审查建议。提示词中会嵌入教育测量领域对“附带内容相似”的操作性定义保证输出口径一致。第四步结果聚合与人工复核。LLM 输出的结构化结果汇入审核表按相似度等级排序高风险的相似对进入人工复核环节。系统同时输出判断依据文本方便专家快速确认或驳回。整体来看这套框架是一个典型的“召回 精排”级联结构语义嵌入模型承担召回大语言模型承担精排人工专家承担最终裁决。2.2 相似度判断维度定义为了让 LLM 的判断从“感觉像”升级为“有依据的评估”我们需要把附带内容的相似度拆解成可操作的分项维度。在提示词中我们要求模型从以下四个方面逐一判断分项维度说明示例语义内容相似度附带内容在含义层面的相似程度不关心具体措辞两道题都使用了“在沙漠中寻找水源”的情境结构功能相似度附带内容在题目中的作用形式是否类似都是“商品打折计算”都是“快递运费阶梯计价”表面文本相似度措辞、语句、专有名词的雷同程度两道题的题干中都有完全相同的背景段落公平敏感度附带内容是否可能对特定群体考生产生不公平影响涉及宗教节日、特定地域生活习惯、性别刻板印象等这个四维拆解的价值在于它把模糊的“相似”变成了可解释、可审计的判断依据。LLM 在推理时逐项评估而不是给一个笼统的结论。最终输出中会包含一个综合相似度等级高/中/低以及每个分项的证据描述。2.3 为什么不能直接只用 LLM 或只用向量匹配这里有必要多说一句。有些同学可能会问既然大语言模型这么强为什么不把所有题目两两组合直接丢给 LLM 判断答案很现实。一个 5000 题的题库有 12497500 个两两组合就算只给每组 20 个 token 的输出调用成本也会达到数百万 token 级别而且推理耗时可能长达数天。相比之下嵌入模型的批量计算速度要快几个数量级成本低得多。反过来如果只用向量匹配虽然快但无法回答“为什么相似”也无法判断相似是否达到需要人工介入的程度。两种方案单独使用都有明显短板级联是性价比最高的方案。3. 环境准备与技术选型3.1 软硬件环境本文示例以 Python 3.10 环境为基础。你需要准备以下软件Python 3.10建议使用虚拟环境pip 包管理工具可以访问外部 API 的大语言模型接口本文示例使用 OpenAI 兼容接口可按你的实际环境替换为其他国产模型服务如通义千问、DeepSeek 等至少 8GB 可用内存用于加载嵌入模型3.2 依赖库安装需要安装的核心库包括sentence-transformers加载预训练嵌入模型生成文本向量。numpy向量运算。openai调用大语言模型 API。pandas结果处理。tenacity处理 API 调用中的限流和临时错误。在终端执行pip install sentence-transformers numpy openai pandas tenacity3.3 嵌入模型选型建议嵌入模型的选择直接决定初筛环节的召回质量。当前常用的选择有模型名称特点适用场景bge-large-zh-v1.5中文语义匹配能力强支持中文长文本中文试题库text-embedding-3-smallOpenAI 托管服务无需本地部署通用场景成本低e5-large-v2英文语义匹配表现优秀英文试题库sentence-transformers/all-MiniLM-L6-v2轻量级推理速度快候选集规模极大时版本需要根据你的项目实际情况调整本文示例以BAAI/bge-large-zh-v1.5为例重点演示配置思路。如果你的题目样本以英文为主可以替换为BAAI/bge-large-en-v1.5或sentence-transformers/all-MiniLM-L6-v2。3.4 LLM 选型与精度问题细粒度判断环节选用的大语言模型需要具备较强的指令遵循能力和较稳定的中文理解能力。当前常见的可选模型包括gpt-4o、gpt-4o-mini、通义千问qwen-plus、deepseek-chat等。如果你的数据涉及敏感教育内容务必确认所选模型服务符合当地数据合规要求。这里想专门讨论一个开发大语言模型应用时绕不开的问题LLM 精度。这里说的“精度”有两个层面的含义。第一层是数值精度也就是热词里经常提到的 fp16、fp32、bf16 问题。在本地部署模型时以 fp16 或 bf16 半精度加载模型可以大幅减少显存占用并提升推理吞吐但代价是可能轻微降低数值精度。对于文本生成任务fp16 基本不会带来肉眼可见的质量损失但如果你的下游任务涉及数学推理、数值计算或对输出格式要求极高使用 fp32 更稳妥。bf16 相对于 fp16 的优势在于其指数范围更广在大模型推理时能避免梯度溢出因此成为很多部署框架的默认选择。如果你只是调用云端 API则无需关心这些细节服务端已经做好了精度配置。第二层是判断精度也就是模型输出结论的可靠性。LLM 在教育测量这类高度专业化的任务上并不会天然输出正确判断。为了提升精度我们通常采用结构化提示词、Few-shot 示例、温度参数调低、强制 JSON 输出等方法。这一点在后面的核心代码中会具体演示。4. 核心代码实现下面进入完整的实战环节。我们将按照数据准备、嵌入初筛、LLM 判断、结果聚合的顺序实现一个最小可运行的框架版本。为了便于演示假设我们有一份包含 8 道题的微型题库实际使用时你需要替换为完整的题库数据。4.1 创建项目结构建议按以下结构组织工程代码item_similarity_framework/ ├── data/ │ ├── raw_items.json # 原始题库数据 │ └── preprocessed_items.csv # 预处理后的附带内容数据 ├── src/ │ ├── __init__.py │ ├── config.py # 配置参数 │ ├── preprocess.py # 数据预处理 │ ├── embedding_retrieval.py # 嵌入初筛维度 │ ├── llm_judge.py # LLM 细粒度判断维度 │ └── pipeline.py # 主流程编排 ├── output/ │ └── similarity_report.csv # 最终结果报告 └── requirements.txt4.2 配置文件先编写配置文件把所有可调节的参数集中管理方便后续调优。# src/config.py class Config: # 嵌入模型配置 EMBEDDING_MODEL_NAME BAAI/bge-large-zh-v1.5 EMBEDDING_BATCH_SIZE 32 COSINE_SIMILARITY_THRESHOLD 0.75 TOP_K_CANDIDATES_PER_ITEM 5 # LLM 配置 LLM_API_BASE https://api.openai.com/v1 LLM_API_KEY your-api-key-here LLM_MODEL_NAME gpt-4o-mini LLM_TEMPERATURE 0.1 LLM_MAX_TOKENS 800 # 数据路径 RAW_ITEMS_PATH data/raw_items.json PREPROCESSED_ITEMS_PATH data/preprocessed_items.csv OUTPUT_REPORT_PATH output/similarity_report.csv这里需要注意LLM_API_KEY属于敏感信息在实际项目中不应直接写在代码文件里而应通过环境变量或密钥管理服务注入。后面我们在最佳实践里会再强调这一点。4.3 模拟原始数据为了演示我们构造一个包含 8 道数学题的微型题库。每道题包含题目本身、附带内容文本以及附带内容类型。[ { item_id: MATH-001, subject: math, item_stem: 某商场开展促销活动所有商品一律八折出售。小明购买了一件原价为 250 元的外套实际支付了多少元, incidental_content: 商场促销活动、外套原价计算, content_type: shopping_scenario }, { item_id: MATH-002, subject: math, item_stem: 一家书店举行开业酬宾每本书在原价基础上降价 20%。小红买了一本原价 40 元的图书需要支付多少钱, incidental_content: 书店开业酬宾、图书降价 20%, content_type: shopping_scenario }, { item_id: MATH-003, subject: math, item_stem: 一辆汽车以每小时 80 千米的速度行驶从甲地到乙地需要 2.5 小时甲乙两地之间的距离是多少千米, incidental_content: 汽车匀速行驶、两地距离计算, content_type: transport_scenario }, { item_id: MATH-004, subject: math, item_stem: 某果园去年收获苹果 6000 千克今年比去年增产 15%今年收获苹果多少千克, incidental_content: 果园苹果产量增长, content_type: agriculture_scenario }, { item_id: MATH-005, subject: math, item_stem: 学校食堂购进大米 300 千克已经吃了 35%还剩下多少千克, incidental_content: 学校食堂大米消耗, content_type: school_life_scenario }, { item_id: MATH-006, subject: math, item_stem: 某工厂去年产值 500 万元今年预计增长 20%今年产值预计为多少万元, incidental_content: 工厂产值增长百分比, content_type: production_scenario }, { item_id: MATH-007, subject: math, item_stem: 篮球比赛中张明投中了 12 个球命中率为 75%他一共投了多少个球, incidental_content: 篮球比赛命中率计算, content_type: sports_scenario }, { item_id: MATH-008, subject: math, item_stem: 一项工程甲队单独完成需要 10 天乙队单独完成需要 15 天两队合作需要多少天完成, incidental_content: 工程问题、两队合作完成时间, content_type: engineering_scenario } ]保存为data/raw_items.json。这是我们模拟的题库实际项目中你可以从题库数据库导出类似结构的 JSON。4.4 数据预处理模块预处理模块负责加载原始数据并将附带内容文本统一转换为后续模块需要的格式。这一步虽然简单却是保证整个流程稳定的基础。# src/preprocess.py import json import pandas as pd def load_raw_items(file_path: str) - list: 加载原始题库 JSON 文件。 with open(file_path, r, encodingutf-8) as f: return json.load(f) def preprocess_items(raw_items: list) - pd.DataFrame: 将原始题库数据转换为 DataFrame并清洗附带内容文本。 records [] for item in raw_items: incidental_content item.get(incidental_content, ).strip() if not incidental_content: # 没有附带内容的题目跳过或者在记录中标记为空 incidental_content [无附带内容] records.append({ item_id: item[item_id], subject: item.get(subject, ), item_stem: item.get(item_stem, ), incidental_content: incidental_content, content_type: item.get(content_type, unknown) }) df pd.DataFrame(records) return df if __name__ __main__: raw_items load_raw_items(data/raw_items.json) df preprocess_items(raw_items) df.to_csv(data/preprocessed_items.csv, indexFalse, encodingutf-8-sig) print(f预处理完成共 {len(df)} 条记录。)预处理模块目前做的事情相对简单主要是去空格、空值兜底和统一格式。在真实项目中这里还会包含更复杂的逻辑比如从长题干中自动截取情境描述段落、过滤公式和图片占位符、统一专有名词的写法等。预处理的质量会直接影响嵌入向量的质量值得多花时间。4.5 维度一语义嵌入初筛这是整个框架的“召回层”。我们使用sentence-transformers加载嵌入模型生成附带内容文本的向量表示然后计算两两余弦相似度输出候选相似对。# src/embedding_retrieval.py import numpy as np import pandas as pd from sentence_transformers import SentenceTransformer from config import Config class EmbeddingRetrieval: 维度一基于语义嵌入的相似度初筛。 def __init__(self, model_name: str None): model_name model_name or Config.EMBEDDING_MODEL_NAME self.model SentenceTransformer(model_name) def encode_texts(self, texts: list) - np.ndarray: 将文本列表批量编码为归一化向量。 embeddings self.model.encode( texts, batch_sizeConfig.EMBEDDING_BATCH_SIZE, normalize_embeddingsTrue, # 归一化后点积即为余弦相似度 show_progress_barTrue ) return np.array(embeddings) staticmethod def compute_pairwise_similarity(embeddings: np.ndarray) - np.ndarray: 计算两两相似度矩阵。 similarity_matrix np.dot(embeddings, embeddings.T) return similarity_matrix def retrieve_candidates( self, df: pd.DataFrame, threshold: float None, top_k: int None ) - pd.DataFrame: 根据阈值和 Top-K 策略输出候选相似对。 threshold threshold or Config.COSINE_SIMILARITY_THRESHOLD top_k top_k or Config.TOP_K_CANDIDATES_PER_ITEM texts df[incidental_content].tolist() item_ids df[item_id].tolist() embeddings self.encode_texts(texts) similarity_matrix self.compute_pairwise_similarity(embeddings) candidates [] for i in range(len(item_ids)): for j in range(i 1, len(item_ids)): score float(similarity_matrix[i][j]) if score threshold: candidates.append({ item_a: item_ids[i], item_b: item_ids[j], embedding_similarity: round(score, 4) }) # 如果候选数量过多按相似度排序后保留每个题目的 Top-K candidates_df pd.DataFrame(candidates) if len(candidates_df) 0 and top_k is not None: # 以 item_a 为基准保留每个题目的 top_k 个候选 candidates_df[rank] candidates_df.groupby(item_a)[embedding_similarity].rank( methodfirst, ascendingFalse ) candidates_df candidates_df[candidates_df[rank] top_k].drop(columnsrank) return candidates_df这里需要解释一个关键点。normalize_embeddingsTrue的作用是在编码阶段就对向量做了 L2 归一化这样后面直接用矩阵乘法np.dot(embeddings, embeddings.T)得到的就是余弦相似度无需再逐对计算。对于几千道题目的题库矩阵乘法的高效性优势非常明显。下面运行一下这个模块看看初筛结果。# 临时运行脚本 import pandas as pd from src.embedding_retrieval import EmbeddingRetrieval df pd.read_csv(data/preprocessed_items.csv) retrieval EmbeddingRetrieval() candidates retrieval.retrieve_candidates(df) print(candidates)预期输出的候选对中MATH-001 和 MATH-002 的相似度应该较高因为它们都涉及“折扣促销 原价计算”的购物情境MATH-004 和 MATH-006 也较相似因为它们都以“产量/产值增长百分比”为情境。这就是初筛维度需要捕捉的相似信号。4.6 维度二LLM 细粒度判断初筛输出的候选对数量已经大幅下降接下来是为每一对候选调用大语言模型进行细粒度判断。这是整个框架中提示词设计最关键的部分。为了让模型输出稳定且易于解析我们要求模型以 JSON 格式返回结果并对四个分项维度逐一打分。同时为了控制温度对结果随机性的影响将temperature设为 0.1。# src/llm_judge.py import json from openai import OpenAI from tenacity import retry, stop_after_attempt, wait_exponential from config import Config class LLMJudge: 维度二基于 LLM 的细粒度相似度判断。 def __init__(self): self.client OpenAI( api_keyConfig.LLM_API_KEY, base_urlConfig.LLM_API_BASE ) def build_prompt(self, item_a: dict, item_b: dict) - str: 构造细粒度判断提示词。 return f 你是一名资深教育测量专家负责审查大规模标准化测评题库中的试题附带内容相似度。 【任务目标】 判断下面两道试题在附带内容层面的相似程度。附带内容指题目中与考查知识点无关的背景情境、场景描述、文化设定等信息。你的判断将用于题库风险筛查因此必须给出可解释、可审计的结论。 【判断维度】 1. 语义内容相似度附带内容的语义含义是否相似。 2. 结构功能相似度附带内容在题目中起到的情境功能是否类似。 3. 表面文本相似度措辞、专有名词、数字等表面特征是否雷同。 4. 公平敏感度附带内容是否可能对特定群体考生产生不公平影响。 【输出格式】 请严格输出 JSON 对象不要包含任何额外文字。JSON 结构如下 {{ semantic_similarity: high/medium/low, structural_function_similarity: high/medium/low, surface_similarity: high/medium/low, fairness_sensitivity: high/medium/low, overall_similarity: high/medium/low, reason: 简要说明判断依据, risk_suggestion: 是否需要人工复核及原因 }} 【试题 A】 题号{item_a[item_id]} 知识点{item_a[subject]} 题干{item_a[item_stem]} 附带内容{item_a[incidental_content]} 附带内容类型{item_a[content_type]} 【试题 B】 题号{item_b[item_id]} 知识点{item_b[subject]} 题干{item_b[item_stem]} 附带内容{item_b[incidental_content]} 附带内容类型{item_b[content_type]} retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def judge_pair(self, item_a: dict, item_b: dict) - dict: 对一对题目执行 LLM 判断返回结构化 JSON 结果。 prompt self.build_prompt(item_a, item_b) response self.client.chat.completions.create( modelConfig.LLM_MODEL_NAME, temperatureConfig.LLM_TEMPERATURE, max_tokensConfig.LLM_MAX_TOKENS, messages[ {role: system, content: 你是一名严格、严谨的教育测量领域专家。}, {role: user, content: prompt} ] ) content response.choices[0].message.content # 防御性解析如果模型输出中包含多余空格或换行先清理 content content.strip().replace(json, ).replace(, ) result json.loads(content) return result提示词中有几个细节值得注意。第一我们明确告诉模型“附带内容”的定义并强调这是与考查知识点无关的背景信息。这是因为大语言模型默认会将整道题视为一个整体来理解如果不加限定它很容易把“考查知识点本身是否相似”混入判断中导致输出失真。第二输出格式采用 JSON 结构约束。这是大语言模型应用中最常用的结构化输出手段之一。在提示词中直接给出完整的 JSON 示例并明确要求“不要包含任何额外文字”可以大幅提高解析成功率。第三加入了tenacity重试机制。实际调用大语言模型 API 时网络抖动、限流是常态重试机制能显著提高框架的鲁棒性。4.7 主流程编排有了两个核心维度后我们来编写一个主流程文件把整个流水线串起来。# src/pipeline.py import json import time import pandas as pd from config import Config from preprocess import load_raw_items, preprocess_items from embedding_retrieval import EmbeddingRetrieval from llm_judge import LLMJudge def run_pipeline(): 执行完整的双维度 LLM 相似度分析流水线。 # 1. 数据准备 print(步骤 1/4加载并预处理原始数据...) raw_items load_raw_items(Config.RAW_ITEMS_PATH) df preprocess_items(raw_items) item_dict df.set_index(item_id).to_dict(orientindex) # 2. 维度一嵌入初筛 print(步骤 2/4执行语义嵌入初筛...) retrieval EmbeddingRetrieval() candidates_df retrieval.retrieve_candidates(df) if len(candidates_df) 0: print(未发现超过相似度阈值的候选对流程结束。) return print(f初筛得到 {len(candidates_df)} 个候选相似对。) # 3. 维度二LLM 细粒度判断 print(步骤 3/4执行 LLM 细粒度判断...) judge LLMJudge() results [] for _, row in candidates_df.iterrows(): item_a item_dict[row[item_a]] item_b item_dict[row[item_b]] try: llm_result judge.judge_pair(item_a, item_b) results.append({ item_a: row[item_a], item_b: row[item_b], embedding_similarity: row[embedding_similarity], **llm_result }) except Exception as e: print(f判断失败{row[item_a]} vs {row[item_b]}错误{e}) results.append({ item_a: row[item_a], item_b: row[item_b], embedding_similarity: row[embedding_similarity], overall_similarity: error, error_message: str(e) }) # 轻量限速避免触发 API 限流 time.sleep(0.2) # 4. 结果聚合与输出 print(步骤 4/4生成报告...) result_df pd.DataFrame(results) result_df result_df.sort_values( by[overall_similarity, embedding_similarity], ascending[True, False] ) result_df.to_csv(Config.OUTPUT_REPORT_PATH, indexFalse, encodingutf-8-sig) print(f分析完成报告已保存至 {Config.OUTPUT_REPORT_PATH}) if __name__ __main__: run_pipeline()这个主流程把四个模块串联成一条完整的流水线。每次运行都会输出similarity_report.csv其中每一行是一个候选相似对包含嵌入相似度分数和 LLM 从四个分项维度给出的判断结果。人工审查人员拿到这份报告后就可以按相似度等级优先处理高风险数据。4.8 运行与验证在项目根目录执行python src/pipeline.py预期输出类似于步骤 1/4加载并预处理原始数据... 预处理完成共 8 条记录。 步骤 2/4执行语义嵌入初筛... 初筛得到 3 个候选相似对。 步骤 3/4执行 LLM 细粒度判断... 判断完成MATH-001 vs MATH-002 判断完成MATH-004 vs MATH-006 判断完成MATH-005 vs MATH-007 步骤 4/4生成报告... 分析完成报告已保存至 output/similarity_report.csv打开output/similarity_report.csv你应该能看到以下结构的表格item_aitem_bembedding_similaritysemantic_similaritystructural_function_similaritysurface_similarityfairness_sensitivityoverall_similarityreasonMATH-001MATH-0020.8721highhighmediumlowhigh两道题都使用商场/书店折扣促销场景附带内容在语义和功能上高度相似MATH-004MATH-0060.8134highhighlowlowhigh果园产量与工厂产值增长在语义上相似均属于百分比增长情境MATH-005MATH-0070.6923mediummediumlowlowmedium食堂大米消耗与篮球命中率情境差异较大但均涉及百分比计算这个结果和我们的直观判断基本一致。MATH-001 和 MATH-002 是典型的“高相似附带内容”如果它们同时出现在一份试卷中考生可能会因为第一个题目熟悉的情境而在第二个题目上获得额外的熟悉感这是命题时应该避免的。LLM 的reason字段为人工复核提供了清晰的线索。5. 大规模场景下的优化策略上面实现的是最小可运行版本。如果把这个框架应用到真实的大规模测试项目中还需要考虑以下几个关键优化方向。5.1 分块与并行处理当候选对数量很大时逐对调用 LLM API 的耗时可能达到数小时甚至更久。这时可以将候选对分成多个批次使用多线程或多进程并发调用。不过这里需要注意并发过高容易触发云 API 的限流策略建议采用“自适应限速”的方式启动少量线程遇到限流错误时根据 API 返回的Retry-After头信息动态调整等待时间。另一种做法是对 LLM 调用做批量拼接。在一次请求中放入多对题目要求模型逐对输出 JSON 数组。这样可以显著减少请求次数但对提示词设计和输出解析的要求更高模型也更容易出现“遗漏某对”的问题需要人工校验结果完整性。5.2 嵌入模型的批量推理与缓存对于超大题库嵌入计算可以直接利用 GPU 加速。sentence-transformers支持将模型加载到 GPU 上只需做如下修改model SentenceTransformer(Config.EMBEDDING_MODEL_NAME, devicecuda)如果使用 GPU批量大小可以适当调大例如 128 或 256以充分利用显存的并行计算能力。此外所有题目的嵌入向量可以缓存到本地文件中例如.npy或parquet格式。因为题目的附带内容通常不会频繁变化缓存可以避免每次运行都重复计算嵌入。对于十万级题库这个优化能将运行时间从小时级缩短到分钟级。5.3 相似度阈值和 Top-K 的动态调整在真实项目中固定阈值往往不够灵活。不同学科、不同题型、不同附带内容类型相似度的“正常水平”可能差异很大。例如数学题中的“购物打折情境”出现频率远高于科学题中的“实验室操作情境”如果统一使用 0.75 的阈值可能导致数学学科候选对过多、科学学科候选对过少。建议的做法是分学科、分内容类型分别确定阈值。先在样本上计算相似度分布选择能够有效区分正负样本的百分位点作为阈值。同时Top-K 策略保证每一个题目最多进入 K 个候选对避免某些“万能情境”题目霸占整张候选列表。5.4 结果去重与聚合在最终报告中一道题目可能出现在多个相似对中。当需要向命题团队输出整改建议时最好将相似对聚类成“相似组”。简单做法是使用连通图聚类如果 A 和 B 相似B 和 C 相似则将 A、B、C 归为同一个相似组。这样审查人员可以一次性处理一组题目而不是零散地处理多个两两组合。6. 质量评估与常见问题6.1 如何评估框架的判断质量要让这套框架真正可信必须做系统性的质量评估。建议从三个层面进行第一抽样人工复核。从 LLM 判断结果中按比例抽取样本交给至少两位教育测量专家独立复核。计算模型结果与专家判断的一致性如 Cohens Kappa 系数。如果 Kappa 值低于 0.7说明提示词或判断标准还需要调整。第二召回率测试。构建一个包含已知相似对的小型测试集测试框架能否成功召回这些相似对。如果某些相似对被初筛阶段遗漏说明嵌入模型或阈值需要调整如果初筛成功但 LLM 判断为“低相似”则可能需要优化提示词或对判断结果设定不同的触发人工复核的规则。第三错误类型分析。重点关注两类错误将不相似的题目误判为相似误报以及将真正相似的题目漏判漏报。在教育测评场景中漏报比误报更危险因为它可能导致潜在公平性问题未被发现。所以如果必须在两者之间取舍建议将阈值调低一些让更多候选对进入 LLM 判断宁可多花一点推理成本也不放过任何潜在风险。6.2 LLM 输出不稳定怎么办在实际开发中即使设置了temperature0.1模型偶尔也会输出不稳定的结果比如 JSON 格式错误、多维度判断之间逻辑矛盾等。这在提示词工程中几乎是无法完全避免的。应对方法分为三层。第一层是防御性解析像我们代码中做的那样在json.loads之前清理代码块标记和多余字符。第二层是失败重试当 JSON 解析失败时重新调用一次模型并额外发送一条消息提示“你上一次输出格式不符合要求请重新严格按 JSON 格式输出”。第三层是最坏情况兜底如果重试后仍然失败将该条记录标记为analysis_failed转入人工审查流程而不是直接丢弃。6.3 常见问题排查清单问题现象常见原因解决思路初筛阶段没有任何候选对相似度阈值设置过高附带内容文本质量差嵌入模型与语言不匹配降低阈值检查预处理文本更换合适的嵌入模型候选对数量过多阈值设置过低附带内容高度模板化提高阈值启用 Top-K 截断按内容类型分布分析LLM 返回 JSON 解析失败模型输出混入了额外文本提示词约束不够强模型能力不足增强提示词约束加入失败重试更换更强大的模型LLM 判断明显错误附带内容定义不清晰模型未正确理解教育测量概念增加 Few-shot 示例细化判断标准描述API 调用频繁超时并发过高网络环境不稳定降低并发数加入重试机制使用异步客户端结果中相似对重复出现同一道题参与了多个候选对通过连通图聚合成相似组6.4 与人工审查流程的衔接自动化框架的价值不在于替代人工而在于降低人工审查的负担。实践中最合理的分工是嵌入初筛和 LLM 判断负责把可能的问题对找出来给出初步判断和理由教育测量专家负责对高风险结果进行最终裁定并将裁定结果作为反馈数据定期微调提示词和阈值。为了让专家更高效地使用报告可以在 CSV 报告中增加一列expert_decision专家判定结果并筛选出overall_similarity为 high 的记录优先处理。如果发现某个 LLM 判断维度与专家意见系统性不一致应该优先检查该维度的提示词定义是否清晰。7. 最佳实践与工程建议7.1 提示词设计的迭代思路提示词不是一次就能写好的。建议把它当作代码一样进行版本管理每次调整都记录变更原因和效果。迭代时注意三个方向定义清晰化。如果模型经常把“考查知识点相似”误判为“附带内容相似”就在提示词中用更直白的反面示例引导模型“注意两道题都是考查百分数计算这不属于附带内容相似附带内容相似仅指背景情境和场景设定层面的相近。”示例引导。Few-shot 示例对提示词效果的影响非常大。准备 2 到 3 组典型正例和反例在提示词中直接给模型参考输出稳定性会明显提升。输出约束强化。如果模型频繁输出非 JSON 内容可以在提示词末尾增加一句“你的回答必须能被 Python 的 json.loads 方法直接解析”。7.2 成本控制与性能优化在大规模题库场景中LLM 调用成本是不可忽视的。一个十万级题库即使初筛后只剩 0.1% 的候选对也有 1000 对需要 LLM 判断。按每次推理消耗 1500 个 token 计算总消耗约 150 万 token。这个数量级在商用 API 上会产生显著费用。控制成本的常用策略包括使用更小的模型做初步压缩。例如先用gpt-4o-mini或国产轻量模型对所有候选对做一次粗判断只把判断为“medium”以上的候选对送入更强的模型做最终审核。压缩附带内容文本。在提示词中只保留与情境相关的关键信息删除数字、单位和重复修饰词可以在不损失判断精度的情况下减少 token 消耗。设置每日调用预算上限。当消耗超过预算时自动将剩余候选对标记为“待人工审查”避免出现意外的高额账单。7.3 数据安全与合规教育测评数据通常涉及未成年人、考试安全等敏感信息处理时必须非常谨慎。使用云端大语言模型 API 前务必确认以下事项数据传输和存储是否满足当地数据保护法规要求。是否可以使用私有化部署模型如基于开源权重的本地推理方案替代公有云 API。在提示词中不要发送考生的身份信息、姓名、考试机构等无关字段只发送题目文本和必要属性。对 API 密钥进行安全存储建议使用环境变量或密钥管理服务禁止硬编码在代码库中。如果合规要求严格可以考虑使用本地部署的量化模型。这里需要用到前面提到的数值精度知识。以 fp16 或 bf16 精度加载一个 7B 参数模型大约需要 14GB 显存使用 4-bit 量化后可以压缩到约 4GB适合中小型测试环境。虽然量化可能带来轻微质量损失但通过良好的提示词设计通常仍能保持可接受的判断精度。7.4 日志与可审计性在自动化质量审查系统中可审计性是不可或缺的。每一次 LLM 判断都应该记录完整的原始提示词、模型输出、版本信息和时间戳。当后续发现某个判断存在问题时能够回溯当时的上下文。建议在结果表中增加以下字段字段名含义prompt_version提示词版本号llm_model_name使用的模型名称llm_temperature温度参数judge_timestamp判断时间raw_response模型原始返回内容expert_review_status人工复核状态有了这些字段整个分析链路才是完整、可信、可重构的。7.5 从试点到全量推广最后给一个工程落地建议。不要把整个题库一次性交给这套框架处理而是采用“试点 → 评估 → 推广”的策略。第一步选取 100 到 200 道覆盖不同学科和题型的题目运行完整流水线。第二步将 LLM 判断结果交给至少两位专家评审计算一致性识别提示词和阈值中需要调整的部分。第三步确认精度达标后逐步扩展到全量题库。这样可以将风险控制在最小范围内也更容易获得业务部门的信任。8. 总结与下一步方向本文完整实现了一个用于大规模教育测评附带内容相似度分析的双维度 LLM 框架。框架由语义嵌入初筛和大语言模型细粒度判断两个维度组成前者负责从海量题目中快速召回候选相似对后者负责对候选对执行多维度、可解释的精细化判断。整个框架兼顾了计算效率、判断精度和人工可审计性是 LLM 应用于教育测量领域的一个典型落地案例。从工程层面你已经掌握了数据预处理、向量相似度计算、提示词设计、结构化输出解析、批量任务编排和结果报告生成的完整链路代码。如果后续需要继续深入可以从以下几个方向切入引入 RAG 知识库将教育测量的相关政策、命题规范文档向量化在 LLM 判断时检索相关知识作为上下文提升判断的专业性。建立反馈闭环将专家复核结果作为训练或微调数据持续优化判断提示词甚至微调一个小型专用模型。扩展到其他分析任务双维度框架的设计思路不仅适用于附带内容相似度也可以迁移到题目难度预测、选项干扰分析、跨学科内容一致性检查等多个场景。完善质量保障体系加入更严格的召回率测试、提示词回归测试和模型版本升级验证流程确保框架在模型迭代后依然稳定可靠。大语言模型给教育测量领域带来了新的自动化可能性但真正落地时严谨的流程设计、可解释的判断依据和人工审核机制仍然不可或缺。希望本文的代码和工程经验能为你自己的项目提供一个可参考的起点。