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,472 @@
1
+ """Types for setting agent response formats."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import uuid
7
+ from dataclasses import dataclass, is_dataclass
8
+ from types import UnionType
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Generic,
13
+ Literal,
14
+ TypeVar,
15
+ Union,
16
+ get_args,
17
+ get_origin,
18
+ )
19
+
20
+ from coco_core.tools import BaseTool, StructuredTool
21
+ from pydantic import BaseModel, TypeAdapter
22
+ from typing_extensions import Self, is_typeddict
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Callable, Iterable
26
+
27
+ from coco_core.messages import AIMessage
28
+
29
+ # Supported schema types: Pydantic models, dataclasses, TypedDict, JSON schema dicts
30
+ SchemaT = TypeVar("SchemaT")
31
+
32
+ SchemaKind = Literal["pydantic", "dataclass", "typeddict", "json_schema"]
33
+
34
+
35
+ class StructuredOutputError(Exception):
36
+ """Base class for structured output errors."""
37
+
38
+ ai_message: AIMessage
39
+
40
+
41
+ class MultipleStructuredOutputsError(StructuredOutputError):
42
+ """Raised when model returns multiple structured output tool calls when only one is expected."""
43
+
44
+ def __init__(self, tool_names: list[str], ai_message: AIMessage) -> None:
45
+ """Initialize `MultipleStructuredOutputsError`.
46
+
47
+ Args:
48
+ tool_names: The names of the tools called for structured output.
49
+ ai_message: The AI message that contained the invalid multiple tool calls.
50
+ """
51
+ self.tool_names = tool_names
52
+ self.ai_message = ai_message
53
+
54
+ super().__init__(
55
+ "Model incorrectly returned multiple structured responses "
56
+ f"({', '.join(tool_names)}) when only one is expected."
57
+ )
58
+
59
+
60
+ class StructuredOutputValidationError(StructuredOutputError):
61
+ """Raised when structured output tool call arguments fail to parse according to the schema."""
62
+
63
+ def __init__(self, tool_name: str, source: Exception, ai_message: AIMessage) -> None:
64
+ """Initialize `StructuredOutputValidationError`.
65
+
66
+ Args:
67
+ tool_name: The name of the tool that failed.
68
+ source: The exception that occurred.
69
+ ai_message: The AI message that contained the invalid structured output.
70
+ """
71
+ self.tool_name = tool_name
72
+ self.source = source
73
+ self.ai_message = ai_message
74
+ super().__init__(f"Failed to parse structured output for tool '{tool_name}': {source}.")
75
+
76
+
77
+ def _parse_with_schema(
78
+ schema: type[SchemaT] | dict[str, Any], schema_kind: SchemaKind, data: dict[str, Any]
79
+ ) -> SchemaT | dict[str, Any]:
80
+ """Parse data using for any supported schema type.
81
+
82
+ Args:
83
+ schema: The schema type (Pydantic model, `dataclass`, or `TypedDict`)
84
+ schema_kind: One of `'pydantic'`, `'dataclass'`, `'typeddict'`, or
85
+ `'json_schema'`
86
+ data: The data to parse
87
+
88
+ Returns:
89
+ The parsed instance according to the schema type
90
+
91
+ Raises:
92
+ ValueError: If parsing fails
93
+ """
94
+ if schema_kind == "json_schema":
95
+ # Raw JSON schema has no corresponding Python type to instantiate.
96
+ return data
97
+ try:
98
+ adapter = TypeAdapter[SchemaT](schema)
99
+ return adapter.validate_python(data)
100
+ except Exception as e:
101
+ schema_name = getattr(schema, "__name__", str(schema))
102
+ msg = f"Failed to parse data to {schema_name}: {e}"
103
+ raise ValueError(msg) from e
104
+
105
+
106
+ @dataclass(init=False)
107
+ class _SchemaSpec(Generic[SchemaT]):
108
+ """Describes a structured output schema."""
109
+
110
+ schema: type[SchemaT] | dict[str, Any]
111
+ """The schema for the response, can be a Pydantic model, `dataclass`, `TypedDict`,
112
+ or JSON schema dict.
113
+ """
114
+
115
+ name: str
116
+ """Name of the schema, used for tool calling.
117
+
118
+ If not provided, the name will be the class name for models/dataclasses/TypedDicts,
119
+ or the `title` field for JSON schemas.
120
+
121
+ Falls back to a generated name if unavailable.
122
+ """
123
+
124
+ description: str
125
+ """Custom description of the schema.
126
+
127
+ If not provided, will use the model's docstring.
128
+ """
129
+
130
+ schema_kind: SchemaKind
131
+ """The kind of schema."""
132
+
133
+ json_schema: dict[str, Any]
134
+ """JSON schema associated with the schema."""
135
+
136
+ strict: bool | None = None
137
+ """Whether to enforce strict validation of the schema."""
138
+
139
+ def __init__(
140
+ self,
141
+ schema: type[SchemaT] | dict[str, Any],
142
+ *,
143
+ name: str | None = None,
144
+ description: str | None = None,
145
+ strict: bool | None = None,
146
+ ) -> None:
147
+ """Initialize `SchemaSpec` with schema and optional parameters.
148
+
149
+ Args:
150
+ schema: Schema to describe.
151
+ name: Optional name for the schema.
152
+ description: Optional description for the schema.
153
+ strict: Whether to enforce strict validation of the schema.
154
+
155
+ Raises:
156
+ ValueError: If the schema type is unsupported.
157
+ """
158
+ self.schema = schema
159
+
160
+ if name:
161
+ self.name = name
162
+ elif isinstance(schema, dict):
163
+ self.name = str(schema.get("title", f"response_format_{str(uuid.uuid4())[:4]}"))
164
+ else:
165
+ self.name = str(getattr(schema, "__name__", f"response_format_{str(uuid.uuid4())[:4]}"))
166
+
167
+ self.description = description or (
168
+ schema.get("description", "")
169
+ if isinstance(schema, dict)
170
+ else getattr(schema, "__doc__", None) or ""
171
+ )
172
+
173
+ self.strict = strict
174
+
175
+ if isinstance(schema, dict):
176
+ self.schema_kind = "json_schema"
177
+ self.json_schema = schema
178
+ elif isinstance(schema, type) and issubclass(schema, BaseModel):
179
+ self.schema_kind = "pydantic"
180
+ self.json_schema = schema.model_json_schema()
181
+ elif is_dataclass(schema):
182
+ self.schema_kind = "dataclass"
183
+ self.json_schema = TypeAdapter(schema).json_schema()
184
+ elif is_typeddict(schema):
185
+ self.schema_kind = "typeddict"
186
+ self.json_schema = TypeAdapter(schema).json_schema()
187
+ else:
188
+ msg = (
189
+ f"Unsupported schema type: {type(schema)}. "
190
+ f"Supported types: Pydantic models, dataclasses, TypedDicts, and JSON schema dicts."
191
+ )
192
+ raise ValueError(msg)
193
+
194
+
195
+ @dataclass(init=False)
196
+ class ToolStrategy(Generic[SchemaT]):
197
+ """Use a tool calling strategy for model responses."""
198
+
199
+ schema: type[SchemaT] | UnionType | dict[str, Any]
200
+ """Schema for the tool calls."""
201
+
202
+ schema_specs: list[_SchemaSpec[Any]]
203
+ """Schema specs for the tool calls."""
204
+
205
+ tool_message_content: str | None
206
+ """The content of the tool message to be returned when the model calls
207
+ an artificial structured output tool.
208
+ """
209
+
210
+ handle_errors: (
211
+ bool | str | type[Exception] | tuple[type[Exception], ...] | Callable[[Exception], str]
212
+ )
213
+ """Error handling strategy for structured output via `ToolStrategy`.
214
+
215
+ - `True`: Catch all errors with default error template
216
+ - `str`: Catch all errors with this custom message
217
+ - `type[Exception]`: Only catch this exception type with default message
218
+ - `tuple[type[Exception], ...]`: Only catch these exception types with default
219
+ message
220
+ - `Callable[[Exception], str]`: Custom function that returns error message
221
+ - `False`: No retry, let exceptions propagate
222
+
223
+ !!! warning "Raw JSON schema dicts are not validated"
224
+ When `schema` is a raw JSON schema `dict` (as opposed to a Pydantic model,
225
+ `dataclass`, or `TypedDict`), the model's tool-call arguments are returned
226
+ **as-is without validation** against the schema.
227
+
228
+ As a result, `handle_errors` is effectively inert for dict schemas. To get
229
+ validation and automatic retries, express the schema as a Pydantic model,
230
+ `dataclass`, or `TypedDict` instead.
231
+ """
232
+
233
+ def __init__(
234
+ self,
235
+ schema: type[SchemaT] | UnionType | dict[str, Any],
236
+ *,
237
+ tool_message_content: str | None = None,
238
+ handle_errors: bool
239
+ | str
240
+ | type[Exception]
241
+ | tuple[type[Exception], ...]
242
+ | Callable[[Exception], str] = True,
243
+ ) -> None:
244
+ """Initialize `ToolStrategy`.
245
+
246
+ Initialize `ToolStrategy` with schemas, tool message content, and error handling
247
+ strategy.
248
+ """
249
+ self.schema = schema
250
+ self.tool_message_content = tool_message_content
251
+ self.handle_errors = handle_errors
252
+
253
+ def _iter_variants(schema: Any) -> Iterable[Any]:
254
+ """Yield leaf variants from Union and JSON Schema oneOf."""
255
+ if get_origin(schema) in {UnionType, Union}:
256
+ for arg in get_args(schema):
257
+ yield from _iter_variants(arg)
258
+ return
259
+
260
+ if isinstance(schema, dict) and "oneOf" in schema:
261
+ for sub in schema.get("oneOf", []):
262
+ yield from _iter_variants(sub)
263
+ return
264
+
265
+ yield schema
266
+
267
+ self.schema_specs = [_SchemaSpec(s) for s in _iter_variants(schema)]
268
+
269
+
270
+ @dataclass(init=False)
271
+ class ProviderStrategy(Generic[SchemaT]):
272
+ """Use the model provider's native structured output method."""
273
+
274
+ schema: type[SchemaT] | dict[str, Any]
275
+ """Schema for native mode."""
276
+
277
+ schema_spec: _SchemaSpec[SchemaT]
278
+ """Schema spec for native mode."""
279
+
280
+ def __init__(
281
+ self,
282
+ schema: type[SchemaT] | dict[str, Any],
283
+ *,
284
+ strict: bool | None = None,
285
+ ) -> None:
286
+ """Initialize `ProviderStrategy` with schema.
287
+
288
+ Args:
289
+ schema: Schema to enforce via the provider's native structured output.
290
+ strict: Whether to request strict provider-side schema enforcement.
291
+ """
292
+ self.schema = schema
293
+ self.schema_spec = _SchemaSpec(schema, strict=strict)
294
+
295
+ def to_model_kwargs(self) -> dict[str, Any]:
296
+ """Convert to kwargs to bind to a model to force structured output.
297
+
298
+ Returns:
299
+ The kwargs to bind to a model.
300
+ """
301
+ # OpenAI:
302
+ # - see https://platform.openai.com/docs/guides/structured-outputs
303
+ json_schema: dict[str, Any] = {
304
+ "name": self.schema_spec.name,
305
+ "schema": self.schema_spec.json_schema,
306
+ }
307
+ if self.schema_spec.strict:
308
+ json_schema["strict"] = True
309
+
310
+ response_format: dict[str, Any] = {
311
+ "type": "json_schema",
312
+ "json_schema": json_schema,
313
+ }
314
+ return {"response_format": response_format}
315
+
316
+
317
+ @dataclass
318
+ class OutputToolBinding(Generic[SchemaT]):
319
+ """Information for tracking structured output tool metadata.
320
+
321
+ This contains all necessary information to handle structured responses generated via
322
+ tool calls, including the original schema, its type classification, and the
323
+ corresponding tool implementation used by the tools strategy.
324
+ """
325
+
326
+ schema: type[SchemaT] | dict[str, Any]
327
+ """The original schema provided for structured output (Pydantic model, dataclass,
328
+ TypedDict, or JSON schema dict).
329
+ """
330
+
331
+ schema_kind: SchemaKind
332
+ """Classification of the schema type for proper response construction."""
333
+
334
+ tool: BaseTool
335
+ """Coco tool instance created from the schema for model binding."""
336
+
337
+ @classmethod
338
+ def from_schema_spec(cls, schema_spec: _SchemaSpec[SchemaT]) -> Self:
339
+ """Create an `OutputToolBinding` instance from a `SchemaSpec`.
340
+
341
+ Args:
342
+ schema_spec: The `SchemaSpec` to convert
343
+
344
+ Returns:
345
+ An `OutputToolBinding` instance with the appropriate tool created
346
+ """
347
+ return cls(
348
+ schema=schema_spec.schema,
349
+ schema_kind=schema_spec.schema_kind,
350
+ tool=StructuredTool(
351
+ args_schema=schema_spec.json_schema,
352
+ name=schema_spec.name,
353
+ description=schema_spec.description,
354
+ ),
355
+ )
356
+
357
+ def parse(self, tool_args: dict[str, Any]) -> SchemaT | dict[str, Any]:
358
+ """Parse tool arguments according to the schema.
359
+
360
+ Args:
361
+ tool_args: The arguments from the tool call
362
+
363
+ Returns:
364
+ The parsed response according to the schema type
365
+
366
+ Raises:
367
+ ValueError: If parsing fails
368
+ """
369
+ return _parse_with_schema(self.schema, self.schema_kind, tool_args)
370
+
371
+
372
+ @dataclass
373
+ class ProviderStrategyBinding(Generic[SchemaT]):
374
+ """Information for tracking native structured output metadata.
375
+
376
+ This contains all necessary information to handle structured responses generated via
377
+ native provider output, including the original schema, its type classification, and
378
+ parsing logic for provider-enforced JSON.
379
+ """
380
+
381
+ schema: type[SchemaT] | dict[str, Any]
382
+ """The original schema provided for structured output (Pydantic model, `dataclass`,
383
+ `TypedDict`, or JSON schema dict).
384
+ """
385
+
386
+ schema_kind: SchemaKind
387
+ """Classification of the schema type for proper response construction."""
388
+
389
+ @classmethod
390
+ def from_schema_spec(cls, schema_spec: _SchemaSpec[SchemaT]) -> Self:
391
+ """Create a `ProviderStrategyBinding` instance from a `SchemaSpec`.
392
+
393
+ Args:
394
+ schema_spec: The `SchemaSpec` to convert
395
+
396
+ Returns:
397
+ A `ProviderStrategyBinding` instance for parsing native structured output
398
+ """
399
+ return cls(
400
+ schema=schema_spec.schema,
401
+ schema_kind=schema_spec.schema_kind,
402
+ )
403
+
404
+ def parse(self, response: AIMessage) -> SchemaT | dict[str, Any]:
405
+ """Parse `AIMessage` content according to the schema.
406
+
407
+ Args:
408
+ response: The `AIMessage` containing the structured output
409
+
410
+ Returns:
411
+ The parsed response according to the schema
412
+
413
+ Raises:
414
+ ValueError: If text extraction, JSON parsing or schema validation fails
415
+ """
416
+ # Extract text content from AIMessage and parse as JSON
417
+ raw_text = self._extract_text_content_from_message(response)
418
+
419
+ try:
420
+ data = json.loads(raw_text)
421
+ except Exception as e:
422
+ schema_name = getattr(self.schema, "__name__", "response_format")
423
+ msg = (
424
+ f"Native structured output expected valid JSON for {schema_name}, "
425
+ f"but parsing failed: {e}."
426
+ )
427
+ raise ValueError(msg) from e
428
+
429
+ # Parse according to schema
430
+ return _parse_with_schema(self.schema, self.schema_kind, data)
431
+
432
+ @staticmethod
433
+ def _extract_text_content_from_message(message: AIMessage) -> str:
434
+ """Extract text content from an `AIMessage`.
435
+
436
+ Args:
437
+ message: The AI message to extract text from
438
+
439
+ Returns:
440
+ The extracted text content
441
+ """
442
+ content = message.content
443
+ if isinstance(content, str):
444
+ return content
445
+ parts: list[str] = []
446
+ for c in content:
447
+ if isinstance(c, dict):
448
+ if c.get("type") == "text" and "text" in c:
449
+ parts.append(str(c["text"]))
450
+ elif "content" in c and isinstance(c["content"], str):
451
+ parts.append(c["content"])
452
+ else:
453
+ parts.append(str(c))
454
+ return "".join(parts)
455
+
456
+
457
+ class AutoStrategy(Generic[SchemaT]):
458
+ """Automatically select the best strategy for structured output."""
459
+
460
+ schema: type[SchemaT] | dict[str, Any]
461
+ """Schema for automatic mode."""
462
+
463
+ def __init__(
464
+ self,
465
+ schema: type[SchemaT] | dict[str, Any],
466
+ ) -> None:
467
+ """Initialize `AutoStrategy` with schema."""
468
+ self.schema = schema
469
+
470
+
471
+ ResponseFormat = ToolStrategy[SchemaT] | ProviderStrategy[SchemaT] | AutoStrategy[SchemaT]
472
+ """Union type for all supported response format strategies."""
@@ -0,0 +1,7 @@
1
+ """Entrypoint to using [chat models](https://docs.langchain.com/oss/python/langchain/models) in Coco.""" # noqa: E501
2
+
3
+ from coco_core.language_models import BaseChatModel
4
+
5
+ from coco.chat_models.base import init_chat_model
6
+
7
+ __all__ = ["BaseChatModel", "init_chat_model"]