arize-phoenix 0.0.32rc1__tar.gz → 0.0.33__tar.gz

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 (144) hide show
  1. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/.gitignore +3 -0
  2. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/PKG-INFO +11 -5
  3. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/pyproject.toml +47 -15
  4. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/__init__.py +3 -1
  5. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/config.py +23 -1
  6. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/core/model_schema.py +14 -37
  7. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/core/model_schema_adapter.py +0 -1
  8. arize_phoenix-0.0.33/src/phoenix/core/traces.py +285 -0
  9. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/datasets/dataset.py +14 -21
  10. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/datasets/errors.py +4 -1
  11. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/datasets/schema.py +1 -1
  12. arize_phoenix-0.0.33/src/phoenix/datetime_utils.py +87 -0
  13. arize_phoenix-0.0.33/src/phoenix/experimental/callbacks/langchain_tracer.py +228 -0
  14. arize_phoenix-0.0.33/src/phoenix/experimental/callbacks/llama_index_trace_callback_handler.py +364 -0
  15. arize_phoenix-0.0.33/src/phoenix/experimental/evals/__init__.py +33 -0
  16. arize_phoenix-0.0.33/src/phoenix/experimental/evals/functions/__init__.py +4 -0
  17. arize_phoenix-0.0.33/src/phoenix/experimental/evals/functions/binary.py +156 -0
  18. arize_phoenix-0.0.33/src/phoenix/experimental/evals/functions/common.py +31 -0
  19. arize_phoenix-0.0.33/src/phoenix/experimental/evals/functions/generate.py +50 -0
  20. arize_phoenix-0.0.33/src/phoenix/experimental/evals/models/__init__.py +4 -0
  21. arize_phoenix-0.0.33/src/phoenix/experimental/evals/models/base.py +130 -0
  22. arize_phoenix-0.0.33/src/phoenix/experimental/evals/models/openai.py +128 -0
  23. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/experimental/evals/retrievals.py +2 -2
  24. arize_phoenix-0.0.33/src/phoenix/experimental/evals/templates/__init__.py +24 -0
  25. arize_phoenix-0.0.33/src/phoenix/experimental/evals/templates/default_templates.py +126 -0
  26. arize_phoenix-0.0.33/src/phoenix/experimental/evals/templates/template.py +107 -0
  27. arize_phoenix-0.0.33/src/phoenix/experimental/evals/utils/downloads.py +33 -0
  28. arize_phoenix-0.0.33/src/phoenix/experimental/evals/utils/threads.py +27 -0
  29. arize_phoenix-0.0.33/src/phoenix/experimental/evals/utils/types.py +9 -0
  30. arize_phoenix-0.0.33/src/phoenix/experimental/evals/utils.py +33 -0
  31. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/binning.py +0 -1
  32. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/timeseries.py +2 -3
  33. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/context.py +2 -0
  34. arize_phoenix-0.0.33/src/phoenix/server/api/input_types/SpanSort.py +60 -0
  35. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/schema.py +85 -4
  36. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DataQualityMetric.py +10 -1
  37. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Dataset.py +2 -4
  38. arize_phoenix-0.0.33/src/phoenix/server/api/types/DatasetInfo.py +10 -0
  39. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/ExportEventsMutation.py +4 -1
  40. arize_phoenix-0.0.33/src/phoenix/server/api/types/Functionality.py +15 -0
  41. arize_phoenix-0.0.33/src/phoenix/server/api/types/MimeType.py +16 -0
  42. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Model.py +3 -5
  43. arize_phoenix-0.0.33/src/phoenix/server/api/types/SortDir.py +13 -0
  44. arize_phoenix-0.0.33/src/phoenix/server/api/types/Span.py +229 -0
  45. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/TimeSeries.py +9 -2
  46. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/pagination.py +2 -0
  47. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/app.py +24 -4
  48. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/main.py +60 -24
  49. arize_phoenix-0.0.33/src/phoenix/server/span_handler.py +39 -0
  50. arize_phoenix-0.0.33/src/phoenix/server/static/index.js +6712 -0
  51. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/thread_server.py +10 -2
  52. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/services.py +39 -16
  53. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/session/session.py +99 -27
  54. arize_phoenix-0.0.33/src/phoenix/trace/exporter.py +71 -0
  55. arize_phoenix-0.0.33/src/phoenix/trace/filter.py +181 -0
  56. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/trace/fixtures.py +23 -8
  57. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/trace/schemas.py +59 -6
  58. arize_phoenix-0.0.33/src/phoenix/trace/semantic_conventions.py +177 -0
  59. arize_phoenix-0.0.33/src/phoenix/trace/span_json_decoder.py +108 -0
  60. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/trace/span_json_encoder.py +1 -9
  61. arize_phoenix-0.0.33/src/phoenix/trace/trace_dataset.py +130 -0
  62. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/trace/tracer.py +26 -3
  63. arize_phoenix-0.0.33/src/phoenix/trace/v1/__init__.py +522 -0
  64. arize_phoenix-0.0.33/src/phoenix/trace/v1/trace_pb2.py +52 -0
  65. arize_phoenix-0.0.33/src/phoenix/trace/v1/trace_pb2.pyi +351 -0
  66. arize_phoenix-0.0.32rc1/src/phoenix/core/dimension_data_type.py +0 -6
  67. arize_phoenix-0.0.32rc1/src/phoenix/core/dimension_type.py +0 -9
  68. arize_phoenix-0.0.32rc1/src/phoenix/server/static/index.js +0 -6235
  69. arize_phoenix-0.0.32rc1/src/phoenix/trace/semantic_conventions.py +0 -37
  70. arize_phoenix-0.0.32rc1/src/phoenix/trace/span_json_decoder.py +0 -54
  71. arize_phoenix-0.0.32rc1/src/phoenix/trace/trace_dataset.py +0 -38
  72. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/IP_NOTICE +0 -0
  73. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/LICENSE +0 -0
  74. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/README.md +0 -0
  75. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/core/__init__.py +0 -0
  76. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/core/embedding_dimension.py +0 -0
  77. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/core/model.py +0 -0
  78. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/datasets/__init__.py +0 -0
  79. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/datasets/fixtures.py +0 -0
  80. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/datasets/validation.py +0 -0
  81. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/experimental/__init__.py +0 -0
  82. {arize_phoenix-0.0.32rc1/src/phoenix/experimental/evals → arize_phoenix-0.0.33/src/phoenix/experimental/callbacks}/__init__.py +0 -0
  83. {arize_phoenix-0.0.32rc1/src/phoenix/server → arize_phoenix-0.0.33/src/phoenix/experimental/evals/utils}/__init__.py +0 -0
  84. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/README.md +0 -0
  85. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/__init__.py +0 -0
  86. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/metrics.py +0 -0
  87. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/mixins.py +0 -0
  88. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/metrics/wrappers.py +0 -0
  89. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/pointcloud/__init__.py +0 -0
  90. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/pointcloud/clustering.py +0 -0
  91. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/pointcloud/pointcloud.py +0 -0
  92. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/pointcloud/projectors.py +0 -0
  93. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/py.typed +0 -0
  94. {arize_phoenix-0.0.32rc1/src/phoenix/server/api → arize_phoenix-0.0.33/src/phoenix/server}/__init__.py +0 -0
  95. {arize_phoenix-0.0.32rc1/src/phoenix/server/api/input_types → arize_phoenix-0.0.33/src/phoenix/server/api}/__init__.py +0 -0
  96. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/helpers.py +0 -0
  97. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/ClusterInput.py +0 -0
  98. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/Coordinates.py +0 -0
  99. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/DataQualityMetricInput.py +0 -0
  100. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/DimensionFilter.py +0 -0
  101. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/DimensionInput.py +0 -0
  102. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/Granularity.py +0 -0
  103. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/PerformanceMetricInput.py +0 -0
  104. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/input_types/TimeRange.py +0 -0
  105. {arize_phoenix-0.0.32rc1/src/phoenix/server/api/types → arize_phoenix-0.0.33/src/phoenix/server/api/input_types}/__init__.py +0 -0
  106. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/interceptor.py +0 -0
  107. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Cluster.py +0 -0
  108. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DatasetRole.py +0 -0
  109. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DatasetValues.py +0 -0
  110. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Dimension.py +0 -0
  111. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DimensionDataType.py +0 -0
  112. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DimensionShape.py +0 -0
  113. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DimensionType.py +0 -0
  114. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/DimensionWithValue.py +0 -0
  115. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/EmbeddingDimension.py +0 -0
  116. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/EmbeddingMetadata.py +0 -0
  117. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Event.py +0 -0
  118. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/EventMetadata.py +0 -0
  119. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/ExportedFile.py +0 -0
  120. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/NumericRange.py +0 -0
  121. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/PerformanceMetric.py +0 -0
  122. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/PromptResponse.py +0 -0
  123. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Retrieval.py +0 -0
  124. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/ScalarDriftMetricEnum.py +0 -0
  125. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/Segments.py +0 -0
  126. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/UMAPPoints.py +0 -0
  127. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/VectorDriftMetricEnum.py +0 -0
  128. {arize_phoenix-0.0.32rc1/src/phoenix/session → arize_phoenix-0.0.33/src/phoenix/server/api/types}/__init__.py +0 -0
  129. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/api/types/node.py +0 -0
  130. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-114x114.png +0 -0
  131. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-120x120.png +0 -0
  132. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-144x144.png +0 -0
  133. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-152x152.png +0 -0
  134. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-180x180.png +0 -0
  135. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-72x72.png +0 -0
  136. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon-76x76.png +0 -0
  137. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/apple-touch-icon.png +0 -0
  138. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/favicon.ico +0 -0
  139. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/index.css +0 -0
  140. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/index.html +0 -0
  141. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/server/static/modernizr.js +0 -0
  142. {arize_phoenix-0.0.32rc1/src/phoenix/trace → arize_phoenix-0.0.33/src/phoenix/session}/__init__.py +0 -0
  143. /arize_phoenix-0.0.32rc1/src/phoenix/core/traces.py → /arize_phoenix-0.0.33/src/phoenix/trace/__init__.py +0 -0
  144. {arize_phoenix-0.0.32rc1 → arize_phoenix-0.0.33}/src/phoenix/trace/utils.py +0 -0
