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
|
@@ -15,45 +15,180 @@ class MCPService:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
def __init__(self, clients: dict[str, MCPClientProtocol] | None = None, *, secrets=None):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the MCPService with optional clients and secrets provider.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
Basic usage with no clients:
|
|
23
|
+
```python
|
|
24
|
+
service = MCPService()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
With pre-registered clients:
|
|
28
|
+
```python
|
|
29
|
+
service = MCPService(clients={"default": my_client})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
clients: Optional dictionary mapping names to MCPClientProtocol instances.
|
|
34
|
+
secrets: Optional secrets provider (not implemented here).
|
|
35
|
+
"""
|
|
18
36
|
self._clients: dict[str, MCPClientProtocol] = clients or {}
|
|
19
37
|
self._secrets = secrets # optional (Secrets provider) Not implemented here
|
|
20
38
|
|
|
21
39
|
# ---- registration ----
|
|
22
40
|
def register(self, name: str, client: MCPClientProtocol) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Register a new MCP client under a given name.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
```python
|
|
46
|
+
context.mcp().register("myserver", my_client)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
name: The name to register the client under.
|
|
51
|
+
client: The MCPClientProtocol instance to register.
|
|
52
|
+
"""
|
|
23
53
|
self._clients[name] = client
|
|
24
54
|
|
|
25
55
|
def remove(self, name: str) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Remove a registered MCP client by name.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
```python
|
|
61
|
+
context.mcp().remove("myserver")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
name: The name of the client to remove.
|
|
66
|
+
"""
|
|
26
67
|
self._clients.pop(name, None)
|
|
27
68
|
|
|
28
69
|
def has(self, name: str) -> bool:
|
|
70
|
+
"""
|
|
71
|
+
Check if a client with the given name is registered.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
```python
|
|
75
|
+
if context.mcp().has("default"):
|
|
76
|
+
print("Client exists")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name: The name to check.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
bool: True if the client exists, False otherwise.
|
|
84
|
+
"""
|
|
29
85
|
return name in self._clients
|
|
30
86
|
|
|
31
87
|
def names(self) -> list[str]:
|
|
88
|
+
"""
|
|
89
|
+
Get a list of all registered client names.
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
```python
|
|
93
|
+
names = context.mcp().names()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
list[str]: List of registered client names.
|
|
98
|
+
"""
|
|
32
99
|
return list(self._clients.keys())
|
|
33
100
|
|
|
34
101
|
def list_clients(self) -> list[str]:
|
|
102
|
+
"""
|
|
103
|
+
List all registered client names.
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
```python
|
|
107
|
+
clients = context.mcp().list_clients()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
list[str]: List of registered client names.
|
|
112
|
+
"""
|
|
35
113
|
return list(self._clients.keys())
|
|
36
114
|
|
|
37
115
|
def get(self, name: str = "default") -> MCPClientProtocol:
|
|
116
|
+
"""
|
|
117
|
+
Retrieve a registered MCP client by name.
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
```python
|
|
121
|
+
client = context.mcp().get("default")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
name: The name of the client to retrieve.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
MCPClientProtocol: The registered client.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
KeyError: If the client is not found.
|
|
132
|
+
"""
|
|
38
133
|
if name not in self._clients:
|
|
39
134
|
raise KeyError(f"Unknown MCP server '{name}'")
|
|
40
135
|
return self._clients[name]
|
|
41
136
|
|
|
42
137
|
# ---- lifecycle ----
|
|
43
138
|
async def open(self, name: str) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Open the connection for a specific MCP client.
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
```python
|
|
144
|
+
await context.mcp().open("default")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
name: The name of the client to open.
|
|
149
|
+
"""
|
|
44
150
|
await self.get(name).open()
|
|
45
151
|
|
|
46
152
|
async def close(self, name: str) -> None:
|
|
153
|
+
"""
|
|
154
|
+
Close the connection for a specific MCP client.
|
|
155
|
+
|
|
156
|
+
Examples:
|
|
157
|
+
```python
|
|
158
|
+
await context.mcp().close("default")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
name: The name of the client to close.
|
|
163
|
+
"""
|
|
47
164
|
try:
|
|
48
165
|
await self.get(name).close()
|
|
49
166
|
except Exception:
|
|
50
167
|
logger.warning(f"Failed to close MCP client '{name}'")
|
|
51
168
|
|
|
52
169
|
async def open_all(self) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Open all registered MCP client connections.
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
```python
|
|
175
|
+
await context.mcp().open_all()
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
"""
|
|
53
179
|
for n in self._clients:
|
|
54
180
|
await self._clients[n].open()
|
|
55
181
|
|
|
56
182
|
async def close_all(self) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Close all registered MCP client connections.
|
|
185
|
+
|
|
186
|
+
Examples:
|
|
187
|
+
```python
|
|
188
|
+
await context.mcp().close_all()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
"""
|
|
57
192
|
for n in self._clients:
|
|
58
193
|
try:
|
|
59
194
|
await self._clients[n].close()
|
|
@@ -64,29 +199,103 @@ class MCPService:
|
|
|
64
199
|
async def call(
|
|
65
200
|
self, name: str, tool: str, params: dict[str, Any] | None = None
|
|
66
201
|
) -> dict[str, Any]:
|
|
202
|
+
"""
|
|
203
|
+
Call a tool on a specific MCP client, opening the connection if needed.
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
```python
|
|
207
|
+
result = await context.mcp().call("default", "sum", {"a": 1, "b": 2})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
name: The name of the client to use.
|
|
212
|
+
tool: The tool name to call.
|
|
213
|
+
params: Optional dictionary of parameters for the tool.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
dict[str, Any]: The result from the tool call.
|
|
217
|
+
"""
|
|
67
218
|
# lazy-open on first use; clients themselves also lazy-reconnect
|
|
68
219
|
c = self.get(name)
|
|
69
220
|
await c.open()
|
|
70
221
|
return await c.call(tool, params or {})
|
|
71
222
|
|
|
72
223
|
async def list_tools(self, name: str) -> list[MCPTool]:
|
|
224
|
+
"""
|
|
225
|
+
List all tools available on a specific MCP client.
|
|
226
|
+
|
|
227
|
+
Examples:
|
|
228
|
+
```python
|
|
229
|
+
tools = await context.mcp().list_tools("default")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
name: The name of the client.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
list[MCPTool]: List of available tools.
|
|
237
|
+
"""
|
|
73
238
|
c = self.get(name)
|
|
74
239
|
await c.open()
|
|
75
240
|
return await c.list_tools()
|
|
76
241
|
|
|
77
242
|
async def list_resources(self, name: str) -> list[MCPResource]:
|
|
243
|
+
"""
|
|
244
|
+
List all resources available on a specific MCP client.
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
```python
|
|
248
|
+
resources = await context.mcp().list_resources("default")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
name: The name of the client.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
list[MCPResource]: List of available resources.
|
|
256
|
+
"""
|
|
78
257
|
c = self.get(name)
|
|
79
258
|
await c.open()
|
|
80
259
|
return await c.list_resources()
|
|
81
260
|
|
|
82
261
|
async def read_resource(self, name: str, uri: str) -> dict[str, Any]:
|
|
262
|
+
"""
|
|
263
|
+
Read a resource from a specific MCP client.
|
|
264
|
+
|
|
265
|
+
Examples:
|
|
266
|
+
```python
|
|
267
|
+
data = await context.mcp().read_resource("default", "resource://foo/bar")
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
name: The name of the client.
|
|
272
|
+
uri: The URI of the resource to read.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
dict[str, Any]: The resource data.
|
|
276
|
+
"""
|
|
83
277
|
c = self.get(name)
|
|
84
278
|
await c.open()
|
|
85
279
|
return await c.read_resource(uri)
|
|
86
280
|
|
|
87
281
|
# ---- optional secrets helpers ----
|
|
88
282
|
def set_header(self, name: str, key: str, value: str) -> None:
|
|
89
|
-
"""
|
|
283
|
+
"""
|
|
284
|
+
Set or override a header for a websocket client at runtime.
|
|
285
|
+
|
|
286
|
+
Examples:
|
|
287
|
+
```python
|
|
288
|
+
context.mcp().set_header("default", "Authorization", "Bearer token")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
name: The name of the client.
|
|
293
|
+
key: The header key.
|
|
294
|
+
value: The header value.
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
RuntimeError: If the client does not support headers.
|
|
298
|
+
"""
|
|
90
299
|
c = self.get(name)
|
|
91
300
|
# duck-typing for ws client
|
|
92
301
|
if hasattr(c, "headers") and isinstance(c.headers, dict): # type: ignore[attr-defined]
|
|
@@ -95,6 +304,21 @@ class MCPService:
|
|
|
95
304
|
raise RuntimeError(f"MCP '{name}' does not support headers")
|
|
96
305
|
|
|
97
306
|
def persist_secret(self, secret_name: str, value: str) -> None:
|
|
307
|
+
"""
|
|
308
|
+
Persist a secret using the configured secrets provider.
|
|
309
|
+
|
|
310
|
+
Examples:
|
|
311
|
+
```python
|
|
312
|
+
context.mcp().persist_secret("API_KEY", "my-secret-value")
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
secret_name: The name of the secret.
|
|
317
|
+
value: The value to persist.
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
RuntimeError: If the secrets provider is not writable.
|
|
321
|
+
"""
|
|
98
322
|
if not self._secrets or not hasattr(self._secrets, "set"):
|
|
99
323
|
raise RuntimeError("Secrets provider is not writable")
|
|
100
324
|
self._secrets.set(secret_name, value) # type: ignore
|
|
@@ -7,13 +7,48 @@ from aethergraph.contracts.services.mcp import MCPClientProtocol, MCPResource, M
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class StdioMCPClient(MCPClientProtocol):
|
|
10
|
+
"""
|
|
11
|
+
Initialize the MCP client service to communicate with a subprocess over stdio using JSON-RPC 2.0.
|
|
12
|
+
|
|
13
|
+
This class launches a subprocess (typically an MCP server), manages its lifecycle, and provides
|
|
14
|
+
asynchronous methods to interact with it using JSON-RPC 2.0 over standard input/output streams.
|
|
15
|
+
It handles command execution, environment setup, request/response serialization, and concurrency
|
|
16
|
+
control for safe multi-call usage.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
Basic usage with default environment:
|
|
20
|
+
```python
|
|
21
|
+
from aethergraph.services.mcp import StdioMCPClient
|
|
22
|
+
client = StdioMCPClient(["python", "mcp_server.py"])
|
|
23
|
+
await client.open()
|
|
24
|
+
tools = await client.list_tools()
|
|
25
|
+
await client.close()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Custom environment and timeout:
|
|
29
|
+
```python
|
|
30
|
+
from aethergraph.services.mcp import StdioMCPClient
|
|
31
|
+
client = StdioMCPClient(
|
|
32
|
+
["python", "mcp_server.py"],
|
|
33
|
+
env={"MY_ENV_VAR": "value"},
|
|
34
|
+
timeout=30.0
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
cmd: Command to start the MCP server subprocess (list of str).
|
|
40
|
+
env: Optional dictionary of environment variables for the subprocess.
|
|
41
|
+
timeout: Timeout in seconds for each RPC call.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
None: Initializes the StdioMCPClient instance.
|
|
45
|
+
|
|
46
|
+
Notes:
|
|
47
|
+
- The subprocess should adhere to the JSON-RPC 2.0 specification over stdio.
|
|
48
|
+
- Ensure proper error handling in the subprocess to avoid deadlocks.
|
|
49
|
+
"""
|
|
50
|
+
|
|
10
51
|
def __init__(self, cmd: list[str], env: dict[str, str] | None = None, timeout: float = 60.0):
|
|
11
|
-
"""MCP client that talks to a subprocess over stdio using JSON-RPC 2.0.
|
|
12
|
-
Args:
|
|
13
|
-
cmd: Command to start the MCP server subprocess (list of str).
|
|
14
|
-
env: Optional environment variables to set for the subprocess.
|
|
15
|
-
timeout: Timeout in seconds for each RPC call.
|
|
16
|
-
"""
|
|
17
52
|
self.cmd, self.env, self.timeout = cmd, env or {}, timeout
|
|
18
53
|
self.proc = None
|
|
19
54
|
self._id = 0
|
|
@@ -3,15 +3,57 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
5
|
import json
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
8
|
import websockets
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
# only imported for static type checkers; no runtime import, no warning
|
|
12
|
+
from websockets.client import WebSocketClientProtocol
|
|
10
13
|
|
|
11
14
|
from aethergraph.contracts.services.mcp import MCPClientProtocol, MCPResource, MCPTool
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class WsMCPClient(MCPClientProtocol):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the WebSocket MCP client with URL, headers, and connection parameters.
|
|
20
|
+
This class manages a WebSocket connection to an MCP (Modular Control Protocol) server,
|
|
21
|
+
handling connection lifecycle, pinging for keepalive, and concurrency for sending JSON-RPC
|
|
22
|
+
requests. It provides methods to list tools, call tools, list resources, and read resources
|
|
23
|
+
via the MCP protocol.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
Basic usage with default headers:
|
|
27
|
+
```python
|
|
28
|
+
from aethergraph.services.mcp import WsMCPClient
|
|
29
|
+
client = WsMCPClient("wss://mcp.example.com/ws")
|
|
30
|
+
await client.open()
|
|
31
|
+
tools = await client.list_tools()
|
|
32
|
+
await client.close()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Custom headers and ping interval:
|
|
36
|
+
```python
|
|
37
|
+
from aethergraph.services.mcp import WsMCPClient
|
|
38
|
+
client = WsMCPClient(
|
|
39
|
+
"wss://mcp.example.com/ws",
|
|
40
|
+
headers={"Authorization": "Bearer <token>"},
|
|
41
|
+
ping_interval=10.0,
|
|
42
|
+
timeout=30.0
|
|
43
|
+
await client.open()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
url: The WebSocket URL of the MCP server (e.g., "wss://mcp.example.com/ws").
|
|
48
|
+
headers: Optional dictionary of additional HTTP headers to include in the WebSocket handshake.
|
|
49
|
+
timeout: Maximum time (in seconds) to wait for connection and responses.
|
|
50
|
+
ping_interval: Interval (in seconds) between WebSocket ping frames for keepalive.
|
|
51
|
+
ping_timeout: Maximum time (in seconds) to wait for a ping response before considering the connection dead.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
None: Initializes the WsMCPClient instance and prepares internal state.
|
|
55
|
+
"""
|
|
56
|
+
|
|
15
57
|
def __init__(
|
|
16
58
|
self,
|
|
17
59
|
url: str,
|
|
File without changes
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from aethergraph.contracts.services.llm import LLMClientProtocol
|
|
8
|
+
from aethergraph.contracts.services.memory import Distiller, Event, HotLog, Indices, Persistence
|
|
9
|
+
from aethergraph.contracts.storage.doc_store import DocStore
|
|
10
|
+
|
|
11
|
+
# metering
|
|
12
|
+
from aethergraph.core.runtime.runtime_metering import current_meter_context, current_metering
|
|
13
|
+
from aethergraph.services.memory.facade.utils import now_iso, stable_event_id
|
|
14
|
+
from aethergraph.services.memory.utils import _summary_doc_id
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LLMLongTermSummarizer(Distiller):
|
|
18
|
+
"""
|
|
19
|
+
LLM-based long-term summarizer.
|
|
20
|
+
|
|
21
|
+
Flow:
|
|
22
|
+
1) Pull recent events from HotLog.
|
|
23
|
+
2) Filter by kind/tag/signal.
|
|
24
|
+
3) Build a prompt that shows the most important events as a transcript.
|
|
25
|
+
4) Call LLM to generate a structured summary.
|
|
26
|
+
5) Save summary JSON via Persistence.save_json(uri).
|
|
27
|
+
6) Emit a long_term_summary Event pointing to summary_uri.
|
|
28
|
+
|
|
29
|
+
This is complementary to RAG:
|
|
30
|
+
- LLM distiller compresses sequences into a digest.
|
|
31
|
+
- RAG uses many such digests + raw docs for retrieval.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
llm: LLMClientProtocol,
|
|
38
|
+
summary_kind: str = "long_term_summary",
|
|
39
|
+
summary_tag: str = "session",
|
|
40
|
+
include_kinds: list[str] | None = None,
|
|
41
|
+
include_tags: list[str] | None = None,
|
|
42
|
+
max_events: int = 200,
|
|
43
|
+
min_signal: float = 0.0,
|
|
44
|
+
model: str | None = None,
|
|
45
|
+
):
|
|
46
|
+
self.llm = llm
|
|
47
|
+
self.summary_kind = summary_kind
|
|
48
|
+
self.summary_tag = summary_tag
|
|
49
|
+
self.include_kinds = include_kinds
|
|
50
|
+
self.include_tags = include_tags
|
|
51
|
+
self.max_events = max_events
|
|
52
|
+
self.min_signal = min_signal
|
|
53
|
+
self.model = model # optional model override
|
|
54
|
+
|
|
55
|
+
def _filter_events(self, events: Iterable[Event]) -> list[Event]:
|
|
56
|
+
out: list[Event] = []
|
|
57
|
+
kinds = set(self.include_kinds) if self.include_kinds else None
|
|
58
|
+
tags = set(self.include_tags) if self.include_tags else None
|
|
59
|
+
|
|
60
|
+
for e in events:
|
|
61
|
+
if kinds is not None and e.kind not in kinds:
|
|
62
|
+
continue
|
|
63
|
+
if tags is not None:
|
|
64
|
+
if not e.tags:
|
|
65
|
+
continue
|
|
66
|
+
if not tags.issubset(set(e.tags)):
|
|
67
|
+
continue
|
|
68
|
+
if (e.signal or 0.0) < self.min_signal:
|
|
69
|
+
continue
|
|
70
|
+
out.append(e)
|
|
71
|
+
return out
|
|
72
|
+
|
|
73
|
+
def _build_prompt(self, events: list[Event]) -> list[dict[str, str]]:
|
|
74
|
+
"""
|
|
75
|
+
Convert events into a chat-style context for summarization.
|
|
76
|
+
|
|
77
|
+
We keep it model-agnostic: a list of {role, content} messages.
|
|
78
|
+
"""
|
|
79
|
+
lines: list[str] = []
|
|
80
|
+
|
|
81
|
+
for e in events:
|
|
82
|
+
role = e.stage or e.kind or "event"
|
|
83
|
+
if e.text:
|
|
84
|
+
lines.append(f"[{role}] {e.text}")
|
|
85
|
+
|
|
86
|
+
transcript = "\n".join(lines)
|
|
87
|
+
|
|
88
|
+
system = (
|
|
89
|
+
"You are a log summarizer for an agent's memory. "
|
|
90
|
+
"Given a chronological transcript of events, produce a concise summary "
|
|
91
|
+
"of what happened, key themes, important user facts, and open TODOs."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
user = (
|
|
95
|
+
"Here is the recent event transcript:\n\n"
|
|
96
|
+
f"{transcript}\n\n"
|
|
97
|
+
"Return a JSON object with keys: "
|
|
98
|
+
"`summary` (string), "
|
|
99
|
+
"`key_facts` (list of strings), "
|
|
100
|
+
"`open_loops` (list of strings)."
|
|
101
|
+
"Do not use markdown or include explanations or context outside the JSON."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return [
|
|
105
|
+
{"role": "system", "content": system},
|
|
106
|
+
{"role": "user", "content": user},
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
async def distill(
|
|
110
|
+
self,
|
|
111
|
+
run_id: str,
|
|
112
|
+
timeline_id: str,
|
|
113
|
+
scope_id: str = None,
|
|
114
|
+
*,
|
|
115
|
+
hotlog: HotLog,
|
|
116
|
+
persistence: Persistence,
|
|
117
|
+
indices: Indices,
|
|
118
|
+
docs: DocStore,
|
|
119
|
+
**kw: Any,
|
|
120
|
+
) -> dict[str, Any]:
|
|
121
|
+
# 1) fetch more events than needed, then filter
|
|
122
|
+
raw = await hotlog.recent(timeline_id, kinds=None, limit=self.max_events * 2)
|
|
123
|
+
kept = self._filter_events(raw)
|
|
124
|
+
if not kept:
|
|
125
|
+
return {}
|
|
126
|
+
|
|
127
|
+
kept = kept[-self.max_events :]
|
|
128
|
+
first_ts = kept[0].ts
|
|
129
|
+
last_ts = kept[-1].ts
|
|
130
|
+
|
|
131
|
+
# 2) Build prompt and call LLM
|
|
132
|
+
messages = self._build_prompt(kept)
|
|
133
|
+
|
|
134
|
+
# LLMClientProtocol: assume chat(...) returns (text, usage)
|
|
135
|
+
summary_json_str, usage = await self.llm.chat(
|
|
136
|
+
messages,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# 3) Parse LLM JSON response
|
|
140
|
+
try:
|
|
141
|
+
payload = json.loads(summary_json_str)
|
|
142
|
+
except Exception:
|
|
143
|
+
payload = {
|
|
144
|
+
"summary": summary_json_str,
|
|
145
|
+
"key_facts": [],
|
|
146
|
+
"open_loops": [],
|
|
147
|
+
}
|
|
148
|
+
ts = now_iso()
|
|
149
|
+
|
|
150
|
+
summary_obj = {
|
|
151
|
+
"type": self.summary_kind,
|
|
152
|
+
"version": 1,
|
|
153
|
+
"run_id": run_id,
|
|
154
|
+
"scope_id": scope_id or run_id,
|
|
155
|
+
"summary_tag": self.summary_tag,
|
|
156
|
+
"ts": ts,
|
|
157
|
+
"time_window": {"from": first_ts, "to": last_ts},
|
|
158
|
+
"num_events": len(kept),
|
|
159
|
+
"source_event_ids": [e.event_id for e in kept],
|
|
160
|
+
"summary": payload.get("summary", ""),
|
|
161
|
+
"key_facts": payload.get("key_facts", []),
|
|
162
|
+
"open_loops": payload.get("open_loops", []),
|
|
163
|
+
"llm_usage": usage,
|
|
164
|
+
"llm_model": self.llm.model if hasattr(self.llm, "model") else None,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
scope = scope_id or run_id
|
|
168
|
+
doc_id = _summary_doc_id(scope, self.summary_tag, ts)
|
|
169
|
+
await docs.put(doc_id, summary_obj)
|
|
170
|
+
|
|
171
|
+
# 4) Emit summary Event with preview + uri in data
|
|
172
|
+
text = summary_obj["summary"] or ""
|
|
173
|
+
preview = text[:2000] + (" …[truncated]" if len(text) > 2000 else "")
|
|
174
|
+
|
|
175
|
+
evt = Event(
|
|
176
|
+
event_id="",
|
|
177
|
+
ts=ts,
|
|
178
|
+
run_id=run_id,
|
|
179
|
+
scope_id=scope,
|
|
180
|
+
kind=self.summary_kind,
|
|
181
|
+
stage="summary_llm",
|
|
182
|
+
text=preview,
|
|
183
|
+
tags=["summary", "llm", self.summary_tag],
|
|
184
|
+
data={
|
|
185
|
+
"summary_doc_id": doc_id,
|
|
186
|
+
"summary_tag": self.summary_tag,
|
|
187
|
+
"time_window": summary_obj["time_window"],
|
|
188
|
+
"num_events": len(kept),
|
|
189
|
+
},
|
|
190
|
+
metrics={"num_events": len(kept)},
|
|
191
|
+
severity=2,
|
|
192
|
+
signal=0.7,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
evt.event_id = stable_event_id(
|
|
196
|
+
{
|
|
197
|
+
"ts": ts,
|
|
198
|
+
"run_id": run_id,
|
|
199
|
+
"kind": self.summary_kind,
|
|
200
|
+
"summary_tag": self.summary_tag,
|
|
201
|
+
"preview": preview[:200],
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
await hotlog.append(timeline_id, evt, ttl_s=7 * 24 * 3600, limit=1000)
|
|
206
|
+
await persistence.append_event(timeline_id, evt)
|
|
207
|
+
|
|
208
|
+
# Metering: record summary event
|
|
209
|
+
try:
|
|
210
|
+
meter = current_metering()
|
|
211
|
+
ctx = current_meter_context.get()
|
|
212
|
+
user_id = ctx.get("user_id")
|
|
213
|
+
org_id = ctx.get("org_id")
|
|
214
|
+
|
|
215
|
+
await meter.record_event(
|
|
216
|
+
user_id=user_id,
|
|
217
|
+
org_id=org_id,
|
|
218
|
+
run_id=run_id,
|
|
219
|
+
scope_id=scope,
|
|
220
|
+
kind=f"memory.{self.summary_kind}", # e.g. "memory.long_term_summary"
|
|
221
|
+
)
|
|
222
|
+
except Exception:
|
|
223
|
+
import logging
|
|
224
|
+
|
|
225
|
+
logger = logging.getLogger("aethergraph.services.memory.distillers.llm_long_term")
|
|
226
|
+
logger.error("Failed to record metering event for long_term_summary")
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
"summary_doc_id": doc_id,
|
|
230
|
+
"summary_kind": self.summary_kind,
|
|
231
|
+
"summary_tag": self.summary_tag,
|
|
232
|
+
"time_window": summary_obj["time_window"],
|
|
233
|
+
"num_events": len(kept),
|
|
234
|
+
}
|