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,798 @@
1
+ """Utility code for `Runnable` objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import asyncio
7
+ import inspect
8
+ import sys
9
+ import textwrap
10
+
11
+ # Cannot move to TYPE_CHECKING as Mapping and Sequence are needed at runtime by
12
+ # RunnableConfigurableFields.
13
+ from collections.abc import Mapping, Sequence # noqa: TC003
14
+ from functools import lru_cache
15
+ from inspect import signature
16
+ from itertools import groupby
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ Any,
20
+ NamedTuple,
21
+ Protocol,
22
+ TypeGuard,
23
+ TypeVar,
24
+ )
25
+
26
+ from typing_extensions import override
27
+
28
+ # Re-export create-model for backwards compatibility
29
+ from coco_core.utils.pydantic import create_model # noqa: F401
30
+
31
+ if TYPE_CHECKING:
32
+ from collections.abc import (
33
+ AsyncIterable,
34
+ AsyncIterator,
35
+ Awaitable,
36
+ Callable,
37
+ Coroutine,
38
+ Iterable,
39
+ )
40
+ from contextvars import Context
41
+
42
+ from coco_core.runnables.schema import StreamEvent
43
+
44
+ Input = TypeVar("Input", contravariant=True) # noqa: PLC0105
45
+ # Output type should implement __concat__, as eg str, list, dict do
46
+ Output = TypeVar("Output", covariant=True) # noqa: PLC0105
47
+
48
+
49
+ async def gated_coro(
50
+ semaphore: asyncio.Semaphore, coro: Coroutine[Any, Any, Any]
51
+ ) -> Any:
52
+ """Run a coroutine with a semaphore.
53
+
54
+ Args:
55
+ semaphore: The semaphore to use.
56
+ coro: The coroutine to run.
57
+
58
+ Returns:
59
+ The result of the coroutine.
60
+ """
61
+ async with semaphore:
62
+ return await coro
63
+
64
+
65
+ async def gather_with_concurrency(
66
+ n: int | None, *coros: Coroutine[Any, Any, Any]
67
+ ) -> list[Any]:
68
+ """Gather coroutines with a limit on the number of concurrent coroutines.
69
+
70
+ Args:
71
+ n: The number of coroutines to run concurrently.
72
+ *coros: The coroutines to run.
73
+
74
+ Returns:
75
+ The results of the coroutines.
76
+ """
77
+ if n is None:
78
+ return await asyncio.gather(*coros)
79
+
80
+ semaphore = asyncio.Semaphore(n)
81
+
82
+ return await asyncio.gather(*(gated_coro(semaphore, c) for c in coros))
83
+
84
+
85
+ def accepts_run_manager(callable: Callable[..., Any]) -> bool: # noqa: A002
86
+ """Check if a callable accepts a run_manager argument.
87
+
88
+ Args:
89
+ callable: The callable to check.
90
+
91
+ Returns:
92
+ `True` if the callable accepts a run_manager argument, `False` otherwise.
93
+ """
94
+ try:
95
+ return signature(callable).parameters.get("run_manager") is not None
96
+ except ValueError:
97
+ return False
98
+
99
+
100
+ def accepts_config(callable: Callable[..., Any]) -> bool: # noqa: A002
101
+ """Check if a callable accepts a config argument.
102
+
103
+ Args:
104
+ callable: The callable to check.
105
+
106
+ Returns:
107
+ `True` if the callable accepts a config argument, `False` otherwise.
108
+ """
109
+ try:
110
+ return signature(callable).parameters.get("config") is not None
111
+ except ValueError:
112
+ return False
113
+
114
+
115
+ def accepts_context(callable: Callable[..., Any]) -> bool: # noqa: A002
116
+ """Check if a callable accepts a context argument.
117
+
118
+ Args:
119
+ callable: The callable to check.
120
+
121
+ Returns:
122
+ `True` if the callable accepts a context argument, `False` otherwise.
123
+ """
124
+ try:
125
+ return signature(callable).parameters.get("context") is not None
126
+ except ValueError:
127
+ return False
128
+
129
+
130
+ def asyncio_accepts_context() -> bool:
131
+ """Check if asyncio.create_task accepts a `context` arg.
132
+
133
+ Returns:
134
+ True if `asyncio.create_task` accepts a context argument, `False` otherwise.
135
+ """
136
+ return sys.version_info >= (3, 11)
137
+
138
+
139
+ _T = TypeVar("_T")
140
+
141
+
142
+ def coro_with_context(
143
+ coro: Awaitable[_T], context: Context, *, create_task: bool = False
144
+ ) -> Awaitable[_T]:
145
+ """Await a coroutine with a context.
146
+
147
+ Args:
148
+ coro: The coroutine to await.
149
+ context: The context to use.
150
+ create_task: Kept for compatibility; this helper always creates a task.
151
+
152
+ Returns:
153
+ The coroutine with the context.
154
+ """
155
+ if asyncio_accepts_context():
156
+ return asyncio.create_task(coro, context=context) # type: ignore[arg-type,call-arg,unused-ignore]
157
+ del create_task
158
+ return context.run(asyncio.create_task, coro) # type: ignore[arg-type]
159
+
160
+
161
+ class IsLocalDict(ast.NodeVisitor):
162
+ """Check if a name is a local dict."""
163
+
164
+ def __init__(self, name: str, keys: set[str]) -> None:
165
+ """Initialize the visitor.
166
+
167
+ Args:
168
+ name: The name to check.
169
+ keys: The keys to populate.
170
+ """
171
+ self.name = name
172
+ self.keys = keys
173
+
174
+ @override
175
+ def visit_Subscript(self, node: ast.Subscript) -> None:
176
+ """Visit a subscript node.
177
+
178
+ Args:
179
+ node: The node to visit.
180
+ """
181
+ if (
182
+ isinstance(node.ctx, ast.Load)
183
+ and isinstance(node.value, ast.Name)
184
+ and node.value.id == self.name
185
+ and isinstance(node.slice, ast.Constant)
186
+ and isinstance(node.slice.value, str)
187
+ ):
188
+ # we've found a subscript access on the name we're looking for
189
+ self.keys.add(node.slice.value)
190
+
191
+ @override
192
+ def visit_Call(self, node: ast.Call) -> None:
193
+ """Visit a call node.
194
+
195
+ Args:
196
+ node: The node to visit.
197
+ """
198
+ if (
199
+ isinstance(node.func, ast.Attribute)
200
+ and isinstance(node.func.value, ast.Name)
201
+ and node.func.value.id == self.name
202
+ and node.func.attr == "get"
203
+ and len(node.args) in {1, 2}
204
+ and isinstance(node.args[0], ast.Constant)
205
+ and isinstance(node.args[0].value, str)
206
+ ):
207
+ # we've found a .get() call on the name we're looking for
208
+ self.keys.add(node.args[0].value)
209
+
210
+
211
+ class IsFunctionArgDict(ast.NodeVisitor):
212
+ """Check if the first argument of a function is a dict."""
213
+
214
+ def __init__(self) -> None:
215
+ """Create a IsFunctionArgDict visitor."""
216
+ self.keys: set[str] = set()
217
+
218
+ @override
219
+ def visit_Lambda(self, node: ast.Lambda) -> None:
220
+ """Visit a lambda function.
221
+
222
+ Args:
223
+ node: The node to visit.
224
+ """
225
+ if not node.args.args:
226
+ return
227
+ input_arg_name = node.args.args[0].arg
228
+ IsLocalDict(input_arg_name, self.keys).visit(node.body)
229
+
230
+ @override
231
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
232
+ """Visit a function definition.
233
+
234
+ Args:
235
+ node: The node to visit.
236
+ """
237
+ if not node.args.args:
238
+ return
239
+ input_arg_name = node.args.args[0].arg
240
+ IsLocalDict(input_arg_name, self.keys).visit(node)
241
+
242
+ @override
243
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
244
+ """Visit an async function definition.
245
+
246
+ Args:
247
+ node: The node to visit.
248
+ """
249
+ if not node.args.args:
250
+ return
251
+ input_arg_name = node.args.args[0].arg
252
+ IsLocalDict(input_arg_name, self.keys).visit(node)
253
+
254
+
255
+ class NonLocals(ast.NodeVisitor):
256
+ """Get nonlocal variables accessed."""
257
+
258
+ def __init__(self) -> None:
259
+ """Create a NonLocals visitor."""
260
+ self.loads: set[str] = set()
261
+ self.stores: set[str] = set()
262
+
263
+ @override
264
+ def visit_Name(self, node: ast.Name) -> None:
265
+ """Visit a name node.
266
+
267
+ Args:
268
+ node: The node to visit.
269
+ """
270
+ if isinstance(node.ctx, ast.Load):
271
+ self.loads.add(node.id)
272
+ elif isinstance(node.ctx, ast.Store):
273
+ self.stores.add(node.id)
274
+
275
+ @override
276
+ def visit_Attribute(self, node: ast.Attribute) -> None:
277
+ """Visit an attribute node.
278
+
279
+ Args:
280
+ node: The node to visit.
281
+ """
282
+ if isinstance(node.ctx, ast.Load):
283
+ parent = node.value
284
+ attr_expr = node.attr
285
+ while isinstance(parent, ast.Attribute):
286
+ attr_expr = parent.attr + "." + attr_expr
287
+ parent = parent.value
288
+ if isinstance(parent, ast.Name):
289
+ self.loads.add(parent.id + "." + attr_expr)
290
+ self.loads.discard(parent.id)
291
+ elif isinstance(parent, ast.Call):
292
+ if isinstance(parent.func, ast.Name):
293
+ self.loads.add(parent.func.id)
294
+ else:
295
+ parent = parent.func
296
+ attr_expr = ""
297
+ while isinstance(parent, ast.Attribute):
298
+ if attr_expr:
299
+ attr_expr = parent.attr + "." + attr_expr
300
+ else:
301
+ attr_expr = parent.attr
302
+ parent = parent.value
303
+ if isinstance(parent, ast.Name):
304
+ self.loads.add(parent.id + "." + attr_expr)
305
+
306
+
307
+ class FunctionNonLocals(ast.NodeVisitor):
308
+ """Get the nonlocal variables accessed of a function."""
309
+
310
+ def __init__(self) -> None:
311
+ """Create a FunctionNonLocals visitor."""
312
+ self.nonlocals: set[str] = set()
313
+
314
+ @override
315
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
316
+ """Visit a function definition.
317
+
318
+ Args:
319
+ node: The node to visit.
320
+ """
321
+ visitor = NonLocals()
322
+ visitor.visit(node)
323
+ self.nonlocals.update(visitor.loads - visitor.stores)
324
+
325
+ @override
326
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
327
+ """Visit an async function definition.
328
+
329
+ Args:
330
+ node: The node to visit.
331
+ """
332
+ visitor = NonLocals()
333
+ visitor.visit(node)
334
+ self.nonlocals.update(visitor.loads - visitor.stores)
335
+
336
+ @override
337
+ def visit_Lambda(self, node: ast.Lambda) -> None:
338
+ """Visit a lambda function.
339
+
340
+ Args:
341
+ node: The node to visit.
342
+ """
343
+ visitor = NonLocals()
344
+ visitor.visit(node)
345
+ self.nonlocals.update(visitor.loads - visitor.stores)
346
+
347
+
348
+ class GetLambdaSource(ast.NodeVisitor):
349
+ """Get the source code of a lambda function."""
350
+
351
+ def __init__(self) -> None:
352
+ """Initialize the visitor."""
353
+ self.source: str | None = None
354
+ self.count = 0
355
+
356
+ @override
357
+ def visit_Lambda(self, node: ast.Lambda) -> None:
358
+ """Visit a lambda function.
359
+
360
+ Args:
361
+ node: The node to visit.
362
+ """
363
+ self.count += 1
364
+ if hasattr(ast, "unparse"):
365
+ self.source = ast.unparse(node)
366
+
367
+
368
+ def get_function_first_arg_dict_keys(func: Callable[..., Any]) -> list[str] | None:
369
+ """Get the keys of the first argument of a function if it is a dict.
370
+
371
+ Args:
372
+ func: The function to check.
373
+
374
+ Returns:
375
+ The keys of the first argument if it is a dict, None otherwise.
376
+ """
377
+ try:
378
+ code = inspect.getsource(func)
379
+ tree = ast.parse(textwrap.dedent(code))
380
+ visitor = IsFunctionArgDict()
381
+ visitor.visit(tree)
382
+ return sorted(visitor.keys) if visitor.keys else None
383
+ except (SyntaxError, TypeError, OSError, SystemError):
384
+ return None
385
+
386
+
387
+ def get_lambda_source(func: Callable[..., Any]) -> str | None:
388
+ """Get the source code of a lambda function.
389
+
390
+ Args:
391
+ func: a Callable that can be a lambda function.
392
+
393
+ Returns:
394
+ the source code of the lambda function.
395
+ """
396
+ try:
397
+ name = func.__name__ if func.__name__ != "<lambda>" else None
398
+ except AttributeError:
399
+ name = None
400
+ try:
401
+ code = inspect.getsource(func)
402
+ tree = ast.parse(textwrap.dedent(code))
403
+ visitor = GetLambdaSource()
404
+ visitor.visit(tree)
405
+ except (SyntaxError, TypeError, OSError, SystemError):
406
+ return name
407
+ return visitor.source if visitor.count == 1 else name
408
+
409
+
410
+ @lru_cache(maxsize=256)
411
+ def get_function_nonlocals(func: Callable[..., Any]) -> list[Any]:
412
+ """Get the nonlocal variables accessed by a function.
413
+
414
+ Args:
415
+ func: The function to check.
416
+
417
+ Returns:
418
+ The nonlocal variables accessed by the function.
419
+ """
420
+ try:
421
+ code = inspect.getsource(func)
422
+ tree = ast.parse(textwrap.dedent(code))
423
+ visitor = FunctionNonLocals()
424
+ visitor.visit(tree)
425
+ values: list[Any] = []
426
+ closure = (
427
+ inspect.getclosurevars(func.__wrapped__)
428
+ if hasattr(func, "__wrapped__") and callable(func.__wrapped__)
429
+ else inspect.getclosurevars(func)
430
+ )
431
+ candidates = {**closure.globals, **closure.nonlocals}
432
+ for k, v in candidates.items():
433
+ if k in visitor.nonlocals:
434
+ values.append(v)
435
+ for kk in visitor.nonlocals:
436
+ if "." in kk and kk.startswith(k):
437
+ vv = v
438
+ for part in kk.split(".")[1:]:
439
+ if vv is None:
440
+ break
441
+ try:
442
+ vv = getattr(vv, part)
443
+ except AttributeError:
444
+ break
445
+ else:
446
+ values.append(vv)
447
+ except (SyntaxError, TypeError, OSError, SystemError):
448
+ return []
449
+
450
+ return values
451
+
452
+
453
+ def indent_lines_after_first(text: str, prefix: str) -> str:
454
+ """Indent all lines of text after the first line.
455
+
456
+ Args:
457
+ text: The text to indent.
458
+ prefix: Used to determine the number of spaces to indent.
459
+
460
+ Returns:
461
+ The indented text.
462
+ """
463
+ n_spaces = len(prefix)
464
+ spaces = " " * n_spaces
465
+ lines = text.splitlines()
466
+ return "\n".join([lines[0]] + [spaces + line for line in lines[1:]])
467
+
468
+
469
+ class AddableDict(dict[str, Any]):
470
+ """Dictionary that can be added to another dictionary."""
471
+
472
+ def __add__(self, other: AddableDict) -> AddableDict:
473
+ """Add a dictionary to this dictionary.
474
+
475
+ Args:
476
+ other: The other dictionary to add.
477
+
478
+ Returns:
479
+ A dictionary that is the result of adding the two dictionaries.
480
+
481
+ Raises:
482
+ TypeError: If a shared key holds values of incompatible types.
483
+ """
484
+ chunk = AddableDict(self)
485
+ for key in other:
486
+ if key not in chunk or chunk[key] is None:
487
+ chunk[key] = other[key]
488
+ elif other[key] is not None:
489
+ try:
490
+ added = chunk[key] + other[key]
491
+ except TypeError as exc:
492
+ msg = (
493
+ f"Cannot add incompatible types for key {key!r}: "
494
+ f"{type(chunk[key]).__name__!r} and "
495
+ f"{type(other[key]).__name__!r}."
496
+ )
497
+ raise TypeError(msg) from exc
498
+ chunk[key] = added
499
+ return chunk
500
+
501
+ def __radd__(self, other: AddableDict) -> AddableDict:
502
+ """Add this dictionary to another dictionary.
503
+
504
+ Args:
505
+ other: The other dictionary to be added to.
506
+
507
+ Returns:
508
+ A dictionary that is the result of adding the two dictionaries.
509
+
510
+ Raises:
511
+ TypeError: If a shared key holds values of incompatible types.
512
+ """
513
+ chunk = AddableDict(other)
514
+ for key in self:
515
+ if key not in chunk or chunk[key] is None:
516
+ chunk[key] = self[key]
517
+ elif self[key] is not None:
518
+ try:
519
+ added = chunk[key] + self[key]
520
+ except TypeError as exc:
521
+ msg = (
522
+ f"Cannot add incompatible types for key {key!r}: "
523
+ f"{type(chunk[key]).__name__!r} and "
524
+ f"{type(self[key]).__name__!r}."
525
+ )
526
+ raise TypeError(msg) from exc
527
+ chunk[key] = added
528
+ return chunk
529
+
530
+
531
+ _T_co = TypeVar("_T_co", covariant=True)
532
+ _T_contra = TypeVar("_T_contra", contravariant=True)
533
+
534
+
535
+ class SupportsAdd(Protocol[_T_contra, _T_co]):
536
+ """Protocol for objects that support addition."""
537
+
538
+ def __add__(self, x: _T_contra, /) -> _T_co:
539
+ """Add the object to another object."""
540
+
541
+
542
+ Addable = TypeVar("Addable", bound=SupportsAdd[Any, Any])
543
+
544
+
545
+ def add(addables: Iterable[Addable]) -> Addable | None:
546
+ """Add a sequence of addable objects together.
547
+
548
+ Args:
549
+ addables: The addable objects to add.
550
+
551
+ Returns:
552
+ The result of adding the addable objects.
553
+ """
554
+ final: Addable | None = None
555
+ for chunk in addables:
556
+ final = chunk if final is None else final + chunk
557
+ return final
558
+
559
+
560
+ async def aadd(addables: AsyncIterable[Addable]) -> Addable | None:
561
+ """Asynchronously add a sequence of addable objects together.
562
+
563
+ Args:
564
+ addables: The addable objects to add.
565
+
566
+ Returns:
567
+ The result of adding the addable objects.
568
+ """
569
+ final: Addable | None = None
570
+ async for chunk in addables:
571
+ final = chunk if final is None else final + chunk
572
+ return final
573
+
574
+
575
+ class ConfigurableField(NamedTuple):
576
+ """Field that can be configured by the user."""
577
+
578
+ id: str
579
+ """The unique identifier of the field."""
580
+
581
+ name: str | None = None
582
+ """The name of the field. """
583
+
584
+ description: str | None = None
585
+ """The description of the field. """
586
+
587
+ annotation: Any | None = None
588
+ """The annotation of the field. """
589
+
590
+ is_shared: bool = False
591
+ """Whether the field is shared."""
592
+
593
+ @override
594
+ def __hash__(self) -> int:
595
+ return hash((self.id, self.annotation))
596
+
597
+
598
+ class ConfigurableFieldSingleOption(NamedTuple):
599
+ """Field that can be configured by the user with a default value."""
600
+
601
+ id: str
602
+ """The unique identifier of the field."""
603
+
604
+ options: Mapping[str, Any]
605
+ """The options for the field."""
606
+
607
+ default: str
608
+ """The default value for the field."""
609
+
610
+ name: str | None = None
611
+ """The name of the field. """
612
+
613
+ description: str | None = None
614
+ """The description of the field. """
615
+
616
+ is_shared: bool = False
617
+ """Whether the field is shared."""
618
+
619
+ @override
620
+ def __hash__(self) -> int:
621
+ return hash((self.id, tuple(self.options.keys()), self.default))
622
+
623
+
624
+ class ConfigurableFieldMultiOption(NamedTuple):
625
+ """Field that can be configured by the user with multiple default values."""
626
+
627
+ id: str
628
+ """The unique identifier of the field."""
629
+
630
+ options: Mapping[str, Any]
631
+ """The options for the field."""
632
+
633
+ default: Sequence[str]
634
+ """The default values for the field."""
635
+
636
+ name: str | None = None
637
+ """The name of the field. """
638
+
639
+ description: str | None = None
640
+ """The description of the field. """
641
+
642
+ is_shared: bool = False
643
+ """Whether the field is shared."""
644
+
645
+ @override
646
+ def __hash__(self) -> int:
647
+ return hash((self.id, tuple(self.options.keys()), tuple(self.default)))
648
+
649
+
650
+ AnyConfigurableField = (
651
+ ConfigurableField | ConfigurableFieldSingleOption | ConfigurableFieldMultiOption
652
+ )
653
+
654
+
655
+ class ConfigurableFieldSpec(NamedTuple):
656
+ """Field that can be configured by the user. It is a specification of a field."""
657
+
658
+ id: str
659
+ """The unique identifier of the field."""
660
+
661
+ annotation: Any
662
+ """The annotation of the field."""
663
+
664
+ name: str | None = None
665
+ """The name of the field. """
666
+
667
+ description: str | None = None
668
+ """The description of the field. """
669
+
670
+ default: Any = None
671
+ """The default value for the field. """
672
+
673
+ is_shared: bool = False
674
+ """Whether the field is shared."""
675
+
676
+ dependencies: list[str] | None = None
677
+ """The dependencies of the field. """
678
+
679
+
680
+ def get_unique_config_specs(
681
+ specs: Iterable[ConfigurableFieldSpec],
682
+ ) -> list[ConfigurableFieldSpec]:
683
+ """Get the unique config specs from a sequence of config specs.
684
+
685
+ Args:
686
+ specs: The config specs.
687
+
688
+ Returns:
689
+ The unique config specs.
690
+
691
+ Raises:
692
+ ValueError: If the runnable sequence contains conflicting config specs.
693
+ """
694
+ grouped = groupby(
695
+ sorted(specs, key=lambda s: (s.id, *(s.dependencies or []))), lambda s: s.id
696
+ )
697
+ unique: list[ConfigurableFieldSpec] = []
698
+ for spec_id, dupes in grouped:
699
+ first = next(dupes)
700
+ others = list(dupes)
701
+ if len(others) == 0 or all(o == first for o in others):
702
+ unique.append(first)
703
+ else:
704
+ msg = (
705
+ "RunnableSequence contains conflicting config specs"
706
+ f"for {spec_id}: {[first, *others]}"
707
+ )
708
+ raise ValueError(msg)
709
+ return unique
710
+
711
+
712
+ class _RootEventFilter:
713
+ def __init__(
714
+ self,
715
+ *,
716
+ include_names: Sequence[str] | None = None,
717
+ include_types: Sequence[str] | None = None,
718
+ include_tags: Sequence[str] | None = None,
719
+ exclude_names: Sequence[str] | None = None,
720
+ exclude_types: Sequence[str] | None = None,
721
+ exclude_tags: Sequence[str] | None = None,
722
+ ) -> None:
723
+ """Utility to filter the root event in the astream_events implementation.
724
+
725
+ This is simply binding the arguments to the namespace to make save on
726
+ a bit of typing in the astream_events implementation.
727
+ """
728
+ self.include_names = include_names
729
+ self.include_types = include_types
730
+ self.include_tags = include_tags
731
+ self.exclude_names = exclude_names
732
+ self.exclude_types = exclude_types
733
+ self.exclude_tags = exclude_tags
734
+
735
+ def include_event(self, event: StreamEvent, root_type: str) -> bool:
736
+ """Determine whether to include an event."""
737
+ if (
738
+ self.include_names is None
739
+ and self.include_types is None
740
+ and self.include_tags is None
741
+ ):
742
+ include = True
743
+ else:
744
+ include = False
745
+
746
+ event_tags = event.get("tags") or []
747
+
748
+ if self.include_names is not None:
749
+ include = include or event["name"] in self.include_names
750
+ if self.include_types is not None:
751
+ include = include or root_type in self.include_types
752
+ if self.include_tags is not None:
753
+ include = include or any(tag in self.include_tags for tag in event_tags)
754
+
755
+ if self.exclude_names is not None:
756
+ include = include and event["name"] not in self.exclude_names
757
+ if self.exclude_types is not None:
758
+ include = include and root_type not in self.exclude_types
759
+ if self.exclude_tags is not None:
760
+ include = include and all(
761
+ tag not in self.exclude_tags for tag in event_tags
762
+ )
763
+
764
+ return include
765
+
766
+
767
+ def is_async_generator(
768
+ func: Any,
769
+ ) -> TypeGuard[Callable[..., AsyncIterator[Any]]]:
770
+ """Check if a function is an async generator.
771
+
772
+ Args:
773
+ func: The function to check.
774
+
775
+ Returns:
776
+ `True` if the function is an async generator, `False` otherwise.
777
+ """
778
+ return inspect.isasyncgenfunction(func) or (
779
+ hasattr(func, "__call__") # noqa: B004
780
+ and inspect.isasyncgenfunction(func.__call__)
781
+ )
782
+
783
+
784
+ def is_async_callable(
785
+ func: Any,
786
+ ) -> TypeGuard[Callable[..., Awaitable[Any]]]:
787
+ """Check if a function is async.
788
+
789
+ Args:
790
+ func: The function to check.
791
+
792
+ Returns:
793
+ `True` if the function is async, `False` otherwise.
794
+ """
795
+ return inspect.iscoroutinefunction(func) or (
796
+ hasattr(func, "__call__") # noqa: B004
797
+ and inspect.iscoroutinefunction(func.__call__)
798
+ )