@@ -21,3 +21,6 @@ sdist
21
21
 
22
22
  # Testing
23
23
  coverage.xml
24
+
25
+ # Un-supported files
26
+ pnpm-lock.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arize-phoenix
3
- Version: 0.0.32rc1
3
+ Version: 0.0.33
4
4
  Summary: ML Observability in your notebook
5
5
  Project-URL: Documentation, https://docs.arize.com/phoenix/
6
6
  Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
@@ -19,13 +19,14 @@ Requires-Python: <3.12,>=3.8
19
19
  Requires-Dist: hdbscan<1.0.0,>=0.8.33
20
20
  Requires-Dist: numpy
21
21
  Requires-Dist: pandas
22
- Requires-Dist: portpicker
22
+ Requires-Dist: protobuf<5.0,>=3.20
23
23
  Requires-Dist: psutil
24
24
  Requires-Dist: pyarrow
25
25
  Requires-Dist: scikit-learn<1.3.0
26
26
  Requires-Dist: scipy
27
+ Requires-Dist: sortedcontainers
27
28
  Requires-Dist: starlette
28
- Requires-Dist: strawberry-graphql==0.178.0
29
+ Requires-Dist: strawberry-graphql==0.205.0
29
30
  Requires-Dist: typing-extensions
30
31
  Requires-Dist: umap-learn
