aethergraph 0.1.0a3__py3-none-any.whl → 0.1.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aethergraph/api/v1/artifacts.py +23 -4
- aethergraph/api/v1/schemas.py +7 -0
- aethergraph/api/v1/session.py +123 -4
- aethergraph/config/config.py +2 -0
- aethergraph/config/search.py +49 -0
- aethergraph/contracts/services/channel.py +18 -1
- aethergraph/contracts/services/execution.py +58 -0
- aethergraph/contracts/services/llm.py +26 -0
- aethergraph/contracts/services/memory.py +10 -4
- aethergraph/contracts/services/planning.py +53 -0
- aethergraph/contracts/storage/event_log.py +8 -0
- aethergraph/contracts/storage/search_backend.py +47 -0
- aethergraph/contracts/storage/vector_index.py +73 -0
- aethergraph/core/graph/action_spec.py +76 -0
- aethergraph/core/graph/graph_fn.py +75 -2
- aethergraph/core/graph/graphify.py +74 -2
- aethergraph/core/runtime/graph_runner.py +2 -1
- aethergraph/core/runtime/node_context.py +66 -3
- aethergraph/core/runtime/node_services.py +8 -0
- aethergraph/core/runtime/run_manager.py +263 -271
- aethergraph/core/runtime/run_types.py +54 -1
- aethergraph/core/runtime/runtime_env.py +35 -14
- aethergraph/core/runtime/runtime_services.py +308 -18
- aethergraph/plugins/agents/default_chat_agent.py +266 -74
- aethergraph/plugins/agents/default_chat_agent_v2.py +487 -0
- aethergraph/plugins/channel/adapters/webui.py +69 -21
- aethergraph/plugins/channel/routes/webui_routes.py +8 -48
- aethergraph/runtime/__init__.py +12 -0
- aethergraph/server/app_factory.py +3 -0
- aethergraph/server/ui_static/assets/index-CFktGdbW.js +4913 -0
- aethergraph/server/ui_static/assets/index-DcfkFlTA.css +1 -0
- aethergraph/server/ui_static/index.html +2 -2
- aethergraph/services/artifacts/facade.py +157 -21
- aethergraph/services/artifacts/types.py +35 -0
- aethergraph/services/artifacts/utils.py +42 -0
- aethergraph/services/channel/channel_bus.py +3 -1
- aethergraph/services/channel/event_hub copy.py +55 -0
- aethergraph/services/channel/event_hub.py +81 -0
- aethergraph/services/channel/factory.py +3 -2
- aethergraph/services/channel/session.py +709 -74
- aethergraph/services/container/default_container.py +69 -7
- aethergraph/services/execution/__init__.py +0 -0
- aethergraph/services/execution/local_python.py +118 -0
- aethergraph/services/indices/__init__.py +0 -0
- aethergraph/services/indices/global_indices.py +21 -0
- aethergraph/services/indices/scoped_indices.py +292 -0
- aethergraph/services/llm/generic_client.py +342 -46
- aethergraph/services/llm/generic_embed_client.py +359 -0
- aethergraph/services/llm/types.py +3 -1
- aethergraph/services/memory/distillers/llm_long_term.py +60 -109
- aethergraph/services/memory/distillers/llm_long_term_v1.py +180 -0
- aethergraph/services/memory/distillers/llm_meta_summary.py +57 -266
- aethergraph/services/memory/distillers/llm_meta_summary_v1.py +342 -0
- aethergraph/services/memory/distillers/long_term.py +48 -131
- aethergraph/services/memory/distillers/long_term_v1.py +170 -0
- aethergraph/services/memory/facade/chat.py +18 -8
- aethergraph/services/memory/facade/core.py +159 -19
- aethergraph/services/memory/facade/distillation.py +86 -31
- aethergraph/services/memory/facade/retrieval.py +100 -1
- aethergraph/services/memory/factory.py +4 -1
- aethergraph/services/planning/__init__.py +0 -0
- aethergraph/services/planning/action_catalog.py +271 -0
- aethergraph/services/planning/bindings.py +56 -0
- aethergraph/services/planning/dependency_index.py +65 -0
- aethergraph/services/planning/flow_validator.py +263 -0
- aethergraph/services/planning/graph_io_adapter.py +150 -0
- aethergraph/services/planning/input_parser.py +312 -0
- aethergraph/services/planning/missing_inputs.py +28 -0
- aethergraph/services/planning/node_planner.py +613 -0
- aethergraph/services/planning/orchestrator.py +112 -0
- aethergraph/services/planning/plan_executor.py +506 -0
- aethergraph/services/planning/plan_types.py +321 -0
- aethergraph/services/planning/planner.py +617 -0
- aethergraph/services/planning/planner_service.py +369 -0
- aethergraph/services/planning/planning_context_builder.py +43 -0
- aethergraph/services/planning/quick_actions.py +29 -0
- aethergraph/services/planning/routers/__init__.py +0 -0
- aethergraph/services/planning/routers/simple_router.py +26 -0
- aethergraph/services/rag/facade.py +0 -3
- aethergraph/services/scope/scope.py +30 -30
- aethergraph/services/scope/scope_factory.py +15 -7
- aethergraph/services/skills/__init__.py +0 -0
- aethergraph/services/skills/skill_registry.py +465 -0
- aethergraph/services/skills/skills.py +220 -0
- aethergraph/services/skills/utils.py +194 -0
- aethergraph/storage/artifacts/artifact_index_jsonl.py +16 -10
- aethergraph/storage/artifacts/artifact_index_sqlite.py +12 -2
- aethergraph/storage/docstore/sqlite_doc_sync.py +1 -1
- aethergraph/storage/memory/event_persist.py +42 -2
- aethergraph/storage/memory/fs_persist.py +32 -2
- aethergraph/storage/search_backend/__init__.py +0 -0
- aethergraph/storage/search_backend/generic_vector_backend.py +230 -0
- aethergraph/storage/search_backend/null_backend.py +34 -0
- aethergraph/storage/search_backend/sqlite_lexical_backend.py +387 -0
- aethergraph/storage/search_backend/utils.py +31 -0
- aethergraph/storage/search_factory.py +75 -0
- aethergraph/storage/vector_index/faiss_index.py +72 -4
- aethergraph/storage/vector_index/sqlite_index.py +521 -52
- aethergraph/storage/vector_index/sqlite_index_vanila.py +311 -0
- aethergraph/storage/vector_index/utils.py +22 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/METADATA +1 -1
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/RECORD +107 -63
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/WHEEL +1 -1
- aethergraph/plugins/agents/default_chat_agent copy.py +0 -90
- aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +0 -1
- aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +0 -400
- aethergraph/services/eventhub/event_hub.py +0 -76
- aethergraph/services/llm/generic_client copy.py +0 -691
- aethergraph/services/prompts/file_store.py +0 -41
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/entry_points.txt +0 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a3.dist-info → aethergraph-0.1.0a4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
JsonType = Literal["string", "number", "boolean", "object", "array"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class IOSlot:
|
|
11
|
+
"""
|
|
12
|
+
One input or output slot for a graph action.
|
|
13
|
+
|
|
14
|
+
- `type` is a coarse JSON-like type for LLM/tool matching.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
type: JsonType | None = None
|
|
19
|
+
description: str | None = None
|
|
20
|
+
required: bool = True
|
|
21
|
+
default: Any | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ActionSpec:
|
|
26
|
+
"""
|
|
27
|
+
Docstring for ActionSpec
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name: str # hunman name: usually the graph/graphfn name
|
|
31
|
+
ref: str # canonical ref: e.g. "graph:mygraph:1.0.0"
|
|
32
|
+
kind: Literal["graph", "graphfn"] # kind of action
|
|
33
|
+
version: str # version string
|
|
34
|
+
|
|
35
|
+
inputs: list[IOSlot] # input slots
|
|
36
|
+
outputs: list[IOSlot] # output slots
|
|
37
|
+
|
|
38
|
+
description: str | None = None # human description for LLM/tool matching
|
|
39
|
+
tags: list[str] = None # optional tags
|
|
40
|
+
flow_id: str | None = None # optional flow ID. If None, treat it globally.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _map_py_type_to_json_type(tp: Any) -> JsonType | None:
|
|
44
|
+
"""
|
|
45
|
+
Docstring for _map_py_type_to_json_type
|
|
46
|
+
|
|
47
|
+
:param tp: Description
|
|
48
|
+
:type tp: Any
|
|
49
|
+
:return: Description
|
|
50
|
+
:rtype: JsonType | None
|
|
51
|
+
"""
|
|
52
|
+
origin = getattr(tp, "__origin__", None)
|
|
53
|
+
|
|
54
|
+
if tp in (str, bytes):
|
|
55
|
+
return "string"
|
|
56
|
+
if tp in (int, float):
|
|
57
|
+
return "number"
|
|
58
|
+
if tp is bool:
|
|
59
|
+
return "boolean"
|
|
60
|
+
|
|
61
|
+
# collections
|
|
62
|
+
if origin in (list, tuple, set) or tp in (list, tuple, set):
|
|
63
|
+
return "array"
|
|
64
|
+
|
|
65
|
+
# dict / mapping -> treat as object
|
|
66
|
+
if origin in (dict,) or tp in (dict,):
|
|
67
|
+
return "object"
|
|
68
|
+
|
|
69
|
+
# Optional[T] / Union[T, None] etc: peel outer layer and re-map
|
|
70
|
+
if origin is __import__("typing").Union:
|
|
71
|
+
args = [a for a in tp.__args__ if a is not type(None)] # noqa: E721
|
|
72
|
+
if len(args) == 1:
|
|
73
|
+
return _map_py_type_to_json_type(args[0])
|
|
74
|
+
|
|
75
|
+
# Fallback = "object" or None; for now we return object
|
|
76
|
+
return "object"
|
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
import inspect
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, get_type_hints
|
|
6
6
|
|
|
7
|
+
from aethergraph.core.graph.action_spec import IOSlot, _map_py_type_to_json_type
|
|
7
8
|
from aethergraph.core.runtime.run_registration import RunRegistrationGuard
|
|
8
9
|
from aethergraph.services.registry.agent_app_meta import (
|
|
9
10
|
build_agent_meta,
|
|
@@ -113,6 +114,68 @@ class GraphFunction:
|
|
|
113
114
|
|
|
114
115
|
return run(self, inputs)
|
|
115
116
|
|
|
117
|
+
def io_signature(self) -> dict[str, list[IOSlot]]:
|
|
118
|
+
"""
|
|
119
|
+
Infer typed IO based on decorator inputs/outputs and Python annotations.
|
|
120
|
+
|
|
121
|
+
Rule of thumbs:
|
|
122
|
+
- Inputs: use decorator list; if missing, use all params except 'context'.
|
|
123
|
+
- Outputs: use decorator list; if missing, use return annotation if any.
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
sig = inspect.signature(self.fn)
|
|
127
|
+
hints = get_type_hints(self.fn)
|
|
128
|
+
|
|
129
|
+
# --- Inputs ---
|
|
130
|
+
if self.inputs is not None:
|
|
131
|
+
input_names = list(self.inputs)
|
|
132
|
+
else:
|
|
133
|
+
# fallback: all params except 'context'
|
|
134
|
+
input_names = [p for p in sig.parameters if p != "context"]
|
|
135
|
+
|
|
136
|
+
input_slots: list[IOSlot] = []
|
|
137
|
+
for name in input_names:
|
|
138
|
+
param = sig.parameters.get(name)
|
|
139
|
+
if param is None:
|
|
140
|
+
# decorator said it's a input but not in signature
|
|
141
|
+
# treat as unknown, required
|
|
142
|
+
input_slots.append(IOSlot(name=name, type=None, required=True))
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
anno = hints.get(name)
|
|
146
|
+
j_type = _map_py_type_to_json_type(anno) if anno is not None else None
|
|
147
|
+
required = param.default is inspect._empty
|
|
148
|
+
default = None if required else param.default
|
|
149
|
+
|
|
150
|
+
input_slots.append(
|
|
151
|
+
IOSlot(
|
|
152
|
+
name=name,
|
|
153
|
+
type=j_type,
|
|
154
|
+
required=required,
|
|
155
|
+
default=default,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# --- Outputs ---
|
|
160
|
+
output_slots: list[IOSlot] = []
|
|
161
|
+
out_names = list(self.outputs or [])
|
|
162
|
+
|
|
163
|
+
ret_anno = hints.get("return")
|
|
164
|
+
if out_names:
|
|
165
|
+
if len(out_names) == 1 and ret_anno is not None:
|
|
166
|
+
j_type = _map_py_type_to_json_type(ret_anno)
|
|
167
|
+
output_slots.append(IOSlot(name=out_names[0], type=j_type, required=True))
|
|
168
|
+
else:
|
|
169
|
+
# Multi-output or no return type: names only
|
|
170
|
+
for name in out_names:
|
|
171
|
+
output_slots.append(IOSlot(name=name, type=None, required=True))
|
|
172
|
+
elif ret_anno is not None:
|
|
173
|
+
# No explicit output names but we *do* know the type: create a single output
|
|
174
|
+
j_type = _map_py_type_to_json_type(ret_anno)
|
|
175
|
+
output_slots.append(IOSlot(name="result", type=j_type, required=True))
|
|
176
|
+
|
|
177
|
+
return {"inputs": input_slots, "outputs": output_slots}
|
|
178
|
+
|
|
116
179
|
|
|
117
180
|
def _is_ref(x: object) -> bool:
|
|
118
181
|
return isinstance(x, dict) and x.get("_type") == "ref" and "from" in x and "key" in x
|
|
@@ -207,6 +270,7 @@ def graph_fn(
|
|
|
207
270
|
tags: list[str] | None = None,
|
|
208
271
|
as_agent: dict[str, Any] | None = None,
|
|
209
272
|
as_app: dict[str, Any] | None = None,
|
|
273
|
+
description: str | None = None,
|
|
210
274
|
) -> Callable[[Callable], GraphFunction]:
|
|
211
275
|
"""
|
|
212
276
|
Decorator to define a graph function and optionally register it as an agent or app.
|
|
@@ -275,6 +339,7 @@ def graph_fn(
|
|
|
275
339
|
tags: List of string tags for discovery and categorization.
|
|
276
340
|
as_agent: Optional dictionary defining agent metadata. Used when running through Aethergraph UI. See additional information below.
|
|
277
341
|
as_app: Optional dictionary defining app metadata. Used when running through Aethergraph UI. See additional information below.
|
|
342
|
+
description: Optional human-readable description of the graph function.
|
|
278
343
|
|
|
279
344
|
Returns:
|
|
280
345
|
Callable: A decorator that wraps the function as a `GraphFunction` and registers it
|
|
@@ -307,11 +372,19 @@ def graph_fn(
|
|
|
307
372
|
return gf
|
|
308
373
|
|
|
309
374
|
base_tags = tags or []
|
|
375
|
+
|
|
376
|
+
# prefer explicit description; then docstring; else name
|
|
377
|
+
doc_desc = inspect.getdoc(fn) or None
|
|
378
|
+
eff_description = description or doc_desc or name
|
|
379
|
+
|
|
310
380
|
graph_meta: dict[str, Any] = {
|
|
311
381
|
"kind": "graphfn",
|
|
312
382
|
"entrypoint": entrypoint,
|
|
313
|
-
"flow_id": flow_id
|
|
383
|
+
"flow_id": flow_id,
|
|
314
384
|
"tags": base_tags,
|
|
385
|
+
"description": eff_description,
|
|
386
|
+
"inputs": inputs,
|
|
387
|
+
"outputs": outputs,
|
|
315
388
|
}
|
|
316
389
|
|
|
317
390
|
registry.register(
|
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, get_origin, get_type_hints
|
|
5
5
|
|
|
6
|
+
from aethergraph.core.graph.action_spec import _map_py_type_to_json_type
|
|
6
7
|
from aethergraph.services.registry.agent_app_meta import build_agent_meta, build_app_meta
|
|
7
8
|
|
|
8
9
|
from ..runtime.runtime_registry import current_registry
|
|
9
10
|
from .task_graph import TaskGraph
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
def _normalize_type_hint(ann: Any) -> str:
|
|
14
|
+
"""Convert a Python annotation into a simple string for IO types."""
|
|
15
|
+
if ann is inspect._empty:
|
|
16
|
+
return "any"
|
|
17
|
+
|
|
18
|
+
origin = get_origin(ann)
|
|
19
|
+
# args = get_args(ann)
|
|
20
|
+
|
|
21
|
+
# Builtins
|
|
22
|
+
if ann is str:
|
|
23
|
+
return "string"
|
|
24
|
+
if ann is int:
|
|
25
|
+
return "int"
|
|
26
|
+
if ann is float:
|
|
27
|
+
return "float"
|
|
28
|
+
if ann is bool:
|
|
29
|
+
return "bool"
|
|
30
|
+
if ann is dict or origin is dict:
|
|
31
|
+
# e.g. dict[str, float]
|
|
32
|
+
return "object"
|
|
33
|
+
if ann in (list, tuple) or origin in (list, tuple):
|
|
34
|
+
# e.g. list[int] / list[dict[str, float]]
|
|
35
|
+
return "array"
|
|
36
|
+
if ann is Any:
|
|
37
|
+
return "any"
|
|
38
|
+
|
|
39
|
+
# Fallback: stringified type name
|
|
40
|
+
return getattr(ann, "__name__", str(ann))
|
|
41
|
+
|
|
42
|
+
|
|
12
43
|
def graphify(
|
|
13
44
|
name="default_graph",
|
|
14
45
|
inputs=(),
|
|
@@ -20,6 +51,7 @@ def graphify(
|
|
|
20
51
|
tags: list[str] | None = None,
|
|
21
52
|
as_agent: dict[str, Any] | None = None,
|
|
22
53
|
as_app: dict[str, Any] | None = None,
|
|
54
|
+
description: str | None = None,
|
|
23
55
|
):
|
|
24
56
|
"""
|
|
25
57
|
Decorator to define a `TaskGraph` and optionally register it as an agent or app.
|
|
@@ -88,6 +120,7 @@ def graphify(
|
|
|
88
120
|
tags: List of string tags for discovery and categorization.
|
|
89
121
|
as_agent: Optional dictionary defining agent metadata. Used when running through Aethergraph UI. See additional information below.
|
|
90
122
|
as_app: Optional dictionary defining app metadata. Used when running through Aethergraph UI. See additional information below.
|
|
123
|
+
description: Optional human-readable description of the graph function.
|
|
91
124
|
|
|
92
125
|
Returns:
|
|
93
126
|
TaskGraph: A decorator that transforms a function into a TaskGraph with the specified configuration.
|
|
@@ -195,11 +228,50 @@ def graphify(
|
|
|
195
228
|
return _build
|
|
196
229
|
|
|
197
230
|
base_tags = tags or []
|
|
231
|
+
|
|
232
|
+
# Effective description
|
|
233
|
+
doc_desc = inspect.getdoc(fn) or None
|
|
234
|
+
eff_description = description or doc_desc or name
|
|
235
|
+
|
|
236
|
+
# Infer IO types from annotations if possible
|
|
237
|
+
try:
|
|
238
|
+
resolved_hints = get_type_hints(fn)
|
|
239
|
+
except Exception:
|
|
240
|
+
# Fallback: use raw __annotations__ if get_type_hints blows up
|
|
241
|
+
resolved_hints = getattr(fn, "__annotations__", {}) or {}
|
|
242
|
+
|
|
243
|
+
# Infer IO types from annotations in a JSON-ish schema
|
|
244
|
+
input_type_map: dict[str, str] = {}
|
|
245
|
+
for pname in required_inputs:
|
|
246
|
+
param = fn_sig.parameters.get(pname)
|
|
247
|
+
if param is None:
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
# Prefer resolved type hint; fall back to the raw annotation
|
|
251
|
+
ann = resolved_hints.get(pname, param.annotation)
|
|
252
|
+
if ann is inspect._empty:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
j = _map_py_type_to_json_type(ann)
|
|
256
|
+
if j is not None:
|
|
257
|
+
input_type_map[pname] = j
|
|
258
|
+
|
|
259
|
+
# for outputs, we only have the return annotation as a whole
|
|
260
|
+
output_names = list(outputs or [])
|
|
261
|
+
output_type_map: dict[str, str] = {n: "any" for n in output_names}
|
|
262
|
+
|
|
198
263
|
graph_meta: dict[str, Any] = {
|
|
199
264
|
"kind": "graph",
|
|
200
265
|
"entrypoint": entrypoint,
|
|
201
|
-
"flow_id": flow_id
|
|
266
|
+
"flow_id": flow_id,
|
|
202
267
|
"tags": base_tags,
|
|
268
|
+
"description": eff_description,
|
|
269
|
+
"inputs": inputs,
|
|
270
|
+
"outputs": outputs,
|
|
271
|
+
"io_types": {
|
|
272
|
+
"inputs": input_type_map,
|
|
273
|
+
"outputs": output_type_map,
|
|
274
|
+
},
|
|
203
275
|
}
|
|
204
276
|
|
|
205
277
|
registry.register(
|
|
@@ -10,7 +10,6 @@ from aethergraph.contracts.errors.errors import GraphHasPendingWaits
|
|
|
10
10
|
from aethergraph.contracts.services.state_stores import GraphSnapshot
|
|
11
11
|
from aethergraph.core.graph.task_graph import TaskGraph
|
|
12
12
|
from aethergraph.core.runtime.recovery import hash_spec, recover_graph_run
|
|
13
|
-
from aethergraph.services.container.default_container import build_default_container # adjust path
|
|
14
13
|
from aethergraph.services.state_stores.graph_observer import PersistenceObserver
|
|
15
14
|
from aethergraph.services.state_stores.resume_policy import (
|
|
16
15
|
assert_snapshot_json_only,
|
|
@@ -29,6 +28,8 @@ from .run_registration import RunRegistrationGuard
|
|
|
29
28
|
|
|
30
29
|
# ---------- env helpers ----------
|
|
31
30
|
def _get_container():
|
|
31
|
+
from aethergraph.services.container.default_container import build_default_container
|
|
32
|
+
|
|
32
33
|
# install once if not installed by sidecar/server
|
|
33
34
|
return ensure_services_installed(build_default_container)
|
|
34
35
|
|
|
@@ -2,7 +2,15 @@ from dataclasses import dataclass
|
|
|
2
2
|
from datetime import timedelta
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
+
from aethergraph.contracts.services.execution import (
|
|
6
|
+
CodeExecutionRequest,
|
|
7
|
+
CodeExecutionResult,
|
|
8
|
+
ExecutionService,
|
|
9
|
+
)
|
|
5
10
|
from aethergraph.services.artifacts.facade import ArtifactFacade
|
|
11
|
+
from aethergraph.services.indices.scoped_indices import ScopedIndices
|
|
12
|
+
from aethergraph.services.planning.node_planner import NodePlanner
|
|
13
|
+
from aethergraph.services.skills.skill_registry import SkillRegistry
|
|
6
14
|
|
|
7
15
|
if TYPE_CHECKING:
|
|
8
16
|
from aethergraph.core.runtime.run_manager import RunManager
|
|
@@ -41,10 +49,37 @@ class NodeContext:
|
|
|
41
49
|
app_id: str | None = None # for app-invoked runs
|
|
42
50
|
bound_memory: BoundMemoryAdapter | None = None # back-compat
|
|
43
51
|
|
|
52
|
+
_planner_facade: NodePlanner | None = None # lazy init
|
|
53
|
+
|
|
44
54
|
# --- accessors (compatible names) ---
|
|
45
55
|
def runtime(self) -> NodeServices:
|
|
46
56
|
return self.services
|
|
47
57
|
|
|
58
|
+
async def execute(
|
|
59
|
+
self,
|
|
60
|
+
code: str,
|
|
61
|
+
*,
|
|
62
|
+
language: str = "python",
|
|
63
|
+
timeout_s: float = 30.0,
|
|
64
|
+
args: list[str] | None = None,
|
|
65
|
+
workdir: str | None = None,
|
|
66
|
+
env: dict[str, str] | None = None,
|
|
67
|
+
) -> CodeExecutionResult:
|
|
68
|
+
""" """
|
|
69
|
+
exe_svs: ExecutionService | None = getattr(self.services, "execution", None)
|
|
70
|
+
if exe_svs is None:
|
|
71
|
+
raise RuntimeError("NodeContext.services.execution is not configured")
|
|
72
|
+
|
|
73
|
+
req = CodeExecutionRequest(
|
|
74
|
+
language=language,
|
|
75
|
+
code=code,
|
|
76
|
+
args=args or [],
|
|
77
|
+
timeout_s=timeout_s,
|
|
78
|
+
workdir=workdir,
|
|
79
|
+
env=env,
|
|
80
|
+
)
|
|
81
|
+
return await exe_svs.execute(req)
|
|
82
|
+
|
|
48
83
|
async def spawn_run(
|
|
49
84
|
self,
|
|
50
85
|
graph_id: str,
|
|
@@ -83,7 +118,7 @@ class NodeContext:
|
|
|
83
118
|
inputs={"foo": "bar"},
|
|
84
119
|
tags=["experiment", "priority"],
|
|
85
120
|
agent_id="agent-123", # associate with an agent if applicable
|
|
86
|
-
visibility=RunVisibility.
|
|
121
|
+
visibility=RunVisibility.inline, # not shown in UI
|
|
87
122
|
)
|
|
88
123
|
```
|
|
89
124
|
|
|
@@ -232,6 +267,7 @@ class NodeContext:
|
|
|
232
267
|
run_id: str,
|
|
233
268
|
*,
|
|
234
269
|
timeout_s: float | None = None,
|
|
270
|
+
return_outputs: bool = False,
|
|
235
271
|
) -> RunRecord:
|
|
236
272
|
"""
|
|
237
273
|
Wait for a run to complete and retrieve its final record.
|
|
@@ -249,16 +285,18 @@ class NodeContext:
|
|
|
249
285
|
|
|
250
286
|
Waiting with a timeout:
|
|
251
287
|
```python
|
|
252
|
-
record = await context.wait_run(run_id, timeout_s=30)
|
|
288
|
+
record, outputs = await context.wait_run(run_id, timeout_s=30, return_outputs=True)
|
|
253
289
|
```
|
|
254
290
|
|
|
255
291
|
Args:
|
|
256
292
|
run_id: The unique identifier of the run to wait for.
|
|
257
293
|
timeout_s: Optional timeout in seconds. If set, the method will raise
|
|
258
294
|
a TimeoutError if the run does not complete in time.
|
|
295
|
+
return_outputs: If True, also return the run's outputs along with the record.
|
|
259
296
|
|
|
260
297
|
Returns:
|
|
261
298
|
RunRecord: The final record of the completed run.
|
|
299
|
+
Output: If `return_outputs` is True, returns a tuple of (RunRecord, outputs dict).
|
|
262
300
|
|
|
263
301
|
Raises:
|
|
264
302
|
RuntimeError: If the RunManager service is not configured in the context.
|
|
@@ -273,7 +311,7 @@ class NodeContext:
|
|
|
273
311
|
rm: RunManager | None = getattr(self.services, "run_manager", None)
|
|
274
312
|
if rm is None:
|
|
275
313
|
raise RuntimeError("NodeContext.services.run_manager is not configured")
|
|
276
|
-
return await rm.wait_run(run_id, timeout_s=timeout_s)
|
|
314
|
+
return await rm.wait_run(run_id, timeout_s=timeout_s, return_outputs=return_outputs)
|
|
277
315
|
|
|
278
316
|
async def cancel_run(self, run_id: str) -> None:
|
|
279
317
|
"""
|
|
@@ -315,6 +353,16 @@ class NodeContext:
|
|
|
315
353
|
raise RuntimeError("NodeContext.services.run_manager is not configured")
|
|
316
354
|
await rm.cancel_run(run_id)
|
|
317
355
|
|
|
356
|
+
def planner(self) -> "NodePlanner":
|
|
357
|
+
if self._planner_facade is None:
|
|
358
|
+
if self.services.planner_service is None:
|
|
359
|
+
raise RuntimeError("NodeContext.services.planner_service is not configured")
|
|
360
|
+
self._planner_facade = NodePlanner(
|
|
361
|
+
service=self.services.planner_service,
|
|
362
|
+
node_ctx=self,
|
|
363
|
+
)
|
|
364
|
+
return self._planner_facade
|
|
365
|
+
|
|
318
366
|
def logger(self):
|
|
319
367
|
return self.services.logger.for_node_ctx(
|
|
320
368
|
run_id=self.run_id, node_id=self.node_id, graph_id=self.graph_id
|
|
@@ -346,6 +394,11 @@ class NodeContext:
|
|
|
346
394
|
"""
|
|
347
395
|
return ChannelSession(self, f"ui:run/{self.run_id}")
|
|
348
396
|
|
|
397
|
+
def skills(self) -> SkillRegistry:
|
|
398
|
+
if not self.services.skills:
|
|
399
|
+
raise RuntimeError("NodeContext.services.skills is not configured")
|
|
400
|
+
return self.services.skills
|
|
401
|
+
|
|
349
402
|
def channel(self, channel_key: str | None = None):
|
|
350
403
|
"""
|
|
351
404
|
Set up a new ChannelSession for the current node context.
|
|
@@ -521,6 +574,16 @@ class NodeContext:
|
|
|
521
574
|
raise RuntimeError("MCPService not available")
|
|
522
575
|
return self.services.mcp.get(name)
|
|
523
576
|
|
|
577
|
+
def indices(self) -> ScopedIndices:
|
|
578
|
+
if not self.services.indices:
|
|
579
|
+
raise RuntimeError("ScopedIndices not available")
|
|
580
|
+
return self.services.indices
|
|
581
|
+
|
|
582
|
+
# def run_manager(self) -> RunManager:
|
|
583
|
+
# if not self.services.run_manager:
|
|
584
|
+
# raise RuntimeError("RunManager not available")
|
|
585
|
+
# return self.services.run_manager
|
|
586
|
+
|
|
524
587
|
def continuations(self):
|
|
525
588
|
return self.services.continuation_store
|
|
526
589
|
|
|
@@ -3,6 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
|
+
from aethergraph.services.indices.scoped_indices import ScopedIndices
|
|
7
|
+
from aethergraph.services.planning.planner_service import PlannerService
|
|
8
|
+
from aethergraph.services.skills.skill_registry import SkillRegistry
|
|
9
|
+
|
|
6
10
|
if TYPE_CHECKING:
|
|
7
11
|
from aethergraph.core.runtime.run_manager import RunManager
|
|
8
12
|
from aethergraph.services.channel.channel_bus import ChannelBus
|
|
@@ -35,3 +39,7 @@ class NodeServices:
|
|
|
35
39
|
rag: NodeRAG | None = None # RAGService
|
|
36
40
|
mcp: MCPService | None = None # MCPService
|
|
37
41
|
run_manager: RunManager | None = None # RunManager
|
|
42
|
+
indices: ScopedIndices | None = None # ScopedIndices for this node
|
|
43
|
+
execution: Any | None = None # ExecutionService
|
|
44
|
+
planner_service: PlannerService | None = None # PlannerService
|
|
45
|
+
skills: SkillRegistry | None = None # SkillRegistry
|