aethergraph 0.1.0a1__py3-none-any.whl → 0.1.0a3__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 +296 -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 +196 -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.0a3.dist-info}/METADATA +138 -31
- aethergraph-0.1.0a3.dist-info/RECORD +356 -0
- aethergraph-0.1.0a3.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.0a3.dist-info}/WHEEL +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
- {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from aethergraph.services.rag.facade import RAGFacade, SearchHit
|
|
7
|
+
from aethergraph.services.scope.scope import Scope
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class NodeRAG:
|
|
12
|
+
"""
|
|
13
|
+
Node-scoped RAG helper.
|
|
14
|
+
|
|
15
|
+
- Wraps a global RAGFacade.
|
|
16
|
+
- Injects Scope into the common node-facing calls.
|
|
17
|
+
- Delegates everything else via __getattr__.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
rag: RAGFacade
|
|
21
|
+
scope: Scope
|
|
22
|
+
default_scope_id: str | None = None
|
|
23
|
+
|
|
24
|
+
# -------- internals --------
|
|
25
|
+
|
|
26
|
+
def _scope_id(self, scope_id: str | None) -> str | None:
|
|
27
|
+
if scope_id is not None:
|
|
28
|
+
return scope_id
|
|
29
|
+
if self.default_scope_id is not None:
|
|
30
|
+
return self.default_scope_id
|
|
31
|
+
return self.scope.memory_scope_id()
|
|
32
|
+
|
|
33
|
+
# -------- scope-aware helpers --------
|
|
34
|
+
|
|
35
|
+
async def bind_corpus(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
corpus_id: str | None = None,
|
|
39
|
+
key: str | None = None,
|
|
40
|
+
create_if_missing: bool = True,
|
|
41
|
+
labels: dict[str, Any] | None = None,
|
|
42
|
+
scope_id: str | None = None,
|
|
43
|
+
) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Bind or create a RAG corpus for the current node scope.
|
|
46
|
+
|
|
47
|
+
This method ensures a corpus exists for the given scope and key, creating it if necessary.
|
|
48
|
+
It automatically injects scope labels and metadata, and returns the resolved corpus ID.
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
Bind a default corpus for the current node:
|
|
52
|
+
```python
|
|
53
|
+
corpus_id = await context.rag().bind_corpus()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Bind or create a corpus with a custom key and extra labels:
|
|
57
|
+
```python
|
|
58
|
+
corpus_id = await context.rag().bind_corpus(
|
|
59
|
+
key="my-data",
|
|
60
|
+
labels={"source": "user-upload"}
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
corpus_id: Optional explicit corpus identifier. If not provided, one is generated from the scope and key.
|
|
66
|
+
key: Optional string to distinguish corpora within the same scope (e.g., "default", "my-data").
|
|
67
|
+
create_if_missing: If True (default), create the corpus if it does not exist.
|
|
68
|
+
labels: Optional dictionary of additional metadata to attach to the corpus.
|
|
69
|
+
scope_id: Optional override for the scope identifier. Defaults to the current node's scope.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
str: The resolved corpus ID, guaranteed to exist if `create_if_missing` is True.
|
|
73
|
+
|
|
74
|
+
Notes:
|
|
75
|
+
- The corpus ID is derived from the scope and key if not explicitly provided.
|
|
76
|
+
- Scope labels are automatically merged into the corpus metadata.
|
|
77
|
+
"""
|
|
78
|
+
sid = self._scope_id(scope_id)
|
|
79
|
+
scope_labels = self.scope.rag_scope_labels(scope_id=sid)
|
|
80
|
+
|
|
81
|
+
if corpus_id:
|
|
82
|
+
cid = corpus_id
|
|
83
|
+
else:
|
|
84
|
+
# e.g. mem:<scope>:<key>
|
|
85
|
+
cid = self.scope.rag_corpus_id(scope_id=sid, key=key or "default")
|
|
86
|
+
|
|
87
|
+
meta = {"scope": scope_labels, **(labels or {})}
|
|
88
|
+
|
|
89
|
+
if create_if_missing:
|
|
90
|
+
await self.rag.add_corpus(
|
|
91
|
+
corpus_id=cid,
|
|
92
|
+
meta=meta,
|
|
93
|
+
scope_labels=scope_labels,
|
|
94
|
+
)
|
|
95
|
+
return cid
|
|
96
|
+
|
|
97
|
+
async def upsert_docs(
|
|
98
|
+
self,
|
|
99
|
+
corpus_id: str,
|
|
100
|
+
docs: list[dict[str, Any]],
|
|
101
|
+
*,
|
|
102
|
+
scope_id: str | None = None,
|
|
103
|
+
) -> dict[str, Any]:
|
|
104
|
+
"""
|
|
105
|
+
Ingest and index a list of documents into the specified corpus for the current node scope.
|
|
106
|
+
|
|
107
|
+
This method ensures the corpus exists for the given scope, merges scope labels into each document,
|
|
108
|
+
and handles both file-based and inline text documents. Documents are chunked, embedded, and indexed
|
|
109
|
+
for retrieval.
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
Ingest a list of inline documents:
|
|
113
|
+
```python
|
|
114
|
+
await context.rag().upsert_docs(
|
|
115
|
+
corpus_id="my-corpus",
|
|
116
|
+
docs=[
|
|
117
|
+
{"text": "Document content...", "title": "Doc Title"},
|
|
118
|
+
{"text": "Another doc", "labels": {"source": "user-upload"}}
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Ingest a PDF file with custom labels:
|
|
124
|
+
```python
|
|
125
|
+
await context.rag().upsert_docs(
|
|
126
|
+
corpus_id="my-corpus",
|
|
127
|
+
docs=[{"path": "/path/to/file.pdf", "labels": {"type": "pdf"}}]
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
corpus_id: The target corpus identifier.
|
|
133
|
+
docs: A list of document specifications. Each document can be:
|
|
134
|
+
- File-based: {"path": "/path/to/doc.pdf", "labels": {...}}
|
|
135
|
+
- Inline text: {"text": "Document content...", "title": "Doc Title", "labels": {...}}
|
|
136
|
+
scope_id: Optional override for the scope identifier. Defaults to the current node's scope.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
dict[str, Any]: Summary of the ingestion, including number of documents and chunks added.
|
|
140
|
+
|
|
141
|
+
Notes:
|
|
142
|
+
- Scope labels are merged into each document's labels.
|
|
143
|
+
- File-based documents are read and chunked automatically.
|
|
144
|
+
- Inline text documents are chunked based on configured chunk size.
|
|
145
|
+
"""
|
|
146
|
+
sid = self._scope_id(scope_id)
|
|
147
|
+
return await self.rag.upsert_docs(
|
|
148
|
+
corpus_id=corpus_id,
|
|
149
|
+
docs=docs,
|
|
150
|
+
scope=self.scope,
|
|
151
|
+
scope_id=sid,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
async def search(
|
|
155
|
+
self,
|
|
156
|
+
corpus_id: str,
|
|
157
|
+
query: str,
|
|
158
|
+
*,
|
|
159
|
+
k: int = 8,
|
|
160
|
+
filters: dict[str, Any] | None = None,
|
|
161
|
+
scope_id: str | None = None,
|
|
162
|
+
mode: str = "hybrid",
|
|
163
|
+
) -> list[SearchHit]:
|
|
164
|
+
"""
|
|
165
|
+
Search the specified RAG corpus for relevant chunks matching a query.
|
|
166
|
+
|
|
167
|
+
This method performs a dense or hybrid (dense + lexical) search over the corpus,
|
|
168
|
+
automatically injecting node scope filters. It returns the top-k most relevant
|
|
169
|
+
results as `SearchHit` objects, including chunk text, metadata, and scores.
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
Basic usage to search a corpus:
|
|
173
|
+
```python
|
|
174
|
+
hits = await context.rag().search(
|
|
175
|
+
corpus_id="my-corpus",
|
|
176
|
+
query="What is the capital of France?"
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Search with custom filters and top-3 results:
|
|
181
|
+
```python
|
|
182
|
+
hits = await context.rag().search(
|
|
183
|
+
corpus_id="my-corpus",
|
|
184
|
+
query="project roadmap",
|
|
185
|
+
k=3,
|
|
186
|
+
filters={"type": "meeting-notes"}
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
corpus_id: The target corpus identifier to search within.
|
|
192
|
+
query: The search query string.
|
|
193
|
+
k: The number of top results to return (default: 8).
|
|
194
|
+
filters: Optional dictionary of metadata filters to apply (merged with scope filters).
|
|
195
|
+
scope_id: Optional override for the scope identifier. Defaults to the current node's scope.
|
|
196
|
+
mode: Search mode, either `"dense"` or `"hybrid"` (default: "hybrid").
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
list[SearchHit]: A list of matching `SearchHit` objects, each containing chunk text,
|
|
200
|
+
metadata, score, and identifiers.
|
|
201
|
+
|
|
202
|
+
Notes:
|
|
203
|
+
- Scope filters are automatically merged with any provided filters.
|
|
204
|
+
- Hybrid mode fuses dense and lexical search for improved relevance.
|
|
205
|
+
- Results are sorted by descending relevance score.
|
|
206
|
+
"""
|
|
207
|
+
sid = self._scope_id(scope_id)
|
|
208
|
+
scoped_filters = self.scope.rag_filter(scope_id=sid)
|
|
209
|
+
if filters:
|
|
210
|
+
scoped_filters.update(filters)
|
|
211
|
+
return await self.rag.search(
|
|
212
|
+
corpus_id=corpus_id,
|
|
213
|
+
query=query,
|
|
214
|
+
k=k,
|
|
215
|
+
filters=scoped_filters,
|
|
216
|
+
mode=mode,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
async def answer(
|
|
220
|
+
self,
|
|
221
|
+
corpus_id: str,
|
|
222
|
+
question: str,
|
|
223
|
+
*,
|
|
224
|
+
llm: str | None = None,
|
|
225
|
+
style: str = "concise",
|
|
226
|
+
with_citations: bool = True,
|
|
227
|
+
k: int = 6,
|
|
228
|
+
scope_id: str | None = None,
|
|
229
|
+
) -> dict[str, Any]:
|
|
230
|
+
"""
|
|
231
|
+
Answer a question using retrieved context from a specified corpus.
|
|
232
|
+
|
|
233
|
+
This method retrieves relevant context chunks from the target corpus, constructs a prompt for the language model, and generates an answer. Citations to the retrieved chunks are included if requested. The function is accessed via `context.rag().answer(...)`.
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
Basic usage to answer a question:
|
|
237
|
+
```python
|
|
238
|
+
result = await context.rag().answer(
|
|
239
|
+
corpus_id="my-corpus",
|
|
240
|
+
question="What is the capital of France?"
|
|
241
|
+
print(result["answer"])
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Requesting a detailed answer with citations:
|
|
245
|
+
```python
|
|
246
|
+
result = await context.rag().answer(
|
|
247
|
+
corpus_id="my-corpus",
|
|
248
|
+
question="Explain the process of photosynthesis.",
|
|
249
|
+
style="detailed",
|
|
250
|
+
with_citations=True,
|
|
251
|
+
k=8
|
|
252
|
+
)
|
|
253
|
+
print("Answer:", result["answer"])
|
|
254
|
+
for cite in result["citations"]:
|
|
255
|
+
print(f"Citation: {cite['text']} (Score: {cite['score']})")
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
corpus_id: Identifier of the target corpus to search for context.
|
|
260
|
+
question: The question to be answered.
|
|
261
|
+
llm: Optional language model client to use for answer generation. If None, the default LLM is used.
|
|
262
|
+
style: The style of the answer, either "concise" (default) or "detailed".
|
|
263
|
+
with_citations: Whether to include citations to the retrieved context chunks in the answer (default: True).
|
|
264
|
+
k: Number of context chunks to retrieve for answering (default: 6).
|
|
265
|
+
scope_id: Optional identifier to restrict retrieval to a specific scope.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
dict[str, Any]: A dictionary containing the generated answer, citations, usage statistics, and optionally resolved citation metadata.
|
|
269
|
+
|
|
270
|
+
Notes:
|
|
271
|
+
- the generated dictionary includes:
|
|
272
|
+
|
|
273
|
+
- `answer`: The generated answer text.
|
|
274
|
+
- `citations`: List of retrieved context chunks used as citations.
|
|
275
|
+
- `usage`: LLM usage statistics (tokens, time, etc.).
|
|
276
|
+
- `resolved_citations`: Optional metadata for citations if available.
|
|
277
|
+
|
|
278
|
+
- Example response:
|
|
279
|
+
```python
|
|
280
|
+
{
|
|
281
|
+
"answer": "The capital of France is Paris.",
|
|
282
|
+
"citations": [
|
|
283
|
+
{"text": "Paris is the capital city of France...", "score": 0.95, ...},
|
|
284
|
+
...
|
|
285
|
+
],
|
|
286
|
+
"usage": {"prompt_tokens": 150, "completion_tokens": 50, "total_tokens": 200, ...},
|
|
287
|
+
"resolved_citations": [
|
|
288
|
+
{"doc_id": "doc123", "title": "Geography of France", ...},
|
|
289
|
+
...
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
"""
|
|
294
|
+
sid = self._scope_id(scope_id)
|
|
295
|
+
return await self.rag.answer(
|
|
296
|
+
corpus_id=corpus_id,
|
|
297
|
+
question=question,
|
|
298
|
+
llm=llm,
|
|
299
|
+
style=style,
|
|
300
|
+
with_citations=with_citations,
|
|
301
|
+
k=k,
|
|
302
|
+
scope=self.scope,
|
|
303
|
+
scope_id=sid,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# -------- delegation: everything else --------
|
|
307
|
+
|
|
308
|
+
def __getattr__(self, name: str) -> Any:
|
|
309
|
+
"""
|
|
310
|
+
Fallback: expose the underlying RAGFacade API for advanced users.
|
|
311
|
+
|
|
312
|
+
Node code can still call low-level stuff if needed:
|
|
313
|
+
ctx.rag.stats(...)
|
|
314
|
+
ctx.rag.list_corpora()
|
|
315
|
+
etc.
|
|
316
|
+
"""
|
|
317
|
+
return getattr(self.rag, name)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from collections import defaultdict, deque
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SimpleRateLimiter:
|
|
6
|
+
def __init__(self, max_events: int, window_seconds: int):
|
|
7
|
+
self.max_events = max_events
|
|
8
|
+
self.window = window_seconds
|
|
9
|
+
self._events: dict[str, deque[float]] = defaultdict(deque)
|
|
10
|
+
|
|
11
|
+
def allow(self, key: str) -> bool:
|
|
12
|
+
now = time.time()
|
|
13
|
+
dq = self._events[key]
|
|
14
|
+
cutoff = now - self.window
|
|
15
|
+
|
|
16
|
+
# Drop old events
|
|
17
|
+
while dq and dq[0] < cutoff:
|
|
18
|
+
dq.popleft()
|
|
19
|
+
|
|
20
|
+
if len(dq) >= self.max_events:
|
|
21
|
+
return False # Rate limit exceeded
|
|
22
|
+
|
|
23
|
+
dq.append(now)
|
|
24
|
+
return True
|
|
File without changes
|