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.
- ski_kg_extractor-3.0.3/PKG-INFO +120 -0
- ski_kg_extractor-3.0.3/README.md +89 -0
- ski_kg_extractor-3.0.3/pyproject.toml +49 -0
- ski_kg_extractor-3.0.3/setup.cfg +4 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/__init__.py +16 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/backends.py +195 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/cli.py +271 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/extractor.py +239 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/models.py +115 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/py.typed +0 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/utils.py +183 -0
- ski_kg_extractor-3.0.3/src/kg_extractor/v3_emitter.py +231 -0
- ski_kg_extractor-3.0.3/src/ski_kg_extractor.egg-info/PKG-INFO +120 -0
- ski_kg_extractor-3.0.3/src/ski_kg_extractor.egg-info/SOURCES.txt +17 -0
- ski_kg_extractor-3.0.3/src/ski_kg_extractor.egg-info/dependency_links.txt +1 -0
- ski_kg_extractor-3.0.3/src/ski_kg_extractor.egg-info/entry_points.txt +2 -0
- ski_kg_extractor-3.0.3/src/ski_kg_extractor.egg-info/requires.txt +9 -0
- ski_kg_extractor-3.0.3/src/ski_kg_extractor.egg-info/top_level.txt +1 -0
- ski_kg_extractor-3.0.3/tests/test_extractor.py +229 -0
|
@@ -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,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}")
|