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,712 @@
1
+ """Configuration utilities for `Runnable` objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ # Cannot move uuid to TYPE_CHECKING as RunnableConfig is used in Pydantic models
8
+ import uuid # noqa: TC003
9
+ import warnings
10
+ from collections.abc import (
11
+ Awaitable,
12
+ Callable,
13
+ Generator,
14
+ Iterable,
15
+ Iterator,
16
+ Mapping,
17
+ Sequence,
18
+ )
19
+ from concurrent.futures import Executor, Future, ThreadPoolExecutor
20
+ from contextlib import contextmanager
21
+ from contextvars import Context, ContextVar, Token, copy_context
22
+ from functools import partial
23
+ from typing import (
24
+ TYPE_CHECKING,
25
+ Any,
26
+ ParamSpec,
27
+ TypeVar,
28
+ cast,
29
+ )
30
+
31
+ from typing_extensions import TypedDict
32
+
33
+ from coco_core.callbacks.manager import AsyncCallbackManager, CallbackManager
34
+ from coco_core.runnables.utils import (
35
+ Input,
36
+ Output,
37
+ accepts_config,
38
+ accepts_run_manager,
39
+ )
40
+
41
+ if TYPE_CHECKING:
42
+ from coco_core.callbacks.base import BaseCallbackManager, Callbacks
43
+ from coco_core.callbacks.manager import (
44
+ AsyncCallbackManagerForChainRun,
45
+ CallbackManagerForChainRun,
46
+ )
47
+ else:
48
+ # Pydantic validates through typed dicts, but
49
+ # the callbacks need forward refs updated
50
+ Callbacks = list | Any | None
51
+
52
+
53
+ class EmptyDict(TypedDict, total=False):
54
+ """Empty dict type."""
55
+
56
+
57
+ class RunnableConfig(TypedDict, total=False):
58
+ """Configuration for a `Runnable`.
59
+
60
+ !!! note Custom values
61
+
62
+ The `TypedDict` has `total=False` set intentionally to:
63
+
64
+ - Allow partial configs to be created and merged together via `merge_configs`
65
+ - Support config propagation from parent to child runnables via
66
+ `var_child_runnable_config` (a `ContextVar` that automatically passes
67
+ config down the call stack without explicit parameter passing), where
68
+ configs are merged rather than replaced
69
+
70
+ !!! example
71
+
72
+ ```python
73
+ # Parent sets tags
74
+ chain.invoke(input, config={"tags": ["parent"]})
75
+ # Child automatically inherits and can add:
76
+ # ensure_config({"tags": ["child"]}) -> {"tags": ["parent", "child"]}
77
+ ```
78
+ """
79
+
80
+ tags: list[str]
81
+ """Tags for this call and any sub-calls (e.g. a Chain calling an LLM).
82
+
83
+ You can use these to filter calls.
84
+ """
85
+
86
+ metadata: dict[str, Any]
87
+ """Metadata for this call and any sub-calls (e.g. a Chain calling an LLM).
88
+
89
+ Keys should be strings, values should be JSON-serializable.
90
+ """
91
+
92
+ callbacks: Callbacks
93
+ """Callbacks for this call and any sub-calls (e.g. a Chain calling an LLM).
94
+
95
+ Tags are passed to all callbacks, metadata is passed to handle*Start callbacks.
96
+ """
97
+
98
+ run_name: str
99
+ """Name for the tracer run for this call.
100
+
101
+ Defaults to the name of the class."""
102
+
103
+ max_concurrency: int | None
104
+ """Maximum number of parallel calls to make.
105
+
106
+ If not provided, defaults to `ThreadPoolExecutor`'s default.
107
+ """
108
+
109
+ recursion_limit: int
110
+ """Maximum number of times a call can recurse.
111
+
112
+ If not provided, defaults to `25`.
113
+ """
114
+
115
+ configurable: dict[str, Any]
116
+ """Runtime values for attributes previously made configurable on this `Runnable`,
117
+ or sub-`Runnable` objects, through `configurable_fields` or
118
+ `configurable_alternatives`.
119
+
120
+ Check `output_schema` for a description of the attributes that have been made
121
+ configurable.
122
+ """
123
+
124
+ run_id: uuid.UUID | None
125
+ """Unique identifier for the tracer run for this call.
126
+
127
+ If not provided, a new UUID will be generated.
128
+ """
129
+
130
+
131
+ CONFIG_KEYS = [
132
+ "tags",
133
+ "metadata",
134
+ "callbacks",
135
+ "run_name",
136
+ "max_concurrency",
137
+ "recursion_limit",
138
+ "configurable",
139
+ "run_id",
140
+ ]
141
+
142
+ COPIABLE_KEYS = [
143
+ "tags",
144
+ "metadata",
145
+ "callbacks",
146
+ "configurable",
147
+ ]
148
+
149
+
150
+ # Users are expected to use the `context` API with a context object
151
+ # (which does not get traced)
152
+ CONFIGURABLE_TO_TRACING_METADATA_EXCLUDED_KEYS = frozenset(("api_key",))
153
+
154
+
155
+ def _get_langsmith_inheritable_metadata_from_config(
156
+ config: RunnableConfig,
157
+ ) -> dict[str, Any] | None:
158
+ """Get LangSmith-only inheritable metadata defaults derived from config."""
159
+ configurable = config.get("configurable") or {}
160
+ metadata = {
161
+ key: value
162
+ for key, value in configurable.items()
163
+ if not key.startswith("__")
164
+ and isinstance(value, (str, int, float, bool))
165
+ and key not in config.get("metadata", {})
166
+ and key not in CONFIGURABLE_TO_TRACING_METADATA_EXCLUDED_KEYS
167
+ }
168
+ return metadata or None
169
+
170
+
171
+ DEFAULT_RECURSION_LIMIT = 25
172
+
173
+
174
+ var_child_runnable_config: ContextVar[RunnableConfig | None] = ContextVar(
175
+ "child_runnable_config", default=None
176
+ )
177
+
178
+
179
+ # This is imported and used in coco_graph, so don't break.
180
+ def _set_config_context(
181
+ config: RunnableConfig,
182
+ ) -> tuple[Token[RunnableConfig | None], dict[str, Any] | None]:
183
+ """Set the child Runnable config + tracing context.
184
+
185
+ Args:
186
+ config: The config to set.
187
+
188
+ Returns:
189
+ The token to reset the config and the previous tracing context.
190
+ """
191
+ # Deferred to avoid importing langsmith at module level (~132ms).
192
+ from coco_core.tracers._context import ( # noqa: PLC0415
193
+ _set_tracing_context,
194
+ get_tracing_context,
195
+ )
196
+
197
+ from coco_core.tracers.coco import CocoTracer # noqa: PLC0415
198
+
199
+ config_token = var_child_runnable_config.set(config)
200
+ current_context = None
201
+ if (
202
+ (callbacks := config.get("callbacks"))
203
+ and (
204
+ parent_run_id := getattr(callbacks, "parent_run_id", None)
205
+ ) # Is callback manager
206
+ and (
207
+ tracer := next(
208
+ (
209
+ handler
210
+ for handler in getattr(callbacks, "handlers", [])
211
+ if isinstance(handler, CocoTracer)
212
+ ),
213
+ None,
214
+ )
215
+ )
216
+ and (run := tracer.run_map.get(str(parent_run_id)))
217
+ ):
218
+ current_context = get_tracing_context()
219
+ _set_tracing_context({"parent": run})
220
+ return config_token, current_context
221
+
222
+
223
+ @contextmanager
224
+ def set_config_context(config: RunnableConfig) -> Generator[Context, None, None]:
225
+ """Set the child Runnable config + tracing context.
226
+
227
+ Args:
228
+ config: The config to set.
229
+
230
+ Yields:
231
+ The config context.
232
+ """
233
+ # Deferred to avoid importing langsmith at module level (~132ms).
234
+ from coco_core.tracers._context import _set_tracing_context # noqa: PLC0415
235
+
236
+ ctx = copy_context()
237
+ config_token, _ = ctx.run(_set_config_context, config)
238
+ try:
239
+ yield ctx
240
+ finally:
241
+ ctx.run(var_child_runnable_config.reset, config_token)
242
+ ctx.run(
243
+ _set_tracing_context,
244
+ {
245
+ "parent": None,
246
+ "project_name": None,
247
+ "tags": None,
248
+ "metadata": None,
249
+ "enabled": None,
250
+ "client": None,
251
+ },
252
+ )
253
+
254
+
255
+ def ensure_config(config: RunnableConfig | None = None) -> RunnableConfig:
256
+ """Ensure that a config is a dict with all keys present.
257
+
258
+ Args:
259
+ config: The config to ensure.
260
+
261
+ Returns:
262
+ The ensured config.
263
+ """
264
+ empty = RunnableConfig(
265
+ tags=[],
266
+ metadata={},
267
+ callbacks=None,
268
+ recursion_limit=DEFAULT_RECURSION_LIMIT,
269
+ configurable={},
270
+ )
271
+ if var_config := var_child_runnable_config.get():
272
+ empty.update(
273
+ cast(
274
+ "RunnableConfig",
275
+ {
276
+ k: v.copy() if k in COPIABLE_KEYS else v # type: ignore[attr-defined]
277
+ for k, v in var_config.items()
278
+ if v is not None
279
+ },
280
+ )
281
+ )
282
+ if config is not None:
283
+ empty.update(
284
+ cast(
285
+ "RunnableConfig",
286
+ {
287
+ k: v.copy() if k in COPIABLE_KEYS else v # type: ignore[attr-defined]
288
+ for k, v in config.items()
289
+ if v is not None and k in CONFIG_KEYS
290
+ },
291
+ )
292
+ )
293
+ if config is not None:
294
+ for k, v in config.items():
295
+ if k not in CONFIG_KEYS and v is not None:
296
+ empty["configurable"][k] = v
297
+ for configurable_key in ("model", "checkpoint_ns"):
298
+ if (
299
+ isinstance(
300
+ configurable_value := empty.get("configurable", {}).get(
301
+ configurable_key
302
+ ),
303
+ str,
304
+ )
305
+ and configurable_key not in empty["metadata"]
306
+ ):
307
+ empty["metadata"][configurable_key] = configurable_value
308
+ return empty
309
+
310
+
311
+ def get_config_list(
312
+ config: RunnableConfig | Sequence[RunnableConfig] | None, length: int
313
+ ) -> list[RunnableConfig]:
314
+ """Get a list of configs from a single config or a list of configs.
315
+
316
+ It is useful for subclasses overriding batch() or abatch().
317
+
318
+ Args:
319
+ config: The config or list of configs.
320
+ length: The length of the list.
321
+
322
+ Returns:
323
+ The list of configs.
324
+
325
+ Raises:
326
+ ValueError: If the length of the list is not equal to the length of the inputs.
327
+
328
+ """
329
+ if length < 0:
330
+ msg = f"length must be >= 0, but got {length}"
331
+ raise ValueError(msg)
332
+ if isinstance(config, Sequence) and len(config) != length:
333
+ msg = (
334
+ f"config must be a list of the same length as inputs, "
335
+ f"but got {len(config)} configs for {length} inputs"
336
+ )
337
+ raise ValueError(msg)
338
+
339
+ if isinstance(config, Sequence):
340
+ return list(map(ensure_config, config))
341
+ if length > 1 and isinstance(config, dict) and config.get("run_id") is not None:
342
+ warnings.warn(
343
+ "Provided run_id be used only for the first element of the batch.",
344
+ category=RuntimeWarning,
345
+ stacklevel=3,
346
+ )
347
+ subsequent = cast(
348
+ "RunnableConfig", {k: v for k, v in config.items() if k != "run_id"}
349
+ )
350
+ return [
351
+ ensure_config(subsequent) if i else ensure_config(config)
352
+ for i in range(length)
353
+ ]
354
+ return [ensure_config(config) for i in range(length)]
355
+
356
+
357
+ def patch_config(
358
+ config: RunnableConfig | None,
359
+ *,
360
+ callbacks: BaseCallbackManager | None = None,
361
+ recursion_limit: int | None = None,
362
+ max_concurrency: int | None = None,
363
+ run_name: str | None = None,
364
+ configurable: dict[str, Any] | None = None,
365
+ ) -> RunnableConfig:
366
+ """Patch a config with new values.
367
+
368
+ Args:
369
+ config: The config to patch.
370
+ callbacks: The callbacks to set.
371
+ recursion_limit: The recursion limit to set.
372
+ max_concurrency: The max concurrency to set.
373
+ run_name: The run name to set.
374
+ configurable: The configurable to set.
375
+
376
+ Returns:
377
+ The patched config.
378
+ """
379
+ config = ensure_config(config)
380
+ if callbacks is not None:
381
+ # If we're replacing callbacks, we need to unset run_name
382
+ # As that should apply only to the same run as the original callbacks
383
+ config["callbacks"] = callbacks
384
+ if "run_name" in config:
385
+ del config["run_name"]
386
+ if "run_id" in config:
387
+ del config["run_id"]
388
+ if recursion_limit is not None:
389
+ config["recursion_limit"] = recursion_limit
390
+ if max_concurrency is not None:
391
+ config["max_concurrency"] = max_concurrency
392
+ if run_name is not None:
393
+ config["run_name"] = run_name
394
+ if configurable is not None:
395
+ config["configurable"] = {**config.get("configurable", {}), **configurable}
396
+ return config
397
+
398
+
399
+ def _merge_metadata_dicts(
400
+ base: Mapping[str, Any], incoming: Mapping[str, Any]
401
+ ) -> dict[str, Any]:
402
+ """Merge metadata dicts, accumulating only `lc_versions` nested mappings.
403
+
404
+ Metadata generally uses last-writer-wins semantics. The Coco-owned
405
+ `lc_versions` key is the only nested mapping that accumulates across merges
406
+ so package versions from core, `coco`, and partner packages coexist.
407
+
408
+ Args:
409
+ base: The base metadata dict.
410
+
411
+ Values here are kept unless overridden by `incoming`.
412
+ incoming: The metadata dict to merge on top.
413
+
414
+ Its values take precedence on conflict.
415
+
416
+ Returns:
417
+ A new merged dict.
418
+ """
419
+ merged = {**base, **incoming}
420
+ base_versions = base.get("lc_versions")
421
+ incoming_versions = incoming.get("lc_versions")
422
+ if isinstance(base_versions, Mapping) and isinstance(incoming_versions, Mapping):
423
+ merged["lc_versions"] = {**base_versions, **incoming_versions}
424
+ elif isinstance(incoming_versions, Mapping):
425
+ merged["lc_versions"] = {**incoming_versions}
426
+ elif "lc_versions" not in incoming and isinstance(base_versions, Mapping):
427
+ merged["lc_versions"] = {**base_versions}
428
+ return merged
429
+
430
+
431
+ def merge_configs(*configs: RunnableConfig | None) -> RunnableConfig:
432
+ """Merge multiple configs into one.
433
+
434
+ Args:
435
+ *configs: The configs to merge.
436
+
437
+ Returns:
438
+ The merged config.
439
+ """
440
+ base: RunnableConfig = {}
441
+ # Even though the keys aren't literals, this is correct
442
+ # because both dicts are the same type
443
+ for config in (ensure_config(c) for c in configs if c is not None):
444
+ for key in config:
445
+ if key == "metadata":
446
+ base["metadata"] = _merge_metadata_dicts(
447
+ base.get("metadata", {}),
448
+ config.get("metadata") or {},
449
+ )
450
+ elif key == "tags":
451
+ base["tags"] = sorted(
452
+ set(base.get("tags", []) + (config.get("tags") or [])),
453
+ )
454
+ elif key == "configurable":
455
+ base["configurable"] = {
456
+ **base.get("configurable", {}),
457
+ **(config.get("configurable") or {}),
458
+ }
459
+ elif key == "callbacks":
460
+ base_callbacks = base.get("callbacks")
461
+ these_callbacks = config["callbacks"]
462
+ # callbacks can be either None, list[handler] or manager
463
+ # so merging two callbacks values has 6 cases
464
+ if isinstance(these_callbacks, list):
465
+ if base_callbacks is None:
466
+ base["callbacks"] = these_callbacks.copy()
467
+ elif isinstance(base_callbacks, list):
468
+ base["callbacks"] = base_callbacks + these_callbacks
469
+ else:
470
+ # base_callbacks is a manager
471
+ mngr = base_callbacks.copy()
472
+ for callback in these_callbacks:
473
+ mngr.add_handler(callback, inherit=True)
474
+ base["callbacks"] = mngr
475
+ elif these_callbacks is not None:
476
+ # these_callbacks is a manager
477
+ if base_callbacks is None:
478
+ base["callbacks"] = these_callbacks.copy()
479
+ elif isinstance(base_callbacks, list):
480
+ mngr = these_callbacks.copy()
481
+ for callback in base_callbacks:
482
+ mngr.add_handler(callback, inherit=True)
483
+ base["callbacks"] = mngr
484
+ else:
485
+ # base_callbacks is also a manager
486
+ base["callbacks"] = base_callbacks.merge(these_callbacks)
487
+ elif key == "recursion_limit":
488
+ if config["recursion_limit"] != DEFAULT_RECURSION_LIMIT:
489
+ base["recursion_limit"] = config["recursion_limit"]
490
+ elif key in COPIABLE_KEYS and config[key] is not None: # type: ignore[literal-required]
491
+ base[key] = config[key].copy() # type: ignore[literal-required]
492
+ else:
493
+ base[key] = config[key] or base.get(key) # type: ignore[literal-required]
494
+ return base
495
+
496
+
497
+ def call_func_with_variable_args(
498
+ func: Callable[[Input], Output]
499
+ | Callable[[Input, RunnableConfig], Output]
500
+ | Callable[[Input, CallbackManagerForChainRun], Output]
501
+ | Callable[[Input, CallbackManagerForChainRun, RunnableConfig], Output],
502
+ input: Input,
503
+ config: RunnableConfig,
504
+ run_manager: CallbackManagerForChainRun | None = None,
505
+ **kwargs: Any,
506
+ ) -> Output:
507
+ """Call function that may optionally accept a run_manager and/or config.
508
+
509
+ Args:
510
+ func: The function to call.
511
+ input: The input to the function.
512
+ config: The config to pass to the function.
513
+ run_manager: The run manager to pass to the function.
514
+ **kwargs: The keyword arguments to pass to the function.
515
+
516
+ Returns:
517
+ The output of the function.
518
+ """
519
+ if accepts_config(func):
520
+ if run_manager is not None:
521
+ kwargs["config"] = patch_config(config, callbacks=run_manager.get_child())
522
+ else:
523
+ kwargs["config"] = config
524
+ if run_manager is not None and accepts_run_manager(func):
525
+ kwargs["run_manager"] = run_manager
526
+ return func(input, **kwargs) # type: ignore[call-arg]
527
+
528
+
529
+ def acall_func_with_variable_args(
530
+ func: Callable[[Input], Awaitable[Output]]
531
+ | Callable[[Input, RunnableConfig], Awaitable[Output]]
532
+ | Callable[[Input, AsyncCallbackManagerForChainRun], Awaitable[Output]]
533
+ | Callable[
534
+ [Input, AsyncCallbackManagerForChainRun, RunnableConfig], Awaitable[Output]
535
+ ],
536
+ input: Input,
537
+ config: RunnableConfig,
538
+ run_manager: AsyncCallbackManagerForChainRun | None = None,
539
+ **kwargs: Any,
540
+ ) -> Awaitable[Output]:
541
+ """Async call function that may optionally accept a run_manager and/or config.
542
+
543
+ Args:
544
+ func: The function to call.
545
+ input: The input to the function.
546
+ config: The config to pass to the function.
547
+ run_manager: The run manager to pass to the function.
548
+ **kwargs: The keyword arguments to pass to the function.
549
+
550
+ Returns:
551
+ The output of the function.
552
+ """
553
+ if accepts_config(func):
554
+ if run_manager is not None:
555
+ kwargs["config"] = patch_config(config, callbacks=run_manager.get_child())
556
+ else:
557
+ kwargs["config"] = config
558
+ if run_manager is not None and accepts_run_manager(func):
559
+ kwargs["run_manager"] = run_manager
560
+ return func(input, **kwargs) # type: ignore[call-arg]
561
+
562
+
563
+ def get_callback_manager_for_config(config: RunnableConfig) -> CallbackManager:
564
+ """Get a callback manager for a config.
565
+
566
+ Args:
567
+ config: The config.
568
+
569
+ Returns:
570
+ The callback manager.
571
+ """
572
+ return CallbackManager.configure(
573
+ inheritable_callbacks=config.get("callbacks"),
574
+ inheritable_tags=config.get("tags"),
575
+ inheritable_metadata=config.get("metadata"),
576
+ langsmith_inheritable_metadata=_get_langsmith_inheritable_metadata_from_config(
577
+ config
578
+ ),
579
+ )
580
+
581
+
582
+ def get_async_callback_manager_for_config(
583
+ config: RunnableConfig,
584
+ ) -> AsyncCallbackManager:
585
+ """Get an async callback manager for a config.
586
+
587
+ Args:
588
+ config: The config.
589
+
590
+ Returns:
591
+ The async callback manager.
592
+ """
593
+ return AsyncCallbackManager.configure(
594
+ inheritable_callbacks=config.get("callbacks"),
595
+ inheritable_tags=config.get("tags"),
596
+ inheritable_metadata=config.get("metadata"),
597
+ langsmith_inheritable_metadata=_get_langsmith_inheritable_metadata_from_config(
598
+ config
599
+ ),
600
+ )
601
+
602
+
603
+ P = ParamSpec("P")
604
+ T = TypeVar("T")
605
+
606
+
607
+ class ContextThreadPoolExecutor(ThreadPoolExecutor):
608
+ """ThreadPoolExecutor that copies the context to the child thread."""
609
+
610
+ def submit( # type: ignore[override]
611
+ self,
612
+ func: Callable[P, T],
613
+ *args: P.args,
614
+ **kwargs: P.kwargs,
615
+ ) -> Future[T]:
616
+ """Submit a function to the executor.
617
+
618
+ Args:
619
+ func: The function to submit.
620
+ *args: The positional arguments to the function.
621
+ **kwargs: The keyword arguments to the function.
622
+
623
+ Returns:
624
+ The future for the function.
625
+ """
626
+ return super().submit(
627
+ cast("Callable[..., T]", partial(copy_context().run, func, *args, **kwargs))
628
+ )
629
+
630
+ def map(
631
+ self,
632
+ fn: Callable[..., T],
633
+ *iterables: Iterable[Any],
634
+ **kwargs: Any,
635
+ ) -> Iterator[T]:
636
+ """Map a function to multiple iterables.
637
+
638
+ Args:
639
+ fn: The function to map.
640
+ *iterables: The iterables to map over.
641
+ timeout: The timeout for the map.
642
+ chunksize: The chunksize for the map.
643
+
644
+ Returns:
645
+ The iterator for the mapped function.
646
+ """
647
+ contexts = [copy_context() for _ in range(len(iterables[0]))] # type: ignore[arg-type]
648
+
649
+ def _wrapped_fn(*args: Any) -> T:
650
+ return contexts.pop().run(fn, *args)
651
+
652
+ return super().map(
653
+ _wrapped_fn,
654
+ *iterables,
655
+ **kwargs,
656
+ )
657
+
658
+
659
+ @contextmanager
660
+ def get_executor_for_config(
661
+ config: RunnableConfig | None,
662
+ ) -> Generator[Executor, None, None]:
663
+ """Get an executor for a config.
664
+
665
+ Args:
666
+ config: The config.
667
+
668
+ Yields:
669
+ The executor.
670
+ """
671
+ config = config or {}
672
+ with ContextThreadPoolExecutor(
673
+ max_workers=config.get("max_concurrency")
674
+ ) as executor:
675
+ yield executor
676
+
677
+
678
+ async def run_in_executor(
679
+ executor_or_config: Executor | RunnableConfig | None,
680
+ func: Callable[P, T],
681
+ *args: P.args,
682
+ **kwargs: P.kwargs,
683
+ ) -> T:
684
+ """Run a function in an executor.
685
+
686
+ Args:
687
+ executor_or_config: The executor or config to run in.
688
+ func: The function.
689
+ *args: The positional arguments to the function.
690
+ **kwargs: The keyword arguments to the function.
691
+
692
+ Returns:
693
+ The output of the function.
694
+ """
695
+
696
+ def wrapper() -> T:
697
+ try:
698
+ return func(*args, **kwargs)
699
+ except StopIteration as exc:
700
+ # StopIteration can't be set on an asyncio.Future
701
+ # it raises a TypeError and leaves the Future pending forever
702
+ # so we need to convert it to a RuntimeError
703
+ raise RuntimeError from exc
704
+
705
+ if executor_or_config is None or isinstance(executor_or_config, dict):
706
+ # Use default executor with context copied from current context
707
+ return await asyncio.get_running_loop().run_in_executor(
708
+ None,
709
+ cast("Callable[..., T]", partial(copy_context().run, wrapper)),
710
+ )
711
+
712
+ return await asyncio.get_running_loop().run_in_executor(executor_or_config, wrapper)