grapheinstein 0.1.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.
Files changed (37) hide show
  1. grapheinstein/__init__.py +3 -0
  2. grapheinstein/__main__.py +6 -0
  3. grapheinstein/api.py +195 -0
  4. grapheinstein/cli.py +938 -0
  5. grapheinstein/core/__init__.py +1 -0
  6. grapheinstein/core/cache.py +287 -0
  7. grapheinstein/core/explain.py +344 -0
  8. grapheinstein/core/graph.py +1008 -0
  9. grapheinstein/core/index.py +440 -0
  10. grapheinstein/core/match.py +192 -0
  11. grapheinstein/core/merge.py +204 -0
  12. grapheinstein/core/parsers/__init__.py +25 -0
  13. grapheinstein/core/parsers/docs.py +198 -0
  14. grapheinstein/core/parsers/extract.py +298 -0
  15. grapheinstein/core/parsers/llm_enrich.py +387 -0
  16. grapheinstein/core/parsers/llm_ollama.py +322 -0
  17. grapheinstein/core/parsers/media_av.py +163 -0
  18. grapheinstein/core/parsers/media_link.py +173 -0
  19. grapheinstein/core/parsers/media_ocr.py +106 -0
  20. grapheinstein/core/parsers/pdf.py +103 -0
  21. grapheinstein/core/parsers/queries/__init__.py +141 -0
  22. grapheinstein/core/parsers/registry.py +156 -0
  23. grapheinstein/core/parsers/resolve.py +310 -0
  24. grapheinstein/core/parsers/resolve_docs.py +145 -0
  25. grapheinstein/core/path.py +593 -0
  26. grapheinstein/core/query.py +706 -0
  27. grapheinstein/core/references.py +85 -0
  28. grapheinstein/core/visualize.py +107 -0
  29. grapheinstein/serve/__init__.py +50 -0
  30. grapheinstein/serve/app.py +161 -0
  31. grapheinstein/utils.py +624 -0
  32. grapheinstein-0.1.0.dist-info/METADATA +148 -0
  33. grapheinstein-0.1.0.dist-info/RECORD +37 -0
  34. grapheinstein-0.1.0.dist-info/WHEEL +5 -0
  35. grapheinstein-0.1.0.dist-info/entry_points.txt +2 -0
  36. grapheinstein-0.1.0.dist-info/licenses/LICENSE +21 -0
  37. grapheinstein-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ """Grapheinstein: local-first project knowledge graph CLI."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow `python -m grapheinstein`."""
