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.
- structverify/__init__.py +83 -0
- structverify/adaptation/__init__.py +0 -0
- structverify/adaptation/adapter_trainer.py +341 -0
- structverify/adaptation/feedback_store.py +31 -0
- structverify/adaptation/kosis_crawler.py +317 -0
- structverify/adaptation/sample_builder.py +149 -0
- structverify/adaptation/synthetic_generator.py +320 -0
- structverify/adaptation/update_embeddings.py +178 -0
- structverify/agent/__init__.py +21 -0
- structverify/agent/builder_agent.py +226 -0
- structverify/agent/conformance_agent.py +171 -0
- structverify/agent/dependency_planner.py +151 -0
- structverify/agent/indexing_agent.py +153 -0
- structverify/agent/indexing_planner.py +169 -0
- structverify/agent/integration_example.py +182 -0
- structverify/agent/loop.py +1165 -0
- structverify/agent/memory.py +207 -0
- structverify/agent/planner.py +817 -0
- structverify/agent/prompts/__init__.py +15 -0
- structverify/agent/prompts/planner_prompts.py +219 -0
- structverify/agent/prompts/reflect_prompts.py +387 -0
- structverify/agent/reflect.py +227 -0
- structverify/agent/runtime_agent.py +1272 -0
- structverify/agent/schemas.py +262 -0
- structverify/agent/source_profiler.py +229 -0
- structverify/agent/tools/__init__.py +64 -0
- structverify/agent/tools/base.py +222 -0
- structverify/agent/tools/calculate.py +244 -0
- structverify/agent/tools/catalog_search.py +859 -0
- structverify/agent/tools/deep_explore.py +293 -0
- structverify/agent/tools/explore_catalog.py +423 -0
- structverify/agent/tools/fetch_evidence.py +922 -0
- structverify/agent/tools/finish.py +423 -0
- structverify/agent/tools/meta_explore.py +267 -0
- structverify/agent/tools/query_rewriter.py +134 -0
- structverify/agent/tools/read_original.py +144 -0
- structverify/agent/tools/replan.py +365 -0
- structverify/agent/workspace.py +958 -0
- structverify/api.py +804 -0
- structverify/config/default.yaml +350 -0
- structverify/core/__init__.py +0 -0
- structverify/core/config_loader.py +30 -0
- structverify/core/pipeline.py +280 -0
- structverify/core/schemas.py +362 -0
- structverify/detection/__init__.py +26 -0
- structverify/detection/_config.py +163 -0
- structverify/detection/_llm.py +24 -0
- structverify/detection/candidate/__init__.py +1 -0
- structverify/detection/candidate/heuristic.py +60 -0
- structverify/detection/candidate/llm.py +51 -0
- structverify/detection/candidate_scorer.py +81 -0
- structverify/detection/claim_detector.py +164 -0
- structverify/detection/claims/__init__.py +1 -0
- structverify/detection/claims/worthiness.py +142 -0
- structverify/detection/domain/__init__.py +1 -0
- structverify/detection/domain/classify.py +84 -0
- structverify/detection/domain/preview.py +36 -0
- structverify/detection/domain/registry.py +99 -0
- structverify/detection/domain_classifier.py +75 -0
- structverify/detection/prompts/__init__.py +1 -0
- structverify/detection/prompts/candidate.py +38 -0
- structverify/detection/prompts/claim_worthiness.py +48 -0
- structverify/detection/prompts/domain.py +41 -0
- structverify/detection/prompts/schema.py +508 -0
- structverify/detection/prompts_loader.py +167 -0
- structverify/detection/schema/__init__.py +1 -0
- structverify/detection/schema/expand.py +83 -0
- structverify/detection/schema/induce.py +441 -0
- structverify/detection/schema/regenerate.py +162 -0
- structverify/detection/schema/temporal_hints.py +130 -0
- structverify/detection/schema/validate.py +193 -0
- structverify/detection/schema_inductor.py +112 -0
- structverify/detection/synthetic_generator.py +270 -0
- structverify/explanation/__init__.py +0 -0
- structverify/explanation/_config.py +18 -0
- structverify/explanation/_llm.py +25 -0
- structverify/explanation/explainer.py +183 -0
- structverify/explanation/fallback.py +29 -0
- structverify/explanation/formatters.py +75 -0
- structverify/explanation/prompts/__init__.py +1 -0
- structverify/explanation/prompts/match.py +27 -0
- structverify/explanation/prompts/mismatch.py +20 -0
- structverify/explanation/prompts/multihop.py +16 -0
- structverify/explanation/prompts/unverifiable.py +17 -0
- structverify/graph/__init__.py +0 -0
- structverify/graph/claim_graph.py +226 -0
- structverify/graph/document_graph.py +487 -0
- structverify/graph/graph_builder.py +238 -0
- structverify/graph/graph_multihop.py +335 -0
- structverify/graph/graph_store.py +281 -0
- structverify/graph/provenance.py +52 -0
- structverify/memory/__init__.py +44 -0
- structverify/memory/agent_memory.py +142 -0
- structverify/memory/embedder.py +69 -0
- structverify/memory/exemplar_store.py +241 -0
- structverify/memory/normalizer.py +91 -0
- structverify/memory/schema.py +119 -0
- structverify/memory/storage/__init__.py +29 -0
- structverify/memory/storage/jsonl_store.py +117 -0
- structverify/memory/working_memory.py +370 -0
- structverify/preprocessing/Dockerfile.scraper +27 -0
- structverify/preprocessing/__init__.py +0 -0
- structverify/preprocessing/extractor.py +574 -0
- structverify/preprocessing/pdf/__init__.py +16 -0
- structverify/preprocessing/pdf/fields.py +95 -0
- structverify/preprocessing/pdf/markdown.py +107 -0
- structverify/preprocessing/pdf/models.py +34 -0
- structverify/preprocessing/pdf/ocr.py +172 -0
- structverify/preprocessing/pdf/pipeline.py +74 -0
- structverify/preprocessing/pdf/reader.py +119 -0
- structverify/preprocessing/pdf/scoring.py +61 -0
- structverify/preprocessing/scraper_sandbox.py +561 -0
- structverify/preprocessing/segmenter.py +48 -0
- structverify/preprocessing/sir_builder.py +240 -0
- structverify/progress.py +591 -0
- structverify/retrieval/__init__.py +0 -0
- structverify/retrieval/base.py +208 -0
- structverify/retrieval/base_connector.py +85 -0
- structverify/retrieval/catalog_ranker.py +300 -0
- structverify/retrieval/catalog_search.py +583 -0
- structverify/retrieval/chunking.py +92 -0
- structverify/retrieval/custom_csv_source.py +386 -0
- structverify/retrieval/custom_db_source.py +396 -0
- structverify/retrieval/custom_docs_source.py +152 -0
- structverify/retrieval/dimension_resolver.py +281 -0
- structverify/retrieval/evidence_subgraph.py +63 -0
- structverify/retrieval/kosis_connector.py +1192 -0
- structverify/retrieval/kosis_relevance.py +142 -0
- structverify/retrieval/kosis_source.py +1541 -0
- structverify/retrieval/query_builder.py +72 -0
- structverify/retrieval/registry.py +133 -0
- structverify/retrieval/relevance_judge.py +141 -0
- structverify/retrieval/row_matcher.py +267 -0
- structverify/storage/__init__.py +0 -0
- structverify/storage/db_manager.py +157 -0
- structverify/storage/dwh_manager.py +92 -0
- structverify/storage/init_db.py +99 -0
- structverify/storage/raw_storage.py +29 -0
- structverify/training/__init__.py +26 -0
- structverify/training/curator.py +124 -0
- structverify/training/dataset.py +134 -0
- structverify/training/doctor.py +99 -0
- structverify/training/evalgate.py +96 -0
- structverify/training/generate.py +101 -0
- structverify/training/loop.py +116 -0
- structverify/training/recipe/train_mlx.py +99 -0
- structverify/training/recipe/train_qlora.py +104 -0
- structverify/training/tasks.py +79 -0
- structverify/utils/__init__.py +0 -0
- structverify/utils/embedding_client.py +248 -0
- structverify/utils/llm_client.py +809 -0
- structverify/utils/logger.py +81 -0
- structverify/verification/__init__.py +0 -0
- structverify/verification/_config.py +45 -0
- structverify/verification/adapters.py +405 -0
- structverify/verification/conformance.py +117 -0
- structverify/verification/decide_verdict.py +216 -0
- structverify/verification/decide_verdict_agent.py +454 -0
- structverify/verification/growth_diff.py +267 -0
- structverify/verification/row_match.py +345 -0
- structverify/verification/units.py +64 -0
- structverify/verification/verdict_thresholds.py +232 -0
- structverify/verification/verifier.py +84 -0
- structverify-0.3.0.dist-info/METADATA +903 -0
- structverify-0.3.0.dist-info/RECORD +168 -0
- structverify-0.3.0.dist-info/WHEEL +5 -0
- structverify-0.3.0.dist-info/licenses/LICENSE +21 -0
- structverify-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# [리팩] explainer.UNVERIFIABLE_PROMPT → prompts/unverifiable.py
|
|
2
|
+
UNVERIFIABLE_PROMPT = """당신은 팩트체크 전문 작가입니다.
|
|
3
|
+
아래 검증 결과를 독자가 이해하기 쉽게 한국어로 설명하세요.
|
|
4
|
+
|
|
5
|
+
[판정: 검증 불가 (UNVERIFIABLE) — 기준 데이터를 찾지 못했습니다.]
|
|
6
|
+
주장: "{claim_text}"
|
|
7
|
+
검증 불가 이유: {reason}
|
|
8
|
+
시도한 통계표: {stat_source}
|
|
9
|
+
시도한 검색어: {search_hint}
|
|
10
|
+
|
|
11
|
+
[작성 규칙]
|
|
12
|
+
- 2~3문장으로 작성
|
|
13
|
+
- "사실입니다" 또는 "사실이 아닙니다"라고 단정하지 마세요
|
|
14
|
+
- 왜 공식 통계를 찾지 못했는지만 설명
|
|
15
|
+
- 독자가 직접 출처(기준 데이터)에서 확인할 방법 제시
|
|
16
|
+
- 수치를 새로 만들어내지 마세요. 위에 명시된 수치만 사용.
|
|
17
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""
|
|
2
|
+
graph/claim_graph.py — ClaimGraph facade (멀티홉 traversal API)
|
|
3
|
+
|
|
4
|
+
[설계 의도]
|
|
5
|
+
graph_builder가 만든 노드/엣지 list를 다운스트림(schema_inductor, verifier,
|
|
6
|
+
explainer)이 쉽게 traverse할 수 있도록 감싸는 lookup 객체.
|
|
7
|
+
|
|
8
|
+
다운스트림은 그래프 내부 구조(어떤 엣지가 어디서 나가는지)를 몰라도
|
|
9
|
+
high-level API만 호출하면 멀티홉 결과를 얻는다.
|
|
10
|
+
|
|
11
|
+
예:
|
|
12
|
+
graph = ClaimGraph(nodes, edges)
|
|
13
|
+
resolved = graph.resolve_time_for_claim(claim)
|
|
14
|
+
# 내부적으로:
|
|
15
|
+
# Claim → HAS_TEMPORAL → TemporalExpr → RESOLVES_TO → ResolvedTime
|
|
16
|
+
# 또는
|
|
17
|
+
# Claim → IN_BLOCK → Sentence → HAS_TEMPORAL → TemporalExpr → RESOLVES_TO
|
|
18
|
+
|
|
19
|
+
이 facade가 있어야 verifier/schema_inductor가 그래프를 1급 입력으로 받게 된다.
|
|
20
|
+
지금 코드의 가장 큰 문제(그래프를 만들어놓고 안 읽음)를 해결하는 핵심 인터페이스.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
from typing import Iterable
|
|
26
|
+
|
|
27
|
+
from structverify.core.schemas import (
|
|
28
|
+
Claim, GraphEdge, GraphEdgeType, GraphNode, GraphNodeType,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ClaimGraph:
|
|
33
|
+
"""
|
|
34
|
+
Claim/Document/Temporal 그래프 위에서 멀티홉 쿼리를 수행하는 facade.
|
|
35
|
+
|
|
36
|
+
인스턴스 하나가 한 문서에 대응. 노드/엣지는 frozen 상태로 가정.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, nodes: Iterable[GraphNode], edges: Iterable[GraphEdge]):
|
|
40
|
+
self._nodes: dict[str, GraphNode] = {n.node_id: n for n in nodes}
|
|
41
|
+
|
|
42
|
+
# 인접 리스트: from_node → [(edge_type, to_node), ...]
|
|
43
|
+
self._out: dict[str, list[tuple[GraphEdgeType, str]]] = defaultdict(list)
|
|
44
|
+
# 역방향: to_node → [(edge_type, from_node), ...]
|
|
45
|
+
self._in: dict[str, list[tuple[GraphEdgeType, str]]] = defaultdict(list)
|
|
46
|
+
|
|
47
|
+
for e in edges:
|
|
48
|
+
self._out[e.from_node].append((e.edge_type, e.to_node))
|
|
49
|
+
self._in[e.to_node].append((e.edge_type, e.from_node))
|
|
50
|
+
|
|
51
|
+
# ── 기본 lookup ─────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def get_node(self, node_id: str) -> GraphNode | None:
|
|
54
|
+
return self._nodes.get(node_id)
|
|
55
|
+
|
|
56
|
+
def neighbors(
|
|
57
|
+
self,
|
|
58
|
+
node_id: str,
|
|
59
|
+
edge_type: GraphEdgeType | None = None,
|
|
60
|
+
) -> list[str]:
|
|
61
|
+
"""node_id의 outgoing 이웃. edge_type 필터 옵션."""
|
|
62
|
+
out = self._out.get(node_id, [])
|
|
63
|
+
if edge_type is None:
|
|
64
|
+
return [t for _, t in out]
|
|
65
|
+
return [t for et, t in out if et == edge_type]
|
|
66
|
+
|
|
67
|
+
def in_neighbors(
|
|
68
|
+
self,
|
|
69
|
+
node_id: str,
|
|
70
|
+
edge_type: GraphEdgeType | None = None,
|
|
71
|
+
) -> list[str]:
|
|
72
|
+
"""node_id의 incoming 이웃."""
|
|
73
|
+
ins = self._in.get(node_id, [])
|
|
74
|
+
if edge_type is None:
|
|
75
|
+
return [f for _, f in ins]
|
|
76
|
+
return [f for et, f in ins if et == edge_type]
|
|
77
|
+
|
|
78
|
+
# ── 문서 레벨 ───────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
def find_document(self) -> GraphNode | None:
|
|
81
|
+
"""문서 노드 찾기. 문서당 하나만 있다고 가정."""
|
|
82
|
+
for n in self._nodes.values():
|
|
83
|
+
if n.node_type == GraphNodeType.DOCUMENT:
|
|
84
|
+
return n
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def get_anchor_year(self) -> int | None:
|
|
88
|
+
"""문서의 anchor_year 속성. 없으면 None."""
|
|
89
|
+
doc = self.find_document()
|
|
90
|
+
if not doc:
|
|
91
|
+
return None
|
|
92
|
+
return doc.properties.get("anchor_year")
|
|
93
|
+
|
|
94
|
+
# ── 시간 해소 (멀티홉 핵심) ─────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
def resolve_time_for_sentence(self, sent_anchor_id: str) -> str | None:
|
|
97
|
+
"""
|
|
98
|
+
문장 노드에 연결된 TemporalExpr 중 가장 먼저 발견되는 resolved 값 반환.
|
|
99
|
+
|
|
100
|
+
Traversal:
|
|
101
|
+
SentenceNode ─HAS_TEMPORAL→ TemporalExpr ─RESOLVES_TO→ ResolvedTime
|
|
102
|
+
|
|
103
|
+
여러 표현이 있으면 첫 번째 것 사용 (LLM이 추출한 순서).
|
|
104
|
+
"""
|
|
105
|
+
for te_id in self.neighbors(sent_anchor_id, GraphEdgeType.HAS_TEMPORAL):
|
|
106
|
+
resolved = self._resolved_value_of(te_id)
|
|
107
|
+
if resolved:
|
|
108
|
+
return resolved
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def resolve_time_for_claim(self, claim: Claim) -> str | None:
|
|
112
|
+
"""
|
|
113
|
+
Claim의 시점 해소.
|
|
114
|
+
|
|
115
|
+
Step 1: claim의 sent_id에 연결된 TemporalExpr 우선 사용
|
|
116
|
+
Step 2: 그게 REFERS_TO로 다른 문장을 가리키면 그 문장으로 다시 traverse
|
|
117
|
+
"""
|
|
118
|
+
sent_anchor = self._claim_to_sent_anchor(claim)
|
|
119
|
+
if not sent_anchor:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
# 1차: 직접 연결된 TemporalExpr
|
|
123
|
+
for te_id in self.neighbors(sent_anchor, GraphEdgeType.HAS_TEMPORAL):
|
|
124
|
+
# REFERS_TO가 있으면 참조 대상 문장의 시점도 시도
|
|
125
|
+
ref_sent = self._first_neighbor(te_id, GraphEdgeType.REFERS_TO)
|
|
126
|
+
|
|
127
|
+
# 우선 자체 resolved 시도
|
|
128
|
+
direct = self._resolved_value_of(te_id)
|
|
129
|
+
if direct:
|
|
130
|
+
return direct
|
|
131
|
+
|
|
132
|
+
# 자체로 안 풀리면 참조 문장으로 hop
|
|
133
|
+
if ref_sent:
|
|
134
|
+
ref_resolved = self.resolve_time_for_sentence(ref_sent)
|
|
135
|
+
if ref_resolved:
|
|
136
|
+
return ref_resolved
|
|
137
|
+
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
def count_temporal_expressions(self, claim: Claim) -> int:
|
|
141
|
+
"""[v6.19] claim의 문장에 달린 TemporalExpr 개수.
|
|
142
|
+
|
|
143
|
+
한 문장에 "작년"+"재작년"처럼 시간표현이 여러 개면,
|
|
144
|
+
temporal_provenance가 어느 것이 이 claim에 맞는지 구분할 수
|
|
145
|
+
없다(첫 번째를 무조건 반환). 이 경우 schema_inductor가
|
|
146
|
+
단정적 hint 대신 anchor 변환표만 받아 LLM이 claim 문맥으로
|
|
147
|
+
직접 고르게 해야 한다.
|
|
148
|
+
"""
|
|
149
|
+
sent_anchor = self._claim_to_sent_anchor(claim)
|
|
150
|
+
if not sent_anchor:
|
|
151
|
+
return 0
|
|
152
|
+
return len(self.neighbors(sent_anchor, GraphEdgeType.HAS_TEMPORAL))
|
|
153
|
+
|
|
154
|
+
def temporal_provenance(self, claim: Claim) -> dict | None:
|
|
155
|
+
"""
|
|
156
|
+
시점 해소의 근거 (디버깅/설명용).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
{
|
|
160
|
+
"expression": "재작년 같은 기간",
|
|
161
|
+
"resolved": "2022-01..2022-11",
|
|
162
|
+
"basis": "anchor_year - 2 + s0009의 기간 참조",
|
|
163
|
+
"via_coref": "node:s0009",
|
|
164
|
+
}
|
|
165
|
+
"""
|
|
166
|
+
sent_anchor = self._claim_to_sent_anchor(claim)
|
|
167
|
+
if not sent_anchor:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
for te_id in self.neighbors(sent_anchor, GraphEdgeType.HAS_TEMPORAL):
|
|
171
|
+
te_node = self._nodes.get(te_id)
|
|
172
|
+
if not te_node:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
resolved = self._resolved_value_of(te_id)
|
|
176
|
+
ref_sent = self._first_neighbor(te_id, GraphEdgeType.REFERS_TO)
|
|
177
|
+
|
|
178
|
+
if not resolved and ref_sent:
|
|
179
|
+
resolved = self.resolve_time_for_sentence(ref_sent)
|
|
180
|
+
|
|
181
|
+
if resolved:
|
|
182
|
+
return {
|
|
183
|
+
"expression": te_node.properties.get("expression"),
|
|
184
|
+
"resolved": resolved,
|
|
185
|
+
"basis": te_node.properties.get("resolution_basis"),
|
|
186
|
+
"via_coref": ref_sent,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
# ── 내부 traversal ──────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def _resolved_value_of(self, temporal_expr_node_id: str) -> str | None:
|
|
194
|
+
"""TemporalExpr 노드에서 RESOLVES_TO로 연결된 ResolvedTime의 value."""
|
|
195
|
+
for rt_id in self.neighbors(temporal_expr_node_id, GraphEdgeType.RESOLVES_TO):
|
|
196
|
+
rt_node = self._nodes.get(rt_id)
|
|
197
|
+
if rt_node:
|
|
198
|
+
return rt_node.properties.get("value") or rt_node.label
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
def _first_neighbor(
|
|
202
|
+
self, node_id: str, edge_type: GraphEdgeType,
|
|
203
|
+
) -> str | None:
|
|
204
|
+
nbrs = self.neighbors(node_id, edge_type)
|
|
205
|
+
return nbrs[0] if nbrs else None
|
|
206
|
+
|
|
207
|
+
def _claim_to_sent_anchor(self, claim: Claim) -> str | None:
|
|
208
|
+
"""Claim → 문장 anchor id."""
|
|
209
|
+
return claim.graph_anchor_id
|
|
210
|
+
|
|
211
|
+
# ── 디버깅 ──────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
def stats(self) -> dict:
|
|
214
|
+
return {
|
|
215
|
+
"nodes": len(self._nodes),
|
|
216
|
+
"edges": sum(len(v) for v in self._out.values()),
|
|
217
|
+
"node_types": self._count_by("node_type"),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
def _count_by(self, attr: str) -> dict:
|
|
221
|
+
out: dict = defaultdict(int)
|
|
222
|
+
for n in self._nodes.values():
|
|
223
|
+
v = getattr(n, attr, None)
|
|
224
|
+
key = v.value if hasattr(v, "value") else str(v)
|
|
225
|
+
out[key] += 1
|
|
226
|
+
return dict(out)
|