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.
Files changed (267) hide show
  1. aethergraph/__init__.py +4 -10
  2. aethergraph/__main__.py +296 -0
  3. aethergraph/api/v1/__init__.py +0 -0
  4. aethergraph/api/v1/agents.py +46 -0
  5. aethergraph/api/v1/apps.py +70 -0
  6. aethergraph/api/v1/artifacts.py +415 -0
  7. aethergraph/api/v1/channels.py +89 -0
  8. aethergraph/api/v1/deps.py +168 -0
  9. aethergraph/api/v1/graphs.py +259 -0
  10. aethergraph/api/v1/identity.py +25 -0
  11. aethergraph/api/v1/memory.py +353 -0
  12. aethergraph/api/v1/misc.py +47 -0
  13. aethergraph/api/v1/pagination.py +29 -0
  14. aethergraph/api/v1/runs.py +568 -0
  15. aethergraph/api/v1/schemas.py +535 -0
  16. aethergraph/api/v1/session.py +323 -0
  17. aethergraph/api/v1/stats.py +201 -0
  18. aethergraph/api/v1/viz.py +152 -0
  19. aethergraph/config/config.py +22 -0
  20. aethergraph/config/loader.py +3 -2
  21. aethergraph/config/storage.py +209 -0
  22. aethergraph/contracts/__init__.py +0 -0
  23. aethergraph/contracts/services/__init__.py +0 -0
  24. aethergraph/contracts/services/artifacts.py +27 -14
  25. aethergraph/contracts/services/memory.py +45 -17
  26. aethergraph/contracts/services/metering.py +129 -0
  27. aethergraph/contracts/services/runs.py +50 -0
  28. aethergraph/contracts/services/sessions.py +87 -0
  29. aethergraph/contracts/services/state_stores.py +3 -0
  30. aethergraph/contracts/services/viz.py +44 -0
  31. aethergraph/contracts/storage/artifact_index.py +88 -0
  32. aethergraph/contracts/storage/artifact_store.py +99 -0
  33. aethergraph/contracts/storage/async_kv.py +34 -0
  34. aethergraph/contracts/storage/blob_store.py +50 -0
  35. aethergraph/contracts/storage/doc_store.py +35 -0
  36. aethergraph/contracts/storage/event_log.py +31 -0
  37. aethergraph/contracts/storage/vector_index.py +48 -0
  38. aethergraph/core/__init__.py +0 -0
  39. aethergraph/core/execution/forward_scheduler.py +13 -2
  40. aethergraph/core/execution/global_scheduler.py +21 -15
  41. aethergraph/core/execution/step_forward.py +10 -1
  42. aethergraph/core/graph/__init__.py +0 -0
  43. aethergraph/core/graph/graph_builder.py +8 -4
  44. aethergraph/core/graph/graph_fn.py +156 -15
  45. aethergraph/core/graph/graph_spec.py +8 -0
  46. aethergraph/core/graph/graphify.py +146 -27
  47. aethergraph/core/graph/node_spec.py +0 -2
  48. aethergraph/core/graph/node_state.py +3 -0
  49. aethergraph/core/graph/task_graph.py +39 -1
  50. aethergraph/core/runtime/__init__.py +0 -0
  51. aethergraph/core/runtime/ad_hoc_context.py +64 -4
  52. aethergraph/core/runtime/base_service.py +28 -4
  53. aethergraph/core/runtime/execution_context.py +13 -15
  54. aethergraph/core/runtime/graph_runner.py +222 -37
  55. aethergraph/core/runtime/node_context.py +510 -6
  56. aethergraph/core/runtime/node_services.py +12 -5
  57. aethergraph/core/runtime/recovery.py +15 -1
  58. aethergraph/core/runtime/run_manager.py +783 -0
  59. aethergraph/core/runtime/run_manager_local.py +204 -0
  60. aethergraph/core/runtime/run_registration.py +2 -2
  61. aethergraph/core/runtime/run_types.py +89 -0
  62. aethergraph/core/runtime/runtime_env.py +136 -7
  63. aethergraph/core/runtime/runtime_metering.py +71 -0
  64. aethergraph/core/runtime/runtime_registry.py +36 -13
  65. aethergraph/core/runtime/runtime_services.py +194 -6
  66. aethergraph/core/tools/builtins/toolset.py +1 -1
  67. aethergraph/core/tools/toolkit.py +5 -0
  68. aethergraph/plugins/agents/default_chat_agent copy.py +90 -0
  69. aethergraph/plugins/agents/default_chat_agent.py +171 -0
  70. aethergraph/plugins/agents/shared.py +81 -0
  71. aethergraph/plugins/channel/adapters/webui.py +112 -112
  72. aethergraph/plugins/channel/routes/webui_routes.py +367 -102
  73. aethergraph/plugins/channel/utils/slack_utils.py +115 -59
  74. aethergraph/plugins/channel/utils/telegram_utils.py +88 -47
  75. aethergraph/plugins/channel/websockets/weibui_ws.py +172 -0
  76. aethergraph/runtime/__init__.py +15 -0
  77. aethergraph/server/app_factory.py +196 -34
  78. aethergraph/server/clients/channel_client.py +202 -0
  79. aethergraph/server/http/channel_http_routes.py +116 -0
  80. aethergraph/server/http/channel_ws_routers.py +45 -0
  81. aethergraph/server/loading.py +117 -0
  82. aethergraph/server/server.py +131 -0
  83. aethergraph/server/server_state.py +240 -0
  84. aethergraph/server/start.py +227 -66
  85. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  86. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  87. aethergraph/server/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  88. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  89. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  90. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  91. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  92. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  93. aethergraph/server/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  94. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  95. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  96. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  97. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  98. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  99. aethergraph/server/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  100. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  101. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  102. aethergraph/server/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  103. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  104. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  105. aethergraph/server/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  106. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  107. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  108. aethergraph/server/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  109. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  110. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  111. aethergraph/server/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  112. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  113. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  114. aethergraph/server/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  115. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  116. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  117. aethergraph/server/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  118. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  119. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  120. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  121. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  122. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  123. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  124. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  125. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  126. aethergraph/server/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  127. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  128. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  129. aethergraph/server/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  130. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  131. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  132. aethergraph/server/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  133. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  134. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  135. aethergraph/server/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  136. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  137. aethergraph/server/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  138. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  139. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  140. aethergraph/server/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  141. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  142. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  143. aethergraph/server/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  144. aethergraph/server/ui_static/assets/index-BR5GtXcZ.css +1 -0
  145. aethergraph/server/ui_static/assets/index-CQ0HZZ83.js +400 -0
  146. aethergraph/server/ui_static/index.html +15 -0
  147. aethergraph/server/ui_static/logo.png +0 -0
  148. aethergraph/services/artifacts/__init__.py +0 -0
  149. aethergraph/services/artifacts/facade.py +1239 -132
  150. aethergraph/services/auth/{dev.py → authn.py} +0 -8
  151. aethergraph/services/auth/authz.py +100 -0
  152. aethergraph/services/channel/__init__.py +0 -0
  153. aethergraph/services/channel/channel_bus.py +19 -1
  154. aethergraph/services/channel/factory.py +13 -1
  155. aethergraph/services/channel/ingress.py +311 -0
  156. aethergraph/services/channel/queue_adapter.py +75 -0
  157. aethergraph/services/channel/session.py +502 -19
  158. aethergraph/services/container/default_container.py +122 -43
  159. aethergraph/services/continuations/continuation.py +6 -0
  160. aethergraph/services/continuations/stores/fs_store.py +19 -0
  161. aethergraph/services/eventhub/event_hub.py +76 -0
  162. aethergraph/services/kv/__init__.py +0 -0
  163. aethergraph/services/kv/ephemeral.py +244 -0
  164. aethergraph/services/llm/__init__.py +0 -0
  165. aethergraph/services/llm/generic_client copy.py +691 -0
  166. aethergraph/services/llm/generic_client.py +1288 -187
  167. aethergraph/services/llm/providers.py +3 -1
  168. aethergraph/services/llm/types.py +47 -0
  169. aethergraph/services/llm/utils.py +284 -0
  170. aethergraph/services/logger/std.py +3 -0
  171. aethergraph/services/mcp/__init__.py +9 -0
  172. aethergraph/services/mcp/http_client.py +38 -0
  173. aethergraph/services/mcp/service.py +225 -1
  174. aethergraph/services/mcp/stdio_client.py +41 -6
  175. aethergraph/services/mcp/ws_client.py +44 -2
  176. aethergraph/services/memory/__init__.py +0 -0
  177. aethergraph/services/memory/distillers/llm_long_term.py +234 -0
  178. aethergraph/services/memory/distillers/llm_meta_summary.py +398 -0
  179. aethergraph/services/memory/distillers/long_term.py +225 -0
  180. aethergraph/services/memory/facade/__init__.py +3 -0
  181. aethergraph/services/memory/facade/chat.py +440 -0
  182. aethergraph/services/memory/facade/core.py +447 -0
  183. aethergraph/services/memory/facade/distillation.py +424 -0
  184. aethergraph/services/memory/facade/rag.py +410 -0
  185. aethergraph/services/memory/facade/results.py +315 -0
  186. aethergraph/services/memory/facade/retrieval.py +139 -0
  187. aethergraph/services/memory/facade/types.py +77 -0
  188. aethergraph/services/memory/facade/utils.py +43 -0
  189. aethergraph/services/memory/facade_dep.py +1539 -0
  190. aethergraph/services/memory/factory.py +9 -3
  191. aethergraph/services/memory/utils.py +10 -0
  192. aethergraph/services/metering/eventlog_metering.py +470 -0
  193. aethergraph/services/metering/noop.py +25 -4
  194. aethergraph/services/rag/__init__.py +0 -0
  195. aethergraph/services/rag/facade.py +279 -23
  196. aethergraph/services/rag/index_factory.py +2 -2
  197. aethergraph/services/rag/node_rag.py +317 -0
  198. aethergraph/services/rate_limit/inmem_rate_limit.py +24 -0
  199. aethergraph/services/registry/__init__.py +0 -0
  200. aethergraph/services/registry/agent_app_meta.py +419 -0
  201. aethergraph/services/registry/registry_key.py +1 -1
  202. aethergraph/services/registry/unified_registry.py +74 -6
  203. aethergraph/services/scope/scope.py +159 -0
  204. aethergraph/services/scope/scope_factory.py +164 -0
  205. aethergraph/services/state_stores/serialize.py +5 -0
  206. aethergraph/services/state_stores/utils.py +2 -1
  207. aethergraph/services/viz/__init__.py +0 -0
  208. aethergraph/services/viz/facade.py +413 -0
  209. aethergraph/services/viz/viz_service.py +69 -0
  210. aethergraph/storage/artifacts/artifact_index_jsonl.py +180 -0
  211. aethergraph/storage/artifacts/artifact_index_sqlite.py +426 -0
  212. aethergraph/storage/artifacts/cas_store.py +422 -0
  213. aethergraph/storage/artifacts/fs_cas.py +18 -0
  214. aethergraph/storage/artifacts/s3_cas.py +14 -0
  215. aethergraph/storage/artifacts/utils.py +124 -0
  216. aethergraph/storage/blob/fs_blob.py +86 -0
  217. aethergraph/storage/blob/s3_blob.py +115 -0
  218. aethergraph/storage/continuation_store/fs_cont.py +283 -0
  219. aethergraph/storage/continuation_store/inmem_cont.py +146 -0
  220. aethergraph/storage/continuation_store/kvdoc_cont.py +261 -0
  221. aethergraph/storage/docstore/fs_doc.py +63 -0
  222. aethergraph/storage/docstore/sqlite_doc.py +31 -0
  223. aethergraph/storage/docstore/sqlite_doc_sync.py +90 -0
  224. aethergraph/storage/eventlog/fs_event.py +136 -0
  225. aethergraph/storage/eventlog/sqlite_event.py +47 -0
  226. aethergraph/storage/eventlog/sqlite_event_sync.py +178 -0
  227. aethergraph/storage/factory.py +432 -0
  228. aethergraph/storage/fs_utils.py +28 -0
  229. aethergraph/storage/graph_state_store/state_store.py +64 -0
  230. aethergraph/storage/kv/inmem_kv.py +103 -0
  231. aethergraph/storage/kv/layered_kv.py +52 -0
  232. aethergraph/storage/kv/sqlite_kv.py +39 -0
  233. aethergraph/storage/kv/sqlite_kv_sync.py +98 -0
  234. aethergraph/storage/memory/event_persist.py +68 -0
  235. aethergraph/storage/memory/fs_persist.py +118 -0
  236. aethergraph/{services/memory/hotlog_kv.py → storage/memory/hotlog.py} +8 -2
  237. aethergraph/{services → storage}/memory/indices.py +31 -7
  238. aethergraph/storage/metering/meter_event.py +55 -0
  239. aethergraph/storage/runs/doc_store.py +280 -0
  240. aethergraph/storage/runs/inmen_store.py +82 -0
  241. aethergraph/storage/runs/sqlite_run_store.py +403 -0
  242. aethergraph/storage/sessions/doc_store.py +183 -0
  243. aethergraph/storage/sessions/inmem_store.py +110 -0
  244. aethergraph/storage/sessions/sqlite_session_store.py +399 -0
  245. aethergraph/storage/vector_index/chroma_index.py +138 -0
  246. aethergraph/storage/vector_index/faiss_index.py +179 -0
  247. aethergraph/storage/vector_index/sqlite_index.py +187 -0
  248. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/METADATA +138 -31
  249. aethergraph-0.1.0a3.dist-info/RECORD +356 -0
  250. aethergraph-0.1.0a3.dist-info/entry_points.txt +3 -0
  251. aethergraph/services/artifacts/factory.py +0 -35
  252. aethergraph/services/artifacts/fs_store.py +0 -656
  253. aethergraph/services/artifacts/jsonl_index.py +0 -123
  254. aethergraph/services/artifacts/sqlite_index.py +0 -209
  255. aethergraph/services/memory/distillers/episode.py +0 -116
  256. aethergraph/services/memory/distillers/rolling.py +0 -74
  257. aethergraph/services/memory/facade.py +0 -633
  258. aethergraph/services/memory/persist_fs.py +0 -40
  259. aethergraph/services/rag/index/base.py +0 -27
  260. aethergraph/services/rag/index/faiss_index.py +0 -121
  261. aethergraph/services/rag/index/sqlite_index.py +0 -134
  262. aethergraph-0.1.0a1.dist-info/RECORD +0 -182
  263. aethergraph-0.1.0a1.dist-info/entry_points.txt +0 -2
  264. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/WHEEL +0 -0
  265. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
  266. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/licenses/NOTICE +0 -0
  267. {aethergraph-0.1.0a1.dist-info → aethergraph-0.1.0a3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,410 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ import json
5
+ from typing import TYPE_CHECKING, Any, Literal
6
+
7
+ from .utils import short_hash, slug
8
+
9
+ if TYPE_CHECKING:
10
+ from aethergraph.contracts.services.memory import Event
11
+
12
+ from .types import MemoryFacadeInterface
13
+
14
+
15
+ class RAGMixin:
16
+ """Methods for interacting with RAG services."""
17
+
18
+ # ----------- RAG: DX helpers (key-based) -----------
19
+ async def rag_remember_events(
20
+ self,
21
+ *,
22
+ key: str = "default",
23
+ where: dict | None = None,
24
+ policy: dict | None = None,
25
+ ) -> dict:
26
+ """
27
+ Bind a RAG corpus by logical key and promote events into it.
28
+
29
+ This method allows you to associate a logical key with a RAG corpus and
30
+ promote events into it based on filtering criteria.
31
+
32
+ Examples:
33
+ Promote events into a session corpus:
34
+ ```python
35
+ await context.memory().rag_remember_events(
36
+ key="session",
37
+ where={"kinds": ["tool_result"], "limit": 200},
38
+ policy={"min_signal": 0.25},
39
+ )
40
+ ```
41
+
42
+ Args:
43
+ key: Logical key for the RAG corpus. Defaults to `"default"`.
44
+ where: Filtering criteria for selecting events.
45
+ policy: Promotion policy, such as minimum signal threshold.
46
+
47
+ Returns:
48
+ dict: A dictionary containing the promotion results.
49
+ """
50
+ corpus_id = await self.rag_bind(key=key, create_if_missing=True)
51
+ return await self.rag_promote_events(
52
+ corpus_id=corpus_id,
53
+ events=None,
54
+ where=where,
55
+ policy=policy,
56
+ )
57
+
58
+ async def rag_remember_docs(
59
+ self,
60
+ docs: Sequence[dict[str, Any]],
61
+ *,
62
+ key: str = "default",
63
+ labels: dict | None = None,
64
+ ) -> dict[str, Any]:
65
+ """
66
+ Bind a RAG corpus by key and upsert documents into it.
67
+
68
+ This method allows you to associate a logical key with a RAG corpus and
69
+ upsert a sequence of documents into it.
70
+
71
+ Examples:
72
+ Upsert documents into a corpus:
73
+ ```python
74
+ await context.memory().rag_remember_docs(
75
+ docs=[{"text": "Document 1"}, {"text": "Document 2"}],
76
+ key="knowledge_base",
77
+ labels={"category": "reference"},
78
+ )
79
+ ```
80
+
81
+ Args:
82
+ docs: A sequence of documents to upsert.
83
+ key: Logical key for the RAG corpus. Defaults to `"default"`.
84
+ labels: Metadata labels to associate with the corpus.
85
+
86
+ Returns:
87
+ dict: A dictionary containing the upsert results.
88
+ """
89
+ corpus_id = await self.rag_bind(key=key, create_if_missing=True, labels=labels)
90
+ return await self.rag_upsert(corpus_id=corpus_id, docs=list(docs))
91
+
92
+ async def rag_search_by_key(
93
+ self,
94
+ *,
95
+ key: str = "default",
96
+ query: str,
97
+ k: int = 8,
98
+ filters: dict | None = None,
99
+ mode: Literal["hybrid", "dense"] = "hybrid",
100
+ ) -> list[dict]:
101
+ """
102
+ Resolve a corpus by logical key and perform a search query.
103
+
104
+ This method allows you to search within a RAG corpus identified by a
105
+ logical key.
106
+
107
+ Examples:
108
+ Perform a search query:
109
+ ```python
110
+ results = await context.memory().rag_search_by_key(
111
+ key="knowledge_base",
112
+ query="What is the capital of France?",
113
+ k=5,
114
+ )
115
+ ```
116
+
117
+ Args:
118
+ key: Logical key for the RAG corpus. Defaults to `"default"`.
119
+ query: The search query string.
120
+ k: Number of top results to return. Defaults to 8.
121
+ filters: Additional filters for the search.
122
+ mode: Search mode, either `"hybrid"` or `"dense"`. Defaults to `"hybrid"`.
123
+
124
+ Returns:
125
+ list[dict]: A list of search results.
126
+ """
127
+ corpus_id = await self.rag_bind(key=key, create_if_missing=False)
128
+ return await self.rag_search(
129
+ corpus_id=corpus_id,
130
+ query=query,
131
+ k=k,
132
+ filters=filters,
133
+ mode=mode,
134
+ )
135
+
136
+ async def rag_answer_by_key(
137
+ self,
138
+ *,
139
+ key: str = "default",
140
+ question: str,
141
+ style: Literal["concise", "detailed"] = "concise",
142
+ with_citations: bool = True,
143
+ k: int = 6,
144
+ ) -> dict:
145
+ """
146
+ Perform RAG QA over a corpus referenced by a logical key.
147
+
148
+ This method allows you to ask a question and retrieve an answer from a
149
+ RAG corpus identified by a logical key.
150
+
151
+ Examples:
152
+ Ask a question:
153
+ ```python
154
+ answer = await context.memory().rag_answer_by_key(
155
+ key="knowledge_base",
156
+ question="What is the capital of France?",
157
+ style="detailed",
158
+ )
159
+ ```
160
+
161
+ Args:
162
+ key: Logical key for the RAG corpus. Defaults to `"default"`.
163
+ question: The question to ask.
164
+ style: Answer style, either `"concise"` or `"detailed"`. Defaults to `"concise"`.
165
+ with_citations: Whether to include citations in the answer. Defaults to `True`.
166
+ k: Number of top results to consider. Defaults to 6.
167
+
168
+ Returns:
169
+ dict: A dictionary containing the answer and related metadata.
170
+ """
171
+ corpus_id = await self.rag_bind(key=key, create_if_missing=False)
172
+ return await self.rag_answer(
173
+ corpus_id=corpus_id,
174
+ question=question,
175
+ style=style,
176
+ with_citations=with_citations,
177
+ k=k,
178
+ )
179
+
180
+ async def rag_upsert(
181
+ self: MemoryFacadeInterface,
182
+ *,
183
+ corpus_id: str,
184
+ docs: Sequence[dict[str, Any]],
185
+ topic: str | None = None,
186
+ ) -> dict[str, Any]:
187
+ """
188
+ Upsert documents into a RAG corpus.
189
+
190
+ This method allows you to add or update documents in a RAG corpus.
191
+
192
+ Args:
193
+ corpus_id: The ID of the RAG corpus.
194
+ docs: A sequence of documents to upsert.
195
+ topic: Optional topic for the documents.
196
+
197
+ Returns:
198
+ dict: A dictionary containing the upsert results.
199
+ """
200
+ if not self.rag:
201
+ raise RuntimeError("RAG facade not configured")
202
+ return await self.rag.upsert_docs(corpus_id=corpus_id, docs=list(docs))
203
+
204
+ async def rag_bind(
205
+ self: MemoryFacadeInterface,
206
+ *,
207
+ corpus_id: str | None = None,
208
+ key: str | None = None,
209
+ create_if_missing: bool = True,
210
+ labels: dict | None = None,
211
+ ) -> str:
212
+ """
213
+ Bind a logical key to a RAG corpus.
214
+
215
+ This method resolves or creates a RAG corpus based on a logical key.
216
+
217
+ Args:
218
+ corpus_id: Optional explicit corpus ID.
219
+ key: Logical key for the RAG corpus.
220
+ create_if_missing: Whether to create the corpus if it doesn't exist.
221
+ labels: Metadata labels to associate with the corpus.
222
+
223
+ Returns:
224
+ str: The resolved or created corpus ID.
225
+ """
226
+ if not self.rag:
227
+ raise RuntimeError("RAG facade not configured")
228
+
229
+ mem_scope = self.memory_scope_id
230
+ if corpus_id:
231
+ cid = corpus_id
232
+ else:
233
+ logical_key = key or "default"
234
+ base = f"{mem_scope}:{logical_key}"
235
+ cid = f"mem:{slug(mem_scope)}:{slug(logical_key)}-{short_hash(base, 8)}"
236
+
237
+ scope_labels = {}
238
+ if self.scope:
239
+ scope_labels = self.scope.rag_labels(scope_id=mem_scope)
240
+
241
+ meta = {"scope": scope_labels, **(labels or {})}
242
+ if create_if_missing:
243
+ await self.rag.add_corpus(cid, meta=meta, scope_labels=scope_labels)
244
+ return cid
245
+
246
+ async def rag_promote_events(
247
+ self: MemoryFacadeInterface,
248
+ *,
249
+ corpus_id: str,
250
+ events: list[Event] | None = None,
251
+ where: dict | None = None,
252
+ policy: dict | None = None,
253
+ ) -> dict:
254
+ """
255
+ Promote events into a RAG corpus.
256
+
257
+ This method selects and promotes events into a RAG corpus based on
258
+ filtering criteria and policies.
259
+
260
+ Args:
261
+ corpus_id: The ID of the RAG corpus.
262
+ events: Optional list of events to promote.
263
+ where: Filtering criteria for selecting events.
264
+ policy: Promotion policy, such as minimum signal threshold.
265
+
266
+ Returns:
267
+ dict: A dictionary containing the promotion results.
268
+ """
269
+ if not self.rag:
270
+ raise RuntimeError("RAG facade not configured")
271
+ policy = policy or {}
272
+ min_signal = policy.get("min_signal", self.default_signal_threshold)
273
+
274
+ if events is None:
275
+ # We use RetrievalMixin's .recent here
276
+ kinds = (where or {}).get("kinds")
277
+ limit = int((where or {}).get("limit", 200))
278
+ recent = await self.recent(kinds=kinds, limit=limit) # type: ignore
279
+ events = [e for e in recent if (getattr(e, "signal", 0.0) or 0.0) >= float(min_signal)]
280
+
281
+ docs: list[dict] = []
282
+ for e in events:
283
+ title = f"{e.kind}:{(e.tool or e.stage or 'n/a')}:{e.ts}"
284
+ scope_labels = (
285
+ self.scope.rag_labels(scope_id=self.memory_scope_id) if self.scope else {}
286
+ )
287
+ labels = {
288
+ **scope_labels,
289
+ "kind": e.kind,
290
+ "tool": e.tool,
291
+ "stage": e.stage,
292
+ "severity": e.severity,
293
+ "tags": list(e.tags or []),
294
+ }
295
+ body = e.text
296
+ if not body:
297
+ body = json.dumps(
298
+ {"inputs": e.inputs, "outputs": e.outputs, "metrics": e.metrics},
299
+ ensure_ascii=False,
300
+ )
301
+ docs.append({"text": body, "title": title, "labels": labels})
302
+
303
+ if not docs:
304
+ return {"added": 0}
305
+
306
+ stats = await self.rag.upsert_docs(corpus_id=corpus_id, docs=docs)
307
+
308
+ # Log result
309
+ await self.write_result(
310
+ tool=f"rag.promote.{corpus_id}",
311
+ outputs=[{"name": "added_docs", "kind": "number", "value": stats.get("added", 0)}],
312
+ tags=["rag", "ingest"],
313
+ message=f"Promoted {stats.get('added', 0)} events",
314
+ severity=2,
315
+ )
316
+ return stats
317
+
318
+ async def rag_answer(
319
+ self: MemoryFacadeInterface,
320
+ *,
321
+ corpus_id: str,
322
+ question: str,
323
+ style: Literal["concise", "detailed"] = "concise",
324
+ with_citations: bool = True,
325
+ k: int = 6,
326
+ ) -> dict:
327
+ """
328
+ Answer a question using a RAG corpus.
329
+
330
+ This method performs question answering over a specified RAG corpus.
331
+
332
+ Args:
333
+ corpus_id: The ID of the RAG corpus.
334
+ question: The question to ask.
335
+ style: Answer style, either `"concise"` or `"detailed"`. Defaults to `"concise"`.
336
+ with_citations: Whether to include citations in the answer. Defaults to `True`.
337
+ k: Number of top results to consider. Defaults to 6.
338
+
339
+ Returns:
340
+ dict: A dictionary containing the answer and related metadata.
341
+ """
342
+ if not self.rag:
343
+ raise RuntimeError("RAG facade not configured")
344
+
345
+ ans = await self.rag.answer(
346
+ corpus_id=corpus_id,
347
+ question=question,
348
+ llm=self.llm,
349
+ style=style,
350
+ with_citations=with_citations,
351
+ k=k,
352
+ )
353
+
354
+ outs = [{"name": "answer", "kind": "text", "value": ans.get("answer", "")}]
355
+ for i, rc in enumerate(ans.get("resolved_citations", []), start=1):
356
+ outs.append({"name": f"cite_{i}", "kind": "json", "value": rc})
357
+
358
+ await self.write_result(
359
+ tool=f"rag.answer.{corpus_id}",
360
+ outputs=outs,
361
+ tags=["rag", "qa"],
362
+ message=f"Q: {question}",
363
+ metrics=ans.get("usage", {}),
364
+ severity=2,
365
+ )
366
+ return ans
367
+
368
+ async def rag_search(
369
+ self,
370
+ *,
371
+ corpus_id: str,
372
+ query: str,
373
+ k: int = 8,
374
+ filters: dict | None = None,
375
+ mode: Literal["hybrid", "dense"] = "hybrid",
376
+ ) -> list[dict]:
377
+ """
378
+ Perform a search query over a RAG corpus.
379
+
380
+ This method allows you to search within a specified RAG corpus.
381
+
382
+ Args:
383
+ corpus_id: The ID of the RAG corpus.
384
+ query: The search query string.
385
+ k: Number of top results to return. Defaults to 8.
386
+ filters: Additional filters for the search.
387
+ mode: Search mode, either `"hybrid"` or `"dense"`. Defaults to `"hybrid"`.
388
+
389
+ Returns:
390
+ list[dict]: A list of search results.
391
+ """
392
+ if not self.rag:
393
+ raise RuntimeError("RAG facade not configured in MemoryFacade")
394
+
395
+ scope = self.scope
396
+ s_filters = scope.rag_filter(scope_id=self.memory_scope_id) if scope else {}
397
+ if filters:
398
+ s_filters.update(filters)
399
+ hits = await self.rag.search(corpus_id, query, k=k, filters=s_filters, mode=mode)
400
+ return [
401
+ dict(
402
+ chunk_id=h.chunk_id,
403
+ doc_id=h.doc_id,
404
+ corpus_id=h.corpus_id,
405
+ score=h.score,
406
+ text=h.text,
407
+ meta=h.meta,
408
+ )
409
+ for h in hits
410
+ ]