Pārlūkot izejas kodu

feat(校验): 角色认知偏差识别 identifyDeviations

Mochocyang 2 mēneši atpakaļ
vecāks
revīzija
23264e7a08

+ 60 - 0
src/lib/agent/skills/draft-review-skill.spec.ts

@@ -1,5 +1,6 @@
 import { describe, it, expect, vi, beforeEach } from "vitest";
 import {
+  identifyDeviations,
   loadReviewEvidence,
   type Deviation,
   type DraftReviewInput,
@@ -163,3 +164,62 @@ describe("loadReviewEvidence", () => {
     expect(evidence.cognition).toBeNull();
   });
 });
+
+describe("identifyDeviations - 角色认知偏差", () => {
+  it("角色说出了 doesNotKnow 里的信息 → 标 high 偏差", () => {
+    const evidence: ReviewEvidence = {
+      cognition: {
+        characters: [
+          { character: "李雷", knows: [], doesNotKnow: ["暗杀计划"] },
+        ],
+        readerKnows: [],
+        lastUpdatedChapter: 3,
+      },
+      characterStates: { characters: [], lastUpdated: "" },
+      foreshadowing: { items: [], lastUpdated: "" },
+      previousSnapshot: null,
+      internalConflict: false,
+      rawLoadError: false,
+    };
+    const draft = '李雷说:"我已经知道了暗杀计划,所以早有准备。"';
+    const deviations = identifyDeviations(draft, evidence);
+    expect(deviations).toHaveLength(1);
+    expect(deviations[0].type).toBe("cognition");
+    expect(deviations[0].severity).toBe("high");
+    expect(deviations[0].expected).toContain("李雷不知道暗杀计划");
+    expect(deviations[0].memoryEvidence).toContain("暗杀计划");
+  });
+
+  it("没有偏差时返回空数组", () => {
+    const evidence: ReviewEvidence = {
+      cognition: {
+        characters: [
+          { character: "李雷", knows: ["暗杀计划"], doesNotKnow: [] },
+        ],
+        readerKnows: [],
+        lastUpdatedChapter: 3,
+      },
+      characterStates: { characters: [], lastUpdated: "" },
+      foreshadowing: { items: [], lastUpdated: "" },
+      previousSnapshot: null,
+      internalConflict: false,
+      rawLoadError: false,
+    };
+    const draft = '李雷说:"我已经知道了暗杀计划,所以早有准备。"';
+    const deviations = identifyDeviations(draft, evidence);
+    expect(deviations).toHaveLength(0);
+  });
+
+  it("记忆中心为空时不下偏差(新作品首章)", () => {
+    const evidence: ReviewEvidence = {
+      cognition: null,
+      characterStates: { characters: [], lastUpdated: "" },
+      foreshadowing: { items: [], lastUpdated: "" },
+      previousSnapshot: null,
+      internalConflict: false,
+      rawLoadError: false,
+    };
+    const deviations = identifyDeviations("任意草稿内容", evidence);
+    expect(deviations).toHaveLength(0);
+  });
+});

+ 86 - 0
src/lib/agent/skills/draft-review-skill.ts

@@ -102,3 +102,89 @@ export async function loadReviewEvidence(
     rawLoadError,
   };
 }
+
+let _nextId = 1;
+function nextDeviationId(): string {
+  return "dev-cog-" + String(_nextId++);
+}
+
+function findMatchingCharacter(draft: string, character: string): boolean {
+  return draft.includes(character);
+}
+
+const COGNITION_LEAK_PATTERNS = [
+  "知道",
+  "知道了",
+  "得知了",
+  "察觉到",
+  "意识到",
+  "已经知道了",
+];
+
+function escapeRegex(str: string): string {
+  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
+function findLocation(draft: string, character: string): string {
+  const lines = draft.split("\n");
+  for (let i = 0; i < lines.length; i++) {
+    if (lines[i].includes(character)) {
+      return "第 " + String(i + 1) + " 行";
+    }
+  }
+  return "第 1 行";
+}
+
+export function identifyDeviations(
+  draft: string,
+  evidence: ReviewEvidence,
+): Deviation[] {
+  const { cognition } = evidence;
+  if (!cognition) return [];
+
+  const deviations: Deviation[] = [];
+
+  for (const cc of cognition.characters) {
+    const { character, doesNotKnow } = cc;
+    if (!findMatchingCharacter(draft, character)) continue;
+
+    for (const unknown of doesNotKnow) {
+      const pattern = COGNITION_LEAK_PATTERNS.some((kw) => {
+        const sentencePattern = new RegExp(
+          escapeRegex(character) +
+            "[^。!?\\n]{0,50}" +
+            escapeRegex(kw) +
+            "[^。!?\\n]{0,50}" +
+            escapeRegex(unknown),
+        );
+        if (sentencePattern.test(draft)) return true;
+
+        const reversePattern = new RegExp(
+          escapeRegex(unknown) +
+            "[^。!?\\n]{0,50}" +
+            escapeRegex(kw) +
+            "[^。!?\\n]{0,50}" +
+            escapeRegex(character),
+        );
+        if (reversePattern.test(draft)) return true;
+
+        return false;
+      });
+
+      if (!pattern) continue;
+
+      deviations.push({
+        id: nextDeviationId(),
+        type: "cognition",
+        location: findLocation(draft, character),
+        originalText: character + "提及了 " + unknown,
+        expected: character + "不知道" + unknown,
+        memoryEvidence:
+          "记忆中心记录:" + character + " 不知道\u300C" + unknown + "\u300D",
+        severity: "high",
+      });
+    }
+  }
+
+  return deviations;
+}