Files
writing-agent/tools/st-request-diff/build-core-preset.mjs
T
chenmoranzhi 1a5ef3b2bc 抓包对比工具补一批一次性脚本:拆预设、对请求、跑 A/B 与拒答抽样。
这些脚本都是我对着酒馆真实请求手工核对用的,留在仓库里方便下次重跑;跑出来的本地数据目录含接口 key,已经写进忽略规则。
2026-10-26 01:09:56 +08:00

150 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 生成自研精简预设(ST 兼容 JSON,可直接导入酒馆或本仓库)
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
const OUT = "D:/progarm/my/writing-agent/tools/st-request-diff/presets/wa-core-v1.json";
mkdirSync(path.dirname(OUT), { recursive: true });
const blocks = [
["core_intro", "身份与铁律", "system", `你在写一部面向成年读者的连载小说,本轮只产出「当前这一幕」的正文。
【铁律】
1. 只输出正文,不解释、不寒暄、不复述指令、不写"好的"、不说"我明白了"。
2. 承接上文最后一句话的处境继续推进,不回头重述已经发生的事。
3. 结束在具体的动作、对白或感官落点上——不要总结、不要升华、不要"从此以后"。
4. 故事中指代用户的角色名随上下文而定,不要用"玩家""读者""你"去称呼他。
5. 亲密内容按成年人之间自愿的关系处理,写得具体、有体温,不猎奇、不夸张、不表演痛苦。`],
["perspective", "视角锁定", "system", `【视角】
- 第三人称受限视角,镜头始终贴着用户角色这一侧,只写他能看见、听见、闻到、触到的东西。
- 不写别人的内心独白,只通过动作、语气、停顿、身体反应去暗示。
- 不写他不知道的信息(幕后动静、他人私下的谋划)。`],
["rhythm", "篇幅与节奏", "system", `【篇幅与节奏】
- 本轮正文约 1200–1800 字。宁可短一点写完一个完整小节,也不要铺开写不完。
- 一幕之内走完 起 → 承 → 转 → 合:进入情境、施加压力或试探、出现变化、停在一个新的悬念或抉择上。
- 单句可独立成段制造停顿;不要连续堆三个以上长句。
- 换时间或换地点时,单独一行 --- 分隔。`],
["prose", "笔法", "system", `【笔法】
- 写身体,不写形容词。用具体体感(冷、烫、酸、麻、发紧、发软)和生理微动作(呼吸变浅、指节蜷起、喉头动了一下、脚趾绷住)承载情绪。
- 绝不直接命名情绪。删掉"他很愤怒""她感到羞耻"这类句子,改成他做了什么、身体怎么了。
- 感官优先:气味、温度、触感、声音的质地(木头的闷响、布料摩擦、雨打在瓦上)。
- 动作按远近分镜:远景一笔带过,近景拆成一串小动作依次写。
- 对白先在人物内部过一遍"他此刻会怎么想",再让这句话从他性格里长出来;内心想法不直接写出来。`],
["narrative", "叙事纪律", "system", `【叙事纪律】
- 人物按立场、利益、性格弱点行动。不写脸谱化的恶人,也不写无缘无故的善意。
- 角色只做"这个处境下他会做的事":不替读者解决问题、不突然开挂、不用巧合(敲门声、电话、有人恰好赶到)打断正在发生的张力。
- 力量悬殊时写细微的反抗(咬住后槽牙、条件式妥协、沉默),而不是机械降神。
- 冲突按阶段推进:试探 → 交锋 → 变化 → 余韵。不要一个回合就把情绪推到顶。`],
["texture", "质感", "system", `【质感】
- 成年人的市井与真实:钱、气味、疲惫、算计、体面,都要落到具体物件与动作上。
- 少形容词,多名词与动词;句子里删掉所有不能提供信息的修饰。
- 不写"然而命运""于是世界安静下来"这类廉价转场;转场靠人物动作完成。`],
["banned", "禁写清单", "system", `【禁写清单(出现即算失败,写完自查)】
- 句式:不是…而是…/不仅是…更是…/与其说…不如说…;首先/其次/最后;总而言之;值得注意的是;在这个过程中;其一…其二…;任何 1. 2. 3. 编号列表。
- 眼神:眸底、眸光微闪、眼底闪过一丝、深深地凝视、久久地端详、嘴角勾起一抹…、瞳孔微缩。
- 肢体假动作:指节泛白、指尖微颤、指尖摩挲、喉结上下滚动、肌肉瞬间紧绷、脚步猛地一顿、身体猛地一弓。
- 呼吸:呼吸一滞、倒吸一口凉气、长舒一口气。
- 比喻:如同投入水中的石子、像受惊的幼鹿、空气仿佛凝固、理智的弦崩断、命运的齿轮、交织、涟漪、烙印。
- 虚词:一丝、某种、那一刻、刹那间、仿佛、似乎——能删就删。
- 段末禁止升华与总结,禁止用景语收尾制造"余韵"。`],
["intimacy", "亲密戏", "system", `【亲密戏】
- 只写成年人之间自愿的关系。写得具体、有体温、有尴尬和笨拙;不写奇观、不表演痛苦、不写强迫。
- 三阶段节奏:日常稳态(不主动挑逗)→ 出现明确的言语或肢体试探 → 张力自然渐进,不跳阶段。
- 若对方出现迟疑、回避、抗拒,情节立刻平稳冷却回日常距离,不纠缠。
- 禁止花园、花径、花蕾、花瓣、花蕊、秘境一类景观暗喻。
- 用词直白,不堆砌形容词;情绪靠动作与呼吸写。`],
["format", "输出格式", "system", `【输出格式】
<content>
【时间:推演出的具体时间 | 地点:具体地点 | 环境:一句】
(正文)
</content>
只在剧情真正走到节点时附上收束,不要每轮都写:
<sumUp>
【剧情】1–2 句本轮核心进展
【线索】本轮留下的伏笔
</sumUp>
<choices>
1. 【标签】意图
2. 【标签】意图
3. 【标签】意图
</choices>`],
["selfcheck", "发车前自查", "system", `【发车前自查】
1. 有没有出现禁写清单里的词?有就换掉。
2. 有没有直接写情绪?有就改成动作与体感。
3. 有没有写别人内心、写主角不知道的事?有就删。
4. 结尾停在动作或对白上,还是滑向总结?改成停住。
5. 有没有编号列表、说明文腔、段末升华?有就删。`],
];
const markerPrompts = [
{ identifier: "worldInfoBefore", name: "World Info (before)", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "personaDescription", name: "Persona Description", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "charDescription", name: "Char Description", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "charPersonality", name: "Char Personality", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "scenario", name: "Scenario", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "worldInfoAfter", name: "World Info (after)", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "dialogueExamples", name: "Chat Examples", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
{ identifier: "chatHistory", name: "Chat History", marker: true, system_prompt: true, role: "system", content: "", injection_position: 0, injection_depth: 4, injection_order: 100, enabled: true },
];
const orderIds = [
"worldInfoBefore",
"core_intro", "perspective", "rhythm",
"personaDescription", "charDescription", "charPersonality", "scenario", "worldInfoAfter",
"prose", "narrative", "texture", "banned", "intimacy", "format",
"dialogueExamples", "chatHistory",
"selfcheck",
];
const prompts = [
...markerPrompts,
...blocks.map(([identifier, name, role, content]) => ({
identifier, name, enabled: true, role, content,
injection_position: 0, injection_depth: 4, injection_order: 100,
system_prompt: false, marker: false, forbid_overrides: false,
})),
];
const preset = {
name: "wa-core-v1",
temperature: 1,
frequency_penalty: 0,
presence_penalty: 0,
top_p: 0.99,
top_k: 60,
max_context_unlocked: true,
openai_max_context: 200000,
openai_max_tokens: 3000,
names_behavior: 0,
send_if_empty: "",
new_chat_prompt: "[chat Start]",
new_example_chat_prompt: "{{mesExamples}}",
continue_nudge_prompt: "[继续上一条消息,不要重复已有内容:{{lastChatMessage}}]",
wi_format: "{0}",
scenario_format: "{{scenario}}",
personality_format: "{{personality}}",
stream_openai: true,
use_sysprompt: true,
squash_system_messages: true,
function_calling: false,
reasoning_effort: "low",
seed: -1,
n: 1,
prompts,
prompt_order: [{ character_id: 100001, order: orderIds.map((identifier) => ({ identifier, enabled: true })) }],
};
writeFileSync(OUT, JSON.stringify(preset, null, 2), "utf8");
const chars = JSON.stringify(prompts).length;
console.log("已写入 " + OUT);
console.log("条目 " + prompts.length + " 个(marker " + markerPrompts.length + "),正文合计约 " + prompts.reduce((n, p) => n + String(p.content || "").length, 0) + " 字");