aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a2__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 +293 -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 +190 -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.0a2.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a2.dist-info/RECORD +356 -0
- aethergraph-0.1.0a2.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.0a2.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a2.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import timedelta
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from aethergraph.services.artifacts.facade import ArtifactFacade
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from aethergraph.core.runtime.run_manager import RunManager
|
|
4
9
|
|
|
5
10
|
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
11
|
+
from aethergraph.core.runtime.run_types import (
|
|
12
|
+
RunImportance,
|
|
13
|
+
RunOrigin,
|
|
14
|
+
RunRecord,
|
|
15
|
+
RunVisibility,
|
|
16
|
+
)
|
|
6
17
|
from aethergraph.core.runtime.runtime_services import get_ext_context_service
|
|
7
18
|
from aethergraph.services.channel.session import ChannelSession
|
|
8
19
|
from aethergraph.services.continuations.continuation import Continuation
|
|
9
20
|
from aethergraph.services.llm.providers import Provider
|
|
10
21
|
from aethergraph.services.memory.facade import MemoryFacade
|
|
22
|
+
from aethergraph.services.scope.scope import Scope
|
|
23
|
+
from aethergraph.services.viz.facade import VizFacade
|
|
11
24
|
|
|
12
25
|
from .base_service import _ServiceHandle
|
|
13
26
|
from .bound_memory import BoundMemoryAdapter
|
|
@@ -17,22 +30,346 @@ from .node_services import NodeServices
|
|
|
17
30
|
@dataclass
|
|
18
31
|
class NodeContext:
|
|
19
32
|
run_id: str
|
|
33
|
+
session_id: str
|
|
20
34
|
graph_id: str
|
|
21
35
|
node_id: str
|
|
22
36
|
services: NodeServices
|
|
37
|
+
identity: Any = None
|
|
23
38
|
resume_payload: dict[str, Any] | None = None
|
|
39
|
+
scope: Scope | None = None
|
|
40
|
+
agent_id: str | None = None # for agent-invoked runs
|
|
41
|
+
app_id: str | None = None # for app-invoked runs
|
|
24
42
|
bound_memory: BoundMemoryAdapter | None = None # back-compat
|
|
25
43
|
|
|
26
44
|
# --- accessors (compatible names) ---
|
|
27
45
|
def runtime(self) -> NodeServices:
|
|
28
46
|
return self.services
|
|
29
47
|
|
|
48
|
+
async def spawn_run(
|
|
49
|
+
self,
|
|
50
|
+
graph_id: str,
|
|
51
|
+
*,
|
|
52
|
+
inputs: dict[str, Any],
|
|
53
|
+
session_id: str | None = None,
|
|
54
|
+
tags: list[str] | None = None,
|
|
55
|
+
visibility: RunVisibility | None = None,
|
|
56
|
+
origin: RunOrigin | None = None,
|
|
57
|
+
importance: RunImportance | None = None,
|
|
58
|
+
agent_id: str | None = None,
|
|
59
|
+
app_id: str | None = None,
|
|
60
|
+
run_id: str | None = None,
|
|
61
|
+
) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Launch a new run from within the current node or graph context.
|
|
64
|
+
|
|
65
|
+
This method creates and schedules a new run for the specified graph, using the provided inputs and optional metadata.
|
|
66
|
+
It does not wait for the run to complete; instead, it returns immediately with the new run's ID.
|
|
67
|
+
The run is managed asynchronously in the background, and is tracked and persisted via the configured RunManager.
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
Basic usage to spawn a run for a graph:
|
|
71
|
+
```python
|
|
72
|
+
run_id = await context.spawn_run(
|
|
73
|
+
"my-graph-id",
|
|
74
|
+
inputs={"x": 1, "y": 2}
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Spawning a run with custom tags and agent context:
|
|
79
|
+
```python
|
|
80
|
+
from aethergraph.runtime import RunVisibility
|
|
81
|
+
run_id = await context.spawn_run(
|
|
82
|
+
"my-graph-id",
|
|
83
|
+
inputs={"foo": "bar"},
|
|
84
|
+
tags=["experiment", "priority"],
|
|
85
|
+
agent_id="agent-123", # associate with an agent if applicable
|
|
86
|
+
visibility=RunVisibility.ineline, # not shown in UI
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
graph_id: The unique identifier of the graph to execute. i.e. the `name` field of a registered graph.
|
|
92
|
+
inputs: Dictionary of input values to pass to the graph.
|
|
93
|
+
session_id: Optional session identifier. Defaults to the current context's session if not provided.
|
|
94
|
+
tags: Optional list of string tags for categorization and tracking.
|
|
95
|
+
visibility: Optional visibility setting for the run (e.g., public, private, normal).
|
|
96
|
+
origin: Optional indicator of the run's origin (e.g., agent, app). Defaults based on agent_id.
|
|
97
|
+
importance: Optional importance level for the run (e.g., normal, high).
|
|
98
|
+
agent_id: Optional agent identifier if the run is associated with an agent.
|
|
99
|
+
app_id: Optional application identifier if the run is associated with an app.
|
|
100
|
+
run_id: Optional explicit run identifier. If not provided, one is generated.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
str: The unique run_id of the newly created run.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
RuntimeError: If the RunManager service is not configured in the context.
|
|
107
|
+
|
|
108
|
+
Notes:
|
|
109
|
+
- The spawned run inherits the context's identity for provenance tracking.
|
|
110
|
+
- Metadata `tags`, `visibility`, `origin`, `importance`, `agent_id`, `app_id`, help manage and monitor the run in AG UI,
|
|
111
|
+
but do not affect the execution logic of the graph itself. If you are not using AG UI, these fields can be omitted.
|
|
112
|
+
"""
|
|
113
|
+
rm: RunManager | None = getattr(self.services, "run_manager", None)
|
|
114
|
+
if rm is None:
|
|
115
|
+
raise RuntimeError("NodeContext.services.run_manager is not configured")
|
|
116
|
+
effective_session_id = session_id or self.session_id
|
|
117
|
+
|
|
118
|
+
record = await rm.submit_run(
|
|
119
|
+
graph_id=graph_id,
|
|
120
|
+
inputs=inputs,
|
|
121
|
+
run_id=run_id,
|
|
122
|
+
session_id=effective_session_id,
|
|
123
|
+
tags=tags,
|
|
124
|
+
visibility=visibility or RunVisibility.normal,
|
|
125
|
+
origin=origin or (RunOrigin.agent if agent_id is not None else RunOrigin.app),
|
|
126
|
+
importance=importance or RunImportance.normal,
|
|
127
|
+
agent_id=agent_id,
|
|
128
|
+
app_id=app_id,
|
|
129
|
+
identity=self.identity, # internal spawn; not coming from HTTP directly
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return record.run_id
|
|
133
|
+
|
|
134
|
+
async def run_and_wait(
|
|
135
|
+
self,
|
|
136
|
+
graph_id: str,
|
|
137
|
+
*,
|
|
138
|
+
inputs: dict[str, Any],
|
|
139
|
+
session_id: str | None = None,
|
|
140
|
+
tags: list[str] | None = None,
|
|
141
|
+
visibility: RunVisibility | None = None,
|
|
142
|
+
origin: RunOrigin | None = None,
|
|
143
|
+
importance: RunImportance | None = None,
|
|
144
|
+
agent_id: str | None = None,
|
|
145
|
+
app_id: str | None = None,
|
|
146
|
+
run_id: str | None = None,
|
|
147
|
+
) -> tuple[str, dict[str, Any] | None, bool, list[dict[str, Any]]]:
|
|
148
|
+
"""
|
|
149
|
+
Run a child graph as a first-class RunManager run and wait for completion.
|
|
150
|
+
|
|
151
|
+
This method launches a new run for the specified graph, waits for it to finish,
|
|
152
|
+
and returns its outputs and metadata. The run is tracked and visualized in the UI,
|
|
153
|
+
and all status updates are persisted via the RunManager.
|
|
154
|
+
|
|
155
|
+
Examples:
|
|
156
|
+
Basic usage to run and wait for a graph:
|
|
157
|
+
```python
|
|
158
|
+
run_id, outputs, has_waits, continuations = await context.run_and_wait(
|
|
159
|
+
"my-graph-id",
|
|
160
|
+
inputs={"x": 1, "y": 2}
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Running with custom tags and agent context:
|
|
165
|
+
```python
|
|
166
|
+
run_id, outputs, has_waits, continuations = await context.run_and_wait(
|
|
167
|
+
"my-graph-id",
|
|
168
|
+
inputs={"foo": "bar"},
|
|
169
|
+
tags=["experiment", "priority"],
|
|
170
|
+
agent_id="agent-123",
|
|
171
|
+
visibility=RunVisibility.inline,
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
graph_id: The unique identifier of the graph to execute.
|
|
177
|
+
inputs: Dictionary of input values to pass to the graph.
|
|
178
|
+
session_id: Optional session identifier. Defaults to the current context's session.
|
|
179
|
+
tags: Optional list of string tags for categorization and tracking.
|
|
180
|
+
visibility: Optional visibility setting for the run (e.g., public, private, normal).
|
|
181
|
+
origin: Optional indicator of the run's origin (e.g., agent, app).
|
|
182
|
+
importance: Optional importance level for the run (e.g., normal, high).
|
|
183
|
+
agent_id: Optional agent identifier if the run is associated with an agent.
|
|
184
|
+
app_id: Optional application identifier if the run is associated with an app.
|
|
185
|
+
run_id: Optional explicit run identifier. If not provided, one is generated.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
run_id (str): The unique run ID of the completed run.
|
|
189
|
+
outputs (dict | None): The outputs returned by the graph.
|
|
190
|
+
has_waits (bool): True if the run contained any wait nodes. [Not currently used]
|
|
191
|
+
continuations (list[dict]): List of continuation metadata, if any. [Not currently used]
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
RuntimeError: If the RunManager service is not configured in the context.
|
|
195
|
+
|
|
196
|
+
Notes:
|
|
197
|
+
- The run is fully tracked and visualized in the AG UI.
|
|
198
|
+
- Use this method for orchestration patterns where you need to await child runs.
|
|
199
|
+
- Metadata fields help with monitoring and provenance, but do not affect graph logic.
|
|
200
|
+
|
|
201
|
+
Warning:
|
|
202
|
+
- This method blocks until the child run completes.
|
|
203
|
+
- This method will not honor the concurrency limits of the parent run, and may lead to deadlocks if the parent run is waiting on resources held by the child run.
|
|
204
|
+
- Avoid using this method in high-concurrency scenarios to prevent deadlocks.
|
|
205
|
+
For such cases, consider using `spawn_run` followed by `wait_run` instead.
|
|
206
|
+
"""
|
|
207
|
+
rm: RunManager | None = getattr(self.services, "run_manager", None)
|
|
208
|
+
if rm is None:
|
|
209
|
+
raise RuntimeError("NodeContext.services.run_manager is not configured")
|
|
210
|
+
|
|
211
|
+
effective_session_id = session_id or self.session_id
|
|
212
|
+
|
|
213
|
+
record, outputs, has_waits, continuations = await rm.run_and_wait(
|
|
214
|
+
graph_id,
|
|
215
|
+
inputs=inputs,
|
|
216
|
+
run_id=run_id,
|
|
217
|
+
session_id=effective_session_id,
|
|
218
|
+
tags=tags,
|
|
219
|
+
visibility=visibility or RunVisibility.normal,
|
|
220
|
+
origin=origin or (RunOrigin.agent if agent_id is not None else RunOrigin.app),
|
|
221
|
+
importance=importance or RunImportance.normal,
|
|
222
|
+
agent_id=agent_id,
|
|
223
|
+
app_id=app_id,
|
|
224
|
+
identity=self.identity, # keep provenance consistent with spawn_run
|
|
225
|
+
count_slot=False, # nested orchestration: avoid deadlock
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return record.run_id, outputs, has_waits, continuations
|
|
229
|
+
|
|
230
|
+
async def wait_run(
|
|
231
|
+
self,
|
|
232
|
+
run_id: str,
|
|
233
|
+
*,
|
|
234
|
+
timeout_s: float | None = None,
|
|
235
|
+
) -> RunRecord:
|
|
236
|
+
"""
|
|
237
|
+
Wait for a run to complete and retrieve its final record.
|
|
238
|
+
|
|
239
|
+
This method waits the RunManager for the specified run until it finishes,
|
|
240
|
+
then returns the completed RunRecord. Optionally, a timeout (in seconds)
|
|
241
|
+
can be set to limit how long to wait.
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
Basic usage to wait for a run:
|
|
245
|
+
```python
|
|
246
|
+
run_id = await context.spawn_run("my-graph-id", inputs={"x": 1})
|
|
247
|
+
record = await context.wait_run(run_id)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Waiting with a timeout:
|
|
251
|
+
```python
|
|
252
|
+
record = await context.wait_run(run_id, timeout_s=30)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
run_id: The unique identifier of the run to wait for.
|
|
257
|
+
timeout_s: Optional timeout in seconds. If set, the method will raise
|
|
258
|
+
a TimeoutError if the run does not complete in time.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
RunRecord: The final record of the completed run.
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
RuntimeError: If the RunManager service is not configured in the context.
|
|
265
|
+
TimeoutError: If the run does not complete within the specified timeout.
|
|
266
|
+
|
|
267
|
+
Notes:
|
|
268
|
+
- This method is useful for orchestration patterns where you need to
|
|
269
|
+
synchronize on the completion of child runs.
|
|
270
|
+
- For high-concurrency scenarios, prefer using `spawn_run` and `wait_run`
|
|
271
|
+
in combination rather than `run_and_wait`.
|
|
272
|
+
"""
|
|
273
|
+
rm: RunManager | None = getattr(self.services, "run_manager", None)
|
|
274
|
+
if rm is None:
|
|
275
|
+
raise RuntimeError("NodeContext.services.run_manager is not configured")
|
|
276
|
+
return await rm.wait_run(run_id, timeout_s=timeout_s)
|
|
277
|
+
|
|
278
|
+
async def cancel_run(self, run_id: str) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Cancel a scheduled or running child run by its unique ID.
|
|
281
|
+
|
|
282
|
+
This method requests cancellation of a run managed by the RunManager.
|
|
283
|
+
The cancellation is propagated to the run's execution context, and any
|
|
284
|
+
in-progress tasks will be interrupted if possible.
|
|
285
|
+
|
|
286
|
+
Examples:
|
|
287
|
+
Basic usage to cancel a spawned run:
|
|
288
|
+
```python
|
|
289
|
+
run_id = await context.spawn_run("my-graph-id", inputs={"x": 1})
|
|
290
|
+
await context.cancel_run(run_id)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Cancel a run after waiting for a condition:
|
|
294
|
+
```python
|
|
295
|
+
if should_abort:
|
|
296
|
+
await context.cancel_run(run_id)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
run_id: The unique identifier of the run to cancel.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
None. The cancellation request is dispatched to the RunManager.
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
RuntimeError: If the RunManager service is not configured in the context.
|
|
307
|
+
|
|
308
|
+
Notes:
|
|
309
|
+
- Cancellation is best-effort and may not immediately terminate all tasks.
|
|
310
|
+
- Use this method for orchestration patterns where you need to abort child runs.
|
|
311
|
+
- The run's status will be updated to "cancelled" in the UI and persistence layer.
|
|
312
|
+
"""
|
|
313
|
+
rm: RunManager | None = getattr(self.services, "run_manager", None)
|
|
314
|
+
if rm is None:
|
|
315
|
+
raise RuntimeError("NodeContext.services.run_manager is not configured")
|
|
316
|
+
await rm.cancel_run(run_id)
|
|
317
|
+
|
|
30
318
|
def logger(self):
|
|
31
319
|
return self.services.logger.for_node_ctx(
|
|
32
320
|
run_id=self.run_id, node_id=self.node_id, graph_id=self.graph_id
|
|
33
321
|
)
|
|
34
322
|
|
|
323
|
+
def ui_session_channel(self) -> "ChannelSession":
|
|
324
|
+
"""
|
|
325
|
+
Creates a new ChannelSession for the current node context with session key as
|
|
326
|
+
`ui:session/<session_id>`.
|
|
327
|
+
|
|
328
|
+
This method is a convenience helper for the AG UI to get the default session channel.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
ChannelSession: The channel session associated with the current session.
|
|
332
|
+
"""
|
|
333
|
+
if not self.session_id:
|
|
334
|
+
raise RuntimeError("NodeContext.session_id is not set")
|
|
335
|
+
return ChannelSession(self, f"ui:session/{self.session_id}")
|
|
336
|
+
|
|
337
|
+
def ui_run_channel(self) -> "ChannelSession":
|
|
338
|
+
"""
|
|
339
|
+
Creates a new ChannelSession for the current node context with session key as
|
|
340
|
+
`ui:run/<run_id>`.
|
|
341
|
+
|
|
342
|
+
This method is a convenience helper for the AG UI to get the default run channel.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
ChannelSession: The channel session associated with the current run.
|
|
346
|
+
"""
|
|
347
|
+
return ChannelSession(self, f"ui:run/{self.run_id}")
|
|
348
|
+
|
|
35
349
|
def channel(self, channel_key: str | None = None):
|
|
350
|
+
"""
|
|
351
|
+
Set up a new ChannelSession for the current node context.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
channel_key (str | None): An optional key to specify a particular channel.
|
|
355
|
+
If not provided, the default channel will be used.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
ChannelSession: An instance representing the session for the specified channel.
|
|
359
|
+
|
|
360
|
+
Notes:
|
|
361
|
+
Supported channel key formats include:
|
|
362
|
+
|
|
363
|
+
| Channel Type | Format Example | Notes |
|
|
364
|
+
|----------------------|-----------------------------------------------|---------------------------------------|
|
|
365
|
+
| Console | `console:stdin` | Console input/output |
|
|
366
|
+
| Slack | `slack:team/{team_id}:chan/{channel_id}` | Needs additional configuration |
|
|
367
|
+
| Telegram | `tg:chat/{chat_id}` | Needs additional configuration |
|
|
368
|
+
| UI Session | `ui:session/{session_id}` | Requires AG web UI |
|
|
369
|
+
| UI Run | `ui:run/{run_id}` | Requires AG web UI |
|
|
370
|
+
| Webhook | `webhook:{unique_identifier}` | For Slack, Discord, Zapier, etc. |
|
|
371
|
+
| File-based channel | `file:path/to/directory` | File system based channels |
|
|
372
|
+
"""
|
|
36
373
|
return ChannelSession(self, channel_key)
|
|
37
374
|
|
|
38
375
|
# New way: prefer memory_facade directly
|
|
@@ -48,7 +385,7 @@ class NodeContext:
|
|
|
48
385
|
return self.bound_memory
|
|
49
386
|
|
|
50
387
|
# Artifacts / index
|
|
51
|
-
def artifacts(self):
|
|
388
|
+
def artifacts(self) -> ArtifactFacade:
|
|
52
389
|
return self.services.artifact_store
|
|
53
390
|
|
|
54
391
|
def kv(self):
|
|
@@ -56,6 +393,11 @@ class NodeContext:
|
|
|
56
393
|
raise RuntimeError("KV not available")
|
|
57
394
|
return self.services.kv
|
|
58
395
|
|
|
396
|
+
def viz(self) -> VizFacade:
|
|
397
|
+
if not self.services.viz:
|
|
398
|
+
raise RuntimeError("Viz service (facade) not available")
|
|
399
|
+
return self.services.viz
|
|
400
|
+
|
|
59
401
|
def llm(
|
|
60
402
|
self,
|
|
61
403
|
profile: str = "default",
|
|
@@ -68,9 +410,43 @@ class NodeContext:
|
|
|
68
410
|
timeout: float | None = None,
|
|
69
411
|
) -> LLMClientProtocol:
|
|
70
412
|
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
413
|
+
Retrieve or configure an LLM client for this context.
|
|
414
|
+
|
|
415
|
+
This method allows you to access a language model client by profile name,
|
|
416
|
+
or dynamically override its configuration at runtime.
|
|
417
|
+
|
|
418
|
+
Examples:
|
|
419
|
+
Get the default LLM client:
|
|
420
|
+
```python
|
|
421
|
+
llm = context.llm()
|
|
422
|
+
response = await llm.complete("Hello, world!")
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Use a custom profile:
|
|
426
|
+
```python
|
|
427
|
+
llm = context.llm(profile="my-profile")
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Override provider and model for a one-off call:
|
|
431
|
+
```python
|
|
432
|
+
llm = context.llm(
|
|
433
|
+
provider=Provider.OpenAI,
|
|
434
|
+
model="gpt-4-turbo",
|
|
435
|
+
api_key="sk-...",
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
profile: The profile name to use (default: "default"). Set up in `.env` or `register_llm_client()` method.
|
|
441
|
+
provider: Optionally override the provider (e.g., `Provider.OpenAI`).
|
|
442
|
+
model: Optionally override the model name.
|
|
443
|
+
base_url: Optionally override the base URL for the LLM API.
|
|
444
|
+
api_key: Optionally override the API key for authentication.
|
|
445
|
+
azure_deployment: Optionally specify an Azure deployment name.
|
|
446
|
+
timeout: Optionally set a request timeout (in seconds).
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
LLMClientProtocol: The configured LLM client instance for this context.
|
|
74
450
|
"""
|
|
75
451
|
svc = self.services.llm
|
|
76
452
|
|
|
@@ -96,7 +472,41 @@ class NodeContext:
|
|
|
96
472
|
|
|
97
473
|
def llm_set_key(self, provider: str, model: str, api_key: str, profile: str = "default"):
|
|
98
474
|
"""
|
|
99
|
-
Quickly configure or override the provider
|
|
475
|
+
Quickly configure or override the LLM provider, model, and API key for a given profile.
|
|
476
|
+
|
|
477
|
+
This method allows you to update the credentials and model configuration for a specific
|
|
478
|
+
LLM profile at runtime. It is useful for dynamically switching providers or rotating keys
|
|
479
|
+
without restarting the application.
|
|
480
|
+
|
|
481
|
+
Examples:
|
|
482
|
+
Set the OpenAI API key for the default profile:
|
|
483
|
+
```python
|
|
484
|
+
context.llm_set_key(
|
|
485
|
+
provider="openai",
|
|
486
|
+
model="gpt-4-turbo",
|
|
487
|
+
api_key="sk-...",
|
|
488
|
+
)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Configure a custom profile for Anthropic:
|
|
492
|
+
```python
|
|
493
|
+
context.llm_set_key(
|
|
494
|
+
provider="anthropic",
|
|
495
|
+
model="claude-3-opus",
|
|
496
|
+
api_key="sk-ant-...",
|
|
497
|
+
profile="anthropic-profile"
|
|
498
|
+
)
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
provider: The LLM provider name (e.g., "openai", "anthropic").
|
|
503
|
+
model: The model name or identifier to use.
|
|
504
|
+
api_key: The API key or credential for the provider.
|
|
505
|
+
profile: The profile name to update (default: "default").
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
None. The profile is updated in-place and will be used for subsequent calls
|
|
509
|
+
to `context.llm(profile=...)`.
|
|
100
510
|
"""
|
|
101
511
|
svc = self.services.llm
|
|
102
512
|
svc.set_key(provider=provider, model=model, api_key=api_key, profile=profile)
|
|
@@ -124,6 +534,34 @@ class NodeContext:
|
|
|
124
534
|
return self.services.clock
|
|
125
535
|
|
|
126
536
|
def svc(self, name: str) -> Any:
|
|
537
|
+
"""
|
|
538
|
+
Retrieve and bind an external context service by name. This method is equivalent to `context.<service_name>()`.
|
|
539
|
+
User can use either `context.svc("service_name")` or `context.service_name()` to access the service.
|
|
540
|
+
|
|
541
|
+
This method accesses a registered external service, optionally binding it to the current
|
|
542
|
+
node context if the service supports context binding via a `bind` method.
|
|
543
|
+
|
|
544
|
+
Examples:
|
|
545
|
+
Basic usage to access a service:
|
|
546
|
+
```python
|
|
547
|
+
db = context.svc("database")
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Accessing a service that requires context binding:
|
|
551
|
+
```python
|
|
552
|
+
logger = context.svc("logger")
|
|
553
|
+
logger.info("Node started.")
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
name: The unique string identifier of the external service to retrieve.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Any: The external service instance, bound to the current context if applicable.
|
|
561
|
+
|
|
562
|
+
Raises:
|
|
563
|
+
KeyError: If the requested service is not registered in the external context.
|
|
564
|
+
"""
|
|
127
565
|
# generic accessor for external context services
|
|
128
566
|
raw = get_ext_context_service(name)
|
|
129
567
|
if raw is None:
|
|
@@ -135,6 +573,68 @@ class NodeContext:
|
|
|
135
573
|
return raw
|
|
136
574
|
|
|
137
575
|
def __getattr__(self, name: str) -> Any:
|
|
576
|
+
"""
|
|
577
|
+
Retrieve and bind an external context service by name. This allows accessing services as attributes on the context object.
|
|
578
|
+
|
|
579
|
+
This method overrides attribute access to dynamically resolve external services registered in the context.
|
|
580
|
+
If a service with the requested name exists, it is retrieved and wrapped in a `_ServiceHandle` for ergonomic access.
|
|
581
|
+
The returned handle allows attribute access, direct retrieval, and call forwarding if the service is callable.
|
|
582
|
+
|
|
583
|
+
Examples:
|
|
584
|
+
```python
|
|
585
|
+
# Retrieve a database service and run a query
|
|
586
|
+
db = context.database()
|
|
587
|
+
db.query("SELECT * FROM users")
|
|
588
|
+
|
|
589
|
+
# Access a logger service and log a message
|
|
590
|
+
context.logger.info("Hello from node!")
|
|
591
|
+
|
|
592
|
+
# Forward arguments to a callable service
|
|
593
|
+
result = context.some_tool("input text")
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
name: The name of the service to resolve as an attribute.
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
_ServiceHandle: A callable handle to the resolved service.
|
|
601
|
+
|
|
602
|
+
Raises:
|
|
603
|
+
AttributeError: If no service with the given name exists in the context.
|
|
604
|
+
|
|
605
|
+
Usage:
|
|
606
|
+
- You can access external services directly as attributes on the context object.
|
|
607
|
+
For example, if you have registered a service named "my_service", you can use:
|
|
608
|
+
|
|
609
|
+
```python
|
|
610
|
+
# Get the service instance
|
|
611
|
+
svc = context.my_service()
|
|
612
|
+
|
|
613
|
+
# Call the service if it's callable
|
|
614
|
+
result = context.my_service(arg1, arg2)
|
|
615
|
+
|
|
616
|
+
# Access service attributes
|
|
617
|
+
value = context.my_service.some_attribute
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
- In your Service, you can use `self.ctx` to access the node context if needed. For example:
|
|
621
|
+
```python
|
|
622
|
+
class MyService:
|
|
623
|
+
...
|
|
624
|
+
def my_method(self, ...):
|
|
625
|
+
context = self.ctx # Access the NodeContext
|
|
626
|
+
# Use context information as needed
|
|
627
|
+
context.channel.send("Hello from MyService!")
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
Notes:
|
|
631
|
+
- If the service is not registered, an AttributeError is raised.
|
|
632
|
+
- If the service is callable, calling `context.service_name(args)` will forward the call.
|
|
633
|
+
- If you call `context.service_name()` with no arguments, you get the underlying service instance.
|
|
634
|
+
- Attribute access (e.g., `context.service_name.some_attr`) is delegated to the service.
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
"""
|
|
138
638
|
# Try to resolve as an external context service
|
|
139
639
|
try:
|
|
140
640
|
bound = self.svc(name)
|
|
@@ -185,6 +685,10 @@ class NodeContext:
|
|
|
185
685
|
created_at=self._now(),
|
|
186
686
|
attempts=attempts,
|
|
187
687
|
payload=payload,
|
|
688
|
+
session_id=getattr(self, "session_id", None),
|
|
689
|
+
agent_id=getattr(self, "agent_id", None),
|
|
690
|
+
app_id=getattr(self, "app_id", None),
|
|
691
|
+
graph_id=getattr(self, "graph_id", None),
|
|
188
692
|
)
|
|
189
693
|
await self.services.continuation_store.save(continuation)
|
|
190
694
|
return continuation
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Any
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from aethergraph.core.runtime.run_manager import RunManager
|
|
5
8
|
from aethergraph.services.channel.channel_bus import ChannelBus
|
|
6
9
|
from aethergraph.services.clock.clock import SystemClock
|
|
7
10
|
from aethergraph.services.continuations.stores.fs_store import FSContinuationStore
|
|
11
|
+
from aethergraph.services.llm.service import LLMService
|
|
8
12
|
from aethergraph.services.logger.std import StdLoggerService
|
|
9
13
|
from aethergraph.services.mcp.service import MCPService
|
|
10
14
|
from aethergraph.services.memory.facade import MemoryFacade
|
|
11
|
-
from aethergraph.services.rag.
|
|
15
|
+
from aethergraph.services.rag.node_rag import NodeRAG
|
|
16
|
+
from aethergraph.services.viz.facade import VizFacade
|
|
12
17
|
from aethergraph.services.waits.wait_registry import WaitRegistry
|
|
13
18
|
|
|
14
19
|
|
|
@@ -25,6 +30,8 @@ class NodeServices:
|
|
|
25
30
|
kv: Any | None = None
|
|
26
31
|
memory: Any | None = None # MemoryFactory (for cross-session needs)
|
|
27
32
|
memory_facade: MemoryFacade | None = None # bound memory for this node
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
viz: VizFacade | None = None # VizFacade
|
|
34
|
+
llm: LLMService | None = None # LLMService
|
|
35
|
+
rag: NodeRAG | None = None # RAGService
|
|
30
36
|
mcp: MCPService | None = None # MCPService
|
|
37
|
+
run_manager: RunManager | None = None # RunManager
|
|
@@ -19,6 +19,8 @@ def hash_spec(spec: TaskGraphSpec) -> str:
|
|
|
19
19
|
raw = json.dumps(
|
|
20
20
|
{
|
|
21
21
|
"graph_id": spec.graph_id,
|
|
22
|
+
"agent_id": spec.agent_id or "",
|
|
23
|
+
"app_id": spec.app_id or "",
|
|
22
24
|
"version": spec.version,
|
|
23
25
|
"nodes": {
|
|
24
26
|
nid: {
|
|
@@ -65,7 +67,15 @@ async def recover_graph_run(
|
|
|
65
67
|
)
|
|
66
68
|
|
|
67
69
|
# Apply snapshot state
|
|
68
|
-
|
|
70
|
+
try:
|
|
71
|
+
_hydrate_state_from_json(g, snap.state)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
import logging
|
|
74
|
+
|
|
75
|
+
logger = logging.getLogger("aethergraph.core.runtime.recovery")
|
|
76
|
+
logger.error(
|
|
77
|
+
f"[recover_graph_run] Failed to hydrate state from snapshot for run {run_id}: {e}"
|
|
78
|
+
)
|
|
69
79
|
|
|
70
80
|
return g
|
|
71
81
|
|
|
@@ -85,6 +95,10 @@ def _hydrate_state_from_json(graph, j: dict[str, Any]) -> None:
|
|
|
85
95
|
# Keep as-is; resume_policy already blocked non-JSON/ref earlier
|
|
86
96
|
ns.outputs = outs
|
|
87
97
|
|
|
98
|
+
# time fields
|
|
99
|
+
ns.started_at = ns_json.get("started_at")
|
|
100
|
+
ns.finished_at = ns_json.get("finished_at")
|
|
101
|
+
|
|
88
102
|
|
|
89
103
|
async def rearm_waits_if_needed(graph, env, *, ttl_s: int = 3600):
|
|
90
104
|
store = env.container.cont_store
|