kd-engine 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. kd_engine-1.0.0.dist-info/METADATA +19 -0
  2. kd_engine-1.0.0.dist-info/RECORD +118 -0
  3. kd_engine-1.0.0.dist-info/WHEEL +5 -0
  4. kd_engine-1.0.0.dist-info/entry_points.txt +2 -0
  5. kd_engine-1.0.0.dist-info/licenses/LICENSE-MIT +21 -0
  6. kd_engine-1.0.0.dist-info/top_level.txt +1 -0
  7. ytk/__init__.py +2 -0
  8. ytk/__main__.py +21 -0
  9. ytk/batch/__init__.py +39 -0
  10. ytk/batch/runner.py +1149 -0
  11. ytk/cache/__init__.py +34 -0
  12. ytk/cache/v2.py +562 -0
  13. ytk/cleaning/density.py +40 -0
  14. ytk/cleaning/fillers.py +18 -0
  15. ytk/cleaning/sentence_boundary.py +30 -0
  16. ytk/cleaning/service.py +62 -0
  17. ytk/cli.py +1281 -0
  18. ytk/commands/__init__.py +3 -0
  19. ytk/commands/merge.py +600 -0
  20. ytk/config.py +757 -0
  21. ytk/dedup/service.py +166 -0
  22. ytk/doctor.py +891 -0
  23. ytk/dotenv.py +334 -0
  24. ytk/errors.py +38 -0
  25. ytk/extraction/service.py +451 -0
  26. ytk/extraction/strategies/base.py +16 -0
  27. ytk/extraction/strategies/code_block.py +285 -0
  28. ytk/extraction/strategies/heuristic.py +791 -0
  29. ytk/extraction/strategies/local_model.py +56 -0
  30. ytk/extraction/text_context.py +177 -0
  31. ytk/extraction/visual_context.py +159 -0
  32. ytk/ingestion/adapters/__init__.py +1 -0
  33. ytk/ingestion/adapters/_common.py +865 -0
  34. ytk/ingestion/adapters/base.py +160 -0
  35. ytk/ingestion/adapters/bilibili.py +271 -0
  36. ytk/ingestion/adapters/douyin.py +445 -0
  37. ytk/ingestion/adapters/tiktok.py +102 -0
  38. ytk/ingestion/adapters/xiaohongshu.py +758 -0
  39. ytk/ingestion/adapters/youtube.py +74 -0
  40. ytk/ingestion/browser_acquired.py +220 -0
  41. ytk/ingestion/http_client.py +43 -0
  42. ytk/ingestion/local_import.py +202 -0
  43. ytk/ingestion/media.py +468 -0
  44. ytk/ingestion/providers/asr.py +902 -0
  45. ytk/ingestion/providers/base.py +13 -0
  46. ytk/ingestion/providers/dtk_client.py +816 -0
  47. ytk/ingestion/providers/minimax.py +350 -0
  48. ytk/ingestion/providers/transcript_api.py +336 -0
  49. ytk/ingestion/providers/xhs_client.py +646 -0
  50. ytk/ingestion/providers/ytdlp.py +400 -0
  51. ytk/ingestion/registry.py +189 -0
  52. ytk/ingestion/resolver.py +417 -0
  53. ytk/ingestion/search.py +56 -0
  54. ytk/ingestion/service.py +167 -0
  55. ytk/ingestion/subtitles.py +602 -0
  56. ytk/ingestion/transcript_cache.py +212 -0
  57. ytk/ingestion/url_parser.py +53 -0
  58. ytk/ingestion/video_frames.py +292 -0
  59. ytk/ingestion/ytdlp_acquire.py +754 -0
  60. ytk/logging.py +383 -0
  61. ytk/models/common.py +20 -0
  62. ytk/models/content.py +451 -0
  63. ytk/models/knowledge.py +43 -0
  64. ytk/models/migrations.py +262 -0
  65. ytk/models/skill.py +13 -0
  66. ytk/models/source.py +265 -0
  67. ytk/models/topic.py +18 -0
  68. ytk/models/transcript.py +30 -0
  69. ytk/ocr/__init__.py +65 -0
  70. ytk/ocr/engine.py +245 -0
  71. ytk/ocr/layout.py +347 -0
  72. ytk/ocr/pipeline.py +440 -0
  73. ytk/ocr/rapidocr_engine.py +144 -0
  74. ytk/pipeline/acquisition_checkpoint.py +240 -0
  75. ytk/pipeline/artifacts.py +18 -0
  76. ytk/pipeline/orchestrator.py +598 -0
  77. ytk/pipeline/processing_identity.py +18 -0
  78. ytk/pipeline/retry.py +728 -0
  79. ytk/pipeline/runners.py +578 -0
  80. ytk/pipeline/state.py +1107 -0
  81. ytk/pipeline/v2.py +1863 -0
  82. ytk/py.typed +1 -0
  83. ytk/quality/__init__.py +9 -0
  84. ytk/quality/completeness.py +260 -0
  85. ytk/renderers/base.py +9 -0
  86. ytk/renderers/citation_alignment.py +617 -0
  87. ytk/renderers/evidence_formatter.py +362 -0
  88. ytk/renderers/json_renderer.py +13 -0
  89. ytk/renderers/jsonl_renderer.py +18 -0
  90. ytk/renderers/knowledge_doc_renderer.py +1984 -0
  91. ytk/renderers/markdown_renderer.py +540 -0
  92. ytk/renderers/obsidian_renderer.py +138 -0
  93. ytk/renderers/skill_package.py +21 -0
  94. ytk/renderers/source_links.py +309 -0
  95. ytk/renderers/summary_renderer.py +163 -0
  96. ytk/renderers/v2.py +171 -0
  97. ytk/renderers/v2_jsonl.py +165 -0
  98. ytk/renderers/v2_markdown.py +477 -0
  99. ytk/renderers/v2_obsidian.py +113 -0
  100. ytk/renderers/v2_summary.py +220 -0
  101. ytk/segmentation/change_points.py +50 -0
  102. ytk/segmentation/embeddings.py +45 -0
  103. ytk/segmentation/service.py +318 -0
  104. ytk/skill_builder/__init__.py +1 -0
  105. ytk/skill_builder/builder.py +419 -0
  106. ytk/skill_builder/templates/SKILL.md.j2 +85 -0
  107. ytk/skill_builder/templates/evals/evals.json.j2 +25 -0
  108. ytk/skill_builder/templates/examples/example-input.md.j2 +5 -0
  109. ytk/skill_builder/templates/examples/example-output.md.j2 +8 -0
  110. ytk/skill_builder/templates/references/commands.md.j2 +34 -0
  111. ytk/skill_builder/templates/references/concepts.md.j2 +22 -0
  112. ytk/skill_builder/templates/references/configs.md.j2 +31 -0
  113. ytk/skill_builder/templates/references/evidence-policy.md.j2 +27 -0
  114. ytk/skill_builder/templates/references/source-summary.md.j2 +24 -0
  115. ytk/skill_builder/templates/scripts/build_skill.py.j2 +14 -0
  116. ytk/skill_builder/templates/scripts/validate_output.py.j2 +136 -0
  117. ytk/skill_builder/templates.py +29 -0
  118. ytk/text_normalization.py +44 -0
