novel_sentence_analysis
句式模式分析。逐句把正文分成九类句式并统计占比、转移、模板与段落结构,同时给出句长分布、情感曲线、细节密度、风格指纹与节奏建议。分析范围可以是全书,也可以是单章。全部为本地规则统计:情感词命中走词表加否定过滤与副词加权,语义隐性情感是在此之上的可选增强。结果写入书库缓存并同时落盘一份报告 JSON,可用 fresh 强制重算。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
book | string | 是 | 书名。经 sanitizeSegment 过滤;对应目录内无章节文件时抛错。 |
chapter | string | 否 | 只分析该章(章号 / 文件名 / 标题子串)。省略则分析全书,scope 形如「全书 N 章」。 |
top | integer | 否 | 返回的高频句式模板条数。允许区间 1–50,默认 8;越界或非整数抛错(不静默钳制)。 |
maxSentences | integer | 否 | 采样句数上限。允许区间 100–100000,默认 20000;触顶后按前缀截断并在建议文本尾部标注。 |
curveSegments | integer | 否 | 情感曲线分段数。允许区间 1–50,默认 20;实际段数为 min(有效段落数, curveSegments)。 |
fresh | boolean | 否 | true 跳过缓存读取并强制重算;结果仍写回同一缓存文件。 |
brief | boolean | 否 | true 时返回 brief 单句摘要,渲染层只输出该句并提示「省略 brief 重新调用」。 |
root | string | 否 | 章节库根目录。省略时取插件 config.root,再省略取会话工作目录;MCP 形态下受启动参数 --root 约束。 |
输出结构
契约字段(output.schema.required):book、scope、enabled、totalChars、totalSentences。功能关闭时返回体只含这五项加 message,且 totalChars 与 totalSentences 均为 0。
| 字段 | 类型 | 内容 |
|---|---|---|
scope | string | 单章时为文件名;全书时为「全书 N 章」 |
enabled / message | boolean / string | 句式分析总开关状态;关闭时 message 给出开启方式 |
autoAnalyze | boolean | 生效的「分析作品时主动使用」开关状态 |
totalChars / totalSentences | integer | 全文字符数与实际参与统计的句数(受 maxSentences 影响) |
categories | array | 固定九项,顺序为 CATEGORY_ORDER:{type, label, count, ratio, avgLength, examples};examples 每类最多 3 条、每条超 42 字截断加「…」 |
transitions | array | 相邻句类型转移,{from, to, count},按次数降序,最多 12 条 |
motifs | array | 2 连句与 3 连句模板合并排序后取前 top 条,{pattern, count},模式用 → 连接 |
paragraphs | object | total / avgSentences / opening / closing / dialogueOnly / psychologyOnly / mixed / narrationOnly / exchanges;四类段落计数之和恒等于 total |
lengths | object | avg / median / shortRatio / mediumRatio / longRatio / distribution(四桶 1-10 / 11-24 / 25-40 / 41+) |
style | object | 对话、心理、环境、疑问(含反问)、感叹占比,短句与长句占比,subjectivityIndex(0–100 整数)、emotionDensity、avgSentenceLength、firstPersonDensity |
emotion | object | dominant、cleanDominant、confidence、caveat、aiAction、pollution、scores、cleanScores、intensity、topWords(≤12)、curve、quantification(含 complexity:score / level / entropy / diversity / conflict / chapterDrift / reliable / reliabilityNote,level 可能为 "unknown") |
chapterPatterns | array | 全书分析时的分章压缩节奏序列,{chapter, sequence};单章分析为空数组 |
guidance | string | 节奏建议多行文本,末尾可能附前缀截断提示 |
density | object | actionVerbsPer1000 / actionChainRatio / actionChainExamples(完整句,非截断:8~70 字、等距抽样 3 条) / objectNounsPer1000 / sensePer1000 / sense(五感官通道) |
fingerprint | string | 一维风格指纹串,格式与 novel_style_check 一致 |
reportFile / cache / cachedAt | string | 报告落盘路径;cache 枚举 hit / miss;cachedAt 为 ISO 时间 |
brief | string | brief:true 时的单句摘要 |
渲染层按 【句式分布】→【句式排列】→【句长与风格】→【主观情感】→【情感净化预警】→【情感量化】→【语义隐性情感】→【细节密度】→【节奏建议】→【风格指纹】→【AI 解读提示】 分段输出纯文本;情感净化预警段仅在 cleanDominant !== dominant 时出现,语义隐性情感段仅在 quantification.semanticImplicit.hits 非空时出现。【情感量化】段的复杂度行为可读行,且 complexity.reliable === false 时会单独起一行写出 reliabilityNote(v6.6.5)。【细节密度】段首另有一行口径说明,标明当前口径(v7.0.0)与前后版本数值不可直接比较。
计算原理
1 · 分块、切句与九类判定
先分段落再切句,两者都做了降噪:
九类句式的判定是固定优先级瀑布,先命中先归类,不做打分:
v6.1.0 支持“小心前面的陷阱!”等长宾语提醒,并排除“注意到”等同形普通用法。祈使排在感叹之前,是为了让「闭嘴!」这类句归祈使,感叹只留给不含祈使词的叹句。反问分支已剔除「哪里」这一最常见的真疑问词,并把「谁 + 情态」收紧为固定格式(谁知道 / 谁说的 / 谁能想到…),避免把「谁会来参加明天的会议?」判成反问。
2 · 采样上限与密度分母
情感密度、主观性指数、细节密度、Valence 量化全部以 sampleText 为样本,与句式计数同源。此前存在的两套口径(截断时用句子拼接、未截断时用含空行与标题行的原文)会让密度类指标仅因是否触顶而不可比,现已统一:代价是未截断路径下空行与段落分隔符不再进分母,密度读数略高于旧版本。
3 · 排列规律:转移、模板、段落结构
四类段落计数互斥且穷尽,dialogueOnly + psychologyOnly + mixed + narrationOnly === paragraphs.total 恒成立——早先缺少 narrationOnly 桶时,全环境句段与全疑问句段会静默漏计。
4 · 句长分布
短 / 中 / 长三档与 distribution 四桶在 10 与 24 处对齐,按 distribution 复算 ratio 必然一致。
5 · 情感词扫描
三张表叠加使用:强情绪词 50 条(深层情感,进 clean)、弱情绪词 52 条(感官 / 爽感反应,只进 raw)、大连理工七类词表 27413 条(懒加载 gzip,兜底;只进 raw)。
「最长匹配 + 区间消费」消除了嵌套双计:「悲痛欲绝」不再被拆成「悲痛」+「悲痛欲绝」两分。DUTIR 兜底同样按最长优先扫描,但将上限由 4 字提到 8 字(词表最长条目 7 字),使 853 条 ≥5 字的成语级情感词恢复可命中;v6.1.0 起兜底词与主词表共享已消费区间,避免“无比幸福”中的“幸福”再次计分。兜底词不做强弱分级,只进 scores 不进 cleanScores。
v6.9.5:DUTIR 的「恶」类不再计入五类主情绪。大连理工词典的「恶」大类有 10,262 个词(占全表 37%,含憎恶 / 烦闷 / 羞愧 / 嫉妒 / 贬义评价如无聊 / 尴尬 / 愚蠢 / 虚伪 / 啰嗦),旧映射是 恶: "anger",于是「他很无聊,场面尴尬」会被算成 4 分愤怒、「表面主导情绪」直接变成 anger,并连带推高氛围轴的暗黑 / 热血评分。现在「恶」不映射到 joy/anger/sorrow/fear/surprise,只由 VALENCE_WORDS 承担负向效价;同时强情绪词表补入狂喜 / 欢天喜地 / 怒发冲冠 / 心灰意冷 / 恐慌,「脸色铁青」「怒气」补进效价表。
6 · 情绪污染源与置信度
命中污染阈值时 caveat 会写明「检测到高密度 X 描写…请勿直接采信,须 novel_read 抽查 2-3 段原文复核真实情感基调后再下结论」,同时给出 aiAction;仅强弱主导不一致时 caveat 以「剔除易污染的情绪词后,主导情感为…」表述;两者都不成立时 caveat 为空串。
7 · 情感曲线
曲线不做零强度段裁剪:段数由 min(段落数, curveSegments) 保证每段至少 1 个段落,不会产生空切片,因此强度为 0 的段是「真实的平缓段」而非伪段,跨章节曲线可按固定段数对齐。
8 · Valence 量化与隐性情感
explicitImplicitCompare 以显性效价均值 ±0.15 与隐性方向占比 0.6 判定「表里不一」,双向冲突(显正隐负、显负隐正)都会置位 explicitImplicitConflict。复合情感共现按段落内共现的情感类对统计,输出前 5 组并补中文标签(悲喜交加 / 又爱又恨 等)。
v6.6.5:复杂度与置信度联动。此前 emotionComplexity 只看计数、与 confidence 完全脱钩:同一份返回体里一边是 confidence: "low" 与「检测到高密度成人向内容 / 感官描写…请勿采信」的 caveat,另一边照样给出 complexity: { score: 0.6, level: "high" }——下游只要读 level 就会得出「情感复杂度高」,而这个结论已被工具自己否定。现在 emotionComplexity(perChapter, reliability) 接收第二个可选参数 reliability({ polluted, pollutedBy, noData }),由 analyzeText 在算完污染与零命中判定后传入(经 emotionalQuantification 的第 4 个参数透传)。判定不可采信有三条路径,都与「结论是否可采信」直接相关:
- 污染:
polluted为真,即成人向内容 / 感官描写 ≥ 1.5‰、或战斗/爽文 ≥ 3‰、或恐怖/疯狂 ≥ 3‰(阈值与EMOTION_POLLUTION词表都是既有实现,本版未改)。 - 无数据:全文零情感词命中(
noData,判据是 raw 计数为零)。 - 无 clean 数据:
stats.length === 0——复杂度用的是 clean 计数,走到这个早退分支说明没有可用的 clean 计数。这一条覆盖了「raw 命中了弱词、clean 一个强词都没有」的情形(例如只有「微笑」「眼泪」「发抖」这类弱情绪词),note 会明写「未检出可用的情感计数(clean 计数为空)」。三条路径的score 0都表示「没测到」,不是「情感简单」。
命中任一条件时,complexity.level 为 "unknown"(新增取值,此前只有 low / medium / high),同时给 reliable: false 与固定的 reliabilityNote(可采信时为空串;纯文本,不含 Markdown 强调符)。score 数值保留,便于跨章对比,但 reliable: false 已标明它的地位。渲染层(lib/core.js 的 formatSentenceAnalysis,【情感量化】段)会把这一项打成可读的一行——- 复杂度: <score>(<level>)熵 X/Y,多样性 N,主次 A↔B(k),章间漂移 有/无(基准 …)——并在 reliable === false 时单独起一行写出 reliabilityNote。此前该段打印的是 JSON.stringify(q.complexity).slice(0, 120),而 reliable / reliabilityNote 排在对象末尾,必然被 120 字符截掉,所以「本项不可采信」永远到不了渲染文本。
9 · 主观性指数、细节密度与指纹
10 · 缓存与报告落盘
缓存指纹用「每章前 8KB 的内容哈希 + 文件大小」而非 stat 的 mtime:内容同大小且 mtime 未变时旧指纹不会失效。命中路径只读每章前缀 8KB,不整章读盘。
约束与边界
- 两层开关,行为不同:工具级开关关闭时
assertToolEnabled抛错(「工具 novel_sentence_analysis…当前已在「写作助手功能」UI 中关闭」);句式分析总开关enabled关闭时不抛错,返回enabled:false、totalChars:0、totalSentences:0与message,且不读章节正文、不写缓存。调用方应据enabled而非totalSentences === 0判断可用性。 - 参数越界是错误而非钳制:
top/maxSentences/curveSegments由optionalInt校验,非整数或越界直接抛「参数 X 必须是 A 到 B 之间的整数」。内部analyzeText另有≥1与1–50的兜底钳制,供直接调用该函数的一方使用。 - 触顶即前缀采样:超过
maxSentences后只统计靠前部分,totalSentences等于上限而不是全书句数。这不是全量结果,提示只写在guidance末尾(不加顶层字段),调用方需要全量时应分章调用。 - 缓存键含内容指纹与全部参数:改一章内容、换一个
top或翻转enabled/autoAnalyze都会生成新缓存文件,旧文件不再命中;fresh:true跳过读取但仍写回同一文件。落盘目录按书最多保留 20 份报告,超出部分按修改时间淘汰,<book>-full.json与<book>-chapters-metrics.json是分析缓存、不匹配报告命名,不会被清理。 - 算法版本是硬失效条件:缓存内
ver与CACHE_VERSION不一致时视为过期并重新分析。而CACHE_VERSION = PLUGIN_VERSION(见源码位置lib/core.js)——缓存跟着插件版本号走,所以升级会自动失效旧口径的结果,不需要手动清缓存:v6.6.5 就是这样让升级后立刻拿到带reliable的新complexity,而不是命中一份连该字段都没有的旧缓存。 - 情感复杂度新增取值
level: "unknown"(v6.6.5):它表示「本项不可采信」,不是第四档复杂度。按low / medium / high三值穷举的读取方需要补这个分支;跨版本比较时,旧版本的level不会出现该值,也不应把两者当作同一把尺子。 - 语义隐性情感受独立门控:仅当
semanticImplicit(含semanticEmbedding总开关)开启且本地引擎可用时才会补充emotion.quantification.semanticImplicit。「用户明确关闭」与「引擎瞬时不可用」被区分对待——后者不会把缓存里已算好的semanticImplicit抹成空。单章分析时命中范围被约束在本章内,越界的旧缓存结果会被重算并写回。 - 情感字段可能被裁剪:
emotionCaveat关闭时emotion只保留dominant/scores/intensity/topWords/curve;emotionComplexity关闭时删除quantification。两个开关各自独立生效,关闭其一不会连带剥掉另一个。 - 成人向内容 / 感官描写密集的文本,情感维度本就不适用(v6.6.5 明确写出):本版做的是「别在不适用的语料上硬给一个高置信度的数字」,不是「让插件准确判断成人向内容的情感」。这类文本请改用节奏 / 爽点 / 对话密度等维度——本页的
lengths/style/paragraphs与density仍然可用,情感项读到level: "unknown"与reliable: false时不要拿score下结论。 - 静默降级点:DUTIR 词表加载失败时跳过兜底(不影响主词表结果);语义裁决与语义隐性情感失败不阻塞规则结果;缓存写盘失败不影响本次返回值;
cleanOutput统一消除-0、把NaN归一为null、剔除undefined键。 - 跨版本不可横比:v4.3.0 起,句末符口径统一(省略号单独成句)、密度分母统一为句子拼接样本、情感命中改为区间消费、Valence 的
adjVariance与deltaRobust改口径、隐性情感三项比率共用三分分母。同一本书在旧版本下产生的报告数值不可与新版本直接比较。
相关工具
- novel_style_report —— 本工具的
fingerprint、lengths、style、emotion是其「维度」聚合的原始来源。 - novel_style_check —— 复用本工具的九类占比、句长与主观性构造指纹向量,做单章对照判定。
- novel_semantic_search —— 与本工具的语义隐性情感共用同一个本地向量引擎、同一份索引缓存与同一套切块规则。
源码位置
- lib/index.js —— 工具注册与执行(
registerNovelSentenceAnalysis,第 403 行起;execute自第 590 行)、缓存键与pruneAnalysisReports(第 155 行)、cropEmotionByFeatures(第 83 行)、semanticGate(第 109 行) - lib/analysis.js —— 九类标签与顺序(
CATEGORY_LABELS第 23 行、CATEGORY_ORDER第 37 行、TYPE_CODE第 43 行)、词表(第 58–142 行)、splitBlocks(第 145 行)、splitSentences(第 183 行)、classifySentence(第 348 行)、scanEmotionHits(第 451 行)、emotionOf(第 500 行)、valenceSeries(第 896 行)、valenceStats(第 953 行)、implicitEmotionScan(第 822 行)、emotionComplexity(第 1187 行)、analyzeText(第 1292 行)、emotionCurve(第 1558 行)、buildFingerprint(第 1600 行)、densityOf(第 1678 行) - lib/core.js ——
buildBriefLine(第 1324 行)、formatSentenceAnalysis(第 1330 行)、enrichSemanticImplicit(第 793 行)、semanticFeatureEnabled(第 852 行)、cropEmotion(第 856 行)、cleanOutput(第 2308 行)、CACHE_VERSION(第 24 行) - lib/embedding.js ——
chunkText(第 520 行)、detectImplicitEmotions(第 292 行)、IMPLICIT_EMOTION_PROTOTYPES(第 232 行,共 29 条原型句)、fingerprint(第 338 行)