structverify 0.3.2__tar.gz → 0.3.4__tar.gz

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 (205) hide show
  1. {structverify-0.3.2/structverify.egg-info → structverify-0.3.4}/PKG-INFO +36 -1
  2. {structverify-0.3.2 → structverify-0.3.4}/README.md +35 -0
  3. {structverify-0.3.2 → structverify-0.3.4}/pyproject.toml +1 -1
  4. {structverify-0.3.2 → structverify-0.3.4}/structverify/__init__.py +11 -1
  5. {structverify-0.3.2 → structverify-0.3.4}/structverify/core/config_loader.py +9 -0
  6. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/loop.py +5 -1
  7. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/recipe/sample.py +24 -5
  8. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/recipe/train_qlora.py +47 -23
  9. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/decide_verdict_agent.py +20 -0
  10. {structverify-0.3.2 → structverify-0.3.4/structverify.egg-info}/PKG-INFO +36 -1
  11. {structverify-0.3.2 → structverify-0.3.4}/LICENSE +0 -0
  12. {structverify-0.3.2 → structverify-0.3.4}/setup.cfg +0 -0
  13. {structverify-0.3.2/structverify/verification → structverify-0.3.4/structverify/adaptation}/__init__.py +0 -0
  14. {structverify-0.3.2 → structverify-0.3.4}/structverify/adaptation/adapter_trainer.py +0 -0
  15. {structverify-0.3.2 → structverify-0.3.4}/structverify/adaptation/feedback_store.py +0 -0
  16. {structverify-0.3.2 → structverify-0.3.4}/structverify/adaptation/kosis_crawler.py +0 -0
  17. {structverify-0.3.2 → structverify-0.3.4}/structverify/adaptation/sample_builder.py +0 -0
  18. {structverify-0.3.2 → structverify-0.3.4}/structverify/adaptation/synthetic_generator.py +0 -0
  19. {structverify-0.3.2 → structverify-0.3.4}/structverify/adaptation/update_embeddings.py +0 -0
  20. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/__init__.py +0 -0
  21. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/builder_agent.py +0 -0
  22. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/conformance_agent.py +0 -0
  23. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/dependency_planner.py +0 -0
  24. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/indexing_agent.py +0 -0
  25. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/indexing_planner.py +0 -0
  26. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/integration_example.py +0 -0
  27. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/loop.py +0 -0
  28. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/memory.py +0 -0
  29. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/planner.py +0 -0
  30. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/prompts/__init__.py +0 -0
  31. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/prompts/planner_prompts.py +0 -0
  32. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/prompts/reflect_prompts.py +0 -0
  33. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/reflect.py +0 -0
  34. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/runtime_agent.py +0 -0
  35. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/schemas.py +0 -0
  36. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/source_profiler.py +0 -0
  37. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/__init__.py +0 -0
  38. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/base.py +0 -0
  39. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/calculate.py +0 -0
  40. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/catalog_search.py +0 -0
  41. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/deep_explore.py +0 -0
  42. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/explore_catalog.py +0 -0
  43. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/fetch_evidence.py +0 -0
  44. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/finish.py +0 -0
  45. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/meta_explore.py +0 -0
  46. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/query_rewriter.py +0 -0
  47. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/read_original.py +0 -0
  48. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/tools/replan.py +0 -0
  49. {structverify-0.3.2 → structverify-0.3.4}/structverify/agent/workspace.py +0 -0
  50. {structverify-0.3.2 → structverify-0.3.4}/structverify/api.py +0 -0
  51. {structverify-0.3.2 → structverify-0.3.4}/structverify/config/default.yaml +0 -0
  52. {structverify-0.3.2/structverify/utils → structverify-0.3.4/structverify/core}/__init__.py +0 -0
  53. {structverify-0.3.2 → structverify-0.3.4}/structverify/core/pipeline.py +0 -0
  54. {structverify-0.3.2 → structverify-0.3.4}/structverify/core/schemas.py +0 -0
  55. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/__init__.py +0 -0
  56. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/_config.py +0 -0
  57. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/_llm.py +0 -0
  58. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/candidate/__init__.py +0 -0
  59. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/candidate/heuristic.py +0 -0
  60. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/candidate/llm.py +0 -0
  61. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/candidate_scorer.py +0 -0
  62. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/claim_detector.py +0 -0
  63. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/claims/__init__.py +0 -0
  64. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/claims/worthiness.py +0 -0
  65. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/domain/__init__.py +0 -0
  66. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/domain/classify.py +0 -0
  67. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/domain/preview.py +0 -0
  68. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/domain/registry.py +0 -0
  69. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/domain_classifier.py +0 -0
  70. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/prompts/__init__.py +0 -0
  71. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/prompts/candidate.py +0 -0
  72. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/prompts/claim_worthiness.py +0 -0
  73. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/prompts/domain.py +0 -0
  74. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/prompts/schema.py +0 -0
  75. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/prompts_loader.py +0 -0
  76. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema/__init__.py +0 -0
  77. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema/expand.py +0 -0
  78. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema/induce.py +0 -0
  79. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema/regenerate.py +0 -0
  80. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema/temporal_hints.py +0 -0
  81. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema/validate.py +0 -0
  82. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/schema_inductor.py +0 -0
  83. {structverify-0.3.2 → structverify-0.3.4}/structverify/detection/synthetic_generator.py +0 -0
  84. {structverify-0.3.2/structverify/storage → structverify-0.3.4/structverify/explanation}/__init__.py +0 -0
  85. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/_config.py +0 -0
  86. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/_llm.py +0 -0
  87. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/explainer.py +0 -0
  88. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/fallback.py +0 -0
  89. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/formatters.py +0 -0
  90. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/prompts/__init__.py +0 -0
  91. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/prompts/match.py +0 -0
  92. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/prompts/mismatch.py +0 -0
  93. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/prompts/multihop.py +0 -0
  94. {structverify-0.3.2 → structverify-0.3.4}/structverify/explanation/prompts/unverifiable.py +0 -0
  95. {structverify-0.3.2/structverify/retrieval → structverify-0.3.4/structverify/graph}/__init__.py +0 -0
  96. {structverify-0.3.2 → structverify-0.3.4}/structverify/graph/claim_graph.py +0 -0
  97. {structverify-0.3.2 → structverify-0.3.4}/structverify/graph/document_graph.py +0 -0
  98. {structverify-0.3.2 → structverify-0.3.4}/structverify/graph/graph_builder.py +0 -0
  99. {structverify-0.3.2 → structverify-0.3.4}/structverify/graph/graph_multihop.py +0 -0
  100. {structverify-0.3.2 → structverify-0.3.4}/structverify/graph/graph_store.py +0 -0
  101. {structverify-0.3.2 → structverify-0.3.4}/structverify/graph/provenance.py +0 -0
  102. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/__init__.py +0 -0
  103. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/agent_memory.py +0 -0
  104. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/embedder.py +0 -0
  105. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/exemplar_store.py +0 -0
  106. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/normalizer.py +0 -0
  107. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/schema.py +0 -0
  108. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/storage/__init__.py +0 -0
  109. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/storage/jsonl_store.py +0 -0
  110. {structverify-0.3.2 → structverify-0.3.4}/structverify/memory/working_memory.py +0 -0
  111. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/Dockerfile.scraper +0 -0
  112. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/__init__.py +0 -0
  113. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/extractor.py +0 -0
  114. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/__init__.py +0 -0
  115. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/fields.py +0 -0
  116. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/markdown.py +0 -0
  117. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/models.py +0 -0
  118. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/ocr.py +0 -0
  119. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/pipeline.py +0 -0
  120. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/reader.py +0 -0
  121. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/pdf/scoring.py +0 -0
  122. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/scraper_sandbox.py +0 -0
  123. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/segmenter.py +0 -0
  124. {structverify-0.3.2 → structverify-0.3.4}/structverify/preprocessing/sir_builder.py +0 -0
  125. {structverify-0.3.2 → structverify-0.3.4}/structverify/progress.py +0 -0
  126. {structverify-0.3.2/structverify/graph → structverify-0.3.4/structverify/retrieval}/__init__.py +0 -0
  127. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/base.py +0 -0
  128. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/base_connector.py +0 -0
  129. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/catalog_ranker.py +0 -0
  130. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/catalog_search.py +0 -0
  131. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/chunking.py +0 -0
  132. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/custom_csv_source.py +0 -0
  133. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/custom_db_source.py +0 -0
  134. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/custom_docs_source.py +0 -0
  135. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/dimension_resolver.py +0 -0
  136. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/evidence_subgraph.py +0 -0
  137. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/kosis_connector.py +0 -0
  138. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/kosis_relevance.py +0 -0
  139. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/kosis_source.py +0 -0
  140. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/query_builder.py +0 -0
  141. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/registry.py +0 -0
  142. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/relevance_judge.py +0 -0
  143. {structverify-0.3.2 → structverify-0.3.4}/structverify/retrieval/row_matcher.py +0 -0
  144. {structverify-0.3.2/structverify/explanation → structverify-0.3.4/structverify/storage}/__init__.py +0 -0
  145. {structverify-0.3.2 → structverify-0.3.4}/structverify/storage/db_manager.py +0 -0
  146. {structverify-0.3.2 → structverify-0.3.4}/structverify/storage/dwh_manager.py +0 -0
  147. {structverify-0.3.2 → structverify-0.3.4}/structverify/storage/init_db.py +0 -0
  148. {structverify-0.3.2 → structverify-0.3.4}/structverify/storage/raw_storage.py +0 -0
  149. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/__init__.py +0 -0
  150. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/curator.py +0 -0
  151. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/dataset.py +0 -0
  152. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/doctor.py +0 -0
  153. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/evalgate.py +0 -0
  154. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/generate.py +0 -0
  155. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/monitor.py +0 -0
  156. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/recipe/train_mlx.py +0 -0
  157. {structverify-0.3.2 → structverify-0.3.4}/structverify/training/tasks.py +0 -0
  158. {structverify-0.3.2/structverify/core → structverify-0.3.4/structverify/utils}/__init__.py +0 -0
  159. {structverify-0.3.2 → structverify-0.3.4}/structverify/utils/embedding_client.py +0 -0
  160. {structverify-0.3.2 → structverify-0.3.4}/structverify/utils/llm_client.py +0 -0
  161. {structverify-0.3.2 → structverify-0.3.4}/structverify/utils/logger.py +0 -0
  162. {structverify-0.3.2/structverify/adaptation → structverify-0.3.4/structverify/verification}/__init__.py +0 -0
  163. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/_config.py +0 -0
  164. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/adapters.py +0 -0
  165. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/conformance.py +0 -0
  166. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/decide_verdict.py +0 -0
  167. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/growth_diff.py +0 -0
  168. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/row_match.py +0 -0
  169. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/units.py +0 -0
  170. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/verdict_thresholds.py +0 -0
  171. {structverify-0.3.2 → structverify-0.3.4}/structverify/verification/verifier.py +0 -0
  172. {structverify-0.3.2 → structverify-0.3.4}/structverify.egg-info/SOURCES.txt +0 -0
  173. {structverify-0.3.2 → structverify-0.3.4}/structverify.egg-info/dependency_links.txt +0 -0
  174. {structverify-0.3.2 → structverify-0.3.4}/structverify.egg-info/requires.txt +0 -0
  175. {structverify-0.3.2 → structverify-0.3.4}/structverify.egg-info/top_level.txt +0 -0
  176. {structverify-0.3.2 → structverify-0.3.4}/tests/test_api.py +0 -0
  177. {structverify-0.3.2 → structverify-0.3.4}/tests/test_catalog_search_embedding.py +0 -0
  178. {structverify-0.3.2 → structverify-0.3.4}/tests/test_custom_csv_source.py +0 -0
  179. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_candidate_heuristic.py +0 -0
  180. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_config.py +0 -0
  181. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_domain_preview.py +0 -0
  182. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_domain_registry.py +0 -0
  183. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_expand_temporal.py +0 -0
  184. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_init_exports.py +0 -0
  185. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_llm.py +0 -0
  186. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_prompts_loader.py +0 -0
  187. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_validate.py +0 -0
  188. {structverify-0.3.2 → structverify-0.3.4}/tests/test_detection_worthiness.py +0 -0
  189. {structverify-0.3.2 → structverify-0.3.4}/tests/test_embedding_client.py +0 -0
  190. {structverify-0.3.2 → structverify-0.3.4}/tests/test_embedding_config_threading.py +0 -0
  191. {structverify-0.3.2 → structverify-0.3.4}/tests/test_explore_catalog_embedding.py +0 -0
  192. {structverify-0.3.2 → structverify-0.3.4}/tests/test_is_table_relevant.py +0 -0
  193. {structverify-0.3.2 → structverify-0.3.4}/tests/test_kosis_crawler_embedding.py +0 -0
  194. {structverify-0.3.2 → structverify-0.3.4}/tests/test_kosis_guards.py +0 -0
  195. {structverify-0.3.2 → structverify-0.3.4}/tests/test_kosis_json_load_pgvector.py +0 -0
  196. {structverify-0.3.2 → structverify-0.3.4}/tests/test_library_entry_smoke.py +0 -0
  197. {structverify-0.3.2 → structverify-0.3.4}/tests/test_llm_client.py +0 -0
  198. {structverify-0.3.2 → structverify-0.3.4}/tests/test_pipeline.py +0 -0
  199. {structverify-0.3.2 → structverify-0.3.4}/tests/test_runtime_datasources.py +0 -0
  200. {structverify-0.3.2 → structverify-0.3.4}/tests/test_step1-9_v3_pipeline.py +0 -0
  201. {structverify-0.3.2 → structverify-0.3.4}/tests/test_step1_6_detection.py +0 -0
  202. {structverify-0.3.2 → structverify-0.3.4}/tests/test_step1_9_pipeline.py +0 -0
  203. {structverify-0.3.2 → structverify-0.3.4}/tests/test_step9_explainer.py +0 -0
  204. {structverify-0.3.2 → structverify-0.3.4}/tests/test_update_embeddings_embedding.py +0 -0
  205. {structverify-0.3.2 → structverify-0.3.4}/tests/test_verification_decide.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structverify
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Compliance & fact verification for documents — check your files against your own rulebook (PDF) or a data source, and read a plain True/False.
5
5
  Author-email: "김예슬 (Yeseul Kim)" <yesul0718@gmail.com>
