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,121 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Sequence
5
+ from typing import Any, Generic, TypeVar
6
+
7
+ from typing_extensions import Self
8
+
9
+ from coco_graph._internal._typing import MISSING
10
+ from coco_graph.errors import EmptyChannelError
11
+
12
+ Value = TypeVar("Value")
13
+ Update = TypeVar("Update")
14
+ Checkpoint = TypeVar("Checkpoint")
15
+
16
+ __all__ = ("BaseChannel",)
17
+
18
+
19
+ class BaseChannel(Generic[Value, Update, Checkpoint], ABC):
20
+ """Base class for all channels."""
21
+
22
+ __slots__ = ("key", "typ")
23
+
24
+ def __init__(self, typ: Any, key: str = "") -> None:
25
+ self.typ = typ
26
+ self.key = key
27
+
28
+ @property
29
+ @abstractmethod
30
+ def ValueType(self) -> Any:
31
+ """The type of the value stored in the channel."""
32
+
33
+ @property
34
+ @abstractmethod
35
+ def UpdateType(self) -> Any:
36
+ """The type of the update received by the channel."""
37
+
38
+ # serialize/deserialize methods
39
+
40
+ def copy(self) -> Self:
41
+ """Return a copy of the channel.
42
+
43
+ By default, delegates to `checkpoint()` and `from_checkpoint()`.
44
+
45
+ Subclasses can override this method with a more efficient implementation.
46
+ """
47
+ return self.from_checkpoint(self.checkpoint())
48
+
49
+ def checkpoint(self) -> Checkpoint | Any:
50
+ """Return a serializable representation of the channel's current state.
51
+
52
+ Raises `EmptyChannelError` if the channel is empty (never updated yet),
53
+ or doesn't support checkpoints.
54
+ """
55
+ try:
56
+ return self.get()
57
+ except EmptyChannelError:
58
+ return MISSING
59
+
60
+ @abstractmethod
61
+ def from_checkpoint(self, checkpoint: Checkpoint | Any) -> Self:
62
+ """Return a new identical channel, optionally initialized from a checkpoint.
63
+
64
+ If the checkpoint contains complex data structures, they should be copied.
65
+ """
66
+
67
+ # read methods
68
+
69
+ @abstractmethod
70
+ def get(self) -> Value:
71
+ """Return the current value of the channel.
72
+
73
+ Raises `EmptyChannelError` if the channel is empty (never updated yet)."""
74
+
75
+ def is_available(self) -> bool:
76
+ """Return `True` if the channel is available (not empty), `False` otherwise.
77
+
78
+ Subclasses should override this method to provide a more efficient
79
+ implementation than calling `get()` and catching `EmptyChannelError`.
80
+ """
81
+ try:
82
+ self.get()
83
+ return True
84
+ except EmptyChannelError:
85
+ return False
86
+
87
+ # write methods
88
+
89
+ @abstractmethod
90
+ def update(self, values: Sequence[Update]) -> bool:
91
+ """Update the channel's value with the given sequence of updates.
92
+ The order of the updates in the sequence is arbitrary.
93
+ This method is called by Pregel for all channels at the end of each step.
94
+
95
+ If there are no updates, it is called with an empty sequence.
96
+
97
+ Raises `InvalidUpdateError` if the sequence of updates is invalid.
98
+
99
+ Returns `True` if the channel was updated, `False` otherwise."""
100
+
101
+ def consume(self) -> bool:
102
+ """Notify the channel that a subscribed task ran.
103
+
104
+ By default, no-op.
105
+
106
+ A channel can use this method to modify its state, preventing the value from being consumed again.
107
+
108
+ Returns `True` if the channel was updated, `False` otherwise.
109
+ """
110
+ return False
111
+
112
+ def finish(self) -> bool:
113
+ """Notify the channel that the Pregel run is finishing.
114
+
115
+ By default, no-op.
116
+
117
+ A channel can use this method to modify its state, preventing finish.
118
+
119
+ Returns `True` if the channel was updated, `False` otherwise.
120
+ """
121
+ return False
@@ -0,0 +1,155 @@
1
+ import collections.abc
2
+ from collections.abc import Callable, Sequence
3
+ from typing import Any, Generic
4
+
5
+ from typing_extensions import NotRequired, Required, Self
6
+
7
+ from coco_graph._internal._constants import OVERWRITE
8
+ from coco_graph._internal._typing import MISSING
9
+ from coco_graph.channels.base import BaseChannel, Value
10
+ from coco_graph.errors import (
11
+ EmptyChannelError,
12
+ ErrorCode,
13
+ InvalidUpdateError,
14
+ create_error_message,
15
+ )
16
+ from coco_graph.types import Overwrite
17
+
18
+ __all__ = ("BinaryOperatorAggregate",)
19
+
20
+
21
+ # Adapted from typing_extensions
22
+ def _strip_extras(t): # type: ignore[no-untyped-def]
23
+ """Strips Annotated, Required and NotRequired from a given type."""
24
+ if hasattr(t, "__origin__"):
25
+ if t.__origin__ in (Required, NotRequired):
26
+ return _strip_extras(t.__args__[0])
27
+ return _strip_extras(t.__origin__)
28
+ return t
29
+
30
+
31
+ def _get_overwrite(value: Any) -> tuple[bool, Any]:
32
+ """Inspects the given value and returns (is_overwrite, overwrite_value).
33
+
34
+ Recognises three forms:
35
+
36
+ * The typed `Overwrite` dataclass instance.
37
+ * The sentinel-keyed `{"__overwrite__": value}` dict form.
38
+ * The dataclass-erased `{"value": ..., "type": "__overwrite__"}` form that
39
+ results from JSON-serialising an `Overwrite` (e.g. an `orjson`-encoded
40
+ state update routed through the CocoGraph API server). This keeps the
41
+ `Overwrite` semantics intact across JSON boundaries that strip dataclass
42
+ types.
43
+ """
44
+ if isinstance(value, Overwrite):
45
+ return True, value.value
46
+ if isinstance(value, dict):
47
+ if len(value) == 1 and OVERWRITE in value:
48
+ return True, value[OVERWRITE]
49
+ if value.get("type") == OVERWRITE and "value" in value:
50
+ return True, value["value"]
51
+ return False, None
52
+
53
+
54
+ def _operators_equal(a: Callable, b: Callable) -> bool:
55
+ """Return True if two reducer operators should be considered equal.
56
+
57
+ Lambdas all share the name '<lambda>' so identity comparison is
58
+ unreliable; treat any pairing that includes a lambda as equal.
59
+ """
60
+ if a.__name__ == "<lambda>" or b.__name__ == "<lambda>":
61
+ return True
62
+ return a is b
63
+
64
+
65
+ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
66
+ """Stores the result of applying a binary operator to the current value and each new value.
67
+
68
+ ```python
69
+ import operator
70
+
71
+ total = Channels.BinaryOperatorAggregate(int, operator.add)
72
+ ```
73
+ """
74
+
75
+ __slots__ = ("value", "operator")
76
+
77
+ def __init__(self, typ: type[Value], operator: Callable[[Value, Value], Value]):
78
+ super().__init__(typ)
79
+ self.operator = operator
80
+ # special forms from typing or collections.abc are not instantiable
81
+ # so we need to replace them with their concrete counterparts
82
+ typ = _strip_extras(typ)
83
+ if typ in (collections.abc.Sequence, collections.abc.MutableSequence):
84
+ typ = list
85
+ if typ in (collections.abc.Set, collections.abc.MutableSet):
86
+ typ = set
87
+ if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
88
+ typ = dict
89
+ try:
90
+ self.value = typ()
91
+ except Exception:
92
+ self.value = MISSING
93
+
94
+ def __eq__(self, value: object) -> bool:
95
+ return isinstance(value, BinaryOperatorAggregate) and _operators_equal(
96
+ self.operator, value.operator
97
+ )
98
+
99
+ @property
100
+ def ValueType(self) -> type[Value]:
101
+ """The type of the value stored in the channel."""
102
+ return self.typ
103
+
104
+ @property
105
+ def UpdateType(self) -> type[Value]:
106
+ """The type of the update received by the channel."""
107
+ return self.typ
108
+
109
+ def copy(self) -> Self:
110
+ """Return a copy of the channel."""
111
+ empty = self.__class__(self.typ, self.operator)
112
+ empty.key = self.key
113
+ empty.value = self.value
114
+ return empty
115
+
116
+ def from_checkpoint(self, checkpoint: Value) -> Self:
117
+ empty = self.__class__(self.typ, self.operator)
118
+ empty.key = self.key
119
+ if checkpoint is not MISSING:
120
+ empty.value = checkpoint
121
+ return empty
122
+
123
+ def update(self, values: Sequence[Value]) -> bool:
124
+ if not values:
125
+ return False
126
+ if self.value is MISSING:
127
+ self.value = values[0]
128
+ values = values[1:]
129
+ seen_overwrite: bool = False
130
+ for value in values:
131
+ is_overwrite, overwrite_value = _get_overwrite(value)
132
+ if is_overwrite:
133
+ if seen_overwrite:
134
+ msg = create_error_message(
135
+ message="Can receive only one Overwrite value per super-step.",
136
+ error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE,
137
+ )
138
+ raise InvalidUpdateError(msg)
139
+ self.value = overwrite_value
140
+ seen_overwrite = True
141
+ continue
142
+ if not seen_overwrite:
143
+ self.value = self.operator(self.value, value)
144
+ return True
145
+
146
+ def get(self) -> Value:
147
+ if self.value is MISSING:
148
+ raise EmptyChannelError()
149
+ return self.value
150
+
151
+ def is_available(self) -> bool:
152
+ return self.value is not MISSING
153
+
154
+ def checkpoint(self) -> Value:
155
+ return self.value
@@ -0,0 +1,202 @@
1
+ from __future__ import annotations
2
+
3
+ import collections.abc
4
+ import copy as _copy
5
+ from collections.abc import Callable, Sequence
6
+ from typing import Any, Generic
7
+
8
+ from coco_graph.checkpoint.base import PendingWrite
9
+ from coco_graph.checkpoint.serde.types import _DeltaSnapshot
10
+ from typing_extensions import Self
11
+
12
+ from coco_graph._internal._typing import MISSING
13
+ from coco_graph.channels.base import BaseChannel, Value
14
+ from coco_graph.channels.binop import _get_overwrite, _operators_equal, _strip_extras
15
+ from coco_graph.errors import (
16
+ EmptyChannelError,
17
+ ErrorCode,
18
+ InvalidUpdateError,
19
+ create_error_message,
20
+ )
21
+
22
+ __all__ = ("DeltaChannel",)
23
+
24
+
25
+ class DeltaChannel(Generic[Value], BaseChannel[Any, Any, Any]):
26
+ """Reducer channel that stores only a sentinel in checkpoint blobs and
27
+ reconstructs state by replaying ancestor writes through the reducer.
28
+
29
+ !!! warning "Beta"
30
+
31
+ `DeltaChannel` is in beta. The API and on-disk representation may
32
+ change in future releases. Threads written with `DeltaChannel` today
33
+ are expected to remain readable, but the surrounding contract
34
+ (`BaseCheckpointSaver.get_delta_channel_history`, the
35
+ `_DeltaSnapshot` blob shape, the `counters_since_delta_snapshot`
36
+ metadata field) is not yet stable.
37
+
38
+ The reducer receives the current accumulated value and a batch of writes
39
+ in one call: `reducer(state, [write1, write2, ...]) -> new_state`.
40
+
41
+ Reducers must be deterministic and batching-invariant (associative across
42
+ folds): applying two consecutive write batches separately must produce the
43
+ same state as applying their concatenation once:
44
+
45
+ reducer(reducer(state, xs), ys) == reducer(state, xs + ys)
46
+
47
+ This lets CocoGraph replay checkpointed writes in larger batches than they
48
+ were originally produced without changing reconstructed state.
49
+
50
+ Snapshot cadence is driven by two counters: per-channel update count and
51
+ total supersteps since last snapshot. `create_checkpoint` writes a full
52
+ `_DeltaSnapshot` blob when EITHER the update count reaches
53
+ `snapshot_frequency` OR the supersteps count reaches the system-wide
54
+ `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT` bound (default 5000), bounding
55
+ replay depth even for channels that stop receiving writes.
56
+
57
+ Parameters:
58
+ reducer: `(state, list[writes]) -> new_state`. Must be deterministic
59
+ and batching-invariant as described above.
60
+ typ: The value type (e.g. `list`, `dict`). Inferred automatically
61
+ from the outer type when used inside `Annotated[T, DeltaChannel(...)]`.
62
+ snapshot_frequency: Every Nth update to this channel writes a snapshot
63
+ blob (default `1000`). Must be a positive int.
64
+ """
65
+
66
+ __slots__ = ("value", "reducer", "snapshot_frequency")
67
+ value: Value | Any
68
+
69
+ def __init__(
70
+ self,
71
+ reducer: Callable[[Any, Sequence[Any]], Any],
72
+ typ: type[Value] | None = None,
73
+ *,
74
+ snapshot_frequency: int = 1000,
75
+ ) -> None:
76
+ if snapshot_frequency <= 0:
77
+ raise ValueError(
78
+ f"snapshot_frequency must be a positive int, got {snapshot_frequency}"
79
+ )
80
+ if typ is None:
81
+ typ = list # type: ignore[assignment] # placeholder; overridden by _is_field_channel
82
+ super().__init__(typ)
83
+ self.reducer = reducer
84
+ self.snapshot_frequency = snapshot_frequency
85
+ typ = _strip_extras(typ)
86
+ if typ in (collections.abc.Sequence, collections.abc.MutableSequence):
87
+ typ = list
88
+ if typ in (collections.abc.Set, collections.abc.MutableSet):
89
+ typ = set
90
+ if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
91
+ typ = dict
92
+ self.typ = typ
93
+ self.value: Any = MISSING
94
+
95
+ def __eq__(self, other: object) -> bool:
96
+ if not isinstance(other, DeltaChannel):
97
+ return False
98
+ if self.snapshot_frequency != other.snapshot_frequency:
99
+ return False
100
+ return _operators_equal(self.reducer, other.reducer)
101
+
102
+ @property
103
+ def ValueType(self) -> Any:
104
+ return self.typ
105
+
106
+ @property
107
+ def UpdateType(self) -> Any:
108
+ return self.typ
109
+
110
+ def copy(self) -> Self:
111
+ new = self.__class__(
112
+ self.reducer, self.typ, snapshot_frequency=self.snapshot_frequency
113
+ )
114
+ new.key = self.key
115
+ new.value = self.value if self.value is MISSING else _copy.copy(self.value)
116
+ return new
117
+
118
+ def from_checkpoint(self, checkpoint: Any) -> Self:
119
+ """Initialize from a stored blob.
120
+
121
+ Blob types:
122
+ * `MISSING`: start empty; caller replays writes.
123
+ * `_DeltaSnapshot(value)`: restore value directly from snapshot.
124
+ * plain value (migration from old `BinaryOperatorAggregate` blobs):
125
+ use directly.
126
+ """
127
+ new = self.__class__(
128
+ self.reducer, self.typ, snapshot_frequency=self.snapshot_frequency
129
+ )
130
+ new.key = self.key
131
+ if checkpoint is MISSING:
132
+ new.value = self.typ()
133
+ elif isinstance(checkpoint, _DeltaSnapshot):
134
+ new.value = checkpoint.value
135
+ else:
136
+ new.value = checkpoint
137
+ return new
138
+
139
+ def replay_writes(self, writes: Sequence[PendingWrite]) -> None:
140
+ """Apply ancestor writes oldest-to-newest via a single reducer call.
141
+
142
+ If any write is an Overwrite, the last one in the sequence acts as
143
+ the reset point: its value becomes the new base and only writes
144
+ after it are passed to the reducer.
145
+ """
146
+ values = [v for _, _, v in writes]
147
+ if not values:
148
+ return
149
+ base = self.value
150
+ start = 0
151
+ for i, v in enumerate(values):
152
+ is_ow, ow_value = _get_overwrite(v)
153
+ if is_ow:
154
+ base = _copy.copy(ow_value) if ow_value is not None else self.typ()
155
+ start = i + 1
156
+ remaining = values[start:]
157
+ self.value = self.reducer(base, remaining) if remaining else base
158
+
159
+ def update(self, values: Sequence[Any]) -> bool:
160
+ if not values:
161
+ return False
162
+ overwrite_idx: int | None = None
163
+ for i, v in enumerate(values):
164
+ is_ow, _ = _get_overwrite(v)
165
+ if is_ow:
166
+ if overwrite_idx is not None:
167
+ msg = create_error_message(
168
+ message="Can receive only one Overwrite value per super-step.",
169
+ error_code=ErrorCode.INVALID_CONCURRENT_GRAPH_UPDATE,
170
+ )
171
+ raise InvalidUpdateError(msg)
172
+ overwrite_idx = i
173
+ if overwrite_idx is not None:
174
+ _, overwrite_value = _get_overwrite(values[overwrite_idx])
175
+ self.value = (
176
+ _copy.copy(overwrite_value)
177
+ if overwrite_value is not None
178
+ else self.typ()
179
+ )
180
+ return True
181
+ base = self.typ() if self.value is MISSING else self.value
182
+ self.value = self.reducer(base, list(values))
183
+ return True
184
+
185
+ def get(self) -> Any:
186
+ if self.value is MISSING:
187
+ raise EmptyChannelError()
188
+ return self.value
189
+
190
+ def is_available(self) -> bool:
191
+ return self.value is not MISSING
192
+
193
+ def checkpoint(self) -> Any:
194
+ """Return stored representation: always `MISSING`.
195
+
196
+ Snapshot decisions live in `create_checkpoint` (which has the channel
197
+ version) and write `_DeltaSnapshot(ch.get())` directly into
198
+ `channel_values`. For non-snapshot steps the channel does not appear
199
+ in `channel_values`; reconstruction walks ancestor writes via the
200
+ saver's `get_delta_channel_history`.
201
+ """
202
+ return MISSING
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import Any, Generic
5
+
6
+ from typing_extensions import Self
7
+
8
+ from coco_graph._internal._typing import MISSING
9
+ from coco_graph.channels.base import BaseChannel, Value
10
+ from coco_graph.errors import EmptyChannelError, InvalidUpdateError
11
+
12
+ __all__ = ("EphemeralValue",)
13
+
14
+
15
+ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
16
+ """Stores the value received in the step immediately preceding, clears after."""
17
+
18
+ __slots__ = ("value", "guard")
19
+
20
+ value: Value | Any
21
+ guard: bool
22
+
23
+ def __init__(self, typ: Any, guard: bool = True) -> None:
24
+ super().__init__(typ)
25
+ self.guard = guard
26
+ self.value = MISSING
27
+
28
+ def __eq__(self, value: object) -> bool:
29
+ return isinstance(value, EphemeralValue) and value.guard == self.guard
30
+
31
+ @property
32
+ def ValueType(self) -> type[Value]:
33
+ """The type of the value stored in the channel."""
34
+ return self.typ
35
+
36
+ @property
37
+ def UpdateType(self) -> type[Value]:
38
+ """The type of the update received by the channel."""
39
+ return self.typ
40
+
41
+ def copy(self) -> Self:
42
+ """Return a copy of the channel."""
43
+ empty = self.__class__(self.typ, self.guard)
44
+ empty.key = self.key
45
+ empty.value = self.value
46
+ return empty
47
+
48
+ def from_checkpoint(self, checkpoint: Value) -> Self:
49
+ empty = self.__class__(self.typ, self.guard)
50
+ empty.key = self.key
51
+ if checkpoint is not MISSING:
52
+ empty.value = checkpoint
53
+ return empty
54
+
55
+ def update(self, values: Sequence[Value]) -> bool:
56
+ if len(values) == 0:
57
+ if self.value is not MISSING:
58
+ self.value = MISSING
59
+ return True
60
+ else:
61
+ return False
62
+ if len(values) != 1 and self.guard:
63
+ raise InvalidUpdateError(
64
+ f"At key '{self.key}': EphemeralValue(guard=True) can receive only one value per step. Use guard=False if you want to store any one of multiple values."
65
+ )
66
+
67
+ self.value = values[-1]
68
+ return True
69
+
70
+ def get(self) -> Value:
71
+ if self.value is MISSING:
72
+ raise EmptyChannelError()
73
+ return self.value
74
+
75
+ def is_available(self) -> bool:
76
+ return self.value is not MISSING
77
+
78
+ def checkpoint(self) -> Value:
79
+ return self.value