mirascope 1.0.5__py3-none-any.whl → 2.1.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 (632) hide show
  1. mirascope/__init__.py +6 -6
  2. mirascope/_stubs.py +384 -0
  3. mirascope/_utils.py +34 -0
  4. mirascope/api/__init__.py +14 -0
  5. mirascope/api/_generated/README.md +207 -0
  6. mirascope/api/_generated/__init__.py +444 -0
  7. mirascope/api/_generated/annotations/__init__.py +33 -0
  8. mirascope/api/_generated/annotations/client.py +506 -0
  9. mirascope/api/_generated/annotations/raw_client.py +1414 -0
  10. mirascope/api/_generated/annotations/types/__init__.py +31 -0
  11. mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
  12. mirascope/api/_generated/annotations/types/annotations_create_response.py +48 -0
  13. mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
  14. mirascope/api/_generated/annotations/types/annotations_get_response.py +48 -0
  15. mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
  16. mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
  17. mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
  18. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +50 -0
  19. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
  20. mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
  21. mirascope/api/_generated/annotations/types/annotations_update_response.py +48 -0
  22. mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
  23. mirascope/api/_generated/api_keys/__init__.py +17 -0
  24. mirascope/api/_generated/api_keys/client.py +530 -0
  25. mirascope/api/_generated/api_keys/raw_client.py +1236 -0
  26. mirascope/api/_generated/api_keys/types/__init__.py +15 -0
  27. mirascope/api/_generated/api_keys/types/api_keys_create_response.py +28 -0
  28. mirascope/api/_generated/api_keys/types/api_keys_get_response.py +27 -0
  29. mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
  30. mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +27 -0
  31. mirascope/api/_generated/client.py +211 -0
  32. mirascope/api/_generated/core/__init__.py +52 -0
  33. mirascope/api/_generated/core/api_error.py +23 -0
  34. mirascope/api/_generated/core/client_wrapper.py +46 -0
  35. mirascope/api/_generated/core/datetime_utils.py +28 -0
  36. mirascope/api/_generated/core/file.py +67 -0
  37. mirascope/api/_generated/core/force_multipart.py +16 -0
  38. mirascope/api/_generated/core/http_client.py +543 -0
  39. mirascope/api/_generated/core/http_response.py +55 -0
  40. mirascope/api/_generated/core/jsonable_encoder.py +100 -0
  41. mirascope/api/_generated/core/pydantic_utilities.py +255 -0
  42. mirascope/api/_generated/core/query_encoder.py +58 -0
  43. mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
  44. mirascope/api/_generated/core/request_options.py +35 -0
  45. mirascope/api/_generated/core/serialization.py +276 -0
  46. mirascope/api/_generated/docs/__init__.py +4 -0
  47. mirascope/api/_generated/docs/client.py +91 -0
  48. mirascope/api/_generated/docs/raw_client.py +178 -0
  49. mirascope/api/_generated/environment.py +9 -0
  50. mirascope/api/_generated/environments/__init__.py +23 -0
  51. mirascope/api/_generated/environments/client.py +649 -0
  52. mirascope/api/_generated/environments/raw_client.py +1567 -0
  53. mirascope/api/_generated/environments/types/__init__.py +25 -0
  54. mirascope/api/_generated/environments/types/environments_create_response.py +24 -0
  55. mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
  56. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
  57. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_models_item.py +22 -0
  58. mirascope/api/_generated/environments/types/environments_get_response.py +24 -0
  59. mirascope/api/_generated/environments/types/environments_list_response_item.py +24 -0
  60. mirascope/api/_generated/environments/types/environments_update_response.py +24 -0
  61. mirascope/api/_generated/errors/__init__.py +25 -0
  62. mirascope/api/_generated/errors/bad_request_error.py +14 -0
  63. mirascope/api/_generated/errors/conflict_error.py +14 -0
  64. mirascope/api/_generated/errors/forbidden_error.py +11 -0
  65. mirascope/api/_generated/errors/internal_server_error.py +10 -0
  66. mirascope/api/_generated/errors/not_found_error.py +11 -0
  67. mirascope/api/_generated/errors/payment_required_error.py +15 -0
  68. mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
  69. mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
  70. mirascope/api/_generated/errors/unauthorized_error.py +11 -0
  71. mirascope/api/_generated/functions/__init__.py +39 -0
  72. mirascope/api/_generated/functions/client.py +647 -0
  73. mirascope/api/_generated/functions/raw_client.py +1890 -0
  74. mirascope/api/_generated/functions/types/__init__.py +53 -0
  75. mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
  76. mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
  77. mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
  78. mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
  79. mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
  80. mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
  81. mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
  82. mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
  83. mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
  84. mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
  85. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
  86. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
  87. mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
  88. mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
  89. mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
  90. mirascope/api/_generated/health/__init__.py +7 -0
  91. mirascope/api/_generated/health/client.py +92 -0
  92. mirascope/api/_generated/health/raw_client.py +175 -0
  93. mirascope/api/_generated/health/types/__init__.py +8 -0
  94. mirascope/api/_generated/health/types/health_check_response.py +22 -0
  95. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  96. mirascope/api/_generated/organization_invitations/__init__.py +33 -0
  97. mirascope/api/_generated/organization_invitations/client.py +546 -0
  98. mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
  99. mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
  100. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
  101. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
  102. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
  103. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
  104. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
  105. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
  106. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
  107. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
  108. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
  109. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
  110. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
  111. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
  112. mirascope/api/_generated/organization_memberships/__init__.py +19 -0
  113. mirascope/api/_generated/organization_memberships/client.py +302 -0
  114. mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
  115. mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
  116. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
  117. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
  118. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
  119. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
  120. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
  121. mirascope/api/_generated/organizations/__init__.py +51 -0
  122. mirascope/api/_generated/organizations/client.py +869 -0
  123. mirascope/api/_generated/organizations/raw_client.py +2593 -0
  124. mirascope/api/_generated/organizations/types/__init__.py +71 -0
  125. mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
  126. mirascope/api/_generated/organizations/types/organizations_create_response.py +26 -0
  127. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +5 -0
  128. mirascope/api/_generated/organizations/types/organizations_get_response.py +26 -0
  129. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +5 -0
  130. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +26 -0
  131. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +5 -0
  132. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
  133. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
  134. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
  135. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
  136. mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
  137. mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
  138. mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
  139. mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
  140. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
  141. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
  142. mirascope/api/_generated/organizations/types/organizations_update_response.py +26 -0
  143. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +5 -0
  144. mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
  145. mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
  146. mirascope/api/_generated/project_memberships/__init__.py +29 -0
  147. mirascope/api/_generated/project_memberships/client.py +528 -0
  148. mirascope/api/_generated/project_memberships/raw_client.py +1278 -0
  149. mirascope/api/_generated/project_memberships/types/__init__.py +33 -0
  150. mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
  151. mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
  152. mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
  153. mirascope/api/_generated/project_memberships/types/project_memberships_get_response.py +33 -0
  154. mirascope/api/_generated/project_memberships/types/project_memberships_get_response_role.py +7 -0
  155. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
  156. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
  157. mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
  158. mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
  159. mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
  160. mirascope/api/_generated/projects/__init__.py +7 -0
  161. mirascope/api/_generated/projects/client.py +428 -0
  162. mirascope/api/_generated/projects/raw_client.py +1302 -0
  163. mirascope/api/_generated/projects/types/__init__.py +10 -0
  164. mirascope/api/_generated/projects/types/projects_create_response.py +25 -0
  165. mirascope/api/_generated/projects/types/projects_get_response.py +25 -0
  166. mirascope/api/_generated/projects/types/projects_list_response_item.py +25 -0
  167. mirascope/api/_generated/projects/types/projects_update_response.py +25 -0
  168. mirascope/api/_generated/reference.md +4987 -0
  169. mirascope/api/_generated/tags/__init__.py +19 -0
  170. mirascope/api/_generated/tags/client.py +504 -0
  171. mirascope/api/_generated/tags/raw_client.py +1288 -0
  172. mirascope/api/_generated/tags/types/__init__.py +17 -0
  173. mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
  174. mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
  175. mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
  176. mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
  177. mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
  178. mirascope/api/_generated/token_cost/__init__.py +7 -0
  179. mirascope/api/_generated/token_cost/client.py +160 -0
  180. mirascope/api/_generated/token_cost/raw_client.py +264 -0
  181. mirascope/api/_generated/token_cost/types/__init__.py +8 -0
  182. mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
  183. mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
  184. mirascope/api/_generated/traces/__init__.py +97 -0
  185. mirascope/api/_generated/traces/client.py +1103 -0
  186. mirascope/api/_generated/traces/raw_client.py +2322 -0
  187. mirascope/api/_generated/traces/types/__init__.py +155 -0
  188. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +29 -0
  189. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +27 -0
  190. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +23 -0
  191. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +38 -0
  192. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +19 -0
  193. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +22 -0
  194. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +20 -0
  195. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +29 -0
  196. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +31 -0
  197. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +23 -0
  198. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +38 -0
  199. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +19 -0
  200. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +22 -0
  201. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +22 -0
  202. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +48 -0
  203. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +23 -0
  204. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +38 -0
  205. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +19 -0
  206. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +24 -0
  207. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +22 -0
  208. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +20 -0
  209. mirascope/api/_generated/traces/types/traces_create_response.py +24 -0
  210. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +22 -0
  211. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +60 -0
  212. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
  213. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
  214. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
  215. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
  216. mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
  217. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +88 -0
  218. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
  219. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
  220. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
  221. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
  222. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
  223. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
  224. mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
  225. mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
  226. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
  227. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
  228. mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
  229. mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
  230. mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
  231. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +50 -0
  232. mirascope/api/_generated/types/__init__.py +85 -0
  233. mirascope/api/_generated/types/already_exists_error.py +22 -0
  234. mirascope/api/_generated/types/already_exists_error_tag.py +5 -0
  235. mirascope/api/_generated/types/bad_request_error_body.py +50 -0
  236. mirascope/api/_generated/types/click_house_error.py +22 -0
  237. mirascope/api/_generated/types/database_error.py +22 -0
  238. mirascope/api/_generated/types/database_error_tag.py +5 -0
  239. mirascope/api/_generated/types/date.py +3 -0
  240. mirascope/api/_generated/types/http_api_decode_error.py +27 -0
  241. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  242. mirascope/api/_generated/types/immutable_resource_error.py +22 -0
  243. mirascope/api/_generated/types/internal_server_error_body.py +49 -0
  244. mirascope/api/_generated/types/issue.py +38 -0
  245. mirascope/api/_generated/types/issue_tag.py +10 -0
  246. mirascope/api/_generated/types/not_found_error_body.py +22 -0
  247. mirascope/api/_generated/types/not_found_error_tag.py +5 -0
  248. mirascope/api/_generated/types/number_from_string.py +3 -0
  249. mirascope/api/_generated/types/permission_denied_error.py +22 -0
  250. mirascope/api/_generated/types/permission_denied_error_tag.py +5 -0
  251. mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
  252. mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
  253. mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
  254. mirascope/api/_generated/types/property_key.py +7 -0
  255. mirascope/api/_generated/types/property_key_key.py +25 -0
  256. mirascope/api/_generated/types/property_key_key_tag.py +5 -0
  257. mirascope/api/_generated/types/rate_limit_error.py +31 -0
  258. mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
  259. mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
  260. mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
  261. mirascope/api/_generated/types/stripe_error.py +20 -0
  262. mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
  263. mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
  264. mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
  265. mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
  266. mirascope/api/client.py +255 -0
  267. mirascope/api/settings.py +99 -0
  268. mirascope/llm/__init__.py +316 -0
  269. mirascope/llm/calls/__init__.py +17 -0
  270. mirascope/llm/calls/calls.py +348 -0
  271. mirascope/llm/calls/decorator.py +268 -0
  272. mirascope/llm/content/__init__.py +71 -0
  273. mirascope/llm/content/audio.py +173 -0
  274. mirascope/llm/content/document.py +94 -0
  275. mirascope/llm/content/image.py +206 -0
  276. mirascope/llm/content/text.py +47 -0
  277. mirascope/llm/content/thought.py +58 -0
  278. mirascope/llm/content/tool_call.py +69 -0
  279. mirascope/llm/content/tool_output.py +43 -0
  280. mirascope/llm/context/__init__.py +6 -0
  281. mirascope/llm/context/_utils.py +41 -0
  282. mirascope/llm/context/context.py +24 -0
  283. mirascope/llm/exceptions.py +360 -0
  284. mirascope/llm/formatting/__init__.py +39 -0
  285. mirascope/llm/formatting/format.py +291 -0
  286. mirascope/llm/formatting/from_call_args.py +30 -0
  287. mirascope/llm/formatting/output_parser.py +178 -0
  288. mirascope/llm/formatting/partial.py +131 -0
  289. mirascope/llm/formatting/primitives.py +192 -0
  290. mirascope/llm/formatting/types.py +83 -0
  291. mirascope/llm/mcp/__init__.py +5 -0
  292. mirascope/llm/mcp/mcp_client.py +130 -0
  293. mirascope/llm/messages/__init__.py +35 -0
  294. mirascope/llm/messages/_utils.py +34 -0
  295. mirascope/llm/messages/message.py +190 -0
  296. mirascope/llm/models/__init__.py +21 -0
  297. mirascope/llm/models/models.py +1339 -0
  298. mirascope/llm/models/params.py +72 -0
  299. mirascope/llm/models/thinking_config.py +61 -0
  300. mirascope/llm/prompts/__init__.py +34 -0
  301. mirascope/llm/prompts/_utils.py +31 -0
  302. mirascope/llm/prompts/decorator.py +215 -0
  303. mirascope/llm/prompts/prompts.py +484 -0
  304. mirascope/llm/prompts/protocols.py +65 -0
  305. mirascope/llm/providers/__init__.py +65 -0
  306. mirascope/llm/providers/anthropic/__init__.py +11 -0
  307. mirascope/llm/providers/anthropic/_utils/__init__.py +27 -0
  308. mirascope/llm/providers/anthropic/_utils/beta_decode.py +297 -0
  309. mirascope/llm/providers/anthropic/_utils/beta_encode.py +272 -0
  310. mirascope/llm/providers/anthropic/_utils/decode.py +326 -0
  311. mirascope/llm/providers/anthropic/_utils/encode.py +431 -0
  312. mirascope/llm/providers/anthropic/_utils/errors.py +46 -0
  313. mirascope/llm/providers/anthropic/beta_provider.py +338 -0
  314. mirascope/llm/providers/anthropic/model_id.py +23 -0
  315. mirascope/llm/providers/anthropic/model_info.py +87 -0
  316. mirascope/llm/providers/anthropic/provider.py +440 -0
  317. mirascope/llm/providers/base/__init__.py +14 -0
  318. mirascope/llm/providers/base/_utils.py +248 -0
  319. mirascope/llm/providers/base/base_provider.py +1463 -0
  320. mirascope/llm/providers/base/kwargs.py +12 -0
  321. mirascope/llm/providers/google/__init__.py +6 -0
  322. mirascope/llm/providers/google/_utils/__init__.py +17 -0
  323. mirascope/llm/providers/google/_utils/decode.py +357 -0
  324. mirascope/llm/providers/google/_utils/encode.py +418 -0
  325. mirascope/llm/providers/google/_utils/errors.py +50 -0
  326. mirascope/llm/providers/google/message.py +7 -0
  327. mirascope/llm/providers/google/model_id.py +22 -0
  328. mirascope/llm/providers/google/model_info.py +63 -0
  329. mirascope/llm/providers/google/provider.py +456 -0
  330. mirascope/llm/providers/mirascope/__init__.py +5 -0
  331. mirascope/llm/providers/mirascope/_utils.py +73 -0
  332. mirascope/llm/providers/mirascope/provider.py +313 -0
  333. mirascope/llm/providers/mlx/__init__.py +9 -0
  334. mirascope/llm/providers/mlx/_utils.py +141 -0
  335. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  336. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  337. mirascope/llm/providers/mlx/encoding/transformers.py +146 -0
  338. mirascope/llm/providers/mlx/mlx.py +242 -0
  339. mirascope/llm/providers/mlx/model_id.py +17 -0
  340. mirascope/llm/providers/mlx/provider.py +416 -0
  341. mirascope/llm/providers/model_id.py +16 -0
  342. mirascope/llm/providers/ollama/__init__.py +7 -0
  343. mirascope/llm/providers/ollama/provider.py +71 -0
  344. mirascope/llm/providers/openai/__init__.py +15 -0
  345. mirascope/llm/providers/openai/_utils/__init__.py +5 -0
  346. mirascope/llm/providers/openai/_utils/errors.py +46 -0
  347. mirascope/llm/providers/openai/completions/__init__.py +7 -0
  348. mirascope/llm/providers/openai/completions/_utils/__init__.py +18 -0
  349. mirascope/llm/providers/openai/completions/_utils/decode.py +252 -0
  350. mirascope/llm/providers/openai/completions/_utils/encode.py +390 -0
  351. mirascope/llm/providers/openai/completions/_utils/feature_info.py +50 -0
  352. mirascope/llm/providers/openai/completions/base_provider.py +522 -0
  353. mirascope/llm/providers/openai/completions/provider.py +28 -0
  354. mirascope/llm/providers/openai/model_id.py +31 -0
  355. mirascope/llm/providers/openai/model_info.py +303 -0
  356. mirascope/llm/providers/openai/provider.py +405 -0
  357. mirascope/llm/providers/openai/responses/__init__.py +5 -0
  358. mirascope/llm/providers/openai/responses/_utils/__init__.py +15 -0
  359. mirascope/llm/providers/openai/responses/_utils/decode.py +289 -0
  360. mirascope/llm/providers/openai/responses/_utils/encode.py +399 -0
  361. mirascope/llm/providers/openai/responses/provider.py +472 -0
  362. mirascope/llm/providers/openrouter/__init__.py +5 -0
  363. mirascope/llm/providers/openrouter/provider.py +67 -0
  364. mirascope/llm/providers/provider_id.py +26 -0
  365. mirascope/llm/providers/provider_registry.py +305 -0
  366. mirascope/llm/providers/together/__init__.py +7 -0
  367. mirascope/llm/providers/together/provider.py +40 -0
  368. mirascope/llm/responses/__init__.py +66 -0
  369. mirascope/llm/responses/_utils.py +146 -0
  370. mirascope/llm/responses/base_response.py +103 -0
  371. mirascope/llm/responses/base_stream_response.py +824 -0
  372. mirascope/llm/responses/finish_reason.py +28 -0
  373. mirascope/llm/responses/response.py +362 -0
  374. mirascope/llm/responses/root_response.py +248 -0
  375. mirascope/llm/responses/stream_response.py +577 -0
  376. mirascope/llm/responses/streams.py +363 -0
  377. mirascope/llm/responses/usage.py +139 -0
  378. mirascope/llm/tools/__init__.py +71 -0
  379. mirascope/llm/tools/_utils.py +34 -0
  380. mirascope/llm/tools/decorator.py +184 -0
  381. mirascope/llm/tools/protocols.py +96 -0
  382. mirascope/llm/tools/provider_tools.py +18 -0
  383. mirascope/llm/tools/tool_schema.py +321 -0
  384. mirascope/llm/tools/toolkit.py +178 -0
  385. mirascope/llm/tools/tools.py +263 -0
  386. mirascope/llm/tools/types.py +112 -0
  387. mirascope/llm/tools/web_search_tool.py +32 -0
  388. mirascope/llm/types/__init__.py +22 -0
  389. mirascope/llm/types/dataclass.py +9 -0
  390. mirascope/llm/types/jsonable.py +44 -0
  391. mirascope/llm/types/type_vars.py +19 -0
  392. mirascope/ops/__init__.py +129 -0
  393. mirascope/ops/_internal/__init__.py +5 -0
  394. mirascope/ops/_internal/closure.py +1172 -0
  395. mirascope/ops/_internal/configuration.py +177 -0
  396. mirascope/ops/_internal/context.py +76 -0
  397. mirascope/ops/_internal/exporters/__init__.py +26 -0
  398. mirascope/ops/_internal/exporters/exporters.py +362 -0
  399. mirascope/ops/_internal/exporters/processors.py +104 -0
  400. mirascope/ops/_internal/exporters/types.py +165 -0
  401. mirascope/ops/_internal/exporters/utils.py +66 -0
  402. mirascope/ops/_internal/instrumentation/__init__.py +28 -0
  403. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  404. mirascope/ops/_internal/instrumentation/llm/common.py +500 -0
  405. mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
  406. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  407. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  408. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  409. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  410. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  411. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  412. mirascope/ops/_internal/instrumentation/llm/llm.py +161 -0
  413. mirascope/ops/_internal/instrumentation/llm/model.py +1777 -0
  414. mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
  415. mirascope/ops/_internal/instrumentation/llm/serialize.py +324 -0
  416. mirascope/ops/_internal/instrumentation/providers/__init__.py +29 -0
  417. mirascope/ops/_internal/instrumentation/providers/anthropic.py +78 -0
  418. mirascope/ops/_internal/instrumentation/providers/base.py +179 -0
  419. mirascope/ops/_internal/instrumentation/providers/google_genai.py +85 -0
  420. mirascope/ops/_internal/instrumentation/providers/openai.py +82 -0
  421. mirascope/ops/_internal/propagation.py +198 -0
  422. mirascope/ops/_internal/protocols.py +133 -0
  423. mirascope/ops/_internal/session.py +139 -0
  424. mirascope/ops/_internal/spans.py +232 -0
  425. mirascope/ops/_internal/traced_calls.py +389 -0
  426. mirascope/ops/_internal/traced_functions.py +528 -0
  427. mirascope/ops/_internal/tracing.py +353 -0
  428. mirascope/ops/_internal/types.py +13 -0
  429. mirascope/ops/_internal/utils.py +131 -0
  430. mirascope/ops/_internal/versioned_calls.py +512 -0
  431. mirascope/ops/_internal/versioned_functions.py +357 -0
  432. mirascope/ops/_internal/versioning.py +303 -0
  433. mirascope/ops/exceptions.py +21 -0
  434. mirascope-2.1.1.dist-info/METADATA +231 -0
  435. mirascope-2.1.1.dist-info/RECORD +437 -0
  436. {mirascope-1.0.5.dist-info → mirascope-2.1.1.dist-info}/WHEEL +1 -1
  437. {mirascope-1.0.5.dist-info → mirascope-2.1.1.dist-info}/licenses/LICENSE +1 -1
  438. mirascope/beta/__init__.py +0 -0
  439. mirascope/beta/openai/__init__.py +0 -5
  440. mirascope/beta/openai/parse.py +0 -129
  441. mirascope/beta/rag/__init__.py +0 -24
  442. mirascope/beta/rag/base/__init__.py +0 -22
  443. mirascope/beta/rag/base/chunkers/__init__.py +0 -2
  444. mirascope/beta/rag/base/chunkers/base_chunker.py +0 -37
  445. mirascope/beta/rag/base/chunkers/text_chunker.py +0 -33
  446. mirascope/beta/rag/base/config.py +0 -8
  447. mirascope/beta/rag/base/document.py +0 -11
  448. mirascope/beta/rag/base/embedders.py +0 -35
  449. mirascope/beta/rag/base/embedding_params.py +0 -18
  450. mirascope/beta/rag/base/embedding_response.py +0 -30
  451. mirascope/beta/rag/base/query_results.py +0 -7
  452. mirascope/beta/rag/base/vectorstore_params.py +0 -18
  453. mirascope/beta/rag/base/vectorstores.py +0 -37
  454. mirascope/beta/rag/chroma/__init__.py +0 -11
  455. mirascope/beta/rag/chroma/types.py +0 -57
  456. mirascope/beta/rag/chroma/vectorstores.py +0 -97
  457. mirascope/beta/rag/cohere/__init__.py +0 -11
  458. mirascope/beta/rag/cohere/embedders.py +0 -87
  459. mirascope/beta/rag/cohere/embedding_params.py +0 -29
  460. mirascope/beta/rag/cohere/embedding_response.py +0 -29
  461. mirascope/beta/rag/cohere/py.typed +0 -0
  462. mirascope/beta/rag/openai/__init__.py +0 -11
  463. mirascope/beta/rag/openai/embedders.py +0 -144
  464. mirascope/beta/rag/openai/embedding_params.py +0 -18
  465. mirascope/beta/rag/openai/embedding_response.py +0 -14
  466. mirascope/beta/rag/openai/py.typed +0 -0
  467. mirascope/beta/rag/pinecone/__init__.py +0 -19
  468. mirascope/beta/rag/pinecone/types.py +0 -143
  469. mirascope/beta/rag/pinecone/vectorstores.py +0 -148
  470. mirascope/beta/rag/weaviate/__init__.py +0 -6
  471. mirascope/beta/rag/weaviate/types.py +0 -92
  472. mirascope/beta/rag/weaviate/vectorstores.py +0 -103
  473. mirascope/core/__init__.py +0 -55
  474. mirascope/core/anthropic/__init__.py +0 -21
  475. mirascope/core/anthropic/_call.py +0 -71
  476. mirascope/core/anthropic/_utils/__init__.py +0 -16
  477. mirascope/core/anthropic/_utils/_calculate_cost.py +0 -63
  478. mirascope/core/anthropic/_utils/_convert_message_params.py +0 -54
  479. mirascope/core/anthropic/_utils/_get_json_output.py +0 -34
  480. mirascope/core/anthropic/_utils/_handle_stream.py +0 -89
  481. mirascope/core/anthropic/_utils/_setup_call.py +0 -76
  482. mirascope/core/anthropic/call_params.py +0 -36
  483. mirascope/core/anthropic/call_response.py +0 -158
  484. mirascope/core/anthropic/call_response_chunk.py +0 -104
  485. mirascope/core/anthropic/dynamic_config.py +0 -26
  486. mirascope/core/anthropic/py.typed +0 -0
  487. mirascope/core/anthropic/stream.py +0 -140
  488. mirascope/core/anthropic/tool.py +0 -77
  489. mirascope/core/base/__init__.py +0 -40
  490. mirascope/core/base/_call_factory.py +0 -323
  491. mirascope/core/base/_create.py +0 -167
  492. mirascope/core/base/_extract.py +0 -139
  493. mirascope/core/base/_partial.py +0 -63
  494. mirascope/core/base/_utils/__init__.py +0 -64
  495. mirascope/core/base/_utils/_base_type.py +0 -17
  496. mirascope/core/base/_utils/_convert_base_model_to_base_tool.py +0 -45
  497. mirascope/core/base/_utils/_convert_base_type_to_base_tool.py +0 -24
  498. mirascope/core/base/_utils/_convert_function_to_base_tool.py +0 -126
  499. mirascope/core/base/_utils/_default_tool_docstring.py +0 -6
  500. mirascope/core/base/_utils/_extract_tool_return.py +0 -36
  501. mirascope/core/base/_utils/_format_template.py +0 -29
  502. mirascope/core/base/_utils/_get_audio_type.py +0 -18
  503. mirascope/core/base/_utils/_get_fn_args.py +0 -14
  504. mirascope/core/base/_utils/_get_image_type.py +0 -26
  505. mirascope/core/base/_utils/_get_metadata.py +0 -17
  506. mirascope/core/base/_utils/_get_possible_user_message_param.py +0 -21
  507. mirascope/core/base/_utils/_get_prompt_template.py +0 -25
  508. mirascope/core/base/_utils/_get_template_values.py +0 -52
  509. mirascope/core/base/_utils/_get_template_variables.py +0 -38
  510. mirascope/core/base/_utils/_json_mode_content.py +0 -15
  511. mirascope/core/base/_utils/_parse_content_template.py +0 -157
  512. mirascope/core/base/_utils/_parse_prompt_messages.py +0 -51
  513. mirascope/core/base/_utils/_protocols.py +0 -215
  514. mirascope/core/base/_utils/_setup_call.py +0 -64
  515. mirascope/core/base/_utils/_setup_extract_tool.py +0 -24
  516. mirascope/core/base/call_params.py +0 -6
  517. mirascope/core/base/call_response.py +0 -189
  518. mirascope/core/base/call_response_chunk.py +0 -91
  519. mirascope/core/base/dynamic_config.py +0 -55
  520. mirascope/core/base/message_param.py +0 -61
  521. mirascope/core/base/metadata.py +0 -13
  522. mirascope/core/base/prompt.py +0 -415
  523. mirascope/core/base/stream.py +0 -365
  524. mirascope/core/base/structured_stream.py +0 -251
  525. mirascope/core/base/tool.py +0 -126
  526. mirascope/core/base/toolkit.py +0 -146
  527. mirascope/core/cohere/__init__.py +0 -21
  528. mirascope/core/cohere/_call.py +0 -71
  529. mirascope/core/cohere/_utils/__init__.py +0 -16
  530. mirascope/core/cohere/_utils/_calculate_cost.py +0 -39
  531. mirascope/core/cohere/_utils/_convert_message_params.py +0 -31
  532. mirascope/core/cohere/_utils/_get_json_output.py +0 -31
  533. mirascope/core/cohere/_utils/_handle_stream.py +0 -33
  534. mirascope/core/cohere/_utils/_setup_call.py +0 -89
  535. mirascope/core/cohere/call_params.py +0 -57
  536. mirascope/core/cohere/call_response.py +0 -167
  537. mirascope/core/cohere/call_response_chunk.py +0 -101
  538. mirascope/core/cohere/dynamic_config.py +0 -24
  539. mirascope/core/cohere/py.typed +0 -0
  540. mirascope/core/cohere/stream.py +0 -113
  541. mirascope/core/cohere/tool.py +0 -92
  542. mirascope/core/gemini/__init__.py +0 -21
  543. mirascope/core/gemini/_call.py +0 -71
  544. mirascope/core/gemini/_utils/__init__.py +0 -16
  545. mirascope/core/gemini/_utils/_calculate_cost.py +0 -8
  546. mirascope/core/gemini/_utils/_convert_message_params.py +0 -74
  547. mirascope/core/gemini/_utils/_get_json_output.py +0 -33
  548. mirascope/core/gemini/_utils/_handle_stream.py +0 -33
  549. mirascope/core/gemini/_utils/_setup_call.py +0 -68
  550. mirascope/core/gemini/call_params.py +0 -28
  551. mirascope/core/gemini/call_response.py +0 -173
  552. mirascope/core/gemini/call_response_chunk.py +0 -85
  553. mirascope/core/gemini/dynamic_config.py +0 -26
  554. mirascope/core/gemini/stream.py +0 -121
  555. mirascope/core/gemini/tool.py +0 -104
  556. mirascope/core/groq/__init__.py +0 -21
  557. mirascope/core/groq/_call.py +0 -71
  558. mirascope/core/groq/_utils/__init__.py +0 -16
  559. mirascope/core/groq/_utils/_calculate_cost.py +0 -68
  560. mirascope/core/groq/_utils/_convert_message_params.py +0 -23
  561. mirascope/core/groq/_utils/_get_json_output.py +0 -27
  562. mirascope/core/groq/_utils/_handle_stream.py +0 -121
  563. mirascope/core/groq/_utils/_setup_call.py +0 -67
  564. mirascope/core/groq/call_params.py +0 -51
  565. mirascope/core/groq/call_response.py +0 -160
  566. mirascope/core/groq/call_response_chunk.py +0 -89
  567. mirascope/core/groq/dynamic_config.py +0 -26
  568. mirascope/core/groq/py.typed +0 -0
  569. mirascope/core/groq/stream.py +0 -136
  570. mirascope/core/groq/tool.py +0 -79
  571. mirascope/core/litellm/__init__.py +0 -6
  572. mirascope/core/litellm/_call.py +0 -73
  573. mirascope/core/litellm/_utils/__init__.py +0 -5
  574. mirascope/core/litellm/_utils/_setup_call.py +0 -46
  575. mirascope/core/litellm/py.typed +0 -0
  576. mirascope/core/mistral/__init__.py +0 -21
  577. mirascope/core/mistral/_call.py +0 -69
  578. mirascope/core/mistral/_utils/__init__.py +0 -16
  579. mirascope/core/mistral/_utils/_calculate_cost.py +0 -47
  580. mirascope/core/mistral/_utils/_convert_message_params.py +0 -23
  581. mirascope/core/mistral/_utils/_get_json_output.py +0 -28
  582. mirascope/core/mistral/_utils/_handle_stream.py +0 -121
  583. mirascope/core/mistral/_utils/_setup_call.py +0 -86
  584. mirascope/core/mistral/call_params.py +0 -36
  585. mirascope/core/mistral/call_response.py +0 -156
  586. mirascope/core/mistral/call_response_chunk.py +0 -84
  587. mirascope/core/mistral/dynamic_config.py +0 -24
  588. mirascope/core/mistral/py.typed +0 -0
  589. mirascope/core/mistral/stream.py +0 -117
  590. mirascope/core/mistral/tool.py +0 -77
  591. mirascope/core/openai/__init__.py +0 -21
  592. mirascope/core/openai/_call.py +0 -71
  593. mirascope/core/openai/_utils/__init__.py +0 -16
  594. mirascope/core/openai/_utils/_calculate_cost.py +0 -110
  595. mirascope/core/openai/_utils/_convert_message_params.py +0 -53
  596. mirascope/core/openai/_utils/_get_json_output.py +0 -27
  597. mirascope/core/openai/_utils/_handle_stream.py +0 -125
  598. mirascope/core/openai/_utils/_setup_call.py +0 -62
  599. mirascope/core/openai/call_params.py +0 -54
  600. mirascope/core/openai/call_response.py +0 -162
  601. mirascope/core/openai/call_response_chunk.py +0 -90
  602. mirascope/core/openai/dynamic_config.py +0 -26
  603. mirascope/core/openai/py.typed +0 -0
  604. mirascope/core/openai/stream.py +0 -148
  605. mirascope/core/openai/tool.py +0 -79
  606. mirascope/core/py.typed +0 -0
  607. mirascope/integrations/__init__.py +0 -20
  608. mirascope/integrations/_middleware_factory.py +0 -277
  609. mirascope/integrations/langfuse/__init__.py +0 -3
  610. mirascope/integrations/langfuse/_utils.py +0 -114
  611. mirascope/integrations/langfuse/_with_langfuse.py +0 -71
  612. mirascope/integrations/logfire/__init__.py +0 -3
  613. mirascope/integrations/logfire/_utils.py +0 -188
  614. mirascope/integrations/logfire/_with_logfire.py +0 -60
  615. mirascope/integrations/otel/__init__.py +0 -5
  616. mirascope/integrations/otel/_utils.py +0 -268
  617. mirascope/integrations/otel/_with_hyperdx.py +0 -61
  618. mirascope/integrations/otel/_with_otel.py +0 -60
  619. mirascope/integrations/tenacity.py +0 -50
  620. mirascope/py.typed +0 -0
  621. mirascope/v0/__init__.py +0 -43
  622. mirascope/v0/anthropic.py +0 -54
  623. mirascope/v0/base/__init__.py +0 -12
  624. mirascope/v0/base/calls.py +0 -118
  625. mirascope/v0/base/extractors.py +0 -122
  626. mirascope/v0/base/ops_utils.py +0 -207
  627. mirascope/v0/base/prompts.py +0 -48
  628. mirascope/v0/base/types.py +0 -14
  629. mirascope/v0/base/utils.py +0 -21
  630. mirascope/v0/openai.py +0 -54
  631. mirascope-1.0.5.dist-info/METADATA +0 -519
  632. mirascope-1.0.5.dist-info/RECORD +0 -198
