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,208 @@
1
+ """Tool that takes in function or coroutine directly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from inspect import signature
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ )
11
+
12
+ from typing_extensions import override
13
+
14
+ # Cannot move to TYPE_CHECKING as _run/_arun parameter annotations are needed at runtime
15
+ from coco_core.callbacks import (
16
+ AsyncCallbackManagerForToolRun, # noqa: TC001
17
+ CallbackManagerForToolRun, # noqa: TC001
18
+ )
19
+ from coco_core.runnables import RunnableConfig, run_in_executor
20
+ from coco_core.tools.base import (
21
+ ArgsSchema,
22
+ BaseTool,
23
+ ToolException,
24
+ _get_runnable_config_param,
25
+ )
26
+
27
+ if TYPE_CHECKING:
28
+ from coco_core.messages import ToolCall
29
+
30
+
31
+ class Tool(BaseTool):
32
+ """Tool that takes in function or coroutine directly."""
33
+
34
+ description: str = ""
35
+
36
+ func: Callable[..., str] | None
37
+ """The function to run when the tool is called."""
38
+
39
+ coroutine: Callable[..., Awaitable[str]] | None = None
40
+ """The asynchronous version of the function."""
41
+
42
+ # --- Runnable ---
43
+
44
+ @override
45
+ async def ainvoke(
46
+ self,
47
+ input: str | dict[str, Any] | ToolCall,
48
+ config: RunnableConfig | None = None,
49
+ **kwargs: Any,
50
+ ) -> Any:
51
+ if not self.coroutine:
52
+ # If the tool does not implement async, fall back to default implementation
53
+ return await run_in_executor(config, self.invoke, input, config, **kwargs)
54
+
55
+ return await super().ainvoke(input, config, **kwargs)
56
+
57
+ # --- Tool ---
58
+
59
+ @property
60
+ def args(self) -> dict[str, Any]:
61
+ """The tool's input arguments.
62
+
63
+ Returns:
64
+ The input arguments for the tool.
65
+ """
66
+ if self.args_schema is not None:
67
+ return super().args
68
+ # For backwards compatibility, if the function signature is ambiguous,
69
+ # assume it takes a single string input.
70
+ return {"tool_input": {"type": "string"}}
71
+
72
+ def _to_args_and_kwargs(
73
+ self, tool_input: str | dict[str, Any], tool_call_id: str | None
74
+ ) -> tuple[tuple[str, ...], dict[str, Any]]:
75
+ """Convert tool input to Pydantic model.
76
+
77
+ Args:
78
+ tool_input: The input to the tool.
79
+ tool_call_id: The ID of the tool call.
80
+
81
+ Raises:
82
+ ToolException: If the tool input is invalid.
83
+
84
+ Returns:
85
+ The Pydantic model args and kwargs.
86
+ """
87
+ args, kwargs = super()._to_args_and_kwargs(tool_input, tool_call_id)
88
+ # For backwards compatibility. The tool must be run with a single input
89
+ all_args = list(args) + list(kwargs.values())
90
+ if len(all_args) != 1:
91
+ msg = (
92
+ f"""Too many arguments to single-input tool {self.name}.
93
+ Consider using StructuredTool instead."""
94
+ f" Args: {all_args}"
95
+ )
96
+ raise ToolException(msg)
97
+ return tuple(all_args), {}
98
+
99
+ def _run(
100
+ self,
101
+ *args: Any,
102
+ config: RunnableConfig,
103
+ run_manager: CallbackManagerForToolRun | None = None,
104
+ **kwargs: Any,
105
+ ) -> Any:
106
+ """Use the tool.
107
+
108
+ Args:
109
+ *args: Positional arguments to pass to the tool
110
+ config: Configuration for the run
111
+ run_manager: Optional callback manager to use for the run
112
+ **kwargs: Keyword arguments to pass to the tool
113
+
114
+ Returns:
115
+ The result of the tool execution
116
+ """
117
+ if self.func:
118
+ if run_manager and signature(self.func).parameters.get("callbacks"):
119
+ kwargs["callbacks"] = run_manager.get_child()
120
+ if config_param := _get_runnable_config_param(self.func):
121
+ kwargs[config_param] = config
122
+ return self.func(*args, **kwargs)
123
+ msg = "Tool does not support sync invocation."
124
+ raise NotImplementedError(msg)
125
+
126
+ async def _arun(
127
+ self,
128
+ *args: Any,
129
+ config: RunnableConfig,
130
+ run_manager: AsyncCallbackManagerForToolRun | None = None,
131
+ **kwargs: Any,
132
+ ) -> Any:
133
+ """Use the tool asynchronously.
134
+
135
+ Args:
136
+ *args: Positional arguments to pass to the tool
137
+ config: Configuration for the run
138
+ run_manager: Optional callback manager to use for the run
139
+ **kwargs: Keyword arguments to pass to the tool
140
+
141
+ Returns:
142
+ The result of the tool execution
143
+ """
144
+ if self.coroutine:
145
+ if run_manager and signature(self.coroutine).parameters.get("callbacks"):
146
+ kwargs["callbacks"] = run_manager.get_child()
147
+ if config_param := _get_runnable_config_param(self.coroutine):
148
+ kwargs[config_param] = config
149
+ return await self.coroutine(*args, **kwargs)
150
+
151
+ # NOTE: this code is unreachable since _arun is only called if coroutine is not
152
+ # None.
153
+ return await super()._arun(
154
+ *args, config=config, run_manager=run_manager, **kwargs
155
+ )
156
+
157
+ # TODO: this is for backwards compatibility, remove in future
158
+ def __init__(
159
+ self,
160
+ name: str,
161
+ func: Callable[..., Any] | None,
162
+ description: str,
163
+ **kwargs: Any,
164
+ ) -> None:
165
+ """Initialize tool."""
166
+ super().__init__(name=name, func=func, description=description, **kwargs)
167
+
168
+ @classmethod
169
+ def from_function(
170
+ cls,
171
+ func: Callable[..., Any] | None,
172
+ name: str, # We keep these required to support backwards compatibility
173
+ description: str,
174
+ return_direct: bool = False, # noqa: FBT001,FBT002
175
+ args_schema: ArgsSchema | None = None,
176
+ coroutine: Callable[..., Awaitable[Any]]
177
+ | None = None, # This is last for compatibility, but should be after func
178
+ **kwargs: Any,
179
+ ) -> Tool:
180
+ """Initialize tool from a function.
181
+
182
+ Args:
183
+ func: The function to create the tool from.
184
+ name: The name of the tool.
185
+ description: The description of the tool.
186
+ return_direct: Whether to return the output directly.
187
+ args_schema: The schema of the tool's input arguments.
188
+ coroutine: The asynchronous version of the function.
189
+ **kwargs: Additional arguments to pass to the tool.
190
+
191
+ Returns:
192
+ The tool.
193
+
194
+ Raises:
195
+ ValueError: If the function is not provided.
196
+ """
197
+ if func is None and coroutine is None:
198
+ msg = "Function and/or coroutine must be provided"
199
+ raise ValueError(msg)
200
+ return cls(
201
+ name=name,
202
+ func=func,
203
+ coroutine=coroutine,
204
+ description=description,
205
+ return_direct=return_direct,
206
+ args_schema=args_schema,
207
+ **kwargs,
208
+ )
@@ -0,0 +1,322 @@
1
+ """Structured tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import textwrap
7
+ from collections.abc import Awaitable, Callable
8
+ from inspect import signature
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Annotated,
12
+ Any,
13
+ Literal,
14
+ )
15
+
16
+ from pydantic import Field, PlainSerializer, SkipValidation
17
+ from typing_extensions import override
18
+
19
+ # Cannot move to TYPE_CHECKING as _run/_arun parameter annotations are needed at runtime
20
+ from coco_core.callbacks import (
21
+ AsyncCallbackManagerForToolRun, # noqa: TC001
22
+ CallbackManagerForToolRun, # noqa: TC001
23
+ )
24
+ from coco_core.runnables import RunnableConfig, run_in_executor
25
+ from coco_core.tools.base import (
26
+ _EMPTY_SET,
27
+ FILTERED_ARGS,
28
+ ArgsSchema,
29
+ BaseTool,
30
+ _get_injected_args_keys_from_signature,
31
+ _get_runnable_config_param,
32
+ create_schema_from_function,
33
+ )
34
+ from coco_core.utils.pydantic import is_basemodel_subclass, model_json_schema
35
+
36
+ if TYPE_CHECKING:
37
+ from coco_core.messages import ToolCall
38
+ from coco_core.utils.pydantic import TypeBaseModel
39
+
40
+
41
+ def _serialize_as_str(value: Any) -> str:
42
+ """Stringify a value that has no JSON form.
43
+
44
+ A plain function rather than the `str` builtin: Pydantic < 2.9 inspects the
45
+ serializer's signature, and `inspect.signature` raises for C builtins.
46
+ """
47
+ return str(value)
48
+
49
+
50
+ @functools.lru_cache(maxsize=256)
51
+ def _model_json_schema(args_schema: TypeBaseModel, _rebuild_token: object) -> Any:
52
+ """Ask a v1 or v2 model class for its JSON schema, or fall back to its repr.
53
+
54
+ Cached because pydantic does not memoize this per class and it dominates the
55
+ dump -- 1.10 ms against 0.02 ms for an eight-tool payload -- which tracing
56
+ pays on every run.
57
+
58
+ `_rebuild_token` is the class's validator, which `model_rebuild()` replaces.
59
+ Keying on it retires the entry for a rebuilt schema, and for one whose
60
+ forward reference was still unresolved when it was first dumped. Mutating a
61
+ class in place without rebuilding it is not detected.
62
+ """
63
+ try:
64
+ return model_json_schema(args_schema)
65
+ except Exception: # a schema holding an arbitrary type has no JSON schema
66
+ return str(args_schema)
67
+
68
+
69
+ def _serialize_args_schema(args_schema: ArgsSchema) -> Any:
70
+ """Represent a schema class by its JSON schema when dumping to JSON.
71
+
72
+ A Pydantic model class has no JSON form, so leaving it to the default
73
+ serializer raises `PydanticSerializationError`. Its own JSON schema is the
74
+ shape a dict schema already has, and a dict is returned unchanged.
75
+ """
76
+ if isinstance(args_schema, dict):
77
+ return args_schema
78
+ return _model_json_schema(
79
+ args_schema, getattr(args_schema, "__pydantic_validator__", None)
80
+ )
81
+
82
+
83
+ # Attached to the fields via `Annotated` rather than declared with
84
+ # `@field_serializer`, which would take the field's one serializer slot and make
85
+ # any subclass that declares its own serializer for the same field fail at class
86
+ # creation with `PydanticUserError: Multiple field serializer functions ...`.
87
+ _JsonSchemaFallback = PlainSerializer(
88
+ _serialize_args_schema, when_used="json-unless-none"
89
+ )
90
+ _JsonCallableFallback = PlainSerializer(_serialize_as_str, when_used="json-unless-none")
91
+
92
+
93
+ class StructuredTool(BaseTool):
94
+ """Tool that can operate on any number of inputs."""
95
+
96
+ description: str = ""
97
+
98
+ args_schema: Annotated[ArgsSchema, SkipValidation(), _JsonSchemaFallback] = Field(
99
+ ..., description="The tool schema."
100
+ )
101
+ """The input arguments' schema."""
102
+
103
+ func: Annotated[Callable[..., Any] | None, _JsonCallableFallback] = None
104
+ """The function to run when the tool is called."""
105
+
106
+ coroutine: Annotated[
107
+ Callable[..., Awaitable[Any]] | None, _JsonCallableFallback
108
+ ] = None
109
+ """The asynchronous version of the function."""
110
+
111
+ # --- Runnable ---
112
+
113
+ # TODO: Is this needed?
114
+ @override
115
+ async def ainvoke(
116
+ self,
117
+ input: str | dict[str, Any] | ToolCall,
118
+ config: RunnableConfig | None = None,
119
+ **kwargs: Any,
120
+ ) -> Any:
121
+ if not self.coroutine:
122
+ # If the tool does not implement async, fall back to default implementation
123
+ return await run_in_executor(config, self.invoke, input, config, **kwargs)
124
+
125
+ return await super().ainvoke(input, config, **kwargs)
126
+
127
+ # --- Tool ---
128
+
129
+ def _run(
130
+ self,
131
+ *args: Any,
132
+ config: RunnableConfig,
133
+ run_manager: CallbackManagerForToolRun | None = None,
134
+ **kwargs: Any,
135
+ ) -> Any:
136
+ """Use the tool.
137
+
138
+ Args:
139
+ *args: Positional arguments to pass to the tool
140
+ config: Configuration for the run
141
+ run_manager: Optional callback manager to use for the run
142
+ **kwargs: Keyword arguments to pass to the tool
143
+
144
+ Returns:
145
+ The result of the tool execution
146
+ """
147
+ if self.func:
148
+ if run_manager and signature(self.func).parameters.get("callbacks"):
149
+ kwargs["callbacks"] = run_manager.get_child()
150
+ if config_param := _get_runnable_config_param(self.func):
151
+ kwargs[config_param] = config
152
+ return self.func(*args, **kwargs)
153
+ msg = "StructuredTool does not support sync invocation."
154
+ raise NotImplementedError(msg)
155
+
156
+ async def _arun(
157
+ self,
158
+ *args: Any,
159
+ config: RunnableConfig,
160
+ run_manager: AsyncCallbackManagerForToolRun | None = None,
161
+ **kwargs: Any,
162
+ ) -> Any:
163
+ """Use the tool asynchronously.
164
+
165
+ Args:
166
+ *args: Positional arguments to pass to the tool
167
+ config: Configuration for the run
168
+ run_manager: Optional callback manager to use for the run
169
+ **kwargs: Keyword arguments to pass to the tool
170
+
171
+ Returns:
172
+ The result of the tool execution
173
+ """
174
+ if self.coroutine:
175
+ if run_manager and signature(self.coroutine).parameters.get("callbacks"):
176
+ kwargs["callbacks"] = run_manager.get_child()
177
+ if config_param := _get_runnable_config_param(self.coroutine):
178
+ kwargs[config_param] = config
179
+ return await self.coroutine(*args, **kwargs)
180
+
181
+ # If self.coroutine is None, then this will delegate to the default
182
+ # implementation which is expected to delegate to _run on a separate thread.
183
+ return await super()._arun(
184
+ *args, config=config, run_manager=run_manager, **kwargs
185
+ )
186
+
187
+ @classmethod
188
+ def from_function(
189
+ cls,
190
+ func: Callable[..., Any] | None = None,
191
+ coroutine: Callable[..., Awaitable[Any]] | None = None,
192
+ name: str | None = None,
193
+ description: str | None = None,
194
+ return_direct: bool = False, # noqa: FBT001,FBT002
195
+ args_schema: ArgsSchema | None = None,
196
+ infer_schema: bool = True, # noqa: FBT001,FBT002
197
+ *,
198
+ response_format: Literal["content", "content_and_artifact"] = "content",
199
+ parse_docstring: bool = False,
200
+ error_on_invalid_docstring: bool = False,
201
+ **kwargs: Any,
202
+ ) -> StructuredTool:
203
+ """Create tool from a given function.
204
+
205
+ A classmethod that helps to create a tool from a function.
206
+
207
+ Args:
208
+ func: The function from which to create a tool.
209
+ coroutine: The async function from which to create a tool.
210
+ name: The name of the tool.
211
+
212
+ Defaults to the function name.
213
+ description: The description of the tool.
214
+
215
+ Defaults to the function docstring.
216
+ return_direct: Whether to return the result directly or as a callback.
217
+ args_schema: The schema of the tool's input arguments.
218
+ infer_schema: Whether to infer the schema from the function's signature.
219
+ response_format: The tool response format.
220
+
221
+ If `'content'` then the output of the tool is interpreted as the
222
+ contents of a `ToolMessage`. If `'content_and_artifact'` then the output
223
+ is expected to be a two-tuple corresponding to the `(content, artifact)`
224
+ of a `ToolMessage`.
225
+ parse_docstring: If `infer_schema` and `parse_docstring`, will attempt
226
+ to parse parameter descriptions from Google Style function docstrings.
227
+ error_on_invalid_docstring: if `parse_docstring` is provided, configure
228
+ whether to raise `ValueError` on invalid Google Style docstrings.
229
+ **kwargs: Additional arguments to pass to the tool
230
+
231
+ Returns:
232
+ The tool.
233
+
234
+ Raises:
235
+ ValueError: If the function is not provided.
236
+ ValueError: If the function does not have a docstring and description
237
+ is not provided.
238
+ TypeError: If the `args_schema` is not a `BaseModel` or dict.
239
+
240
+ Examples:
241
+ ```python
242
+ def add(a: int, b: int) -> int:
243
+ \"\"\"Add two numbers\"\"\"
244
+ return a + b
245
+ tool = StructuredTool.from_function(add)
246
+ tool.run(1, 2) # 3
247
+
248
+ ```
249
+ """
250
+ if func is not None:
251
+ source_function = func
252
+ elif coroutine is not None:
253
+ source_function = coroutine
254
+ else:
255
+ msg = "Function and/or coroutine must be provided"
256
+ raise ValueError(msg)
257
+ name = name or source_function.__name__
258
+ if args_schema is None and infer_schema:
259
+ # schema name is appended within function
260
+ args_schema = create_schema_from_function(
261
+ name,
262
+ source_function,
263
+ parse_docstring=parse_docstring,
264
+ error_on_invalid_docstring=error_on_invalid_docstring,
265
+ filter_args=_filter_schema_args(source_function),
266
+ )
267
+ description_ = description
268
+ if description is None and not parse_docstring:
269
+ description_ = source_function.__doc__ or None
270
+ if description_ is None and args_schema:
271
+ if isinstance(args_schema, type) and is_basemodel_subclass(args_schema):
272
+ description_ = args_schema.__doc__
273
+ if (
274
+ description_
275
+ and "A base class for creating Pydantic models" in description_
276
+ ):
277
+ description_ = ""
278
+ elif not description_:
279
+ description_ = None
280
+ elif isinstance(args_schema, dict):
281
+ description_ = args_schema.get("description")
282
+ else:
283
+ msg = (
284
+ "Invalid args_schema: expected BaseModel or dict, "
285
+ f"got {args_schema}"
286
+ )
287
+ raise TypeError(msg)
288
+ if description_ is None:
289
+ msg = "Function must have a docstring if description not provided."
290
+ raise ValueError(msg)
291
+ if description is None:
292
+ # Only apply if using the function's docstring
293
+ description_ = textwrap.dedent(description_).strip()
294
+
295
+ # Description example:
296
+ # search_api(query: str) - Searches the API for the query.
297
+ description_ = f"{description_.strip()}"
298
+ return cls(
299
+ name=name,
300
+ func=func,
301
+ coroutine=coroutine,
302
+ args_schema=args_schema,
303
+ description=description_,
304
+ return_direct=return_direct,
305
+ response_format=response_format,
306
+ **kwargs,
307
+ )
308
+
309
+ @functools.cached_property
310
+ def _injected_args_keys(self) -> frozenset[str]:
311
+ fn = self.func or self.coroutine
312
+ if fn is None:
313
+ return _EMPTY_SET
314
+ return _get_injected_args_keys_from_signature(fn)
315
+
316
+
317
+ def _filter_schema_args(func: Callable[..., Any]) -> list[str]:
318
+ filter_args = list(FILTERED_ARGS)
319
+ if config_param := _get_runnable_config_param(func):
320
+ filter_args.append(config_param)
321
+ # filter_args.extend(_get_non_model_params(type_hints))
322
+ return filter_args
@@ -0,0 +1,47 @@
1
+ """Tracers are classes for tracing runs."""
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.tracers.base import BaseTracer
9
+ from coco_core.tracers.coco import CocoTracer
10
+ from coco_core.tracers.log_stream import (
11
+ LogStreamCallbackHandler,
12
+ RunLog,
13
+ RunLogPatch,
14
+ )
15
+ from coco_core.tracers.schemas import Run
16
+ from coco_core.tracers.stdout import ConsoleCallbackHandler
17
+
18
+ __all__ = (
19
+ "BaseTracer",
20
+ "ConsoleCallbackHandler",
21
+ "CocoTracer",
22
+ "LogStreamCallbackHandler",
23
+ "Run",
24
+ "RunLog",
25
+ "RunLogPatch",
26
+ )
27
+
28
+ _dynamic_imports = {
29
+ "BaseTracer": "base",
30
+ "CocoTracer": "coco",
31
+ "LogStreamCallbackHandler": "log_stream",
32
+ "RunLog": "log_stream",
33
+ "RunLogPatch": "log_stream",
34
+ "Run": "schemas",
35
+ "ConsoleCallbackHandler": "stdout",
36
+ }
37
+
38
+
39
+ def __getattr__(attr_name: str) -> object:
40
+ module_name = _dynamic_imports.get(attr_name)
41
+ result = import_attr(attr_name, module_name, __spec__.parent)
42
+ globals()[attr_name] = result
43
+ return result
44
+
45
+
46
+ def __dir__() -> list[str]:
47
+ return list(__all__)
@@ -0,0 +1,95 @@
1
+ """Compatibility helpers for Pydantic v1/v2 with langsmith `Run` objects.
2
+
3
+ !!! note
4
+
5
+ The generic helpers (`pydantic_to_dict`, `pydantic_copy`) detect Pydanti version
6
+ based on the langsmith `Run` model. They're intended for langsmith objects (`Run`,
7
+ `Example`) which migrate together.
8
+
9
+ For general Pydantic v1/v2 handling, see `coco_core.utils.pydantic`.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, TypeVar
15
+
16
+ from coco_core.tracers.schemas import Run
17
+
18
+ # Detect Pydantic version once at import time based on Run model
19
+ _RUN_IS_PYDANTIC_V2 = hasattr(Run, "model_dump")
20
+
21
+ T = TypeVar("T")
22
+
23
+
24
+ def run_to_dict(run: Run, **kwargs: Any) -> dict[str, Any]:
25
+ """Convert run to dict, compatible with both Pydantic v1 and v2.
26
+
27
+ Args:
28
+ run: The run to convert.
29
+ **kwargs: Additional arguments passed to `model_dump`/`dict`.
30
+
31
+ Returns:
32
+ Dictionary representation of the run.
33
+ """
34
+ if _RUN_IS_PYDANTIC_V2:
35
+ return run.model_dump(**kwargs)
36
+ return run.dict(**kwargs) # type: ignore[deprecated]
37
+
38
+
39
+ def run_copy(run: Run, **kwargs: Any) -> Run:
40
+ """Copy run, compatible with both Pydantic v1 and v2.
41
+
42
+ Args:
43
+ run: The run to copy.
44
+ **kwargs: Additional arguments passed to `model_copy`/`copy`.
45
+
46
+ Returns:
47
+ A copy of the run.
48
+ """
49
+ if _RUN_IS_PYDANTIC_V2:
50
+ return run.model_copy(**kwargs)
51
+ return run.copy(**kwargs) # type: ignore[deprecated]
52
+
53
+
54
+ def run_construct(**kwargs: Any) -> Run:
55
+ """Construct run without validation, compatible with both Pydantic v1 and v2.
56
+
57
+ Args:
58
+ **kwargs: Fields to set on the run.
59
+
60
+ Returns:
61
+ A new `Run` instance constructed without validation.
62
+ """
63
+ if _RUN_IS_PYDANTIC_V2:
64
+ return Run.model_construct(**kwargs)
65
+ return Run.construct(**kwargs) # type: ignore[deprecated]
66
+
67
+
68
+ def pydantic_to_dict(obj: Any, **kwargs: Any) -> dict[str, Any]:
69
+ """Convert any Pydantic model to dict, compatible with both v1 and v2.
70
+
71
+ Args:
72
+ obj: The Pydantic model to convert.
73
+ **kwargs: Additional arguments passed to `model_dump`/`dict`.
74
+
75
+ Returns:
76
+ Dictionary representation of the model.
77
+ """
78
+ if _RUN_IS_PYDANTIC_V2:
79
+ return obj.model_dump(**kwargs) # type: ignore[no-any-return]
80
+ return obj.dict(**kwargs) # type: ignore[no-any-return]
81
+
82
+
83
+ def pydantic_copy(obj: T, **kwargs: Any) -> T:
84
+ """Copy any Pydantic model, compatible with both v1 and v2.
85
+
86
+ Args:
87
+ obj: The Pydantic model to copy.
88
+ **kwargs: Additional arguments passed to `model_copy`/`copy`.
89
+
90
+ Returns:
91
+ A copy of the model.
92
+ """
93
+ if _RUN_IS_PYDANTIC_V2:
94
+ return obj.model_copy(**kwargs) # type: ignore[attr-defined,no-any-return]
95
+ return obj.copy(**kwargs) # type: ignore[attr-defined,no-any-return]