2
+
3
+ from grapheinstein.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app() # argv-rewriting entry (supports bare project path)
grapheinstein/api.py ADDED
@@ -0,0 +1,195 @@
1
+ """Public Python API for agent / slash-command integration.
2
+
3
+ Importing this module MUST NOT require FastAPI or Uvicorn.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from collections.abc import Sequence
9
+ from dataclasses import asdict, dataclass, is_dataclass
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from grapheinstein.core.cache import CacheStore
14
+ from grapheinstein.core.graph import GraphError, GraphStats, load_artifact
15
+ from grapheinstein.core.index import MediaExtrasError, index_project
16
+ from grapheinstein.core.parsers import LanguageError, parse_languages_csv
17
+ from grapheinstein.core.query import (
18
+ EmptyCorpusError,
19
+ NoEvidenceError,
20
+ QueryError,
21
+ run_query,
22
+ )
23
+ from grapheinstein.utils import ConfigError, load_config, setup_logging
24
+
25
+ __all__ = [
26
+ "IndexResult",
27
+ "index",
28
+ "query",
29
+ "stats_to_dict",
30
+ "ConfigError",
31
+ "GraphError",
32
+ "MediaExtrasError",
33
+ "QueryError",
34
+ "EmptyCorpusError",
35
+ "NoEvidenceError",
36
+ ]
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class IndexResult:
41
+ """Result of a successful API index call."""
42
+
43
+ output_path: Path
44
+ stats: GraphStats | dict[str, Any]
45
+ artifact: dict[str, Any] | None = None
46
+
47
+
48
+ def _stats_as_dict(stats: GraphStats | dict[str, Any]) -> dict[str, Any]:
49
+ if isinstance(stats, dict):
50
+ return dict(stats)
51
+ if is_dataclass(stats):
52
+ return asdict(stats)
53
+ return {
54
+ "total_nodes": getattr(stats, "total_nodes", 0),
55
+ "graph_path": str(getattr(stats, "graph_path", "")),
56
+ }
57
+
58
+
59
+ def _normalize_languages(
60
+ languages: str | Sequence[str] | None,
61
+ ) -> list[str] | None:
62
+ if languages is None:
63
+ return None
64
+ if isinstance(languages, str):
65
+ try:
66
+ return list(parse_languages_csv(languages))
67
+ except LanguageError as exc:
68
+ raise ConfigError(str(exc)) from exc
69
+ try:
70
+ return list(languages)
71
+ except TypeError as exc:
72
+ raise ConfigError("languages must be a string or sequence of strings") from exc
73
+
74
+
75
+ def index(
76
+ project_path: str | Path,
77
+ *,
78
+ output: str | Path | None = None,
79
+ config: str | Path | None = None,
80
+ languages: str | Sequence[str] | None = None,
81
+ include_docs: bool = False,
82
+ include_pdfs: bool = False,
83
+ transcribe_media: bool = False,
84
+ enrich_llm: bool = False,
85
+ llm_model: str | None = None,
86
+ llm_base_url: str | None = None,
87
+ embedding_model: str | None = None,
88
+ compress: bool = False,
89
+ versioned: bool = False,
90
+ include_artifact: bool = False,
91
+ show_progress: bool = False,
92
+ ) -> IndexResult:
93
+ """
94
+ Index a project folder into a portable graph (CLI `index` semantics).
95
+
96
+ Raises FileNotFoundError, NotADirectoryError, OSError, ConfigError,
97
+ GraphError, MediaExtrasError on hard failures — never returns an empty success.
98
+ """
99
+ languages_override = _normalize_languages(languages)
100
+ cfg = load_config(
101
+ config_path=Path(config).expanduser() if config is not None else None,
102
+ output_override=Path(output).expanduser() if output is not None else None,
103
+ languages_override=languages_override,
104
+ llm_model_override=llm_model,
105
+ llm_base_url_override=llm_base_url,
106
+ embedding_model_override=embedding_model,
107
+ compress_override=True if compress else None,
108
+ versioned_override=True if versioned else None,
109
+ )
110
+ setup_logging(cfg.log_level)
111
+ output_path = Path(cfg.output)
112
+
113
+ written, stats = index_project(
114
+ Path(project_path),
115
+ output_path,
116
+ languages=list(cfg.languages),
117
+ include_docs=include_docs,
118
+ include_pdfs=include_pdfs,
119
+ transcribe_media=transcribe_media,
120
+ enrich_llm=enrich_llm,
121
+ llm_model=cfg.llm_model,
122
+ llm_base_url=cfg.llm_base_url,
123
+ llm_confidence_threshold=cfg.llm_confidence_threshold,
124
+ compress=cfg.compress,
125
+ versioned=cfg.versioned,
126
+ ignored_patterns=list(cfg.ignored_patterns),
127
+ max_file_size=cfg.max_file_size,
128
+ cache_dir=cfg.cache_dir,
129
+ embedding_model=cfg.embedding_model,
130
+ show_progress=show_progress,
131
+ )
132
+
133
+ artifact: dict[str, Any] | None = None
134
+ if include_artifact:
135
+ artifact = load_artifact(written)
136
+
137
+ return IndexResult(output_path=written, stats=stats, artifact=artifact)
138
+
139
+
140
+ def query(
141
+ question: str,
142
+ *,
143
+ input: str | Path,
144
+ output: str | Path | None = None,
145
+ config: str | Path | None = None,
146
+ k: int | None = None,
147
+ hops: int | None = None,
148
+ match_threshold: float | None = None,
149
+ no_answer: bool = False,
150
+ llm_model: str | None = None,
151
+ llm_base_url: str | None = None,
152
+ embedding_model: str | None = None,
153
+ ) -> dict[str, Any]:
154
+ """
155
+ Answer a natural-language question over a graph (CLI `query` semantics).
156
+
157
+ Returns the query-answer JSON envelope (schema_version 1.0.0).
158
+ """
159
+ input_path = Path(input).expanduser()
160
+ if output is not None:
161
+ output_path = Path(output).expanduser()
162
+ else:
163
+ output_path = input_path.parent / "subgraph.json"
164
+
165
+ cfg = load_config(
166
+ config_path=Path(config).expanduser() if config is not None else None,
167
+ llm_model_override=llm_model,
168
+ llm_base_url_override=llm_base_url,
169
+ embedding_model_override=embedding_model,
170
+ query_k_override=k,
171
+ query_hops_override=hops,
172
+ query_match_threshold_override=match_threshold,
173
+ )
174
+ setup_logging(cfg.log_level)
175
+
176
+ result = run_query(
177
+ question,
178
+ input_path,
179
+ output_path,
180
+ k=cfg.query_k,
181
+ hops=cfg.query_hops,
182
+ match_threshold=cfg.query_match_threshold,
183
+ node_cap=cfg.query_node_cap,
184
+ want_answer=not no_answer,
185
+ llm_model=cfg.llm_model,
186
+ llm_base_url=cfg.llm_base_url,
187
+ embedding_model=cfg.embedding_model,
188
+ cache=CacheStore(cfg.cache_dir),
189
+ )
190
+ return dict(result.answer_envelope)
191
+
192
+
193
+ def stats_to_dict(stats: GraphStats | dict[str, Any]) -> dict[str, Any]:
194
+ """Serialize index stats for HTTP / agents."""
195
+ return _stats_as_dict(stats)