6
6
  License: MIT License
@@ -156,6 +156,7 @@ pip install "structverify[all]" # 전부
156
156
  - **큰 규정집엔 ReAct 에이전트 루프.** 검색→판정→쿼리 재구성→재검색을 반복해 적용 조항을 찾음.
157
157
  - **provider 무관.** `upstage` \| `openai` \| `gemini` \| `hcx` — `api_key` 한 줄이면 끝.
158
158
  - **결과가 곧 boolean.** `.ok` `.compliant` `.violated` / `bool(result)` / iterable / `.to_dict()`.
159
+ - **감독 학습 루프.** 확정 정답으로 자체 모델을 파인튜닝 — 실시간 감독·조기 종료·자가평가(EvalGate)까지 내장.
159
160
 
160
161
  ## 30초 시작
161
162
 
@@ -327,6 +328,10 @@ backend/structverify/
327
328
  `.ok` / `.compliant` 같은 **평범한 True/False** 를 바로 읽는다. 모든 sync 메서드는
328
329
  `a` 접두사 async 쌍을 가진다 (`check`/`acheck`, `verify`/`averify`).
329
330
 
331
+ > 🏃 **바로 돌려볼 수 있는 예제**: [`examples/`](examples/README.md) — 규정 준수 빠른 시작,
332
+ > CSV/DB 사실검증(에이전틱 포함), 설정·대시보드, 감독 학습 루프, 학습 모델 주입까지
333
+ > 시나리오별 단일 파일 6종. 시연 데이터 동봉이라 `python examples/01_quickstart_conformance.py` 로 즉시 실행.
334
+
330
335
  ### ① 규정 준수 검사 — `Ruleset` (내 규정집으로 검증)
