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,357 @@
1
+ """Asynchronous iterator utilities.
2
+
3
+ Adapted from
4
+ https://github.com/maxfischer2781/asyncstdlib/blob/master/asyncstdlib/itertools.py
5
+ MIT License.
6
+ """
7
+
8
+ from collections import deque
9
+ from collections.abc import (
10
+ AsyncGenerator,
11
+ AsyncIterable,
12
+ AsyncIterator,
13
+ Awaitable,
14
+ Callable,
15
+ Iterator,
16
+ )
17
+ from contextlib import AbstractAsyncContextManager
18
+ from types import TracebackType
19
+ from typing import (
20
+ Any,
21
+ Generic,
22
+ TypeVar,
23
+ cast,
24
+ overload,
25
+ )
26
+
27
+ from typing_extensions import override
28
+
29
+ from coco_core._api.deprecation import deprecated
30
+
31
+ T = TypeVar("T")
32
+
33
+ _no_default = object()
34
+
35
+
36
+ # https://github.com/python/cpython/blob/main/Lib/test/test_asyncgen.py#L54
37
+ @deprecated(since="1.1.2", removal="2.0.0")
38
+ def py_anext(
39
+ iterator: AsyncIterator[T], default: T | Any = _no_default
40
+ ) -> Awaitable[T | Any | None]:
41
+ """Pure-Python implementation of `anext()` for testing purposes.
42
+
43
+ Closely matches the builtin `anext()` C implementation.
44
+
45
+ Can be used to compare the built-in implementation of the inner coroutines machinery
46
+ to C-implementation of `__anext__()` and `send()` or `throw()` on the returned
47
+ generator.
48
+
49
+ Args:
50
+ iterator: The async iterator to advance.
51
+ default: The value to return if the iterator is exhausted.
52
+
53
+ If not provided, a `StopAsyncIteration` exception is raised.
54
+
55
+ Returns:
56
+ The next value from the iterator, or the default value if the iterator is
57
+ exhausted.
58
+
59
+ Raises:
60
+ TypeError: If the iterator is not an async iterator.
61
+ """
62
+ try:
63
+ __anext__ = cast(
64
+ "Callable[[AsyncIterator[T]], Awaitable[T]]", type(iterator).__anext__
65
+ )
66
+ except AttributeError as e:
67
+ msg = f"{iterator!r} is not an async iterator"
68
+ raise TypeError(msg) from e
69
+
70
+ if default is _no_default:
71
+ return __anext__(iterator)
72
+
73
+ async def anext_impl() -> T | Any:
74
+ try:
75
+ # The C code is way more low-level than this, as it implements
76
+ # all methods of the iterator protocol. In this implementation
77
+ # we're relying on higher-level coroutine concepts, but that's
78
+ # exactly what we want -- crosstest pure-Python high-level
79
+ # implementation and low-level C anext() iterators.
80
+ return await __anext__(iterator)
81
+ except StopAsyncIteration:
82
+ return default
83
+
84
+ return anext_impl()
85
+
86
+
87
+ class NoLock:
88
+ """Dummy lock that provides the proper interface but no protection."""
89
+
90
+ async def __aenter__(self) -> None:
91
+ """Do nothing."""
92
+
93
+ async def __aexit__(
94
+ self,
95
+ exc_type: type[BaseException] | None,
96
+ exc_val: BaseException | None,
97
+ exc_tb: TracebackType | None,
98
+ ) -> bool:
99
+ """Return False, exception not suppressed."""
100
+ return False
101
+
102
+
103
+ async def tee_peer(
104
+ iterator: AsyncIterator[T],
105
+ # the buffer specific to this peer
106
+ buffer: deque[T],
107
+ # the buffers of all peers, including our own
108
+ peers: list[deque[T]],
109
+ lock: AbstractAsyncContextManager[Any],
110
+ ) -> AsyncGenerator[T, None]:
111
+ """An individual iterator of a `tee`.
112
+
113
+ This function is a generator that yields items from the shared iterator
114
+ `iterator`. It buffers items until the least advanced iterator has yielded them as
115
+ well.
116
+
117
+ The buffer is shared with all other peers.
118
+
119
+ Args:
120
+ iterator: The shared iterator.
121
+ buffer: The buffer for this peer.
122
+ peers: The buffers of all peers.
123
+ lock: The lock to synchronise access to the shared buffers.
124
+
125
+ Yields:
126
+ The next item from the shared iterator.
127
+ """
128
+ try:
129
+ while True:
130
+ if not buffer:
131
+ async with lock:
132
+ # Another peer produced an item while we were waiting for the lock.
133
+ # Proceed with the next loop iteration to yield the item.
134
+ if buffer:
135
+ continue
136
+ try:
137
+ item = await anext(iterator)
138
+ except StopAsyncIteration:
139
+ break
140
+ else:
141
+ # Append to all buffers, including our own. We'll fetch our
142
+ # item from the buffer again, instead of yielding it directly.
143
+ # This ensures the proper item ordering if any of our peers
144
+ # are fetching items concurrently. They may have buffered their
145
+ # item already.
146
+ for peer_buffer in peers:
147
+ peer_buffer.append(item)
148
+ yield buffer.popleft()
149
+ finally:
150
+ async with lock:
151
+ # this peer is done - remove its buffer
152
+ for idx, peer_buffer in enumerate(peers): # pragma: no branch
153
+ if peer_buffer is buffer:
154
+ peers.pop(idx)
155
+ break
156
+ # if we are the last peer, try and close the iterator
157
+ if not peers and hasattr(iterator, "aclose"):
158
+ await iterator.aclose()
159
+
160
+
161
+ class Tee(Generic[T]):
162
+ """Create `n` separate asynchronous iterators over `iterable`.
163
+
164
+ This splits a single `iterable` into multiple iterators, each providing
165
+ the same items in the same order.
166
+
167
+ All child iterators may advance separately but share the same items from `iterable`
168
+ -- when the most advanced iterator retrieves an item, it is buffered until the least
169
+ advanced iterator has yielded it as well.
170
+
171
+ A `tee` works lazily and can handle an infinite `iterable`, provided
172
+ that all iterators advance.
173
+
174
+ ```python
175
+ async def derivative(sensor_data):
176
+ previous, current = a.tee(sensor_data, n=2)
177
+ await a.anext(previous) # advance one iterator
178
+ return a.map(operator.sub, previous, current)
179
+ ```
180
+
181
+ Unlike `itertools.tee`, `.tee` returns a custom type instead of a `tuple`. Like a
182
+ tuple, it can be indexed, iterated and unpacked to get the child iterators. In
183
+ addition, its `.tee.aclose` method immediately closes all children, and it can be
184
+ used in an `async with` context for the same effect.
185
+
186
+ If `iterable` is an iterator and read elsewhere, `tee` will *not* provide these
187
+ items. Also, `tee` must internally buffer each item until the last iterator has
188
+ yielded it; if the most and least advanced iterator differ by most data, using a
189
+ `list` is more efficient (but not lazy).
190
+
191
+ If the underlying iterable is concurrency safe (`anext` may be awaited concurrently)
192
+ the resulting iterators are concurrency safe as well. Otherwise, the iterators are
193
+ safe if there is only ever one single "most advanced" iterator.
194
+
195
+ To enforce sequential use of `anext`, provide a `lock`
196
+
197
+ - e.g. an `asyncio.Lock` instance in an `asyncio` application - and access is
198
+ automatically synchronised.
199
+
200
+ """
201
+
202
+ def __init__(
203
+ self,
204
+ iterable: AsyncIterator[T],
205
+ n: int = 2,
206
+ *,
207
+ lock: AbstractAsyncContextManager[Any] | None = None,
208
+ ):
209
+ """Create a `tee`.
210
+
211
+ Args:
212
+ iterable: The iterable to split.
213
+ n: The number of iterators to create.
214
+ lock: The lock to synchronise access to the shared buffers.
215
+
216
+ """
217
+ self._iterator = aiter(iterable)
218
+ self._buffers: list[deque[T]] = [deque() for _ in range(n)]
219
+ self._children = tuple(
220
+ tee_peer(
221
+ iterator=self._iterator,
222
+ buffer=buffer,
223
+ peers=self._buffers,
224
+ lock=lock if lock is not None else NoLock(),
225
+ )
226
+ for buffer in self._buffers
227
+ )
228
+
229
+ def __len__(self) -> int:
230
+ """Return the number of child iterators."""
231
+ return len(self._children)
232
+
233
+ @overload
234
+ def __getitem__(self, item: int) -> AsyncIterator[T]: ...
235
+
236
+ @overload
237
+ def __getitem__(self, item: slice) -> tuple[AsyncIterator[T], ...]: ...
238
+
239
+ def __getitem__(
240
+ self, item: int | slice
241
+ ) -> AsyncIterator[T] | tuple[AsyncIterator[T], ...]:
242
+ """Return the child iterator(s) for the given index or slice."""
243
+ return self._children[item]
244
+
245
+ def __iter__(self) -> Iterator[AsyncIterator[T]]:
246
+ """Iterate over the child iterators.
247
+
248
+ Yields:
249
+ The child iterators.
250
+ """
251
+ yield from self._children
252
+
253
+ async def __aenter__(self) -> "Tee[T]":
254
+ """Return the tee instance."""
255
+ return self
256
+
257
+ async def __aexit__(
258
+ self,
259
+ exc_type: type[BaseException] | None,
260
+ exc_val: BaseException | None,
261
+ exc_tb: TracebackType | None,
262
+ ) -> bool:
263
+ """Close all child iterators.
264
+
265
+ Returns:
266
+ `False`, exceptions not suppressed.
267
+ """
268
+ await self.aclose()
269
+ return False
270
+
271
+ async def aclose(self) -> None:
272
+ """Async close all child iterators."""
273
+ for child in self._children:
274
+ await child.aclose()
275
+
276
+
277
+ atee = Tee
278
+
279
+
280
+ class aclosing(AbstractAsyncContextManager[Any]): # noqa: N801
281
+ """Async context manager to wrap an `AsyncGenerator` that has a `aclose()` method.
282
+
283
+ Code like this:
284
+
285
+ ```python
286
+ async with aclosing(<module>.fetch(<arguments>)) as agen:
287
+ <block>
288
+ ```
289
+
290
+ ...is equivalent to this:
291
+
292
+ ```python
293
+ agen = <module>.fetch(<arguments>)
294
+ try:
295
+ <block>
296
+ finally:
297
+ await agen.aclose()
298
+
299
+ ```
300
+ """
301
+
302
+ def __init__(self, thing: AsyncGenerator[Any, Any] | AsyncIterator[Any]) -> None:
303
+ """Create the context manager.
304
+
305
+ Args:
306
+ thing: The resource to wrap.
307
+ """
308
+ self.thing = thing
309
+
310
+ @override
311
+ async def __aenter__(self) -> AsyncGenerator[Any, Any] | AsyncIterator[Any]:
312
+ return self.thing
313
+
314
+ @override
315
+ async def __aexit__(
316
+ self,
317
+ exc_type: type[BaseException] | None,
318
+ exc_value: BaseException | None,
319
+ traceback: TracebackType | None,
320
+ ) -> None:
321
+ if hasattr(self.thing, "aclose"):
322
+ await self.thing.aclose()
323
+
324
+
325
+ async def abatch_iterate(
326
+ size: int | None, iterable: AsyncIterable[T]
327
+ ) -> AsyncIterator[list[T]]:
328
+ """Utility batching function for async iterables.
329
+
330
+ Args:
331
+ size: The size of the batch.
332
+
333
+ If `None`, returns a single batch.
334
+ iterable: The async iterable to batch.
335
+
336
+ Yields:
337
+ The batches.
338
+
339
+ Raises:
340
+ ValueError: If `size` is not `None` and is not a positive integer.
341
+ """
342
+ if size is None:
343
+ single_batch = [el async for el in iterable]
344
+ if single_batch:
345
+ yield single_batch
346
+ return
347
+ if size <= 0:
348
+ msg = f"Batch size must be a positive integer, got {size}."
349
+ raise ValueError(msg)
350
+ batch: list[T] = []
351
+ async for element in iterable:
352
+ batch.append(element)
353
+ if len(batch) >= size:
354
+ yield batch
355
+ batch = []
356
+ if batch:
357
+ yield batch
coco_core/utils/env.py ADDED
@@ -0,0 +1,86 @@
1
+ """Utilities for environment variables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+
9
+ def env_var_is_set(env_var: str) -> bool:
10
+ """Check if an environment variable is set.
11
+
12
+ Args:
13
+ env_var: The name of the environment variable.
14
+
15
+ Returns:
16
+ `True` if the environment variable is set, `False` otherwise.
17
+ """
18
+ return env_var in os.environ and os.environ[env_var] not in {
19
+ "",
20
+ "0",
21
+ "false",
22
+ "False",
23
+ }
24
+
25
+
26
+ def get_from_dict_or_env(
27
+ data: dict[str, Any],
28
+ key: str | list[str],
29
+ env_key: str,
30
+ default: str | None = None,
31
+ ) -> str:
32
+ """Get a value from a dictionary or an environment variable.
33
+
34
+ Args:
35
+ data: The dictionary to look up the key in.
36
+ key: The key to look up in the dictionary.
37
+
38
+ This can be a list of keys to try in order.
39
+ env_key: The environment variable to look up if the key is not
40
+ in the dictionary.
41
+ default: The default value to return if the key is not in the dictionary
42
+ or the environment.
43
+
44
+ Returns:
45
+ The dict value or the environment variable value.
46
+ """
47
+ if isinstance(key, (list, tuple)):
48
+ for k in key:
49
+ if value := data.get(k):
50
+ return str(value)
51
+
52
+ if isinstance(key, str) and key in data and data[key]:
53
+ return str(data[key])
54
+
55
+ key_for_err = key[0] if isinstance(key, (list, tuple)) else key
56
+
57
+ return get_from_env(key_for_err, env_key, default=default)
58
+
59
+
60
+ def get_from_env(key: str, env_key: str, default: str | None = None) -> str:
61
+ """Get a value from a dictionary or an environment variable.
62
+
63
+ Args:
64
+ key: The key to look up in the dictionary.
65
+ env_key: The environment variable to look up if the key is not
66
+ in the dictionary.
67
+ default: The default value to return if the key is not in the dictionary
68
+ or the environment.
69
+
70
+ Returns:
71
+ The value of the key.
72
+
73
+ Raises:
74
+ ValueError: If the key is not in the dictionary and no default value is
75
+ provided or if the environment variable is not set.
76
+ """
77
+ if env_value := os.getenv(env_key):
78
+ return env_value
79
+ if default is not None:
80
+ return default
81
+ msg = (
82
+ f"Did not find {key}, please add an environment variable"
83
+ f" `{env_key}` which contains it, or pass"
84
+ f" `{key}` as a named parameter."
85
+ )
86
+ raise ValueError(msg)
@@ -0,0 +1,80 @@
1
+ """Utilities for formatting strings."""
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from string import Formatter
5
+ from typing import Any
6
+
7
+
8
+ class StrictFormatter(Formatter):
9
+ """A string formatter that enforces keyword-only argument substitution.
10
+
11
+ This formatter extends Python's built-in `string.Formatter` to provide stricter
12
+ validation for prompt template formatting. It ensures that all variable
13
+ substitutions use keyword arguments rather than positional arguments, which improves
14
+ clarity and reduces errors when formatting prompt templates.
15
+
16
+ Example:
17
+ >>> fmt = StrictFormatter()
18
+ >>> fmt.format("Hello, {name}!", name="World")
19
+ 'Hello, World!'
20
+ >>> fmt.format("Hello, {}!", "World") # Raises ValueError
21
+ """
22
+
23
+ def vformat(
24
+ self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
25
+ ) -> str:
26
+ """Format a string using only keyword arguments.
27
+
28
+ Overrides the base `vformat` to reject positional arguments, ensuring all
29
+ substitutions are explicit and named.
30
+
31
+ Args:
32
+ format_string: A string containing replacement fields (e.g., `'{name}'`).
33
+ args: Positional arguments (must be empty).
34
+ kwargs: Keyword arguments for substitution into the format string.
35
+
36
+ Returns:
37
+ The formatted string with all replacement fields substituted.
38
+
39
+ Raises:
40
+ ValueError: If any positional arguments are provided.
41
+ """
42
+ if len(args) > 0:
43
+ msg = (
44
+ "No arguments should be provided, "
45
+ "everything should be passed as keyword arguments."
46
+ )
47
+ raise ValueError(msg)
48
+ return super().vformat(format_string, args, kwargs)
49
+
50
+ def validate_input_variables(
51
+ self, format_string: str, input_variables: list[str]
52
+ ) -> None:
53
+ """Validate that input variables match the placeholders in a format string.
54
+
55
+ Checks that the provided input variables can be used to format the given string
56
+ without missing or extra keys. This is useful for validating prompt templates
57
+ before runtime.
58
+
59
+ Args:
60
+ format_string: A string containing replacement fields to validate
61
+ against (e.g., `'Hello, {name}!'`).
62
+ input_variables: List of variable names expected to fill the
63
+ replacement fields.
64
+
65
+ Raises:
66
+ KeyError: If the format string contains placeholders not present
67
+ in input_variables.
68
+
69
+ Example:
70
+ >>> fmt = StrictFormatter()
71
+ >>> fmt.validate_input_variables("Hello, {name}!", ["name"]) # OK
72
+ >>> fmt.validate_input_variables("Hello, {name}!", ["other"]) # Raises
73
+ """
74
+ dummy_inputs = dict.fromkeys(input_variables, "foo")
75
+ super().format(format_string, **dummy_inputs)
76
+
77
+
78
+ #: Default StrictFormatter instance for use throughout Coco.
79
+ #: Used internally for formatting prompt templates with named variables.
80
+ formatter = StrictFormatter()