arize-phoenix 5.5.2__py3-none-any.whl → 5.7.0__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.

Potentially problematic release.


This version of arize-phoenix might be problematic. Click here for more details.

Files changed (186) hide show
  1. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/METADATA +4 -7
  2. arize_phoenix-5.7.0.dist-info/RECORD +330 -0
  3. phoenix/config.py +50 -8
  4. phoenix/core/model.py +3 -3
  5. phoenix/core/model_schema.py +41 -50
  6. phoenix/core/model_schema_adapter.py +17 -16
  7. phoenix/datetime_utils.py +2 -2
  8. phoenix/db/bulk_inserter.py +10 -20
  9. phoenix/db/engines.py +2 -1
  10. phoenix/db/enums.py +2 -2
  11. phoenix/db/helpers.py +8 -7
  12. phoenix/db/insertion/dataset.py +9 -19
  13. phoenix/db/insertion/document_annotation.py +14 -13
  14. phoenix/db/insertion/helpers.py +6 -16
  15. phoenix/db/insertion/span_annotation.py +14 -13
  16. phoenix/db/insertion/trace_annotation.py +14 -13
  17. phoenix/db/insertion/types.py +19 -30
  18. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +8 -8
  19. phoenix/db/models.py +28 -28
  20. phoenix/experiments/evaluators/base.py +2 -1
  21. phoenix/experiments/evaluators/code_evaluators.py +4 -5
  22. phoenix/experiments/evaluators/llm_evaluators.py +157 -4
  23. phoenix/experiments/evaluators/utils.py +3 -2
  24. phoenix/experiments/functions.py +10 -21
  25. phoenix/experiments/tracing.py +2 -1
  26. phoenix/experiments/types.py +20 -29
  27. phoenix/experiments/utils.py +2 -1
  28. phoenix/inferences/errors.py +6 -5
  29. phoenix/inferences/fixtures.py +6 -5
  30. phoenix/inferences/inferences.py +37 -37
  31. phoenix/inferences/schema.py +11 -10
  32. phoenix/inferences/validation.py +13 -14
  33. phoenix/logging/_formatter.py +3 -3
  34. phoenix/metrics/__init__.py +5 -4
  35. phoenix/metrics/binning.py +2 -1
  36. phoenix/metrics/metrics.py +2 -1
  37. phoenix/metrics/mixins.py +7 -6
  38. phoenix/metrics/retrieval_metrics.py +2 -1
  39. phoenix/metrics/timeseries.py +5 -4
  40. phoenix/metrics/wrappers.py +2 -2
  41. phoenix/pointcloud/clustering.py +3 -4
  42. phoenix/pointcloud/pointcloud.py +7 -5
  43. phoenix/pointcloud/umap_parameters.py +2 -1
  44. phoenix/server/api/dataloaders/annotation_summaries.py +12 -19
  45. phoenix/server/api/dataloaders/average_experiment_run_latency.py +2 -2
  46. phoenix/server/api/dataloaders/cache/two_tier_cache.py +3 -2
  47. phoenix/server/api/dataloaders/dataset_example_revisions.py +3 -8
  48. phoenix/server/api/dataloaders/dataset_example_spans.py +2 -5
  49. phoenix/server/api/dataloaders/document_evaluation_summaries.py +12 -18
  50. phoenix/server/api/dataloaders/document_evaluations.py +3 -7
  51. phoenix/server/api/dataloaders/document_retrieval_metrics.py +6 -13
  52. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +4 -8
  53. phoenix/server/api/dataloaders/experiment_error_rates.py +2 -5
  54. phoenix/server/api/dataloaders/experiment_run_annotations.py +3 -7
  55. phoenix/server/api/dataloaders/experiment_run_counts.py +1 -5
  56. phoenix/server/api/dataloaders/experiment_sequence_number.py +2 -5
  57. phoenix/server/api/dataloaders/latency_ms_quantile.py +21 -30
  58. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +7 -13
  59. phoenix/server/api/dataloaders/project_by_name.py +3 -3
  60. phoenix/server/api/dataloaders/record_counts.py +11 -18
  61. phoenix/server/api/dataloaders/span_annotations.py +3 -7
  62. phoenix/server/api/dataloaders/span_dataset_examples.py +3 -8
  63. phoenix/server/api/dataloaders/span_descendants.py +3 -7
  64. phoenix/server/api/dataloaders/span_projects.py +2 -2
  65. phoenix/server/api/dataloaders/token_counts.py +12 -19
  66. phoenix/server/api/dataloaders/trace_row_ids.py +3 -7
  67. phoenix/server/api/dataloaders/user_roles.py +3 -3
  68. phoenix/server/api/dataloaders/users.py +3 -3
  69. phoenix/server/api/helpers/__init__.py +4 -3
  70. phoenix/server/api/helpers/dataset_helpers.py +10 -9
  71. phoenix/server/api/helpers/playground_clients.py +671 -0
  72. phoenix/server/api/helpers/playground_registry.py +70 -0
  73. phoenix/server/api/helpers/playground_spans.py +325 -0
  74. phoenix/server/api/input_types/AddExamplesToDatasetInput.py +2 -2
  75. phoenix/server/api/input_types/AddSpansToDatasetInput.py +2 -2
  76. phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
  77. phoenix/server/api/input_types/ChatCompletionMessageInput.py +13 -1
  78. phoenix/server/api/input_types/ClusterInput.py +2 -2
  79. phoenix/server/api/input_types/DeleteAnnotationsInput.py +1 -3
  80. phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +2 -2
  81. phoenix/server/api/input_types/DeleteExperimentsInput.py +1 -3
  82. phoenix/server/api/input_types/DimensionFilter.py +4 -4
  83. phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
  84. phoenix/server/api/input_types/Granularity.py +1 -1
  85. phoenix/server/api/input_types/InvocationParameters.py +156 -13
  86. phoenix/server/api/input_types/PatchDatasetExamplesInput.py +2 -2
  87. phoenix/server/api/input_types/TemplateOptions.py +10 -0
  88. phoenix/server/api/mutations/__init__.py +4 -0
  89. phoenix/server/api/mutations/chat_mutations.py +374 -0
  90. phoenix/server/api/mutations/dataset_mutations.py +4 -4
  91. phoenix/server/api/mutations/experiment_mutations.py +1 -2
  92. phoenix/server/api/mutations/export_events_mutations.py +7 -7
  93. phoenix/server/api/mutations/span_annotations_mutations.py +4 -4
  94. phoenix/server/api/mutations/trace_annotations_mutations.py +4 -4
  95. phoenix/server/api/mutations/user_mutations.py +4 -4
  96. phoenix/server/api/openapi/schema.py +2 -2
  97. phoenix/server/api/queries.py +61 -72
  98. phoenix/server/api/routers/oauth2.py +4 -4
  99. phoenix/server/api/routers/v1/datasets.py +22 -36
  100. phoenix/server/api/routers/v1/evaluations.py +6 -5
  101. phoenix/server/api/routers/v1/experiment_evaluations.py +2 -2
  102. phoenix/server/api/routers/v1/experiment_runs.py +2 -2
  103. phoenix/server/api/routers/v1/experiments.py +4 -4
  104. phoenix/server/api/routers/v1/spans.py +13 -12
  105. phoenix/server/api/routers/v1/traces.py +5 -5
  106. phoenix/server/api/routers/v1/utils.py +5 -5
  107. phoenix/server/api/schema.py +42 -10
  108. phoenix/server/api/subscriptions.py +347 -494
  109. phoenix/server/api/types/AnnotationSummary.py +3 -3
  110. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +44 -0
  111. phoenix/server/api/types/Cluster.py +8 -7
  112. phoenix/server/api/types/Dataset.py +5 -4
  113. phoenix/server/api/types/Dimension.py +3 -3
  114. phoenix/server/api/types/DocumentEvaluationSummary.py +8 -7
  115. phoenix/server/api/types/EmbeddingDimension.py +6 -5
  116. phoenix/server/api/types/EvaluationSummary.py +3 -3
  117. phoenix/server/api/types/Event.py +7 -7
  118. phoenix/server/api/types/Experiment.py +3 -3
  119. phoenix/server/api/types/ExperimentComparison.py +2 -4
  120. phoenix/server/api/types/GenerativeProvider.py +27 -3
  121. phoenix/server/api/types/Inferences.py +9 -8
  122. phoenix/server/api/types/InferencesRole.py +2 -2
  123. phoenix/server/api/types/Model.py +2 -2
  124. phoenix/server/api/types/Project.py +11 -18
  125. phoenix/server/api/types/Segments.py +3 -3
  126. phoenix/server/api/types/Span.py +45 -7
  127. phoenix/server/api/types/TemplateLanguage.py +9 -0
  128. phoenix/server/api/types/TimeSeries.py +8 -7
  129. phoenix/server/api/types/Trace.py +2 -2
  130. phoenix/server/api/types/UMAPPoints.py +6 -6
  131. phoenix/server/api/types/User.py +3 -3
  132. phoenix/server/api/types/node.py +1 -3
  133. phoenix/server/api/types/pagination.py +4 -4
  134. phoenix/server/api/utils.py +2 -4
  135. phoenix/server/app.py +76 -37
  136. phoenix/server/bearer_auth.py +4 -10
  137. phoenix/server/dml_event.py +3 -3
  138. phoenix/server/dml_event_handler.py +10 -24
  139. phoenix/server/grpc_server.py +3 -2
  140. phoenix/server/jwt_store.py +22 -21
  141. phoenix/server/main.py +17 -4
  142. phoenix/server/oauth2.py +3 -2
  143. phoenix/server/rate_limiters.py +5 -8
  144. phoenix/server/static/.vite/manifest.json +31 -31
  145. phoenix/server/static/assets/components-Csu8UKOs.js +1612 -0
  146. phoenix/server/static/assets/{index-DCzakdJq.js → index-Bk5C9EA7.js} +2 -2
  147. phoenix/server/static/assets/{pages-CAL1FDMt.js → pages-UeWaKXNs.js} +337 -442
  148. phoenix/server/static/assets/{vendor-6IcPAw_j.js → vendor-CtqfhlbC.js} +6 -6
  149. phoenix/server/static/assets/{vendor-arizeai-DRZuoyuF.js → vendor-arizeai-C_3SBz56.js} +2 -2
  150. phoenix/server/static/assets/{vendor-codemirror-DVE2_WBr.js → vendor-codemirror-wfdk9cjp.js} +1 -1
  151. phoenix/server/static/assets/{vendor-recharts-DwrexFA4.js → vendor-recharts-BiVnSv90.js} +1 -1
  152. phoenix/server/templates/index.html +1 -0
  153. phoenix/server/thread_server.py +1 -1
  154. phoenix/server/types.py +17 -29
  155. phoenix/services.py +8 -3
  156. phoenix/session/client.py +12 -24
  157. phoenix/session/data_extractor.py +3 -3
  158. phoenix/session/evaluation.py +1 -2
  159. phoenix/session/session.py +26 -21
  160. phoenix/trace/attributes.py +16 -28
  161. phoenix/trace/dsl/filter.py +17 -21
  162. phoenix/trace/dsl/helpers.py +3 -3
  163. phoenix/trace/dsl/query.py +13 -22
  164. phoenix/trace/fixtures.py +11 -17
  165. phoenix/trace/otel.py +5 -15
  166. phoenix/trace/projects.py +3 -2
  167. phoenix/trace/schemas.py +2 -2
  168. phoenix/trace/span_evaluations.py +9 -8
  169. phoenix/trace/span_json_decoder.py +3 -3
  170. phoenix/trace/span_json_encoder.py +2 -2
  171. phoenix/trace/trace_dataset.py +6 -5
  172. phoenix/trace/utils.py +6 -6
  173. phoenix/utilities/deprecation.py +3 -2
  174. phoenix/utilities/error_handling.py +3 -2
  175. phoenix/utilities/json.py +2 -1
  176. phoenix/utilities/logging.py +2 -2
  177. phoenix/utilities/project.py +1 -1
  178. phoenix/utilities/re.py +3 -4
  179. phoenix/utilities/template_formatters.py +16 -5
  180. phoenix/version.py +1 -1
  181. arize_phoenix-5.5.2.dist-info/RECORD +0 -321
  182. phoenix/server/static/assets/components-hX0LgYz3.js +0 -1428
  183. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/WHEEL +0 -0
  184. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/entry_points.txt +0 -0
  185. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/licenses/IP_NOTICE +0 -0
  186. {arize_phoenix-5.5.2.dist-info → arize_phoenix-5.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,70 @@
1
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union
2
+
3
+ from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
4
+
5
+ if TYPE_CHECKING:
6
+ from phoenix.server.api.helpers.playground_clients import PlaygroundStreamingClient
7
+
8
+ ModelName = Union[str, None]
9
+ ModelKey = tuple[GenerativeProviderKey, ModelName]
10
+
11
+ PROVIDER_DEFAULT = None
12
+
13
+
14
+ class SingletonMeta(type):
15
+ _instances: dict[Any, Any] = dict()
16
+
17
+ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
18
+ if cls not in cls._instances:
19
+ cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
20
+ return cls._instances[cls]
21
+
22
+
23
+ class PlaygroundClientRegistry(metaclass=SingletonMeta):
24
+ def __init__(self) -> None:
25
+ self._registry: dict[
26
+ GenerativeProviderKey, dict[ModelName, Optional[type["PlaygroundStreamingClient"]]]
27
+ ] = {}
28
+
29
+ def get_client(
30
+ self,
31
+ provider_key: GenerativeProviderKey,
32
+ model_name: ModelName,
33
+ ) -> Optional[type["PlaygroundStreamingClient"]]:
34
+ provider_registry = self._registry.get(provider_key, {})
35
+ client_class = provider_registry.get(model_name)
36
+ if client_class is None and None in provider_registry:
37
+ client_class = provider_registry[PROVIDER_DEFAULT] # Fallback to provider default
38
+ return client_class
39
+
40
+ def list_all_providers(
41
+ self,
42
+ ) -> list[GenerativeProviderKey]:
43
+ return [provider_key for provider_key in self._registry]
44
+
45
+ def list_models(self, provider_key: GenerativeProviderKey) -> list[str]:
46
+ provider_registry = self._registry.get(provider_key, {})
47
+ return [model_name for model_name in provider_registry.keys() if model_name is not None]
48
+
49
+ def list_all_models(self) -> list[ModelKey]:
50
+ return [
51
+ (provider_key, model_name)
52
+ for provider_key, provider_registry in self._registry.items()
53
+ for model_name in provider_registry.keys()
54
+ ]
55
+
56
+
57
+ PLAYGROUND_CLIENT_REGISTRY: PlaygroundClientRegistry = PlaygroundClientRegistry()
58
+
59
+
60
+ def register_llm_client(
61
+ provider_key: GenerativeProviderKey,
62
+ model_names: list[ModelName],
63
+ ) -> Callable[[type["PlaygroundStreamingClient"]], type["PlaygroundStreamingClient"]]:
64
+ def decorator(cls: type["PlaygroundStreamingClient"]) -> type["PlaygroundStreamingClient"]:
65
+ provider_registry = PLAYGROUND_CLIENT_REGISTRY._registry.setdefault(provider_key, {})
66
+ for model_name in model_names:
67
+ provider_registry[model_name] = cls
68
+ return cls
69
+
70
+ return decorator
@@ -0,0 +1,325 @@
1
+ import json
2
+ from collections import defaultdict
3
+ from collections.abc import Mapping
4
+ from dataclasses import asdict
5
+ from datetime import datetime, timezone
6
+ from itertools import chain
7
+ from traceback import format_exc
8
+ from types import TracebackType
9
+ from typing import (
10
+ Any,
11
+ Iterable,
12
+ Iterator,
13
+ Optional,
14
+ Union,
15
+ cast,
16
+ )
17
+
18
+ from openinference.instrumentation import safe_json_dumps
19
+ from openinference.semconv.trace import (
20
+ MessageAttributes,
21
+ OpenInferenceMimeTypeValues,
22
+ OpenInferenceSpanKindValues,
23
+ SpanAttributes,
24
+ ToolAttributes,
25
+ ToolCallAttributes,
26
+ )
27
+ from opentelemetry.sdk.trace.id_generator import RandomIdGenerator as DefaultOTelIDGenerator
28
+ from opentelemetry.trace import StatusCode
29
+ from sqlalchemy.ext.asyncio import AsyncSession
30
+ from strawberry.scalars import JSON as JSONScalarType
31
+ from typing_extensions import Self, TypeAlias, assert_never
32
+
33
+ from phoenix.datetime_utils import local_now, normalize_datetime
34
+ from phoenix.db import models
35
+ from phoenix.server.api.input_types.ChatCompletionInput import (
36
+ ChatCompletionInput,
37
+ ChatCompletionOverDatasetInput,
38
+ )
39
+ from phoenix.server.api.types.ChatCompletionMessageRole import ChatCompletionMessageRole
40
+ from phoenix.server.api.types.ChatCompletionSubscriptionPayload import (
41
+ TextChunk,
42
+ ToolCallChunk,
43
+ )
44
+ from phoenix.trace.attributes import unflatten
45
+ from phoenix.trace.schemas import (
46
+ SpanEvent,
47
+ SpanException,
48
+ )
49
+ from phoenix.utilities.json import jsonify
50
+
51
+ ChatCompletionMessage: TypeAlias = tuple[
52
+ ChatCompletionMessageRole, str, Optional[str], Optional[list[str]]
53
+ ]
54
+ ToolCallID: TypeAlias = str
55
+
56
+
57
+ class streaming_llm_span:
58
+ """
59
+ Creates an LLM span for a streaming chat completion.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ *,
65
+ input: Union[ChatCompletionInput, ChatCompletionOverDatasetInput],
66
+ messages: list[ChatCompletionMessage],
67
+ invocation_parameters: Mapping[str, Any],
68
+ attributes: Optional[dict[str, Any]] = None,
69
+ ) -> None:
70
+ self._input = input
71
+ self._attributes: dict[str, Any] = attributes if attributes is not None else {}
72
+ self._attributes.update(
73
+ chain(
74
+ _llm_span_kind(),
75
+ _llm_model_name(input.model.name),
76
+ _llm_tools(input.tools or []),
77
+ _llm_input_messages(messages),
78
+ _llm_invocation_parameters(invocation_parameters),
79
+ _input_value_and_mime_type(input),
80
+ )
81
+ )
82
+ self._events: list[SpanEvent] = []
83
+ self._start_time: datetime
84
+ self._end_time: datetime
85
+ self._response_chunks: list[Union[TextChunk, ToolCallChunk]] = []
86
+ self._text_chunks: list[TextChunk] = []
87
+ self._tool_call_chunks: defaultdict[ToolCallID, list[ToolCallChunk]] = defaultdict(list)
88
+ self._status_code: StatusCode
89
+ self._status_message: str
90
+ self._db_span: models.Span
91
+ self._db_trace: models.Trace
92
+
93
+ async def __aenter__(self) -> Self:
94
+ self._start_time = cast(datetime, normalize_datetime(dt=local_now(), tz=timezone.utc))
95
+ return self
96
+
97
+ async def __aexit__(
98
+ self,
99
+ exc_type: Optional[type[BaseException]],
100
+ exc_value: Optional[BaseException],
101
+ traceback: Optional[TracebackType],
102
+ ) -> bool:
103
+ self._end_time = cast(datetime, normalize_datetime(dt=local_now(), tz=timezone.utc))
104
+ self._status_code = StatusCode.OK
105
+ self._status_message = ""
106
+ if exc_type is not None:
107
+ self._status_code = StatusCode.ERROR
108
+ self._status_message = str(exc_value)
109
+ self._events.append(
110
+ SpanException(
111
+ timestamp=self._end_time,
112
+ message=self._status_message,
113
+ exception_type=type(exc_value).__name__,
114
+ exception_escaped=False,
115
+ exception_stacktrace=format_exc(),
116
+ )
117
+ )
118
+ if self._response_chunks:
119
+ self._attributes.update(
120
+ chain(
121
+ _output_value_and_mime_type(self._response_chunks),
122
+ _llm_output_messages(self._text_chunks, self._tool_call_chunks),
123
+ )
124
+ )
125
+ return True
126
+
127
+ def set_attributes(self, attributes: Mapping[str, Any]) -> None:
128
+ self._attributes.update(attributes)
129
+
130
+ def add_to_session(
131
+ self,
132
+ session: AsyncSession,
133
+ project_id: int,
134
+ ) -> models.Span:
135
+ prompt_tokens = self._attributes.get(LLM_TOKEN_COUNT_PROMPT, 0)
136
+ completion_tokens = self._attributes.get(LLM_TOKEN_COUNT_COMPLETION, 0)
137
+ trace_id = _generate_trace_id()
138
+ span_id = _generate_span_id()
139
+ self._db_trace = models.Trace(
140
+ project_rowid=project_id,
141
+ trace_id=trace_id,
142
+ start_time=self._start_time,
143
+ end_time=self._end_time,
144
+ )
145
+ self._db_span = models.Span(
146
+ trace_rowid=self._db_trace.id,
147
+ span_id=span_id,
148
+ parent_id=None,
149
+ name="ChatCompletion",
150
+ span_kind=LLM,
151
+ start_time=self._start_time,
152
+ end_time=self._end_time,
153
+ attributes=unflatten(self._attributes.items()),
154
+ events=[_serialize_event(event) for event in self._events],
155
+ status_code=self._status_code.name,
156
+ status_message=self._status_message,
157
+ cumulative_error_count=int(self._status_code is StatusCode.ERROR),
158
+ cumulative_llm_token_count_prompt=prompt_tokens,
159
+ cumulative_llm_token_count_completion=completion_tokens,
160
+ llm_token_count_prompt=prompt_tokens,
161
+ llm_token_count_completion=completion_tokens,
162
+ trace=self._db_trace,
163
+ )
164
+ session.add(self._db_trace)
165
+ session.add(self._db_span)
166
+ return self._db_span
167
+
168
+ def add_response_chunk(self, chunk: Union[TextChunk, ToolCallChunk]) -> None:
169
+ self._response_chunks.append(chunk)
170
+ if isinstance(chunk, TextChunk):
171
+ self._text_chunks.append(chunk)
172
+ elif isinstance(chunk, ToolCallChunk):
173
+ self._tool_call_chunks[chunk.id].append(chunk)
174
+ else:
175
+ assert_never(chunk)
176
+
177
+ @property
178
+ def start_time(self) -> datetime:
179
+ return self._db_span.start_time
180
+
181
+ @property
182
+ def end_time(self) -> datetime:
183
+ return self._db_span.end_time
184
+
185
+ @property
186
+ def error_message(self) -> Optional[str]:
187
+ return self._status_message if self._status_code is StatusCode.ERROR else None
188
+
189
+ @property
190
+ def trace_id(self) -> str:
191
+ return self._db_trace.trace_id
192
+
193
+ @property
194
+ def attributes(self) -> dict[str, Any]:
195
+ return self._db_span.attributes
196
+
197
+
198
+ def _llm_span_kind() -> Iterator[tuple[str, Any]]:
199
+ yield OPENINFERENCE_SPAN_KIND, LLM
200
+
201
+
202
+ def _llm_model_name(model_name: str) -> Iterator[tuple[str, Any]]:
203
+ yield LLM_MODEL_NAME, model_name
204
+
205
+
206
+ def _llm_invocation_parameters(
207
+ invocation_parameters: Mapping[str, Any],
208
+ ) -> Iterator[tuple[str, Any]]:
209
+ if invocation_parameters:
210
+ yield LLM_INVOCATION_PARAMETERS, safe_json_dumps(invocation_parameters)
211
+
212
+
213
+ def _llm_tools(tools: list[JSONScalarType]) -> Iterator[tuple[str, Any]]:
214
+ for tool_index, tool in enumerate(tools):
215
+ yield f"{LLM_TOOLS}.{tool_index}.{TOOL_JSON_SCHEMA}", json.dumps(tool)
216
+
217
+
218
+ def _input_value_and_mime_type(input: Any) -> Iterator[tuple[str, Any]]:
219
+ assert (api_key := "api_key") in (input_data := jsonify(input))
220
+ disallowed_keys = {"api_key", "invocation_parameters"}
221
+ input_data = {k: v for k, v in input_data.items() if k not in disallowed_keys}
222
+ assert api_key not in input_data
223
+ yield INPUT_MIME_TYPE, JSON
224
+ yield INPUT_VALUE, safe_json_dumps(input_data)
225
+
226
+
227
+ def _output_value_and_mime_type(output: Any) -> Iterator[tuple[str, Any]]:
228
+ yield OUTPUT_MIME_TYPE, JSON
229
+ yield OUTPUT_VALUE, safe_json_dumps(jsonify(output))
230
+
231
+
232
+ def _llm_input_messages(
233
+ messages: Iterable[
234
+ tuple[ChatCompletionMessageRole, str, Optional[str], Optional[list[JSONScalarType]]]
235
+ ],
236
+ ) -> Iterator[tuple[str, Any]]:
237
+ for i, (role, content, _tool_call_id, tool_calls) in enumerate(messages):
238
+ yield f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_ROLE}", role.value.lower()
239
+ yield f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_CONTENT}", content
240
+ if tool_calls is not None:
241
+ for tool_call_index, tool_call in enumerate(tool_calls):
242
+ yield (
243
+ f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_NAME}",
244
+ tool_call["function"]["name"],
245
+ )
246
+ if arguments := tool_call["function"]["arguments"]:
247
+ yield (
248
+ f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_ARGUMENTS_JSON}",
249
+ safe_json_dumps(jsonify(arguments)),
250
+ )
251
+
252
+
253
+ def _llm_output_messages(
254
+ text_chunks: list[TextChunk],
255
+ tool_call_chunks: defaultdict[ToolCallID, list[ToolCallChunk]],
256
+ ) -> Iterator[tuple[str, Any]]:
257
+ yield f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_ROLE}", "assistant"
258
+ if content := "".join(chunk.content for chunk in text_chunks):
259
+ yield f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_CONTENT}", content
260
+ for tool_call_index, (_tool_call_id, tool_call_chunks_) in enumerate(tool_call_chunks.items()):
261
+ if tool_call_chunks_ and (name := tool_call_chunks_[0].function.name):
262
+ yield (
263
+ f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_NAME}",
264
+ name,
265
+ )
266
+ if arguments := "".join(chunk.function.arguments for chunk in tool_call_chunks_):
267
+ yield (
268
+ f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_ARGUMENTS_JSON}",
269
+ arguments,
270
+ )
271
+
272
+
273
+ def _generate_trace_id() -> str:
274
+ """
275
+ Generates a random trace ID in hexadecimal format.
276
+ """
277
+ return _hex(DefaultOTelIDGenerator().generate_trace_id())
278
+
279
+
280
+ def _generate_span_id() -> str:
281
+ """
282
+ Generates a random span ID in hexadecimal format.
283
+ """
284
+ return _hex(DefaultOTelIDGenerator().generate_span_id())
285
+
286
+
287
+ def _hex(number: int) -> str:
288
+ """
289
+ Converts an integer to a hexadecimal string.
290
+ """
291
+ return hex(number)[2:]
292
+
293
+
294
+ def _serialize_event(event: SpanEvent) -> dict[str, Any]:
295
+ """
296
+ Serializes a SpanEvent to a dictionary.
297
+ """
298
+ return {k: (v.isoformat() if isinstance(v, datetime) else v) for k, v in asdict(event).items()}
299
+
300
+
301
+ JSON = OpenInferenceMimeTypeValues.JSON.value
302
+
303
+ LLM = OpenInferenceSpanKindValues.LLM.value
304
+
305
+ OPENINFERENCE_SPAN_KIND = SpanAttributes.OPENINFERENCE_SPAN_KIND
306
+ INPUT_MIME_TYPE = SpanAttributes.INPUT_MIME_TYPE
307
+ INPUT_VALUE = SpanAttributes.INPUT_VALUE
308
+ OUTPUT_MIME_TYPE = SpanAttributes.OUTPUT_MIME_TYPE
309
+ OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE
310
+ LLM_INPUT_MESSAGES = SpanAttributes.LLM_INPUT_MESSAGES
311
+ LLM_OUTPUT_MESSAGES = SpanAttributes.LLM_OUTPUT_MESSAGES
312
+ LLM_MODEL_NAME = SpanAttributes.LLM_MODEL_NAME
313
+ LLM_INVOCATION_PARAMETERS = SpanAttributes.LLM_INVOCATION_PARAMETERS
314
+ LLM_TOOLS = SpanAttributes.LLM_TOOLS
315
+ LLM_TOKEN_COUNT_PROMPT = SpanAttributes.LLM_TOKEN_COUNT_PROMPT
316
+ LLM_TOKEN_COUNT_COMPLETION = SpanAttributes.LLM_TOKEN_COUNT_COMPLETION
317
+
318
+ MESSAGE_CONTENT = MessageAttributes.MESSAGE_CONTENT
319
+ MESSAGE_ROLE = MessageAttributes.MESSAGE_ROLE
320
+ MESSAGE_TOOL_CALLS = MessageAttributes.MESSAGE_TOOL_CALLS
321
+
322
+ TOOL_CALL_FUNCTION_NAME = ToolCallAttributes.TOOL_CALL_FUNCTION_NAME
323
+ TOOL_CALL_FUNCTION_ARGUMENTS_JSON = ToolCallAttributes.TOOL_CALL_FUNCTION_ARGUMENTS_JSON
324
+
325
+ TOOL_JSON_SCHEMA = ToolAttributes.TOOL_JSON_SCHEMA
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  import strawberry
4
4
  from strawberry import UNSET
@@ -11,6 +11,6 @@ from .DatasetExampleInput import DatasetExampleInput
11
11
  @strawberry.input
12
12
  class AddExamplesToDatasetInput:
13
13
  dataset_id: GlobalID
14
- examples: List[DatasetExampleInput]
14
+ examples: list[DatasetExampleInput]
15
15
  dataset_version_description: Optional[str] = UNSET
16
16
  dataset_version_metadata: Optional[JSON] = UNSET
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  import strawberry
4
4
  from strawberry import UNSET
@@ -9,6 +9,6 @@ from strawberry.scalars import JSON
9
9
  @strawberry.input
10
10
  class AddSpansToDatasetInput:
11
11
  dataset_id: GlobalID
12
- span_ids: List[GlobalID]
12
+ span_ids: list[GlobalID]
13
13
  dataset_version_description: Optional[str] = UNSET
14
14
  dataset_version_metadata: Optional[JSON] = UNSET
@@ -0,0 +1,38 @@
1
+ from typing import Optional
2
+
3
+ import strawberry
4
+ from strawberry import UNSET
5
+ from strawberry.relay.types import GlobalID
6
+ from strawberry.scalars import JSON
7
+
8
+ from phoenix.server.api.types.TemplateLanguage import TemplateLanguage
9
+
10
+ from .ChatCompletionMessageInput import ChatCompletionMessageInput
11
+ from .GenerativeModelInput import GenerativeModelInput
12
+ from .InvocationParameters import InvocationParameterInput
13
+ from .TemplateOptions import TemplateOptions
14
+
15
+
16
+ @strawberry.input
17
+ class ChatCompletionInput:
18
+ messages: list[ChatCompletionMessageInput]
19
+ model: GenerativeModelInput
20
+ invocation_parameters: list[InvocationParameterInput] = strawberry.field(default_factory=list)
21
+ tools: Optional[list[JSON]] = UNSET
22
+ api_key: Optional[str] = strawberry.field(default=None)
23
+ template: Optional[TemplateOptions] = UNSET
24
+
25
+
26
+ @strawberry.input
27
+ class ChatCompletionOverDatasetInput:
28
+ messages: list[ChatCompletionMessageInput]
29
+ model: GenerativeModelInput
30
+ invocation_parameters: list[InvocationParameterInput] = strawberry.field(default_factory=list)
31
+ tools: Optional[list[JSON]] = UNSET
32
+ api_key: Optional[str] = strawberry.field(default=None)
33
+ template_language: TemplateLanguage
34
+ dataset_id: GlobalID
35
+ dataset_version_id: Optional[GlobalID] = None
36
+ experiment_name: Optional[str] = None
37
+ experiment_description: Optional[str] = None
38
+ experiment_metadata: Optional[JSON] = strawberry.field(default_factory=dict)
@@ -1,4 +1,7 @@
1
+ from typing import Optional
2
+
1
3
  import strawberry
4
+ from strawberry import UNSET
2
5
  from strawberry.scalars import JSON
3
6
 
4
7
  from phoenix.server.api.types.ChatCompletionMessageRole import ChatCompletionMessageRole
@@ -8,5 +11,14 @@ from phoenix.server.api.types.ChatCompletionMessageRole import ChatCompletionMes
8
11
  class ChatCompletionMessageInput:
9
12
  role: ChatCompletionMessageRole
10
13
  content: JSON = strawberry.field(
11
- description="The content of the message as JSON to support text and tools",
14
+ default="",
15
+ description="The content of the message as JSON to support various kinds of text",
16
+ )
17
+ tool_calls: Optional[list[JSON]] = strawberry.field(
18
+ description="The tool calls that were made in the message",
19
+ default=UNSET,
20
+ )
21
+ tool_call_id: Optional[str] = strawberry.field(
22
+ description="The ID that corresponds to a prior tool call. Used to link a tool message to a pre-existing tool call.", # noqa: E501
23
+ default=UNSET,
12
24
  )
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  import strawberry
4
4
  from strawberry import ID, UNSET
@@ -6,5 +6,5 @@ from strawberry import ID, UNSET
6
6
 
7
7
  @strawberry.input
8
8
  class ClusterInput:
9
- event_ids: List[ID]
9
+ event_ids: list[ID]
10
10
  id: Optional[ID] = UNSET
@@ -1,9 +1,7 @@
1
- from typing import List
2
-
3
1
  import strawberry
4
2
  from strawberry.relay import GlobalID
5
3
 
6
4
 
7
5
  @strawberry.input
8
6
  class DeleteAnnotationsInput:
9
- annotation_ids: List[GlobalID]
7
+ annotation_ids: list[GlobalID]
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  import strawberry
4
4
  from strawberry import UNSET
@@ -8,6 +8,6 @@ from strawberry.scalars import JSON
8
8
 
9
9
  @strawberry.input
10
10
  class DeleteDatasetExamplesInput:
11
- example_ids: List[GlobalID]
11
+ example_ids: list[GlobalID]
12
12
  dataset_version_description: Optional[str] = UNSET
13
13
  dataset_version_metadata: Optional[JSON] = UNSET
@@ -1,9 +1,7 @@
1
- from typing import List
2
-
3
1
  import strawberry
4
2
  from strawberry.relay import GlobalID
5
3
 
6
4
 
7
5
  @strawberry.input
8
6
  class DeleteExperimentsInput:
9
- experiment_ids: List[GlobalID]
7
+ experiment_ids: list[GlobalID]
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  import strawberry
4
4
  from strawberry import UNSET
@@ -52,9 +52,9 @@ class DimensionFilter:
52
52
 
53
53
  """
54
54
 
55
- types: Optional[List[DimensionType]] = UNSET
56
- shapes: Optional[List[DimensionShape]] = UNSET
57
- data_types: Optional[List[DimensionDataType]] = UNSET
55
+ types: Optional[list[DimensionType]] = UNSET
56
+ shapes: Optional[list[DimensionShape]] = UNSET
57
+ data_types: Optional[list[DimensionDataType]] = UNSET
58
58
 
59
59
  def __post_init__(self) -> None:
60
60
  self.types = ensure_list(self.types)
@@ -0,0 +1,17 @@
1
+ from typing import Optional
2
+
3
+ import strawberry
4
+ from strawberry import UNSET
5
+
6
+ from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
7
+
8
+
9
+ @strawberry.input
10
+ class GenerativeModelInput:
11
+ provider_key: GenerativeProviderKey
12
+ name: str
13
+ """ The name of the model. Or the Deployment Name for Azure OpenAI models. """
14
+ endpoint: Optional[str] = UNSET
15
+ """ The endpoint to use for the model. Only required for Azure OpenAI models. """
16
+ api_version: Optional[str] = UNSET
17
+ """ The API version to use for the model. """
@@ -1,6 +1,6 @@
1
+ from collections.abc import Iterator
1
2
  from datetime import datetime, timedelta
2
3
  from itertools import accumulate, repeat, takewhile
3
- from typing import Iterator
4
4
 
5
5
  import strawberry
6
6