@lingjingai/scriptctl 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/changes/0.43.0.md +21 -0
  2. package/dist/cli.js +0 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/domain/ingest/video-apply.d.ts +0 -33
  5. package/dist/domain/ingest/video-apply.js +8 -159
  6. package/dist/domain/ingest/video-apply.js.map +1 -1
  7. package/dist/domain/ingest/video-context.d.ts +51 -0
  8. package/dist/domain/ingest/video-context.js +293 -0
  9. package/dist/domain/ingest/video-context.js.map +1 -0
  10. package/dist/domain/ingest/video-serial-md.d.ts +60 -0
  11. package/dist/domain/ingest/video-serial-md.js +277 -0
  12. package/dist/domain/ingest/video-serial-md.js.map +1 -0
  13. package/dist/help-text.js +13 -10
  14. package/dist/help-text.js.map +1 -1
  15. package/dist/infra/ingest/video-transcode.d.ts +7 -0
  16. package/dist/infra/ingest/video-transcode.js +86 -0
  17. package/dist/infra/ingest/video-transcode.js.map +1 -0
  18. package/dist/infra/llm/gemini-video-model.js +14 -2
  19. package/dist/infra/llm/gemini-video-model.js.map +1 -1
  20. package/dist/llm/config.js +1 -2
  21. package/dist/llm/config.js.map +1 -1
  22. package/dist/llm/registry.d.ts +1 -7
  23. package/dist/llm/registry.js +5 -13
  24. package/dist/llm/registry.js.map +1 -1
  25. package/dist/llm/tasks/ingest/video-serial.d.ts +9 -0
  26. package/dist/llm/tasks/ingest/video-serial.js +120 -0
  27. package/dist/llm/tasks/ingest/video-serial.js.map +1 -0
  28. package/dist/llm/types.d.ts +1 -1
  29. package/dist/llm/types.js.map +1 -1
  30. package/dist/usecases/ingest/command.js +0 -3
  31. package/dist/usecases/ingest/command.js.map +1 -1
  32. package/dist/usecases/ingest/status.js +8 -20
  33. package/dist/usecases/ingest/status.js.map +1 -1
  34. package/dist/usecases/ingest/video-pipeline.d.ts +0 -6
  35. package/dist/usecases/ingest/video-pipeline.js +145 -127
  36. package/dist/usecases/ingest/video-pipeline.js.map +1 -1
  37. package/dist/usecases/ingest/view-server.d.ts +11 -0
  38. package/dist/usecases/ingest/view-server.js +89 -12
  39. package/dist/usecases/ingest/view-server.js.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/domain/ingest/video-aggregate.d.ts +0 -23
  42. package/dist/domain/ingest/video-aggregate.js +0 -89
  43. package/dist/domain/ingest/video-aggregate.js.map +0 -1
  44. package/dist/domain/ingest/video-md-parser.d.ts +0 -2
  45. package/dist/domain/ingest/video-md-parser.js +0 -233
  46. package/dist/domain/ingest/video-md-parser.js.map +0 -1
  47. package/dist/llm/tasks/ingest/plot-speaker.d.ts +0 -11
  48. package/dist/llm/tasks/ingest/plot-speaker.js +0 -72
  49. package/dist/llm/tasks/ingest/plot-speaker.js.map +0 -1
  50. package/dist/llm/tasks/ingest/roster-merge.d.ts +0 -46
  51. package/dist/llm/tasks/ingest/roster-merge.js +0 -235
  52. package/dist/llm/tasks/ingest/roster-merge.js.map +0 -1
  53. package/dist/llm/tasks/ingest/state-merge.d.ts +0 -10
  54. package/dist/llm/tasks/ingest/state-merge.js +0 -84
  55. package/dist/llm/tasks/ingest/state-merge.js.map +0 -1
  56. package/dist/llm/tasks/ingest/video-correct.d.ts +0 -30
  57. package/dist/llm/tasks/ingest/video-correct.js +0 -144
  58. package/dist/llm/tasks/ingest/video-correct.js.map +0 -1
  59. package/dist/llm/tasks/ingest/video-transcribe.d.ts +0 -4
  60. package/dist/llm/tasks/ingest/video-transcribe.js +0 -105
  61. package/dist/llm/tasks/ingest/video-transcribe.js.map +0 -1
  62. package/dist/usecases/ingest/video-pass3.d.ts +0 -26
  63. package/dist/usecases/ingest/video-pass3.js +0 -0
  64. package/dist/usecases/ingest/video-pass3.js.map +0 -1
