aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__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/__init__.py +4 -10
- aethergraph/__main__.py +296 -0
- aethergraph/api/v1/__init__.py +0 -0
- aethergraph/api/v1/agents.py +46 -0
- aethergraph/api/v1/apps.py +70 -0
- aethergraph/api/v1/artifacts.py +415 -0
- aethergraph/api/v1/channels.py +89 -0
- aethergraph/api/v1/deps.py +168 -0
- aethergraph/api/v1/graphs.py +259 -0
- aethergraph/api/v1/identity.py +25 -0
- aethergraph/api/v1/memory.py +353 -0
- aethergraph/api/v1/misc.py +47 -0
- aethergraph/api/v1/pagination.py +29 -0
- aethergraph/api/v1/runs.py +568 -0
- aethergraph/api/v1/schemas.py +535 -0
- aethergraph/api/v1/session.py +323 -0
- aethergraph/api/v1/stats.py +201 -0
- aethergraph/api/v1/viz.py +152 -0
- aethergraph/config/config.py +22 -0
- aethergraph/config/loader.py +3 -2
- aethergraph/config/storage.py +209 -0
- aethergraph/contracts/__init__.py +0 -0
- aethergraph/contracts/services/__init__.py +0 -0
- aethergraph/contracts/services/artifacts.py +27 -14
- aethergraph/contracts/services/memory.py +45 -17
- aethergraph/contracts/services/metering.py +129 -0
- aethergraph/contracts/services/runs.py +50 -0
- aethergraph/contracts/services/sessions.py +87 -0
- aethergraph/contracts/services/state_stores.py +3 -0
- aethergraph/contracts/services/viz.py +44 -0
- aethergraph/contracts/storage/artifact_index.py +88 -0
- aethergraph/contracts/storage/artifact_store.py +99 -0
- aethergraph/contracts/storage/async_kv.py +34 -0
- aethergraph/contracts/storage/blob_store.py +50 -0
- aethergraph/contracts/storage/doc_store.py +35 -0
- aethergraph/contracts/storage/event_log.py +31 -0
- aethergraph/contracts/storage/vector_index.py +48 -0
- aethergraph/core/__init__.py +0 -0
- aethergraph/core/execution/forward_scheduler.py +13 -2
- aethergraph/core/execution/global_scheduler.py +21 -15
- aethergraph/core/execution/step_forward.py +10 -1
- aethergraph/core/graph/__init__.py +0 -0
- aethergraph/core/graph/graph_builder.py +8 -4
- aethergraph/core/graph/graph_fn.py +156 -15
- aethergraph/core/graph/graph_spec.py +8 -0
- aethergraph/core/graph/graphify.py +146 -27
- aethergraph/core/graph/node_spec.py +0 -2
- aethergraph/core/graph/node_state.py +3 -0
- aethergraph/core/graph/task_graph.py +39 -1
- aethergraph/core/runtime/__init__.py +0 -0
- aethergraph/core/runtime/ad_hoc_context.py +64 -4
- aethergraph/core/runtime/base_service.py +28 -4
- aethergraph/core/runtime/execution_context.py +13 -15
- aethergraph/core/runtime/graph_runner.py +222 -37
- aethergraph/core/runtime/node_context.py +510 -6
- aethergraph/core/runtime/node_services.py +12 -5
- aethergraph/core/runtime/recovery.py +15 -1
- aethergraph/core/runtime/run_manager.py +783 -0
- aethergraph/core/runtime/run_manager_local.py +204 -0
- aethergraph/core/runtime/run_registration.py +2 -2
- aethergraph/core/runtime/run_types.py +89 -0
- aethergraph/core/runtime/runtime_env.py +136 -7
- aethergraph/core/runtime/runtime_metering.py +71 -0
- aethergraph/core/runtime/runtime_registry.py +36 -13
- aethergraph/core/runtime/runtime_services.py +194 -6
- aethergraph/core/tools/builtins/toolset.py +1 -1
- aethergraph/core/tools/toolkit.py +5 -0
- aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
- aethergraph/plugins/agents/default_chat_agent.py +171 -0
- aethergraph/plugins/agents/shared.py +81 -0
- aethergraph/plugins/channel/adapters/webui.py +112 -112
- aethergraph/plugins/channel/routes/webui_routes.py +367 -102
- aethergraph/plugins/channel/utils/slack_utils.py +115 -59
- aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
- aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
- aethergraph/runtime/__init__.py +15 -0
- aethergraph/server/app_factory.py +196 -34
- aethergraph/server/clients/channel_client.py +202 -0
- aethergraph/server/http/channel_http_routes.py +116 -0
- aethergraph/server/http/channel_ws_routers.py +45 -0
- aethergraph/server/loading.py +117 -0
- aethergraph/server/server.py +131 -0
- aethergraph/server/server_state.py +240 -0
- aethergraph/server/start.py +227 -66
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
- aethergraph/server/ui_static/index.html +15 -0
- aethergraph/server/ui_static/logo.png +0 -0
- aethergraph/services/artifacts/__init__.py +0 -0
- aethergraph/services/artifacts/facade.py +1239 -132
- aethergraph/services/auth/{dev.py → authn.py} +0 -8
- aethergraph/services/auth/authz.py +100 -0
- aethergraph/services/channel/__init__.py +0 -0
- aethergraph/services/channel/channel_bus.py +19 -1
- aethergraph/services/channel/factory.py +13 -1
- aethergraph/services/channel/ingress.py +311 -0
- aethergraph/services/channel/queue_adapter.py +75 -0
- aethergraph/services/channel/session.py +502 -19
- aethergraph/services/container/default_container.py +122 -43
- aethergraph/services/continuations/continuation.py +6 -0
- aethergraph/services/continuations/stores/fs_store.py +19 -0
- aethergraph/services/eventhub/event_hub.py +76 -0
- aethergraph/services/kv/__init__.py +0 -0
- aethergraph/services/kv/ephemeral.py +244 -0
- aethergraph/services/llm/__init__.py +0 -0
- aethergraph/services/llm/generic_client copy.py +691 -0
- aethergraph/services/llm/generic_client.py +1288 -187
- aethergraph/services/llm/providers.py +3 -1
- aethergraph/services/llm/types.py +47 -0
- aethergraph/services/llm/utils.py +284 -0
- aethergraph/services/logger/std.py +3 -0
- aethergraph/services/mcp/__init__.py +9 -0
- aethergraph/services/mcp/http_client.py +38 -0
- aethergraph/services/mcp/service.py +225 -1
- aethergraph/services/mcp/stdio_client.py +41 -6
- aethergraph/services/mcp/ws_client.py +44 -2
- aethergraph/services/memory/__init__.py +0 -0
- aethergraph/services/memory/distillers/llm_long_term.py +234 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
- aethergraph/services/memory/distillers/long_term.py +225 -0
- aethergraph/services/memory/facade/__init__.py +3 -0
- aethergraph/services/memory/facade/chat.py +440 -0
- aethergraph/services/memory/facade/core.py +447 -0
- aethergraph/services/memory/facade/distillation.py +424 -0
- aethergraph/services/memory/facade/rag.py +410 -0
- aethergraph/services/memory/facade/results.py +315 -0
- aethergraph/services/memory/facade/retrieval.py +139 -0
- aethergraph/services/memory/facade/types.py +77 -0
- aethergraph/services/memory/facade/utils.py +43 -0
- aethergraph/services/memory/facade_dep.py +1539 -0
- aethergraph/services/memory/factory.py +9 -3
- aethergraph/services/memory/utils.py +10 -0
- aethergraph/services/metering/eventlog_metering.py +470 -0
- aethergraph/services/metering/noop.py +25 -4
- aethergraph/services/rag/__init__.py +0 -0
- aethergraph/services/rag/facade.py +279 -23
- aethergraph/services/rag/index_factory.py +2 -2
- aethergraph/services/rag/node_rag.py +317 -0
- aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
- aethergraph/services/registry/__init__.py +0 -0
- aethergraph/services/registry/agent_app_meta.py +419 -0
- aethergraph/services/registry/registry_key.py +1 -1
- aethergraph/services/registry/unified_registry.py +74 -6
- aethergraph/services/scope/scope.py +159 -0
- aethergraph/services/scope/scope_factory.py +164 -0
- aethergraph/services/state_stores/serialize.py +5 -0
- aethergraph/services/state_stores/utils.py +2 -1
- aethergraph/services/viz/__init__.py +0 -0
- aethergraph/services/viz/facade.py +413 -0
- aethergraph/services/viz/viz_service.py +69 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
- aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
- aethergraph/storage/artifacts/cas_store.py +422 -0
- aethergraph/storage/artifacts/fs_cas.py +18 -0
- aethergraph/storage/artifacts/s3_cas.py +14 -0
- aethergraph/storage/artifacts/utils.py +124 -0
- aethergraph/storage/blob/fs_blob.py +86 -0
- aethergraph/storage/blob/s3_blob.py +115 -0
- aethergraph/storage/continuation_store/fs_cont.py +283 -0
- aethergraph/storage/continuation_store/inmem_cont.py +146 -0
- aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
- aethergraph/storage/docstore/fs_doc.py +63 -0
- aethergraph/storage/docstore/sqlite_doc.py +31 -0
- aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
- aethergraph/storage/eventlog/fs_event.py +136 -0
- aethergraph/storage/eventlog/sqlite_event.py +47 -0
- aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
- aethergraph/storage/factory.py +432 -0
- aethergraph/storage/fs_utils.py +28 -0
- aethergraph/storage/graph_state_store/state_store.py +64 -0
- aethergraph/storage/kv/inmem_kv.py +103 -0
- aethergraph/storage/kv/layered_kv.py +52 -0
- aethergraph/storage/kv/sqlite_kv.py +39 -0
- aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
- aethergraph/storage/memory/event_persist.py +68 -0
- aethergraph/storage/memory/fs_persist.py +118 -0
- aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
- aethergraph/{services → storage}/memory/indices.py +31 -7
- aethergraph/storage/metering/meter_event.py +55 -0
- aethergraph/storage/runs/doc_store.py +280 -0
- aethergraph/storage/runs/inmen_store.py +82 -0
- aethergraph/storage/runs/sqlite_run_store.py +403 -0
- aethergraph/storage/sessions/doc_store.py +183 -0
- aethergraph/storage/sessions/inmem_store.py +110 -0
- aethergraph/storage/sessions/sqlite_session_store.py +399 -0
- aethergraph/storage/vector_index/chroma_index.py +138 -0
- aethergraph/storage/vector_index/faiss_index.py +179 -0
- aethergraph/storage/vector_index/sqlite_index.py +187 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a3.dist-info/RECORD +356 -0
- aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
- aethergraph/services/artifacts/factory.py +0 -35
- aethergraph/services/artifacts/fs_store.py +0 -656
- aethergraph/services/artifacts/jsonl_index.py +0 -123
- aethergraph/services/artifacts/sqlite_index.py +0 -209
- aethergraph/services/memory/distillers/episode.py +0 -116
- aethergraph/services/memory/distillers/rolling.py +0 -74
- aethergraph/services/memory/facade.py +0 -633
- aethergraph/services/memory/persist_fs.py +0 -40
- aethergraph/services/rag/index/base.py +0 -27
- aethergraph/services/rag/index/faiss_index.py +0 -121
- aethergraph/services/rag/index/sqlite_index.py +0 -134
- aethergraph-0.1.0a1.dist-info/RECORD +0 -182
- aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# aethergraph/artifacts/index_jsonl.py
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
import threading
|
|
8
|
-
from typing import Literal
|
|
9
|
-
|
|
10
|
-
from aethergraph.contracts.services.artifacts import Artifact
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class JsonlArtifactIndexSync:
|
|
14
|
-
"""Simple JSONL-based artifact index for small to medium scale use cases.
|
|
15
|
-
Not suitable for very large scale (millions of artifacts) due to linear scans.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, path: str, occurrences_path: str | None = None):
|
|
19
|
-
self.path = path
|
|
20
|
-
self.occ_path = occurrences_path or (os.path.splitext(path)[0] + "_occurrences.jsonl")
|
|
21
|
-
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
22
|
-
# small in-memory map for quick lookup / dedup of last write
|
|
23
|
-
self._by_id = {}
|
|
24
|
-
self._lock = threading.Lock()
|
|
25
|
-
if os.path.exists(self.path):
|
|
26
|
-
with open(self.path, encoding="utf-8") as f:
|
|
27
|
-
for line in f:
|
|
28
|
-
if not line.strip():
|
|
29
|
-
continue
|
|
30
|
-
rec = json.loads(line)
|
|
31
|
-
self._by_id[rec["artifact_id"]] = rec
|
|
32
|
-
|
|
33
|
-
def upsert(self, a: Artifact) -> None:
|
|
34
|
-
"""Upsert an artifact record."""
|
|
35
|
-
with self._lock:
|
|
36
|
-
rec = a.to_dict()
|
|
37
|
-
self._by_id[a.artifact_id] = rec
|
|
38
|
-
with open(self.path, "a", encoding="utf-8") as f:
|
|
39
|
-
f.write(json.dumps(rec) + "\n")
|
|
40
|
-
|
|
41
|
-
def list_for_run(self, run_id: str) -> list[Artifact]:
|
|
42
|
-
"""List all artifacts for a given run_id."""
|
|
43
|
-
return [Artifact(**r) for r in self._by_id.values() if r.get("run_id") == run_id]
|
|
44
|
-
|
|
45
|
-
def search(
|
|
46
|
-
self,
|
|
47
|
-
*,
|
|
48
|
-
kind: str | None = None,
|
|
49
|
-
labels: dict[str, str] | None = None,
|
|
50
|
-
metric: str | None = None,
|
|
51
|
-
mode: Literal["max", "min"] | None = None,
|
|
52
|
-
) -> list[Artifact]:
|
|
53
|
-
"""Search artifacts by kind, labels (exact match), and metric (min/max)."""
|
|
54
|
-
rows = list(self._by_id.values())
|
|
55
|
-
if kind:
|
|
56
|
-
rows = [r for r in rows if r.get("kind") == kind]
|
|
57
|
-
if labels:
|
|
58
|
-
for k, v in labels.items():
|
|
59
|
-
rows = [r for r in rows if r.get("labels", {}).get(k) == v]
|
|
60
|
-
if metric and mode:
|
|
61
|
-
rows = [r for r in rows if metric in r.get("metrics", {})]
|
|
62
|
-
rows.sort(key=lambda r: r["metrics"][metric], reverse=(mode == "max"))
|
|
63
|
-
return [Artifact(**r) for r in rows]
|
|
64
|
-
|
|
65
|
-
def best(
|
|
66
|
-
self,
|
|
67
|
-
*,
|
|
68
|
-
kind: str,
|
|
69
|
-
metric: str,
|
|
70
|
-
mode: Literal["max", "min"],
|
|
71
|
-
filters: dict[str, str] | None = None,
|
|
72
|
-
) -> Artifact | None:
|
|
73
|
-
"""Get the best artifact by metric with optional filters."""
|
|
74
|
-
rows = self.search(kind=kind, labels=filters, metric=metric, mode=mode)
|
|
75
|
-
return rows[0] if rows else None
|
|
76
|
-
|
|
77
|
-
def pin(self, artifact_id: str, pinned: bool = True) -> None:
|
|
78
|
-
"""Pin or unpin an artifact by artifact_id."""
|
|
79
|
-
if artifact_id in self._by_id:
|
|
80
|
-
self._by_id[artifact_id]["pinned"] = bool(pinned)
|
|
81
|
-
with open(self.path, "a", encoding="utf-8") as f:
|
|
82
|
-
f.write(json.dumps(self._by_id[artifact_id]) + "\n")
|
|
83
|
-
|
|
84
|
-
def record_occurrence(self, a: Artifact, extra_labels: dict | None = None):
|
|
85
|
-
"""
|
|
86
|
-
Append-only log that this artifact appeared in this run/node at this time.
|
|
87
|
-
Keeps lineage even if bytes are identical across runs.
|
|
88
|
-
"""
|
|
89
|
-
row = {
|
|
90
|
-
"artifact_id": a.artifact_id,
|
|
91
|
-
"run_id": a.run_id,
|
|
92
|
-
"graph_id": a.graph_id,
|
|
93
|
-
"node_id": a.node_id,
|
|
94
|
-
"tool_name": a.tool_name,
|
|
95
|
-
"tool_version": a.tool_version,
|
|
96
|
-
"created_at": a.created_at,
|
|
97
|
-
"labels": a.labels | (extra_labels or {}),
|
|
98
|
-
}
|
|
99
|
-
with open(self.occ_path, "a", encoding="utf-8") as f:
|
|
100
|
-
f.write(json.dumps(row) + "\n")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class JsonlArtifactIndex: # implements AsyncArtifactIndex
|
|
104
|
-
def __init__(self, path: str, occurrences_path: str | None = None):
|
|
105
|
-
self._sync = JsonlArtifactIndexSync(path, occurrences_path)
|
|
106
|
-
|
|
107
|
-
async def upsert(self, a: Artifact) -> None:
|
|
108
|
-
await asyncio.to_thread(self._sync.upsert, a)
|
|
109
|
-
|
|
110
|
-
async def list_for_run(self, run_id: str) -> list[Artifact]:
|
|
111
|
-
return await asyncio.to_thread(self._sync.list_for_run, run_id)
|
|
112
|
-
|
|
113
|
-
async def search(self, **kw) -> list[Artifact]:
|
|
114
|
-
return await asyncio.to_thread(self._sync.search, **kw)
|
|
115
|
-
|
|
116
|
-
async def best(self, **kw) -> Artifact | None:
|
|
117
|
-
return await asyncio.to_thread(self._sync.best, **kw)
|
|
118
|
-
|
|
119
|
-
async def pin(self, artifact_id: str, pinned: bool = True) -> None:
|
|
120
|
-
await asyncio.to_thread(self._sync.pin, artifact_id, pinned)
|
|
121
|
-
|
|
122
|
-
async def record_occurrence(self, a: Artifact, extra_labels: dict | None = None):
|
|
123
|
-
await asyncio.to_thread(self._sync.record_occurrence, a, extra_labels)
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# aethergraph/artifacts/index_sqlite.py
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import asyncio
|
|
5
|
-
import json
|
|
6
|
-
import sqlite3
|
|
7
|
-
from typing import Literal
|
|
8
|
-
|
|
9
|
-
from aethergraph.contracts.services.artifacts import Artifact
|
|
10
|
-
from aethergraph.services.artifacts.jsonl_index import JsonlArtifactIndexSync
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SqliteArtifactIndexSync:
|
|
14
|
-
"""SQLite-based artifact index for medium to large scale use cases.
|
|
15
|
-
Suitable for larger scale (millions of artifacts) with indexing.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, db_path: str):
|
|
19
|
-
self.db_path = db_path
|
|
20
|
-
self._init()
|
|
21
|
-
|
|
22
|
-
def _init(self):
|
|
23
|
-
con = sqlite3.connect(self.db_path)
|
|
24
|
-
cur = con.cursor()
|
|
25
|
-
cur.execute("""
|
|
26
|
-
CREATE TABLE IF NOT EXISTS artifacts (
|
|
27
|
-
artifact_id TEXT PRIMARY KEY,
|
|
28
|
-
uri TEXT NOT NULL,
|
|
29
|
-
kind TEXT NOT NULL,
|
|
30
|
-
bytes INTEGER,
|
|
31
|
-
sha256 TEXT,
|
|
32
|
-
mime TEXT,
|
|
33
|
-
run_id TEXT,
|
|
34
|
-
graph_id TEXT,
|
|
35
|
-
node_id TEXT,
|
|
36
|
-
tool_name TEXT,
|
|
37
|
-
tool_version TEXT,
|
|
38
|
-
created_at TEXT,
|
|
39
|
-
labels TEXT,
|
|
40
|
-
metrics TEXT,
|
|
41
|
-
params TEXT,
|
|
42
|
-
preview_uri TEXT,
|
|
43
|
-
pinned INTEGER DEFAULT 0
|
|
44
|
-
)""")
|
|
45
|
-
cur.execute("CREATE INDEX IF NOT EXISTS idx_kind ON artifacts(kind)")
|
|
46
|
-
cur.execute("CREATE INDEX IF NOT EXISTS idx_run ON artifacts(run_id)")
|
|
47
|
-
con.commit()
|
|
48
|
-
con.close()
|
|
49
|
-
|
|
50
|
-
def upsert(self, a: Artifact) -> None:
|
|
51
|
-
con = sqlite3.connect(self.db_path)
|
|
52
|
-
cur = con.cursor()
|
|
53
|
-
cur.execute(
|
|
54
|
-
"""
|
|
55
|
-
INSERT INTO artifacts
|
|
56
|
-
(artifact_id, uri, kind, bytes, sha256, mime, run_id, graph_id, node_id,
|
|
57
|
-
tool_name, tool_version, created_at, labels, metrics, params, preview_uri, pinned)
|
|
58
|
-
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
59
|
-
ON CONFLICT(artifact_id) DO UPDATE SET
|
|
60
|
-
uri=excluded.uri, kind=excluded.kind, bytes=excluded.bytes,
|
|
61
|
-
sha256=excluded.sha256, mime=excluded.mime, run_id=excluded.run_id,
|
|
62
|
-
graph_id=excluded.graph_id, node_id=excluded.node_id,
|
|
63
|
-
tool_name=excluded.tool_name, tool_version=excluded.tool_version,
|
|
64
|
-
created_at=excluded.created_at, labels=excluded.labels,
|
|
65
|
-
metrics=excluded.metrics, params=excluded.params,
|
|
66
|
-
preview_uri=excluded.preview_uri, pinned=excluded.pinned
|
|
67
|
-
""",
|
|
68
|
-
(
|
|
69
|
-
a.artifact_id,
|
|
70
|
-
a.uri,
|
|
71
|
-
a.kind,
|
|
72
|
-
a.bytes,
|
|
73
|
-
a.sha256,
|
|
74
|
-
a.mime,
|
|
75
|
-
a.run_id,
|
|
76
|
-
a.graph_id,
|
|
77
|
-
a.node_id,
|
|
78
|
-
a.tool_name,
|
|
79
|
-
a.tool_version,
|
|
80
|
-
a.created_at,
|
|
81
|
-
json.dumps(a.labels),
|
|
82
|
-
json.dumps(a.metrics),
|
|
83
|
-
json.dumps(a.params),
|
|
84
|
-
a.preview_uri,
|
|
85
|
-
1 if a.pinned else 0,
|
|
86
|
-
),
|
|
87
|
-
)
|
|
88
|
-
con.commit()
|
|
89
|
-
con.close()
|
|
90
|
-
|
|
91
|
-
def list_for_run(self, run_id: str) -> list[Artifact]:
|
|
92
|
-
con = sqlite3.connect(self.db_path)
|
|
93
|
-
cur = con.cursor()
|
|
94
|
-
cur.execute("SELECT * FROM artifacts WHERE run_id=? ORDER BY created_at", (run_id,))
|
|
95
|
-
rows = cur.fetchall()
|
|
96
|
-
con.close()
|
|
97
|
-
return [self._row_to_artifact(r) for r in rows]
|
|
98
|
-
|
|
99
|
-
def search(
|
|
100
|
-
self,
|
|
101
|
-
*,
|
|
102
|
-
kind: str | None = None,
|
|
103
|
-
labels: dict[str, str] | None = None,
|
|
104
|
-
metric: str | None = None,
|
|
105
|
-
mode: Literal["max", "min"] | None = None,
|
|
106
|
-
) -> list[Artifact]:
|
|
107
|
-
con = sqlite3.connect(self.db_path)
|
|
108
|
-
cur = con.cursor()
|
|
109
|
-
q = "SELECT * FROM artifacts WHERE 1=1"
|
|
110
|
-
args = []
|
|
111
|
-
if kind:
|
|
112
|
-
q += " AND kind=?"
|
|
113
|
-
args.append(kind)
|
|
114
|
-
# naive label filter: all requested label kv must be contained in labels json
|
|
115
|
-
if labels:
|
|
116
|
-
for k, v in labels.items():
|
|
117
|
-
q += " AND json_extract(labels, ?) = ?"
|
|
118
|
-
args += (f"$.{k}", v)
|
|
119
|
-
cur.execute(q, args)
|
|
120
|
-
rows = [self._row_to_artifact(r) for r in cur.fetchall()]
|
|
121
|
-
con.close()
|
|
122
|
-
if metric and mode and rows:
|
|
123
|
-
rows = [r for r in rows if r.metrics and metric in r.metrics]
|
|
124
|
-
reverse = mode == "max"
|
|
125
|
-
rows.sort(key=lambda a: a.metrics.get(metric, float("-inf")), reverse=reverse)
|
|
126
|
-
return rows
|
|
127
|
-
|
|
128
|
-
def best(
|
|
129
|
-
self,
|
|
130
|
-
*,
|
|
131
|
-
kind: str,
|
|
132
|
-
metric: str,
|
|
133
|
-
mode: Literal["max", "min"],
|
|
134
|
-
filters: dict[str, str] | None = None,
|
|
135
|
-
) -> Artifact | None:
|
|
136
|
-
rows = self.search(kind=kind, labels=filters, metric=metric, mode=mode)
|
|
137
|
-
return rows[0] if rows else None
|
|
138
|
-
|
|
139
|
-
def pin(self, artifact_id: str, pinned: bool = True) -> None:
|
|
140
|
-
con = sqlite3.connect(self.db_path)
|
|
141
|
-
cur = con.cursor()
|
|
142
|
-
cur.execute(
|
|
143
|
-
"UPDATE artifacts SET pinned=? WHERE artifact_id=?", (1 if pinned else 0, artifact_id)
|
|
144
|
-
)
|
|
145
|
-
con.commit()
|
|
146
|
-
con.close()
|
|
147
|
-
|
|
148
|
-
def _row_to_artifact(self, r) -> Artifact:
|
|
149
|
-
(
|
|
150
|
-
artifact_id,
|
|
151
|
-
uri,
|
|
152
|
-
kind,
|
|
153
|
-
bytes_,
|
|
154
|
-
sha256,
|
|
155
|
-
mime,
|
|
156
|
-
run_id,
|
|
157
|
-
graph_id,
|
|
158
|
-
node_id,
|
|
159
|
-
tool_name,
|
|
160
|
-
tool_version,
|
|
161
|
-
created_at,
|
|
162
|
-
labels,
|
|
163
|
-
metrics,
|
|
164
|
-
params,
|
|
165
|
-
preview_uri,
|
|
166
|
-
pinned,
|
|
167
|
-
) = r
|
|
168
|
-
return Artifact(
|
|
169
|
-
artifact_id=artifact_id,
|
|
170
|
-
uri=uri,
|
|
171
|
-
kind=kind,
|
|
172
|
-
bytes=bytes_,
|
|
173
|
-
sha256=sha256,
|
|
174
|
-
mime=mime,
|
|
175
|
-
run_id=run_id,
|
|
176
|
-
graph_id=graph_id,
|
|
177
|
-
node_id=node_id,
|
|
178
|
-
tool_name=tool_name,
|
|
179
|
-
tool_version=tool_version,
|
|
180
|
-
created_at=created_at,
|
|
181
|
-
labels=json.loads(labels or "{}"),
|
|
182
|
-
metrics=json.loads(metrics or "{}"),
|
|
183
|
-
params=json.loads(params or "{}"),
|
|
184
|
-
preview_uri=preview_uri,
|
|
185
|
-
pinned=bool(pinned),
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
class SqliteArtifactIndex: # implements AsyncArtifactIndex
|
|
190
|
-
def __init__(self, path: str, occurrences_path: str | None = None):
|
|
191
|
-
self._sync = JsonlArtifactIndexSync(path, occurrences_path)
|
|
192
|
-
|
|
193
|
-
async def upsert(self, a: Artifact) -> None:
|
|
194
|
-
await asyncio.to_thread(self._sync.upsert, a)
|
|
195
|
-
|
|
196
|
-
async def list_for_run(self, run_id: str) -> list[Artifact]:
|
|
197
|
-
return await asyncio.to_thread(self._sync.list_for_run, run_id)
|
|
198
|
-
|
|
199
|
-
async def search(self, **kw) -> list[Artifact]:
|
|
200
|
-
return await asyncio.to_thread(self._sync.search, **kw)
|
|
201
|
-
|
|
202
|
-
async def best(self, **kw) -> Artifact | None:
|
|
203
|
-
return await asyncio.to_thread(self._sync.best, **kw)
|
|
204
|
-
|
|
205
|
-
async def pin(self, artifact_id: str, pinned: bool = True) -> None:
|
|
206
|
-
await asyncio.to_thread(self._sync.pin, artifact_id, pinned)
|
|
207
|
-
|
|
208
|
-
async def record_occurrence(self, a: Artifact, extra_labels: dict | None = None):
|
|
209
|
-
await asyncio.to_thread(self._sync.record_occurrence, a, extra_labels)
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
4
|
-
import json
|
|
5
|
-
import time
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from aethergraph.contracts.services.memory import Distiller, Event, HotLog, Indices, Persistence
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _now_iso() -> str:
|
|
12
|
-
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _stable_event_id(parts: dict[str, Any]) -> str:
|
|
16
|
-
blob = json.dumps(parts, sort_keys=True, ensure_ascii=False).encode("utf-8")
|
|
17
|
-
return hashlib.sha256(blob).hexdigest()[:24]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _episode_uri(sessiorun_idn_id: str, tool: str, run_id: str) -> str:
|
|
21
|
-
safe = tool.replace("/", "_")
|
|
22
|
-
return f"file://mem/{run_id}/episodes/{safe}/{run_id}.json"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class EpisodeSummarizer(Distiller):
|
|
26
|
-
"""
|
|
27
|
-
Aggregate all events for (tool, run_id) into a compact episode summary:
|
|
28
|
-
- sources: event_ids
|
|
29
|
-
- merged metrics (last-write-wins)
|
|
30
|
-
- notes: last N textual notes
|
|
31
|
-
Writes a JSON summary artifact and emits a lightweight run_summary event.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
def __init__(
|
|
35
|
-
self, *, include_metrics: bool = True, note_limit: int = 8, note_chars: int = 2000
|
|
36
|
-
):
|
|
37
|
-
self.include_metrics = include_metrics
|
|
38
|
-
self.note_limit = note_limit
|
|
39
|
-
self.note_chars = note_chars
|
|
40
|
-
|
|
41
|
-
async def distill(
|
|
42
|
-
self,
|
|
43
|
-
run_id: str,
|
|
44
|
-
*,
|
|
45
|
-
hotlog: HotLog,
|
|
46
|
-
persistence: Persistence,
|
|
47
|
-
indices: Indices,
|
|
48
|
-
tool: str,
|
|
49
|
-
**kw,
|
|
50
|
-
) -> dict[str, Any]:
|
|
51
|
-
# Pull a reasonable window from hot memory; filter in-process.
|
|
52
|
-
# (If needed later, add a Persistence scan by day.)
|
|
53
|
-
events = await hotlog.recent(
|
|
54
|
-
run_id, kinds=["tool_start", "tool_result", "error", "run_summary"], limit=400
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
eps = [e for e in events if e.run_id == run_id and (e.tool or "") == tool]
|
|
58
|
-
if not eps:
|
|
59
|
-
return {}
|
|
60
|
-
|
|
61
|
-
srcs: list[str] = []
|
|
62
|
-
notes: list[str] = []
|
|
63
|
-
metrics: dict[str, float] = {}
|
|
64
|
-
|
|
65
|
-
for e in eps:
|
|
66
|
-
if e.event_id:
|
|
67
|
-
srcs.append(e.event_id)
|
|
68
|
-
if self.include_metrics and e.metrics:
|
|
69
|
-
metrics.update(e.metrics) # simple merge; last-write-wins
|
|
70
|
-
if e.text:
|
|
71
|
-
notes.append(e.text)
|
|
72
|
-
|
|
73
|
-
ts = _now_iso()
|
|
74
|
-
summary = {
|
|
75
|
-
"kind": "episode_summary",
|
|
76
|
-
"run_id": run_id,
|
|
77
|
-
"tool": tool,
|
|
78
|
-
"ts": ts,
|
|
79
|
-
"sources": srcs,
|
|
80
|
-
"metrics": metrics,
|
|
81
|
-
"notes": notes[-self.note_limit :],
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
uri = _episode_uri(run_id, tool, run_id)
|
|
85
|
-
await persistence.save_json(uri, summary)
|
|
86
|
-
|
|
87
|
-
# Emit a compact run_summary event
|
|
88
|
-
compact_text = "\n".join(summary["notes"][-self.note_limit :])[: self.note_chars]
|
|
89
|
-
evt_base = {
|
|
90
|
-
"run_id": run_id,
|
|
91
|
-
"tool": tool,
|
|
92
|
-
"kind": "run_summary",
|
|
93
|
-
"severity": 1,
|
|
94
|
-
"tags": ["summary", "episode"],
|
|
95
|
-
}
|
|
96
|
-
eid = _stable_event_id(
|
|
97
|
-
{
|
|
98
|
-
"ts": ts,
|
|
99
|
-
"run_id": run_id,
|
|
100
|
-
"tool": tool,
|
|
101
|
-
"kind": "run_summary",
|
|
102
|
-
"text": compact_text[:200],
|
|
103
|
-
}
|
|
104
|
-
)
|
|
105
|
-
evt = Event(
|
|
106
|
-
event_id=eid,
|
|
107
|
-
ts=ts,
|
|
108
|
-
text=compact_text,
|
|
109
|
-
metrics={"notes": len(notes)},
|
|
110
|
-
signal=0.5,
|
|
111
|
-
**evt_base,
|
|
112
|
-
)
|
|
113
|
-
await hotlog.append(run_id, evt, ttl_s=7 * 24 * 3600, limit=1000)
|
|
114
|
-
await persistence.append_event(run_id, evt)
|
|
115
|
-
|
|
116
|
-
return {"uri": uri, "sources": srcs, "metrics": metrics}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from aethergraph.contracts.services.memory import Distiller, Event, HotLog, Indices, Persistence
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _now_iso():
|
|
8
|
-
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def ar_summary_uri(run_id: str, tag: str, ts: str) -> str:
|
|
12
|
-
# Save summaries under the same base "mem/<run_id>/..." tree as append_event,
|
|
13
|
-
# but using a file:// URI so FSPersistence can handle it.
|
|
14
|
-
safe_ts = ts.replace(":", "-")
|
|
15
|
-
return f"file://mem/{run_id}/summaries/{tag}/{safe_ts}.json"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class RollingSummarizer(Distiller):
|
|
19
|
-
def __init__(
|
|
20
|
-
self, *, max_turns: int = 20, min_signal: float = 0.25, turn_kinds: list[str] | None = None
|
|
21
|
-
):
|
|
22
|
-
self.max_turns = max_turns
|
|
23
|
-
self.min_signal = min_signal
|
|
24
|
-
self.turn_kinds = turn_kinds or ["user_msg", "assistant_msg"]
|
|
25
|
-
|
|
26
|
-
async def distill(
|
|
27
|
-
self, run_id: str, *, hotlog: HotLog, persistence: Persistence, indices: Indices, **kw
|
|
28
|
-
) -> dict[str, Any]:
|
|
29
|
-
turns = await hotlog.recent(run_id, kinds=self.turn_kinds, limit=self.max_turns * 2)
|
|
30
|
-
kept = [t for t in turns if (t.signal or 0.0) >= self.min_signal]
|
|
31
|
-
if not kept:
|
|
32
|
-
return {}
|
|
33
|
-
|
|
34
|
-
lines = []
|
|
35
|
-
srcs: list[str] = []
|
|
36
|
-
for t in kept[-self.max_turns :]:
|
|
37
|
-
role = "User" if t.kind == "user_msg" else "Assistant"
|
|
38
|
-
if t.text:
|
|
39
|
-
lines.append(f"{role}: {t.text}")
|
|
40
|
-
srcs.append(t.event_id)
|
|
41
|
-
digest_text = "\n".join(lines)
|
|
42
|
-
ts = _now_iso()
|
|
43
|
-
summary = {
|
|
44
|
-
"kind": "rolling_summary",
|
|
45
|
-
"run_id": run_id,
|
|
46
|
-
"ts": ts,
|
|
47
|
-
"sources": srcs,
|
|
48
|
-
"text": digest_text,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
uri = ar_summary_uri(run_id, "rolling", ts)
|
|
52
|
-
|
|
53
|
-
await persistence.save_json(uri=uri, obj=summary)
|
|
54
|
-
|
|
55
|
-
evt = Event(
|
|
56
|
-
event_id="",
|
|
57
|
-
ts=ts,
|
|
58
|
-
run_id=run_id,
|
|
59
|
-
kind="rolling_summary",
|
|
60
|
-
severity=1,
|
|
61
|
-
signal=0.5,
|
|
62
|
-
text=digest_text,
|
|
63
|
-
metrics={"num_turns": len(kept)},
|
|
64
|
-
tags=["summary"],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
from aethergraph.services.memory.facade import stable_event_id
|
|
68
|
-
|
|
69
|
-
evt.event_id = stable_event_id(
|
|
70
|
-
{"ts": ts, "run_id": run_id, "kind": "rolling_summary", "text": digest_text[:200]}
|
|
71
|
-
)
|
|
72
|
-
await hotlog.append(run_id, evt, ttl_s=7 * 24 * 3600, limit=1000)
|
|
73
|
-
await persistence.append_event(run_id, evt)
|
|
74
|
-
return {"uri": uri, "sources": srcs}
|