pretensor 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 (198) hide show
  1. pretensor/__init__.py +50 -0
  2. pretensor/benchmark/__init__.py +54 -0
  3. pretensor/benchmark/cli.py +294 -0
  4. pretensor/benchmark/fixtures.py +84 -0
  5. pretensor/benchmark/l1/__init__.py +23 -0
  6. pretensor/benchmark/l1/metrics.py +141 -0
  7. pretensor/benchmark/l1/pipeline.py +188 -0
  8. pretensor/benchmark/l1/runner.py +245 -0
  9. pretensor/benchmark/l2/__init__.py +27 -0
  10. pretensor/benchmark/l2/gold.py +236 -0
  11. pretensor/benchmark/l2/metrics.py +146 -0
  12. pretensor/benchmark/l2/pipeline.py +124 -0
  13. pretensor/benchmark/l2/runner.py +530 -0
  14. pretensor/benchmark/l3/__init__.py +73 -0
  15. pretensor/benchmark/l3/agent.py +316 -0
  16. pretensor/benchmark/l3/db.py +188 -0
  17. pretensor/benchmark/l3/gold.py +85 -0
  18. pretensor/benchmark/l3/llm_client.py +395 -0
  19. pretensor/benchmark/l3/mcp_client.py +357 -0
  20. pretensor/benchmark/l3/pretensor_runner.py +456 -0
  21. pretensor/benchmark/l3/prompt.py +132 -0
  22. pretensor/benchmark/l3/runner.py +358 -0
  23. pretensor/benchmark/l3/sql_equivalence.py +176 -0
  24. pretensor/benchmark/release_gate.py +448 -0
  25. pretensor/benchmark/results.py +298 -0
  26. pretensor/benchmark/runner.py +109 -0
  27. pretensor/cli/__init__.py +1 -0
  28. pretensor/cli/commands/_source_runner.py +147 -0
  29. pretensor/cli/commands/analyze.py +201 -0
  30. pretensor/cli/commands/connections/__init__.py +7 -0
  31. pretensor/cli/commands/connections/add_remove.py +126 -0
  32. pretensor/cli/commands/connections/register.py +12 -0
  33. pretensor/cli/commands/export.py +131 -0
  34. pretensor/cli/commands/index.py +559 -0
  35. pretensor/cli/commands/list.py +76 -0
  36. pretensor/cli/commands/quickstart.py +207 -0
  37. pretensor/cli/commands/reindex.py +646 -0
  38. pretensor/cli/commands/semantic.py +190 -0
  39. pretensor/cli/commands/serve.py +144 -0
  40. pretensor/cli/commands/sync_grants.py +149 -0
  41. pretensor/cli/commands/validate.py +176 -0
  42. pretensor/cli/config_file.py +442 -0
  43. pretensor/cli/constants.py +10 -0
  44. pretensor/cli/dbt_enrichment.py +96 -0
  45. pretensor/cli/main.py +109 -0
  46. pretensor/cli/paths.py +43 -0
  47. pretensor/cli/plugin.py +52 -0
  48. pretensor/config.py +226 -0
  49. pretensor/connectors/__init__.py +29 -0
  50. pretensor/connectors/base.py +165 -0
  51. pretensor/connectors/bigquery.py +468 -0
  52. pretensor/connectors/inspect.py +321 -0
  53. pretensor/connectors/lineage_sqlglot.py +97 -0
  54. pretensor/connectors/models.py +130 -0
  55. pretensor/connectors/mysql.py +402 -0
  56. pretensor/connectors/pg_array_parse.py +53 -0
  57. pretensor/connectors/postgres.py +938 -0
  58. pretensor/connectors/registry.py +93 -0
  59. pretensor/connectors/snapshot.py +244 -0
  60. pretensor/connectors/snowflake.py +908 -0
  61. pretensor/core/__init__.py +1 -0
  62. pretensor/core/builder.py +307 -0
  63. pretensor/core/dsn_crypto.py +51 -0
  64. pretensor/core/graph_schema_manager.py +246 -0
  65. pretensor/core/graph_store.py +1226 -0
  66. pretensor/core/ids.py +101 -0
  67. pretensor/core/portable_export.py +276 -0
  68. pretensor/core/query_runner.py +67 -0
  69. pretensor/core/registry.py +209 -0
  70. pretensor/core/schema.py +473 -0
  71. pretensor/core/secure_io.py +93 -0
  72. pretensor/core/store.py +469 -0
  73. pretensor/enrichment/__init__.py +1 -0
  74. pretensor/enrichment/analyze/__init__.py +0 -0
  75. pretensor/enrichment/analyze/classify.py +49 -0
  76. pretensor/enrichment/analyze/extract_python.py +196 -0
  77. pretensor/enrichment/analyze/parse.py +141 -0
  78. pretensor/enrichment/analyze/pipeline.py +195 -0
  79. pretensor/enrichment/analyze/summary.py +38 -0
  80. pretensor/enrichment/analyze/walker.py +98 -0
  81. pretensor/enrichment/analyze/writers.py +214 -0
  82. pretensor/enrichment/dbt/__init__.py +30 -0
  83. pretensor/enrichment/dbt/lineage.py +100 -0
  84. pretensor/enrichment/dbt/manifest.py +300 -0
  85. pretensor/enrichment/dbt/metadata.py +263 -0
  86. pretensor/enrichment/dbt/pipeline.py +77 -0
  87. pretensor/enrichment/dbt/resolution.py +101 -0
  88. pretensor/enrichment/dbt/signals.py +305 -0
  89. pretensor/entities/__init__.py +27 -0
  90. pretensor/entities/builder.py +63 -0
  91. pretensor/entities/classifier.py +383 -0
  92. pretensor/entities/llm_extract.py +66 -0
  93. pretensor/errors.py +35 -0
  94. pretensor/graph_models/__init__.py +17 -0
  95. pretensor/graph_models/base.py +11 -0
  96. pretensor/graph_models/consumer.py +71 -0
  97. pretensor/graph_models/edge.py +35 -0
  98. pretensor/graph_models/entity.py +21 -0
  99. pretensor/graph_models/node.py +79 -0
  100. pretensor/graph_models/relationship.py +33 -0
  101. pretensor/integrations/__init__.py +42 -0
  102. pretensor/integrations/_base.py +138 -0
  103. pretensor/integrations/google_adk.py +49 -0
  104. pretensor/integrations/langchain.py +55 -0
  105. pretensor/integrations/llamaindex.py +53 -0
  106. pretensor/intelligence/__init__.py +33 -0
  107. pretensor/intelligence/cluster_labeler.py +425 -0
  108. pretensor/intelligence/clustering.py +168 -0
  109. pretensor/intelligence/combining.py +32 -0
  110. pretensor/intelligence/discovery.py +114 -0
  111. pretensor/intelligence/embeddings.py +317 -0
  112. pretensor/intelligence/graph_export.py +200 -0
  113. pretensor/intelligence/heuristic.py +544 -0
  114. pretensor/intelligence/join_paths/__init__.py +130 -0
  115. pretensor/intelligence/join_paths/on_demand.py +516 -0
  116. pretensor/intelligence/join_paths/storage.py +70 -0
  117. pretensor/intelligence/llm_infer.py +78 -0
  118. pretensor/intelligence/llm_runtime.py +62 -0
  119. pretensor/intelligence/metric_templates.py +193 -0
  120. pretensor/intelligence/pipeline.py +364 -0
  121. pretensor/intelligence/role_exemplars.py +263 -0
  122. pretensor/intelligence/schema_classification.py +360 -0
  123. pretensor/intelligence/scoring.py +76 -0
  124. pretensor/intelligence/semantic.py +240 -0
  125. pretensor/intelligence/shadow_alias.py +101 -0
  126. pretensor/intelligence/statistical.py +50 -0
  127. pretensor/intelligence/steps.py +191 -0
  128. pretensor/intelligence/steps_embedding.py +168 -0
  129. pretensor/introspection/__init__.py +6 -0
  130. pretensor/introspection/inspector.py +5 -0
  131. pretensor/introspection/models/__init__.py +0 -0
  132. pretensor/introspection/models/base.py +5 -0
  133. pretensor/introspection/models/config.py +237 -0
  134. pretensor/introspection/models/dsn.py +550 -0
  135. pretensor/introspection/models/plan.py +116 -0
  136. pretensor/introspection/models/schema.py +10 -0
  137. pretensor/introspection/models/semantic.py +121 -0
  138. pretensor/introspection/models/validation.py +116 -0
  139. pretensor/introspection/snapshot.py +46 -0
  140. pretensor/mcp/__init__.py +16 -0
  141. pretensor/mcp/config_json.py +24 -0
  142. pretensor/mcp/payload_types.py +274 -0
  143. pretensor/mcp/resources/__init__.py +17 -0
  144. pretensor/mcp/resources/markdown.py +314 -0
  145. pretensor/mcp/server.py +285 -0
  146. pretensor/mcp/service.py +49 -0
  147. pretensor/mcp/service_context.py +142 -0
  148. pretensor/mcp/service_registry.py +294 -0
  149. pretensor/mcp/store_cache.py +43 -0
  150. pretensor/mcp/tool_registry.py +136 -0
  151. pretensor/mcp/tools/__init__.py +1 -0
  152. pretensor/mcp/tools/_rank.py +244 -0
  153. pretensor/mcp/tools/_timed.py +26 -0
  154. pretensor/mcp/tools/compile_metric.py +144 -0
  155. pretensor/mcp/tools/consumers.py +161 -0
  156. pretensor/mcp/tools/context.py +1121 -0
  157. pretensor/mcp/tools/cypher.py +509 -0
  158. pretensor/mcp/tools/detect_changes.py +254 -0
  159. pretensor/mcp/tools/impact.py +271 -0
  160. pretensor/mcp/tools/list.py +131 -0
  161. pretensor/mcp/tools/schema.py +170 -0
  162. pretensor/mcp/tools/search.py +316 -0
  163. pretensor/mcp/tools/semantic_search.py +282 -0
  164. pretensor/mcp/tools/traverse.py +1027 -0
  165. pretensor/mcp/tools/validate_sql.py +150 -0
  166. pretensor/observability.py +203 -0
  167. pretensor/py.typed +0 -0
  168. pretensor/quickstart/README.md +29 -0
  169. pretensor/quickstart/__init__.py +6 -0
  170. pretensor/quickstart/docker-compose.yml +18 -0
  171. pretensor/quickstart/pagila_data.sql +63 -0
  172. pretensor/quickstart/pagila_ddl.sql +92 -0
  173. pretensor/search/__init__.py +6 -0
  174. pretensor/search/base.py +80 -0
  175. pretensor/search/index.py +435 -0
  176. pretensor/semantic/__init__.py +24 -0
  177. pretensor/semantic/base.py +123 -0
  178. pretensor/semantic/compiler.py +487 -0
  179. pretensor/semantic/yaml_layer.py +180 -0
  180. pretensor/skills/__init__.py +5 -0
  181. pretensor/skills/generator.py +235 -0
  182. pretensor/staleness/__init__.py +15 -0
  183. pretensor/staleness/graph_patcher.py +355 -0
  184. pretensor/staleness/impact_analyzer.py +162 -0
  185. pretensor/staleness/snapshot_store.py +38 -0
  186. pretensor/validation/__init__.py +9 -0
  187. pretensor/validation/query_validator.py +436 -0
  188. pretensor/visibility/__init__.py +23 -0
  189. pretensor/visibility/config.py +126 -0
  190. pretensor/visibility/filter.py +143 -0
  191. pretensor/visibility/kuzu_helpers.py +32 -0
  192. pretensor/visibility/runtime.py +36 -0
  193. pretensor/visibility/sync_grants.py +188 -0
  194. pretensor-0.1.0.dist-info/METADATA +251 -0
  195. pretensor-0.1.0.dist-info/RECORD +198 -0
  196. pretensor-0.1.0.dist-info/WHEEL +4 -0
  197. pretensor-0.1.0.dist-info/entry_points.txt +2 -0
  198. pretensor-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,282 @@