@@ -1,72 +0,0 @@
1
- // Pure-plot speaker audit (§10.2 pass4b/pass5, ported from cezhi-verify 04b).
2
- // No video: reads the whole-series per-episode briefs + roster + one episode's
3
- // transcript and finds mis-attributed speakers using plot logic (称呼/对话回合/
4
- // 因果/阵营). Catches errors whose SOURCE is the video (lip-sync residue) where
5
- // pass4's video-trusting audit fails. Prompt VERBATIM from the tuned scaffold.
6
- export const PLOT_SPEAKER_SYSTEM = "你是短剧剧本的说话人校对器。你只拿到文字:一份**全剧分集梗概**(建立人物阵营/敌友关系)、一份跨集资产花名册(context)、和某一集的剧本流 md(逐句带说话人标注和时间戳)。**你看不到视频,也不需要看**。" +
7
- "你的唯一任务:通读这一集的剧情,找出**说话人被标错**的台词行。" +
8
- "错误来源:第一遍是对着视频画面标说话人的,当镜头没切、上一句某人刚说完口型还在,下一句其实换了人说,画面会把它误标成同一个人。这类错**光看画面救不了,只能靠剧情**。" +
9
- "判断用四种线索,且必须**唯一确定**才改:" +
10
- "① 称呼——谁喊谁「老大/哥/老X/X总/爸/妈」:喊出这个称呼的人不可能是被称呼者本人(一个人不会当面用敬称/长辈称/绰号喊自己)。" +
11
- "② 对话回合——一问一答、一让一应:谁抛的话、谁接的话,说话人应当在两个人之间交替,不会连着两句都是同一人自问自答(除非明显是独白)。" +
12
- "③ 因果——谁得利谁道谢、谁做错谁道歉、谁下命令谁执行:由剧情角色关系唯一锁定。" +
13
- "④ **阵营/立场矛盾(重要,靠全剧梗概判)**——某句话的立场(帮腔/示好/维护/巴结 vs 羞辱/打压/敌视)与被标说话人的**已知阵营**相反时,说话人标错了。例:一句在维护/巴结男主(『老板肯定站你的』『我就说吧』),却被标成男主的死对头——死对头不会替男主帮腔,这句必是男主的盟友说的。**先从梗概搞清谁和谁一伙、谁是谁的对头,再判这话该谁说。**" +
14
- "**画面残留的典型形态(重点召回)**:某句台词的前一拍或后一拍动作描写指向的是另一个人(如『X 伸手抓住 Y』紧跟一句安抚的话——说话人多半是 X 不是镜头里的第三人),或连续两句被标同一人、但一句在喊称呼/表立场明显换了人。这类最容易被画面骗过,要主动查。" +
15
- "**铁律:只在剧情明确矛盾、能唯一指认正确说话人时才输出一条 fix。** 只要有一丝不确定(可能是独白、可能你没读懂关系、正确人选不唯一),就**不要动**——宁可漏纠,不可错纠。不改台词内容,只纠说话人。没有要改的就返回空数组。";
16
- const PLOT_SPEAKER_SCHEMA = {
17
- type: "object",
18
- additionalProperties: false,
19
- required: ["speaker_fixes"],
20
- properties: {
21
- speaker_fixes: {
22
- type: "array",
23
- description: "说话人校正。只收『纯靠剧情逻辑就能确定标错了』的,证据不足一律不收。",
24
- items: {
25
- type: "object",
26
- additionalProperties: false,
27
- required: ["ts", "line", "from", "to", "reason"],
28
- properties: {
29
- ts: { type: "string", description: "这句被标错说话人的台词时间戳,原样照抄剧本流里那一行的 [MM:SS.sss]" },
30
- line: { type: "string", description: "这句台词的原文(截前 15 字即可),用于人工核对定位" },
31
- from: { type: "string", description: "剧本流里当前标的(错误)说话人" },
32
- to: { type: "string", description: "按剧情逻辑应为的正确说话人(必须来自本集在场角色/花名册,不能凭空造名)" },
33
- reason: { type: "string", description: "剧情依据一句话:靠什么称呼/对话回合/因果锁定是这个人(如:这句『谢谢老大』是得利者道谢、且喊对方『老大』,只能是受益并示好的一方,不是被喊老大的那个人)" },
34
- },
35
- },
36
- },
37
- },
38
- };
39
- export function plotSpeakerPrompt(episodeNo, allSynopsis, contextMd, pass1Md) {
40
- return `第 ${episodeNo} 集的纯剧情说话人校对。
41
-
42
- ========== 全剧分集梗概(建立人物阵营/敌友关系,用于判「谁不可能说这话」) ==========
43
- ${allSynopsis}
44
-
45
- ========== 跨集资产 CONTEXT(花名册,人物关系/称呼参考) ==========
46
- ${contextMd}
47
-
48
- ========== 本集剧本流 md(逐句带说话人 [dlg|说话人] 和时间戳) ==========
49
- ${pass1Md}
50
-
51
- ========== 任务 ==========
52
- 先读全剧梗概,搞清本集涉及的人物**谁和谁一伙、谁是谁的对头**;再通读本集,用「称呼 / 对话回合 / 因果 / 阵营立场」四种线索,找出**说话人标错**的 [dlg]/[think] 行。
53
- - 逐个可疑处问自己:这句话按剧情+阵营,说话人只可能是谁?和当前标注冲突吗?冲突且唯一 → 收一条 fix;不唯一或没读懂 → 跳过。
54
- - 典型信号:① 某句在喊「老大/老X/哥/X总」,却把说话人标成了被喊的那个人本人;② 一段对话回合里说话人没有正常交替(连着两句标同一人、但内容明显是一问一答);③ 某句在帮腔/维护/巴结某人,却被标成那个人的**对头**(阵营矛盾);④ 一句台词紧挨的上一拍/下一拍动作指向的是另一个人。
55
- - 正确说话人必须是本集真实在场、花名册里有的角色,不能编。注意本集可能用职称(宁总/齐总)指代,先对到梗概里的本名和阵营再判。
56
- - ts 原样照抄那一行的时间戳。严格按 schema 返回 JSON,没有要改的返回空数组。`;
57
- }
58
- export async function runPlotSpeaker(executor, episodeNo, allSynopsis, contextMd, pass1Md, signal) {
59
- const res = await executor.generateTool("ingest.speaker-audit", {
60
- system: PLOT_SPEAKER_SYSTEM,
61
- user: plotSpeakerPrompt(episodeNo, allSynopsis, contextMd, pass1Md),
62
- tool: { name: "submit_speaker_fixes", description: "提交纯剧情说话人校正", inputSchema: PLOT_SPEAKER_SCHEMA },
63
- ...(signal ? { signal } : {}),
64
- });
65
- const fixes = Array.isArray(res.input?.["speaker_fixes"]) ? res.input["speaker_fixes"] : [];
66
- return fixes.map((f) => {
67
- const o = f;
68
- const s = (k) => (typeof o[k] === "string" ? o[k] : "");
69
- return { ts: s("ts"), line: s("line"), from: s("from"), to: s("to"), reason: s("reason") };
70
- }).filter((f) => f.from && f.to);
71
- }
72
- //# sourceMappingURL=plot-speaker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plot-speaker.js","sourceRoot":"","sources":["../../../../src/llm/tasks/ingest/plot-speaker.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAI/E,MAAM,CAAC,MAAM,mBAAmB,GAC9B,+GAA+G;IAC/G,mCAAmC;IACnC,sFAAsF;IACtF,wBAAwB;IACxB,qEAAqE;IACrE,qEAAqE;IACrE,0CAA0C;IAC1C,qLAAqL;IACrL,oIAAoI;IACpI,sHAAsH,CAAC;AAEzH,MAAM,mBAAmB,GAAe;IACtC,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,QAAQ,EAAE,CAAC,eAAe,CAAC;IAC3B,UAAU,EAAE;QACV,aAAa,EAAE;YACb,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,oCAAoC;YACjD,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,KAAK;gBAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC;gBAChD,UAAU,EAAE;oBACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE;oBAC9E,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6BAA6B,EAAE;oBACpE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;oBACxD,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sCAAsC,EAAE;oBAC3E,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+EAA+E,EAAE;iBACzH;aACF;SACF;KACF;CACF,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,WAAmB,EAAE,SAAiB,EAAE,OAAe;IAC1G,OAAO,KAAK,SAAS;;;EAGrB,WAAW;;;EAGX,SAAS;;;EAGT,OAAO;;;;;;;gDAOuC,CAAC;AACjD,CAAC;AAID,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAyB,EACzB,SAAiB,EACjB,WAAmB,EACnB,SAAiB,EACjB,OAAe,EACf,MAAoB;IAEpB,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,sBAAsB,EAAE;QAC9D,MAAM,EAAE,mBAAmB;QAC3B,IAAI,EAAE,iBAAiB,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC;QACnE,IAAI,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE;QACnG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAE,GAAG,CAAC,KAAiC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAG,GAAG,CAAC,KAAiC,CAAC,eAAe,CAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IACrK,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACrB,MAAM,CAAC,GAAG,CAA4B,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpF,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC7F,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;AACnC,CAAC"}
@@ -1,46 +0,0 @@
1
- import type { LlmTaskExecutor } from "../../types.js";
2
- export interface RosterEntryInput {
3
- name: string;
4
- episodes: number[];
5
- anchors: string[];
6
- appearances: string[];
7
- roles: string[];
8
- }
9
- export declare const RECALL_SYSTEM: string;
10
- export declare function mergePrompt(actors: RosterEntryInput[]): string;
11
- export declare const REFINE_SYSTEM: string;
12
- export declare function refinePrompt(members: RosterEntryInput[]): string;
13
- export declare const CANON_SYSTEM: string;
14
- export declare function canonPrompt(members: Array<{
15
- name: string;
16
- episodes: number[];
17
- }>): string;
18
- export declare const REMERGE_SYSTEM: string;
19
- export declare function remergePrompt(entries: Array<{
20
- name: string;
21
- episodes: number[];
22
- anchors: string[];
23
- roles: string[];
24
- }>): string;
25
- export interface RosterGroup {
26
- canonical: string;
27
- aliases: string[];
28
- reason: string;
29
- }
30
- export declare function runRosterRecall(executor: LlmTaskExecutor, entries: RosterEntryInput[], signal?: AbortSignal): Promise<RosterGroup[]>;
31
- export declare function runRosterRefine(executor: LlmTaskExecutor, members: RosterEntryInput[], signal?: AbortSignal): Promise<string[][]>;
32
- export declare function runRosterCanon(executor: LlmTaskExecutor, members: Array<{
33
- name: string;
34
- episodes: number[];
35
- }>, signal?: AbortSignal): Promise<string>;
36
- export interface RemergeGroup {
37
- into: string;
38
- names: string[];
39
- reason: string;
40
- }
41
- export declare function runRosterRemerge(executor: LlmTaskExecutor, entries: Array<{
42
- name: string;
43
- episodes: number[];
44
- anchors: string[];
45
- roles: string[];
46
- }>, signal?: AbortSignal): Promise<RemergeGroup[]>;
@@ -1,235 +0,0 @@
1
- // Video roster merge (§10.2 pass3, ported from cezhi-verify 03b). Cross-episode
2
- // identity resolution driven by VISUAL ANCHORS — the same character transcribed
3
- // independently per episode gets different names (本名 vs 职务/外貌 vs 听错), and
4
- // only anchor + role continuity can re-unify them. Four stages, all VERBATIM from
5
- // the tuned scaffold prompts:
6
- // A recall — one global pass, recall-first, over-cluster candidate groups
7
- // B refine — per candidate group, split mis-merges + pick canonical
8
- // C canon — pick a canonical name for a confirmed cluster (本名优先)
9
- // D remerge — global recompose, merge-only, recover B's conservative splits
10
- // Structured output via forced tool calls; the tool-input JSON schemas carry the
11
- // scaffold's field descriptions verbatim (they are tuned prompt content).
12
- function joinEvidence(xs, max = 4) {
13
- const v = xs.map((s) => s.trim()).filter(Boolean).slice(0, max);
14
- return v.length ? v.join(" / ") : "无";
15
- }
16
- function evidenceBlock(m) {
17
- return `- 「${m.name}」 ep[${m.episodes.join(",")}]\n 锚点: ${joinEvidence(m.anchors)}\n 作用: ${joinEvidence(m.roles, 3)}\n 外观: ${joinEvidence(m.appearances, 3)}`;
18
- }
19
- // ===== Stage A: recall (high recall candidate grouping) =====
20
- export const RECALL_SYSTEM = "你是长篇剧集人物花名册的『候选粗聚』器,是两阶段归并的第一步。" +
21
- "同一角色跨集常被记成不同名字(听到本名 vs 按职务/外貌称呼、听错写成近音字、某集认错人)。" +
22
- "你会拿到完整花名册,每个名字带跨集锚点、外观、剧情作用、出现集号。" +
23
- "任务:把**可能是同一个人**的名字聚成候选组。" +
24
- "**这一步召回优先**:后面有专门的精判环节会逐组复核、把不该合的拆出去,所以你现在宁可多合、别漏——" +
25
- "只要两个名字在视觉锚点或剧情身份上**有合理的同人可能**,就放进同一候选组,哪怕不完全确定。" +
26
- "唯一的硬边界:锚点直接冲突(性别不同、年龄段明显不符)的绝不放同组。" +
27
- "canonical 这一步随便选组内一个名字即可(后面会重定)。每组至少 2 个名字才有意义。";
28
- // MergeDecisionSchema (recall reuses it) — verbatim field descriptions.
29
- const MERGE_DECISION_SCHEMA = {
30
- type: "object",
31
- additionalProperties: false,
32
- required: ["groups"],
33
- properties: {
34
- groups: {
35
- type: "array",
36
- description: "每组=一个真实人物+其所有别名。只输出确实需要合并(至少 1 个别名)的组;独一无二的人物不必输出",
37
- items: {
38
- type: "object",
39
- additionalProperties: false,
40
- required: ["canonical", "aliases", "reason"],
41
- properties: {
42
- canonical: { type: "string", description: "这一组同人的规范名。优先用有名有姓的本名,其次用信息最完整的称呼;避免用纯职务/敬称/临时描述作为规范名(除非整组都没有本名)" },
43
- aliases: { type: "array", items: { type: "string" }, description: "与 canonical 确认为同一个人的其他名字(不含 canonical)。只放视觉锚点确实吻合的" },
44
- reason: { type: "string", description: "判定同人的依据:视觉锚点/剧情作用/出现集,一句话" },
45
- },
46
- },
47
- },
48
- },
49
- };
50
- export function mergePrompt(actors) {
51
- const lines = actors.map(evidenceBlock).join("\n");
52
- return `下面是整部剧的完整人物花名册(${actors.length} 个名字)。找出所有『其实是同一个人』的名字并分组:
53
-
54
- ${lines}
55
-
56
- 要求:
57
- - 只输出需要合并的组(每组至少 1 个别名);独一无二的人物不用输出。
58
- - 每组选 canonical(优先本名,不用职务/敬称),其余同人放 aliases。
59
- - 判据是视觉锚点吻合 + 剧情作用连续;名字字面是否相同不重要。
60
- - 锚点确实吻合就合,别因别名多而犹豫;真拿不准(锚点冲突或信息太少)才保守不合。
61
- - 尤其:两个名字都只出现在很少几集、锚点又都很泛时,证据不足,倾向不合。
62
- 严格按 schema 返回。`;
63
- }
64
- // ===== Stage B: refine (per-group high-precision split) =====
65
- export const REFINE_SYSTEM = "你是人物同人判定的『精判』器,是两阶段归并的第二步。" +
66
- "上一步用宽松口径把一批**可能同人**的名字聚成了一个候选组。你的任务:只看这一个候选组,把它**拆成若干真正是同一个人的子簇**。" +
67
- "判据**同等看重两类**:①视觉锚点(发型/发色/脸型五官/体型/年龄段/性别/疤痣眼镜胡须配饰等标志特征);②剧情身份(所属队伍/组织/阵营/职务定位、与主角的关系、出场场合)。" +
68
- "**关键:外貌像 ≠ 同人。** 一部剧里常有多个外貌高度相似、只能靠身份区分的角色(例如同类职务的一批人:几个平头运动服的男教练、一批花白头发西装的中老年高管、几个同款球衣的球员)——" +
69
- "这些人视觉锚点几乎一样,光看外貌必然误合。**必须先看剧情身份:所属不同(不同队/不同组织/不同阵营/不同场合)、或角色定位矛盾,就是不同的人,哪怕长相一模一样也绝不合。**" +
70
- "**核心心态:拆和合两个方向的错误一样有害——既不能把不同人误合,也不能把同一个人拆散。** " +
71
- "拆的唯一正当理由是『存在真实矛盾』(身份冲突或锚点冲突);**没有矛盾就必须合,别因为名字字面不同就拆。** " +
72
- "候选组本就是同一角色跨集被记成不同名字的高发结果——一个人在不同集常被同时叫本名、职务、编号、外貌描述、临时称呼(例如同一个『某号+某发型』的配角,可能分别被记成本名、『X号队员』、『某发型队员』、『队长』等),别名多、叫法杂是正常现象,不是拆的理由。" +
73
- "原则:" +
74
- "① **身份一致 + 锚点吻合 → 必须合,这是硬性要求**:只要几个名字的所属(同一支队伍/组织)、角色定位、以及具体视觉锚点(如同一组『编号球衣+发型+配饰』特征)都对得上,就是同一个人,**哪怕一个是编号称呼、一个是职务称呼、一个是本名、一个是外貌描述,也必须合进一簇。名字字面不同绝不是拆的理由。**" +
75
- "② **只在真实矛盾时才拆**:(a)身份冲突——分属对立的不同队伍/组织/阵营、角色定位对立(一个主角亲信一个对手阵营)、活动场合完全不同;(b)锚点冲突——性别不同、年龄段明显不符、发型/脸型明显对不上(如一个大背头一个寸头)。**有明确的这类矛盾才拆,没有就合。**" +
76
- "③ 身份冲突时优先级高于外貌像:不同队/不同阵营的两个人,即使发型脸型服装都像也必须拆(防止把对立两方的相似角色误合)。" +
77
- "④ 拆一个名字前先自问:它和簇里其他人,有哪一条身份或锚点是**明确冲突**的?答不上来(只是名字不同、或描述简单)就**不许拆**。" +
78
- "⑤ 同名不一定同人:字面相同但身份/锚点明确冲突,才是两个人。" +
79
- "⑥ canonical 强制本名优先(见 schema)。";
80
- const REFINE_DECISION_SCHEMA = {
81
- type: "object",
82
- additionalProperties: false,
83
- required: ["clusters"],
84
- properties: {
85
- clusters: {
86
- type: "array",
87
- description: "把输入候选组拆成若干真正同人的子簇。不该合的名字各自单独成簇(members 只含自己)。",
88
- items: {
89
- type: "object",
90
- additionalProperties: false,
91
- required: ["canonical", "members", "reason"],
92
- properties: {
93
- canonical: { type: "string", description: "这个子簇的规范名。**强制本名优先**:只要簇内有一个是像本名的(有名有姓/像人名),就必须选它,不要选职务('X总''董事长''助理')、外貌('黑衣女子')、临时称呼。全簇都没本名时才退而用信息最全的称呼" },
94
- members: { type: "array", items: { type: "string" }, description: "确认是同一个人的名字(含 canonical 本身)。至少 1 个" },
95
- reason: { type: "string", description: "为什么这些是同人、以及(若有)为什么把某些名字排除出去,一句话" },
96
- },
97
- },
98
- },
99
- },
100
- };
101
- export function refinePrompt(members) {
102
- const lines = members.map(evidenceBlock).join("\n");
103
- return `上一步把下面这 ${members.length} 个名字聚成了一个「可能同人」候选组。请复核,把它拆成真正是同一个人的子簇:
104
-
105
- ${lines}
106
-
107
- 要求:
108
- - **判同人前先对身份**:看每个名字的「作用」——所属队伍/组织/阵营、职务定位、与主角关系。身份一致再看外貌是否吻合。
109
- - **身份不同就拆,哪怕外貌一样**:分属对立两方、角色定位矛盾、活动场合完全不同的,是不同的人,即使发型脸型服装都像也必须拆开。(尤其一批同类职务、同款服装的相似角色,必须靠身份区分,别因为外貌像就合。)
110
- - 身份一致 + 外貌吻合的若干称呼(哪怕措辞略有出入、一个本名一个职务、只出现一集),属于同人,合。
111
- - 每个子簇选 canonical:**本名优先**,有本名就用本名,别用职务/外貌/临时称呼。
112
- - 覆盖输入的每一个名字:每个名字必须恰好出现在一个子簇里。
113
- 严格按 schema 返回。`;
114
- }
115
- // ===== Stage C: canonical selection for a confirmed cluster =====
116
- export const CANON_SYSTEM = "你要为『同一个人物的一组不同称呼』挑一个规范名(canonical)。这些名字已确认是同一个人。" +
117
- "挑选优先级:① 有名有姓、像真实姓名的(如「张伟」「李文博」)最优先;" +
118
- "② 其次是含姓的称呼(如「张总」「李董」);③ 最不优先的是纯职务/身份/外貌/占位描述(如「助理」「秘书」「黑衣女子」「前上司」「男员工」)和英文/编号名。" +
119
- "在同一优先级里,选出现集数多、信息更完整的。" +
120
- "只能从给定列表里原样挑一个,不要创造新名字。";
121
- const CANON_SCHEMA = {
122
- type: "object",
123
- additionalProperties: false,
124
- required: ["canonical"],
125
- properties: {
126
- canonical: { type: "string", description: "从给定名字里挑一个当规范名。**必须原样是列表中的某个名字**,不要改写、不要合成新名。" },
127
- },
128
- };
129
- export function canonPrompt(members) {
130
- const lines = members.map((m) => `- ${m.name}(出现 ${m.episodes.length} 集)`).join("\n");
131
- return `这些名字是同一个人的不同称呼,挑一个当规范名(本名优先,其次含姓称呼,最后才用职务/描述名):\n\n${lines}\n\n只从上面原样选一个,按 schema 返回。`;
132
- }
133
- // ===== Stage D: global remerge (merge-only) =====
134
- export const REMERGE_SYSTEM = "你是人物花名册的『全局复合』器,是归并的最后一步。前面的步骤已把名字聚成了一批规范名,每个规范名代表一个人。" +
135
- "但前面的精判偏保守(宁可拆开也别误合),可能把**同一个人**拆成了几个独立规范名——" +
136
- "例如同一个角色,在不同集分别被记成本名、编号称呼、职务、外貌描述,前面没能全部并到一起,就散成了好几个条目。" +
137
- "你的**唯一任务**:通读全部规范名 + 它们的视觉锚点、剧情作用、出现集,找出其中**其实是同一个人**的,把它们合并。" +
138
- "**只做合并,绝不拆分**:你不能把任何一个规范名拆开,只能指出'这几个规范名是同一个人、应合成一个'。" +
139
- "判据:①所属/身份一致(同一支队伍/组织、同一角色定位、同一关系);②视觉锚点吻合(同一组具体特征:发型+配饰+服装编号等)。两者都对得上才合。" +
140
- "铁律:" +
141
- "① **只合确实同人的**:所属身份一致 + 锚点吻合,才判同人合并。典型可合信号:同一批具体特征(如同号球衣+同发型+同配饰)反复出现,只是被记成了不同称呼(本名/编号/职务/外貌)。" +
142
- "② **身份或锚点冲突的绝不合**:分属对立的不同队伍/组织/阵营、角色定位相反、或外貌锚点直接冲突(性别不同、一个背头一个寸头、年龄段明显不符)——这些是不同的人,即使名字或球衣号相近也不能合。" +
143
- "③ 拿不准就**不合**:只输出你有把握是同一人的合并;宁可漏合,不可错合。" +
144
- "④ into 与 names 里的每个名字,都必须原样是给定列表中的规范名,不要造新名。";
145
- const REMERGE_SCHEMA = {
146
- type: "object",
147
- additionalProperties: false,
148
- required: ["merges"],
149
- properties: {
150
- merges: {
151
- type: "array",
152
- description: "需要再合并的组。只输出**确实是同一个人被拆散**的组;拿不准就不要合。没有要合的返回空数组。",
153
- items: {
154
- type: "object",
155
- additionalProperties: false,
156
- required: ["into", "names", "reason"],
157
- properties: {
158
- into: { type: "string", description: "合并后保留的规范名(本名优先;从这一组里挑最像本名的,或出现集数最多的)。必须原样是给定列表中的某个名字。" },
159
- names: { type: "array", items: { type: "string" }, description: "要合并到 into 的其他规范名(不含 into 自身),至少 1 个。必须都是给定列表中的名字。" },
160
- reason: { type: "string", description: "为什么这些是同一个人:所属/身份 + 视觉锚点如何吻合,一句话。" },
161
- },
162
- },
163
- },
164
- },
165
- };
166
- export function remergePrompt(entries) {
167
- const lines = entries.map((e) => {
168
- const anc = joinEvidence(e.anchors, 3);
169
- const role = joinEvidence(e.roles, 2);
170
- return `- 「${e.name}」 ep[${e.episodes.join(",")}] 锚点: ${anc} 作用: ${role}`;
171
- }).join("\n");
172
- return `下面是全剧当前的 ${entries.length} 个规范名(每个本应代表一个人)。请找出其中**其实是同一个人、被拆成了多个规范名**的,合并它们:
173
-
174
- ${lines}
175
-
176
- 要求:
177
- - 只合并**确实是同一个人**的规范名:所属/身份一致 + 视觉锚点吻合,才合。
178
- - 分属不同队伍/组织/阵营、角色定位对立、或外貌锚点冲突(如一个背头一个寸头)的,绝不合。
179
- - 每组指定 into(合并后保留的名,本名优先)和要并入的其他规范名。
180
- - 拿不准就不要合。没有要合的返回空 merges 数组。
181
- 严格按 schema 返回。`;
182
- }
183
- // ===== run helpers (forced tool call → structured object) =====
184
- const asList = (v) => (Array.isArray(v) ? v : []);
185
- const str = (v) => (typeof v === "string" ? v : "");
186
- const strList = (v) => asList(v).map(str).filter(Boolean);
187
- function tool(name, description, schema) {
188
- return { name, description, inputSchema: schema };
189
- }
190
- export async function runRosterRecall(executor, entries, signal) {
191
- const res = await executor.generateTool("ingest.roster-recall", {
192
- system: RECALL_SYSTEM,
193
- user: mergePrompt(entries),
194
- tool: tool("submit_groups", "提交同人候选分组", MERGE_DECISION_SCHEMA),
195
- ...(signal ? { signal } : {}),
196
- });
197
- const input = res.input;
198
- return asList(input?.["groups"]).map((g) => {
199
- const o = g;
200
- return { canonical: str(o["canonical"]), aliases: strList(o["aliases"]), reason: str(o["reason"]) };
201
- });
202
- }
203
- export async function runRosterRefine(executor, members, signal) {
204
- const res = await executor.generateTool("ingest.roster-refine", {
205
- system: REFINE_SYSTEM,
206
- user: refinePrompt(members),
207
- tool: tool("submit_clusters", "提交同人子簇拆分", REFINE_DECISION_SCHEMA),
208
- ...(signal ? { signal } : {}),
209
- });
210
- const input = res.input;
211
- return asList(input?.["clusters"]).map((c) => strList(c["members"]));
212
- }
213
- export async function runRosterCanon(executor, members, signal) {
214
- const res = await executor.generateTool("ingest.roster-canon", {
215
- system: CANON_SYSTEM,
216
- user: canonPrompt(members),
217
- tool: tool("submit_canonical", "提交规范名", CANON_SCHEMA),
218
- ...(signal ? { signal } : {}),
219
- });
220
- return str(res.input?.["canonical"]);
221
- }
222
- export async function runRosterRemerge(executor, entries, signal) {
223
- const res = await executor.generateTool("ingest.roster-remerge", {
224
- system: REMERGE_SYSTEM,
225
- user: remergePrompt(entries),
226
- tool: tool("submit_merges", "提交需再合并的规范名组", REMERGE_SCHEMA),
227
- ...(signal ? { signal } : {}),
228
- });
229
- const input = res.input;
230
- return asList(input?.["merges"]).map((m) => {
231
- const o = m;
232
- return { into: str(o["into"]), names: strList(o["names"]), reason: str(o["reason"]) };
233
- });
234
- }
235
- //# sourceMappingURL=roster-merge.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"roster-merge.js","sourceRoot":"","sources":["../../../../src/llm/tasks/ingest/roster-merge.ts"],"names":[],"mappings":"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"}
@@ -1,10 +0,0 @@
1
- import type { LlmTaskExecutor } from "../../types.js";
2
- export declare const STATE_MERGE_SYSTEM: string;
3
- export declare function stateMergePrompt(personName: string, states: Array<{
4
- name: string;
5
- count: number;
6
- }>): string;
7
- export declare function runStateMerge(executor: LlmTaskExecutor, person: string, states: Array<{
8
- name: string;
9
- count: number;
10
- }>, signal?: AbortSignal): Promise<Record<string, string>>;
@@ -1,84 +0,0 @@
1
- // Video state merge (§10.2 pass3c, ported from cezhi-verify 03c). Per character,
2
- // collapse the many free-form outfit wordings (Gemini describes the same outfit
3
- // differently each episode) into canonical states. Distinct from govern's
4
- // state-reconcile: this dedups the wording fragmentation before convergence;
5
- // govern later selects default + drops transient. Prompt is VERBATIM from the
6
- // tuned scaffold; the tool-input schema carries its field descriptions verbatim.
7
- export const STATE_MERGE_SYSTEM = "你是剧集人物『造型状态』的归并器。同一个角色在不同镜头/不同集里穿的**同一套衣服**," +
8
- "常被逐集自由描述成很多不同措辞——有的写全(连颜色、款式、编号都写上),有的只写个大概(只有『白衣』『球服』这种笼统类别)," +
9
- "还有的把颜色/款式说岔(把某个颜色写成邻近色、把一种服装误认成另一种)。" +
10
- "你的任务:只看**一个人物**的全部状态措辞,把其中**其实是同一套装扮**的合并成一组,每组选一个规范状态名。" +
11
- "核心判据:**是不是同一套服装造型**(同一件/同一套衣服),而不是颜色字面像不像。" +
12
- "原则:" +
13
- "① 同一套的不同繁简说法要合:同一套衣服,有的措辞带全颜色+款式+编号、有的只写大类——只要都指这人那一套,就合成一组,canonical 取信息最完整的。" +
14
- "② 明显的口误/误认要并进对应造型:把某类服装误写成相近的另一类、把某颜色写成邻近色调,只要指的是同一套,就并进去。" +
15
- "③ **不同造型绝不合**:不同颜色/不同主色的两套(如一套浅色、一套深色)、正装 vs 便装、正常 vs 受伤/特殊状态,是不同造型,各自成组。" +
16
- "④ 拿不准是不是同一套时,宁可**不合**、让它单独成组——错合(把两套并成一套)比漏合更糟。" +
17
- "⑤ canonical 必须原样是组内出现过的措辞,不要合成新词、不要改字。" +
18
- "⑥ 覆盖每一个输入措辞:每个措辞必须恰好属于一个组(哪怕它独自成组)。";
19
- const STATE_MERGE_SCHEMA = {
20
- type: "object",
21
- additionalProperties: false,
22
- required: ["groups"],
23
- properties: {
24
- groups: {
25
- type: "array",
26
- description: "把输入的状态措辞分组。每组=同一套造型的不同说法。只指同一套装扮的才归一组;不同颜色/不同款式/不同场合的造型各自单独成组。",
27
- items: {
28
- type: "object",
29
- additionalProperties: false,
30
- required: ["canonical", "members", "reason"],
31
- properties: {
32
- canonical: { type: "string", description: "这一组的规范状态名。从组内措辞里挑**信息最完整、最清晰**的那个:同时含颜色+款式+编号/标识细节的,优于只有笼统类别的(信息全的一版优于「白衣」「运动服」这种大概说法)。必须原样是组内出现过的某个措辞,不要新造。" },
33
- members: { type: "array", items: { type: "string" }, description: "确认是**同一套服装造型**的所有措辞(含 canonical 自身)。只放真正指同一套的。" },
34
- reason: { type: "string", description: "为什么这些是同一套造型,一句话(如:都是同一套主色球衣,只是措辞繁简不同)。" },
35
- },
36
- },
37
- },
38
- },
39
- };
40
- export function stateMergePrompt(personName, states) {
41
- const lines = states.map((s) => `- 「${s.name}」(出现 ${s.count} 场)`).join("\n");
42
- return `下面是角色「${personName}」在全剧里被记录到的所有造型状态措辞。请把其中**其实是同一套装扮**的合并分组:
43
-
44
- ${lines}
45
-
46
- 要求:
47
- - 判据是「是不是同一套服装造型」,不是颜色字面像不像。
48
- - 同一套衣服的繁简/口误/误认说法合成一组,canonical 取信息最完整清晰的那个措辞(原样照抄,别新造)。
49
- - 不同颜色/款式/场合的造型(如一套浅色装 vs 一套深色装 vs 正装 vs 受伤/特殊状态)各自单独成组。
50
- - 拿不准就让它单独成组,别硬合。
51
- - 覆盖每一个措辞:每个措辞恰好属于一个组。
52
- 严格按 schema 返回。`;
53
- }
54
- // Returns { wording -> canonical } for the merged (≥2-member) groups only,
55
- // mirroring the scaffold mergePerson: canonical from LLM if valid, else the
56
- // highest-count wording; hallucinated members dropped.
57
- export async function runStateMerge(executor, person, states, signal) {
58
- const valid = new Set(states.map((s) => s.name));
59
- const cnt = new Map(states.map((s) => [s.name, s.count]));
60
- const res = await executor.generateTool("ingest.state-merge", {
61
- system: STATE_MERGE_SYSTEM,
62
- user: stateMergePrompt(person, states),
63
- tool: { name: "submit_state_groups", description: "提交造型状态归并分组", inputSchema: STATE_MERGE_SCHEMA },
64
- ...(signal ? { signal } : {}),
65
- });
66
- const groups = Array.isArray(res.input?.["groups"]) ? res.input["groups"] : [];
67
- const map = {};
68
- for (const g of groups) {
69
- const o = g;
70
- const membersRaw = Array.isArray(o["members"]) ? o["members"] : [];
71
- const mem = membersRaw.map((m) => (typeof m === "string" ? m : "")).filter((m, i, a) => valid.has(m) && a.indexOf(m) === i);
72
- if (mem.length < 2)
73
- continue;
74
- const canonRaw = typeof o["canonical"] === "string" ? o["canonical"] : "";
75
- let canon = valid.has(canonRaw) && mem.includes(canonRaw) ? canonRaw : "";
76
- if (!canon)
77
- canon = mem.reduce((b, s) => ((cnt.get(s) ?? 0) > (cnt.get(b) ?? 0) ? s : b), mem[0]);
78
- for (const s of mem)
79
- if (s !== canon)
80
- map[s] = canon;
81
- }
82
- return map;
83
- }
84
- //# sourceMappingURL=state-merge.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"state-merge.js","sourceRoot":"","sources":["../../../../src/llm/tasks/ingest/state-merge.ts"],"names":[],"mappings":"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"}
@@ -1,30 +0,0 @@
1
- import type { TaskVideoPort, VideoFileHandle } from "../../types.js";
2
- export declare const VIDEO_CORRECT_SYSTEM: string;
3
- export declare function videoCorrectPrompt(episodeNo: number, contextMd: string, pass1Md: string): string;
4
- export interface VideoCorrection {
5
- name_fixes: Array<{
6
- from: string;
7
- to: string;
8
- scope: string;
9
- reason: string;
10
- }>;
11
- same_person: Array<{
12
- names: string[];
13
- reason: string;
14
- }>;
15
- speaker_fixes: Array<{
16
- ts: string;
17
- from: string;
18
- to: string;
19
- reason: string;
20
- }>;
21
- dialogue_fixes: Array<{
22
- ts: string;
23
- speaker: string;
24
- wrong: string;
25
- right: string;
26
- reason: string;
27
- }>;
28
- notes: string[];
29
- }
30
- export declare function runVideoCorrect(port: TaskVideoPort, file: VideoFileHandle, episodeNo: number, contextMd: string, pass1Md: string, fps: number, signal?: AbortSignal): Promise<VideoCorrection>;