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,765 @@
1
+ """Tracer that streams run logs to a stream."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import contextlib
7
+ import copy
8
+ import threading
9
+ from collections import defaultdict
10
+ from pprint import pformat
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ Literal,
15
+ TypeVar,
16
+ overload,
17
+ )
18
+
19
+ import jsonpatch # type: ignore[import-untyped]
20
+ from typing_extensions import NotRequired, TypedDict, override
21
+
22
+ from coco_core.callbacks.base import BaseCallbackManager
23
+ from coco_core.load import dumps
24
+ from coco_core.load.load import load
25
+ from coco_core.outputs import ChatGenerationChunk, GenerationChunk
26
+ from coco_core.runnables import RunnableConfig, ensure_config
27
+ from coco_core.tracers._streaming import _StreamingCallbackHandler
28
+ from coco_core.tracers.base import BaseTracer
29
+ from coco_core.tracers.memory_stream import _get_or_create_loop, _MemoryStream
30
+
31
+ if TYPE_CHECKING:
32
+ from collections.abc import AsyncIterator, Iterator, Sequence
33
+ from uuid import UUID
34
+
35
+ from coco_core.runnables import Runnable
36
+ from coco_core.runnables.utils import Input, Output
37
+ from coco_core.tracers.schemas import Run
38
+
39
+
40
+ class LogEntry(TypedDict):
41
+ """A single entry in the run log."""
42
+
43
+ id: str
44
+ """ID of the sub-run."""
45
+
46
+ name: str
47
+ """Name of the object being run."""
48
+
49
+ type: str
50
+ """Type of the object being run, eg. prompt, chain, llm, etc."""
51
+
52
+ tags: list[str]
53
+ """List of tags for the run."""
54
+
55
+ metadata: dict[str, Any]
56
+ """Key-value pairs of metadata for the run."""
57
+
58
+ start_time: str
59
+ """ISO-8601 timestamp of when the run started."""
60
+
61
+ streamed_output_str: list[str]
62
+ """List of LLM tokens streamed by this run, if applicable."""
63
+
64
+ streamed_output: list[Any]
65
+ """List of output chunks streamed by this run, if available."""
66
+
67
+ inputs: NotRequired[Any | None]
68
+ """Inputs to this run. Not available currently via `astream_log`."""
69
+
70
+ final_output: Any | None
71
+ """Final output of this run.
72
+
73
+ Only available after the run has finished successfully.
74
+ """
75
+
76
+ end_time: str | None
77
+ """ISO-8601 timestamp of when the run ended.
78
+
79
+ Only available after the run has finished.
80
+ """
81
+
82
+
83
+ class RunState(TypedDict):
84
+ """State of the run."""
85
+
86
+ id: str
87
+ """ID of the run."""
88
+
89
+ streamed_output: list[Any]
90
+ """List of output chunks streamed by `Runnable.stream()`"""
91
+
92
+ final_output: Any | None
93
+ """Final output of the run, usually the result of aggregating (`+`) streamed_output.
94
+
95
+ Updated throughout the run when supported by the `Runnable`.
96
+ """
97
+
98
+ name: str
99
+ """Name of the object being run."""
100
+
101
+ type: str
102
+ """Type of the object being run, e.g. prompt, chain, llm, etc."""
103
+
104
+ # Do we want tags/metadata on the root run? Client kinda knows it in most situations
105
+ # tags: list[str]
106
+
107
+ logs: dict[str, LogEntry]
108
+ """Map of run names to sub-runs.
109
+
110
+ If filters were supplied, this list will contain only the runs that matched the
111
+ filters.
112
+ """
113
+
114
+
115
+ class RunLogPatch:
116
+ """Patch to the run log."""
117
+
118
+ ops: list[dict[str, Any]]
119
+ """List of `JSONPatch` operations, which describe how to create the run state
120
+ from an empty dict.
121
+
122
+ This is the minimal representation of the log, designed to be serialized as JSON and
123
+ sent over the wire to reconstruct the log on the other side. Reconstruction of the
124
+ state can be done with any JSONPatch-compliant library, see https://jsonpatch.com
125
+ for more information.
126
+ """
127
+
128
+ def __init__(self, *ops: dict[str, Any]) -> None:
129
+ """Create a RunLogPatch.
130
+
131
+ Args:
132
+ *ops: The operations to apply to the state.
133
+ """
134
+ self.ops = list(ops)
135
+
136
+ def __add__(self, other: RunLogPatch | Any) -> RunLog:
137
+ """Combine two `RunLogPatch` instances.
138
+
139
+ Args:
140
+ other: The other `RunLogPatch` to combine with.
141
+
142
+ Raises:
143
+ TypeError: If the other object is not a `RunLogPatch`.
144
+
145
+ Returns:
146
+ A new `RunLog` representing the combination of the two.
147
+ """
148
+ if type(other) is RunLogPatch:
149
+ ops = self.ops + other.ops
150
+ state = jsonpatch.apply_patch(None, copy.deepcopy(ops))
151
+ return RunLog(*ops, state=state)
152
+
153
+ msg = f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'"
154
+ raise TypeError(msg)
155
+
156
+ @override
157
+ def __repr__(self) -> str:
158
+ # 1:-1 to get rid of the [] around the list
159
+ return f"RunLogPatch({pformat(self.ops)[1:-1]})"
160
+
161
+ @override
162
+ def __eq__(self, other: object) -> bool:
163
+ return isinstance(other, RunLogPatch) and self.ops == other.ops
164
+
165
+ __hash__ = None # type: ignore[assignment]
166
+
167
+
168
+ class RunLog(RunLogPatch):
169
+ """Run log."""
170
+
171
+ state: RunState
172
+ """Current state of the log, obtained from applying all ops in sequence."""
173
+
174
+ def __init__(self, *ops: dict[str, Any], state: RunState) -> None:
175
+ """Create a RunLog.
176
+
177
+ Args:
178
+ *ops: The operations to apply to the state.
179
+ state: The initial state of the run log.
180
+ """
181
+ super().__init__(*ops)
182
+ self.state = state
183
+
184
+ def __add__(self, other: RunLogPatch | Any) -> RunLog:
185
+ """Combine two `RunLog` objects.
186
+
187
+ Args:
188
+ other: The other `RunLog` or `RunLogPatch` to combine with.
189
+
190
+ Raises:
191
+ TypeError: If the other object is not a `RunLog` or `RunLogPatch`.
192
+
193
+ Returns:
194
+ A new `RunLog` representing the combination of the two.
195
+ """
196
+ if type(other) is RunLogPatch:
197
+ ops = self.ops + other.ops
198
+ state = jsonpatch.apply_patch(self.state, other.ops)
199
+ return RunLog(*ops, state=state)
200
+
201
+ msg = f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'"
202
+ raise TypeError(msg)
203
+
204
+ @override
205
+ def __repr__(self) -> str:
206
+ return f"RunLog({pformat(self.state)})"
207
+
208
+ @override
209
+ def __eq__(self, other: object) -> bool:
210
+ """Check if two `RunLog`s are equal.
211
+
212
+ Args:
213
+ other: The other `RunLog` to compare to.
214
+
215
+ Returns:
216
+ `True` if the `RunLog`s are equal, `False` otherwise.
217
+ """
218
+ # First compare that the state is the same
219
+ if not isinstance(other, RunLog):
220
+ return False
221
+ if self.state != other.state:
222
+ return False
223
+ # Then compare that the ops are the same
224
+ return super().__eq__(other)
225
+
226
+ __hash__ = None
227
+
228
+
229
+ T = TypeVar("T")
230
+
231
+
232
+ class LogStreamCallbackHandler(BaseTracer, _StreamingCallbackHandler[Any]):
233
+ """Tracer that streams run logs to a stream."""
234
+
235
+ def __init__(
236
+ self,
237
+ *,
238
+ auto_close: bool = True,
239
+ include_names: Sequence[str] | None = None,
240
+ include_types: Sequence[str] | None = None,
241
+ include_tags: Sequence[str] | None = None,
242
+ exclude_names: Sequence[str] | None = None,
243
+ exclude_types: Sequence[str] | None = None,
244
+ exclude_tags: Sequence[str] | None = None,
245
+ # Schema format is for internal use only.
246
+ _schema_format: Literal["original", "streaming_events"] = "streaming_events",
247
+ ) -> None:
248
+ """A tracer that streams run logs to a stream.
249
+
250
+ Args:
251
+ auto_close: Whether to close the stream when the root run finishes.
252
+ include_names: Only include runs from `Runnable` objects with matching
253
+ names.
254
+ include_types: Only include runs from `Runnable` objects with matching
255
+ types.
256
+ include_tags: Only include runs from `Runnable` objects with matching tags.
257
+ exclude_names: Exclude runs from `Runnable` objects with matching names.
258
+ exclude_types: Exclude runs from `Runnable` objects with matching types.
259
+ exclude_tags: Exclude runs from `Runnable` objects with matching tags.
260
+ _schema_format: Primarily changes how the inputs and outputs are handled.
261
+
262
+ **For internal use only. This API will change.**
263
+
264
+ - `'original'` is the format used by all current tracers. This format is
265
+ slightly inconsistent with respect to inputs and outputs.
266
+ - 'streaming_events' is used for supporting streaming events, for
267
+ internal usage. It will likely change in the future,
268
+ or be deprecated entirely in favor of a dedicated async
269
+ tracer for streaming events.
270
+
271
+ Raises:
272
+ ValueError: If an invalid schema format is provided (internal use only).
273
+ """
274
+ if _schema_format not in {"original", "streaming_events"}:
275
+ msg = (
276
+ f"Invalid schema format: {_schema_format}. "
277
+ f"Expected one of 'original', 'streaming_events'."
278
+ )
279
+ raise ValueError(msg)
280
+ super().__init__(_schema_format=_schema_format)
281
+
282
+ self.auto_close = auto_close
283
+ self.include_names = include_names
284
+ self.include_types = include_types
285
+ self.include_tags = include_tags
286
+ self.exclude_names = exclude_names
287
+ self.exclude_types = exclude_types
288
+ self.exclude_tags = exclude_tags
289
+
290
+ memory_stream = _MemoryStream[RunLogPatch](_get_or_create_loop(self))
291
+ self.lock = threading.Lock()
292
+ self.send_stream = memory_stream.get_send_stream()
293
+ self.receive_stream = memory_stream.get_receive_stream()
294
+ self._key_map_by_run_id: dict[UUID, str] = {}
295
+ self._counter_map_by_name: dict[str, int] = defaultdict(int)
296
+ self.root_id: UUID | None = None
297
+
298
+ def __aiter__(self) -> AsyncIterator[RunLogPatch]:
299
+ """Iterate over the stream of run logs.
300
+
301
+ Returns:
302
+ An async iterator over the run log patches.
303
+ """
304
+ return self.receive_stream.__aiter__()
305
+
306
+ def send(self, *ops: dict[str, Any]) -> bool:
307
+ """Send a patch to the stream, return `False` if the stream is closed.
308
+
309
+ Args:
310
+ *ops: The operations to send to the stream.
311
+
312
+ Returns:
313
+ `True` if the patch was sent successfully, `False` if the stream is closed.
314
+ """
315
+ # We will likely want to wrap this in try / except at some point
316
+ # to handle exceptions that might arise at run time.
317
+ # For now we'll let the exception bubble up, and always return
318
+ # True on the happy path.
319
+ self.send_stream.send_nowait(RunLogPatch(*ops))
320
+ return True
321
+
322
+ async def tap_output_aiter(
323
+ self, run_id: UUID, output: AsyncIterator[T]
324
+ ) -> AsyncIterator[T]:
325
+ """Tap an output async iterator to stream its values to the log.
326
+
327
+ Args:
328
+ run_id: The ID of the run.
329
+ output: The output async iterator.
330
+
331
+ Yields:
332
+ The output value.
333
+ """
334
+ async for chunk in output:
335
+ # root run is handled in .astream_log()
336
+ # if we can't find the run silently ignore
337
+ # eg. because this run wasn't included in the log
338
+ if (
339
+ run_id != self.root_id
340
+ and (key := self._key_map_by_run_id.get(run_id))
341
+ and (
342
+ not self.send(
343
+ {
344
+ "op": "add",
345
+ "path": f"/logs/{key}/streamed_output/-",
346
+ "value": chunk,
347
+ }
348
+ )
349
+ )
350
+ ):
351
+ break
352
+
353
+ yield chunk
354
+
355
+ def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
356
+ """Tap an output iterator to stream its values to the log.
357
+
358
+ Args:
359
+ run_id: The ID of the run.
360
+ output: The output iterator.
361
+
362
+ Yields:
363
+ The output value.
364
+ """
365
+ for chunk in output:
366
+ # root run is handled in .astream_log()
367
+ # if we can't find the run silently ignore
368
+ # eg. because this run wasn't included in the log
369
+ if (
370
+ run_id != self.root_id
371
+ and (key := self._key_map_by_run_id.get(run_id))
372
+ and (
373
+ not self.send(
374
+ {
375
+ "op": "add",
376
+ "path": f"/logs/{key}/streamed_output/-",
377
+ "value": chunk,
378
+ }
379
+ )
380
+ )
381
+ ):
382
+ break
383
+
384
+ yield chunk
385
+
386
+ def include_run(self, run: Run) -> bool:
387
+ """Check if a `Run` should be included in the log.
388
+
389
+ Args:
390
+ run: The `Run` to check.
391
+
392
+ Returns:
393
+ `True` if the `Run` should be included, `False` otherwise.
394
+ """
395
+ if run.id == self.root_id:
396
+ return False
397
+
398
+ run_tags = run.tags or []
399
+
400
+ if (
401
+ self.include_names is None
402
+ and self.include_types is None
403
+ and self.include_tags is None
404
+ ):
405
+ include = True
406
+ else:
407
+ include = False
408
+
409
+ if self.include_names is not None:
410
+ include = include or run.name in self.include_names
411
+ if self.include_types is not None:
412
+ include = include or run.run_type in self.include_types
413
+ if self.include_tags is not None:
414
+ include = include or any(tag in self.include_tags for tag in run_tags)
415
+
416
+ if self.exclude_names is not None:
417
+ include = include and run.name not in self.exclude_names
418
+ if self.exclude_types is not None:
419
+ include = include and run.run_type not in self.exclude_types
420
+ if self.exclude_tags is not None:
421
+ include = include and all(tag not in self.exclude_tags for tag in run_tags)
422
+
423
+ return include
424
+
425
+ def _persist_run(self, run: Run) -> None:
426
+ # This is a legacy method only called once for an entire run tree
427
+ # therefore not useful here
428
+ pass
429
+
430
+ def _on_run_create(self, run: Run) -> None:
431
+ """Start a run."""
432
+ if self.root_id is None:
433
+ self.root_id = run.id
434
+ if not self.send(
435
+ {
436
+ "op": "replace",
437
+ "path": "",
438
+ "value": RunState(
439
+ id=str(run.id),
440
+ streamed_output=[],
441
+ final_output=None,
442
+ logs={},
443
+ name=run.name,
444
+ type=run.run_type,
445
+ ),
446
+ }
447
+ ):
448
+ return
449
+
450
+ if not self.include_run(run):
451
+ return
452
+
453
+ # Determine previous index, increment by 1
454
+ with self.lock:
455
+ self._counter_map_by_name[run.name] += 1
456
+ count = self._counter_map_by_name[run.name]
457
+ self._key_map_by_run_id[run.id] = (
458
+ run.name if count == 1 else f"{run.name}:{count}"
459
+ )
460
+
461
+ entry = LogEntry(
462
+ id=str(run.id),
463
+ name=run.name,
464
+ type=run.run_type,
465
+ tags=run.tags or [],
466
+ metadata=(run.extra or {}).get("metadata", {}),
467
+ start_time=run.start_time.isoformat(timespec="milliseconds"),
468
+ streamed_output=[],
469
+ streamed_output_str=[],
470
+ final_output=None,
471
+ end_time=None,
472
+ )
473
+
474
+ if self._schema_format == "streaming_events":
475
+ # If using streaming events let's add inputs as well
476
+ entry["inputs"] = _get_standardized_inputs(run, self._schema_format)
477
+
478
+ # Add the run to the stream
479
+ self.send(
480
+ {
481
+ "op": "add",
482
+ "path": f"/logs/{self._key_map_by_run_id[run.id]}",
483
+ "value": entry,
484
+ }
485
+ )
486
+
487
+ def _on_run_update(self, run: Run) -> None:
488
+ """Finish a `Run`."""
489
+ try:
490
+ index = self._key_map_by_run_id.get(run.id)
491
+
492
+ if index is None:
493
+ return
494
+
495
+ ops = []
496
+
497
+ if self._schema_format == "streaming_events":
498
+ ops.append(
499
+ {
500
+ "op": "replace",
501
+ "path": f"/logs/{index}/inputs",
502
+ "value": _get_standardized_inputs(run, self._schema_format),
503
+ }
504
+ )
505
+
506
+ ops.extend(
507
+ [
508
+ # Replace 'inputs' with final inputs
509
+ # This is needed because in many cases the inputs are not
510
+ # known until after the run is finished and the entire
511
+ # input stream has been processed by the runnable.
512
+ {
513
+ "op": "add",
514
+ "path": f"/logs/{index}/final_output",
515
+ # to undo the dumpd done by some runnables / tracer / etc
516
+ "value": _get_standardized_outputs(run, self._schema_format),
517
+ },
518
+ {
519
+ "op": "add",
520
+ "path": f"/logs/{index}/end_time",
521
+ "value": run.end_time.isoformat(timespec="milliseconds")
522
+ if run.end_time is not None
523
+ else None,
524
+ },
525
+ ]
526
+ )
527
+
528
+ self.send(*ops)
529
+ finally:
530
+ if run.id == self.root_id and self.auto_close:
531
+ self.send_stream.close()
532
+
533
+ def _on_llm_new_token(
534
+ self,
535
+ run: Run,
536
+ token: str | list[str | dict[str, Any]],
537
+ chunk: GenerationChunk | ChatGenerationChunk | None,
538
+ ) -> None:
539
+ """Process new LLM token."""
540
+ index = self._key_map_by_run_id.get(run.id)
541
+
542
+ if index is None:
543
+ return
544
+
545
+ self.send(
546
+ {
547
+ "op": "add",
548
+ "path": f"/logs/{index}/streamed_output_str/-",
549
+ "value": token,
550
+ },
551
+ {
552
+ "op": "add",
553
+ "path": f"/logs/{index}/streamed_output/-",
554
+ "value": chunk.message
555
+ if isinstance(chunk, ChatGenerationChunk)
556
+ else token,
557
+ },
558
+ )
559
+
560
+
561
+ def _get_standardized_inputs(
562
+ run: Run, schema_format: Literal["original", "streaming_events"]
563
+ ) -> Any:
564
+ """Extract standardized inputs from a `Run`.
565
+
566
+ Standardizes the inputs based on the type of the runnable used.
567
+
568
+ Args:
569
+ run: `Run` object
570
+ schema_format: The schema format to use.
571
+
572
+ Returns:
573
+ Valid inputs are only dict. By conventions, inputs always represented invocation
574
+ using named arguments. `None` means that the input is not yet known!
575
+ """
576
+ if schema_format == "original":
577
+ msg = (
578
+ "Do not assign inputs with original schema drop the key for now."
579
+ "When inputs are added to astream_log they should be added with "
580
+ "standardized schema for streaming events."
581
+ )
582
+ raise NotImplementedError(msg)
583
+
584
+ inputs = load(run.inputs, allowed_objects="messages")
585
+
586
+ if run.run_type in {"retriever", "llm", "chat_model"}:
587
+ return inputs
588
+
589
+ # new style chains
590
+ # These nest an additional 'input' key inside the 'inputs' to make sure
591
+ # the input is always a dict. We need to unpack and use the inner value.
592
+ inputs = inputs["input"]
593
+ # We should try to fix this in Runnables and callbacks/tracers
594
+ # Runnables should be using a None type here not a placeholder
595
+ # dict.
596
+ if inputs == {"input": ""}: # Workaround for Runnables not using None
597
+ # The input is not known, so we don't assign data['input']
598
+ return None
599
+ return inputs
600
+
601
+
602
+ def _get_standardized_outputs(
603
+ run: Run, schema_format: Literal["original", "streaming_events", "original+chat"]
604
+ ) -> Any | None:
605
+ """Extract standardized output from a run.
606
+
607
+ Standardizes the outputs based on the type of the runnable used.
608
+
609
+ Args:
610
+ run: the run object.
611
+ schema_format: The schema format to use.
612
+
613
+ Returns:
614
+ An output if returned, otherwise `None`.
615
+ """
616
+ outputs = load(run.outputs, allowed_objects="messages")
617
+ if schema_format == "original":
618
+ if run.run_type == "prompt" and "output" in outputs:
619
+ # These were previously dumped before the tracer.
620
+ # Now we needn't do anything to them.
621
+ return outputs["output"]
622
+ # Return the old schema, without standardizing anything
623
+ return outputs
624
+
625
+ if run.run_type in {"retriever", "llm", "chat_model"}:
626
+ return outputs
627
+
628
+ if isinstance(outputs, dict):
629
+ return outputs.get("output", None)
630
+
631
+ return None
632
+
633
+
634
+ @overload
635
+ def _astream_log_implementation(
636
+ runnable: Runnable[Input, Output],
637
+ value: Any,
638
+ config: RunnableConfig | None = None,
639
+ *,
640
+ stream: LogStreamCallbackHandler,
641
+ diff: Literal[True] = True,
642
+ with_streamed_output_list: bool = True,
643
+ **kwargs: Any,
644
+ ) -> AsyncIterator[RunLogPatch]: ...
645
+
646
+
647
+ @overload
648
+ def _astream_log_implementation(
649
+ runnable: Runnable[Input, Output],
650
+ value: Any,
651
+ config: RunnableConfig | None = None,
652
+ *,
653
+ stream: LogStreamCallbackHandler,
654
+ diff: Literal[False],
655
+ with_streamed_output_list: bool = True,
656
+ **kwargs: Any,
657
+ ) -> AsyncIterator[RunLog]: ...
658
+
659
+
660
+ async def _astream_log_implementation(
661
+ runnable: Runnable[Input, Output],
662
+ value: Any,
663
+ config: RunnableConfig | None = None,
664
+ *,
665
+ stream: LogStreamCallbackHandler,
666
+ diff: bool = True,
667
+ with_streamed_output_list: bool = True,
668
+ **kwargs: Any,
669
+ ) -> AsyncIterator[RunLogPatch] | AsyncIterator[RunLog]:
670
+ """Implementation of astream_log for a given runnable.
671
+
672
+ The implementation has been factored out (at least temporarily) as both
673
+ `astream_log` and `astream_events` rely on it.
674
+
675
+ Args:
676
+ runnable: The runnable to run in streaming mode.
677
+ value: The input to the runnable.
678
+ config: The config to pass to the runnable.
679
+ stream: The stream to send the run logs to.
680
+ diff: Whether to yield run log patches (`True`) or full run logs (`False`).
681
+ with_streamed_output_list: Whether to include a list of all streamed outputs in
682
+ each patch. If `False`, only the final output will be included in the
683
+ patches.
684
+ **kwargs: Additional keyword arguments to pass to the `Runnable`.
685
+
686
+ Raises:
687
+ ValueError: If the callbacks in the config are of an unexpected type.
688
+
689
+ Yields:
690
+ The run log patches or states, depending on the value of `diff`.
691
+ """
692
+ # Assign the stream handler to the config
693
+ config = ensure_config(config)
694
+ callbacks = config.get("callbacks")
695
+ if callbacks is None:
696
+ config["callbacks"] = [stream]
697
+ elif isinstance(callbacks, list):
698
+ config["callbacks"] = [*callbacks, stream]
699
+ elif isinstance(callbacks, BaseCallbackManager):
700
+ callbacks = callbacks.copy()
701
+ callbacks.add_handler(stream, inherit=True)
702
+ config["callbacks"] = callbacks
703
+ else:
704
+ msg = ( # type: ignore[unreachable]
705
+ f"Unexpected type for callbacks: {callbacks}."
706
+ "Expected None, list or AsyncCallbackManager."
707
+ )
708
+ raise ValueError(msg)
709
+
710
+ # Call the runnable in streaming mode,
711
+ # add each chunk to the output stream
712
+ async def consume_astream() -> None:
713
+ try:
714
+ prev_final_output: Output | None = None
715
+ final_output: Output | None = None
716
+
717
+ async for chunk in runnable.astream(value, config, **kwargs):
718
+ prev_final_output = final_output
719
+ if final_output is None:
720
+ final_output = chunk
721
+ else:
722
+ try:
723
+ final_output = final_output + chunk # type: ignore[operator]
724
+ except TypeError:
725
+ prev_final_output = None
726
+ final_output = chunk
727
+ patches: list[dict[str, Any]] = []
728
+ if with_streamed_output_list:
729
+ patches.append(
730
+ {
731
+ "op": "add",
732
+ "path": "/streamed_output/-",
733
+ # chunk cannot be shared between
734
+ # streamed_output and final_output
735
+ # otherwise jsonpatch.apply will
736
+ # modify both
737
+ "value": copy.deepcopy(chunk),
738
+ }
739
+ )
740
+ patches.extend(
741
+ {**op, "path": f"/final_output{op['path']}"}
742
+ for op in jsonpatch.JsonPatch.from_diff(
743
+ prev_final_output, final_output, dumps=dumps
744
+ )
745
+ )
746
+ await stream.send_stream.send(RunLogPatch(*patches))
747
+ finally:
748
+ await stream.send_stream.aclose()
749
+
750
+ # Start the runnable in a task, so we can start consuming output
751
+ task = asyncio.create_task(consume_astream())
752
+ try:
753
+ # Yield each chunk from the output stream
754
+ if diff:
755
+ async for log in stream:
756
+ yield log
757
+ else:
758
+ state = RunLog(state=None) # type: ignore[arg-type]
759
+ async for log in stream:
760
+ state += log
761
+ yield state
762
+ finally:
763
+ # Wait for the runnable to finish, if not cancelled (eg. by break)
764
+ with contextlib.suppress(asyncio.CancelledError):
765
+ await task