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,369 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from aethergraph.api.v1.deps import RequestIdentity
|
|
8
|
+
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
9
|
+
from aethergraph.core.runtime.run_types import (
|
|
10
|
+
RunImportance,
|
|
11
|
+
RunOrigin,
|
|
12
|
+
RunVisibility,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from aethergraph.core.runtime.run_manager import RunManager
|
|
17
|
+
|
|
18
|
+
from .action_catalog import ActionCatalog
|
|
19
|
+
from .input_parser import InputParser, ParsedInputs
|
|
20
|
+
from .plan_executor import (
|
|
21
|
+
BackgroundExecutionHandle,
|
|
22
|
+
ExecutionResult,
|
|
23
|
+
OnCompleteCallback,
|
|
24
|
+
PlanExecutor,
|
|
25
|
+
)
|
|
26
|
+
from .plan_types import (
|
|
27
|
+
CandidatePlan,
|
|
28
|
+
ExecutionEventCallback,
|
|
29
|
+
PlanningContext,
|
|
30
|
+
PlanningEvent,
|
|
31
|
+
PlanningEventCallback,
|
|
32
|
+
PlanResult,
|
|
33
|
+
ValidationResult,
|
|
34
|
+
)
|
|
35
|
+
from .planner import ActionPlanner
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class PlannerService:
|
|
40
|
+
"""
|
|
41
|
+
High-level planning facade exposed as `context.planner()`.
|
|
42
|
+
|
|
43
|
+
Internally composes:
|
|
44
|
+
- ActionCatalog
|
|
45
|
+
- ActionPlanner
|
|
46
|
+
- PlanExecutor
|
|
47
|
+
- InputParser
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
catalog: ActionCatalog
|
|
51
|
+
llm: LLMClientProtocol
|
|
52
|
+
validator: Any # FlowValidator
|
|
53
|
+
run_manager: RunManager | None = None # for executing plans
|
|
54
|
+
|
|
55
|
+
# Lazily initialized components
|
|
56
|
+
_planner: ActionPlanner | None = None
|
|
57
|
+
_executor: PlanExecutor | None = None
|
|
58
|
+
_input_parser: InputParser | None = None
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def planner_core(self) -> ActionPlanner:
|
|
62
|
+
if self._planner is None:
|
|
63
|
+
self._planner = ActionPlanner(
|
|
64
|
+
catalog=self.catalog,
|
|
65
|
+
validator=self.validator,
|
|
66
|
+
llm=self.llm,
|
|
67
|
+
)
|
|
68
|
+
return self._planner
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def executor(self) -> PlanExecutor:
|
|
72
|
+
if self._executor is None:
|
|
73
|
+
self._executor = PlanExecutor(catalog=self.catalog, run_manager=self.run_manager)
|
|
74
|
+
return self._executor
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def input_parser(self) -> InputParser:
|
|
78
|
+
if self._input_parser is None:
|
|
79
|
+
self._input_parser = InputParser(llm=self.llm)
|
|
80
|
+
return self._input_parser
|
|
81
|
+
|
|
82
|
+
# ------------- Planning API -------------
|
|
83
|
+
async def plan(
|
|
84
|
+
self,
|
|
85
|
+
*,
|
|
86
|
+
goal: str,
|
|
87
|
+
user_inputs: dict[str, Any] | None = None,
|
|
88
|
+
external_slots: dict[str, Any] | None = None, # advanced use with IO slots
|
|
89
|
+
flow_ids: list[str] | None = None,
|
|
90
|
+
instruction: str | None = None,
|
|
91
|
+
allow_partial: bool = True,
|
|
92
|
+
preferred_external_keys: list[str] | None = None,
|
|
93
|
+
memory_snippets: list[str] | None = None,
|
|
94
|
+
artifact_snippets: list[str] | None = None,
|
|
95
|
+
on_event: PlanningEventCallback | None = None,
|
|
96
|
+
) -> PlanResult:
|
|
97
|
+
"""
|
|
98
|
+
Ergonomic entrypoint: build a PlanningContext and delegate to plan_with_context().
|
|
99
|
+
"""
|
|
100
|
+
ctx = PlanningContext(
|
|
101
|
+
goal=goal,
|
|
102
|
+
user_inputs=user_inputs or {},
|
|
103
|
+
external_slots=external_slots or {},
|
|
104
|
+
memory_snippets=list(memory_snippets or []),
|
|
105
|
+
artifact_snippets=list(artifact_snippets or []),
|
|
106
|
+
flow_ids=list(flow_ids) if flow_ids is not None else None,
|
|
107
|
+
instruction=instruction,
|
|
108
|
+
allow_partial_plans=allow_partial,
|
|
109
|
+
preferred_external_keys=list(preferred_external_keys or []),
|
|
110
|
+
)
|
|
111
|
+
return await self.plan_with_context(ctx, on_event=on_event)
|
|
112
|
+
|
|
113
|
+
async def plan_with_context(
|
|
114
|
+
self,
|
|
115
|
+
ctx: PlanningContext,
|
|
116
|
+
*,
|
|
117
|
+
on_event: PlanningEventCallback | None = None,
|
|
118
|
+
) -> PlanResult:
|
|
119
|
+
"""
|
|
120
|
+
Plan towards the goal in the given context.
|
|
121
|
+
Emits PlanningEvents via on_event callback.
|
|
122
|
+
"""
|
|
123
|
+
events: list[PlanningEvent] = []
|
|
124
|
+
|
|
125
|
+
async def _capture(ev: PlanningEvent) -> None:
|
|
126
|
+
# Always record locally
|
|
127
|
+
events.append(ev)
|
|
128
|
+
|
|
129
|
+
# Forward to caller if provided
|
|
130
|
+
if on_event:
|
|
131
|
+
result = on_event(ev)
|
|
132
|
+
if inspect.isawaitable(result):
|
|
133
|
+
await result
|
|
134
|
+
|
|
135
|
+
plan, history = await self.planner_core.plan_with_loop(
|
|
136
|
+
ctx,
|
|
137
|
+
on_event=_capture,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
validation: ValidationResult | None
|
|
141
|
+
validation = history[-1] if history else None
|
|
142
|
+
|
|
143
|
+
return PlanResult(plan=plan, validation=validation, events=events)
|
|
144
|
+
|
|
145
|
+
# ------------- input parsing API -------------
|
|
146
|
+
async def parse_inputs(
|
|
147
|
+
self,
|
|
148
|
+
*,
|
|
149
|
+
message: str,
|
|
150
|
+
missing_keys: list[str],
|
|
151
|
+
instruction: str | None = None, # currently unused, keep for future use
|
|
152
|
+
) -> ParsedInputs:
|
|
153
|
+
"""
|
|
154
|
+
Parse user message into structured inputs for the given inputs.
|
|
155
|
+
"""
|
|
156
|
+
# For now we ignore `instruction` and rely on meta,
|
|
157
|
+
# We can thread it into the system prompt later.
|
|
158
|
+
return await self.input_parser.parse_message_for_fields(
|
|
159
|
+
message=message,
|
|
160
|
+
missing_keys=missing_keys,
|
|
161
|
+
instruction=instruction,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async def execute_plan(
|
|
165
|
+
self,
|
|
166
|
+
plan: CandidatePlan,
|
|
167
|
+
*,
|
|
168
|
+
user_inputs: dict[str, Any] | None = None,
|
|
169
|
+
on_event: ExecutionEventCallback | None = None,
|
|
170
|
+
identity: RequestIdentity | None = None,
|
|
171
|
+
visibility: RunVisibility = RunVisibility.normal,
|
|
172
|
+
importance: RunImportance = RunImportance.normal,
|
|
173
|
+
session_id: str | None = None,
|
|
174
|
+
agent_id: str | None = None,
|
|
175
|
+
app_id: str | None = None,
|
|
176
|
+
tags: list[str] | None = None,
|
|
177
|
+
origin: RunOrigin | None = None,
|
|
178
|
+
) -> ExecutionResult:
|
|
179
|
+
return await self.executor.execute(
|
|
180
|
+
plan,
|
|
181
|
+
user_inputs=user_inputs,
|
|
182
|
+
on_event=on_event,
|
|
183
|
+
identity=identity,
|
|
184
|
+
visibility=visibility,
|
|
185
|
+
importance=importance,
|
|
186
|
+
session_id=session_id,
|
|
187
|
+
agent_id=agent_id,
|
|
188
|
+
app_id=app_id,
|
|
189
|
+
tags=tags,
|
|
190
|
+
origin=origin,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
async def execute_background(
|
|
194
|
+
self,
|
|
195
|
+
plan: CandidatePlan,
|
|
196
|
+
*,
|
|
197
|
+
user_inputs: dict[str, Any] | None = None,
|
|
198
|
+
on_event: ExecutionEventCallback | None = None,
|
|
199
|
+
identity: RequestIdentity | None = None,
|
|
200
|
+
visibility: RunVisibility = RunVisibility.normal,
|
|
201
|
+
importance: RunImportance = RunImportance.normal,
|
|
202
|
+
session_id: str | None = None,
|
|
203
|
+
agent_id: str | None = None,
|
|
204
|
+
app_id: str | None = None,
|
|
205
|
+
tags: list[str] | None = None,
|
|
206
|
+
origin: RunOrigin | None = None,
|
|
207
|
+
on_complete: OnCompleteCallback | None = None,
|
|
208
|
+
exec_id: str | None = None,
|
|
209
|
+
) -> BackgroundExecutionHandle:
|
|
210
|
+
"""
|
|
211
|
+
Schedule plan execution in the background and return a handle.
|
|
212
|
+
|
|
213
|
+
This is async only for API consistency with other services;
|
|
214
|
+
it returns as soon as the background task is scheduled.
|
|
215
|
+
"""
|
|
216
|
+
handle = self.executor.execute_background(
|
|
217
|
+
plan,
|
|
218
|
+
user_inputs=user_inputs,
|
|
219
|
+
on_event=on_event,
|
|
220
|
+
identity=identity,
|
|
221
|
+
visibility=visibility,
|
|
222
|
+
importance=importance,
|
|
223
|
+
session_id=session_id,
|
|
224
|
+
agent_id=agent_id,
|
|
225
|
+
app_id=app_id,
|
|
226
|
+
tags=tags,
|
|
227
|
+
origin=origin,
|
|
228
|
+
on_complete=on_complete,
|
|
229
|
+
exec_id=exec_id,
|
|
230
|
+
)
|
|
231
|
+
return handle
|
|
232
|
+
|
|
233
|
+
async def plan_and_execute(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
goal: str,
|
|
237
|
+
user_inputs: dict[str, Any] | None = None,
|
|
238
|
+
external_slots: dict[str, Any] | None = None,
|
|
239
|
+
flow_ids: list[str] | None = None,
|
|
240
|
+
instruction: str | None = None,
|
|
241
|
+
allow_partial: bool = False,
|
|
242
|
+
preferred_external_keys: list[str] | None = None,
|
|
243
|
+
memory_snippets: list[str] | None = None,
|
|
244
|
+
artifact_snippets: list[str] | None = None,
|
|
245
|
+
planning_events_cb: PlanningEventCallback | None = None,
|
|
246
|
+
execution_events_cb: ExecutionEventCallback | None = None,
|
|
247
|
+
identity: RequestIdentity | None = None,
|
|
248
|
+
visibility: RunVisibility | None = RunVisibility.normal,
|
|
249
|
+
importance: RunImportance | None = RunImportance.normal,
|
|
250
|
+
session_id: str | None = None,
|
|
251
|
+
agent_id: str | None = None,
|
|
252
|
+
app_id: str | None = None,
|
|
253
|
+
tags: list[str] | None = None,
|
|
254
|
+
origin: RunOrigin | None = None,
|
|
255
|
+
) -> tuple[PlanResult, ExecutionResult | None]:
|
|
256
|
+
"""
|
|
257
|
+
Convenience: plan first, then execute if plan is acceptable.
|
|
258
|
+
"""
|
|
259
|
+
plan_result = await self.plan(
|
|
260
|
+
goal=goal,
|
|
261
|
+
user_inputs=user_inputs,
|
|
262
|
+
external_slots=external_slots,
|
|
263
|
+
flow_ids=flow_ids,
|
|
264
|
+
instruction=instruction,
|
|
265
|
+
allow_partial=allow_partial,
|
|
266
|
+
preferred_external_keys=preferred_external_keys,
|
|
267
|
+
memory_snippets=memory_snippets,
|
|
268
|
+
artifact_snippets=artifact_snippets,
|
|
269
|
+
on_event=planning_events_cb,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
plan = plan_result.plan
|
|
273
|
+
validation = plan_result.validation
|
|
274
|
+
|
|
275
|
+
if plan is None or validation is None:
|
|
276
|
+
return plan_result, None
|
|
277
|
+
|
|
278
|
+
accept_partial = allow_partial and validation.is_partial_ok()
|
|
279
|
+
if not validation.ok and not accept_partial:
|
|
280
|
+
# Invalid plan and partial not allowed → do not execute
|
|
281
|
+
return plan_result, None
|
|
282
|
+
|
|
283
|
+
exec_result = await self.execute_plan(
|
|
284
|
+
plan,
|
|
285
|
+
user_inputs=user_inputs,
|
|
286
|
+
on_event=execution_events_cb,
|
|
287
|
+
identity=identity,
|
|
288
|
+
visibility=visibility,
|
|
289
|
+
importance=importance,
|
|
290
|
+
session_id=session_id,
|
|
291
|
+
agent_id=agent_id,
|
|
292
|
+
app_id=app_id,
|
|
293
|
+
tags=tags,
|
|
294
|
+
origin=origin,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return plan_result, exec_result
|
|
298
|
+
|
|
299
|
+
async def plan_and_execute_background(
|
|
300
|
+
self,
|
|
301
|
+
*,
|
|
302
|
+
goal: str,
|
|
303
|
+
user_inputs: dict[str, Any] | None = None,
|
|
304
|
+
external_slots: dict[str, Any] | None = None,
|
|
305
|
+
flow_ids: list[str] | None = None,
|
|
306
|
+
instruction: str | None = None,
|
|
307
|
+
allow_partial: bool = False,
|
|
308
|
+
preferred_external_keys: list[str] | None = None,
|
|
309
|
+
memory_snippets: list[str] | None = None,
|
|
310
|
+
artifact_snippets: list[str] | None = None,
|
|
311
|
+
planning_events_cb: PlanningEventCallback | None = None,
|
|
312
|
+
execution_events_cb: ExecutionEventCallback | None = None,
|
|
313
|
+
identity: RequestIdentity | None = None,
|
|
314
|
+
visibility: RunVisibility | None = RunVisibility.normal,
|
|
315
|
+
importance: RunImportance | None = RunImportance.normal,
|
|
316
|
+
session_id: str | None = None,
|
|
317
|
+
agent_id: str | None = None,
|
|
318
|
+
app_id: str | None = None,
|
|
319
|
+
tags: list[str] | None = None,
|
|
320
|
+
origin: RunOrigin | None = None,
|
|
321
|
+
on_complete: OnCompleteCallback | None = None,
|
|
322
|
+
exec_id: str | None = None,
|
|
323
|
+
) -> tuple[PlanResult, BackgroundExecutionHandle | None]:
|
|
324
|
+
"""
|
|
325
|
+
Plan first, then execute the plan in the background if acceptable.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
(PlanResult, BackgroundExecutionHandle | None)
|
|
329
|
+
"""
|
|
330
|
+
plan_result = await self.plan(
|
|
331
|
+
goal=goal,
|
|
332
|
+
user_inputs=user_inputs,
|
|
333
|
+
external_slots=external_slots,
|
|
334
|
+
flow_ids=flow_ids,
|
|
335
|
+
instruction=instruction,
|
|
336
|
+
allow_partial=allow_partial,
|
|
337
|
+
preferred_external_keys=preferred_external_keys,
|
|
338
|
+
memory_snippets=memory_snippets,
|
|
339
|
+
artifact_snippets=artifact_snippets,
|
|
340
|
+
on_event=planning_events_cb,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
plan = plan_result.plan
|
|
344
|
+
validation = plan_result.validation
|
|
345
|
+
|
|
346
|
+
if plan is None or validation is None:
|
|
347
|
+
return plan_result, None
|
|
348
|
+
|
|
349
|
+
accept_partial = allow_partial and validation.is_partial_ok()
|
|
350
|
+
if not validation.ok and not accept_partial:
|
|
351
|
+
return plan_result, None
|
|
352
|
+
|
|
353
|
+
handle = await self.execute_background(
|
|
354
|
+
plan,
|
|
355
|
+
user_inputs=user_inputs,
|
|
356
|
+
on_event=execution_events_cb,
|
|
357
|
+
identity=identity,
|
|
358
|
+
visibility=visibility,
|
|
359
|
+
importance=importance,
|
|
360
|
+
session_id=session_id,
|
|
361
|
+
agent_id=agent_id,
|
|
362
|
+
app_id=app_id,
|
|
363
|
+
tags=tags,
|
|
364
|
+
origin=origin,
|
|
365
|
+
on_complete=on_complete,
|
|
366
|
+
exec_id=exec_id,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
return plan_result, handle
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from aethergraph.contracts.services.planning import (
|
|
7
|
+
PlanningContextBuilderProtocol,
|
|
8
|
+
RoutedIntent,
|
|
9
|
+
SessionState,
|
|
10
|
+
)
|
|
11
|
+
from aethergraph.services.planning.planner import PlanningContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SimplePlanningContextBuilder(PlanningContextBuilderProtocol):
|
|
16
|
+
"""
|
|
17
|
+
Baseline builder: treat the raw user message as goal and
|
|
18
|
+
pass through user_inputs as-is (you can enrich with RAG later).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def _extract_user_inputs(self, user_message: str) -> dict[str, Any]:
|
|
22
|
+
# For now: rely on external caller to provide user_inputs;
|
|
23
|
+
# later add LLM-based extraction or explicit forms.
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
async def build(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
user_message: str,
|
|
30
|
+
routed: RoutedIntent,
|
|
31
|
+
session_state: SessionState,
|
|
32
|
+
) -> PlanningContext:
|
|
33
|
+
goal = user_message.strip()
|
|
34
|
+
user_inputs = self._extract_user_inputs(user_message)
|
|
35
|
+
|
|
36
|
+
return PlanningContext(
|
|
37
|
+
goal=goal,
|
|
38
|
+
user_inputs=user_inputs,
|
|
39
|
+
external_slots={}, # you can thread structured external inputs later
|
|
40
|
+
memory_snippets=[], # future: fetch from ctx.memory()
|
|
41
|
+
artifact_snippets=[], # future: fetch from indices/artifacts
|
|
42
|
+
flow_ids=routed.flow_ids,
|
|
43
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Protocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class QuickActionSpec:
|
|
9
|
+
id: str
|
|
10
|
+
description: str
|
|
11
|
+
# e.g. `list_recent_runs`, `open_notebook`, `answer_with_rag`
|
|
12
|
+
requires_confirmation: bool = False
|
|
13
|
+
action_ref: str | None = None # optional link to ActionSpec.ref
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class QuickActionHandler(Protocol):
|
|
17
|
+
async def __call__(self, *, context: Any) -> Any: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class QuickActionRegistry:
|
|
22
|
+
actions: dict[str, QuickActionSpec]
|
|
23
|
+
handlers: dict[str, QuickActionHandler]
|
|
24
|
+
|
|
25
|
+
def get_spec(self, action_id: str) -> QuickActionSpec | None:
|
|
26
|
+
return self.actions.get(action_id)
|
|
27
|
+
|
|
28
|
+
def get_handler(self, action_id: str) -> QuickActionHandler | None:
|
|
29
|
+
return self.handlers.get(action_id)
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from aethergraph.contracts.services.planning import IntentRouter, RoutedIntent, SessionState
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SimpleIntentRouter(IntentRouter):
|
|
10
|
+
"""
|
|
11
|
+
Docstring for SimpleIntentRouter
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
async def route(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
user_message: str,
|
|
18
|
+
session_state: SessionState,
|
|
19
|
+
) -> RoutedIntent:
|
|
20
|
+
# Simple routing logic: always plan and execute with no specific flows
|
|
21
|
+
return RoutedIntent(
|
|
22
|
+
mode="plan_and_execute",
|
|
23
|
+
flow_ids=None,
|
|
24
|
+
quick_action_id=None,
|
|
25
|
+
metadata={},
|
|
26
|
+
)
|
|
@@ -439,9 +439,6 @@ class RAGFacade:
|
|
|
439
439
|
k: Number of top results to return.
|
|
440
440
|
rerank: Whether to rerank results using hybrid scoring.
|
|
441
441
|
"""
|
|
442
|
-
print(
|
|
443
|
-
f"🍏 RAGFacade.retrieve: corpus_id={corpus_id}, query={query}, k={k}, rerank={rerank}"
|
|
444
|
-
)
|
|
445
442
|
# For now, rerank flag is ignored; fused hybrid already sorts reasonably.
|
|
446
443
|
return await self.search(corpus_id, query, k=k, mode="hybrid")
|
|
447
444
|
|
|
@@ -14,6 +14,7 @@ class Scope:
|
|
|
14
14
|
|
|
15
15
|
# App / execution context
|
|
16
16
|
app_id: str | None = None
|
|
17
|
+
agent_id: str | None = None
|
|
17
18
|
session_id: str | None = None
|
|
18
19
|
run_id: str | None = None
|
|
19
20
|
graph_id: str | None = None
|
|
@@ -36,10 +37,13 @@ class Scope:
|
|
|
36
37
|
def get(self, key: str, default: Any = None) -> Any:
|
|
37
38
|
return getattr(self, key, default)
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
+
def _base_identity_labels(self) -> dict[str, str]:
|
|
40
41
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
Docstring for _base_identity_labels
|
|
43
|
+
|
|
44
|
+
:param self: Description
|
|
45
|
+
:return: Description
|
|
46
|
+
:rtype: dict[str, str]
|
|
43
47
|
"""
|
|
44
48
|
out: dict[str, str] = {}
|
|
45
49
|
if self.org_id:
|
|
@@ -50,6 +54,8 @@ class Scope:
|
|
|
50
54
|
out["client_id"] = self.client_id
|
|
51
55
|
if self.app_id:
|
|
52
56
|
out["app_id"] = self.app_id
|
|
57
|
+
if self.agent_id:
|
|
58
|
+
out["agent_id"] = self.agent_id
|
|
53
59
|
if self.session_id:
|
|
54
60
|
out["session_id"] = self.session_id
|
|
55
61
|
if self.run_id:
|
|
@@ -58,16 +64,21 @@ class Scope:
|
|
|
58
64
|
out["graph_id"] = self.graph_id
|
|
59
65
|
if self.node_id:
|
|
60
66
|
out["node_id"] = self.node_id
|
|
67
|
+
if self.flow_id:
|
|
68
|
+
out["flow_id"] = self.flow_id
|
|
69
|
+
return out
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
def artifact_scope_labels(self) -> dict[str, str]:
|
|
72
|
+
"""
|
|
73
|
+
Labels to attach to every artifact for this scope.
|
|
74
|
+
These will be mirrored both into Artifact.labels and the index.
|
|
75
|
+
"""
|
|
76
|
+
out: dict[str, str] = {}
|
|
77
|
+
out.update(self._base_identity_labels())
|
|
78
|
+
|
|
79
|
+
# Cononical scope id for artifacts == memory scope id
|
|
80
|
+
# So filter the memory + artifacts by the same value
|
|
81
|
+
out["scope_id"] = self.memory_scope_id()
|
|
71
82
|
return out
|
|
72
83
|
|
|
73
84
|
def metering_dimensions(self) -> dict[str, Any]:
|
|
@@ -123,22 +134,7 @@ class Scope:
|
|
|
123
134
|
but can be any logical scope key.
|
|
124
135
|
"""
|
|
125
136
|
out: dict[str, Any] = {}
|
|
126
|
-
|
|
127
|
-
out["user_id"] = self.user_id
|
|
128
|
-
if self.org_id:
|
|
129
|
-
out["org_id"] = self.org_id
|
|
130
|
-
if self.client_id:
|
|
131
|
-
out["client_id"] = self.client_id
|
|
132
|
-
if self.app_id:
|
|
133
|
-
out["app_id"] = self.app_id
|
|
134
|
-
if self.session_id:
|
|
135
|
-
out["session_id"] = self.session_id
|
|
136
|
-
if self.run_id:
|
|
137
|
-
out["run_id"] = self.run_id
|
|
138
|
-
if self.graph_id:
|
|
139
|
-
out["graph_id"] = self.graph_id
|
|
140
|
-
if self.node_id:
|
|
141
|
-
out["node_id"] = self.node_id
|
|
137
|
+
out.update(self._base_identity_labels())
|
|
142
138
|
if scope_id:
|
|
143
139
|
out["scope_id"] = scope_id
|
|
144
140
|
return out
|
|
@@ -146,14 +142,18 @@ class Scope:
|
|
|
146
142
|
def rag_filter(self, *, scope_id: str | None = None) -> dict[str, Any]:
|
|
147
143
|
"""
|
|
148
144
|
Default filter for RAG search based on identity.
|
|
149
|
-
|
|
145
|
+
|
|
146
|
+
By default we isolate on:
|
|
147
|
+
- org_id (tenant)
|
|
148
|
+
- user_id (actor within tenant)
|
|
149
|
+
- scope_id (memory bucket, usually memory_scope_id)
|
|
150
150
|
"""
|
|
151
151
|
out: dict[str, Any] = {}
|
|
152
152
|
if self.user_id:
|
|
153
153
|
out["user_id"] = self.user_id
|
|
154
154
|
if self.org_id:
|
|
155
155
|
out["org_id"] = self.org_id
|
|
156
|
+
# scope_id is usually memory_scope_id() (e.g. session:, user:, run:, org:, app:)
|
|
156
157
|
if scope_id:
|
|
157
158
|
out["scope_id"] = scope_id
|
|
158
|
-
# you can choose to include session_id / run_id only for very strict isolation
|
|
159
159
|
return out
|
|
@@ -63,12 +63,19 @@ class ScopeFactory:
|
|
|
63
63
|
session_id: str | None = None,
|
|
64
64
|
flow_id: str | None = None,
|
|
65
65
|
) -> Scope:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
base = self.base_from_identity(identity)
|
|
67
|
+
return Scope(
|
|
68
|
+
org_id=base.org_id,
|
|
69
|
+
user_id=base.user_id,
|
|
70
|
+
client_id=base.client_id,
|
|
71
|
+
mode=base.mode,
|
|
72
|
+
app_id=base.app_id,
|
|
73
|
+
session_id=session_id,
|
|
74
|
+
run_id=run_id,
|
|
75
|
+
graph_id=graph_id,
|
|
76
|
+
node_id=None,
|
|
77
|
+
flow_id=flow_id,
|
|
78
|
+
)
|
|
72
79
|
|
|
73
80
|
def for_memory_custom_override(
|
|
74
81
|
self,
|
|
@@ -148,8 +155,9 @@ class ScopeFactory:
|
|
|
148
155
|
base = session_id or run_id
|
|
149
156
|
root = f"session:{base}"
|
|
150
157
|
elif level == "user":
|
|
158
|
+
o = s.org_id or "orgless"
|
|
151
159
|
u = s.user_id or s.client_id or "anon"
|
|
152
|
-
root = f"user:{u}"
|
|
160
|
+
root = f"org:{o}:user:{u}"
|
|
153
161
|
elif level == "run":
|
|
154
162
|
root = f"run:{run_id}"
|
|
155
163
|
elif level == "org":
|
|
File without changes
|