331
336
 
332
337
  PDF/txt 규정집(안전기준·사내정책·표시기준 등)을 색인하고, 문장이 그 규정을 지키는지 판정한다.
@@ -433,6 +438,36 @@ for r in report.results:
433
438
  print(r.verdict, r.confidence, r.evidence.official_value, r.explanation)
434
439
  ```
435
440
 
441
+ ### ③ 감독 학습 루프 — `LearningLoop` (내 데이터로 모델 기르기)
442
+
443
+ 검증에서 나온 **확정 정답**으로 자체 7B 모델을 LoRA 파인튜닝한다. "그냥 학습 스크립트"가
444
+ 아니라 **감독 에이전트 3종**이 붙는 게 차별점 — 🕵 DataCurator(데이터 품질·중복·편중 격리),
445
+ 🩺 TrainDoctor(v0.3.1+: **실시간 감독** — 터미널 한 줄 UI, plateau/발산이면 스스로 조기 종료),
446
+ 🚦 EvalGate(학습 전/후를 검증 엔진으로 자가평가 → 채택/거부).
447
+
448
+ ```python
449
+ from structverify.training import LearningLoop
450
+
451
+ loop = LearningLoop(engine, base_model="unsloth/Qwen2.5-7B-Instruct")
452
+ loop.add_seed().add_reports(reports).add_jsonl("corrections.jsonl") # 데이터 수집 (체이닝)
453
+ ds, curation = loop.prepare("train.jsonl") # 🕵 품질검사 → clean 데이터셋
454
+ loop.train(ds, "./adapter", backend="auto", # NVIDIA→QLoRA · Apple Silicon→MLX
455
+ run=True, early_stop=True, patience=200) # 🩺 수렴하면 스스로 멈춤
456
+ loop.diagnose("./adapter/trainer_state.json") # 🩺 사후 진단 + 처방
457
+ gate = loop.evaluate(eval_set, tuned_engine=tuned) # 🚦 개선 확인 후에만 채택
458
+ ```
459
+
460
+ 학습 중 터미널에는 감독 한 줄만 갱신된다 (색상은 tty 자동 감지 · `NO_COLOR`/`SV_COLOR` 지원):
461
+
462
+ ```
463
+ 🧠 336/2496 ▓░░░░░░░ 13% · loss 0.108 ▆█▄▄▁▁ · lr 1.5e-04 · ETA 2:29 · ⚡1 · 🩺 수렴 유지
464
+ ✂ step 336: 200스텝 동안 이동평균 개선 없음. 더 학습해도 이득이 없어 조기 종료합니다.
465
+ ```
466
+
467
+ GPU 레시피는 extras로: `[training]`(NVIDIA — RTX 3060/Colab T4·L4, unsloth QLoRA) ·
468
+ `[training-mac]`(Apple Silicon — MLX). 코어(수집·품질검사·진단·평가)는 GPU 불필요.
469
+ 학습 후 어댑터 확인은 `python -m structverify.training.recipe.sample --adapter ./adapter`.
470
+
436
471
  ---
437
472
 
438
473
  ## Phase D Agent Loop 상세
@@ -39,6 +39,7 @@ pip install "structverify[all]" # 전부
39
39
  - **큰 규정집엔 ReAct 에이전트 루프.** 검색→판정→쿼리 재구성→재검색을 반복해 적용 조항을 찾음.
40
40
  - **provider 무관.** `upstage` \| `openai` \| `gemini` \| `hcx` — `api_key` 한 줄이면 끝.
41
41
  - **결과가 곧 boolean.** `.ok` `.compliant` `.violated` / `bool(result)` / iterable / `.to_dict()`.
42
+ - **감독 학습 루프.** 확정 정답으로 자체 모델을 파인튜닝 — 실시간 감독·조기 종료·자가평가(EvalGate)까지 내장.
42
43
 
43
44
  ## 30초 시작
44
45
 
@@ -210,6 +211,10 @@ backend/structverify/
210
211
  `.ok` / `.compliant` 같은 **평범한 True/False** 를 바로 읽는다. 모든 sync 메서드는
211
212
  `a` 접두사 async 쌍을 가진다 (`check`/`acheck`, `verify`/`averify`).
212
213
 
214
+ > 🏃 **바로 돌려볼 수 있는 예제**: [`examples/`](examples/README.md) — 규정 준수 빠른 시작,
215
+ > CSV/DB 사실검증(에이전틱 포함), 설정·대시보드, 감독 학습 루프, 학습 모델 주입까지
216
+ > 시나리오별 단일 파일 6종. 시연 데이터 동봉이라 `python examples/01_quickstart_conformance.py` 로 즉시 실행.
217
+
213
218
  ### ① 규정 준수 검사 — `Ruleset` (내 규정집으로 검증)
214
219
 
215
220
  PDF/txt 규정집(안전기준·사내정책·표시기준 등)을 색인하고, 문장이 그 규정을 지키는지 판정한다.
@@ -316,6 +321,36 @@ for r in report.results:
316
321
  print(r.verdict, r.confidence, r.evidence.official_value, r.explanation)
317
322
  ```
