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
File without changes
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from collections.abc import Sequence
6
+ from typing import Any
7
+
8
+ from coco_core.runnables import RunnableConfig
9
+ from coco_graph.checkpoint.base import get_checkpoint_id
10
+
11
+ _FILTER_PATTERN = re.compile(r"^[a-zA-Z0-9_.-]+$")
12
+
13
+
14
+ def _validate_filter_key(key: str) -> None:
15
+ """Validate that a filter key is safe for use in SQL queries.
16
+
17
+ Args:
18
+ key: The filter key to validate
19
+
20
+ Raises:
21
+ ValueError: If the key contains invalid characters that could enable SQL injection
22
+ """
23
+ # Allow alphanumeric characters, underscores, dots, and hyphens
24
+ # This covers typical JSON property names while preventing SQL injection
25
+ if not _FILTER_PATTERN.match(key):
26
+ raise ValueError(
27
+ f"Invalid filter key: '{key}'. Filter keys must contain only alphanumeric characters, underscores, dots, and hyphens."
28
+ )
29
+
30
+
31
+ def _metadata_predicate(
32
+ metadata_filter: dict[str, Any],
33
+ ) -> tuple[Sequence[str], Sequence[Any]]:
34
+ """Return WHERE clause predicates for (a)search() given metadata filter.
35
+
36
+ This method returns a tuple of a string and a tuple of values. The string
37
+ is the parametered WHERE clause predicate (excluding the WHERE keyword):
38
+ "column1 = ? AND column2 IS ?". The tuple of values contains the values
39
+ for each of the corresponding parameters.
40
+ """
41
+
42
+ def _where_value(query_value: Any) -> tuple[str, Any]:
43
+ """Return tuple of operator and value for WHERE clause predicate."""
44
+ if query_value is None:
45
+ return ("IS ?", None)
46
+ elif (
47
+ isinstance(query_value, str)
48
+ or isinstance(query_value, int)
49
+ or isinstance(query_value, float)
50
+ ):
51
+ return ("= ?", query_value)
52
+ elif isinstance(query_value, bool):
53
+ return ("= ?", 1 if query_value else 0)
54
+ elif isinstance(query_value, dict) or isinstance(query_value, list):
55
+ # query value for JSON object cannot have trailing space after separators (, :)
56
+ # SQLite json_extract() returns JSON string without whitespace
57
+ return ("= ?", json.dumps(query_value, separators=(",", ":")))
58
+ else:
59
+ return ("= ?", str(query_value))
60
+
61
+ predicates = []
62
+ param_values = []
63
+
64
+ # process metadata query
65
+ for query_key, query_value in metadata_filter.items():
66
+ _validate_filter_key(query_key)
67
+ operator, param_value = _where_value(query_value)
68
+ predicates.append(
69
+ f"json_extract(CAST(metadata AS TEXT), '$.{query_key}') {operator}"
70
+ )
71
+ param_values.append(param_value)
72
+
73
+ return (predicates, param_values)
74
+
75
+
76
+ def search_where(
77
+ config: RunnableConfig | None,
78
+ filter: dict[str, Any] | None,
79
+ before: RunnableConfig | None = None,
80
+ ) -> tuple[str, Sequence[Any]]:
81
+ """Return WHERE clause predicates for (a)search() given metadata filter
82
+ and `before` config.
83
+
84
+ This method returns a tuple of a string and a tuple of values. The string
85
+ is the parametered WHERE clause predicate (including the WHERE keyword):
86
+ "WHERE column1 = ? AND column2 IS ?". The tuple of values contains the
87
+ values for each of the corresponding parameters.
88
+ """
89
+ wheres = []
90
+ param_values = []
91
+
92
+ # construct predicate for config filter
93
+ if config is not None:
94
+ wheres.append("thread_id = ?")
95
+ param_values.append(config["configurable"]["thread_id"])
96
+ checkpoint_ns = config["configurable"].get("checkpoint_ns")
97
+ if checkpoint_ns is not None:
98
+ wheres.append("checkpoint_ns = ?")
99
+ param_values.append(checkpoint_ns)
100
+
101
+ if checkpoint_id := get_checkpoint_id(config):
102
+ wheres.append("checkpoint_id = ?")
103
+ param_values.append(checkpoint_id)
104
+
105
+ # construct predicate for metadata filter
106
+ if filter:
107
+ metadata_predicates, metadata_values = _metadata_predicate(filter)
108
+ wheres.extend(metadata_predicates)
109
+ param_values.extend(metadata_values)
110
+
111
+ # construct predicate for `before`
112
+ if before is not None:
113
+ wheres.append("checkpoint_id < ?")
114
+ param_values.append(get_checkpoint_id(before))
115
+
116
+ return ("WHERE " + " AND ".join(wheres) if wheres else "", param_values)
coco_graph/config.py ADDED
@@ -0,0 +1,196 @@
1
+ import asyncio
2
+ import sys
3
+ from typing import Any
4
+
5
+ from coco_core.runnables import RunnableConfig
6
+ from coco_core.runnables.config import var_child_runnable_config
7
+ from coco_graph.store.base import BaseStore
8
+
9
+ from coco_graph._internal._constants import CONF, CONFIG_KEY_RUNTIME
10
+ from coco_graph.types import StreamWriter
11
+
12
+
13
+ def _no_op_stream_writer(c: Any) -> None:
14
+ pass
15
+
16
+
17
+ def get_config() -> RunnableConfig:
18
+ if sys.version_info < (3, 11):
19
+ try:
20
+ if asyncio.current_task():
21
+ raise RuntimeError(
22
+ "Python 3.11 or later required to use this in an async context"
23
+ )
24
+ except RuntimeError:
25
+ pass
26
+ if var_config := var_child_runnable_config.get():
27
+ return var_config
28
+ else:
29
+ raise RuntimeError("Called get_config outside of a runnable context")
30
+
31
+
32
+ def get_store() -> BaseStore:
33
+ """Access CocoGraph store from inside a graph node or entrypoint task at runtime.
34
+
35
+ Can be called from inside any [`StateGraph`][coco_graph.graph.StateGraph] node or
36
+ functional API [`task`][coco_graph.func.task], as long as the `StateGraph` or the [`entrypoint`][coco_graph.func.entrypoint]
37
+ was initialized with a store, e.g.:
38
+
39
+ ```python
40
+ # with StateGraph
41
+ graph = (
42
+ StateGraph(...)
43
+ ...
44
+ .compile(store=store)
45
+ )
46
+
47
+ # or with entrypoint
48
+ @entrypoint(store=store)
49
+ def workflow(inputs):
50
+ ...
51
+ ```
52
+
53
+ !!! warning "Async with Python < 3.11"
54
+
55
+ If you are using Python < 3.11 and are running CocoGraph asynchronously,
56
+ `get_store()` won't work since it uses [`contextvar`](https://docs.python.org/3/library/contextvars.html) propagation (only available in [Python >= 3.11](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)).
57
+
58
+
59
+ Example: Using with `StateGraph`
60
+ ```python
61
+ from typing_extensions import TypedDict
62
+ from coco_graph.graph import StateGraph, START
63
+ from coco_graph.store.memory import InMemoryStore
64
+ from coco_graph.config import get_store
65
+
66
+ store = InMemoryStore()
67
+ store.put(("values",), "foo", {"bar": 2})
68
+
69
+
70
+ class State(TypedDict):
71
+ foo: int
72
+
73
+
74
+ def my_node(state: State):
75
+ my_store = get_store()
76
+ stored_value = my_store.get(("values",), "foo").value["bar"]
77
+ return {"foo": stored_value + 1}
78
+
79
+
80
+ graph = (
81
+ StateGraph(State)
82
+ .add_node(my_node)
83
+ .add_edge(START, "my_node")
84
+ .compile(store=store)
85
+ )
86
+
87
+ graph.invoke({"foo": 1})
88
+ ```
89
+
90
+ ```pycon
91
+ {"foo": 3}
92
+ ```
93
+
94
+ Example: Using with functional API
95
+ ```python
96
+ from coco_graph.func import entrypoint, task
97
+ from coco_graph.store.memory import InMemoryStore
98
+ from coco_graph.config import get_store
99
+
100
+ store = InMemoryStore()
101
+ store.put(("values",), "foo", {"bar": 2})
102
+
103
+
104
+ @task
105
+ def my_task(value: int):
106
+ my_store = get_store()
107
+ stored_value = my_store.get(("values",), "foo").value["bar"]
108
+ return stored_value + 1
109
+
110
+
111
+ @entrypoint(store=store)
112
+ def workflow(value: int):
113
+ return my_task(value).result()
114
+
115
+
116
+ workflow.invoke(1)
117
+ ```
118
+
119
+ ```pycon
120
+ 3
121
+ ```
122
+ """
123
+ return get_config()[CONF][CONFIG_KEY_RUNTIME].store
124
+
125
+
126
+ def get_stream_writer() -> StreamWriter:
127
+ """Access CocoGraph [`StreamWriter`][coco_graph.types.StreamWriter] from inside a graph node or entrypoint task at runtime.
128
+
129
+ Can be called from inside any [`StateGraph`][coco_graph.graph.StateGraph] node or
130
+ functional API [`task`][coco_graph.func.task].
131
+
132
+ !!! warning "Async with Python < 3.11"
133
+
134
+ If you are using Python < 3.11 and are running CocoGraph asynchronously,
135
+ `get_stream_writer()` won't work since it uses [`contextvar`](https://docs.python.org/3/library/contextvars.html) propagation (only available in [Python >= 3.11](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task)).
136
+
137
+ Example: Using with `StateGraph`
138
+ ```python
139
+ from typing_extensions import TypedDict
140
+ from coco_graph.graph import StateGraph, START
141
+ from coco_graph.config import get_stream_writer
142
+
143
+
144
+ class State(TypedDict):
145
+ foo: int
146
+
147
+
148
+ def my_node(state: State):
149
+ my_stream_writer = get_stream_writer()
150
+ my_stream_writer({"custom_data": "Hello!"})
151
+ return {"foo": state["foo"] + 1}
152
+
153
+
154
+ graph = (
155
+ StateGraph(State)
156
+ .add_node(my_node)
157
+ .add_edge(START, "my_node")
158
+ .compile(store=store)
159
+ )
160
+
161
+ for chunk in graph.stream({"foo": 1}, stream_mode="custom"):
162
+ print(chunk)
163
+ ```
164
+
165
+ ```pycon
166
+ {"custom_data": "Hello!"}
167
+ ```
168
+
169
+ Example: Using with functional API
170
+ ```python
171
+ from coco_graph.func import entrypoint, task
172
+ from coco_graph.config import get_stream_writer
173
+
174
+
175
+ @task
176
+ def my_task(value: int):
177
+ my_stream_writer = get_stream_writer()
178
+ my_stream_writer({"custom_data": "Hello!"})
179
+ return value + 1
180
+
181
+
182
+ @entrypoint(store=store)
183
+ def workflow(value: int):
184
+ return my_task(value).result()
185
+
186
+
187
+ for chunk in workflow.stream(1, stream_mode="custom"):
188
+ print(chunk)
189
+ ```
190
+
191
+ ```pycon
192
+ {"custom_data": "Hello!"}
193
+ ```
194
+ """
195
+ runtime = get_config()[CONF][CONFIG_KEY_RUNTIME]
196
+ return runtime.stream_writer
@@ -0,0 +1,64 @@
1
+ import sys
2
+ from typing import Any
3
+ from warnings import warn
4
+
5
+ from coco_graph._internal._constants import (
6
+ CONF,
7
+ CONFIG_KEY_CHECKPOINTER,
8
+ TASKS,
9
+ )
10
+ from coco_graph.warnings import CocoGraphDeprecatedSinceV10
11
+
12
+ __all__ = (
13
+ "TAG_NOSTREAM",
14
+ "TAG_HIDDEN",
15
+ "START",
16
+ "END",
17
+ # retained for backwards compatibility (mostly trustcoco-graph-api), should be removed in v2 (or earlier)
18
+ "CONF",
19
+ "TASKS",
20
+ "CONFIG_KEY_CHECKPOINTER",
21
+ )
22
+
23
+ # --- Public constants ---
24
+ TAG_NOSTREAM = sys.intern("nostream")
25
+ """Tag to disable streaming for a chat model."""
26
+ TAG_HIDDEN = sys.intern("langsmith:hidden")
27
+ """Tag to hide a node/edge from certain tracing/streaming environments."""
28
+ END = sys.intern("__end__")
29
+ """The last (maybe virtual) node in graph-style Pregel."""
30
+ START = sys.intern("__start__")
31
+ """The first (maybe virtual) node in graph-style Pregel."""
32
+
33
+
34
+ def __getattr__(name: str) -> Any:
35
+ if name in ["Send", "Interrupt"]:
36
+ warn(
37
+ f"Importing {name} from coco_graph.constants is deprecated. "
38
+ f"Please use 'from coco_graph.types import {name}' instead.",
39
+ CocoGraphDeprecatedSinceV10,
40
+ stacklevel=2,
41
+ )
42
+
43
+ from importlib import import_module
44
+
45
+ module = import_module("coco_graph.types")
46
+ return getattr(module, name)
47
+
48
+ try:
49
+ from importlib import import_module
50
+
51
+ private_constants = import_module("coco_graph._internal._constants")
52
+ attr = getattr(private_constants, name)
53
+ warn(
54
+ f"Importing {name} from coco_graph.constants is deprecated. "
55
+ f"This constant is now private and should not be used directly. "
56
+ "Please let the CocoGraph team know if you need this value.",
57
+ CocoGraphDeprecatedSinceV10,
58
+ stacklevel=2,
59
+ )
60
+ return attr
61
+ except AttributeError:
62
+ pass
63
+
64
+ raise AttributeError(f"module has no attribute '{name}'")
coco_graph/errors.py ADDED
@@ -0,0 +1,241 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+ from typing import Any, Literal
7
+ from warnings import warn
8
+
9
+ # EmptyChannelError is re-exported from coco_graph.channels.base
10
+ from coco_graph.checkpoint.base import EmptyChannelError
11
+ from typing_extensions import deprecated
12
+
13
+ from coco_graph.types import Command, Interrupt
14
+ from coco_graph.warnings import CocoGraphDeprecatedSinceV10
15
+
16
+ __all__ = (
17
+ "EmptyChannelError",
18
+ "ErrorCode",
19
+ "GraphDrained",
20
+ "GraphRecursionError",
21
+ "InvalidUpdateError",
22
+ "GraphBubbleUp",
23
+ "GraphInterrupt",
24
+ "NodeCancelledError",
25
+ "NodeError",
26
+ "NodeInterrupt",
27
+ "NodeTimeoutError",
28
+ "ParentCommand",
29
+ "EmptyInputError",
30
+ "TaskNotFound",
31
+ )
32
+
33
+
34
+ class ErrorCode(Enum):
35
+ GRAPH_RECURSION_LIMIT = "GRAPH_RECURSION_LIMIT"
36
+ INVALID_CONCURRENT_GRAPH_UPDATE = "INVALID_CONCURRENT_GRAPH_UPDATE"
37
+ INVALID_GRAPH_NODE_RETURN_VALUE = "INVALID_GRAPH_NODE_RETURN_VALUE"
38
+ MULTIPLE_SUBGRAPHS = "MULTIPLE_SUBGRAPHS"
39
+ INVALID_CHAT_HISTORY = "INVALID_CHAT_HISTORY"
40
+
41
+
42
+ def create_error_message(*, message: str, error_code: ErrorCode) -> str:
43
+ return (
44
+ f"{message}\n"
45
+ "For troubleshooting, visit: https://docs.langchain.com/oss/python/langgraph/"
46
+ f"errors/{error_code.value}"
47
+ )
48
+
49
+
50
+ class GraphBubbleUp(Exception):
51
+ pass
52
+
53
+
54
+ class GraphDrained(GraphBubbleUp):
55
+ """Raised when a graph run exits early due to a drain request.
56
+
57
+ This indicates the graph stopped cooperatively at a superstep boundary
58
+ because `RunControl.request_drain()` was called (e.g., in response to
59
+ SIGTERM). The checkpoint is saved and the run can be resumed later.
60
+ """
61
+
62
+ def __init__(self, reason: str = "shutdown") -> None:
63
+ self.reason = reason
64
+ super().__init__(f"Graph drained: {reason}")
65
+
66
+
67
+ class GraphRecursionError(RecursionError):
68
+ """Raised when the graph has exhausted the maximum number of steps.
69
+
70
+ This prevents infinite loops. To increase the maximum number of steps,
71
+ run your graph with a config specifying a higher `recursion_limit`.
72
+
73
+ Troubleshooting guides:
74
+
75
+ - [`GRAPH_RECURSION_LIMIT`](https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT)
76
+
77
+ Examples:
78
+
79
+ graph = builder.compile()
80
+ graph.invoke(
81
+ {"messages": [("user", "Hello, world!")]},
82
+ # The config is the second positional argument
83
+ {"recursion_limit": 1000},
84
+ )
85
+ """
86
+
87
+ pass
88
+
89
+
90
+ class InvalidUpdateError(Exception):
91
+ """Raised when attempting to update a channel with an invalid set of updates.
92
+
93
+ Troubleshooting guides:
94
+
95
+ - [`INVALID_CONCURRENT_GRAPH_UPDATE`](https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE)
96
+ - [`INVALID_GRAPH_NODE_RETURN_VALUE`](https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE)
97
+ """
98
+
99
+ pass
100
+
101
+
102
+ class GraphInterrupt(GraphBubbleUp):
103
+ """Raised when a subgraph is interrupted, suppressed by the root graph.
104
+ Never raised directly, or surfaced to the user."""
105
+
106
+ def __init__(self, interrupts: Sequence[Interrupt] = ()) -> None:
107
+ super().__init__(interrupts)
108
+
109
+
110
+ @deprecated(
111
+ "NodeInterrupt is deprecated. Please use [`interrupt`][coco_graph.types.interrupt] instead.",
112
+ category=None,
113
+ )
114
+ class NodeInterrupt(GraphInterrupt):
115
+ """Raised by a node to interrupt execution."""
116
+
117
+ def __init__(self, value: Any, id: str | None = None) -> None:
118
+ warn(
119
+ "NodeInterrupt is deprecated. Please use `coco_graph.types.interrupt` instead.",
120
+ CocoGraphDeprecatedSinceV10,
121
+ stacklevel=2,
122
+ )
123
+ if id is None:
124
+ super().__init__([Interrupt(value=value)])
125
+ else:
126
+ super().__init__([Interrupt(value=value, id=id)])
127
+
128
+
129
+ class ParentCommand(GraphBubbleUp):
130
+ args: tuple[Command]
131
+
132
+ def __init__(self, command: Command) -> None:
133
+ super().__init__(command)
134
+
135
+
136
+ class EmptyInputError(Exception):
137
+ """Raised when graph receives an empty input."""
138
+
139
+ pass
140
+
141
+
142
+ class TaskNotFound(Exception):
143
+ """Raised when the executor is unable to find a task (for distributed mode)."""
144
+
145
+ pass
146
+
147
+
148
+ @dataclass(frozen=True, slots=True)
149
+ class NodeError:
150
+ """Failure context passed to a node-level error handler.
151
+
152
+ Inject by adding a parameter typed `NodeError` to a handler registered via
153
+ `StateGraph.add_node(..., error_handler=...)`:
154
+
155
+ ```python
156
+ def handler(state: State, error: NodeError) -> Command:
157
+ return Command(update={"status": f"recovered from {error.node}: {error.error}"})
158
+ ```
159
+ """
160
+
161
+ node: str
162
+ """Name of the node whose execution failed."""
163
+
164
+ error: BaseException
165
+ """Exception raised by the failed node."""
166
+
167
+
168
+ class NodeCancelledError(Exception):
169
+ """Raised when a node body raises ``asyncio.CancelledError`` itself.
170
+
171
+ ``asyncio.CancelledError`` is a ``BaseException`` and the pregel runner
172
+ treats cancelled task futures as silent tear-down (e.g. when it stops
173
+ sibling tasks after a peer fails). That is the correct behaviour for
174
+ *framework-initiated* cancellation, but a user node that raises
175
+ ``asyncio.CancelledError`` from its own body should surface as a node
176
+ failure, the same way any other exception would.
177
+
178
+ The retry layer converts user-raised ``asyncio.CancelledError`` into this
179
+ type so it flows through the normal error path and the run reports as
180
+ ``error`` instead of silently succeeding.
181
+ """
182
+
183
+ node: str
184
+
185
+ def __init__(self, node: str, message: str | None = None) -> None:
186
+ super().__init__(message or f"Node {node!r} raised asyncio.CancelledError")
187
+ self.node = node
188
+
189
+
190
+ class NodeTimeoutError(Exception):
191
+ """Raised when a node invocation exceeds one of its configured timeouts.
192
+
193
+ Does **not** inherit from the built-in `TimeoutError` (a subclass of
194
+ `OSError`) so that the default `RetryPolicy` treats it as retryable.
195
+
196
+ Both `idle_timeout` and `run_timeout` reflect the configured policy at the
197
+ time of the failure (each is `None` if not configured). `kind` and
198
+ `timeout` identify which one fired.
199
+ """
200
+
201
+ node: str
202
+ timeout: float
203
+ run_timeout: float | None
204
+ idle_timeout: float | None
205
+ elapsed: float
206
+ kind: Literal["idle", "run"]
207
+
208
+ def __init__(
209
+ self,
210
+ node: str,
211
+ elapsed: float,
212
+ *,
213
+ kind: Literal["idle", "run"],
214
+ idle_timeout: float | None = None,
215
+ run_timeout: float | None = None,
216
+ ) -> None:
217
+ if kind == "idle":
218
+ if idle_timeout is None:
219
+ raise ValueError("idle_timeout is required when kind='idle'")
220
+ message = (
221
+ f"Node '{node}' exceeded its idle timeout of "
222
+ f"{idle_timeout:.3f}s without making progress "
223
+ f"(elapsed: {elapsed:.3f}s)."
224
+ )
225
+ self.timeout = idle_timeout
226
+ elif kind == "run":
227
+ if run_timeout is None:
228
+ raise ValueError("run_timeout is required when kind='run'")
229
+ message = (
230
+ f"Node '{node}' exceeded its run timeout of "
231
+ f"{run_timeout:.3f}s (elapsed: {elapsed:.3f}s)."
232
+ )
233
+ self.timeout = run_timeout
234
+ else:
235
+ raise ValueError("kind must be 'idle' or 'run'")
236
+ super().__init__(message)
237
+ self.node = node
238
+ self.elapsed = elapsed
239
+ self.kind = kind
240
+ self.idle_timeout = idle_timeout
241
+ self.run_timeout = run_timeout