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,457 @@
1
+ """Runnable that selects which branch to run based on a condition."""
2
+
3
+ from collections.abc import (
4
+ AsyncIterator,
5
+ Awaitable,
6
+ Callable,
7
+ Iterator,
8
+ Mapping,
9
+ Sequence,
10
+ )
11
+ from typing import (
12
+ Any,
13
+ cast,
14
+ )
15
+
16
+ from pydantic import ConfigDict
17
+ from typing_extensions import override
18
+
19
+ from coco_core.runnables.base import (
20
+ Runnable,
21
+ RunnableLike,
22
+ RunnableSerializable,
23
+ coerce_to_runnable,
24
+ )
25
+ from coco_core.runnables.config import (
26
+ RunnableConfig,
27
+ ensure_config,
28
+ get_async_callback_manager_for_config,
29
+ get_callback_manager_for_config,
30
+ patch_config,
31
+ )
32
+ from coco_core.runnables.utils import (
33
+ ConfigurableFieldSpec,
34
+ Input,
35
+ Output,
36
+ get_unique_config_specs,
37
+ )
38
+ from coco_core.utils.pydantic import TypeBaseModel
39
+
40
+ _MIN_BRANCHES = 2
41
+
42
+
43
+ class RunnableBranch(RunnableSerializable[Input, Output]):
44
+ """`Runnable` that selects which branch to run based on a condition.
45
+
46
+ The `Runnable` is initialized with a list of `(condition, Runnable)` pairs and
47
+ a default branch.
48
+
49
+ When operating on an input, the first condition that evaluates to True is
50
+ selected, and the corresponding `Runnable` is run on the input.
51
+
52
+ If no condition evaluates to `True`, the default branch is run on the input.
53
+
54
+ Examples:
55
+ ```python
56
+ from coco_core.runnables import RunnableBranch
57
+
58
+ branch = RunnableBranch(
59
+ (lambda x: isinstance(x, str), lambda x: x.upper()),
60
+ (lambda x: isinstance(x, int), lambda x: x + 1),
61
+ (lambda x: isinstance(x, float), lambda x: x * 2),
62
+ lambda x: "goodbye",
63
+ )
64
+
65
+ branch.invoke("hello") # "HELLO"
66
+ branch.invoke(None) # "goodbye"
67
+ ```
68
+ """
69
+
70
+ branches: Sequence[tuple[Runnable[Input, bool], Runnable[Input, Output]]]
71
+ """A list of `(condition, Runnable)` pairs."""
72
+ default: Runnable[Input, Output]
73
+ """A `Runnable` to run if no condition is met."""
74
+
75
+ def __init__(
76
+ self,
77
+ *branches: tuple[
78
+ Runnable[Input, bool]
79
+ | Callable[[Input], bool]
80
+ | Callable[[Input], Awaitable[bool]],
81
+ RunnableLike[Input, Output],
82
+ ]
83
+ | RunnableLike[Input, Output],
84
+ ) -> None:
85
+ """A `Runnable` that runs one of two branches based on a condition.
86
+
87
+ Args:
88
+ *branches: A list of `(condition, Runnable)` pairs.
89
+ Defaults a `Runnable` to run if no condition is met.
90
+
91
+ Raises:
92
+ ValueError: If the number of branches is less than `2`.
93
+ TypeError: If the default branch is not `Runnable`, `Callable` or `Mapping`.
94
+ TypeError: If a branch is not a `tuple` or `list`.
95
+ ValueError: If a branch is not of length `2`.
96
+ """
97
+ if len(branches) < _MIN_BRANCHES:
98
+ msg = "RunnableBranch requires at least two branches"
99
+ raise ValueError(msg)
100
+
101
+ default = branches[-1]
102
+
103
+ if not isinstance(
104
+ default,
105
+ (Runnable, Callable, Mapping), # type: ignore[arg-type]
106
+ ):
107
+ msg = "RunnableBranch default must be Runnable, callable or mapping."
108
+ raise TypeError(msg)
109
+
110
+ default_ = coerce_to_runnable(cast("Runnable[Input, Output]", default))
111
+
112
+ branches_ = []
113
+
114
+ for branch in branches[:-1]:
115
+ if not isinstance(branch, (tuple, list)):
116
+ msg = (
117
+ f"RunnableBranch branches must be "
118
+ f"tuples or lists, not {type(branch)}"
119
+ )
120
+ raise TypeError(msg)
121
+
122
+ if len(branch) != _MIN_BRANCHES:
123
+ msg = (
124
+ f"RunnableBranch branches must be "
125
+ f"tuples or lists of length 2, not {len(branch)}"
126
+ )
127
+ raise ValueError(msg)
128
+ condition, runnable = branch
129
+ condition = cast("Runnable[Input, bool]", coerce_to_runnable(condition))
130
+ runnable = coerce_to_runnable(cast("Runnable[Input, Output]", runnable))
131
+ branches_.append((condition, runnable))
132
+
133
+ super().__init__(
134
+ branches=branches_,
135
+ default=default_,
136
+ )
137
+
138
+ model_config = ConfigDict(
139
+ arbitrary_types_allowed=True,
140
+ )
141
+
142
+ @classmethod
143
+ def is_lc_serializable(cls) -> bool:
144
+ """Return `True` as this class is serializable."""
145
+ return True
146
+
147
+ @classmethod
148
+ @override
149
+ def get_lc_namespace(cls) -> list[str]:
150
+ """Get the namespace of the Coco object.
151
+
152
+ Returns:
153
+ `["coco", "schema", "runnable"]`
154
+ """
155
+ return ["coco", "schema", "runnable"]
156
+
157
+ @override
158
+ def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
159
+ runnables = (
160
+ [self.default]
161
+ + [r for _, r in self.branches]
162
+ + [r for r, _ in self.branches]
163
+ )
164
+
165
+ for runnable in runnables:
166
+ if runnable.get_input_jsonschema(config).get("type") is not None:
167
+ return runnable.get_input_schema(config)
168
+
169
+ return super().get_input_schema(config)
170
+
171
+ @property
172
+ @override
173
+ def config_specs(self) -> list[ConfigurableFieldSpec]:
174
+ return get_unique_config_specs(
175
+ spec
176
+ for step in (
177
+ [self.default]
178
+ + [r for _, r in self.branches]
179
+ + [r for r, _ in self.branches]
180
+ )
181
+ for spec in step.config_specs
182
+ )
183
+
184
+ @override
185
+ def invoke(
186
+ self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
187
+ ) -> Output:
188
+ """First evaluates the condition, then delegate to `True` or `False` branch.
189
+
190
+ Args:
191
+ input: The input to the `Runnable`.
192
+ config: The configuration for the `Runnable`.
193
+ **kwargs: Additional keyword arguments to pass to the `Runnable`.
194
+
195
+ Returns:
196
+ The output of the branch that was run.
197
+ """
198
+ config = ensure_config(config)
199
+ callback_manager = get_callback_manager_for_config(config)
200
+ run_manager = callback_manager.on_chain_start(
201
+ None,
202
+ input,
203
+ name=config.get("run_name") or self.get_name(),
204
+ run_id=config.pop("run_id", None),
205
+ )
206
+
207
+ try:
208
+ for idx, branch in enumerate(self.branches):
209
+ condition, runnable = branch
210
+
211
+ expression_value = condition.invoke(
212
+ input,
213
+ config=patch_config(
214
+ config,
215
+ callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
216
+ ),
217
+ )
218
+
219
+ if expression_value:
220
+ output = runnable.invoke(
221
+ input,
222
+ config=patch_config(
223
+ config,
224
+ callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
225
+ ),
226
+ **kwargs,
227
+ )
228
+ break
229
+ else:
230
+ output = self.default.invoke(
231
+ input,
232
+ config=patch_config(
233
+ config, callbacks=run_manager.get_child(tag="branch:default")
234
+ ),
235
+ **kwargs,
236
+ )
237
+ except BaseException as e:
238
+ run_manager.on_chain_error(e)
239
+ raise
240
+ run_manager.on_chain_end(output)
241
+ return output
242
+
243
+ @override
244
+ async def ainvoke(
245
+ self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
246
+ ) -> Output:
247
+ config = ensure_config(config)
248
+ callback_manager = get_async_callback_manager_for_config(config)
249
+ run_manager = await callback_manager.on_chain_start(
250
+ None,
251
+ input,
252
+ name=config.get("run_name") or self.get_name(),
253
+ run_id=config.pop("run_id", None),
254
+ )
255
+ try:
256
+ for idx, branch in enumerate(self.branches):
257
+ condition, runnable = branch
258
+
259
+ expression_value = await condition.ainvoke(
260
+ input,
261
+ config=patch_config(
262
+ config,
263
+ callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
264
+ ),
265
+ )
266
+
267
+ if expression_value:
268
+ output = await runnable.ainvoke(
269
+ input,
270
+ config=patch_config(
271
+ config,
272
+ callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
273
+ ),
274
+ **kwargs,
275
+ )
276
+ break
277
+ else:
278
+ output = await self.default.ainvoke(
279
+ input,
280
+ config=patch_config(
281
+ config, callbacks=run_manager.get_child(tag="branch:default")
282
+ ),
283
+ **kwargs,
284
+ )
285
+ except BaseException as e:
286
+ await run_manager.on_chain_error(e)
287
+ raise
288
+ await run_manager.on_chain_end(output)
289
+ return output
290
+
291
+ @override
292
+ def stream(
293
+ self,
294
+ input: Input,
295
+ config: RunnableConfig | None = None,
296
+ **kwargs: Any | None,
297
+ ) -> Iterator[Output]:
298
+ """First evaluates the condition, then delegate to `True` or `False` branch.
299
+
300
+ Args:
301
+ input: The input to the `Runnable`.
302
+ config: The configuration for the `Runnable`.
303
+ **kwargs: Additional keyword arguments to pass to the `Runnable`.
304
+
305
+ Yields:
306
+ The output of the branch that was run.
307
+ """
308
+ config = ensure_config(config)
309
+ callback_manager = get_callback_manager_for_config(config)
310
+ run_manager = callback_manager.on_chain_start(
311
+ None,
312
+ input,
313
+ name=config.get("run_name") or self.get_name(),
314
+ run_id=config.pop("run_id", None),
315
+ )
316
+ final_output: Output | None = None
317
+ final_output_supported = True
318
+
319
+ try:
320
+ for idx, branch in enumerate(self.branches):
321
+ condition, runnable = branch
322
+
323
+ expression_value = condition.invoke(
324
+ input,
325
+ config=patch_config(
326
+ config,
327
+ callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
328
+ ),
329
+ )
330
+
331
+ if expression_value:
332
+ for chunk in runnable.stream(
333
+ input,
334
+ config=patch_config(
335
+ config,
336
+ callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
337
+ ),
338
+ **kwargs,
339
+ ):
340
+ yield chunk
341
+ if final_output_supported:
342
+ if final_output is None:
343
+ final_output = chunk
344
+ else:
345
+ try:
346
+ final_output = final_output + chunk # type: ignore[operator]
347
+ except TypeError:
348
+ final_output = None
349
+ final_output_supported = False
350
+ break
351
+ else:
352
+ for chunk in self.default.stream(
353
+ input,
354
+ config=patch_config(
355
+ config,
356
+ callbacks=run_manager.get_child(tag="branch:default"),
357
+ ),
358
+ **kwargs,
359
+ ):
360
+ yield chunk
361
+ if final_output_supported:
362
+ if final_output is None:
363
+ final_output = chunk
364
+ else:
365
+ try:
366
+ final_output = final_output + chunk # type: ignore[operator]
367
+ except TypeError:
368
+ final_output = None
369
+ final_output_supported = False
370
+ except BaseException as e:
371
+ run_manager.on_chain_error(e)
372
+ raise
373
+ run_manager.on_chain_end(final_output)
374
+
375
+ @override
376
+ async def astream(
377
+ self,
378
+ input: Input,
379
+ config: RunnableConfig | None = None,
380
+ **kwargs: Any | None,
381
+ ) -> AsyncIterator[Output]:
382
+ """First evaluates the condition, then delegate to `True` or `False` branch.
383
+
384
+ Args:
385
+ input: The input to the `Runnable`.
386
+ config: The configuration for the `Runnable`.
387
+ **kwargs: Additional keyword arguments to pass to the `Runnable`.
388
+
389
+ Yields:
390
+ The output of the branch that was run.
391
+ """
392
+ config = ensure_config(config)
393
+ callback_manager = get_async_callback_manager_for_config(config)
394
+ run_manager = await callback_manager.on_chain_start(
395
+ None,
396
+ input,
397
+ name=config.get("run_name") or self.get_name(),
398
+ run_id=config.pop("run_id", None),
399
+ )
400
+ final_output: Output | None = None
401
+ final_output_supported = True
402
+
403
+ try:
404
+ for idx, branch in enumerate(self.branches):
405
+ condition, runnable = branch
406
+
407
+ expression_value = await condition.ainvoke(
408
+ input,
409
+ config=patch_config(
410
+ config,
411
+ callbacks=run_manager.get_child(tag=f"condition:{idx + 1}"),
412
+ ),
413
+ )
414
+
415
+ if expression_value:
416
+ async for chunk in runnable.astream(
417
+ input,
418
+ config=patch_config(
419
+ config,
420
+ callbacks=run_manager.get_child(tag=f"branch:{idx + 1}"),
421
+ ),
422
+ **kwargs,
423
+ ):
424
+ yield chunk
425
+ if final_output_supported:
426
+ if final_output is None:
427
+ final_output = chunk
428
+ else:
429
+ try:
430
+ final_output = final_output + chunk # type: ignore[operator]
431
+ except TypeError:
432
+ final_output = None
433
+ final_output_supported = False
434
+ break
435
+ else:
436
+ async for chunk in self.default.astream(
437
+ input,
438
+ config=patch_config(
439
+ config,
440
+ callbacks=run_manager.get_child(tag="branch:default"),
441
+ ),
442
+ **kwargs,
443
+ ):
444
+ yield chunk
445
+ if final_output_supported:
446
+ if final_output is None:
447
+ final_output = chunk
448
+ else:
449
+ try:
450
+ final_output = final_output + chunk # type: ignore[operator]
451
+ except TypeError:
452
+ final_output = None
453
+ final_output_supported = False
454
+ except BaseException as e:
455
+ await run_manager.on_chain_error(e)
456
+ raise
457
+ await run_manager.on_chain_end(final_output)