31
32
  Requires-Dist: uvicorn
@@ -33,15 +34,20 @@ Requires-Dist: wrapt
33
34
  Provides-Extra: dev
34
35
  Requires-Dist: arize[autoembeddings,llm-evaluation]; extra == 'dev'
35
36
  Requires-Dist: black[jupyter]; extra == 'dev'
37
+ Requires-Dist: gcsfs; extra == 'dev'
36
38
  Requires-Dist: hatch; extra == 'dev'
37
39
  Requires-Dist: jupyter; extra == 'dev'
38
- Requires-Dist: pandas-stubs; extra == 'dev'
40
+ Requires-Dist: nbqa; extra == 'dev'
41
+ Requires-Dist: pandas-stubs<=2.0.2.230605; extra == 'dev'
39
42
  Requires-Dist: pre-commit; extra == 'dev'
40
43
  Requires-Dist: pytest; extra == 'dev'
41
44
  Requires-Dist: pytest-cov; extra == 'dev'
42
45
  Requires-Dist: pytest-lazy-fixture; extra == 'dev'
43
- Requires-Dist: strawberry-graphql[debug-server]==0.178.0; extra == 'dev'
46
+ Requires-Dist: ruff==0.0.289; extra == 'dev'
47
+ Requires-Dist: strawberry-graphql[debug-server]==0.205.0; extra == 'dev'
44
48
  Provides-Extra: experimental
49
+ Requires-Dist: langchain>=0.0.257; extra == 'experimental'
50
+ Requires-Dist: llama-index>=0.8.25; extra == 'experimental'
45
51
  Requires-Dist: openai; extra == 'experimental'
46
52
  Requires-Dist: tenacity; extra == 'experimental'
47
53
  Description-Content-Type: text/markdown
@@ -29,29 +29,35 @@ dependencies = [
29
29
  "starlette",
30
30
  "uvicorn",
31
31
  "psutil",
32
- "strawberry-graphql==0.178.0",
32
+ "strawberry-graphql==0.205.0",
33
33
  "pyarrow",
34
34
  "typing_extensions",
35
35
  "scipy",
36
- "portpicker",
37
36
  "wrapt",
37
+ "sortedcontainers",
38
+ "protobuf>=3.20, <5.0",
38
39
  ]
39
40
  dynamic = ["version"]
40
41
 
41
42
  [project.optional-dependencies]
42
43
  dev = [
43
44
  "black[jupyter]",
45
+ "gcsfs",
44
46
  "hatch",
45
47
  "jupyter",
46
- "pandas-stubs",
48
+ "nbqa",
49
+ "ruff==0.0.289",
50
+ "pandas-stubs<=2.0.2.230605", # version 2.0.3.230814 is causing a dependency conflict.
47
51
  "pytest",
48
52
  "pytest-cov",
49
53
  "pytest-lazy-fixture",
50
- "strawberry-graphql[debug-server]==0.178.0",
54
+ "strawberry-graphql[debug-server]==0.205.0",
51
55
  "pre-commit",
52
56
  "arize[AutoEmbeddings, LLM_Evaluation]",
53
57
  ]
54
58
  experimental = [
59
+ "langchain>=0.0.257",
60
+ "llama-index>=0.8.25",
55
61
  "openai",
56
62
  "tenacity",
57
63
  ]
@@ -90,29 +96,40 @@ artifacts = ["src/phoenix/server/static"]
90
96
 
91
97
  [tool.hatch.envs.default]