318
323
 
324
+ ### ③ 감독 학습 루프 — `LearningLoop` (내 데이터로 모델 기르기)
325
+
326
+ 검증에서 나온 **확정 정답**으로 자체 7B 모델을 LoRA 파인튜닝한다. "그냥 학습 스크립트"가
327
+ 아니라 **감독 에이전트 3종**이 붙는 게 차별점 — 🕵 DataCurator(데이터 품질·중복·편중 격리),
328
+ 🩺 TrainDoctor(v0.3.1+: **실시간 감독** — 터미널 한 줄 UI, plateau/발산이면 스스로 조기 종료),
329
+ 🚦 EvalGate(학습 전/후를 검증 엔진으로 자가평가 → 채택/거부).
330
+
331
+ ```python
332
+ from structverify.training import LearningLoop
333
+
334
+ loop = LearningLoop(engine, base_model="unsloth/Qwen2.5-7B-Instruct")
335
+ loop.add_seed().add_reports(reports).add_jsonl("corrections.jsonl") # 데이터 수집 (체이닝)
336
+ ds, curation = loop.prepare("train.jsonl") # 🕵 품질검사 → clean 데이터셋
337
+ loop.train(ds, "./adapter", backend="auto", # NVIDIA→QLoRA · Apple Silicon→MLX
338
+ run=True, early_stop=True, patience=200) # 🩺 수렴하면 스스로 멈춤
339
+ loop.diagnose("./adapter/trainer_state.json") # 🩺 사후 진단 + 처방
340
+ gate = loop.evaluate(eval_set, tuned_engine=tuned) # 🚦 개선 확인 후에만 채택
341
+ ```
342
+
343
+ 학습 중 터미널에는 감독 한 줄만 갱신된다 (색상은 tty 자동 감지 · `NO_COLOR`/`SV_COLOR` 지원):
344
+
345
+ ```
346
+ 🧠 336/2496 ▓░░░░░░░ 13% · loss 0.108 ▆█▄▄▁▁ · lr 1.5e-04 · ETA 2:29 · ⚡1 · 🩺 수렴 유지
347
+ ✂ step 336: 200스텝 동안 이동평균 개선 없음. 더 학습해도 이득이 없어 조기 종료합니다.
348
+ ```
349
+
350
+ GPU 레시피는 extras로: `[training]`(NVIDIA — RTX 3060/Colab T4·L4, unsloth QLoRA) ·
351
+ `[training-mac]`(Apple Silicon — MLX). 코어(수집·품질검사·진단·평가)는 GPU 불필요.
352
+ 학습 후 어댑터 확인은 `python -m structverify.training.recipe.sample --adapter ./adapter`.
353
+
319
354
  ---
