arize-phoenix 3.16.1__py3-none-any.whl → 7.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 (338) hide show
  1. arize_phoenix-7.7.0.dist-info/METADATA +261 -0
  2. arize_phoenix-7.7.0.dist-info/RECORD +345 -0
  3. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.dist-info}/WHEEL +1 -1
  4. arize_phoenix-7.7.0.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.0.dist-info}/licenses/IP_NOTICE +0 -0
  335. {arize_phoenix-3.16.1.dist-info → arize_phoenix-7.7.0.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
@@ -2,7 +2,7 @@ import json
2
2
  from dataclasses import asdict
3
3
  from datetime import datetime
4
4
  from enum import Enum
5
- from typing import Any, List
5
+ from typing import Any
6
6
  from uuid import UUID
7
7
 
8
8
  import numpy as np
@@ -60,5 +60,5 @@ def span_to_json(span: Span) -> str:
60
60
  return json.dumps(span, cls=SpanJSONEncoder)
61
61
 
62
62
 
63
- def spans_to_jsonl(spans: List[Span]) -> str:
63
+ def spans_to_jsonl(spans: list[Span]) -> str:
64
64
  return "\n".join(span_to_json(span) for span in spans)
@@ -1,10 +1,12 @@
1
1
  import json
2
+ from collections.abc import Iterable, Iterator
2
3
  from datetime import datetime
3
4
  from pathlib import Path
4
- from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union, cast
5
+ from typing import Any, Optional, Union, cast
5
6
  from uuid import UUID, uuid4
6
7
  from warnings import warn
7
8
 
9
+ import numpy as np
8
10
  import pandas as pd
9
11
  from openinference.semconv.trace import (
10
12
  DocumentAttributes,
@@ -14,8 +16,9 @@ from openinference.semconv.trace import (
14
16
  from pandas import DataFrame, read_parquet
15
17
  from pyarrow import Schema, Table, parquet
16
18
 
17
- from phoenix.config import DATASET_DIR, GENERATED_DATASET_NAME_PREFIX, TRACE_DATASET_DIR
19
+ from phoenix.config import GENERATED_INFERENCES_NAME_PREFIX, INFERENCES_DIR, TRACE_DATASETS_DIR
18
20
  from phoenix.datetime_utils import normalize_timestamps
21
+ from phoenix.trace.attributes import flatten, unflatten
19
22
  from phoenix.trace.errors import InvalidParquetMetadataError
20
23
  from phoenix.trace.schemas import ATTRIBUTE_PREFIX, CONTEXT_PREFIX, Span
21
24
  from phoenix.trace.span_evaluations import Evaluations, SpanEvaluations
@@ -59,6 +62,7 @@ def normalize_dataframe(dataframe: DataFrame) -> "DataFrame":
59
62
  # Convert the start and end times to datetime
60
63
  dataframe["start_time"] = normalize_timestamps(dataframe["start_time"])
61
64
  dataframe["end_time"] = normalize_timestamps(dataframe["end_time"])
65
+ dataframe = dataframe.replace({np.nan: None})
62
66
  return dataframe
63
67
 
64
68
 
@@ -98,6 +102,14 @@ class TraceDataset:
98
102
  """
99
103
  A TraceDataset is a wrapper around a dataframe which is a flattened representation
100
104
  of Spans. The collection of spans trace the LLM application's execution.
105
+
106
+ Typical usage example::
107
+
108
+ from phoenix.trace.utils import json_lines_to_df
109
+
110
+ with open("trace.jsonl", "r") as f:
111
+ trace_ds = TraceDataset(json_lines_to_df(f.readlines()))
112
+ px.launch_app(trace=trace_ds)
101
113
  """
102
114
 
103
115
  name: str
@@ -105,7 +117,7 @@ class TraceDataset:
105
117
  A human readable name for the dataset.
106
118
  """
107
119
  dataframe: pd.DataFrame
108
- evaluations: List[Evaluations] = []
120
+ evaluations: list[Evaluations] = []
109
121
  _id: UUID
110
122
  _data_file_name: str = "data.parquet"
111
123
 
@@ -119,15 +131,15 @@ class TraceDataset:
119
131
  Constructs a TraceDataset from a dataframe of spans. Optionally takes in
120
132
  evaluations for the spans in the dataset.
121
133
 
122
- Parameters
123
- __________
124
- dataframe: pandas.DataFrame
125
- the pandas dataframe containing the tracing data. Each row
126
- represents a span.
127
- evaluations: Optional[Iterable[SpanEvaluations]]
128
- an optional list of evaluations for the spans in the dataset. If
129
- provided, the evaluations can be materialized into a unified
130
- dataframe as annotations.
134
+ Args:
135
+ dataframe (pandas.DataFrame): The pandas dataframe containing the
136
+ tracing data. Each row of which is a flattened representation
137
+ of a span.
138
+ name (str): The name used to identify the dataset in the application.
139
+ If not provided, a random name will be generated.
140
+ evaluations (Optional[Iterable[SpanEvaluations]]): An optional list of
141
+ evaluations for the spans in the dataset. If provided, the evaluations
142
+ can be materialized into a unified dataframe as annotations.
131
143
  """
132
144
  # Validate the the dataframe has required fields
133
145
  if missing_columns := set(REQUIRED_COLUMNS) - set(dataframe.columns):
@@ -137,15 +149,15 @@ class TraceDataset:
137
149
  self._id = uuid4()
138
150
  self.dataframe = normalize_dataframe(dataframe)
139
151
  # TODO: This is not used in any meaningful way. Should remove
140
- self.name = name or f"{GENERATED_DATASET_NAME_PREFIX}{str(self._id)}"
152
+ self.name = name or f"{GENERATED_INFERENCES_NAME_PREFIX}{str(self._id)}"
141
153
  self.evaluations = list(evaluations)
142
154
 
143
155
  @classmethod
144
- def from_spans(cls, spans: List[Span]) -> "TraceDataset":
156
+ def from_spans(cls, spans: list[Span]) -> "TraceDataset":
145
157
  """Creates a TraceDataset from a list of spans.
146
158
 
147
159
  Args:
148
- spans (List[Span]): A list of spans.
160
+ spans (list[Span]): A list of spans.
149
161
 
150
162
  Returns:
151
163
  TraceDataset: A TraceDataset containing the spans.
@@ -161,13 +173,16 @@ class TraceDataset:
161
173
  for _, row in self.dataframe.iterrows():
162
174
  is_attribute = row.index.str.startswith(ATTRIBUTE_PREFIX)
163
175
  attribute_keys = row.index[is_attribute]
164
- attributes = (
165
- row.loc[is_attribute]
166
- .rename(
167
- {key: key[len(ATTRIBUTE_PREFIX) :] for key in attribute_keys},
176
+ attributes = unflatten(
177
+ flatten(
178
+ row.loc[is_attribute]
179
+ .rename(
180
+ {key: key[len(ATTRIBUTE_PREFIX) :] for key in attribute_keys},
181
+ )
182
+ .dropna()
183
+ .to_dict(),
184
+ recurse_on_sequence=True,
168
185
  )
169
- .dropna()
170
- .to_dict()
171
186
  )
172
187
  is_context = row.index.str.startswith(CONTEXT_PREFIX)
173
188
  context_keys = row.index[is_context]
@@ -200,13 +215,13 @@ class TraceDataset:
200
215
  @classmethod
201
216
  def from_name(cls, name: str) -> "TraceDataset":
202
217
  """Retrieves a dataset by name from the file system"""
203
- directory = DATASET_DIR / name
218
+ directory = INFERENCES_DIR / name
204
219
  df = read_parquet(directory / cls._data_file_name)
205
220
  return cls(df, name)
206
221
 
207
222
  def to_disc(self) -> None:
208
223
  """writes the data to disc"""
209
- directory = DATASET_DIR / self.name
224
+ directory = INFERENCES_DIR / self.name
210
225
  directory.mkdir(parents=True, exist_ok=True)
211
226
  get_serializable_spans_dataframe(self.dataframe).to_parquet(
212
227
  directory / self._data_file_name,
@@ -222,14 +237,14 @@ class TraceDataset:
222
237
 
223
238
  Args:
224
239
  directory (Optional[Union[str, Path]], optional): An optional path
225
- to a directory where the data will be written. If not provided, the
226
- data will be written to a default location.
240
+ to a directory where the data will be written. If not provided, the
241
+ data will be written to a default location.
227
242
 
228
243
  Returns:
229
244
  UUID: The id of the trace dataset, which can be used as key to load
230
- the dataset from disk using `load`.
245
+ the dataset from disk using `load`.
231
246
  """
232
- directory = Path(directory or TRACE_DATASET_DIR)
247
+ directory = Path(directory or TRACE_DATASETS_DIR)
233
248
  for evals in self.evaluations:
234
249
  evals.save(directory)
235
250
  path = directory / TRACE_DATASET_PARQUET_FILE_NAME.format(id=self._id)
@@ -270,16 +285,16 @@ class TraceDataset:
270
285
  id (Union[str, UUID]): The ID of the trace dataset to be loaded.
271
286
 
272
287
  directory (Optional[Union[str, Path]], optional): The path to the
273
- directory containing the persisted trace dataset parquet file. If
274
- not provided, the parquet file will be loaded from the same default
275
- location used by `save`.
288
+ directory containing the persisted trace dataset parquet file. If
289
+ not provided, the parquet file will be loaded from the same default
290
+ location used by `save`.
276
291
 
277
292
  Returns:
278
293
  TraceDataset: The loaded trace dataset.
279
294
  """
280
295
  if not isinstance(id, UUID):
281
296
  id = UUID(id)
282
- path = Path(directory or TRACE_DATASET_DIR) / TRACE_DATASET_PARQUET_FILE_NAME.format(id=id)
297
+ path = Path(directory or TRACE_DATASETS_DIR) / TRACE_DATASET_PARQUET_FILE_NAME.format(id=id)
283
298
  schema = parquet.read_schema(path)
284
299
  dataset_id, dataset_name, eval_ids = _parse_schema_metadata(schema)
285
300
  if id != dataset_id:
@@ -335,7 +350,7 @@ class TraceDataset:
335
350
  return pd.concat([df, evals_df], axis=1)
336
351
 
337
352
 
338
- def _parse_schema_metadata(schema: Schema) -> Tuple[UUID, str, List[UUID]]:
353
+ def _parse_schema_metadata(schema: Schema) -> tuple[UUID, str, list[UUID]]:
339
354
  """
340
355
  Returns parsed metadata from a parquet schema or raises an exception if the
341
356
  metadata is invalid.
phoenix/trace/utils.py CHANGED
@@ -1,12 +1,29 @@
1
1
  import json
2
+ import os
2
3
  import re
3
4
  from traceback import format_exception
4
- from typing import List, Optional, Tuple, cast
5
+ from typing import Optional, cast
6
+ from urllib import request
5
7
 
6
8
  import pandas as pd
7
9
 
8
10
 
9
- def json_lines_to_df(lines: List[str]) -> pd.DataFrame:
11
+ def parse_file_extension(file_path: str) -> str:
12
+ return os.path.splitext(file_path)[-1]
13
+
14
+
15
+ def download_json_traces_fixture(
16
+ url: str,
17
+ ) -> list[str]:
18
+ """
19
+ Stores the traces fixture as list of jsons from the jsonl files in the phoenix bucket.
20
+ """
21
+
22
+ with request.urlopen(url) as f:
23
+ return cast(list[str], f.readlines())
24
+
25
+
26
+ def json_lines_to_df(lines: list[str]) -> pd.DataFrame:
10
27
  """
11
28
  Convert a list of JSON line strings to a Pandas DataFrame.
12
29
  """
@@ -39,9 +56,9 @@ def get_stacktrace(exception: BaseException) -> str:
39
56
  _VERSION_TRIPLET_REGEX = re.compile(r"(\d+)\.(\d+)\.(\d+)")
40
57
 
41
58
 
42
- def extract_version_triplet(version: str) -> Optional[Tuple[int, int, int]]:
59
+ def extract_version_triplet(version: str) -> Optional[tuple[int, int, int]]:
43
60
  return (
44
- cast(Tuple[int, int, int], tuple(map(int, match.groups())))
61
+ cast(tuple[int, int, int], tuple(map(int, match.groups())))
45
62
  if (match := _VERSION_TRIPLET_REGEX.search(version))
46
63
  else None
47
64
  )
@@ -1,26 +0,0 @@
1
- from datetime import datetime
2
- from typing import List, Optional
3
-
4
- import pandas as pd
5
-
6
- from phoenix.core.project import Project
7
- from phoenix.trace.dsl import SpanQuery
8
-
9
-
10
- def query_spans(
11
- project: Optional[Project],
12
- *queries: SpanQuery,
13
- start_time: Optional[datetime] = None,
14
- stop_time: Optional[datetime] = None,
15
- root_spans_only: Optional[bool] = None,
16
- ) -> List[pd.DataFrame]:
17
- if not queries or not project:
18
- return []
19
- spans = tuple(
20
- project.get_spans(
21
- start_time=start_time,
22
- stop_time=stop_time,
23
- root_spans_only=root_spans_only,
24
- )
25
- )
26
- return [query(spans) for query in queries]
@@ -0,0 +1,132 @@
1
+ import warnings
2
+ from typing import Any
3
+
4
+ import httpx
5
+
6
+ from phoenix.config import get_env_client_headers, get_env_phoenix_api_key
7
+
8
+ PHOENIX_SERVER_VERSION_HEADER = "x-phoenix-server-version"
9
+
10
+
11
+ class VersionedClient(httpx.Client):
12
+ """
13
+ A httpx.Client wrapper that warns if there is a server/client version mismatch.
14
+ """
15
+
16
+ def __init__(self, *args: Any, **kwargs: Any):
17
+ from phoenix.version import __version__ as phoenix_version
18
+
19
+ super().__init__(*args, **kwargs)
20
+
21
+ if env_headers := get_env_client_headers():
22
+ self.headers.update(env_headers)
23
+ if "authorization" not in [k.lower() for k in self.headers]:
24
+ if api_key := get_env_phoenix_api_key():
25
+ self.headers["Authorization"] = f"Bearer {api_key}"
26
+
27
+ self._client_phoenix_version = phoenix_version
28
+ self._warned_on_minor_version_mismatch = False
29
+
30
+ def _check_version(self, response: httpx.Response) -> None:
31
+ server_version = response.headers.get(PHOENIX_SERVER_VERSION_HEADER)
32
+
33
+ if server_version is None:
34
+ warnings.warn(
35
+ "The Phoenix server has an unknown version and may have compatibility issues."
36
+ )
37
+ return
38
+
39
+ try:
40
+ client_major, client_minor, client_patch = map(
41
+ int, self._client_phoenix_version.split(".")
42
+ )
43
+ server_major, server_minor, server_patch = map(int, server_version.split("."))
44
+ if abs(server_major - client_major) >= 1:
45
+ warnings.warn(
46
+ f"⚠️⚠️ The Phoenix server ({server_version}) and client "
47
+ f"({self._client_phoenix_version}) versions are severely mismatched. Upgrade "
48
+ " either the client or server to ensure API compatibility ⚠️⚠️"
49
+ )
50
+ elif (
51
+ abs(server_minor - client_minor) >= 1 and not self._warned_on_minor_version_mismatch
52
+ ):
53
+ self._warned_on_minor_version_mismatch = True
54
+ warnings.warn(
55
+ f"The Phoenix server ({server_version}) and client "
56
+ f"({self._client_phoenix_version}) versions are mismatched and may have "
57
+ "compatibility issues."
58
+ )
59
+ except ValueError:
60
+ # if either the client or server version includes a suffix e.g. "rc1", check for an
61
+ # exact version match of the version string
62
+ if self._client_phoenix_version != server_version:
63
+ warnings.warn(
64
+ f"The Phoenix server ({server_version}) and client "
65
+ f"({self._client_phoenix_version}) versions are mismatched and may have "
66
+ "compatibility issues."
67
+ )
68
+
69
+ def request(self, *args: Any, **kwargs: Any) -> httpx.Response:
70
+ response = super().request(*args, **kwargs)
71
+ self._check_version(response)
72
+ return response
73
+
74
+
75
+ class VersionedAsyncClient(httpx.AsyncClient):
76
+ """
77
+ A httpx.Client wrapper that warns if there is a server/client version mismatch.
78
+ """
79
+
80
+ def __init__(self, *args: Any, **kwargs: Any):
81
+ from phoenix.version import __version__ as phoenix_version
82
+
83
+ super().__init__(*args, **kwargs)
84
+
85
+ if env_headers := get_env_client_headers():
86
+ self.headers.update(env_headers)
87
+ if "authorization" not in [k.lower() for k in self.headers]:
88
+ if api_key := get_env_phoenix_api_key():
89
+ self.headers["Authorization"] = f"Bearer {api_key}"
90
+
91
+ self._client_phoenix_version = phoenix_version
92
+ self._warned_on_minor_version_mismatch = False
93
+
94
+ def _check_version(self, response: httpx.Response) -> None:
95
+ server_version = response.headers.get(PHOENIX_SERVER_VERSION_HEADER)
96
+
97
+ if server_version is None:
98
+ return
99
+
100
+ try:
101
+ client_major, client_minor, client_patch = map(
102
+ int, self._client_phoenix_version.split(".")
103
+ )
104
+ server_major, server_minor, server_patch = map(int, server_version.split("."))
105
+ if abs(server_major - client_major) >= 1:
106
+ warnings.warn(
107
+ f"⚠️⚠️ The Phoenix server ({server_version}) and client "
108
+ f"({self._client_phoenix_version}) versions are severely mismatched. Upgrade "
109
+ " either the client or server to ensure API compatibility ⚠️⚠️"
110
+ )
111
+ elif (
112
+ abs(server_minor - client_minor) >= 1 and not self._warned_on_minor_version_mismatch
113
+ ):
114
+ self._warned_on_minor_version_mismatch = True
115
+ warnings.warn(
116
+ f"The Phoenix server ({server_version}) and client "
117
+ f"({self._client_phoenix_version}) versions are mismatched and may have "
118
+ "compatibility issues."
119
+ )
120
+ except ValueError:
121
+ # if the version includes a suffix e.g. "rc1", check for an exact version match
122
+ if self._client_phoenix_version != server_version:
123
+ warnings.warn(
124
+ f"The Phoenix server ({server_version}) and client "
125
+ f"({self._client_phoenix_version}) versions are mismatched and may have "
126
+ "compatibility issues."
127
+ )
128
+
129
+ async def request(self, *args: Any, **kwargs: Any) -> httpx.Response:
130
+ response = await super().request(*args, **kwargs)
131
+ self._check_version(response)
132
+ return response
@@ -0,0 +1,31 @@
1
+ import functools
2
+ import warnings
3
+ from collections.abc import Callable
4
+ from typing import Any, TypeVar
5
+
6
+ GenericClass = TypeVar("GenericClass", bound=type[Any])
7
+ CallableType = TypeVar("CallableType", bound=Callable[..., Any])
8
+
9
+
10
+ def deprecated_class(message: str) -> Callable[[GenericClass], GenericClass]:
11
+ def decorator(original_class: GenericClass) -> GenericClass:
12
+ @functools.wraps(original_class)
13
+ def new_class(*args: Any, **kwargs: Any) -> Any:
14
+ warnings.warn(message, DeprecationWarning, stacklevel=2)
15
+ return original_class(*args, **kwargs)
16
+
17
+ return new_class # type: ignore
18
+
19
+ return decorator
20
+
21
+
22
+ def deprecated(message: str) -> Callable[[CallableType], CallableType]:
23
+ def decorator(original_func: CallableType) -> CallableType:
24
+ @functools.wraps(original_func)
25
+ def new_func(*args: Any, **kwargs: Any) -> Any:
26
+ warnings.warn(message, DeprecationWarning, stacklevel=2)
27
+ return original_func(*args, **kwargs)
28
+
29
+ return new_func # type: ignore
30
+
31
+ return decorator
@@ -1,12 +1,13 @@
1
1
  import logging
2
2
  import traceback
3
- from typing import Any, Callable, Iterable, Optional, Type, TypeVar, cast
3
+ from collections.abc import Callable, Iterable
4
+ from typing import Any, Optional, TypeVar, cast
4
5
 
5
6
  F = TypeVar("F", bound=Callable[..., Any])
6
7
 
7
8
 
8
9
  def graceful_fallback(
9
- fallback_method: Callable[..., Any], exceptions: Optional[Iterable[Type[BaseException]]] = None
10
+ fallback_method: Callable[..., Any], exceptions: Optional[Iterable[type[BaseException]]] = None
10
11
  ) -> Callable[[F], F]:
11
12
  """
12
13
  Decorator that reroutes failing functions to a specified fallback method.
@@ -0,0 +1,109 @@
1
+ import dataclasses
2
+ import datetime
3
+ from collections.abc import Mapping, Sequence
4
+ from enum import Enum
5
+ from io import StringIO
6
+ from pathlib import Path
7
+ from typing import Any, Union, cast, get_args, get_origin
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ from pandas.io.json import build_table_schema
12
+ from pandas.io.json._table_schema import parse_table_schema # type: ignore
13
+ from strawberry import UNSET
14
+ from strawberry.types.base import StrawberryObjectDefinition
15
+
16
+ try:
17
+ from pandas.io.json import ujson_dumps # type: ignore
18
+ except ImportError:
19
+ # https://github.com/pandas-dev/pandas/pull/54581
20
+ from pandas.io.json import dumps as ujson_dumps # type: ignore
21
+
22
+
23
+ def jsonify(obj: Any) -> Any:
24
+ """
25
+ Coerce object to be json serializable.
26
+ """
27
+ if isinstance(obj, Enum):
28
+ return jsonify(obj.value)
29
+ if isinstance(obj, (str, int, float, bool)) or obj is None:
30
+ return obj
31
+ if isinstance(obj, (list, set, frozenset, Sequence)):
32
+ return [jsonify(v) for v in obj]
33
+ if isinstance(obj, (dict, Mapping)):
34
+ return {jsonify(k): jsonify(v) for k, v in obj.items()}
35
+ is_strawberry_type = isinstance(
36
+ getattr(obj, "__strawberry_definition__", None), StrawberryObjectDefinition
37
+ )
38
+ if is_strawberry_type:
39
+ return {
40
+ k: jsonify(v)
41
+ for field in dataclasses.fields(obj)
42
+ if (v := getattr(obj, (k := field.name))) is not UNSET
43
+ }
44
+ if dataclasses.is_dataclass(obj):
45
+ return {
46
+ k: jsonify(v)
47
+ for field in dataclasses.fields(obj)
48
+ if not (
49
+ (v := getattr(obj, (k := field.name))) is None
50
+ and get_origin(field) is Union
51
+ and type(None) in get_args(field)
52
+ )
53
+ }
54
+ if isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):
55
+ return obj.isoformat()
56
+ if isinstance(obj, datetime.timedelta):
57
+ return obj.total_seconds()
58
+ if isinstance(obj, Path):
59
+ return str(obj)
60
+ if isinstance(obj, BaseException):
61
+ return str(obj)
62
+ if isinstance(obj, np.ndarray):
63
+ return [jsonify(v) for v in obj]
64
+ if hasattr(obj, "__float__"):
65
+ return float(obj)
66
+ if hasattr(obj, "model_dump") and callable(obj.model_dump):
67
+ # pydantic v2
68
+ try:
69
+ assert isinstance(d := obj.model_dump(), dict)
70
+ except BaseException:
71
+ pass
72
+ else:
73
+ return jsonify(d)
74
+ if hasattr(obj, "dict") and callable(obj.dict):
75
+ # pydantic v1
76
+ try:
77
+ assert isinstance(d := obj.dict(), dict)
78
+ except BaseException:
79
+ pass
80
+ else:
81
+ return jsonify(d)
82
+ cls = obj.__class__
83
+ return f"<{cls.__module__}.{cls.__name__} object>"
84
+
85
+
86
+ def encode_df_as_json_string(df: pd.DataFrame) -> str:
87
+ index_names = df.index.names
88
+ n = len(index_names)
89
+ primary_key = [f"{i}_{(x or '')}" for i, x in enumerate(index_names)]
90
+ df = df.set_axis([f"{i}_{x}" for i, x in enumerate(df.columns, n)], axis=1)
91
+ df = df.reset_index(names=primary_key)
92
+ schema = build_table_schema(df, False, primary_key) # type: ignore
93
+ data = df.to_dict("records")
94
+ return cast(
95
+ str,
96
+ ujson_dumps(
97
+ {"schema": schema, "data": data},
98
+ date_unit="ns",
99
+ iso_dates=True,
100
+ ensure_ascii=False,
101
+ ),
102
+ )
103
+
104
+
105
+ def decode_df_from_json_string(obj: str) -> pd.DataFrame:
106
+ # Note: read_json converts an all null column to NaN
107
+ df = cast(pd.DataFrame, parse_table_schema(StringIO(obj).read(), False))
108
+ df.index.names = [x.split("_", 1)[1] or None for x in df.index.names] # type: ignore
109
+ return df.set_axis([x.split("_", 1)[1] for x in df.columns], axis=1)
@@ -8,3 +8,11 @@ from tqdm.auto import tqdm
8
8
  def printif(condition: bool, *args: Any, **kwargs: Any) -> None:
9
9
  if condition:
10
10
  tqdm.write(*args, **kwargs)
11
+
12
+
13
+ def log_a_list(list_of_str: list[str], join_word: str) -> str:
14
+ if list_of_str is None or len(list_of_str) == 0:
15
+ return ""
16
+ if len(list_of_str) == 1:
17
+ return list_of_str[0]
18
+ return f"{', '.join(map(str, list_of_str[:-1]))} {join_word} {list_of_str[-1]}"
@@ -1,4 +1,4 @@
1
- from typing import Iterable
1
+ from collections.abc import Iterable
2
2
 
3
3
  from openinference.semconv.resource import ResourceAttributes
4
4
  from opentelemetry.proto.common.v1.common_pb2 import KeyValue
@@ -9,5 +9,5 @@ from phoenix.config import DEFAULT_PROJECT_NAME
9
9
  def get_project_name(attributes: Iterable[KeyValue]) -> str:
10
10
  for kv in attributes:
11
11
  if kv.key == ResourceAttributes.PROJECT_NAME and (v := kv.value.string_value):
12
- return v
12
+ return str(v)
13
13
  return DEFAULT_PROJECT_NAME
@@ -0,0 +1,49 @@
1
+ import logging
2
+ from re import compile, split
3
+ from urllib.parse import unquote
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ # Optional whitespace
8
+ _OWS = r"[ \t]*"
9
+ # A key contains printable US-ASCII characters except: SP and "(),/:;<=>?@[\]{}
10
+ _KEY_FORMAT = r"[\x21\x23-\x27\x2a\x2b\x2d\x2e\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+"
11
+ # A value contains a URL-encoded UTF-8 string. The encoded form can contain any
12
+ # printable US-ASCII characters (0x20-0x7f) other than SP, DEL, and ",;/
13
+ _VALUE_FORMAT = r"[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"
14
+ # A key-value is key=value, with optional whitespace surrounding key and value
15
+ _KEY_VALUE_FORMAT = rf"{_OWS}{_KEY_FORMAT}{_OWS}={_OWS}{_VALUE_FORMAT}{_OWS}"
16
+
17
+ _HEADER_PATTERN = compile(_KEY_VALUE_FORMAT)
18
+ _DELIMITER_PATTERN = compile(r"[ \t]*,[ \t]*")
19
+
20
+
21
+ def parse_env_headers(s: str) -> dict[str, str]:
22
+ """
23
+ Parse ``s``, which is a ``str`` instance containing HTTP headers encoded
24
+ for use in ENV variables per the W3C Baggage HTTP header format at
25
+ https://www.w3.org/TR/baggage/#baggage-http-header-format, except that
26
+ additional semi-colon delimited metadata is not supported.
27
+
28
+ src: https://github.com/open-telemetry/opentelemetry-python/blob/2d5cd58f33bd8a16f45f30be620a96699bc14297/opentelemetry-api/src/opentelemetry/util/re.py#L52
29
+ """
30
+ headers: dict[str, str] = {}
31
+ headers_list: list[str] = split(_DELIMITER_PATTERN, s)
32
+ for header in headers_list:
33
+ if not header: # empty string
34
+ continue
35
+ match = _HEADER_PATTERN.fullmatch(header.strip())
36
+ if not match:
37
+ logger.warning(
38
+ "Header format invalid! Header values in environment variables must be "
39
+ "URL encoded: %s",
40
+ header,
41
+ )
42
+ continue
43
+ # value may contain any number of `=`
44
+ name, value = match.string.split("=", 1)
45
+ name = unquote(name).strip().lower()
46
+ value = unquote(value).strip()
47
+ headers[name] = value
48
+
49
+ return headers
@@ -1,23 +0,0 @@
1
- from typing import Optional
2
-
3
- from phoenix.config import get_env_span_storage_type, get_storage_dir
4
- from phoenix.core.traces import Traces
5
- from phoenix.storage.span_store import SPAN_STORE_FACTORIES, SpanStore
6
- from phoenix.trace.otel import decode
7
- from phoenix.utilities.project import get_project_name
8
-
9
-
10
- def get_span_store() -> Optional[SpanStore]:
11
- if span_store_type := get_env_span_storage_type():
12
- span_store_factory = SPAN_STORE_FACTORIES[span_store_type]
13
- return span_store_factory(get_storage_dir())
14
- return None
15
-
16
-
17
- def load_traces_data_from_store(traces: Traces, span_store: SpanStore) -> None:
18
- for traces_data in span_store.load():
19
- for resource_spans in traces_data.resource_spans:
20
- project_name = get_project_name(resource_spans.resource.attributes)
21
- for scope_span in resource_spans.scope_spans:
22
- for span in scope_span.spans:
23
- traces.put(decode(span), project_name=project_name)