aethergraph 0.1.0a3__py3-none-any.whl → 0.1.0a4__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.
- aethergraph/api/v1/artifacts.py +23 -4
- aethergraph/api/v1/schemas.py +7 -0
- aethergraph/api/v1/session.py +123 -4
- aethergraph/config/config.py +2 -0
- aethergraph/config/search.py +49 -0
- aethergraph/contracts/services/channel.py +18 -1
- aethergraph/contracts/services/execution.py +58 -0
- aethergraph/contracts/services/llm.py +26 -0
- aethergraph/contracts/services/memory.py +10 -4
- aethergraph/contracts/services/planning.py +53 -0
- aethergraph/contracts/storage/event_log.py +8 -0
- aethergraph/contracts/storage/search_backend.py +47 -0
- aethergraph/contracts/storage/vector_index.py +73 -0
- aethergraph/core/graph/action_spec.py +76 -0
- aethergraph/core/graph/graph_fn.py +75 -2
- aethergraph/core/graph/graphify.py +74 -2
- aethergraph/core/runtime/graph_runner.py +2 -1
- aethergraph/core/runtime/node_context.py +66 -3
- aethergraph/core/runtime/node_services.py +8 -0
- aethergraph/core/runtime/run_manager.py +263 -271
- aethergraph/core/runtime/run_types.py +54 -1
- aethergraph/core/runtime/runtime_env.py +35 -14
- aethergraph/core/runtime/runtime_services.py +308 -18
- aethergraph/plugins/agents/default_chat_agent.py +266 -74
- aethergraph/plugins/agents/default_chat_agent_v2.py +487 -0
- aethergraph/plugins/channel/adapters/webui.py +69 -21
- aethergraph/plugins/channel/routes/webui_routes.py +8 -48
- aethergraph/runtime/__init__.py +12 -0
- aethergraph/server/app_factory.py +3 -0
- aethergraph/server/ui_static/assets/index-CFktGdbW.js +4913 -0
- aethergraph/server/ui_static/assets/index-DcfkFlTA.css +1 -0
- aethergraph/server/ui_static/index.html +2 -2
- aethergraph/services/artifacts/facade.py +157 -21
- aethergraph/services/artifacts/types.py +35 -0
- aethergraph/services/artifacts/utils.py +42 -0
- aethergraph/services/channel/channel_bus.py +3 -1
- aethergraph/services/channel/event_hub copy.py +55 -0
- aethergraph/services/channel/event_hub.py +81 -0
- aethergraph/services/channel/factory.py +3 -2
- aethergraph/services/channel/session.py +709 -74
- aethergraph/services/container/default_container.py +69 -7
- aethergraph/services/execution/__init__.py +0 -0
- aethergraph/services/execution/local_python.py +118 -0
- aethergraph/services/indices/__init__.py +0 -0
- aethergraph/services/indices/global_indices.py +21 -0
- aethergraph/services/indices/scoped_indices.py +292 -0
- aethergraph/services/llm/generic_client.py +342 -46
- aethergraph/services/llm/generic_embed_client.py +359 -0
- aethergraph/services/llm/types.py +3 -1
- aethergraph/services/memory/distillers/llm_long_term.py +60 -109
- aethergraph/services/memory/distillers/llm_long_term_v1.py +180 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +57 -266
- aethergraph/services/memory/distillers/llm_meta_summary_v1.py +342 -0
- aethergraph/services/memory/distillers/long_term.py +48 -131
- aethergraph/services/memory/distillers/long_term_v1.py +170 -0
- aethergraph/services/memory/facade/chat.py +18 -8
- aethergraph/services/memory/facade/core.py +159 -19
- aethergraph/services/memory/facade/distillation.py +86 -31
- aethergraph/services/memory/facade/retrieval.py +100 -1
- aethergraph/services/memory/factory.py +4 -1
- aethergraph/services/planning/__init__.py +0 -0
- aethergraph/services/planning/action_catalog.py +271 -0
- aethergraph/services/planning/bindings.py +56 -0
- aethergraph/services/planning/dependency_index.py +65 -0
- aethergraph/services/planning/flow_validator.py +263 -0
- aethergraph/services/planning/graph_io_adapter.py +150 -0
- aethergraph/services/planning/input_parser.py +312 -0
- aethergraph/services/planning/missing_inputs.py +28 -0
- aethergraph/services/planning/node_planner.py +613 -0
- aethergraph/services/planning/orchestrator.py +112 -0
- aethergraph/services/planning/plan_executor.py +506 -0
- aethergraph/services/planning/plan_types.py +321 -0
- aethergraph/services/planning/planner.py +617 -0
- aethergraph/services/planning/planner_service.py +369 -0
- aethergraph/services/planning/planning_context_builder.py +43 -0
- aethergraph/services/planning/quick_actions.py +29 -0
- aethergraph/services/planning/routers/__init__.py +0 -0
- aethergraph/services/planning/routers/simple_router.py +26 -0
- aethergraph/services/rag/facade.py +0 -3
- aethergraph/services/scope/scope.py +30 -30
- aethergraph/services/scope/scope_factory.py +15 -7
- aethergraph/services/skills/__init__.py +0 -0
- aethergraph/services/skills/skill_registry.py +465 -0
- aethergraph/services/skills/skills.py +220 -0
- aethergraph/services/skills/utils.py +194 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +16 -10
- aethergraph/storage/artifacts/artifact_index_sqlite.py +12 -2
- aethergraph/storage/docstore/sqlite_doc_sync.py +1 -1
- aethergraph/storage/memory/event_persist.py +42 -2
- aethergraph/storage/memory/fs_persist.py +32 -2
- aethergraph/storage/search_backend/__init__.py +0 -0
- aethergraph/storage/search_backend/generic_vector_backend.py +230 -0
- aethergraph/storage/search_backend/null_backend.py +34 -0
- aethergraph/storage/search_backend/sqlite_lexical_backend.py +387 -0
- aethergraph/storage/search_backend/utils.py +31 -0
- aethergraph/storage/search_factory.py +75 -0
- aethergraph/storage/vector_index/faiss_index.py +72 -4
- aethergraph/storage/vector_index/sqlite_index.py +521 -52
- aethergraph/storage/vector_index/sqlite_index_vanila.py +311 -0
- aethergraph/storage/vector_index/utils.py +22 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/METADATA +1 -1
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/RECORD +107 -63
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/WHEEL +1 -1
- aethergraph/plugins/agents/default_chat_agent copy.py +0 -90
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +0 -1
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +0 -400
- aethergraph/services/eventhub/event_hub.py +0 -76
- aethergraph/services/llm/generic_client copy.py +0 -691
- aethergraph/services/prompts/file_store.py +0 -41
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/entry_points.txt +0 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sqlite3
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from aethergraph.contracts.storage.vector_index import VectorIndex
|
|
12
|
+
|
|
13
|
+
SCHEMA = """
|
|
14
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
15
|
+
corpus_id TEXT,
|
|
16
|
+
chunk_id TEXT,
|
|
17
|
+
meta_json TEXT,
|
|
18
|
+
PRIMARY KEY (corpus_id, chunk_id)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
22
|
+
corpus_id TEXT,
|
|
23
|
+
chunk_id TEXT,
|
|
24
|
+
vec BLOB, -- np.float32 array bytes
|
|
25
|
+
norm REAL,
|
|
26
|
+
-- promoted / hot fields
|
|
27
|
+
scope_id TEXT,
|
|
28
|
+
user_id TEXT,
|
|
29
|
+
org_id TEXT,
|
|
30
|
+
client_id TEXT,
|
|
31
|
+
session_id TEXT,
|
|
32
|
+
run_id TEXT,
|
|
33
|
+
graph_id TEXT,
|
|
34
|
+
node_id TEXT,
|
|
35
|
+
kind TEXT,
|
|
36
|
+
source TEXT,
|
|
37
|
+
created_at_ts REAL,
|
|
38
|
+
PRIMARY KEY (corpus_id, chunk_id)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_emb_corpus_scope_time
|
|
42
|
+
ON embeddings(corpus_id, scope_id, created_at_ts DESC);
|
|
43
|
+
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_emb_corpus_user_time
|
|
45
|
+
ON embeddings(corpus_id, user_id, created_at_ts DESC);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_emb_corpus_org_time
|
|
48
|
+
ON embeddings(corpus_id, org_id, created_at_ts DESC);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_emb_corpus_kind_time
|
|
51
|
+
ON embeddings(corpus_id, kind, created_at_ts DESC);
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _ensure_db(path: str) -> None:
|
|
56
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
conn = sqlite3.connect(path, check_same_thread=False)
|
|
58
|
+
try:
|
|
59
|
+
cur = conn.cursor()
|
|
60
|
+
for stmt in SCHEMA.strip().split(";\n\n"):
|
|
61
|
+
s = stmt.strip()
|
|
62
|
+
if s:
|
|
63
|
+
cur.execute(s)
|
|
64
|
+
conn.commit()
|
|
65
|
+
finally:
|
|
66
|
+
conn.close()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SQLiteVectorIndex(VectorIndex):
|
|
70
|
+
"""
|
|
71
|
+
Simple SQLite-backed vector index.
|
|
72
|
+
|
|
73
|
+
Uses brute-force cosine similarity per corpus.
|
|
74
|
+
|
|
75
|
+
Promoted fields you *may* pass in meta:
|
|
76
|
+
- scope_id, user_id, org_id, client_id, session_id
|
|
77
|
+
- run_id, graph_id, node_id
|
|
78
|
+
- kind, source
|
|
79
|
+
- created_at_ts (float UNIX timestamp)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, root: str):
|
|
83
|
+
self.root = Path(root)
|
|
84
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
self.db_path = str(self.root / "index.sqlite")
|
|
86
|
+
_ensure_db(self.db_path)
|
|
87
|
+
|
|
88
|
+
def _connect(self) -> sqlite3.Connection:
|
|
89
|
+
return sqlite3.connect(self.db_path, check_same_thread=False)
|
|
90
|
+
|
|
91
|
+
async def add(
|
|
92
|
+
self,
|
|
93
|
+
corpus_id: str,
|
|
94
|
+
chunk_ids: list[str],
|
|
95
|
+
vectors: list[list[float]],
|
|
96
|
+
metas: list[dict[str, Any]],
|
|
97
|
+
) -> None:
|
|
98
|
+
if not chunk_ids:
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
def _add_sync():
|
|
102
|
+
conn = self._connect()
|
|
103
|
+
try:
|
|
104
|
+
cur = conn.cursor()
|
|
105
|
+
for cid, vec, meta in zip(chunk_ids, vectors, metas, strict=True):
|
|
106
|
+
v = np.asarray(vec, dtype=np.float32)
|
|
107
|
+
norm = float(np.linalg.norm(v) + 1e-9)
|
|
108
|
+
|
|
109
|
+
meta_json = json.dumps(meta, ensure_ascii=False)
|
|
110
|
+
|
|
111
|
+
# promoted, optional
|
|
112
|
+
scope_id = meta.get("scope_id")
|
|
113
|
+
user_id = meta.get("user_id")
|
|
114
|
+
org_id = meta.get("org_id")
|
|
115
|
+
client_id = meta.get("client_id")
|
|
116
|
+
session_id = meta.get("session_id")
|
|
117
|
+
run_id = meta.get("run_id")
|
|
118
|
+
graph_id = meta.get("graph_id")
|
|
119
|
+
node_id = meta.get("node_id")
|
|
120
|
+
kind = meta.get("kind")
|
|
121
|
+
source = meta.get("source")
|
|
122
|
+
created_at_ts = meta.get("created_at_ts")
|
|
123
|
+
|
|
124
|
+
cur.execute(
|
|
125
|
+
"REPLACE INTO chunks(corpus_id,chunk_id,meta_json) VALUES(?,?,?)",
|
|
126
|
+
(corpus_id, cid, meta_json),
|
|
127
|
+
)
|
|
128
|
+
cur.execute(
|
|
129
|
+
"""
|
|
130
|
+
REPLACE INTO embeddings(
|
|
131
|
+
corpus_id,
|
|
132
|
+
chunk_id,
|
|
133
|
+
vec,
|
|
134
|
+
norm,
|
|
135
|
+
scope_id,
|
|
136
|
+
user_id,
|
|
137
|
+
org_id,
|
|
138
|
+
client_id,
|
|
139
|
+
session_id,
|
|
140
|
+
run_id,
|
|
141
|
+
graph_id,
|
|
142
|
+
node_id,
|
|
143
|
+
kind,
|
|
144
|
+
source,
|
|
145
|
+
created_at_ts
|
|
146
|
+
)
|
|
147
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
148
|
+
""",
|
|
149
|
+
(
|
|
150
|
+
corpus_id,
|
|
151
|
+
cid,
|
|
152
|
+
v.tobytes(),
|
|
153
|
+
norm,
|
|
154
|
+
scope_id,
|
|
155
|
+
user_id,
|
|
156
|
+
org_id,
|
|
157
|
+
client_id,
|
|
158
|
+
session_id,
|
|
159
|
+
run_id,
|
|
160
|
+
graph_id,
|
|
161
|
+
node_id,
|
|
162
|
+
kind,
|
|
163
|
+
source,
|
|
164
|
+
created_at_ts,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
conn.commit()
|
|
168
|
+
finally:
|
|
169
|
+
conn.close()
|
|
170
|
+
|
|
171
|
+
await asyncio.to_thread(_add_sync)
|
|
172
|
+
|
|
173
|
+
async def delete(self, corpus_id: str, chunk_ids: list[str] | None = None) -> None:
|
|
174
|
+
def _delete_sync():
|
|
175
|
+
conn = self._connect()
|
|
176
|
+
try:
|
|
177
|
+
cur = conn.cursor()
|
|
178
|
+
if chunk_ids:
|
|
179
|
+
placeholders = ",".join("?" for _ in chunk_ids)
|
|
180
|
+
cur.execute(
|
|
181
|
+
f"DELETE FROM chunks WHERE corpus_id=? AND chunk_id IN ({placeholders})",
|
|
182
|
+
[corpus_id, *chunk_ids],
|
|
183
|
+
)
|
|
184
|
+
cur.execute(
|
|
185
|
+
f"DELETE FROM embeddings WHERE corpus_id=? AND chunk_id IN ({placeholders})",
|
|
186
|
+
[corpus_id, *chunk_ids],
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
cur.execute("DELETE FROM chunks WHERE corpus_id=?", (corpus_id,))
|
|
190
|
+
cur.execute("DELETE FROM embeddings WHERE corpus_id=?", (corpus_id,))
|
|
191
|
+
conn.commit()
|
|
192
|
+
finally:
|
|
193
|
+
conn.close()
|
|
194
|
+
|
|
195
|
+
await asyncio.to_thread(_delete_sync)
|
|
196
|
+
|
|
197
|
+
async def list_chunks(self, corpus_id: str) -> list[str]:
|
|
198
|
+
def _list_sync() -> list[str]:
|
|
199
|
+
conn = self._connect()
|
|
200
|
+
try:
|
|
201
|
+
cur = conn.cursor()
|
|
202
|
+
cur.execute("SELECT chunk_id FROM chunks WHERE corpus_id=?", (corpus_id,))
|
|
203
|
+
return [r[0] for r in cur.fetchall()]
|
|
204
|
+
finally:
|
|
205
|
+
conn.close()
|
|
206
|
+
|
|
207
|
+
return await asyncio.to_thread(_list_sync)
|
|
208
|
+
|
|
209
|
+
async def list_corpora(self) -> list[str]:
|
|
210
|
+
def _list_sync() -> list[str]:
|
|
211
|
+
conn = self._connect()
|
|
212
|
+
try:
|
|
213
|
+
cur = conn.cursor()
|
|
214
|
+
cur.execute("SELECT DISTINCT corpus_id FROM chunks")
|
|
215
|
+
return [r[0] for r in cur.fetchall()]
|
|
216
|
+
finally:
|
|
217
|
+
conn.close()
|
|
218
|
+
|
|
219
|
+
return await asyncio.to_thread(_list_sync)
|
|
220
|
+
|
|
221
|
+
async def search(
|
|
222
|
+
self,
|
|
223
|
+
corpus_id: str,
|
|
224
|
+
query_vec: list[float],
|
|
225
|
+
k: int,
|
|
226
|
+
where: dict[str, Any] | None = None,
|
|
227
|
+
max_candidates: int | None = None,
|
|
228
|
+
created_at_min: float | None = None,
|
|
229
|
+
created_at_max: float | None = None,
|
|
230
|
+
) -> list[dict[str, Any]]:
|
|
231
|
+
q = np.asarray(query_vec, dtype=np.float32)
|
|
232
|
+
qn = float(np.linalg.norm(q) + 1e-9)
|
|
233
|
+
|
|
234
|
+
where = where or {}
|
|
235
|
+
|
|
236
|
+
def _search_sync() -> list[dict[str, Any]]:
|
|
237
|
+
conn = self._connect()
|
|
238
|
+
try:
|
|
239
|
+
cur = conn.cursor()
|
|
240
|
+
|
|
241
|
+
sql = """
|
|
242
|
+
SELECT e.chunk_id, e.vec, e.norm, c.meta_json
|
|
243
|
+
FROM embeddings e
|
|
244
|
+
JOIN chunks c
|
|
245
|
+
ON e.corpus_id = c.corpus_id AND e.chunk_id = c.chunk_id
|
|
246
|
+
WHERE e.corpus_id=?
|
|
247
|
+
"""
|
|
248
|
+
params: list[Any] = [corpus_id]
|
|
249
|
+
|
|
250
|
+
promoted_cols = {
|
|
251
|
+
"scope_id",
|
|
252
|
+
"user_id",
|
|
253
|
+
"org_id",
|
|
254
|
+
"client_id",
|
|
255
|
+
"session_id",
|
|
256
|
+
"run_id",
|
|
257
|
+
"graph_id",
|
|
258
|
+
"node_id",
|
|
259
|
+
"kind",
|
|
260
|
+
"source",
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# Equality predicates on promoted fields
|
|
264
|
+
for key, val in where.items():
|
|
265
|
+
if val is None:
|
|
266
|
+
continue
|
|
267
|
+
if key in promoted_cols:
|
|
268
|
+
sql += f" AND e.{key} = ?"
|
|
269
|
+
params.append(val)
|
|
270
|
+
|
|
271
|
+
# Time bounds (if any)
|
|
272
|
+
if created_at_min is not None:
|
|
273
|
+
sql += " AND e.created_at_ts >= ?"
|
|
274
|
+
params.append(created_at_min)
|
|
275
|
+
if created_at_max is not None:
|
|
276
|
+
sql += " AND e.created_at_ts <= ?"
|
|
277
|
+
params.append(created_at_max)
|
|
278
|
+
|
|
279
|
+
# Candidate bound + recency bias
|
|
280
|
+
candidate_limit = max_candidates or 5000
|
|
281
|
+
sql += " ORDER BY e.created_at_ts DESC"
|
|
282
|
+
sql += " LIMIT ?"
|
|
283
|
+
params.append(candidate_limit)
|
|
284
|
+
|
|
285
|
+
cur.execute(sql, params)
|
|
286
|
+
rows = cur.fetchall()
|
|
287
|
+
finally:
|
|
288
|
+
conn.close()
|
|
289
|
+
|
|
290
|
+
scored: list[tuple[float, str, dict[str, Any]]] = []
|
|
291
|
+
for chunk_id, vec_bytes, norm, meta_json in rows:
|
|
292
|
+
v = np.frombuffer(vec_bytes, dtype=np.float32)
|
|
293
|
+
score = float(np.dot(q, v) / (qn * (norm or 1e-9)))
|
|
294
|
+
meta = json.loads(meta_json)
|
|
295
|
+
scored.append((score, chunk_id, meta))
|
|
296
|
+
|
|
297
|
+
scored.sort(key=lambda x: x[0], reverse=True)
|
|
298
|
+
top = scored[:k]
|
|
299
|
+
|
|
300
|
+
out: list[dict[str, Any]] = []
|
|
301
|
+
for score, chunk_id, meta in top:
|
|
302
|
+
out.append(
|
|
303
|
+
{
|
|
304
|
+
"chunk_id": chunk_id,
|
|
305
|
+
"score": score,
|
|
306
|
+
"meta": meta,
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
return out
|
|
310
|
+
|
|
311
|
+
return await asyncio.to_thread(_search_sync)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from aethergraph.contracts.storage.vector_index import IndexMeta
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_index_meta_from_scope(
|
|
7
|
+
*,
|
|
8
|
+
kind: str | None,
|
|
9
|
+
source: str | None,
|
|
10
|
+
ts: str | None,
|
|
11
|
+
created_at_ts: float | None,
|
|
12
|
+
extra: dict[str, Any] | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
m = IndexMeta(
|
|
15
|
+
kind=kind,
|
|
16
|
+
source=source,
|
|
17
|
+
ts=ts,
|
|
18
|
+
created_at_ts=created_at_ts,
|
|
19
|
+
extra=extra or {},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return m.to_dict()
|