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,849 @@
1
+ """Implementation of the `RunnablePassthrough`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import inspect
7
+ import threading
8
+ from collections.abc import Awaitable, Callable
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ )
13
+
14
+ from pydantic import RootModel
15
+ from typing_extensions import override
16
+
17
+ from coco_core.runnables.base import (
18
+ Other,
19
+ Runnable,
20
+ RunnableParallel,
21
+ RunnableSerializable,
22
+ _get_schema_field_definition,
23
+ )
24
+ from coco_core.runnables.config import (
25
+ RunnableConfig,
26
+ acall_func_with_variable_args,
27
+ call_func_with_variable_args,
28
+ ensure_config,
29
+ get_executor_for_config,
30
+ patch_config,
31
+ )
32
+ from coco_core.runnables.utils import (
33
+ AddableDict,
34
+ ConfigurableFieldSpec,
35
+ )
36
+ from coco_core.utils.aiter import atee
37
+ from coco_core.utils.iter import safetee
38
+ from coco_core.utils.pydantic import TypeBaseModel, create_model_v2, get_fields
39
+
40
+ if TYPE_CHECKING:
41
+ from collections.abc import AsyncIterator, Iterator, Mapping
42
+
43
+ from coco_core.callbacks.manager import (
44
+ AsyncCallbackManagerForChainRun,
45
+ CallbackManagerForChainRun,
46
+ )
47
+ from coco_core.runnables.graph import Graph
48
+
49
+
50
+ def identity(x: Other) -> Other:
51
+ """Identity function.
52
+
53
+ Args:
54
+ x: Input.
55
+
56
+ Returns:
57
+ Output.
58
+ """
59
+ return x
60
+
61
+
62
+ async def aidentity(x: Other) -> Other:
63
+ """Async identity function.
64
+
65
+ Args:
66
+ x: Input.
67
+
68
+ Returns:
69
+ Output.
70
+ """
71
+ return x
72
+
73
+
74
+ class RunnablePassthrough(RunnableSerializable[Other, Other]):
75
+ """Runnable to passthrough inputs unchanged or with additional keys.
76
+
77
+ This `Runnable` behaves almost like the identity function, except that it
78
+ can be configured to add additional keys to the output, if the input is a
79
+ dict.
80
+
81
+ The examples below demonstrate this `Runnable` works using a few simple
82
+ chains. The chains rely on simple lambdas to make the examples easy to execute
83
+ and experiment with.
84
+
85
+ Examples:
86
+ ```python
87
+ from coco_core.runnables import (
88
+ RunnableLambda,
89
+ RunnableParallel,
90
+ RunnablePassthrough,
91
+ )
92
+
93
+ runnable = RunnableParallel(
94
+ origin=RunnablePassthrough(), modified=lambda x: x + 1
95
+ )
96
+
97
+ runnable.invoke(1) # {'origin': 1, 'modified': 2}
98
+
99
+
100
+ def fake_llm(prompt: str) -> str: # Fake LLM for the example
101
+ return "completion"
102
+
103
+
104
+ chain = RunnableLambda(fake_llm) | {
105
+ "original": RunnablePassthrough(), # Original LLM output
106
+ "parsed": lambda text: text[::-1], # Parsing logic
107
+ }
108
+
109
+ chain.invoke("hello") # {'original': 'completion', 'parsed': 'noitelpmoc'}
110
+ ```
111
+
112
+ In some cases, it may be useful to pass the input through while adding some
113
+ keys to the output. In this case, you can use the `assign` method:
114
+
115
+ ```python
116
+ from coco_core.runnables import RunnablePassthrough
117
+
118
+
119
+ def fake_llm(prompt: str) -> str: # Fake LLM for the example
120
+ return "completion"
121
+
122
+
123
+ runnable = {
124
+ "llm1": fake_llm,
125
+ "llm2": fake_llm,
126
+ } | RunnablePassthrough.assign(
127
+ total_chars=lambda inputs: len(inputs["llm1"] + inputs["llm2"])
128
+ )
129
+
130
+ runnable.invoke("hello")
131
+ # {'llm1': 'completion', 'llm2': 'completion', 'total_chars': 20}
132
+ ```
133
+ """
134
+
135
+ input_type: type[Other] | None = None
136
+
137
+ func: Callable[[Other], None] | Callable[[Other, RunnableConfig], None] | None = (
138
+ None
139
+ )
140
+
141
+ afunc: (
142
+ Callable[[Other], Awaitable[None]]
143
+ | Callable[[Other, RunnableConfig], Awaitable[None]]
144
+ | None
145
+ ) = None
146
+
147
+ @override
148
+ def __repr_args__(self) -> Any:
149
+ # Without this repr(self) raises a RecursionError
150
+ # See https://github.com/pydantic/pydantic/issues/7327
151
+ return []
152
+
153
+ def __init__(
154
+ self,
155
+ func: Callable[[Other], None]
156
+ | Callable[[Other, RunnableConfig], None]
157
+ | Callable[[Other], Awaitable[None]]
158
+ | Callable[[Other, RunnableConfig], Awaitable[None]]
159
+ | None = None,
160
+ afunc: Callable[[Other], Awaitable[None]]
161
+ | Callable[[Other, RunnableConfig], Awaitable[None]]
162
+ | None = None,
163
+ *,
164
+ input_type: type[Other] | None = None,
165
+ **kwargs: Any,
166
+ ) -> None:
167
+ """Create a `RunnablePassthrough`.
168
+
169
+ Args:
170
+ func: Function to be called with the input.
171
+ afunc: Async function to be called with the input.
172
+ input_type: Type of the input.
173
+ """
174
+ if inspect.iscoroutinefunction(func):
175
+ afunc = func
176
+ func = None
177
+
178
+ super().__init__(func=func, afunc=afunc, input_type=input_type, **kwargs)
179
+
180
+ @classmethod
181
+ @override
182
+ def is_lc_serializable(cls) -> bool:
183
+ """Return `True` as this class is serializable."""
184
+ return True
185
+
186
+ @classmethod
187
+ def get_lc_namespace(cls) -> list[str]:
188
+ """Get the namespace of the Coco object.
189
+
190
+ Returns:
191
+ `["coco", "schema", "runnable"]`
192
+ """
193
+ return ["coco", "schema", "runnable"]
194
+
195
+ @property
196
+ @override
197
+ def InputType(self) -> Any:
198
+ return self.input_type or Any
199
+
200
+ @property
201
+ @override
202
+ def OutputType(self) -> Any:
203
+ return self.input_type or Any
204
+
205
+ @classmethod
206
+ @override
207
+ def assign(
208
+ cls,
209
+ **kwargs: Runnable[dict[str, Any], Any]
210
+ | Callable[[dict[str, Any]], Any]
211
+ | Mapping[str, Runnable[dict[str, Any], Any] | Callable[[dict[str, Any]], Any]],
212
+ ) -> RunnableAssign:
213
+ """Merge the Dict input with the output produced by the mapping argument.
214
+
215
+ Args:
216
+ **kwargs: `Runnable`, `Callable` or a `Mapping` from keys to `Runnable`
217
+ objects or `Callable`s.
218
+
219
+ Returns:
220
+ A `Runnable` that merges the `dict` input with the output produced by the
221
+ mapping argument.
222
+ """
223
+ return RunnableAssign(RunnableParallel[dict[str, Any]](kwargs))
224
+
225
+ @override
226
+ def invoke(
227
+ self, input: Other, config: RunnableConfig | None = None, **kwargs: Any
228
+ ) -> Other:
229
+ if self.func is not None:
230
+ call_func_with_variable_args(
231
+ self.func, input, ensure_config(config), **kwargs
232
+ )
233
+ return self._call_with_config(identity, input, config)
234
+
235
+ @override
236
+ async def ainvoke(
237
+ self,
238
+ input: Other,
239
+ config: RunnableConfig | None = None,
240
+ **kwargs: Any | None,
241
+ ) -> Other:
242
+ if self.afunc is not None:
243
+ await acall_func_with_variable_args(
244
+ self.afunc, input, ensure_config(config), **kwargs
245
+ )
246
+ elif self.func is not None:
247
+ call_func_with_variable_args(
248
+ self.func, input, ensure_config(config), **kwargs
249
+ )
250
+ return await self._acall_with_config(aidentity, input, config)
251
+
252
+ @override
253
+ def transform(
254
+ self,
255
+ input: Iterator[Other],
256
+ config: RunnableConfig | None = None,
257
+ **kwargs: Any,
258
+ ) -> Iterator[Other]:
259
+ if self.func is None:
260
+ for chunk in self._transform_stream_with_config(input, identity, config):
261
+ yield chunk
262
+ else:
263
+ final: Other
264
+ got_first_chunk = False
265
+
266
+ for chunk in self._transform_stream_with_config(input, identity, config):
267
+ yield chunk
268
+
269
+ if not got_first_chunk:
270
+ final = chunk
271
+ got_first_chunk = True
272
+ else:
273
+ try:
274
+ final = final + chunk # type: ignore[operator]
275
+ except TypeError:
276
+ final = chunk
277
+
278
+ if got_first_chunk:
279
+ call_func_with_variable_args(
280
+ self.func, final, ensure_config(config), **kwargs
281
+ )
282
+
283
+ @override
284
+ async def atransform(
285
+ self,
286
+ input: AsyncIterator[Other],
287
+ config: RunnableConfig | None = None,
288
+ **kwargs: Any,
289
+ ) -> AsyncIterator[Other]:
290
+ if self.afunc is None and self.func is None:
291
+ async for chunk in self._atransform_stream_with_config(
292
+ input, identity, config
293
+ ):
294
+ yield chunk
295
+ else:
296
+ got_first_chunk = False
297
+
298
+ async for chunk in self._atransform_stream_with_config(
299
+ input, identity, config
300
+ ):
301
+ yield chunk
302
+
303
+ # By definitions, a function will operate on the aggregated
304
+ # input. So we'll aggregate the input until we get to the last
305
+ # chunk.
306
+ # If the input is not addable, then we'll assume that we can
307
+ # only operate on the last chunk.
308
+ if not got_first_chunk:
309
+ final = chunk
310
+ got_first_chunk = True
311
+ else:
312
+ try:
313
+ final = final + chunk # type: ignore[operator]
314
+ except TypeError:
315
+ final = chunk
316
+
317
+ if got_first_chunk:
318
+ config = ensure_config(config)
319
+ if self.afunc is not None:
320
+ await acall_func_with_variable_args(
321
+ self.afunc, final, config, **kwargs
322
+ )
323
+ elif self.func is not None:
324
+ call_func_with_variable_args(self.func, final, config, **kwargs)
325
+
326
+ @override
327
+ def stream(
328
+ self,
329
+ input: Other,
330
+ config: RunnableConfig | None = None,
331
+ **kwargs: Any,
332
+ ) -> Iterator[Other]:
333
+ return self.transform(iter([input]), config, **kwargs)
334
+
335
+ @override
336
+ async def astream(
337
+ self,
338
+ input: Other,
339
+ config: RunnableConfig | None = None,
340
+ **kwargs: Any,
341
+ ) -> AsyncIterator[Other]:
342
+ async def input_aiter() -> AsyncIterator[Other]:
343
+ yield input
344
+
345
+ async for chunk in self.atransform(input_aiter(), config, **kwargs):
346
+ yield chunk
347
+
348
+
349
+ _graph_passthrough = RunnablePassthrough[Any]()
350
+
351
+
352
+ class RunnableAssign(RunnableSerializable[dict[str, Any], dict[str, Any]]):
353
+ """Runnable that assigns key-value pairs to `dict[str, Any]` inputs.
354
+
355
+ The `RunnableAssign` class takes input dictionaries and, through a
356
+ `RunnableParallel` instance, applies transformations, then combines
357
+ these with the original data, introducing new key-value pairs based
358
+ on the mapper's logic.
359
+
360
+ Examples:
361
+ ```python
362
+ # This is a RunnableAssign
363
+ from coco_core.runnables.passthrough import (
364
+ RunnableAssign,
365
+ RunnableParallel,
366
+ )
367
+ from coco_core.runnables.base import RunnableLambda
368
+
369
+
370
+ def add_ten(x: dict[str, int]) -> dict[str, int]:
371
+ return {"added": x["input"] + 10}
372
+
373
+
374
+ mapper = RunnableParallel(
375
+ {
376
+ "add_step": RunnableLambda(add_ten),
377
+ }
378
+ )
379
+
380
+ runnable_assign = RunnableAssign(mapper)
381
+
382
+ # Synchronous example
383
+ runnable_assign.invoke({"input": 5})
384
+ # returns {'input': 5, 'add_step': {'added': 15}}
385
+
386
+ # Asynchronous example
387
+ await runnable_assign.ainvoke({"input": 5})
388
+ # returns {'input': 5, 'add_step': {'added': 15}}
389
+ ```
390
+ """
391
+
392
+ # Ideally we would type mapper as RunnableParallel[dict[str, Any]]
393
+ # but this fails validation for Pydantic <2.10
394
+ mapper: RunnableParallel # type: ignore[type-arg]
395
+
396
+ def __init__(self, mapper: RunnableParallel[dict[str, Any]], **kwargs: Any) -> None:
397
+ """Create a `RunnableAssign`.
398
+
399
+ Args:
400
+ mapper: A `RunnableParallel` instance that will be used to transform the
401
+ input dictionary.
402
+ """
403
+ super().__init__(mapper=mapper, **kwargs)
404
+
405
+ @classmethod
406
+ @override
407
+ def is_lc_serializable(cls) -> bool:
408
+ """Return `True` as this class is serializable."""
409
+ return True
410
+
411
+ @classmethod
412
+ @override
413
+ def get_lc_namespace(cls) -> list[str]:
414
+ """Get the namespace of the Coco object.
415
+
416
+ Returns:
417
+ `["coco", "schema", "runnable"]`
418
+ """
419
+ return ["coco", "schema", "runnable"]
420
+
421
+ @override
422
+ def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
423
+ name = (
424
+ name
425
+ or self.name
426
+ or f"RunnableAssign<{','.join(self.mapper.steps__.keys())}>"
427
+ )
428
+ return super().get_name(suffix, name=name)
429
+
430
+ @override
431
+ def get_input_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
432
+ map_input_schema = self.mapper.get_input_schema(config)
433
+ if not issubclass(map_input_schema, RootModel):
434
+ # ie. it's a dict
435
+ return map_input_schema
436
+
437
+ return super().get_input_schema(config)
438
+
439
+ @override
440
+ def get_output_schema(self, config: RunnableConfig | None = None) -> TypeBaseModel:
441
+ # The return type stays `TypeBaseModel` (rather than narrowing to
442
+ # `type[BaseModel]` as `RunnableParallel.get_output_schema` does) because
443
+ # the fallback branches return the mapper's output schema or delegate to
444
+ # `super().get_output_schema()`, either of which may be a Pydantic v1 model.
445
+ map_input_schema = self.mapper.get_input_schema(config)
446
+ map_output_schema = self.mapper.get_output_schema(config)
447
+ if not issubclass(map_input_schema, RootModel) and not issubclass(
448
+ map_output_schema, RootModel
449
+ ):
450
+ fields = {}
451
+
452
+ for name, field_info in get_fields(map_input_schema).items():
453
+ fields[name] = _get_schema_field_definition(field_info)
454
+
455
+ for name, field_info in get_fields(map_output_schema).items():
456
+ fields[name] = _get_schema_field_definition(field_info)
457
+
458
+ return create_model_v2("RunnableAssignOutput", field_definitions=fields)
459
+ if not issubclass(map_output_schema, RootModel):
460
+ # ie. only map output is a dict
461
+ # ie. input type is either unknown or inferred incorrectly
462
+ return map_output_schema
463
+
464
+ return super().get_output_schema(config)
465
+
466
+ @property
467
+ @override
468
+ def config_specs(self) -> list[ConfigurableFieldSpec]:
469
+ return self.mapper.config_specs
470
+
471
+ @override
472
+ def get_graph(self, config: RunnableConfig | None = None) -> Graph:
473
+ # get graph from mapper
474
+ graph = self.mapper.get_graph(config)
475
+ # add passthrough node and edges
476
+ input_node = graph.first_node()
477
+ output_node = graph.last_node()
478
+ if input_node is not None and output_node is not None:
479
+ passthrough_node = graph.add_node(_graph_passthrough)
480
+ graph.add_edge(input_node, passthrough_node)
481
+ graph.add_edge(passthrough_node, output_node)
482
+ return graph
483
+
484
+ def _invoke(
485
+ self,
486
+ value: dict[str, Any],
487
+ run_manager: CallbackManagerForChainRun,
488
+ config: RunnableConfig,
489
+ **kwargs: Any,
490
+ ) -> dict[str, Any]:
491
+ if not isinstance(value, dict):
492
+ msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
493
+ raise ValueError(msg) # noqa: TRY004
494
+
495
+ return {
496
+ **value,
497
+ **self.mapper.invoke(
498
+ value,
499
+ patch_config(config, callbacks=run_manager.get_child()),
500
+ **kwargs,
501
+ ),
502
+ }
503
+
504
+ @override
505
+ def invoke(
506
+ self,
507
+ input: dict[str, Any],
508
+ config: RunnableConfig | None = None,
509
+ **kwargs: Any,
510
+ ) -> dict[str, Any]:
511
+ return self._call_with_config(self._invoke, input, config, **kwargs)
512
+
513
+ async def _ainvoke(
514
+ self,
515
+ value: dict[str, Any],
516
+ run_manager: AsyncCallbackManagerForChainRun,
517
+ config: RunnableConfig,
518
+ **kwargs: Any,
519
+ ) -> dict[str, Any]:
520
+ if not isinstance(value, dict):
521
+ msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
522
+ raise ValueError(msg) # noqa: TRY004
523
+
524
+ return {
525
+ **value,
526
+ **await self.mapper.ainvoke(
527
+ value,
528
+ patch_config(config, callbacks=run_manager.get_child()),
529
+ **kwargs,
530
+ ),
531
+ }
532
+
533
+ @override
534
+ async def ainvoke(
535
+ self,
536
+ input: dict[str, Any],
537
+ config: RunnableConfig | None = None,
538
+ **kwargs: Any,
539
+ ) -> dict[str, Any]:
540
+ return await self._acall_with_config(self._ainvoke, input, config, **kwargs)
541
+
542
+ def _transform(
543
+ self,
544
+ values: Iterator[dict[str, Any]],
545
+ run_manager: CallbackManagerForChainRun,
546
+ config: RunnableConfig,
547
+ **kwargs: Any,
548
+ ) -> Iterator[dict[str, Any]]:
549
+ # collect mapper keys
550
+ mapper_keys = set(self.mapper.steps__.keys())
551
+ # create two streams, one for the map and one for the passthrough
552
+ for_passthrough, for_map = safetee(values, 2, lock=threading.Lock())
553
+
554
+ # create map output stream
555
+ map_output = self.mapper.transform(
556
+ for_map,
557
+ patch_config(
558
+ config,
559
+ callbacks=run_manager.get_child(),
560
+ ),
561
+ **kwargs,
562
+ )
563
+
564
+ # get executor to start map output stream in background
565
+ with get_executor_for_config(config) as executor:
566
+ # start map output stream
567
+ first_map_chunk_future = executor.submit(
568
+ next,
569
+ map_output,
570
+ None,
571
+ )
572
+ # consume passthrough stream
573
+ for chunk in for_passthrough:
574
+ if not isinstance(chunk, dict):
575
+ msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
576
+ raise ValueError(msg) # noqa: TRY004
577
+ # remove mapper keys from passthrough chunk, to be overwritten by map
578
+ filtered = AddableDict(
579
+ {k: v for k, v in chunk.items() if k not in mapper_keys}
580
+ )
581
+ if filtered:
582
+ yield filtered
583
+ # yield map output
584
+ first_chunk = first_map_chunk_future.result()
585
+ if first_chunk is not None:
586
+ yield first_chunk
587
+ for chunk in map_output:
588
+ yield chunk
589
+
590
+ @override
591
+ def transform(
592
+ self,
593
+ input: Iterator[dict[str, Any]],
594
+ config: RunnableConfig | None = None,
595
+ **kwargs: Any | None,
596
+ ) -> Iterator[dict[str, Any]]:
597
+ yield from self._transform_stream_with_config(
598
+ input, self._transform, config, **kwargs
599
+ )
600
+
601
+ async def _atransform(
602
+ self,
603
+ values: AsyncIterator[dict[str, Any]],
604
+ run_manager: AsyncCallbackManagerForChainRun,
605
+ config: RunnableConfig,
606
+ **kwargs: Any,
607
+ ) -> AsyncIterator[dict[str, Any]]:
608
+ # collect mapper keys
609
+ mapper_keys = set(self.mapper.steps__.keys())
610
+ # create two streams, one for the map and one for the passthrough
611
+ for_passthrough, for_map = atee(values, 2, lock=asyncio.Lock())
612
+ # create map output stream
613
+ map_output = self.mapper.atransform(
614
+ for_map,
615
+ patch_config(
616
+ config,
617
+ callbacks=run_manager.get_child(),
618
+ ),
619
+ **kwargs,
620
+ )
621
+ # start map output stream
622
+ first_map_chunk_task = asyncio.create_task(
623
+ anext(map_output, None),
624
+ )
625
+ # consume passthrough stream
626
+ async for chunk in for_passthrough:
627
+ if not isinstance(chunk, dict):
628
+ msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
629
+ raise ValueError(msg) # noqa: TRY004
630
+
631
+ # remove mapper keys from passthrough chunk, to be overwritten by map output
632
+ filtered = AddableDict(
633
+ {k: v for k, v in chunk.items() if k not in mapper_keys}
634
+ )
635
+ if filtered:
636
+ yield filtered
637
+ # yield map output
638
+ first_chunk = await first_map_chunk_task
639
+ if first_chunk is not None:
640
+ yield first_chunk
641
+ async for chunk in map_output:
642
+ yield chunk
643
+
644
+ @override
645
+ async def atransform(
646
+ self,
647
+ input: AsyncIterator[dict[str, Any]],
648
+ config: RunnableConfig | None = None,
649
+ **kwargs: Any,
650
+ ) -> AsyncIterator[dict[str, Any]]:
651
+ async for chunk in self._atransform_stream_with_config(
652
+ input, self._atransform, config, **kwargs
653
+ ):
654
+ yield chunk
655
+
656
+ @override
657
+ def stream(
658
+ self,
659
+ input: dict[str, Any],
660
+ config: RunnableConfig | None = None,
661
+ **kwargs: Any,
662
+ ) -> Iterator[dict[str, Any]]:
663
+ return self.transform(iter([input]), config, **kwargs)
664
+
665
+ @override
666
+ async def astream(
667
+ self,
668
+ input: dict[str, Any],
669
+ config: RunnableConfig | None = None,
670
+ **kwargs: Any,
671
+ ) -> AsyncIterator[dict[str, Any]]:
672
+ async def input_aiter() -> AsyncIterator[dict[str, Any]]:
673
+ yield input
674
+
675
+ async for chunk in self.atransform(input_aiter(), config, **kwargs):
676
+ yield chunk
677
+
678
+
679
+ class RunnablePick(RunnableSerializable[dict[str, Any], Any]):
680
+ """`Runnable` that picks keys from `dict[str, Any]` inputs.
681
+
682
+ `RunnablePick` class represents a `Runnable` that selectively picks keys from a
683
+ dictionary input. It allows you to specify one or more keys to extract
684
+ from the input dictionary.
685
+
686
+ !!! note "Return Type Behavior"
687
+ The return type depends on the `keys` parameter:
688
+
689
+ - When `keys` is a `str`: Returns the single value associated with that key
690
+ - When `keys` is a `list`: Returns a dictionary containing only the selected
691
+ keys
692
+
693
+ Example:
694
+ ```python
695
+ from coco_core.runnables.passthrough import RunnablePick
696
+
697
+ input_data = {
698
+ "name": "John",
699
+ "age": 30,
700
+ "city": "New York",
701
+ "country": "USA",
702
+ }
703
+
704
+ # Single key - returns the value directly
705
+ runnable_single = RunnablePick(keys="name")
706
+ result_single = runnable_single.invoke(input_data)
707
+ print(result_single) # Output: "John"
708
+
709
+ # Multiple keys - returns a dictionary
710
+ runnable_multiple = RunnablePick(keys=["name", "age"])
711
+ result_multiple = runnable_multiple.invoke(input_data)
712
+ print(result_multiple) # Output: {'name': 'John', 'age': 30}
713
+ ```
714
+ """
715
+
716
+ keys: str | list[str]
717
+
718
+ def __init__(self, keys: str | list[str], **kwargs: Any) -> None:
719
+ """Create a `RunnablePick`.
720
+
721
+ Args:
722
+ keys: A single key or a list of keys to pick from the input dictionary.
723
+ """
724
+ super().__init__(keys=keys, **kwargs)
725
+
726
+ @classmethod
727
+ @override
728
+ def is_lc_serializable(cls) -> bool:
729
+ """Return `True` as this class is serializable."""
730
+ return True
731
+
732
+ @classmethod
733
+ @override
734
+ def get_lc_namespace(cls) -> list[str]:
735
+ """Get the namespace of the Coco object.
736
+
737
+ Returns:
738
+ `["coco", "schema", "runnable"]`
739
+ """
740
+ return ["coco", "schema", "runnable"]
741
+
742
+ @override
743
+ def get_name(self, suffix: str | None = None, *, name: str | None = None) -> str:
744
+ name = (
745
+ name
746
+ or self.name
747
+ or "RunnablePick"
748
+ f"<{','.join([self.keys] if isinstance(self.keys, str) else self.keys)}>"
749
+ )
750
+ return super().get_name(suffix, name=name)
751
+
752
+ def _pick(self, value: dict[str, Any]) -> Any:
753
+ if not isinstance(value, dict):
754
+ msg = "The input to RunnablePassthrough.assign() must be a dict." # type: ignore[unreachable]
755
+ raise ValueError(msg) # noqa: TRY004
756
+
757
+ if isinstance(self.keys, str):
758
+ return value.get(self.keys)
759
+ picked = {k: value.get(k) for k in self.keys if k in value}
760
+ if picked:
761
+ return AddableDict(picked)
762
+ return None
763
+
764
+ @override
765
+ def invoke(
766
+ self,
767
+ input: dict[str, Any],
768
+ config: RunnableConfig | None = None,
769
+ **kwargs: Any,
770
+ ) -> Any:
771
+ return self._call_with_config(self._pick, input, config, **kwargs)
772
+
773
+ async def _ainvoke(
774
+ self,
775
+ value: dict[str, Any],
776
+ ) -> Any:
777
+ return self._pick(value)
778
+
779
+ @override
780
+ async def ainvoke(
781
+ self,
782
+ input: dict[str, Any],
783
+ config: RunnableConfig | None = None,
784
+ **kwargs: Any,
785
+ ) -> Any:
786
+ return await self._acall_with_config(self._ainvoke, input, config, **kwargs)
787
+
788
+ def _transform(
789
+ self,
790
+ chunks: Iterator[dict[str, Any]],
791
+ ) -> Iterator[Any]:
792
+ for chunk in chunks:
793
+ picked = self._pick(chunk)
794
+ if picked is not None:
795
+ yield picked
796
+
797
+ @override
798
+ def transform(
799
+ self,
800
+ input: Iterator[dict[str, Any]],
801
+ config: RunnableConfig | None = None,
802
+ **kwargs: Any,
803
+ ) -> Iterator[Any]:
804
+ yield from self._transform_stream_with_config(
805
+ input, self._transform, config, **kwargs
806
+ )
807
+
808
+ async def _atransform(
809
+ self,
810
+ chunks: AsyncIterator[dict[str, Any]],
811
+ ) -> AsyncIterator[Any]:
812
+ async for chunk in chunks:
813
+ picked = self._pick(chunk)
814
+ if picked is not None:
815
+ yield picked
816
+
817
+ @override
818
+ async def atransform(
819
+ self,
820
+ input: AsyncIterator[dict[str, Any]],
821
+ config: RunnableConfig | None = None,
822
+ **kwargs: Any,
823
+ ) -> AsyncIterator[Any]:
824
+ async for chunk in self._atransform_stream_with_config(
825
+ input, self._atransform, config, **kwargs
826
+ ):
827
+ yield chunk
828
+
829
+ @override
830
+ def stream(
831
+ self,
832
+ input: dict[str, Any],
833
+ config: RunnableConfig | None = None,
834
+ **kwargs: Any,
835
+ ) -> Iterator[Any]:
836
+ return self.transform(iter([input]), config, **kwargs)
837
+
838
+ @override
839
+ async def astream(
840
+ self,
841
+ input: dict[str, Any],
842
+ config: RunnableConfig | None = None,
843
+ **kwargs: Any,
844
+ ) -> AsyncIterator[Any]:
845
+ async def input_aiter() -> AsyncIterator[dict[str, Any]]:
846
+ yield input
847
+
848
+ async for chunk in self.atransform(input_aiter(), config, **kwargs):
849
+ yield chunk