mirascope 2.0.0a6__py3-none-any.whl → 2.0.2__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 (230) hide show
  1. mirascope/_utils.py +34 -0
  2. mirascope/api/_generated/__init__.py +186 -5
  3. mirascope/api/_generated/annotations/client.py +38 -6
  4. mirascope/api/_generated/annotations/raw_client.py +366 -47
  5. mirascope/api/_generated/annotations/types/annotations_create_response.py +19 -6
  6. mirascope/api/_generated/annotations/types/annotations_get_response.py +19 -6
  7. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +22 -7
  8. mirascope/api/_generated/annotations/types/annotations_update_response.py +19 -6
  9. mirascope/api/_generated/api_keys/__init__.py +12 -2
  10. mirascope/api/_generated/api_keys/client.py +107 -6
  11. mirascope/api/_generated/api_keys/raw_client.py +486 -38
  12. mirascope/api/_generated/api_keys/types/__init__.py +7 -1
  13. mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
  14. mirascope/api/_generated/client.py +36 -0
  15. mirascope/api/_generated/docs/raw_client.py +71 -9
  16. mirascope/api/_generated/environment.py +3 -3
  17. mirascope/api/_generated/environments/__init__.py +6 -0
  18. mirascope/api/_generated/environments/client.py +158 -9
  19. mirascope/api/_generated/environments/raw_client.py +620 -52
  20. mirascope/api/_generated/environments/types/__init__.py +10 -0
  21. mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
  22. mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
  23. mirascope/api/_generated/{organizations/types/organizations_credits_response.py → environments/types/environments_get_analytics_response_top_models_item.py} +6 -3
  24. mirascope/api/_generated/errors/__init__.py +6 -0
  25. mirascope/api/_generated/errors/bad_request_error.py +5 -2
  26. mirascope/api/_generated/errors/conflict_error.py +5 -2
  27. mirascope/api/_generated/errors/payment_required_error.py +15 -0
  28. mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
  29. mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
  30. mirascope/api/_generated/functions/__init__.py +10 -0
  31. mirascope/api/_generated/functions/client.py +222 -8
  32. mirascope/api/_generated/functions/raw_client.py +975 -134
  33. mirascope/api/_generated/functions/types/__init__.py +28 -4
  34. mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
  35. mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
  36. mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
  37. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
  38. mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
  39. mirascope/api/_generated/health/raw_client.py +74 -10
  40. mirascope/api/_generated/organization_invitations/__init__.py +33 -0
  41. mirascope/api/_generated/organization_invitations/client.py +546 -0
  42. mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
  43. mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
  44. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
  45. mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
  46. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
  47. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
  48. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
  49. mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
  50. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
  51. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
  52. mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
  53. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
  54. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
  55. mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
  56. mirascope/api/_generated/organization_memberships/__init__.py +19 -0
  57. mirascope/api/_generated/organization_memberships/client.py +302 -0
  58. mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
  59. mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
  60. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
  61. mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
  62. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
  63. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
  64. mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
  65. mirascope/api/_generated/organizations/__init__.py +26 -2
  66. mirascope/api/_generated/organizations/client.py +442 -20
  67. mirascope/api/_generated/organizations/raw_client.py +1763 -164
  68. mirascope/api/_generated/organizations/types/__init__.py +48 -2
  69. mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
  70. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
  71. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
  72. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
  73. mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
  74. mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
  75. mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
  76. mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
  77. mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
  78. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
  79. mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
  80. mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
  81. mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
  82. mirascope/api/_generated/project_memberships/__init__.py +25 -0
  83. mirascope/api/_generated/project_memberships/client.py +437 -0
  84. mirascope/api/_generated/project_memberships/raw_client.py +1039 -0
  85. mirascope/api/_generated/project_memberships/types/__init__.py +29 -0
  86. mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
  87. mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
  88. mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
  89. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
  90. mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
  91. mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
  92. mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
  93. mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
  94. mirascope/api/_generated/projects/raw_client.py +415 -58
  95. mirascope/api/_generated/reference.md +2767 -397
  96. mirascope/api/_generated/tags/__init__.py +19 -0
  97. mirascope/api/_generated/tags/client.py +504 -0
  98. mirascope/api/_generated/tags/raw_client.py +1288 -0
  99. mirascope/api/_generated/tags/types/__init__.py +17 -0
  100. mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
  101. mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
  102. mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
  103. mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
  104. mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
  105. mirascope/api/_generated/token_cost/__init__.py +7 -0
  106. mirascope/api/_generated/token_cost/client.py +160 -0
  107. mirascope/api/_generated/token_cost/raw_client.py +264 -0
  108. mirascope/api/_generated/token_cost/types/__init__.py +8 -0
  109. mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
  110. mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
  111. mirascope/api/_generated/traces/__init__.py +20 -0
  112. mirascope/api/_generated/traces/client.py +543 -0
  113. mirascope/api/_generated/traces/raw_client.py +1366 -96
  114. mirascope/api/_generated/traces/types/__init__.py +28 -0
  115. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +6 -0
  116. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
  117. mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
  118. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +0 -2
  119. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
  120. mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
  121. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
  122. mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
  123. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
  124. mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
  125. mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
  126. mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
  127. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +10 -1
  128. mirascope/api/_generated/types/__init__.py +32 -2
  129. mirascope/api/_generated/types/bad_request_error_body.py +50 -0
  130. mirascope/api/_generated/types/date.py +3 -0
  131. mirascope/api/_generated/types/immutable_resource_error.py +22 -0
  132. mirascope/api/_generated/types/internal_server_error_body.py +3 -3
  133. mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
  134. mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
  135. mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
  136. mirascope/api/_generated/types/rate_limit_error.py +31 -0
  137. mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
  138. mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
  139. mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
  140. mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
  141. mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
  142. mirascope/api/settings.py +19 -1
  143. mirascope/llm/__init__.py +53 -10
  144. mirascope/llm/calls/__init__.py +2 -1
  145. mirascope/llm/calls/calls.py +29 -20
  146. mirascope/llm/calls/decorator.py +21 -7
  147. mirascope/llm/content/tool_output.py +22 -5
  148. mirascope/llm/exceptions.py +284 -71
  149. mirascope/llm/formatting/__init__.py +17 -0
  150. mirascope/llm/formatting/format.py +112 -35
  151. mirascope/llm/formatting/output_parser.py +178 -0
  152. mirascope/llm/formatting/partial.py +80 -7
  153. mirascope/llm/formatting/primitives.py +192 -0
  154. mirascope/llm/formatting/types.py +20 -8
  155. mirascope/llm/messages/__init__.py +3 -0
  156. mirascope/llm/messages/_utils.py +34 -0
  157. mirascope/llm/models/__init__.py +5 -0
  158. mirascope/llm/models/models.py +137 -69
  159. mirascope/llm/{providers/base → models}/params.py +7 -57
  160. mirascope/llm/models/thinking_config.py +61 -0
  161. mirascope/llm/prompts/_utils.py +0 -32
  162. mirascope/llm/prompts/decorator.py +16 -5
  163. mirascope/llm/prompts/prompts.py +160 -92
  164. mirascope/llm/providers/__init__.py +1 -4
  165. mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
  166. mirascope/llm/providers/anthropic/_utils/beta_decode.py +18 -9
  167. mirascope/llm/providers/anthropic/_utils/beta_encode.py +62 -13
  168. mirascope/llm/providers/anthropic/_utils/decode.py +18 -9
  169. mirascope/llm/providers/anthropic/_utils/encode.py +26 -7
  170. mirascope/llm/providers/anthropic/_utils/errors.py +2 -2
  171. mirascope/llm/providers/anthropic/beta_provider.py +64 -18
  172. mirascope/llm/providers/anthropic/provider.py +91 -33
  173. mirascope/llm/providers/base/__init__.py +0 -4
  174. mirascope/llm/providers/base/_utils.py +55 -6
  175. mirascope/llm/providers/base/base_provider.py +116 -37
  176. mirascope/llm/providers/google/_utils/__init__.py +2 -0
  177. mirascope/llm/providers/google/_utils/decode.py +20 -7
  178. mirascope/llm/providers/google/_utils/encode.py +26 -7
  179. mirascope/llm/providers/google/_utils/errors.py +3 -2
  180. mirascope/llm/providers/google/provider.py +64 -18
  181. mirascope/llm/providers/mirascope/_utils.py +13 -17
  182. mirascope/llm/providers/mirascope/provider.py +49 -18
  183. mirascope/llm/providers/mlx/_utils.py +7 -2
  184. mirascope/llm/providers/mlx/encoding/base.py +5 -2
  185. mirascope/llm/providers/mlx/encoding/transformers.py +5 -2
  186. mirascope/llm/providers/mlx/mlx.py +23 -6
  187. mirascope/llm/providers/mlx/provider.py +42 -13
  188. mirascope/llm/providers/openai/_utils/errors.py +2 -2
  189. mirascope/llm/providers/openai/completions/_utils/encode.py +20 -16
  190. mirascope/llm/providers/openai/completions/base_provider.py +40 -11
  191. mirascope/llm/providers/openai/provider.py +40 -10
  192. mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
  193. mirascope/llm/providers/openai/responses/_utils/decode.py +19 -6
  194. mirascope/llm/providers/openai/responses/_utils/encode.py +22 -10
  195. mirascope/llm/providers/openai/responses/provider.py +56 -18
  196. mirascope/llm/providers/provider_registry.py +93 -19
  197. mirascope/llm/responses/__init__.py +6 -1
  198. mirascope/llm/responses/_utils.py +102 -12
  199. mirascope/llm/responses/base_response.py +5 -2
  200. mirascope/llm/responses/base_stream_response.py +115 -25
  201. mirascope/llm/responses/response.py +2 -1
  202. mirascope/llm/responses/root_response.py +89 -17
  203. mirascope/llm/responses/stream_response.py +6 -9
  204. mirascope/llm/tools/decorator.py +9 -4
  205. mirascope/llm/tools/tool_schema.py +17 -6
  206. mirascope/llm/tools/toolkit.py +35 -27
  207. mirascope/llm/tools/tools.py +45 -20
  208. mirascope/ops/__init__.py +4 -0
  209. mirascope/ops/_internal/closure.py +4 -1
  210. mirascope/ops/_internal/configuration.py +82 -31
  211. mirascope/ops/_internal/exporters/exporters.py +55 -35
  212. mirascope/ops/_internal/exporters/utils.py +37 -0
  213. mirascope/ops/_internal/instrumentation/llm/common.py +530 -0
  214. mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
  215. mirascope/ops/_internal/instrumentation/llm/encode.py +1 -1
  216. mirascope/ops/_internal/instrumentation/llm/llm.py +116 -1242
  217. mirascope/ops/_internal/instrumentation/llm/model.py +1798 -0
  218. mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
  219. mirascope/ops/_internal/instrumentation/llm/serialize.py +300 -0
  220. mirascope/ops/_internal/protocols.py +83 -1
  221. mirascope/ops/_internal/traced_calls.py +18 -0
  222. mirascope/ops/_internal/traced_functions.py +125 -10
  223. mirascope/ops/_internal/tracing.py +78 -1
  224. mirascope/ops/_internal/utils.py +60 -4
  225. mirascope/ops/_internal/versioned_functions.py +1 -1
  226. {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/METADATA +12 -11
  227. mirascope-2.0.2.dist-info/RECORD +424 -0
  228. {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/licenses/LICENSE +1 -1
  229. mirascope-2.0.0a6.dist-info/RECORD +0 -316
  230. {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.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",
@@ -738,12 +738,15 @@ class _DependencyCollector:
738
738
  # For Python 3.13+
739
739
  return definition.func # pyright: ignore[reportFunctionMemberAccess] # pragma: no cover
740
740
 
741
+ # Handle objects with .fn but no __qualname__ (e.g., old-style wrappers).
742
+ # With copy_function_metadata() now copying __qualname__ to ToolSchema, Prompt,
743
+ # Call, etc., this branch is no longer reached in normal usage.
741
744
  if (
742
745
  (wrapped_function := getattr(definition, "fn", None)) is not None
743
746
  and not hasattr(definition, "__qualname__")
744
747
  and callable(wrapped_function)
745
748
  ):
746
- return wrapped_function
749
+ return wrapped_function # pragma: no cover
747
750
 
748
751
  return definition
749
752
 
@@ -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:
@@ -20,14 +20,17 @@ from ....api._generated.traces.types import (
20
20
  TracesCreateRequestResourceSpansItemResource,
21
21
  TracesCreateRequestResourceSpansItemResourceAttributesItem,
22
22
  TracesCreateRequestResourceSpansItemResourceAttributesItemValue,
23
+ TracesCreateRequestResourceSpansItemResourceAttributesItemValueArrayValue,
23
24
  TracesCreateRequestResourceSpansItemScopeSpansItem,
24
25
  TracesCreateRequestResourceSpansItemScopeSpansItemScope,
25
26
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem,
26
27
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItem,
27
28
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue,
29
+ TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValueArrayValue,
28
30
  TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemStatus,
29
31
  )
30
32
  from ....api.client import Mirascope
33
+ from .utils import to_otlp_any_value
31
34
 
32
35
  logger = logging.getLogger(__name__)
33
36
 
@@ -233,39 +236,56 @@ class MirascopeOTLPExporter(SpanExporter):
233
236
  message=span.status.description or "",
234
237
  )
235
238
 
239
+ # Convert events
240
+ events = None
241
+ if span.events:
242
+ events = []
243
+ for event in span.events:
244
+ event_attrs: list[dict[str, object]] = []
245
+ if event.attributes:
246
+ for key, value in event.attributes.items():
247
+ # Convert to OTLP attribute format with typed values
248
+ attr_value = self._convert_event_attribute_value(value)
249
+ event_attrs.append({"key": key, "value": attr_value})
250
+ events.append(
251
+ {
252
+ "name": event.name,
253
+ "timeUnixNano": str(event.timestamp)
254
+ if event.timestamp
255
+ else None,
256
+ "attributes": event_attrs if event_attrs else None,
257
+ }
258
+ )
259
+
236
260
  trace_id = format(context.trace_id, "032x")
237
261
  span_id = format(context.span_id, "016x")
238
262
 
239
- return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem(
240
- trace_id=trace_id,
241
- span_id=span_id,
242
- parent_span_id=(
263
+ # Build kwargs, only including events if present (to avoid sending null)
264
+ kwargs: dict[str, object] = {
265
+ "trace_id": trace_id,
266
+ "span_id": span_id,
267
+ "parent_span_id": (
243
268
  format(span.parent.span_id, "016x")
244
269
  if span.parent and span.parent.span_id
245
270
  else None
246
271
  ),
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
- )
272
+ "name": span.name,
273
+ "kind": span.kind.value if span.kind else 0,
274
+ "start_time_unix_nano": str(span.start_time) if span.start_time else "0",
275
+ "end_time_unix_nano": str(span.end_time) if span.end_time else "0",
276
+ "attributes": attributes or None,
277
+ "status": status,
278
+ }
279
+ # Only include events if present (omit entirely to avoid sending null)
280
+ if events:
281
+ kwargs["events"] = events
282
+
283
+ return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem(**kwargs) # type: ignore[arg-type]
254
284
 
255
285
  def _convert_attribute_value(
256
286
  self, value: AttributeValue
257
287
  ) -> TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue:
258
- """Convert OpenTelemetry AttributeValue to Mirascope API's KeyValueValue.
259
-
260
- This conversion is necessary because the Fern-generated API client
261
- expects KeyValueValue objects, not OpenTelemetry's AttributeValue types.
262
-
263
- Args:
264
- value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
265
-
266
- Returns:
267
- A KeyValueValue object for the Mirascope API
268
- """
288
+ """Convert OpenTelemetry AttributeValue to Mirascope API's KeyValueValue."""
269
289
  match value:
270
290
  case str():
271
291
  return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue(
@@ -285,23 +305,21 @@ class MirascopeOTLPExporter(SpanExporter):
285
305
  )
286
306
  case _:
287
307
  return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValue(
288
- string_value=str(list(value))
308
+ array_value=TracesCreateRequestResourceSpansItemScopeSpansItemSpansItemAttributesItemValueArrayValue(
309
+ values=[to_otlp_any_value(v) for v in value]
310
+ )
289
311
  )
290
312
 
313
+ def _convert_event_attribute_value(
314
+ self, value: AttributeValue
315
+ ) -> dict[str, object]:
316
+ """Convert OpenTelemetry AttributeValue to OTLP event attribute value format."""
317
+ return to_otlp_any_value(value)
318
+
291
319
  def _convert_resource_attribute_value(
292
320
  self, value: AttributeValue
293
321
  ) -> TracesCreateRequestResourceSpansItemResourceAttributesItemValue:
294
- """Convert OpenTelemetry AttributeValue to Mirascope API's resource KeyValueValue.
295
-
296
- This conversion is necessary because the Fern-generated API client
297
- expects KeyValueValue objects, not OpenTelemetry's AttributeValue types.
298
-
299
- Args:
300
- value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
301
-
302
- Returns:
303
- A KeyValueValue object for the Mirascope API resource attributes
304
- """
322
+ """Convert OpenTelemetry AttributeValue to Mirascope API's resource KeyValueValue."""
305
323
  match value:
306
324
  case str():
307
325
  return TracesCreateRequestResourceSpansItemResourceAttributesItemValue(
@@ -321,7 +339,9 @@ class MirascopeOTLPExporter(SpanExporter):
321
339
  )
322
340
  case _:
323
341
  return TracesCreateRequestResourceSpansItemResourceAttributesItemValue(
324
- string_value=str(list(value))
342
+ array_value=TracesCreateRequestResourceSpansItemResourceAttributesItemValueArrayValue(
343
+ values=[to_otlp_any_value(v) for v in value]
344
+ )
325
345
  )
326
346
 
327
347
  def shutdown(self) -> None:
@@ -4,6 +4,43 @@ This module provides helper functions for formatting and converting
4
4
  OpenTelemetry data types for export.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping, Sequence
10
+
11
+ from opentelemetry.util.types import AttributeValue
12
+
13
+
14
+ def to_otlp_any_value(value: AttributeValue) -> dict[str, object]:
15
+ """Convert AttributeValue to OTLP AnyValue (dict form).
16
+
17
+ - string/bool/int/float are converted to stringValue/boolValue/intValue/doubleValue
18
+ - Sequence (excluding str/bytes/Mapping) is converted to arrayValue.values
19
+ - Unsupported types fallback to stringValue=str(value)
20
+
21
+ Args:
22
+ value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
23
+
24
+ Returns:
25
+ A dict representing OTLP AnyValue (e.g., {"stringValue": "..."})
26
+ """
27
+ match value:
28
+ case str():
29
+ return {"stringValue": value}
30
+ case bool():
31
+ return {"boolValue": value}
32
+ case int():
33
+ return {"intValue": str(value)}
34
+ case float():
35
+ return {"doubleValue": value}
36
+ case _ if isinstance(value, bytes | bytearray | memoryview | Mapping):
37
+ return {"stringValue": str(value)}
38
+ case _ if isinstance(value, Sequence):
39
+ values = [to_otlp_any_value(v) for v in value]
40
+ return {"arrayValue": {"values": values}}
41
+ case _:
42
+ return {"stringValue": str(value)}
43
+
7
44
 
8
45
  def format_trace_id(trace_id: int) -> str:
9
46
  """Format a trace ID as a 32-character hex string.