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
@@ -1,1238 +1,68 @@
1
- """OpenTelemetry GenAI instrumentation for `mirascope.llm`."""
1
+ """OpenTelemetry GenAI instrumentation for `mirascope.llm`.
2
+
3
+ This module provides the public instrument_llm() and uninstrument_llm() functions
4
+ that enable/disable instrumentation for Model calls and Response.resume methods.
5
+ """
2
6
 
3
7
  from __future__ import annotations
4
8
 
5
9
  import os
6
- import weakref
7
- from collections.abc import AsyncIterator, Callable, Iterator, Mapping, Sequence
8
- from contextlib import AbstractContextManager, contextmanager
9
- from dataclasses import dataclass
10
- from functools import wraps
11
- from types import TracebackType
12
- from typing import TYPE_CHECKING, Protocol, TypeAlias, overload, runtime_checkable
13
- from typing_extensions import TypeIs
14
-
15
- from opentelemetry.semconv._incubating.attributes import (
16
- gen_ai_attributes as GenAIAttributes,
17
- )
18
- from opentelemetry.semconv.attributes import (
19
- error_attributes as ErrorAttributes,
20
- )
21
10
 
22
- from .....llm.context import Context, DepsT
23
- from .....llm.formatting import Format, FormattableT
24
- from .....llm.messages import Message
25
- from .....llm.models import Model
26
- from .....llm.providers import Params, ProviderId
27
- from .....llm.providers.model_id import ModelId
28
- from .....llm.responses import (
29
- AsyncContextResponse,
30
- AsyncContextStreamResponse,
31
- AsyncResponse,
32
- ContextResponse,
33
- ContextStreamResponse,
34
- Response,
35
- StreamResponse,
36
- StreamResponseChunk,
37
- )
38
- from .....llm.responses.root_response import RootResponse
39
- from .....llm.tools import (
40
- AnyToolFn,
41
- AnyToolSchema,
42
- AsyncContextTool,
43
- AsyncContextToolkit,
44
- AsyncTool,
45
- AsyncToolkit,
46
- ContextTool,
47
- ContextToolkit,
48
- Tool,
49
- Toolkit,
50
- )
51
- from .....llm.tools.tool_schema import ToolSchema
52
- from .....llm.tools.toolkit import BaseToolkit, ToolkitT
53
- from .....llm.types import Jsonable
54
- from ...configuration import (
55
- get_tracer,
11
+ from opentelemetry import trace as otel_trace
12
+
13
+ from ...configuration import get_tracer
14
+ from .model import (
15
+ unwrap_model_call,
16
+ unwrap_model_call_async,
17
+ unwrap_model_context_call,
18
+ unwrap_model_context_call_async,
19
+ unwrap_model_context_resume,
20
+ unwrap_model_context_resume_async,
21
+ unwrap_model_context_resume_stream,
22
+ unwrap_model_context_resume_stream_async,
23
+ unwrap_model_context_stream,
24
+ unwrap_model_context_stream_async,
25
+ unwrap_model_resume,
26
+ unwrap_model_resume_async,
27
+ unwrap_model_resume_stream,
28
+ unwrap_model_resume_stream_async,
29
+ unwrap_model_stream,
30
+ unwrap_model_stream_async,
31
+ wrap_model_call,
32
+ wrap_model_call_async,
33
+ wrap_model_context_call,
34
+ wrap_model_context_call_async,
35
+ wrap_model_context_resume,
36
+ wrap_model_context_resume_async,
37
+ wrap_model_context_resume_stream,
38
+ wrap_model_context_resume_stream_async,
39
+ wrap_model_context_stream,
40
+ wrap_model_context_stream_async,
41
+ wrap_model_resume,
42
+ wrap_model_resume_async,
43
+ wrap_model_resume_stream,
44
+ wrap_model_resume_stream_async,
45
+ wrap_model_stream,
46
+ wrap_model_stream_async,
56
47
  )
57
- from ...utils import json_dumps
58
- from .encode import (
59
- map_finish_reason,
60
- snapshot_from_root_response,
61
- split_request_messages,
62
- )
63
-
64
- # TODO: refactor alongside all other import error handling to provide nice error messages
65
- try:
66
- from opentelemetry import trace as otel_trace
67
- from opentelemetry.trace import SpanKind, Status, StatusCode
68
- except ImportError: # pragma: no cover
69
- if not TYPE_CHECKING:
70
- otel_trace = None
71
- SpanKind = None
72
- StatusCode = None
73
- Status = None
74
-
75
- if TYPE_CHECKING:
76
- from opentelemetry import trace as otel_trace
77
- from opentelemetry.trace import (
78
- Span,
79
- SpanKind,
80
- Status,
81
- StatusCode,
82
- Tracer,
83
- )
84
- from opentelemetry.util.types import AttributeValue
85
-
86
- from . import gen_ai_types
87
- else:
88
- AttributeValue = None
89
- Span = None
90
- Tracer = None
91
-
92
-
93
- ToolsParam: TypeAlias = (
94
- Sequence[ToolSchema[AnyToolFn]] | BaseToolkit[AnyToolSchema] | None
48
+ from .response import (
49
+ unwrap_async_context_response_resume,
50
+ unwrap_async_context_stream_response_resume,
51
+ unwrap_async_response_resume,
52
+ unwrap_async_stream_response_resume,
53
+ unwrap_context_response_resume,
54
+ unwrap_context_stream_response_resume,
55
+ unwrap_response_resume,
56
+ unwrap_stream_response_resume,
57
+ wrap_async_context_response_resume,
58
+ wrap_async_context_stream_response_resume,
59
+ wrap_async_response_resume,
60
+ wrap_async_stream_response_resume,
61
+ wrap_context_response_resume,
62
+ wrap_context_stream_response_resume,
63
+ wrap_response_resume,
64
+ wrap_stream_response_resume,
95
65
  )
96
- FormatParam: TypeAlias = Format[FormattableT] | None
97
- ParamsDict: TypeAlias = Mapping[str, str | int | float | bool | Sequence[str] | None]
98
- SpanAttributes: TypeAlias = Mapping[str, AttributeValue]
99
- AttributeSetter: TypeAlias = Callable[[str, AttributeValue], None]
100
- ParamsValue = str | int | float | bool | Sequence[str] | None
101
-
102
-
103
- @dataclass(slots=True)
104
- class SpanContext:
105
- """Container for a GenAI span and its associated dropped parameters."""
106
-
107
- span: Span | None
108
- """The active span, if any."""
109
-
110
- dropped_params: dict[str, Jsonable]
111
- """Parameters that could not be recorded as span attributes."""
112
-
113
-
114
- @runtime_checkable
115
- class Identifiable(Protocol):
116
- """Protocol for objects with an optional ID attribute."""
117
-
118
- id: str | None
119
- """Optional ID attribute."""
120
-
121
-
122
- _PARAM_ATTRIBUTE_MAP: Mapping[str, str] = {
123
- "temperature": GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE,
124
- "max_tokens": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS,
125
- "max_output_tokens": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS,
126
- "max_completion_tokens": GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS,
127
- "top_p": GenAIAttributes.GEN_AI_REQUEST_TOP_P,
128
- "top_k": GenAIAttributes.GEN_AI_REQUEST_TOP_K,
129
- "frequency_penalty": GenAIAttributes.GEN_AI_REQUEST_FREQUENCY_PENALTY,
130
- "presence_penalty": GenAIAttributes.GEN_AI_REQUEST_PRESENCE_PENALTY,
131
- "seed": GenAIAttributes.GEN_AI_REQUEST_SEED,
132
- "stop_sequences": GenAIAttributes.GEN_AI_REQUEST_STOP_SEQUENCES,
133
- "stop": GenAIAttributes.GEN_AI_REQUEST_STOP_SEQUENCES,
134
- "n": GenAIAttributes.GEN_AI_REQUEST_CHOICE_COUNT,
135
- "choice_count": GenAIAttributes.GEN_AI_REQUEST_CHOICE_COUNT,
136
- }
137
-
138
-
139
- def _record_exception(span: Span, exc: Exception) -> None:
140
- """Record exception details on span following OpenTelemetry semantic conventions."""
141
- span.record_exception(exc)
142
- span.set_attribute(ErrorAttributes.ERROR_TYPE, exc.__class__.__name__)
143
- error_message = str(exc)
144
- if error_message:
145
- span.set_attribute("error.message", error_message)
146
- span.set_status(Status(StatusCode.ERROR, error_message))
147
-
148
-
149
- def _infer_output_type(format_obj: FormatParam) -> str:
150
- """Infer the GenAI output type from the format parameter."""
151
- if format_obj is None:
152
- return GenAIAttributes.GenAiOutputTypeValues.TEXT.value
153
- return GenAIAttributes.GenAiOutputTypeValues.JSON.value
154
-
155
-
156
- def _apply_param_attributes(
157
- attrs: dict[str, AttributeValue], params: ParamsDict
158
- ) -> None:
159
- """Apply model parameters as span attributes."""
160
- if not params:
161
- return
162
-
163
- for key, attr_key in _PARAM_ATTRIBUTE_MAP.items():
164
- if key not in params:
165
- continue
166
- value = params[key]
167
- if value is None:
168
- continue
169
- if key in {"stop", "stop_sequences"} and isinstance(value, str):
170
- value = [value]
171
- attrs[attr_key] = value
172
-
173
-
174
- def _set_json_attribute(
175
- setter: AttributeSetter,
176
- *,
177
- key: str,
178
- payload: (
179
- gen_ai_types.SystemInstructions
180
- | gen_ai_types.InputMessages
181
- | gen_ai_types.OutputMessages
182
- ),
183
- ) -> None:
184
- """Assign a JSON-serialized attribute to a span."""
185
- if not payload:
186
- return
187
- setter(key, json_dumps(payload))
188
-
189
-
190
- def _assign_request_message_attributes(
191
- setter: AttributeSetter,
192
- *,
193
- messages: Sequence[Message],
194
- ) -> None:
195
- """Assign request message attributes to a span."""
196
- system_payload, input_payload = split_request_messages(messages)
197
- _set_json_attribute(
198
- setter,
199
- key=GenAIAttributes.GEN_AI_SYSTEM_INSTRUCTIONS,
200
- payload=system_payload,
201
- )
202
- _set_json_attribute(
203
- setter,
204
- key=GenAIAttributes.GEN_AI_INPUT_MESSAGES,
205
- payload=input_payload,
206
- )
207
-
208
-
209
- def _collect_tool_schemas(
210
- tools: Sequence[ToolSchema[AnyToolFn]] | BaseToolkit[AnyToolSchema],
211
- ) -> list[ToolSchema[AnyToolFn]]:
212
- """Collect ToolSchema instances from a tools parameter."""
213
- iterable = list(tools.tools) if isinstance(tools, BaseToolkit) else list(tools)
214
- schemas: list[ToolSchema[AnyToolFn]] = []
215
- for tool in iterable:
216
- if isinstance(tool, ToolSchema):
217
- schemas.append(tool)
218
- return schemas
219
-
220
-
221
- def _serialize_tool_definitions(
222
- tools: ToolsParam,
223
- format: FormatParam = None,
224
- ) -> str | None:
225
- """Serialize tool definitions to JSON for span attributes."""
226
- if tools is None:
227
- tool_schemas: list[ToolSchema[AnyToolFn]] = []
228
- else:
229
- tool_schemas = _collect_tool_schemas(tools)
230
-
231
- if isinstance(format, Format) and format.mode == "tool":
232
- tool_schemas.append(format.create_tool_schema())
233
-
234
- if not tool_schemas:
235
- return None
236
- definitions: list[dict[str, str | int | bool | dict[str, str | int | bool]]] = []
237
- for tool in tool_schemas:
238
- definitions.append(
239
- {
240
- "name": tool.name,
241
- "description": tool.description,
242
- "strict": tool.strict,
243
- "parameters": tool.parameters.model_dump(by_alias=True, mode="json"),
244
- }
245
- )
246
- return json_dumps(definitions)
247
-
248
-
249
- def _build_request_attributes(
250
- *,
251
- operation: str,
252
- provider: ProviderId,
253
- model_id: ModelId,
254
- messages: Sequence[Message],
255
- tools: ToolsParam,
256
- format: FormatParam,
257
- params: ParamsDict,
258
- ) -> dict[str, AttributeValue]:
259
- """Build GenAI request attributes for a span."""
260
- attrs: dict[str, AttributeValue] = {
261
- GenAIAttributes.GEN_AI_OPERATION_NAME: operation,
262
- GenAIAttributes.GEN_AI_PROVIDER_NAME: provider,
263
- GenAIAttributes.GEN_AI_REQUEST_MODEL: model_id,
264
- GenAIAttributes.GEN_AI_OUTPUT_TYPE: _infer_output_type(format),
265
- }
266
- _apply_param_attributes(attrs, params)
267
-
268
- _assign_request_message_attributes(
269
- attrs.__setitem__,
270
- messages=messages,
271
- )
272
-
273
- tool_payload = _serialize_tool_definitions(tools, format=format)
274
- if tool_payload:
275
- # The incubating semconv module does not yet expose a constant for this key.
276
- attrs["gen_ai.tool.definitions"] = tool_payload
277
-
278
- return attrs
279
-
280
-
281
- def _extract_response_id(
282
- raw: dict[str, str | int] | str | Identifiable | None,
283
- ) -> str | None:
284
- """Extract response ID from raw response data."""
285
- if isinstance(raw, dict):
286
- for key in ("id", "response_id", "responseId"):
287
- value = raw.get(key)
288
- if isinstance(value, str):
289
- return value
290
- elif isinstance(raw, Identifiable):
291
- return raw.id
292
- return None
293
-
294
-
295
- def _attach_response(
296
- span: Span,
297
- response: RootResponse[ToolkitT, FormattableT | None],
298
- *,
299
- request_messages: Sequence[Message],
300
- ) -> None:
301
- """Attach response attributes to a GenAI span."""
302
- span.set_attribute(GenAIAttributes.GEN_AI_RESPONSE_MODEL, response.model_id)
303
- span.set_attribute(
304
- GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS,
305
- [map_finish_reason(response.finish_reason)],
306
- )
307
- response_id = _extract_response_id(getattr(response, "raw", None))
308
- if response_id:
309
- span.set_attribute(GenAIAttributes.GEN_AI_RESPONSE_ID, response_id)
310
-
311
- snapshot = snapshot_from_root_response(
312
- response,
313
- request_messages=request_messages,
314
- )
315
- _set_json_attribute(
316
- span.set_attribute,
317
- key=GenAIAttributes.GEN_AI_SYSTEM_INSTRUCTIONS,
318
- payload=snapshot.system_instructions,
319
- )
320
- _set_json_attribute(
321
- span.set_attribute,
322
- key=GenAIAttributes.GEN_AI_INPUT_MESSAGES,
323
- payload=snapshot.inputs,
324
- )
325
- _set_json_attribute(
326
- span.set_attribute,
327
- key=GenAIAttributes.GEN_AI_OUTPUT_MESSAGES,
328
- payload=snapshot.outputs,
329
- )
330
- # TODO: Emit gen_ai.usage metrics once Response exposes provider-agnostic usage fields.
331
-
332
-
333
- _ORIGINAL_MODEL_CALL = Model.call
334
- _MODEL_CALL_WRAPPED = False
335
- _ORIGINAL_MODEL_CALL_ASYNC = Model.call_async
336
- _MODEL_CALL_ASYNC_WRAPPED = False
337
- _ORIGINAL_MODEL_CONTEXT_CALL = Model.context_call
338
- _MODEL_CONTEXT_CALL_WRAPPED = False
339
- _ORIGINAL_MODEL_CONTEXT_CALL_ASYNC = Model.context_call_async
340
- _MODEL_CONTEXT_CALL_ASYNC_WRAPPED = False
341
- _ORIGINAL_MODEL_STREAM = Model.stream
342
- _MODEL_STREAM_WRAPPED = False
343
- _ORIGINAL_MODEL_CONTEXT_STREAM = Model.context_stream
344
- _MODEL_CONTEXT_STREAM_WRAPPED = False
345
- _ORIGINAL_MODEL_CONTEXT_STREAM_ASYNC = Model.context_stream_async
346
- _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED = False
347
-
348
-
349
- def _is_supported_param_value(value: object) -> TypeIs[ParamsValue]:
350
- """Returns True if the value can be exported as an OTEL attribute."""
351
- if isinstance(value, str | int | float | bool) or value is None:
352
- return True
353
- if isinstance(value, Sequence) and not isinstance(value, str | bytes):
354
- return all(isinstance(item, str) for item in value)
355
- return False
356
-
357
-
358
- def _normalize_dropped_value(value: object) -> Jsonable:
359
- """Returns a JSON-safe representation for unsupported param values."""
360
- if isinstance(value, str | int | float | bool) or value is None:
361
- return value
362
- if isinstance(value, Mapping):
363
- normalized: dict[str, Jsonable] = {}
364
- for key, item in value.items():
365
- normalized[str(key)] = _normalize_dropped_value(item)
366
- return normalized
367
- if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
368
- return [_normalize_dropped_value(item) for item in value]
369
- try:
370
- return str(value)
371
- except Exception: # pragma: no cover
372
- return f"<{type(value).__name__}>"
373
-
374
-
375
- def _params_as_mapping(params: Params) -> tuple[ParamsDict, dict[str, Jsonable]]:
376
- """Returns supported params and a mapping of dropped params."""
377
- filtered: dict[str, ParamsValue] = {}
378
- dropped: dict[str, Jsonable] = {}
379
- for key, value in params.items():
380
- if _is_supported_param_value(value):
381
- filtered[key] = value
382
- else:
383
- dropped[key] = _normalize_dropped_value(value)
384
- return filtered, dropped
385
-
386
-
387
- def _record_dropped_params(
388
- span: Span,
389
- dropped_params: Mapping[str, Jsonable],
390
- ) -> None:
391
- """Emit an event with JSON-encoded params that cannot become attributes.
392
-
393
- See https://opentelemetry.io/docs/specs/otel/common/ for the attribute type limits,
394
- https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-events/ for the GenAI
395
- guidance on recording richer payloads via events, and
396
- https://opentelemetry.io/blog/2025/complex-attribute-types/ for the recommendation
397
- to serialize unsupported complex types instead of dropping them outright.
398
- """
399
- if not dropped_params:
400
- return None
401
- payload = json_dumps(dropped_params)
402
- span.add_event(
403
- "gen_ai.request.params.untracked",
404
- attributes={
405
- "gen_ai.untracked_params.count": len(dropped_params),
406
- "gen_ai.untracked_params.keys": list(dropped_params.keys()),
407
- "gen_ai.untracked_params.json": payload,
408
- },
409
- )
410
- return None
411
-
412
-
413
- @contextmanager
414
- def _start_model_span(
415
- model: Model,
416
- *,
417
- messages: Sequence[Message],
418
- tools: ToolsParam,
419
- format: FormatParam,
420
- activate: bool = True,
421
- ) -> Iterator[SpanContext]:
422
- """Context manager that yields a SpanContext for a model call."""
423
- params, dropped_params = _params_as_mapping(model.params)
424
- tracer = get_tracer()
425
-
426
- if tracer is None or otel_trace is None:
427
- yield SpanContext(None, dropped_params)
428
- return
429
-
430
- operation = GenAIAttributes.GenAiOperationNameValues.CHAT.value
431
- attrs = _build_request_attributes(
432
- operation=operation,
433
- provider=model.provider_id,
434
- model_id=model.model_id,
435
- messages=messages,
436
- tools=tools,
437
- format=format,
438
- params=params,
439
- )
440
- span_name = f"{operation} {model.model_id}"
441
-
442
- if activate:
443
- with tracer.start_as_current_span(
444
- name=span_name,
445
- kind=SpanKind.CLIENT,
446
- ) as active_span:
447
- for key, value in attrs.items():
448
- active_span.set_attribute(key, value)
449
- try:
450
- yield SpanContext(active_span, dropped_params)
451
- except Exception as exc:
452
- _record_exception(active_span, exc)
453
- raise
454
- return
455
-
456
- span = tracer.start_span(
457
- name=span_name,
458
- kind=SpanKind.CLIENT,
459
- )
460
- for key, value in attrs.items():
461
- span.set_attribute(key, value)
462
- try:
463
- yield SpanContext(span, dropped_params)
464
- except Exception as exc:
465
- _record_exception(span, exc)
466
- raise
467
- finally:
468
- span.end()
469
-
470
-
471
- @overload
472
- def _instrumented_model_call(
473
- self: Model,
474
- *,
475
- messages: Sequence[Message],
476
- tools: Sequence[Tool] | Toolkit | None = None,
477
- format: None = None,
478
- ) -> Response: ...
479
-
480
-
481
- @overload
482
- def _instrumented_model_call(
483
- self: Model,
484
- *,
485
- messages: Sequence[Message],
486
- tools: Sequence[Tool] | Toolkit | None = None,
487
- format: type[FormattableT] | Format[FormattableT],
488
- ) -> Response[FormattableT]: ...
489
-
490
-
491
- @overload
492
- def _instrumented_model_call(
493
- self: Model,
494
- *,
495
- messages: Sequence[Message],
496
- tools: Sequence[Tool] | Toolkit | None = None,
497
- format: type[FormattableT] | Format[FormattableT] | None = None,
498
- ) -> Response | Response[FormattableT]: ...
499
-
500
-
501
- @wraps(_ORIGINAL_MODEL_CALL)
502
- def _instrumented_model_call(
503
- self: Model,
504
- *,
505
- messages: Sequence[Message],
506
- tools: Sequence[Tool] | Toolkit | None = None,
507
- format: FormatParam = None,
508
- ) -> Response | Response[FormattableT]:
509
- """Returns a GenAI-instrumented result of `Model.call`."""
510
- with _start_model_span(
511
- self,
512
- messages=messages,
513
- tools=tools,
514
- format=format,
515
- ) as span_ctx:
516
- response = _ORIGINAL_MODEL_CALL(
517
- self,
518
- messages=messages,
519
- tools=tools,
520
- format=format,
521
- )
522
- if span_ctx.span is not None:
523
- _attach_response(
524
- span_ctx.span,
525
- response,
526
- request_messages=messages,
527
- )
528
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
529
- return response
530
-
531
-
532
- def _wrap_model_call() -> None:
533
- """Returns None. Replaces `Model.call` with the instrumented wrapper."""
534
- global _MODEL_CALL_WRAPPED
535
- if _MODEL_CALL_WRAPPED:
536
- return
537
- Model.call = _instrumented_model_call
538
- _MODEL_CALL_WRAPPED = True
539
-
540
-
541
- def _unwrap_model_call() -> None:
542
- """Returns None. Restores the original `Model.call` implementation."""
543
- global _MODEL_CALL_WRAPPED
544
- if not _MODEL_CALL_WRAPPED:
545
- return
546
- Model.call = _ORIGINAL_MODEL_CALL
547
- _MODEL_CALL_WRAPPED = False
548
-
549
-
550
- @overload
551
- async def _instrumented_model_call_async(
552
- self: Model,
553
- *,
554
- messages: Sequence[Message],
555
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
556
- format: None = None,
557
- ) -> AsyncResponse: ...
558
-
559
-
560
- @overload
561
- async def _instrumented_model_call_async(
562
- self: Model,
563
- *,
564
- messages: Sequence[Message],
565
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
566
- format: type[FormattableT] | Format[FormattableT],
567
- ) -> AsyncResponse[FormattableT]: ...
568
-
569
-
570
- @overload
571
- async def _instrumented_model_call_async(
572
- self: Model,
573
- *,
574
- messages: Sequence[Message],
575
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
576
- format: type[FormattableT] | Format[FormattableT] | None = None,
577
- ) -> AsyncResponse | AsyncResponse[FormattableT]: ...
578
-
579
-
580
- @wraps(_ORIGINAL_MODEL_CALL_ASYNC)
581
- async def _instrumented_model_call_async(
582
- self: Model,
583
- *,
584
- messages: Sequence[Message],
585
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
586
- format: FormatParam = None,
587
- ) -> AsyncResponse | AsyncResponse[FormattableT]:
588
- """Returns a GenAI-instrumented result of `Model.call_async`."""
589
- with _start_model_span(
590
- self,
591
- messages=messages,
592
- tools=tools,
593
- format=format,
594
- activate=True,
595
- ) as span_ctx:
596
- response = await _ORIGINAL_MODEL_CALL_ASYNC(
597
- self,
598
- messages=messages,
599
- tools=tools,
600
- format=format,
601
- )
602
- if span_ctx.span is not None:
603
- _attach_response(
604
- span_ctx.span,
605
- response,
606
- request_messages=messages,
607
- )
608
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
609
- return response
610
-
611
-
612
- def _wrap_model_call_async() -> None:
613
- """Returns None. Replaces `Model.call_async` with the instrumented wrapper."""
614
- global _MODEL_CALL_ASYNC_WRAPPED
615
- if _MODEL_CALL_ASYNC_WRAPPED:
616
- return
617
- Model.call_async = _instrumented_model_call_async
618
- _MODEL_CALL_ASYNC_WRAPPED = True
619
-
620
-
621
- def _unwrap_model_call_async() -> None:
622
- """Returns None. Restores the original `Model.call_async` implementation."""
623
- global _MODEL_CALL_ASYNC_WRAPPED
624
- if not _MODEL_CALL_ASYNC_WRAPPED:
625
- return
626
- Model.call_async = _ORIGINAL_MODEL_CALL_ASYNC
627
- _MODEL_CALL_ASYNC_WRAPPED = False
628
-
629
-
630
- @overload
631
- def _instrumented_model_context_call(
632
- self: Model,
633
- *,
634
- ctx: Context[DepsT],
635
- messages: Sequence[Message],
636
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
637
- format: None = None,
638
- ) -> ContextResponse[DepsT, None]: ...
639
-
640
-
641
- @overload
642
- def _instrumented_model_context_call(
643
- self: Model,
644
- *,
645
- ctx: Context[DepsT],
646
- messages: Sequence[Message],
647
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
648
- format: type[FormattableT] | Format[FormattableT],
649
- ) -> ContextResponse[DepsT, FormattableT]: ...
650
-
651
-
652
- @overload
653
- def _instrumented_model_context_call(
654
- self: Model,
655
- *,
656
- ctx: Context[DepsT],
657
- messages: Sequence[Message],
658
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
659
- format: type[FormattableT] | Format[FormattableT] | None = None,
660
- ) -> ContextResponse[DepsT, None] | ContextResponse[DepsT, FormattableT]: ...
661
-
662
-
663
- @wraps(_ORIGINAL_MODEL_CONTEXT_CALL)
664
- def _instrumented_model_context_call(
665
- self: Model,
666
- *,
667
- ctx: Context[DepsT],
668
- messages: Sequence[Message],
669
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
670
- format: FormatParam = None,
671
- ) -> ContextResponse[DepsT, None] | ContextResponse[DepsT, FormattableT]:
672
- """Returns a GenAI-instrumented result of `Model.context_call`."""
673
- with _start_model_span(
674
- self,
675
- messages=messages,
676
- tools=tools,
677
- format=format,
678
- activate=True,
679
- ) as span_ctx:
680
- response = _ORIGINAL_MODEL_CONTEXT_CALL(
681
- self,
682
- ctx=ctx,
683
- messages=messages,
684
- tools=tools,
685
- format=format,
686
- )
687
- if span_ctx.span is not None:
688
- _attach_response(
689
- span_ctx.span,
690
- response,
691
- request_messages=messages,
692
- )
693
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
694
- return response
695
-
696
-
697
- def _wrap_model_context_call() -> None:
698
- """Returns None. Replaces `Model.context_call` with the instrumented wrapper."""
699
- global _MODEL_CONTEXT_CALL_WRAPPED
700
- if _MODEL_CONTEXT_CALL_WRAPPED:
701
- return
702
- Model.context_call = _instrumented_model_context_call
703
- _MODEL_CONTEXT_CALL_WRAPPED = True
704
-
705
-
706
- def _unwrap_model_context_call() -> None:
707
- """Returns None. Restores the original `Model.context_call` implementation."""
708
- global _MODEL_CONTEXT_CALL_WRAPPED
709
- if not _MODEL_CONTEXT_CALL_WRAPPED:
710
- return
711
- Model.context_call = _ORIGINAL_MODEL_CONTEXT_CALL
712
- _MODEL_CONTEXT_CALL_WRAPPED = False
713
-
714
-
715
- @overload
716
- async def _instrumented_model_context_call_async(
717
- self: Model,
718
- *,
719
- ctx: Context[DepsT],
720
- messages: Sequence[Message],
721
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
722
- | AsyncContextToolkit[DepsT]
723
- | None = None,
724
- format: None = None,
725
- ) -> AsyncContextResponse[DepsT, None]: ...
726
-
727
-
728
- @overload
729
- async def _instrumented_model_context_call_async(
730
- self: Model,
731
- *,
732
- ctx: Context[DepsT],
733
- messages: Sequence[Message],
734
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
735
- | AsyncContextToolkit[DepsT]
736
- | None = None,
737
- format: type[FormattableT] | Format[FormattableT],
738
- ) -> AsyncContextResponse[DepsT, FormattableT]: ...
739
-
740
-
741
- @overload
742
- async def _instrumented_model_context_call_async(
743
- self: Model,
744
- *,
745
- ctx: Context[DepsT],
746
- messages: Sequence[Message],
747
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
748
- | AsyncContextToolkit[DepsT]
749
- | None = None,
750
- format: type[FormattableT] | Format[FormattableT] | None = None,
751
- ) -> AsyncContextResponse[DepsT, None] | AsyncContextResponse[DepsT, FormattableT]: ...
752
-
753
-
754
- @wraps(_ORIGINAL_MODEL_CONTEXT_CALL_ASYNC)
755
- async def _instrumented_model_context_call_async(
756
- self: Model,
757
- *,
758
- ctx: Context[DepsT],
759
- messages: Sequence[Message],
760
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
761
- | AsyncContextToolkit[DepsT]
762
- | None = None,
763
- format: FormatParam = None,
764
- ) -> AsyncContextResponse[DepsT, None] | AsyncContextResponse[DepsT, FormattableT]:
765
- """Returns a GenAI-instrumented result of `Model.context_call_async`."""
766
- with _start_model_span(
767
- self,
768
- messages=messages,
769
- tools=tools,
770
- format=format,
771
- activate=True,
772
- ) as span_ctx:
773
- response = await _ORIGINAL_MODEL_CONTEXT_CALL_ASYNC(
774
- self,
775
- ctx=ctx,
776
- messages=messages,
777
- tools=tools,
778
- format=format,
779
- )
780
- if span_ctx.span is not None:
781
- _attach_response(
782
- span_ctx.span,
783
- response,
784
- request_messages=messages,
785
- )
786
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
787
- return response
788
-
789
-
790
- def _wrap_model_context_call_async() -> None:
791
- """Returns None. Replaces `Model.context_call_async` with the instrumented wrapper."""
792
- global _MODEL_CONTEXT_CALL_ASYNC_WRAPPED
793
- if _MODEL_CONTEXT_CALL_ASYNC_WRAPPED:
794
- return
795
- Model.context_call_async = _instrumented_model_context_call_async
796
- _MODEL_CONTEXT_CALL_ASYNC_WRAPPED = True
797
-
798
-
799
- def _unwrap_model_context_call_async() -> None:
800
- """Returns None. Restores the original `Model.context_call_async` implementation."""
801
- global _MODEL_CONTEXT_CALL_ASYNC_WRAPPED
802
- if not _MODEL_CONTEXT_CALL_ASYNC_WRAPPED:
803
- return
804
- Model.context_call_async = _ORIGINAL_MODEL_CONTEXT_CALL_ASYNC
805
- _MODEL_CONTEXT_CALL_ASYNC_WRAPPED = False
806
-
807
-
808
- @overload
809
- def _instrumented_model_stream(
810
- self: Model,
811
- *,
812
- messages: Sequence[Message],
813
- tools: Sequence[Tool] | Toolkit | None = None,
814
- format: None = None,
815
- ) -> StreamResponse: ...
816
-
817
-
818
- @overload
819
- def _instrumented_model_stream(
820
- self: Model,
821
- *,
822
- messages: Sequence[Message],
823
- tools: Sequence[Tool] | Toolkit | None = None,
824
- format: type[FormattableT] | Format[FormattableT],
825
- ) -> StreamResponse[FormattableT]: ...
826
-
827
-
828
- @overload
829
- def _instrumented_model_stream(
830
- self: Model,
831
- *,
832
- messages: Sequence[Message],
833
- tools: Sequence[Tool] | Toolkit | None = None,
834
- format: type[FormattableT] | Format[FormattableT] | None = None,
835
- ) -> StreamResponse | StreamResponse[FormattableT]: ...
836
-
837
-
838
- @wraps(_ORIGINAL_MODEL_STREAM)
839
- def _instrumented_model_stream(
840
- self: Model,
841
- *,
842
- messages: Sequence[Message],
843
- tools: Sequence[Tool] | Toolkit | None = None,
844
- format: FormatParam = None,
845
- ) -> StreamResponse | StreamResponse[FormattableT]:
846
- """Returns a GenAI-instrumented result of `Model.stream`."""
847
- span_cm = _start_model_span(
848
- self,
849
- messages=messages,
850
- tools=tools,
851
- format=format,
852
- activate=False,
853
- )
854
- span_ctx = span_cm.__enter__()
855
- if span_ctx.span is None:
856
- response = _ORIGINAL_MODEL_STREAM(
857
- self,
858
- messages=messages,
859
- tools=tools,
860
- format=format,
861
- )
862
- span_cm.__exit__(None, None, None)
863
- return response
864
-
865
- try:
866
- with otel_trace.use_span(span_ctx.span, end_on_exit=False):
867
- response = _ORIGINAL_MODEL_STREAM(
868
- self,
869
- messages=messages,
870
- tools=tools,
871
- format=format,
872
- )
873
- except Exception as exc:
874
- span_cm.__exit__(type(exc), exc, exc.__traceback__)
875
- raise
876
-
877
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
878
-
879
- try:
880
- _attach_stream_span_handlers(
881
- response=response,
882
- span_cm=span_cm,
883
- span=span_ctx.span,
884
- request_messages=messages,
885
- )
886
- except Exception as exc: # pragma: no cover
887
- span_cm.__exit__(type(exc), exc, exc.__traceback__)
888
- raise
889
-
890
- return response
891
-
892
-
893
- def _attach_stream_span_handlers(
894
- *,
895
- response: ContextStreamResponse[DepsT, FormattableT | None]
896
- | StreamResponse[FormattableT | None],
897
- span_cm: AbstractContextManager[SpanContext],
898
- span: Span,
899
- request_messages: Sequence[Message],
900
- ) -> None:
901
- """Returns None. Closes the span when streaming completes."""
902
- chunk_iterator: Iterator[StreamResponseChunk] = response._chunk_iterator
903
-
904
- response_ref = weakref.ref(response)
905
- closed = False
906
-
907
- def _close_span(
908
- exc_type: type[BaseException] | None,
909
- exc: BaseException | None,
910
- tb: TracebackType | None,
911
- ) -> None:
912
- nonlocal closed
913
- if closed:
914
- return
915
- closed = True
916
- response_obj = response_ref()
917
- if response_obj is not None:
918
- _attach_response(
919
- span,
920
- response_obj,
921
- request_messages=request_messages,
922
- )
923
- span_cm.__exit__(exc_type, exc, tb)
924
-
925
- def _wrapped_iterator() -> Iterator[StreamResponseChunk]:
926
- with otel_trace.use_span(span, end_on_exit=False):
927
- try:
928
- yield from chunk_iterator
929
- except Exception as exc: # noqa: BLE001
930
- _close_span(type(exc), exc, exc.__traceback__)
931
- raise
932
- else:
933
- _close_span(None, None, None)
934
- finally:
935
- _close_span(None, None, None)
936
-
937
- response._chunk_iterator = _wrapped_iterator()
938
-
939
-
940
- def _wrap_model_stream() -> None:
941
- """Returns None. Replaces `Model.stream` with the instrumented wrapper."""
942
- global _MODEL_STREAM_WRAPPED
943
- if _MODEL_STREAM_WRAPPED:
944
- return
945
- Model.stream = _instrumented_model_stream
946
- _MODEL_STREAM_WRAPPED = True
947
-
948
-
949
- def _unwrap_model_stream() -> None:
950
- """Returns None. Restores the original `Model.stream` implementation."""
951
- global _MODEL_STREAM_WRAPPED
952
- if not _MODEL_STREAM_WRAPPED:
953
- return
954
- Model.stream = _ORIGINAL_MODEL_STREAM
955
- _MODEL_STREAM_WRAPPED = False
956
-
957
-
958
- @overload
959
- def _instrumented_model_context_stream(
960
- self: Model,
961
- *,
962
- ctx: Context[DepsT],
963
- messages: Sequence[Message],
964
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
965
- format: None = None,
966
- ) -> ContextStreamResponse[DepsT, None]: ...
967
-
968
-
969
- @overload
970
- def _instrumented_model_context_stream(
971
- self: Model,
972
- *,
973
- ctx: Context[DepsT],
974
- messages: Sequence[Message],
975
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
976
- format: type[FormattableT] | Format[FormattableT],
977
- ) -> ContextStreamResponse[DepsT, FormattableT]: ...
978
-
979
-
980
- @overload
981
- def _instrumented_model_context_stream(
982
- self: Model,
983
- *,
984
- ctx: Context[DepsT],
985
- messages: Sequence[Message],
986
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
987
- format: type[FormattableT] | Format[FormattableT] | None = None,
988
- ) -> (
989
- ContextStreamResponse[DepsT, None] | ContextStreamResponse[DepsT, FormattableT]
990
- ): ...
991
-
992
-
993
- @wraps(_ORIGINAL_MODEL_CONTEXT_STREAM)
994
- def _instrumented_model_context_stream(
995
- self: Model,
996
- *,
997
- ctx: Context[DepsT],
998
- messages: Sequence[Message],
999
- tools: Sequence[Tool | ContextTool[DepsT]] | ContextToolkit[DepsT] | None = None,
1000
- format: FormatParam = None,
1001
- ) -> ContextStreamResponse[DepsT, None] | ContextStreamResponse[DepsT, FormattableT]:
1002
- """Returns a GenAI-instrumented result of `Model.context_stream`."""
1003
- span_cm = _start_model_span(
1004
- self,
1005
- messages=messages,
1006
- tools=tools,
1007
- format=format,
1008
- activate=False,
1009
- )
1010
- span_ctx = span_cm.__enter__()
1011
- if span_ctx.span is None:
1012
- response = _ORIGINAL_MODEL_CONTEXT_STREAM(
1013
- self,
1014
- ctx=ctx,
1015
- messages=messages,
1016
- tools=tools,
1017
- format=format,
1018
- )
1019
- span_cm.__exit__(None, None, None)
1020
- return response
1021
-
1022
- try:
1023
- with otel_trace.use_span(span_ctx.span, end_on_exit=False):
1024
- response = _ORIGINAL_MODEL_CONTEXT_STREAM(
1025
- self,
1026
- ctx=ctx,
1027
- messages=messages,
1028
- tools=tools,
1029
- format=format,
1030
- )
1031
- except Exception as exc:
1032
- span_cm.__exit__(type(exc), exc, exc.__traceback__)
1033
- raise
1034
-
1035
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
1036
-
1037
- try:
1038
- _attach_stream_span_handlers(
1039
- response=response,
1040
- span_cm=span_cm,
1041
- span=span_ctx.span,
1042
- request_messages=messages,
1043
- )
1044
- except Exception as exc: # pragma: no cover
1045
- span_cm.__exit__(type(exc), exc, exc.__traceback__)
1046
- raise
1047
-
1048
- return response
1049
-
1050
-
1051
- def _wrap_model_context_stream() -> None:
1052
- """Returns None. Replaces `Model.context_stream` with the instrumented wrapper."""
1053
- global _MODEL_CONTEXT_STREAM_WRAPPED
1054
- if _MODEL_CONTEXT_STREAM_WRAPPED:
1055
- return
1056
- Model.context_stream = _instrumented_model_context_stream
1057
- _MODEL_CONTEXT_STREAM_WRAPPED = True
1058
-
1059
-
1060
- def _unwrap_model_context_stream() -> None:
1061
- """Returns None. Restores the original `Model.context_stream` implementation."""
1062
- global _MODEL_CONTEXT_STREAM_WRAPPED
1063
- if not _MODEL_CONTEXT_STREAM_WRAPPED:
1064
- return
1065
- Model.context_stream = _ORIGINAL_MODEL_CONTEXT_STREAM
1066
- _MODEL_CONTEXT_STREAM_WRAPPED = False
1067
-
1068
-
1069
- def _attach_async_stream_span_handlers(
1070
- *,
1071
- response: AsyncContextStreamResponse[DepsT, FormattableT | None],
1072
- span_cm: AbstractContextManager[SpanContext],
1073
- span: Span,
1074
- request_messages: Sequence[Message],
1075
- ) -> None:
1076
- """Returns None. Closes the span when async streaming completes."""
1077
- chunk_iterator: AsyncIterator[StreamResponseChunk] = response._chunk_iterator
1078
-
1079
- response_ref = weakref.ref(response)
1080
- closed = False
1081
-
1082
- def _close_span(
1083
- exc_type: type[BaseException] | None,
1084
- exc: BaseException | None,
1085
- tb: TracebackType | None,
1086
- ) -> None:
1087
- nonlocal closed
1088
- if closed:
1089
- return
1090
- closed = True
1091
- response_obj = response_ref()
1092
- if response_obj is not None:
1093
- _attach_response(
1094
- span,
1095
- response_obj,
1096
- request_messages=request_messages,
1097
- )
1098
- span_cm.__exit__(exc_type, exc, tb)
1099
-
1100
- async def _wrapped_iterator() -> AsyncIterator[StreamResponseChunk]:
1101
- try:
1102
- async for chunk in chunk_iterator:
1103
- yield chunk
1104
- except Exception as exc: # noqa: BLE001
1105
- _close_span(type(exc), exc, exc.__traceback__)
1106
- raise
1107
- else:
1108
- _close_span(None, None, None)
1109
- finally:
1110
- _close_span(None, None, None)
1111
-
1112
- response._chunk_iterator = _wrapped_iterator()
1113
-
1114
-
1115
- @overload
1116
- async def _instrumented_model_context_stream_async(
1117
- self: Model,
1118
- *,
1119
- ctx: Context[DepsT],
1120
- messages: Sequence[Message],
1121
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
1122
- | AsyncContextToolkit[DepsT]
1123
- | None = None,
1124
- format: None = None,
1125
- ) -> AsyncContextStreamResponse[DepsT, None]: ...
1126
-
1127
-
1128
- @overload
1129
- async def _instrumented_model_context_stream_async(
1130
- self: Model,
1131
- *,
1132
- ctx: Context[DepsT],
1133
- messages: Sequence[Message],
1134
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
1135
- | AsyncContextToolkit[DepsT]
1136
- | None = None,
1137
- format: type[FormattableT] | Format[FormattableT],
1138
- ) -> AsyncContextStreamResponse[DepsT, FormattableT]: ...
1139
-
1140
-
1141
- @overload
1142
- async def _instrumented_model_context_stream_async(
1143
- self: Model,
1144
- *,
1145
- ctx: Context[DepsT],
1146
- messages: Sequence[Message],
1147
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
1148
- | AsyncContextToolkit[DepsT]
1149
- | None = None,
1150
- format: type[FormattableT] | Format[FormattableT] | None = None,
1151
- ) -> (
1152
- AsyncContextStreamResponse[DepsT, None]
1153
- | AsyncContextStreamResponse[DepsT, FormattableT]
1154
- ): ...
1155
-
1156
-
1157
- @wraps(_ORIGINAL_MODEL_CONTEXT_STREAM_ASYNC)
1158
- async def _instrumented_model_context_stream_async(
1159
- self: Model,
1160
- *,
1161
- ctx: Context[DepsT],
1162
- messages: Sequence[Message],
1163
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
1164
- | AsyncContextToolkit[DepsT]
1165
- | None = None,
1166
- format: FormatParam = None,
1167
- ) -> (
1168
- AsyncContextStreamResponse[DepsT, None]
1169
- | AsyncContextStreamResponse[DepsT, FormattableT]
1170
- ):
1171
- """Returns a GenAI-instrumented result of `Model.context_stream_async`."""
1172
- span_cm = _start_model_span(
1173
- self,
1174
- messages=messages,
1175
- tools=tools,
1176
- format=format,
1177
- activate=False,
1178
- )
1179
- span_ctx = span_cm.__enter__()
1180
- if span_ctx.span is None:
1181
- response = await _ORIGINAL_MODEL_CONTEXT_STREAM_ASYNC(
1182
- self,
1183
- ctx=ctx,
1184
- messages=messages,
1185
- tools=tools,
1186
- format=format,
1187
- )
1188
- span_cm.__exit__(None, None, None)
1189
- return response
1190
-
1191
- try:
1192
- with otel_trace.use_span(span_ctx.span, end_on_exit=False):
1193
- response = await _ORIGINAL_MODEL_CONTEXT_STREAM_ASYNC(
1194
- self,
1195
- ctx=ctx,
1196
- messages=messages,
1197
- tools=tools,
1198
- format=format,
1199
- )
1200
- except Exception as exc:
1201
- span_cm.__exit__(type(exc), exc, exc.__traceback__)
1202
- raise
1203
-
1204
- _record_dropped_params(span_ctx.span, span_ctx.dropped_params)
1205
-
1206
- try:
1207
- _attach_async_stream_span_handlers(
1208
- response=response,
1209
- span_cm=span_cm,
1210
- span=span_ctx.span,
1211
- request_messages=messages,
1212
- )
1213
- except Exception as exc: # pragma: no cover
1214
- span_cm.__exit__(type(exc), exc, exc.__traceback__)
1215
- raise
1216
-
1217
- return response
1218
-
1219
-
1220
- def _wrap_model_context_stream_async() -> None:
1221
- """Returns None. Replaces `Model.context_stream_async` with the instrumented wrapper."""
1222
- global _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED
1223
- if _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED:
1224
- return
1225
- Model.context_stream_async = _instrumented_model_context_stream_async
1226
- _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED = True
1227
-
1228
-
1229
- def _unwrap_model_context_stream_async() -> None:
1230
- """Returns None. Restores the original `Model.context_stream_async` implementation."""
1231
- global _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED
1232
- if not _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED:
1233
- return
1234
- Model.context_stream_async = _ORIGINAL_MODEL_CONTEXT_STREAM_ASYNC
1235
- _MODEL_CONTEXT_STREAM_ASYNC_WRAPPED = False
1236
66
 
1237
67
 
1238
68
  def instrument_llm() -> None:
@@ -1267,21 +97,65 @@ def instrument_llm() -> None:
1267
97
  "You must call `configure()` before calling `instrument_llm()`."
1268
98
  )
1269
99
 
1270
- _wrap_model_call()
1271
- _wrap_model_call_async()
1272
- _wrap_model_context_call()
1273
- _wrap_model_context_call_async()
1274
- _wrap_model_stream()
1275
- _wrap_model_context_stream()
1276
- _wrap_model_context_stream_async()
100
+ # Model call methods
101
+ wrap_model_call()
102
+ wrap_model_call_async()
103
+ wrap_model_context_call()
104
+ wrap_model_context_call_async()
105
+ wrap_model_stream()
106
+ wrap_model_stream_async()
107
+ wrap_model_context_stream()
108
+ wrap_model_context_stream_async()
109
+
110
+ # Model resume methods
111
+ wrap_model_resume()
112
+ wrap_model_resume_async()
113
+ wrap_model_context_resume()
114
+ wrap_model_context_resume_async()
115
+ wrap_model_resume_stream()
116
+ wrap_model_resume_stream_async()
117
+ wrap_model_context_resume_stream()
118
+ wrap_model_context_resume_stream_async()
119
+
120
+ # Response resume methods (Mirascope-specific spans)
121
+ wrap_response_resume()
122
+ wrap_async_response_resume()
123
+ wrap_context_response_resume()
124
+ wrap_async_context_response_resume()
125
+ wrap_stream_response_resume()
126
+ wrap_async_stream_response_resume()
127
+ wrap_context_stream_response_resume()
128
+ wrap_async_context_stream_response_resume()
1277
129
 
1278
130
 
1279
131
  def uninstrument_llm() -> None:
1280
132
  """Disable previously configured instrumentation."""
1281
- _unwrap_model_call()
1282
- _unwrap_model_call_async()
1283
- _unwrap_model_context_call()
1284
- _unwrap_model_context_call_async()
1285
- _unwrap_model_stream()
1286
- _unwrap_model_context_stream()
1287
- _unwrap_model_context_stream_async()
133
+ # Model call methods
134
+ unwrap_model_call()
135
+ unwrap_model_call_async()
136
+ unwrap_model_context_call()
137
+ unwrap_model_context_call_async()
138
+ unwrap_model_stream()
139
+ unwrap_model_stream_async()
140
+ unwrap_model_context_stream()
141
+ unwrap_model_context_stream_async()
142
+
143
+ # Model resume methods
144
+ unwrap_model_resume()
145
+ unwrap_model_resume_async()
146
+ unwrap_model_context_resume()
147
+ unwrap_model_context_resume_async()
148
+ unwrap_model_resume_stream()
149
+ unwrap_model_resume_stream_async()
150
+ unwrap_model_context_resume_stream()
151
+ unwrap_model_context_resume_stream_async()
152
+
153
+ # Response resume methods (Mirascope-specific spans)
154
+ unwrap_response_resume()
155
+ unwrap_async_response_resume()
156
+ unwrap_context_response_resume()
157
+ unwrap_async_context_response_resume()
158
+ unwrap_stream_response_resume()
159
+ unwrap_async_stream_response_resume()
160
+ unwrap_context_stream_response_resume()
161
+ unwrap_async_context_stream_response_resume()