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,2826 @@
1
+ """Run managers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import atexit
7
+ import functools
8
+ import inspect
9
+ import logging
10
+ from abc import ABC, abstractmethod
11
+ from collections.abc import Callable, Mapping
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ from contextlib import asynccontextmanager, contextmanager
14
+ from contextvars import copy_context
15
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
16
+
17
+ from typing_extensions import Self, override
18
+
19
+ from coco_core.callbacks.base import (
20
+ BaseCallbackHandler,
21
+ BaseCallbackManager,
22
+ Callbacks,
23
+ ChainManagerMixin,
24
+ LLMManagerMixin,
25
+ RetrieverManagerMixin,
26
+ RunManagerMixin,
27
+ ToolManagerMixin,
28
+ )
29
+ from coco_core.callbacks.stdout import StdOutCallbackHandler
30
+ from coco_core.globals import get_debug
31
+ from coco_core.messages import BaseMessage, get_buffer_string
32
+ from coco_core.utils.env import env_var_is_set
33
+ from coco_core.utils.uuid import uuid7
34
+
35
+ if TYPE_CHECKING:
36
+ from collections.abc import AsyncGenerator, Coroutine, Generator, Sequence
37
+ from uuid import UUID
38
+
39
+ from coco_core._protocol import MessagesData
40
+ from tenacity import RetryCallState
41
+
42
+ from coco_core.agents import AgentAction, AgentFinish
43
+ from coco_core.documents import Document
44
+ from coco_core.outputs import ChatGenerationChunk, GenerationChunk, LLMResult
45
+ from coco_core.runnables.config import RunnableConfig
46
+ from coco_core.tracers.schemas import Run
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ def _get_debug() -> bool:
52
+ return get_debug()
53
+
54
+
55
+ @contextmanager
56
+ def trace_as_chain_group(
57
+ group_name: str,
58
+ callback_manager: CallbackManager | None = None,
59
+ *,
60
+ inputs: dict[str, Any] | None = None,
61
+ project_name: str | None = None,
62
+ example_id: str | UUID | None = None,
63
+ run_id: UUID | None = None,
64
+ tags: list[str] | None = None,
65
+ metadata: dict[str, Any] | None = None,
66
+ ) -> Generator[CallbackManagerForChainGroup, None, None]:
67
+ """Get a callback manager for a chain group in a context manager.
68
+
69
+ Useful for grouping different calls together as a single run even if they aren't
70
+ composed in a single chain.
71
+
72
+ Args:
73
+ group_name: The name of the chain group.
74
+ callback_manager: The callback manager to use.
75
+ inputs: The inputs to the chain group.
76
+ project_name: The name of the project.
77
+ example_id: The ID of the example.
78
+ run_id: The ID of the run.
79
+ tags: The inheritable tags to apply to all runs.
80
+ metadata: The metadata to apply to all runs.
81
+
82
+ !!! note
83
+
84
+ Must have `COCO_TRACING_V2` env var set to true to see the trace in
85
+ LangSmith.
86
+
87
+ Yields:
88
+ The callback manager for the chain group.
89
+
90
+ Example:
91
+ ```python
92
+ llm_input = "Foo"
93
+ with trace_as_chain_group("group_name", inputs={"input": llm_input}) as manager:
94
+ # Use the callback manager for the chain group
95
+ res = llm.invoke(llm_input, {"callbacks": manager})
96
+ manager.on_chain_end({"output": res})
97
+ ```
98
+ """
99
+ from coco_core.tracers.context import ( # noqa: PLC0415 -- deferred to avoid importing langsmith at module level
100
+ _get_trace_callbacks,
101
+ )
102
+
103
+ cb = _get_trace_callbacks(
104
+ project_name, example_id, callback_manager=callback_manager
105
+ )
106
+ cm = CallbackManager.configure(
107
+ inheritable_callbacks=cb,
108
+ inheritable_tags=tags,
109
+ inheritable_metadata=metadata,
110
+ )
111
+
112
+ run_manager = cm.on_chain_start({"name": group_name}, inputs or {}, run_id=run_id)
113
+ child_cm = run_manager.get_child()
114
+ group_cm = CallbackManagerForChainGroup(
115
+ child_cm.handlers,
116
+ child_cm.inheritable_handlers,
117
+ child_cm.parent_run_id,
118
+ parent_run_manager=run_manager,
119
+ tags=child_cm.tags,
120
+ inheritable_tags=child_cm.inheritable_tags,
121
+ metadata=child_cm.metadata,
122
+ inheritable_metadata=child_cm.inheritable_metadata,
123
+ )
124
+ try:
125
+ yield group_cm
126
+ except BaseException as e:
127
+ if not group_cm.ended:
128
+ run_manager.on_chain_error(e)
129
+ raise
130
+ else:
131
+ if not group_cm.ended:
132
+ run_manager.on_chain_end({})
133
+
134
+
135
+ @asynccontextmanager
136
+ async def atrace_as_chain_group(
137
+ group_name: str,
138
+ callback_manager: AsyncCallbackManager | None = None,
139
+ *,
140
+ inputs: dict[str, Any] | None = None,
141
+ project_name: str | None = None,
142
+ example_id: str | UUID | None = None,
143
+ run_id: UUID | None = None,
144
+ tags: list[str] | None = None,
145
+ metadata: dict[str, Any] | None = None,
146
+ ) -> AsyncGenerator[AsyncCallbackManagerForChainGroup, None]:
147
+ """Get an async callback manager for a chain group in a context manager.
148
+
149
+ Useful for grouping different async calls together as a single run even if they
150
+ aren't composed in a single chain.
151
+
152
+ Args:
153
+ group_name: The name of the chain group.
154
+ callback_manager: The async callback manager to use, which manages tracing and
155
+ other callback behavior.
156
+ inputs: The inputs to the chain group.
157
+ project_name: The name of the project.
158
+ example_id: The ID of the example.
159
+ run_id: The ID of the run.
160
+ tags: The inheritable tags to apply to all runs.
161
+ metadata: The metadata to apply to all runs.
162
+
163
+ Yields:
164
+ The async callback manager for the chain group.
165
+
166
+ !!! note
167
+
168
+ Must have `COCO_TRACING_V2` env var set to true to see the trace in
169
+ LangSmith.
170
+
171
+ Example:
172
+ ```python
173
+ llm_input = "Foo"
174
+ async with atrace_as_chain_group(
175
+ "group_name", inputs={"input": llm_input}
176
+ ) as manager:
177
+ # Use the async callback manager for the chain group
178
+ res = await llm.ainvoke(llm_input, {"callbacks": manager})
179
+ await manager.on_chain_end({"output": res})
180
+ ```
181
+ """
182
+ from coco_core.tracers.context import ( # noqa: PLC0415 -- deferred to avoid importing langsmith at module level
183
+ _get_trace_callbacks,
184
+ )
185
+
186
+ cb = _get_trace_callbacks(
187
+ project_name, example_id, callback_manager=callback_manager
188
+ )
189
+ cm = AsyncCallbackManager.configure(
190
+ inheritable_callbacks=cb, inheritable_tags=tags, inheritable_metadata=metadata
191
+ )
192
+
193
+ run_manager = await cm.on_chain_start(
194
+ {"name": group_name}, inputs or {}, run_id=run_id
195
+ )
196
+ child_cm = run_manager.get_child()
197
+ group_cm = AsyncCallbackManagerForChainGroup(
198
+ child_cm.handlers,
199
+ child_cm.inheritable_handlers,
200
+ child_cm.parent_run_id,
201
+ parent_run_manager=run_manager,
202
+ tags=child_cm.tags,
203
+ inheritable_tags=child_cm.inheritable_tags,
204
+ metadata=child_cm.metadata,
205
+ inheritable_metadata=child_cm.inheritable_metadata,
206
+ )
207
+ try:
208
+ yield group_cm
209
+ except BaseException as e:
210
+ if not group_cm.ended:
211
+ await run_manager.on_chain_error(e)
212
+ raise
213
+ else:
214
+ if not group_cm.ended:
215
+ await run_manager.on_chain_end({})
216
+
217
+
218
+ Func = TypeVar("Func", bound=Callable[..., Any])
219
+
220
+
221
+ def shielded(func: Func) -> Func:
222
+ """Makes so an awaitable method is always shielded from cancellation.
223
+
224
+ Args:
225
+ func: The function to shield.
226
+
227
+ Returns:
228
+ The shielded function
229
+
230
+ """
231
+
232
+ @functools.wraps(func)
233
+ async def wrapped(*args: Any, **kwargs: Any) -> Any:
234
+ # Capture the current context to preserve context variables
235
+ ctx = copy_context()
236
+
237
+ # Create the coroutine
238
+ coro = func(*args, **kwargs)
239
+
240
+ # For Python 3.11+, create task with explicit context
241
+ # For older versions, fallback to original behavior
242
+ try:
243
+ # Create a task with the captured context to preserve context variables
244
+ task = asyncio.create_task(coro, context=ctx) # type: ignore[call-arg, unused-ignore]
245
+ # `call-arg` used to not fail 3.9 or 3.10 tests
246
+ return await asyncio.shield(task)
247
+ except TypeError:
248
+ # Python < 3.11 fallback - create task normally then shield
249
+ # This won't preserve context perfectly but is better than nothing
250
+ task = asyncio.create_task(coro)
251
+ return await asyncio.shield(task)
252
+
253
+ return cast("Func", wrapped)
254
+
255
+
256
+ async def _achat_model_start_fallback(
257
+ coro: Coroutine[Any, Any, Any],
258
+ handler: BaseCallbackHandler,
259
+ *args: Any,
260
+ **kwargs: Any,
261
+ ) -> None:
262
+ """Wrap an async `on_chat_model_start` coroutine with fallback.
263
+
264
+ Catches `NotImplementedError` and triggers the `on_llm_start` fallback.
265
+ This covers async handlers invoked from a **sync** `handle_event` call,
266
+ where the coroutine is collected into `coros` and executed later by
267
+ `_run_coros`. Without this wrapper the `NotImplementedError` would be
268
+ caught generically by `_run_coros` and the trace would be lost.
269
+ """
270
+ try:
271
+ await coro
272
+ except NotImplementedError:
273
+ message_strings = [get_buffer_string(m) for m in args[1]]
274
+ await _ahandle_event_for_handler(
275
+ handler,
276
+ "on_llm_start",
277
+ "ignore_llm",
278
+ args[0],
279
+ message_strings,
280
+ *args[2:],
281
+ **kwargs,
282
+ )
283
+
284
+
285
+ def handle_event(
286
+ handlers: list[BaseCallbackHandler],
287
+ event_name: str,
288
+ ignore_condition_name: str | None,
289
+ *args: Any,
290
+ **kwargs: Any,
291
+ ) -> None:
292
+ """Generic event handler for `CallbackManager`.
293
+
294
+ Args:
295
+ handlers: The list of handlers that will handle the event.
296
+ event_name: The name of the event (e.g., `'on_llm_start'`).
297
+ ignore_condition_name: Name of the attribute defined on handler that if `True`
298
+ will cause the handler to be skipped for the given event.
299
+ *args: The arguments to pass to the event handler.
300
+ **kwargs: The keyword arguments to pass to the event handler
301
+
302
+ """
303
+ coros: list[Coroutine[Any, Any, Any]] = []
304
+
305
+ try:
306
+ message_strings: list[str] | None = None
307
+ for handler in handlers:
308
+ try:
309
+ if ignore_condition_name is None or not getattr(
310
+ handler, ignore_condition_name
311
+ ):
312
+ event = getattr(handler, event_name)(*args, **kwargs)
313
+ if asyncio.iscoroutine(event):
314
+ if event_name == "on_chat_model_start":
315
+ event = _achat_model_start_fallback(
316
+ event, handler, *args, **kwargs
317
+ )
318
+ coros.append(event)
319
+ except NotImplementedError as e:
320
+ if event_name == "on_chat_model_start":
321
+ if message_strings is None:
322
+ message_strings = [get_buffer_string(m) for m in args[1]]
323
+ handle_event(
324
+ [handler],
325
+ "on_llm_start",
326
+ "ignore_llm",
327
+ args[0],
328
+ message_strings,
329
+ *args[2:],
330
+ **kwargs,
331
+ )
332
+ else:
333
+ handler_name = handler.__class__.__name__
334
+ logger.warning(
335
+ "NotImplementedError in %s.%s callback: %s",
336
+ handler_name,
337
+ event_name,
338
+ repr(e),
339
+ )
340
+ except Exception as e:
341
+ logger.warning(
342
+ "Error in %s.%s callback: %s",
343
+ handler.__class__.__name__,
344
+ event_name,
345
+ repr(e),
346
+ )
347
+ if handler.raise_error:
348
+ raise
349
+ finally:
350
+ if coros:
351
+ try:
352
+ # Raises RuntimeError if there is no current event loop.
353
+ asyncio.get_running_loop()
354
+ loop_running = True
355
+ except RuntimeError:
356
+ loop_running = False
357
+
358
+ if loop_running:
359
+ # If we try to submit this coroutine to the running loop
360
+ # we end up in a deadlock, as we'd have gotten here from a
361
+ # running coroutine, which we cannot interrupt to run this one.
362
+ # The solution is to run the synchronous function on the globally shared
363
+ # thread pool executor to avoid blocking the main event loop.
364
+ _executor().submit(copy_context().run, _run_coros, coros).result()
365
+ else:
366
+ # If there's no running loop, we can run the coroutines directly.
367
+ _run_coros(coros)
368
+
369
+
370
+ def _run_coros(coros: list[Coroutine[Any, Any, Any]]) -> None:
371
+ # Note: exceptions raised by these coroutines are always logged and swallowed
372
+ # here, regardless of the handler's `raise_error` setting. Async-handler errors
373
+ # driven through sync `handle_event` therefore never propagate, unlike errors
374
+ # from sync handlers (which honor `raise_error`). This is a pre-existing
375
+ # asymmetry between the sync and async callback paths.
376
+ if hasattr(asyncio, "Runner"):
377
+ # Python 3.11+
378
+ # Run the coroutines in a new event loop, taking care to
379
+ # - install signal handlers
380
+ # - run pending tasks scheduled by `coros`
381
+ # - close asyncgens and executors
382
+ # - close the loop
383
+ with asyncio.Runner() as runner:
384
+ # Run the coroutine, get the result
385
+ for coro in coros:
386
+ try:
387
+ runner.run(coro)
388
+ except Exception as e:
389
+ logger.warning("Error in callback coroutine: %s", repr(e))
390
+
391
+ # Run pending tasks scheduled by coros until they are all done
392
+ while pending := asyncio.all_tasks(runner.get_loop()):
393
+ runner.run(asyncio.wait(pending))
394
+ else:
395
+ # Before Python 3.11 we need to run each coroutine in a new event loop
396
+ # as the Runner api is not available.
397
+ for coro in coros:
398
+ try:
399
+ asyncio.run(coro)
400
+ except Exception as e:
401
+ logger.warning("Error in callback coroutine: %s", repr(e))
402
+
403
+
404
+ async def _ahandle_event_for_handler(
405
+ handler: BaseCallbackHandler,
406
+ event_name: str,
407
+ ignore_condition_name: str | None,
408
+ *args: Any,
409
+ **kwargs: Any,
410
+ ) -> None:
411
+ try:
412
+ if ignore_condition_name is None or not getattr(handler, ignore_condition_name):
413
+ event = getattr(handler, event_name)
414
+ if inspect.iscoroutinefunction(event):
415
+ await event(*args, **kwargs)
416
+ elif handler.run_inline:
417
+ event(*args, **kwargs)
418
+ else:
419
+ await asyncio.get_running_loop().run_in_executor(
420
+ None,
421
+ functools.partial(copy_context().run, event, *args, **kwargs),
422
+ )
423
+ except NotImplementedError as e:
424
+ if event_name == "on_chat_model_start":
425
+ message_strings = [get_buffer_string(m) for m in args[1]]
426
+ await _ahandle_event_for_handler(
427
+ handler,
428
+ "on_llm_start",
429
+ "ignore_llm",
430
+ args[0],
431
+ message_strings,
432
+ *args[2:],
433
+ **kwargs,
434
+ )
435
+ else:
436
+ logger.warning(
437
+ "NotImplementedError in %s.%s callback: %s",
438
+ handler.__class__.__name__,
439
+ event_name,
440
+ repr(e),
441
+ )
442
+ except Exception as e:
443
+ logger.warning(
444
+ "Error in %s.%s callback: %s",
445
+ handler.__class__.__name__,
446
+ event_name,
447
+ repr(e),
448
+ )
449
+ if handler.raise_error:
450
+ raise
451
+
452
+
453
+ async def ahandle_event(
454
+ handlers: list[BaseCallbackHandler],
455
+ event_name: str,
456
+ ignore_condition_name: str | None,
457
+ *args: Any,
458
+ **kwargs: Any,
459
+ ) -> None:
460
+ """Async generic event handler for `AsyncCallbackManager`.
461
+
462
+ Args:
463
+ handlers: The list of handlers that will handle the event.
464
+ event_name: The name of the event (e.g., `'on_llm_start'`).
465
+ ignore_condition_name: Name of the attribute defined on handler that if `True`
466
+ will cause the handler to be skipped for the given event.
467
+ *args: The arguments to pass to the event handler.
468
+ **kwargs: The keyword arguments to pass to the event handler.
469
+
470
+ """
471
+ for handler in [h for h in handlers if h.run_inline]:
472
+ await _ahandle_event_for_handler(
473
+ handler, event_name, ignore_condition_name, *args, **kwargs
474
+ )
475
+ await asyncio.gather(
476
+ *(
477
+ _ahandle_event_for_handler(
478
+ handler,
479
+ event_name,
480
+ ignore_condition_name,
481
+ *args,
482
+ **kwargs,
483
+ )
484
+ for handler in handlers
485
+ if not handler.run_inline
486
+ )
487
+ )
488
+
489
+
490
+ class BaseRunManager(RunManagerMixin):
491
+ """Base class for run manager (a bound callback manager)."""
492
+
493
+ def __init__(
494
+ self,
495
+ *,
496
+ run_id: UUID,
497
+ handlers: list[BaseCallbackHandler],
498
+ inheritable_handlers: list[BaseCallbackHandler],
499
+ parent_run_id: UUID | None = None,
500
+ tags: list[str] | None = None,
501
+ inheritable_tags: list[str] | None = None,
502
+ metadata: dict[str, Any] | None = None,
503
+ inheritable_metadata: dict[str, Any] | None = None,
504
+ ) -> None:
505
+ """Initialize the run manager.
506
+
507
+ Args:
508
+ run_id: The ID of the run.
509
+ handlers: The list of handlers.
510
+ inheritable_handlers: The list of inheritable handlers.
511
+ parent_run_id: The ID of the parent run.
512
+ tags: The list of tags.
513
+ inheritable_tags: The list of inheritable tags.
514
+ metadata: The metadata.
515
+ inheritable_metadata: The inheritable metadata.
516
+
517
+ """
518
+ self.run_id = run_id
519
+ self.handlers = handlers
520
+ self.inheritable_handlers = inheritable_handlers
521
+ self.parent_run_id = parent_run_id
522
+ self.tags = tags or []
523
+ self.inheritable_tags = inheritable_tags or []
524
+ self.metadata = metadata or {}
525
+ self.inheritable_metadata = inheritable_metadata or {}
526
+
527
+ @classmethod
528
+ def get_noop_manager(cls) -> Self:
529
+ """Return a manager that doesn't perform any operations.
530
+
531
+ Returns:
532
+ The noop manager.
533
+
534
+ """
535
+ return cls(
536
+ run_id=uuid7(),
537
+ handlers=[],
538
+ inheritable_handlers=[],
539
+ tags=[],
540
+ inheritable_tags=[],
541
+ metadata={},
542
+ inheritable_metadata={},
543
+ )
544
+
545
+
546
+ class RunManager(BaseRunManager):
547
+ """Synchronous run manager."""
548
+
549
+ def on_text(
550
+ self,
551
+ text: str,
552
+ **kwargs: Any,
553
+ ) -> None:
554
+ """Run when a text is received.
555
+
556
+ Args:
557
+ text: The received text.
558
+ **kwargs: Additional keyword arguments.
559
+ """
560
+ if not self.handlers:
561
+ return
562
+ handle_event(
563
+ self.handlers,
564
+ "on_text",
565
+ None,
566
+ text,
567
+ run_id=self.run_id,
568
+ parent_run_id=self.parent_run_id,
569
+ tags=self.tags,
570
+ **kwargs,
571
+ )
572
+
573
+ def on_retry(
574
+ self,
575
+ retry_state: RetryCallState,
576
+ **kwargs: Any,
577
+ ) -> None:
578
+ """Run when a retry is received.
579
+
580
+ Args:
581
+ retry_state: The retry state.
582
+ **kwargs: Additional keyword arguments.
583
+
584
+ """
585
+ if not self.handlers:
586
+ return
587
+ handle_event(
588
+ self.handlers,
589
+ "on_retry",
590
+ "ignore_retry",
591
+ retry_state,
592
+ run_id=self.run_id,
593
+ parent_run_id=self.parent_run_id,
594
+ tags=self.tags,
595
+ **kwargs,
596
+ )
597
+
598
+
599
+ class ParentRunManager(RunManager):
600
+ """Synchronous parent run manager."""
601
+
602
+ def get_child(self, tag: str | None = None) -> CallbackManager:
603
+ """Get a child callback manager.
604
+
605
+ Args:
606
+ tag: The tag for the child callback manager.
607
+
608
+ Returns:
609
+ The child callback manager.
610
+
611
+ """
612
+ manager = CallbackManager(handlers=[], parent_run_id=self.run_id)
613
+ manager.set_handlers(self.inheritable_handlers)
614
+ manager.add_tags(self.inheritable_tags)
615
+ manager.add_metadata(self.inheritable_metadata)
616
+ if tag is not None:
617
+ manager.add_tags([tag], inherit=False)
618
+ return manager
619
+
620
+
621
+ class AsyncRunManager(BaseRunManager, ABC):
622
+ """Async run manager."""
623
+
624
+ @abstractmethod
625
+ def get_sync(self) -> RunManager:
626
+ """Get the equivalent sync `RunManager`.
627
+
628
+ Returns:
629
+ The sync `RunManager`.
630
+
631
+ """
632
+
633
+ async def on_text(
634
+ self,
635
+ text: str,
636
+ **kwargs: Any,
637
+ ) -> None:
638
+ """Run when a text is received.
639
+
640
+ Args:
641
+ text: The received text.
642
+ **kwargs: Additional keyword arguments.
643
+ """
644
+ if not self.handlers:
645
+ return
646
+ await ahandle_event(
647
+ self.handlers,
648
+ "on_text",
649
+ None,
650
+ text,
651
+ run_id=self.run_id,
652
+ parent_run_id=self.parent_run_id,
653
+ tags=self.tags,
654
+ **kwargs,
655
+ )
656
+
657
+ async def on_retry(
658
+ self,
659
+ retry_state: RetryCallState,
660
+ **kwargs: Any,
661
+ ) -> None:
662
+ """Async run when a retry is received.
663
+
664
+ Args:
665
+ retry_state: The retry state.
666
+ **kwargs: Additional keyword arguments.
667
+
668
+ """
669
+ if not self.handlers:
670
+ return
671
+ await ahandle_event(
672
+ self.handlers,
673
+ "on_retry",
674
+ "ignore_retry",
675
+ retry_state,
676
+ run_id=self.run_id,
677
+ parent_run_id=self.parent_run_id,
678
+ tags=self.tags,
679
+ **kwargs,
680
+ )
681
+
682
+
683
+ class AsyncParentRunManager(AsyncRunManager):
684
+ """Async parent run manager."""
685
+
686
+ def get_child(self, tag: str | None = None) -> AsyncCallbackManager:
687
+ """Get a child callback manager.
688
+
689
+ Args:
690
+ tag: The tag for the child callback manager.
691
+
692
+ Returns:
693
+ The child callback manager.
694
+
695
+ """
696
+ manager = AsyncCallbackManager(handlers=[], parent_run_id=self.run_id)
697
+ manager.set_handlers(self.inheritable_handlers)
698
+ manager.add_tags(self.inheritable_tags)
699
+ manager.add_metadata(self.inheritable_metadata)
700
+ if tag is not None:
701
+ manager.add_tags([tag], inherit=False)
702
+ return manager
703
+
704
+
705
+ class CallbackManagerForLLMRun(RunManager, LLMManagerMixin):
706
+ """Callback manager for LLM run."""
707
+
708
+ def on_llm_new_token(
709
+ self,
710
+ token: str | list[str | dict[str, Any]],
711
+ *,
712
+ chunk: GenerationChunk | ChatGenerationChunk | None = None,
713
+ **kwargs: Any,
714
+ ) -> None:
715
+ """Run when LLM generates a new token.
716
+
717
+ Args:
718
+ token: The new token, or a list of content blocks.
719
+ chunk: The chunk.
720
+ **kwargs: Additional keyword arguments.
721
+
722
+ """
723
+ if not self.handlers:
724
+ return
725
+ handle_event(
726
+ self.handlers,
727
+ "on_llm_new_token",
728
+ "ignore_llm",
729
+ token=token,
730
+ run_id=self.run_id,
731
+ parent_run_id=self.parent_run_id,
732
+ tags=self.tags,
733
+ chunk=chunk,
734
+ **kwargs,
735
+ )
736
+
737
+ def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
738
+ """Run when LLM ends running.
739
+
740
+ Args:
741
+ response: The LLM result.
742
+ **kwargs: Additional keyword arguments.
743
+
744
+ """
745
+ if not self.handlers:
746
+ return
747
+ handle_event(
748
+ self.handlers,
749
+ "on_llm_end",
750
+ "ignore_llm",
751
+ response,
752
+ run_id=self.run_id,
753
+ parent_run_id=self.parent_run_id,
754
+ tags=self.tags,
755
+ **kwargs,
756
+ )
757
+
758
+ def on_llm_error(
759
+ self,
760
+ error: BaseException,
761
+ **kwargs: Any,
762
+ ) -> None:
763
+ """Run when LLM errors.
764
+
765
+ Args:
766
+ error: The error.
767
+ **kwargs: Additional keyword arguments.
768
+
769
+ - response (LLMResult): The response which was generated before
770
+ the error occurred.
771
+ """
772
+ if not self.handlers:
773
+ return
774
+ handle_event(
775
+ self.handlers,
776
+ "on_llm_error",
777
+ "ignore_llm",
778
+ error,
779
+ run_id=self.run_id,
780
+ parent_run_id=self.parent_run_id,
781
+ tags=self.tags,
782
+ **kwargs,
783
+ )
784
+
785
+ def on_stream_event(self, event: MessagesData, **kwargs: Any) -> None:
786
+ """Run on each protocol event from `stream_events(version="v3")`.
787
+
788
+ Args:
789
+ event: The protocol event.
790
+ **kwargs: Additional keyword arguments.
791
+ """
792
+ if not self.handlers:
793
+ return
794
+ handle_event(
795
+ self.handlers,
796
+ "on_stream_event",
797
+ "ignore_llm",
798
+ event,
799
+ run_id=self.run_id,
800
+ parent_run_id=self.parent_run_id,
801
+ tags=self.tags,
802
+ **kwargs,
803
+ )
804
+
805
+
806
+ class AsyncCallbackManagerForLLMRun(AsyncRunManager, LLMManagerMixin):
807
+ """Async callback manager for LLM run."""
808
+
809
+ def get_sync(self) -> CallbackManagerForLLMRun:
810
+ """Get the equivalent sync `RunManager`.
811
+
812
+ Returns:
813
+ The sync `RunManager`.
814
+
815
+ """
816
+ return CallbackManagerForLLMRun(
817
+ run_id=self.run_id,
818
+ handlers=self.handlers,
819
+ inheritable_handlers=self.inheritable_handlers,
820
+ parent_run_id=self.parent_run_id,
821
+ tags=self.tags,
822
+ inheritable_tags=self.inheritable_tags,
823
+ metadata=self.metadata,
824
+ inheritable_metadata=self.inheritable_metadata,
825
+ )
826
+
827
+ async def on_llm_new_token(
828
+ self,
829
+ token: str | list[str | dict[str, Any]],
830
+ *,
831
+ chunk: GenerationChunk | ChatGenerationChunk | None = None,
832
+ **kwargs: Any,
833
+ ) -> None:
834
+ """Run when LLM generates a new token.
835
+
836
+ Args:
837
+ token: The new token, or a list of content blocks.
838
+ chunk: The chunk.
839
+ **kwargs: Additional keyword arguments.
840
+
841
+ """
842
+ if not self.handlers:
843
+ return
844
+ await ahandle_event(
845
+ self.handlers,
846
+ "on_llm_new_token",
847
+ "ignore_llm",
848
+ token,
849
+ chunk=chunk,
850
+ run_id=self.run_id,
851
+ parent_run_id=self.parent_run_id,
852
+ tags=self.tags,
853
+ **kwargs,
854
+ )
855
+
856
+ @shielded
857
+ async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
858
+ """Run when LLM ends running.
859
+
860
+ Args:
861
+ response: The LLM result.
862
+ **kwargs: Additional keyword arguments.
863
+
864
+ """
865
+ if not self.handlers:
866
+ return
867
+ await ahandle_event(
868
+ self.handlers,
869
+ "on_llm_end",
870
+ "ignore_llm",
871
+ response,
872
+ run_id=self.run_id,
873
+ parent_run_id=self.parent_run_id,
874
+ tags=self.tags,
875
+ **kwargs,
876
+ )
877
+
878
+ @shielded
879
+ async def on_llm_error(
880
+ self,
881
+ error: BaseException,
882
+ **kwargs: Any,
883
+ ) -> None:
884
+ """Run when LLM errors.
885
+
886
+ Args:
887
+ error: The error.
888
+ **kwargs: Additional keyword arguments.
889
+
890
+ - response (LLMResult): The response which was generated before
891
+ the error occurred.
892
+
893
+ """
894
+ if not self.handlers:
895
+ return
896
+ await ahandle_event(
897
+ self.handlers,
898
+ "on_llm_error",
899
+ "ignore_llm",
900
+ error,
901
+ run_id=self.run_id,
902
+ parent_run_id=self.parent_run_id,
903
+ tags=self.tags,
904
+ **kwargs,
905
+ )
906
+
907
+ async def on_stream_event(self, event: MessagesData, **kwargs: Any) -> None:
908
+ """Run on each protocol event from `astream_events(version="v3")`.
909
+
910
+ Args:
911
+ event: The protocol event.
912
+ **kwargs: Additional keyword arguments.
913
+ """
914
+ if not self.handlers:
915
+ return
916
+ await ahandle_event(
917
+ self.handlers,
918
+ "on_stream_event",
919
+ "ignore_llm",
920
+ event,
921
+ run_id=self.run_id,
922
+ parent_run_id=self.parent_run_id,
923
+ tags=self.tags,
924
+ **kwargs,
925
+ )
926
+
927
+
928
+ class CallbackManagerForChainRun(ParentRunManager, ChainManagerMixin):
929
+ """Callback manager for chain run."""
930
+
931
+ def on_chain_end(self, outputs: dict[str, Any] | Any, **kwargs: Any) -> None:
932
+ """Run when chain ends running.
933
+
934
+ Args:
935
+ outputs: The outputs of the chain.
936
+ **kwargs: Additional keyword arguments.
937
+
938
+ """
939
+ if not self.handlers:
940
+ return
941
+ handle_event(
942
+ self.handlers,
943
+ "on_chain_end",
944
+ "ignore_chain",
945
+ outputs,
946
+ run_id=self.run_id,
947
+ parent_run_id=self.parent_run_id,
948
+ tags=self.tags,
949
+ **kwargs,
950
+ )
951
+
952
+ def on_chain_error(
953
+ self,
954
+ error: BaseException,
955
+ **kwargs: Any,
956
+ ) -> None:
957
+ """Run when chain errors.
958
+
959
+ Args:
960
+ error: The error.
961
+ **kwargs: Additional keyword arguments.
962
+
963
+ """
964
+ if not self.handlers:
965
+ return
966
+ handle_event(
967
+ self.handlers,
968
+ "on_chain_error",
969
+ "ignore_chain",
970
+ error,
971
+ run_id=self.run_id,
972
+ parent_run_id=self.parent_run_id,
973
+ tags=self.tags,
974
+ **kwargs,
975
+ )
976
+
977
+ def on_agent_action(self, action: AgentAction, **kwargs: Any) -> None:
978
+ """Run when agent action is received.
979
+
980
+ Args:
981
+ action: The agent action.
982
+ **kwargs: Additional keyword arguments.
983
+ """
984
+ if not self.handlers:
985
+ return
986
+ handle_event(
987
+ self.handlers,
988
+ "on_agent_action",
989
+ "ignore_agent",
990
+ action,
991
+ run_id=self.run_id,
992
+ parent_run_id=self.parent_run_id,
993
+ tags=self.tags,
994
+ **kwargs,
995
+ )
996
+
997
+ def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None:
998
+ """Run when agent finish is received.
999
+
1000
+ Args:
1001
+ finish: The agent finish.
1002
+ **kwargs: Additional keyword arguments.
1003
+ """
1004
+ if not self.handlers:
1005
+ return
1006
+ handle_event(
1007
+ self.handlers,
1008
+ "on_agent_finish",
1009
+ "ignore_agent",
1010
+ finish,
1011
+ run_id=self.run_id,
1012
+ parent_run_id=self.parent_run_id,
1013
+ tags=self.tags,
1014
+ **kwargs,
1015
+ )
1016
+
1017
+
1018
+ class AsyncCallbackManagerForChainRun(AsyncParentRunManager, ChainManagerMixin):
1019
+ """Async callback manager for chain run."""
1020
+
1021
+ def get_sync(self) -> CallbackManagerForChainRun:
1022
+ """Get the equivalent sync `RunManager`.
1023
+
1024
+ Returns:
1025
+ The sync `RunManager`.
1026
+ """
1027
+ return CallbackManagerForChainRun(
1028
+ run_id=self.run_id,
1029
+ handlers=self.handlers,
1030
+ inheritable_handlers=self.inheritable_handlers,
1031
+ parent_run_id=self.parent_run_id,
1032
+ tags=self.tags,
1033
+ inheritable_tags=self.inheritable_tags,
1034
+ metadata=self.metadata,
1035
+ inheritable_metadata=self.inheritable_metadata,
1036
+ )
1037
+
1038
+ @shielded
1039
+ async def on_chain_end(self, outputs: dict[str, Any] | Any, **kwargs: Any) -> None:
1040
+ """Run when a chain ends running.
1041
+
1042
+ Args:
1043
+ outputs: The outputs of the chain.
1044
+ **kwargs: Additional keyword arguments.
1045
+
1046
+ """
1047
+ if not self.handlers:
1048
+ return
1049
+ await ahandle_event(
1050
+ self.handlers,
1051
+ "on_chain_end",
1052
+ "ignore_chain",
1053
+ outputs,
1054
+ run_id=self.run_id,
1055
+ parent_run_id=self.parent_run_id,
1056
+ tags=self.tags,
1057
+ **kwargs,
1058
+ )
1059
+
1060
+ @shielded
1061
+ async def on_chain_error(
1062
+ self,
1063
+ error: BaseException,
1064
+ **kwargs: Any,
1065
+ ) -> None:
1066
+ """Run when chain errors.
1067
+
1068
+ Args:
1069
+ error: The error.
1070
+ **kwargs: Additional keyword arguments.
1071
+
1072
+ """
1073
+ if not self.handlers:
1074
+ return
1075
+ await ahandle_event(
1076
+ self.handlers,
1077
+ "on_chain_error",
1078
+ "ignore_chain",
1079
+ error,
1080
+ run_id=self.run_id,
1081
+ parent_run_id=self.parent_run_id,
1082
+ tags=self.tags,
1083
+ **kwargs,
1084
+ )
1085
+
1086
+ async def on_agent_action(self, action: AgentAction, **kwargs: Any) -> None:
1087
+ """Run when agent action is received.
1088
+
1089
+ Args:
1090
+ action: The agent action.
1091
+ **kwargs: Additional keyword arguments.
1092
+ """
1093
+ if not self.handlers:
1094
+ return
1095
+ await ahandle_event(
1096
+ self.handlers,
1097
+ "on_agent_action",
1098
+ "ignore_agent",
1099
+ action,
1100
+ run_id=self.run_id,
1101
+ parent_run_id=self.parent_run_id,
1102
+ tags=self.tags,
1103
+ **kwargs,
1104
+ )
1105
+
1106
+ async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None:
1107
+ """Run when agent finish is received.
1108
+
1109
+ Args:
1110
+ finish: The agent finish.
1111
+ **kwargs: Additional keyword arguments.
1112
+ """
1113
+ if not self.handlers:
1114
+ return
1115
+ await ahandle_event(
1116
+ self.handlers,
1117
+ "on_agent_finish",
1118
+ "ignore_agent",
1119
+ finish,
1120
+ run_id=self.run_id,
1121
+ parent_run_id=self.parent_run_id,
1122
+ tags=self.tags,
1123
+ **kwargs,
1124
+ )
1125
+
1126
+
1127
+ class CallbackManagerForToolRun(ParentRunManager, ToolManagerMixin):
1128
+ """Callback manager for tool run."""
1129
+
1130
+ def on_tool_end(
1131
+ self,
1132
+ output: Any,
1133
+ **kwargs: Any,
1134
+ ) -> None:
1135
+ """Run when the tool ends running.
1136
+
1137
+ Args:
1138
+ output: The output of the tool.
1139
+ **kwargs: The keyword arguments to pass to the event handler
1140
+
1141
+ """
1142
+ if not self.handlers:
1143
+ return
1144
+ handle_event(
1145
+ self.handlers,
1146
+ "on_tool_end",
1147
+ "ignore_agent",
1148
+ output,
1149
+ run_id=self.run_id,
1150
+ parent_run_id=self.parent_run_id,
1151
+ tags=self.tags,
1152
+ **kwargs,
1153
+ )
1154
+
1155
+ def on_tool_error(
1156
+ self,
1157
+ error: BaseException,
1158
+ **kwargs: Any,
1159
+ ) -> None:
1160
+ """Run when tool errors.
1161
+
1162
+ Args:
1163
+ error: The error.
1164
+ **kwargs: Additional keyword arguments.
1165
+
1166
+ """
1167
+ if not self.handlers:
1168
+ return
1169
+ handle_event(
1170
+ self.handlers,
1171
+ "on_tool_error",
1172
+ "ignore_agent",
1173
+ error,
1174
+ run_id=self.run_id,
1175
+ parent_run_id=self.parent_run_id,
1176
+ tags=self.tags,
1177
+ **kwargs,
1178
+ )
1179
+
1180
+
1181
+ class AsyncCallbackManagerForToolRun(AsyncParentRunManager, ToolManagerMixin):
1182
+ """Async callback manager for tool run."""
1183
+
1184
+ def get_sync(self) -> CallbackManagerForToolRun:
1185
+ """Get the equivalent sync `RunManager`.
1186
+
1187
+ Returns:
1188
+ The sync `RunManager`.
1189
+ """
1190
+ return CallbackManagerForToolRun(
1191
+ run_id=self.run_id,
1192
+ handlers=self.handlers,
1193
+ inheritable_handlers=self.inheritable_handlers,
1194
+ parent_run_id=self.parent_run_id,
1195
+ tags=self.tags,
1196
+ inheritable_tags=self.inheritable_tags,
1197
+ metadata=self.metadata,
1198
+ inheritable_metadata=self.inheritable_metadata,
1199
+ )
1200
+
1201
+ async def on_tool_end(self, output: Any, **kwargs: Any) -> None:
1202
+ """Async run when the tool ends running.
1203
+
1204
+ Args:
1205
+ output: The output of the tool.
1206
+ **kwargs: Additional keyword arguments.
1207
+
1208
+ """
1209
+ if not self.handlers:
1210
+ return
1211
+ await ahandle_event(
1212
+ self.handlers,
1213
+ "on_tool_end",
1214
+ "ignore_agent",
1215
+ output,
1216
+ run_id=self.run_id,
1217
+ parent_run_id=self.parent_run_id,
1218
+ tags=self.tags,
1219
+ **kwargs,
1220
+ )
1221
+
1222
+ async def on_tool_error(
1223
+ self,
1224
+ error: BaseException,
1225
+ **kwargs: Any,
1226
+ ) -> None:
1227
+ """Run when tool errors.
1228
+
1229
+ Args:
1230
+ error: The error.
1231
+ **kwargs: Additional keyword arguments.
1232
+
1233
+ """
1234
+ if not self.handlers:
1235
+ return
1236
+ await ahandle_event(
1237
+ self.handlers,
1238
+ "on_tool_error",
1239
+ "ignore_agent",
1240
+ error,
1241
+ run_id=self.run_id,
1242
+ parent_run_id=self.parent_run_id,
1243
+ tags=self.tags,
1244
+ **kwargs,
1245
+ )
1246
+
1247
+
1248
+ class CallbackManagerForRetrieverRun(ParentRunManager, RetrieverManagerMixin):
1249
+ """Callback manager for retriever run."""
1250
+
1251
+ def on_retriever_end(
1252
+ self,
1253
+ documents: Sequence[Document],
1254
+ **kwargs: Any,
1255
+ ) -> None:
1256
+ """Run when retriever ends running.
1257
+
1258
+ Args:
1259
+ documents: The retrieved documents.
1260
+ **kwargs: Additional keyword arguments.
1261
+
1262
+ """
1263
+ if not self.handlers:
1264
+ return
1265
+ handle_event(
1266
+ self.handlers,
1267
+ "on_retriever_end",
1268
+ "ignore_retriever",
1269
+ documents,
1270
+ run_id=self.run_id,
1271
+ parent_run_id=self.parent_run_id,
1272
+ tags=self.tags,
1273
+ **kwargs,
1274
+ )
1275
+
1276
+ def on_retriever_error(
1277
+ self,
1278
+ error: BaseException,
1279
+ **kwargs: Any,
1280
+ ) -> None:
1281
+ """Run when retriever errors.
1282
+
1283
+ Args:
1284
+ error: The error.
1285
+ **kwargs: Additional keyword arguments.
1286
+
1287
+ """
1288
+ if not self.handlers:
1289
+ return
1290
+ handle_event(
1291
+ self.handlers,
1292
+ "on_retriever_error",
1293
+ "ignore_retriever",
1294
+ error,
1295
+ run_id=self.run_id,
1296
+ parent_run_id=self.parent_run_id,
1297
+ tags=self.tags,
1298
+ **kwargs,
1299
+ )
1300
+
1301
+
1302
+ class AsyncCallbackManagerForRetrieverRun(
1303
+ AsyncParentRunManager,
1304
+ RetrieverManagerMixin,
1305
+ ):
1306
+ """Async callback manager for retriever run."""
1307
+
1308
+ def get_sync(self) -> CallbackManagerForRetrieverRun:
1309
+ """Get the equivalent sync `RunManager`.
1310
+
1311
+ Returns:
1312
+ The sync `RunManager`.
1313
+
1314
+ """
1315
+ return CallbackManagerForRetrieverRun(
1316
+ run_id=self.run_id,
1317
+ handlers=self.handlers,
1318
+ inheritable_handlers=self.inheritable_handlers,
1319
+ parent_run_id=self.parent_run_id,
1320
+ tags=self.tags,
1321
+ inheritable_tags=self.inheritable_tags,
1322
+ metadata=self.metadata,
1323
+ inheritable_metadata=self.inheritable_metadata,
1324
+ )
1325
+
1326
+ @shielded
1327
+ async def on_retriever_end(
1328
+ self, documents: Sequence[Document], **kwargs: Any
1329
+ ) -> None:
1330
+ """Run when the retriever ends running.
1331
+
1332
+ Args:
1333
+ documents: The retrieved documents.
1334
+ **kwargs: Additional keyword arguments.
1335
+
1336
+ """
1337
+ if not self.handlers:
1338
+ return
1339
+ await ahandle_event(
1340
+ self.handlers,
1341
+ "on_retriever_end",
1342
+ "ignore_retriever",
1343
+ documents,
1344
+ run_id=self.run_id,
1345
+ parent_run_id=self.parent_run_id,
1346
+ tags=self.tags,
1347
+ **kwargs,
1348
+ )
1349
+
1350
+ @shielded
1351
+ async def on_retriever_error(
1352
+ self,
1353
+ error: BaseException,
1354
+ **kwargs: Any,
1355
+ ) -> None:
1356
+ """Run when retriever errors.
1357
+
1358
+ Args:
1359
+ error: The error.
1360
+ **kwargs: Additional keyword arguments.
1361
+
1362
+ """
1363
+ if not self.handlers:
1364
+ return
1365
+ await ahandle_event(
1366
+ self.handlers,
1367
+ "on_retriever_error",
1368
+ "ignore_retriever",
1369
+ error,
1370
+ run_id=self.run_id,
1371
+ parent_run_id=self.parent_run_id,
1372
+ tags=self.tags,
1373
+ **kwargs,
1374
+ )
1375
+
1376
+
1377
+ class CallbackManager(BaseCallbackManager):
1378
+ """Callback manager for Coco."""
1379
+
1380
+ def on_llm_start(
1381
+ self,
1382
+ serialized: dict[str, Any],
1383
+ prompts: list[str],
1384
+ run_id: UUID | None = None,
1385
+ **kwargs: Any,
1386
+ ) -> list[CallbackManagerForLLMRun]:
1387
+ """Run when LLM starts running.
1388
+
1389
+ Args:
1390
+ serialized: The serialized LLM.
1391
+ prompts: The list of prompts.
1392
+ run_id: The ID of the run.
1393
+ **kwargs: Additional keyword arguments.
1394
+
1395
+ Returns:
1396
+ A callback manager for each prompt as an LLM run.
1397
+
1398
+ """
1399
+ managers = []
1400
+ for i, prompt in enumerate(prompts):
1401
+ # Can't have duplicate runs with the same run ID (if provided)
1402
+ run_id_ = run_id if i == 0 and run_id is not None else uuid7()
1403
+ handle_event(
1404
+ self.handlers,
1405
+ "on_llm_start",
1406
+ "ignore_llm",
1407
+ serialized,
1408
+ [prompt],
1409
+ run_id=run_id_,
1410
+ parent_run_id=self.parent_run_id,
1411
+ tags=self.tags,
1412
+ metadata=self.metadata,
1413
+ **kwargs,
1414
+ )
1415
+
1416
+ managers.append(
1417
+ CallbackManagerForLLMRun(
1418
+ run_id=run_id_,
1419
+ handlers=self.handlers,
1420
+ inheritable_handlers=self.inheritable_handlers,
1421
+ parent_run_id=self.parent_run_id,
1422
+ tags=self.tags,
1423
+ inheritable_tags=self.inheritable_tags,
1424
+ metadata=self.metadata,
1425
+ inheritable_metadata=self.inheritable_metadata,
1426
+ )
1427
+ )
1428
+
1429
+ return managers
1430
+
1431
+ def on_chat_model_start(
1432
+ self,
1433
+ serialized: dict[str, Any],
1434
+ messages: list[list[BaseMessage]],
1435
+ run_id: UUID | None = None,
1436
+ **kwargs: Any,
1437
+ ) -> list[CallbackManagerForLLMRun]:
1438
+ """Run when chat model starts running.
1439
+
1440
+ Args:
1441
+ serialized: The serialized LLM.
1442
+ messages: The list of messages.
1443
+ run_id: The ID of the run.
1444
+ **kwargs: Additional keyword arguments.
1445
+
1446
+ Returns:
1447
+ A callback manager for each list of messages as an LLM run.
1448
+
1449
+ """
1450
+ managers = []
1451
+ for message_list in messages:
1452
+ if run_id is not None:
1453
+ run_id_ = run_id
1454
+ run_id = None
1455
+ else:
1456
+ run_id_ = uuid7()
1457
+ handle_event(
1458
+ self.handlers,
1459
+ "on_chat_model_start",
1460
+ "ignore_chat_model",
1461
+ serialized,
1462
+ [message_list],
1463
+ run_id=run_id_,
1464
+ parent_run_id=self.parent_run_id,
1465
+ tags=self.tags,
1466
+ metadata=self.metadata,
1467
+ **kwargs,
1468
+ )
1469
+
1470
+ managers.append(
1471
+ CallbackManagerForLLMRun(
1472
+ run_id=run_id_,
1473
+ handlers=self.handlers,
1474
+ inheritable_handlers=self.inheritable_handlers,
1475
+ parent_run_id=self.parent_run_id,
1476
+ tags=self.tags,
1477
+ inheritable_tags=self.inheritable_tags,
1478
+ metadata=self.metadata,
1479
+ inheritable_metadata=self.inheritable_metadata,
1480
+ )
1481
+ )
1482
+
1483
+ return managers
1484
+
1485
+ def on_chain_start(
1486
+ self,
1487
+ serialized: dict[str, Any] | None,
1488
+ inputs: dict[str, Any] | Any,
1489
+ run_id: UUID | None = None,
1490
+ **kwargs: Any,
1491
+ ) -> CallbackManagerForChainRun:
1492
+ """Run when chain starts running.
1493
+
1494
+ Args:
1495
+ serialized: The serialized chain.
1496
+ inputs: The inputs to the chain.
1497
+ run_id: The ID of the run.
1498
+ **kwargs: Additional keyword arguments.
1499
+
1500
+ Returns:
1501
+ The callback manager for the chain run.
1502
+
1503
+ """
1504
+ if run_id is None:
1505
+ run_id = uuid7()
1506
+ handle_event(
1507
+ self.handlers,
1508
+ "on_chain_start",
1509
+ "ignore_chain",
1510
+ serialized,
1511
+ inputs,
1512
+ run_id=run_id,
1513
+ parent_run_id=self.parent_run_id,
1514
+ tags=self.tags,
1515
+ metadata=self.metadata,
1516
+ **kwargs,
1517
+ )
1518
+
1519
+ return CallbackManagerForChainRun(
1520
+ run_id=run_id,
1521
+ handlers=self.handlers,
1522
+ inheritable_handlers=self.inheritable_handlers,
1523
+ parent_run_id=self.parent_run_id,
1524
+ tags=self.tags,
1525
+ inheritable_tags=self.inheritable_tags,
1526
+ metadata=self.metadata,
1527
+ inheritable_metadata=self.inheritable_metadata,
1528
+ )
1529
+
1530
+ @override
1531
+ def on_tool_start(
1532
+ self,
1533
+ serialized: dict[str, Any] | None,
1534
+ input_str: str,
1535
+ run_id: UUID | None = None,
1536
+ parent_run_id: UUID | None = None,
1537
+ inputs: dict[str, Any] | None = None,
1538
+ **kwargs: Any,
1539
+ ) -> CallbackManagerForToolRun:
1540
+ """Run when tool starts running.
1541
+
1542
+ Args:
1543
+ serialized: Serialized representation of the tool.
1544
+ input_str: The input to the tool as a string.
1545
+
1546
+ Non-string inputs are cast to strings.
1547
+ run_id: ID for the run.
1548
+ parent_run_id: The ID of the parent run.
1549
+ inputs: The original input to the tool if provided.
1550
+
1551
+ Recommended for usage instead of input_str when the original input is
1552
+ needed.
1553
+
1554
+ If provided, the inputs are expected to be formatted as a dict. The keys
1555
+ will correspond to the named-arguments in the tool.
1556
+ **kwargs: The keyword arguments to pass to the event handler
1557
+
1558
+ Returns:
1559
+ The callback manager for the tool run.
1560
+
1561
+ """
1562
+ if run_id is None:
1563
+ run_id = uuid7()
1564
+
1565
+ handle_event(
1566
+ self.handlers,
1567
+ "on_tool_start",
1568
+ "ignore_agent",
1569
+ serialized,
1570
+ input_str,
1571
+ run_id=run_id,
1572
+ parent_run_id=self.parent_run_id,
1573
+ tags=self.tags,
1574
+ metadata=self.metadata,
1575
+ inputs=inputs,
1576
+ **kwargs,
1577
+ )
1578
+
1579
+ return CallbackManagerForToolRun(
1580
+ run_id=run_id,
1581
+ handlers=self.handlers,
1582
+ inheritable_handlers=self.inheritable_handlers,
1583
+ parent_run_id=self.parent_run_id,
1584
+ tags=self.tags,
1585
+ inheritable_tags=self.inheritable_tags,
1586
+ metadata=self.metadata,
1587
+ inheritable_metadata=self.inheritable_metadata,
1588
+ )
1589
+
1590
+ @override
1591
+ def on_retriever_start(
1592
+ self,
1593
+ serialized: dict[str, Any] | None,
1594
+ query: str,
1595
+ run_id: UUID | None = None,
1596
+ parent_run_id: UUID | None = None,
1597
+ **kwargs: Any,
1598
+ ) -> CallbackManagerForRetrieverRun:
1599
+ """Run when the retriever starts running.
1600
+
1601
+ Args:
1602
+ serialized: The serialized retriever.
1603
+ query: The query.
1604
+ run_id: The ID of the run.
1605
+ parent_run_id: The ID of the parent run.
1606
+ **kwargs: Additional keyword arguments.
1607
+
1608
+ Returns:
1609
+ The callback manager for the retriever run.
1610
+ """
1611
+ if run_id is None:
1612
+ run_id = uuid7()
1613
+
1614
+ handle_event(
1615
+ self.handlers,
1616
+ "on_retriever_start",
1617
+ "ignore_retriever",
1618
+ serialized,
1619
+ query,
1620
+ run_id=run_id,
1621
+ parent_run_id=self.parent_run_id,
1622
+ tags=self.tags,
1623
+ metadata=self.metadata,
1624
+ **kwargs,
1625
+ )
1626
+
1627
+ return CallbackManagerForRetrieverRun(
1628
+ run_id=run_id,
1629
+ handlers=self.handlers,
1630
+ inheritable_handlers=self.inheritable_handlers,
1631
+ parent_run_id=self.parent_run_id,
1632
+ tags=self.tags,
1633
+ inheritable_tags=self.inheritable_tags,
1634
+ metadata=self.metadata,
1635
+ inheritable_metadata=self.inheritable_metadata,
1636
+ )
1637
+
1638
+ def on_custom_event(
1639
+ self,
1640
+ name: str,
1641
+ data: Any,
1642
+ run_id: UUID | None = None,
1643
+ **kwargs: Any,
1644
+ ) -> None:
1645
+ """Dispatch an adhoc event to the handlers (async version).
1646
+
1647
+ This event should NOT be used in any internal Coco code. The event is meant
1648
+ specifically for users of the library to dispatch custom events that are
1649
+ tailored to their application.
1650
+
1651
+ Args:
1652
+ name: The name of the adhoc event.
1653
+ data: The data for the adhoc event.
1654
+ run_id: The ID of the run.
1655
+
1656
+ Raises:
1657
+ ValueError: If additional keyword arguments are passed.
1658
+ """
1659
+ if not self.handlers:
1660
+ return
1661
+ if kwargs:
1662
+ msg = (
1663
+ "The dispatcher API does not accept additional keyword arguments."
1664
+ "Please do not pass any additional keyword arguments, instead "
1665
+ "include them in the data field."
1666
+ )
1667
+ raise ValueError(msg)
1668
+ if run_id is None:
1669
+ run_id = uuid7()
1670
+
1671
+ handle_event(
1672
+ self.handlers,
1673
+ "on_custom_event",
1674
+ "ignore_custom_event",
1675
+ name,
1676
+ data,
1677
+ run_id=run_id,
1678
+ tags=self.tags,
1679
+ metadata=self.metadata,
1680
+ )
1681
+
1682
+ @classmethod
1683
+ def configure(
1684
+ cls,
1685
+ inheritable_callbacks: Callbacks = None,
1686
+ local_callbacks: Callbacks = None,
1687
+ verbose: bool = False, # noqa: FBT001,FBT002
1688
+ inheritable_tags: list[str] | None = None,
1689
+ local_tags: list[str] | None = None,
1690
+ inheritable_metadata: dict[str, Any] | None = None,
1691
+ local_metadata: dict[str, Any] | None = None,
1692
+ *,
1693
+ langsmith_inheritable_metadata: Mapping[str, Any] | None = None,
1694
+ langsmith_inheritable_tags: list[str] | None = None,
1695
+ ) -> CallbackManager:
1696
+ """Configure the callback manager.
1697
+
1698
+ Args:
1699
+ inheritable_callbacks: The inheritable callbacks.
1700
+ local_callbacks: The local callbacks.
1701
+ verbose: Whether to enable verbose mode.
1702
+ inheritable_tags: The inheritable tags.
1703
+ local_tags: The local tags.
1704
+ inheritable_metadata: The inheritable metadata.
1705
+ local_metadata: The local metadata.
1706
+ langsmith_inheritable_metadata: Default inheritable metadata applied
1707
+ to any `CocoTracer` handlers via `set_defaults`.
1708
+ langsmith_inheritable_tags: Default inheritable tags applied to any
1709
+ `CocoTracer` handlers via `set_defaults`.
1710
+
1711
+ Returns:
1712
+ The configured callback manager.
1713
+ """
1714
+ return _configure(
1715
+ cls,
1716
+ inheritable_callbacks,
1717
+ local_callbacks,
1718
+ inheritable_tags,
1719
+ local_tags,
1720
+ inheritable_metadata,
1721
+ local_metadata,
1722
+ verbose=verbose,
1723
+ langsmith_inheritable_metadata=langsmith_inheritable_metadata,
1724
+ langsmith_inheritable_tags=langsmith_inheritable_tags,
1725
+ )
1726
+
1727
+
1728
+ class CallbackManagerForChainGroup(CallbackManager):
1729
+ """Callback manager for the chain group."""
1730
+
1731
+ def __init__(
1732
+ self,
1733
+ handlers: list[BaseCallbackHandler],
1734
+ inheritable_handlers: list[BaseCallbackHandler] | None = None,
1735
+ parent_run_id: UUID | None = None,
1736
+ *,
1737
+ parent_run_manager: CallbackManagerForChainRun,
1738
+ **kwargs: Any,
1739
+ ) -> None:
1740
+ """Initialize the callback manager.
1741
+
1742
+ Args:
1743
+ handlers: The list of handlers.
1744
+ inheritable_handlers: The list of inheritable handlers.
1745
+ parent_run_id: The ID of the parent run.
1746
+ parent_run_manager: The parent run manager.
1747
+ **kwargs: Additional keyword arguments.
1748
+
1749
+ """
1750
+ super().__init__(
1751
+ handlers,
1752
+ inheritable_handlers,
1753
+ parent_run_id,
1754
+ **kwargs,
1755
+ )
1756
+ self.parent_run_manager = parent_run_manager
1757
+ self.ended = False
1758
+
1759
+ @override
1760
+ def copy(self) -> CallbackManagerForChainGroup:
1761
+ return self.__class__(
1762
+ handlers=self.handlers.copy(),
1763
+ inheritable_handlers=self.inheritable_handlers.copy(),
1764
+ parent_run_id=self.parent_run_id,
1765
+ tags=self.tags.copy(),
1766
+ inheritable_tags=self.inheritable_tags.copy(),
1767
+ metadata=self.metadata.copy(),
1768
+ inheritable_metadata=self.inheritable_metadata.copy(),
1769
+ parent_run_manager=self.parent_run_manager,
1770
+ )
1771
+
1772
+ def merge(
1773
+ self: CallbackManagerForChainGroup, other: BaseCallbackManager
1774
+ ) -> CallbackManagerForChainGroup:
1775
+ """Merge the group callback manager with another callback manager.
1776
+
1777
+ Overwrites the merge method in the base class to ensure that the parent run
1778
+ manager is preserved. Keeps the `parent_run_manager` from the current object.
1779
+
1780
+ Returns:
1781
+ A copy of the current object with the handlers, tags, and other attributes
1782
+ merged from the other object.
1783
+
1784
+ Example:
1785
+ ```python
1786
+ # Merging two callback managers
1787
+ from coco_core.callbacks.manager import (
1788
+ CallbackManager,
1789
+ trace_as_chain_group,
1790
+ )
1791
+ from coco_core.callbacks.stdout import StdOutCallbackHandler
1792
+
1793
+ manager = CallbackManager(handlers=[StdOutCallbackHandler()], tags=["tag2"])
1794
+ with trace_as_chain_group("My Group Name", tags=["tag1"]) as group_manager:
1795
+ merged_manager = group_manager.merge(manager)
1796
+ print(type(merged_manager))
1797
+ # <class 'coco_core.callbacks.manager.CallbackManagerForChainGroup'>
1798
+
1799
+ print(merged_manager.handlers)
1800
+ # [
1801
+ # <coco_core.callbacks.stdout.CocoTracer object at ...>,
1802
+ # <coco_core.callbacks.streaming_stdout.StdOutCallbackHandler object at ...>,
1803
+ # ]
1804
+
1805
+ print(merged_manager.tags)
1806
+ # ['tag2', 'tag1']
1807
+ ```
1808
+ """ # noqa: E501
1809
+ manager = self.__class__(
1810
+ parent_run_id=self.parent_run_id or other.parent_run_id,
1811
+ handlers=[],
1812
+ inheritable_handlers=[],
1813
+ tags=list(set(self.tags + other.tags)),
1814
+ inheritable_tags=list(set(self.inheritable_tags + other.inheritable_tags)),
1815
+ metadata={
1816
+ **self.metadata,
1817
+ **other.metadata,
1818
+ },
1819
+ parent_run_manager=self.parent_run_manager,
1820
+ )
1821
+
1822
+ handlers = self.handlers + other.handlers
1823
+ inheritable_handlers = self.inheritable_handlers + other.inheritable_handlers
1824
+
1825
+ for handler in handlers:
1826
+ manager.add_handler(handler)
1827
+
1828
+ for handler in inheritable_handlers:
1829
+ manager.add_handler(handler, inherit=True)
1830
+ return manager
1831
+
1832
+ def on_chain_end(self, outputs: dict[str, Any] | Any, **kwargs: Any) -> None:
1833
+ """Run when traced chain group ends.
1834
+
1835
+ Args:
1836
+ outputs: The outputs of the chain.
1837
+ **kwargs: Additional keyword arguments.
1838
+
1839
+ """
1840
+ self.ended = True
1841
+ return self.parent_run_manager.on_chain_end(outputs, **kwargs)
1842
+
1843
+ def on_chain_error(
1844
+ self,
1845
+ error: BaseException,
1846
+ **kwargs: Any,
1847
+ ) -> None:
1848
+ """Run when chain errors.
1849
+
1850
+ Args:
1851
+ error: The error.
1852
+ **kwargs: Additional keyword arguments.
1853
+
1854
+ """
1855
+ self.ended = True
1856
+ return self.parent_run_manager.on_chain_error(error, **kwargs)
1857
+
1858
+
1859
+ class AsyncCallbackManager(BaseCallbackManager):
1860
+ """Async callback manager that handles callbacks from Coco."""
1861
+
1862
+ @property
1863
+ def is_async(self) -> bool:
1864
+ """Return whether the handler is async."""
1865
+ return True
1866
+
1867
+ async def on_llm_start(
1868
+ self,
1869
+ serialized: dict[str, Any],
1870
+ prompts: list[str],
1871
+ run_id: UUID | None = None,
1872
+ **kwargs: Any,
1873
+ ) -> list[AsyncCallbackManagerForLLMRun]:
1874
+ """Run when LLM starts running.
1875
+
1876
+ Args:
1877
+ serialized: The serialized LLM.
1878
+ prompts: The list of prompts.
1879
+ run_id: The ID of the run.
1880
+ **kwargs: Additional keyword arguments.
1881
+
1882
+ Returns:
1883
+ The list of async callback managers, one for each LLM run corresponding to
1884
+ each prompt.
1885
+ """
1886
+ inline_tasks = []
1887
+ non_inline_tasks = []
1888
+ inline_handlers = [handler for handler in self.handlers if handler.run_inline]
1889
+ non_inline_handlers = [
1890
+ handler for handler in self.handlers if not handler.run_inline
1891
+ ]
1892
+ managers = []
1893
+
1894
+ for prompt in prompts:
1895
+ if run_id is not None:
1896
+ run_id_ = run_id
1897
+ run_id = None
1898
+ else:
1899
+ run_id_ = uuid7()
1900
+
1901
+ if inline_handlers:
1902
+ inline_tasks.append(
1903
+ ahandle_event(
1904
+ inline_handlers,
1905
+ "on_llm_start",
1906
+ "ignore_llm",
1907
+ serialized,
1908
+ [prompt],
1909
+ run_id=run_id_,
1910
+ parent_run_id=self.parent_run_id,
1911
+ tags=self.tags,
1912
+ metadata=self.metadata,
1913
+ **kwargs,
1914
+ )
1915
+ )
1916
+ else:
1917
+ non_inline_tasks.append(
1918
+ ahandle_event(
1919
+ non_inline_handlers,
1920
+ "on_llm_start",
1921
+ "ignore_llm",
1922
+ serialized,
1923
+ [prompt],
1924
+ run_id=run_id_,
1925
+ parent_run_id=self.parent_run_id,
1926
+ tags=self.tags,
1927
+ metadata=self.metadata,
1928
+ **kwargs,
1929
+ )
1930
+ )
1931
+
1932
+ managers.append(
1933
+ AsyncCallbackManagerForLLMRun(
1934
+ run_id=run_id_,
1935
+ handlers=self.handlers,
1936
+ inheritable_handlers=self.inheritable_handlers,
1937
+ parent_run_id=self.parent_run_id,
1938
+ tags=self.tags,
1939
+ inheritable_tags=self.inheritable_tags,
1940
+ metadata=self.metadata,
1941
+ inheritable_metadata=self.inheritable_metadata,
1942
+ )
1943
+ )
1944
+
1945
+ # Run inline tasks sequentially
1946
+ for inline_task in inline_tasks:
1947
+ await inline_task
1948
+
1949
+ # Run non-inline tasks concurrently
1950
+ if non_inline_tasks:
1951
+ await asyncio.gather(*non_inline_tasks)
1952
+
1953
+ return managers
1954
+
1955
+ async def on_chat_model_start(
1956
+ self,
1957
+ serialized: dict[str, Any],
1958
+ messages: list[list[BaseMessage]],
1959
+ run_id: UUID | None = None,
1960
+ **kwargs: Any,
1961
+ ) -> list[AsyncCallbackManagerForLLMRun]:
1962
+ """Async run when LLM starts running.
1963
+
1964
+ Args:
1965
+ serialized: The serialized LLM.
1966
+ messages: The list of messages.
1967
+ run_id: The ID of the run.
1968
+ **kwargs: Additional keyword arguments.
1969
+
1970
+ Returns:
1971
+ The list of async callback managers, one for each LLM run corresponding to
1972
+ each inner message list.
1973
+ """
1974
+ inline_tasks = []
1975
+ non_inline_tasks = []
1976
+ managers = []
1977
+
1978
+ for message_list in messages:
1979
+ if run_id is not None:
1980
+ run_id_ = run_id
1981
+ run_id = None
1982
+ else:
1983
+ run_id_ = uuid7()
1984
+
1985
+ for handler in self.handlers:
1986
+ task = ahandle_event(
1987
+ [handler],
1988
+ "on_chat_model_start",
1989
+ "ignore_chat_model",
1990
+ serialized,
1991
+ [message_list],
1992
+ run_id=run_id_,
1993
+ parent_run_id=self.parent_run_id,
1994
+ tags=self.tags,
1995
+ metadata=self.metadata,
1996
+ **kwargs,
1997
+ )
1998
+ if handler.run_inline:
1999
+ inline_tasks.append(task)
2000
+ else:
2001
+ non_inline_tasks.append(task)
2002
+
2003
+ managers.append(
2004
+ AsyncCallbackManagerForLLMRun(
2005
+ run_id=run_id_,
2006
+ handlers=self.handlers,
2007
+ inheritable_handlers=self.inheritable_handlers,
2008
+ parent_run_id=self.parent_run_id,
2009
+ tags=self.tags,
2010
+ inheritable_tags=self.inheritable_tags,
2011
+ metadata=self.metadata,
2012
+ inheritable_metadata=self.inheritable_metadata,
2013
+ )
2014
+ )
2015
+
2016
+ # Run inline tasks sequentially
2017
+ for task in inline_tasks:
2018
+ await task
2019
+
2020
+ # Run non-inline tasks concurrently
2021
+ if non_inline_tasks:
2022
+ await asyncio.gather(*non_inline_tasks)
2023
+
2024
+ return managers
2025
+
2026
+ async def on_chain_start(
2027
+ self,
2028
+ serialized: dict[str, Any] | None,
2029
+ inputs: dict[str, Any] | Any,
2030
+ run_id: UUID | None = None,
2031
+ **kwargs: Any,
2032
+ ) -> AsyncCallbackManagerForChainRun:
2033
+ """Async run when chain starts running.
2034
+
2035
+ Args:
2036
+ serialized: The serialized chain.
2037
+ inputs: The inputs to the chain.
2038
+ run_id: The ID of the run.
2039
+ **kwargs: Additional keyword arguments.
2040
+
2041
+ Returns:
2042
+ The async callback manager for the chain run.
2043
+ """
2044
+ if run_id is None:
2045
+ run_id = uuid7()
2046
+
2047
+ await ahandle_event(
2048
+ self.handlers,
2049
+ "on_chain_start",
2050
+ "ignore_chain",
2051
+ serialized,
2052
+ inputs,
2053
+ run_id=run_id,
2054
+ parent_run_id=self.parent_run_id,
2055
+ tags=self.tags,
2056
+ metadata=self.metadata,
2057
+ **kwargs,
2058
+ )
2059
+
2060
+ return AsyncCallbackManagerForChainRun(
2061
+ run_id=run_id,
2062
+ handlers=self.handlers,
2063
+ inheritable_handlers=self.inheritable_handlers,
2064
+ parent_run_id=self.parent_run_id,
2065
+ tags=self.tags,
2066
+ inheritable_tags=self.inheritable_tags,
2067
+ metadata=self.metadata,
2068
+ inheritable_metadata=self.inheritable_metadata,
2069
+ )
2070
+
2071
+ @override
2072
+ async def on_tool_start(
2073
+ self,
2074
+ serialized: dict[str, Any] | None,
2075
+ input_str: str,
2076
+ run_id: UUID | None = None,
2077
+ parent_run_id: UUID | None = None,
2078
+ **kwargs: Any,
2079
+ ) -> AsyncCallbackManagerForToolRun:
2080
+ """Run when the tool starts running.
2081
+
2082
+ Args:
2083
+ serialized: The serialized tool.
2084
+ input_str: The input to the tool.
2085
+ run_id: The ID of the run.
2086
+ parent_run_id: The ID of the parent run.
2087
+ **kwargs: Additional keyword arguments.
2088
+
2089
+ Returns:
2090
+ The async callback manager for the tool run.
2091
+ """
2092
+ if run_id is None:
2093
+ run_id = uuid7()
2094
+
2095
+ await ahandle_event(
2096
+ self.handlers,
2097
+ "on_tool_start",
2098
+ "ignore_agent",
2099
+ serialized,
2100
+ input_str,
2101
+ run_id=run_id,
2102
+ parent_run_id=self.parent_run_id,
2103
+ tags=self.tags,
2104
+ metadata=self.metadata,
2105
+ **kwargs,
2106
+ )
2107
+
2108
+ return AsyncCallbackManagerForToolRun(
2109
+ run_id=run_id,
2110
+ handlers=self.handlers,
2111
+ inheritable_handlers=self.inheritable_handlers,
2112
+ parent_run_id=self.parent_run_id,
2113
+ tags=self.tags,
2114
+ inheritable_tags=self.inheritable_tags,
2115
+ metadata=self.metadata,
2116
+ inheritable_metadata=self.inheritable_metadata,
2117
+ )
2118
+
2119
+ async def on_custom_event(
2120
+ self,
2121
+ name: str,
2122
+ data: Any,
2123
+ run_id: UUID | None = None,
2124
+ **kwargs: Any,
2125
+ ) -> None:
2126
+ """Dispatch an adhoc event to the handlers (async version).
2127
+
2128
+ This event should NOT be used in any internal Coco code. The event is meant
2129
+ specifically for users of the library to dispatch custom events that are
2130
+ tailored to their application.
2131
+
2132
+ Args:
2133
+ name: The name of the adhoc event.
2134
+ data: The data for the adhoc event.
2135
+ run_id: The ID of the run.
2136
+
2137
+ Raises:
2138
+ ValueError: If additional keyword arguments are passed.
2139
+ """
2140
+ if not self.handlers:
2141
+ return
2142
+ if run_id is None:
2143
+ run_id = uuid7()
2144
+
2145
+ if kwargs:
2146
+ msg = (
2147
+ "The dispatcher API does not accept additional keyword arguments."
2148
+ "Please do not pass any additional keyword arguments, instead "
2149
+ "include them in the data field."
2150
+ )
2151
+ raise ValueError(msg)
2152
+ await ahandle_event(
2153
+ self.handlers,
2154
+ "on_custom_event",
2155
+ "ignore_custom_event",
2156
+ name,
2157
+ data,
2158
+ run_id=run_id,
2159
+ tags=self.tags,
2160
+ metadata=self.metadata,
2161
+ )
2162
+
2163
+ @override
2164
+ async def on_retriever_start(
2165
+ self,
2166
+ serialized: dict[str, Any] | None,
2167
+ query: str,
2168
+ run_id: UUID | None = None,
2169
+ parent_run_id: UUID | None = None,
2170
+ **kwargs: Any,
2171
+ ) -> AsyncCallbackManagerForRetrieverRun:
2172
+ """Run when the retriever starts running.
2173
+
2174
+ Args:
2175
+ serialized: The serialized retriever.
2176
+ query: The query.
2177
+ run_id: The ID of the run.
2178
+ parent_run_id: The ID of the parent run.
2179
+ **kwargs: Additional keyword arguments.
2180
+
2181
+ Returns:
2182
+ The async callback manager for the retriever run.
2183
+ """
2184
+ if run_id is None:
2185
+ run_id = uuid7()
2186
+
2187
+ await ahandle_event(
2188
+ self.handlers,
2189
+ "on_retriever_start",
2190
+ "ignore_retriever",
2191
+ serialized,
2192
+ query,
2193
+ run_id=run_id,
2194
+ parent_run_id=self.parent_run_id,
2195
+ tags=self.tags,
2196
+ metadata=self.metadata,
2197
+ **kwargs,
2198
+ )
2199
+
2200
+ return AsyncCallbackManagerForRetrieverRun(
2201
+ run_id=run_id,
2202
+ handlers=self.handlers,
2203
+ inheritable_handlers=self.inheritable_handlers,
2204
+ parent_run_id=self.parent_run_id,
2205
+ tags=self.tags,
2206
+ inheritable_tags=self.inheritable_tags,
2207
+ metadata=self.metadata,
2208
+ inheritable_metadata=self.inheritable_metadata,
2209
+ )
2210
+
2211
+ @classmethod
2212
+ def configure(
2213
+ cls,
2214
+ inheritable_callbacks: Callbacks = None,
2215
+ local_callbacks: Callbacks = None,
2216
+ verbose: bool = False, # noqa: FBT001,FBT002
2217
+ inheritable_tags: list[str] | None = None,
2218
+ local_tags: list[str] | None = None,
2219
+ inheritable_metadata: dict[str, Any] | None = None,
2220
+ local_metadata: dict[str, Any] | None = None,
2221
+ *,
2222
+ langsmith_inheritable_metadata: Mapping[str, Any] | None = None,
2223
+ langsmith_inheritable_tags: list[str] | None = None,
2224
+ ) -> AsyncCallbackManager:
2225
+ """Configure the async callback manager.
2226
+
2227
+ Args:
2228
+ inheritable_callbacks: The inheritable callbacks.
2229
+ local_callbacks: The local callbacks.
2230
+ verbose: Whether to enable verbose mode.
2231
+ inheritable_tags: The inheritable tags.
2232
+ local_tags: The local tags.
2233
+ inheritable_metadata: The inheritable metadata.
2234
+ local_metadata: The local metadata.
2235
+ langsmith_inheritable_metadata: Default inheritable metadata applied
2236
+ to any `CocoTracer` handlers via `set_defaults`.
2237
+ langsmith_inheritable_tags: Default inheritable tags applied to any
2238
+ `CocoTracer` handlers via `set_defaults`.
2239
+
2240
+ Returns:
2241
+ The configured async callback manager.
2242
+ """
2243
+ return _configure(
2244
+ cls,
2245
+ inheritable_callbacks,
2246
+ local_callbacks,
2247
+ inheritable_tags,
2248
+ local_tags,
2249
+ inheritable_metadata,
2250
+ local_metadata,
2251
+ verbose=verbose,
2252
+ langsmith_inheritable_metadata=langsmith_inheritable_metadata,
2253
+ langsmith_inheritable_tags=langsmith_inheritable_tags,
2254
+ )
2255
+
2256
+
2257
+ class AsyncCallbackManagerForChainGroup(AsyncCallbackManager):
2258
+ """Async callback manager for the chain group."""
2259
+
2260
+ def __init__(
2261
+ self,
2262
+ handlers: list[BaseCallbackHandler],
2263
+ inheritable_handlers: list[BaseCallbackHandler] | None = None,
2264
+ parent_run_id: UUID | None = None,
2265
+ *,
2266
+ parent_run_manager: AsyncCallbackManagerForChainRun,
2267
+ **kwargs: Any,
2268
+ ) -> None:
2269
+ """Initialize the async callback manager.
2270
+
2271
+ Args:
2272
+ handlers: The list of handlers.
2273
+ inheritable_handlers: The list of inheritable handlers.
2274
+ parent_run_id: The ID of the parent run.
2275
+ parent_run_manager: The parent run manager.
2276
+ **kwargs: Additional keyword arguments.
2277
+ """
2278
+ super().__init__(
2279
+ handlers,
2280
+ inheritable_handlers,
2281
+ parent_run_id,
2282
+ **kwargs,
2283
+ )
2284
+ self.parent_run_manager = parent_run_manager
2285
+ self.ended = False
2286
+
2287
+ def copy(self) -> AsyncCallbackManagerForChainGroup:
2288
+ """Return a copy the async callback manager."""
2289
+ return self.__class__(
2290
+ handlers=self.handlers.copy(),
2291
+ inheritable_handlers=self.inheritable_handlers.copy(),
2292
+ parent_run_id=self.parent_run_id,
2293
+ tags=self.tags.copy(),
2294
+ inheritable_tags=self.inheritable_tags.copy(),
2295
+ metadata=self.metadata.copy(),
2296
+ inheritable_metadata=self.inheritable_metadata.copy(),
2297
+ parent_run_manager=self.parent_run_manager,
2298
+ )
2299
+
2300
+ def merge(
2301
+ self: AsyncCallbackManagerForChainGroup, other: BaseCallbackManager
2302
+ ) -> AsyncCallbackManagerForChainGroup:
2303
+ """Merge the group callback manager with another callback manager.
2304
+
2305
+ Overwrites the merge method in the base class to ensure that the parent run
2306
+ manager is preserved. Keeps the `parent_run_manager` from the current object.
2307
+
2308
+ Returns:
2309
+ A copy of the current `AsyncCallbackManagerForChainGroup` with the handlers,
2310
+ tags, etc. of the other callback manager merged in.
2311
+
2312
+ Example:
2313
+ ```python
2314
+ # Merging two callback managers
2315
+ from coco_core.callbacks.manager import (
2316
+ CallbackManager,
2317
+ atrace_as_chain_group,
2318
+ )
2319
+ from coco_core.callbacks.stdout import StdOutCallbackHandler
2320
+
2321
+ manager = CallbackManager(handlers=[StdOutCallbackHandler()], tags=["tag2"])
2322
+ async with atrace_as_chain_group(
2323
+ "My Group Name", tags=["tag1"]
2324
+ ) as group_manager:
2325
+ merged_manager = group_manager.merge(manager)
2326
+ print(type(merged_manager))
2327
+ # <class 'coco_core.callbacks.manager.AsyncCallbackManagerForChainGroup'>
2328
+
2329
+ print(merged_manager.handlers)
2330
+ # [
2331
+ # <coco_core.callbacks.stdout.CocoTracer object at ...>,
2332
+ # <coco_core.callbacks.streaming_stdout.StdOutCallbackHandler object at ...>,
2333
+ # ]
2334
+
2335
+ print(merged_manager.tags)
2336
+ # ['tag2', 'tag1']
2337
+ ```
2338
+ """ # noqa: E501
2339
+ manager = self.__class__(
2340
+ parent_run_id=self.parent_run_id or other.parent_run_id,
2341
+ handlers=[],
2342
+ inheritable_handlers=[],
2343
+ tags=list(set(self.tags + other.tags)),
2344
+ inheritable_tags=list(set(self.inheritable_tags + other.inheritable_tags)),
2345
+ metadata={
2346
+ **self.metadata,
2347
+ **other.metadata,
2348
+ },
2349
+ parent_run_manager=self.parent_run_manager,
2350
+ )
2351
+
2352
+ handlers = self.handlers + other.handlers
2353
+ inheritable_handlers = self.inheritable_handlers + other.inheritable_handlers
2354
+
2355
+ for handler in handlers:
2356
+ manager.add_handler(handler)
2357
+
2358
+ for handler in inheritable_handlers:
2359
+ manager.add_handler(handler, inherit=True)
2360
+ return manager
2361
+
2362
+ async def on_chain_end(self, outputs: dict[str, Any] | Any, **kwargs: Any) -> None:
2363
+ """Run when traced chain group ends.
2364
+
2365
+ Args:
2366
+ outputs: The outputs of the chain.
2367
+ **kwargs: Additional keyword arguments.
2368
+ """
2369
+ self.ended = True
2370
+ await self.parent_run_manager.on_chain_end(outputs, **kwargs)
2371
+
2372
+ async def on_chain_error(
2373
+ self,
2374
+ error: BaseException,
2375
+ **kwargs: Any,
2376
+ ) -> None:
2377
+ """Run when chain errors.
2378
+
2379
+ Args:
2380
+ error: The error.
2381
+ **kwargs: Additional keyword arguments.
2382
+ """
2383
+ self.ended = True
2384
+ await self.parent_run_manager.on_chain_error(error, **kwargs)
2385
+
2386
+
2387
+ T = TypeVar("T", CallbackManager, AsyncCallbackManager)
2388
+
2389
+
2390
+ def _configure(
2391
+ callback_manager_cls: type[T],
2392
+ inheritable_callbacks: Callbacks = None,
2393
+ local_callbacks: Callbacks = None,
2394
+ inheritable_tags: list[str] | None = None,
2395
+ local_tags: list[str] | None = None,
2396
+ inheritable_metadata: dict[str, Any] | None = None,
2397
+ local_metadata: dict[str, Any] | None = None,
2398
+ *,
2399
+ verbose: bool = False,
2400
+ langsmith_inheritable_metadata: Mapping[str, Any] | None = None,
2401
+ langsmith_inheritable_tags: list[str] | None = None,
2402
+ ) -> T:
2403
+ """Configure the callback manager.
2404
+
2405
+ Args:
2406
+ callback_manager_cls: The callback manager class.
2407
+ inheritable_callbacks: The inheritable callbacks.
2408
+ local_callbacks: The local callbacks.
2409
+ inheritable_tags: The inheritable tags.
2410
+ local_tags: The local tags.
2411
+ inheritable_metadata: The inheritable metadata.
2412
+ local_metadata: The local metadata.
2413
+ verbose: Whether to enable verbose mode.
2414
+ langsmith_inheritable_metadata: Default inheritable metadata applied to
2415
+ any `CocoTracer` handlers via `set_defaults`.
2416
+ langsmith_inheritable_tags: Default inheritable tags applied to any
2417
+ `CocoTracer` handlers via `set_defaults`.
2418
+
2419
+ Raises:
2420
+ RuntimeError: If `COCO_TRACING` is set but `COCO_TRACING_V2` is not.
2421
+
2422
+ Returns:
2423
+ The configured callback manager.
2424
+ """
2425
+ # Deferred to avoid importing langsmith at module level (~132ms).
2426
+ from coco_core.tracers._context import get_tracing_context # noqa: PLC0415
2427
+
2428
+ from coco_core.tracers.context import ( # noqa: PLC0415
2429
+ _configure_hooks,
2430
+ _get_tracer_project,
2431
+ _tracing_v2_is_enabled,
2432
+ tracing_v2_callback_var,
2433
+ )
2434
+ from coco_core.tracers.coco import CocoTracer # noqa: PLC0415
2435
+ from coco_core.tracers.stdout import ConsoleCallbackHandler # noqa: PLC0415
2436
+
2437
+ tracing_context = get_tracing_context()
2438
+ tracing_metadata = tracing_context["metadata"]
2439
+ tracing_tags = tracing_context["tags"]
2440
+ run_tree: Run | None = tracing_context["parent"]
2441
+ parent_run_id = None if run_tree is None else run_tree.id
2442
+ callback_manager = callback_manager_cls(
2443
+ handlers=[],
2444
+ parent_run_id=parent_run_id,
2445
+ )
2446
+ if inheritable_callbacks or local_callbacks:
2447
+ if isinstance(inheritable_callbacks, list) or inheritable_callbacks is None:
2448
+ inheritable_callbacks_ = inheritable_callbacks or []
2449
+ callback_manager = callback_manager_cls(
2450
+ handlers=inheritable_callbacks_.copy(),
2451
+ inheritable_handlers=inheritable_callbacks_.copy(),
2452
+ parent_run_id=parent_run_id,
2453
+ )
2454
+ else:
2455
+ parent_run_id_ = inheritable_callbacks.parent_run_id
2456
+ # Break ties between the external tracing context and inherited context
2457
+ if parent_run_id is not None and (
2458
+ parent_run_id_ is None
2459
+ # If the LC parent has already been reflected
2460
+ # in the run tree, we know the run_tree is either the
2461
+ # same parent or a child of the parent.
2462
+ or (run_tree and str(parent_run_id_) in run_tree.dotted_order)
2463
+ ):
2464
+ parent_run_id_ = parent_run_id
2465
+ # Otherwise, we assume the LC context has progressed
2466
+ # beyond the run tree and we should not inherit the parent.
2467
+ callback_manager = callback_manager_cls(
2468
+ handlers=inheritable_callbacks.handlers.copy(),
2469
+ inheritable_handlers=inheritable_callbacks.inheritable_handlers.copy(),
2470
+ parent_run_id=parent_run_id_,
2471
+ tags=inheritable_callbacks.tags.copy(),
2472
+ inheritable_tags=inheritable_callbacks.inheritable_tags.copy(),
2473
+ metadata=inheritable_callbacks.metadata.copy(),
2474
+ inheritable_metadata=inheritable_callbacks.inheritable_metadata.copy(),
2475
+ )
2476
+ local_handlers_ = (
2477
+ local_callbacks
2478
+ if isinstance(local_callbacks, list)
2479
+ else (local_callbacks.handlers if local_callbacks else [])
2480
+ )
2481
+ for handler in local_handlers_:
2482
+ callback_manager.add_handler(handler, inherit=False)
2483
+ if inheritable_tags or local_tags:
2484
+ callback_manager.add_tags(inheritable_tags or [])
2485
+ callback_manager.add_tags(local_tags or [], inherit=False)
2486
+ if inheritable_metadata or local_metadata:
2487
+ callback_manager.add_metadata(inheritable_metadata or {})
2488
+ callback_manager.add_metadata(local_metadata or {}, inherit=False)
2489
+ if tracing_tags:
2490
+ callback_manager.add_tags(tracing_tags.copy())
2491
+
2492
+ v1_tracing_enabled_ = env_var_is_set("COCO_TRACING") or env_var_is_set(
2493
+ "COCO_HANDLER"
2494
+ )
2495
+
2496
+ tracer_v2 = tracing_v2_callback_var.get()
2497
+ tracing_v2_enabled_ = _tracing_v2_is_enabled()
2498
+
2499
+ if v1_tracing_enabled_ and not tracing_v2_enabled_:
2500
+ # if both are enabled, can silently ignore the v1 tracer
2501
+ msg = (
2502
+ "Tracing using CocoTracerV1 is no longer supported. "
2503
+ "Please set the COCO_TRACING_V2 environment variable to enable "
2504
+ "tracing instead."
2505
+ )
2506
+ raise RuntimeError(msg)
2507
+
2508
+ tracer_project = _get_tracer_project()
2509
+ debug = _get_debug()
2510
+ if verbose or debug or tracing_v2_enabled_:
2511
+ if verbose and not any(
2512
+ isinstance(handler, StdOutCallbackHandler)
2513
+ for handler in callback_manager.handlers
2514
+ ):
2515
+ if debug:
2516
+ pass
2517
+ else:
2518
+ callback_manager.add_handler(StdOutCallbackHandler(), inherit=False)
2519
+ if debug and not any(
2520
+ isinstance(handler, ConsoleCallbackHandler)
2521
+ for handler in callback_manager.handlers
2522
+ ):
2523
+ callback_manager.add_handler(ConsoleCallbackHandler())
2524
+ if tracing_v2_enabled_ and not any(
2525
+ isinstance(handler, CocoTracer)
2526
+ for handler in callback_manager.handlers
2527
+ ):
2528
+ if tracer_v2:
2529
+ callback_manager.add_handler(tracer_v2)
2530
+ else:
2531
+ try:
2532
+ handler = CocoTracer(
2533
+ project_name=tracer_project,
2534
+ client=(
2535
+ run_tree.client
2536
+ if run_tree is not None
2537
+ else tracing_context["client"]
2538
+ ),
2539
+ tags=tracing_tags,
2540
+ metadata=tracing_metadata,
2541
+ )
2542
+ callback_manager.add_handler(handler)
2543
+ except Exception as e:
2544
+ logger.warning(
2545
+ "Unable to load requested CocoTracer."
2546
+ " To disable this warning,"
2547
+ " unset the COCO_TRACING_V2 environment variables.\n"
2548
+ "%s",
2549
+ repr(e),
2550
+ )
2551
+ if run_tree is not None:
2552
+ for handler in callback_manager.handlers:
2553
+ if isinstance(handler, CocoTracer):
2554
+ handler.order_map[run_tree.id] = (
2555
+ run_tree.trace_id,
2556
+ run_tree.dotted_order,
2557
+ )
2558
+ run_id_str = str(run_tree.id)
2559
+ if run_id_str not in handler.run_map:
2560
+ handler.run_map[run_id_str] = run_tree
2561
+ handler._external_run_ids.setdefault( # noqa: SLF001
2562
+ run_id_str, 0
2563
+ )
2564
+ for var, inheritable, handler_class, env_var in _configure_hooks:
2565
+ create_one = (
2566
+ env_var is not None
2567
+ and env_var_is_set(env_var)
2568
+ and handler_class is not None
2569
+ )
2570
+ if var.get() is not None or create_one:
2571
+ var_handler = (
2572
+ var.get() or cast("type[BaseCallbackHandler]", handler_class)()
2573
+ )
2574
+ if handler_class is None:
2575
+ if not any(
2576
+ handler is var_handler # direct pointer comparison
2577
+ for handler in callback_manager.handlers
2578
+ ):
2579
+ callback_manager.add_handler(var_handler, inheritable)
2580
+ elif not any(
2581
+ isinstance(handler, handler_class)
2582
+ for handler in callback_manager.handlers
2583
+ ):
2584
+ callback_manager.add_handler(var_handler, inheritable)
2585
+
2586
+ if tracing_metadata:
2587
+ langsmith_inheritable_metadata = {
2588
+ **tracing_metadata,
2589
+ **(langsmith_inheritable_metadata or {}),
2590
+ }
2591
+
2592
+ if langsmith_inheritable_metadata or langsmith_inheritable_tags:
2593
+ callback_manager.handlers = [
2594
+ handler.copy_with_metadata_defaults(
2595
+ metadata=langsmith_inheritable_metadata,
2596
+ tags=langsmith_inheritable_tags,
2597
+ )
2598
+ if isinstance(handler, CocoTracer)
2599
+ else handler
2600
+ for handler in callback_manager.handlers
2601
+ ]
2602
+ callback_manager.inheritable_handlers = [
2603
+ handler.copy_with_metadata_defaults(
2604
+ metadata=langsmith_inheritable_metadata,
2605
+ tags=langsmith_inheritable_tags,
2606
+ )
2607
+ if isinstance(handler, CocoTracer)
2608
+ else handler
2609
+ for handler in callback_manager.inheritable_handlers
2610
+ ]
2611
+ return callback_manager
2612
+
2613
+
2614
+ async def adispatch_custom_event(
2615
+ name: str, data: Any, *, config: RunnableConfig | None = None
2616
+ ) -> None:
2617
+ """Dispatch an adhoc event to the handlers.
2618
+
2619
+ Args:
2620
+ name: The name of the adhoc event.
2621
+ data: The data for the adhoc event.
2622
+
2623
+ Free form data. Ideally should be JSON serializable to avoid serialization
2624
+ issues downstream, but this is not enforced.
2625
+ config: Optional config object.
2626
+
2627
+ Mirrors the async API but not strictly needed.
2628
+
2629
+ Raises:
2630
+ RuntimeError: If there is no parent run ID available to associate the event
2631
+ with.
2632
+
2633
+ Example:
2634
+ ```python
2635
+ from coco_core.callbacks import (
2636
+ AsyncCallbackHandler,
2637
+ adispatch_custom_event
2638
+ )
2639
+ from coco_core.runnable import RunnableLambda
2640
+
2641
+ class CustomCallbackManager(AsyncCallbackHandler):
2642
+ async def on_custom_event(
2643
+ self,
2644
+ name: str,
2645
+ data: Any,
2646
+ *,
2647
+ run_id: UUID,
2648
+ tags: list[str] | None = None,
2649
+ metadata: dict[str, Any] | None = None,
2650
+ **kwargs: Any,
2651
+ ) -> None:
2652
+ print(f"Received custom event: {name} with data: {data}")
2653
+
2654
+ callback = CustomCallbackManager()
2655
+
2656
+ async def foo(inputs):
2657
+ await adispatch_custom_event("my_event", {"bar": "buzz})
2658
+ return inputs
2659
+
2660
+ foo_ = RunnableLambda(foo)
2661
+ await foo_.ainvoke({"a": "1"}, {"callbacks": [CustomCallbackManager()]})
2662
+ ```
2663
+
2664
+ Example: Use with astream events
2665
+
2666
+ ```python
2667
+ from coco_core.callbacks import (
2668
+ AsyncCallbackHandler,
2669
+ adispatch_custom_event
2670
+ )
2671
+ from coco_core.runnable import RunnableLambda
2672
+
2673
+ class CustomCallbackManager(AsyncCallbackHandler):
2674
+ async def on_custom_event(
2675
+ self,
2676
+ name: str,
2677
+ data: Any,
2678
+ *,
2679
+ run_id: UUID,
2680
+ tags: list[str] | None = None,
2681
+ metadata: dict[str, Any] | None = None,
2682
+ **kwargs: Any,
2683
+ ) -> None:
2684
+ print(f"Received custom event: {name} with data: {data}")
2685
+
2686
+ callback = CustomCallbackManager()
2687
+
2688
+ async def foo(inputs):
2689
+ await adispatch_custom_event("event_type_1", {"bar": "buzz})
2690
+ await adispatch_custom_event("event_type_2", 5)
2691
+ return inputs
2692
+
2693
+ foo_ = RunnableLambda(foo)
2694
+
2695
+ async for event in foo_.ainvoke_stream(
2696
+ {"a": "1"},
2697
+ version="v2",
2698
+ config={"callbacks": [CustomCallbackManager()]}
2699
+ ):
2700
+ print(event)
2701
+ ```
2702
+
2703
+ !!! warning
2704
+
2705
+ If using python 3.10 and async, you MUST specify the `config` parameter or the
2706
+ function will raise an error. This is due to a limitation in asyncio for python
2707
+ 3.10 that prevents Coco from automatically propagating the config object on
2708
+ the user's behalf.
2709
+ """
2710
+ # Import locally to prevent circular imports.
2711
+ from coco_core.runnables.config import ( # noqa: PLC0415
2712
+ ensure_config,
2713
+ get_async_callback_manager_for_config,
2714
+ )
2715
+
2716
+ config = ensure_config(config)
2717
+ callback_manager = get_async_callback_manager_for_config(config)
2718
+ # We want to get the callback manager for the parent run.
2719
+ # This is a work-around for now to be able to dispatch adhoc events from
2720
+ # within a tool or a lambda and have the metadata events associated
2721
+ # with the parent run rather than have a new run id generated for each.
2722
+ if callback_manager.parent_run_id is None:
2723
+ msg = (
2724
+ "Unable to dispatch an adhoc event without a parent run id."
2725
+ "This function can only be called from within an existing run (e.g.,"
2726
+ "inside a tool or a RunnableLambda or a RunnableGenerator.)"
2727
+ "If you are doing that and still seeing this error, try explicitly"
2728
+ "passing the config parameter to this function."
2729
+ )
2730
+ raise RuntimeError(msg)
2731
+
2732
+ await callback_manager.on_custom_event(
2733
+ name,
2734
+ data,
2735
+ run_id=callback_manager.parent_run_id,
2736
+ )
2737
+
2738
+
2739
+ def dispatch_custom_event(
2740
+ name: str, data: Any, *, config: RunnableConfig | None = None
2741
+ ) -> None:
2742
+ """Dispatch an adhoc event.
2743
+
2744
+ Args:
2745
+ name: The name of the adhoc event.
2746
+ data: The data for the adhoc event.
2747
+
2748
+ Free form data. Ideally should be JSON serializable to avoid serialization
2749
+ issues downstream, but this is not enforced.
2750
+ config: Optional config object.
2751
+
2752
+ Mirrors the async API but not strictly needed.
2753
+
2754
+ Raises:
2755
+ RuntimeError: If there is no parent run ID available to associate the event
2756
+ with.
2757
+
2758
+ Example:
2759
+ ```python
2760
+ from coco_core.callbacks import BaseCallbackHandler
2761
+ from coco_core.callbacks import dispatch_custom_event
2762
+ from coco_core.runnable import RunnableLambda
2763
+
2764
+ class CustomCallbackManager(BaseCallbackHandler):
2765
+ def on_custom_event(
2766
+ self,
2767
+ name: str,
2768
+ data: Any,
2769
+ *,
2770
+ run_id: UUID,
2771
+ tags: list[str] | None = None,
2772
+ metadata: dict[str, Any] | None = None,
2773
+ **kwargs: Any,
2774
+ ) -> None:
2775
+ print(f"Received custom event: {name} with data: {data}")
2776
+
2777
+ def foo(inputs):
2778
+ dispatch_custom_event("my_event", {"bar": "buzz})
2779
+ return inputs
2780
+
2781
+ foo_ = RunnableLambda(foo)
2782
+ foo_.invoke({"a": "1"}, {"callbacks": [CustomCallbackManager()]})
2783
+ ```
2784
+ """
2785
+ # Import locally to prevent circular imports.
2786
+ from coco_core.runnables.config import ( # noqa: PLC0415
2787
+ ensure_config,
2788
+ get_callback_manager_for_config,
2789
+ )
2790
+
2791
+ config = ensure_config(config)
2792
+ callback_manager = get_callback_manager_for_config(config)
2793
+ # We want to get the callback manager for the parent run.
2794
+ # This is a work-around for now to be able to dispatch adhoc events from
2795
+ # within a tool or a lambda and have the metadata events associated
2796
+ # with the parent run rather than have a new run id generated for each.
2797
+ if callback_manager.parent_run_id is None:
2798
+ msg = (
2799
+ "Unable to dispatch an adhoc event without a parent run id."
2800
+ "This function can only be called from within an existing run (e.g.,"
2801
+ "inside a tool or a RunnableLambda or a RunnableGenerator.)"
2802
+ "If you are doing that and still seeing this error, try explicitly"
2803
+ "passing the config parameter to this function."
2804
+ )
2805
+ raise RuntimeError(msg)
2806
+ callback_manager.on_custom_event(
2807
+ name,
2808
+ data,
2809
+ run_id=callback_manager.parent_run_id,
2810
+ )
2811
+
2812
+
2813
+ @functools.lru_cache(maxsize=1)
2814
+ def _executor() -> ThreadPoolExecutor:
2815
+ # If the user is specifying ASYNC callback handlers to be run from a
2816
+ # SYNC context, and an event loop is already running,
2817
+ # we cannot submit the coroutine to the running loop, because it
2818
+ # would result in a deadlock. Instead we have to schedule them
2819
+ # on a background thread. To avoid creating & shutting down
2820
+ # a new executor every time, we use a lazily-created, shared
2821
+ # executor. If you're using regular langgchain parallelism (batch, etc.)
2822
+ # you'd only ever need 1 worker, but we permit more for now to reduce the chance
2823
+ # of slowdown if you are mixing with your own executor.
2824
+ cutie = ThreadPoolExecutor(max_workers=10)
2825
+ atexit.register(cutie.shutdown, wait=True)
2826
+ return cutie