framing.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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 子集,body 里放各项;"
  40. "只有已展开成独立方法、判断框架或完整结构的构成项才单独拆 What;背后的原理/标准拆 Why;"
  41. "不要 how、不要组件颗,parent 一律 null。作用域字段留空 []。】\n\n"
  42. f"原标题:{knowledge.get('title', '')}\n{body}\n\n"
  43. "只输出 JSON:{\"knowledges\":[ ... 仅 what/why,见模板 ... ]}"
  44. )
  45. try:
  46. out = chat_json_fn(contract.phase1, user, timeout=120).get("knowledges") or []
  47. except Exception:
  48. out = []
  49. reshaped: list[dict[str, Any]] = []
  50. for idx, item in enumerate(out, 1):
  51. if item.get("type") == "how":
  52. continue
  53. item["id"] = f"{knowledge.get('id', 'k')}r{idx}"
  54. item["role"], item["parent"] = "主", None
  55. reshaped.append(item)
  56. return reshaped or [knowledge]
  57. def fix_fake_hows(
  58. knowledges: list[dict[str, Any]],
  59. *,
  60. contract: SkillContract | None = None,
  61. chat_json_fn: ChatJsonFn = default_chat_json,
  62. ) -> list[dict[str, Any]]:
  63. out: list[dict[str, Any]] = []
  64. for knowledge in knowledges:
  65. if knowledge.get("type") == "how" and len(knowledge.get("steps", [])) >= 2:
  66. gate = how_gate(knowledge, chat_json_fn=chat_json_fn)
  67. if not gate.passed:
  68. reshaped = reshape_nonhow(knowledge, contract=contract, chat_json_fn=chat_json_fn)
  69. for item in reshaped:
  70. item.setdefault("metadata", {})["how_gate_reason"] = gate.reason
  71. out.extend(reshaped)
  72. continue
  73. out.append(knowledge)
  74. return out
  75. def drop_fake_whys(
  76. knowledges: list[dict[str, Any]],
  77. *,
  78. chat_json_fn: ChatJsonFn = default_chat_json,
  79. ) -> list[dict[str, Any]]:
  80. out: list[dict[str, Any]] = []
  81. for knowledge in knowledges:
  82. if knowledge.get("type") == "why":
  83. gate = why_refute_gate(knowledge, chat_json_fn=chat_json_fn)
  84. if not gate.passed:
  85. verdict = gate.details.get("verdict")
  86. if verdict == "废话":
  87. continue
  88. if verdict in {"实为what", "实为how"}:
  89. knowledge["inferred"] = True
  90. knowledge["inferred_reason"] = f"why闸疑似{verdict}:{gate.reason}"
  91. out.append(knowledge)
  92. return out
  93. def normalize_knowledge_shapes(knowledges: list[dict[str, Any]]) -> list[dict[str, Any]]:
  94. how_ids = {k.get("id") for k in knowledges if k.get("type") == "how"}
  95. for knowledge in knowledges:
  96. knowledge["业务阶段"] = [
  97. stage for stage in (knowledge.get("业务阶段") or []) if stage in BUSINESS_STAGES
  98. ]
  99. if knowledge.get("type") == "what" and knowledge.get("kind") not in KINDS:
  100. knowledge["kind"] = KIND_FIX.get((knowledge.get("kind") or "").strip(), knowledge.get("kind"))
  101. for step in knowledge.get("steps", []):
  102. if step.get("创作阶段") not in CSTAGE:
  103. step["创作阶段"] = None
  104. if knowledge.get("role") == "组件" and (knowledge.get("parent") or {}).get("how_id") not in how_ids:
  105. knowledge["role"] = "主"
  106. knowledge["parent"] = None
  107. return knowledges
  108. def frame_and_clean(
  109. post: Post,
  110. read_text: str,
  111. *,
  112. contract: SkillContract | None = None,
  113. chat_json_fn: ChatJsonFn = default_chat_json,
  114. ) -> list[dict[str, Any]]:
  115. contract = contract or load_contract()
  116. knowledges = frame_knowledges(post, read_text, contract=contract, chat_json_fn=chat_json_fn)
  117. knowledges = fix_fake_hows(knowledges, contract=contract, chat_json_fn=chat_json_fn)
  118. knowledges = drop_fake_whys(knowledges, chat_json_fn=chat_json_fn)
  119. return normalize_knowledge_shapes(knowledges)
  120. def knowledge_debug_json(knowledges: list[dict[str, Any]]) -> str:
  121. return json.dumps(knowledges, ensure_ascii=False)