coco-framework 0.1.0__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 (400) hide show
  1. coco/__init__.py +3 -0
  2. coco/agents/__init__.py +9 -0
  3. coco/agents/_subagent_transformer.py +307 -0
  4. coco/agents/factory.py +2164 -0
  5. coco/agents/middleware/__init__.py +100 -0
  6. coco/agents/middleware/_execution.py +385 -0
  7. coco/agents/middleware/_redaction.py +454 -0
  8. coco/agents/middleware/_retry.py +134 -0
  9. coco/agents/middleware/_trace_policy.py +61 -0
  10. coco/agents/middleware/context_editing.py +306 -0
  11. coco/agents/middleware/file_search.py +433 -0
  12. coco/agents/middleware/human_in_the_loop.py +680 -0
  13. coco/agents/middleware/internal_call_transformer.py +108 -0
  14. coco/agents/middleware/model_call_limit.py +267 -0
  15. coco/agents/middleware/model_fallback.py +417 -0
  16. coco/agents/middleware/model_retry.py +327 -0
  17. coco/agents/middleware/pii.py +878 -0
  18. coco/agents/middleware/provider_tool_search.py +307 -0
  19. coco/agents/middleware/shell_tool.py +949 -0
  20. coco/agents/middleware/summarization.py +910 -0
  21. coco/agents/middleware/todo.py +357 -0
  22. coco/agents/middleware/tool_call_limit.py +495 -0
  23. coco/agents/middleware/tool_emulator.py +251 -0
  24. coco/agents/middleware/tool_error.py +209 -0
  25. coco/agents/middleware/tool_retry.py +421 -0
  26. coco/agents/middleware/tool_selection.py +515 -0
  27. coco/agents/middleware/types.py +2201 -0
  28. coco/agents/structured_output.py +472 -0
  29. coco/chat_models/__init__.py +7 -0
  30. coco/chat_models/base.py +1082 -0
  31. coco/embeddings/__init__.py +18 -0
  32. coco/embeddings/base.py +279 -0
  33. coco/mcp/__init__.py +36 -0
  34. coco/mcp/adapter.py +245 -0
  35. coco/mcp/elicitation.py +394 -0
  36. coco/mcp/tools.py +307 -0
  37. coco/messages/__init__.py +73 -0
  38. coco/py.typed +0 -0
  39. coco/rate_limiters/__init__.py +13 -0
  40. coco/tools/__init__.py +22 -0
  41. coco/tools/tool_node.py +20 -0
  42. coco_anthropic/__init__.py +15 -0
  43. coco_anthropic/_client_utils.py +81 -0
  44. coco_anthropic/_compat.py +228 -0
  45. coco_anthropic/_sdk_compat.py +124 -0
  46. coco_anthropic/_version.py +3 -0
  47. coco_anthropic/chat_models.py +3349 -0
  48. coco_anthropic/data/__init__.py +1 -0
  49. coco_anthropic/data/_profiles.py +491 -0
  50. coco_anthropic/data/profile_augmentations.toml +69 -0
  51. coco_anthropic/experimental.py +140 -0
  52. coco_anthropic/llms.py +428 -0
  53. coco_anthropic/middleware/__init__.py +25 -0
  54. coco_anthropic/middleware/anthropic_tools.py +1197 -0
  55. coco_anthropic/middleware/bash.py +84 -0
  56. coco_anthropic/middleware/file_search.py +360 -0
  57. coco_anthropic/middleware/prompt_caching.py +276 -0
  58. coco_anthropic/output_parsers.py +93 -0
  59. coco_anthropic/py.typed +0 -0
  60. coco_core/__init__.py +20 -0
  61. coco_core/_api/__init__.py +87 -0
  62. coco_core/_api/beta_decorator.py +266 -0
  63. coco_core/_api/deprecation.py +637 -0
  64. coco_core/_api/internal.py +23 -0
  65. coco_core/_api/path.py +50 -0
  66. coco_core/_import_utils.py +41 -0
  67. coco_core/_protocol.py +625 -0
  68. coco_core/_security/__init__.py +36 -0
  69. coco_core/_security/_exceptions.py +9 -0
  70. coco_core/_security/_policy.py +313 -0
  71. coco_core/_security/_ssrf_protection.py +155 -0
  72. coco_core/_security/_transport.py +254 -0
  73. coco_core/agents.py +258 -0
  74. coco_core/caches.py +272 -0
  75. coco_core/callbacks/__init__.py +132 -0
  76. coco_core/callbacks/base.py +1229 -0
  77. coco_core/callbacks/file.py +266 -0
  78. coco_core/callbacks/manager.py +2826 -0
  79. coco_core/callbacks/stdout.py +123 -0
  80. coco_core/callbacks/streaming_stdout.py +154 -0
  81. coco_core/callbacks/usage.py +124 -0
  82. coco_core/chat_history.py +267 -0
  83. coco_core/chat_loaders.py +26 -0
  84. coco_core/chat_sessions.py +19 -0
  85. coco_core/cross_encoders.py +18 -0
  86. coco_core/document_loaders/__init__.py +36 -0
  87. coco_core/document_loaders/base.py +155 -0
  88. coco_core/document_loaders/blob_loaders.py +38 -0
  89. coco_core/documents/__init__.py +55 -0
  90. coco_core/documents/base.py +347 -0
  91. coco_core/documents/compressor.py +74 -0
  92. coco_core/documents/transformers.py +79 -0
  93. coco_core/embeddings/__init__.py +31 -0
  94. coco_core/embeddings/embeddings.py +78 -0
  95. coco_core/embeddings/fake.py +129 -0
  96. coco_core/env.py +22 -0
  97. coco_core/example_selectors/__init__.py +47 -0
  98. coco_core/example_selectors/base.py +60 -0
  99. coco_core/example_selectors/length_based.py +131 -0
  100. coco_core/example_selectors/semantic_similarity.py +366 -0
  101. coco_core/exceptions.py +166 -0
  102. coco_core/globals.py +72 -0
  103. coco_core/indexing/__init__.py +53 -0
  104. coco_core/indexing/api.py +954 -0
  105. coco_core/indexing/base.py +661 -0
  106. coco_core/indexing/in_memory.py +104 -0
  107. coco_core/language_models/__init__.py +116 -0
  108. coco_core/language_models/_compat_bridge.py +844 -0
  109. coco_core/language_models/_utils.py +343 -0
  110. coco_core/language_models/base.py +500 -0
  111. coco_core/language_models/chat_model_stream.py +1525 -0
  112. coco_core/language_models/chat_models.py +2748 -0
  113. coco_core/language_models/fake.py +137 -0
  114. coco_core/language_models/fake_chat_models.py +396 -0
  115. coco_core/language_models/llms.py +1569 -0
  116. coco_core/language_models/model_profile.py +183 -0
  117. coco_core/load/__init__.py +44 -0
  118. coco_core/load/_validation.py +191 -0
  119. coco_core/load/dump.py +120 -0
  120. coco_core/load/load.py +828 -0
  121. coco_core/load/mapping.py +1097 -0
  122. coco_core/load/serializable.py +406 -0
  123. coco_core/messages/__init__.py +198 -0
  124. coco_core/messages/ai.py +854 -0
  125. coco_core/messages/base.py +518 -0
  126. coco_core/messages/block_translators/__init__.py +112 -0
  127. coco_core/messages/block_translators/anthropic.py +534 -0
  128. coco_core/messages/block_translators/bedrock.py +108 -0
  129. coco_core/messages/block_translators/bedrock_converse.py +329 -0
  130. coco_core/messages/block_translators/coco_v0.py +303 -0
  131. coco_core/messages/block_translators/google_genai.py +610 -0
  132. coco_core/messages/block_translators/google_vertexai.py +21 -0
  133. coco_core/messages/block_translators/groq.py +157 -0
  134. coco_core/messages/block_translators/openai.py +1089 -0
  135. coco_core/messages/chat.py +64 -0
  136. coco_core/messages/content.py +1488 -0
  137. coco_core/messages/function.py +62 -0
  138. coco_core/messages/human.py +70 -0
  139. coco_core/messages/modifier.py +33 -0
  140. coco_core/messages/system.py +70 -0
  141. coco_core/messages/tool.py +418 -0
  142. coco_core/messages/utils.py +2442 -0
  143. coco_core/output_parsers/__init__.py +101 -0
  144. coco_core/output_parsers/base.py +361 -0
  145. coco_core/output_parsers/format_instructions.py +16 -0
  146. coco_core/output_parsers/json.py +139 -0
  147. coco_core/output_parsers/list.py +249 -0
  148. coco_core/output_parsers/openai_functions.py +324 -0
  149. coco_core/output_parsers/openai_tools.py +383 -0
  150. coco_core/output_parsers/pydantic.py +141 -0
  151. coco_core/output_parsers/string.py +63 -0
  152. coco_core/output_parsers/transform.py +175 -0
  153. coco_core/output_parsers/xml.py +301 -0
  154. coco_core/outputs/__init__.py +66 -0
  155. coco_core/outputs/chat_generation.py +147 -0
  156. coco_core/outputs/chat_result.py +39 -0
  157. coco_core/outputs/generation.py +80 -0
  158. coco_core/outputs/llm_result.py +112 -0
  159. coco_core/outputs/run_info.py +22 -0
  160. coco_core/prompt_values.py +161 -0
  161. coco_core/prompts/__init__.py +101 -0
  162. coco_core/prompts/base.py +508 -0
  163. coco_core/prompts/chat.py +1495 -0
  164. coco_core/prompts/dict.py +177 -0
  165. coco_core/prompts/few_shot.py +483 -0
  166. coco_core/prompts/few_shot_with_templates.py +239 -0
  167. coco_core/prompts/image.py +177 -0
  168. coco_core/prompts/loading.py +296 -0
  169. coco_core/prompts/message.py +97 -0
  170. coco_core/prompts/prompt.py +312 -0
  171. coco_core/prompts/string.py +399 -0
  172. coco_core/prompts/structured.py +214 -0
  173. coco_core/py.typed +0 -0
  174. coco_core/rate_limiters.py +254 -0
  175. coco_core/retrievers.py +328 -0
  176. coco_core/runnables/__init__.py +136 -0
  177. coco_core/runnables/base.py +6725 -0
  178. coco_core/runnables/branch.py +457 -0
  179. coco_core/runnables/config.py +712 -0
  180. coco_core/runnables/configurable.py +710 -0
  181. coco_core/runnables/fallbacks.py +663 -0
  182. coco_core/runnables/graph.py +741 -0
  183. coco_core/runnables/graph_ascii.py +366 -0
  184. coco_core/runnables/graph_mermaid.py +503 -0
  185. coco_core/runnables/graph_png.py +215 -0
  186. coco_core/runnables/history.py +631 -0
  187. coco_core/runnables/passthrough.py +849 -0
  188. coco_core/runnables/retry.py +379 -0
  189. coco_core/runnables/router.py +239 -0
  190. coco_core/runnables/schema.py +188 -0
  191. coco_core/runnables/utils.py +798 -0
  192. coco_core/stores.py +291 -0
  193. coco_core/structured_query.py +203 -0
  194. coco_core/sys_info.py +154 -0
  195. coco_core/tools/__init__.py +95 -0
  196. coco_core/tools/base.py +1948 -0
  197. coco_core/tools/convert.py +497 -0
  198. coco_core/tools/render.py +67 -0
  199. coco_core/tools/retriever.py +94 -0
  200. coco_core/tools/simple.py +208 -0
  201. coco_core/tools/structured.py +322 -0
  202. coco_core/tracers/__init__.py +47 -0
  203. coco_core/tracers/_compat.py +95 -0
  204. coco_core/tracers/_context.py +337 -0
  205. coco_core/tracers/_streaming.py +53 -0
  206. coco_core/tracers/base.py +955 -0
  207. coco_core/tracers/coco.py +279 -0
  208. coco_core/tracers/context.py +205 -0
  209. coco_core/tracers/core.py +766 -0
  210. coco_core/tracers/event_stream.py +1101 -0
  211. coco_core/tracers/log_stream.py +765 -0
  212. coco_core/tracers/memory_stream.py +200 -0
  213. coco_core/tracers/root_listeners.py +130 -0
  214. coco_core/tracers/run_collector.py +40 -0
  215. coco_core/tracers/schemas.py +14 -0
  216. coco_core/tracers/stdout.py +205 -0
  217. coco_core/utils/__init__.py +111 -0
  218. coco_core/utils/_gateway.py +274 -0
  219. coco_core/utils/_merge.py +218 -0
  220. coco_core/utils/aiter.py +357 -0
  221. coco_core/utils/env.py +86 -0
  222. coco_core/utils/formatting.py +80 -0
  223. coco_core/utils/function_calling.py +869 -0
  224. coco_core/utils/html.py +132 -0
  225. coco_core/utils/image.py +15 -0
  226. coco_core/utils/input.py +82 -0
  227. coco_core/utils/interactive_env.py +12 -0
  228. coco_core/utils/iter.py +229 -0
  229. coco_core/utils/json.py +230 -0
  230. coco_core/utils/json_schema.py +273 -0
  231. coco_core/utils/mustache.py +706 -0
  232. coco_core/utils/pydantic.py +631 -0
  233. coco_core/utils/strings.py +69 -0
  234. coco_core/utils/usage.py +61 -0
  235. coco_core/utils/utils.py +528 -0
  236. coco_core/utils/uuid.py +57 -0
  237. coco_core/vectorstores/__init__.py +53 -0
  238. coco_core/vectorstores/base.py +1111 -0
  239. coco_core/vectorstores/in_memory.py +546 -0
  240. coco_core/vectorstores/utils.py +163 -0
  241. coco_core/version.py +3 -0
  242. coco_framework-0.1.0.dist-info/METADATA +184 -0
  243. coco_framework-0.1.0.dist-info/RECORD +400 -0
  244. coco_framework-0.1.0.dist-info/WHEEL +4 -0
  245. coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
  246. coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
  247. coco_graph/_internal/__init__.py +4 -0
  248. coco_graph/_internal/_cache.py +31 -0
  249. coco_graph/_internal/_config.py +474 -0
  250. coco_graph/_internal/_constants.py +140 -0
  251. coco_graph/_internal/_fields.py +213 -0
  252. coco_graph/_internal/_future.py +220 -0
  253. coco_graph/_internal/_pydantic.py +275 -0
  254. coco_graph/_internal/_queue.py +124 -0
  255. coco_graph/_internal/_replay.py +90 -0
  256. coco_graph/_internal/_retry.py +29 -0
  257. coco_graph/_internal/_runnable.py +969 -0
  258. coco_graph/_internal/_scratchpad.py +19 -0
  259. coco_graph/_internal/_serde.py +253 -0
  260. coco_graph/_internal/_timeout.py +25 -0
  261. coco_graph/_internal/_typing.py +54 -0
  262. coco_graph/cache/base/__init__.py +48 -0
  263. coco_graph/cache/base/py.typed +0 -0
  264. coco_graph/cache/memory/__init__.py +73 -0
  265. coco_graph/cache/redis/__init__.py +144 -0
  266. coco_graph/cache/sqlite/__init__.py +120 -0
  267. coco_graph/callbacks.py +394 -0
  268. coco_graph/channels/__init__.py +29 -0
  269. coco_graph/channels/any_value.py +72 -0
  270. coco_graph/channels/base.py +121 -0
  271. coco_graph/channels/binop.py +155 -0
  272. coco_graph/channels/delta.py +202 -0
  273. coco_graph/channels/ephemeral_value.py +79 -0
  274. coco_graph/channels/last_value.py +151 -0
  275. coco_graph/channels/named_barrier_value.py +167 -0
  276. coco_graph/channels/topic.py +94 -0
  277. coco_graph/channels/untracked_value.py +73 -0
  278. coco_graph/checkpoint/base/__init__.py +861 -0
  279. coco_graph/checkpoint/base/id.py +109 -0
  280. coco_graph/checkpoint/base/py.typed +0 -0
  281. coco_graph/checkpoint/memory/__init__.py +698 -0
  282. coco_graph/checkpoint/memory/py.typed +0 -0
  283. coco_graph/checkpoint/postgres/__init__.py +602 -0
  284. coco_graph/checkpoint/postgres/_ainternal.py +23 -0
  285. coco_graph/checkpoint/postgres/_internal.py +21 -0
  286. coco_graph/checkpoint/postgres/aio.py +691 -0
  287. coco_graph/checkpoint/postgres/base.py +666 -0
  288. coco_graph/checkpoint/postgres/py.typed +0 -0
  289. coco_graph/checkpoint/postgres/shallow.py +967 -0
  290. coco_graph/checkpoint/serde/__init__.py +0 -0
  291. coco_graph/checkpoint/serde/_msgpack.py +97 -0
  292. coco_graph/checkpoint/serde/base.py +64 -0
  293. coco_graph/checkpoint/serde/encrypted.py +80 -0
  294. coco_graph/checkpoint/serde/event_hooks.py +52 -0
  295. coco_graph/checkpoint/serde/jsonplus.py +883 -0
  296. coco_graph/checkpoint/serde/py.typed +0 -0
  297. coco_graph/checkpoint/serde/types.py +68 -0
  298. coco_graph/checkpoint/sqlite/__init__.py +645 -0
  299. coco_graph/checkpoint/sqlite/_delta.py +172 -0
  300. coco_graph/checkpoint/sqlite/aio.py +738 -0
  301. coco_graph/checkpoint/sqlite/py.typed +0 -0
  302. coco_graph/checkpoint/sqlite/utils.py +116 -0
  303. coco_graph/config.py +196 -0
  304. coco_graph/constants.py +64 -0
  305. coco_graph/errors.py +241 -0
  306. coco_graph/func/__init__.py +620 -0
  307. coco_graph/graph/__init__.py +12 -0
  308. coco_graph/graph/_branch.py +225 -0
  309. coco_graph/graph/_node.py +103 -0
  310. coco_graph/graph/message.py +437 -0
  311. coco_graph/graph/state.py +1978 -0
  312. coco_graph/graph/ui.py +227 -0
  313. coco_graph/managed/__init__.py +3 -0
  314. coco_graph/managed/base.py +31 -0
  315. coco_graph/managed/is_last_step.py +24 -0
  316. coco_graph/prebuilt/__init__.py +23 -0
  317. coco_graph/prebuilt/_tool_call_stream.py +117 -0
  318. coco_graph/prebuilt/_tool_call_transformer.py +165 -0
  319. coco_graph/prebuilt/chat_agent_executor.py +1015 -0
  320. coco_graph/prebuilt/interrupt.py +105 -0
  321. coco_graph/prebuilt/py.typed +0 -0
  322. coco_graph/prebuilt/tool_node.py +2030 -0
  323. coco_graph/prebuilt/tool_validator.py +221 -0
  324. coco_graph/pregel/__init__.py +3 -0
  325. coco_graph/pregel/_algo.py +1460 -0
  326. coco_graph/pregel/_call.py +298 -0
  327. coco_graph/pregel/_checkpoint.py +331 -0
  328. coco_graph/pregel/_config.py +0 -0
  329. coco_graph/pregel/_draw.py +294 -0
  330. coco_graph/pregel/_executor.py +223 -0
  331. coco_graph/pregel/_io.py +174 -0
  332. coco_graph/pregel/_log.py +3 -0
  333. coco_graph/pregel/_loop.py +1988 -0
  334. coco_graph/pregel/_messages.py +461 -0
  335. coco_graph/pregel/_read.py +312 -0
  336. coco_graph/pregel/_retry.py +854 -0
  337. coco_graph/pregel/_runner.py +941 -0
  338. coco_graph/pregel/_tools.py +268 -0
  339. coco_graph/pregel/_utils.py +221 -0
  340. coco_graph/pregel/_validate.py +120 -0
  341. coco_graph/pregel/_write.py +192 -0
  342. coco_graph/pregel/debug.py +302 -0
  343. coco_graph/pregel/main.py +4364 -0
  344. coco_graph/pregel/protocol.py +288 -0
  345. coco_graph/pregel/types.py +38 -0
  346. coco_graph/py.typed +0 -0
  347. coco_graph/runtime.py +350 -0
  348. coco_graph/store/base/__init__.py +1322 -0
  349. coco_graph/store/base/batch.py +371 -0
  350. coco_graph/store/base/embed.py +436 -0
  351. coco_graph/store/base/py.typed +0 -0
  352. coco_graph/store/memory/__init__.py +592 -0
  353. coco_graph/store/memory/py.typed +0 -0
  354. coco_graph/store/postgres/__init__.py +4 -0
  355. coco_graph/store/postgres/aio.py +592 -0
  356. coco_graph/store/postgres/base.py +1480 -0
  357. coco_graph/store/postgres/py.typed +0 -0
  358. coco_graph/store/sqlite/__init__.py +4 -0
  359. coco_graph/store/sqlite/aio.py +632 -0
  360. coco_graph/store/sqlite/base.py +1514 -0
  361. coco_graph/stream/__init__.py +45 -0
  362. coco_graph/stream/_convert.py +32 -0
  363. coco_graph/stream/_mux.py +523 -0
  364. coco_graph/stream/_types.py +330 -0
  365. coco_graph/stream/run_stream.py +735 -0
  366. coco_graph/stream/stream_channel.py +341 -0
  367. coco_graph/stream/transformers.py +1039 -0
  368. coco_graph/types.py +1079 -0
  369. coco_graph/typing.py +48 -0
  370. coco_graph/utils/__init__.py +1 -0
  371. coco_graph/utils/config.py +4 -0
  372. coco_graph/utils/runnable.py +3 -0
  373. coco_graph/version.py +13 -0
  374. coco_graph/warnings.py +69 -0
  375. coco_openai/__init__.py +20 -0
  376. coco_openai/_compat.py +45 -0
  377. coco_openai/_version.py +3 -0
  378. coco_openai/chat_models/__init__.py +6 -0
  379. coco_openai/chat_models/_client_utils.py +684 -0
  380. coco_openai/chat_models/_compat.py +525 -0
  381. coco_openai/chat_models/azure.py +1227 -0
  382. coco_openai/chat_models/base.py +5654 -0
  383. coco_openai/chat_models/codex.py +557 -0
  384. coco_openai/chatgpt_oauth.py +1063 -0
  385. coco_openai/data/__init__.py +1 -0
  386. coco_openai/data/_profiles.py +1655 -0
  387. coco_openai/data/profile_augmentations.toml +127 -0
  388. coco_openai/embeddings/__init__.py +6 -0
  389. coco_openai/embeddings/azure.py +240 -0
  390. coco_openai/embeddings/base.py +820 -0
  391. coco_openai/llms/__init__.py +6 -0
  392. coco_openai/llms/azure.py +249 -0
  393. coco_openai/llms/base.py +919 -0
  394. coco_openai/middleware/__init__.py +11 -0
  395. coco_openai/middleware/openai_moderation.py +484 -0
  396. coco_openai/output_parsers/__init__.py +9 -0
  397. coco_openai/output_parsers/tools.py +9 -0
  398. coco_openai/py.typed +0 -0
  399. coco_openai/tools/__init__.py +5 -0
  400. coco_openai/tools/custom_tool.py +111 -0