320
355
 
321
356
  ## Phase D Agent Loop 상세
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "structverify"
3
- version = "0.3.2"
3
+ version = "0.3.4"
4
4
  description = "Compliance & fact verification for documents — check your files against your own rulebook (PDF) or a data source, and read a plain True/False."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -20,6 +20,16 @@ Graph + JSON 하이브리드 저장 · 2-Agent 아키텍처 · 도메인 적응
20
20
  → import/패키지 탐지가 가볍고, 선택적 deps 없이도 import 자체는 성공.
21
21
  """
22
22
 
23
+ # .env 자동 로드 — `import structverify` 시점에 실행 위치의 .env를 읽는다.
24
+ # (이미 설정된 환경변수는 덮어쓰지 않음. dotenv 부재 시 조용히 스킵.)
25
+ try:
26
+ from dotenv import load_dotenv as _load_dotenv
27
+
28
+ _load_dotenv()
29
+ except ImportError: # pragma: no cover
30
+ pass
31
+
32
+
23
33
  # High-level ergonomic API (structverify.api) — the "point-and-call" surface.
24
34
  _API_SYMBOLS = frozenset({
25
35
  "verify", # one-shot fact verification → Report
@@ -54,7 +64,7 @@ _TRAINING_SYMBOLS = frozenset({
54
64
  })
55
65
 
56
66
  __all__ = sorted(_API_SYMBOLS | _PIPELINE_SYMBOLS | _PROGRESS_SYMBOLS | _TRAINING_SYMBOLS)
57
- __version__ = "0.3.2"
67
+ __version__ = "0.3.4"
58
68
 
59
69
 
60
70
  def __getattr__(name: str):
@@ -2,6 +2,15 @@ from __future__ import annotations
2
2
  import os
3
3
  from pathlib import Path
4
4
 
5
+ # .env 자동 로드 — 문서가 약속한 동작. 실행 위치(cwd)의 .env를 읽으며,
6
+ # 이미 설정된 환경변수는 덮어쓰지 않는다(override=False 기본).
7
+ try:
8
+ from dotenv import load_dotenv
9
+
10
+ load_dotenv()
11
+ except ImportError: # pragma: no cover — python-dotenv는 코어 의존성이지만 방어
12
+ pass
13
+
5
14
  # 패키지에 번들된 기본 설정 — pip 설치 후에도 항상 존재한다.
6
15
  _PACKAGED_DEFAULT = Path(__file__).parent.parent / "config" / "default.yaml"
7
16
  # 개발 체크아웃(backend/config/default.yaml) 오버라이드 — 있으면 우선.
@@ -96,7 +96,11 @@ class LearningLoop:
96
96
  print(" " + info["command"])
97
97
  return info
98
98
  try:
99
- __import__(_probe)
99
+ # 백엔드 존재 확인 — torch 등 무거운 import가 뿜는 잡음(torchao·Flax)은 숨긴다.
100
+ import contextlib
101
+ with open(os.devnull, "w") as _dn, \
102
+ contextlib.redirect_stdout(_dn), contextlib.redirect_stderr(_dn):
103
+ __import__(_probe)
100
104
  except Exception: # noqa: BLE001
101
105
  raise RuntimeError(f"run=True에는 학습 백엔드가 필요합니다 → {need}. "
102
106
  "또는 run=False로 명령만 받아 별도 실행하세요.")
@@ -9,10 +9,27 @@ GPU 머신(어댑터 학습한 곳)에서 실행:
9
9
  --task schema --claim "당사 물류 파트너는 2,300개사에 달했습니다."
10
10
  """
