test_stages.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """M4 离线测试:注入假 chat,逐环节断言;assemble 纯函数对齐设计文档 §6。"""
  2. from __future__ import annotations
  3. import json
  4. from creation_knowledge.models import (
  5. Deconstruction,
  6. Evidence,
  7. ExtractedContent,
  8. KnowledgeItem,
  9. Post,
  10. Scope,
  11. )
  12. from creation_knowledge.stages import (
  13. build_ingest_payload,
  14. deconstruct_item,
  15. screen_post,
  16. split_post,
  17. )
  18. def _post() -> Post:
  19. return Post(
  20. id="xhs_abc", url="https://www.xiaohongshu.com/explore/abc", content_id="abc",
  21. title="只要学会这几样,写短视频脚本真的不难", platform="xiaohongshu",
  22. author_name="海狸教自媒体运营", topic_list=["短视频"],
  23. )
  24. def _content() -> ExtractedContent:
  25. return ExtractedContent(
  26. text="短视频脚本包含标题、拍摄地点、分镜等要素",
  27. from_image="九宫格图讲了脚本6要素", is_empty=False,
  28. )
  29. def test_screen_passed():
  30. r = screen_post(_post(), _content(),
  31. chat=lambda s, u: {"passed": True, "score": 8, "reason": "含明确 How"})
  32. assert r.passed and r.score == 8
  33. def test_screen_feeds_extracted_not_body():
  34. seen = {}
  35. def chat(s, u):
  36. seen["u"] = u
  37. return {"passed": True, "score": 7, "reason": "x"}
  38. screen_post(_post(), _content(), chat=chat)
  39. assert "九宫格" in seen["u"] # 多模态提取内容进了 prompt,而非空 body_text
  40. def test_split_consistency_and_drop_empty():
  41. def chat(s, u):
  42. return {"items": [
  43. {"title": "脚本要素", "knowledge_types": ["what", "how", "why"],
  44. "what": "脚本含6要素", "why": "null", "how": "逐个填要素",
  45. "evidence": ["原句"]},
  46. {"title": "空", "knowledge_types": ["what"],
  47. "what": "null", "why": "null", "how": "null", "evidence": []},
  48. ]}
  49. items = split_post(_post(), _content(), chat=chat)
  50. assert len(items) == 1 # 三个都空的片段被丢弃
  51. it = items[0]
  52. assert it.why is None # "null" 归一为 None
  53. assert set(it.knowledge_types) == {"what", "how"} # 与非空字段对齐,why 被剔除
  54. def test_split_source_cards_and_evidence():
  55. def chat(s, u):
  56. return {"items": [{"title": "评论区选题法", "knowledge_types": ["how"],
  57. "what": "null", "why": "null", "how": "收集高赞评论改写",
  58. "source_cards": [1, 3],
  59. "evidence": [{"text": "高赞评论反映痛点", "card": 1},
  60. "无卡片的纯字符串证据"]}]}
  61. it = split_post(_post(), _content(), chat=chat)[0]
  62. assert it.source_cards == [1, 3]
  63. assert it.evidence[0].card == 1 and it.evidence[0].text
  64. assert it.evidence[1].card is None # 字符串证据 → card=None
  65. def test_deconstruct_filters_invalid():
  66. def chat(s, u):
  67. return {"stages": ["选题", "脚本", "乱写"],
  68. "scopes": [{"scope_type": "form", "value": "操作流程"},
  69. {"scope_type": "bad", "value": "x"},
  70. {"scope_type": "effect", "value": ""}],
  71. "stage_reason": "r1", "scope_reason": "r2"}
  72. d = deconstruct_item(KnowledgeItem(title="t", knowledge_types=["how"], how="做法"),
  73. chat=chat)
  74. assert d.stages == ["选题", "脚本"] # 非法阶段过滤
  75. assert [s.scope_type for s in d.scopes] == ["form"] # 非法 scope / 空 value 过滤
  76. def test_assemble_matches_design_doc():
  77. item = KnowledgeItem(title="评论区选题法", knowledge_types=["what", "how"],
  78. what="从评论提取痛点", why=None, how="收集高赞评论改写",
  79. source_cards=[2], evidence=[Evidence(text="原句a", card=2)])
  80. deco = Deconstruction(
  81. stages=["选题", "脚本"],
  82. scopes=[Scope(scope_type="form", value="公式框架"),
  83. Scope(scope_type="effect", value="生成选题")],
  84. stage_reason="既选题又脚本", scope_reason="形式+作用")
  85. p = build_ingest_payload(_post(), item, deco)
  86. assert p.source["id"] == "xhs_abc"
  87. assert p.source["source_type"] == "post"
  88. assert p.source["source_metadata"]["platform"] == "xiaohongshu"
  89. assert p.dim_attributes == ["what", "how"]
  90. assert p.dim_creations == ["选题", "脚本"]
  91. assert {"scope_type": "form", "value": "公式框架"} in p.scopes
  92. assert json.loads(p.content) == {"what": "从评论提取痛点", "why": None,
  93. "how": "收集高赞评论改写"}
  94. keys = [e["key"] for e in p.custom_ext]
  95. assert "原文证据" in keys and "来源卡片" in keys