structverify 0.3.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 (168) hide show
  1. structverify/__init__.py +83 -0
  2. structverify/adaptation/__init__.py +0 -0
  3. structverify/adaptation/adapter_trainer.py +341 -0
  4. structverify/adaptation/feedback_store.py +31 -0
  5. structverify/adaptation/kosis_crawler.py +317 -0
  6. structverify/adaptation/sample_builder.py +149 -0
  7. structverify/adaptation/synthetic_generator.py +320 -0
  8. structverify/adaptation/update_embeddings.py +178 -0
  9. structverify/agent/__init__.py +21 -0
  10. structverify/agent/builder_agent.py +226 -0
  11. structverify/agent/conformance_agent.py +171 -0
  12. structverify/agent/dependency_planner.py +151 -0
  13. structverify/agent/indexing_agent.py +153 -0
  14. structverify/agent/indexing_planner.py +169 -0
  15. structverify/agent/integration_example.py +182 -0
  16. structverify/agent/loop.py +1165 -0
  17. structverify/agent/memory.py +207 -0
  18. structverify/agent/planner.py +817 -0
  19. structverify/agent/prompts/__init__.py +15 -0
  20. structverify/agent/prompts/planner_prompts.py +219 -0
  21. structverify/agent/prompts/reflect_prompts.py +387 -0
  22. structverify/agent/reflect.py +227 -0
  23. structverify/agent/runtime_agent.py +1272 -0
  24. structverify/agent/schemas.py +262 -0
  25. structverify/agent/source_profiler.py +229 -0
  26. structverify/agent/tools/__init__.py +64 -0
  27. structverify/agent/tools/base.py +222 -0
  28. structverify/agent/tools/calculate.py +244 -0
  29. structverify/agent/tools/catalog_search.py +859 -0
  30. structverify/agent/tools/deep_explore.py +293 -0
  31. structverify/agent/tools/explore_catalog.py +423 -0
  32. structverify/agent/tools/fetch_evidence.py +922 -0
  33. structverify/agent/tools/finish.py +423 -0
  34. structverify/agent/tools/meta_explore.py +267 -0
  35. structverify/agent/tools/query_rewriter.py +134 -0
  36. structverify/agent/tools/read_original.py +144 -0
  37. structverify/agent/tools/replan.py +365 -0
  38. structverify/agent/workspace.py +958 -0
  39. structverify/api.py +804 -0
  40. structverify/config/default.yaml +350 -0
  41. structverify/core/__init__.py +0 -0
  42. structverify/core/config_loader.py +30 -0
  43. structverify/core/pipeline.py +280 -0
  44. structverify/core/schemas.py +362 -0
  45. structverify/detection/__init__.py +26 -0
  46. structverify/detection/_config.py +163 -0
  47. structverify/detection/_llm.py +24 -0
  48. structverify/detection/candidate/__init__.py +1 -0
  49. structverify/detection/candidate/heuristic.py +60 -0
  50. structverify/detection/candidate/llm.py +51 -0
  51. structverify/detection/candidate_scorer.py +81 -0
  52. structverify/detection/claim_detector.py +164 -0
  53. structverify/detection/claims/__init__.py +1 -0
  54. structverify/detection/claims/worthiness.py +142 -0
  55. structverify/detection/domain/__init__.py +1 -0
  56. structverify/detection/domain/classify.py +84 -0
  57. structverify/detection/domain/preview.py +36 -0
  58. structverify/detection/domain/registry.py +99 -0
  59. structverify/detection/domain_classifier.py +75 -0
  60. structverify/detection/prompts/__init__.py +1 -0
  61. structverify/detection/prompts/candidate.py +38 -0
  62. structverify/detection/prompts/claim_worthiness.py +48 -0
  63. structverify/detection/prompts/domain.py +41 -0
  64. structverify/detection/prompts/schema.py +508 -0
  65. structverify/detection/prompts_loader.py +167 -0
  66. structverify/detection/schema/__init__.py +1 -0
  67. structverify/detection/schema/expand.py +83 -0
  68. structverify/detection/schema/induce.py +441 -0
  69. structverify/detection/schema/regenerate.py +162 -0
  70. structverify/detection/schema/temporal_hints.py +130 -0
  71. structverify/detection/schema/validate.py +193 -0
  72. structverify/detection/schema_inductor.py +112 -0
  73. structverify/detection/synthetic_generator.py +270 -0
  74. structverify/explanation/__init__.py +0 -0
  75. structverify/explanation/_config.py +18 -0
  76. structverify/explanation/_llm.py +25 -0
  77. structverify/explanation/explainer.py +183 -0
  78. structverify/explanation/fallback.py +29 -0
  79. structverify/explanation/formatters.py +75 -0
  80. structverify/explanation/prompts/__init__.py +1 -0
  81. structverify/explanation/prompts/match.py +27 -0
  82. structverify/explanation/prompts/mismatch.py +20 -0
  83. structverify/explanation/prompts/multihop.py +16 -0
  84. structverify/explanation/prompts/unverifiable.py +17 -0
  85. structverify/graph/__init__.py +0 -0
  86. structverify/graph/claim_graph.py +226 -0
  87. structverify/graph/document_graph.py +487 -0
  88. structverify/graph/graph_builder.py +238 -0
  89. structverify/graph/graph_multihop.py +335 -0
  90. structverify/graph/graph_store.py +281 -0
  91. structverify/graph/provenance.py +52 -0
  92. structverify/memory/__init__.py +44 -0
  93. structverify/memory/agent_memory.py +142 -0
  94. structverify/memory/embedder.py +69 -0
  95. structverify/memory/exemplar_store.py +241 -0
  96. structverify/memory/normalizer.py +91 -0
  97. structverify/memory/schema.py +119 -0
  98. structverify/memory/storage/__init__.py +29 -0
  99. structverify/memory/storage/jsonl_store.py +117 -0
  100. structverify/memory/working_memory.py +370 -0
  101. structverify/preprocessing/Dockerfile.scraper +27 -0
  102. structverify/preprocessing/__init__.py +0 -0
  103. structverify/preprocessing/extractor.py +574 -0
  104. structverify/preprocessing/pdf/__init__.py +16 -0
  105. structverify/preprocessing/pdf/fields.py +95 -0
  106. structverify/preprocessing/pdf/markdown.py +107 -0
  107. structverify/preprocessing/pdf/models.py +34 -0
  108. structverify/preprocessing/pdf/ocr.py +172 -0
  109. structverify/preprocessing/pdf/pipeline.py +74 -0
  110. structverify/preprocessing/pdf/reader.py +119 -0
  111. structverify/preprocessing/pdf/scoring.py +61 -0
  112. structverify/preprocessing/scraper_sandbox.py +561 -0
  113. structverify/preprocessing/segmenter.py +48 -0
  114. structverify/preprocessing/sir_builder.py +240 -0
  115. structverify/progress.py +591 -0
  116. structverify/retrieval/__init__.py +0 -0
  117. structverify/retrieval/base.py +208 -0
  118. structverify/retrieval/base_connector.py +85 -0
  119. structverify/retrieval/catalog_ranker.py +300 -0
  120. structverify/retrieval/catalog_search.py +583 -0
  121. structverify/retrieval/chunking.py +92 -0
  122. structverify/retrieval/custom_csv_source.py +386 -0
  123. structverify/retrieval/custom_db_source.py +396 -0
  124. structverify/retrieval/custom_docs_source.py +152 -0
  125. structverify/retrieval/dimension_resolver.py +281 -0
  126. structverify/retrieval/evidence_subgraph.py +63 -0
  127. structverify/retrieval/kosis_connector.py +1192 -0
  128. structverify/retrieval/kosis_relevance.py +142 -0
  129. structverify/retrieval/kosis_source.py +1541 -0
  130. structverify/retrieval/query_builder.py +72 -0
  131. structverify/retrieval/registry.py +133 -0
  132. structverify/retrieval/relevance_judge.py +141 -0
  133. structverify/retrieval/row_matcher.py +267 -0
  134. structverify/storage/__init__.py +0 -0
  135. structverify/storage/db_manager.py +157 -0
  136. structverify/storage/dwh_manager.py +92 -0
  137. structverify/storage/init_db.py +99 -0
  138. structverify/storage/raw_storage.py +29 -0
  139. structverify/training/__init__.py +26 -0
  140. structverify/training/curator.py +124 -0
  141. structverify/training/dataset.py +134 -0
  142. structverify/training/doctor.py +99 -0
  143. structverify/training/evalgate.py +96 -0
  144. structverify/training/generate.py +101 -0
  145. structverify/training/loop.py +116 -0
  146. structverify/training/recipe/train_mlx.py +99 -0
  147. structverify/training/recipe/train_qlora.py +104 -0
  148. structverify/training/tasks.py +79 -0
  149. structverify/utils/__init__.py +0 -0
  150. structverify/utils/embedding_client.py +248 -0
  151. structverify/utils/llm_client.py +809 -0
  152. structverify/utils/logger.py +81 -0
  153. structverify/verification/__init__.py +0 -0
  154. structverify/verification/_config.py +45 -0
  155. structverify/verification/adapters.py +405 -0
  156. structverify/verification/conformance.py +117 -0
  157. structverify/verification/decide_verdict.py +216 -0
  158. structverify/verification/decide_verdict_agent.py +454 -0
  159. structverify/verification/growth_diff.py +267 -0
  160. structverify/verification/row_match.py +345 -0
  161. structverify/verification/units.py +64 -0
  162. structverify/verification/verdict_thresholds.py +232 -0
  163. structverify/verification/verifier.py +84 -0
  164. structverify-0.3.0.dist-info/METADATA +903 -0
  165. structverify-0.3.0.dist-info/RECORD +168 -0
  166. structverify-0.3.0.dist-info/WHEEL +5 -0
  167. structverify-0.3.0.dist-info/licenses/LICENSE +21 -0
  168. structverify-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,167 @@
