arize-phoenix 3.16.1__py3-none-any.whl → 7.7.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.

Potentially problematic release.


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

Files changed (338) hide show
  1. arize_phoenix-7.7.1.dist-info/METADATA +261 -0
  2. arize_phoenix-7.7.1.dist-info/RECORD +345 -0
  3. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.1.dist-info}/WHEEL +1 -1
  4. arize_phoenix-7.7.1.dist-info/entry_points.txt +3 -0
  5. phoenix/__init__.py +86 -14
  6. phoenix/auth.py +309 -0
  7. phoenix/config.py +675 -45
  8. phoenix/core/model.py +32 -30
  9. phoenix/core/model_schema.py +102 -109
  10. phoenix/core/model_schema_adapter.py +48 -45
  11. phoenix/datetime_utils.py +24 -3
  12. phoenix/db/README.md +54 -0
  13. phoenix/db/__init__.py +4 -0
  14. phoenix/db/alembic.ini +85 -0
  15. phoenix/db/bulk_inserter.py +294 -0
  16. phoenix/db/engines.py +208 -0
  17. phoenix/db/enums.py +20 -0
  18. phoenix/db/facilitator.py +113 -0
  19. phoenix/db/helpers.py +159 -0
  20. phoenix/db/insertion/constants.py +2 -0
  21. phoenix/db/insertion/dataset.py +227 -0
  22. phoenix/db/insertion/document_annotation.py +171 -0
  23. phoenix/db/insertion/evaluation.py +191 -0
  24. phoenix/db/insertion/helpers.py +98 -0
  25. phoenix/db/insertion/span.py +193 -0
  26. phoenix/db/insertion/span_annotation.py +158 -0
  27. phoenix/db/insertion/trace_annotation.py +158 -0
  28. phoenix/db/insertion/types.py +256 -0
  29. phoenix/db/migrate.py +86 -0
  30. phoenix/db/migrations/data_migration_scripts/populate_project_sessions.py +199 -0
  31. phoenix/db/migrations/env.py +114 -0
  32. phoenix/db/migrations/script.py.mako +26 -0
  33. phoenix/db/migrations/versions/10460e46d750_datasets.py +317 -0
  34. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +126 -0
  35. phoenix/db/migrations/versions/4ded9e43755f_create_project_sessions_table.py +66 -0
  36. phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
  37. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
  38. phoenix/db/models.py +807 -0
  39. phoenix/exceptions.py +5 -1
  40. phoenix/experiments/__init__.py +6 -0
  41. phoenix/experiments/evaluators/__init__.py +29 -0
  42. phoenix/experiments/evaluators/base.py +158 -0
  43. phoenix/experiments/evaluators/code_evaluators.py +184 -0
  44. phoenix/experiments/evaluators/llm_evaluators.py +473 -0
  45. phoenix/experiments/evaluators/utils.py +236 -0
  46. phoenix/experiments/functions.py +772 -0
  47. phoenix/experiments/tracing.py +86 -0
  48. phoenix/experiments/types.py +726 -0
  49. phoenix/experiments/utils.py +25 -0
  50. phoenix/inferences/__init__.py +0 -0
  51. phoenix/{datasets → inferences}/errors.py +6 -5
  52. phoenix/{datasets → inferences}/fixtures.py +49 -42
  53. phoenix/{datasets/dataset.py → inferences/inferences.py} +121 -105
  54. phoenix/{datasets → inferences}/schema.py +11 -11
  55. phoenix/{datasets → inferences}/validation.py +13 -14
  56. phoenix/logging/__init__.py +3 -0
  57. phoenix/logging/_config.py +90 -0
  58. phoenix/logging/_filter.py +6 -0
  59. phoenix/logging/_formatter.py +69 -0
  60. phoenix/metrics/__init__.py +5 -4
  61. phoenix/metrics/binning.py +4 -3
  62. phoenix/metrics/metrics.py +2 -1
  63. phoenix/metrics/mixins.py +7 -6
  64. phoenix/metrics/retrieval_metrics.py +2 -1
  65. phoenix/metrics/timeseries.py +5 -4
  66. phoenix/metrics/wrappers.py +9 -3
  67. phoenix/pointcloud/clustering.py +5 -5
  68. phoenix/pointcloud/pointcloud.py +7 -5
  69. phoenix/pointcloud/projectors.py +5 -6
  70. phoenix/pointcloud/umap_parameters.py +53 -52
  71. phoenix/server/api/README.md +28 -0
  72. phoenix/server/api/auth.py +44 -0
  73. phoenix/server/api/context.py +152 -9
  74. phoenix/server/api/dataloaders/__init__.py +91 -0
  75. phoenix/server/api/dataloaders/annotation_summaries.py +139 -0
  76. phoenix/server/api/dataloaders/average_experiment_run_latency.py +54 -0
  77. phoenix/server/api/dataloaders/cache/__init__.py +3 -0
  78. phoenix/server/api/dataloaders/cache/two_tier_cache.py +68 -0
  79. phoenix/server/api/dataloaders/dataset_example_revisions.py +131 -0
  80. phoenix/server/api/dataloaders/dataset_example_spans.py +38 -0
  81. phoenix/server/api/dataloaders/document_evaluation_summaries.py +144 -0
  82. phoenix/server/api/dataloaders/document_evaluations.py +31 -0
  83. phoenix/server/api/dataloaders/document_retrieval_metrics.py +89 -0
  84. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +79 -0
  85. phoenix/server/api/dataloaders/experiment_error_rates.py +58 -0
  86. phoenix/server/api/dataloaders/experiment_run_annotations.py +36 -0
  87. phoenix/server/api/dataloaders/experiment_run_counts.py +49 -0
  88. phoenix/server/api/dataloaders/experiment_sequence_number.py +44 -0
  89. phoenix/server/api/dataloaders/latency_ms_quantile.py +188 -0
  90. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +85 -0
  91. phoenix/server/api/dataloaders/project_by_name.py +31 -0
  92. phoenix/server/api/dataloaders/record_counts.py +116 -0
  93. phoenix/server/api/dataloaders/session_io.py +79 -0
  94. phoenix/server/api/dataloaders/session_num_traces.py +30 -0
  95. phoenix/server/api/dataloaders/session_num_traces_with_error.py +32 -0
  96. phoenix/server/api/dataloaders/session_token_usages.py +41 -0
  97. phoenix/server/api/dataloaders/session_trace_latency_ms_quantile.py +55 -0
  98. phoenix/server/api/dataloaders/span_annotations.py +26 -0
  99. phoenix/server/api/dataloaders/span_dataset_examples.py +31 -0
  100. phoenix/server/api/dataloaders/span_descendants.py +57 -0
  101. phoenix/server/api/dataloaders/span_projects.py +33 -0
  102. phoenix/server/api/dataloaders/token_counts.py +124 -0
  103. phoenix/server/api/dataloaders/trace_by_trace_ids.py +25 -0
  104. phoenix/server/api/dataloaders/trace_root_spans.py +32 -0
  105. phoenix/server/api/dataloaders/user_roles.py +30 -0
  106. phoenix/server/api/dataloaders/users.py +33 -0
  107. phoenix/server/api/exceptions.py +48 -0
  108. phoenix/server/api/helpers/__init__.py +12 -0
  109. phoenix/server/api/helpers/dataset_helpers.py +217 -0
  110. phoenix/server/api/helpers/experiment_run_filters.py +763 -0
  111. phoenix/server/api/helpers/playground_clients.py +948 -0
  112. phoenix/server/api/helpers/playground_registry.py +70 -0
  113. phoenix/server/api/helpers/playground_spans.py +455 -0
  114. phoenix/server/api/input_types/AddExamplesToDatasetInput.py +16 -0
  115. phoenix/server/api/input_types/AddSpansToDatasetInput.py +14 -0
  116. phoenix/server/api/input_types/ChatCompletionInput.py +38 -0
  117. phoenix/server/api/input_types/ChatCompletionMessageInput.py +24 -0
  118. phoenix/server/api/input_types/ClearProjectInput.py +15 -0
  119. phoenix/server/api/input_types/ClusterInput.py +2 -2
  120. phoenix/server/api/input_types/CreateDatasetInput.py +12 -0
  121. phoenix/server/api/input_types/CreateSpanAnnotationInput.py +18 -0
  122. phoenix/server/api/input_types/CreateTraceAnnotationInput.py +18 -0
  123. phoenix/server/api/input_types/DataQualityMetricInput.py +5 -2
  124. phoenix/server/api/input_types/DatasetExampleInput.py +14 -0
  125. phoenix/server/api/input_types/DatasetSort.py +17 -0
  126. phoenix/server/api/input_types/DatasetVersionSort.py +16 -0
  127. phoenix/server/api/input_types/DeleteAnnotationsInput.py +7 -0
  128. phoenix/server/api/input_types/DeleteDatasetExamplesInput.py +13 -0
  129. phoenix/server/api/input_types/DeleteDatasetInput.py +7 -0
  130. phoenix/server/api/input_types/DeleteExperimentsInput.py +7 -0
  131. phoenix/server/api/input_types/DimensionFilter.py +4 -4
  132. phoenix/server/api/input_types/GenerativeModelInput.py +17 -0
  133. phoenix/server/api/input_types/Granularity.py +1 -1
  134. phoenix/server/api/input_types/InvocationParameters.py +162 -0
  135. phoenix/server/api/input_types/PatchAnnotationInput.py +19 -0
  136. phoenix/server/api/input_types/PatchDatasetExamplesInput.py +35 -0
  137. phoenix/server/api/input_types/PatchDatasetInput.py +14 -0
  138. phoenix/server/api/input_types/PerformanceMetricInput.py +5 -2
  139. phoenix/server/api/input_types/ProjectSessionSort.py +29 -0
  140. phoenix/server/api/input_types/SpanAnnotationSort.py +17 -0
  141. phoenix/server/api/input_types/SpanSort.py +134 -69
  142. phoenix/server/api/input_types/TemplateOptions.py +10 -0
  143. phoenix/server/api/input_types/TraceAnnotationSort.py +17 -0
  144. phoenix/server/api/input_types/UserRoleInput.py +9 -0
  145. phoenix/server/api/mutations/__init__.py +28 -0
  146. phoenix/server/api/mutations/api_key_mutations.py +167 -0
  147. phoenix/server/api/mutations/chat_mutations.py +593 -0
  148. phoenix/server/api/mutations/dataset_mutations.py +591 -0
  149. phoenix/server/api/mutations/experiment_mutations.py +75 -0
  150. phoenix/server/api/{types/ExportEventsMutation.py → mutations/export_events_mutations.py} +21 -18
  151. phoenix/server/api/mutations/project_mutations.py +57 -0
  152. phoenix/server/api/mutations/span_annotations_mutations.py +128 -0
  153. phoenix/server/api/mutations/trace_annotations_mutations.py +127 -0
  154. phoenix/server/api/mutations/user_mutations.py +329 -0
  155. phoenix/server/api/openapi/__init__.py +0 -0
  156. phoenix/server/api/openapi/main.py +17 -0
  157. phoenix/server/api/openapi/schema.py +16 -0
  158. phoenix/server/api/queries.py +738 -0
  159. phoenix/server/api/routers/__init__.py +11 -0
  160. phoenix/server/api/routers/auth.py +284 -0
  161. phoenix/server/api/routers/embeddings.py +26 -0
  162. phoenix/server/api/routers/oauth2.py +488 -0
  163. phoenix/server/api/routers/v1/__init__.py +64 -0
  164. phoenix/server/api/routers/v1/datasets.py +1017 -0
  165. phoenix/server/api/routers/v1/evaluations.py +362 -0
  166. phoenix/server/api/routers/v1/experiment_evaluations.py +115 -0
  167. phoenix/server/api/routers/v1/experiment_runs.py +167 -0
  168. phoenix/server/api/routers/v1/experiments.py +308 -0
  169. phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
  170. phoenix/server/api/routers/v1/spans.py +267 -0
  171. phoenix/server/api/routers/v1/traces.py +208 -0
  172. phoenix/server/api/routers/v1/utils.py +95 -0
  173. phoenix/server/api/schema.py +44 -241
  174. phoenix/server/api/subscriptions.py +597 -0
  175. phoenix/server/api/types/Annotation.py +21 -0
  176. phoenix/server/api/types/AnnotationSummary.py +55 -0
  177. phoenix/server/api/types/AnnotatorKind.py +16 -0
  178. phoenix/server/api/types/ApiKey.py +27 -0
  179. phoenix/server/api/types/AuthMethod.py +9 -0
  180. phoenix/server/api/types/ChatCompletionMessageRole.py +11 -0
  181. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +46 -0
  182. phoenix/server/api/types/Cluster.py +25 -24
  183. phoenix/server/api/types/CreateDatasetPayload.py +8 -0
  184. phoenix/server/api/types/DataQualityMetric.py +31 -13
  185. phoenix/server/api/types/Dataset.py +288 -63
  186. phoenix/server/api/types/DatasetExample.py +85 -0
  187. phoenix/server/api/types/DatasetExampleRevision.py +34 -0
  188. phoenix/server/api/types/DatasetVersion.py +14 -0
  189. phoenix/server/api/types/Dimension.py +32 -31
  190. phoenix/server/api/types/DocumentEvaluationSummary.py +9 -8
  191. phoenix/server/api/types/EmbeddingDimension.py +56 -49
  192. phoenix/server/api/types/Evaluation.py +25 -31
  193. phoenix/server/api/types/EvaluationSummary.py +30 -50
  194. phoenix/server/api/types/Event.py +20 -20
  195. phoenix/server/api/types/ExampleRevisionInterface.py +14 -0
  196. phoenix/server/api/types/Experiment.py +152 -0
  197. phoenix/server/api/types/ExperimentAnnotationSummary.py +13 -0
  198. phoenix/server/api/types/ExperimentComparison.py +17 -0
  199. phoenix/server/api/types/ExperimentRun.py +119 -0
  200. phoenix/server/api/types/ExperimentRunAnnotation.py +56 -0
  201. phoenix/server/api/types/GenerativeModel.py +9 -0
  202. phoenix/server/api/types/GenerativeProvider.py +85 -0
  203. phoenix/server/api/types/Inferences.py +80 -0
  204. phoenix/server/api/types/InferencesRole.py +23 -0
  205. phoenix/server/api/types/LabelFraction.py +7 -0
  206. phoenix/server/api/types/MimeType.py +2 -2
  207. phoenix/server/api/types/Model.py +54 -54
  208. phoenix/server/api/types/PerformanceMetric.py +8 -5
  209. phoenix/server/api/types/Project.py +407 -142
  210. phoenix/server/api/types/ProjectSession.py +139 -0
  211. phoenix/server/api/types/Segments.py +4 -4
  212. phoenix/server/api/types/Span.py +221 -176
  213. phoenix/server/api/types/SpanAnnotation.py +43 -0
  214. phoenix/server/api/types/SpanIOValue.py +15 -0
  215. phoenix/server/api/types/SystemApiKey.py +9 -0
  216. phoenix/server/api/types/TemplateLanguage.py +10 -0
  217. phoenix/server/api/types/TimeSeries.py +19 -15
  218. phoenix/server/api/types/TokenUsage.py +11 -0
  219. phoenix/server/api/types/Trace.py +154 -0
  220. phoenix/server/api/types/TraceAnnotation.py +45 -0
  221. phoenix/server/api/types/UMAPPoints.py +7 -7
  222. phoenix/server/api/types/User.py +60 -0
  223. phoenix/server/api/types/UserApiKey.py +45 -0
  224. phoenix/server/api/types/UserRole.py +15 -0
  225. phoenix/server/api/types/node.py +4 -112
  226. phoenix/server/api/types/pagination.py +156 -57
  227. phoenix/server/api/utils.py +34 -0
  228. phoenix/server/app.py +864 -115
  229. phoenix/server/bearer_auth.py +163 -0
  230. phoenix/server/dml_event.py +136 -0
  231. phoenix/server/dml_event_handler.py +256 -0
  232. phoenix/server/email/__init__.py +0 -0
  233. phoenix/server/email/sender.py +97 -0
  234. phoenix/server/email/templates/__init__.py +0 -0
  235. phoenix/server/email/templates/password_reset.html +19 -0
  236. phoenix/server/email/types.py +11 -0
  237. phoenix/server/grpc_server.py +102 -0
  238. phoenix/server/jwt_store.py +505 -0
  239. phoenix/server/main.py +305 -116
  240. phoenix/server/oauth2.py +52 -0
  241. phoenix/server/openapi/__init__.py +0 -0
  242. phoenix/server/prometheus.py +111 -0
  243. phoenix/server/rate_limiters.py +188 -0
  244. phoenix/server/static/.vite/manifest.json +87 -0
  245. phoenix/server/static/assets/components-Cy9nwIvF.js +2125 -0
  246. phoenix/server/static/assets/index-BKvHIxkk.js +113 -0
  247. phoenix/server/static/assets/pages-CUi2xCVQ.js +4449 -0
  248. phoenix/server/static/assets/vendor-DvC8cT4X.js +894 -0
  249. phoenix/server/static/assets/vendor-DxkFTwjz.css +1 -0
  250. phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +662 -0
  251. phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +24 -0
  252. phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +59 -0
  253. phoenix/server/static/assets/vendor-shiki-Cl9QBraO.js +5 -0
  254. phoenix/server/static/assets/vendor-three-DwGkEfCM.js +2998 -0
  255. phoenix/server/telemetry.py +68 -0
  256. phoenix/server/templates/index.html +82 -23
  257. phoenix/server/thread_server.py +3 -3
  258. phoenix/server/types.py +275 -0
  259. phoenix/services.py +27 -18
  260. phoenix/session/client.py +743 -68
  261. phoenix/session/data_extractor.py +31 -7
  262. phoenix/session/evaluation.py +3 -9
  263. phoenix/session/session.py +263 -219
  264. phoenix/settings.py +22 -0
  265. phoenix/trace/__init__.py +2 -22
  266. phoenix/trace/attributes.py +338 -0
  267. phoenix/trace/dsl/README.md +116 -0
  268. phoenix/trace/dsl/filter.py +663 -213
  269. phoenix/trace/dsl/helpers.py +73 -21
  270. phoenix/trace/dsl/query.py +574 -201
  271. phoenix/trace/exporter.py +24 -19
  272. phoenix/trace/fixtures.py +368 -32
  273. phoenix/trace/otel.py +71 -219
  274. phoenix/trace/projects.py +3 -2
  275. phoenix/trace/schemas.py +33 -11
  276. phoenix/trace/span_evaluations.py +21 -16
  277. phoenix/trace/span_json_decoder.py +6 -4
  278. phoenix/trace/span_json_encoder.py +2 -2
  279. phoenix/trace/trace_dataset.py +47 -32
  280. phoenix/trace/utils.py +21 -4
  281. phoenix/utilities/__init__.py +0 -26
  282. phoenix/utilities/client.py +132 -0
  283. phoenix/utilities/deprecation.py +31 -0
  284. phoenix/utilities/error_handling.py +3 -2
  285. phoenix/utilities/json.py +109 -0
  286. phoenix/utilities/logging.py +8 -0
  287. phoenix/utilities/project.py +2 -2
  288. phoenix/utilities/re.py +49 -0
  289. phoenix/utilities/span_store.py +0 -23
  290. phoenix/utilities/template_formatters.py +99 -0
  291. phoenix/version.py +1 -1
  292. arize_phoenix-3.16.1.dist-info/METADATA +0 -495
  293. arize_phoenix-3.16.1.dist-info/RECORD +0 -178
  294. phoenix/core/project.py +0 -619
  295. phoenix/core/traces.py +0 -96
  296. phoenix/experimental/evals/__init__.py +0 -73
  297. phoenix/experimental/evals/evaluators.py +0 -413
  298. phoenix/experimental/evals/functions/__init__.py +0 -4
  299. phoenix/experimental/evals/functions/classify.py +0 -453
  300. phoenix/experimental/evals/functions/executor.py +0 -353
  301. phoenix/experimental/evals/functions/generate.py +0 -138
  302. phoenix/experimental/evals/functions/processing.py +0 -76
  303. phoenix/experimental/evals/models/__init__.py +0 -14
  304. phoenix/experimental/evals/models/anthropic.py +0 -175
  305. phoenix/experimental/evals/models/base.py +0 -170
  306. phoenix/experimental/evals/models/bedrock.py +0 -221
  307. phoenix/experimental/evals/models/litellm.py +0 -134
  308. phoenix/experimental/evals/models/openai.py +0 -448
  309. phoenix/experimental/evals/models/rate_limiters.py +0 -246
  310. phoenix/experimental/evals/models/vertex.py +0 -173
  311. phoenix/experimental/evals/models/vertexai.py +0 -186
  312. phoenix/experimental/evals/retrievals.py +0 -96
  313. phoenix/experimental/evals/templates/__init__.py +0 -50
  314. phoenix/experimental/evals/templates/default_templates.py +0 -472
  315. phoenix/experimental/evals/templates/template.py +0 -195
  316. phoenix/experimental/evals/utils/__init__.py +0 -172
  317. phoenix/experimental/evals/utils/threads.py +0 -27
  318. phoenix/server/api/helpers.py +0 -11
  319. phoenix/server/api/routers/evaluation_handler.py +0 -109
  320. phoenix/server/api/routers/span_handler.py +0 -70
  321. phoenix/server/api/routers/trace_handler.py +0 -60
  322. phoenix/server/api/types/DatasetRole.py +0 -23
  323. phoenix/server/static/index.css +0 -6
  324. phoenix/server/static/index.js +0 -7447
  325. phoenix/storage/span_store/__init__.py +0 -23
  326. phoenix/storage/span_store/text_file.py +0 -85
  327. phoenix/trace/dsl/missing.py +0 -60
  328. phoenix/trace/langchain/__init__.py +0 -3
  329. phoenix/trace/langchain/instrumentor.py +0 -35
  330. phoenix/trace/llama_index/__init__.py +0 -3
  331. phoenix/trace/llama_index/callback.py +0 -102
  332. phoenix/trace/openai/__init__.py +0 -3
  333. phoenix/trace/openai/instrumentor.py +0 -30
  334. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.1.dist-info}/licenses/IP_NOTICE +0 -0
  335. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.1.dist-info}/licenses/LICENSE +0 -0
  336. /phoenix/{datasets → db/insertion}/__init__.py +0 -0
  337. /phoenix/{experimental → db/migrations}/__init__.py +0 -0
  338. /phoenix/{storage → db/migrations/data_migration_scripts}/__init__.py +0 -0
