krail 0.2.1__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.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: krail
3
+ Version: 0.2.1
4
+ Summary: KRAIL client for local projects and the local API runtime
5
+ Author: AkeBoss Tech
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AkeBoss-tech/knowledge
8
+ Project-URL: Repository, https://github.com/AkeBoss-tech/knowledge
9
+ Project-URL: Issues, https://github.com/AkeBoss-tech/knowledge/issues
10
+ Keywords: knowledge,research,agents,local-first,mcp
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: httpx>=0.27
24
+ Requires-Dist: pydantic>=2
25
+ Requires-Dist: pyyaml>=6
26
+ Provides-Extra: analysis
27
+ Requires-Dist: numpy; extra == "analysis"
28
+ Requires-Dist: pandas; extra == "analysis"
29
+ Requires-Dist: statsmodels; extra == "analysis"
30
+ Requires-Dist: matplotlib; extra == "analysis"
31
+ Provides-Extra: local
32
+ Requires-Dist: owlready2; extra == "local"
33
+ Requires-Dist: duckdb; extra == "local"
34
+ Provides-Extra: embeddings
35
+ Requires-Dist: sentence-transformers>=2.7; extra == "embeddings"
36
+
37
+ # krail
38
+
39
+ KRAIL client supporting local project mode and the local FastAPI runtime.
40
+
41
+ Install from PyPI:
42
+
43
+ ```bash
44
+ pip install krail
45
+ ```
46
+
47
+ The distribution is named `krail`; the Python import namespace remains `rail`.
48
+
49
+ ## Usage Examples
50
+
51
+ ```python
52
+ import rail
53
+
54
+ # API mode
55
+ project = rail.connect("nj-economics")
56
+
57
+ # DataFrame queries
58
+ df = project.query("SELECT county_name, unemployment_rate FROM County ORDER BY unemployment_rate DESC LIMIT 10")
59
+
60
+ # Agent research
61
+ answer = project.agent.ask("What counties had unemployment above 10% in 2020?")
62
+ print(answer)
63
+
64
+ # Streaming agent
65
+ for event in project.agent.ask("Compare Hudson and Bergen County unemployment trends", stream=True):
66
+ if event["type"] == "text_delta":
67
+ print(event["text"], end="", flush=True)
68
+
69
+ # Direct local mode
70
+ project = rail.local("./nj-economics")
71
+ ont = project.ontology()
72
+ counties = ont.individuals("County")
73
+ print(f"Loaded {len(counties)} counties")
74
+ ```
@@ -0,0 +1,26 @@
1
+ rail/__init__.py,sha256=DbCsxwtTQpdMvGj1msSfssBIgTHCd696AMyE6PB6evY,1480
2
+ rail/agent.py,sha256=rXq_LWusbUIrvdhMhk9zxiBYymAxtaoXEGW6HqOsJa4,2589
3
+ rail/bootstrap.py,sha256=MXq_KWY66IrCfj9XjvD62ojca2dfjpgGuUTil0vmecc,49402
4
+ rail/cli.py,sha256=QOFyqQSbouF9m3RlqtRH86bc02ZJyWQ6txYw-Ewzh_s,44619
5
+ rail/client.py,sha256=VRCpz-oaFR6JyIhb_-DPV3a022DlyFqCOXUtfg92nzc,5155
6
+ rail/completion_gate.py,sha256=WqG1h4e690sohU2tptOtiHymiNUTrZyxmWGOIq0BvTU,8940
7
+ rail/exceptions.py,sha256=0SPYgg1fUz0L72miE6S-BCiQ3s0afDJmM3KQTm6mLT0,299
8
+ rail/integrity.py,sha256=3904cIKQUZbGR6-kSUAybnjOdygBUN2EzTIJ84bexN0,210824
9
+ rail/knowledge.py,sha256=I3mL-eUYh6kBgQDNiAg_H3yXz_MC5vgvtP2flVEVk7c,147494
10
+ rail/local.py,sha256=2tikTligGjDcSc7tN8lYPdD6I0a_jn8DuFku1BrealA,28718
11
+ rail/manifest.py,sha256=-S9J5w3zJIFu9DAp13iaWkf8xpjIJbjcqJ-LLunSKJA,18714
12
+ rail/markdown_graph.py,sha256=2zh4O6Ykd6IMn37ZvwgOlc8SrMu5ZRpdIAhDsEhTuhM,22133
13
+ rail/models.py,sha256=uXHhrtO6fU12YvvCHsLEz_9Tt6MELUJ1AF-o9RRucqo,516
14
+ rail/modes.py,sha256=ubrwKWD-zDQF50yYY_y-0yfEWu6JSmiUly2vOqJRfjc,6730
15
+ rail/ontology.py,sha256=2uZJ7Gjg4tDlcG-9nYpp-CZ3DskO9ivoozRWqyxhzgg,2202
16
+ rail/planner_sync.py,sha256=zToU3LpR1p35QlIxd1Rog9KGW6AhnMuBoX9zOtjeWY0,8093
17
+ rail/project.py,sha256=jbdbbgZywJ5F_GfZBsxF6YxCwS3wvOZjW78tjjRev-M,28928
18
+ rail/session_state.py,sha256=dSKd15-V4SdcfRFItkYqiycinUg2uv6eoIBssARB404,1431
19
+ rail/source_dependencies.py,sha256=CqygACp1xL96DPgefD_Ly70qWy3aufBaUrXsPrKmxfA,10377
20
+ rail/vector_store.py,sha256=VjUQIQn5OVWEYi0wDNZLGKyRUzcICFvQiNQ_vBZMgiI,10278
21
+ rail/verification.py,sha256=lA2xX2aYXU_hqz_YAmeDQqeR1DimDomWDM7kMJMxzL8,17126
22
+ krail-0.2.1.dist-info/METADATA,sha256=igN7HxIosLS6KreQlOrp8H0BonVgeO7HTCEpMqKZI-g,2404
23
+ krail-0.2.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
24
+ krail-0.2.1.dist-info/entry_points.txt,sha256=z0p7tay632AJM1f3z9MB-VzBix56I5-beIsEbLzEMgc,61
25
+ krail-0.2.1.dist-info/top_level.txt,sha256=6R6sqn-85I8YZTzmB9gv7sqckB2QaOTHbu-QLGTWBRE,5
26
+ krail-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ krail = rail.cli:main
3
+ rail = rail.cli:main
@@ -0,0 +1 @@
1
+ rail
rail/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ from rail.project import Project
2
+ from rail.client import CloudClient
3
+ from rail.local import LocalEngine
4
+ from rail.integrity import (
5
+ ArtifactLineageRecord,
6
+ AssumptionRecord,
7
+ ClaimRecord,
8
+ IntegrityIndexes,
9
+ ResearchIntegrityRepo,
10
+ SourceRecord,
11
+ VerificationRunRecord,
12
+ )
13
+ from rail.manifest import (
14
+ ContractViolation,
15
+ ManifestValidationError,
16
+ RailManifest,
17
+ boot_validate_project,
18
+ load_and_validate_manifest,
19
+ load_manifest,
20
+ parse_manifest_content,
21
+ )
22
+ from rail.session_state import (
23
+ ACTIVE_STATUSES,
24
+ TERMINAL_STATUSES,
25
+ is_active_status,
26
+ is_terminal_status,
27
+ normalize_session_record,
28
+ normalize_session_status,
29
+ )
30
+ from rail.exceptions import RailError
31
+
32
+ __version__ = "0.2.1"
33
+
34
+ def connect(
35
+ slug: str,
36
+ api_url: str | None = None,
37
+ api_key: str | None = None,
38
+ ) -> Project:
39
+ """Connect to a KRAIL project via the local FastAPI runtime."""
40
+ import os
41
+ url = api_url or os.environ.get("RAIL_API_URL", "http://localhost:8000/api/v1")
42
+ key = api_key or os.environ.get("RAIL_API_KEY", "")
43
+ client = CloudClient(base_url=url, api_key=key)
44
+ return Project(slug=slug, backend=client)
45
+
46
+ def local(
47
+ path: str = ".",
48
+ engine_path: str | None = None,
49
+ ) -> Project:
50
+ """Load a KRAIL project from a local repo directory."""
51
+ engine = LocalEngine(project_path=path, engine_path=engine_path)
52
+ slug = engine.read_rail_yaml().project.slug
53
+ return Project(slug=slug, backend=engine)
rail/agent.py ADDED
@@ -0,0 +1,59 @@
1
+ import json
2
+ import httpx
3
+ from typing import Generator, AsyncGenerator
4
+
5
+ class AgentClient:
6
+ def __init__(self, base_url: str, project_slug: str, api_key: str = ""):
7
+ self.base_url = base_url.rstrip("/")
8
+ self.project_slug = project_slug
9
+ self.headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
10
+
11
+ def ask(self, question: str, stream: bool = False, model: str | None = None):
12
+ """Ask a question. Returns full answer text (stream=False) or event generator (stream=True)."""
13
+ if stream:
14
+ return self._stream(question, model)
15
+ else:
16
+ return self._blocking(question, model)
17
+
18
+ def _blocking(self, question: str, model: str | None) -> str:
19
+ """Collect all text_delta events and return the full answer."""
20
+ text = []
21
+ for event in self._stream(question, model):
22
+ if event.get("type") == "text_delta":
23
+ text.append(event.get("text", ""))
24
+ return "".join(text)
25
+
26
+ def _stream(self, question: str, model: str | None) -> Generator[dict, None, None]:
27
+ """Yield raw SSE event dicts."""
28
+ url = f"{self.base_url}/agent/chat?project={self.project_slug}"
29
+ payload = {"message": question, "history": []}
30
+ if model:
31
+ payload["model"] = model
32
+
33
+ with httpx.Client(timeout=300) as client:
34
+ with client.stream("POST", url, json=payload, headers=self.headers) as resp:
35
+ resp.raise_for_status()
36
+ for line in resp.iter_lines():
37
+ if line.startswith("data: "):
38
+ try:
39
+ event = json.loads(line[6:])
40
+ yield event
41
+ except json.JSONDecodeError:
42
+ pass
43
+
44
+ async def ask_async(self, question: str, model: str | None = None) -> AsyncGenerator[dict, None]:
45
+ """Async streaming version."""
46
+ url = f"{self.base_url}/agent/chat?project={self.project_slug}"
47
+ payload = {"message": question, "history": []}
48
+ if model:
49
+ payload["model"] = model
50
+
51
+ async with httpx.AsyncClient(timeout=300) as client:
52
+ async with client.stream("POST", url, json=payload, headers=self.headers) as resp:
53
+ resp.raise_for_status()
54
+ async for line in resp.aiter_lines():
55
+ if line.startswith("data: "):
56
+ try:
57
+ yield json.loads(line[6:])
58
+ except json.JSONDecodeError:
59
+ pass