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,279 @@
1
+ """A tracer that keeps runs in memory. It sends nothing anywhere.
2
+
3
+ Upstream's tracer at this path posted every run to its vendor's hosted service. Coco
4
+ keeps the class, so the callback manager, the graph runtime and user code that look
5
+ for it still work, and drops the network. The last finished root run stays on
6
+ `latest_run`, which is where the Coco receipt reads from.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from datetime import datetime, timezone
13
+ from typing import TYPE_CHECKING, Any, cast
14
+ from uuid import UUID
15
+
16
+ from typing_extensions import override
17
+
18
+ from coco_core.env import get_runtime_environment
19
+ from coco_core.load import dumpd
20
+ from coco_core.messages.ai import UsageMetadata, add_usage
21
+ from coco_core.tracers._compat import run_construct, run_to_dict
22
+ from coco_core.tracers._context import get_tracer_project, get_tracing_context
23
+ from coco_core.tracers.base import BaseTracer
24
+ from coco_core.tracers.schemas import Run
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Mapping
28
+
29
+ from coco_core.messages import BaseMessage
30
+ from coco_core.outputs import ChatGenerationChunk, GenerationChunk
31
+
32
+ logger = logging.getLogger(__name__)
33
+ _LOGGED: set[tuple[str, type[Exception]]] = set()
34
+
35
+ OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS: frozenset[str] = frozenset({"ls_agent_type"})
36
+ """Tracing metadata keys a nested caller may override. Every other key is first wins."""
37
+
38
+
39
+ def log_error_once(method: str, exception: Exception) -> None:
40
+ """Log an error once per method and exception type."""
41
+ if (method, type(exception)) in _LOGGED:
42
+ return
43
+ _LOGGED.add((method, type(exception)))
44
+ logger.error(exception)
45
+
46
+
47
+ def wait_for_all_tracers() -> None:
48
+ """Nothing is queued, so there is nothing to wait for."""
49
+
50
+
51
+ def _get_usage_metadata_from_generations(
52
+ generations: list[list[dict[str, Any]]],
53
+ ) -> UsageMetadata | None:
54
+ """Sum `usage_metadata` across serialized generations."""
55
+ output: UsageMetadata | None = None
56
+ for batch in generations:
57
+ for generation in batch:
58
+ if isinstance(generation, dict) and "message" in generation:
59
+ usage = _get_usage_metadata_from_message(generation["message"])
60
+ if usage is not None:
61
+ output = add_usage(output, usage)
62
+ return output
63
+
64
+
65
+ def _get_usage_metadata_from_message(message: Any) -> UsageMetadata | None:
66
+ if not isinstance(message, dict):
67
+ return None
68
+ kwargs = message.get("kwargs")
69
+ if isinstance(kwargs, dict) and isinstance(kwargs.get("usage_metadata"), dict):
70
+ return cast("UsageMetadata", kwargs["usage_metadata"])
71
+ return None
72
+
73
+
74
+ class CocoTracer(BaseTracer):
75
+ """Keeps each trace's runs in memory, with project, tags and metadata applied."""
76
+
77
+ run_inline = True
78
+
79
+ def __init__(
80
+ self,
81
+ example_id: UUID | str | None = None,
82
+ project_name: str | None = None,
83
+ client: Any | None = None,
84
+ tags: list[str] | None = None,
85
+ *,
86
+ metadata: Mapping[str, str] | None = None,
87
+ **kwargs: Any,
88
+ ) -> None:
89
+ """Create the tracer.
90
+
91
+ Args:
92
+ example_id: Attached to root runs as `reference_example_id`.
93
+ project_name: The project on each run. Defaults to `COCO_PROJECT`.
94
+ client: Accepted for signature compatibility and never called.
95
+ tags: Added to every run.
96
+ metadata: Added to a run's metadata where the run lacks the key.
97
+ **kwargs: Passed to `BaseTracer`.
98
+ """
99
+ super().__init__(**kwargs)
100
+ self.example_id = UUID(example_id) if isinstance(example_id, str) else example_id
101
+ self.project_name = project_name or get_tracer_project()
102
+ self.client = client
103
+ self.tags = tags or []
104
+ self.latest_run: Run | None = None
105
+ self.run_has_token_event_map: dict[str, bool] = {}
106
+ self.tracing_metadata: dict[str, str] | None = (
107
+ dict(metadata) if metadata is not None else None
108
+ )
109
+
110
+ def copy_with_metadata_defaults(
111
+ self,
112
+ *,
113
+ metadata: Mapping[str, str] | None = None,
114
+ tags: list[str] | None = None,
115
+ ) -> CocoTracer:
116
+ """Return a new tracer with merged tracer-only defaults."""
117
+ base = self.tracing_metadata
118
+ if metadata is None:
119
+ merged = dict(base) if base is not None else None
120
+ elif base is None:
121
+ merged = dict(metadata)
122
+ else:
123
+ merged = dict(base)
124
+ for key, value in metadata.items():
125
+ if key not in merged or key in OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS:
126
+ merged[key] = value
127
+ merged_tags = sorted(set(self.tags + tags)) if tags else self.tags
128
+ return self.__class__(
129
+ example_id=self.example_id,
130
+ project_name=self.project_name,
131
+ client=self.client,
132
+ tags=merged_tags,
133
+ metadata=merged,
134
+ run_map=self.run_map,
135
+ order_map=self.order_map,
136
+ _external_run_ids=self._external_run_ids,
137
+ )
138
+
139
+ def _start_trace(self, run: Run) -> None:
140
+ if self.project_name:
141
+ run.session_name = self.project_name
142
+ if self.tags is not None:
143
+ run.tags = sorted(set(run.tags + self.tags)) if run.tags else self.tags.copy()
144
+ super()._start_trace(run)
145
+ if get_tracing_context().get("enabled") is False:
146
+ run.extra["__disabled"] = True
147
+
148
+ def on_chat_model_start(
149
+ self,
150
+ serialized: dict[str, Any],
151
+ messages: list[list[BaseMessage]],
152
+ *,
153
+ run_id: UUID,
154
+ tags: list[str] | None = None,
155
+ parent_run_id: UUID | None = None,
156
+ metadata: dict[str, Any] | None = None,
157
+ name: str | None = None,
158
+ **kwargs: Any,
159
+ ) -> Run:
160
+ """Start a run for a chat model call, recorded as an LLM run."""
161
+ start_time = datetime.now(timezone.utc)
162
+ if metadata:
163
+ kwargs.update({"metadata": metadata})
164
+ run = Run(
165
+ id=run_id,
166
+ parent_run_id=parent_run_id,
167
+ serialized=serialized,
168
+ inputs={"messages": [[dumpd(msg) for msg in batch] for batch in messages]},
169
+ extra=kwargs,
170
+ events=[{"name": "start", "time": start_time}],
171
+ start_time=start_time,
172
+ run_type="llm",
173
+ tags=tags,
174
+ name=name,
175
+ )
176
+ self._start_trace(run)
177
+ self._on_chat_model_start(run)
178
+ return run
179
+
180
+ def _persist_run(self, run: Run) -> None:
181
+ # Keep the finished root without its whole child tree.
182
+ data = run_to_dict(run, exclude={"child_runs", "inputs", "outputs"})
183
+ self.latest_run = run_construct(**data, inputs=run.inputs, outputs=run.outputs)
184
+
185
+ def get_run_url(self) -> str:
186
+ """Traces stay in this process, so there is no URL to give.
187
+
188
+ Raises:
189
+ ValueError: Always.
190
+ """
191
+ msg = "CocoTracer keeps runs in memory; there is no run URL."
192
+ raise ValueError(msg)
193
+
194
+ def _get_tags(self, run: Run) -> list[str]:
195
+ tags = set(run.tags or [])
196
+ tags.update(self.tags or [])
197
+ return list(tags)
198
+
199
+ def _record(self, run: Run) -> None:
200
+ if run.extra.get("__disabled"):
201
+ return
202
+ run.extra["runtime"] = get_runtime_environment()
203
+ run.tags = self._get_tags(run)
204
+ _patch_missing_metadata(self, run)
205
+
206
+ def _on_llm_start(self, run: Run) -> None:
207
+ if run.parent_run_id is None:
208
+ run.reference_example_id = self.example_id
209
+ self._record(run)
210
+
211
+ @override
212
+ def _llm_run_with_token_event(
213
+ self,
214
+ token: str | list[str | dict[str, Any]],
215
+ run_id: UUID,
216
+ chunk: GenerationChunk | ChatGenerationChunk | None = None,
217
+ parent_run_id: UUID | None = None,
218
+ ) -> Run:
219
+ run_id_str = str(run_id)
220
+ if run_id_str not in self.run_has_token_event_map:
221
+ self.run_has_token_event_map[run_id_str] = True
222
+ else:
223
+ return self._get_run(run_id, run_type={"llm", "chat_model"})
224
+ return super()._llm_run_with_token_event(
225
+ token, run_id, chunk=None, parent_run_id=parent_run_id
226
+ )
227
+
228
+ def _on_chat_model_start(self, run: Run) -> None:
229
+ if run.parent_run_id is None:
230
+ run.reference_example_id = self.example_id
231
+ self._record(run)
232
+
233
+ def _on_llm_end(self, run: Run) -> None:
234
+ if run.outputs and "generations" in run.outputs:
235
+ usage = _get_usage_metadata_from_generations(run.outputs["generations"])
236
+ if usage is not None:
237
+ run.extra.setdefault("metadata", {})["usage_metadata"] = usage
238
+
239
+ def _on_chain_start(self, run: Run) -> None:
240
+ if run.parent_run_id is None:
241
+ run.reference_example_id = self.example_id
242
+ if not run.extra.get("defers_inputs"):
243
+ self._record(run)
244
+
245
+ def _on_chain_end(self, run: Run) -> None:
246
+ if run.extra.get("defers_inputs"):
247
+ self._record(run)
248
+
249
+ def _on_chain_error(self, run: Run) -> None:
250
+ if run.extra.get("defers_inputs"):
251
+ self._record(run)
252
+
253
+ def _on_tool_start(self, run: Run) -> None:
254
+ if run.parent_run_id is None:
255
+ run.reference_example_id = self.example_id
256
+ self._record(run)
257
+
258
+ def _on_retriever_start(self, run: Run) -> None:
259
+ if run.parent_run_id is None:
260
+ run.reference_example_id = self.example_id
261
+ self._record(run)
262
+
263
+ def wait_for_futures(self) -> None:
264
+ """Nothing is queued, so there is nothing to wait for."""
265
+
266
+
267
+ def _patch_missing_metadata(self: CocoTracer, run: Run) -> None:
268
+ if not self.tracing_metadata:
269
+ return
270
+ metadata = run.metadata
271
+ patched = None
272
+ for k, v in self.tracing_metadata.items():
273
+ if k not in metadata or k in OVERRIDABLE_LANGSMITH_INHERITABLE_METADATA_KEYS:
274
+ if k in metadata and metadata[k] == v:
275
+ continue
276
+ if patched is None:
277
+ patched = {**metadata}
278
+ run.extra["metadata"] = patched
279
+ patched[k] = v
@@ -0,0 +1,205 @@
1
+ """Context management for tracers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import contextmanager
6
+ from contextvars import ContextVar
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ Literal,
11
+ cast,
12
+ )
13
+ from uuid import UUID
14
+
15
+ from coco_core.tracers import _context as ls_rh
16
+ from coco_core.tracers import _context as ls_utils
17
+
18
+ from coco_core.tracers.coco import CocoTracer
19
+ from coco_core.tracers.run_collector import RunCollectorCallbackHandler
20
+
21
+ if TYPE_CHECKING:
22
+ from collections.abc import Generator
23
+
24
+ from coco_core.tracers._context import Client as LangSmithClient
25
+
26
+ from coco_core.callbacks.base import BaseCallbackHandler, Callbacks
27
+ from coco_core.callbacks.manager import AsyncCallbackManager, CallbackManager
28
+
29
+ # for backwards partial compatibility if this is imported by users but unused
30
+ tracing_callback_var: Any = None
31
+ tracing_v2_callback_var: ContextVar[CocoTracer | None] = ContextVar(
32
+ "tracing_callback_v2", default=None
33
+ )
34
+ run_collector_var: ContextVar[RunCollectorCallbackHandler | None] = ContextVar(
35
+ "run_collector", default=None
36
+ )
37
+
38
+
39
+ @contextmanager
40
+ def tracing_v2_enabled(
41
+ project_name: str | None = None,
42
+ *,
43
+ example_id: str | UUID | None = None,
44
+ tags: list[str] | None = None,
45
+ client: LangSmithClient | None = None,
46
+ ) -> Generator[CocoTracer, None, None]:
47
+ """Instruct Coco to log all runs in context to LangSmith.
48
+
49
+ Args:
50
+ project_name: The name of the project.
51
+
52
+ Defaults to `'default'`.
53
+ example_id: The ID of the example.
54
+ tags: The tags to add to the run.
55
+ client: The client of the langsmith.
56
+
57
+ Yields:
58
+ The Coco tracer.
59
+
60
+ Example:
61
+ >>> with tracing_v2_enabled():
62
+ ... # Coco code will automatically be traced
63
+
64
+ You can use this to fetch the LangSmith run URL:
65
+
66
+ >>> with tracing_v2_enabled() as cb:
67
+ ... chain.invoke("foo")
68
+ ... run_url = cb.get_run_url()
69
+ """
70
+ if isinstance(example_id, str):
71
+ example_id = UUID(example_id)
72
+ cb = CocoTracer(
73
+ example_id=example_id,
74
+ project_name=project_name,
75
+ tags=tags,
76
+ client=client,
77
+ )
78
+ token = tracing_v2_callback_var.set(cb)
79
+ try:
80
+ yield cb
81
+ finally:
82
+ tracing_v2_callback_var.reset(token)
83
+
84
+
85
+ @contextmanager
86
+ def collect_runs() -> Generator[RunCollectorCallbackHandler, None, None]:
87
+ """Collect all run traces in context.
88
+
89
+ Yields:
90
+ The run collector callback handler.
91
+
92
+ Example:
93
+ >>> with collect_runs() as runs_cb:
94
+ chain.invoke("foo")
95
+ run_id = runs_cb.traced_runs[0].id
96
+ """
97
+ cb = RunCollectorCallbackHandler()
98
+ token = run_collector_var.set(cb)
99
+ try:
100
+ yield cb
101
+ finally:
102
+ run_collector_var.reset(token)
103
+
104
+
105
+ def _get_trace_callbacks(
106
+ project_name: str | None = None,
107
+ example_id: str | UUID | None = None,
108
+ callback_manager: CallbackManager | AsyncCallbackManager | None = None,
109
+ ) -> Callbacks:
110
+ if _tracing_v2_is_enabled():
111
+ project_name_ = project_name or _get_tracer_project()
112
+ tracer = tracing_v2_callback_var.get() or CocoTracer(
113
+ project_name=project_name_,
114
+ example_id=example_id,
115
+ )
116
+ if callback_manager is None:
117
+ cb = cast("Callbacks", [tracer])
118
+ else:
119
+ if not any(
120
+ isinstance(handler, CocoTracer)
121
+ for handler in callback_manager.handlers
122
+ ):
123
+ callback_manager.add_handler(tracer)
124
+ # If it already has a CocoTracer, we don't need to add another one.
125
+ # this would likely mess up the trace hierarchy.
126
+ cb = callback_manager
127
+ else:
128
+ cb = None
129
+ return cb
130
+
131
+
132
+ def _tracing_v2_is_enabled() -> bool | Literal["local"]:
133
+ if tracing_v2_callback_var.get() is not None:
134
+ return True
135
+ return ls_utils.tracing_is_enabled()
136
+
137
+
138
+ def _get_tracer_project() -> str:
139
+ tracing_context = ls_rh.get_tracing_context()
140
+ run_tree = tracing_context["parent"]
141
+ if run_tree is None and tracing_context["project_name"] is not None:
142
+ return cast("str", tracing_context["project_name"])
143
+ return getattr(
144
+ run_tree,
145
+ "session_name",
146
+ getattr(
147
+ # Note, if people are trying to nest @traceable functions and the
148
+ # tracing_v2_enabled context manager, this will likely mess up the
149
+ # tree structure.
150
+ tracing_v2_callback_var.get(),
151
+ "project",
152
+ # Have to set this to a string even though it always will return
153
+ # a string because `get_tracer_project` technically can return
154
+ # None, but only when a specific argument is supplied.
155
+ # Therefore, this just tricks the mypy type checker
156
+ str(ls_utils.get_tracer_project()),
157
+ ),
158
+ )
159
+
160
+
161
+ _configure_hooks: list[
162
+ tuple[
163
+ ContextVar[BaseCallbackHandler | None],
164
+ bool,
165
+ type[BaseCallbackHandler] | None,
166
+ str | None,
167
+ ]
168
+ ] = []
169
+
170
+
171
+ def register_configure_hook(
172
+ context_var: ContextVar[Any | None],
173
+ inheritable: bool, # noqa: FBT001
174
+ handle_class: type[BaseCallbackHandler] | None = None,
175
+ env_var: str | None = None,
176
+ ) -> None:
177
+ """Register a configure hook.
178
+
179
+ Args:
180
+ context_var: The context variable.
181
+ inheritable: Whether the context variable is inheritable.
182
+ handle_class: The callback handler class.
183
+ env_var: The environment variable.
184
+
185
+ Raises:
186
+ ValueError: If `env_var` is set, `handle_class` must also be set to a non-`None`
187
+ value.
188
+ """
189
+ if env_var is not None and handle_class is None:
190
+ msg = "If env_var is set, handle_class must also be set to a non-None value."
191
+ raise ValueError(msg)
192
+
193
+ _configure_hooks.append(
194
+ (
195
+ # the typings of ContextVar do not have the generic arg set as covariant
196
+ # so we have to cast it
197
+ cast("ContextVar[BaseCallbackHandler | None]", context_var),
198
+ inheritable,
199
+ handle_class,
200
+ env_var,
201
+ )
202
+ )
203
+
204
+
205
+ register_configure_hook(run_collector_var, inheritable=False)