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,227 @@
|
|
|
1
|
+
"""structverify.agent.reflect — Reflect Agent (Phase E).
|
|
2
|
+
|
|
3
|
+
ReAct 패턴의 *Reflect* 단계:
|
|
4
|
+
매 iter 시작 시 LLM이 last_observation 보고 *다음 action 동적 결정*.
|
|
5
|
+
|
|
6
|
+
Phase D의 deterministic plan 따르기 → Phase E의 LLM-driven 결정.
|
|
7
|
+
룰베이스 (_select_best_row, _infer_claim_type 등)는 *fallback only*.
|
|
8
|
+
|
|
9
|
+
사용:
|
|
10
|
+
from structverify.agent.reflect import ReflectAgent
|
|
11
|
+
|
|
12
|
+
reflect = ReflectAgent(llm_call=my_llm_call, config={...})
|
|
13
|
+
verdict = await agent_loop(
|
|
14
|
+
plan=plan, claim=claim, workspace=ws, datasources=ds,
|
|
15
|
+
reflect_fn=reflect, # callable, agent_loop이 매 iter 호출
|
|
16
|
+
loop_config=LoopConfig(mode="reflect"),
|
|
17
|
+
)
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import re
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import Any, Awaitable, Callable
|
|
25
|
+
|
|
26
|
+
from structverify.utils.logger import get_logger
|
|
27
|
+
from .schemas import ActionType, Observation, Plan, ReflectDecision, VerdictType
|
|
28
|
+
from .prompts.reflect_prompts import build_reflect_prompt
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ── JSON 추출 (planner와 동일 패턴) ──────────────────────────────
|
|
34
|
+
|
|
35
|
+
_JSON_FENCE_RE = re.compile(r"```(?:json)?\s*\n?(.*?)\n?```", re.DOTALL)
|
|
36
|
+
_JSON_OBJ_RE = re.compile(r"\{.*\}", re.DOTALL)
|
|
37
|
+
|
|
38
|
+
# JSON 표준엔 주석 없는데 LLM이 자주 박음 — 파싱 전 제거
|
|
39
|
+
_LINE_COMMENT_RE = re.compile(r"//[^\n\r]*")
|
|
40
|
+
_BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.DOTALL)
|
|
41
|
+
# trailing comma도 흔함
|
|
42
|
+
_TRAILING_COMMA_RE = re.compile(r",\s*([}\]])")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _clean_json_text(text: str) -> str:
|
|
46
|
+
"""LLM 응답에서 JSON 표준 위반 흔한 패턴 제거."""
|
|
47
|
+
text = _BLOCK_COMMENT_RE.sub("", text)
|
|
48
|
+
text = _LINE_COMMENT_RE.sub("", text)
|
|
49
|
+
text = _TRAILING_COMMA_RE.sub(r"\1", text)
|
|
50
|
+
return text
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _extract_json(text: str) -> dict | None:
|
|
54
|
+
"""LLM 응답에서 JSON object 추출. fenced ``` 블록 우선, 그 다음 첫 { ... }."""
|
|
55
|
+
if not text:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
# 1. fenced code block
|
|
59
|
+
m = _JSON_FENCE_RE.search(text)
|
|
60
|
+
if m:
|
|
61
|
+
body = _clean_json_text(m.group(1).strip())
|
|
62
|
+
try:
|
|
63
|
+
return json.loads(body)
|
|
64
|
+
except json.JSONDecodeError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
# 2. 첫 { ... } 매칭
|
|
68
|
+
m = _JSON_OBJ_RE.search(text)
|
|
69
|
+
if m:
|
|
70
|
+
body = _clean_json_text(m.group(0))
|
|
71
|
+
try:
|
|
72
|
+
return json.loads(body)
|
|
73
|
+
except json.JSONDecodeError:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# 3. 전체 text
|
|
77
|
+
try:
|
|
78
|
+
return json.loads(_clean_json_text(text.strip()))
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _parse_reflect_decision(response_text: str) -> ReflectDecision | None:
|
|
84
|
+
"""LLM 응답 → ReflectDecision. 실패 시 None (호출자가 fallback)."""
|
|
85
|
+
data = _extract_json(response_text)
|
|
86
|
+
if not data or not isinstance(data, dict):
|
|
87
|
+
logger.warning(f"[reflect] JSON 추출 실패. 응답 일부: {response_text[:300]!r}")
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
# action 파싱
|
|
91
|
+
raw_action = (data.get("action") or "").strip().lower()
|
|
92
|
+
action_map = {
|
|
93
|
+
"catalog_search": ActionType.CATALOG_SEARCH,
|
|
94
|
+
"explore_catalog": ActionType.EXPLORE_CATALOG,
|
|
95
|
+
"fetch_evidence": ActionType.FETCH_EVIDENCE,
|
|
96
|
+
"read_original": ActionType.READ_ORIGINAL,
|
|
97
|
+
"calculate": ActionType.CALCULATE,
|
|
98
|
+
"replan": ActionType.REPLAN,
|
|
99
|
+
"finish": ActionType.FINISH,
|
|
100
|
+
}
|
|
101
|
+
action = action_map.get(raw_action)
|
|
102
|
+
if action is None:
|
|
103
|
+
logger.warning(f"[reflect] 알 수 없는 action: {raw_action!r}")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# input
|
|
107
|
+
inp = data.get("input") or {}
|
|
108
|
+
if not isinstance(inp, dict):
|
|
109
|
+
logger.warning(f"[reflect] input이 dict 아님: {type(inp).__name__}")
|
|
110
|
+
inp = {}
|
|
111
|
+
|
|
112
|
+
# confidence
|
|
113
|
+
try:
|
|
114
|
+
conf = float(data.get("confidence_so_far", 0.0))
|
|
115
|
+
except (TypeError, ValueError):
|
|
116
|
+
conf = 0.0
|
|
117
|
+
conf = max(0.0, min(1.0, conf))
|
|
118
|
+
|
|
119
|
+
# proposed_verdict (finish action에서만 의미)
|
|
120
|
+
raw_verdict = data.get("proposed_verdict")
|
|
121
|
+
verdict_obj: VerdictType | None = None
|
|
122
|
+
if raw_verdict:
|
|
123
|
+
try:
|
|
124
|
+
verdict_obj = VerdictType(str(raw_verdict).strip().lower())
|
|
125
|
+
except ValueError:
|
|
126
|
+
logger.debug(f"[reflect] proposed_verdict 파싱 실패: {raw_verdict!r}")
|
|
127
|
+
|
|
128
|
+
return ReflectDecision(
|
|
129
|
+
thought=str(data.get("thought") or "").strip(),
|
|
130
|
+
action=action,
|
|
131
|
+
input=inp,
|
|
132
|
+
confidence_so_far=conf,
|
|
133
|
+
proposed_verdict=verdict_obj,
|
|
134
|
+
proposed_explanation=(data.get("proposed_explanation") or None),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ── ReflectAgent ─────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class ReflectConfig:
|
|
142
|
+
model_tier: str = "light"
|
|
143
|
+
"""LLM 모델 선택. heavy는 비용 큼."""
|
|
144
|
+
temperature: float = 0.2
|
|
145
|
+
"""낮을수록 일관적. 0.2~0.3 권장."""
|
|
146
|
+
max_retries: int = 1
|
|
147
|
+
"""JSON 파싱 실패 시 retry 횟수."""
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ReflectAgent:
|
|
151
|
+
"""LLM 호출 → ReflectDecision 반환.
|
|
152
|
+
|
|
153
|
+
agent_loop의 reflect_fn 시그니처에 맞춤:
|
|
154
|
+
async __call__(plan, memory_text, last_observation, iter_num) -> ReflectDecision | None
|
|
155
|
+
|
|
156
|
+
실패 시 None 반환 → loop이 deterministic fallback (plan의 다음 step).
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
llm_call: Callable[..., Awaitable[str]],
|
|
162
|
+
claim: Any,
|
|
163
|
+
config: ReflectConfig | None = None,
|
|
164
|
+
max_iterations: int = 10,
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Args:
|
|
168
|
+
llm_call: async function. signature: `async (prompt: str) -> str`.
|
|
169
|
+
(보통 lambda로 model_tier/temperature를 binding)
|
|
170
|
+
claim: 현재 claim. schema 정보 접근용.
|
|
171
|
+
config: ReflectConfig
|
|
172
|
+
max_iterations: prompt 안에 표시할 max iter
|
|
173
|
+
"""
|
|
174
|
+
self.llm_call = llm_call
|
|
175
|
+
self.claim = claim
|
|
176
|
+
self.config = config or ReflectConfig()
|
|
177
|
+
self.max_iterations = max_iterations
|
|
178
|
+
self._call_count = 0
|
|
179
|
+
|
|
180
|
+
async def __call__(
|
|
181
|
+
self,
|
|
182
|
+
plan: Plan,
|
|
183
|
+
memory_text: str,
|
|
184
|
+
last_observation: Observation | None,
|
|
185
|
+
iter_num: int,
|
|
186
|
+
) -> ReflectDecision | None:
|
|
187
|
+
"""매 iter 호출됨. ReflectDecision 또는 None (fallback)."""
|
|
188
|
+
self._call_count += 1
|
|
189
|
+
|
|
190
|
+
prompt = build_reflect_prompt(
|
|
191
|
+
claim=self.claim,
|
|
192
|
+
plan=plan,
|
|
193
|
+
memory_text=memory_text,
|
|
194
|
+
last_observation=last_observation,
|
|
195
|
+
iter_num=iter_num,
|
|
196
|
+
max_iterations=self.max_iterations,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
for attempt in range(1, self.config.max_retries + 2): # 최초 1회 + retry
|
|
200
|
+
try:
|
|
201
|
+
response = await self.llm_call(prompt)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.warning(
|
|
204
|
+
f"[reflect] LLM 호출 실패 (iter={iter_num}, attempt={attempt}): "
|
|
205
|
+
f"{type(e).__name__}: {e}"
|
|
206
|
+
)
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
decision = _parse_reflect_decision(response)
|
|
210
|
+
if decision is not None:
|
|
211
|
+
logger.info(
|
|
212
|
+
f"[reflect] iter {iter_num}: action={decision.action.value} "
|
|
213
|
+
f"thought={decision.thought[:120]!r} "
|
|
214
|
+
f"confidence={decision.confidence_so_far:.2f}"
|
|
215
|
+
)
|
|
216
|
+
return decision
|
|
217
|
+
|
|
218
|
+
logger.warning(
|
|
219
|
+
f"[reflect] iter {iter_num} attempt {attempt}: 파싱 실패. "
|
|
220
|
+
f"응답 일부: {response[:200]!r}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# 모든 시도 실패 → None (loop이 deterministic fallback)
|
|
224
|
+
logger.warning(
|
|
225
|
+
f"[reflect] iter {iter_num}: 모든 시도 실패, deterministic fallback"
|
|
226
|
+
)
|
|
227
|
+
return None
|