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
@@ -0,0 +1,300 @@
1
+ """Mirascope-specific serialization for span attributes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Sequence
7
+ from typing import TYPE_CHECKING, Any, Protocol
8
+
9
+ from .....llm.content import (
10
+ Audio,
11
+ Base64ImageSource,
12
+ Document,
13
+ Image,
14
+ Text,
15
+ Thought,
16
+ ToolCall,
17
+ ToolOutput,
18
+ )
19
+ from .....llm.content.document import Base64DocumentSource, TextDocumentSource
20
+ from .....llm.messages import AssistantMessage, Message, SystemMessage, UserMessage
21
+ from .....llm.responses.usage import Usage
22
+ from ...utils import json_dumps
23
+ from .cost import calculate_cost_async, calculate_cost_sync
24
+
25
+ if TYPE_CHECKING:
26
+ from opentelemetry.util.types import AttributeValue
27
+
28
+ from .....llm.responses.root_response import RootResponse
29
+ from .....llm.types import Jsonable
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class SpanProtocol(Protocol):
35
+ """Protocol for span objects that support setting attributes."""
36
+
37
+ def set(self, **attributes: AttributeValue) -> None:
38
+ """Set attributes on the span."""
39
+ ...
40
+
41
+
42
+ def _serialize_content_part(
43
+ part: Text | ToolCall | Thought | Image | Audio | Document | ToolOutput[Jsonable],
44
+ ) -> dict[str, Any]:
45
+ """Serialize a single content part to a dict matching the Mirascope dataclass structure."""
46
+ if isinstance(part, Text):
47
+ return {"type": "text", "text": part.text}
48
+ elif isinstance(part, ToolCall):
49
+ return {
50
+ "type": "tool_call",
51
+ "id": part.id,
52
+ "name": part.name,
53
+ "args": part.args,
54
+ }
55
+ elif isinstance(part, Thought):
56
+ return {"type": "thought", "thought": part.thought}
57
+ elif isinstance(part, ToolOutput):
58
+ return {
59
+ "type": "tool_output",
60
+ "id": part.id,
61
+ "name": part.name,
62
+ "result": part.result,
63
+ }
64
+ elif isinstance(part, Image):
65
+ if isinstance(part.source, Base64ImageSource):
66
+ return {
67
+ "type": "image",
68
+ "source": {
69
+ "type": "base64_image_source",
70
+ "mime_type": part.source.mime_type,
71
+ "data": part.source.data,
72
+ },
73
+ }
74
+ else: # URLImageSource
75
+ return {
76
+ "type": "image",
77
+ "source": {"type": "url_image_source", "url": part.source.url},
78
+ }
79
+ elif isinstance(part, Audio):
80
+ return {
81
+ "type": "audio",
82
+ "source": {
83
+ "type": "base64_audio_source",
84
+ "mime_type": part.source.mime_type,
85
+ "data": part.source.data,
86
+ },
87
+ }
88
+ elif isinstance(part, Document):
89
+ # Document has multiple source types - serialize based on actual type
90
+ if isinstance(part.source, Base64DocumentSource):
91
+ return {
92
+ "type": "document",
93
+ "source": {
94
+ "type": "base64_document_source",
95
+ "data": part.source.data,
96
+ "media_type": part.source.media_type,
97
+ },
98
+ }
99
+ elif isinstance(part.source, TextDocumentSource):
100
+ return {
101
+ "type": "document",
102
+ "source": {
103
+ "type": "text_document_source",
104
+ "data": part.source.data,
105
+ "media_type": part.source.media_type,
106
+ },
107
+ }
108
+ else: # URLDocumentSource
109
+ return {
110
+ "type": "document",
111
+ "source": {
112
+ "type": "url_document_source",
113
+ "url": part.source.url,
114
+ },
115
+ }
116
+ return {"type": "unknown"} # pragma: no cover
117
+
118
+
119
+ def _serialize_message(message: Message) -> dict[str, Any]:
120
+ """Serialize a Message to a dict matching the Mirascope dataclass structure."""
121
+ if isinstance(message, SystemMessage):
122
+ return {
123
+ "role": "system",
124
+ "content": _serialize_content_part(message.content),
125
+ }
126
+ elif isinstance(message, UserMessage):
127
+ return {
128
+ "role": "user",
129
+ "content": [_serialize_content_part(p) for p in message.content],
130
+ "name": message.name,
131
+ }
132
+ elif isinstance(message, AssistantMessage):
133
+ return {
134
+ "role": "assistant",
135
+ "content": [_serialize_content_part(p) for p in message.content],
136
+ "name": message.name,
137
+ }
138
+ return {"role": "unknown"} # pragma: no cover
139
+
140
+
141
+ def serialize_mirascope_messages(messages: Sequence[Message]) -> str:
142
+ """Serialize input messages to JSON for span attributes."""
143
+ return json_dumps([_serialize_message(m) for m in messages])
144
+
145
+
146
+ def serialize_mirascope_content(
147
+ content: Sequence[Text | ToolCall | Thought],
148
+ ) -> str:
149
+ """Serialize response content to JSON for span attributes."""
150
+ return json_dumps([_serialize_content_part(p) for p in content])
151
+
152
+
153
+ def serialize_mirascope_usage(usage: Usage | None) -> AttributeValue | None:
154
+ """Serialize response usage to JSON for span attributes. Returns None if usage is None."""
155
+ if usage is None:
156
+ return None
157
+ return json_dumps(
158
+ {
159
+ "input_tokens": usage.input_tokens,
160
+ "output_tokens": usage.output_tokens,
161
+ "cache_read_tokens": usage.cache_read_tokens,
162
+ "cache_write_tokens": usage.cache_write_tokens,
163
+ "reasoning_tokens": usage.reasoning_tokens,
164
+ "total_tokens": usage.total_tokens,
165
+ }
166
+ )
167
+
168
+
169
+ def serialize_mirascope_cost(
170
+ input_cost: float,
171
+ output_cost: float,
172
+ total_cost: float,
173
+ cache_read_cost: float | None = None,
174
+ cache_write_cost: float | None = None,
175
+ ) -> str:
176
+ """Serialize cost to JSON for span attributes.
177
+
178
+ All costs are in centicents (1 centicent = $0.0001).
179
+ Consumers can divide by 10,000 to get dollar amounts.
180
+ """
181
+ return json_dumps(
182
+ {
183
+ "input_cost": input_cost,
184
+ "output_cost": output_cost,
185
+ "cache_read_cost": cache_read_cost,
186
+ "cache_write_cost": cache_write_cost,
187
+ "total_cost": total_cost,
188
+ }
189
+ )
190
+
191
+
192
+ def attach_mirascope_response(
193
+ span: SpanProtocol, response: RootResponse[Any, Any]
194
+ ) -> None:
195
+ """Attach Mirascope-specific response attributes to a span.
196
+
197
+ Sets the following attributes:
198
+ - mirascope.trace.output: Pretty-printed response
199
+ - mirascope.response.messages: Serialized input messages (excluding final assistant message)
200
+ - mirascope.response.content: Serialized response content
201
+ - mirascope.response.usage: Serialized usage (if available)
202
+ - mirascope.response.cost: Serialized cost (if available)
203
+ """
204
+ span.set(
205
+ **{
206
+ "mirascope.response.provider_id": response.provider_id,
207
+ "mirascope.response.model_id": response.model_id,
208
+ "mirascope.trace.output": response.pretty(),
209
+ "mirascope.response.messages": serialize_mirascope_messages(
210
+ response.messages[:-1]
211
+ ),
212
+ "mirascope.response.content": serialize_mirascope_content(response.content),
213
+ }
214
+ )
215
+ if (usage_json := serialize_mirascope_usage(response.usage)) is not None:
216
+ span.set(**{"mirascope.response.usage": usage_json})
217
+ logger.debug("Attached usage to span")
218
+ else:
219
+ logger.debug("No usage available, skipping cost calculation")
220
+
221
+ # Calculate and attach cost if usage is available
222
+ if response.usage is not None:
223
+ logger.debug("Attempting cost calculation (sync)")
224
+ cost = calculate_cost_sync(
225
+ response.provider_id, response.model_id, response.usage
226
+ )
227
+ if cost is not None:
228
+ span.set(
229
+ **{
230
+ "mirascope.response.cost": serialize_mirascope_cost(
231
+ input_cost=cost.input_cost_centicents,
232
+ output_cost=cost.output_cost_centicents,
233
+ total_cost=cost.total_cost_centicents,
234
+ cache_read_cost=cost.cache_read_cost_centicents,
235
+ cache_write_cost=cost.cache_write_cost_centicents,
236
+ )
237
+ }
238
+ )
239
+ logger.debug(
240
+ "Attached cost to span: total=%s centicents", cost.total_cost_centicents
241
+ )
242
+ else:
243
+ logger.debug("Cost calculation returned None, not attaching cost to span")
244
+
245
+
246
+ async def attach_mirascope_response_async(
247
+ span: SpanProtocol, response: RootResponse[Any, Any]
248
+ ) -> None:
249
+ """Attach Mirascope-specific response attributes to a span (async version).
250
+
251
+ Sets the following attributes:
252
+ - mirascope.trace.output: Pretty-printed response
253
+ - mirascope.response.messages: Serialized input messages (excluding final assistant message)
254
+ - mirascope.response.content: Serialized response content
255
+ - mirascope.response.usage: Serialized usage (if available)
256
+ - mirascope.response.cost: Serialized cost (if available)
257
+ """
258
+ span.set(
259
+ **{
260
+ "mirascope.response.provider_id": response.provider_id,
261
+ "mirascope.response.model_id": response.model_id,
262
+ "mirascope.trace.output": response.pretty(),
263
+ "mirascope.response.messages": serialize_mirascope_messages(
264
+ response.messages[:-1]
265
+ ),
266
+ "mirascope.response.content": serialize_mirascope_content(response.content),
267
+ }
268
+ )
269
+ if (usage_json := serialize_mirascope_usage(response.usage)) is not None:
270
+ span.set(**{"mirascope.response.usage": usage_json})
271
+ logger.debug("Attached usage to span (async)")
272
+ else:
273
+ logger.debug("No usage available, skipping cost calculation (async)")
274
+
275
+ # Calculate and attach cost if usage is available (async)
276
+ if response.usage is not None:
277
+ logger.debug("Attempting cost calculation (async)")
278
+ cost = await calculate_cost_async(
279
+ response.provider_id, response.model_id, response.usage
280
+ )
281
+ if cost is not None:
282
+ span.set(
283
+ **{
284
+ "mirascope.response.cost": serialize_mirascope_cost(
285
+ input_cost=cost.input_cost_centicents,
286
+ output_cost=cost.output_cost_centicents,
287
+ total_cost=cost.total_cost_centicents,
288
+ cache_read_cost=cost.cache_read_cost_centicents,
289
+ cache_write_cost=cost.cache_write_cost_centicents,
290
+ )
291
+ }
292
+ )
293
+ logger.debug(
294
+ "Attached cost to span (async): total=%s centicents",
295
+ cost.total_cost_centicents,
296
+ )
297
+ else:
298
+ logger.debug(
299
+ "Cost calculation returned None, not attaching cost to span (async)"
300
+ )
@@ -3,12 +3,15 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import inspect
6
- from typing import Protocol
6
+ from typing import TYPE_CHECKING, Protocol
7
7
  from typing_extensions import TypeIs
8
8
 
9
9
  from ...llm.context import Context, DepsT
10
10
  from .types import P, R
11
11
 
12
+ if TYPE_CHECKING:
13
+ from .spans import Span
14
+
12
15
 
13
16
  class SyncFunction(Protocol[P, R]):
14
17
  """Protocol for synchronous callable functions."""
@@ -26,6 +29,30 @@ class AsyncFunction(Protocol[P, R]):
26
29
  ... # pragma: no cover
27
30
 
28
31
 
32
+ class SyncSpanFunction(Protocol[P, R]):
33
+ """Protocol for synchronous functions that receive injected Span.
34
+
35
+ Functions matching this protocol have `trace_ctx: Span` as their first
36
+ parameter. The trace decorator will inject the span automatically.
37
+ """
38
+
39
+ def __call__(self, trace_ctx: Span, *args: P.args, **kwargs: P.kwargs) -> R:
40
+ """The function receives a Span as first parameter."""
41
+ ... # pragma: no cover
42
+
43
+
44
+ class AsyncSpanFunction(Protocol[P, R]):
45
+ """Protocol for asynchronous functions that receive injected Span.
46
+
47
+ Functions matching this protocol have `trace_ctx: Span` as their first
48
+ parameter. The trace decorator will inject the span automatically.
49
+ """
50
+
51
+ async def __call__(self, trace_ctx: Span, *args: P.args, **kwargs: P.kwargs) -> R:
52
+ """The function receives a Span as first parameter."""
53
+ ... # pragma: no cover
54
+
55
+
29
56
  class SyncContextFunction(Protocol[P, DepsT, R]):
30
57
  """Protocol for synchronous callable functions with Context parameter."""
31
58
 
@@ -49,3 +76,58 @@ def fn_is_async(
49
76
  ) -> TypeIs[AsyncFunction[P, R]]:
50
77
  """Type check to determine if a given function is asynchronous."""
51
78
  return inspect.iscoroutinefunction(fn) or inspect.isasyncgenfunction(fn)
79
+
80
+
81
+ def fn_wants_span(
82
+ fn: (
83
+ SyncFunction[P, R]
84
+ | AsyncFunction[P, R]
85
+ | SyncSpanFunction[P, R]
86
+ | AsyncSpanFunction[P, R]
87
+ ),
88
+ ) -> TypeIs[SyncSpanFunction[P, R] | AsyncSpanFunction[P, R]]:
89
+ """Check if function wants Span injection as first parameter.
90
+
91
+ Returns True if the function has a first parameter named `trace_ctx`
92
+ with type annotation `Span`.
93
+ """
94
+ # Import here to avoid circular imports
95
+ from .spans import Span
96
+
97
+ try:
98
+ sig = inspect.signature(fn)
99
+ except (ValueError, TypeError):
100
+ return False
101
+
102
+ params = list(sig.parameters.values())
103
+ if not params:
104
+ return False
105
+
106
+ first_param = params[0]
107
+ if first_param.name != "trace_ctx":
108
+ return False
109
+
110
+ # Check annotation
111
+ annotation = first_param.annotation
112
+ if annotation is inspect.Parameter.empty:
113
+ return False
114
+
115
+ # Handle string annotations (forward references)
116
+ # The annotation could be "Span", "ops.Span", "mirascope.ops.Span", etc.
117
+ if isinstance(annotation, str):
118
+ return annotation == "Span" or annotation.endswith(".Span")
119
+
120
+ # Check by identity first
121
+ if annotation is Span:
122
+ return True
123
+
124
+ # Fallback: check by class name and module for robustness
125
+ # This handles cases where the same class might have different identities
126
+ # due to module reloading or import issues in test environments
127
+ if isinstance(annotation, type):
128
+ return (
129
+ annotation.__name__ == "Span"
130
+ and annotation.__module__ == "mirascope.ops._internal.spans"
131
+ )
132
+
133
+ return False
@@ -22,8 +22,10 @@ from ...llm.responses import (
22
22
  from ...llm.types import P
23
23
  from .protocols import (
24
24
  AsyncFunction,
25
+ AsyncSpanFunction,
25
26
  R,
26
27
  SyncFunction,
28
+ SyncSpanFunction,
27
29
  )
28
30
  from .traced_functions import (
29
31
  AsyncTrace,
@@ -47,6 +49,8 @@ def is_call_type(
47
49
  fn: (
48
50
  SyncFunction[P, R]
49
51
  | AsyncFunction[P, R]
52
+ | SyncSpanFunction[P, R]
53
+ | AsyncSpanFunction[P, R]
50
54
  | ContextCall[P, DepsT, FormattableT]
51
55
  | AsyncContextCall[P, DepsT, FormattableT]
52
56
  | Call[P, FormattableT]
@@ -6,26 +6,31 @@ from abc import ABC, abstractmethod
6
6
  from collections.abc import Generator
7
7
  from contextlib import contextmanager
8
8
  from dataclasses import dataclass, field
9
- from typing import (
10
- Any,
11
- Generic,
12
- Literal,
13
- TypeVar,
14
- )
9
+ from typing import Any, Generic, Literal, TypeVar
15
10
 
16
11
  from opentelemetry.util.types import AttributeValue
17
12
 
13
+ from ...api.client import get_async_client, get_sync_client
18
14
  from ...llm.context import Context, DepsT
19
15
  from ...llm.responses.root_response import RootResponse
16
+ from .instrumentation.llm.serialize import attach_mirascope_response
20
17
  from .protocols import (
21
18
  AsyncContextFunction,
22
19
  AsyncFunction,
20
+ AsyncSpanFunction,
23
21
  SyncContextFunction,
24
22
  SyncFunction,
23
+ SyncSpanFunction,
25
24
  )
26
25
  from .spans import Span
27
26
  from .types import Jsonable, P, R
28
- from .utils import PrimitiveType, extract_arguments, get_qualified_name, json_dumps
27
+ from .utils import (
28
+ PrimitiveType,
29
+ extract_arguments,
30
+ get_original_fn,
31
+ get_qualified_name,
32
+ json_dumps,
33
+ )
29
34
 
30
35
  FunctionT = TypeVar(
31
36
  "FunctionT",
@@ -46,6 +51,7 @@ def record_result_to_span(span: Span, result: object) -> None:
46
51
  output: str | int | float | bool = result
47
52
  elif isinstance(result, RootResponse):
48
53
  output = result.pretty()
54
+ attach_mirascope_response(span, result)
49
55
  else:
50
56
  try:
51
57
  output = json_dumps(result)
@@ -91,7 +97,16 @@ class Trace(_BaseTrace[R]):
91
97
  metadata: dict[str, Jsonable] | None = None,
92
98
  ) -> None:
93
99
  """Annotates the current trace span."""
94
- raise NotImplementedError("Trace.annotate not yet implemented")
100
+ if self.span.span_id is None or self.span.trace_id is None:
101
+ return
102
+
103
+ get_sync_client().annotations.create(
104
+ otel_span_id=self.span.span_id,
105
+ otel_trace_id=self.span.trace_id,
106
+ label=label,
107
+ reasoning=reasoning,
108
+ metadata=metadata,
109
+ )
95
110
 
96
111
  def tag(self, *tags: str) -> None:
97
112
  """Adds given tags to the current trace span."""
@@ -113,12 +128,21 @@ class AsyncTrace(_BaseTrace[R]):
113
128
  async def annotate(
114
129
  self,
115
130
  *,
116
- label: str | None = None,
117
- data: dict[str, Jsonable] | None = None,
131
+ label: Literal["pass", "fail"],
118
132
  reasoning: str | None = None,
133
+ metadata: dict[str, Jsonable] | None = None,
119
134
  ) -> None:
120
135
  """Annotates the current trace span."""
121
- raise NotImplementedError("AsyncTrace.annotate not yet implemented")
136
+ if self.span.span_id is None or self.span.trace_id is None:
137
+ return
138
+
139
+ await get_async_client().annotations.create(
140
+ otel_span_id=self.span.span_id,
141
+ otel_trace_id=self.span.trace_id,
142
+ label=label,
143
+ reasoning=reasoning,
144
+ metadata=metadata,
145
+ )
122
146
 
123
147
  async def tag(self, *tags: str) -> None:
124
148
  """Adds given tags to the current trace span."""
@@ -154,7 +178,8 @@ class _BaseFunction(Generic[P, R, FunctionT], ABC):
154
178
  def __post_init__(self) -> None:
155
179
  """Initialize additional attributes after dataclass init."""
156
180
  self._qualified_name = get_qualified_name(self.fn)
157
- self._module_name = getattr(self.fn, "__module__", "")
181
+ original_fn = get_original_fn(self.fn)
182
+ self._module_name = getattr(original_fn, "__module__", "")
158
183
 
159
184
 
160
185
  @dataclass(kw_only=True)
@@ -392,3 +417,107 @@ class AsyncTracedContextFunction(BaseTracedAsyncContextFunction[P, DepsT, R]):
392
417
  result = await self.fn(ctx, *args, **kwargs)
393
418
  record_result_to_span(span, result)
394
419
  return AsyncTrace(result=result, span=span)
420
+
421
+
422
+ @dataclass(kw_only=True)
423
+ class BaseSyncTracedSpanFunction(_BaseTracedFunction[P, R, SyncSpanFunction[P, R]]):
424
+ """Abstract base class for synchronous traced span function wrappers."""
425
+
426
+ _is_async: bool = field(default=False, init=False)
427
+ """Whether the wrapped function is asynchronous."""
428
+
429
+ @abstractmethod
430
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
431
+ """Returns the result of the traced function directly."""
432
+ ...
433
+
434
+ @abstractmethod
435
+ def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> Trace[R]:
436
+ """Returns the trace containing the function result and span info."""
437
+ ...
438
+
439
+
440
+ @dataclass(kw_only=True)
441
+ class TracedSpanFunction(BaseSyncTracedSpanFunction[P, R]):
442
+ """Wrapper for synchronous functions that receive Span as first parameter.
443
+
444
+ The external interface does NOT include `trace_ctx` - it is injected
445
+ automatically by the decorator when calling the inner function.
446
+
447
+ Example:
448
+ ```python
449
+ @ops.trace
450
+ def my_fn(trace_ctx: Span, arg: str) -> str:
451
+ trace_ctx.info(f"Processing: {arg}")
452
+ return arg.upper()
453
+
454
+ # Call without trace_ctx - it's injected
455
+ result = my_fn("hello") # Returns "HELLO"
456
+ ```
457
+ """
458
+
459
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
460
+ """Returns the result of the traced function directly."""
461
+ with self._span(*args, **kwargs) as span:
462
+ result = self.fn(span, *args, **kwargs)
463
+ record_result_to_span(span, result)
464
+ return result
465
+
466
+ def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> Trace[R]:
467
+ """Returns the trace containing the function result and span info."""
468
+ with self._span(*args, **kwargs) as span:
469
+ result = self.fn(span, *args, **kwargs)
470
+ record_result_to_span(span, result)
471
+ return Trace(result=result, span=span)
472
+
473
+
474
+ @dataclass(kw_only=True)
475
+ class BaseAsyncTracedSpanFunction(_BaseTracedFunction[P, R, AsyncSpanFunction[P, R]]):
476
+ """Abstract base class for asynchronous traced span function wrappers."""
477
+
478
+ _is_async: bool = field(default=True, init=False)
479
+ """Whether the wrapped function is asynchronous."""
480
+
481
+ @abstractmethod
482
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
483
+ """Returns the result of the traced function directly."""
484
+ ...
485
+
486
+ @abstractmethod
487
+ async def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> AsyncTrace[R]:
488
+ """Returns the trace containing the function result and span info."""
489
+ ...
490
+
491
+
492
+ @dataclass(kw_only=True)
493
+ class AsyncTracedSpanFunction(BaseAsyncTracedSpanFunction[P, R]):
494
+ """Wrapper for asynchronous functions that receive Span as first parameter.
495
+
496
+ The external interface does NOT include `trace_ctx` - it is injected
497
+ automatically by the decorator when calling the inner function.
498
+
499
+ Example:
500
+ ```python
501
+ @ops.trace
502
+ async def my_fn(trace_ctx: Span, arg: str) -> str:
503
+ trace_ctx.info(f"Processing: {arg}")
504
+ return arg.upper()
505
+
506
+ # Call without trace_ctx - it's injected
507
+ result = await my_fn("hello") # Returns "HELLO"
508
+ ```
509
+ """
510
+
511
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
512
+ """Returns the result of the traced function directly."""
513
+ with self._span(*args, **kwargs) as span:
514
+ result = await self.fn(span, *args, **kwargs)
515
+ record_result_to_span(span, result)
516
+ return result
517
+
518
+ async def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> AsyncTrace[R]:
519
+ """Returns the trace containing the function result and span info."""
520
+ with self._span(*args, **kwargs) as span:
521
+ result = await self.fn(span, *args, **kwargs)
522
+ record_result_to_span(span, result)
523
+ return AsyncTrace(result=result, span=span)