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
|
@@ -16,6 +16,7 @@ from aethergraph.core.runtime.run_types import (
|
|
|
16
16
|
RunRecord,
|
|
17
17
|
RunStatus,
|
|
18
18
|
RunVisibility,
|
|
19
|
+
_make_preview,
|
|
19
20
|
)
|
|
20
21
|
from aethergraph.core.runtime.runtime_metering import current_metering
|
|
21
22
|
from aethergraph.core.runtime.runtime_registry import current_registry
|
|
@@ -40,68 +41,6 @@ def _is_graphfn(obj: Any) -> bool:
|
|
|
40
41
|
|
|
41
42
|
class RunManager:
|
|
42
43
|
"""
|
|
43
|
-
High-level coordinator for running graphs.
|
|
44
|
-
|
|
45
|
-
Responsibilities
|
|
46
|
-
----------------
|
|
47
|
-
- Resolve graph targets (TaskGraph / GraphFunction) from the UnifiedRegistry.
|
|
48
|
-
- Create and persist RunRecord metadata in the RunStore.
|
|
49
|
-
- Enforce a soft concurrency limit via an in-process run slot counter.
|
|
50
|
-
- Drive execution via run_or_resume_async and record status / errors.
|
|
51
|
-
- Emit metering events (duration, status, user/org, graph_id).
|
|
52
|
-
- Best-effort cancellation by talking to the scheduler registry.
|
|
53
|
-
|
|
54
|
-
Key entrypoints
|
|
55
|
-
---------------
|
|
56
|
-
submit_run(...)
|
|
57
|
-
Non-blocking API entrypoint (used by HTTP routes).
|
|
58
|
-
- Acquires a run slot (respecting max_concurrent_runs).
|
|
59
|
-
- Creates a RunRecord (status=running) and saves it.
|
|
60
|
-
- Schedules a background coroutine (_bg) that:
|
|
61
|
-
* Calls _run_and_finalize(...)
|
|
62
|
-
* Always releases the run slot in a finally block.
|
|
63
|
-
- Returns immediately with the RunRecord so the caller can poll status.
|
|
64
|
-
|
|
65
|
-
start_run(...)
|
|
66
|
-
Blocking helper (tests / CLI).
|
|
67
|
-
- Same setup as submit_run, but runs _run_and_finalize(...) inline.
|
|
68
|
-
- Returns (RunRecord, outputs, has_waits, continuations).
|
|
69
|
-
|
|
70
|
-
_run_and_finalize(...)
|
|
71
|
-
Shared core logic used by both submit_run and start_run.
|
|
72
|
-
- Calls run_or_resume_async(target, inputs, run_id, session_id).
|
|
73
|
-
- Maps successful results into a dict of outputs.
|
|
74
|
-
- Handles:
|
|
75
|
-
* Normal completion -> status = succeeded.
|
|
76
|
-
* GraphHasPendingWaits -> status = failed (for now), has_waits=True.
|
|
77
|
-
* asyncio.CancelledError -> status = canceled.
|
|
78
|
-
* Other exceptions -> status = failed, error message recorded.
|
|
79
|
-
- Updates RunStore status fields (finished_at, error).
|
|
80
|
-
- Sends a metering event with status / duration.
|
|
81
|
-
|
|
82
|
-
Concurrency model
|
|
83
|
-
-----------------
|
|
84
|
-
- _acquire_run_slot / _release_run_slot protect a _running counter with an
|
|
85
|
-
asyncio.Lock to enforce max_concurrent_runs within this process.
|
|
86
|
-
- submit_run takes ownership of a slot until responsibility is handed to
|
|
87
|
-
the background runner (_bg). Once _bg is scheduled, it is responsible
|
|
88
|
-
for releasing the slot in its finally block.
|
|
89
|
-
- If submit_run fails before the handoff, it releases the slot itself to
|
|
90
|
-
avoid leaks.
|
|
91
|
-
|
|
92
|
-
Cancellation
|
|
93
|
-
------------
|
|
94
|
-
cancel_run(run_id)
|
|
95
|
-
- Looks up the RunRecord (if available) and, if not terminal, marks it
|
|
96
|
-
as cancellation_requested in the RunStore.
|
|
97
|
-
- Uses the scheduler registry to find the scheduler for this run:
|
|
98
|
-
* GlobalForwardScheduler: terminate_run(run_id)
|
|
99
|
-
* ForwardScheduler: terminate()
|
|
100
|
-
- The actual transition to RunStatus.canceled happens when the
|
|
101
|
-
scheduler cancels the task and run_or_resume_async raises
|
|
102
|
-
asyncio.CancelledError, which _run_and_finalize() translates into
|
|
103
|
-
a canceled run.
|
|
104
|
-
|
|
105
44
|
TODO: for global schedulers, we may want to have a dedicated run manager -- current
|
|
106
45
|
implementation utilize the async_run which create a local ForwardScheduler instance
|
|
107
46
|
each graph run. This is fine for concurrent graphs under thousands but may
|
|
@@ -167,6 +106,84 @@ class RunManager:
|
|
|
167
106
|
raise KeyError(f"Graph '{graph_id}' not found")
|
|
168
107
|
|
|
169
108
|
# -------- core execution helper --------
|
|
109
|
+
async def _build_run_record(
|
|
110
|
+
self,
|
|
111
|
+
*,
|
|
112
|
+
graph_id: str,
|
|
113
|
+
run_id: str | None,
|
|
114
|
+
session_id: str | None,
|
|
115
|
+
tags: list[str] | None,
|
|
116
|
+
identity: RequestIdentity,
|
|
117
|
+
origin: RunOrigin | None,
|
|
118
|
+
visibility: RunVisibility | None,
|
|
119
|
+
importance: RunImportance | None,
|
|
120
|
+
agent_id: str | None,
|
|
121
|
+
app_id: str | None,
|
|
122
|
+
) -> tuple[RunRecord, Any]:
|
|
123
|
+
"""
|
|
124
|
+
Shared helper for submit_run and run_and_wait:
|
|
125
|
+
- Resolves target
|
|
126
|
+
- Determines kind
|
|
127
|
+
- Attaches flow_id and session tags
|
|
128
|
+
|
|
129
|
+
Return:
|
|
130
|
+
- RunRecord (not yet persisted)
|
|
131
|
+
- target object: graph or graphfn
|
|
132
|
+
"""
|
|
133
|
+
rid = run_id or f"run-{uuid4().hex[:8]}"
|
|
134
|
+
started_at = _utcnow()
|
|
135
|
+
tags = list(tags or [])
|
|
136
|
+
|
|
137
|
+
target = await self._resolve_target(graph_id)
|
|
138
|
+
if _is_task_graph(target):
|
|
139
|
+
kind = "taskgraph"
|
|
140
|
+
elif _is_graphfn(target):
|
|
141
|
+
kind = "graphfn"
|
|
142
|
+
else:
|
|
143
|
+
kind = "other"
|
|
144
|
+
|
|
145
|
+
flow_id: str | None = None
|
|
146
|
+
reg = self.registry()
|
|
147
|
+
if reg is not None:
|
|
148
|
+
if kind == "taskgraph":
|
|
149
|
+
meta = reg.get_meta(nspace="graph", name=graph_id, version=None) or {}
|
|
150
|
+
elif kind == "graphfn":
|
|
151
|
+
meta = reg.get_meta(nspace="graphfn", name=graph_id, version=None) or {}
|
|
152
|
+
else:
|
|
153
|
+
meta = {}
|
|
154
|
+
flow_id = meta.get("flow_id") or graph_id
|
|
155
|
+
|
|
156
|
+
if session_id is None:
|
|
157
|
+
session_id = rid
|
|
158
|
+
|
|
159
|
+
record = RunRecord(
|
|
160
|
+
run_id=rid,
|
|
161
|
+
graph_id=graph_id,
|
|
162
|
+
kind=kind,
|
|
163
|
+
status=RunStatus.running,
|
|
164
|
+
started_at=started_at,
|
|
165
|
+
tags=tags,
|
|
166
|
+
user_id=identity.user_id,
|
|
167
|
+
org_id=identity.org_id,
|
|
168
|
+
meta={},
|
|
169
|
+
session_id=session_id,
|
|
170
|
+
origin=origin or RunOrigin.app,
|
|
171
|
+
visibility=visibility or RunVisibility.normal,
|
|
172
|
+
importance=importance or RunImportance.normal,
|
|
173
|
+
agent_id=agent_id,
|
|
174
|
+
app_id=app_id,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if flow_id:
|
|
178
|
+
record.meta["flow_id"] = flow_id
|
|
179
|
+
if f"flow:{flow_id}" not in record.tags:
|
|
180
|
+
record.tags.append(f"flow:{flow_id}")
|
|
181
|
+
if session_id:
|
|
182
|
+
record.meta["session_id"] = session_id
|
|
183
|
+
if f"session:{session_id}" not in record.tags:
|
|
184
|
+
record.tags.append(f"session:{session_id}")
|
|
185
|
+
|
|
186
|
+
return record, target
|
|
170
187
|
|
|
171
188
|
async def _run_and_finalize(
|
|
172
189
|
self,
|
|
@@ -214,6 +231,18 @@ class RunManager:
|
|
|
214
231
|
record.status = RunStatus.succeeded
|
|
215
232
|
record.finished_at = _utcnow()
|
|
216
233
|
|
|
234
|
+
# Optional: store a UI-only output preview
|
|
235
|
+
try:
|
|
236
|
+
preview, truncated = _make_preview(outputs)
|
|
237
|
+
record.meta["output_preview"] = preview
|
|
238
|
+
record.meta["output_truncated"] = truncated
|
|
239
|
+
except Exception:
|
|
240
|
+
import logging
|
|
241
|
+
|
|
242
|
+
logging.getLogger("aethergraph.runtime.run_manager").exception(
|
|
243
|
+
"Error creating output preview for run_id=%s", record.run_id
|
|
244
|
+
)
|
|
245
|
+
|
|
217
246
|
except asyncio.CancelledError:
|
|
218
247
|
# Cancellation path: scheduler.terminate() or external cancel.
|
|
219
248
|
import logging
|
|
@@ -281,7 +310,8 @@ class RunManager:
|
|
|
281
310
|
|
|
282
311
|
try:
|
|
283
312
|
if record.status in {RunStatus.succeeded, RunStatus.failed, RunStatus.canceled}:
|
|
284
|
-
|
|
313
|
+
# IMPORTANT: now resolve with (record, outputs)
|
|
314
|
+
await self._resolve_run_future(record.run_id, (record, outputs))
|
|
285
315
|
except Exception: # noqa: BLE001
|
|
286
316
|
import logging
|
|
287
317
|
|
|
@@ -319,9 +349,6 @@ class RunManager:
|
|
|
319
349
|
if identity is None:
|
|
320
350
|
identity = RequestIdentity(user_id="local", org_id="local", mode="local")
|
|
321
351
|
|
|
322
|
-
user_id = identity.user_id
|
|
323
|
-
org_id = identity.org_id
|
|
324
|
-
|
|
325
352
|
# Acquire run slot (rate limiting)
|
|
326
353
|
await self._acquire_run_slot()
|
|
327
354
|
# Tracks whether responsibility for releasing the slot has been handed
|
|
@@ -331,59 +358,31 @@ class RunManager:
|
|
|
331
358
|
|
|
332
359
|
try:
|
|
333
360
|
tags = tags or []
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
started_at = _utcnow()
|
|
337
|
-
|
|
338
|
-
if _is_task_graph(target):
|
|
339
|
-
kind = "taskgraph"
|
|
340
|
-
elif _is_graphfn(target):
|
|
341
|
-
kind = "graphfn"
|
|
342
|
-
else:
|
|
343
|
-
kind = "other"
|
|
344
|
-
|
|
345
|
-
# pull flow_id and entrypoint from registry if possible
|
|
346
|
-
flow_id: str | None = None
|
|
347
|
-
reg = self.registry()
|
|
348
|
-
if reg is not None:
|
|
349
|
-
if kind == "taskgraph":
|
|
350
|
-
meta = reg.get_meta(nspace="graph", name=graph_id, version=None) or {}
|
|
351
|
-
elif kind == "graphfn":
|
|
352
|
-
meta = reg.get_meta(nspace="graphfn", name=graph_id, version=None) or {}
|
|
353
|
-
else:
|
|
354
|
-
meta = {}
|
|
355
|
-
flow_id = meta.get("flow_id") or graph_id
|
|
356
|
-
|
|
357
|
-
# use run_id as session_id if not provided
|
|
358
|
-
if session_id is None:
|
|
359
|
-
session_id = rid
|
|
360
|
-
|
|
361
|
-
record = RunRecord(
|
|
362
|
-
run_id=rid,
|
|
361
|
+
|
|
362
|
+
record, target = await self._build_run_record(
|
|
363
363
|
graph_id=graph_id,
|
|
364
|
-
|
|
365
|
-
status=RunStatus.running, # we go straight to running as before
|
|
366
|
-
started_at=started_at,
|
|
367
|
-
tags=list(tags),
|
|
368
|
-
user_id=user_id,
|
|
369
|
-
org_id=org_id,
|
|
370
|
-
meta={},
|
|
364
|
+
run_id=run_id,
|
|
371
365
|
session_id=session_id,
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
366
|
+
tags=tags,
|
|
367
|
+
identity=identity,
|
|
368
|
+
origin=origin,
|
|
369
|
+
visibility=visibility,
|
|
370
|
+
importance=importance,
|
|
375
371
|
agent_id=agent_id,
|
|
376
372
|
app_id=app_id,
|
|
377
373
|
)
|
|
378
374
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
375
|
+
# Optional: store a UI-only input preview
|
|
376
|
+
try:
|
|
377
|
+
preview, truncated = _make_preview(inputs)
|
|
378
|
+
record.meta["input_preview"] = preview
|
|
379
|
+
record.meta["input_truncated"] = truncated
|
|
380
|
+
except Exception:
|
|
381
|
+
import logging
|
|
382
|
+
|
|
383
|
+
logging.getLogger("aethergraph.runtime.run_manager").exception(
|
|
384
|
+
"Error creating input preview for run_id=%s", record.run_id
|
|
385
|
+
)
|
|
387
386
|
|
|
388
387
|
if self._store is not None:
|
|
389
388
|
await self._store.create(record)
|
|
@@ -395,28 +394,22 @@ class RunManager:
|
|
|
395
394
|
target=target,
|
|
396
395
|
graph_id=graph_id,
|
|
397
396
|
inputs=inputs,
|
|
398
|
-
# user_id=user_id,
|
|
399
|
-
# org_id=org_id,
|
|
400
397
|
identity=identity,
|
|
401
398
|
)
|
|
402
399
|
finally:
|
|
403
400
|
await self._release_run_slot()
|
|
404
401
|
|
|
405
|
-
# If we're in an event loop (server), schedule in the background.
|
|
406
|
-
# If not (CLI), just run inline so behaviour is still sane.
|
|
407
402
|
try:
|
|
408
403
|
loop = asyncio.get_running_loop()
|
|
409
404
|
except RuntimeError:
|
|
410
|
-
# Not inside a running loop – e.g., CLI usage.
|
|
411
405
|
slot_handed_to_bg = True
|
|
412
|
-
# _bg() is responsible for releasing the slot in its finally.
|
|
413
406
|
await _bg()
|
|
414
407
|
else:
|
|
415
408
|
slot_handed_to_bg = True
|
|
416
|
-
# Background tasks; _bg() will release the slot in its finally.
|
|
417
409
|
loop.create_task(_bg())
|
|
418
410
|
|
|
419
411
|
return record
|
|
412
|
+
|
|
420
413
|
except Exception:
|
|
421
414
|
# If submit_run itself fails *before* handing off to _bg, we must release the slot here.
|
|
422
415
|
# Once slot_handed_to_bg is True, _bg is responsible for releasing the slot.
|
|
@@ -459,65 +452,35 @@ class RunManager:
|
|
|
459
452
|
|
|
460
453
|
try:
|
|
461
454
|
tags = tags or []
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
) # same resolver as submit_run :contentReference[oaicite:1]{index=1}
|
|
465
|
-
rid = run_id or f"run-{uuid4().hex[:8]}"
|
|
466
|
-
started_at = _utcnow()
|
|
467
|
-
|
|
468
|
-
if _is_task_graph(target):
|
|
469
|
-
kind = "taskgraph"
|
|
470
|
-
elif _is_graphfn(target):
|
|
471
|
-
kind = "graphfn"
|
|
472
|
-
else:
|
|
473
|
-
kind = "other"
|
|
474
|
-
|
|
475
|
-
# flow_id extraction same pattern as submit_run :contentReference[oaicite:2]{index=2}
|
|
476
|
-
flow_id: str | None = None
|
|
477
|
-
reg = self.registry()
|
|
478
|
-
if reg is not None:
|
|
479
|
-
if kind == "taskgraph":
|
|
480
|
-
meta = reg.get_meta(nspace="graph", name=graph_id, version=None) or {}
|
|
481
|
-
elif kind == "graphfn":
|
|
482
|
-
meta = reg.get_meta(nspace="graphfn", name=graph_id, version=None) or {}
|
|
483
|
-
else:
|
|
484
|
-
meta = {}
|
|
485
|
-
flow_id = meta.get("flow_id") or graph_id
|
|
486
|
-
|
|
487
|
-
if session_id is None:
|
|
488
|
-
session_id = rid
|
|
489
|
-
|
|
490
|
-
record = RunRecord(
|
|
491
|
-
run_id=rid,
|
|
455
|
+
|
|
456
|
+
record, target = await self._build_run_record(
|
|
492
457
|
graph_id=graph_id,
|
|
493
|
-
|
|
494
|
-
status=RunStatus.running,
|
|
495
|
-
started_at=started_at,
|
|
496
|
-
tags=list(tags),
|
|
497
|
-
user_id=identity.user_id,
|
|
498
|
-
org_id=identity.org_id,
|
|
499
|
-
meta={},
|
|
458
|
+
run_id=run_id,
|
|
500
459
|
session_id=session_id,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
460
|
+
tags=tags,
|
|
461
|
+
identity=identity,
|
|
462
|
+
origin=origin,
|
|
463
|
+
visibility=visibility,
|
|
464
|
+
importance=importance,
|
|
504
465
|
agent_id=agent_id,
|
|
505
466
|
app_id=app_id,
|
|
506
467
|
)
|
|
507
468
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
469
|
+
# Optional: UI-only input preview
|
|
470
|
+
try:
|
|
471
|
+
preview, truncated = _make_preview(inputs)
|
|
472
|
+
record.meta["input_preview"] = preview
|
|
473
|
+
record.meta["input_truncated"] = truncated
|
|
474
|
+
except Exception:
|
|
475
|
+
import logging
|
|
476
|
+
|
|
477
|
+
logging.getLogger("aethergraph.runtime.run_manager").exception(
|
|
478
|
+
"Error creating input preview for run_id=%s", record.run_id
|
|
479
|
+
)
|
|
516
480
|
|
|
517
481
|
if self._store is not None:
|
|
518
482
|
await self._store.create(record)
|
|
519
483
|
|
|
520
|
-
# Inline execution; still uses run_or_resume_async under the hood :contentReference[oaicite:3]{index=3}
|
|
521
484
|
return await self._run_and_finalize(
|
|
522
485
|
record=record,
|
|
523
486
|
target=target,
|
|
@@ -529,105 +492,6 @@ class RunManager:
|
|
|
529
492
|
if count_slot:
|
|
530
493
|
await self._release_run_slot()
|
|
531
494
|
|
|
532
|
-
# -------- old: blocking start_run (CLI/tests) --------
|
|
533
|
-
async def start_run(
|
|
534
|
-
self,
|
|
535
|
-
graph_id: str,
|
|
536
|
-
*,
|
|
537
|
-
inputs: dict[str, Any],
|
|
538
|
-
run_id: str | None = None,
|
|
539
|
-
session_id: str | None = None,
|
|
540
|
-
tags: list[str] | None = None,
|
|
541
|
-
identity: RequestIdentity | None = None,
|
|
542
|
-
agent_id: str | None = None,
|
|
543
|
-
app_id: str | None = None,
|
|
544
|
-
) -> tuple[RunRecord, dict[str, Any] | None, bool, list[dict[str, Any]]]:
|
|
545
|
-
"""
|
|
546
|
-
Blocking helper (original behaviour).
|
|
547
|
-
|
|
548
|
-
- Resolves target.
|
|
549
|
-
- Creates RunRecord with status=running.
|
|
550
|
-
- Runs once via run_or_resume_async.
|
|
551
|
-
- Updates store + metering.
|
|
552
|
-
- Returns (record, outputs, has_waits, continuations).
|
|
553
|
-
|
|
554
|
-
Still useful for tests/CLI, but the HTTP route should prefer submit_run().
|
|
555
|
-
|
|
556
|
-
NOTE:
|
|
557
|
-
agent_id and app_id will override any value pulled from original graphs. Use it
|
|
558
|
-
only when you want to explicitly set these fields for tracking purpose.
|
|
559
|
-
"""
|
|
560
|
-
if identity is None:
|
|
561
|
-
identity = RequestIdentity(user_id="local", org_id="local", mode="local")
|
|
562
|
-
|
|
563
|
-
tags = tags or []
|
|
564
|
-
target = await self._resolve_target(graph_id)
|
|
565
|
-
rid = run_id or f"run-{uuid4().hex[:8]}"
|
|
566
|
-
started_at = _utcnow()
|
|
567
|
-
|
|
568
|
-
if _is_task_graph(target):
|
|
569
|
-
kind = "taskgraph"
|
|
570
|
-
elif _is_graphfn(target):
|
|
571
|
-
kind = "graphfn"
|
|
572
|
-
else:
|
|
573
|
-
kind = "other"
|
|
574
|
-
|
|
575
|
-
# pull flow_id and entrypoint from registry if possible
|
|
576
|
-
flow_id: str | None = None
|
|
577
|
-
reg = self.registry()
|
|
578
|
-
if reg is not None:
|
|
579
|
-
if kind == "taskgraph":
|
|
580
|
-
meta = reg.get_meta(nspace="graph", name=graph_id, version=None) or {}
|
|
581
|
-
elif kind == "graphfn":
|
|
582
|
-
meta = reg.get_meta(nspace="graphfn", name=graph_id, version=None) or {}
|
|
583
|
-
else:
|
|
584
|
-
meta = {}
|
|
585
|
-
flow_id = meta.get("flow_id") or graph_id
|
|
586
|
-
|
|
587
|
-
# use run_id as session_id if not provided
|
|
588
|
-
if session_id is None:
|
|
589
|
-
session_id = rid
|
|
590
|
-
|
|
591
|
-
record = RunRecord(
|
|
592
|
-
run_id=rid,
|
|
593
|
-
graph_id=graph_id,
|
|
594
|
-
kind=kind,
|
|
595
|
-
status=RunStatus.running, # we go straight to running as before
|
|
596
|
-
started_at=started_at,
|
|
597
|
-
tags=list(tags),
|
|
598
|
-
user_id=identity.user_id,
|
|
599
|
-
org_id=identity.org_id,
|
|
600
|
-
meta={},
|
|
601
|
-
session_id=session_id,
|
|
602
|
-
origin=RunOrigin.app, # app is a typical default for graph runs
|
|
603
|
-
visibility=RunVisibility.normal,
|
|
604
|
-
importance=RunImportance.normal,
|
|
605
|
-
agent_id=agent_id,
|
|
606
|
-
app_id=app_id,
|
|
607
|
-
)
|
|
608
|
-
|
|
609
|
-
if flow_id:
|
|
610
|
-
record.meta["flow_id"] = flow_id
|
|
611
|
-
if f"flow:{flow_id}" not in record.tags:
|
|
612
|
-
record.tags.append(f"flow:{flow_id}") # add flow tag if missing
|
|
613
|
-
if session_id:
|
|
614
|
-
record.meta["session_id"] = session_id
|
|
615
|
-
if f"session:{session_id}" not in record.tags:
|
|
616
|
-
record.tags.append(f"session:{session_id}") # add session tag if missing
|
|
617
|
-
|
|
618
|
-
if self._store is not None:
|
|
619
|
-
await self._store.create(record)
|
|
620
|
-
|
|
621
|
-
return await self._run_and_finalize(
|
|
622
|
-
record=record,
|
|
623
|
-
target=target,
|
|
624
|
-
graph_id=graph_id,
|
|
625
|
-
inputs=inputs,
|
|
626
|
-
identity=identity,
|
|
627
|
-
# agent_id=agent_id,
|
|
628
|
-
# app_id=app_id,
|
|
629
|
-
)
|
|
630
|
-
|
|
631
495
|
async def get_record(self, run_id: str) -> RunRecord | None:
|
|
632
496
|
if self._store is None:
|
|
633
497
|
return None
|
|
@@ -748,16 +612,45 @@ class RunManager:
|
|
|
748
612
|
run_id: str,
|
|
749
613
|
*,
|
|
750
614
|
timeout_s: float | None = None,
|
|
751
|
-
|
|
615
|
+
return_outputs: bool = False,
|
|
616
|
+
) -> RunRecord | tuple[RunRecord, dict[str, Any] | None]:
|
|
617
|
+
"""
|
|
618
|
+
Wait for a run to reach a terminal state.
|
|
619
|
+
|
|
620
|
+
- If return_outputs=False (default), returns RunRecord (backwards compatible).
|
|
621
|
+
- If return_outputs=True, returns (RunRecord, outputs_dict_or_none).
|
|
622
|
+
|
|
623
|
+
NOTE:
|
|
624
|
+
- outputs are only guaranteed when the run was executed in THIS process
|
|
625
|
+
via RunManager. If the run is already terminal in the store and no
|
|
626
|
+
in-process outputs exist, outputs will be None.
|
|
627
|
+
"""
|
|
752
628
|
# Fast path: already terminal in store
|
|
753
629
|
rec = await self.get_record(run_id)
|
|
754
630
|
if rec and rec.status in {RunStatus.succeeded, RunStatus.failed, RunStatus.canceled}:
|
|
631
|
+
if return_outputs:
|
|
632
|
+
return rec, None
|
|
755
633
|
return rec
|
|
756
634
|
|
|
757
635
|
fut = await self._get_or_create_run_future(run_id)
|
|
636
|
+
|
|
758
637
|
if timeout_s is not None:
|
|
759
|
-
|
|
760
|
-
|
|
638
|
+
result = await asyncio.wait_for(fut, timeout=timeout_s)
|
|
639
|
+
else:
|
|
640
|
+
result = await fut
|
|
641
|
+
|
|
642
|
+
# result is either:
|
|
643
|
+
# - RunRecord (old-style resolvers)
|
|
644
|
+
# - or (RunRecord, outputs) from _run_and_finalize
|
|
645
|
+
if isinstance(result, RunRecord):
|
|
646
|
+
if return_outputs:
|
|
647
|
+
return result, None
|
|
648
|
+
return result
|
|
649
|
+
|
|
650
|
+
rec2, outputs = result
|
|
651
|
+
if return_outputs:
|
|
652
|
+
return rec2, outputs
|
|
653
|
+
return rec2
|
|
761
654
|
|
|
762
655
|
async def _get_or_create_run_future(self, run_id: str) -> asyncio.Future:
|
|
763
656
|
async with self._run_waiters_lock:
|
|
@@ -781,3 +674,102 @@ class RunManager:
|
|
|
781
674
|
if fut and not fut.done():
|
|
782
675
|
fut.set_exception(err)
|
|
783
676
|
self._run_waiters.pop(run_id, None)
|
|
677
|
+
|
|
678
|
+
# -------- old: blocking start_run (CLI/tests) --------
|
|
679
|
+
async def start_run(
|
|
680
|
+
self,
|
|
681
|
+
graph_id: str,
|
|
682
|
+
*,
|
|
683
|
+
inputs: dict[str, Any],
|
|
684
|
+
run_id: str | None = None,
|
|
685
|
+
session_id: str | None = None,
|
|
686
|
+
tags: list[str] | None = None,
|
|
687
|
+
identity: RequestIdentity | None = None,
|
|
688
|
+
agent_id: str | None = None,
|
|
689
|
+
app_id: str | None = None,
|
|
690
|
+
) -> tuple[RunRecord, dict[str, Any] | None, bool, list[dict[str, Any]]]:
|
|
691
|
+
"""
|
|
692
|
+
Blocking helper (original behaviour).
|
|
693
|
+
|
|
694
|
+
- Resolves target.
|
|
695
|
+
- Creates RunRecord with status=running.
|
|
696
|
+
- Runs once via run_or_resume_async.
|
|
697
|
+
- Updates store + metering.
|
|
698
|
+
- Returns (record, outputs, has_waits, continuations).
|
|
699
|
+
|
|
700
|
+
Still useful for tests/CLI, but the HTTP route should prefer submit_run().
|
|
701
|
+
|
|
702
|
+
NOTE:
|
|
703
|
+
agent_id and app_id will override any value pulled from original graphs. Use it
|
|
704
|
+
only when you want to explicitly set these fields for tracking purpose.
|
|
705
|
+
"""
|
|
706
|
+
if identity is None:
|
|
707
|
+
identity = RequestIdentity(user_id="local", org_id="local", mode="local")
|
|
708
|
+
|
|
709
|
+
tags = tags or []
|
|
710
|
+
target = await self._resolve_target(graph_id)
|
|
711
|
+
rid = run_id or f"run-{uuid4().hex[:8]}"
|
|
712
|
+
started_at = _utcnow()
|
|
713
|
+
|
|
714
|
+
if _is_task_graph(target):
|
|
715
|
+
kind = "taskgraph"
|
|
716
|
+
elif _is_graphfn(target):
|
|
717
|
+
kind = "graphfn"
|
|
718
|
+
else:
|
|
719
|
+
kind = "other"
|
|
720
|
+
|
|
721
|
+
# pull flow_id and entrypoint from registry if possible
|
|
722
|
+
flow_id: str | None = None
|
|
723
|
+
reg = self.registry()
|
|
724
|
+
if reg is not None:
|
|
725
|
+
if kind == "taskgraph":
|
|
726
|
+
meta = reg.get_meta(nspace="graph", name=graph_id, version=None) or {}
|
|
727
|
+
elif kind == "graphfn":
|
|
728
|
+
meta = reg.get_meta(nspace="graphfn", name=graph_id, version=None) or {}
|
|
729
|
+
else:
|
|
730
|
+
meta = {}
|
|
731
|
+
flow_id = meta.get("flow_id") or graph_id
|
|
732
|
+
|
|
733
|
+
# use run_id as session_id if not provided
|
|
734
|
+
if session_id is None:
|
|
735
|
+
session_id = rid
|
|
736
|
+
|
|
737
|
+
record = RunRecord(
|
|
738
|
+
run_id=rid,
|
|
739
|
+
graph_id=graph_id,
|
|
740
|
+
kind=kind,
|
|
741
|
+
status=RunStatus.running, # we go straight to running as before
|
|
742
|
+
started_at=started_at,
|
|
743
|
+
tags=list(tags),
|
|
744
|
+
user_id=identity.user_id,
|
|
745
|
+
org_id=identity.org_id,
|
|
746
|
+
meta={},
|
|
747
|
+
session_id=session_id,
|
|
748
|
+
origin=RunOrigin.app, # app is a typical default for graph runs
|
|
749
|
+
visibility=RunVisibility.normal,
|
|
750
|
+
importance=RunImportance.normal,
|
|
751
|
+
agent_id=agent_id,
|
|
752
|
+
app_id=app_id,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
if flow_id:
|
|
756
|
+
record.meta["flow_id"] = flow_id
|
|
757
|
+
if f"flow:{flow_id}" not in record.tags:
|
|
758
|
+
record.tags.append(f"flow:{flow_id}") # add flow tag if missing
|
|
759
|
+
if session_id:
|
|
760
|
+
record.meta["session_id"] = session_id
|
|
761
|
+
if f"session:{session_id}" not in record.tags:
|
|
762
|
+
record.tags.append(f"session:{session_id}") # add session tag if missing
|
|
763
|
+
|
|
764
|
+
if self._store is not None:
|
|
765
|
+
await self._store.create(record)
|
|
766
|
+
|
|
767
|
+
return await self._run_and_finalize(
|
|
768
|
+
record=record,
|
|
769
|
+
target=target,
|
|
770
|
+
graph_id=graph_id,
|
|
771
|
+
inputs=inputs,
|
|
772
|
+
identity=identity,
|
|
773
|
+
# agent_id=agent_id,
|
|
774
|
+
# app_id=app_id,
|
|
775
|
+
)
|