ski-kg-extractor 3.0.3__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.
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: ski-kg-extractor
3
+ Version: 3.0.3
4
+ Summary: Extract compliance rules from regulatory documents using LLM-assisted parsing. Phase 1 only.
5
+ Author-email: KpiFinity <hello@kpifinity.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/kpifinity/ski-framework
8
+ Project-URL: Documentation, https://kpifinity.github.io/ski-framework/
9
+ Project-URL: Issues, https://github.com/kpifinity/ski-framework/issues
10
+ Keywords: ski-framework,compliance,knowledge-graph,extraction
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Legal Industry
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: pydantic<3,>=2.13.4
23
+ Requires-Dist: python-dotenv<2,>=1.2.2
24
+ Requires-Dist: click<9,>=8.1.7
25
+ Requires-Dist: pyyaml<7,>=6.0.1
26
+ Requires-Dist: httpx<0.29,>=0.27
27
+ Requires-Dist: pypdf<7,>=6.12.2
28
+ Requires-Dist: python-docx<2,>=1.1.0
29
+ Requires-Dist: anthropic<1,>=0.21.3
30
+ Requires-Dist: openai<2,>=1.16.2
31
+
32
+ # kg-extractor
33
+
34
+ > Install from PyPI: `pip install ski-kg-extractor` (publishing starts with the first release after June 2026).
35
+
36
+ > **Status:** v3.0 — first production-target release.
37
+
38
+ Extract structured compliance rules from regulatory documents
39
+ (LLM-assisted) and emit a **v3 Knowledge Graph** ready for
40
+ `kg-validator` and the SKI Model runtime.
41
+
42
+ `kg-extractor` is a **Phase 1 (compilation)** tool. The output goes
43
+ through `kg-validator` (schema + §3.6 validation passes) and is then
44
+ signed before crossing the sovereign boundary. The extractor's LLM
45
+ backend may be local or cloud — its output is not part of the runtime
46
+ path.
47
+
48
+ ## Highlights (v3.0)
49
+
50
+ - **Emits v3 KG JSON by default.** Each extracted rule is wrapped into
51
+ the typed-graph shape from spec v3.0 §3: a Rule node, an Obligation
52
+ with a guessed `obligation_type` and numeric `value`/`unit`, a
53
+ Subject, a Citation, and the four edges connecting them
54
+ (`applies_to`, `consists_of`, `scoped_to`, `cited_by`).
55
+ - **`--jurisdiction` flag.** Every KG carries a Jurisdiction node and
56
+ every rule is `scoped_to` it. Defaults to `global`.
57
+ - **Pluggable LLM backend.** Default is **Ollama** (local). Cloud
58
+ backends (`anthropic`, `openai`) exist for compilation-phase use only.
59
+ - **Deterministic by default.** Temperature 0, fixed seed, recorded
60
+ prompt SHA-256 in extraction metadata for reproducibility audits.
61
+ - **`IMPLIED` is prohibited (B2.1 Anchor Constraint).** The prompt
62
+ instructs the model not to emit `IMPLIED` and the parser downgrades
63
+ any that slip through to `DISCRETIONARY` with a warning. Rules
64
+ cannot be inferred beyond source text.
65
+ - **`extraction_quality` replaces `confidence`.** The per-rule trust
66
+ signal is now `extraction_quality` (EXPLICIT / DISCRETIONARY /
67
+ CONFLICTING) — a Phase-1 authoring concept, separate from the
68
+ runtime's categorical verdict (Axiom 2 prohibits confidence scores
69
+ in the audit trail).
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ pip install -e tools/kg-extractor
75
+ ```
76
+
77
+ ## Quick start
78
+
79
+ ```bash
80
+ # Extract a v3 KG from a regulation:
81
+ kg-extractor extract --file regulation.txt --sector energy \
82
+ --jurisdiction us.federal \
83
+ --output kg-extracted-v3.json
84
+
85
+ # Validate the emitted KG:
86
+ kg-validator validate --input kg-extracted-v3.json
87
+
88
+ # Batch:
89
+ kg-extractor batch --input-dir ./regulations --output-dir ./kgs \
90
+ --sector energy --jurisdiction us.federal
91
+
92
+ # Debugging — dump the raw flat-rule list before wrapping:
93
+ kg-extractor extract --file regulation.txt --emit-raw --output flat.json
94
+ ```
95
+
96
+ ## Architecture
97
+
98
+ ```
99
+ kg_extractor/
100
+ ├── __init__.py public API: Extractor, ExtractionResult, ExtractionQuality, emit_v3_kg
101
+ ├── extractor.py chunk → LLM → flat ComplianceRule list
102
+ ├── models.py ComplianceRule, ExtractionResult, ExtractionQuality enum
103
+ ├── backends.py Ollama / Anthropic / OpenAI backends (compilation phase only)
104
+ ├── v3_emitter.py wraps ExtractionResult into a v3 KG dict (spec §3 shape)
105
+ ├── utils.py chunking, document parsing, rule validation
106
+ └── cli.py click-based CLI (`extract`, `batch`, `filter`, `examples`, `version`)
107
+ ```
108
+
109
+ ## Notes on `extraction_quality`
110
+
111
+ - **EXPLICIT** — the LLM produced a verbatim source quote in
112
+ `reasoning`. The rule is safe to ship without further review.
113
+ - **DISCRETIONARY** — the LLM could not anchor the rule in a verbatim
114
+ source quote. A human reviewer must approve before shipping.
115
+ - **CONFLICTING** — the LLM detected internal contradiction in the
116
+ source. Surface for legal review.
117
+
118
+ This is **not** a confidence score in the probabilistic sense. The
119
+ runtime never reads it. It exists solely to drive the Phase-1
120
+ human-review queue.
@@ -0,0 +1,89 @@
1
+ # kg-extractor
2
+
3
+ > Install from PyPI: `pip install ski-kg-extractor` (publishing starts with the first release after June 2026).
4
+
5
+ > **Status:** v3.0 — first production-target release.
6
+
7
+ Extract structured compliance rules from regulatory documents
8
+ (LLM-assisted) and emit a **v3 Knowledge Graph** ready for
9
+ `kg-validator` and the SKI Model runtime.
10
+
11
+ `kg-extractor` is a **Phase 1 (compilation)** tool. The output goes
12
+ through `kg-validator` (schema + §3.6 validation passes) and is then
13
+ signed before crossing the sovereign boundary. The extractor's LLM
14
+ backend may be local or cloud — its output is not part of the runtime
15
+ path.
16
+
17
+ ## Highlights (v3.0)
18
+
19
+ - **Emits v3 KG JSON by default.** Each extracted rule is wrapped into
20
+ the typed-graph shape from spec v3.0 §3: a Rule node, an Obligation
21
+ with a guessed `obligation_type` and numeric `value`/`unit`, a
22
+ Subject, a Citation, and the four edges connecting them
23
+ (`applies_to`, `consists_of`, `scoped_to`, `cited_by`).
24
+ - **`--jurisdiction` flag.** Every KG carries a Jurisdiction node and
25
+ every rule is `scoped_to` it. Defaults to `global`.
26
+ - **Pluggable LLM backend.** Default is **Ollama** (local). Cloud
27
+ backends (`anthropic`, `openai`) exist for compilation-phase use only.
28
+ - **Deterministic by default.** Temperature 0, fixed seed, recorded
29
+ prompt SHA-256 in extraction metadata for reproducibility audits.
30
+ - **`IMPLIED` is prohibited (B2.1 Anchor Constraint).** The prompt
31
+ instructs the model not to emit `IMPLIED` and the parser downgrades
32
+ any that slip through to `DISCRETIONARY` with a warning. Rules
33
+ cannot be inferred beyond source text.
34
+ - **`extraction_quality` replaces `confidence`.** The per-rule trust
35
+ signal is now `extraction_quality` (EXPLICIT / DISCRETIONARY /
36
+ CONFLICTING) — a Phase-1 authoring concept, separate from the
37
+ runtime's categorical verdict (Axiom 2 prohibits confidence scores
38
+ in the audit trail).
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install -e tools/kg-extractor
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ```bash
49
+ # Extract a v3 KG from a regulation:
50
+ kg-extractor extract --file regulation.txt --sector energy \
51
+ --jurisdiction us.federal \
52
+ --output kg-extracted-v3.json
53
+
54
+ # Validate the emitted KG:
55
+ kg-validator validate --input kg-extracted-v3.json
56
+
57
+ # Batch:
58
+ kg-extractor batch --input-dir ./regulations --output-dir ./kgs \
59
+ --sector energy --jurisdiction us.federal
60
+
61
+ # Debugging — dump the raw flat-rule list before wrapping:
62
+ kg-extractor extract --file regulation.txt --emit-raw --output flat.json
63
+ ```
64
+
65
+ ## Architecture
66
+
67
+ ```
68
+ kg_extractor/
69
+ ├── __init__.py public API: Extractor, ExtractionResult, ExtractionQuality, emit_v3_kg
70
+ ├── extractor.py chunk → LLM → flat ComplianceRule list
71
+ ├── models.py ComplianceRule, ExtractionResult, ExtractionQuality enum
72
+ ├── backends.py Ollama / Anthropic / OpenAI backends (compilation phase only)
73
+ ├── v3_emitter.py wraps ExtractionResult into a v3 KG dict (spec §3 shape)
74
+ ├── utils.py chunking, document parsing, rule validation
75
+ └── cli.py click-based CLI (`extract`, `batch`, `filter`, `examples`, `version`)
76
+ ```
77
+
78
+ ## Notes on `extraction_quality`
79
+
80
+ - **EXPLICIT** — the LLM produced a verbatim source quote in
81
+ `reasoning`. The rule is safe to ship without further review.
82
+ - **DISCRETIONARY** — the LLM could not anchor the rule in a verbatim
83
+ source quote. A human reviewer must approve before shipping.
84
+ - **CONFLICTING** — the LLM detected internal contradiction in the
85
+ source. Surface for legal review.
86
+
87
+ This is **not** a confidence score in the probabilistic sense. The
88
+ runtime never reads it. It exists solely to drive the Phase-1
89
+ human-review queue.
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ski-kg-extractor"
7
+ version = "3.0.3"
8
+ description = "Extract compliance rules from regulatory documents using LLM-assisted parsing. Phase 1 only."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "KpiFinity", email = "hello@kpifinity.com" }]
13
+ keywords = ["ski-framework", "compliance", "knowledge-graph", "extraction"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Legal Industry",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "pydantic>=2.13.4,<3",
27
+ "python-dotenv>=1.2.2,<2",
28
+ "click>=8.1.7,<9",
29
+ "pyyaml>=6.0.1,<7",
30
+ "httpx>=0.27,<0.29",
31
+ "pypdf>=6.12.2,<7",
32
+ "python-docx>=1.1.0,<2",
33
+ "anthropic>=0.21.3,<1",
34
+ "openai>=1.16.2,<2",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/kpifinity/ski-framework"
39
+ Documentation = "https://kpifinity.github.io/ski-framework/"
40
+ Issues = "https://github.com/kpifinity/ski-framework/issues"
41
+
42
+ [project.scripts]
43
+ kg-extractor = "kg_extractor.cli:main"
44
+
45
+ [tool.setuptools.packages.find]
46
+ where = ["src"]
47
+
48
+ [tool.setuptools.package-data]
49
+ kg_extractor = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ """kg-extractor — extract compliance rules from regulatory text and emit v3 KGs."""
2
+
3
+ from .extractor import Extractor
4
+ from .models import ComplianceRule, ExtractionQuality, ExtractionResult
5
+ from .v3_emitter import emit_v3_kg
6
+
7
+ __version__ = "3.0.3"
8
+ __author__ = "KpiFinity"
9
+
10
+ __all__ = [
11
+ "ComplianceRule",
12
+ "ExtractionQuality",
13
+ "ExtractionResult",
14
+ "Extractor",
15
+ "emit_v3_kg",
16
+ ]
@@ -0,0 +1,195 @@
1
+ """LLM backend abstraction for kg-extractor.
2
+
3
+ Phase 1 (compilation) is permitted to use cloud LLMs because the output
4
+ is signed and validated before the boundary crossing. The extractor
5
+ defaults to a local backend, but `anthropic` and `openai` are also
6
+ available for compilation-phase use.
7
+
8
+ NONE of these backends are conformant when used at the SKI Model runtime.
9
+ The extractor refuses to talk to runtime endpoints.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import os
16
+ from dataclasses import dataclass
17
+ from typing import Optional, Protocol
18
+
19
+
20
+ @dataclass
21
+ class ExtractionCallResult:
22
+ text: str
23
+ backend: str
24
+ model: str
25
+ seed: Optional[int]
26
+ temperature: float
27
+ prompt_sha256: str
28
+
29
+
30
+ class ExtractorBackend(Protocol):
31
+ name: str
32
+ model: str
33
+
34
+ def extract_json(self, prompt: str, max_tokens: int) -> ExtractionCallResult: ...
35
+
36
+
37
+ def _hash_prompt(prompt: str) -> str:
38
+ return hashlib.sha256(prompt.encode("utf-8")).hexdigest()
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Local Ollama (default, preferred for reproducibility)
43
+ # ---------------------------------------------------------------------------
44
+
45
+
46
+ class OllamaBackend:
47
+ name = "ollama"
48
+
49
+ def __init__(self, *, model: str, base_url: str, seed: int = 42, temperature: float = 0.0):
50
+ self.model = model
51
+ self._base_url = base_url.rstrip("/")
52
+ self._seed = seed
53
+ self._temperature = temperature
54
+
55
+ def extract_json(self, prompt: str, max_tokens: int) -> ExtractionCallResult:
56
+ import httpx
57
+
58
+ payload = {
59
+ "model": self.model,
60
+ "prompt": prompt,
61
+ "stream": False,
62
+ "format": "json",
63
+ "options": {
64
+ "temperature": self._temperature,
65
+ "seed": self._seed,
66
+ "num_predict": max_tokens,
67
+ "top_p": 1.0,
68
+ "top_k": 1,
69
+ },
70
+ }
71
+ resp = httpx.post(f"{self._base_url}/api/generate", json=payload, timeout=120.0)
72
+ resp.raise_for_status()
73
+ return ExtractionCallResult(
74
+ text=resp.json().get("response", ""),
75
+ backend=self.name,
76
+ model=self.model,
77
+ seed=self._seed,
78
+ temperature=self._temperature,
79
+ prompt_sha256=_hash_prompt(prompt),
80
+ )
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Anthropic (compilation-phase only)
85
+ # ---------------------------------------------------------------------------
86
+
87
+
88
+ class AnthropicBackend:
89
+ name = "anthropic"
90
+
91
+ def __init__(self, *, model: str, api_key: str, temperature: float = 0.0, seed: Optional[int] = None):
92
+ try:
93
+ import anthropic # type: ignore
94
+ except ImportError as exc:
95
+ raise RuntimeError(
96
+ "anthropic backend requires `pip install anthropic`. "
97
+ "Or — preferably — use the ollama backend."
98
+ ) from exc
99
+ self._client = anthropic.Anthropic(api_key=api_key)
100
+ self.model = model
101
+ self._temperature = temperature
102
+ self._seed = seed # Anthropic API does not currently honour `seed`; recorded for audit only.
103
+
104
+ def extract_json(self, prompt: str, max_tokens: int) -> ExtractionCallResult:
105
+ msg = self._client.messages.create(
106
+ model=self.model,
107
+ max_tokens=max_tokens,
108
+ temperature=self._temperature,
109
+ messages=[{"role": "user", "content": prompt}],
110
+ )
111
+ text = msg.content[0].text if msg.content else ""
112
+ return ExtractionCallResult(
113
+ text=text,
114
+ backend=self.name,
115
+ model=self.model,
116
+ seed=self._seed,
117
+ temperature=self._temperature,
118
+ prompt_sha256=_hash_prompt(prompt),
119
+ )
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # OpenAI (compilation-phase only)
124
+ # ---------------------------------------------------------------------------
125
+
126
+
127
+ class OpenAIBackend:
128
+ name = "openai"
129
+
130
+ def __init__(self, *, model: str, api_key: str, temperature: float = 0.0, seed: Optional[int] = 42):
131
+ try:
132
+ from openai import OpenAI # type: ignore
133
+ except ImportError as exc:
134
+ raise RuntimeError(
135
+ "openai backend requires `pip install openai`. Or — preferably — use the ollama backend."
136
+ ) from exc
137
+ self._client = OpenAI(api_key=api_key)
138
+ self.model = model
139
+ self._temperature = temperature
140
+ self._seed = seed
141
+
142
+ def extract_json(self, prompt: str, max_tokens: int) -> ExtractionCallResult:
143
+ resp = self._client.chat.completions.create(
144
+ model=self.model,
145
+ max_tokens=max_tokens,
146
+ temperature=self._temperature,
147
+ seed=self._seed,
148
+ response_format={"type": "json_object"},
149
+ messages=[{"role": "user", "content": prompt}],
150
+ )
151
+ text = resp.choices[0].message.content or ""
152
+ return ExtractionCallResult(
153
+ text=text,
154
+ backend=self.name,
155
+ model=self.model,
156
+ seed=self._seed,
157
+ temperature=self._temperature,
158
+ prompt_sha256=_hash_prompt(prompt),
159
+ )
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # Factory
164
+ # ---------------------------------------------------------------------------
165
+
166
+
167
+ def build_backend(name: Optional[str] = None) -> ExtractorBackend:
168
+ name = (name or os.getenv("KG_EXTRACTOR_LLM_BACKEND", "ollama")).lower()
169
+ if name == "ollama":
170
+ return OllamaBackend(
171
+ model=os.getenv("KG_EXTRACTOR_MODEL", "qwen2.5:7b-instruct"),
172
+ base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434"),
173
+ seed=int(os.getenv("KG_EXTRACTOR_SEED", "42")),
174
+ temperature=float(os.getenv("KG_EXTRACTOR_TEMPERATURE", "0")),
175
+ )
176
+ if name == "anthropic":
177
+ api_key = os.getenv("ANTHROPIC_API_KEY", "")
178
+ if not api_key:
179
+ raise RuntimeError("ANTHROPIC_API_KEY required for the anthropic backend.")
180
+ return AnthropicBackend(
181
+ model=os.getenv("KG_EXTRACTOR_MODEL", "claude-haiku-4-5-20251001"),
182
+ api_key=api_key,
183
+ temperature=float(os.getenv("KG_EXTRACTOR_TEMPERATURE", "0")),
184
+ )
185
+ if name == "openai":
186
+ api_key = os.getenv("OPENAI_API_KEY", "")
187
+ if not api_key:
188
+ raise RuntimeError("OPENAI_API_KEY required for the openai backend.")
189
+ return OpenAIBackend(
190
+ model=os.getenv("KG_EXTRACTOR_MODEL", "gpt-4o-mini"),
191
+ api_key=api_key,
192
+ temperature=float(os.getenv("KG_EXTRACTOR_TEMPERATURE", "0")),
193
+ seed=int(os.getenv("KG_EXTRACTOR_SEED", "42")),
194
+ )
195
+ raise RuntimeError(f"Unknown KG_EXTRACTOR_LLM_BACKEND: {name!r}")