92
98
  dependencies = [
99
+ "pandas==1.4.0",
93
100
  "pytest",
94
101
  "pytest-cov",
95
102
  "pytest-lazy-fixture",
96
103
  "arize",
104
+ "langchain>=0.0.257",
105
+ "llama-index>=0.8.25",
97
106
  "openai",
98
107
  "tenacity",
108
+ "nltk==3.8.1",
109
+ "sentence-transformers==2.2.2",
110
+ "pydantic<2", # for @root_validator in llama-index
111
+ "requests",
112
+ "protobuf==3.20", # version minimum (for tests)
113
+ "responses",
99
114
  ]
100
115
 
101
116
  [tool.hatch.envs.type]
102
117
  dependencies = [
103
- "mypy==1.3.0",
104
- "pandas-stubs",
105
- "pytest",
118
+ "mypy==1.5.1",
119
+ "llama-index>=0.8.25",
120
+ "pandas-stubs<=2.0.2.230605", # version 2.0.3.230814 is causing a dependency conflict.
106
121
  "types-psutil",
107
- "tenacity",
122
+ "types-tqdm",
123
+ "types-requests",
124
+ "types-protobuf",
108
125
  ]
109
126
 
110
127
  [tool.hatch.envs.style]
111
128
  detached = true
112
129
  dependencies = [
113
- "black~=23.1.0",
114
- "black[jupyter]~=23.1.0",
115
- "ruff~=0.0.254",
130
+ "black~=23.3.0",
131
+ "black[jupyter]~=23.3.0",
132
+ "ruff~=0.0.289",
116
133
  ]
117
134
 
118
135
  [tool.hatch.envs.notebooks]