phoenix/trace/otel.py CHANGED
@@ -1,39 +1,34 @@
1
- import inspect
2
1
  import json
3
2
  from binascii import hexlify, unhexlify
3
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
4
4
  from datetime import datetime, timezone
5
5
  from types import MappingProxyType
6
- from typing import (
7
- Any,
8
- DefaultDict,
9
- Dict,
10
- Iterable,
11
- Iterator,
12
- List,
13
- Mapping,
14
- Optional,
15
- Sequence,
16
- Set,
17
- SupportsFloat,
18
- Tuple,
19
- Union,
20
- cast,
21
- )
6
+ from typing import Any, Optional, SupportsFloat, cast
22
7
 
23
8
  import numpy as np
24
9
  import opentelemetry.proto.trace.v1.trace_pb2 as otlp
25
- from openinference.semconv import trace
26
- from openinference.semconv.trace import DocumentAttributes, SpanAttributes
10
+ from openinference.semconv.trace import (
11
+ DocumentAttributes,
12
+ OpenInferenceMimeTypeValues,
13
+ SpanAttributes,
14
+ )
27
15
  from opentelemetry.proto.common.v1.common_pb2 import AnyValue, ArrayValue, KeyValue
28
16
  from opentelemetry.util.types import Attributes, AttributeValue
