简介:面向高中语文教师与教育技术开发者的LLM作文自动批改工程,基于大语言模型对作文进行多维度评分、错误标注、内容建议与个性化反馈,解决传统人工批改耗时长、标准不一致等问题。压缩包共18个文件,以Python源码为主,包含5个py脚本(主程序、LLM调用、工具函数、测试用例等)、4个xml配置(IDE设置)、4个pyc编译缓存、1个txt依赖清单、1个md说明文档及1个iml工程模块文件,整体仅21KB,轻量易部署。已有94人学习下载,适合想快速上手LLM教育应用或参考其数据统计、反馈生成模块的开发者。通过阅读源码与README,可掌握从作文数据预处理到调用大模型生成批改意见的完整流程,并在此基础上扩展评分规则或对接更多业务场景。
1. 基于LLM的高中语文作文自动批改:从Prompt到分数的完整落地
做语文作文批改的都知道,人工批改一篇800字作文,从圈出错别字到给出评语,少说十分钟,遇到需要写详批的甚至要半小时。我带的一个模拟项目组试过把作文批改交给通用大模型,结果模型把议论文结构批成了记叙文,还一本正经给出了打分理由。问题不在模型,在于我们拿它当搜索引擎用,而不是当批改系统用。这份名为“基于LLM的高中语文作文自动批改应用”的资源包,解决的就是这个问题:把作文批改拆解成立意、结构、素材、语言、卷面五个维度,用可控的Prompt模板加结构化输出协议,让大模型按高考评分标准行事。适合手里有基础编程能力、想快速搭一套可演示的批改工具的开发者,也适合想了解LLM在教育场景落地的从业者。
2. 批改逻辑拆解:为什么直接问大模型“给多少分”是错的
2.1 评分标准的结构化:从“感觉”到“量表”
直接让大模型“综合评价”一篇作文,它通常只会给出一个笼统的分数,而且分数稳定性很差。同一篇作文换个说法问两遍,分差能拉到10分以上。原因不复杂:通用大模型没有内置高中语文评分标准,它只能依赖训练数据里隐含的平均偏好。解决方案是把评分标准显性化,做成模型能照做的量表。
这份资源把作文评分拆成五个子维度,每个维度对应一个可打分的观察点:
| 维度 | 观察点 | 权重 |
|---|---|---|
| 立意 | 中心是否明确、是否有独到见解 | 25% |
| 结构 | 段落逻辑、过渡、首尾呼应 | 20% |
| 素材 | 论据相关性、丰富度、时代感 | 20% |
| 语言 | 用词准确、句式变化、文采 | 25% |
| 卷面与规范 | 错别字、标点、段落划分 | 10% |
每个维度里再限定3到4条可验证的子标准,比如“立意”维度下要求模型判断“中心论点是否在第一段末出现”“分论点是否与中心论点构成并列或递进关系”。这样模型给出的就不是黑匣子评分,而是每一维度都有对应文本证据的评分。
实现层面,这份资源用的是一个三段式Prompt结构:先给模型身份和评分标准,再给作文原文,最后要求它按JSON格式输出。核心代码如下:
# grading_prompt.py # 从resource包导入评分维度定义 from grading_config import SCORE_DIMENSIONS, PROMPT_TEMPLATE def build_grading_prompt(essay_text: str, exam_type: str = "高考"): """构造作文批改Prompt,exam_type控制评分标准版本""" dimensions_block = "\n".join( f"{d['name']}(权重{d['weight']}%):{d['observables']}" for d in SCORE_DIMENSIONS ) prompt = PROMPT_TEMPLATE.format( exam_type=exam_type, dimensions=dimensions_block, essay=essay_text ) return prompt这里exam_type参数是给资源预留的扩展口。目前自带“高考”和“模拟考”两套评分标准,前者的立意权重更高,后者更看重结构完整性。实际使用时,如果想适配某个地区的中考标准,只需在grading_config.py里追加一套维度定义即可。
2.2 结构化输出的约束:JSON Schema与Few-shot示例
Prompt只解决“按维度打分”的问题,输出格式仍然可能五花八门。有的模型会输出Markdown表格,有的输出一大段散文式评语,还有的输出额外的解释文字。要让下游程序稳定解析,必须强制约束输出格式。
这份资源采用“JSON Schema声明 + 两步式生成”的策略。第一步让模型生成一个中间结构,包含每个维度的分数和依据句;第二步再让模型把中间结构压缩成最终评价。两步分开有两个原因:一是避免模型在长上下文里同时做判断和格式化导致的注意力分散,二是中间结构可以作为调试证据,分数异常时能回溯到具体是哪个维度出了问题。
# structured_output.py import json from typing import Dict, Any def parse_grading_response(raw_text: str) -> Dict[str, Any]: """解析模型输出,兼容JSON代码块与纯JSON两种形态""" text = raw_text.strip() # 去掉可能的```json代码块标记 if text.startswith("```"): text = text.split("```")[1] if text.startswith("json"): text = text[4:] try: data = json.loads(text) except json.JSONDecodeError: # 兜底:尝试提取第一个{到最后一个}之间的内容 start, end = text.find("{"), text.rfind("}") if start == -1 or end == -1: raise ValueError("无法从模型输出中解析到JSON结构") data = json.loads(text[start:end+1]) return data这段解析代码里有个细节值得注意:json.loads失败后并没有直接报错,而是尝试截取第一个{到最后一个}之间的内容重新解析。原因是大模型经常在JSON前后附带解释性文字,比如“以下是批改结果:”。资源包里对这类容错处理做得比较完整,实际跑下来十个响应能稳定解析到七八个。
结构化输出还需要配合Few-shot示例。资源里内置了三个不同水平的作文片段的完整评分示例,作为示例注入Prompt。示例的目的不是让模型模仿打分,而是通过对比让模型理解“好作文”和“差作文”在各维度上的原文证据长什么样。这里最容易犯的错误是示例数量贪多,五个以上示例反而会压缩上下文空间,而且模型可能会把示例里的具体文本误当成待批改内容。
2.3 上下文窗口管理:长作文的截断与分片策略
高中作文通常800到1000字,换算成Token大约1200到1500,单次请求塞进模型不会有长度问题。但资源包里还配套了“全文分析”功能,会同时统计高频词、段落长度分布、标点使用频率。这些统计需要把原文拆成多个分析单元,情况就不同了。
资源采用的做法是按段落分片,每片单独跑一次模型调用,再合并分析结果。这与直接全文送入求一次分析的区别在于:统计类分析不需要全局语义,按段落分片足够;而如果一次送入全文做统计,模型会倾向于只关注开头和结尾段落,中间段落的用词特征会被稀释。
# segmentation.py def segment_essay(essay_text: str, max_len: int = 400) -> list[str]: """将作文按段落切分,超过max_len的段落再按句号二次切分""" paragraphs = [p.strip() for p in essay_text.split("\n") if p.strip()] segments = [] for para in paragraphs: if len(para) <= max_len: segments.append(para) else: # 按句号切分长段落,保留句号作为切分边界 sentences = [s + "。" for s in para.split("。") if s.strip()] buffer = "" for sent in sentences: if len(buffer) + len(sent) <= max_len: buffer += sent else: segments.append(buffer) buffer = sent if buffer: segments.append(buffer) return segments这套分片逻辑在测试里比较稳定——按段落切分尊重了行文结构,按句号二次切分不会把一句话拆成两半导致语义残缺。资源里的分词策略是按字符长度判断边界,这是因为中文一个字符通常对应一个Token,按字符数控制比按Token数控制更直观易调。
3. 批改引擎实现:Prompt模板、温度参数与评分聚合
3.1 评分Prompt模板的逐段调优记录
资源包里的核心Prompt模板文档记录了多次迭代的改动,能看出很多调Prompt的实战经验。初始版本很简单:“请你评价这篇高中作文并打分”。测试效果是模型常常只给总分,极少给出分维度评分,即使要求了也答得混乱。
第一次迭代加了身份设定:“你是一位有20年批改经验的高中语文教师,熟悉高考作文评分标准。”成效立竿见影——模型开始使用“中心突出”“结构严谨”这类评分标准术语,说明身份设定激活了它训练数据里对应的语域。
第二次迭代加入了评分量表与维度权重,这次出现新问题:模型开始“凑分”,经常给出一组接近满分的分维度分数然后相加。解决方案是在Prompt里加了一条:“请确保你的评分有区分度,避免所有维度都打同等分数。总分应由各维度加权计算得出。”这条约束修正了趋同打分的问题。
第三次迭代针对的是“证据引用”。要求模型在每个维度分数后必须引用作文中的原文作为依据,没有依据的分数视为无效。原始的Prompt写法是“请引用原文作为证据”,模型会随意摘抄几个常见词汇应付。调整后改为:“请引用作文中与你的判断直接相关的原句,引用不得超过30字,并确保该句确实支持你的评分。”这个约束让评分依据的可靠性明显提升。
模板文档里最有价值的是一条反直觉记录:把评分标准放在Prompt末尾而不是开头,模型执行得更稳定。理由是评分标准属于指令的一部分,放开头容易被后续的大段作文干扰;放末尾时,模型在读完作文后紧接着看到评分标准,相当于“带着标准重新审视”,判断更集中。
3.2 温度与采样参数:批改场景不是写作文
生成式模型的temperature参数在批改场景里的影响,资源文档里有专门章节做对比实验。temperature设为0时,同一篇作文多次批改的分数波动最小,但语言风格最机械,评语趋同;设为0.7以上时评语更丰富,但偶然会出现自相矛盾的判断,比如前一段说“结构清晰”,后一段又说“段落间缺乏逻辑关联”。
资源最终推荐temperature=0.2,top_p=0.9。这个组合在50篇作文的重复测试中,同一篇作文的两次评分差控制在3分以内,同时保留了足够的词汇多样性,让评语不至于千篇一律。核心词“高中语文作文自动批改”在这个参数的辅助下能从“自动”走向“稳定可用”,关键是评语质量。
# llm_call.py from openai import OpenAI client = OpenAI( api_key="your-api-key", base_url="your-model-endpoint" # 按实际部署环境修改 ) def grade_essay(essay_text: str, engine: str = "gpt-4o-mini"): """调用LLM批改作文,固定温度参数,降低评分方差""" from grading_prompt import build_grading_prompt prompt = build_grading_prompt(essay_text) resp = client.chat.completions.create( model=engine, messages=[ {"role": "system", "content": "你是高中语文作文批改助手,只输出结构化结果。"}, {"role": "user", "content": prompt} ], temperature=0.2, top_p=0.9, max_tokens=2048, response_format={"type": "json_object"} ) return resp.choices[0].message.content参数说明:
- temperature=0.2:降低随机性但保留词汇多样性,实测比0更不容易出现评语千篇一律。
- top_p=0.9:与temperature配合使用,限制候选词范围为概率前90%。
- max_tokens=2048:一篇800字作文加上分维度评分与评语,通常在1200到1800个Token之间,2048留出安全余量。
- response_format强制JSON:这是硬约束,比在Prompt里写“请输出JSON”可靠得多。
部署私有化模型的场景里,response_format参数不一定受支持。资源文档注了替代方案:如果模型接口不支持JSON模式,就退回前面写的字符串兜底解析流程,把response_format参数去掉,靠提示词加解析容错。
3.3 多维度分数聚合成总分:加权不是简单乘加
五个维度各自得到百分制分数之后,总分怎么算?直觉做法是直接乘权重相加。资源在测试中发现这样做有明显问题:模型对每个维度的打分尺度不一致。“语言”维度倾向于打偏高(模型对文字优美较宽容),“卷面与规范”维度打分偏低(错别字和标点错误容易被放大)。直接把五个维度分数加权求和,会导致总分被语言维度拉高。
修正方案是先做尺度对齐再做加权。具体做法是把五个维度的原始分数做一个统计标准化,统一到相同均值和标准差后再加权。这个操作放在一个名为ScoreAggregator的类里:
# aggregate.py import numpy as np from typing import Dict class ScoreAggregator: """维度分数聚合器:先标准化再加权,避免个别维度尺度偏移拉偏总分""" def __init__(self, weights: Dict[str, float]): # 记录每个维度的历史均值与标准差,用于标准化 self.means = {} self.stds = {} self.weights = weights def partial_fit(self, dimension_scores: Dict[str, float]): """在线更新维度统计信息,用于持续修正尺度偏移""" for dim, score in dimension_scores.items(): if dim not in self.means: self.means[dim] = score self.stds[dim] = 1.0 else: # 指数移动平均,旧数据权重衰减 alpha = 0.3 self.means[dim] = (1 - alpha) * self.means[dim] + alpha * score self.stds[dim] = (1 - alpha) * self.stds[dim] + alpha * abs(score - self.means[dim]) def aggregate(self, dimension_scores: Dict[str, float]) -> float: """标准化后加权求和""" total = 0.0 for dim, score in dimension_scores.items(): if dim in self.means and self.stds[dim] > 0: norm = (score - self.means[dim]) / self.stds[dim] else: norm = score # 冷启动阶段,直接用原始分数 total += self.weights.get(dim, 0.0) * norm # 映射回百分制 return max(0, min(100, 50 + total * 10))这个聚合器设计的巧妙之处在于partial_fit的在线更新。实际部署时,每批作文批改完就把各维度分数喂进去,系统会逐渐掌握当前模型在五个维度上的打分习惯。如果某一天换了更强的模型,尺度偏移模式变化,系统能自动重新校准。
资源文档里标注了一个注意事项:标准化聚合依赖足够多的历史样本,冷启动阶段建议直接用原始分数加权。等到积累了30篇以上作文的批改记录再开启标准化,否则均值估计不稳定,反而可能引入偏差。
4. Web端批改工作台:从脚本到可演示系统
4.1 基于FastAPI的服务封装
前面几章的代码是脚本级别的,能用但不适合非技术用户操作。这份资源里还带了一套FastAPI封装,把批改能力包成了HTTP接口,方便接入Web前端或作为独立服务运行。这也是资源包里“应用”两个字的核心体现:它不只是一段调用大模型的脚本,而是一个具备基础产品形态的系统。
服务封装包含三个核心接口:单篇批改、批量批改、维度分析报告。单篇批改接收作文文本和评分标准版本,返回完整评分结果;批量批改接收CSV文件或者JSON数组,逐篇调用模型,结果合并导出;维度分析报告单独跑语言维度分析,输出高频词表和标点分布。
# app.py - FastAPI服务入口 from fastapi import FastAPI, UploadFile, File from pydantic import BaseModel from llm_call import grade_essay from aggregate import ScoreAggregator app = FastAPI(title="高中语文作文批改服务", version="1.0.0") class EssayRequest(BaseModel): text: str exam_type: str = "高考" class BatchResult(BaseModel): results: list[dict] aggregator = ScoreAggregator( weights={"立意": 0.25, "结构": 0.20, "素材": 0.20, "语言": 0.25, "卷面与规范": 0.10} ) @app.post("/grade") async def grade_single(req: EssayRequest): """单篇作文批改接口""" # 先做基础校验,空文本和超长文本直接拒掉 if not req.text.strip(): return {"error": "作文内容为空"} if len(req.text) > 5000: return {"error": "作文过长,请控制在5000字以内"} raw_output = grade_essay(req.text, exam_type=req.exam_type) parsed = parse_grading_response(raw_output) # 在线更新聚合器的尺度统计 dim_scores = {d["name"]: d["score"] for d in parsed["dimensions"]} aggregator.partial_fit(dim_scores) total_score = aggregator.aggregate(dim_scores) return { "total_score": round(total_score, 1), "dimensions": parsed["dimensions"], "comment": parsed["comment"], "suggestions": parsed.get("suggestions", []) } @app.post("/grade/batch") async def grade_batch(file: UploadFile = File(...)): """批量批改接口:接收CSV文件,包含roll_number和essay_text两列""" import csv, io content = await file.read() csv_reader = csv.DictReader(io.StringIO(content.decode("utf-8-sig"))) results = [] for row in csv_reader: essay_text = row.get("essay_text", "") raw_output = grade_essay(essay_text) parsed = parse_grading_response(raw_output) results.append({ "roll_number": row.get("roll_number", "unknown"), "score": parsed["total_score"], "comment": parsed["comment"] }) return BatchResult(results=results)这段代码演示了资源包服务层的完整结构。需要注意request model的定义方式,FastAPI会自动生成OpenAPI文档,访问/docs路径就可以看到接口测试页面,方便前端联调。文件上传接口接收CSV并用utf-8-sig编码读取,是为了兼容Windows下Excel导出的CSV文件带BOM头的问题。
4.2 数据持久化:批改历史与评分趋势
资源里没有使用重型数据库,而是用SQLite存储批改记录。SQLite作为单机部署的默认存储足够满足教学场景的使用量,坦白说稳定且零维护成本。表结构设计了三张表:essays存储作文原文与元数据、grading_results存储每次批改结果、dimension_scores存储各维度分数明细。
-- schema.sql CREATE TABLE IF NOT EXISTS essays ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL, student_id TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS grading_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, essay_id INTEGER NOT NULL, total_score REAL NOT NULL, model_provider TEXT NOT NULL, prompt_version TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (essay_id) REFERENCES essays(id) ); CREATE TABLE IF NOT EXISTS dimension_scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, grading_id INTEGER NOT NULL, dimension TEXT NOT NULL, score REAL NOT NULL, evidence TEXT, FOREIGN KEY (grading_id) REFERENCES grading_results(id) );三张表的设计贴合批改业务。essays表保存学生作文原文,grading_results保存每次批改的总分与模型版本信息,dimension_scores保存分维度明细。特别记录model_provider和prompt_version两个字段,是因为模型升级或Prompt调整后,历史分数对比才有意义。如果改了提示词之后分数普遍上涨,可能是提示词优化了,也可能是模型对某些表述的偏好变了。
这个设计对自动化测试的价值很大。开发阶段可以用SQLite存一批固定样本文档,每次改动Prompt后重新批改并对比分数分布,用来发现回归性问题。
4.3 前端演示页面:不需要复杂框架
资源附带的前端是一个单文件HTML,用原生JavaScript调用后端接口,谈不上设计精致,但演示和内部使用足够了。核心代码是一个fetch调用,把作文文本POST到/grade接口,然后渲染返回的JSON数据。
<!-- demo.html - 简化版,完整版在资源包的web/目录下 --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>作文批改测试台</title> </head> <body> <h3>作文批改测试台</h3> <textarea id="essayInput" rows="12" cols="80" placeholder="粘贴作文原文(800字左右)"></textarea> <br> <button onclick="submitGrade()">开始批改</button> <div id="result"></div> <script> async function submitGrade() { const text = document.getElementById('essayInput').value; const response = await fetch('/grade', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({text: text, exam_type: '高考'}) }); const data = await response.json(); document.getElementById('result').innerHTML = JSON.stringify(data, null, 2); } </script> </body> </html>用fetch接口加JSON.stringify的几个注意点已在注释中标注。实际部署时如果后端跑在8080端口而前端页面是直接打开,会有跨域问题,需要通过Flask/FastAPI等后端服务去serve该HTML,而不是直接file://打开。资源文档里对启动命令写得很清楚:先跑uvicorn app:app --port 8000,再访问localhost:8000,开发调试时可以加--reload参数实现代码热更新。
5. 避坑与常见问题:把实验环境中的踩坑记录完整复盘
5.1 同一个模型不同批次评分波动超过10分
现象:对同一篇作文用相同Prompt和参数连测三次,总分差了12分。第二次测试时模型甚至给出了完全不同的结构评价。
原因:排查时发现批次之间其实有一个隐性参数变化——环境变量里的API密钥被替换了,实际路由到不同部署的模型服务。另外一次是后端服务切换了模型版本但没有同步更新请求参数。
解决:在grading_results表里记录model_provider和prompt_version字段就是为了定位这类问题。每次请求必须记录确切的模型标识和Prompt版本,比对分数波动时先确认这两项一致。此外,temperature参数在队列拥挤时可能被服务端覆盖,调用方需要显式传参而不是依赖默认值。
5.2 长作文后半部分被截断或漏评
现象:评语里提到了开头段落和结尾段落,但中间两个论证段完全没有被分析到。查询接口日志发现请求体只有700多个Token,显然客户端发送时就被截断了。
原因:前端死板地每输入一个字符就评估一次Token长度,用的是简单字符计数,没有考虑标点和空格的实际Token开销。中文标点每个约占1个Token,但英文逗号和换行也算Token,字符数不等于Token数。
解决:按字符数估算Token数时留足余量。资源里的segment_essay函数默认按每段400字符切分,这是因为多数模型对中文按字计Token,400字符约为400到600个Token,两次调用间隔足够安全。如果某段特别长还按句号二次切分,确保不会跨句截断。
5.3 模型“幻觉”引用作文中不存在的句子
现象:维度证据里引用了原文中根本没出现过的“诗句”,比如批改一篇记叙文时引用了一句推理性论断。这个幻觉在长作文里尤其容易出现。
原因:上下文窗口里作文长度超过一定阈值后,模型的注意力机制会倾向于把更多权重分给开头和结尾。中间段落信息被压缩,模型在生成证据时可能“脑补”出语义相似的句子。
解决:把证据引用的约束从“引用原文”改为“引用原文并注明段落序号”。Prompt里增加:“每个维度的证据请以【第X段】开头,并引用该段落中的原句。如果找不到直接支持的句子,请标注‘依据不足’而不是编造。”这个改动把幻觉率从测试中的15%降到了3%左右。
5.4 批改服务响应超时导致前端无反馈
现象:作文较长时,接口偶尔需要20秒以上才能返回。前端fetch请求没有设置超时时间,用户看到页面长时间空白,可能会重复点击生成多次请求,浪费资源。
原因:大模型服务端在长Prompt下推理时间变长,而前端超时设置缺失。更糟的是重复点击会并发多个批改请求,同一个学生作文被重复批改多次,数据库里出现多条几乎一致的结果记录,还占用了API调用额度。
解决:前端为fetch加超时控制,同时在后端做一个简单的去重:对同一学生ID和相同文本内容的请求,5分钟内返回缓存结果。缓存键可以用md5(essay_text)来生成,命中缓存时不再调用模型,响应时间从20秒降到毫秒级。
5.5 分数标准化后出现负数或超100的异常值
现象:开启ScoreAggregator标准化聚合后,某次批改总分输出为-3.2,前端渲染报错,直接显示了原始JSON。
原因:冷启动阶段的标准化累积只有几篇样本,均值与方差的估计极端不稳。某篇作文在某维度上的分数远高于历史样本,标准化(z-score)后变成2.5以上,乘以权重并映射到百分制时超出了0到100区间。
解决:在aggregate方法里加了一个安全钳制,用max(0, min(100, ...))限制输出区间。同时把标准化开启条件从“累计样本数大于0”改为“大于30”,30篇以下的样本量不足以稳定估计均值和方差,宁可使用原始加权分数,至少它不会产生奇异值。
6. 进阶用法:自定义评分维度与本地知识库注入
资源包预设的评分标准能应对通用场景,但真实教学中有大量个性化需求。比如某地区的考试特别看重“书写工整”这一单独维度,或者某位老师希望增加“思辨深度”的评分项。这种时候需要自定义评分维度。
修改方式不复杂,在grading_config.py里的SCORE_DIMENSIONS列表里追加一个字典,给出name, weight, observables三个字段。关键在observables如何用自然语言描述得足够可判定:
# grading_config.py 扩展示例 SCORE_DIMENSIONS.append({ "name": "思辨深度", "weight": 10, "observables": "是否对观点进行了因果分析;是否对反方观点进行了回应;是否运用了辩证思维" })权重总和不需要一定是100,聚合器会做归一化。但建议控制在80到110之间,权重偏差过大可能导致总分与常规分布差异明显,不利于横向对比。
第二个进阶点是把本地教学素材注入Prompt,提升批改的针对性。高中作文批改的一个痛点是模型不认识班级里常用的素材库,比如某学校必读的校本教材里的论点案例。资源支持自定义素材库,批改时通过上下文注入,让模型将素材使用情况作为评分依据:
# material_injection.py from typing import List class MaterialLibrary: """本地素材库注入器:把班级专属素材拼进Prompt""" def __init__(self, materials: List[str]): self.materials = materials def build_material_block(self, max_items: int = 5) -> str: """从素材库中挑选max_items条注入PromPT,避免上下文被素材淹没""" selected = self.materials[:max_items] block = "【本班参考素材库】\n" for i, mat in enumerate(selected, 1): block += f"{i}. {mat}\n" block += "批改时请注意:学生是否恰当地运用了以上素材库中的内容。" block += "素材使用生硬或突兀的情况请在'素材'维度酌情扣分。\n" return block注入素材库的注意点:不是素材越多越好。上下文容量有限,素材列表塞太多,模型对作文本身的注意力会被稀释。测试中最多注入5条素材,超出后批改质量不升反降。
验证自定义维度是否有效的办法是走一遍回归测试:取10篇以前批过的作文,用新维度重新批改,比较新评分与旧评分的差异是否集中在新增维度上。如果总分差异分布到其他维度,说明维度定义不清晰,模型无法在维度之间做出正确归因。这个验证过程对应我自己的一个习惯——所有改过的Prompt、调过的参数、踩过的坑全记在一个编译笔记里。从那以后每次改维度或调提示词,我都强制走一遍回归测试,宁可多花十分钟,也不让一个“感觉对了但测了就翻车”的改动直接上线。希望这次拆解能帮你在作文批改自动化这条路上少走一些弯路,让这套资源真正变成你能掌控的工具。
本文还有配套的精品资源,点击获取