@@ -0,0 +1,500 @@
1
+ """Shared utilities and types for OpenTelemetry GenAI instrumentation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Callable, Iterator, Mapping, Sequence
7
+ from contextlib import contextmanager
8
+ from dataclasses import dataclass
9
+ from typing import TYPE_CHECKING, Protocol, TypeAlias, runtime_checkable
10
+ from typing_extensions import TypeIs
11
+
12
+ from opentelemetry import trace as otel_trace
13
+ from opentelemetry.semconv._incubating.attributes import (
14
+ gen_ai_attributes as GenAIAttributes,
15
+ )
16
+ from opentelemetry.semconv.attributes import error_attributes as ErrorAttributes
17
+ from opentelemetry.trace import SpanKind, Status, StatusCode
18
+
19
+ from .....llm import (
20
+ AnyTools,
21
+ AnyToolSchema,
22
+ BaseToolkit,
23
+ Format,
24
+ FormatSpec,
25
+ FormattableT,
26
+ Jsonable,
27
+ Message,
28
+ Model,
29
+ ModelId,
30
+ Params,
31
+ ProviderId,
32
+ ProviderTool,
33
+ RootResponse,
34
+ ToolkitT,
35
+ )
36
+ from ...configuration import get_tracer
37
+ from ...utils import json_dumps
38
+ from .cost import calculate_cost_async, calculate_cost_sync
39
+ from .encode import (
40
+ map_finish_reason,
41
+ snapshot_from_root_response,
42
+ split_request_messages,
43
+ )
44
+ from .serialize import (
45
+ serialize_mirascope_content,
46
+ serialize_mirascope_cost,
47
+ serialize_mirascope_messages,
48
+ serialize_mirascope_usage,
49
+ serialize_tools,
50
+ )
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+ # Re-export for backwards compatibility
55
+ _calculate_cost_sync = calculate_cost_sync
56
+ _calculate_cost_async = calculate_cost_async
57
+
58
+ if TYPE_CHECKING:
59
+ from opentelemetry.trace import Span, Tracer
60
+ from opentelemetry.util.types import AttributeValue
61
+
62
+ from . import gen_ai_types
63
+ else:
64
+ AttributeValue = None
65
+ Span = None
66
+ Tracer = None
67
+
68
+
69
+ FormatParam: TypeAlias = FormatSpec[FormattableT] | None
70
+ ParamsDict: TypeAlias = Mapping[str, str | int | float | bool | Sequence[str] | None]
71
+ SpanAttributes: TypeAlias = Mapping[str, AttributeValue]
72
+ AttributeSetter: TypeAlias = Callable[[str, AttributeValue], None]
73
+ ParamsValue = str | int | float | bool | Sequence[str] | None
74
+
75
+
76
+ @dataclass(slots=True)
77
+ class SpanContext:
78
+ """Container for a GenAI span and its associated dropped parameters."""
79
+
80
+ span: Span | None
81
+ """The active span, if any."""
82
+
83
+ dropped_params: dict[str, Jsonable]
84
+ """Parameters that could not be recorded as span attributes."""
85
+
86
+
87
+ @runtime_checkable
88
+ class Identifiable(Protocol):
89
+ """Protocol for objects with an optional ID attribute."""
90
+
91
+ id: str | None
92
+ """Optional ID attribute."""
93
+
94
+
95
+ _PARAM_ATTRIBUTE_MAP: Mapping[str, str] = {
96
+ "temperature": GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE,
97
+ "max_tokens": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS,
98
+ "max_output_tokens": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS,
99
+ "max_completion_tokens": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS,
100
+ "top_p": GenAIAttributes.GEN_AI_REQUEST_TOP_P,
101
+ "top_k": GenAIAttributes.GEN_AI_REQUEST_TOP_K,
102
+ "frequency_penalty": GenAIAttributes.GEN_AI_REQUEST_FREQUENCY_PENALTY,
103
+ "presence_penalty": GenAIAttributes.GEN_AI_REQUEST_PRESENCE_PENALTY,
104
+ "seed": GenAIAttributes.GEN_AI_REQUEST_SEED,
105
+ "stop_sequences": GenAIAttributes.GEN_AI_REQUEST_STOP_SEQUENCES,
106
+ "stop": GenAIAttributes.GEN_AI_REQUEST_STOP_SEQUENCES,
107
+ "n": GenAIAttributes.GEN_AI_REQUEST_CHOICE_COUNT,
108
+ "choice_count": GenAIAttributes.GEN_AI_REQUEST_CHOICE_COUNT,
109
+ }
110
+
111
+
112
+ def record_exception(span: Span, exc: Exception) -> None:
113
+ """Record exception details on span following OpenTelemetry semantic conventions."""
114
+ span.record_exception(exc)
115
+ span.set_attribute(ErrorAttributes.ERROR_TYPE, exc.__class__.__name__)
116
+ error_message = str(exc)
117
+ if error_message:
118
+ span.set_attribute("error.message", error_message)
119
+ span.set_status(Status(StatusCode.ERROR, error_message))
120
+
121
+
122
+ def _infer_output_type(format_obj: FormatParam) -> str:
123
+ """Infer the GenAI output type from the format parameter."""
124
+ if format_obj is None:
125
+ return GenAIAttributes.GenAiOutputTypeValues.TEXT.value
126
+ return GenAIAttributes.GenAiOutputTypeValues.JSON.value
127
+
128
+
129
+ def _apply_param_attributes(
130
+ attrs: dict[str, AttributeValue], params: ParamsDict
131
+ ) -> None:
132
+ """Apply model parameters as span attributes."""
133
+ if not params:
134
+ return
135
+
136
+ for key, attr_key in _PARAM_ATTRIBUTE_MAP.items():
137
+ if key not in params:
138
+ continue
139
+ value = params[key]
140
+ if value is None:
141
+ continue
142
+ if key in {"stop", "stop_sequences"} and isinstance(value, str):
143
+ value = [value]
144
+ attrs[attr_key] = value
145
+
146
+
147
+ def _set_json_attribute(
148
+ setter: AttributeSetter,
149
+ *,
150
+ key: str,
151
+ payload: (
152
+ gen_ai_types.SystemInstructions
153
+ | gen_ai_types.InputMessages
154
+ | gen_ai_types.OutputMessages
155
+ ),
156
+ ) -> None:
157
+ """Assign a JSON-serialized attribute to a span."""
158
+ if not payload:
159
+ return
160
+ setter(key, json_dumps(payload))
161
+
162
+
163
+ def _assign_request_message_attributes(
164
+ setter: AttributeSetter,
165
+ *,
166
+ messages: Sequence[Message],
167
+ ) -> None:
168
+ """Assign request message attributes to a span."""
169
+ system_payload, input_payload = split_request_messages(messages)
170
+ _set_json_attribute(
171
+ setter,
172
+ key=GenAIAttributes.GEN_AI_SYSTEM_INSTRUCTIONS,
173
+ payload=system_payload,
174
+ )
175
+ _set_json_attribute(
176
+ setter,
177
+ key=GenAIAttributes.GEN_AI_INPUT_MESSAGES,
178
+ payload=input_payload,
179
+ )
180
+
181
+
182
+ def _build_request_attributes(
183
+ *,
184
+ operation: str,
185
+ provider: ProviderId,
186
+ model_id: ModelId,
187
+ messages: Sequence[Message],
188
+ tools: AnyTools | None,
189
+ format: FormatParam,
190
+ params: ParamsDict,
191
+ ) -> dict[str, AttributeValue]:
192
+ """Build GenAI request attributes for a span."""
193
+ attrs: dict[str, AttributeValue] = {
194
+ GenAIAttributes.GEN_AI_OPERATION_NAME: operation,
195
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: provider,
196
+ GenAIAttributes.GEN_AI_REQUEST_MODEL: model_id,
197
+ GenAIAttributes.GEN_AI_OUTPUT_TYPE: _infer_output_type(format),
198
+ }
199
+ _apply_param_attributes(attrs, params)
200
+
201
+ _assign_request_message_attributes(
202
+ attrs.__setitem__,
203
+ messages=messages,
204
+ )
205
+
206
+ tool_schemas: list[AnyToolSchema | ProviderTool] = []
207
+ if tools is None:
208
+ tool_schemas = []
209
+ elif isinstance(tools, BaseToolkit):
210
+ tool_schemas = list(tools.tools)
211
+ else:
212
+ tool_schemas = list(tools)
213
+
214
+ if isinstance(format, Format) and format.mode == "tool":
215
+ tool_schemas.append(format.create_tool_schema())
216
+
217
+ tool_payload = serialize_tools(tool_schemas)
218
+ if tool_payload:
219
+ # The incubating semconv module does not yet expose a constant for this key.
220
+ attrs["gen_ai.tool.definitions"] = tool_payload
221
+
222
+ return attrs
223
+
224
+
225
+ def _extract_response_id(
226
+ raw: dict[str, str | int] | str | Identifiable | None,
227
+ ) -> str | None:
228
+ """Extract response ID from raw response data."""
229
+ if isinstance(raw, dict):
230
+ for key in ("id", "response_id", "responseId"):
231
+ value = raw.get(key)
232
+ if isinstance(value, str):
233
+ return value
234
+ elif isinstance(raw, Identifiable):
235
+ return raw.id
236
+ return None
237
+
238
+
239
+ def attach_response(
240
+ span: Span,
241
+ response: RootResponse[ToolkitT, FormattableT | None],
242
+ *,
243
+ request_messages: Sequence[Message],
244
+ ) -> None:
245
+ """Attach response attributes to a GenAI span."""
246
+ span.set_attribute(GenAIAttributes.GEN_AI_RESPONSE_MODEL, response.model_id)
247
+ span.set_attribute(
248
+ GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS,
249
+ [map_finish_reason(response.finish_reason)],
250
+ )
251
+ response_id = _extract_response_id(getattr(response, "raw", None))
252
+ if response_id:
253
+ span.set_attribute(GenAIAttributes.GEN_AI_RESPONSE_ID, response_id)
254
+
255
+ snapshot = snapshot_from_root_response(
256
+ response,
257
+ request_messages=request_messages,
258
+ )
259
+ _set_json_attribute(
260
+ span.set_attribute,
261
+ key=GenAIAttributes.GEN_AI_SYSTEM_INSTRUCTIONS,
262
+ payload=snapshot.system_instructions,
263
+ )
264
+ _set_json_attribute(
265
+ span.set_attribute,
266
+ key=GenAIAttributes.GEN_AI_INPUT_MESSAGES,
267
+ payload=snapshot.inputs,
268
+ )
269
+ _set_json_attribute(
270
+ span.set_attribute,
271
+ key=GenAIAttributes.GEN_AI_OUTPUT_MESSAGES,
272
+ payload=snapshot.outputs,
273
+ )
274
+ if response.usage is not None:
275
+ span.set_attribute(
276
+ GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, response.usage.input_tokens
277
+ )
278
+ span.set_attribute(
279
+ GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, response.usage.output_tokens
280
+ )
281
+
282
+ # Mirascope-specific attributes
283
+ span.set_attribute(
284
+ "mirascope.response.messages", serialize_mirascope_messages(request_messages)
285
+ )
286
+ span.set_attribute(
287
+ "mirascope.response.content", serialize_mirascope_content(response.content)
288
+ )
289
+ if (usage_json := serialize_mirascope_usage(response.usage)) is not None:
290
+ span.set_attribute("mirascope.response.usage", usage_json)
291
+
292
+ # Calculate and attach cost if usage is available
293
+ if response.usage is not None:
294
+ cost = _calculate_cost_sync(
295
+ response.provider_id, response.model_id, response.usage
296
+ )
297
+ if cost is not None:
298
+ span.set_attribute(
299
+ "mirascope.response.cost",
300
+ serialize_mirascope_cost(
301
+ input_cost=cost.input_cost_centicents,
302
+ output_cost=cost.output_cost_centicents,
303
+ total_cost=cost.total_cost_centicents,
304
+ cache_read_cost=cost.cache_read_cost_centicents,
305
+ cache_write_cost=cost.cache_write_cost_centicents,
306
+ ),
307
+ )
308
+
309
+
310
+ async def attach_response_async(
311
+ span: Span,
312
+ response: RootResponse[ToolkitT, FormattableT | None],
313
+ *,
314
+ request_messages: Sequence[Message],
315
+ ) -> None:
316
+ """Attach response attributes to a GenAI span (async version for cost calculation)."""
317
+ span.set_attribute(GenAIAttributes.GEN_AI_RESPONSE_MODEL, response.model_id)
318
+ span.set_attribute(
319
+ GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS,
320
+ [map_finish_reason(response.finish_reason)],
321
+ )
322
+ response_id = _extract_response_id(getattr(response, "raw", None))
323
+ if response_id:
324
+ span.set_attribute(GenAIAttributes.GEN_AI_RESPONSE_ID, response_id)
325
+
326
+ snapshot = snapshot_from_root_response(
327
+ response,
328
+ request_messages=request_messages,
329
+ )
330
+ _set_json_attribute(
331
+ span.set_attribute,
332
+ key=GenAIAttributes.GEN_AI_SYSTEM_INSTRUCTIONS,
333
+ payload=snapshot.system_instructions,
334
+ )
335
+ _set_json_attribute(
336
+ span.set_attribute,
337
+ key=GenAIAttributes.GEN_AI_INPUT_MESSAGES,
338
+ payload=snapshot.inputs,
339
+ )
340
+ _set_json_attribute(
341
+ span.set_attribute,
342
+ key=GenAIAttributes.GEN_AI_OUTPUT_MESSAGES,
343
+ payload=snapshot.outputs,
344
+ )
345
+ if response.usage is not None:
346
+ span.set_attribute(
347
+ GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, response.usage.input_tokens
348
+ )
349
+ span.set_attribute(
350
+ GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, response.usage.output_tokens
351
+ )
352
+
353
+ # Mirascope-specific attributes
354
+ span.set_attribute(
355
+ "mirascope.response.messages", serialize_mirascope_messages(request_messages)
356
+ )
357
+ span.set_attribute(
358
+ "mirascope.response.content", serialize_mirascope_content(response.content)
359
+ )
360
+ if (usage_json := serialize_mirascope_usage(response.usage)) is not None:
361
+ span.set_attribute("mirascope.response.usage", usage_json)
362
+
363
+ # Calculate and attach cost if usage is available (async)
364
+ if response.usage is not None:
365
+ cost = await _calculate_cost_async(
366
+ response.provider_id, response.model_id, response.usage
367
+ )
368
+ if cost is not None:
369
+ span.set_attribute(
370
+ "mirascope.response.cost",
371
+ serialize_mirascope_cost(
372
+ input_cost=cost.input_cost_centicents,
373
+ output_cost=cost.output_cost_centicents,
374
+ total_cost=cost.total_cost_centicents,
375
+ cache_read_cost=cost.cache_read_cost_centicents,
376
+ cache_write_cost=cost.cache_write_cost_centicents,
377
+ ),
378
+ )
379
+
380
+
381
+ def _is_supported_param_value(value: object) -> TypeIs[ParamsValue]:
382
+ """Returns True if the value can be exported as an OTEL attribute."""
383
+ if isinstance(value, str | int | float | bool) or value is None:
384
+ return True
385
+ if isinstance(value, Sequence) and not isinstance(value, str | bytes):
386
+ return all(isinstance(item, str) for item in value)
387
+ return False
388
+
389
+
390
+ def _normalize_dropped_value(value: object) -> Jsonable:
391
+ """Returns a JSON-safe representation for unsupported param values."""
392
+ if isinstance(value, str | int | float | bool) or value is None:
393
+ return value
394
+ if isinstance(value, Mapping):
395
+ normalized: dict[str, Jsonable] = {}
396
+ for key, item in value.items():
397
+ normalized[str(key)] = _normalize_dropped_value(item)
398
+ return normalized
399
+ if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
400
+ return [_normalize_dropped_value(item) for item in value]
401
+ try:
402
+ return str(value)
403
+ except Exception: # pragma: no cover
404
+ return f"<{type(value).__name__}>"
405
+
406
+
407
+ def _params_as_mapping(params: Params) -> tuple[ParamsDict, dict[str, Jsonable]]:
408
+ """Returns supported params and a mapping of dropped params."""
409
+ filtered: dict[str, ParamsValue] = {}
410
+ dropped: dict[str, Jsonable] = {}
411
+ for key, value in params.items():
412
+ if _is_supported_param_value(value):
413
+ filtered[key] = value
414
+ else:
415
+ dropped[key] = _normalize_dropped_value(value)
416
+ return filtered, dropped
417
+
418
+
419
+ def record_dropped_params(
420
+ span: Span,
421
+ dropped_params: Mapping[str, Jsonable],
422
+ ) -> None:
423
+ """Emit an event with JSON-encoded params that cannot become attributes.
424
+
425
+ See https://opentelemetry.io/docs/specs/otel/common/ for the attribute type limits,
426
+ https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-events/ for the GenAI
427
+ guidance on recording richer payloads via events, and
428
+ https://opentelemetry.io/blog/2025/complex-attribute-types/ for the recommendation
429
+ to serialize unsupported complex types instead of dropping them outright.
430
+ """
431
+ if not dropped_params:
432
+ return None
433
+ payload = json_dumps(dropped_params)
434
+ span.add_event(
435
+ "gen_ai.request.params.untracked",
436
+ attributes={
437
+ "gen_ai.untracked_params.count": len(dropped_params),
438
+ "gen_ai.untracked_params.keys": list(dropped_params.keys()),
439
+ "gen_ai.untracked_params.json": payload,
440
+ },
441
+ )
442
+ return None
443
+
444
+
445
+ @contextmanager
446
+ def start_model_span(
447
+ model: Model,
448
+ *,
449
+ messages: Sequence[Message],
450
+ tools: AnyTools | None,
451
+ format: FormatParam,
452
+ activate: bool = True,
453
+ ) -> Iterator[SpanContext]:
454
+ """Context manager that yields a SpanContext for a model call."""
455
+ params, dropped_params = _params_as_mapping(model.params)
456
+ tracer = get_tracer()
457
+
458
+ if tracer is None or otel_trace is None:
459
+ yield SpanContext(None, dropped_params)
460
+ return
461
+
462
+ operation = GenAIAttributes.GenAiOperationNameValues.CHAT.value
463
+ attrs = _build_request_attributes(
464
+ operation=operation,
465
+ provider=model.provider_id,
466
+ model_id=model.model_id,
467
+ messages=messages,
468
+ tools=tools,
469
+ format=format,
470
+ params=params,
471
+ )
472
+ span_name = f"{operation} {model.model_id}"
473
+
474
+ if activate:
475
+ with tracer.start_as_current_span(
476
+ name=span_name,
477
+ kind=SpanKind.CLIENT,
478
+ ) as active_span:
479
+ for key, value in attrs.items():
480
+ active_span.set_attribute(key, value)
481
+ try:
482
+ yield SpanContext(active_span, dropped_params)
483
+ except Exception as exc:
484
+ record_exception(active_span, exc)
485
+ raise
486
+ return
487
+
488
+ span = tracer.start_span(
489
+ name=span_name,
490
+ kind=SpanKind.CLIENT,
491
+ )
492
+ for key, value in attrs.items():
493
+ span.set_attribute(key, value)
494
+ try:
495
+ yield SpanContext(span, dropped_params)
496
+ except Exception as exc:
497
+ record_exception(span, exc)
498
+ raise
499
+ finally:
500
+ span.end()
@@ -0,0 +1,190 @@
1
+ """Cost calculation utilities for LLM instrumentation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ from typing import Any
8
+
9
+ from .....api._generated.token_cost import (
10
+ TokenCostCalculateRequestUsage,
11
+ TokenCostCalculateResponse,
12
+ )
13
+ from .....api.client import get_async_client, get_sync_client
14
+ from .....llm.providers import get_provider_for_model
15
+ from .....llm.providers.mirascope import MirascopeProvider
16
+ from .....llm.responses.usage import Usage
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def _is_via_router(model_id: str) -> bool:
22
+ """Check if the model_id routes through MirascopeProvider (Mirascope Router).
23
+
24
+ Args:
25
+ model_id: The full model ID (e.g., "openai/gpt-4o").
26
+
27
+ Returns:
28
+ True if the registered provider for this model is MirascopeProvider.
29
+ """
30
+ try:
31
+ provider = get_provider_for_model(model_id)
32
+ return isinstance(provider, MirascopeProvider)
33
+ except Exception:
34
+ return False
35
+
36
+
37
+ def _normalize_model_id(model_id: str) -> str:
38
+ """Normalize model_id by stripping provider prefix and any :suffix.
39
+
40
+ Args:
41
+ model_id: The full model ID (e.g., "openai/gpt-4o:responses").
42
+
43
+ Returns:
44
+ The base model name (e.g., "gpt-4o").
45
+ """
46
+ # Strip provider prefix (e.g., "openai/gpt-4o" -> "gpt-4o")
47
+ if "/" in model_id:
48
+ model_id = model_id.split("/", 1)[1]
49
+ # Strip any :suffix (e.g., ":responses", ":completions")
50
+ return re.sub(r":.*$", "", model_id)
51
+
52
+
53
+ def extract_cache_write_breakdown(usage: Usage) -> dict[str, float] | None:
54
+ """Extract cache write breakdown from Anthropic's raw usage data.
55
+
56
+ Returns a dict like {"ephemeral5m": 100, "ephemeral1h": 50} if available,
57
+ or None if the breakdown isn't available.
58
+ """
59
+ raw = usage.raw
60
+ if raw is None:
61
+ return None
62
+
63
+ # Check for Anthropic's cache_creation breakdown
64
+ cache_creation = getattr(raw, "cache_creation", None)
65
+ if cache_creation is None:
66
+ return None
67
+
68
+ ephemeral_5m = getattr(cache_creation, "ephemeral_5m_input_tokens", None)
69
+ ephemeral_1h = getattr(cache_creation, "ephemeral_1h_input_tokens", None)
70
+
71
+ if ephemeral_5m is None and ephemeral_1h is None:
72
+ return None
73
+
74
+ breakdown: dict[str, float] = {}
75
+ if ephemeral_5m is not None and ephemeral_5m > 0:
76
+ breakdown["ephemeral5m"] = float(ephemeral_5m)
77
+ if ephemeral_1h is not None and ephemeral_1h > 0:
78
+ breakdown["ephemeral1h"] = float(ephemeral_1h)
79
+
80
+ return breakdown if breakdown else None
81
+
82
+
83
+ def calculate_cost_sync(
84
+ provider_id: str,
85
+ model_id: str,
86
+ usage: Usage,
87
+ ) -> TokenCostCalculateResponse | None:
88
+ """Calculate cost synchronously. Returns None on any failure."""
89
+ normalized_model = _normalize_model_id(model_id)
90
+ via_router = _is_via_router(model_id)
91
+ logger.debug(
92
+ "Calculating cost for provider=%s, model=%s (normalized=%s), "
93
+ "via_router=%s, usage=(input=%s, output=%s, cache_read=%s, cache_write=%s)",
94
+ provider_id,
95
+ model_id,
96
+ normalized_model,
97
+ via_router,
98
+ usage.input_tokens,
99
+ usage.output_tokens,
100
+ usage.cache_read_tokens,
101
+ usage.cache_write_tokens,
102
+ )
103
+ try:
104
+ client = get_sync_client()
105
+ logger.debug("Got sync client, making API request")
106
+ cache_breakdown = extract_cache_write_breakdown(usage)
107
+ if cache_breakdown:
108
+ logger.debug("Cache write breakdown: %s", cache_breakdown)
109
+ # Build usage kwargs, only including optional fields if they have values
110
+ usage_kwargs: dict[str, Any] = {
111
+ "input_tokens": usage.input_tokens,
112
+ "output_tokens": usage.output_tokens,
113
+ }
114
+ if usage.cache_read_tokens:
115
+ usage_kwargs["cache_read_tokens"] = usage.cache_read_tokens
116
+ if usage.cache_write_tokens:
117
+ usage_kwargs["cache_write_tokens"] = usage.cache_write_tokens
118
+ if cache_breakdown:
119
+ usage_kwargs["cache_write_breakdown"] = cache_breakdown
120
+ result = client.token_cost.calculate(
121
+ provider=provider_id,
122
+ model=normalized_model,
123
+ usage=TokenCostCalculateRequestUsage(**usage_kwargs),
124
+ via_router=via_router,
125
+ )
126
+ logger.debug(
127
+ "Cost calculation result: input=%s, output=%s, total=%s centicents",
128
+ result.input_cost_centicents,
129
+ result.output_cost_centicents,
130
+ result.total_cost_centicents,
131
+ )
132
+ return result
133
+ except Exception:
134
+ logger.debug("Failed to calculate cost", exc_info=True)
135
+ return None
136
+
137
+
138
+ async def calculate_cost_async(
139
+ provider_id: str,
140
+ model_id: str,
141
+ usage: Usage,
142
+ ) -> TokenCostCalculateResponse | None:
143
+ """Calculate cost asynchronously. Returns None on any failure."""
144
+ normalized_model = _normalize_model_id(model_id)
145
+ via_router = _is_via_router(model_id)
146
+ logger.debug(
147
+ "Calculating cost (async) for provider=%s, model=%s (normalized=%s), "
148
+ "via_router=%s, usage=(input=%s, output=%s, cache_read=%s, cache_write=%s)",
149
+ provider_id,
150
+ model_id,
151
+ normalized_model,
152
+ via_router,
153
+ usage.input_tokens,
154
+ usage.output_tokens,
155
+ usage.cache_read_tokens,
156
+ usage.cache_write_tokens,
157
+ )
158
+ try:
159
+ client = get_async_client()
160
+ logger.debug("Got async client, making API request")
161
+ cache_breakdown = extract_cache_write_breakdown(usage)
162
+ if cache_breakdown:
163
+ logger.debug("Cache write breakdown: %s", cache_breakdown)
164
+ # Build usage kwargs, only including optional fields if they have values
165
+ usage_kwargs: dict[str, Any] = {
166
+ "input_tokens": usage.input_tokens,
167
+ "output_tokens": usage.output_tokens,
168
+ }
169
+ if usage.cache_read_tokens:
170
+ usage_kwargs["cache_read_tokens"] = usage.cache_read_tokens
171
+ if usage.cache_write_tokens:
172
+ usage_kwargs["cache_write_tokens"] = usage.cache_write_tokens
173
+ if cache_breakdown:
174
+ usage_kwargs["cache_write_breakdown"] = cache_breakdown
175
+ result = await client.token_cost.calculate(
176
+ provider=provider_id,
177
+ model=normalized_model,
178
+ usage=TokenCostCalculateRequestUsage(**usage_kwargs),
179
+ via_router=via_router,
180
+ )
181
+ logger.debug(
182
+ "Cost calculation result (async): input=%s, output=%s, total=%s centicents",
183
+ result.input_cost_centicents,
184
+ result.output_cost_centicents,
185
+ result.total_cost_centicents,
186
+ )
187
+ return result
188
+ except Exception:
189
+ logger.debug("Failed to calculate cost (async)", exc_info=True)
190
+ return None