29
17
  from typing_extensions import TypeAlias, assert_never
30
18
 
19
+ from phoenix.trace.attributes import (
20
+ JSON_STRING_ATTRIBUTES,
21
+ flatten,
22
+ get_attribute_value,
23
+ has_mapping,
24
+ load_json_strings,
25
+ unflatten,
26
+ )
31
27
  from phoenix.trace.schemas import (
32
28
  EXCEPTION_ESCAPED,
33
29
  EXCEPTION_MESSAGE,
34
30
  EXCEPTION_STACKTRACE,
35
31
  EXCEPTION_TYPE,
36
- MimeType,
37
32
  Span,
38
33
  SpanContext,
39
34
  SpanEvent,
@@ -43,6 +38,7 @@ from phoenix.trace.schemas import (
43
38
  SpanStatusCode,
44
39
  TraceID,
45
40
  )
41
+ from phoenix.utilities.json import jsonify
46
42
 
47
43
  DOCUMENT_METADATA = DocumentAttributes.DOCUMENT_METADATA
48
44
  INPUT_MIME_TYPE = SpanAttributes.INPUT_MIME_TYPE
@@ -53,28 +49,45 @@ OUTPUT_MIME_TYPE = SpanAttributes.OUTPUT_MIME_TYPE
53
49
  OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE
54
50
  TOOL_PARAMETERS = SpanAttributes.TOOL_PARAMETERS
55
51
  LLM_PROMPT_TEMPLATE_VARIABLES = SpanAttributes.LLM_PROMPT_TEMPLATE_VARIABLES
52
+ LLM_TOKEN_COUNT_PROMPT = SpanAttributes.LLM_TOKEN_COUNT_PROMPT
53
+ LLM_TOKEN_COUNT_COMPLETION = SpanAttributes.LLM_TOKEN_COUNT_COMPLETION
54
+ LLM_TOKEN_COUNT_TOTAL = SpanAttributes.LLM_TOKEN_COUNT_TOTAL
56
55
 
57
56
 
58
- def decode(otlp_span: otlp.Span) -> Span:
57
+ def coerce_otlp_span_attributes(
58
+ decoded_attributes: Iterable[tuple[str, Any]],
59
+ ) -> Iterator[tuple[str, Any]]:
60
+ for key, value in decoded_attributes:
61
+ if key in (LLM_TOKEN_COUNT_PROMPT, LLM_TOKEN_COUNT_COMPLETION, LLM_TOKEN_COUNT_TOTAL):
62
+ try:
63
+ value = int(value)
64
+ except BaseException:
65
+ pass
66
+ yield key, value
67
+
68
+
69
+ def decode_otlp_span(otlp_span: otlp.Span) -> Span:
59
70
  trace_id = cast(TraceID, _decode_identifier(otlp_span.trace_id))
60
71
  span_id = cast(SpanID, _decode_identifier(otlp_span.span_id))
61
72
  parent_id = _decode_identifier(otlp_span.parent_span_id)
62
73
 
63
74
  start_time = _decode_unix_nano(otlp_span.start_time_unix_nano)
64
- end_time = (
65
- _decode_unix_nano(otlp_span.end_time_unix_nano) if otlp_span.end_time_unix_nano else None
66
- )
75
+ end_time = _decode_unix_nano(otlp_span.end_time_unix_nano)
67
76
 
68
- attributes = dict(_unflatten(_load_json_strings(_decode_key_values(otlp_span.attributes))))
69
- span_kind = SpanKind(attributes.pop(OPENINFERENCE_SPAN_KIND, None))
70
-
71
- for mime_type in (INPUT_MIME_TYPE, OUTPUT_MIME_TYPE):
72
- if mime_type in attributes:
73
- attributes[mime_type] = MimeType(attributes[mime_type])
77
+ attributes = unflatten(
78
+ load_json_strings(coerce_otlp_span_attributes(_decode_key_values(otlp_span.attributes)))
79
+ )
80
+ span_kind = SpanKind(get_attribute_value(attributes, OPENINFERENCE_SPAN_KIND))
74
81
 
75
82
  status_code, status_message = _decode_status(otlp_span.status)
76
83
  events = [_decode_event(event) for event in otlp_span.events]
77
84
 
85
+ if (input_value := get_attribute_value(attributes, INPUT_VALUE)) and not isinstance(
86
+ input_value, str
87
+ ):
88
+ attributes["input"]["value"] = json.dumps(input_value)
89
+ attributes["input"]["mime_type"] = OpenInferenceMimeTypeValues.JSON.value
90
+
78
91
  return Span(
79
92
  name=otlp_span.name,
80
93
  context=SpanContext(
@@ -127,7 +140,7 @@ def _decode_unix_nano(time_unix_nano: int) -> datetime:
127
140
 
128
141
  def _decode_key_values(
129
142
  key_values: Iterable[KeyValue],
130
- ) -> Iterator[Tuple[str, Any]]:
143
+ ) -> Iterator[tuple[str, Any]]:
131
144
  return ((kv.key, _decode_value(kv.value)) for kv in key_values)
132
145
 
133
146
 
@@ -152,28 +165,6 @@ def _decode_value(any_value: AnyValue) -> Any:
152
165
  assert_never(which)
153
166
 
154
167
 
155
- _JSON_STRING_ATTRIBUTES = (
156
- DOCUMENT_METADATA,
157
- LLM_PROMPT_TEMPLATE_VARIABLES,
158
- METADATA,
159
- TOOL_PARAMETERS,
160
- )
161
-
162
-
163
- def _load_json_strings(key_values: Iterable[Tuple[str, Any]]) -> Iterator[Tuple[str, Any]]:
164
- for key, value in key_values:
165
- if key.endswith(_JSON_STRING_ATTRIBUTES):
166
- try:
167
- dict_value = json.loads(value)
168
- except Exception:
169
- yield key, value
170
- else:
171
- if dict_value:
172
- yield key, dict_value
173
- else:
174
- yield key, value
175
-
176
-
177
168
  StatusMessage: TypeAlias = str
178
169
 
179
170
  _STATUS_DECODING = MappingProxyType(
@@ -185,129 +176,15 @@ _STATUS_DECODING = MappingProxyType(
185
176
  )
186
177
 
187
178
 
188
- def _decode_status(otlp_status: otlp.Status) -> Tuple[SpanStatusCode, StatusMessage]:
179
+ def _decode_status(otlp_status: otlp.Status) -> tuple[SpanStatusCode, StatusMessage]:
189
180
  status_code = _STATUS_DECODING.get(otlp_status.code, SpanStatusCode.UNSET)
190
181
  return status_code, otlp_status.message
191
182
 
192
183
 
193
- _SEMANTIC_CONVENTIONS: List[str] = sorted(
194
- (
195
- getattr(klass, attr)
196
- for name in dir(trace)
197
- if name.endswith("Attributes") and inspect.isclass(klass := getattr(trace, name))
198
- for attr in dir(klass)
199
- if attr.isupper()
200
- ),
201
- reverse=True,
202
- ) # sorted so the longer strings go first
203
-
204
-
205
- def _semantic_convention_prefix_partition(key: str, separator: str = ".") -> Tuple[str, str, str]:
206
- """Return the longest prefix of `key` that is a semantic convention, and the remaining suffix
207
- separated by `.`. For example, if `key` is "retrieval.documents.2.document.score", return
208
- ("retrieval.documents", ".", "2.document.score"). The return signature is based on Python's
209
- `.partition` method for strings.
210
- """
211
- for prefix in _SEMANTIC_CONVENTIONS:
212
- if key == prefix:
213
- return key, "", ""
214
- if key.startswith(prefix) and key[len(prefix) :].startswith(separator):
215
- return prefix, separator, key[len(prefix) + len(separator) :]
216
- return "", "", ""
217
-
218
-
219
- class _Trie(DefaultDict[Union[str, int], "_Trie"]):
220
- """Prefix Tree with special handling for indices (i.e. all-digit keys)."""
221
-
222
- def __init__(self) -> None:
223
- super().__init__(_Trie)
224
- self.value: Any = None
225
- self.indices: Set[int] = set()
226
- self.branches: Set[Union[str, int]] = set()
227
-
228
- def set_value(self, value: Any) -> None:
229
- self.value = value
230
- # value and indices must not coexist
231
- self.branches.update(self.indices)
232
- self.indices.clear()
233
-
234
- def add_index(self, index: int) -> "_Trie":
235
- if self.value is not None:
236
- self.branches.add(index)
237
- elif index not in self.branches:
238
- self.indices.add(index)
239
- return self[index]
240
-
241
- def add_branch(self, branch: Union[str, int]) -> "_Trie":
242
- if branch in self.indices:
243
- self.indices.discard(cast(int, branch))
244
- self.branches.add(branch)
245
- return self[branch]
246
-
247
-
248
- # FIXME: Ideally we should not need something so complicated as a Trie, but it's useful here
249
- # for backward compatibility reasons regarding some deeply nested objects such as TOOL_PARAMETERS.
250
- # In the future, we should `json_dumps` them and not let things get too deeply nested.
251
- def _build_trie(
252
- key_value_pairs: Iterable[Tuple[str, Any]],
253
- separator: str = ".",
254
- ) -> _Trie:
255
- """Build a Trie (a.k.a. prefix tree) from `key_value_pairs`, by partitioning the keys by
256
- separator. Each partition is a branch in the Trie. Special handling is done for partitions
257
- that are all digits, e.g. "0", "12", etc., which are converted to integers and collected
258
- as indices.
259
- """
260
- trie = _Trie()
261
- for key, value in key_value_pairs:
262
- if value is None:
263
- continue
264
- t = trie
265
- while True:
266
- prefix, _, suffix = _semantic_convention_prefix_partition(key, separator)
267
- if prefix:
268
- t = t.add_branch(prefix)
269
- else:
270
- prefix, _, suffix = key.partition(separator)
271
- if prefix.isdigit():
272
- index = int(prefix)
273
- t = t.add_index(index) if suffix else t.add_branch(index)
274
- else:
275
- t = t.add_branch(prefix)
276
- if not suffix:
277
- break
278
- key = suffix
279
- t.set_value(value)
280
- return trie
281
-
282
-
283
- def _walk(trie: _Trie, prefix: str = "") -> Iterator[Tuple[str, Any]]:
284
- if trie.value is not None:
285
- yield prefix, trie.value
286
- elif prefix and trie.indices:
287
- yield prefix, [dict(_walk(trie[index])) for index in sorted(trie.indices)]
288
- elif trie.indices:
289
- for index in trie.indices:
290
- yield from _walk(trie[index], prefix=f"{index}")
291
- elif prefix:
292
- yield prefix, dict(_walk(trie))
293
- return
294
- for branch in trie.branches:
295
- new_prefix = f"{prefix}.{branch}" if prefix else f"{branch}"
296
- yield from _walk(trie[branch], new_prefix)
297
-
298
-
299
- def _unflatten(
300
- key_value_pairs: Iterable[Tuple[str, Any]],
301
- separator: str = ".",
302
- ) -> Iterator[Tuple[str, Any]]:
303
- trie = _build_trie(key_value_pairs, separator)
304
- yield from _walk(trie)
305
-
306
-
307
184
  _BILLION = 1_000_000_000 # for converting seconds to nanoseconds
308
185
 
309
186
 
310
- def encode(span: Span) -> otlp.Span:
187
+ def encode_span_to_otlp(span: Span) -> otlp.Span:
311
188
  trace_id: bytes = _encode_identifier(span.context.trace_id)
312
189
  span_id: bytes = _encode_identifier(span.context.span_id)
313
190
  parent_span_id: bytes = _encode_identifier(span.parent_id)
@@ -316,11 +193,7 @@ def encode(span: Span) -> otlp.Span:
316
193
  start_time_unix_nano: int = int(span.start_time.timestamp() * _BILLION)
317
194
  end_time_unix_nano: int = int(span.end_time.timestamp() * _BILLION) if span.end_time else 0
318
195
 
319
- attributes: Dict[str, Any] = span.attributes.copy()
320
-
321
- for mime_type in (INPUT_MIME_TYPE, OUTPUT_MIME_TYPE):
322
- if mime_type in attributes:
323
- attributes[mime_type] = attributes[mime_type].value
196
+ attributes: dict[str, Any] = dict(span.attributes)
324
197
 
325
198
  for key, value in span.attributes.items():
326
199
  if value is None:
@@ -328,19 +201,34 @@ def encode(span: Span) -> otlp.Span:
328
201
  attributes.pop(key, None)
329
202
  elif isinstance(value, Mapping):
330
203
  attributes.pop(key, None)
331
- if key.endswith(_JSON_STRING_ATTRIBUTES):
332
- attributes[key] = json.dumps(value)
204
+ if key.endswith(JSON_STRING_ATTRIBUTES):
205
+ attributes[key] = json.dumps(jsonify(value))
333
206
  else:
334
- attributes.update(_flatten_mapping(value, key))
207
+ attributes.update(
208
+ flatten(
209
+ value,
210
+ prefix=key,
211
+ recurse_on_sequence=True,
212
+ json_string_attributes=JSON_STRING_ATTRIBUTES,
213
+ )
214
+ )
335
215
  elif (
336
216
  not isinstance(value, str)
337
217
  and (isinstance(value, Sequence) or isinstance(value, np.ndarray))
338
- and _has_mapping(value)
218
+ and has_mapping(value)
339
219
  ):
340
220
  attributes.pop(key, None)
341
- attributes.update(_flatten_sequence(value, key))
342
-
343
- attributes[OPENINFERENCE_SPAN_KIND] = span.span_kind.value
221
+ attributes.update(
222
+ flatten(
223
+ value,
224
+ prefix=key,
225
+ recurse_on_sequence=True,
226
+ json_string_attributes=JSON_STRING_ATTRIBUTES,
227
+ )
228
+ )
229
+
230
+ if OPENINFERENCE_SPAN_KIND not in attributes:
231
+ attributes[OPENINFERENCE_SPAN_KIND] = span.span_kind.value
344
232
 
345
233
  status = _encode_status(span.status_code, span.status_message)
346
234
  events = map(_encode_event, span.events)
@@ -381,42 +269,6 @@ def _encode_identifier(identifier: Optional[str]) -> bytes:
381
269
  return unhexlify(identifier)
382
270
 
383
271
 
384
- def _has_mapping(sequence: Sequence[Any]) -> bool:
385
- for item in sequence:
386
- if isinstance(item, Mapping):
387
- return True
388
- return False
389
-
390
-
391
- def _flatten_mapping(
392
- mapping: Mapping[str, Any],
393
- prefix: str,
394
- ) -> Iterator[Tuple[str, Any]]:
395
- for key, value in mapping.items():
396
- prefixed_key = f"{prefix}.{key}"
397
- if isinstance(value, Mapping):
398
- if key.endswith(_JSON_STRING_ATTRIBUTES):
399
- yield prefixed_key, json.dumps(value)
400
- else:
401
- yield from _flatten_mapping(value, prefixed_key)
402
- elif isinstance(value, Sequence):
403
- yield from _flatten_sequence(value, prefixed_key)
404
- elif value is not None:
405
- yield prefixed_key, value
406
-
407
-
408
- def _flatten_sequence(
409
- sequence: Sequence[Any],
410
- prefix: str,
411
- ) -> Iterator[Tuple[str, Any]]:
412
- if isinstance(sequence, str) or not _has_mapping(sequence):
413
- yield prefix, sequence
414
- for idx, obj in enumerate(sequence):
415
- if not isinstance(obj, Mapping):
416
- continue
417
- yield from _flatten_mapping(obj, f"{prefix}.{idx}")
418
-
419
-
420
272
  def _encode_event(event: SpanEvent) -> otlp.Span.Event:
421
273
  return otlp.Span.Event(
422
274
  name=event.name,
@@ -451,6 +303,6 @@ def _encode_value(value: AttributeValue) -> AnyValue:
451
303
 
452
304
 
453
305
  __all__ = [
454
- "encode",
455
- "decode",
306
+ "encode_span_to_otlp",
307
+ "decode_otlp_span",
456
308
  ]
phoenix/trace/projects.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import types
2
- from typing import Any, Callable, Optional, Type
2
+ from collections.abc import Callable
3
+ from typing import Any, Optional
3
4
 
4
5
  from openinference.semconv.resource import ResourceAttributes
5
6
  from opentelemetry.sdk import trace
@@ -58,7 +59,7 @@ class using_project:
58
59
 
59
60
  def __exit__(
60
61
  self,
61
- exc_type: Optional[Type[BaseException]],
62
+ exc_type: Optional[type[BaseException]],
62
63
  exc_value: Optional[BaseException],
63
64
  traceback: Optional[types.TracebackType],
64
65
  ) -> None:
phoenix/trace/schemas.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import datetime
3
3
  from enum import Enum
4
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import Any, Mapping, NamedTuple, Optional
5
5
  from uuid import UUID
6
6
 
7
7
  EXCEPTION_TYPE = "exception.type"
@@ -29,8 +29,6 @@ class SpanKind(Enum):
29
29
  """
30
30
  SpanKind is loosely inspired by OpenTelemetry's SpanKind
31
31
  It captures the type of work that a Span encapsulates.
32
-
33
- NB: this is actively under construction
34
32
  """
35
33
 
36
34
  TOOL = "TOOL"
@@ -40,6 +38,8 @@ class SpanKind(Enum):
40
38
  EMBEDDING = "EMBEDDING"
41
39
  AGENT = "AGENT"
42
40
  RERANKER = "RERANKER"
41
+ EVALUATOR = "EVALUATOR"
42
+ GUARDRAIL = "GUARDRAIL"
43
43
  UNKNOWN = "UNKNOWN"
44
44
 
45
45
  def __str__(self) -> str:
@@ -47,16 +47,14 @@ class SpanKind(Enum):
47
47
 
48
48
  @classmethod
49
49
  def _missing_(cls, v: Any) -> Optional["SpanKind"]:
50
- if v and isinstance(v, str) and not v.isupper():
50
+ if v and isinstance(v, str) and v.isascii() and not v.isupper():
51
51
  return cls(v.upper())
52
- return None if v else cls.UNKNOWN
52
+ return cls.UNKNOWN
53
53
 
54
54
 
55
55
  TraceID = str
56
56
  SpanID = str
57
- AttributePrimitiveValue = Union[str, bool, float, int]
58
- AttributeValue = Union[AttributePrimitiveValue, List[AttributePrimitiveValue]]
59
- SpanAttributes = Dict[str, AttributeValue]
57
+ SpanAttributes = Mapping[str, Any]
60
58
 
61
59
 
62
60
  @dataclass(frozen=True)
@@ -73,7 +71,7 @@ class SpanConversationAttributes:
73
71
 
74
72
 
75
73
  @dataclass(frozen=True)
76
- class SpanEvent(Dict[str, Any]):
74
+ class SpanEvent:
77
75
  """
78
76
  A Span Event can be thought of as a structured log message (or annotation)
79
77
  on a Span, typically used to denote a meaningful, singular point in time
@@ -142,7 +140,7 @@ class Span:
142
140
  "If the parent_id is None, this is the root span"
143
141
  parent_id: Optional[SpanID]
144
142
  start_time: datetime
145
- end_time: Optional[datetime]
143
+ end_time: datetime
146
144
  status_code: SpanStatusCode
147
145
  status_message: str
148
146
  """
@@ -171,7 +169,7 @@ class Span:
171
169
  OpenTelemetry Inspiration:
172
170
  https://opentelemetry.io/docs/concepts/signals/traces/#span-events
173
171
  """
174
- events: List[SpanEvent]
172
+ events: list[SpanEvent]
175
173
 
176
174
  """
177
175
  An extension of the OpenTelemetry Span interface to include the
@@ -189,6 +187,22 @@ class MimeType(Enum):
189
187
  return None if v else cls.TEXT
190
188
 
191
189
 
190
+ @dataclass(frozen=True)
191
+ class SpanIOValue:
192
+ value: str
193
+ mime_type: MimeType = MimeType.TEXT
194
+
195
+
196
+ @dataclass(frozen=True)
197
+ class TokenUsage:
198
+ prompt: int = 0
199
+ completion: int = 0
200
+
201
+ def __post_init__(self) -> None:
202
+ assert self.prompt >= 0, "prompt must be non-negative"
203
+ assert self.completion >= 0, "completion must be non-negative"
204
+
205
+
192
206
  ATTRIBUTE_PREFIX = "attributes."
193
207
  CONTEXT_PREFIX = "context."
194
208
  COMPUTED_PREFIX = "__computed__."
@@ -202,3 +216,11 @@ class ComputedAttributes(Enum):
202
216
  CUMULATIVE_LLM_TOKEN_COUNT_COMPLETION = "cumulative_token_count.completion"
203
217
  ERROR_COUNT = "error_count"
204
218
  CUMULATIVE_ERROR_COUNT = "cumulative_error_count"
219
+
220
+
221
+ class ComputedValues(NamedTuple):
222
+ latency_ms: float
223
+ cumulative_error_count: int
224
+ cumulative_llm_token_count_prompt: int
225
+ cumulative_llm_token_count_completion: int
226
+ cumulative_llm_token_count_total: int
@@ -1,17 +1,19 @@
1
1
  import json
2
2
  from abc import ABC
3
+ from collections.abc import Callable, Mapping, Sequence
3
4
  from dataclasses import dataclass, field
4
5
  from itertools import product
5
6
  from pathlib import Path
6
7
  from types import MappingProxyType
7
- from typing import Any, Callable, List, Mapping, Optional, Sequence, Set, Tuple, Type, Union
8
+ from typing import Any, Optional, Union
8
9
  from uuid import UUID, uuid4
9
10
 
10
11
  import pandas as pd
11
12
  from pandas.api.types import is_integer_dtype, is_numeric_dtype, is_string_dtype
12
13
  from pyarrow import RecordBatchStreamReader, Schema, Table, parquet
13
14
 
14
- from phoenix.config import TRACE_DATASET_DIR
15
+ from phoenix.config import TRACE_DATASETS_DIR
16
+ from phoenix.exceptions import PhoenixEvaluationNameIsMissing
15
17
  from phoenix.trace.errors import InvalidParquetMetadataError
16
18
 
17
19
  EVAL_NAME_COLUMN_PREFIX = "eval."
@@ -19,11 +21,11 @@ EVAL_PARQUET_FILE_NAME = "evaluations-{id}.parquet"
19
21
 
20
22
 
21
23
  class NeedsNamedIndex(ABC):
22
- index_names: Mapping[Tuple[str, ...], Callable[[Any], bool]]
23
- all_valid_index_name_sorted_combos: Set[Tuple[str, ...]]
24
+ index_names: Mapping[tuple[str, ...], Callable[[Any], bool]]
25
+ all_valid_index_name_sorted_combos: set[tuple[str, ...]]
24
26
 
25
27
  @classmethod
26
- def preferred_names(cls) -> List[str]:
28
+ def preferred_names(cls) -> list[str]:
27
29
  return [choices[0] for choices in cls.index_names.keys()]
28
30
 
29
31
  @classmethod
@@ -42,7 +44,7 @@ class NeedsNamedIndex(ABC):
42
44
  )
43
45
 
44
46
  @classmethod
45
- def find_valid_index_names(cls, dtypes: "pd.Series[Any]") -> Optional[List[str]]:
47
+ def find_valid_index_names(cls, dtypes: "pd.Series[Any]") -> Optional[list[str]]:
46
48
  valid_names = []
47
49
  for names, check_type in cls.index_names.items():
48
50
  for name in names:
@@ -64,14 +66,15 @@ class NeedsResultColumns(ABC):
64
66
  )
65
67
 
66
68
  @classmethod
67
- def is_valid_result_columns(cls, dtypes: "pd.Series[Any]") -> bool:
69
+ def is_valid_result_columns(cls, df: pd.DataFrame) -> bool:
70
+ dtypes = df.dtypes
68
71
  names = cls.result_column_names.keys()
69
72
  intersection = dtypes.index.intersection(names) # type: ignore
70
73
  if not len(intersection):
71
74
  return False
72
75
  for name in intersection:
73
76
  check_type = cls.result_column_names[name]
74
- if not check_type(dtypes[name]):
77
+ if not check_type(dtypes[name]) and not df.loc[:, name].isna().all():
75
78
  return False
76
79
  return True
77
80
 
@@ -91,7 +94,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
91
94
  f"dataframe=<rows: {len(self.dataframe)!r}>)"
92
95
  )
93
96
 
94
- def __dir__(self) -> List[str]:
97
+ def __dir__(self) -> list[str]:
95
98
  return ["get_dataframe"]
96
99
 
97
100
  def get_dataframe(self, prefix_columns_with_name: bool = True) -> pd.DataFrame:
@@ -136,7 +139,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
136
139
  )
