framing.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. """Knowledge particle framing and type-shape cleanup."""
  2. from __future__ import annotations
  3. import json
  4. from typing import Any
  5. from core.llm import chat_json as default_chat_json
  6. from core.models import Post
  7. from decode_content.contracts import BUSINESS_STAGES, CREATION_STAGES, WHAT_KINDS, SkillContract, load_contract
  8. from decode_content.gates import ChatJsonFn, how_gate, why_refute_gate
  9. CSTAGE = set(CREATION_STAGES)
  10. KINDS = set(WHAT_KINDS)
  11. KIND_FIX = {"多维度关系": "多维关系", "多维": "多维关系", "集合": "子集", "序列型": "序列"}
  12. def frame_knowledges(
  13. post: Post,
  14. read_text: str,
  15. *,
  16. contract: SkillContract | None = None,
  17. chat_json_fn: ChatJsonFn = default_chat_json,
  18. ) -> list[dict[str, Any]]:
  19. contract = contract or load_contract()
  20. user = (
  21. f"原帖标题:{post.title or '(无)'}\n\n读懂后的完整内容:\n{read_text}\n\n"
  22. "按上面规则拆颗+判类型+成形+轻标签。作用域字段一律留空 []。"
  23. "只输出 JSON:{\"knowledges\":[ ... 见模板 ... ]}"
  24. )
  25. return chat_json_fn(contract.phase1, user, timeout=120).get("knowledges") or []
  26. def reshape_nonhow(
  27. knowledge: dict[str, Any],
  28. *,
  29. contract: SkillContract | None = None,
  30. chat_json_fn: ChatJsonFn = default_chat_json,
  31. ) -> list[dict[str, Any]]:
  32. contract = contract or load_contract()
  33. body = f"目标:{knowledge.get('purpose', '')}\n" + "\n".join(
  34. f"- 输入:{step.get('input', '')}|方法:{step.get('directive', '')}|产出:{step.get('output', '')}"
  35. for step in knowledge.get("steps", [])
  36. )
  37. user = (
  38. "【下面这块原被误判为 how 工序,实为「离散构成 / 原理」,请只拆成 What/Why 主颗——"
  39. "每个'是什么/分几类'的构成块拆一颗 What,背后的原理/标准拆一颗 Why;"
  40. "不要 how、不要组件颗,parent 一律 null。作用域字段留空 []。】\n\n"
  41. f"原标题:{knowledge.get('title', '')}\n{body}\n\n"
  42. "只输出 JSON:{\"knowledges\":[ ... 仅 what/why,见模板 ... ]}"
  43. )
  44. try:
  45. out = chat_json_fn(contract.phase1, user, timeout=120).get("knowledges") or []
  46. except Exception:
  47. out = []
  48. reshaped: list[dict[str, Any]] = []
  49. for idx, item in enumerate(out, 1):
  50. if item.get("type") == "how":
  51. continue
  52. item["id"] = f"{knowledge.get('id', 'k')}r{idx}"
  53. item["role"], item["parent"] = "主", None
  54. reshaped.append(item)
  55. return reshaped or [knowledge]
  56. def fix_fake_hows(
  57. knowledges: list[dict[str, Any]],
  58. *,
  59. contract: SkillContract | None = None,
  60. chat_json_fn: ChatJsonFn = default_chat_json,
  61. ) -> list[dict[str, Any]]:
  62. out: list[dict[str, Any]] = []
  63. for knowledge in knowledges:
  64. if knowledge.get("type") == "how" and len(knowledge.get("steps", [])) >= 2:
  65. gate = how_gate(knowledge, chat_json_fn=chat_json_fn)
  66. if not gate.passed:
  67. reshaped = reshape_nonhow(knowledge, contract=contract, chat_json_fn=chat_json_fn)
  68. for item in reshaped:
  69. item.setdefault("metadata", {})["how_gate_reason"] = gate.reason
  70. out.extend(reshaped)
  71. continue
  72. out.append(knowledge)
  73. return out
  74. def drop_fake_whys(
  75. knowledges: list[dict[str, Any]],
  76. *,
  77. chat_json_fn: ChatJsonFn = default_chat_json,
  78. ) -> list[dict[str, Any]]:
  79. out: list[dict[str, Any]] = []
  80. for knowledge in knowledges:
  81. if knowledge.get("type") == "why":
  82. gate = why_refute_gate(knowledge, chat_json_fn=chat_json_fn)
  83. if not gate.passed:
  84. verdict = gate.details.get("verdict")
  85. if verdict == "废话":
  86. continue
  87. if verdict in {"实为what", "实为how"}:
  88. knowledge["inferred"] = True
  89. knowledge["inferred_reason"] = f"why闸疑似{verdict}:{gate.reason}"
  90. out.append(knowledge)
  91. return out
  92. def normalize_knowledge_shapes(knowledges: list[dict[str, Any]]) -> list[dict[str, Any]]:
  93. how_ids = {k.get("id") for k in knowledges if k.get("type") == "how"}
  94. for knowledge in knowledges:
  95. knowledge["业务阶段"] = [
  96. stage for stage in (knowledge.get("业务阶段") or []) if stage in BUSINESS_STAGES
  97. ]
  98. if knowledge.get("type") == "what" and knowledge.get("kind") not in KINDS:
  99. knowledge["kind"] = KIND_FIX.get((knowledge.get("kind") or "").strip(), knowledge.get("kind"))
  100. for step in knowledge.get("steps", []):
  101. if step.get("创作阶段") not in CSTAGE:
  102. step["创作阶段"] = None
  103. if knowledge.get("role") == "组件" and (knowledge.get("parent") or {}).get("how_id") not in how_ids:
  104. knowledge["role"] = "主"
  105. knowledge["parent"] = None
  106. return knowledges
  107. def frame_and_clean(
  108. post: Post,
  109. read_text: str,
  110. *,
  111. contract: SkillContract | None = None,
  112. chat_json_fn: ChatJsonFn = default_chat_json,
  113. ) -> list[dict[str, Any]]:
  114. contract = contract or load_contract()
  115. knowledges = frame_knowledges(post, read_text, contract=contract, chat_json_fn=chat_json_fn)
  116. knowledges = fix_fake_hows(knowledges, contract=contract, chat_json_fn=chat_json_fn)
  117. knowledges = drop_fake_whys(knowledges, chat_json_fn=chat_json_fn)
  118. return normalize_knowledge_shapes(knowledges)
  119. def knowledge_debug_json(knowledges: list[dict[str, Any]]) -> str:
  120. return json.dumps(knowledges, ensure_ascii=False)