@@ -199,6 +216,7 @@ pypi = [
199
216
 
200
217
  [tool.black]
201
218
  line-length = 100
219
+ exclude = '_pb2\.pyi?$'
202
220
 
203
221
  [tool.hatch.envs.docs.scripts]
204
222
  check = [
@@ -206,10 +224,23 @@ check = [
206
224
  ]
207
225
 
208
226
  [tool.hatch.envs.gql]
227
+ dependencies = [
228
+ "strawberry-graphql[cli]==0.205.0",
229
+ ]
209
230
 
210
231
  [tool.hatch.envs.gql.scripts]
211
232
  build = 'strawberry export-schema phoenix.server.api.schema:schema > app/schema.graphql'
212
233
 
234
+ [tool.hatch.envs.proto]
235
+ detached = true
236
+ dependencies = [
237
+ "grpcio-tools==1.54.3",
238
+ "mypy-protobuf==3.5.0",
239
+ ]
240
+
241
+ [tool.hatch.envs.proto.scripts]
242
+ recompile = "python -m grpc_tools.protoc -I src/phoenix/proto --python_out=src/phoenix --mypy_out=src/phoenix src/phoenix/proto/trace/v1/trace.proto"
243
+
213
244
  [tool.interrogate]
214
245
  fail-under = 0
215
246
  # generate-badge = "badges/"
@@ -226,13 +257,14 @@ ignore-nested-classes = false
226
257
  ignore-setters = false
227
258
 
228
259
  [tool.mypy]
229
- plugins = ["strawberry.ext.mypy_plugin"]
260
+ plugins = ["strawberry.ext.mypy_plugin", "pydantic.mypy"]
230
261
  disallow_untyped_calls = true
231
262
  disallow_untyped_defs = true
232
263
  disallow_incomplete_defs = true
233
264
  strict = true
234
265
  exclude = [
235
266
  "dist/",
267
+ "scripts/data/",
236
268
  "sdist/",
237
269
  "tests/",
238
270
  "tutorials/",
@@ -246,14 +278,14 @@ module = [
246
278
  "scipy.*",
247
279
  "sklearn.*",
248
280
  "arize.*",
249
- "portpicker",
250
281
  "wrapt",
251
- "openai",
282
+ "sortedcontainers",
283
+ "langchain.*",
252
284
  ]
253
285
  ignore_missing_imports = true
254
286
 
255
287
  [tool.ruff]
256
- exclude = [".git", "__pycache__", "docs/source/conf.py"]
288
+ exclude = [".git", "__pycache__", "docs/source/conf.py", "*_pb2.py*"]
257
289
  ignore-init-module-imports = true
258
290
  line-length = 100
259
291
  select = ["E", "F", "W", "I"]
@@ -3,8 +3,9 @@ from .datasets.fixtures import ExampleDatasets, load_example
3
3
  from .datasets.schema import EmbeddingColumnNames, RetrievalEmbeddingColumnNames, Schema
4
4
  from .session.session import Session, active_session, close_app, launch_app
5
5
  from .trace.fixtures import load_example_traces
6
+ from .trace.trace_dataset import TraceDataset
6
7
 
7
- __version__ = "0.0.32rc1"
8
+ __version__ = "0.0.33"
8
9
 
9
10
  # module level doc-string
10
11
  __doc__ = """
@@ -34,4 +35,5 @@ __all__ = [
34
35
  "launch_app",
35
36
  "Session",
36
37
  "load_example_traces",
38
+ "TraceDataset",
37
39
  ]
@@ -1,6 +1,7 @@
1
+ import os
1
2
  import tempfile
2
3
  from pathlib import Path
3
- from typing import List
4
+ from typing import List, Optional
4
5
 
5
6
 
6
7
  def _get_temp_path() -> Path:
@@ -18,6 +19,13 @@ def get_pids_path() -> Path:
18
19
  return path
19
20
 
20
21
 
22
+ def get_running_pid() -> Optional[int]:
23
+ for file in get_pids_path().iterdir():
24
+ if file.name.isnumeric():
25
+ return int(file.name)
26
+ return None
27
+
28
+
21
29
  for path in (
22
30
  ROOT_DIR := Path.home().resolve() / ".phoenix",
23
31
  EXPORT_DIR := ROOT_DIR / "exports",
@@ -28,6 +36,8 @@ for path in (
28
36
  PHOENIX_DIR = Path(__file__).resolve().parent
29
37
  # Server config
30
38
  SERVER_DIR = PHOENIX_DIR / "server"
39
+ # The host the server will run on after launch_app is called
40
+ HOST = "127.0.0.1"
31
41
  # The port the server will run on after launch_app is called
32
42
  PORT = 6060
33
43
  # The prefix of datasets that are auto-assigned a name
@@ -49,3 +59,15 @@ def get_exported_files(directory: Path) -> List[Path]:
49
59
  List of paths of the exported files.
50
60
  """
51
61
  return list(directory.glob("*.parquet"))
62
+
63
+
64
+ def get_env_port() -> int:
65
+ return (
66
+ int(port)
67
+ if isinstance(port := os.getenv("PHOENIX_PORT"), str) and port.isnumeric()
68
+ else PORT
69
+ )
70
+
71
+
72
+ def get_env_host() -> str:
73
+ return os.getenv("PHOENIX_HOST") or HOST
@@ -49,6 +49,7 @@ from typing_extensions import TypeAlias, TypeGuard
49
49
  from wrapt import ObjectProxy
50
50
 
51
51
  from phoenix.config import GENERATED_DATASET_NAME_PREFIX
52
+ from phoenix.datetime_utils import floor_to_minute
52
53
 
53
54
 
54
55
  class DimensionRole(IntEnum):
@@ -328,10 +329,7 @@ class Column:
328
329
  except KeyError:
329
330
  # It's important to glue the index to the default series,
330
331
  # so it would look like the series came from the dataframe.
331
- return self._default(len(data)).set_axis(
332
- data.index,
333
- copy=False,
334
- )
332
+ return self._default(len(data)).set_axis(data.index)
335
333
  if isinstance(data, pd.Series):
336
334
  try:
337
335
  return data.at[self.name]
@@ -657,8 +655,8 @@ class Events(ModelData):
657
655
  # open and one minute is the smallest interval allowed.
658
656
  stop_time = end_time + timedelta(minutes=1)
659
657
  # Round down to the nearest minute.
660
- start = _floor_to_minute(start_time)
661
- stop = _floor_to_minute(stop_time)
658
+ start = floor_to_minute(start_time)
659
+ stop = floor_to_minute(stop_time)
662
660
  return TimeRange(start, stop)
663
661
 
664
662
  def __iter__(self) -> Iterator[Event]:
@@ -721,6 +719,10 @@ class Dataset(Events):
721
719
  def role(self) -> DatasetRole:
722
720
  return self._self_role
723
721
 
722
+ @property
723
+ def is_empty(self) -> bool:
724
+ return len(self) == 0
725
+
724
726
  @cached_property
725
727
  def primary_key(self) -> pd.Index:
726
728
  return pd.Index(self[PREDICTION_ID])
@@ -736,10 +738,7 @@ class Dataset(Events):
736
738
  def __getitem__(self, key: Any) -> Any:
737
739
  if isinstance(key, list):
738
740
  return Events(
739
- self.iloc[key].set_axis(
740
- key,
741
- copy=False,
742
- ),
741
+ self.iloc[key].set_axis(key),
743
742
  role=self._self_role,
744
743
  _model=self._self_model,
745
744
  )
@@ -913,6 +912,11 @@ class Model:
913
912
  df, name=dataset.name, role=dataset_role
914
913
  )
915
914
 
915
+ @cached_property
916
+ def is_empty(self) -> bool:
917
+ """Returns True if the model has no data."""
918
+ return not any(map(len, self._datasets.values()))
919
+
916
920
  def export_rows_as_parquet_file(
917
921
  self,
918
922
  row_numbers: Mapping[DatasetRole, Iterable[int]],
@@ -1390,7 +1394,6 @@ def _coerce_str_column_names(
1390
1394
  df.set_axis(
1391
1395
  df.columns.astype(str),
1392
1396
  axis=1,
1393
- copy=False,
1394
1397
  )
1395
1398
  for df in dataframes
1396
1399
  )
@@ -1432,32 +1435,6 @@ def _title_case_no_underscore(name: str) -> str:
1432
1435
  return _id_pat.sub("ID", name.replace("_", " ").title())
1433
1436
 
1434
1437
 
1435
- MINUTE_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:00%z"
1436
-
1437
-
1438
- def _floor_to_minute(dt: datetime) -> datetime:
1439
- """Floor datetime to the minute by taking a round-trip through string
1440
- format because there isn't always an available function to strip the
1441
- nanoseconds if present."""
1442
- try:
1443
- dt_as_string = dt.astimezone(
1444
- timezone.utc,
1445
- ).strftime(
1446
- MINUTE_DATETIME_FORMAT,
1447
- )
1448
- except ValueError:
1449
- # NOTE: as of Python 3.8.16, pandas 1.5.3:
1450
- # >>> isinstance(pd.NaT, datetime.datetime)
1451
- # True
1452
- return cast(datetime, pd.NaT)
1453
- return datetime.strptime(
1454
- dt_as_string,
1455
- MINUTE_DATETIME_FORMAT,
1456
- ).astimezone(
1457
- timezone.utc,
1458
- )
1459
-
1460
-
1461
1438
  def _jsonify(obj: Any) -> Any:
1462
1439
  if getattr(obj, "__dataclass_fields__", None):
1463
1440
  return {
@@ -43,7 +43,6 @@ def create_model_from_datasets(*datasets: Optional[Dataset]) -> Model:
43
43
  df = df.set_axis(
44
44
  map(str, df.columns),
45
45
  axis=1,
46
- copy=False,
47
46
  )
48
47
  named_dataframes.append((dataset.name, df))
49
48
  dataset_schema = dataset.schema if dataset.schema is not None else DatasetSchema()
@@ -0,0 +1,285 @@
1
+ import weakref
2
+ from collections import defaultdict
3
+ from datetime import datetime, timezone
4
+ from queue import SimpleQueue
5
+ from threading import RLock, Thread
6
+ from types import MethodType
7
+ from typing import (
8
+ Any,
9
+ DefaultDict,
10
+ Dict,
11
+ Iterable,
12
+ Iterator,
13
+ List,
14
+ Optional,
15
+ SupportsFloat,
16
+ Tuple,
17
+ Union,
18
+ cast,
19
+ )
20
+ from uuid import UUID
21
+
22
+ from sortedcontainers import SortedKeyList
23
+ from typing_extensions import TypeAlias
24
+ from wrapt import ObjectProxy
25
+
26
+ import phoenix.trace.v1.trace_pb2 as pb
27
+ from phoenix.datetime_utils import right_open_time_range
28
+ from phoenix.trace import semantic_conventions
29
+ from phoenix.trace.schemas import (
30
+ ATTRIBUTE_PREFIX,
31
+ COMPUTED_PREFIX,
32
+ CONTEXT_PREFIX,
33
+ Span,
34
+ SpanAttributes,
35
+ SpanID,
36
+ TraceID,
37
+ )
38
+ from phoenix.trace.v1 import decode, encode
39
+
40
+ NAME = "name"
41
+ STATUS_CODE = "status_code"
42
+ SPAN_KIND = "span_kind"
43
+ TRACE_ID = CONTEXT_PREFIX + "trace_id"
44
+ SPAN_ID = CONTEXT_PREFIX + "span_id"
45
+ PARENT_ID = "parent_id"
46
+ START_TIME = "start_time"
47
+ END_TIME = "end_time"
48
+ LLM_TOKEN_COUNT_TOTAL = ATTRIBUTE_PREFIX + semantic_conventions.LLM_TOKEN_COUNT_TOTAL
49
+ LLM_TOKEN_COUNT_PROMPT = ATTRIBUTE_PREFIX + semantic_conventions.LLM_TOKEN_COUNT_PROMPT
50
+ LLM_TOKEN_COUNT_COMPLETION = ATTRIBUTE_PREFIX + semantic_conventions.LLM_TOKEN_COUNT_COMPLETION
51
+ LATENCY_MS = COMPUTED_PREFIX + "latency_ms" # The latency (or duration) of the span in milliseconds
52
+ CUMULATIVE_LLM_TOKEN_COUNT_TOTAL = COMPUTED_PREFIX + "cumulative_token_count_total"
53
+ CUMULATIVE_LLM_TOKEN_COUNT_PROMPT = COMPUTED_PREFIX + "cumulative_token_count_prompt"
54
+ CUMULATIVE_LLM_TOKEN_COUNT_COMPLETION = COMPUTED_PREFIX + "cumulative_token_count_completion"
55
+
56
+
57
+ class ReadableSpan(ObjectProxy): # type: ignore
58
+ """
59
+ A wrapped a protobuf Span, with access methods and ability to decode to
60
+ a python span. It's meant to be interface layer separating use from
61
+ implementation. It can also provide computed values that are not intrinsic
62
+ to the span, e.g. the latency rank percent which can change as more spans
63
+ are ingested, and would need to be re-computed on the fly.
64
+ """
65
+
66
+ __wrapped__: pb.Span
67
+
68
+ def __init__(self, span: pb.Span) -> None:
69
+ super().__init__(span)
70
+ self._self_computed_values: Dict[str, SupportsFloat] = {}
71
+
72
+ @property
73
+ def span(self) -> Span:
74
+ span = decode(self.__wrapped__)
75
+ span.attributes.update(cast(SpanAttributes, self._self_computed_values))
76
+ # TODO: compute latency rank percent (which can change depending on how
77
+ # many spans already ingested).
78
+ return span
79
+
80
+ def __getitem__(self, key: str) -> Any:
81
+ if key.startswith(COMPUTED_PREFIX):
82
+ return self._self_computed_values.get(key)
83
+ if key.startswith(CONTEXT_PREFIX):
84
+ suffix_key = key[len(CONTEXT_PREFIX) :]
85
+ return getattr(self.__wrapped__.context, suffix_key, None)
86
+ if key.startswith(ATTRIBUTE_PREFIX):
87
+ suffix_key = key[len(ATTRIBUTE_PREFIX) :]
88
+ if suffix_key not in self.__wrapped__.attributes:
89
+ return None
90
+ return self.__wrapped__.attributes[suffix_key]
91
+ return getattr(self.__wrapped__, key, None)
92
+
93
+ def __setitem__(self, key: str, value: Any) -> None:
94
+ if not key.startswith(COMPUTED_PREFIX):
95
+ raise KeyError(f"{key} is not a computed value")
96
+ self._self_computed_values[key] = value
97
+
98
+
99
+ ParentSpanID: TypeAlias = SpanID
100
+ ChildSpanID: TypeAlias = SpanID
101
+
102
+
103
+ class Traces:
104
+ def __init__(self, spans: Optional[Iterable[Span]] = None) -> None:
105
+ self._queue: "SimpleQueue[Optional[pb.Span]]" = SimpleQueue()
106
+ # Putting `None` as the sentinel value for queue termination.
107
+ weakref.finalize(self, self._queue.put, None)
108
+ for span in spans or ():
109
+ self.put(span)
110
+ self._lock = RLock()
111
+ self._spans: Dict[SpanID, ReadableSpan] = {}
112
+ self._parent_span_ids: Dict[SpanID, ParentSpanID] = {}
113
+ self._traces: Dict[TraceID, List[SpanID]] = defaultdict(list)
114
+ self._child_span_ids: DefaultDict[SpanID, List[ChildSpanID]] = defaultdict(list)
115
+ self._orphan_spans: DefaultDict[ParentSpanID, List[pb.Span]] = defaultdict(list)
116
+ self._start_time_sorted_span_ids: SortedKeyList[SpanID] = SortedKeyList(
117
+ key=lambda span_id: self._spans[span_id].start_time.ToDatetime(timezone.utc),
118
+ )
119
+ self._start_time_sorted_root_span_ids: SortedKeyList[SpanID] = SortedKeyList(
120
+ key=lambda span_id: self._spans[span_id].start_time.ToDatetime(timezone.utc),
121
+ )
122
+ self._latency_sorted_root_span_ids: SortedKeyList[SpanID] = SortedKeyList(
123
+ key=lambda span_id: self._spans[span_id][LATENCY_MS],
124
+ )
125
+ self._min_start_time: Optional[datetime] = None
126
+ self._max_start_time: Optional[datetime] = None
127
+ self._start_consumer()
128
+
129
+ def put(self, span: Optional[Union[Span, pb.Span]] = None) -> None:
130
+ self._queue.put(encode(span) if isinstance(span, Span) else span)
131
+
132
+ def get_trace(self, trace_id: TraceID) -> Iterator[Span]:
133
+ for span_id in self._traces[trace_id]:
134
+ if span := self[span_id]:
135
+ yield span
136
+
137
+ def get_spans(
138
+ self,
139
+ start_time: Optional[datetime] = None,
140
+ stop_time: Optional[datetime] = None,
141
+ root_spans_only: Optional[bool] = False,
142
+ ) -> Iterator[Span]:
143
+ if not self._spans:
144
+ return
145
+ min_start_time, max_stop_time = cast(
146
+ Tuple[datetime, datetime],
147
+ self.right_open_time_range,
148
+ )
149
+ start_time = start_time or min_start_time
150
+ stop_time = stop_time or max_stop_time
151
+ sorted_span_ids = (
152
+ self._start_time_sorted_root_span_ids
153
+ if root_spans_only
154
+ else self._start_time_sorted_span_ids
155
+ )
156
+ for span_id in sorted_span_ids.irange_key(
157
+ start_time.astimezone(timezone.utc),
158
+ stop_time.astimezone(timezone.utc),
159
+ inclusive=(True, False),
160
+ reverse=True, # most recent spans first
161
+ ):
162
+ if span := self[span_id]:
163
+ yield span
164
+
165
+ def latency_rank_percent(self, latency_ms: float) -> Optional[float]:
166
+ """
167
+ Returns a value between 0 and 100 approximating the rank of the
168
+ latency value as percent of the total count of root spans. E.g., for
169
+ a latency value at the 75th percentile, the result is roughly 75.
170
+ """
171
+ root_span_ids = self._latency_sorted_root_span_ids
172
+ if not (n := len(root_span_ids)):
173
+ return None
174
+ rank = cast(int, root_span_ids.bisect_key_left(latency_ms))
175
+ return rank / n * 100
176
+
177
+ def get_descendant_span_ids(self, span_id: SpanID) -> Iterator[SpanID]:
178
+ for child_span_id in self._child_span_ids.get(span_id) or ():
179
+ yield child_span_id
180
+ yield from self.get_descendant_span_ids(child_span_id)
181
+
182
+ @property
183
+ def span_count(self) -> int:
184
+ """Total number of spans (excluding orphan spans if any)"""
185
+ return len(self._spans)
186
+
187
+ @property
188
+ def right_open_time_range(self) -> Tuple[Optional[datetime], Optional[datetime]]:
189
+ return right_open_time_range(self._min_start_time, self._max_start_time)
190
+
191
+ def __getitem__(self, span_id: SpanID) -> Optional[Span]:
192
+ with self._lock:
193
+ if span := self._spans.get(span_id):
194
+ return span.span
195
+ return None
196
+
197
+ def _start_consumer(self) -> None:
198
+ Thread(
199
+ target=MethodType(
200
+ self.__class__._consume_spans,
201
+ weakref.proxy(self),
202
+ ),
203
+ daemon=True,
204
+ ).start()
205
+
206
+ def _consume_spans(self) -> None:
207
+ while True:
208
+ if not (span := self._queue.get()):
209
+ return
210
+ with self._lock:
211
+ self._process_span(span)
212
+
213
+ def _process_span(self, span: pb.Span) -> None:
214
+ span_id = UUID(bytes=span.context.span_id)
215
+ existing_span = self._spans.get(span_id)
216
+ if existing_span and existing_span.end_time:
217
+ # Reject updates if span has ended.
218
+ return
219
+ is_root_span = not span.HasField("parent_span_id")
220
+ if not is_root_span:
221
+ parent_span_id = UUID(bytes=span.parent_span_id.value)
222
+ if parent_span_id not in self._spans:
223
+ # Span can't be processed before its parent.
224
+ self._orphan_spans[parent_span_id].append(span)
225
+ return
226
+ self._child_span_ids[parent_span_id].append(span_id)
227
+ self._parent_span_ids[span_id] = parent_span_id
228
+ new_span = ReadableSpan(span)
229
+ start_time = span.start_time.ToDatetime(timezone.utc)
230
+ end_time = span.end_time.ToDatetime(timezone.utc) if span.HasField("end_time") else None
231
+ if end_time:
232
+ new_span[LATENCY_MS] = (end_time - start_time).total_seconds() * 1000
233
+ self._spans[span_id] = new_span
234
+ if is_root_span and end_time:
235
+ self._latency_sorted_root_span_ids.add(span_id)
236
+ if not existing_span:
237
+ trace_id = UUID(bytes=span.context.trace_id)
238
+ self._traces[trace_id].append(span_id)
239
+ if is_root_span:
240
+ self._start_time_sorted_root_span_ids.add(span_id)
241
+ self._start_time_sorted_span_ids.add(span_id)
242
+ self._min_start_time = (
243
+ start_time
244
+ if self._min_start_time is None
245
+ else min(self._min_start_time, start_time)
246
+ )
247
+ self._max_start_time = (
248
+ start_time
249
+ if self._max_start_time is None
250
+ else max(self._max_start_time, start_time)
251
+ )
252
+ # Update cumulative values for span's ancestors.
253
+ for attribute_name, cumulative_attribute_name in (
254
+ (LLM_TOKEN_COUNT_TOTAL, CUMULATIVE_LLM_TOKEN_COUNT_TOTAL),
255
+ (LLM_TOKEN_COUNT_PROMPT, CUMULATIVE_LLM_TOKEN_COUNT_PROMPT),
256
+ (LLM_TOKEN_COUNT_COMPLETION, CUMULATIVE_LLM_TOKEN_COUNT_COMPLETION),
257
+ ):
258
+ existing_value = (existing_span[attribute_name] or 0) if existing_span else 0
259
+ new_value = new_span[attribute_name] or 0
260
+ if not (difference := new_value - existing_value):
261
+ continue
262
+ existing_cumulative_value = (
263
+ (existing_span[cumulative_attribute_name] or 0) if existing_span else 0
264
+ )
265
+ new_span[cumulative_attribute_name] = difference + existing_cumulative_value
266
+ self._add_value_to_span_ancestors(
267
+ span_id,
268
+ cumulative_attribute_name,
269
+ difference,
270
+ )
271
+ # Process previously orphaned spans, if any.
272
+ for orphan_span in self._orphan_spans[span_id]:
273
+ self._process_span(orphan_span)
274
+
275
+ def _add_value_to_span_ancestors(
276
+ self,
277
+ span_id: SpanID,
278
+ attribute_name: str,
279
+ value: float,
280
+ ) -> None:
281
+ while parent_span_id := self._parent_span_ids.get(span_id):
282
+ parent_span = self._spans[parent_span_id]
283
+ cumulative_value = parent_span[attribute_name] or 0
284
+ parent_span[attribute_name] = cumulative_value + value
285
+ span_id = parent_span_id