11
11
  import argparse
12
+ import contextlib
13
+ import logging
12
14
  import os
15
+ import sys
16
+ import warnings
13
17
 
14
18
  os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "3")
15
19
  os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
20
+ sys.unraisablehook = lambda *_: None
21
+ warnings.filterwarnings("ignore")
22
+ logging.getLogger("torchao").setLevel(logging.CRITICAL)
23
+
24
+
25
+ @contextlib.contextmanager
26
+ def _quiet():
27
+ if os.environ.get("SV_VERBOSE"):
28
+ yield
29
+ return
30
+ with open(os.devnull, "w") as dn, \
31
+ contextlib.redirect_stdout(dn), contextlib.redirect_stderr(dn):
32
+ yield
16
33
 
17
34
  # 내장 테스트 — 시드/증강 데이터에 없는 새 표현으로 일반화를 확인.
18
35
  _DEFAULT_TESTS = [
@@ -37,7 +54,8 @@ def main():
37
54
  ap.add_argument("--max-tokens", type=int, default=120)
38
55
  args = ap.parse_args()
39
56
 
40
- from unsloth import FastLanguageModel
57
+ with _quiet():
58
+ from unsloth import FastLanguageModel
41
59
 
42
60
  from structverify.training.tasks import build_example
43
61
 
@@ -49,10 +67,11 @@ def main():
49
67
  else:
50
68
  tests = _DEFAULT_TESTS
51
69
 
52
- print(f"어댑터 로드: {args.adapter}")
53
- model, tok = FastLanguageModel.from_pretrained(
54
- args.adapter, max_seq_length=2048, load_in_4bit=True)
55
- FastLanguageModel.for_inference(model)
70
+ print(f"어댑터 로드 {args.adapter}")
71
+ with _quiet():
72
+ model, tok = FastLanguageModel.from_pretrained(
73
+ args.adapter, max_seq_length=2048, load_in_4bit=True)
74
+ FastLanguageModel.for_inference(model)
56
75
 
57
76
  for t in tests:
58
77
  msgs = build_example(**t)["messages"][:2] # system + user만
@@ -31,6 +31,7 @@ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
31
31
  # 워커가 fork로 뜨면서 이 훅을 물려받아 자식 프로세스 출력까지 조용해진다.
32
32
  sys.unraisablehook = lambda *_: None
33
33
 
34
+ import contextlib
34
35
  import logging
35
36
  import warnings
36
37
 
@@ -38,6 +39,17 @@ warnings.filterwarnings("ignore") # Flax deprecated 등
38
39
  logging.getLogger("torchao").setLevel(logging.CRITICAL) # torchao .so 로드 WARNING
39
40
 
40
41
 
42
+ @contextlib.contextmanager
43
+ def _quiet():
44
+ """서드파티 import·로드가 뿜는 무관한 출력 숨김. SV_VERBOSE=1이면 전부 표시."""
45
+ if os.environ.get("SV_VERBOSE"):
46
+ yield
47
+ return
48
+ with open(os.devnull, "w") as dn, \
49
+ contextlib.redirect_stdout(dn), contextlib.redirect_stderr(dn):
50
+ yield
51
+
52
+
41
53
  def load_chat_dataset(path):
42
54
  from datasets import Dataset
43
55
  rows = []
@@ -72,35 +84,47 @@ def main():
72
84
  help="중간 체크포인트(checkpoint-*) 보존 — 기본은 성공 시 삭제 (어댑터만 수십 MB)")
