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,969 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import enum
5
+ import inspect
6
+ import logging
7
+ import sys
8
+ import warnings
9
+ from collections.abc import (
10
+ AsyncIterator,
11
+ Awaitable,
12
+ Callable,
13
+ Coroutine,
14
+ Generator,
15
+ Iterator,
16
+ Sequence,
17
+ )
18
+ from contextlib import AsyncExitStack, contextmanager
19
+ from contextvars import Context, Token, copy_context
20
+ from functools import partial, wraps
21
+ from typing import (
22
+ Any,
23
+ Optional,
24
+ Protocol,
25
+ TypeGuard,
26
+ cast,
27
+ )
28
+
29
+ from coco_core.runnables.base import (
30
+ Runnable,
31
+ RunnableConfig,
32
+ RunnableLambda,
33
+ RunnableParallel,
34
+ RunnableSequence,
35
+ )
36
+ from coco_core.runnables.base import (
37
+ RunnableLike as LCRunnableLike,
38
+ )
39
+ from coco_core.runnables.config import (
40
+ run_in_executor,
41
+ var_child_runnable_config,
42
+ )
43
+ from coco_core.runnables.utils import Input, Output
44
+ from coco_core.tracers.coco import CocoTracer
45
+ from coco_graph.store.base import BaseStore
46
+
47
+ from coco_graph._internal._config import (
48
+ ensure_config,
49
+ get_async_callback_manager_for_config,
50
+ get_callback_manager_for_config,
51
+ patch_config,
52
+ )
53
+ from coco_graph._internal._constants import (
54
+ CONF,
55
+ CONFIG_KEY_NODE_ERROR,
56
+ CONFIG_KEY_RUNTIME,
57
+ )
58
+ from coco_graph._internal._typing import MISSING
59
+ from coco_graph.errors import NodeError
60
+ from coco_graph.types import StreamWriter
61
+
62
+ try:
63
+ from coco_core.tracers._streaming import _StreamingCallbackHandler
64
+ except ImportError:
65
+ _StreamingCallbackHandler = None # type: ignore
66
+
67
+ logger = logging.getLogger(__name__)
68
+
69
+
70
+ def _trace_payload(value: Any, transform: Callable[[Any], Any] | None) -> Any:
71
+ """Return the payload to record on a run for `value`.
72
+
73
+ When `transform` is unset this is a passthrough, so unspecified nodes record exactly
74
+ as before. When set it always runs (regardless of tracing), but never affects
75
+ execution: if it raises, the untransformed value is recorded instead.
76
+ """
77
+ if transform is None:
78
+ return value
79
+ try:
80
+ return transform(value)
81
+ except Exception:
82
+ logger.exception(
83
+ "trace input/output processor raised; recording untransformed payload"
84
+ )
85
+ return value
86
+
87
+
88
+ def _set_config_context(
89
+ config: RunnableConfig, run: Any = None
90
+ ) -> Token[RunnableConfig | None]:
91
+ """Set the child Runnable config + tracing context.
92
+
93
+ Args:
94
+ config: The config to set.
95
+ """
96
+ config_token = var_child_runnable_config.set(config)
97
+ if run is not None:
98
+ from coco_core.tracers._context import _set_tracing_context
99
+
100
+ _set_tracing_context({"parent": run})
101
+ return config_token
102
+
103
+
104
+ def _unset_config_context(token: Token[RunnableConfig | None], run: Any = None) -> None:
105
+ """Set the child Runnable config + tracing context.
106
+
107
+ Args:
108
+ token: The config token to reset.
109
+ """
110
+ var_child_runnable_config.reset(token)
111
+ if run is not None:
112
+ from coco_core.tracers._context import _set_tracing_context
113
+
114
+ _set_tracing_context(
115
+ {
116
+ "parent": None,
117
+ "project_name": None,
118
+ "tags": None,
119
+ "metadata": None,
120
+ "enabled": None,
121
+ "client": None,
122
+ }
123
+ )
124
+
125
+
126
+ @contextmanager
127
+ def set_config_context(
128
+ config: RunnableConfig, run: Any = None
129
+ ) -> Generator[Context, None, None]:
130
+ """Set the child Runnable config + tracing context.
131
+
132
+ Args:
133
+ config: The config to set.
134
+ """
135
+ ctx = copy_context()
136
+ config_token = ctx.run(_set_config_context, config, run)
137
+ try:
138
+ yield ctx
139
+ finally:
140
+ ctx.run(_unset_config_context, config_token, run)
141
+
142
+
143
+ def create_task_in_config_context(
144
+ coro_factory: Callable[[], Coroutine[Any, Any, Any]], config: RunnableConfig
145
+ ) -> asyncio.Task[Any]:
146
+ """Create an asyncio.Task that inherits `config` as the child runnable context.
147
+
148
+ `asyncio.create_task` snapshots the current contextvars onto the new task,
149
+ so calling `create_task` while the config context is set ensures the task
150
+ sees `config` via `var_child_runnable_config` and any tracing parent.
151
+ """
152
+ with set_config_context(config) as context:
153
+ return context.run(lambda: asyncio.create_task(coro_factory()))
154
+
155
+
156
+ # Before Python 3.11 native StrEnum is not available
157
+ class StrEnum(str, enum.Enum):
158
+ """A string enum."""
159
+
160
+
161
+ # Special type to denote any type is accepted
162
+ ANY_TYPE = object()
163
+
164
+ ASYNCIO_ACCEPTS_CONTEXT = sys.version_info >= (3, 11)
165
+
166
+ # List of keyword arguments that can be injected into nodes / tasks / tools at runtime.
167
+ # A named argument may appear multiple times if it appears with distinct types.
168
+ KWARGS_CONFIG_KEYS: tuple[tuple[str, tuple[Any, ...], str, Any], ...] = (
169
+ (
170
+ "config",
171
+ (
172
+ RunnableConfig,
173
+ "RunnableConfig",
174
+ Optional[RunnableConfig], # noqa: UP045
175
+ "Optional[RunnableConfig]",
176
+ inspect.Parameter.empty,
177
+ ),
178
+ # for now, use config directly, eventually, will pop off of Runtime
179
+ "N/A",
180
+ inspect.Parameter.empty,
181
+ ),
182
+ (
183
+ "writer",
184
+ (StreamWriter, "StreamWriter", inspect.Parameter.empty),
185
+ "stream_writer",
186
+ lambda _: None,
187
+ ),
188
+ (
189
+ "store",
190
+ (
191
+ BaseStore,
192
+ "BaseStore",
193
+ inspect.Parameter.empty,
194
+ ),
195
+ "store",
196
+ inspect.Parameter.empty,
197
+ ),
198
+ (
199
+ "store",
200
+ (
201
+ Optional[BaseStore], # noqa: UP045
202
+ "Optional[BaseStore]",
203
+ ),
204
+ "store",
205
+ None,
206
+ ),
207
+ (
208
+ "previous",
209
+ (ANY_TYPE,),
210
+ "previous",
211
+ inspect.Parameter.empty,
212
+ ),
213
+ (
214
+ "runtime",
215
+ (ANY_TYPE,),
216
+ # we never hit this block, we just inject runtime directly
217
+ "N/A",
218
+ inspect.Parameter.empty,
219
+ ),
220
+ (
221
+ "error",
222
+ (NodeError, "NodeError"),
223
+ # we never hit this block, we read directly from configurable
224
+ "N/A",
225
+ # default to None so non-handler nodes that happen to type a parameter
226
+ # `error: NodeError` don't blow up; handlers always receive a NodeError.
227
+ None,
228
+ ),
229
+ )
230
+ """List of kwargs that can be passed to functions, and their corresponding
231
+ config keys, default values and type annotations.
232
+
233
+ Used to configure keyword arguments that can be injected at runtime
234
+ from the `Runtime` object as kwargs to `invoke`, `ainvoke`, `stream` and `astream`.
235
+
236
+ For a keyword to be injected from the config object, the function signature
237
+ must contain a kwarg with the same name and a matching type annotation.
238
+
239
+ Each tuple contains:
240
+ - the name of the kwarg in the function signature
241
+ - the type annotation(s) for the kwarg
242
+ - the `Runtime` attribute for fetching the value (N/A if not applicable)
243
+
244
+ This is fully internal and should be further refactored to use `get_type_hints`
245
+ to resolve forward references and optional types formatted like BaseStore | None.
246
+ """
247
+
248
+ VALID_KINDS = (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
249
+
250
+
251
+ class _RunnableWithWriter(Protocol[Input, Output]):
252
+ def __call__(self, state: Input, *, writer: StreamWriter) -> Output: ...
253
+
254
+
255
+ class _RunnableWithStore(Protocol[Input, Output]):
256
+ def __call__(self, state: Input, *, store: BaseStore) -> Output: ...
257
+
258
+
259
+ class _RunnableWithWriterStore(Protocol[Input, Output]):
260
+ def __call__(
261
+ self, state: Input, *, writer: StreamWriter, store: BaseStore
262
+ ) -> Output: ...
263
+
264
+
265
+ class _RunnableWithConfigWriter(Protocol[Input, Output]):
266
+ def __call__(
267
+ self, state: Input, *, config: RunnableConfig, writer: StreamWriter
268
+ ) -> Output: ...
269
+
270
+
271
+ class _RunnableWithConfigStore(Protocol[Input, Output]):
272
+ def __call__(
273
+ self, state: Input, *, config: RunnableConfig, store: BaseStore
274
+ ) -> Output: ...
275
+
276
+
277
+ class _RunnableWithConfigWriterStore(Protocol[Input, Output]):
278
+ def __call__(
279
+ self,
280
+ state: Input,
281
+ *,
282
+ config: RunnableConfig,
283
+ writer: StreamWriter,
284
+ store: BaseStore,
285
+ ) -> Output: ...
286
+
287
+
288
+ RunnableLike = (
289
+ LCRunnableLike
290
+ | _RunnableWithWriter[Input, Output]
291
+ | _RunnableWithStore[Input, Output]
292
+ | _RunnableWithWriterStore[Input, Output]
293
+ | _RunnableWithConfigWriter[Input, Output]
294
+ | _RunnableWithConfigStore[Input, Output]
295
+ | _RunnableWithConfigWriterStore[Input, Output]
296
+ )
297
+
298
+
299
+ class RunnableCallable(Runnable):
300
+ """A much simpler version of RunnableLambda that requires sync and async functions."""
301
+
302
+ def __init__(
303
+ self,
304
+ func: Callable[..., Any | Runnable] | None,
305
+ afunc: Callable[..., Awaitable[Any | Runnable]] | None = None,
306
+ *,
307
+ name: str | None = None,
308
+ tags: Sequence[str] | None = None,
309
+ trace: bool = True,
310
+ recurse: bool = True,
311
+ explode_args: bool = False,
312
+ **kwargs: Any,
313
+ ) -> None:
314
+ self.name = name
315
+ if self.name is None:
316
+ if func:
317
+ try:
318
+ if func.__name__ != "<lambda>":
319
+ self.name = func.__name__
320
+ except AttributeError:
321
+ pass
322
+ elif afunc:
323
+ try:
324
+ self.name = afunc.__name__
325
+ except AttributeError:
326
+ pass
327
+ self.func = func
328
+ self.afunc = afunc
329
+ self.tags = tags
330
+ self.kwargs = kwargs
331
+ self.trace = trace
332
+ self.recurse = recurse
333
+ self.explode_args = explode_args
334
+ # check signature
335
+ if func is None and afunc is None:
336
+ raise ValueError("At least one of func or afunc must be provided.")
337
+
338
+ self.func_accepts: dict[str, tuple[str, Any]] = {}
339
+ params = inspect.signature(cast(Callable, func or afunc)).parameters
340
+
341
+ for kw, typ, runtime_key, default in KWARGS_CONFIG_KEYS:
342
+ p = params.get(kw)
343
+
344
+ if p is None or p.kind not in VALID_KINDS:
345
+ # If parameter is not found or is not a valid kind, skip
346
+ continue
347
+
348
+ if typ != (ANY_TYPE,) and p.annotation not in typ:
349
+ # A specific type is required, but the function annotation does
350
+ # not match the expected type.
351
+
352
+ # If this is a config parameter with incorrect typing, emit a warning
353
+ # because we used to support any type but are moving towards more correct typing
354
+ if kw == "config" and p.annotation != inspect.Parameter.empty:
355
+ warnings.warn(
356
+ f"The 'config' parameter should be typed as 'RunnableConfig' or "
357
+ f"'RunnableConfig | None', not '{p.annotation}'. ",
358
+ UserWarning,
359
+ stacklevel=4,
360
+ )
361
+ continue
362
+
363
+ # If the kwarg is accepted by the function, store the key / runtime attribute to inject
364
+ self.func_accepts[kw] = (runtime_key, default)
365
+
366
+ def __repr__(self) -> str:
367
+ repr_args = {
368
+ k: v
369
+ for k, v in self.__dict__.items()
370
+ if k not in {"name", "func", "afunc", "config", "kwargs", "trace"}
371
+ }
372
+ return f"{self.get_name()}({', '.join(f'{k}={v!r}' for k, v in repr_args.items())})"
373
+
374
+ def invoke(
375
+ self, input: Any, config: RunnableConfig | None = None, **kwargs: Any
376
+ ) -> Any:
377
+ if self.func is None:
378
+ raise TypeError(
379
+ f'No synchronous function provided to "{self.name}".'
380
+ "\nEither initialize with a synchronous function or invoke"
381
+ " via the async API (ainvoke, astream, etc.)"
382
+ )
383
+ if config is None:
384
+ config = ensure_config()
385
+ if self.explode_args:
386
+ args, _kwargs = input
387
+ kwargs = {**self.kwargs, **_kwargs, **kwargs}
388
+ else:
389
+ args = (input,)
390
+ kwargs = {**self.kwargs, **kwargs}
391
+
392
+ runtime = config.get(CONF, {}).get(CONFIG_KEY_RUNTIME)
393
+
394
+ for kw, (runtime_key, default) in self.func_accepts.items():
395
+ # If the kwarg is already set, use the set value
396
+ if kw in kwargs:
397
+ continue
398
+
399
+ kw_value: Any = MISSING
400
+ if kw == "config":
401
+ kw_value = config
402
+ elif kw == "error":
403
+ kw_value = config.get(CONF, {}).get(CONFIG_KEY_NODE_ERROR, MISSING)
404
+ elif runtime:
405
+ if kw == "runtime":
406
+ kw_value = runtime
407
+ else:
408
+ try:
409
+ kw_value = getattr(runtime, runtime_key)
410
+ except AttributeError:
411
+ pass
412
+
413
+ if kw_value is MISSING:
414
+ if default is inspect.Parameter.empty:
415
+ raise ValueError(
416
+ f"Missing required config key '{runtime_key}' for '{self.name}'."
417
+ )
418
+ kw_value = default
419
+ kwargs[kw] = kw_value
420
+
421
+ if self.trace:
422
+ callback_manager = get_callback_manager_for_config(config, self.tags)
423
+ run_manager = callback_manager.on_chain_start(
424
+ None,
425
+ input,
426
+ name=config.get("run_name") or self.get_name(),
427
+ run_id=config.pop("run_id", None),
428
+ )
429
+ try:
430
+ child_config = patch_config(config, callbacks=run_manager.get_child())
431
+ # get the run
432
+ for h in run_manager.handlers:
433
+ if isinstance(h, CocoTracer):
434
+ run = h.run_map.get(str(run_manager.run_id))
435
+ break
436
+ else:
437
+ run = None
438
+ # run in context
439
+ with set_config_context(child_config, run) as context:
440
+ ret = context.run(self.func, *args, **kwargs)
441
+ except BaseException as e:
442
+ run_manager.on_chain_error(e)
443
+ raise
444
+ else:
445
+ run_manager.on_chain_end(ret)
446
+ else:
447
+ ret = self.func(*args, **kwargs)
448
+ if self.recurse and isinstance(ret, Runnable):
449
+ return ret.invoke(input, config)
450
+ return ret
451
+
452
+ async def ainvoke(
453
+ self, input: Any, config: RunnableConfig | None = None, **kwargs: Any
454
+ ) -> Any:
455
+ if not self.afunc:
456
+ return self.invoke(input, config)
457
+ if config is None:
458
+ config = ensure_config()
459
+ if self.explode_args:
460
+ args, _kwargs = input
461
+ kwargs = {**self.kwargs, **_kwargs, **kwargs}
462
+ else:
463
+ args = (input,)
464
+ kwargs = {**self.kwargs, **kwargs}
465
+
466
+ runtime = config.get(CONF, {}).get(CONFIG_KEY_RUNTIME)
467
+
468
+ for kw, (runtime_key, default) in self.func_accepts.items():
469
+ # If the kwarg has already been set, use the set value
470
+ if kw in kwargs:
471
+ continue
472
+
473
+ kw_value: Any = MISSING
474
+ if kw == "config":
475
+ kw_value = config
476
+ elif kw == "error":
477
+ kw_value = config.get(CONF, {}).get(CONFIG_KEY_NODE_ERROR, MISSING)
478
+ elif runtime:
479
+ if kw == "runtime":
480
+ kw_value = runtime
481
+ else:
482
+ try:
483
+ kw_value = getattr(runtime, runtime_key)
484
+ except AttributeError:
485
+ pass
486
+ if kw_value is MISSING:
487
+ if default is inspect.Parameter.empty:
488
+ raise ValueError(
489
+ f"Missing required config key '{runtime_key}' for '{self.name}'."
490
+ )
491
+ kw_value = default
492
+ kwargs[kw] = kw_value
493
+
494
+ if self.trace:
495
+ callback_manager = get_async_callback_manager_for_config(config, self.tags)
496
+ run_manager = await callback_manager.on_chain_start(
497
+ None,
498
+ input,
499
+ name=config.get("run_name") or self.name,
500
+ run_id=config.pop("run_id", None),
501
+ )
502
+ try:
503
+ child_config = patch_config(config, callbacks=run_manager.get_child())
504
+ coro = cast(Coroutine[None, None, Any], self.afunc(*args, **kwargs))
505
+ if ASYNCIO_ACCEPTS_CONTEXT:
506
+ for h in run_manager.handlers:
507
+ if isinstance(h, CocoTracer):
508
+ run = h.run_map.get(str(run_manager.run_id))
509
+ break
510
+ else:
511
+ run = None
512
+ with set_config_context(child_config, run) as context:
513
+ ret = await asyncio.create_task(coro, context=context)
514
+ else:
515
+ ret = await coro
516
+ except BaseException as e:
517
+ await run_manager.on_chain_error(e)
518
+ raise
519
+ else:
520
+ await run_manager.on_chain_end(ret)
521
+ else:
522
+ ret = await self.afunc(*args, **kwargs)
523
+ if self.recurse and isinstance(ret, Runnable):
524
+ return await ret.ainvoke(input, config)
525
+ return ret
526
+
527
+
528
+ def is_async_callable(
529
+ func: Any,
530
+ ) -> TypeGuard[Callable[..., Awaitable]]:
531
+ """Check if a function is async."""
532
+ return (
533
+ inspect.iscoroutinefunction(func)
534
+ or hasattr(func, "__call__")
535
+ and inspect.iscoroutinefunction(func.__call__)
536
+ )
537
+
538
+
539
+ def is_async_generator(
540
+ func: Any,
541
+ ) -> TypeGuard[Callable[..., AsyncIterator]]:
542
+ """Check if a function is an async generator."""
543
+ return (
544
+ inspect.isasyncgenfunction(func)
545
+ or hasattr(func, "__call__")
546
+ and inspect.isasyncgenfunction(func.__call__)
547
+ )
548
+
549
+
550
+ def coerce_to_runnable(
551
+ thing: RunnableLike, *, name: str | None, trace: bool
552
+ ) -> Runnable:
553
+ """Coerce a runnable-like object into a Runnable.
554
+
555
+ Args:
556
+ thing: A runnable-like object.
557
+
558
+ Returns:
559
+ A Runnable.
560
+ """
561
+ if isinstance(thing, Runnable):
562
+ return thing
563
+ elif is_async_generator(thing) or inspect.isgeneratorfunction(thing):
564
+ return RunnableLambda(thing, name=name)
565
+ elif callable(thing):
566
+ if is_async_callable(thing):
567
+ return RunnableCallable(None, thing, name=name, trace=trace)
568
+ else:
569
+ return RunnableCallable(
570
+ thing,
571
+ wraps(thing)(partial(run_in_executor, None, thing)), # type: ignore[arg-type]
572
+ name=name,
573
+ trace=trace,
574
+ )
575
+ elif isinstance(thing, dict):
576
+ return RunnableParallel(thing)
577
+ else:
578
+ raise TypeError(
579
+ f"Expected a Runnable, callable or dict."
580
+ f"Instead got an unsupported type: {type(thing)}"
581
+ )
582
+
583
+
584
+ class RunnableSeq(Runnable):
585
+ """Sequence of `Runnable`, where the output of each is the input of the next.
586
+
587
+ `RunnableSeq` is a simpler version of `RunnableSequence` that is internal to
588
+ CocoGraph.
589
+ """
590
+
591
+ def __init__(
592
+ self,
593
+ *steps: RunnableLike,
594
+ name: str | None = None,
595
+ trace_inputs: Callable[[Any], Any] | None = None,
596
+ trace_outputs: Callable[[Any], Any] | None = None,
597
+ ) -> None:
598
+ """Create a new RunnableSeq.
599
+
600
+ Args:
601
+ steps: The steps to include in the sequence.
602
+ name: The name of the `Runnable`.
603
+
604
+ Raises:
605
+ ValueError: If the sequence has less than 2 steps.
606
+ """
607
+ steps_flat: list[Runnable] = []
608
+ for step in steps:
609
+ if isinstance(step, RunnableSequence):
610
+ steps_flat.extend(step.steps)
611
+ elif isinstance(step, RunnableSeq):
612
+ steps_flat.extend(step.steps)
613
+ else:
614
+ steps_flat.append(coerce_to_runnable(step, name=None, trace=True))
615
+ if len(steps_flat) < 2:
616
+ raise ValueError(
617
+ f"RunnableSeq must have at least 2 steps, got {len(steps_flat)}"
618
+ )
619
+ self.steps = steps_flat
620
+ self.name = name
621
+ self.trace_inputs = trace_inputs
622
+ self.trace_outputs = trace_outputs
623
+
624
+ def __or__(
625
+ self,
626
+ other: Any,
627
+ ) -> Runnable:
628
+ if isinstance(other, RunnableSequence):
629
+ return RunnableSeq(
630
+ *self.steps,
631
+ other.first,
632
+ *other.middle,
633
+ other.last,
634
+ name=self.name or other.name,
635
+ )
636
+ elif isinstance(other, RunnableSeq):
637
+ return RunnableSeq(
638
+ *self.steps,
639
+ *other.steps,
640
+ name=self.name or other.name,
641
+ )
642
+ else:
643
+ return RunnableSeq(
644
+ *self.steps,
645
+ coerce_to_runnable(other, name=None, trace=True),
646
+ name=self.name,
647
+ )
648
+
649
+ def __ror__(
650
+ self,
651
+ other: Any,
652
+ ) -> Runnable:
653
+ if isinstance(other, RunnableSequence):
654
+ return RunnableSequence(
655
+ other.first,
656
+ *other.middle,
657
+ other.last,
658
+ *self.steps,
659
+ name=other.name or self.name,
660
+ )
661
+ elif isinstance(other, RunnableSeq):
662
+ return RunnableSeq(
663
+ *other.steps,
664
+ *self.steps,
665
+ name=other.name or self.name,
666
+ )
667
+ else:
668
+ return RunnableSequence(
669
+ coerce_to_runnable(other, name=None, trace=True),
670
+ *self.steps,
671
+ name=self.name,
672
+ )
673
+
674
+ def invoke(
675
+ self, input: Input, config: RunnableConfig | None = None, **kwargs: Any
676
+ ) -> Any:
677
+ if config is None:
678
+ config = ensure_config()
679
+ # setup callbacks and context
680
+ callback_manager = get_callback_manager_for_config(config)
681
+ # start the root run
682
+ run_manager = callback_manager.on_chain_start(
683
+ None,
684
+ _trace_payload(input, self.trace_inputs),
685
+ name=config.get("run_name") or self.get_name(),
686
+ run_id=config.pop("run_id", None),
687
+ )
688
+ # invoke all steps in sequence
689
+ try:
690
+ for i, step in enumerate(self.steps):
691
+ # mark each step as a child run
692
+ config = patch_config(
693
+ config, callbacks=run_manager.get_child(f"seq:step:{i + 1}")
694
+ )
695
+ # 1st step is the actual node,
696
+ # others are writers which don't need to be run in context
697
+ if i == 0:
698
+ # get the run object
699
+ for h in run_manager.handlers:
700
+ if isinstance(h, CocoTracer):
701
+ run = h.run_map.get(str(run_manager.run_id))
702
+ break
703
+ else:
704
+ run = None
705
+ # run in context
706
+ with set_config_context(config, run) as context:
707
+ input = context.run(step.invoke, input, config, **kwargs)
708
+ else:
709
+ input = step.invoke(input, config)
710
+ # finish the root run
711
+ except BaseException as e:
712
+ run_manager.on_chain_error(e)
713
+ raise
714
+ else:
715
+ run_manager.on_chain_end(_trace_payload(input, self.trace_outputs))
716
+ return input
717
+
718
+ async def ainvoke(
719
+ self,
720
+ input: Input,
721
+ config: RunnableConfig | None = None,
722
+ **kwargs: Any | None,
723
+ ) -> Any:
724
+ if config is None:
725
+ config = ensure_config()
726
+ # setup callbacks
727
+ callback_manager = get_async_callback_manager_for_config(config)
728
+ # start the root run
729
+ run_manager = await callback_manager.on_chain_start(
730
+ None,
731
+ _trace_payload(input, self.trace_inputs),
732
+ name=config.get("run_name") or self.get_name(),
733
+ run_id=config.pop("run_id", None),
734
+ )
735
+
736
+ # invoke all steps in sequence
737
+ try:
738
+ for i, step in enumerate(self.steps):
739
+ # mark each step as a child run
740
+ config = patch_config(
741
+ config, callbacks=run_manager.get_child(f"seq:step:{i + 1}")
742
+ )
743
+ # 1st step is the actual node,
744
+ # others are writers which don't need to be run in context
745
+ if i == 0:
746
+ if ASYNCIO_ACCEPTS_CONTEXT:
747
+ # get the run object
748
+ for h in run_manager.handlers:
749
+ if isinstance(h, CocoTracer):
750
+ run = h.run_map.get(str(run_manager.run_id))
751
+ break
752
+ else:
753
+ run = None
754
+ # run in context
755
+ with set_config_context(config, run) as context:
756
+ input = await asyncio.create_task(
757
+ step.ainvoke(input, config, **kwargs), context=context
758
+ )
759
+ else:
760
+ input = await step.ainvoke(input, config, **kwargs)
761
+ else:
762
+ input = await step.ainvoke(input, config)
763
+ # finish the root run
764
+ except BaseException as e:
765
+ await run_manager.on_chain_error(e)
766
+ raise
767
+ else:
768
+ await run_manager.on_chain_end(_trace_payload(input, self.trace_outputs))
769
+ return input
770
+
771
+ def stream(
772
+ self,
773
+ input: Input,
774
+ config: RunnableConfig | None = None,
775
+ **kwargs: Any | None,
776
+ ) -> Iterator[Any]:
777
+ if config is None:
778
+ config = ensure_config()
779
+ # setup callbacks
780
+ callback_manager = get_callback_manager_for_config(config)
781
+ # start the root run
782
+ run_manager = callback_manager.on_chain_start(
783
+ None,
784
+ _trace_payload(input, self.trace_inputs),
785
+ name=config.get("run_name") or self.get_name(),
786
+ run_id=config.pop("run_id", None),
787
+ )
788
+ # get the run object
789
+ for h in run_manager.handlers:
790
+ if isinstance(h, CocoTracer):
791
+ run = h.run_map.get(str(run_manager.run_id))
792
+ break
793
+ else:
794
+ run = None
795
+ # create first step config
796
+ config = patch_config(
797
+ config,
798
+ callbacks=run_manager.get_child(f"seq:step:{1}"),
799
+ )
800
+ # run all in context
801
+ with set_config_context(config, run) as context:
802
+ try:
803
+ # stream the last steps
804
+ # transform the input stream of each step with the next
805
+ # steps that don't natively support transforming an input stream will
806
+ # buffer input in memory until all available, and then start emitting output
807
+ for idx, step in enumerate(self.steps):
808
+ if idx == 0:
809
+ iterator = step.stream(input, config, **kwargs)
810
+ else:
811
+ config = patch_config(
812
+ config,
813
+ callbacks=run_manager.get_child(f"seq:step:{idx + 1}"),
814
+ )
815
+ iterator = step.transform(iterator, config)
816
+ # populates streamed_output in astream_log() output if needed
817
+ if _StreamingCallbackHandler is not None:
818
+ for h in run_manager.handlers:
819
+ if isinstance(h, _StreamingCallbackHandler):
820
+ iterator = h.tap_output_iter(run_manager.run_id, iterator)
821
+ # consume into final output
822
+ output = context.run(_consume_iter, iterator)
823
+ # sequence doesn't emit output, yield to mark as generator
824
+ yield
825
+ except BaseException as e:
826
+ run_manager.on_chain_error(e)
827
+ raise
828
+ else:
829
+ run_manager.on_chain_end(_trace_payload(output, self.trace_outputs))
830
+
831
+ async def astream(
832
+ self,
833
+ input: Input,
834
+ config: RunnableConfig | None = None,
835
+ **kwargs: Any | None,
836
+ ) -> AsyncIterator[Any]:
837
+ if config is None:
838
+ config = ensure_config()
839
+ # setup callbacks
840
+ callback_manager = get_async_callback_manager_for_config(config)
841
+ # start the root run
842
+ run_manager = await callback_manager.on_chain_start(
843
+ None,
844
+ _trace_payload(input, self.trace_inputs),
845
+ name=config.get("run_name") or self.get_name(),
846
+ run_id=config.pop("run_id", None),
847
+ )
848
+ # stream the last steps
849
+ # transform the input stream of each step with the next
850
+ # steps that don't natively support transforming an input stream will
851
+ # buffer input in memory until all available, and then start emitting output
852
+ if ASYNCIO_ACCEPTS_CONTEXT:
853
+ # get the run object
854
+ for h in run_manager.handlers:
855
+ if isinstance(h, CocoTracer):
856
+ run = h.run_map.get(str(run_manager.run_id))
857
+ break
858
+ else:
859
+ run = None
860
+ # create first step config
861
+ config = patch_config(
862
+ config,
863
+ callbacks=run_manager.get_child(f"seq:step:{1}"),
864
+ )
865
+ # run all in context
866
+ with set_config_context(config, run) as context:
867
+ try:
868
+ async with AsyncExitStack() as stack:
869
+ for idx, step in enumerate(self.steps):
870
+ if idx == 0:
871
+ aiterator = step.astream(input, config, **kwargs)
872
+ else:
873
+ config = patch_config(
874
+ config,
875
+ callbacks=run_manager.get_child(
876
+ f"seq:step:{idx + 1}"
877
+ ),
878
+ )
879
+ aiterator = step.atransform(aiterator, config)
880
+ if hasattr(aiterator, "aclose"):
881
+ stack.push_async_callback(aiterator.aclose)
882
+ # populates streamed_output in astream_log() output if needed
883
+ if _StreamingCallbackHandler is not None:
884
+ for h in run_manager.handlers:
885
+ if isinstance(h, _StreamingCallbackHandler):
886
+ aiterator = h.tap_output_aiter(
887
+ run_manager.run_id, aiterator
888
+ )
889
+ # consume into final output
890
+ output = await asyncio.create_task(
891
+ _consume_aiter(aiterator), context=context
892
+ )
893
+ # sequence doesn't emit output, yield to mark as generator
894
+ yield
895
+ except BaseException as e:
896
+ await run_manager.on_chain_error(e)
897
+ raise
898
+ else:
899
+ await run_manager.on_chain_end(
900
+ _trace_payload(output, self.trace_outputs)
901
+ )
902
+ else:
903
+ try:
904
+ async with AsyncExitStack() as stack:
905
+ for idx, step in enumerate(self.steps):
906
+ config = patch_config(
907
+ config,
908
+ callbacks=run_manager.get_child(f"seq:step:{idx + 1}"),
909
+ )
910
+ if idx == 0:
911
+ aiterator = step.astream(input, config, **kwargs)
912
+ else:
913
+ aiterator = step.atransform(aiterator, config)
914
+ if hasattr(aiterator, "aclose"):
915
+ stack.push_async_callback(aiterator.aclose)
916
+ # populates streamed_output in astream_log() output if needed
917
+ if _StreamingCallbackHandler is not None:
918
+ for h in run_manager.handlers:
919
+ if isinstance(h, _StreamingCallbackHandler):
920
+ aiterator = h.tap_output_aiter(
921
+ run_manager.run_id, aiterator
922
+ )
923
+ # consume into final output
924
+ output = await _consume_aiter(aiterator)
925
+ # sequence doesn't emit output, yield to mark as generator
926
+ yield
927
+ except BaseException as e:
928
+ await run_manager.on_chain_error(e)
929
+ raise
930
+ else:
931
+ await run_manager.on_chain_end(
932
+ _trace_payload(output, self.trace_outputs)
933
+ )
934
+
935
+
936
+ def _consume_iter(it: Iterator[Any]) -> Any:
937
+ """Consume an iterator."""
938
+ output: Any = None
939
+ add_supported = False
940
+ for chunk in it:
941
+ # collect final output
942
+ if output is None:
943
+ output = chunk
944
+ elif add_supported:
945
+ try:
946
+ output = output + chunk
947
+ except TypeError:
948
+ output = chunk
949
+ add_supported = False
950
+ else:
951
+ output = chunk
952
+ return output
953
+
954
+
955
+ async def _consume_aiter(it: AsyncIterator[Any]) -> Any:
956
+ """Consume an async iterator."""
957
+ output: Any = None
958
+ add_supported = False
959
+ async for chunk in it:
960
+ # collect final output
961
+ if add_supported:
962
+ try:
963
+ output = output + chunk
964
+ except TypeError:
965
+ output = chunk
966
+ add_supported = False
967
+ else:
968
+ output = chunk
969
+ return output