ytk/dedup/service.py ADDED
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from collections.abc import Iterable
5
+
6
+ from ytk.models.content import KnowledgeAtomV2
7
+ from ytk.models.knowledge import KnowledgeAtom
8
+
9
+
10
+ def _normalize_content(text: str) -> str:
11
+ return re.sub(r"[^\w\s]", "", text.casefold().strip())
12
+
13
+
14
+ #: Negation markers. Two atoms whose content differs only by one of these must
15
+ #: never be merged (T41.1: "保留否定、条件和相反结论").
16
+ NEGATION_TOKENS: frozenset[str] = frozenset(
17
+ {
18
+ "not",
19
+ "no",
20
+ "never",
21
+ "none",
22
+ "without",
23
+ "avoid",
24
+ "cannot",
25
+ "cant",
26
+ "can't",
27
+ "dont",
28
+ "don't",
29
+ "doesnt",
30
+ "doesn't",
31
+ "isnt",
32
+ "isn't",
33
+ "arent",
34
+ "aren't",
35
+ "wont",
36
+ "won't",
37
+ "shouldnt",
38
+ "shouldn't",
39
+ "mustnt",
40
+ "mustn't",
41
+ "禁止",
42
+ "不要",
43
+ "不能",
44
+ "不得",
45
+ "无需",
46
+ "避免",
47
+ "不可",
48
+ "严禁",
49
+ }
50
+ )
51
+
52
+ #: Condition / precondition markers. "Do X" and "Do X only if Y" are
53
+ #: different conclusions even when the action text matches.
54
+ CONDITION_TOKENS: frozenset[str] = frozenset(
55
+ {
56
+ "if",
57
+ "unless",
58
+ "when",
59
+ "whenever",
60
+ "only",
61
+ "before",
62
+ "after",
63
+ "provided",
64
+ "otherwise",
65
+ "else",
66
+ "如果",
67
+ "只有",
68
+ "除非",
69
+ "当",
70
+ "之前",
71
+ "之后",
72
+ "否则",
73
+ "前提",
74
+ }
75
+ )
76
+
77
+ _WORD_RE = re.compile(r"[a-z][a-z'\u2019_-]*")
78
+ _CJK_RE = re.compile(r"[\u4e00-\u9fff]+")
79
+
80
+
81
+ def _qualifier_tokens(text: str, vocabulary: frozenset[str]) -> frozenset[str]:
82
+ """Tokens from ``vocabulary`` present in ``text`` (latin words + CJK runs)."""
83
+ lowered = str(text).casefold()
84
+ words = set(_WORD_RE.findall(lowered))
85
+ tokens = {entry for entry in vocabulary if entry in words}
86
+ cjk_entries = [entry for entry in vocabulary if _CJK_RE.search(entry)]
87
+ if cjk_entries:
88
+ runs = _CJK_RE.findall(lowered)
89
+ tokens.update(
90
+ entry for entry in cjk_entries if any(entry in run for run in runs)
91
+ )
92
+ return frozenset(tokens)
93
+
94
+
95
+ def _ordered_union(left: Iterable[str], right: Iterable[str]) -> list[str]:
96
+ merged: list[str] = []
97
+ for value in (*left, *right):
98
+ if value not in merged:
99
+ merged.append(value)
100
+ return merged
101
+
102
+
103
+ class DeduplicationService:
104
+ def __init__(
105
+ self,
106
+ embedding_client: object | None = None,
107
+ semantic_threshold: float = 0.85,
108
+ ) -> None:
109
+ self.embedding_client = embedding_client
110
+ self.semantic_threshold = semantic_threshold
111
+
112
+ def merge_v2(self, atoms: list[KnowledgeAtomV2]) -> list[KnowledgeAtomV2]:
113
+ """Merge v2 atoms by type + normalized content (+ qualifier signature).
114
+
115
+ A merge unions ``source_ids``/``evidence_ids``/``topic_path`` and keeps
116
+ the **maximum** confidence of the inputs. Confidence is never raised
117
+ because more sources carry the same claim (a repost is not evidence),
118
+ and atoms that differ in negation or condition never merge.
119
+ """
120
+ merged: dict[tuple[object, ...], KnowledgeAtomV2] = {}
121
+ for atom in atoms:
122
+ key: tuple[object, ...] = (
123
+ atom.atom_type,
124
+ _normalize_content(atom.content),
125
+ _qualifier_tokens(atom.content, NEGATION_TOKENS),
126
+ _qualifier_tokens(atom.content, CONDITION_TOKENS),
127
+ )
128
+ if atom.atom_type == "core_concept":
129
+ # Concept content is often only a name. Its definition carries
130
+ # the claim, including negation, conditions and numeric values.
131
+ key += (" ".join(atom.explanation.casefold().split()),)
132
+ current = merged.get(key)
133
+ if current is None:
134
+ merged[key] = atom
135
+ continue
136
+ merged[key] = current.model_copy(
137
+ update={
138
+ "source_ids": _ordered_union(current.source_ids, atom.source_ids),
139
+ "evidence_ids": _ordered_union(current.evidence_ids, atom.evidence_ids),
140
+ "topic_path": _ordered_union(current.topic_path, atom.topic_path),
141
+ # max of the *individual* judgments; not count-boosted
142
+ "confidence": max(current.confidence, atom.confidence),
143
+ "metadata": {**atom.metadata, **current.metadata},
144
+ }
145
+ )
146
+ return list(merged.values())
147
+
148
+ def merge(self, atoms: list[KnowledgeAtom]) -> list[KnowledgeAtom]:
149
+ merged: dict[tuple[str, ...], KnowledgeAtom] = {}
150
+ for atom in atoms:
151
+ key: tuple[str, ...] = (atom.atom_type, _normalize_content(atom.content))
152
+ if atom.atom_type == "core_concept":
153
+ key += (" ".join(atom.explanation.casefold().split()),)
154
+ if key not in merged:
155
+ merged[key] = atom
156
+ continue
157
+
158
+ current = merged[key]
159
+ merged[key] = current.model_copy(
160
+ update={
161
+ "timestamps": sorted(set(current.timestamps + atom.timestamps)),
162
+ "evidence": current.evidence + atom.evidence,
163
+ "confidence": max(current.confidence, atom.confidence),
164
+ }
165
+ )
166
+ return list(merged.values())