73
85
  args = ap.parse_args()
74
86
 
75
- # unsloth: 3060/T4에서 QLoRA 2배 빠름 + 메모리 절약
76
- from unsloth import FastLanguageModel
77
- from trl import SFTTrainer, SFTConfig
87
+ # unsloth: 3060/T4에서 QLoRA 2배 빠름 + 메모리 절약.
88
+ # import·로드 단계의 배너·경고는 숨기고(_quiet) 우리 요약 한 줄로 대체.
89
+ with _quiet():
90
+ from unsloth import FastLanguageModel
91
+ from trl import SFTTrainer, SFTConfig
92
+
93
+ # tqdm 진행바(체크포인트 로드·Map·Tokenizing) 대신 우리 phase 라인으로.
94
+ try:
95
+ import datasets as _ds
96
+ import transformers as _tf
97
+ _ds.disable_progress_bars()
98
+ _tf.utils.logging.disable_progress_bar()
99
+ except Exception: # noqa: BLE001
100
+ pass
78
101
 
79
- # tqdm 진행바(체크포인트 로드·Map·Tokenizing) 대신 우리 phase 라인으로.
80
102
  try:
81
- import datasets as _ds
82
- import transformers as _tf
83
- _ds.disable_progress_bars()
84
- _tf.utils.logging.disable_progress_bar()
103
+ import torch
104
+ _p = torch.cuda.get_device_properties(0)
105
+ _prec = "bf16" if torch.cuda.is_bf16_supported() else "fp16"
106
+ print(f"⚙ 학습 엔진 준비 — unsloth · {_p.name} ({_p.total_memory/2**30:.0f}GB) · {_prec}",
107
+ file=sys.stderr)
85
108
  except Exception: # noqa: BLE001
86
109
  pass
87
110
 
88
111
  print(f"⚙ 베이스 모델 로드 — {args.model} (4bit)", file=sys.stderr)
89
- model, tokenizer = FastLanguageModel.from_pretrained(
90
- model_name=args.model,
91
- max_seq_length=args.seq_len,
92
- load_in_4bit=True, # QLoRA — 12GB VRAM에 7B 적재
93
- )
94
- model = FastLanguageModel.get_peft_model(
95
- model,
96
- r=args.rank,
97
- target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
98
- "gate_proj", "up_proj", "down_proj"],
99
- lora_alpha=args.rank * 2,
100
- lora_dropout=0.0,
101
- bias="none",
102
- use_gradient_checkpointing="unsloth",
103
- )
112
+ with _quiet():
113
+ model, tokenizer = FastLanguageModel.from_pretrained(
114
+ model_name=args.model,
115
+ max_seq_length=args.seq_len,
116
+ load_in_4bit=True, # QLoRA — 12GB VRAM에 7B 적재
117
+ )
118
+ model = FastLanguageModel.get_peft_model(
119
+ model,
120
+ r=args.rank,
121
+ target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
122
+ "gate_proj", "up_proj", "down_proj"],
123
+ lora_alpha=args.rank * 2,
124
+ lora_dropout=0.0,
125
+ bias="none",
126
+ use_gradient_checkpointing="unsloth",
127
+ )
104
128
 
105
129
  print("⚙ 데이터 준비·토큰화 중 …", file=sys.stderr)
106
130
  ds = load_chat_dataset(args.data)
@@ -163,6 +163,26 @@ def decide_verdict_agent_fetch(
163
163
  f"조회 {_fv0:.4g}×{_fs:g}={fv:.4g}{fetched_unit}"
164
164
  )
165
165
 
166
+ # 단위 계열 불일치 가드 — 주장은 비율(%)인데 조회값은 절대 단위(달러/건/명 등),
167
+ # 혹은 그 반대면 값 비교 자체가 성립하지 않는다. 이때 mismatch를 확정하면
168
+ # 정답 주장을 오판하게 되므로(예: "비중 20%" vs 조회 4,367억 달러) 판단 보류.
169
+ def _is_ratio_unit(u: str | None) -> bool:
170
+ s = (u or "").strip()
171
+ return "%" in s or "퍼센트" in s or s.endswith("p") # %p 포함
172
+
173
+ # 양쪽 단위가 모두 명시된 경우에만 — 한쪽이 빈 단위면 기존처럼 값으로 비교.
174
+ if claim_unit and fetched_unit and _is_ratio_unit(claim_unit) != _is_ratio_unit(fetched_unit):
175
+ return VerdictDecision(
176
+ claim_id=claim_id,
177
+ verdict=VerdictType.UNVERIFIABLE,
178
+ confidence=0.4,
179
+ explanation=(
180
+ f"단위 계열 불일치 — 주장 {cv:.4g}{claim_unit or ''}(비율)와 "
181
+ f"조회값 {fv:.4g}{fetched_unit or ''}(절대값)은 직접 비교할 수 없음. "
182
+ f"비율 판정에는 분자/분모 산출이 필요해 판단을 보류함."
183
+ ),
184
+ )
185
+
166
186
  if abs(cv) < 1e-9:
