mirascope 2.0.0a5__py3-none-any.whl → 2.0.1__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 (349) hide show
  1. mirascope/__init__.py +10 -1
  2. mirascope/_stubs.py +363 -0
  3. mirascope/api/__init__.py +8 -0
  4. mirascope/api/_generated/__init__.py +285 -2
  5. mirascope/api/_generated/annotations/__init__.py +33 -0
  6. mirascope/api/_generated/annotations/client.py +506 -0
  7. mirascope/api/_generated/annotations/raw_client.py +1414 -0
  8. mirascope/api/_generated/annotations/types/__init__.py +31 -0
  9. mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
  10. mirascope/api/_generated/annotations/types/annotations_create_response.py +48 -0
  11. mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
  12. mirascope/api/_generated/annotations/types/annotations_get_response.py +48 -0
  13. mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
  14. mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
  15. mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
  16. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +50 -0
  17. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
  18. mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
  19. mirascope/api/_generated/annotations/types/annotations_update_response.py +48 -0
  20. mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
  21. mirascope/api/_generated/api_keys/__init__.py +12 -2
  22. mirascope/api/_generated/api_keys/client.py +77 -0
  23. mirascope/api/_generated/api_keys/raw_client.py +422 -39
  24. mirascope/api/_generated/api_keys/types/__init__.py +7 -1
  25. mirascope/api/_generated/api_keys/types/api_keys_create_response.py +4 -12
  26. mirascope/api/_generated/api_keys/types/api_keys_get_response.py +4 -12
  27. mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
  28. mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +4 -12
  29. mirascope/api/_generated/client.py +42 -0
  30. mirascope/api/_generated/core/client_wrapper.py +2 -14
  31. mirascope/api/_generated/core/datetime_utils.py +1 -3
  32. mirascope/api/_generated/core/file.py +2 -5
  33. mirascope/api/_generated/core/http_client.py +36 -112
  34. mirascope/api/_generated/core/jsonable_encoder.py +1 -3
  35. mirascope/api/_generated/core/pydantic_utilities.py +19 -74
  36. mirascope/api/_generated/core/query_encoder.py +1 -3
  37. mirascope/api/_generated/core/serialization.py +4 -10
  38. mirascope/api/_generated/docs/client.py +2 -6
  39. mirascope/api/_generated/docs/raw_client.py +51 -5
  40. mirascope/api/_generated/environment.py +3 -3
  41. mirascope/api/_generated/environments/__init__.py +6 -0
  42. mirascope/api/_generated/environments/client.py +117 -0
  43. mirascope/api/_generated/environments/raw_client.py +530 -51
  44. mirascope/api/_generated/environments/types/__init__.py +10 -0
  45. mirascope/api/_generated/environments/types/environments_create_response.py +1 -3
  46. mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
  47. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
  48. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_models_item.py +22 -0
  49. mirascope/api/_generated/environments/types/environments_get_response.py +1 -3
  50. mirascope/api/_generated/environments/types/environments_list_response_item.py +1 -3
  51. mirascope/api/_generated/environments/types/environments_update_response.py +1 -3
  52. mirascope/api/_generated/errors/__init__.py +8 -0
  53. mirascope/api/_generated/errors/bad_request_error.py +1 -2
  54. mirascope/api/_generated/errors/conflict_error.py +1 -2
  55. mirascope/api/_generated/errors/forbidden_error.py +1 -5
  56. mirascope/api/_generated/errors/internal_server_error.py +1 -6
  57. mirascope/api/_generated/errors/not_found_error.py +1 -5
  58. mirascope/api/_generated/errors/payment_required_error.py +15 -0
  59. mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
  60. mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
  61. mirascope/api/_generated/errors/unauthorized_error.py +11 -0
  62. mirascope/api/_generated/functions/__init__.py +39 -0
  63. mirascope/api/_generated/functions/client.py +647 -0
  64. mirascope/api/_generated/functions/raw_client.py +1890 -0
  65. mirascope/api/_generated/functions/types/__init__.py +53 -0
  66. mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
  67. mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
  68. mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
  69. mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
  70. mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
  71. mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
  72. mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
  73. mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
  74. mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
  75. mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
  76. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
  77. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
  78. mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
  79. mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
  80. mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
  81. mirascope/api/_generated/health/client.py +2 -6
  82. mirascope/api/_generated/health/raw_client.py +51 -5
  83. mirascope/api/_generated/health/types/health_check_response.py +1 -3
  84. mirascope/api/_generated/organization_invitations/__init__.py +33 -0
  85. mirascope/api/_generated/organization_invitations/client.py +546 -0
  86. mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
  87. mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
  88. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
  89. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
  90. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
  91. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
  92. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
  93. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
  94. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
  95. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
  96. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
  97. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
  98. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
  99. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
  100. mirascope/api/_generated/organization_memberships/__init__.py +19 -0
  101. mirascope/api/_generated/organization_memberships/client.py +302 -0
  102. mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
  103. mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
  104. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
  105. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
  106. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
  107. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
  108. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
  109. mirascope/api/_generated/organizations/__init__.py +26 -0
  110. mirascope/api/_generated/organizations/client.py +465 -0
  111. mirascope/api/_generated/organizations/raw_client.py +1799 -108
  112. mirascope/api/_generated/organizations/types/__init__.py +48 -0
  113. mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
  114. mirascope/api/_generated/organizations/types/organizations_create_response.py +4 -3
  115. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +1 -3
  116. mirascope/api/_generated/organizations/types/organizations_get_response.py +4 -3
  117. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +1 -3
  118. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +4 -3
  119. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +1 -3
  120. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
  121. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
  122. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
  123. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
  124. mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
  125. mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
  126. mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
  127. mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
  128. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
  129. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
  130. mirascope/api/_generated/organizations/types/organizations_update_response.py +4 -3
  131. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +1 -3
  132. mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
  133. mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
  134. mirascope/api/_generated/project_memberships/__init__.py +25 -0
  135. mirascope/api/_generated/project_memberships/client.py +437 -0
  136. mirascope/api/_generated/project_memberships/raw_client.py +1039 -0
  137. mirascope/api/_generated/project_memberships/types/__init__.py +29 -0
  138. mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
  139. mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
  140. mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
  141. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
  142. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
  143. mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
  144. mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
  145. mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
  146. mirascope/api/_generated/projects/__init__.py +2 -12
  147. mirascope/api/_generated/projects/client.py +17 -71
  148. mirascope/api/_generated/projects/raw_client.py +295 -51
  149. mirascope/api/_generated/projects/types/__init__.py +1 -6
  150. mirascope/api/_generated/projects/types/projects_create_response.py +3 -9
  151. mirascope/api/_generated/projects/types/projects_get_response.py +3 -9
  152. mirascope/api/_generated/projects/types/projects_list_response_item.py +3 -9
  153. mirascope/api/_generated/projects/types/projects_update_response.py +3 -9
  154. mirascope/api/_generated/reference.md +3619 -182
  155. mirascope/api/_generated/tags/__init__.py +19 -0
  156. mirascope/api/_generated/tags/client.py +504 -0
  157. mirascope/api/_generated/tags/raw_client.py +1288 -0
  158. mirascope/api/_generated/tags/types/__init__.py +17 -0
  159. mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
  160. mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
  161. mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
  162. mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
  163. mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
  164. mirascope/api/_generated/token_cost/__init__.py +7 -0
  165. mirascope/api/_generated/token_cost/client.py +160 -0
  166. mirascope/api/_generated/token_cost/raw_client.py +264 -0
  167. mirascope/api/_generated/token_cost/types/__init__.py +8 -0
  168. mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
  169. mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
  170. mirascope/api/_generated/traces/__init__.py +42 -0
  171. mirascope/api/_generated/traces/client.py +941 -0
  172. mirascope/api/_generated/traces/raw_client.py +2177 -23
  173. mirascope/api/_generated/traces/types/__init__.py +60 -0
  174. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +4 -11
  175. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +2 -6
  176. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +1 -3
  177. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +8 -24
  178. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +2 -6
  179. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +3 -9
  180. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +2 -6
  181. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +3 -9
  182. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +4 -8
  183. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +2 -6
  184. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +8 -24
  185. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +2 -6
  186. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +3 -9
  187. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +1 -3
  188. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +6 -18
  189. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +3 -9
  190. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +8 -24
  191. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +2 -6
  192. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +2 -6
  193. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +1 -3
  194. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +2 -6
  195. mirascope/api/_generated/traces/types/traces_create_response.py +2 -5
  196. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +3 -9
  197. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +60 -0
  198. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
  199. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
  200. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
  201. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
  202. mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
  203. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +88 -0
  204. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
  205. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
  206. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
  207. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
  208. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
  209. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
  210. mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
  211. mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
  212. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
  213. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
  214. mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
  215. mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
  216. mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
  217. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +50 -0
  218. mirascope/api/_generated/types/__init__.py +48 -0
  219. mirascope/api/_generated/types/already_exists_error.py +1 -3
  220. mirascope/api/_generated/types/bad_request_error_body.py +50 -0
  221. mirascope/api/_generated/types/click_house_error.py +22 -0
  222. mirascope/api/_generated/types/database_error.py +1 -3
  223. mirascope/api/_generated/types/date.py +3 -0
  224. mirascope/api/_generated/types/http_api_decode_error.py +1 -3
  225. mirascope/api/_generated/types/immutable_resource_error.py +22 -0
  226. mirascope/api/_generated/types/internal_server_error_body.py +49 -0
  227. mirascope/api/_generated/types/issue.py +1 -3
  228. mirascope/api/_generated/types/issue_tag.py +1 -8
  229. mirascope/api/_generated/types/not_found_error_body.py +1 -3
  230. mirascope/api/_generated/types/number_from_string.py +3 -0
  231. mirascope/api/_generated/types/permission_denied_error.py +1 -3
  232. mirascope/api/_generated/types/permission_denied_error_tag.py +1 -3
  233. mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
  234. mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
  235. mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
  236. mirascope/api/_generated/types/property_key_key.py +1 -3
  237. mirascope/api/_generated/types/rate_limit_error.py +31 -0
  238. mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
  239. mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
  240. mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
  241. mirascope/api/_generated/types/stripe_error.py +20 -0
  242. mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
  243. mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
  244. mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
  245. mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
  246. mirascope/api/settings.py +19 -1
  247. mirascope/llm/__init__.py +55 -8
  248. mirascope/llm/calls/__init__.py +2 -1
  249. mirascope/llm/calls/calls.py +3 -1
  250. mirascope/llm/calls/decorator.py +21 -7
  251. mirascope/llm/content/tool_call.py +6 -0
  252. mirascope/llm/content/tool_output.py +22 -5
  253. mirascope/llm/exceptions.py +284 -71
  254. mirascope/llm/formatting/__init__.py +19 -2
  255. mirascope/llm/formatting/format.py +219 -30
  256. mirascope/llm/formatting/output_parser.py +178 -0
  257. mirascope/llm/formatting/partial.py +80 -7
  258. mirascope/llm/formatting/primitives.py +192 -0
  259. mirascope/llm/formatting/types.py +21 -64
  260. mirascope/llm/mcp/__init__.py +2 -2
  261. mirascope/llm/mcp/mcp_client.py +130 -0
  262. mirascope/llm/messages/__init__.py +3 -0
  263. mirascope/llm/messages/_utils.py +34 -0
  264. mirascope/llm/models/__init__.py +5 -0
  265. mirascope/llm/models/models.py +137 -69
  266. mirascope/llm/{providers/base → models}/params.py +16 -37
  267. mirascope/llm/models/thinking_config.py +61 -0
  268. mirascope/llm/prompts/_utils.py +0 -32
  269. mirascope/llm/prompts/decorator.py +16 -5
  270. mirascope/llm/prompts/prompts.py +131 -68
  271. mirascope/llm/providers/__init__.py +18 -2
  272. mirascope/llm/providers/anthropic/__init__.py +3 -21
  273. mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
  274. mirascope/llm/providers/anthropic/_utils/beta_decode.py +22 -11
  275. mirascope/llm/providers/anthropic/_utils/beta_encode.py +75 -25
  276. mirascope/llm/providers/anthropic/_utils/decode.py +22 -11
  277. mirascope/llm/providers/anthropic/_utils/encode.py +82 -20
  278. mirascope/llm/providers/anthropic/_utils/errors.py +2 -2
  279. mirascope/llm/providers/anthropic/beta_provider.py +64 -18
  280. mirascope/llm/providers/anthropic/provider.py +91 -33
  281. mirascope/llm/providers/base/__init__.py +0 -2
  282. mirascope/llm/providers/base/_utils.py +55 -11
  283. mirascope/llm/providers/base/base_provider.py +116 -37
  284. mirascope/llm/providers/google/__init__.py +2 -17
  285. mirascope/llm/providers/google/_utils/__init__.py +2 -0
  286. mirascope/llm/providers/google/_utils/decode.py +37 -15
  287. mirascope/llm/providers/google/_utils/encode.py +127 -19
  288. mirascope/llm/providers/google/_utils/errors.py +3 -2
  289. mirascope/llm/providers/google/model_info.py +1 -0
  290. mirascope/llm/providers/google/provider.py +68 -19
  291. mirascope/llm/providers/mirascope/__init__.py +5 -0
  292. mirascope/llm/providers/mirascope/_utils.py +73 -0
  293. mirascope/llm/providers/mirascope/provider.py +349 -0
  294. mirascope/llm/providers/mlx/__init__.py +2 -17
  295. mirascope/llm/providers/mlx/_utils.py +8 -3
  296. mirascope/llm/providers/mlx/encoding/base.py +5 -2
  297. mirascope/llm/providers/mlx/encoding/transformers.py +5 -2
  298. mirascope/llm/providers/mlx/mlx.py +23 -6
  299. mirascope/llm/providers/mlx/provider.py +42 -13
  300. mirascope/llm/providers/ollama/__init__.py +1 -13
  301. mirascope/llm/providers/openai/_utils/errors.py +2 -2
  302. mirascope/llm/providers/openai/completions/__init__.py +2 -20
  303. mirascope/llm/providers/openai/completions/_utils/decode.py +14 -3
  304. mirascope/llm/providers/openai/completions/_utils/encode.py +35 -28
  305. mirascope/llm/providers/openai/completions/base_provider.py +40 -11
  306. mirascope/llm/providers/openai/provider.py +40 -10
  307. mirascope/llm/providers/openai/responses/__init__.py +1 -17
  308. mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
  309. mirascope/llm/providers/openai/responses/_utils/decode.py +21 -8
  310. mirascope/llm/providers/openai/responses/_utils/encode.py +59 -19
  311. mirascope/llm/providers/openai/responses/provider.py +56 -18
  312. mirascope/llm/providers/provider_id.py +1 -0
  313. mirascope/llm/providers/provider_registry.py +96 -19
  314. mirascope/llm/providers/together/__init__.py +1 -13
  315. mirascope/llm/responses/__init__.py +6 -1
  316. mirascope/llm/responses/_utils.py +102 -12
  317. mirascope/llm/responses/base_response.py +5 -2
  318. mirascope/llm/responses/base_stream_response.py +139 -45
  319. mirascope/llm/responses/response.py +2 -1
  320. mirascope/llm/responses/root_response.py +89 -17
  321. mirascope/llm/responses/stream_response.py +6 -9
  322. mirascope/llm/tools/decorator.py +17 -8
  323. mirascope/llm/tools/tool_schema.py +43 -10
  324. mirascope/llm/tools/toolkit.py +35 -27
  325. mirascope/llm/tools/tools.py +123 -30
  326. mirascope/ops/__init__.py +64 -109
  327. mirascope/ops/_internal/configuration.py +82 -31
  328. mirascope/ops/_internal/exporters/exporters.py +64 -11
  329. mirascope/ops/_internal/instrumentation/llm/common.py +530 -0
  330. mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
  331. mirascope/ops/_internal/instrumentation/llm/encode.py +1 -1
  332. mirascope/ops/_internal/instrumentation/llm/llm.py +116 -1243
  333. mirascope/ops/_internal/instrumentation/llm/model.py +1798 -0
  334. mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
  335. mirascope/ops/_internal/instrumentation/llm/serialize.py +300 -0
  336. mirascope/ops/_internal/protocols.py +83 -1
  337. mirascope/ops/_internal/traced_calls.py +4 -0
  338. mirascope/ops/_internal/traced_functions.py +141 -12
  339. mirascope/ops/_internal/tracing.py +78 -1
  340. mirascope/ops/_internal/utils.py +52 -4
  341. mirascope/ops/_internal/versioned_functions.py +54 -43
  342. {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/METADATA +14 -13
  343. mirascope-2.0.1.dist-info/RECORD +423 -0
  344. {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/licenses/LICENSE +1 -1
  345. mirascope/llm/formatting/_utils.py +0 -78
  346. mirascope/llm/mcp/client.py +0 -118
  347. mirascope/llm/providers/_missing_import_stubs.py +0 -49
  348. mirascope-2.0.0a5.dist-info/RECORD +0 -265
  349. {mirascope-2.0.0a5.dist-info → mirascope-2.0.1.dist-info}/WHEEL +0 -0
@@ -1,32 +1,173 @@
1
1
  """The `llm.format` decorator for defining response formats as classes."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import inspect
6
+ import json
7
+ from dataclasses import dataclass
8
+ from typing import Any, Generic, cast
4
9
 
10
+ from ..tools import FORMAT_TOOL_NAME, ToolFn, ToolParameterSchema, ToolSchema
5
11
  from ..types import NoneType
6
- from ._utils import default_formatting_instructions
7
- from .types import Format, FormattableT, FormattingMode, HasFormattingInstructions
12
+ from .output_parser import OutputParser, is_output_parser
13
+ from .primitives import create_wrapper_model, is_primitive_type
14
+ from .types import FormattableT, FormattingMode, HasFormattingInstructions
15
+
16
+ TOOL_MODE_INSTRUCTIONS = f"""Always respond to the user's query using the {FORMAT_TOOL_NAME} tool for structured output."""
17
+
18
+
19
+ JSON_MODE_INSTRUCTIONS = (
20
+ "Respond only with valid JSON that matches this exact schema:\n{json_schema}"
21
+ )
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class Format(Generic[FormattableT]):
26
+ """Class representing a structured output format for LLM responses.
27
+
28
+ A `Format` contains metadata needed to describe a structured output type
29
+ to the LLM, including the expected schema. This class is not instantiated directly,
30
+ but is created by calling `llm.format`, or is automatically generated by LLM
31
+ providers when a `Formattable` is passed to a call method.
32
+
33
+ Example:
34
+
35
+ ```python
36
+ from mirascope import llm
37
+
38
+ class Book:
39
+ title: str
40
+ author: str
41
+
42
+ print(llm.format(Book, mode="tool"))
43
+ ```
44
+ """
45
+
46
+ name: str
47
+ """The name of the response format."""
48
+
49
+ description: str | None
50
+ """A description of the response format, if available."""
51
+
52
+ schema: dict[str, object]
53
+ """JSON schema representation of the structured output format."""
54
+
55
+ mode: FormattingMode
56
+ """The decorator-provided mode of the response format.
57
+
58
+ Determines how the LLM call may be modified in order to extract the expected format.
59
+ """
60
+
61
+ formattable: type[FormattableT] | OutputParser[FormattableT]
62
+ """The formattable type or custom output parser.
63
+
64
+ Can be one of:
65
+ - type[BaseModel]: A Pydantic model class for structured output
66
+ - PrimitiveType: A primitive type (str, int, list, etc.) for simple output
67
+ - OutputParser[FormattableT]: A custom parser created with @llm.output_parser
68
+
69
+ The type determines how the response will be parsed in response.parse().
70
+ OutputParser uses Any for the response type since it works with any response.
71
+ """
72
+
73
+ @property
74
+ def formatting_instructions(self) -> str | None:
75
+ """The formatting instructions that will be added to the LLM system prompt.
76
+
77
+ If the format has a custom `OutputParser`, its formatting instructions will be used.
78
+ Otherwise, if the format type has a `formatting_instructions` class method,
79
+ the output of that call will be used. Otherwise, instructions may be
80
+ auto-generated based on the formatting mode.
81
+ """
82
+ if is_output_parser(self.formattable) or isinstance(
83
+ self.formattable, HasFormattingInstructions
84
+ ):
85
+ return self.formattable.formatting_instructions()
86
+
87
+ if self.mode == "tool":
88
+ return TOOL_MODE_INSTRUCTIONS
89
+ elif self.mode == "json":
90
+ json_schema = json.dumps(self.schema, indent=2)
91
+ instructions = JSON_MODE_INSTRUCTIONS.format(json_schema=json_schema)
92
+ return inspect.cleandoc(instructions)
93
+ elif self.mode == "parser":
94
+ return None # pragma: no cover
95
+
96
+ def create_tool_schema(
97
+ self,
98
+ ) -> ToolSchema[ToolFn[..., None]]:
99
+ """Generate a `ToolSchema` for parsing this format.
100
+
101
+ Returns:
102
+ `ToolSchema` for the format tool
103
+ """
104
+
105
+ schema_dict: dict[str, Any] = self.schema.copy()
106
+ schema_dict["type"] = "object"
107
+
108
+ properties = schema_dict.get("properties")
109
+ if not properties or not isinstance(properties, dict):
110
+ properties = {} # pragma: no cover
111
+ properties = cast(dict[str, Any], properties)
112
+ required: list[str] = list(properties.keys())
113
+
114
+ description = (
115
+ f"Use this tool to extract data in {self.name} format for a final response."
116
+ )
117
+ if self.description:
118
+ description += "\n" + self.description
119
+
120
+ parameters = ToolParameterSchema(
121
+ properties=properties,
122
+ required=required,
123
+ additionalProperties=False,
124
+ )
125
+ if "$defs" in schema_dict and isinstance(schema_dict["$defs"], dict):
126
+ parameters.defs = schema_dict["$defs"]
127
+
128
+ def _unused_format_fn() -> None:
129
+ raise TypeError(
130
+ "Format tool function should not be called."
131
+ ) # pragma: no cover
132
+
133
+ return ToolSchema(
134
+ fn=cast(ToolFn[..., None], _unused_format_fn),
135
+ name=FORMAT_TOOL_NAME,
136
+ description=description,
137
+ parameters=parameters,
138
+ strict=None, # Provider determines whether to use strict mode.
139
+ )
8
140
 
9
141
 
10
142
  def format(
11
- formattable: type[FormattableT] | None,
143
+ formattable: type[FormattableT] | OutputParser[FormattableT] | None,
12
144
  *,
13
145
  mode: FormattingMode,
14
146
  ) -> Format[FormattableT] | None:
15
- """Returns a `Format` that describes structured output for a Formattable type.
147
+ """Returns a `Format` that describes structured output or custom parsing.
148
+
149
+ This function converts a Formattable type (e.g. Pydantic `BaseModel` or primitive type)
150
+ or an `OutputParser` into a `Format` object that describes how the output should be
151
+ formatted and parsed. Calling `llm.format` is optional, as all the APIs that expect
152
+ a `Format` can also take the Formattable type or `OutputParser` directly. However,
153
+ calling `llm.format` is necessary in order to specify the formatting mode for
154
+ `BaseModel`/primitive types.
16
155
 
17
- This function converts a Formattable type (e.g. Pydantic BaseModel) into a `Format`
18
- object that describes how the object should be formatted. Calling `llm.format`
19
- is optional, as all the APIs that expect a `Format` can also take the Formattable
20
- type directly. However, calling `llm.format` is necessary in order to specify the
21
- formatting mode that will be used.
156
+ Primitive types are automatically wrapped in a `BaseModel` with an "output" field
157
+ for schema generation, then unwrapped during parsing.
22
158
 
23
159
  Args:
24
- mode: The format mode to use, one of the following:
160
+ formattable: The type or parser to format:
161
+ - BaseModel type: Uses structured output with JSON schema
162
+ - Primitive type: Wrapped in schema for structured output
163
+ - OutputParser: Uses custom parsing with instructions
164
+ mode: The format mode to use (required):
25
165
  - "strict": Use model strict structured outputs, or fail if unavailable.
26
166
  - "tool": Use forced tool calling with a special tool that represents a
27
167
  formatted response.
28
168
  - "json": Use provider json mode if available, or modify prompt to request
29
169
  json if not.
170
+ - "parser": Must be used for OutputParser types.
30
171
 
31
172
  The Formattable type may provide custom formatting instructions via a
32
173
  `formatting_instructions(cls)` classmethod. If that method is present, it will be called,
@@ -37,34 +178,44 @@ def format(
37
178
  you can add the `formatting_instructions` classmethod and have it return `None`.
38
179
 
39
180
  Returns:
40
- A `Format` object describing the Formattable type.
181
+ A `Format` object describing the format type or parser.
41
182
 
42
183
  Example:
43
- Using with an LLM call:
184
+ Using with a BaseModel:
44
185
 
45
186
  ```python
46
187
  from pydantic import BaseModel
47
-
48
188
  from mirascope import llm
49
189
 
50
-
51
190
  class Book(BaseModel):
52
191
  title: str
53
192
  author: str
54
193
 
55
194
  format = llm.format(Book, mode="strict")
56
195
 
57
- @llm.call(
58
- provider_id="openai",
59
- model_id="openai/gpt-5-mini",
60
- format=format,
61
- )
196
+ @llm.call("openai/gpt-5-mini", format=format)
62
197
  def recommend_book(genre: str):
63
198
  return f"Recommend a {genre} book."
64
199
 
65
200
  response = recommend_book("fantasy")
66
201
  book: Book = response.parse()
67
- print(f"{book.title} by {book.author}")
202
+ ```
203
+
204
+ Example:
205
+
206
+ Using with an `OutputParser`:
207
+
208
+ ```python
209
+ @llm.output_parser(
210
+ formatting_instructions="Return XML: <book><title>...</title></book>"
211
+ )
212
+ def parse_book_xml(response: llm.AnyResponse) -> Book:
213
+ # ... parsing logic ...
214
+ return Book(...)
215
+
216
+ @llm.call("openai/gpt-5-mini", format=parse_book_xml)
217
+ def recommend_book(genre: str):
218
+ return f"Recommend a {genre} book."
68
219
  ```
69
220
  """
70
221
  # TODO: Add caching or memoization to this function (e.g. functools.lru_cache)
@@ -72,33 +223,71 @@ def format(
72
223
  if formattable is None or formattable is NoneType:
73
224
  return None
74
225
 
226
+ if is_output_parser(formattable):
227
+ if mode != "parser":
228
+ raise ValueError(f"mode must be 'parser' for OutputParser, got '{mode}'")
229
+ return Format[Any](
230
+ name=formattable.__name__,
231
+ description=formattable.__doc__,
232
+ schema={},
233
+ mode="parser",
234
+ formattable=formattable,
235
+ )
236
+
237
+ if is_primitive_type(formattable):
238
+ wrapper_model = create_wrapper_model(formattable)
239
+ schema = wrapper_model.model_json_schema()
240
+ name = (
241
+ formattable.__name__
242
+ if hasattr(formattable, "__name__")
243
+ else str(formattable)
244
+ )
245
+
246
+ return Format[FormattableT](
247
+ name=name,
248
+ description=None,
249
+ schema=schema,
250
+ mode=mode,
251
+ formattable=formattable,
252
+ )
253
+
75
254
  description = None
76
255
  if formattable.__doc__:
77
256
  description = inspect.cleandoc(formattable.__doc__)
78
257
 
79
258
  schema = formattable.model_json_schema()
80
- formatting_instructions = None
81
- if isinstance(formattable, HasFormattingInstructions):
82
- formatting_instructions = formattable.formatting_instructions()
83
- else:
84
- formatting_instructions = default_formatting_instructions(schema, mode)
85
259
 
86
260
  return Format[FormattableT](
87
261
  name=formattable.__name__,
88
262
  description=description,
89
263
  schema=schema,
90
264
  mode=mode,
91
- formatting_instructions=formatting_instructions,
92
265
  formattable=formattable,
93
266
  )
94
267
 
95
268
 
96
269
  def resolve_format(
97
- formattable: type[FormattableT] | Format[FormattableT] | None,
270
+ formattable: (
271
+ type[FormattableT] | Format[FormattableT] | OutputParser[FormattableT] | None
272
+ ),
98
273
  default_mode: FormattingMode,
99
274
  ) -> Format[FormattableT] | None:
100
- """Resolve a `Format` (or None) from a possible `Format` or Formattable."""
275
+ """Resolve a `Format` (or None) from a possible `Format`, Formattable, or `OutputParser`.
276
+
277
+ Args:
278
+ formattable: The format specification:
279
+ - Format: Returned as-is
280
+ - BaseModel/primitive type: Converted to Format with default_mode
281
+ - OutputParser: Converted to Format with mode='parser'
282
+ default_mode: The mode to use for BaseModel/primitive types.
283
+
284
+ Returns:
285
+ A Format object or None.
286
+ """
101
287
  if isinstance(formattable, Format):
102
288
  return formattable
103
- else:
104
- return format(formattable, mode=default_mode)
289
+
290
+ if is_output_parser(formattable):
291
+ return format(formattable, mode="parser")
292
+
293
+ return format(formattable, mode=default_mode)
@@ -0,0 +1,178 @@
1
+ """The `llm.output_parser` decorator for creating custom output parsers."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
5
+ from typing_extensions import TypeIs
6
+
7
+ if TYPE_CHECKING:
8
+ from ..responses import AnyResponse
9
+
10
+ OutputT = TypeVar("OutputT")
11
+
12
+
13
+ class OutputParser(Generic[OutputT]):
14
+ """Represents a custom output parser created with @llm.output_parser.
15
+
16
+ This class wraps a parsing function and stores formatting instructions.
17
+ It is created by the @llm.output_parser decorator and used as a format
18
+ argument in LLM calls.
19
+
20
+ Unlike BaseModel and primitive type formats that use structured outputs
21
+ (JSON schema, tools, strict mode), OutputParser works with raw text responses
22
+ and custom parsing logic.
23
+
24
+ Example:
25
+ ```python
26
+ @llm.output_parser(
27
+ formatting_instructions="Return XML: <book><title>...</title></book>"
28
+ )
29
+ def parse_book_xml(response: llm.AnyResponse) -> Book:
30
+ text = "".join(part.text for part in response.texts)
31
+ root = ET.fromstring(text)
32
+ return Book(title=root.find("title").text, ...)
33
+
34
+ @llm.call("openai/gpt-4o", format=parse_book_xml)
35
+ def recommend_book(genre: str):
36
+ return f"Recommend a {genre} book."
37
+
38
+ response = recommend_book("fantasy")
39
+ book = response.parse() # Returns Book instance
40
+ ```
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ func: Callable[["AnyResponse"], OutputT],
46
+ formatting_instructions: str,
47
+ ) -> None:
48
+ """Initialize the OutputParser.
49
+
50
+ Args:
51
+ func: The parsing function that takes a Response and returns parsed output.
52
+ formatting_instructions: Instructions for the LLM on how to format output.
53
+ """
54
+ self.func = func
55
+ self._formatting_instructions = formatting_instructions
56
+ self.__name__ = func.__name__
57
+ self.__doc__ = func.__doc__
58
+
59
+ def formatting_instructions(self) -> str:
60
+ """Return the formatting instructions for the LLM.
61
+
62
+ These instructions are added to the system prompt to guide the LLM
63
+ on how to format its output for parsing.
64
+
65
+ Returns:
66
+ The formatting instructions string.
67
+ """
68
+ return self._formatting_instructions
69
+
70
+ def __call__(self, response: "AnyResponse") -> OutputT:
71
+ """Parse the response using the wrapped function.
72
+
73
+ Args:
74
+ response: The response object from the LLM call.
75
+
76
+ Returns:
77
+ The parsed output of type OutputT.
78
+
79
+ Raises:
80
+ Any exception raised by the wrapped parsing function.
81
+ """
82
+ return self.func(response)
83
+
84
+
85
+ def output_parser(
86
+ *,
87
+ formatting_instructions: str,
88
+ ) -> Callable[[Callable[["AnyResponse"], OutputT]], OutputParser[OutputT]]:
89
+ """Decorator to create an output parser for custom format parsing.
90
+
91
+ Use this decorator to create custom parsers for non-JSON formats like
92
+ XML, YAML, CSV, or any custom text structure. The decorated function
93
+ receives the full Response object and returns the parsed output.
94
+
95
+ This is the recommended way to handle custom output formats that don't
96
+ fit the JSON/BaseModel paradigm. The formatting instructions guide the
97
+ LLM on how to structure its output, and the parsing function extracts
98
+ the data you need.
99
+
100
+ Args:
101
+ formatting_instructions: Instructions for the LLM on how to format
102
+ the output. These will be added to the system prompt.
103
+
104
+ Returns:
105
+ Decorator that converts a function into an OutputParser.
106
+
107
+ Example:
108
+
109
+ XML parsing:
110
+ ```python
111
+ @llm.output_parser(
112
+ formatting_instructions='''
113
+ Return the book information in this XML structure:
114
+ <book>
115
+ <title>Book Title</title>
116
+ <author>Author Name</author>
117
+ <rating>5</rating>
118
+ </book>
119
+ '''
120
+ )
121
+ def parse_book_xml(response: llm.AnyResponse) -> Book:
122
+ import xml.etree.ElementTree as ET
123
+ text = "".join(part.text for part in response.texts)
124
+ root = ET.fromstring(text)
125
+ return Book(
126
+ title=root.find("title").text,
127
+ author=root.find("author").text,
128
+ rating=int(root.find("rating").text),
129
+ )
130
+ ```
131
+
132
+ Example:
133
+
134
+ CSV parsing:
135
+ ```python
136
+ @llm.output_parser(
137
+ formatting_instructions='''
138
+ Return book information as CSV format with header:
139
+ title,author,rating
140
+ Book 1,Author 1,5
141
+ Book 2,Author 2,4
142
+ '''
143
+ )
144
+ def parse_books_csv(response: llm.AnyResponse) -> list[Book]:
145
+ text = "".join(part.text for part in response.texts)
146
+ lines = text.strip().split('\\n')[1:] # Skip header
147
+ return [
148
+ Book(
149
+ title=line.split(',')[0].strip(),
150
+ author=line.split(',')[1].strip(),
151
+ rating=int(line.split(',')[2]),
152
+ )
153
+ for line in lines
154
+ ]
155
+ ```
156
+ """
157
+
158
+ def decorator(
159
+ func: Callable[["AnyResponse"], OutputT],
160
+ ) -> OutputParser[OutputT]:
161
+ return OutputParser(func, formatting_instructions)
162
+
163
+ return decorator
164
+
165
+
166
+ def is_output_parser(obj: Any) -> TypeIs[OutputParser[Any]]: # noqa: ANN401
167
+ """Check if an object is an OutputParser.
168
+
169
+ This is a type guard function that narrows the type of `obj` to
170
+ `OutputParser[Any, Any]` when it returns True.
171
+
172
+ Args:
173
+ obj: The object to check.
174
+
175
+ Returns:
176
+ True if the object is an OutputParser instance, False otherwise.
177
+ """
178
+ return isinstance(obj, OutputParser)
@@ -8,10 +8,16 @@ serves as an acknowledgment of the original author's contribution to this projec
8
8
  --------------------------------------------------------------------------------
9
9
  """
10
10
 
11
- from typing import Generic, NoReturn
11
+ import inspect
12
+ from typing import Any, Generic, NoReturn, Union, cast, get_args, get_origin
13
+
14
+ from pydantic import BaseModel, create_model
12
15
 
13
16
  from .format import FormattableT
14
17
 
18
+ # Cache for generated partial models to avoid recreation
19
+ _partial_model_cache: dict[type[Any], type[Any]] = {}
20
+
15
21
 
16
22
  class Partial(Generic[FormattableT]):
17
23
  """Generate a new class with all attributes optionals.
@@ -34,7 +40,9 @@ class Partial(Generic[FormattableT]):
34
40
  Raises:
35
41
  TypeError: Direct instantiation not allowed.
36
42
  """
37
- raise TypeError("Cannot instantiate abstract Partial class.")
43
+ raise TypeError(
44
+ "Cannot instantiate abstract Partial class."
45
+ ) # pragma: no cover
38
46
 
39
47
  def __init_subclass__(
40
48
  cls,
@@ -46,13 +54,78 @@ class Partial(Generic[FormattableT]):
46
54
  Raises:
47
55
  TypeError: Subclassing not allowed.
48
56
  """
49
- raise TypeError(f"Cannot subclass {cls.__module__}.Partial")
57
+ raise TypeError(f"Cannot subclass {cls.__module__}.Partial") # pragma: no cover
50
58
 
51
59
  def __class_getitem__(
52
60
  cls,
53
61
  wrapped_class: type[FormattableT],
54
62
  ) -> type[FormattableT]:
55
- """Convert model to a partial model with all fields being optionals."""
56
- # TODO: Implement proper partial model generation
57
- # For now, return the original class to avoid import errors
58
- return wrapped_class
63
+ """Convert model to a partial model with all fields being optionals.
64
+
65
+ Recursively converts all fields in a Pydantic BaseModel to optional,
66
+ handling nested models and generic types like list[Book].
67
+
68
+ Args:
69
+ wrapped_class: The BaseModel class to make partial
70
+
71
+ Returns:
72
+ A new BaseModel class with all fields optional (or original if not BaseModel)
73
+
74
+ Example:
75
+ >>> class Author(BaseModel):
76
+ ... first_name: str
77
+ ... last_name: str
78
+ >>> class Book(BaseModel):
79
+ ... title: str
80
+ ... author: Author
81
+ >>> PartialBook = Partial[Book]
82
+ >>> partial = PartialBook(title="The Name")
83
+ >>> partial.author # None
84
+ """
85
+ # Return non-BaseModel types unchanged
86
+ if not (
87
+ inspect.isclass(wrapped_class) and issubclass(wrapped_class, BaseModel)
88
+ ):
89
+ return wrapped_class
90
+
91
+ # Check cache to avoid regenerating
92
+ if wrapped_class in _partial_model_cache:
93
+ return cast(type[FormattableT], _partial_model_cache[wrapped_class])
94
+
95
+ # Recursively make all fields optional
96
+ partial_fields: dict[str, Any] = {}
97
+ for field_name, field_info in wrapped_class.model_fields.items():
98
+ field_type = field_info.annotation
99
+
100
+ # Recursively handle nested BaseModel fields
101
+ if inspect.isclass(field_type) and issubclass(field_type, BaseModel):
102
+ field_type = Partial[field_type]
103
+
104
+ # Handle generic types with BaseModel args (e.g., list[Book], dict[str, Book])
105
+ origin = get_origin(field_type)
106
+ if origin is not None:
107
+ args = get_args(field_type)
108
+ # Recursively convert BaseModel args to partial
109
+ new_args = tuple(
110
+ Partial[arg]
111
+ if inspect.isclass(arg) and issubclass(arg, BaseModel)
112
+ else arg
113
+ for arg in args
114
+ )
115
+ # Reconstruct generic type with new args
116
+ if new_args != args:
117
+ field_type = origin[new_args]
118
+
119
+ # Make field optional with None default
120
+ optional_type = Union[field_type, None] # noqa: UP007
121
+ partial_fields[field_name] = (optional_type, None)
122
+
123
+ # Create new model with "Partial" prefix
124
+ partial_model = create_model(
125
+ f"Partial{wrapped_class.__name__}", __base__=BaseModel, **partial_fields
126
+ )
127
+
128
+ # Cache the generated model
129
+ _partial_model_cache[wrapped_class] = partial_model
130
+
131
+ return cast(type[FormattableT], partial_model)