137
140
 
138
141
  # Validate that the dataframe contains result columns of appropriate types.
139
- if not self.is_valid_result_columns(dataframe.dtypes):
142
+ if not self.is_valid_result_columns(dataframe):
140
143
  raise ValueError(
141
144
  f"The dataframe must contain one of these columns with appropriate "
142
145
  f"value types: {self.result_column_names.keys()} "
@@ -152,7 +155,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
152
155
 
153
156
  def __init_subclass__(
154
157
  cls,
155
- index_names: Mapping[Tuple[str, ...], Callable[[Any], bool]],
158
+ index_names: Mapping[tuple[str, ...], Callable[[Any], bool]],
156
159
  **kwargs: Any,
157
160
  ) -> None:
158
161
  super().__init_subclass__(**kwargs)
@@ -200,7 +203,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
200
203
  UUID: The ID of the evaluations, which can be used as a key to load
201
204
  the evaluations from disk using `load`.
202
205
  """
203
- directory = Path(directory) if directory else TRACE_DATASET_DIR
206
+ directory = Path(directory) if directory else TRACE_DATASETS_DIR
204
207
  path = directory / EVAL_PARQUET_FILE_NAME.format(id=self.id)
205
208
  table = self.to_pyarrow_table()
206
209
  parquet.write_table(table, path)
@@ -228,7 +231,7 @@ class Evaluations(NeedsNamedIndex, NeedsResultColumns, ABC):
228
231
  """
229
232
  if not isinstance(id, UUID):
230
233
  id = UUID(id)
231
- path = Path(directory or TRACE_DATASET_DIR) / EVAL_PARQUET_FILE_NAME.format(id=id)
234
+ path = Path(directory or TRACE_DATASETS_DIR) / EVAL_PARQUET_FILE_NAME.format(id=id)
232
235
  schema = parquet.read_schema(path)
233
236
  eval_id, eval_name, evaluations_cls = _parse_schema_metadata(schema)
234
237
  if id != eval_id:
@@ -326,7 +329,7 @@ class TraceEvaluations(
326
329
  ): ...
327
330
 
328
331
 
329
- def _parse_schema_metadata(schema: Schema) -> Tuple[UUID, str, Type[Evaluations]]:
332
+ def _parse_schema_metadata(schema: Schema) -> tuple[UUID, str, type[Evaluations]]:
330
333
  """
331
334
  Validates and parses the pyarrow schema metadata.
332
335
  """
@@ -335,8 +338,10 @@ def _parse_schema_metadata(schema: Schema) -> Tuple[UUID, str, Type[Evaluations]
335
338
  arize_metadata = json.loads(metadata[b"arize"])
336
339
  eval_classes = {subclass.__name__: subclass for subclass in Evaluations.__subclasses__()}
337
340
  eval_id = UUID(arize_metadata["eval_id"])
338
- if not isinstance((eval_name := arize_metadata["eval_name"]), str):
339
- raise ValueError('Arize metadata must contain a string value for key "eval_name"')
341
+ if not isinstance((eval_name := arize_metadata["eval_name"]), str) or not eval_name.strip():
342
+ raise PhoenixEvaluationNameIsMissing(
343
+ 'Arize metadata must contain a non-empty string value for key "eval_name"'
344
+ )
340
345
  evaluations_cls = eval_classes[arize_metadata["eval_type"]]
341
346
  return eval_id, eval_name, evaluations_cls
342
347
  except Exception as err:
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from datetime import datetime
3
- from typing import Any, Dict, Optional
3
+ from typing import Any, Optional
4
4
 
5
5
  from openinference.semconv.trace import SpanAttributes
6
6
 
@@ -22,7 +22,7 @@ INPUT_MIME_TYPE = SpanAttributes.INPUT_MIME_TYPE
22
22
  OUTPUT_MIME_TYPE = SpanAttributes.OUTPUT_MIME_TYPE
23
23
 
24
24
 
25
- def json_to_attributes(obj: Optional[Dict[str, Any]]) -> Dict[str, Any]:
25
+ def json_to_attributes(obj: Optional[dict[str, Any]]) -> dict[str, Any]:
26
26
  if obj is None:
27
27
  return {}
28
28
  if not isinstance(obj, dict):
@@ -34,7 +34,7 @@ def json_to_attributes(obj: Optional[Dict[str, Any]]) -> Dict[str, Any]:
34
34
  return obj
35
35
 
36
36
 
37
- def json_to_span(data: Dict[str, Any]) -> Any:
37
+ def json_to_span(data: dict[str, Any]) -> Any:
38
38
  """
39
39
  A hook for json.loads to convert a dict to a Span object.
40
40
  """
@@ -80,7 +80,9 @@ def json_to_span(data: Dict[str, Any]) -> Any:
80
80
  attributes=event.get("attributes") or {},
81
81
  timestamp=datetime.fromisoformat(event["timestamp"]),
82
82
  )
83
- for event in data["events"]
83
+ for event in (
84
+ json.loads(data["events"]) if isinstance(data["events"], str) else data["events"]
85
+ )
84
86
  ]
85
87
  data["conversation"] = (
86
88
  SpanConversationAttributes(**data["conversation"])