1
+ """detection/prompts_loader.py — domain-pack YAML 로드·few-shot 헬퍼.
2
+
3
+ domain_classifier.py의 _load_domain_pack()에서 분리 (로직 move-only).
4
+
5
+ [김예슬 - 2026-04-22] domain-packs/{domain}/prompts.yaml
6
+ TODO [김예슬]: claim_detector/candidate_scorer few-shot 주입은 #13 이후 연결
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from typing import Any
12
+
13
+ import yaml
14
+
15
+ from structverify.detection._config import domain_packs_dir as _domain_packs_dir
16
+ from structverify.utils.logger import get_logger
17
+
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ def domain_packs_dir(config: dict | None) -> str:
22
+ """config에서 domain-packs 루트 경로 (detection._config 위임)."""
23
+ return _domain_packs_dir(config)
24
+
25
+
26
+ def prompts_yaml_path(domain: str, config: dict | None) -> str:
27
+ """domain-packs/{domain}/prompts.yaml 절대/상대 경로."""
28
+ return os.path.join(domain_packs_dir(config), domain, "prompts.yaml")
29
+
30
+
31
+ def load_domain_pack(domain: str, config: dict | None = None) -> dict[str, Any] | None:
32
+ """
33
+ domain-packs/{domain}/prompts.yaml 로드.
34
+ 없으면 None 반환 (에러 아님).
35
+ """
36
+ yaml_path = prompts_yaml_path(domain, config)
37
+
38
+ if not os.path.exists(yaml_path):
39
+ logger.debug(f"Domain Pack 없음: {yaml_path}")
40
+ return None
41
+
42
+ try:
43
+ with open(yaml_path, encoding="utf-8") as f:
44
+ pack = yaml.safe_load(f)
45
+ logger.info(f"Domain Pack 로드: {yaml_path}")
46
+ if isinstance(pack, dict):
47
+ return pack
48
+ return None
49
+ except Exception as e:
50
+ logger.warning(f"Domain Pack 로드 실패: {yaml_path} — {e}")
51
+ return None
52
+
53
+
54
+ def load_domain_prompts(domain: str, config: dict | None = None) -> dict[str, Any] | None:
55
+ """load_domain_pack 별칭 — claim_detector TODO 명칭과 동일."""
56
+ return load_domain_pack(domain, config)
57
+
58
+
59
+ def few_shot_examples_from_pack(
60
+ pack: dict[str, Any] | None,
61
+ *,
62
+ section: str = "few_shot_examples",
63
+ ) -> list[Any]:
64
+ """
65
+ pack dict에서 few-shot 예시 리스트 추출.
66
+
67
+ section 키가 없으면 빈 리스트 (동작 변경 없이 주입 전 단계용).
68
+ """
69
+ if not pack:
70
+ return []
71
+ raw = pack.get(section)
72
+ if isinstance(raw, list):
73
+ return raw
74
+ return []
75
+
76
+
77
+ def format_few_shot_block(examples: list[Any]) -> str:
78
+ """
79
+ few-shot 예시를 프롬프트에 붙일 블록 문자열로 변환.
80
+
81
+ examples 원소: str 또는 {"input": ..., "output": ...} dict.
82
+ """
83
+ if not examples:
84
+ return ""
85
+ lines: list[str] = ["", "[도메인 few-shot 예시]"]
86
+ for i, ex in enumerate(examples, 1):
87
+ if isinstance(ex, str):
88
+ lines.append(f" {i}. {ex}")
89
+ elif isinstance(ex, dict):
90
+ inp = ex.get("input") or ex.get("sentence") or ""
91
+ out = ex.get("output") or ex.get("label") or ""
92
+ lines.append(f" {i}. input={inp!r} → {out!r}")
93
+ else:
94
+ lines.append(f" {i}. {ex!r}")
95
+ return "\n".join(lines)
96
+
97
+
98
+ def inject_few_shot(
99
+ base_prompt: str,
100
+ pack: dict[str, Any] | None,
101
+ *,
102
+ section: str = "few_shot_examples",
103
+ ) -> str:
104
+ """base_prompt 뒤에 pack의 few-shot 블록을 붙인다. pack 없으면 원문 그대로."""
105
+ block = format_few_shot_block(few_shot_examples_from_pack(pack, section=section))
106
+ if not block:
107
+ return base_prompt
108
+ return base_prompt.rstrip() + block + "\n"
109
+
110
+
111
+ def resolve_prompt_for_step(
112
+ base_prompt: str,
113
+ domain: str | None,
114
+ config: dict | None,
115
+ *,
116
+ step: str,
117
+ ) -> str:
118
+ """
119
+ domain-pack에서 step별 few-shot을 찾아 base_prompt에 붙인다.
120
+
121
+ 키 우선순위: {step}_few_shot → {step}_examples → {step} → few_shot_examples
122
+ pack/예시 없으면 base_prompt 그대로 (기존 동작 유지).
123
+ """
124
+ if not domain:
125
+ return base_prompt
126
+ pack = load_domain_pack(domain, config)
127
+ if not pack:
128
+ return base_prompt
129
+ for section in (f"{step}_few_shot", f"{step}_examples", step, "few_shot_examples"):
130
+ if few_shot_examples_from_pack(pack, section=section):
131
+ return inject_few_shot(base_prompt, pack, section=section)
132
+ return base_prompt
133
+
134
+
135
+ def resolve_step_prompt(
136
+ default_template: str,
137
+ fmt_kwargs: dict[str, Any],
138
+ domain: str | None,
139
+ config: dict | None,
140
+ *,
141
+ step: str,
142
+ ) -> str:
143
+ """step별 프롬프트 해석 — 도메인팩이 `{step}_prompt`로 *기본 정의 자체를 교체* 가능.
144
+
145
+ few-shot append(resolve_prompt_for_step)로는 base 프롬프트의 강한 기준을 못 이기는
146
+ 경우(예: 컴플라이언스 check-worthiness)를 위해, 도메인팩이 base 프롬프트를 통째로
147
+ 바꿀 수 있게 한다. 교체 없으면 default_template. 이후 few-shot 예시를 append.
148
+
149
+ default_template / 교체 template 은 fmt_kwargs로 .format() 된다 (예: {sentence}).
150
+ """
151
+ tmpl = default_template
152
+ # 1) config.advanced.prompt_overrides.{step} — 배포 전역(도메인 무관). 컴플라이언스 배포용.
153
+ _ov = ((config or {}).get("advanced", {}) or {}).get("prompt_overrides", {}) or {}
154
+ if _ov.get("enabled") and isinstance(_ov.get(step), str) and _ov[step].strip():
155
+ tmpl = _ov[step]
156
+ logger.info(f"[prompts] step={step} config override 프롬프트 적용")
157
+ # 2) 도메인팩 {step}_prompt — 도메인별. config override 없을 때만.
158
+ pack = load_domain_pack(domain, config) if domain else None
159
+ if tmpl is default_template and pack and isinstance(pack.get(f"{step}_prompt"), str):
160
+ tmpl = pack[f"{step}_prompt"]
161
+ logger.info(f"[prompts] step={step} 도메인({domain}) 프롬프트 교체 적용")
162
+ base = tmpl.format(**fmt_kwargs)
163
+ if pack:
164
+ for section in (f"{step}_few_shot", f"{step}_examples", step, "few_shot_examples"):
165
+ if few_shot_examples_from_pack(pack, section=section):
166
+ return inject_few_shot(base, pack, section=section)
167
+ return base
@@ -0,0 +1 @@
1
+ """detection/schema — Step 5 schema induction 로직 (LLM·검증·후처리)."""
@@ -0,0 +1,83 @@
1
+ """detection/schema/expand.py — induce_schemas 후처리 (dedup + claim 복제).
2
+
3
+ schema_inductor.py에서 분리 (로직 move-only, 동작 변경 없음).
4
+
5
+ [v6.13] 한 claim → 여러 ClaimSchema 시 claim 복제
6
+ [v6.17] value=null 중복 schema 제거
7
+ [2026-05-21] seen_keys dedup — agent loop 회귀 차단
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from uuid import uuid4
12
+
13
+ from structverify.core.schemas import Claim, ClaimSchema
14
+ from structverify.utils.logger import get_logger
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ def _dedup_null_schemas(schemas: list[ClaimSchema]) -> list[ClaimSchema]:
20
+ # [v6.17] value=null 중복 schema 제거
21
+ # LLM이 value를 못 채우고 indicator만 같은 빈 schema를 N개 만드는
22
+ # 경우만 정리. 단, population까지 같아야 진짜 중복으로 간주.
23
+ # ★ "동작구 10.6%, 성동구 8.9%"처럼 지역만 다른 정상 다중 수치는
24
+ # population이 다르므로 합쳐지지 않음 (이전엔 다 뭉개지던 버그).
25
+ # [2026-05-21] seen_keys로 통합 — 같은 (indicator, time, population) 키에
26
+ # *value 있는* schema가 이미 존재하면 *value=null* 후속 schema는 폐기.
27
+ # 효과: LLM이 base claim에 "합계출산율 0.79명" 정상 schema +
28
+ # "합계출산율 null" 빈 schema를 *함께* 출력하던 케이스에서, 빈 schema가
29
+ # 별도 sub-claim으로 살아남아 agent loop이 4 iter 돌다가
30
+ # "주장값=None명 vs KOSIS 0.8명" 으로 끝나던 회귀를 차단.
31
+ deduped: list[ClaimSchema] = []
32
+ seen_keys: set[tuple] = set()
33
+ for sch in schemas:
34
+ key = (
35
+ sch.indicator or "",
36
+ sch.time_period or "",
37
+ sch.population or "", # ★ population 추가 — 지역별 구분
38
+ )
39
+ if sch.value is None and key in seen_keys:
40
+ logger.info(
41
+ f" [중복 제거] value=null schema 폐기 — 같은 키의 "
42
+ f"value 있는 schema가 이미 존재 "
43
+ f"(indicator={sch.indicator}, time={sch.time_period}, "
44
+ f"population={sch.population})"
45
+ )
46
+ continue
47
+ seen_keys.add(key)
48
+ deduped.append(sch)
49
+ return deduped
50
+
51
+
52
+ def _expand_claims_from_schemas(
53
+ claim: Claim,
54
+ schemas: list[ClaimSchema],
55
+ ) -> list[Claim]:
56
+ """첫 schema는 원래 claim에 부착, 나머지는 claim 복제 후 부착."""
57
+ if not schemas:
58
+ return []
59
+
60
+ # 첫 schema는 원래 claim에 부착
61
+ claim.schema = schemas[0]
62
+ expanded: list[Claim] = [claim]
63
+ logger.info(
64
+ f"스키마 유도: {claim.sent_id} [1/{len(schemas)}] "
65
+ f"indicator={schemas[0].indicator}, value={schemas[0].value}, "
66
+ f"unit={schemas[0].unit}, time_period={schemas[0].time_period}, "
67
+ f"parent_path={schemas[0].parent_path}"
68
+ )
69
+
70
+ # 추가 schema들은 claim 복제 후 부착 (claim_id 새로 발급)
71
+ for i, sch in enumerate(schemas[1:], start=2):
72
+ cloned = claim.model_copy(update={
73
+ "claim_id": uuid4(),
74
+ "schema": sch,
75
+ })
76
+ expanded.append(cloned)
77
+ logger.info(
78
+ f"스키마 유도: {claim.sent_id} [{i}/{len(schemas)}] (복제) "
79
+ f"indicator={sch.indicator}, value={sch.value}, "
80
+ f"unit={sch.unit}, time_period={sch.time_period}, "
81
+ f"parent_path={sch.parent_path}"
82
+ )
83
+ return expanded