
“不是……而是……”这个句式本身没有原罪。它的问题在于被当成了万能胶水一段话里出现三四次读者就会觉得这段话不是人在说是模型在填。很多人把“AI味”理解成玄学其实拆开看就是几个可量化的指标——对举句式密度、抽象名词占比、句长方差、连接词堆叠。指标一旦能被数出来就能被改掉。这次我们不聊“AI写得好不好”这种没有结论的话题只解决一个具体问题怎么把一段充满“不是A而是B”的文字改成读起来像人写的。文章会给出一套可以落地的流程先用规则脚本做体检把问题句标出来再按四类策略逐条改写最后接上大模型做批量处理几十篇稿子一起过。整套流程对硬件几乎没有要求纯 CPU 就能跑普通笔记本足够。如果你平时写技术博客、公众号、产品文案或者要给团队做内容审核这套方法可以直接搬走。如果你只是想搞清楚为什么自己写的句子会被说“有AI味”前三节看完就有答案。后面几节偏工程实现涉及 Python 脚本、正则规则、提示词模板和批量调用代码都能复制运行路径和模型名按自己环境替换即可。需要先说明一点本文讨论的是写作质量不涉及任何规避平台原创检测的内容。改写的目的是让表达更具体、信息更密实而不是把别人的东西洗一遍当成自己的。素材来源、引用出处、合作授权这些该标的还是要标。1. AI味问题速览先搞清楚要改什么在动手之前得先明确“AI味”到底指什么。很多讨论把它神秘化说是一种“感觉”。但从文本层面看它是一组可以列举的特征集合。把特征列成表改的时候就有靶子不至于一边改一边怀疑人生。下面这张表是我在整理大量被吐槽“有AI味”的段落之后归纳出来的覆盖了句法、词汇、逻辑、排版四个层面。每一项都配了典型表现和可操作的调整方向。问题维度典型表现为什么显假调整方向对举句式过密“不是A而是B”“并非A而是B”连续出现否定半句常是零信息铺垫直接删否定半句只留结论抽象名词堆叠“能力”“体系”“维度”“抓手”“闭环”无法验证读者抓不住具体对象换成动作、数字、对象名句长过于均匀每句 20 到 30 字节奏一致缺少人类写作的呼吸感长句拆短偶尔用一句话收尾连接词套路化“首先/其次/此外/然而/因此”齐活逻辑关系被词强行标注靠语序和内容承接删连接词万能形容词“强大”“高效”“极致”“全面提升”没有比较基准给出对比对象和测量方式段落结构模板化每段都是“观点解释总结”读者能预测下一句打乱顺序先给场景再给结论结论前置过度开篇三段全是判断句没铺垫就下结论像推销先给事实和操作再给判断排比收尾三句短句并列做结尾视频稿残留痕迹用具体的下一步替代口号这张表可以直接当检查清单用。写完之后对着扫一遍命中三项以上就值得改。从表里能看出一个规律AI味的核心不在于用了哪些词而在于信息密度和具体性。一段话如果每句都能落到具体对象、具体动作、具体数字上即使用了“不是而是”读者也不会觉得假。反过来一段话全是抽象判断就算一个套路词都没有读起来照样空。所以后面所有的改写策略本质都在做同一件事把不可验证的判断换成可验证的描述。这个原则记住具体技巧忘了也能自己推。本文要完成的实操内容包括搭建一个纯 CPU 的文本体检脚本、定义六类可量化的检测指标、给出四类改写策略和对照示例、写一套大模型批量改写的接口调用代码、整理一份排错清单。整套流程不需要显卡不需要联网也能跑检测部分。2. 适用场景与使用边界先说清楚这套方法适合谁用免得读者花时间看完发现用不上。适合的场景有这么几类。第一类是自媒体和技术博客作者稿子是自己写的但写着写着就滑进了模板句式需要工具帮忙挑出来。第二类是团队里的内容审核岗每天要过大量稿件靠人眼扫效率低用规则先筛一遍能省很多时间。第三类是产品和技术文档写作者说明书、帮助文档、更新日志这类文本对“具体性”要求高抽象表达会直接导致用户看不懂。第四类是做提示词工程的人你需要知道模型的默认输出长什么样才能在提示词里针对性地约束。不适合的场景也要讲明白。学术论文、法律文书、医疗说明这类文本句式僵化有时候是规范要求不能为了“像人写的”就随便改。引用他人原文的段落不要动改了就成了篡改。已经发布的正式公告、合同条款改写前必须走审核流程。还有几条边界必须划清楚。版权方面批量改写只适用于你自己拥有版权或者已获授权的文本。拿别人的文章改写后署自己名字不管改得多彻底都是侵权。素材、数据、案例的来源该标注就标注改写不改变引用义务。隐私方面如果批量处理的文本里含有人名、联系方式、内部项目代号走云端接口之前要做脱敏。本地跑脚本没有这个问题但调用外部 API 时数据会离开你的机器这一点要有意识。平台规则方面改写的目的应该是提升可读性。如果目的是绕过原创检测、批量生产低质内容去铺量那属于另一回事不在本文讨论范围。技术本身中立用途得自己把握。团队协作方面如果有多个人的稿子一起过流水线改写结果不要直接覆盖原文落盘时保留原始版本和修改版本两套文件方便回溯和对比。这一点在第七节的目录设计里会具体展开。3. AI味的三层来源拆解要改得准得先知道问题出在哪一层。把所有毛病笼统归为“AI味”改的时候就会变成凭感觉调词。拆成三层之后每一层对应不同的处理手段。3.1 句法层对举句式的信息结构问题“不是A而是B”这个结构在中文里是合法且常用的鲁迅也写钱钟书写得更多。它的问题出在使用频率和填充内容上。拆开看这个句式提供两个信息位A 是被否定的说法B 是作者主张的说法。它的修辞效果建立在“读者本来可能误以为 A”这个前提上。如果读者压根没这么想A 就是白说的。举个典型的空转例子这不是一个工具的问题而是一个思维方式的问题。读者原本没觉得这是工具问题你否定了一个不存在的误解然后给出一个更抽象的说法。整句话的信息增量接近于零但字数不少还带着一种“我要说重点了”的姿态。这种句子出现一次无伤大雅一段里出现三次读者就会产生被灌水的感觉。再看一个有效的用法这个报错不是权限问题而是配置文件里port写成了字符串。这里 A 是真实存在的误判方向——大部分人看到这个报错第一反应就是查权限。否定它有实际价值B 又落到具体字段和具体类型上。同样是对举信息密度完全不同。区别在哪A 是否对应真实误解B 是否落到具体对象。这两条就是句法层的判断标准。3.2 逻辑层先否定再肯定的推理偷懒比句式更隐蔽的是逻辑习惯。模型在组织段落时倾向于用“先破后立”的结构先说旧观念不对再说新观念对。这个结构本身没问题问题是它经常被用来替代真正的论证。正常的论证需要给出证据、数据、案例、反例。而“先破后立”只用一句话就完成了立场切换中间缺少支撑。读者读完知道自己该相信什么但不知道为什么。这种写法在演讲里有效因为演讲靠语气和停顿补足说服力。落到文字上就露馅了。文字没有语气读者只能靠信息判断信息不够就会觉得空。破解办法很直接每次写完“A 不对B 才对”追问一句“凭什么”。答得出来就把答案补进去答不出来就说明这句话暂时不该写或者应该降级成猜测语气。3.3 词汇层抽象名词与万能形容词词汇层是最容易改的一层也是最容易反弹的一层。因为抽象名词读起来“高级”写作者会不自觉地往上靠。常见的抽象名词清单能力、体系、维度、层面、抓手、闭环、链路、生态、范式、底座、赋能、势能、心智、颗粒度。常见的万能形容词清单强大、高效、极致、全面、深度、智能、丝滑、稳定可靠、开箱即用。这些词不是不能用是不能单独用。它们必须挂靠具体对象才有意义。举例抽象表达问题具体化改写性能强大强到什么程度跟谁比单张 8G 显卡上 512×512 出图约 3 秒一张高效处理处理什么多快1000 条 CSV 记录单线程约 40 秒跑完开箱即用开箱之后要做什么解压后双击start.bat浏览器打开 7860 端口全面支持支持哪些不支持哪些支持 JPG/PNG暂不支持 HEIC深度优化优化了哪个环节把重复的正则编译提前到循环外耗时降了一半右边这一列的共同点都能被验证。读者可以自己去试试完发现对得上信任就建立了。这就是具体性的价值跟文笔好坏无关。三层拆完之后改写的优先级也清楚了词汇层最好改但收益最小句法层改起来快、收益明显逻辑层最难改但决定文章质量。时间有限的话先扫句法层。4. 环境准备与文本体检脚本搭建从这一节开始进入工程实现。先说明硬件和软件门槛其实非常低。硬件方面第 4 到第 6 节的检测脚本纯 CPU 运行内存占用取决于文本长度处理几万字的稿子也就几十兆。普通办公本、老旧笔记本都能跑。第 7 节的大模型改写可以选择调用云端接口或者本地跑小模型本地跑的话显存需求按实际模型版本和量化精度测试本文不给具体数字因为不同模型差异太大写死数字容易误导。软件方面需要 Python 3.8 以上版本。第三方依赖只需要requests调用接口用文本处理全用标准库的re和json就够不装 jieba 也能跑。如果你的文本是中英混排想做得更细可以加分词库但基础检测不需要。目录结构建议这样组织后面所有代码都基于这个结构ai-tone-check/ ├── check.py # 检测脚本 ├── rewrite.py # 批量改写脚本 ├── config.json # 配置 ├── rules/ │ └── patterns.py # 正则规则 ├── inputs/ # 待检测文本一行一个文件 ├── reports/ # 检测报告输出 └── outputs/ # 改写结果输出前置检查清单如下跑之前过一遍能省不少事Python 版本确认python --version低于 3.8 就升级。依赖安装pip install requests只做检测的话可以不装。编码统一所有输入文件存成 UTF-8Windows 下用记事本另存时注意选编码不然读进来全是乱码。文件命名输入文件建议用英文或拼音命名避免个别系统下的路径编码问题。网络检查只有调用云端接口时才需要联网检测脚本离线可用。端口检查本地起服务做改写的话确认目标端口没被占用具体见第八节。依赖安装一条命令足够# 只做规则检测标准库就够无需额外依赖 python -c import re, json, sys; print(sys.version) # 需要调用接口时再装 requests pip install requests如果在公司内网pip 走不通可以用离线包# 先在有网环境下载 pip download requests -d ./pkgs # 拷贝到目标机器后离线安装 pip install --no-index --find-links./pkgs requests环境准备好之后下一步写规则。5. 规则检测把AI味变成可量化指标这一节把前面讨论的特征翻译成正则和统计量。核心思路是不做语义判断只做模式匹配和统计速度快、结果稳定、可以反复跑。5.1 六类检测指标的定义我选了六个指标覆盖句式、词汇、节奏三个方向。每个指标都能独立计算最后合成一个总分。第一个是对举句式密度统计“不是A而是B”“并非A而是B”“与其说A不如说B”这类结构的出现次数除以总句数。经验上超过 8% 就该警觉超过 15% 基本可以确定是模板输出。第二个是抽象名词密度统计抽象名词词表在全文的命中次数除以总字数并乘以一千得到每千字命中数。超过 15 就偏多。第三个是万能形容词密度算法同上词表单独列。超过 10 偏多。第四个是句长标准差把全文按句号问号感叹号切成句子算每句字数的标准差。人类写作标准差通常较大长短句交错模板输出往往集中在 15 到 30 字之间标准差偏小。这个指标要结合文本类型看技术文档本身句长就均匀不能一刀切。第五个是连接词密度统计“首先、其次、此外、然而、因此、综上、总的来说”这类词的每千字命中数。超过 8 偏多。第六个是三连排比检测检测连续三个结构相似的短句。这个用正则不太好做简化处理检测连续三个句子长度接近且都以相同虚词开头的情况。5.2 检测脚本实现下面是完整的检测脚本直接可运行。规则集中在PATTERNS和两个词表里后续想加规则改这里就行。# check.py import re import json import math from pathlib import Path from dataclasses import dataclass, field, asdict # ---------- 规则定义 ---------- PAIR_PATTERNS [ re.compile(r不是[^。\n]{1,30}[,]?\s*而是), re.compile(r并非[^。\n]{1,30}[,]?\s*而是), re.compile(r与其说[^。\n]{1,30}不如说), re.compile(r不在于[^。\n]{1,30}而在于), re.compile(r与其[^。\n]{1,20}不如), ] ABSTRACT_WORDS [ 能力, 体系, 维度, 层面, 抓手, 闭环, 链路, 生态, 范式, 底座, 赋能, 势能, 心智, 颗粒度, 矩阵, 组合拳, ] BUZZ_WORDS [ 强大, 高效, 极致, 全面, 深度, 智能, 丝滑, 稳定可靠, 开箱即用, 颠覆, 革命性, 里程碑, ] CONNECTORS [ 首先, 其次, 再次, 此外, 然而, 因此, 综上, 总的来说, 总而言之, 值得注意的是, 不难发现, ] SENT_SPLIT re.compile(r[。!?;\n]) dataclass class Report: file: str total_chars: int 0 total_sents: int 0 pair_hits: list field(default_factorylist) pair_density: float 0.0 abstract_per_k: float 0.0 buzz_per_k: float 0.0 connector_per_k: float 0.0 sent_len_std: float 0.0 score: float 0.0 def split_sents(text: str): return [s.strip() for s in SENT_SPLIT.split(text) if s.strip()] def per_k(count: int, total_chars: int) - float: if total_chars 0: return 0.0 return round(count / total_chars * 1000, 2) def analyze(text: str, name: str) - Report: r Report(filename) r.total_chars len(re.sub(r\s, , text)) sents split_sents(text) r.total_sents len(sents) # 1. 对举句式 for pat in PAIR_PATTERNS: for m in pat.finditer(text): r.pair_hits.append(m.group(0)) if r.total_sents: r.pair_density round(len(r.pair_hits) / r.total_sents * 100, 2) # 2/3/4. 词表密度 r.abstract_per_k per_k(sum(text.count(w) for w in ABSTRACT_WORDS), r.total_chars) r.buzz_per_k per_k(sum(text.count(w) for w in BUZZ_WORDS), r.total_chars) r.connector_per_k per_k(sum(text.count(w) for w in CONNECTORS), r.total_chars) # 5. 句长标准差 lens [len(s) for s in sents] if len(lens) 1: mean sum(lens) / len(lens) var sum((x - mean) ** 2 for x in lens) / len(lens) r.sent_len_std round(math.sqrt(var), 2) # 6. 加权总分权重可按自己语料调整 r.score round( min(r.pair_density / 8, 1) * 35 min(r.abstract_per_k / 15, 1) * 20 min(r.buzz_per_k / 10, 1) * 15 min(r.connector_per_k / 8, 1) * 15 max(0, 1 - r.sent_len_std / 12) * 15, 1, ) return r def main(): in_dir Path(./inputs) out_dir Path(./reports) out_dir.mkdir(exist_okTrue) results [] for f in sorted(in_dir.glob(*.txt)): text f.read_text(encodingutf-8) rep analyze(text, f.name) results.append(asdict(rep)) print(f{f.name}: score{rep.score} pair{rep.pair_density}% fabstract{rep.abstract_per_k}/k std{rep.sent_len_std}) (out_dir / report.json).write_text( json.dumps(results, ensure_asciiFalse, indent2), encodingutf-8 ) if __name__ __main__: main()脚本跑起来是这样的把待检测的 txt 丢进inputs/执行python check.py终端会打印每篇的分数和关键指标同时reports/report.json里落一份完整结果包含所有命中的对举句原文方便逐条对照修改。5.3 分数怎么读分数是 0 到 100越高越像模板输出。但不是分数高就必须大改要结合文本类型。技术文档、API 说明这类文本句长本来就均匀词表命中也会偏高分数到 40 左右是正常的。营销文案、观点文章这类分数超过 50 就值得动手了。真正需要重点看的是pair_hits这个列表。它记录了每一处对举句的原句这些句子是最好的修改切入点。逐条看过去问自己两个问题否定半句是在反驳真实存在的误解吗肯定半句落到具体对象了吗。两个问题有一个答不上这句就改。一个实际例子。假设原文某段是这样的这不是简单的参数调整而是对整个流程的重新设计。它不只是提升了速度更是改变了工作方式。检测会命中两处对举句pair_density在这一段里是 50%严重超标。改写时把两句都拆开落到具体内容上原来要手动改三个配置文件现在改一个。步骤从 7 步降到 3 步中间那次重启也省掉了。改写后一个对举句式都没有但信息量更足读者能直接对照自己的情况判断。5.4 规则库的维护规则不是写完就一劳永逸的。跑一段时间之后会出现两类情况需要调整。一类是误报。比如技术文档里“不是必须项”这种正常用法会被“不是”开头匹配到虽然正则限定了后面必须跟“而是”但边界情况还是会漏。处理办法是把误报的原文片段收集起来在规则里加排除条件。另一类是漏报。新的模板句式会不断出现比如“从A到B的转变”“让X回归Y”这类结构需要定期补充进规则库。建议每处理完 20 篇稿子回看一次把新发现的模式加进去。规则文件建议单独放一个模块不要和主逻辑混在一起改规则的时候不用动主脚本。6. 改写实战四类可复用策略检测只是定位问题真正决定质量的是改写。这一节给四类策略覆盖绝大部分对举句的处理场景。每类都配原句和改后句可以直接照搬。6.1 策略一直接删掉否定半句适用条件否定半句反驳的是读者根本没想过的说法。原句这个功能不是锦上添花而是解决了一个真实痛点。问题没人觉得这是锦上添花否定半句是空转。改后只留肯定部分但还不够具体。改后这个功能解决的是批量导入时字段错位的问题之前每次都要手动核对一遍。再举一例原句这不是一个技术问题而是一个管理问题。改后代码本身的改动量很小难点在于要协调三个团队同时上线。改后的版本把“管理问题”这个抽象说法拆成了具体的协调动作和时间约束。信息量翻倍字数差不多。6.2 策略二把对举换成因果或条件适用条件两个分句之间其实存在因果、条件、转折关系用对举掩盖了真实逻辑。原句不是模型不够强而是数据没准备好。改写方向把“数据没准备好”作为原因“模型表现不好”作为结果用因果关系重写。改后训练集里同一个实体的标注有三套写法模型学到的是噪声指标自然上不去。这种改法的好处是逻辑关系显性化。读者能直接看到因果链不用自己脑补。条件关系的例子原句与其纠结选哪个模型不如先把数据清洗做扎实。改后如果训练集里还有 20% 的重复样本换任何模型指标都差不多。先把去重和标注规范定下来再比模型。6.3 策略三用数字和场景替换判断适用条件句子表达的是程度、效果、规模这类可以量化的东西。原句这个优化不是小幅提升而是数量级的改善。改后优化前跑完 10 万条要 47 分钟优化后 3 分 20 秒主要省在把重复的正则匹配提到了循环外。这里要提醒一句修改时补进去的数字必须是你自己测过的不能为了好看编造。没有实测数据就写“在我们那台测试机上大约快了十倍具体数字因机器而异”这样既给了量级又留了余地。场景替换的例子原句使用门槛不是降低了而是彻底消失了。改后原来要装 Python、配环境变量、跑三条命令现在解压双击start.bat浏览器打开就能用。6.4 策略四保留对举但把 A 写实有些场景下对举是必要的尤其是要纠正一个普遍误解的时候。这时候不要删而是把 A 写具体。原句这不是显存不足而是显存碎片。改后报错写的是显存不足但任务管理器里显存还有 3G 空着。这种情况一般是碎片导致的把 batch size 调小、重启进程通常能解决。这个改法保留了“不是A而是B”的结构因为 A 确实是真实存在的误判。同时把判断依据和解决方向都补上了读者能自己动手验证。四类策略的适用条件整理成表策略适用条件主要收益风险点删否定半句A 是虚构误解立刻减字数去套路感可能丢掉必要的澄清换成因果/条件分句间有真实逻辑关系逻辑链显性化需要想清楚因果关系补数字和场景表达程度、规模、效果可信度提升数字必须真实把 A 写实A 是普遍误解保留修辞且信息更足需要掌握具体细节这四类策略在实际改写时可以混用。一段话里既删了否定半句又把结论落到了数字上效果最好。6.5 一段完整改写对照光看单句不够直观贴一段完整对照。改前这个项目的重点不是功能有多全而是能不能稳定跑起来。它不是要替代现有工具而是提供另一种可能。真正的价值不在于技术本身而在于它降低了门槛。检测结果三处对举句一段 78 字密度爆表。抽象词命中“价值”“门槛”“技术”。改后这个项目功能不多就三个批量转码、自动重命名、失败重试。它跑在我一台 2019 年的笔记本上4G 内存处理 200 个文件用了 11 分钟中间没崩。如果你现在的流程是手动一个个点它会省你一些时间如果你已经在用带队列管理的工具那它帮不上什么忙。改后 130 字左右没有一个对举句式但信息密度高得多功能清单、测试环境、具体耗时、适用和不适用的情况都有。读者读完能立刻判断自己要不要试。这就是改写的目标——不是把句子改漂亮是把判断依据交给读者。7. 接入大模型批量改写提示词模板与接口调用规则检测能定位问题句但改写还得有人动手。稿子少的时候手改就行稿子多的时候需要上自动化。这一节讲怎么用大模型做批量改写包括提示词怎么写、接口怎么调、目录怎么组织。7.1 提示词模板批量改写用的提示词和聊天用的不一样核心要求是稳定、可控、输出格式固定。下面这个模板可以直接用方括号里的内容按需替换。你是一名中文技术编辑。请改写下面这段文字目标是去掉模板化表达提升具体性。 改写要求 1. 删除所有不是A而是B并非A而是B与其说A不如说B结构。如果否定半句提供了必要澄清改成因果或条件句。 2. 把抽象名词能力、体系、维度、层面、抓手、闭环等替换成具体对象或动作。 3. 把万能形容词强大、高效、极致、全面等替换成可验证的描述。原文没有数据的地方不要编造数字改为描述测试条件或直接删除。 4. 句长要有变化避免每句都是 20 到 30 字。 5. 不要增加原文没有的事实、数字、机构名、人名。 6. 保持原文的技术准确性专有名词和代码不要改动。 7. 输出只包含改写后的正文不要解释改了什么不要加小标题。 原文 {content} 这个模板有几个设计要点值得说明。第一明确禁止增加事实。大模型改写时特别喜欢“帮忙补充”补出来的数字和案例都是假的这是批量流程里最大的坑。提示词里必须写死。第二要求输出纯正文。如果不限制模型会在前面加“改写后”后面加“改写说明”批量处理时还得再写代码清洗很麻烦。第三用三引号包裹原文。这样原文里出现引号、括号不会干扰模型解析边界。第四专有名词保护。技术文档里有大量代码、命令、参数名模型可能会“顺手优化”必须明确禁止。7.2 批量调用脚本下面是调用示例。接口地址、模型名、鉴权方式按你实际使用的服务替换不同服务的请求体结构不一样这里给的是通用模板。# rewrite.py import json import time from pathlib import Path import requests CONFIG json.loads(Path(./config.json).read_text(encodingutf-8)) PROMPT_TMPL Path(./prompt.txt).read_text(encodingutf-8) def build_prompt(text: str) - str: return PROMPT_TMPL.replace({content}, text) def call_model(text: str, retries: int 3) - str: payload { model: CONFIG[model], messages: [ {role: user, content: build_prompt(text)} ], temperature: CONFIG.get(temperature, 0.3), max_tokens: CONFIG.get(max_tokens, 4096), } headers { Content-Type: application/json, Authorization: fBearer {CONFIG[api_key]}, } for attempt in range(1, retries 1): try: resp requests.post( CONFIG[api_base], jsonpayload, headersheaders, timeoutCONFIG.get(timeout, 120), ) resp.raise_for_status() data resp.json() return data[choices][0][message][content].strip() except Exception as e: print(f 第 {attempt} 次失败: {e}) if attempt retries: raise time.sleep(2 ** attempt) # 指数退避 return def chunk_text(text: str, size: int 1500) - list: 按段落边界切块避免把句子切断 paras [p for p in text.split(\n) if p.strip()] chunks, buf [], for p in paras: if len(buf) len(p) size and buf: chunks.append(buf) buf p else: buf buf \n p if buf else p if buf: chunks.append(buf) return chunks def main(): in_dir Path(./inputs) out_dir Path(./outputs) out_dir.mkdir(exist_okTrue) for f in sorted(in_dir.glob(*.txt)): target out_dir / f.name if target.exists(): print(f跳过已处理: {f.name}) continue text f.read_text(encodingutf-8) chunks chunk_text(text) results [] print(f处理 {f.name}共 {len(chunks)} 块) for i, ck in enumerate(chunks, 1): out call_model(ck) results.append(out) print(f 块 {i}/{len(chunks)} 完成输出 {len(out)} 字) time.sleep(CONFIG.get(interval, 1)) target.write_text(\n\n.join(results), encodingutf-8) print(f已写入 {target}) if __name__ __main__: main()配套的config.json{ api_base: http://127.0.0.1:8000/v1/chat/completions, api_key: replace-with-your-key, model: replace-with-your-model, temperature: 0.3, max_tokens: 4096, timeout: 120, interval: 1 }几个工程细节必须注意。chunk_text按段落切块而不是按字数硬切目的是不让句子断在中间。模型拿到半句话会自己补全补出来的内容就不可控了。跳过已处理文件这个逻辑看着简单但很有用。批量跑几十个文件时中途可能失败有了这个判断重跑不会重复消耗额度。指数退避2 ** attempt是为了应对限流。频率限制触发时立刻重试通常还是失败等 2 秒、4 秒、8 秒成功率高得多。temperature设低一点。改写任务不需要创造性0.2 到 0.4 之间比较稳太高会出现模型自由发挥改写内容的情况。7.3 输出校验与二次检测批量改写完之后不能直接用必须过一遍校验。校验分两步。第一步是自动检测。把outputs/里的文件再跑一次check.py对比改写前后的分数。正常情况下分数应该明显下降。如果某篇分数不降反升说明这次改写失败了需要单独看。第二步是差异比对。用difflib生成改写前后的对照重点看三类问题有没有冒出原文没有的数字、专有名词有没有被改、有没有整段被删。# diff_check.py import difflib from pathlib import Path for src in sorted(Path(./inputs).glob(*.txt)): dst Path(./outputs) / src.name if not dst.exists(): continue a src.read_text(encodingutf-8).splitlines() b dst.read_text(encodingutf-8).splitlines() diff list(difflib.unified_diff(a, b, fromfilesrc.name, tofilerewritten, lineterm)) out Path(./reports) / f{src.stem}.diff.txt out.write_text(\n.join(diff), encodingutf-8) print(f{src.name}: 差异行数 {len(diff)})差异文件人工扫一遍重点盯数字和专有名词。这一步花的时间不多但能挡住大部分事故。8. 资源占用、批量任务与性能观察这一节讲怎么观察资源消耗以及批量跑的时候要注意什么。8.1 检测脚本的开销规则检测是纯字符串操作速度极快。一篇 5000 字的文章六类指标全算一遍在普通笔记本上通常是毫秒级。真正的瓶颈在文件 IO 和正则复杂度上。影响速度的因素有两个。一是正则的复杂度像不是[^。\n]{1,30}[,]?\s*而是这种带否定字符类的模式回溯少效率高。如果写成嵌套量词就容易出问题。二是文件数量几千个小文件时打开关闭的开销会超过计算本身可以考虑合并成一个大文件批量处理。内存方面基本不用担心处理单篇文本时占用可以忽略。如果要一次读入上百篇再做全局统计注意总字数乘以 2 字节UTF-8 中文估算一下几百万字也就几兆到几十兆。8.2 大模型改写的耗时与成本这部分差异最大取决于你选云端还是本地。云端接口的耗时主要看两件事单次请求的输出长度和并发数。输出越长越慢这是自回归生成的固有特性。一篇 3000 字的稿子切成两块串行跑完大概几十秒到几分钟具体取决于服务方的排队情况。想提速就上并发但要注意两点。一是服务方通常有并发上限超了直接报错或者排队二是并发太高时单次请求的延迟反而上升。稳妥的做法是先试 2 到 4 并发观察成功率稳定了再加。代码上可以用concurrent.futures.ThreadPoolExecutor把for循环换成提交任务。本地跑的话显存占用跟模型参数量、量化精度、上下文长度都有关系不同模型差得很远不给具体数字。观察方法很直接跑起来之后看nvidia-smi的显存占用和利用率或者 Windows 任务管理器里的专用 GPU 内存。上下文长度从 2K 加到 8K占用会明显上升这是正常的。判断资源是否够用的标准不是“能不能跑起来”而是“跑起来之后还剩多少余量”。如果显存占到 95% 以上稍微长一点的输入就会 OOM这种配置不适合批量跑。8.3 批量任务的目录与队列设计批量任务最重要的是可恢复。跑到第 37 个文件的时候断了不能从头再来。目录设计建议三层inputs放原始稿outputs放改写结果failed放失败的。主循环开始前先扫一遍outputs已存在就跳过这就是前面代码里那段判断的作用。失败文件单独存一份附上错误信息# 失败时落盘方便单独重跑 failed_dir Path(./failed) failed_dir.mkdir(exist_okTrue) (failed_dir / f{f.name}.err).write_text( f{time.strftime(%Y-%m-%d %H:%M:%S)}\n{error_msg}, encodingutf-8 )重跑的时候只扫failed目录处理完把文件挪回inputs。日志要记三样东西处理到哪个文件、每块耗时、token 用量。有了这些数据才能判断是该加大并发还是该缩短单块长度。8.4 观察指标清单跑批量任务时重点看这几个数单个文件的处理耗时异常长的要单独看是不是输入太长。失败率超过 5% 就要停下来查原因别硬跑。改写前后的分数变化平均下降幅度太小说明提示词需要调。token 消耗用来估算成本也用来发现异常长的输出模型跑偏了会啰嗦。输出长度与输入长度的比值。正常改写这个比值在 0.6 到 1.3 之间超过 1.5 基本是模型在加戏要人工看。这些指标不需要复杂工具打印到终端再用tee存一份日志就够python rewrite.py 21 | tee logs/run_$(date %Y%m%d_%H%M).log9. 常见问题与排查方法这一节把实际跑流程时容易遇到的问题列出来。表格里的方案都是通用思路具体命令按自己环境调整。问题现象可能原因排查方式解决方案脚本报UnicodeDecodeError输入文件不是 UTF-8用编辑器另存为 UTF-8 再看统一编码或在代码里加errorsignore临时绕过检测分数全为 0输入目录没匹配到文件打印glob结果确认扩展名是.txt路径大小写正确对举句漏检正则的字符类范围太窄拿漏检原句单独测试正则放宽{1,30}的长度限制补充变体写法误报太多规则过于宽泛收集误报样本在规则里加排除条件或给规则降权接口返回 401鉴权信息错误检查 header 里的 key确认 key 有效、没过期、格式正确接口返回 429触发频率限制看响应头和日志降低并发加大退避间隔改写后出现原文没有的数字模型自由发挥用 diff 比对提示词里强化禁止增补人工复核专有名词被改模型不理解上下文diff 里搜索关键词提示词加保护条款代码块单独隔离输出被截断max_tokens太小看返回的 finish_reason调大上限或缩短单块长度批量跑到一半卡住单次请求超时未处理看最后一条日志加 timeout 和重试减小块大小改写后字数暴涨模型在扩写看输出/输入长度比提示词明确要求“不扩写”限制输出长度显存 OOM模型太大或上下文太长看显存占用曲线换量化版本缩短上下文减小并发端口被占用本地服务重复启动netstat -anofindstr 端口输出目录权限不足路径在系统保护目录看报错路径换到用户目录下断点续跑重复处理跳过逻辑失效检查outputs是否有同名文件确认文件名一致注意扩展名差异挑三个高频问题展开说。改写后专有名词被改。这是最常见的坑。技术文档里的函数名、参数、命令模型很容易“顺手优化”成同义词。解决办法是把代码块和行内代码单独抽出来用占位符替换改写完再替换回去。占位符用不太可能出现在正文里的字符串比如__CODE_001__。分数下降但读起来还是别扭。这种情况通常是提示词只约束了句式没约束节奏。可以加一条要求段落内句子长度要有明显差异最长句和最短句的字数差至少 2 倍。这个约束不精确但比没有强。批量跑完发现改错方向了。一次处理几百篇的时候建议先跑 5 篇做抽样人工看完再全量。这个习惯能省不少返工。10. 最佳实践与下一步把前面几节的东西收一下给几条能落地的建议。第一次上手别急着上自动化。先挑自己写的一段话手工跑一遍检测脚本看看分数和命中的句子然后按四类策略手动改一遍。改完再跑一次检测对比分数变化。这个过程走一遍你就知道自己写作时最容易踩哪个坑——有人是对举句用得多有人是抽象词多有人是句长太均匀。找到自己的主要问题后面改起来效率高得多。保留一套最小可运行配置。check.py加规则文件总共两百行左右不需要任何第三方库随时能跑。这套东西的价值在于长期可用别为了增加功能把它搞复杂。目录管理上原始稿、改写稿、失败文件、报告分开存放文件名保持一致。批量任务一定要有跳过已处理的逻辑和失败重试这两条是能不能跑完几十个文件的关键。接口服务如果部署在内网注意访问范围控制。别绑0.0.0.0让整个局域网都能访问绑127.0.0.1自己用就行。涉及他人文本的批量改写走外部服务前做脱敏人名、联系方式、内部代号都替换掉。改写完的结果必须人工过一遍。自动流程能解决 80% 的模板句式剩下的 20% 往往是最关键的判断句机器判断不了准确性。发布之前花十分钟扫一遍 diff重点看数字和专有名词这一步不能省。还有一条容易被忽略这套方法的目标是写出更好的文字不是把文字改得看不出是机器参与的。用 AI 辅助写作本身没问题问题在于把生成的初稿原样发出去。工具帮你把套路句式挑出来把抽象表达标出来剩下的具体内容还得自己填。填不出来的地方说明那块你本来就没想清楚。继续往下走的话有几个方向可以扩展。规则库可以按行业细分技术文档、产品文案、学术写作的阈值和词表都不一样分开维护效果更好。检测可以加上跨段落维度的统计比如段落长度分布、段落开头句式重复率这些是单段检测看不到的。改写环节可以做成两级第一级规则改写处理明显的模板句第二级模型改写处理需要重组的段落规则能搞定的不用麻烦模型省时间也省成本。最后回到标题那个问题。“不是A而是B”本身不是毛病毛病在于把它当成不用思考就能填满一段话的模板。写之前先想清楚 A 到底有没有人真的这么以为B 能不能落到具体的人和事上。这两个问题想明白了句式自然就散了。