167
187
  diff_ratio = 0.0 if abs(fv) < 1e-9 else 1.0
168
188
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structverify
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Compliance & fact verification for documents — check your files against your own rulebook (PDF) or a data source, and read a plain True/False.
5
5
  Author-email: "김예슬 (Yeseul Kim)" <yesul0718@gmail.com>
6
6
  License: MIT License
@@ -156,6 +156,7 @@ pip install "structverify[all]" # 전부
156
156
  - **큰 규정집엔 ReAct 에이전트 루프.** 검색→판정→쿼리 재구성→재검색을 반복해 적용 조항을 찾음.
157
157
  - **provider 무관.** `upstage` \| `openai` \| `gemini` \| `hcx` — `api_key` 한 줄이면 끝.
158
158
  - **결과가 곧 boolean.** `.ok` `.compliant` `.violated` / `bool(result)` / iterable / `.to_dict()`.
159
+ - **감독 학습 루프.** 확정 정답으로 자체 모델을 파인튜닝 — 실시간 감독·조기 종료·자가평가(EvalGate)까지 내장.
159
160
 
160
161
  ## 30초 시작
161
162
 
@@ -327,6 +328,10 @@ backend/structverify/
327
328
  `.ok` / `.compliant` 같은 **평범한 True/False** 를 바로 읽는다. 모든 sync 메서드는
328
329
  `a` 접두사 async 쌍을 가진다 (`check`/`acheck`, `verify`/`averify`).
329
330
 
331
+ > 🏃 **바로 돌려볼 수 있는 예제**: [`examples/`](examples/README.md) — 규정 준수 빠른 시작,
332
+ > CSV/DB 사실검증(에이전틱 포함), 설정·대시보드, 감독 학습 루프, 학습 모델 주입까지
333
+ > 시나리오별 단일 파일 6종. 시연 데이터 동봉이라 `python examples/01_quickstart_conformance.py` 로 즉시 실행.
334
+
330
335
  ### ① 규정 준수 검사 — `Ruleset` (내 규정집으로 검증)
331
336
 
332
337
  PDF/txt 규정집(안전기준·사내정책·표시기준 등)을 색인하고, 문장이 그 규정을 지키는지 판정한다.
@@ -433,6 +438,36 @@ for r in report.results:
433
438
  print(r.verdict, r.confidence, r.evidence.official_value, r.explanation)
434
439
  ```
435
440
 
441
+ ### ③ 감독 학습 루프 — `LearningLoop` (내 데이터로 모델 기르기)
442
+
443
+ 검증에서 나온 **확정 정답**으로 자체 7B 모델을 LoRA 파인튜닝한다. "그냥 학습 스크립트"가
444
+ 아니라 **감독 에이전트 3종**이 붙는 게 차별점 — 🕵 DataCurator(데이터 품질·중복·편중 격리),
445
+ 🩺 TrainDoctor(v0.3.1+: **실시간 감독** — 터미널 한 줄 UI, plateau/발산이면 스스로 조기 종료),
446
+ 🚦 EvalGate(학습 전/후를 검증 엔진으로 자가평가 → 채택/거부).
447
+
448
+ ```python
449
+ from structverify.training import LearningLoop
450
+
451
+ loop = LearningLoop(engine, base_model="unsloth/Qwen2.5-7B-Instruct")
452
+ loop.add_seed().add_reports(reports).add_jsonl("corrections.jsonl") # 데이터 수집 (체이닝)
453
+ ds, curation = loop.prepare("train.jsonl") # 🕵 품질검사 → clean 데이터셋
454
+ loop.train(ds, "./adapter", backend="auto", # NVIDIA→QLoRA · Apple Silicon→MLX
455
+ run=True, early_stop=True, patience=200) # 🩺 수렴하면 스스로 멈춤
456
+ loop.diagnose("./adapter/trainer_state.json") # 🩺 사후 진단 + 처방
457
+ gate = loop.evaluate(eval_set, tuned_engine=tuned) # 🚦 개선 확인 후에만 채택
458
+ ```
459
+
460
+ 학습 중 터미널에는 감독 한 줄만 갱신된다 (색상은 tty 자동 감지 · `NO_COLOR`/`SV_COLOR` 지원):
461
+
462
+ ```
463
+ 🧠 336/2496 ▓░░░░░░░ 13% · loss 0.108 ▆█▄▄▁▁ · lr 1.5e-04 · ETA 2:29 · ⚡1 · 🩺 수렴 유지
464
+ ✂ step 336: 200스텝 동안 이동평균 개선 없음. 더 학습해도 이득이 없어 조기 종료합니다.
465
+ ```
466
+
467
+ GPU 레시피는 extras로: `[training]`(NVIDIA — RTX 3060/Colab T4·L4, unsloth QLoRA) ·
468
+ `[training-mac]`(Apple Silicon — MLX). 코어(수집·품질검사·진단·평가)는 GPU 불필요.
469
+ 학습 후 어댑터 확인은 `python -m structverify.training.recipe.sample --adapter ./adapter`.
470
+
436
471
  ---
437
472
 
438
473
  ## Phase D Agent Loop 상세
File without changes
File without changes