mirascope 2.0.0a6__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 (226) hide show
  1. mirascope/api/_generated/__init__.py +186 -5
  2. mirascope/api/_generated/annotations/client.py +38 -6
  3. mirascope/api/_generated/annotations/raw_client.py +366 -47
  4. mirascope/api/_generated/annotations/types/annotations_create_response.py +19 -6
  5. mirascope/api/_generated/annotations/types/annotations_get_response.py +19 -6
  6. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +22 -7
  7. mirascope/api/_generated/annotations/types/annotations_update_response.py +19 -6
  8. mirascope/api/_generated/api_keys/__init__.py +12 -2
  9. mirascope/api/_generated/api_keys/client.py +107 -6
  10. mirascope/api/_generated/api_keys/raw_client.py +486 -38
  11. mirascope/api/_generated/api_keys/types/__init__.py +7 -1
  12. mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
  13. mirascope/api/_generated/client.py +36 -0
  14. mirascope/api/_generated/docs/raw_client.py +71 -9
  15. mirascope/api/_generated/environment.py +3 -3
  16. mirascope/api/_generated/environments/__init__.py +6 -0
  17. mirascope/api/_generated/environments/client.py +158 -9
  18. mirascope/api/_generated/environments/raw_client.py +620 -52
  19. mirascope/api/_generated/environments/types/__init__.py +10 -0
  20. mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
  21. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
  22. mirascope/api/_generated/{organizations/types/organizations_credits_response.py → environments/types/environments_get_analytics_response_top_models_item.py} +6 -3
  23. mirascope/api/_generated/errors/__init__.py +6 -0
  24. mirascope/api/_generated/errors/bad_request_error.py +5 -2
  25. mirascope/api/_generated/errors/conflict_error.py +5 -2
  26. mirascope/api/_generated/errors/payment_required_error.py +15 -0
  27. mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
  28. mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
  29. mirascope/api/_generated/functions/__init__.py +10 -0
  30. mirascope/api/_generated/functions/client.py +222 -8
  31. mirascope/api/_generated/functions/raw_client.py +975 -134
  32. mirascope/api/_generated/functions/types/__init__.py +28 -4
  33. mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
  34. mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
  35. mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
  36. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
  37. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
  38. mirascope/api/_generated/health/raw_client.py +74 -10
  39. mirascope/api/_generated/organization_invitations/__init__.py +33 -0
  40. mirascope/api/_generated/organization_invitations/client.py +546 -0
  41. mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
  42. mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
  43. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
  44. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
  45. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
  46. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
  47. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
  48. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
  49. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
  50. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
  51. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
  52. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
  53. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
  54. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
  55. mirascope/api/_generated/organization_memberships/__init__.py +19 -0
  56. mirascope/api/_generated/organization_memberships/client.py +302 -0
  57. mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
  58. mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
  59. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
  60. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
  61. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
  62. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
  63. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
  64. mirascope/api/_generated/organizations/__init__.py +26 -2
  65. mirascope/api/_generated/organizations/client.py +442 -20
  66. mirascope/api/_generated/organizations/raw_client.py +1763 -164
  67. mirascope/api/_generated/organizations/types/__init__.py +48 -2
  68. mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
  69. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
  70. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
  71. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
  72. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
  73. mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
  74. mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
  75. mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
  76. mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
  77. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
  78. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
  79. mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
  80. mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
  81. mirascope/api/_generated/project_memberships/__init__.py +25 -0
  82. mirascope/api/_generated/project_memberships/client.py +437 -0
  83. mirascope/api/_generated/project_memberships/raw_client.py +1039 -0
  84. mirascope/api/_generated/project_memberships/types/__init__.py +29 -0
  85. mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
  86. mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
  87. mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
  88. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
  89. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
  90. mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
  91. mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
  92. mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
  93. mirascope/api/_generated/projects/raw_client.py +415 -58
  94. mirascope/api/_generated/reference.md +2767 -397
  95. mirascope/api/_generated/tags/__init__.py +19 -0
  96. mirascope/api/_generated/tags/client.py +504 -0
  97. mirascope/api/_generated/tags/raw_client.py +1288 -0
  98. mirascope/api/_generated/tags/types/__init__.py +17 -0
  99. mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
  100. mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
  101. mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
  102. mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
  103. mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
  104. mirascope/api/_generated/token_cost/__init__.py +7 -0
  105. mirascope/api/_generated/token_cost/client.py +160 -0
  106. mirascope/api/_generated/token_cost/raw_client.py +264 -0
  107. mirascope/api/_generated/token_cost/types/__init__.py +8 -0
  108. mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
  109. mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
  110. mirascope/api/_generated/traces/__init__.py +20 -0
  111. mirascope/api/_generated/traces/client.py +543 -0
  112. mirascope/api/_generated/traces/raw_client.py +1366 -96
  113. mirascope/api/_generated/traces/types/__init__.py +28 -0
  114. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +6 -0
  115. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
  116. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
  117. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +0 -2
  118. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
  119. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
  120. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
  121. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
  122. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
  123. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
  124. mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
  125. mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
  126. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +10 -1
  127. mirascope/api/_generated/types/__init__.py +32 -2
  128. mirascope/api/_generated/types/bad_request_error_body.py +50 -0
  129. mirascope/api/_generated/types/date.py +3 -0
  130. mirascope/api/_generated/types/immutable_resource_error.py +22 -0
  131. mirascope/api/_generated/types/internal_server_error_body.py +3 -3
  132. mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
  133. mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
  134. mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
  135. mirascope/api/_generated/types/rate_limit_error.py +31 -0
  136. mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
  137. mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
  138. mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
  139. mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
  140. mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
  141. mirascope/api/settings.py +19 -1
  142. mirascope/llm/__init__.py +53 -10
  143. mirascope/llm/calls/__init__.py +2 -1
  144. mirascope/llm/calls/calls.py +3 -1
  145. mirascope/llm/calls/decorator.py +21 -7
  146. mirascope/llm/content/tool_output.py +22 -5
  147. mirascope/llm/exceptions.py +284 -71
  148. mirascope/llm/formatting/__init__.py +17 -0
  149. mirascope/llm/formatting/format.py +112 -35
  150. mirascope/llm/formatting/output_parser.py +178 -0
  151. mirascope/llm/formatting/partial.py +80 -7
  152. mirascope/llm/formatting/primitives.py +192 -0
  153. mirascope/llm/formatting/types.py +20 -8
  154. mirascope/llm/messages/__init__.py +3 -0
  155. mirascope/llm/messages/_utils.py +34 -0
  156. mirascope/llm/models/__init__.py +5 -0
  157. mirascope/llm/models/models.py +137 -69
  158. mirascope/llm/{providers/base → models}/params.py +7 -57
  159. mirascope/llm/models/thinking_config.py +61 -0
  160. mirascope/llm/prompts/_utils.py +0 -32
  161. mirascope/llm/prompts/decorator.py +16 -5
  162. mirascope/llm/prompts/prompts.py +131 -68
  163. mirascope/llm/providers/__init__.py +1 -4
  164. mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
  165. mirascope/llm/providers/anthropic/_utils/beta_decode.py +18 -9
  166. mirascope/llm/providers/anthropic/_utils/beta_encode.py +62 -13
  167. mirascope/llm/providers/anthropic/_utils/decode.py +18 -9
  168. mirascope/llm/providers/anthropic/_utils/encode.py +26 -7
  169. mirascope/llm/providers/anthropic/_utils/errors.py +2 -2
  170. mirascope/llm/providers/anthropic/beta_provider.py +64 -18
  171. mirascope/llm/providers/anthropic/provider.py +91 -33
  172. mirascope/llm/providers/base/__init__.py +0 -4
  173. mirascope/llm/providers/base/_utils.py +55 -6
  174. mirascope/llm/providers/base/base_provider.py +116 -37
  175. mirascope/llm/providers/google/_utils/__init__.py +2 -0
  176. mirascope/llm/providers/google/_utils/decode.py +20 -7
  177. mirascope/llm/providers/google/_utils/encode.py +26 -7
  178. mirascope/llm/providers/google/_utils/errors.py +3 -2
  179. mirascope/llm/providers/google/provider.py +64 -18
  180. mirascope/llm/providers/mirascope/_utils.py +13 -17
  181. mirascope/llm/providers/mirascope/provider.py +49 -18
  182. mirascope/llm/providers/mlx/_utils.py +7 -2
  183. mirascope/llm/providers/mlx/encoding/base.py +5 -2
  184. mirascope/llm/providers/mlx/encoding/transformers.py +5 -2
  185. mirascope/llm/providers/mlx/mlx.py +23 -6
  186. mirascope/llm/providers/mlx/provider.py +42 -13
  187. mirascope/llm/providers/openai/_utils/errors.py +2 -2
  188. mirascope/llm/providers/openai/completions/_utils/encode.py +20 -16
  189. mirascope/llm/providers/openai/completions/base_provider.py +40 -11
  190. mirascope/llm/providers/openai/provider.py +40 -10
  191. mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
  192. mirascope/llm/providers/openai/responses/_utils/decode.py +19 -6
  193. mirascope/llm/providers/openai/responses/_utils/encode.py +22 -10
  194. mirascope/llm/providers/openai/responses/provider.py +56 -18
  195. mirascope/llm/providers/provider_registry.py +93 -19
  196. mirascope/llm/responses/__init__.py +6 -1
  197. mirascope/llm/responses/_utils.py +102 -12
  198. mirascope/llm/responses/base_response.py +5 -2
  199. mirascope/llm/responses/base_stream_response.py +115 -25
  200. mirascope/llm/responses/response.py +2 -1
  201. mirascope/llm/responses/root_response.py +89 -17
  202. mirascope/llm/responses/stream_response.py +6 -9
  203. mirascope/llm/tools/decorator.py +9 -4
  204. mirascope/llm/tools/tool_schema.py +12 -6
  205. mirascope/llm/tools/toolkit.py +35 -27
  206. mirascope/llm/tools/tools.py +45 -20
  207. mirascope/ops/__init__.py +4 -0
  208. mirascope/ops/_internal/configuration.py +82 -31
  209. mirascope/ops/_internal/exporters/exporters.py +64 -11
  210. mirascope/ops/_internal/instrumentation/llm/common.py +530 -0
  211. mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
  212. mirascope/ops/_internal/instrumentation/llm/encode.py +1 -1
  213. mirascope/ops/_internal/instrumentation/llm/llm.py +116 -1242
  214. mirascope/ops/_internal/instrumentation/llm/model.py +1798 -0
  215. mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
  216. mirascope/ops/_internal/instrumentation/llm/serialize.py +300 -0
  217. mirascope/ops/_internal/protocols.py +83 -1
  218. mirascope/ops/_internal/traced_calls.py +4 -0
  219. mirascope/ops/_internal/traced_functions.py +118 -8
  220. mirascope/ops/_internal/tracing.py +78 -1
  221. mirascope/ops/_internal/utils.py +52 -4
  222. {mirascope-2.0.0a6.dist-info → mirascope-2.0.1.dist-info}/METADATA +12 -11
  223. mirascope-2.0.1.dist-info/RECORD +423 -0
  224. {mirascope-2.0.0a6.dist-info → mirascope-2.0.1.dist-info}/licenses/LICENSE +1 -1
  225. mirascope-2.0.0a6.dist-info/RECORD +0 -316
  226. {mirascope-2.0.0a6.dist-info → mirascope-2.0.1.dist-info}/WHEEL +0 -0