@@ -0,0 +1,1322 @@
1
+ """Base classes and types for persistent key-value stores.
2
+
3
+ Stores provide long-term memory that persists across threads and conversations.
4
+ Supports hierarchical namespaces, key-value storage, and optional vector search.
5
+
6
+ Core types:
7
+ - `BaseStore`: Store interface with sync/async operations
8
+ - `Item`: Stored key-value pairs with metadata
9
+ - `Op`: Get/Put/Search/List operations
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from abc import ABC, abstractmethod
15
+ from collections.abc import Iterable, Mapping
16
+ from datetime import datetime
17
+ from typing import (
18
+ Any,
19
+ Literal,
20
+ NamedTuple,
21
+ TypedDict,
22
+ cast,
23
+ )
24
+
25
+ from coco_core.embeddings import Embeddings
26
+ from typing_extensions import override
27
+
28
+ from coco_graph.store.base.embed import (
29
+ AEmbeddingsFunc,
30
+ EmbeddingsFunc,
31
+ ensure_embeddings,
32
+ get_text_at_path,
33
+ tokenize_path,
34
+ )
35
+
36
+
37
+ class NotProvided:
38
+ """Sentinel singleton."""
39
+
40
+ def __bool__(self) -> Literal[False]:
41
+ return False
42
+
43
+ @override
44
+ def __repr__(self) -> str:
45
+ return "NOT_GIVEN"
46
+
47
+
48
+ NOT_PROVIDED = NotProvided()
49
+
50
+
51
+ class Item:
52
+ """Represents a stored item with metadata.
53
+
54
+ Args:
55
+ value: The stored data as a dictionary. Keys are filterable.
56
+ key: Unique identifier within the namespace.
57
+ namespace: Hierarchical path defining the collection in which this document resides.
58
+ Represented as a tuple of strings, allowing for nested categorization.
59
+ For example: `("documents", 'user123')`
60
+ created_at: Timestamp of item creation.
61
+ updated_at: Timestamp of last update.
62
+ """
63
+
64
+ __slots__ = ("value", "key", "namespace", "created_at", "updated_at")
65
+
66
+ def __init__(
67
+ self,
68
+ *,
69
+ value: dict[str, Any],
70
+ key: str,
71
+ namespace: tuple[str, ...],
72
+ created_at: datetime,
73
+ updated_at: datetime,
74
+ ):
75
+ self.value = value
76
+ self.key = key
77
+ # The casting from json-like types is for if this object is
78
+ # deserialized.
79
+ self.namespace = tuple(namespace)
80
+ self.created_at = (
81
+ datetime.fromisoformat(cast(str, created_at))
82
+ if isinstance(created_at, str)
83
+ else created_at
84
+ )
85
+ self.updated_at = (
86
+ datetime.fromisoformat(cast(str, updated_at))
87
+ if isinstance(updated_at, str)
88
+ else updated_at
89
+ )
90
+
91
+ def __eq__(self, other: object) -> bool:
92
+ if not isinstance(other, Item):
93
+ return False
94
+ return (
95
+ self.value == other.value
96
+ and self.key == other.key
97
+ and self.namespace == other.namespace
98
+ and self.created_at == other.created_at
99
+ and self.updated_at == other.updated_at
100
+ )
101
+
102
+ def __hash__(self) -> int:
103
+ return hash((self.namespace, self.key))
104
+
105
+ def dict(self) -> dict:
106
+ return {
107
+ "namespace": list(self.namespace),
108
+ "key": self.key,
109
+ "value": self.value,
110
+ "created_at": self.created_at.isoformat(),
111
+ "updated_at": self.updated_at.isoformat(),
112
+ }
113
+
114
+ def __repr__(self) -> str:
115
+ return f"Item({', '.join(f'{k}={v!r}' for k, v in self.dict().items())})"
116
+
117
+
118
+ class SearchItem(Item):
119
+ """Represents an item returned from a search operation with additional metadata."""
120
+
121
+ __slots__ = ("score",)
122
+
123
+ def __init__(
124
+ self,
125
+ namespace: tuple[str, ...],
126
+ key: str,
127
+ value: dict[str, Any],
128
+ created_at: datetime,
129
+ updated_at: datetime,
130
+ score: float | None = None,
131
+ ) -> None:
132
+ """Initialize a result item.
133
+
134
+ Args:
135
+ namespace: Hierarchical path to the item.
136
+ key: Unique identifier within the namespace.
137
+ value: The stored value.
138
+ created_at: When the item was first created.
139
+ updated_at: When the item was last updated.
140
+ score: Relevance/similarity score if from a ranked operation.
141
+ """
142
+ super().__init__(
143
+ value=value,
144
+ key=key,
145
+ namespace=namespace,
146
+ created_at=created_at,
147
+ updated_at=updated_at,
148
+ )
149
+ self.score = score
150
+
151
+ def dict(self) -> dict:
152
+ result = super().dict()
153
+ result["score"] = self.score
154
+ return result
155
+
156
+
157
+ class GetOp(NamedTuple):
158
+ """Operation to retrieve a specific item by its namespace and key.
159
+
160
+ This operation allows precise retrieval of stored items using their full path
161
+ (namespace) and unique identifier (key) combination.
162
+
163
+ ???+ example "Examples"
164
+
165
+ Basic item retrieval:
166
+
167
+ ```python
168
+ GetOp(namespace=("users", "profiles"), key="user123")
169
+ GetOp(namespace=("cache", "embeddings"), key="doc456")
170
+ ```
171
+ """
172
+
173
+ namespace: tuple[str, ...]
174
+ """Hierarchical path that uniquely identifies the item's location.
175
+
176
+ ???+ example "Examples"
177
+
178
+ ```python
179
+ ("users",) # Root level users namespace
180
+ ("users", "profiles") # Profiles within users namespace
181
+ ```
182
+ """
183
+
184
+ key: str
185
+ """Unique identifier for the item within its specific namespace.
186
+
187
+ ???+ example "Examples"
188
+
189
+ ```python
190
+ "user123" # For a user profile
191
+ "doc456" # For a document
192
+ ```
193
+ """
194
+ refresh_ttl: bool = True
195
+ """Whether to refresh TTLs for the returned item.
196
+
197
+ If no TTL was specified for the original item(s),
198
+ or if TTL support is not enabled for your adapter,
199
+ this argument is ignored.
200
+ """
201
+
202
+
203
+ class SearchOp(NamedTuple):
204
+ """Operation to search for items within a specified namespace hierarchy.
205
+
206
+ This operation supports both structured filtering and natural language search
207
+ within a given namespace prefix. It provides pagination through limit and offset
208
+ parameters.
209
+
210
+ !!! note
211
+
212
+ Natural language search support depends on your store implementation.
213
+
214
+ ???+ example "Examples"
215
+
216
+ Search with filters and pagination:
217
+
218
+ ```python
219
+ SearchOp(
220
+ namespace_prefix=("documents",),
221
+ filter={"type": "report", "status": "active"},
222
+ limit=5,
223
+ offset=10
224
+ )
225
+ ```
226
+
227
+ Natural language search:
228
+
229
+ ```python
230
+ SearchOp(
231
+ namespace_prefix=("users", "content"),
232
+ query="technical documentation about APIs",
233
+ limit=20
234
+ )
235
+ ```
236
+ """
237
+
238
+ namespace_prefix: tuple[str, ...]
239
+ """Hierarchical path prefix defining the search scope.
240
+
241
+ ???+ example "Examples"
242
+
243
+ ```python
244
+ () # Search entire store
245
+ ("documents",) # Search all documents
246
+ ("users", "content") # Search within user content
247
+ ```
248
+ """
249
+
250
+ filter: dict[str, Any] | None = None
251
+ """Key-value pairs for filtering results based on exact matches or comparison operators.
252
+
253
+ The filter supports both exact matches and operator-based comparisons.
254
+
255
+ Supported Operators:
256
+ - `$eq`: Equal to (same as direct value comparison)
257
+ - `$ne`: Not equal to
258
+ - `$gt`: Greater than
259
+ - `$gte`: Greater than or equal to
260
+ - `$lt`: Less than
261
+ - `$lte`: Less than or equal to
262
+
263
+ ???+ example "Examples"
264
+
265
+ Simple exact match:
266
+
267
+ ```python
268
+ {"status": "active"}
269
+ ```
270
+
271
+ Comparison operators:
272
+
273
+ ```python
274
+ {"score": {"$gt": 4.99}} # Score greater than 4.99
275
+ ```
276
+
277
+ Multiple conditions:
278
+
279
+ ```python
280
+ {
281
+ "score": {"$gte": 3.0},
282
+ "color": "red"
283
+ }
284
+ ```
285
+ """
286
+
287
+ limit: int = 10
288
+ """Maximum number of items to return in the search results."""
289
+
290
+ offset: int = 0
291
+ """Number of matching items to skip for pagination."""
292
+
293
+ query: str | None = None
294
+ """Natural language search query for semantic search capabilities.
295
+
296
+ ???+ example "Examples"
297
+
298
+ - "technical documentation about REST APIs"
299
+ - "machine learning papers from 2023"
300
+ """
301
+ refresh_ttl: bool = True
302
+ """Whether to refresh TTLs for the returned item.
303
+
304
+ If no TTL was specified for the original item(s),
305
+ or if TTL support is not enabled for your adapter,
306
+ this argument is ignored.
307
+ """
308
+
309
+
310
+ # Type representing a namespace path that can include wildcards
311
+ NamespacePath = tuple[str | Literal["*"], ...]
312
+ """A tuple representing a namespace path that can include wildcards.
313
+
314
+ ???+ example "Examples"
315
+
316
+ ```python
317
+ ("users",) # Exact users namespace
318
+ ("documents", "*") # Any sub-namespace under documents
319
+ ("cache", "*", "v1") # Any cache category with v1 version
320
+ ```
321
+ """
322
+
323
+ # Type for specifying how to match namespaces
324
+ NamespaceMatchType = Literal["prefix", "suffix"]
325
+ """Specifies how to match namespace paths.
326
+
327
+ Values:
328
+ "prefix": Match from the start of the namespace
329
+ "suffix": Match from the end of the namespace
330
+ """
331
+
332
+
333
+ class MatchCondition(NamedTuple):
334
+ """Represents a pattern for matching namespaces in the store.
335
+
336
+ This class combines a match type (prefix or suffix) with a namespace path
337
+ pattern that can include wildcards to flexibly match different namespace
338
+ hierarchies.
339
+
340
+ ???+ example "Examples"
341
+
342
+ Prefix matching:
343
+
344
+ ```python
345
+ MatchCondition(match_type="prefix", path=("users", "profiles"))
346
+ ```
347
+
348
+ Suffix matching with wildcard:
349
+
350
+ ```python
351
+ MatchCondition(match_type="suffix", path=("cache", "*"))
352
+ ```
353
+
354
+ Simple suffix matching:
355
+
356
+ ```python
357
+ MatchCondition(match_type="suffix", path=("v1",))
358
+ ```
359
+ """
360
+
361
+ match_type: NamespaceMatchType
362
+ """Type of namespace matching to perform."""
363
+
364
+ path: NamespacePath
365
+ """Namespace path pattern that can include wildcards."""
366
+
367
+
368
+ class ListNamespacesOp(NamedTuple):
369
+ """Operation to list and filter namespaces in the store.
370
+
371
+ This operation allows exploring the organization of data, finding specific
372
+ collections, and navigating the namespace hierarchy.
373
+
374
+ ???+ example "Examples"
375
+
376
+ List all namespaces under the `"documents"` path:
377
+
378
+ ```python
379
+ ListNamespacesOp(
380
+ match_conditions=(MatchCondition(match_type="prefix", path=("documents",)),),
381
+ max_depth=2
382
+ )
383
+ ```
384
+
385
+ List all namespaces that end with `"v1"`:
386
+
387
+ ```python
388
+ ListNamespacesOp(
389
+ match_conditions=(MatchCondition(match_type="suffix", path=("v1",)),),
390
+ limit=50
391
+ )
392
+ ```
393
+
394
+ """
395
+
396
+ match_conditions: tuple[MatchCondition, ...] | None = None
397
+ """Optional conditions for filtering namespaces.
398
+
399
+ ???+ example "Examples"
400
+
401
+ All user namespaces:
402
+
403
+ ```python
404
+ (MatchCondition(match_type="prefix", path=("users",)),)
405
+ ```
406
+
407
+ All namespaces that start with `"docs"` and end with `"draft"`:
408
+
409
+ ```python
410
+ (
411
+ MatchCondition(match_type="prefix", path=("docs",)),
412
+ MatchCondition(match_type="suffix", path=("draft",))
413
+ )
414
+ ```
415
+ """
416
+
417
+ max_depth: int | None = None
418
+ """Maximum depth of namespace hierarchy to return.
419
+
420
+ Note:
421
+ Namespaces deeper than this level will be truncated.
422
+ """
423
+
424
+ limit: int = 100
425
+ """Maximum number of namespaces to return."""
426
+
427
+ offset: int = 0
428
+ """Number of namespaces to skip for pagination."""
429
+
430
+
431
+ class PutOp(NamedTuple):
432
+ """Operation to store, update, or delete an item in the store.
433
+
434
+ This class represents a single operation to modify the store's contents,
435
+ whether adding new items, updating existing ones, or removing them.
436
+ """
437
+
438
+ namespace: tuple[str, ...]
439
+ """Hierarchical path that identifies the location of the item.
440
+
441
+ The namespace acts as a folder-like structure to organize items.
442
+ Each element in the tuple represents one level in the hierarchy.
443
+
444
+ ???+ example "Examples"
445
+
446
+ Root level documents:
447
+
448
+ ```python
449
+ ("documents",)
450
+ ```
451
+
452
+ User-specific documents:
453
+
454
+ ```python
455
+ ("documents", "user123")
456
+ ```
457
+
458
+ Nested cache structure:
459
+
460
+ ```python
461
+ ("cache", "embeddings", "v1")
462
+ ```
463
+ """
464
+
465
+ key: str
466
+ """Unique identifier for the item within its namespace.
467
+
468
+ The key must be unique within the specific namespace to avoid conflicts.
469
+ Together with the namespace, it forms a complete path to the item.
470
+
471
+ Example:
472
+ If namespace is `("documents", "user123")` and key is `"report1"`,
473
+ the full path would effectively be `"documents/user123/report1"`
474
+ """
475
+
476
+ value: Mapping[str, Any] | None
477
+ """The data to store, or `None` to mark the item for deletion.
478
+
479
+ The value must be a mapping with string keys and JSON-serializable values.
480
+ Setting this to `None` signals that the item should be deleted.
481
+
482
+ Example:
483
+ {
484
+ "field1": "string value",
485
+ "field2": 123,
486
+ "nested": {"can": "contain", "any": "serializable data"}
487
+ }
488
+ """
489
+
490
+ index: Literal[False] | list[str] | None = None # type: ignore[assignment]
491
+ """Controls how the item's fields are indexed for search operations.
492
+
493
+ Indexing configuration determines how the item can be found through search:
494
+ - `None` (default): Uses the store's default indexing configuration (if provided)
495
+ - `False`: Disables indexing for this item
496
+ - `list[str]`: Specifies which json path fields to index for search
497
+
498
+ The item remains accessible through direct get() operations regardless of indexing.
499
+ When indexed, fields can be searched using natural language queries through
500
+ vector similarity search (if supported by the store implementation).
501
+
502
+ Path Syntax:
503
+ - Simple field access: `"field"`
504
+ - Nested fields: `"parent.child.grandchild"`
505
+ - Array indexing:
506
+ - Specific index: `"array[0]"`
507
+ - Last element: `"array[-1]"`
508
+ - All elements (each individually): `"array[*]"`
509
+
510
+ ???+ example "Examples"
511
+
512
+ - `None` - Use store defaults (whole item)
513
+ - `list[str]` - List of fields to index
514
+
515
+ ```python
516
+ [
517
+ "metadata.title", # Nested field access
518
+ "context[*].content", # Index content from all context as separate vectors
519
+ "authors[0].name", # First author's name
520
+ "revisions[-1].changes", # Most recent revision's changes
521
+ "sections[*].paragraphs[*].text", # All text from all paragraphs in all sections
522
+ "metadata.tags[*]", # All tags in metadata
523
+ ]
524
+ ```
525
+ """
526
+ ttl: float | None = None
527
+ """Controls the TTL (time-to-live) for the item in minutes.
528
+
529
+ If provided, and if the store you are using supports this feature, the item
530
+ will expire this many minutes after it was last accessed. The expiration timer
531
+ refreshes on both read operations (get/search) and write operations (put/update).
532
+ When the TTL expires, the item will be scheduled for deletion on a best-effort basis.
533
+ Defaults to `None` (no expiration).
534
+ """
535
+
536
+
537
+ Op = GetOp | SearchOp | PutOp | ListNamespacesOp
538
+ Result = Item | list[Item] | list[SearchItem] | list[tuple[str, ...]] | None
539
+
540
+
541
+ class InvalidNamespaceError(ValueError):
542
+ """Provided namespace is invalid."""
543
+
544
+
545
+ class TTLConfig(TypedDict, total=False):
546
+ """Configuration for TTL (time-to-live) behavior in the store."""
547
+
548
+ refresh_on_read: bool
549
+ """Default behavior for refreshing TTLs on read operations (`GET` and `SEARCH`).
550
+
551
+ If `True`, TTLs will be refreshed on read operations (get/search) by default.
552
+ This can be overridden per-operation by explicitly setting `refresh_ttl`.
553
+ Defaults to `True` if not configured.
554
+ """
555
+ omit_expired: bool
556
+ """Whether to omit expired items from read operations.
557
+
558
+ If `True`, and if the store supports this option, expired items will not be
559
+ returned by `GET` or `SEARCH`, or included in namespace listings, even before
560
+ a TTL sweep deletes them.
561
+ Defaults to `False` if not configured.
562
+ """
563
+ default_ttl: float | None
564
+ """Default TTL (time-to-live) in minutes for new items.
565
+
566
+ If provided, new items will expire after this many minutes after their last access.
567
+ The expiration timer refreshes on both read and write operations.
568
+ Defaults to `None` (no expiration).
569
+ """
570
+ sweep_interval_minutes: int | None
571
+ """Interval in minutes between TTL sweep operations.
572
+
573
+ If provided, the store will periodically delete expired items based on TTL.
574
+ Defaults to None (no sweeping).
575
+ """
576
+
577
+
578
+ class IndexConfig(TypedDict, total=False):
579
+ """Configuration for indexing documents for semantic search in the store.
580
+
581
+ If not provided to the store, the store will not support vector search.
582
+ In that case, all `index` arguments to `put()` and `aput()` operations will be ignored.
583
+ """
584
+
585
+ dims: int
586
+ """Number of dimensions in the embedding vectors.
587
+
588
+ Common embedding models have the following dimensions:
589
+ - `openai:text-embedding-3-large`: `3072`
590
+ - `openai:text-embedding-3-small`: `1536`
591
+ - `openai:text-embedding-ada-002`: `1536`
592
+ - `cohere:embed-english-v3.0`: `1024`
593
+ - `cohere:embed-english-light-v3.0`: `384`
594
+ - `cohere:embed-multilingual-v3.0`: `1024`
595
+ - `cohere:embed-multilingual-light-v3.0`: `384`
596
+ """
597
+
598
+ embed: Embeddings | EmbeddingsFunc | AEmbeddingsFunc | str
599
+ """Optional function to generate embeddings from text.
600
+
601
+ Can be specified in three ways:
602
+ 1. A Coco `Embeddings` instance
603
+ 2. A synchronous embedding function (`EmbeddingsFunc`)
604
+ 3. An asynchronous embedding function (`AEmbeddingsFunc`)
605
+ 4. A provider string (e.g., `"openai:text-embedding-3-small"`)
606
+
607
+ ???+ example "Examples"
608
+
609
+ Using Coco's initialization with `InMemoryStore`:
610
+
611
+ ```python
612
+ from coco.embeddings import init_embeddings
613
+ from coco_graph.store.memory import InMemoryStore
614
+
615
+ store = InMemoryStore(
616
+ index={
617
+ "dims": 1536,
618
+ "embed": init_embeddings("openai:text-embedding-3-small")
619
+ }
620
+ )
621
+ ```
622
+
623
+ Using a custom embedding function with `InMemoryStore`:
624
+
625
+ ```python
626
+ from openai import OpenAI
627
+ from coco_graph.store.memory import InMemoryStore
628
+
629
+ client = OpenAI()
630
+
631
+ def embed_texts(texts: list[str]) -> list[list[float]]:
632
+ response = client.embeddings.create(
633
+ model="text-embedding-3-small",
634
+ input=texts
635
+ )
636
+ return [e.embedding for e in response.data]
637
+
638
+ store = InMemoryStore(
639
+ index={
640
+ "dims": 1536,
641
+ "embed": embed_texts
642
+ }
643
+ )
644
+ ```
645
+
646
+ Using an asynchronous embedding function with `InMemoryStore`:
647
+
648
+ ```python
649
+ from openai import AsyncOpenAI
650
+ from coco_graph.store.memory import InMemoryStore
651
+
652
+ client = AsyncOpenAI()
653
+
654
+ async def aembed_texts(texts: list[str]) -> list[list[float]]:
655
+ response = await client.embeddings.create(
656
+ model="text-embedding-3-small",
657
+ input=texts
658
+ )
659
+ return [e.embedding for e in response.data]
660
+
661
+ store = InMemoryStore(
662
+ index={
663
+ "dims": 1536,
664
+ "embed": aembed_texts
665
+ }
666
+ )
667
+ ```
668
+ """
669
+
670
+ fields: list[str] | None
671
+ """Fields to extract text from for embedding generation.
672
+
673
+ Controls which parts of stored items are embedded for semantic search. Follows JSON path syntax:
674
+
675
+ - `["$"]`: Embeds the entire JSON object as one vector (default)
676
+ - `["field1", "field2"]`: Embeds specific top-level fields
677
+ - `["parent.child"]`: Embeds nested fields using dot notation
678
+ - `["array[*].field"]`: Embeds field from each array element separately
679
+
680
+ Note:
681
+ You can always override this behavior when storing an item using the
682
+ `index` parameter in the `put` or `aput` operations.
683
+
684
+ ???+ example "Examples"
685
+
686
+ ```python
687
+ # Embed entire document (default)
688
+ fields=["$"]
689
+
690
+ # Embed specific fields
691
+ fields=["text", "summary"]
692
+
693
+ # Embed nested fields
694
+ fields=["metadata.title", "content.body"]
695
+
696
+ # Embed from arrays
697
+ fields=["messages[*].content"] # Each message content separately
698
+ fields=["context[0].text"] # First context item's text
699
+ ```
700
+
701
+ Note:
702
+ - Fields missing from a document are skipped
703
+ - Array notation creates separate embeddings for each element
704
+ - Complex nested paths are supported (e.g., `"a.b[*].c.d"`)
705
+ """
706
+
707
+
708
+ class BaseStore(ABC):
709
+ """Abstract base class for persistent key-value stores.
710
+
711
+ Stores enable persistence and memory that can be shared across threads,
712
+ scoped to user IDs, assistant IDs, or other arbitrary namespaces.
713
+ Some implementations may support semantic search capabilities through
714
+ an optional `index` configuration.
715
+
716
+ Note:
717
+ Semantic search capabilities vary by implementation and are typically
718
+ disabled by default. Stores that support this feature can be configured
719
+ by providing an `index` configuration at creation time. Without this
720
+ configuration, semantic search is disabled and any `index` arguments
721
+ to storage operations will have no effect.
722
+
723
+ Similarly, TTL (time-to-live) support is disabled by default.
724
+ Subclasses must explicitly set `supports_ttl = True` to enable this feature.
725
+ """
726
+
727
+ supports_ttl: bool = False
728
+ ttl_config: TTLConfig | None = None
729
+
730
+ __slots__ = ("__weakref__",)
731
+
732
+ @abstractmethod
733
+ def batch(self, ops: Iterable[Op]) -> list[Result]:
734
+ """Execute multiple operations synchronously in a single batch.
735
+
736
+ Args:
737
+ ops: An iterable of operations to execute.
738
+
739
+ Returns:
740
+ A list of results, where each result corresponds to an operation in the input.
741
+ The order of results matches the order of input operations.
742
+ """
743
+
744
+ @abstractmethod
745
+ async def abatch(self, ops: Iterable[Op]) -> list[Result]:
746
+ """Execute multiple operations asynchronously in a single batch.
747
+
748
+ Args:
749
+ ops: An iterable of operations to execute.
750
+
751
+ Returns:
752
+ A list of results, where each result corresponds to an operation in the input.
753
+ The order of results matches the order of input operations.
754
+ """
755
+
756
+ def get(
757
+ self,
758
+ namespace: tuple[str, ...],
759
+ key: str,
760
+ *,
761
+ refresh_ttl: bool | None = None,
762
+ ) -> Item | None:
763
+ """Retrieve a single item.
764
+
765
+ Args:
766
+ namespace: Hierarchical path for the item.
767
+ key: Unique identifier within the namespace.
768
+ refresh_ttl: Whether to refresh TTLs for the returned item.
769
+ If `None`, uses the store's default `refresh_ttl` setting.
770
+ If no TTL is specified, this argument is ignored.
771
+
772
+ Returns:
773
+ The retrieved item or `None` if not found.
774
+ """
775
+ return self.batch(
776
+ [GetOp(namespace, str(key), _ensure_refresh(self.ttl_config, refresh_ttl))]
777
+ )[0]
778
+
779
+ def search(
780
+ self,
781
+ namespace_prefix: tuple[str, ...],
782
+ /,
783
+ *,
784
+ query: str | None = None,
785
+ filter: dict[str, Any] | None = None,
786
+ limit: int = 10,
787
+ offset: int = 0,
788
+ refresh_ttl: bool | None = None,
789
+ ) -> list[SearchItem]:
790
+ """Search for items within a namespace prefix.
791
+
792
+ Args:
793
+ namespace_prefix: Hierarchical path prefix to search within.
794
+ query: Optional query for natural language search.
795
+ filter: Key-value pairs to filter results.
796
+ limit: Maximum number of items to return.
797
+ offset: Number of items to skip before returning results.
798
+ refresh_ttl: Whether to refresh TTLs for the returned items.
799
+ If no TTL is specified, this argument is ignored.
800
+
801
+ Returns:
802
+ List of items matching the search criteria.
803
+
804
+ ???+ example "Examples"
805
+
806
+ Basic filtering:
807
+
808
+ ```python
809
+ # Search for documents with specific metadata
810
+ results = store.search(
811
+ ("docs",),
812
+ filter={"type": "article", "status": "published"}
813
+ )
814
+ ```
815
+
816
+ Natural language search (requires vector store implementation):
817
+
818
+ ```python
819
+ # Initialize store with embedding configuration
820
+ store = YourStore( # e.g., InMemoryStore, AsyncPostgresStore
821
+ index={
822
+ "dims": 1536, # embedding dimensions
823
+ "embed": your_embedding_function, # function to create embeddings
824
+ "fields": ["text"] # fields to embed. Defaults to ["$"]
825
+ }
826
+ )
827
+
828
+ # Search for semantically similar documents
829
+
830
+ results = store.search(
831
+ ("docs",),
832
+ query="machine learning applications in healthcare",
833
+ filter={"type": "research_paper"},
834
+ limit=5
835
+ )
836
+ ```
837
+
838
+ !!! note
839
+
840
+ Natural language search support depends on your store implementation
841
+ and requires proper embedding configuration.
842
+ """
843
+ return self.batch(
844
+ [
845
+ SearchOp(
846
+ namespace_prefix,
847
+ filter,
848
+ limit,
849
+ offset,
850
+ query,
851
+ _ensure_refresh(self.ttl_config, refresh_ttl),
852
+ )
853
+ ]
854
+ )[0]
855
+
856
+ def put(
857
+ self,
858
+ namespace: tuple[str, ...],
859
+ key: str,
860
+ value: Mapping[str, Any],
861
+ index: Literal[False] | list[str] | None = None,
862
+ *,
863
+ ttl: float | None | NotProvided = NOT_PROVIDED,
864
+ ) -> None:
865
+ """Store or update an item in the store.
866
+
867
+ Args:
868
+ namespace: Hierarchical path for the item, represented as a tuple of strings.
869
+ Example: `("documents", "user123")`
870
+ key: Unique identifier within the namespace. Together with namespace forms
871
+ the complete path to the item.
872
+ value: Mapping containing the item's data. Must contain string keys
873
+ and JSON-serializable values.
874
+ index: Controls how the item's fields are indexed for search:
875
+
876
+ - None (default): Use `fields` you configured when creating the store (if any)
877
+ If you do not initialize the store with indexing capabilities,
878
+ the `index` parameter will be ignored
879
+ - False: Disable indexing for this item
880
+ - `list[str]`: List of field paths to index, supporting:
881
+ - Nested fields: `"metadata.title"`
882
+ - Array access: `"chapters[*].content"` (each indexed separately)
883
+ - Specific indices: `"authors[0].name"`
884
+ ttl: Time to live in minutes. Support for this argument depends on your store adapter.
885
+ If specified, the item will expire after this many minutes from when it was last accessed.
886
+ None means no expiration. Expired runs will be deleted opportunistically.
887
+ By default, the expiration timer refreshes on both read operations (get/search)
888
+ and write operations (put/update), whenever the item is included in the operation.
889
+
890
+ Note:
891
+ Indexing support depends on your store implementation.
892
+ If you do not initialize the store with indexing capabilities,
893
+ the `index` parameter will be ignored.
894
+
895
+ Similarly, TTL support depends on the specific store implementation.
896
+ Some implementations may not support expiration of items.
897
+
898
+ ???+ example "Examples"
899
+
900
+ Store item. Indexing depends on how you configure the store:
901
+
902
+ ```python
903
+ store.put(("docs",), "report", {"memory": "Will likes ai"})
904
+ ```
905
+
906
+ Do not index item for semantic search. Still accessible through `get()`
907
+ and `search()` operations but won't have a vector representation.
908
+
909
+ ```python
910
+ store.put(("docs",), "report", {"memory": "Will likes ai"}, index=False)
911
+ ```
912
+
913
+ Index specific fields for search:
914
+
915
+ ```python
916
+ store.put(("docs",), "report", {"memory": "Will likes ai"}, index=["memory"])
917
+ ```
918
+ """
919
+ _validate_namespace(namespace)
920
+ if ttl not in (NOT_PROVIDED, None) and not self.supports_ttl:
921
+ raise NotImplementedError(
922
+ f"TTL is not supported by {self.__class__.__name__}. "
923
+ f"Use a store implementation that supports TTL or set ttl=None."
924
+ )
925
+ self.batch(
926
+ [
927
+ PutOp(
928
+ namespace,
929
+ str(key),
930
+ value,
931
+ index=index,
932
+ ttl=_ensure_ttl(self.ttl_config, ttl),
933
+ )
934
+ ]
935
+ )
936
+
937
+ def delete(self, namespace: tuple[str, ...], key: str) -> None:
938
+ """Delete an item.
939
+
940
+ Args:
941
+ namespace: Hierarchical path for the item.
942
+ key: Unique identifier within the namespace.
943
+ """
944
+ self.batch([PutOp(namespace, str(key), None, ttl=None)])
945
+
946
+ def list_namespaces(
947
+ self,
948
+ *,
949
+ prefix: NamespacePath | None = None,
950
+ suffix: NamespacePath | None = None,
951
+ max_depth: int | None = None,
952
+ limit: int = 100,
953
+ offset: int = 0,
954
+ ) -> list[tuple[str, ...]]:
955
+ """List and filter namespaces in the store.
956
+
957
+ Used to explore the organization of data,
958
+ find specific collections, or navigate the namespace hierarchy.
959
+
960
+ Args:
961
+ prefix: Filter namespaces that start with this path.
962
+ suffix: Filter namespaces that end with this path.
963
+ max_depth: Return namespaces up to this depth in the hierarchy.
964
+ Namespaces deeper than this level will be truncated.
965
+ limit: Maximum number of namespaces to return.
966
+ offset: Number of namespaces to skip for pagination.
967
+
968
+ Returns:
969
+ A list of namespace tuples that match the criteria. Each tuple represents a
970
+ full namespace path up to `max_depth`.
971
+
972
+ ???+ example "Examples":
973
+
974
+ Setting `max_depth=3`. Given the namespaces:
975
+
976
+ ```python
977
+ # Example if you have the following namespaces:
978
+ # ("a", "b", "c")
979
+ # ("a", "b", "d", "e")
980
+ # ("a", "b", "d", "i")
981
+ # ("a", "b", "f")
982
+ # ("a", "c", "f")
983
+ store.list_namespaces(prefix=("a", "b"), max_depth=3)
984
+ # [("a", "b", "c"), ("a", "b", "d"), ("a", "b", "f")]
985
+ ```
986
+ """
987
+ match_conditions = []
988
+ if prefix:
989
+ match_conditions.append(MatchCondition(match_type="prefix", path=prefix))
990
+ if suffix:
991
+ match_conditions.append(MatchCondition(match_type="suffix", path=suffix))
992
+
993
+ op = ListNamespacesOp(
994
+ match_conditions=tuple(match_conditions),
995
+ max_depth=max_depth,
996
+ limit=limit,
997
+ offset=offset,
998
+ )
999
+ return self.batch([op])[0]
1000
+
1001
+ async def aget(
1002
+ self,
1003
+ namespace: tuple[str, ...],
1004
+ key: str,
1005
+ *,
1006
+ refresh_ttl: bool | None = None,
1007
+ ) -> Item | None:
1008
+ """Asynchronously retrieve a single item.
1009
+
1010
+ Args:
1011
+ namespace: Hierarchical path for the item.
1012
+ key: Unique identifier within the namespace.
1013
+
1014
+ Returns:
1015
+ The retrieved item or `None` if not found.
1016
+ """
1017
+ return (
1018
+ await self.abatch(
1019
+ [
1020
+ GetOp(
1021
+ namespace,
1022
+ str(key),
1023
+ _ensure_refresh(self.ttl_config, refresh_ttl),
1024
+ )
1025
+ ]
1026
+ )
1027
+ )[0]
1028
+
1029
+ async def asearch(
1030
+ self,
1031
+ namespace_prefix: tuple[str, ...],
1032
+ /,
1033
+ *,
1034
+ query: str | None = None,
1035
+ filter: dict[str, Any] | None = None,
1036
+ limit: int = 10,
1037
+ offset: int = 0,
1038
+ refresh_ttl: bool | None = None,
1039
+ ) -> list[SearchItem]:
1040
+ """Asynchronously search for items within a namespace prefix.
1041
+
1042
+ Args:
1043
+ namespace_prefix: Hierarchical path prefix to search within.
1044
+ query: Optional query for natural language search.
1045
+ filter: Key-value pairs to filter results.
1046
+ limit: Maximum number of items to return.
1047
+ offset: Number of items to skip before returning results.
1048
+ refresh_ttl: Whether to refresh TTLs for the returned items.
1049
+ If `None`, uses the store's `TTLConfig.refresh_default` setting.
1050
+ If `TTLConfig` is not provided or no TTL is specified, this argument is ignored.
1051
+
1052
+ Returns:
1053
+ List of items matching the search criteria.
1054
+
1055
+ ???+ example "Examples"
1056
+
1057
+ Basic filtering:
1058
+
1059
+ ```python
1060
+ # Search for documents with specific metadata
1061
+ results = await store.asearch(
1062
+ ("docs",),
1063
+ filter={"type": "article", "status": "published"}
1064
+ )
1065
+ ```
1066
+
1067
+ Natural language search (requires vector store implementation):
1068
+
1069
+ ```python
1070
+ # Initialize store with embedding configuration
1071
+ store = YourStore( # e.g., InMemoryStore, AsyncPostgresStore
1072
+ index={
1073
+ "dims": 1536, # embedding dimensions
1074
+ "embed": your_embedding_function, # function to create embeddings
1075
+ "fields": ["text"] # fields to embed
1076
+ }
1077
+ )
1078
+
1079
+ # Search for semantically similar documents
1080
+
1081
+ results = await store.asearch(
1082
+ ("docs",),
1083
+ query="machine learning applications in healthcare",
1084
+ filter={"type": "research_paper"},
1085
+ limit=5
1086
+ )
1087
+ ```
1088
+
1089
+ !!! note
1090
+
1091
+ Natural language search support depends on your store implementation
1092
+ and requires proper embedding configuration.
1093
+ """
1094
+ return (
1095
+ await self.abatch(
1096
+ [
1097
+ SearchOp(
1098
+ namespace_prefix,
1099
+ filter,
1100
+ limit,
1101
+ offset,
1102
+ query,
1103
+ _ensure_refresh(self.ttl_config, refresh_ttl),
1104
+ )
1105
+ ]
1106
+ )
1107
+ )[0]
1108
+
1109
+ async def aput(
1110
+ self,
1111
+ namespace: tuple[str, ...],
1112
+ key: str,
1113
+ value: Mapping[str, Any],
1114
+ index: Literal[False] | list[str] | None = None,
1115
+ *,
1116
+ ttl: float | None | NotProvided = NOT_PROVIDED,
1117
+ ) -> None:
1118
+ """Asynchronously store or update an item in the store.
1119
+
1120
+ Args:
1121
+ namespace: Hierarchical path for the item, represented as a tuple of strings.
1122
+ Example: `("documents", "user123")`
1123
+ key: Unique identifier within the namespace. Together with namespace forms
1124
+ the complete path to the item.
1125
+ value: Mapping containing the item's data. Must contain string keys
1126
+ and JSON-serializable values.
1127
+ index: Controls how the item's fields are indexed for search:
1128
+
1129
+ - None (default): Use `fields` you configured when creating the store (if any)
1130
+ If you do not initialize the store with indexing capabilities,
1131
+ the `index` parameter will be ignored
1132
+ - False: Disable indexing for this item
1133
+ - `list[str]`: List of field paths to index, supporting:
1134
+ - Nested fields: `"metadata.title"`
1135
+ - Array access: `"chapters[*].content"` (each indexed separately)
1136
+ - Specific indices: `"authors[0].name"`
1137
+ ttl: Time to live in minutes. Support for this argument depends on your store adapter.
1138
+ If specified, the item will expire after this many minutes from when it was last accessed.
1139
+ None means no expiration. Expired runs will be deleted opportunistically.
1140
+ By default, the expiration timer refreshes on both read operations (get/search)
1141
+ and write operations (put/update), whenever the item is included in the operation.
1142
+
1143
+ Note:
1144
+ Indexing support depends on your store implementation.
1145
+ If you do not initialize the store with indexing capabilities,
1146
+ the `index` parameter will be ignored.
1147
+
1148
+ Similarly, TTL support depends on the specific store implementation.
1149
+ Some implementations may not support expiration of items.
1150
+
1151
+ ???+ example "Examples"
1152
+
1153
+ Store item. Indexing depends on how you configure the store:
1154
+
1155
+ ```python
1156
+ await store.aput(("docs",), "report", {"memory": "Will likes ai"})
1157
+ ```
1158
+
1159
+ Do not index item for semantic search. Still accessible through `get()`
1160
+ and `search()` operations but won't have a vector representation.
1161
+
1162
+ ```python
1163
+ await store.aput(("docs",), "report", {"memory": "Will likes ai"}, index=False)
1164
+ ```
1165
+
1166
+ Index specific fields for search (if store configured to index items):
1167
+
1168
+ ```python
1169
+ await store.aput(
1170
+ ("docs",),
1171
+ "report",
1172
+ {
1173
+ "memory": "Will likes ai",
1174
+ "context": [{"content": "..."}, {"content": "..."}]
1175
+ },
1176
+ index=["memory", "context[*].content"]
1177
+ )
1178
+ ```
1179
+ """
1180
+ _validate_namespace(namespace)
1181
+ if ttl not in (NOT_PROVIDED, None) and not self.supports_ttl:
1182
+ raise NotImplementedError(
1183
+ f"TTL is not supported by {self.__class__.__name__}. "
1184
+ f"Use a store implementation that supports TTL or set ttl=None."
1185
+ )
1186
+ await self.abatch(
1187
+ [
1188
+ PutOp(
1189
+ namespace,
1190
+ str(key),
1191
+ value,
1192
+ index=index,
1193
+ ttl=_ensure_ttl(self.ttl_config, ttl),
1194
+ )
1195
+ ]
1196
+ )
1197
+
1198
+ async def adelete(self, namespace: tuple[str, ...], key: str) -> None:
1199
+ """Asynchronously delete an item.
1200
+
1201
+ Args:
1202
+ namespace: Hierarchical path for the item.
1203
+ key: Unique identifier within the namespace.
1204
+ """
1205
+ await self.abatch([PutOp(namespace, str(key), None)])
1206
+
1207
+ async def alist_namespaces(
1208
+ self,
1209
+ *,
1210
+ prefix: NamespacePath | None = None,
1211
+ suffix: NamespacePath | None = None,
1212
+ max_depth: int | None = None,
1213
+ limit: int = 100,
1214
+ offset: int = 0,
1215
+ ) -> list[tuple[str, ...]]:
1216
+ """List and filter namespaces in the store asynchronously.
1217
+
1218
+ Used to explore the organization of data,
1219
+ find specific collections, or navigate the namespace hierarchy.
1220
+
1221
+ Args:
1222
+ prefix: Filter namespaces that start with this path.
1223
+ suffix: Filter namespaces that end with this path.
1224
+ max_depth: Return namespaces up to this depth in the hierarchy.
1225
+ Namespaces deeper than this level will be truncated to this depth.
1226
+ limit: Maximum number of namespaces to return.
1227
+ offset: Number of namespaces to skip for pagination.
1228
+
1229
+ Returns:
1230
+ A list of namespace tuples that match the criteria. Each tuple represents a
1231
+ full namespace path up to `max_depth`.
1232
+
1233
+ ???+ example "Examples"
1234
+
1235
+ Setting `max_depth=3` with existing namespaces:
1236
+ ```python
1237
+ # Given the following namespaces:
1238
+ # ("a", "b", "c")
1239
+ # ("a", "b", "d", "e")
1240
+ # ("a", "b", "d", "i")
1241
+ # ("a", "b", "f")
1242
+ # ("a", "c", "f")
1243
+
1244
+ await store.alist_namespaces(prefix=("a", "b"), max_depth=3)
1245
+ # Returns: [("a", "b", "c"), ("a", "b", "d"), ("a", "b", "f")]
1246
+ ```
1247
+ """
1248
+ match_conditions = []
1249
+ if prefix:
1250
+ match_conditions.append(MatchCondition(match_type="prefix", path=prefix))
1251
+ if suffix:
1252
+ match_conditions.append(MatchCondition(match_type="suffix", path=suffix))
1253
+
1254
+ op = ListNamespacesOp(
1255
+ match_conditions=tuple(match_conditions),
1256
+ max_depth=max_depth,
1257
+ limit=limit,
1258
+ offset=offset,
1259
+ )
1260
+ return (await self.abatch([op]))[0]
1261
+
1262
+
1263
+ def _validate_namespace(namespace: tuple[str, ...]) -> None:
1264
+ if not namespace:
1265
+ raise InvalidNamespaceError("Namespace cannot be empty.")
1266
+ for label in namespace:
1267
+ if not isinstance(label, str):
1268
+ raise InvalidNamespaceError(
1269
+ f"Invalid namespace label '{label}' found in {namespace}. Namespace labels"
1270
+ f" must be strings, but got {type(label).__name__}."
1271
+ )
1272
+ if "." in label:
1273
+ raise InvalidNamespaceError(
1274
+ f"Invalid namespace label '{label}' found in {namespace}. Namespace labels cannot contain periods ('.')."
1275
+ )
1276
+ elif not label:
1277
+ raise InvalidNamespaceError(
1278
+ f"Namespace labels cannot be empty strings. Got {label} in {namespace}"
1279
+ )
1280
+ if namespace[0] == "coco_graph":
1281
+ raise InvalidNamespaceError(
1282
+ f'Root label for namespace cannot be "coco_graph". Got: {namespace}'
1283
+ )
1284
+
1285
+
1286
+ def _ensure_refresh(
1287
+ ttl_config: TTLConfig | None, refresh_ttl: bool | None = None
1288
+ ) -> bool:
1289
+ if refresh_ttl is not None:
1290
+ return refresh_ttl
1291
+ if ttl_config is not None:
1292
+ return ttl_config.get("refresh_on_read", True)
1293
+ return True
1294
+
1295
+
1296
+ def _ensure_ttl(
1297
+ ttl_config: TTLConfig | None,
1298
+ ttl: float | None | NotProvided = NOT_PROVIDED,
1299
+ ) -> float | None:
1300
+ if ttl is NOT_PROVIDED:
1301
+ if ttl_config:
1302
+ return ttl_config.get("default_ttl")
1303
+ return None
1304
+ return ttl
1305
+
1306
+
1307
+ __all__ = [
1308
+ "BaseStore",
1309
+ "Item",
1310
+ "Op",
1311
+ "PutOp",
1312
+ "GetOp",
1313
+ "SearchOp",
1314
+ "ListNamespacesOp",
1315
+ "MatchCondition",
1316
+ "NamespacePath",
1317
+ "NamespaceMatchType",
1318
+ "Embeddings",
1319
+ "ensure_embeddings",
1320
+ "tokenize_path",
1321
+ "get_text_at_path",
1322
+ ]