1
+ """MCP ``semantic_search`` tool — cosine ranking over ``SchemaTable.embedding``.
2
+
3
+ Read-only. Additive signal. Returns a structured ``fallback_bm25`` envelope
4
+ when the ``[embeddings]`` extra is absent or no candidate table carries a
5
+ vector — per the determinism contract, the tool must never raise
6
+ from its happy path and must never create graph edges.
7
+
8
+ At index time, each table is embedded as ``"{schema}.{table}: cols..."``.
9
+ At query time, the natural-language ``query`` string is embedded **raw**
10
+ (asymmetric retrieval); callers who want lexical search should use the
11
+ BM25 ``query`` tool.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from pretensor.core.registry import RegistryEntry
21
+ from pretensor.intelligence.embeddings import (
22
+ EmbeddingClient,
23
+ embeddings_disabled_via_env,
24
+ get_default_embedding_client,
25
+ )
26
+ from pretensor.mcp.tool_registry import McpTool
27
+ from pretensor.observability import log_timed_operation
28
+ from pretensor.visibility.filter import VisibilityFilter
29
+
30
+ from ..payload_types import SemanticHit, snippet
31
+ from ..service_context import get_effective_visibility_filter
32
+ from ..service_registry import (
33
+ load_registry,
34
+ resolve_registry_entry,
35
+ )
36
+ from ._rank import CosineHit, any_entry_has_vectors, cosine_rank_tables
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ _K_MIN = 1
41
+ _K_MAX = 50
42
+ _FALLBACK_HINT = "Install `pretensor[embeddings]` and reindex, or use the `query` tool."
43
+
44
+
45
+ def _fallback(
46
+ *,
47
+ query: str,
48
+ database: str | None,
49
+ cluster: str | None,
50
+ hint: str | None = None,
51
+ ) -> dict[str, Any]:
52
+ """Return the documented ``fallback_bm25`` envelope.
53
+
54
+ Extended with ``query`` / ``database`` / ``cluster`` echo so callers can
55
+ log and correlate without branching on envelope shape. Reserved for
56
+ "embeddings unavailable" (extra absent, disabled, or no vectors) —
57
+ caller mistakes such as an unresolved ``database`` get a plain error
58
+ envelope instead, matching sibling tools.
59
+ """
60
+ return {
61
+ "mode": "fallback_bm25",
62
+ "hint": hint or _FALLBACK_HINT,
63
+ "results": [],
64
+ "query": query,
65
+ "database": database,
66
+ "cluster": cluster,
67
+ }
68
+
69
+
70
+ def _select_entries(
71
+ graph_dir: Path, database: str | None
72
+ ) -> tuple[list[RegistryEntry], str | None]:
73
+ """Resolve candidate registry entries; return ``(entries, error)``."""
74
+ reg = load_registry(graph_dir)
75
+ entries = reg.list_entries()
76
+ if not entries:
77
+ return [], None # empty registry → normal fallback, not an error
78
+ if database is None:
79
+ return entries, None
80
+ resolved = resolve_registry_entry(reg, database)
81
+ if resolved is None:
82
+ return [], f"Unknown database: {database!r}"
83
+ return [resolved], None
84
+
85
+
86
+ def _hit_from_cosine(hit: CosineHit) -> SemanticHit:
87
+ result: SemanticHit = {
88
+ "node_type": "SchemaTable",
89
+ "name": (
90
+ f"{hit.schema_name}.{hit.table_name}" if hit.schema_name else hit.table_name
91
+ ),
92
+ "database_name": hit.database,
93
+ "connection_name": hit.connection_name,
94
+ "description": hit.description,
95
+ "snippet": snippet(hit.description),
96
+ "score": hit.score,
97
+ "cluster_id": hit.cluster_id,
98
+ }
99
+ return result
100
+
101
+
102
+ def semantic_search_payload(
103
+ graph_dir: Path,
104
+ *,
105
+ query: str,
106
+ k: int = 10,
107
+ database: str | None = None,
108
+ cluster: str | None = None,
109
+ visibility_filter: VisibilityFilter | None = None,
110
+ embedding_client: EmbeddingClient | None = None,
111
+ ) -> dict[str, Any]:
112
+ """Top-K ``SchemaTable`` ids by cosine similarity against ``SchemaTable.embedding``.
113
+
114
+ Returns the happy-path envelope ``{mode: "semantic", query, database,
115
+ cluster, results: [...]}`` when embeddings are available, otherwise the
116
+ documented ``fallback_bm25`` envelope. The response never raises; any
117
+ ``ImportError`` from the embedding client (missing ``[embeddings]``
118
+ extra) or unexpected runtime failure funnels to the fallback envelope
119
+ with a single ``WARNING`` logged.
120
+ """
121
+ k_clamped = max(_K_MIN, min(_K_MAX, int(k)))
122
+ with log_timed_operation(
123
+ logger,
124
+ event="mcp.semantic_search_payload",
125
+ query=query,
126
+ database=database,
127
+ cluster=cluster,
128
+ k=k_clamped,
129
+ graph_dir=str(graph_dir),
130
+ ):
131
+ # Kill switch wins over everything, including an injected client,
132
+ # so PRETENSOR_EMBEDDINGS_DISABLED=1 yields the same fallback
133
+ # envelope as a missing extra.
134
+ if embeddings_disabled_via_env():
135
+ return _fallback(
136
+ query=query,
137
+ database=database,
138
+ cluster=cluster,
139
+ hint=(
140
+ "Embeddings are disabled via PRETENSOR_EMBEDDINGS_DISABLED; "
141
+ "unset it to enable semantic ranking, or use the `query` tool."
142
+ ),
143
+ )
144
+
145
+ vf = visibility_filter or get_effective_visibility_filter()
146
+
147
+ entries, error = _select_entries(graph_dir, database)
148
+ if error is not None:
149
+ # Invalid required argument: surface a plain error envelope so
150
+ # callers can correct the database name, matching the contract
151
+ # of sibling tools (`context`, `traverse`). The fallback_bm25
152
+ # envelope is reserved for "embeddings unavailable", not for
153
+ # caller mistakes.
154
+ return {
155
+ "error": error,
156
+ "query": query,
157
+ "database": database,
158
+ "cluster": cluster,
159
+ "hint": (
160
+ "Resolve the database name with the `list_databases` "
161
+ "tool, then retry."
162
+ ),
163
+ }
164
+ if not entries:
165
+ return _fallback(query=query, database=database, cluster=cluster)
166
+
167
+ # Probe for stored vectors BEFORE embedding the query: with none
168
+ # present the response is the fallback envelope regardless, and on
169
+ # a cold process the first embed() call would trigger the ONNX
170
+ # model download + session init for nothing.
171
+ if not any_entry_has_vectors(entries):
172
+ return _fallback(query=query, database=database, cluster=cluster)
173
+
174
+ # Embed the natural-language query as-is. The indexed documents use
175
+ # ``"{schema}.{table}: cols..."``; asymmetric CLS-pooled retrieval.
176
+ # Construction is I/O-free (the ONNX session loads lazily inside
177
+ # ``embed()``); only the embed() call can raise, and it's caught
178
+ # below to route to the never-raises fallback envelope.
179
+ client: EmbeddingClient = embedding_client or get_default_embedding_client()
180
+ try:
181
+ vectors = client.embed([query])
182
+ except ImportError as exc:
183
+ logger.warning("semantic_search: [embeddings] extra not installed: %s", exc)
184
+ return _fallback(query=query, database=database, cluster=cluster)
185
+ except Exception as exc: # noqa: BLE001 — tool must never raise
186
+ logger.warning(
187
+ "semantic_search: embedding client failed (%s); returning fallback",
188
+ exc,
189
+ )
190
+ return _fallback(query=query, database=database, cluster=cluster)
191
+
192
+ if not vectors:
193
+ # NullEmbeddingClient path.
194
+ return _fallback(query=query, database=database, cluster=cluster)
195
+
196
+ qvec = vectors[0]
197
+
198
+ cosine_hits, any_candidate_had_vector = cosine_rank_tables(
199
+ entries,
200
+ qvec,
201
+ database=database,
202
+ cluster=cluster,
203
+ visibility_filter=vf,
204
+ k=k_clamped,
205
+ )
206
+
207
+ if not any_candidate_had_vector:
208
+ return _fallback(query=query, database=database, cluster=cluster)
209
+
210
+ results = [_hit_from_cosine(h) for h in cosine_hits]
211
+
212
+ return {
213
+ "mode": "semantic",
214
+ "query": query,
215
+ "database": database,
216
+ "cluster": cluster,
217
+ "results": results,
218
+ }
219
+
220
+
221
+ __all__ = ["create_tool", "semantic_search_payload"]
222
+
223
+
224
+ def create_tool(graph_dir: Path) -> McpTool:
225
+ from ._timed import timed_tool
226
+
227
+ async def _handle(args: dict) -> dict:
228
+ q = str(args.get("query", "")).strip()
229
+ if not q:
230
+ return {"error": "Missing or empty `query`"}
231
+ k = int(args.get("k", 10))
232
+ database = args.get("database")
233
+ database_s = str(database) if database is not None else None
234
+ cluster = args.get("cluster")
235
+ cluster_s = str(cluster) if cluster is not None else None
236
+ with timed_tool(
237
+ "semantic_search", graph_dir, database=database_s, cluster=cluster_s, k=k
238
+ ):
239
+ return semantic_search_payload(
240
+ graph_dir,
241
+ query=q,
242
+ k=k,
243
+ database=database_s,
244
+ cluster=cluster_s,
245
+ )
246
+
247
+ return McpTool(
248
+ name="semantic_search",
249
+ description=(
250
+ "Natural-language semantic search over SchemaTable nodes by cosine similarity "
251
+ "against the persisted embedding column. Requires the `[embeddings]` extra — "
252
+ "falls back to a structured hint pointing at the `query` (BM25) tool when the "
253
+ "extra is absent or no tables carry vectors."
254
+ ),
255
+ input_schema={
256
+ "type": "object",
257
+ "properties": {
258
+ "query": {
259
+ "type": "string",
260
+ "description": "Natural-language query to embed and match against table vectors",
261
+ },
262
+ "k": {
263
+ "type": "integer",
264
+ "default": 10,
265
+ "minimum": 1,
266
+ "maximum": 50,
267
+ "description": "Maximum number of hits to return (clamped to 1..50)",
268
+ },
269
+ "database": {
270
+ "type": ["string", "null"],
271
+ "description": "Restrict scan to one connection_name or logical database",
272
+ },
273
+ "cluster": {
274
+ "type": ["string", "null"],
275
+ "description": "Restrict scan to one Cluster id (node_id)",
276
+ },
277
+ },
278
+ "required": ["query"],
279
+ "additionalProperties": False,
280
+ },
281
+ handler=_handle,
282
+ )