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.
- grapheinstein/__init__.py +3 -0
- grapheinstein/__main__.py +6 -0
- grapheinstein/api.py +195 -0
- grapheinstein/cli.py +938 -0
- grapheinstein/core/__init__.py +1 -0
- grapheinstein/core/cache.py +287 -0
- grapheinstein/core/explain.py +344 -0
- grapheinstein/core/graph.py +1008 -0
- grapheinstein/core/index.py +440 -0
- grapheinstein/core/match.py +192 -0
- grapheinstein/core/merge.py +204 -0
- grapheinstein/core/parsers/__init__.py +25 -0
- grapheinstein/core/parsers/docs.py +198 -0
- grapheinstein/core/parsers/extract.py +298 -0
- grapheinstein/core/parsers/llm_enrich.py +387 -0
- grapheinstein/core/parsers/llm_ollama.py +322 -0
- grapheinstein/core/parsers/media_av.py +163 -0
- grapheinstein/core/parsers/media_link.py +173 -0
- grapheinstein/core/parsers/media_ocr.py +106 -0
- grapheinstein/core/parsers/pdf.py +103 -0
- grapheinstein/core/parsers/queries/__init__.py +141 -0
- grapheinstein/core/parsers/registry.py +156 -0
- grapheinstein/core/parsers/resolve.py +310 -0
- grapheinstein/core/parsers/resolve_docs.py +145 -0
- grapheinstein/core/path.py +593 -0
- grapheinstein/core/query.py +706 -0
- grapheinstein/core/references.py +85 -0
- grapheinstein/core/visualize.py +107 -0
- grapheinstein/serve/__init__.py +50 -0
- grapheinstein/serve/app.py +161 -0
- grapheinstein/utils.py +624 -0
- grapheinstein-0.1.0.dist-info/METADATA +148 -0
- grapheinstein-0.1.0.dist-info/RECORD +37 -0
- grapheinstein-0.1.0.dist-info/WHEEL +5 -0
- grapheinstein-0.1.0.dist-info/entry_points.txt +2 -0
- grapheinstein-0.1.0.dist-info/licenses/LICENSE +21 -0
- grapheinstein-0.1.0.dist-info/top_level.txt +1 -0
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)
|