selector.spec.ts 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { describe, expect, it } from "vitest"
  2. import type { UserMemoryRule } from "./types"
  3. import { inferUserMemorySurface, selectUserMemoryRules } from "./selector"
  4. function rule(partial: Partial<UserMemoryRule>): UserMemoryRule {
  5. return {
  6. id: partial.id ?? "r1",
  7. rule: partial.rule ?? "回答时先给结论。",
  8. category: partial.category ?? "interaction_preference",
  9. source: partial.source ?? "automatic",
  10. surfaces: partial.surfaces ?? ["all"],
  11. confidence: partial.confidence ?? 0.8,
  12. evidenceSummary: "",
  13. sourceHash: null,
  14. fingerprint: partial.fingerprint ?? "fp",
  15. enabled: partial.enabled ?? true,
  16. createdAt: 1,
  17. updatedAt: 1,
  18. ...partial,
  19. }
  20. }
  21. describe("user memory selector", () => {
  22. it("根据任务识别审稿、大纲和章节写作场景", () => {
  23. expect(inferUserMemorySurface("请审查这一章的问题")).toBe("review")
  24. expect(inferUserMemorySurface("生成完整故事大纲")).toBe("ai-outline")
  25. expect(inferUserMemorySurface("续写第十章正文")).toBe("chapter-writing")
  26. })
  27. it("审查任务不注入创作文风偏好", () => {
  28. const selected = selectUserMemoryRules([
  29. rule({ id: "style", category: "writing_preference", rule: "写作保持幽默。", surfaces: ["all"] }),
  30. rule({ id: "review", category: "constraint", rule: "审查时列出证据。", surfaces: ["review"] }),
  31. ], { task: "请审查这一章", surface: "review" })
  32. expect(selected.map((item) => item.id)).toEqual(["review"])
  33. })
  34. it("当前请求明确否定旧规则时不注入旧规则", () => {
  35. const selected = selectUserMemoryRules([
  36. rule({ rule: "大纲输出使用分层标题。", category: "format_preference", surfaces: ["ai-outline"] }),
  37. ], { task: "本次大纲不要使用分层标题,直接连续输出。", surface: "ai-outline" })
  38. expect(selected).toEqual([])
  39. })
  40. it("只选择 active 规则并按会话、作品、全局层级覆盖", () => {
  41. const selected = selectUserMemoryRules([
  42. rule({ id: "global", rule: "回答时先给结论。", fingerprint: "same", scope: "global", status: "active" }),
  43. rule({ id: "project", rule: "本作品先给结论。", fingerprint: "same", scope: "project", projectKey: "p1", status: "active" }),
  44. rule({ id: "session", rule: "本会话先给结论。", fingerprint: "same", scope: "session", projectKey: "p1", sessionKey: "s1", status: "active" }),
  45. rule({ id: "candidate", rule: "候选规则。", status: "candidate" }),
  46. rule({ id: "conflicted", rule: "冲突规则。", status: "conflicted" }),
  47. ], {
  48. task: "请回答问题",
  49. surface: "ai-chat",
  50. projectKey: "p1",
  51. sessionKey: "s1",
  52. })
  53. expect(selected.map((item) => item.id)).toContain("session")
  54. expect(selected.map((item) => item.id)).not.toContain("project")
  55. expect(selected.map((item) => item.id)).not.toContain("global")
  56. expect(selected.map((item) => item.id)).not.toContain("candidate")
  57. expect(selected.map((item) => item.id)).not.toContain("conflicted")
  58. })
  59. it("没有作品或会话标识时只使用全局规则", () => {
  60. const selected = selectUserMemoryRules([
  61. rule({ id: "global", scope: "global", status: "active" }),
  62. rule({ id: "project", scope: "project", projectKey: "p1", status: "active" }),
  63. rule({ id: "session", scope: "session", sessionKey: "s1", status: "active" }),
  64. ], { task: "请回答问题", surface: "ai-chat" })
  65. expect(selected.map((item) => item.id)).toEqual(["global"])
  66. })
  67. })