aethergraph 0.1.0a2__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/__main__.py +3 -0
- 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 +10 -1
- 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.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/METADATA +1 -1
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/RECORD +108 -64
- {aethergraph-0.1.0a2.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.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/entry_points.txt +0 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a2.dist-info → aethergraph-0.1.0a4.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
# ---- core services ----
|
|
8
8
|
from aethergraph.config.config import AppSettings
|
|
9
|
+
from aethergraph.contracts.services.execution import ExecutionService
|
|
9
10
|
|
|
10
11
|
# ---- optional services (not used by default) ----
|
|
11
12
|
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
@@ -27,6 +28,9 @@ from aethergraph.services.auth.authn import DevTokenAuthn
|
|
|
27
28
|
from aethergraph.services.auth.authz import AllowAllAuthz
|
|
28
29
|
from aethergraph.services.channel.channel_bus import ChannelBus
|
|
29
30
|
|
|
31
|
+
# from aethergraph.services.eventhub.event_hub import EventHub
|
|
32
|
+
from aethergraph.services.channel.event_hub import EventHub
|
|
33
|
+
|
|
30
34
|
# ---- channel services ----
|
|
31
35
|
from aethergraph.services.channel.factory import build_bus, make_channel_adapters_from_env
|
|
32
36
|
from aethergraph.services.channel.ingress import ChannelIngress
|
|
@@ -35,9 +39,14 @@ from aethergraph.services.continuations.stores.fs_store import (
|
|
|
35
39
|
FSContinuationStore, # AsyncContinuationStore
|
|
36
40
|
)
|
|
37
41
|
from aethergraph.services.eventbus.inmem import InMemoryEventBus
|
|
42
|
+
from aethergraph.services.execution.local_python import LocalPythonExecutionService
|
|
43
|
+
|
|
44
|
+
# ---- Global Indices ----
|
|
45
|
+
from aethergraph.services.indices.global_indices import GlobalIndices
|
|
38
46
|
|
|
39
47
|
# ---- kv services ----
|
|
40
48
|
from aethergraph.services.llm.factory import build_llm_clients
|
|
49
|
+
from aethergraph.services.llm.generic_embed_client import GenericEmbeddingClient
|
|
41
50
|
from aethergraph.services.llm.service import LLMService
|
|
42
51
|
from aethergraph.services.logger.std import LoggingConfig, StdLoggerService
|
|
43
52
|
from aethergraph.services.mcp.service import MCPService
|
|
@@ -45,11 +54,15 @@ from aethergraph.services.mcp.service import MCPService
|
|
|
45
54
|
# ---- memory services ----
|
|
46
55
|
from aethergraph.services.memory.factory import MemoryFactory
|
|
47
56
|
from aethergraph.services.metering.eventlog_metering import EventLogMeteringService
|
|
48
|
-
|
|
57
|
+
|
|
58
|
+
# ---- Planning components ----
|
|
59
|
+
from aethergraph.services.planning.action_catalog import ActionCatalog
|
|
60
|
+
from aethergraph.services.planning.flow_validator import FlowValidator
|
|
61
|
+
from aethergraph.services.planning.planner_service import PlannerService
|
|
49
62
|
from aethergraph.services.rag.chunker import TextSplitter
|
|
50
63
|
from aethergraph.services.rag.facade import RAGFacade
|
|
51
64
|
|
|
52
|
-
# ----
|
|
65
|
+
# ---- Other components ----
|
|
53
66
|
from aethergraph.services.rate_limit.inmem_rate_limit import SimpleRateLimiter
|
|
54
67
|
from aethergraph.services.redactor.simple import RegexRedactor # Simple PII redactor
|
|
55
68
|
from aethergraph.services.registry.unified_registry import UnifiedRegistry
|
|
@@ -58,10 +71,13 @@ from aethergraph.services.resume.router import ResumeRouter
|
|
|
58
71
|
from aethergraph.services.schedulers.registry import SchedulerRegistry
|
|
59
72
|
from aethergraph.services.scope.scope_factory import ScopeFactory
|
|
60
73
|
from aethergraph.services.secrets.env import EnvSecrets
|
|
74
|
+
from aethergraph.services.skills.skill_registry import SkillRegistry
|
|
61
75
|
from aethergraph.services.tracing.noop import NoopTracer
|
|
62
76
|
from aethergraph.services.viz.viz_service import VizService
|
|
63
77
|
from aethergraph.services.waits.wait_registry import WaitRegistry
|
|
64
78
|
from aethergraph.services.wakeup.memory_queue import ThreadSafeWakeupQueue
|
|
79
|
+
|
|
80
|
+
# ---- storage builders ----
|
|
65
81
|
from aethergraph.storage.factory import (
|
|
66
82
|
build_artifact_index,
|
|
67
83
|
build_artifact_store,
|
|
@@ -78,6 +94,7 @@ from aethergraph.storage.factory import (
|
|
|
78
94
|
)
|
|
79
95
|
from aethergraph.storage.kv.inmem_kv import InMemoryKV as EphemeralKV
|
|
80
96
|
from aethergraph.storage.metering.meter_event import EventLogMeteringStore
|
|
97
|
+
from aethergraph.storage.search_factory import build_search_backend
|
|
81
98
|
|
|
82
99
|
SERVICE_KEYS = [
|
|
83
100
|
# core
|
|
@@ -129,6 +146,7 @@ class DefaultContainer:
|
|
|
129
146
|
|
|
130
147
|
# channels and interactions
|
|
131
148
|
channels: ChannelBus
|
|
149
|
+
eventhub: EventHub
|
|
132
150
|
|
|
133
151
|
# continuations and resume
|
|
134
152
|
cont_store: FSContinuationStore
|
|
@@ -144,6 +162,7 @@ class DefaultContainer:
|
|
|
144
162
|
artifacts: AsyncArtifactStore
|
|
145
163
|
artifact_index: AsyncArtifactIndex
|
|
146
164
|
eventlog: EventLog
|
|
165
|
+
global_indices: GlobalIndices
|
|
147
166
|
|
|
148
167
|
# memory
|
|
149
168
|
memory_factory: MemoryFactory
|
|
@@ -161,9 +180,15 @@ class DefaultContainer:
|
|
|
161
180
|
run_manager: RunManager | None = None # RunManager
|
|
162
181
|
session_store: SessionStore | None = None # SessionStore
|
|
163
182
|
|
|
183
|
+
# planner
|
|
184
|
+
planner_service: PlannerService | None = None
|
|
185
|
+
|
|
186
|
+
# skills
|
|
187
|
+
skills_registry: SkillRegistry | None = None
|
|
188
|
+
|
|
164
189
|
# optional services (not used by default)
|
|
190
|
+
execution: ExecutionService | None = None
|
|
165
191
|
event_bus: InMemoryEventBus | None = None
|
|
166
|
-
prompts: FilePromptStore | None = None
|
|
167
192
|
authn: DevTokenAuthn | None = None
|
|
168
193
|
authz: AllowAllAuthz | None = None
|
|
169
194
|
redactor: RegexRedactor | None = None
|
|
@@ -255,7 +280,10 @@ def build_default_container(
|
|
|
255
280
|
}
|
|
256
281
|
|
|
257
282
|
# channels
|
|
258
|
-
|
|
283
|
+
event_hub = (
|
|
284
|
+
EventHub()
|
|
285
|
+
) # in-memory event hub for WebUI and other real-time events; not configurable yet
|
|
286
|
+
channel_adapters = make_channel_adapters_from_env(cfg, event_log=eventlog, event_hub=event_hub)
|
|
259
287
|
channels = build_bus(
|
|
260
288
|
channel_adapters,
|
|
261
289
|
default="console:stdin",
|
|
@@ -278,6 +306,7 @@ def build_default_container(
|
|
|
278
306
|
) # get secrets from env vars -- for local development; in prod, use a proper secrets manager
|
|
279
307
|
llm_clients = build_llm_clients(cfg.llm, secrets) # return {profile: GenericLLMClient}
|
|
280
308
|
llm_service = LLMService(clients=llm_clients) if llm_clients else None
|
|
309
|
+
embed_client = GenericEmbeddingClient(provider="openai", model="text-embedding-3-small")
|
|
281
310
|
|
|
282
311
|
# RAG facade
|
|
283
312
|
vec_index = build_vector_index(cfg)
|
|
@@ -295,12 +324,12 @@ def build_default_container(
|
|
|
295
324
|
# memory factory
|
|
296
325
|
persistence = build_memory_persistence(cfg)
|
|
297
326
|
hotlog = build_memory_hotlog(cfg)
|
|
298
|
-
|
|
327
|
+
memory_indices = build_memory_indices(cfg)
|
|
299
328
|
docs = build_doc_store(cfg)
|
|
300
329
|
memory_factory = MemoryFactory(
|
|
301
330
|
hotlog=hotlog,
|
|
302
331
|
persistence=persistence,
|
|
303
|
-
indices=
|
|
332
|
+
indices=memory_indices,
|
|
304
333
|
artifacts=artifacts,
|
|
305
334
|
docs=docs,
|
|
306
335
|
hot_limit=int(cfg.memory.hot_limit),
|
|
@@ -337,6 +366,35 @@ def build_default_container(
|
|
|
337
366
|
authn = DevTokenAuthn()
|
|
338
367
|
authz = AllowAllAuthz()
|
|
339
368
|
|
|
369
|
+
# global scoped indices
|
|
370
|
+
# from aethergraph.storage.search_backend.generic_vector_backend import SQLiteVectorSearchBackend
|
|
371
|
+
|
|
372
|
+
# search_backend = SQLiteVectorSearchBackend(
|
|
373
|
+
# index=vec_index,
|
|
374
|
+
# embedder=embed_client,
|
|
375
|
+
# )
|
|
376
|
+
|
|
377
|
+
search_backend = build_search_backend(cfg=cfg, embedder=embed_client)
|
|
378
|
+
global_indices = GlobalIndices(backend=search_backend) # to be set up later as needed
|
|
379
|
+
|
|
380
|
+
# Execution service
|
|
381
|
+
execution = (
|
|
382
|
+
LocalPythonExecutionService()
|
|
383
|
+
) # simple local python executor -- NOT SANDBOXED; just for local functionality testing
|
|
384
|
+
|
|
385
|
+
# Planner service
|
|
386
|
+
catalog = ActionCatalog(registry=registry)
|
|
387
|
+
flow_validator = FlowValidator(catalog=catalog)
|
|
388
|
+
planner_service = PlannerService(
|
|
389
|
+
catalog=catalog,
|
|
390
|
+
llm=llm_service.get("default") if llm_service else None,
|
|
391
|
+
validator=flow_validator,
|
|
392
|
+
run_manager=run_manager,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# skills registry
|
|
396
|
+
skills_registry = SkillRegistry()
|
|
397
|
+
|
|
340
398
|
container = DefaultContainer(
|
|
341
399
|
root=str(root_p),
|
|
342
400
|
scope_factory=scope_factory,
|
|
@@ -345,16 +403,21 @@ def build_default_container(
|
|
|
345
403
|
logger=logger_factory,
|
|
346
404
|
clock=clock,
|
|
347
405
|
channels=channels,
|
|
406
|
+
eventhub=event_hub,
|
|
407
|
+
skills_registry=skills_registry,
|
|
348
408
|
cont_store=cont_store,
|
|
349
409
|
sched_registry=sched_registry,
|
|
350
410
|
wait_registry=wait_registry,
|
|
351
411
|
resume_bus=resume_bus,
|
|
352
412
|
resume_router=resume_router,
|
|
353
413
|
wakeup_queue=wakeup_queue,
|
|
414
|
+
execution=execution,
|
|
415
|
+
planner_service=planner_service,
|
|
354
416
|
kv_hot=kv_hot,
|
|
355
417
|
state_store=state_store,
|
|
356
418
|
artifacts=artifacts,
|
|
357
419
|
artifact_index=artifact_index,
|
|
420
|
+
global_indices=global_indices,
|
|
358
421
|
viz_service=viz_service,
|
|
359
422
|
eventlog=eventlog,
|
|
360
423
|
memory_factory=memory_factory,
|
|
@@ -366,7 +429,6 @@ def build_default_container(
|
|
|
366
429
|
session_store=session_store,
|
|
367
430
|
secrets=secrets,
|
|
368
431
|
event_bus=None,
|
|
369
|
-
prompts=None,
|
|
370
432
|
authn=authn,
|
|
371
433
|
authz=authz,
|
|
372
434
|
redactor=None,
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import subprocess
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
from aethergraph.contracts.services.execution import (
|
|
7
|
+
CodeExecutionRequest,
|
|
8
|
+
CodeExecutionResult,
|
|
9
|
+
ExecutionService,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LocalPythonExecutionService(ExecutionService):
|
|
14
|
+
def __init__(self, python_executable: str = "python"):
|
|
15
|
+
self.python_executable = python_executable
|
|
16
|
+
|
|
17
|
+
async def execute(self, request: CodeExecutionRequest) -> CodeExecutionResult:
|
|
18
|
+
if request.language != "python":
|
|
19
|
+
return CodeExecutionResult(
|
|
20
|
+
stdout="",
|
|
21
|
+
stderr="",
|
|
22
|
+
exit_code=1,
|
|
23
|
+
error=f"Unsupported language: {request.language}",
|
|
24
|
+
metadata={"reason": "unsupported_language"},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
28
|
+
tmp_path = Path(tmpdir)
|
|
29
|
+
script_path = tmp_path / "script.py"
|
|
30
|
+
script_path.write_text(request.code, encoding="utf-8")
|
|
31
|
+
|
|
32
|
+
cmd: list[str] = [self.python_executable, str(script_path)]
|
|
33
|
+
if request.args:
|
|
34
|
+
cmd.extend(request.args)
|
|
35
|
+
|
|
36
|
+
cwd = request.workdir or tmpdir
|
|
37
|
+
env: dict[str, str] | None = None
|
|
38
|
+
if request.env is not None:
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
env = os.environ.copy()
|
|
42
|
+
env.update(request.env)
|
|
43
|
+
|
|
44
|
+
# Try async subprocess first
|
|
45
|
+
try:
|
|
46
|
+
proc = await asyncio.create_subprocess_exec(
|
|
47
|
+
*cmd,
|
|
48
|
+
cwd=cwd,
|
|
49
|
+
env=env,
|
|
50
|
+
stdout=asyncio.subprocess.PIPE,
|
|
51
|
+
stderr=asyncio.subprocess.PIPE,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
stdout_bytes, stderr_bytes = await asyncio.wait_for(
|
|
56
|
+
proc.communicate(), timeout=request.timeout_s
|
|
57
|
+
)
|
|
58
|
+
stdout = stdout_bytes.decode("utf-8", errors="replace")
|
|
59
|
+
stderr = stderr_bytes.decode("utf-8", errors="replace")
|
|
60
|
+
|
|
61
|
+
return CodeExecutionResult(
|
|
62
|
+
stdout=stdout,
|
|
63
|
+
stderr=stderr,
|
|
64
|
+
exit_code=proc.returncode,
|
|
65
|
+
error=None,
|
|
66
|
+
metadata={
|
|
67
|
+
"timeout_s": request.timeout_s,
|
|
68
|
+
"mode": "async_subprocess",
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
except asyncio.TimeoutError:
|
|
72
|
+
proc.kill()
|
|
73
|
+
await proc.wait()
|
|
74
|
+
return CodeExecutionResult(
|
|
75
|
+
stdout="",
|
|
76
|
+
stderr="Execution timed out",
|
|
77
|
+
exit_code=-1,
|
|
78
|
+
error="timeout",
|
|
79
|
+
metadata={
|
|
80
|
+
"timeout_s": request.timeout_s,
|
|
81
|
+
"mode": "async_subprocess",
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
except NotImplementedError as exc:
|
|
86
|
+
# Fallback for event loops that don't support subprocesses (common on Windows)
|
|
87
|
+
def _run_sync():
|
|
88
|
+
completed = subprocess.run(
|
|
89
|
+
cmd,
|
|
90
|
+
cwd=cwd,
|
|
91
|
+
env=env,
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
)
|
|
95
|
+
return completed
|
|
96
|
+
|
|
97
|
+
completed = await asyncio.to_thread(_run_sync)
|
|
98
|
+
|
|
99
|
+
return CodeExecutionResult(
|
|
100
|
+
stdout=completed.stdout,
|
|
101
|
+
stderr=completed.stderr,
|
|
102
|
+
exit_code=completed.returncode,
|
|
103
|
+
error=None,
|
|
104
|
+
metadata={
|
|
105
|
+
"timeout_s": request.timeout_s,
|
|
106
|
+
"mode": "thread_subprocess_fallback",
|
|
107
|
+
"exception_type": type(exc).__name__,
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
return CodeExecutionResult(
|
|
113
|
+
stdout="",
|
|
114
|
+
stderr=str(exc),
|
|
115
|
+
exit_code=-1,
|
|
116
|
+
error="spawn_failed",
|
|
117
|
+
metadata={"exception_type": type(exc).__name__},
|
|
118
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from aethergraph.contracts.storage.search_backend import SearchBackend
|
|
4
|
+
from aethergraph.services.indices.scoped_indices import ScopedIndices
|
|
5
|
+
from aethergraph.services.scope.scope import Scope
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class GlobalIndices:
|
|
10
|
+
backend: SearchBackend
|
|
11
|
+
|
|
12
|
+
def for_scope(
|
|
13
|
+
self,
|
|
14
|
+
scope: Scope,
|
|
15
|
+
scope_id: str | None = None,
|
|
16
|
+
) -> ScopedIndices:
|
|
17
|
+
return ScopedIndices(
|
|
18
|
+
backend=self.backend,
|
|
19
|
+
scope=scope,
|
|
20
|
+
scope_id=scope_id,
|
|
21
|
+
)
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# aethergraph/indices.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from aethergraph.contracts.storage.search_backend import ScoredItem, SearchBackend
|
|
9
|
+
from aethergraph.services.scope.scope import Scope
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ScopedIndices:
|
|
14
|
+
"""
|
|
15
|
+
Scope-aware wrapper around the global SearchBackend.
|
|
16
|
+
|
|
17
|
+
- scope: Scope defining org/user/app/run/session/node
|
|
18
|
+
- scope_id: usually a memory_scope_id for memory-tied corpora,
|
|
19
|
+
but can be anything logical (or None).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
backend: SearchBackend
|
|
23
|
+
scope: Scope
|
|
24
|
+
scope_id: str | None = None
|
|
25
|
+
|
|
26
|
+
# --- internals --------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
def _base_metadata(self) -> dict[str, Any]:
|
|
29
|
+
"""
|
|
30
|
+
Default metadata to attach on *writes*.
|
|
31
|
+
|
|
32
|
+
For memory-ish corpora, this matches RAG docs:
|
|
33
|
+
- user_id, org_id, client_id, app_id, session_id, run_id, graph_id, node_id
|
|
34
|
+
- scope_id (usually memory_scope_id)
|
|
35
|
+
"""
|
|
36
|
+
return self.scope.rag_labels(scope_id=self.scope_id)
|
|
37
|
+
|
|
38
|
+
def _base_filters(self) -> dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Default filters for *reads*.
|
|
41
|
+
|
|
42
|
+
For memory-ish corpora, this matches RAG search:
|
|
43
|
+
- user_id, org_id, (and scope_id if provided)
|
|
44
|
+
"""
|
|
45
|
+
return self.scope.rag_filter(scope_id=self.scope_id)
|
|
46
|
+
|
|
47
|
+
# --- public APIs ------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
async def upsert(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
corpus: str,
|
|
53
|
+
item_id: str,
|
|
54
|
+
text: str,
|
|
55
|
+
metadata: Mapping[str, Any] | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Upsert (insert or update) a text item with associated metadata into the backend index.
|
|
59
|
+
This method merges base metadata with any provided metadata, strips out keys with None values,
|
|
60
|
+
and delegates the upsert operation to the backend. This ensures that only meaningful metadata
|
|
61
|
+
is stored and that None values are treated as wildcards by the backend.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
Basic usage to upsert a text item:
|
|
65
|
+
```python
|
|
66
|
+
await service.upsert(
|
|
67
|
+
corpus="my_corpus",
|
|
68
|
+
item_id="item123",
|
|
69
|
+
text="Sample document text."
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Upserting with additional metadata:
|
|
73
|
+
```python
|
|
74
|
+
await service.upsert(
|
|
75
|
+
corpus="my_corpus",
|
|
76
|
+
item_id="item123",
|
|
77
|
+
text="Sample document text.",
|
|
78
|
+
metadata={"author": "Alice", "category": "news"}
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Args:
|
|
82
|
+
corpus: The name of the corpus or collection to upsert the item into.
|
|
83
|
+
item_id: The unique identifier for the item within the corpus.
|
|
84
|
+
text: The text content to be indexed or updated.
|
|
85
|
+
metadata: Optional mapping of additional metadata to associate with the item.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
None
|
|
89
|
+
|
|
90
|
+
Notes:
|
|
91
|
+
Metadata keys with None values are omitted before upserting to the backend.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
base = self._base_metadata()
|
|
95
|
+
merged: dict[str, Any] = {**base, **(metadata or {})}
|
|
96
|
+
# strip None so backends can treat them as wildcards
|
|
97
|
+
merged = {k: v for k, v in merged.items() if v is not None}
|
|
98
|
+
|
|
99
|
+
await self.backend.upsert(
|
|
100
|
+
corpus=corpus,
|
|
101
|
+
item_id=item_id,
|
|
102
|
+
text=text,
|
|
103
|
+
metadata=merged,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def search(
|
|
107
|
+
self,
|
|
108
|
+
*,
|
|
109
|
+
corpus: str,
|
|
110
|
+
query: str,
|
|
111
|
+
top_k: int = 10,
|
|
112
|
+
filters: Mapping[str, Any] | None = None,
|
|
113
|
+
time_window: str | None = None,
|
|
114
|
+
created_at_min: float | None = None,
|
|
115
|
+
created_at_max: float | None = None,
|
|
116
|
+
) -> list[ScoredItem]:
|
|
117
|
+
"""
|
|
118
|
+
Perform a search operation on the specified corpus.
|
|
119
|
+
This method executes a search query against the backend, applying optional filters,
|
|
120
|
+
time constraints, and other parameters to refine the results.
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
Basic usage to search a corpus:
|
|
124
|
+
```python
|
|
125
|
+
results = await search(corpus="documents", query="machine learning")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Searching with additional filters and time constraints:
|
|
129
|
+
```python
|
|
130
|
+
results = await search(
|
|
131
|
+
corpus="articles",
|
|
132
|
+
query="AI advancements",
|
|
133
|
+
top_k=5,
|
|
134
|
+
filters={"author": "John Doe"},
|
|
135
|
+
time_window="7d"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
corpus: The name of the corpus to search within.
|
|
140
|
+
query: The search query string.
|
|
141
|
+
top_k: The maximum number of results to return (default: 10).
|
|
142
|
+
filters: Optional dictionary of additional filters to apply to the search.
|
|
143
|
+
time_window: Optional human-friendly duration (e.g., "7d", "24h", "30m")
|
|
144
|
+
interpreted as [now - window, now] in created_at_ts. Ignored if `created_at_min` is provided.
|
|
145
|
+
created_at_min: Optional minimum UNIX timestamp (float) for filtering results by creation time.
|
|
146
|
+
created_at_max: Optional maximum UNIX timestamp (float) for filtering results by creation time.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
A list of `ScoredItem` objects representing the search results.
|
|
150
|
+
|
|
151
|
+
Notes:
|
|
152
|
+
- If `time_window` is provided, it is used to calculate the time range unless `created_at_min` is explicitly set.
|
|
153
|
+
- Filters with `None` values are automatically excluded from the search.
|
|
154
|
+
"""
|
|
155
|
+
base = self._base_filters()
|
|
156
|
+
merged: dict[str, Any] = {**base, **(filters or {})}
|
|
157
|
+
merged = {k: v for k, v in merged.items() if v is not None}
|
|
158
|
+
|
|
159
|
+
return await self.backend.search(
|
|
160
|
+
corpus=corpus,
|
|
161
|
+
query=query,
|
|
162
|
+
top_k=top_k,
|
|
163
|
+
filters=merged,
|
|
164
|
+
time_window=time_window,
|
|
165
|
+
created_at_min=created_at_min,
|
|
166
|
+
created_at_max=created_at_max,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# ergonomic helpers (optional but nice)
|
|
170
|
+
|
|
171
|
+
async def search_events(
|
|
172
|
+
self,
|
|
173
|
+
query: str,
|
|
174
|
+
*,
|
|
175
|
+
top_k: int = 20,
|
|
176
|
+
filters: Mapping[str, Any] | None = None,
|
|
177
|
+
time_window: str | None = None,
|
|
178
|
+
created_at_min: float | None = None,
|
|
179
|
+
created_at_max: float | None = None,
|
|
180
|
+
) -> list[ScoredItem]:
|
|
181
|
+
"""
|
|
182
|
+
Perform a search for events based on the given query and optional filters.
|
|
183
|
+
|
|
184
|
+
This method queries the "event" corpus using the specified parameters to retrieve
|
|
185
|
+
a list of scored items matching the search criteria.
|
|
186
|
+
|
|
187
|
+
Examples:
|
|
188
|
+
Basic usage to search for events:
|
|
189
|
+
```python
|
|
190
|
+
results = await search_events("error logs")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Searching with additional filters and a time window:
|
|
194
|
+
```python
|
|
195
|
+
results = await search_events(
|
|
196
|
+
"user activity",
|
|
197
|
+
top_k=10,
|
|
198
|
+
filters={"status": "active"},
|
|
199
|
+
time_window="last_24_hours",
|
|
200
|
+
created_at_min=1672531200.0,
|
|
201
|
+
created_at_max=1672617600.0
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
query: The search query string.
|
|
206
|
+
top_k: The maximum number of results to return (default: 20).
|
|
207
|
+
filters: Optional dictionary of filters to apply to the search.
|
|
208
|
+
time_window: Optional time window for the search (e.g., "last_24_hours").
|
|
209
|
+
created_at_min: Optional minimum creation timestamp for filtering results.
|
|
210
|
+
created_at_max: Optional maximum creation timestamp for filtering results.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
A list of `ScoredItem` objects representing the search results.
|
|
214
|
+
|
|
215
|
+
Notes:
|
|
216
|
+
- The `filters` parameter allows you to specify key-value pairs to narrow down the search results.
|
|
217
|
+
- The `time_window` parameter can be used to specify a predefined time range for the search.
|
|
218
|
+
- The `created_at_min` and `created_at_max` parameters allow for fine-grained control over the
|
|
219
|
+
creation time range of the events being searched.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
items = await self.search(
|
|
223
|
+
corpus="event",
|
|
224
|
+
query=query,
|
|
225
|
+
top_k=top_k,
|
|
226
|
+
filters=filters,
|
|
227
|
+
time_window=time_window,
|
|
228
|
+
created_at_min=created_at_min,
|
|
229
|
+
created_at_max=created_at_max,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return items
|
|
233
|
+
|
|
234
|
+
async def search_artifacts(
|
|
235
|
+
self,
|
|
236
|
+
query: str,
|
|
237
|
+
*,
|
|
238
|
+
top_k: int = 20,
|
|
239
|
+
filters: Mapping[str, Any] | None = None,
|
|
240
|
+
time_window: str | None = None,
|
|
241
|
+
created_at_min: float | None = None,
|
|
242
|
+
created_at_max: float | None = None,
|
|
243
|
+
) -> list[ScoredItem]:
|
|
244
|
+
"""
|
|
245
|
+
Perform a search for artifacts based on the provided query and optional filters.
|
|
246
|
+
|
|
247
|
+
This method queries the "artifact" corpus using the specified parameters and returns
|
|
248
|
+
a list of scored items matching the search criteria.
|
|
249
|
+
|
|
250
|
+
Examples:
|
|
251
|
+
Basic usage to search for artifacts:
|
|
252
|
+
```python
|
|
253
|
+
results = await search_artifacts("example query")
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Searching with additional filters and a time window:
|
|
257
|
+
```python
|
|
258
|
+
results = await search_artifacts(
|
|
259
|
+
"example query",
|
|
260
|
+
top_k=10,
|
|
261
|
+
filters={"type": "document"},
|
|
262
|
+
time_window="last_7_days",
|
|
263
|
+
created_at_min=1672531200.0,
|
|
264
|
+
created_at_max=1672617600.0,
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
query: The search query string.
|
|
269
|
+
top_k: The maximum number of results to return (default: 20).
|
|
270
|
+
filters: Optional dictionary of filters to apply to the search.
|
|
271
|
+
time_window: Optional time window for the search (e.g., "last_7_days").
|
|
272
|
+
created_at_min: Optional minimum creation timestamp for filtering results.
|
|
273
|
+
created_at_max: Optional maximum creation timestamp for filtering results.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
A list of `ScoredItem` objects representing the search results.
|
|
277
|
+
|
|
278
|
+
Notes:
|
|
279
|
+
- The `filters` parameter allows specifying additional constraints for the search.
|
|
280
|
+
- The `time_window` parameter can be used to limit results to a specific time range.
|
|
281
|
+
- The `created_at_min` and `created_at_max` parameters allow filtering by creation time.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
return await self.search(
|
|
285
|
+
corpus="artifact",
|
|
286
|
+
query=query,
|
|
287
|
+
top_k=top_k,
|
|
288
|
+
filters=filters,
|
|
289
|
+
time_window=time_window,
|
|
290
|
+
created_at_min=created_at_min,
|
|
291
|
+
created_at_max=created_at_max,
|
|
292
|
+
)
|