@@ -9,6 +9,7 @@ from typing_extensions import TypeVar
9
9
 
10
10
  from ..content import ToolCall, ToolOutput
11
11
  from ..context import Context, DepsT
12
+ from ..exceptions import ToolError, ToolExecutionError
12
13
  from ..types import AnyP, JsonableCovariantT
13
14
  from .protocols import (
14
15
  AsyncContextToolFn,
@@ -42,13 +43,14 @@ class Tool(
42
43
 
43
44
  @classmethod
44
45
  def from_function( # pyright: ignore[reportIncompatibleMethodOverride]
45
- cls, fn: ToolFn[AnyP, JsonableCovariantT], *, strict: bool = False
46
+ cls, fn: ToolFn[AnyP, JsonableCovariantT], *, strict: bool | None = None
46
47
  ) -> Tool[AnyP, JsonableCovariantT]:
47
48
  """Create a `Tool` by inspecting a function and its docstring.
48
49
 
49
50
  Args:
50
51
  fn: The function to extract schema from
51
- strict: Whether the tool should use strict mode when supported
52
+ strict: Whether the tool should use strict mode when supported.
53
+ If None, uses provider's default (usually as strict as possible).
52
54
 
53
55
  Returns:
54
56
  a `Tool` representing the function
@@ -68,10 +70,15 @@ class Tool(
68
70
 
69
71
  def execute(self, tool_call: ToolCall) -> ToolOutput[JsonableCovariantT]:
70
72
  """Execute the tool using an LLM-provided `ToolCall`."""
71
- kwargs_from_json = json.loads(tool_call.args)
72
73
  kwargs_callable = cast(KwargsCallable[JsonableCovariantT], self.fn)
73
- result = kwargs_callable(**kwargs_from_json)
74
- return ToolOutput(id=tool_call.id, value=result, name=self.name)
74
+ error: ToolError | None = None
75
+ try:
76
+ kwargs_from_json = json.loads(tool_call.args)
77
+ result = kwargs_callable(**kwargs_from_json)
78
+ except Exception as e:
79
+ result = str(e)
80
+ error = ToolExecutionError(e)
81
+ return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
75
82
 
76
83
 
77
84
  class AsyncTool(
@@ -88,13 +95,14 @@ class AsyncTool(
88
95
 
89
96
  @classmethod
90
97
  def from_function( # pyright: ignore[reportIncompatibleMethodOverride]
91
- cls, fn: AsyncToolFn[AnyP, JsonableCovariantT], *, strict: bool = False
98
+ cls, fn: AsyncToolFn[AnyP, JsonableCovariantT], *, strict: bool | None = None
92
99
  ) -> AsyncTool[AnyP, JsonableCovariantT]:
93
100
  """Create an `AsyncTool` by inspecting a function and its docstring.
94
101
 
95
102
  Args:
96
103
  fn: The function to extract schema from
97
- strict: Whether the tool should use strict mode when supported
104
+ strict: Whether the tool should use strict mode when supported.
105
+ If None, uses provider's default (usually as strict as possible).
98
106
 
99
107
  Returns:
100
108
  an `AsyncTool` representing the function
@@ -116,10 +124,15 @@ class AsyncTool(
116
124
 
117
125
  async def execute(self, tool_call: ToolCall) -> ToolOutput[JsonableCovariantT]:
118
126
  """Execute the async tool using an LLM-provided `ToolCall`."""
119
- kwargs_from_json = json.loads(tool_call.args)
120
127
  kwargs_callable = cast(AsyncKwargsCallable[JsonableCovariantT], self.fn)
121
- result = await kwargs_callable(**kwargs_from_json)
122
- return ToolOutput(id=tool_call.id, value=result, name=self.name)
128
+ error: ToolError | None = None
129
+ try:
130
+ kwargs_from_json = json.loads(tool_call.args)
131
+ result = await kwargs_callable(**kwargs_from_json)
132
+ except Exception as e:
133
+ result = str(e)
134
+ error = ToolExecutionError(e)
135
+ return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
123
136
 
124
137
 
125
138
  class ContextTool(
@@ -139,13 +152,14 @@ class ContextTool(
139
152
  cls,
140
153
  fn: ContextToolFn[DepsT, AnyP, JsonableCovariantT],
141
154
  *,
142
- strict: bool = False,
155
+ strict: bool | None = None,
143
156
  ) -> ContextTool[DepsT, JsonableCovariantT, AnyP]:
144
157
  """Create a `ContextTool` by inspecting a function and its docstring.
145
158
 
146
159
  Args:
147
160
  fn: The function to extract schema from
148
- strict: Whether the tool should use strict mode when supported
161
+ strict: Whether the tool should use strict mode when supported.
162
+ If None, uses provider's default (usually as strict as possible).
149
163
 
150
164
  Returns:
151
165
  a `ContextTool` representing the function
@@ -172,12 +186,17 @@ class ContextTool(
172
186
  self, ctx: Context[DepsT], tool_call: ToolCall
173
187
  ) -> ToolOutput[JsonableCovariantT]:
174
188
  """Execute the context tool using an LLM-provided `ToolCall`."""
175
- kwargs_from_json = json.loads(tool_call.args)
176
189
  kwargs_callable = cast(
177
190
  ContextKwargsCallable[DepsT, JsonableCovariantT], self.fn
178
191
  )
179
- result = kwargs_callable(ctx, **kwargs_from_json)
180
- return ToolOutput(id=tool_call.id, value=result, name=self.name)
192
+ error: ToolError | None = None
193
+ try:
194
+ kwargs_from_json = json.loads(tool_call.args)
195
+ result = kwargs_callable(ctx, **kwargs_from_json)
196
+ except Exception as e:
197
+ result = str(e)
198
+ error = ToolExecutionError(e)
199
+ return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
181
200
 
182
201
 
183
202
  class AsyncContextTool(
@@ -197,13 +216,14 @@ class AsyncContextTool(
197
216
  cls,
198
217
  fn: AsyncContextToolFn[DepsT, AnyP, JsonableCovariantT],
199
218
  *,
200
- strict: bool = False,
219
+ strict: bool | None = None,
201
220
  ) -> AsyncContextTool[DepsT, JsonableCovariantT, AnyP]:
202
221
  """Create an `AsyncContextTool` by inspecting a function and its docstring.
203
222
 
204
223
  Args:
205
224
  fn: The function to extract schema from
206
- strict: Whether the tool should use strict mode when supported
225
+ strict: Whether the tool should use strict mode when supported.
226
+ If None, uses provider's default (usually as strict as possible).
207
227
 
208
228
  Returns:
209
229
  an `AsyncContextTool` representing the function
@@ -230,9 +250,14 @@ class AsyncContextTool(
230
250
  self, ctx: Context[DepsT], tool_call: ToolCall
231
251
  ) -> ToolOutput[JsonableCovariantT]:
232
252
  """Execute the async context tool using an LLM-provided `ToolCall`."""
233
- kwargs_from_json = json.loads(tool_call.args)
234
253
  kwargs_callable = cast(
235
254
  AsyncJsonKwargsCallable[DepsT, JsonableCovariantT], self.fn
236
255
  )
237
- result = await kwargs_callable(ctx, **kwargs_from_json)
238
- return ToolOutput(id=tool_call.id, value=result, name=self.name)
256
+ error: ToolError | None = None
257
+ try:
258
+ kwargs_from_json = json.loads(tool_call.args)
259
+ result = await kwargs_callable(ctx, **kwargs_from_json)
260
+ except Exception as e:
261
+ result = str(e)
262
+ error = ToolExecutionError(e)
263
+ return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
mirascope/ops/__init__.py CHANGED
@@ -41,8 +41,10 @@ from ._internal.traced_calls import (
41
41
  from ._internal.traced_functions import (
42
42
  AsyncTrace,
43
43
  AsyncTracedFunction,
44
+ AsyncTracedSpanFunction,
44
45
  Trace,
45
46
  TracedFunction,
47
+ TracedSpanFunction,
46
48
  )
47
49
  from ._internal.tracing import (
48
50
  TraceDecorator,
@@ -69,6 +71,7 @@ __all__ = [
69
71
  "SESSION_HEADER_NAME",
70
72
  "AsyncTrace",
71
73
  "AsyncTracedFunction",
74
+ "AsyncTracedSpanFunction",
72
75
  "AsyncVersionedFunction",
73
76
  "ClosureComputationError",
74
77
  "ContextPropagator",
@@ -82,6 +85,7 @@ __all__ = [
82
85
  "TracedCall",
83
86
  "TracedContextCall",
84
87
  "TracedFunction",
88
+ "TracedSpanFunction",
85
89
  "VersionDecorator",
86
90
  "VersionInfo",
87
91
  "VersionedAsyncCall",
@@ -2,22 +2,25 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
5
6
  from collections.abc import Iterator
6
7
  from contextlib import contextmanager
7
8
  from typing import TYPE_CHECKING
8
9
 
10
+ from opentelemetry import trace as otel_trace
11
+ from opentelemetry.sdk.trace import TracerProvider
12
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
13
+
14
+ from ...api.client import Mirascope
15
+ from ...api.settings import update_settings
16
+ from .exporters import MirascopeOTLPExporter
17
+
9
18
  if TYPE_CHECKING:
10
- from opentelemetry.sdk.trace import TracerProvider
11
19
  from opentelemetry.trace import Tracer
12
- else:
13
- Tracer = None
14
20
 
15
21
  DEFAULT_TRACER_NAME = "mirascope.llm"
16
22
 
17
- try:
18
- from opentelemetry import trace as otel_trace
19
- except ImportError: # pragma: no cover
20
- otel_trace = None
23
+ logger = logging.getLogger(__name__)
21
24
 
22
25
  _tracer_provider: TracerProvider | None = None
23
26
  _tracer_name: str = DEFAULT_TRACER_NAME
@@ -25,59 +28,107 @@ _tracer_version: str | None = None
25
28
  _tracer: Tracer | None = None
26
29
 
27
30
 
31
+ def _create_mirascope_cloud_provider(
32
+ api_key: str | None = None, base_url: str | None = None
33
+ ) -> TracerProvider:
34
+ """Create a TracerProvider configured for Mirascope Cloud.
35
+
36
+ Args:
37
+ api_key: Optional API key. If not provided, uses MIRASCOPE_API_KEY env var.
38
+
39
+ Returns:
40
+ Configured TracerProvider with MirascopeOTLPExporter.
41
+
42
+ Raises:
43
+ RuntimeError: If API key is not available.
44
+ """
45
+ try:
46
+ client = Mirascope(api_key=api_key, base_url=base_url)
47
+ except (ValueError, RuntimeError) as e:
48
+ raise RuntimeError(
49
+ "Failed to create Mirascope Cloud client. "
50
+ "Set MIRASCOPE_API_KEY environment variable or pass api_key parameter."
51
+ ) from e
52
+
53
+ exporter = MirascopeOTLPExporter(client=client)
54
+ provider = TracerProvider()
55
+ provider.add_span_processor(BatchSpanProcessor(exporter))
56
+
57
+ return provider
58
+
59
+
28
60
  def configure(
29
61
  *,
62
+ api_key: str | None = None,
63
+ base_url: str | None = None,
30
64
  tracer_provider: TracerProvider | None = None,
31
65
  tracer_name: str = DEFAULT_TRACER_NAME,
32
66
  tracer_version: str | None = None,
33
67
  ) -> None:
34
- """Configure the ops module defaults for tracing.
68
+ """Configure the ops module for tracing.
35
69
 
36
- Sets up default tracer settings for the ops module. If a tracer_provider
37
- is supplied, it will be installed as the global OpenTelemetry tracer provider.
70
+ When called without arguments, automatically configures Mirascope Cloud
71
+ using the MIRASCOPE_API_KEY environment variable.
38
72
 
39
73
  Args:
40
- tracer_provider: Optional OpenTelemetry TracerProvider to use as default
41
- and to install globally.
74
+ tracer_provider: Optional custom TracerProvider. If provided, this takes
75
+ precedence over automatic Mirascope Cloud configuration.
76
+ api_key: Optional Mirascope Cloud API key. If not provided, uses
77
+ MIRASCOPE_API_KEY environment variable.
42
78
  tracer_name: Tracer name to use when creating a tracer.
43
79
  Defaults to "mirascope.llm".
44
80
  tracer_version: Optional tracer version.
45
81
 
82
+ Raises:
83
+ RuntimeError: If no tracer_provider is given and Mirascope Cloud
84
+ cannot be configured (missing API key).
85
+
46
86
  Example:
87
+ Simple Mirascope Cloud configuration (recommended):
88
+ ```python
89
+ import os
90
+ os.environ["MIRASCOPE_API_KEY"] = "your-api-key"
47
91
 
48
- Configure custom tracer settings:
92
+ from mirascope import ops
93
+
94
+ ops.configure() # Automatically uses Mirascope Cloud
95
+ ```
96
+
97
+ With explicit API key:
98
+ ```python
99
+ from mirascope import ops
100
+
101
+ ops.configure(api_key="your-api-key")
102
+ ```
103
+
104
+ With custom tracer provider:
49
105
  ```python
50
106
  from mirascope import ops
51
107
  from opentelemetry.sdk.trace import TracerProvider
52
108
 
53
109
  provider = TracerProvider()
54
110
  ops.configure(tracer_provider=provider)
55
- ops.instrument_llm()
56
111
  ```
57
112
  """
58
- # TODO: refactor alongside other import error handling improvements
59
- if otel_trace is None: # pragma: no cover
60
- raise ImportError(
61
- "OpenTelemetry is not installed. Run `pip install mirascope[otel]` "
62
- "before calling `ops.configure(tracer_provider=...)`."
63
- )
64
-
65
113
  global _tracer_provider, _tracer_name, _tracer_version, _tracer
66
114
 
67
- if tracer_provider is not None:
68
- _tracer_provider = tracer_provider
69
- otel_trace.set_tracer_provider(tracer_provider)
115
+ # Update settings so get_sync_client/get_async_client can use these values
116
+ if api_key is not None or base_url is not None:
117
+ update_settings(api_key=api_key, base_url=base_url)
118
+
119
+ # If no tracer_provider given, auto-configure Mirascope Cloud
120
+ if tracer_provider is None:
121
+ tracer_provider = _create_mirascope_cloud_provider(
122
+ api_key=api_key, base_url=base_url
123
+ )
124
+
125
+ _tracer_provider = tracer_provider
126
+ otel_trace.set_tracer_provider(tracer_provider)
70
127
 
71
128
  _tracer_name = tracer_name
72
129
  _tracer_version = tracer_version
73
130
 
74
- if otel_trace is not None:
75
- provider = (
76
- otel_trace.get_tracer_provider()
77
- if _tracer_provider is None
78
- else _tracer_provider
79
- )
80
- _tracer = provider.get_tracer(_tracer_name, _tracer_version)
131
+ _tracer = tracer_provider.get_tracer(_tracer_name, _tracer_version)
81
132
 
82
133
 
83
134
  def set_tracer(tracer: Tracer | None) -> None:
@@ -233,24 +233,51 @@ class MirascopeOTLPExporter(SpanExporter):
233
233
  message=span.status.description or "",
234
234
  )
235
235
 
236
+ # Convert events
237
+ events = None
238
+ if span.events:
239
+ events = []
240
+ for event in span.events:
241
+ event_attrs: list[dict[str, object]] = []
242
+ if event.attributes:
243
+ for key, value in event.attributes.items():
244
+ # Convert to OTLP attribute format with typed values
245
+ attr_value = self._convert_event_attribute_value(value)
246
+ event_attrs.append({"key": key, "value": attr_value})
247
+ events.append(
248
+ {
249
+ "name": event.name,
250
+ "timeUnixNano": str(event.timestamp)
251
+ if event.timestamp
252
+ else None,
253
+ "attributes": event_attrs if event_attrs else None,
254
+ }
255
+ )
256
+
236
257
  trace_id = format(context.trace_id, "032x")
237
258
  span_id = format(context.span_id, "016x")
238
259
 
239
- return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem(
240
- trace_id=trace_id,
241
- span_id=span_id,
242
- parent_span_id=(
260
+ # Build kwargs, only including events if present (to avoid sending null)
261
+ kwargs: dict[str, object] = {
262
+ "trace_id": trace_id,
263
+ "span_id": span_id,
264
+ "parent_span_id": (
243
265
  format(span.parent.span_id, "016x")
244
266
  if span.parent and span.parent.span_id
245
267
  else None
246
268
  ),
247
- name=span.name,
248
- kind=span.kind.value if span.kind else 0,
249
- start_time_unix_nano=str(span.start_time) if span.start_time else "0",
250
- end_time_unix_nano=str(span.end_time) if span.end_time else "0",
251
- attributes=attributes or None,
252
- status=status,
253
- )
269
+ "name": span.name,
270
+ "kind": span.kind.value if span.kind else 0,
271
+ "start_time_unix_nano": str(span.start_time) if span.start_time else "0",
272
+ "end_time_unix_nano": str(span.end_time) if span.end_time else "0",
273
+ "attributes": attributes or None,
274
+ "status": status,
275
+ }
276
+ # Only include events if present (omit entirely to avoid sending null)
277
+ if events:
278
+ kwargs["events"] = events
279
+
280
+ return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem(**kwargs) # type: ignore[arg-type]
254
281
 
255
282
  def _convert_attribute_value(
256
283
  self, value: AttributeValue
@@ -288,6 +315,32 @@ class MirascopeOTLPExporter(SpanExporter):
288
315
  string_value=str(list(value))
289
316
  )
290
317
 
318
+ def _convert_event_attribute_value(
319
+ self, value: AttributeValue
320
+ ) -> dict[str, object]:
321
+ """Convert OpenTelemetry AttributeValue to OTLP event attribute value format.
322
+
323
+ This uses the OTLP JSON format with typed value wrappers (e.g., stringValue, intValue).
324
+
325
+ Args:
326
+ value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
327
+
328
+ Returns:
329
+ A dict with the typed value (e.g., {"stringValue": "..."})
330
+ """
331
+ match value:
332
+ case str():
333
+ return {"stringValue": value}
334
+ case bool():
335
+ return {"boolValue": value}
336
+ case int():
337
+ return {"intValue": str(value)}
338
+ case float():
339
+ return {"doubleValue": value}
340
+ case _:
341
+ # Sequences - convert to string representation
342
+ return {"stringValue": str(list(value))}
343
+
291
344
  def _convert_resource_attribute_value(
292
345
  self, value: AttributeValue
293
346
  ) -> TracesCreateRequestResourceSpansItemResourceAttributesItemValue: