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,61 @@
1
+ """Usage utilities."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+
7
+ def _dict_int_op(
8
+ left: dict[str, Any],
9
+ right: dict[str, Any],
10
+ op: Callable[[int, int], int],
11
+ *,
12
+ default: int = 0,
13
+ depth: int = 0,
14
+ max_depth: int = 100,
15
+ ) -> dict[str, Any]:
16
+ """Apply an integer operation to corresponding values in two dictionaries.
17
+
18
+ Recursively combines two dictionaries by applying the given operation to integer
19
+ values at matching keys.
20
+
21
+ Supports nested dictionaries.
22
+
23
+ Args:
24
+ left: First dictionary to combine.
25
+ right: Second dictionary to combine.
26
+ op: Binary operation function to apply to integer values.
27
+ default: Default value to use when a key is missing from a dictionary.
28
+ depth: Current recursion depth (used internally).
29
+ max_depth: Maximum recursion depth (to prevent infinite loops).
30
+
31
+ Returns:
32
+ A new dictionary with combined values.
33
+
34
+ Raises:
35
+ ValueError: If `max_depth` is exceeded or if value types are not supported.
36
+ """
37
+ if depth >= max_depth:
38
+ msg = f"{max_depth=} exceeded, unable to combine dicts."
39
+ raise ValueError(msg)
40
+ combined: dict[str, Any] = {}
41
+ for k in set(left).union(right):
42
+ if isinstance(left.get(k, default), int) and isinstance(
43
+ right.get(k, default), int
44
+ ):
45
+ combined[k] = op(left.get(k, default), right.get(k, default))
46
+ elif isinstance(left.get(k, {}), dict) and isinstance(right.get(k, {}), dict):
47
+ combined[k] = _dict_int_op(
48
+ left.get(k, {}),
49
+ right.get(k, {}),
50
+ op,
51
+ default=default,
52
+ depth=depth + 1,
53
+ max_depth=max_depth,
54
+ )
55
+ else:
56
+ types = [type(d[k]) for d in (left, right) if k in d]
57
+ msg = (
58
+ f"Unknown value types: {types}. Only dict and int values are supported."
59
+ )
60
+ raise ValueError(msg) # noqa: TRY004
61
+ return combined
@@ -0,0 +1,528 @@
1
+ """Generic utility functions."""
2
+
3
+ import contextlib
4
+ import datetime
5
+ import functools
6
+ import importlib
7
+ import os
8
+ import warnings
9
+ from collections.abc import Callable, Iterator, Sequence
10
+ from importlib.metadata import version
11
+ from typing import Any, overload
12
+ from uuid import uuid4
13
+
14
+ from packaging.version import parse
15
+ from pydantic import SecretStr
16
+ from requests import HTTPError, Response
17
+ from typing_extensions import override
18
+
19
+ from coco_core.utils.pydantic import (
20
+ is_pydantic_v1_subclass,
21
+ )
22
+
23
+
24
+ def xor_args(*arg_groups: tuple[str, ...]) -> Callable[..., Any]:
25
+ """Validate specified keyword args are mutually exclusive.
26
+
27
+ Args:
28
+ *arg_groups: Groups of mutually exclusive keyword args.
29
+
30
+ Returns:
31
+ Decorator that validates the specified keyword args are mutually exclusive.
32
+ """
33
+
34
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
35
+ @functools.wraps(func)
36
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
37
+ """Validate exactly one arg in each group is not None."""
38
+ counts = [
39
+ sum(1 for arg in arg_group if kwargs.get(arg) is not None)
40
+ for arg_group in arg_groups
41
+ ]
42
+ invalid_groups = [i for i, count in enumerate(counts) if count != 1]
43
+ if invalid_groups:
44
+ invalid_group_names = [", ".join(arg_groups[i]) for i in invalid_groups]
45
+ msg = (
46
+ "Exactly one argument in each of the following"
47
+ " groups must be defined:"
48
+ f" {', '.join(invalid_group_names)}"
49
+ )
50
+ raise ValueError(msg)
51
+ return func(*args, **kwargs)
52
+
53
+ return wrapper
54
+
55
+ return decorator
56
+
57
+
58
+ def raise_for_status_with_text(response: Response) -> None:
59
+ """Raise an error with the response text.
60
+
61
+ Args:
62
+ response: The response to check for errors.
63
+
64
+ Raises:
65
+ ValueError: If the response has an error status code.
66
+ """
67
+ try:
68
+ response.raise_for_status()
69
+ except HTTPError as e:
70
+ raise ValueError(response.text) from e
71
+
72
+
73
+ @contextlib.contextmanager
74
+ def mock_now(dt_value: datetime.datetime) -> Iterator[type]:
75
+ """Context manager for mocking out datetime.now() in unit tests.
76
+
77
+ Args:
78
+ dt_value: The datetime value to use for datetime.now().
79
+
80
+ Yields:
81
+ The mocked datetime class.
82
+
83
+ Example:
84
+ ```python
85
+ with mock_now(datetime.datetime(2011, 2, 3, 10, 11)):
86
+ assert datetime.datetime.now() == datetime.datetime(2011, 2, 3, 10, 11)
87
+ ```
88
+ """
89
+
90
+ class MockDateTime(datetime.datetime):
91
+ """Mock datetime.datetime.now() with a fixed datetime."""
92
+
93
+ @classmethod
94
+ @override
95
+ def now(cls, tz: datetime.tzinfo | None = None) -> "MockDateTime":
96
+ # Create a copy of dt_value.
97
+ return MockDateTime(
98
+ dt_value.year,
99
+ dt_value.month,
100
+ dt_value.day,
101
+ dt_value.hour,
102
+ dt_value.minute,
103
+ dt_value.second,
104
+ dt_value.microsecond,
105
+ dt_value.tzinfo,
106
+ )
107
+
108
+ real_datetime = datetime.datetime
109
+ datetime.datetime = MockDateTime # type: ignore[misc]
110
+ try:
111
+ yield datetime.datetime
112
+ finally:
113
+ datetime.datetime = real_datetime # type: ignore[misc]
114
+
115
+
116
+ def guard_import(
117
+ module_name: str, *, pip_name: str | None = None, package: str | None = None
118
+ ) -> Any:
119
+ """Dynamically import a module.
120
+
121
+ Raise an exception if the module is not installed.
122
+
123
+ Args:
124
+ module_name: The name of the module to import.
125
+ pip_name: The name of the module to install with pip.
126
+ package: The package to import the module from.
127
+
128
+ Returns:
129
+ The imported module.
130
+
131
+ Raises:
132
+ ImportError: If the module is not installed.
133
+ """
134
+ try:
135
+ module = importlib.import_module(module_name, package)
136
+ except (ImportError, ModuleNotFoundError) as e:
137
+ top_level = module_name.split(".", maxsplit=1)[0]
138
+ # Coco modules ship in trustcoco distributions: coco_core in
139
+ # trustcoco-core, coco in trustcoco. Any other module keeps the
140
+ # underscore to hyphen rule.
141
+ default = top_level.replace("_", "-")
142
+ if top_level == "coco" or top_level.startswith("coco_"):
143
+ default = "trust" + default
144
+ pip_name = pip_name or default
145
+ msg = (
146
+ f"Could not import {module_name} python package. "
147
+ f"Please install it with `pip install {pip_name}`."
148
+ )
149
+ raise ImportError(msg) from e
150
+ return module
151
+
152
+
153
+ def check_package_version(
154
+ package: str,
155
+ lt_version: str | None = None,
156
+ lte_version: str | None = None,
157
+ gt_version: str | None = None,
158
+ gte_version: str | None = None,
159
+ ) -> None:
160
+ """Check the version of a package.
161
+
162
+ Args:
163
+ package: The name of the package.
164
+ lt_version: The version must be less than this.
165
+ lte_version: The version must be less than or equal to this.
166
+ gt_version: The version must be greater than this.
167
+ gte_version: The version must be greater than or equal to this.
168
+
169
+
170
+ Raises:
171
+ ValueError: If the package version does not meet the requirements.
172
+ """
173
+ imported_version = parse(version(package))
174
+ if lt_version is not None and imported_version >= parse(lt_version):
175
+ msg = (
176
+ f"Expected {package} version to be < {lt_version}. Received "
177
+ f"{imported_version}."
178
+ )
179
+ raise ValueError(msg)
180
+ if lte_version is not None and imported_version > parse(lte_version):
181
+ msg = (
182
+ f"Expected {package} version to be <= {lte_version}. Received "
183
+ f"{imported_version}."
184
+ )
185
+ raise ValueError(msg)
186
+ if gt_version is not None and imported_version <= parse(gt_version):
187
+ msg = (
188
+ f"Expected {package} version to be > {gt_version}. Received "
189
+ f"{imported_version}."
190
+ )
191
+ raise ValueError(msg)
192
+ if gte_version is not None and imported_version < parse(gte_version):
193
+ msg = (
194
+ f"Expected {package} version to be >= {gte_version}. Received "
195
+ f"{imported_version}."
196
+ )
197
+ raise ValueError(msg)
198
+
199
+
200
+ def get_pydantic_field_names(pydantic_cls: Any) -> set[str]:
201
+ """Get field names, including aliases, for a pydantic class.
202
+
203
+ Args:
204
+ pydantic_cls: Pydantic class.
205
+
206
+ Returns:
207
+ Field names.
208
+ """
209
+ all_required_field_names = set()
210
+ if is_pydantic_v1_subclass(pydantic_cls):
211
+ for field in pydantic_cls.__fields__.values():
212
+ all_required_field_names.add(field.name)
213
+ if field.has_alias:
214
+ all_required_field_names.add(field.alias)
215
+ else: # Assuming pydantic 2 for now
216
+ for name, field in pydantic_cls.model_fields.items():
217
+ all_required_field_names.add(name)
218
+ if field.alias:
219
+ all_required_field_names.add(field.alias)
220
+ return all_required_field_names
221
+
222
+
223
+ def _build_model_kwargs(
224
+ values: dict[str, Any],
225
+ all_required_field_names: set[str],
226
+ ) -> dict[str, Any]:
227
+ """Build `model_kwargs` param from Pydantic constructor values.
228
+
229
+ Args:
230
+ values: All init args passed in by user.
231
+ all_required_field_names: All required field names for the pydantic class.
232
+
233
+ Returns:
234
+ Extra kwargs.
235
+
236
+ Raises:
237
+ ValueError: If a field is specified in both `values` and `extra_kwargs`.
238
+ ValueError: If a field is specified in `model_kwargs`.
239
+ """
240
+ extra_kwargs = values.get("model_kwargs", {})
241
+ for field_name in list(values):
242
+ if field_name in extra_kwargs:
243
+ msg = f"Found {field_name} supplied twice."
244
+ raise ValueError(msg)
245
+ if field_name not in all_required_field_names:
246
+ warnings.warn(
247
+ f"""WARNING! {field_name} is not default parameter.
248
+ {field_name} was transferred to model_kwargs.
249
+ Please confirm that {field_name} is what you intended.""",
250
+ stacklevel=7,
251
+ )
252
+ extra_kwargs[field_name] = values.pop(field_name)
253
+
254
+ invalid_model_kwargs = all_required_field_names.intersection(extra_kwargs.keys())
255
+ if invalid_model_kwargs:
256
+ warnings.warn(
257
+ f"Parameters {invalid_model_kwargs} should be specified explicitly. "
258
+ f"Instead they were passed in as part of `model_kwargs` parameter.",
259
+ stacklevel=7,
260
+ )
261
+ for k in invalid_model_kwargs:
262
+ values[k] = extra_kwargs.pop(k)
263
+
264
+ values["model_kwargs"] = extra_kwargs
265
+ return values
266
+
267
+
268
+ # DON'T USE! Kept for backwards-compatibility but should never have been public.
269
+ def build_extra_kwargs(
270
+ extra_kwargs: dict[str, Any],
271
+ values: dict[str, Any],
272
+ all_required_field_names: set[str],
273
+ ) -> dict[str, Any]:
274
+ """Build extra kwargs from values and extra_kwargs.
275
+
276
+ !!! danger "DON'T USE"
277
+
278
+ Kept for backwards-compatibility but should never have been public. Use the
279
+ internal `_build_model_kwargs` function instead.
280
+
281
+ Args:
282
+ extra_kwargs: Extra kwargs passed in by user.
283
+ values: Values passed in by user.
284
+ all_required_field_names: All required field names for the pydantic class.
285
+
286
+ Returns:
287
+ Extra kwargs.
288
+
289
+ Raises:
290
+ ValueError: If a field is specified in both `values` and `extra_kwargs`.
291
+ ValueError: If a field is specified in `model_kwargs`.
292
+ """
293
+ # DON'T USE! Kept for backwards-compatibility but should never have been public.
294
+ for field_name in list(values):
295
+ if field_name in extra_kwargs:
296
+ msg = f"Found {field_name} supplied twice."
297
+ raise ValueError(msg)
298
+ if field_name not in all_required_field_names:
299
+ warnings.warn(
300
+ f"""WARNING! {field_name} is not default parameter.
301
+ {field_name} was transferred to model_kwargs.
302
+ Please confirm that {field_name} is what you intended.""",
303
+ stacklevel=7,
304
+ )
305
+ extra_kwargs[field_name] = values.pop(field_name)
306
+
307
+ # DON'T USE! Kept for backwards-compatibility but should never have been public.
308
+ invalid_model_kwargs = all_required_field_names.intersection(extra_kwargs.keys())
309
+ if invalid_model_kwargs:
310
+ msg = (
311
+ f"Parameters {invalid_model_kwargs} should be specified explicitly. "
312
+ f"Instead they were passed in as part of `model_kwargs` parameter."
313
+ )
314
+ raise ValueError(msg)
315
+
316
+ # DON'T USE! Kept for backwards-compatibility but should never have been public.
317
+ return extra_kwargs
318
+
319
+
320
+ def convert_to_secret_str(value: SecretStr | str) -> SecretStr:
321
+ """Convert a string to a `SecretStr` if needed.
322
+
323
+ Args:
324
+ value: The value to convert.
325
+
326
+ Returns:
327
+ The `SecretStr` value.
328
+ """
329
+ if isinstance(value, SecretStr):
330
+ return value
331
+ return SecretStr(value)
332
+
333
+
334
+ class _NoDefaultType:
335
+ """Type to indicate no default value is provided."""
336
+
337
+
338
+ _NoDefault = _NoDefaultType()
339
+
340
+
341
+ @overload
342
+ def from_env(key: str, /) -> Callable[[], str]: ...
343
+
344
+
345
+ @overload
346
+ def from_env(key: str, /, *, default: str) -> Callable[[], str]: ...
347
+
348
+
349
+ @overload
350
+ def from_env(key: Sequence[str], /, *, default: str) -> Callable[[], str]: ...
351
+
352
+
353
+ @overload
354
+ def from_env(key: str, /, *, error_message: str) -> Callable[[], str]: ...
355
+
356
+
357
+ @overload
358
+ def from_env(
359
+ key: str | Sequence[str], /, *, default: str, error_message: str | None
360
+ ) -> Callable[[], str]: ...
361
+
362
+
363
+ @overload
364
+ def from_env(
365
+ key: str, /, *, default: None, error_message: str | None
366
+ ) -> Callable[[], str | None]: ...
367
+
368
+
369
+ @overload
370
+ def from_env(
371
+ key: str | Sequence[str], /, *, default: None
372
+ ) -> Callable[[], str | None]: ...
373
+
374
+
375
+ def from_env(
376
+ key: str | Sequence[str],
377
+ /,
378
+ *,
379
+ default: str | _NoDefaultType | None = _NoDefault,
380
+ error_message: str | None = None,
381
+ ) -> Callable[[], str] | Callable[[], str | None]:
382
+ """Create a factory method that gets a value from an environment variable.
383
+
384
+ Args:
385
+ key: The environment variable to look up.
386
+
387
+ If a list of keys is provided, the first key found in the environment will
388
+ be used. If no key is found, the default value will be used if set,
389
+ otherwise an error will be raised.
390
+ default: The default value to return if the environment variable is not set.
391
+ error_message: The error message which will be raised if the key is not found
392
+ and no default value is provided.
393
+
394
+ This will be raised as a ValueError.
395
+
396
+ Returns:
397
+ Factory method that will look up the value from the environment.
398
+ """
399
+
400
+ def get_from_env_fn() -> str | None:
401
+ """Get a value from an environment variable.
402
+
403
+ Raises:
404
+ ValueError: If the environment variable is not set and no default is
405
+ provided.
406
+
407
+ Returns:
408
+ The value from the environment.
409
+ """
410
+ if isinstance(key, (list, tuple)):
411
+ for k in key:
412
+ if k in os.environ:
413
+ return os.environ[k]
414
+ if isinstance(key, str) and key in os.environ:
415
+ return os.environ[key]
416
+
417
+ if isinstance(default, (str, type(None))):
418
+ return default
419
+ if error_message:
420
+ raise ValueError(error_message)
421
+ msg = (
422
+ f"Did not find {key}, please add an environment variable"
423
+ f" `{key}` which contains it, or pass"
424
+ f" `{key}` as a named parameter."
425
+ )
426
+ raise ValueError(msg)
427
+
428
+ return get_from_env_fn
429
+
430
+
431
+ @overload
432
+ def secret_from_env(key: str | Sequence[str], /) -> Callable[[], SecretStr]: ...
433
+
434
+
435
+ @overload
436
+ def secret_from_env(key: str, /, *, default: str) -> Callable[[], SecretStr]: ...
437
+
438
+
439
+ @overload
440
+ def secret_from_env(
441
+ key: str | Sequence[str], /, *, default: None
442
+ ) -> Callable[[], SecretStr | None]: ...
443
+
444
+
445
+ @overload
446
+ def secret_from_env(key: str, /, *, error_message: str) -> Callable[[], SecretStr]: ...
447
+
448
+
449
+ def secret_from_env(
450
+ key: str | Sequence[str],
451
+ /,
452
+ *,
453
+ default: str | _NoDefaultType | None = _NoDefault,
454
+ error_message: str | None = None,
455
+ ) -> Callable[[], SecretStr | None] | Callable[[], SecretStr]:
456
+ """Secret from env.
457
+
458
+ Args:
459
+ key: The environment variable to look up.
460
+ default: The default value to return if the environment variable is not set.
461
+ error_message: The error message which will be raised if the key is not found
462
+ and no default value is provided.
463
+
464
+ This will be raised as a `ValueError`.
465
+
466
+ Returns:
467
+ Factory method that will look up the secret from the environment.
468
+ """
469
+
470
+ def get_secret_from_env() -> SecretStr | None:
471
+ """Get a value from an environment variable.
472
+
473
+ Raises:
474
+ ValueError: If the environment variable is not set and no default is
475
+ provided.
476
+
477
+ Returns:
478
+ The secret from the environment.
479
+ """
480
+ if isinstance(key, (list, tuple)):
481
+ for k in key:
482
+ if k in os.environ:
483
+ return SecretStr(os.environ[k])
484
+ if isinstance(key, str) and key in os.environ:
485
+ return SecretStr(os.environ[key])
486
+ if isinstance(default, str):
487
+ return SecretStr(default)
488
+ if default is None:
489
+ return None
490
+ if error_message:
491
+ raise ValueError(error_message)
492
+ msg = (
493
+ f"Did not find {key}, please add an environment variable"
494
+ f" `{key}` which contains it, or pass"
495
+ f" `{key}` as a named parameter."
496
+ )
497
+ raise ValueError(msg)
498
+
499
+ return get_secret_from_env
500
+
501
+
502
+ LC_AUTO_PREFIX = "lc_"
503
+ """Coco auto-generated ID prefix for messages and content blocks."""
504
+
505
+ LC_ID_PREFIX = "lc_run-"
506
+ """Internal tracing/callback system identifier.
507
+
508
+ Used for:
509
+
510
+ - Tracing. Every Coco operation (LLM call, chain execution, tool use, etc.)
511
+ gets a unique run_id (UUID)
512
+ - Enables tracking parent-child relationships between operations
513
+ """
514
+
515
+
516
+ def ensure_id(id_val: str | None) -> str:
517
+ """Ensure the ID is a valid string, generating a new UUID if not provided.
518
+
519
+ Auto-generated UUIDs are prefixed by `'lc_'` to indicate they are
520
+ Coco-generated IDs.
521
+
522
+ Args:
523
+ id_val: Optional string ID value to validate.
524
+
525
+ Returns:
526
+ A string ID, either the validated provided value or a newly generated UUID4.
527
+ """
528
+ return id_val or f"{LC_AUTO_PREFIX}{uuid4()}"
@@ -0,0 +1,57 @@
1
+ """UUID utility functions.
2
+
3
+ This module exports a uuid7 function to generate monotonic, time-ordered UUIDs
4
+ for tracing and similar operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import typing
10
+ from uuid import UUID
11
+
12
+ from uuid_utils.compat import uuid7 as _uuid_utils_uuid7
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from uuid import UUID
16
+
17
+ _NANOS_PER_SECOND: typing.Final = 1_000_000_000
18
+
19
+
20
+ def _to_timestamp_and_nanos(nanoseconds: int) -> tuple[int, int]:
21
+ """Split a nanosecond timestamp into seconds and remaining nanoseconds."""
22
+ seconds, nanos = divmod(nanoseconds, _NANOS_PER_SECOND)
23
+ return seconds, nanos
24
+
25
+
26
+ def uuid7(nanoseconds: int | None = None) -> UUID:
27
+ """Generate a UUID from a Unix timestamp in nanoseconds and random bits.
28
+
29
+ UUIDv7 objects feature monotonicity within a millisecond.
30
+
31
+ Args:
32
+ nanoseconds: Optional ns timestamp. If not provided, uses current time.
33
+
34
+ Returns:
35
+ A UUIDv7 object.
36
+ """
37
+ # --- 48 --- -- 4 -- --- 12 --- -- 2 -- --- 30 --- - 32 -
38
+ # unix_ts_ms | version | counter_hi | variant | counter_lo | random
39
+ #
40
+ # 'counter = counter_hi | counter_lo' is a 42-bit counter constructed
41
+ # with Method 1 of RFC 9562, §6.2, and its MSB is set to 0.
42
+ #
43
+ # 'random' is a 32-bit random value regenerated for every new UUID.
44
+ #
45
+ # If multiple UUIDs are generated within the same millisecond, the LSB
46
+ # of 'counter' is incremented by 1. When overflowing, the timestamp is
47
+ # advanced and the counter is reset to a random 42-bit integer with MSB
48
+ # set to 0.
49
+
50
+ # For now, just delegate to the uuid_utils implementation
51
+ if nanoseconds is None:
52
+ return _uuid_utils_uuid7()
53
+ seconds, nanos = _to_timestamp_and_nanos(nanoseconds)
54
+ return _uuid_utils_uuid7(timestamp=seconds, nanos=nanos)
55
+
56
+
57
+ __all__ = ["uuid7"]
@@ -0,0 +1,53 @@
1
+ """Vector stores."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from coco_core._import_utils import import_attr
6
+
7
+ if TYPE_CHECKING:
8
+ from coco_core.vectorstores.base import VST, VectorStore, VectorStoreRetriever
9
+ from coco_core.vectorstores.in_memory import InMemoryVectorStore
10
+
11
+ __all__ = (
12
+ "VST",
13
+ "InMemoryVectorStore",
14
+ "VectorStore",
15
+ "VectorStoreRetriever",
16
+ )
17
+
18
+ _dynamic_imports = {
19
+ "VectorStore": "base",
20
+ "VST": "base",
21
+ "VectorStoreRetriever": "base",
22
+ "InMemoryVectorStore": "in_memory",
23
+ }
24
+
25
+
26
+ def __getattr__(attr_name: str) -> object:
27
+ """Dynamically import and return an attribute from a submodule.
28
+
29
+ This function enables lazy loading of vectorstore classes from submodules, reducing
30
+ initial import time and circular dependency issues.
31
+
32
+ Args:
33
+ attr_name: Name of the attribute to import.
34
+
35
+ Returns:
36
+ The imported attribute object.
37
+
38
+ Raises:
39
+ AttributeError: If the attribute is not found in `_dynamic_imports`.
40
+ """
41
+ module_name = _dynamic_imports.get(attr_name)
42
+ result = import_attr(attr_name, module_name, __spec__.parent)
43
+ globals()[attr_name] = result
44
+ return result
45
+
46
+
47
+ def __dir__() -> list[str]:
48
+ """Return a list of available attributes for this module.
49
+
50
+ Returns:
51
+ List of attribute names that can be imported from this module.
52
+ """
53
+ return list(__all__)