mirascope 2.0.0a1__py3-none-any.whl → 2.0.0a3__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 (205) hide show
  1. mirascope/__init__.py +2 -2
  2. mirascope/api/__init__.py +6 -0
  3. mirascope/api/_generated/README.md +207 -0
  4. mirascope/api/_generated/__init__.py +85 -0
  5. mirascope/api/_generated/client.py +155 -0
  6. mirascope/api/_generated/core/__init__.py +52 -0
  7. mirascope/api/_generated/core/api_error.py +23 -0
  8. mirascope/api/_generated/core/client_wrapper.py +58 -0
  9. mirascope/api/_generated/core/datetime_utils.py +30 -0
  10. mirascope/api/_generated/core/file.py +70 -0
  11. mirascope/api/_generated/core/force_multipart.py +16 -0
  12. mirascope/api/_generated/core/http_client.py +619 -0
  13. mirascope/api/_generated/core/http_response.py +55 -0
  14. mirascope/api/_generated/core/jsonable_encoder.py +102 -0
  15. mirascope/api/_generated/core/pydantic_utilities.py +310 -0
  16. mirascope/api/_generated/core/query_encoder.py +60 -0
  17. mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
  18. mirascope/api/_generated/core/request_options.py +35 -0
  19. mirascope/api/_generated/core/serialization.py +282 -0
  20. mirascope/api/_generated/docs/__init__.py +4 -0
  21. mirascope/api/_generated/docs/client.py +95 -0
  22. mirascope/api/_generated/docs/raw_client.py +132 -0
  23. mirascope/api/_generated/environment.py +9 -0
  24. mirascope/api/_generated/errors/__init__.py +7 -0
  25. mirascope/api/_generated/errors/bad_request_error.py +15 -0
  26. mirascope/api/_generated/health/__init__.py +7 -0
  27. mirascope/api/_generated/health/client.py +96 -0
  28. mirascope/api/_generated/health/raw_client.py +129 -0
  29. mirascope/api/_generated/health/types/__init__.py +8 -0
  30. mirascope/api/_generated/health/types/health_check_response.py +24 -0
  31. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  32. mirascope/api/_generated/reference.md +167 -0
  33. mirascope/api/_generated/traces/__init__.py +55 -0
  34. mirascope/api/_generated/traces/client.py +162 -0
  35. mirascope/api/_generated/traces/raw_client.py +168 -0
  36. mirascope/api/_generated/traces/types/__init__.py +95 -0
  37. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
  38. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
  39. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
  40. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
  41. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
  42. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
  43. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
  44. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
  45. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
  46. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
  47. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
  48. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
  49. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
  50. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +24 -0
  51. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
  52. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
  53. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
  54. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
  55. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
  56. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +24 -0
  57. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
  58. mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
  59. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
  60. mirascope/api/_generated/types/__init__.py +21 -0
  61. mirascope/api/_generated/types/http_api_decode_error.py +31 -0
  62. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  63. mirascope/api/_generated/types/issue.py +44 -0
  64. mirascope/api/_generated/types/issue_tag.py +17 -0
  65. mirascope/api/_generated/types/property_key.py +7 -0
  66. mirascope/api/_generated/types/property_key_tag.py +29 -0
  67. mirascope/api/_generated/types/property_key_tag_tag.py +5 -0
  68. mirascope/api/client.py +255 -0
  69. mirascope/api/settings.py +81 -0
  70. mirascope/llm/__init__.py +41 -11
  71. mirascope/llm/calls/calls.py +81 -57
  72. mirascope/llm/calls/decorator.py +121 -115
  73. mirascope/llm/content/__init__.py +3 -2
  74. mirascope/llm/context/_utils.py +19 -6
  75. mirascope/llm/exceptions.py +30 -16
  76. mirascope/llm/formatting/_utils.py +9 -5
  77. mirascope/llm/formatting/format.py +2 -2
  78. mirascope/llm/formatting/from_call_args.py +2 -2
  79. mirascope/llm/messages/message.py +13 -5
  80. mirascope/llm/models/__init__.py +2 -2
  81. mirascope/llm/models/models.py +189 -81
  82. mirascope/llm/prompts/__init__.py +13 -12
  83. mirascope/llm/prompts/_utils.py +27 -24
  84. mirascope/llm/prompts/decorator.py +133 -204
  85. mirascope/llm/prompts/prompts.py +424 -0
  86. mirascope/llm/prompts/protocols.py +25 -59
  87. mirascope/llm/providers/__init__.py +38 -0
  88. mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
  89. mirascope/llm/providers/anthropic/__init__.py +24 -0
  90. mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +5 -4
  91. mirascope/llm/{clients → providers}/anthropic/_utils/encode.py +31 -10
  92. mirascope/llm/providers/anthropic/model_id.py +40 -0
  93. mirascope/llm/{clients/anthropic/clients.py → providers/anthropic/provider.py} +33 -418
  94. mirascope/llm/{clients → providers}/base/__init__.py +3 -3
  95. mirascope/llm/{clients → providers}/base/_utils.py +10 -7
  96. mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
  97. mirascope/llm/providers/google/__init__.py +21 -0
  98. mirascope/llm/{clients → providers}/google/_utils/decode.py +6 -4
  99. mirascope/llm/{clients → providers}/google/_utils/encode.py +30 -24
  100. mirascope/llm/providers/google/model_id.py +28 -0
  101. mirascope/llm/providers/google/provider.py +438 -0
  102. mirascope/llm/providers/load_provider.py +48 -0
  103. mirascope/llm/providers/mlx/__init__.py +24 -0
  104. mirascope/llm/providers/mlx/_utils.py +107 -0
  105. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  106. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  107. mirascope/llm/providers/mlx/encoding/transformers.py +131 -0
  108. mirascope/llm/providers/mlx/mlx.py +237 -0
  109. mirascope/llm/providers/mlx/model_id.py +17 -0
  110. mirascope/llm/providers/mlx/provider.py +411 -0
  111. mirascope/llm/providers/model_id.py +16 -0
  112. mirascope/llm/providers/openai/__init__.py +6 -0
  113. mirascope/llm/providers/openai/completions/__init__.py +20 -0
  114. mirascope/llm/{clients/openai/responses → providers/openai/completions}/_utils/__init__.py +2 -0
  115. mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +5 -3
  116. mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +33 -23
  117. mirascope/llm/providers/openai/completions/provider.py +456 -0
  118. mirascope/llm/providers/openai/model_id.py +31 -0
  119. mirascope/llm/providers/openai/model_info.py +246 -0
  120. mirascope/llm/providers/openai/provider.py +386 -0
  121. mirascope/llm/providers/openai/responses/__init__.py +21 -0
  122. mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +5 -3
  123. mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +28 -17
  124. mirascope/llm/providers/openai/responses/provider.py +470 -0
  125. mirascope/llm/{clients → providers}/openai/shared/_utils.py +7 -3
  126. mirascope/llm/providers/provider_id.py +13 -0
  127. mirascope/llm/providers/provider_registry.py +167 -0
  128. mirascope/llm/responses/base_response.py +10 -5
  129. mirascope/llm/responses/base_stream_response.py +10 -5
  130. mirascope/llm/responses/response.py +24 -13
  131. mirascope/llm/responses/root_response.py +7 -12
  132. mirascope/llm/responses/stream_response.py +35 -23
  133. mirascope/llm/tools/__init__.py +9 -2
  134. mirascope/llm/tools/_utils.py +12 -3
  135. mirascope/llm/tools/decorator.py +10 -10
  136. mirascope/llm/tools/protocols.py +4 -4
  137. mirascope/llm/tools/tool_schema.py +44 -9
  138. mirascope/llm/tools/tools.py +12 -11
  139. mirascope/ops/__init__.py +156 -0
  140. mirascope/ops/_internal/__init__.py +5 -0
  141. mirascope/ops/_internal/closure.py +1118 -0
  142. mirascope/ops/_internal/configuration.py +126 -0
  143. mirascope/ops/_internal/context.py +76 -0
  144. mirascope/ops/_internal/exporters/__init__.py +26 -0
  145. mirascope/ops/_internal/exporters/exporters.py +342 -0
  146. mirascope/ops/_internal/exporters/processors.py +104 -0
  147. mirascope/ops/_internal/exporters/types.py +165 -0
  148. mirascope/ops/_internal/exporters/utils.py +29 -0
  149. mirascope/ops/_internal/instrumentation/__init__.py +8 -0
  150. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  151. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  152. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  153. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  154. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  155. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  156. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  157. mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
  158. mirascope/ops/_internal/propagation.py +198 -0
  159. mirascope/ops/_internal/protocols.py +51 -0
  160. mirascope/ops/_internal/session.py +139 -0
  161. mirascope/ops/_internal/spans.py +232 -0
  162. mirascope/ops/_internal/traced_calls.py +371 -0
  163. mirascope/ops/_internal/traced_functions.py +394 -0
  164. mirascope/ops/_internal/tracing.py +276 -0
  165. mirascope/ops/_internal/types.py +13 -0
  166. mirascope/ops/_internal/utils.py +75 -0
  167. mirascope/ops/_internal/versioned_calls.py +512 -0
  168. mirascope/ops/_internal/versioned_functions.py +346 -0
  169. mirascope/ops/_internal/versioning.py +303 -0
  170. mirascope/ops/exceptions.py +21 -0
  171. {mirascope-2.0.0a1.dist-info → mirascope-2.0.0a3.dist-info}/METADATA +77 -1
  172. mirascope-2.0.0a3.dist-info/RECORD +206 -0
  173. {mirascope-2.0.0a1.dist-info → mirascope-2.0.0a3.dist-info}/WHEEL +1 -1
  174. mirascope/graphs/__init__.py +0 -22
  175. mirascope/graphs/finite_state_machine.py +0 -625
  176. mirascope/llm/agents/__init__.py +0 -15
  177. mirascope/llm/agents/agent.py +0 -97
  178. mirascope/llm/agents/agent_template.py +0 -45
  179. mirascope/llm/agents/decorator.py +0 -176
  180. mirascope/llm/calls/base_call.py +0 -33
  181. mirascope/llm/clients/__init__.py +0 -34
  182. mirascope/llm/clients/anthropic/__init__.py +0 -25
  183. mirascope/llm/clients/anthropic/model_ids.py +0 -8
  184. mirascope/llm/clients/google/__init__.py +0 -20
  185. mirascope/llm/clients/google/clients.py +0 -853
  186. mirascope/llm/clients/google/model_ids.py +0 -15
  187. mirascope/llm/clients/openai/__init__.py +0 -25
  188. mirascope/llm/clients/openai/completions/__init__.py +0 -28
  189. mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
  190. mirascope/llm/clients/openai/completions/clients.py +0 -833
  191. mirascope/llm/clients/openai/completions/model_ids.py +0 -8
  192. mirascope/llm/clients/openai/responses/__init__.py +0 -26
  193. mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
  194. mirascope/llm/clients/openai/responses/clients.py +0 -832
  195. mirascope/llm/clients/openai/responses/model_ids.py +0 -8
  196. mirascope/llm/clients/providers.py +0 -175
  197. mirascope-2.0.0a1.dist-info/RECORD +0 -102
  198. /mirascope/llm/{clients → providers}/anthropic/_utils/__init__.py +0 -0
  199. /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
  200. /mirascope/llm/{clients → providers}/base/params.py +0 -0
  201. /mirascope/llm/{clients → providers}/google/_utils/__init__.py +0 -0
  202. /mirascope/llm/{clients → providers}/google/message.py +0 -0
  203. /mirascope/llm/{clients/openai/completions → providers/openai/responses}/_utils/__init__.py +0 -0
  204. /mirascope/llm/{clients → providers}/openai/shared/__init__.py +0 -0
  205. {mirascope-2.0.0a1.dist-info → mirascope-2.0.0a3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,165 @@
1
+ """Type definitions for the two-phase export system.
2
+
3
+ This module defines the event types and data structures used for
4
+ immediate start event transmission and batched end event export.
5
+ """
6
+
7
+ from enum import Enum
8
+ from typing import Literal, TypedDict
9
+
10
+ from opentelemetry.util.types import AttributeValue
11
+
12
+ # TODO: unify/DRY types in the _internal package
13
+
14
+ SpanKind = Literal["CLIENT", "SERVER", "PRODUCER", "CONSUMER", "INTERNAL"]
15
+ StatusCode = Literal["UNSET", "OK", "ERROR"]
16
+
17
+
18
+ class SpanEventType(str, Enum):
19
+ """Event types for span lifecycle tracking."""
20
+
21
+ SPAN_STARTED = "span_started"
22
+ SPAN_UPDATED = "span_updated"
23
+ SPAN_COMPLETED = "span_completed"
24
+
25
+
26
+ class SpanEvent(TypedDict):
27
+ """Individual event within a span."""
28
+
29
+ name: str
30
+ """The name of the event."""
31
+
32
+ timestamp: int
33
+ """Nanoseconds since epoch (OTel standard)."""
34
+
35
+ attributes: dict[str, AttributeValue]
36
+ """Event-specific attributes."""
37
+
38
+
39
+ class Status(TypedDict):
40
+ """Status representation for serialization."""
41
+
42
+ code: StatusCode
43
+ """The status code: UNSET, OK, or ERROR."""
44
+
45
+ description: str | None
46
+ """Optional human-readable description of the status."""
47
+
48
+
49
+ class SpanContextDict(TypedDict):
50
+ """Span context for links and parent references."""
51
+
52
+ trace_id: str
53
+ """The trace ID as a 32-character hex string."""
54
+
55
+ span_id: str
56
+ """The span ID as a 16-character hex string."""
57
+
58
+ trace_flags: int
59
+ """Trace flags (e.g., for sampling decisions)."""
60
+
61
+ trace_state: str | None
62
+ """Optional vendor-specific trace state."""
63
+
64
+
65
+ class Link(TypedDict):
66
+ """Link representation for span relationships."""
67
+
68
+ context: SpanContextDict
69
+ """The linked span's context."""
70
+
71
+ attributes: dict[str, AttributeValue]
72
+ """Attributes describing the link."""
73
+
74
+
75
+ class SpanStartEvent(TypedDict):
76
+ """Minimal span data for immediate transmission.
77
+
78
+ This event is sent immediately when a span starts to provide
79
+ real-time visibility into long-running operations.
80
+ """
81
+
82
+ trace_id: str
83
+ """The trace ID as a 32-character hex string."""
84
+
85
+ span_id: str
86
+ """The span ID as a 16-character hex string."""
87
+
88
+ parent_span_id: str | None
89
+ """The parent span ID if this is a child span."""
90
+
91
+ name: str
92
+ """The name of the span."""
93
+
94
+ start_time: int
95
+ """Nanoseconds since epoch."""
96
+
97
+ kind: SpanKind
98
+ """The span kind: CLIENT, SERVER, PRODUCER, CONSUMER, or INTERNAL."""
99
+
100
+ attributes: dict[str, AttributeValue]
101
+ """Minimal required attributes only."""
102
+
103
+
104
+ class SpanUpdateEvent(TypedDict, total=False):
105
+ """Incremental updates to span data.
106
+
107
+ These events are batched and sent periodically to update
108
+ span attributes or add events without waiting for completion.
109
+ """
110
+
111
+ trace_id: str
112
+ """The trace ID as a 32-character hex string."""
113
+
114
+ span_id: str
115
+ """The span ID as a 16-character hex string."""
116
+
117
+ timestamp: int
118
+ """Nanoseconds since epoch."""
119
+
120
+ attributes: dict[str, AttributeValue]
121
+ """Additional or updated attributes."""
122
+
123
+ events: list[SpanEvent]
124
+ """New events to add to the span."""
125
+
126
+
127
+ class SpanCompleteEvent(TypedDict):
128
+ """Complete span data for batch export.
129
+
130
+ This event contains all span data and is sent when the span
131
+ completes, typically in batches for efficiency.
132
+ """
133
+
134
+ trace_id: str
135
+ """The trace ID as a 32-character hex string."""
136
+
137
+ span_id: str
138
+ """The span ID as a 16-character hex string."""
139
+
140
+ parent_span_id: str | None
141
+ """The parent span ID if this is a child span."""
142
+
143
+ name: str
144
+ """The name of the span."""
145
+
146
+ kind: SpanKind
147
+ """The span kind: CLIENT, SERVER, PRODUCER, CONSUMER, or INTERNAL."""
148
+
149
+ start_time: int
150
+ """Nanoseconds since epoch."""
151
+
152
+ end_time: int
153
+ """Nanoseconds since epoch."""
154
+
155
+ status: Status
156
+ """The final status of the span."""
157
+
158
+ attributes: dict[str, AttributeValue]
159
+ """All span attributes."""
160
+
161
+ events: list[SpanEvent]
162
+ """All events that occurred during the span."""
163
+
164
+ links: list[Link]
165
+ """Links to other spans."""
@@ -0,0 +1,29 @@
1
+ """Utility functions for OpenTelemetry exporters.
2
+
3
+ This module provides helper functions for formatting and converting
4
+ OpenTelemetry data types for export.
5
+ """
6
+
7
+
8
+ def format_trace_id(trace_id: int) -> str:
9
+ """Format a trace ID as a 32-character hex string.
10
+
11
+ Args:
12
+ trace_id: The trace ID as an integer.
13
+
14
+ Returns:
15
+ 32-character hexadecimal string representation of the trace ID.
16
+ """
17
+ return format(trace_id, "032x")
18
+
19
+
20
+ def format_span_id(span_id: int) -> str:
21
+ """Format a span ID as a 16-character hex string.
22
+
23
+ Args:
24
+ span_id: The span ID as an integer.
25
+
26
+ Returns:
27
+ 16-character hexadecimal string representation of the span ID.
28
+ """
29
+ return format(span_id, "016x")
@@ -0,0 +1,8 @@
1
+ """Instrumentation modules for various frameworks and libraries."""
2
+
3
+ from .llm import instrument_llm, uninstrument_llm
4
+
5
+ __all__ = [
6
+ "instrument_llm",
7
+ "uninstrument_llm",
8
+ ]
@@ -0,0 +1,8 @@
1
+ """Instrumentation llm modules for various frameworks and libraries."""
2
+
3
+ from .llm import instrument_llm, uninstrument_llm
4
+
5
+ __all__ = [
6
+ "instrument_llm",
7
+ "uninstrument_llm",
8
+ ]
@@ -0,0 +1,238 @@
1
+ """Utilities for encoding Mirascope messages into GenAI semconv payloads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Sequence
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING
9
+
10
+ from . import gen_ai_types
11
+
12
+ if TYPE_CHECKING:
13
+ from typing import TypeAlias
14
+
15
+ MessagePart: TypeAlias = (
16
+ gen_ai_types.TextPart
17
+ | gen_ai_types.ToolCallRequestPart
18
+ | gen_ai_types.ToolCallResponsePart
19
+ | gen_ai_types.BlobPart
20
+ | gen_ai_types.FilePart
21
+ | gen_ai_types.UriPart
22
+ | gen_ai_types.ReasoningPart
23
+ | gen_ai_types.GenericPart
24
+ )
25
+
26
+
27
+ from .....llm.content import (
28
+ Audio,
29
+ Base64ImageSource,
30
+ Document,
31
+ Image,
32
+ Text,
33
+ Thought,
34
+ ToolCall,
35
+ ToolOutput,
36
+ )
37
+ from .....llm.formatting import FormattableT
38
+ from .....llm.messages import AssistantMessage, Message, SystemMessage
39
+ from .....llm.responses.finish_reason import FinishReason
40
+ from .....llm.responses.root_response import RootResponse
41
+ from .....llm.tools import ToolkitT
42
+ from .....llm.types import Jsonable
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class OTelMessageSnapshot:
47
+ """Structured view of system, input, and output messages."""
48
+
49
+ system_instructions: gen_ai_types.SystemInstructions
50
+ """Instructions to be executed prior to the conversation."""
51
+
52
+ inputs: gen_ai_types.InputMessages
53
+ """Messages to be sent to the model."""
54
+
55
+ outputs: gen_ai_types.OutputMessages
56
+ """Messages received from the model."""
57
+
58
+
59
+ def _serialize_message_parts(
60
+ parts: Sequence[
61
+ Text | ToolCall | ToolOutput[Jsonable] | Thought | Image | Audio | Document
62
+ ],
63
+ ) -> list[MessagePart]:
64
+ """Serialize message content parts into GenAI-compliant dictionaries.
65
+
66
+ Handles Text, ToolCall, ToolOutput, Thought, Image, Audio, Document,
67
+ and str types.
68
+ """
69
+ serialized: list[MessagePart] = []
70
+ for part in parts:
71
+ match part:
72
+ case Text():
73
+ text_part: gen_ai_types.TextPart = {
74
+ "type": "text",
75
+ "content": part.text,
76
+ }
77
+ serialized.append(text_part)
78
+ case ToolCall():
79
+ try:
80
+ arguments = json.loads(part.args)
81
+ except json.JSONDecodeError: # pragma: no cover
82
+ arguments = ""
83
+ tool_call_part: gen_ai_types.ToolCallRequestPart = {
84
+ "type": "tool_call",
85
+ "id": part.id,
86
+ "name": part.name,
87
+ "arguments": arguments,
88
+ }
89
+ serialized.append(tool_call_part)
90
+ case ToolOutput():
91
+ tool_output_part: gen_ai_types.ToolCallResponsePart = {
92
+ "type": "tool_call_response",
93
+ "id": part.id,
94
+ "response": part.value,
95
+ }
96
+ serialized.append(tool_output_part)
97
+ case Thought():
98
+ serialized.append(
99
+ gen_ai_types.ReasoningPart(type="reasoning", content=part.thought)
100
+ )
101
+ case Image():
102
+ if isinstance(part.source, Base64ImageSource):
103
+ image = gen_ai_types.BlobPart(
104
+ type="blob",
105
+ modality="image",
106
+ mime_type=part.source.mime_type,
107
+ content=part.source.data,
108
+ )
109
+ else:
110
+ image = gen_ai_types.UriPart(
111
+ type="uri", modality="image", uri=part.source.url
112
+ )
113
+ serialized.append(image)
114
+ case Audio():
115
+ serialized.append(
116
+ gen_ai_types.BlobPart(
117
+ type="blob",
118
+ modality="audio",
119
+ mime_type=part.source.mime_type,
120
+ content=part.source.data,
121
+ )
122
+ )
123
+ case Document(): # pragma: no cover
124
+ raise NotImplementedError(
125
+ "Document serialization is not yet supported by any provider. "
126
+ "This will be implemented when provider support is added."
127
+ )
128
+ return serialized
129
+
130
+
131
+ def _serialize_message(message: Message) -> gen_ai_types.ChatMessage:
132
+ """Serialize a Mirascope message into a GenAI ChatMessage."""
133
+ content_parts = _serialize_message_parts(
134
+ message.content if not isinstance(message, SystemMessage) else [message.content]
135
+ )
136
+ serialized: gen_ai_types.ChatMessage = {
137
+ "role": message.role,
138
+ "parts": content_parts,
139
+ }
140
+ name = getattr(message, "name", None)
141
+ if name:
142
+ serialized["name"] = name
143
+ return serialized
144
+
145
+
146
+ def map_finish_reason(
147
+ reason: FinishReason | None,
148
+ ) -> gen_ai_types.FinishReason:
149
+ """Map a finish reason to a GenAI finish reason."""
150
+ if reason is None:
151
+ return "stop"
152
+ elif reason == FinishReason.MAX_TOKENS:
153
+ return "length"
154
+ elif reason == FinishReason.REFUSAL: # pragma: no cover
155
+ return "content_filter"
156
+ raise ValueError(f"Unknown finish reason: {reason}") # pragma: no cover
157
+
158
+
159
+ def _serialize_output_message(
160
+ message: AssistantMessage, finish_reason: FinishReason | None
161
+ ) -> gen_ai_types.OutputMessage:
162
+ """Serialize an assistant message into a GenAI OutputMessage with finish_reason."""
163
+ chat_message = _serialize_message(message)
164
+ output_message: gen_ai_types.OutputMessage = {
165
+ "role": chat_message["role"],
166
+ "parts": chat_message["parts"],
167
+ "finish_reason": map_finish_reason(finish_reason),
168
+ }
169
+ if "name" in chat_message:
170
+ output_message["name"] = chat_message["name"]
171
+ return output_message
172
+
173
+
174
+ def split_request_messages(
175
+ messages: Sequence[Message],
176
+ ) -> tuple[gen_ai_types.SystemInstructions, gen_ai_types.InputMessages]:
177
+ """Return serialized system instructions and non-system input messages.
178
+
179
+ System messages are flattened into a list of parts (not wrapped in message objects),
180
+ while other messages are serialized as complete ChatMessage objects.
181
+ """
182
+ system_instructions: gen_ai_types.SystemInstructions = []
183
+ inputs: gen_ai_types.InputMessages = []
184
+ for message in messages:
185
+ if message.role == "system":
186
+ # System messages contribute only their parts (flattened)
187
+ parts = _serialize_message_parts(
188
+ [message.content]
189
+ if isinstance(message, SystemMessage)
190
+ else message.content
191
+ )
192
+ system_instructions.extend(parts)
193
+ else:
194
+ # Non-system messages are serialized as full ChatMessage objects
195
+ serialized = _serialize_message(message)
196
+ inputs.append(serialized)
197
+ return system_instructions, inputs
198
+
199
+
200
+ def snapshot_from_response_messages(
201
+ *,
202
+ request_messages: Sequence[Message],
203
+ assistant_message: AssistantMessage,
204
+ finish_reason: FinishReason | None,
205
+ ) -> OTelMessageSnapshot:
206
+ """Build a snapshot using the request/response boundary for a call."""
207
+
208
+ system_instructions, inputs = split_request_messages(request_messages)
209
+ outputs = [_serialize_output_message(assistant_message, finish_reason)]
210
+ return OTelMessageSnapshot(
211
+ system_instructions=system_instructions,
212
+ inputs=inputs,
213
+ outputs=outputs,
214
+ )
215
+
216
+
217
+ def snapshot_from_root_response(
218
+ response: RootResponse[ToolkitT, FormattableT | None],
219
+ *,
220
+ request_messages: Sequence[Message] | None = None,
221
+ ) -> OTelMessageSnapshot:
222
+ """Build a snapshot directly from a `RootResponse`.
223
+
224
+ Args:
225
+ response: The response that includes the entire conversation history.
226
+ request_messages: Optional explicit request message sequence. Defaults to all
227
+ but the final assistant message inside `response.messages`.
228
+ """
229
+
230
+ assistant_message = response.messages[-1]
231
+ if not isinstance(assistant_message, AssistantMessage): # pragma: no cover
232
+ raise TypeError("Final response message must be an AssistantMessage")
233
+
234
+ return snapshot_from_response_messages(
235
+ request_messages=request_messages or response.messages[:-1],
236
+ assistant_message=assistant_message,
237
+ finish_reason=response.finish_reason,
238
+ )
@@ -0,0 +1,38 @@
1
+ """OpenTelemetry Gen AI Semantic Conventions types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .gen_ai_input_messages import ChatMessage, InputMessages
6
+ from .gen_ai_output_messages import FinishReason, OutputMessage, OutputMessages
7
+ from .gen_ai_system_instructions import SystemInstructions
8
+ from .shared import (
9
+ BlobPart,
10
+ FilePart,
11
+ GenericPart,
12
+ Modality,
13
+ ReasoningPart,
14
+ Role,
15
+ TextPart,
16
+ ToolCallRequestPart,
17
+ ToolCallResponsePart,
18
+ UriPart,
19
+ )
20
+
21
+ __all__ = [
22
+ "BlobPart",
23
+ "ChatMessage",
24
+ "FilePart",
25
+ "FinishReason",
26
+ "GenericPart",
27
+ "InputMessages",
28
+ "Modality",
29
+ "OutputMessage",
30
+ "OutputMessages",
31
+ "ReasoningPart",
32
+ "Role",
33
+ "SystemInstructions",
34
+ "TextPart",
35
+ "ToolCallRequestPart",
36
+ "ToolCallResponsePart",
37
+ "UriPart",
38
+ ]
@@ -0,0 +1,31 @@
1
+ """OpenTelemetry Gen AI Semantic Conventions types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TypeAlias, TypedDict
6
+ from typing_extensions import NotRequired
7
+
8
+ from . import shared
9
+
10
+
11
+ class ChatMessage(TypedDict):
12
+ role: shared.Role | str
13
+ """Role of the entity that created the message."""
14
+
15
+ parts: list[
16
+ shared.TextPart
17
+ | shared.ToolCallRequestPart
18
+ | shared.ToolCallResponsePart
19
+ | shared.BlobPart
20
+ | shared.FilePart
21
+ | shared.UriPart
22
+ | shared.ReasoningPart
23
+ | shared.GenericPart
24
+ ]
25
+ """List of message parts that make up the message content."""
26
+
27
+ name: NotRequired[str | None]
28
+ """The name of the participant."""
29
+
30
+
31
+ InputMessages: TypeAlias = list[ChatMessage]
@@ -0,0 +1,38 @@
1
+ """OpenTelemetry Gen AI Semantic Conventions types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, TypeAlias, TypedDict
6
+ from typing_extensions import NotRequired
7
+
8
+ from . import shared
9
+
10
+ FinishReason: TypeAlias = Literal[
11
+ "stop", "length", "content_filter", "tool_call", "error"
12
+ ]
13
+
14
+
15
+ class OutputMessage(TypedDict):
16
+ role: shared.Role | str
17
+ """Role of the entity that created the message."""
18
+
19
+ parts: list[
20
+ shared.TextPart
21
+ | shared.ToolCallRequestPart
22
+ | shared.ToolCallResponsePart
23
+ | shared.BlobPart
24
+ | shared.FilePart
25
+ | shared.UriPart
26
+ | shared.ReasoningPart
27
+ | shared.GenericPart
28
+ ]
29
+ """List of message parts that make up the message content."""
30
+
31
+ name: NotRequired[str | None]
32
+ """The name of the participant."""
33
+
34
+ finish_reason: FinishReason | str
35
+ """Reason for finishing the generation."""
36
+
37
+
38
+ OutputMessages: TypeAlias = list[OutputMessage]
@@ -0,0 +1,18 @@
1
+ """OpenTelemetry Gen AI Semantic Conventions types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TypeAlias
6
+
7
+ from . import shared
8
+
9
+ SystemInstructions: TypeAlias = list[
10
+ shared.TextPart
11
+ | shared.ToolCallRequestPart
12
+ | shared.ToolCallResponsePart
13
+ | shared.BlobPart
14
+ | shared.FilePart
15
+ | shared.UriPart
16
+ | shared.ReasoningPart
17
+ | shared.GenericPart
18
+ ]
@@ -0,0 +1,100 @@
1
+ """OpenTelemetry Gen AI Semantic Conventions types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal, TypeAlias, TypedDict
6
+ from typing_extensions import NotRequired
7
+
8
+
9
+ class BlobPart(TypedDict):
10
+ type: Literal["blob"]
11
+ """The type of the content captured in this part."""
12
+
13
+ mime_type: NotRequired[str | None]
14
+ """The IANA MIME type of the attached data."""
15
+
16
+ modality: Modality | str
17
+ """The general modality of the data if it is known. Instrumentations SHOULD also set the mimeType field if the specific type is known."""
18
+
19
+ content: str
20
+ """Raw bytes of the attached data. This field SHOULD be encoded as a base64 string when transmitted as JSON."""
21
+
22
+
23
+ class FilePart(TypedDict):
24
+ type: Literal["file"]
25
+ """The type of the content captured in this part."""
26
+
27
+ mime_type: NotRequired[str | None]
28
+ """The IANA MIME type of the attached data."""
29
+
30
+ modality: Modality | str
31
+ """The general modality of the data if it is known. Instrumentations SHOULD also set the mimeType field if the specific type is known."""
32
+
33
+ file_id: str
34
+ """An identifier referencing a file that was pre-uploaded to the provider."""
35
+
36
+
37
+ class GenericPart(TypedDict):
38
+ type: str
39
+ """The type of the content captured in this part."""
40
+
41
+
42
+ Modality: TypeAlias = Literal["image", "video", "audio"]
43
+
44
+
45
+ class ReasoningPart(TypedDict):
46
+ type: Literal["reasoning"]
47
+ """The type of the content captured in this part."""
48
+
49
+ content: str
50
+ """Reasoning/thinking content received from the model."""
51
+
52
+
53
+ Role: TypeAlias = Literal["system", "user", "assistant", "tool"]
54
+
55
+
56
+ class TextPart(TypedDict):
57
+ type: Literal["text"]
58
+ """The type of the content captured in this part."""
59
+
60
+ content: str
61
+ """Text content sent to or received from the model."""
62
+
63
+
64
+ class ToolCallRequestPart(TypedDict):
65
+ type: Literal["tool_call"]
66
+ """The type of the content captured in this part."""
67
+
68
+ id: NotRequired[str | None]
69
+ """Unique identifier for the tool call."""
70
+
71
+ name: str
72
+ """Name of the tool."""
73
+
74
+ arguments: NotRequired[Any]
75
+ """Arguments for the tool call."""
76
+
77
+
78
+ class ToolCallResponsePart(TypedDict):
79
+ type: Literal["tool_call_response"]
80
+ """The type of the content captured in this part."""
81
+
82
+ id: NotRequired[str | None]
83
+ """Unique tool call identifier."""
84
+
85
+ response: Any
86
+ """Tool call response."""
87
+
88
+
89
+ class UriPart(TypedDict):
90
+ type: Literal["uri"]
91
+ """The type of the content captured in this part."""
92
+
93
+ mime_type: NotRequired[str | None]
94
+ """The IANA MIME type of the attached data."""
95
+
96
+ modality: Modality | str
97
+ """The general modality of the data if it is known. Instrumentations SHOULD also set the mimeType field if the specific type is known."""
98
+
99
+ uri: str
100
+ """A URI referencing attached data. It should not be a base64 data URL, which should use the `blob` part instead. The URI may use a scheme known to the provider api (e.g. `gs://bucket/object.png`), or be a publicly accessible location."""