arize-phoenix 0.1.1__tar.gz → 1.0.0__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.
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/PKG-INFO +9 -4
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/README.md +5 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/pyproject.toml +11 -9
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/__init__.py +1 -1
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/models/openai.py +119 -71
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/retrievals.py +6 -3
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/openai/instrumentor.py +49 -40
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/.gitignore +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/IP_NOTICE +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/LICENSE +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/config.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/core/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/core/embedding_dimension.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/core/model.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/core/model_schema.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/core/model_schema_adapter.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/core/traces.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datasets/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datasets/dataset.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datasets/errors.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datasets/fixtures.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datasets/schema.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datasets/validation.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/datetime_utils.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/evaluators.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/functions/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/functions/classify.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/functions/generate.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/functions/processing.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/models/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/models/base.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/models/bedrock.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/models/vertexai.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/templates/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/templates/default_templates.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/templates/template.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/utils/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/utils/downloads.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/utils/threads.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/utils/types.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/experimental/evals/utils.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/README.md +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/binning.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/metrics.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/mixins.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/timeseries.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/metrics/wrappers.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/pointcloud/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/pointcloud/clustering.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/pointcloud/pointcloud.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/pointcloud/projectors.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/pointcloud/umap_parameters.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/py.typed +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/context.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/helpers.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/ClusterInput.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/Coordinates.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/DataQualityMetricInput.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/DimensionFilter.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/DimensionInput.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/Granularity.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/PerformanceMetricInput.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/SpanSort.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/TimeRange.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/input_types/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/interceptor.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/schema.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Cluster.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DataQualityMetric.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Dataset.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DatasetInfo.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DatasetRole.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DatasetValues.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Dimension.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DimensionDataType.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DimensionShape.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DimensionType.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/DimensionWithValue.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/EmbeddingDimension.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/EmbeddingMetadata.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Event.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/EventMetadata.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/ExportEventsMutation.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/ExportedFile.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Functionality.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/MimeType.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Model.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/NumericRange.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/PerformanceMetric.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/PromptResponse.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Retrieval.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/ScalarDriftMetricEnum.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Segments.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/SortDir.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/Span.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/TimeSeries.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/UMAPPoints.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/ValidationResult.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/VectorDriftMetricEnum.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/node.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/api/types/pagination.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/app.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/main.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/span_handler.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-114x114.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-120x120.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-144x144.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-152x152.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-180x180.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-72x72.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon-76x76.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/apple-touch-icon.png +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/favicon.ico +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/index.css +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/index.js +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/static/modernizr.js +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/templates/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/templates/index.html +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/server/thread_server.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/services.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/session/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/session/session.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/evaluation_conventions.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/exporter.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/filter.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/fixtures.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/langchain/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/langchain/instrumentor.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/langchain/tracer.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/llama_index/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/llama_index/callback.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/llama_index/debug_callback.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/openai/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/schemas.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/semantic_conventions.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/span_json_decoder.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/span_json_encoder.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/trace_dataset.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/tracer.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/utils.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/v1/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/v1/trace_pb2.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/trace/v1/trace_pb2.pyi +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/utilities/__init__.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/utilities/error_handling.py +0 -0
- {arize_phoenix-0.1.1 → arize_phoenix-1.0.0}/src/phoenix/utilities/logging.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: arize-phoenix
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
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
|
|
@@ -38,15 +38,15 @@ Requires-Dist: arize[autoembeddings,llm-evaluation]; extra == 'dev'
|
|
|
38
38
|
Requires-Dist: gcsfs; extra == 'dev'
|
|
39
39
|
Requires-Dist: hatch; extra == 'dev'
|
|
40
40
|
Requires-Dist: jupyter; extra == 'dev'
|
|
41
|
-
Requires-Dist: langchain>=0.0.
|
|
42
|
-
Requires-Dist: llama-index>=0.8.
|
|
41
|
+
Requires-Dist: langchain>=0.0.334; extra == 'dev'
|
|
42
|
+
Requires-Dist: llama-index>=0.8.64; extra == 'dev'
|
|
43
43
|
Requires-Dist: nbqa; extra == 'dev'
|
|
44
44
|
Requires-Dist: pandas-stubs<=2.0.2.230605; extra == 'dev'
|
|
45
45
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
46
46
|
Requires-Dist: pytest; extra == 'dev'
|
|
47
47
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
48
48
|
Requires-Dist: pytest-lazy-fixture; extra == 'dev'
|
|
49
|
-
Requires-Dist: ruff==0.1.
|
|
49
|
+
Requires-Dist: ruff==0.1.5; extra == 'dev'
|
|
50
50
|
Requires-Dist: strawberry-graphql[debug-server]==0.208.2; extra == 'dev'
|
|
51
51
|
Provides-Extra: experimental
|
|
52
52
|
Requires-Dist: tenacity; extra == 'experimental'
|
|
@@ -101,6 +101,7 @@ Phoenix provides MLOps and LLMOps insights at lightning speed with zero-config o
|
|
|
101
101
|
- [Exportable Clusters](#exportable-clusters)
|
|
102
102
|
- [Retrieval-Augmented Generation Analysis](#retrieval-augmented-generation-analysis)
|
|
103
103
|
- [Structured Data Analysis](#structured-data-analysis)
|
|
104
|
+
- [Breaking Changes](#breaking-changes)
|
|
104
105
|
- [Community](#community)
|
|
105
106
|
- [Thanks](#thanks)
|
|
106
107
|
- [Copyright, Patent, and License](#copyright-patent-and-license)
|
|
@@ -418,6 +419,10 @@ train_ds = px.Dataset(dataframe=train_df, schema=schema, name="training")
|
|
|
418
419
|
session = px.launch_app(primary=prod_ds, reference=train_ds)
|
|
419
420
|
```
|
|
420
421
|
|
|
422
|
+
## Breaking Changes
|
|
423
|
+
|
|
424
|
+
- **v1.0.0** - Phoenix now exclusively supports the `openai>=1.0.0` sdk. If you are using an older version of the OpenAI SDK, you can continue to use `arize-phoenix==0.1.1`. However, we recommend upgrading to the latest version of the OpenAI SDK as it contains many improvements. If you are using Phoenix with LlamaIndex and and LangChain, you will have to upgrade to the versions of these packages that support the OpenAI `1.0.0` SDK as well (`llama-index>=0.8.64`, `langchain>=0.0.334`)
|
|
425
|
+
|
|
421
426
|
## Community
|
|
422
427
|
|
|
423
428
|
Join our community to connect with thousands of machine learning practitioners and ML observability enthusiasts.
|
|
@@ -47,6 +47,7 @@ Phoenix provides MLOps and LLMOps insights at lightning speed with zero-config o
|
|
|
47
47
|
- [Exportable Clusters](#exportable-clusters)
|
|
48
48
|
- [Retrieval-Augmented Generation Analysis](#retrieval-augmented-generation-analysis)
|
|
49
49
|
- [Structured Data Analysis](#structured-data-analysis)
|
|
50
|
+
- [Breaking Changes](#breaking-changes)
|
|
50
51
|
- [Community](#community)
|
|
51
52
|
- [Thanks](#thanks)
|
|
52
53
|
- [Copyright, Patent, and License](#copyright-patent-and-license)
|
|
@@ -364,6 +365,10 @@ train_ds = px.Dataset(dataframe=train_df, schema=schema, name="training")
|
|
|
364
365
|
session = px.launch_app(primary=prod_ds, reference=train_ds)
|
|
365
366
|
```
|
|
366
367
|
|
|
368
|
+
## Breaking Changes
|
|
369
|
+
|
|
370
|
+
- **v1.0.0** - Phoenix now exclusively supports the `openai>=1.0.0` sdk. If you are using an older version of the OpenAI SDK, you can continue to use `arize-phoenix==0.1.1`. However, we recommend upgrading to the latest version of the OpenAI SDK as it contains many improvements. If you are using Phoenix with LlamaIndex and and LangChain, you will have to upgrade to the versions of these packages that support the OpenAI `1.0.0` SDK as well (`llama-index>=0.8.64`, `langchain>=0.0.334`)
|
|
371
|
+
|
|
367
372
|
## Community
|
|
368
373
|
|
|
369
374
|
Join our community to connect with thousands of machine learning practitioners and ML observability enthusiasts.
|
|
@@ -47,7 +47,7 @@ dev = [
|
|
|
47
47
|
"hatch",
|
|
48
48
|
"jupyter",
|
|
49
49
|
"nbqa",
|
|
50
|
-
"ruff==0.1.
|
|
50
|
+
"ruff==0.1.5",
|
|
51
51
|
"pandas-stubs<=2.0.2.230605", # version 2.0.3.230814 is causing a dependency conflict.
|
|
52
52
|
"pytest",
|
|
53
53
|
"pytest-cov",
|
|
@@ -55,8 +55,8 @@ dev = [
|
|
|
55
55
|
"strawberry-graphql[debug-server]==0.208.2",
|
|
56
56
|
"pre-commit",
|
|
57
57
|
"arize[AutoEmbeddings, LLM_Evaluation]",
|
|
58
|
-
"llama-index>=0.8.
|
|
59
|
-
"langchain>=0.0.
|
|
58
|
+
"llama-index>=0.8.64",
|
|
59
|
+
"langchain>=0.0.334",
|
|
60
60
|
]
|
|
61
61
|
experimental = [
|
|
62
62
|
"tenacity",
|
|
@@ -91,9 +91,9 @@ dependencies = [
|
|
|
91
91
|
"pytest-cov",
|
|
92
92
|
"pytest-lazy-fixture",
|
|
93
93
|
"arize",
|
|
94
|
-
"langchain>=0.0.
|
|
95
|
-
"llama-index>=0.8.
|
|
96
|
-
"openai
|
|
94
|
+
"langchain>=0.0.334",
|
|
95
|
+
"llama-index>=0.8.63.post2",
|
|
96
|
+
"openai>=1.0.0",
|
|
97
97
|
"tenacity",
|
|
98
98
|
"nltk==3.8.1",
|
|
99
99
|
"sentence-transformers==2.2.2",
|
|
@@ -103,24 +103,26 @@ dependencies = [
|
|
|
103
103
|
"responses",
|
|
104
104
|
"tiktoken",
|
|
105
105
|
"typing-extensions<4.6.0", # for Colab
|
|
106
|
+
"httpx", # For OpenAI testing
|
|
107
|
+
"respx", # For OpenAI testing
|
|
106
108
|
]
|
|
107
109
|
|
|
108
110
|
[tool.hatch.envs.type]
|
|
109
111
|
dependencies = [
|
|
110
112
|
"mypy==1.5.1",
|
|
111
|
-
"llama-index>=0.8.
|
|
113
|
+
"llama-index>=0.8.64",
|
|
112
114
|
"pandas-stubs<=2.0.2.230605", # version 2.0.3.230814 is causing a dependency conflict.
|
|
113
115
|
"types-psutil",
|
|
114
116
|
"types-tqdm",
|
|
115
117
|
"types-requests",
|
|
116
118
|
"types-protobuf",
|
|
117
|
-
"openai
|
|
119
|
+
"openai>=1.0.0",
|
|
118
120
|
]
|
|
119
121
|
|
|
120
122
|
[tool.hatch.envs.style]
|
|
121
123
|
detached = true
|
|
122
124
|
dependencies = [
|
|
123
|
-
"ruff~=0.1.
|
|
125
|
+
"ruff~=0.1.5",
|
|
124
126
|
]
|
|
125
127
|
|
|
126
128
|
[tool.hatch.envs.notebooks]
|
|
@@ -5,7 +5,7 @@ from .session.session import Session, active_session, close_app, launch_app
|
|
|
5
5
|
from .trace.fixtures import load_example_traces
|
|
6
6
|
from .trace.trace_dataset import TraceDataset
|
|
7
7
|
|
|
8
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
9
|
|
|
10
10
|
# module level doc-string
|
|
11
11
|
__doc__ = """
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import
|
|
3
|
+
from dataclasses import dataclass, field, fields
|
|
4
|
+
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Dict,
|
|
9
|
+
List,
|
|
10
|
+
Optional,
|
|
11
|
+
Tuple,
|
|
12
|
+
Union,
|
|
13
|
+
get_args,
|
|
14
|
+
get_origin,
|
|
15
|
+
)
|
|
5
16
|
|
|
6
17
|
from phoenix.experimental.evals.models.base import BaseEvalModel
|
|
7
18
|
|
|
@@ -9,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
9
20
|
from tiktoken import Encoding
|
|
10
21
|
|
|
11
22
|
OPENAI_API_KEY_ENVVAR_NAME = "OPENAI_API_KEY"
|
|
12
|
-
MINIMUM_OPENAI_VERSION = "0.
|
|
23
|
+
MINIMUM_OPENAI_VERSION = "1.0.0"
|
|
13
24
|
MODEL_TOKEN_LIMIT_MAPPING = {
|
|
14
25
|
"gpt-3.5-turbo-instruct": 4096,
|
|
15
26
|
"gpt-3.5-turbo-0301": 4096,
|
|
@@ -24,17 +35,31 @@ LEGACY_COMPLETION_API_MODELS = ("gpt-3.5-turbo-instruct",)
|
|
|
24
35
|
logger = logging.getLogger(__name__)
|
|
25
36
|
|
|
26
37
|
|
|
38
|
+
@dataclass
|
|
39
|
+
class AzureOptions:
|
|
40
|
+
api_version: str
|
|
41
|
+
azure_endpoint: str
|
|
42
|
+
azure_deployment: Optional[str]
|
|
43
|
+
azure_ad_token: Optional[str]
|
|
44
|
+
azure_ad_token_provider: Optional[Callable[[], str]]
|
|
45
|
+
|
|
46
|
+
|
|
27
47
|
@dataclass
|
|
28
48
|
class OpenAIModel(BaseEvalModel):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"""
|
|
49
|
+
api_key: Optional[str] = field(repr=False, default=None)
|
|
50
|
+
"""Your OpenAI key. If not provided, will be read from the environment variable"""
|
|
51
|
+
organization: Optional[str] = field(repr=False, default=None)
|
|
52
|
+
"""
|
|
53
|
+
The organization to use for the OpenAI API. If not provided, will default
|
|
54
|
+
to what's configured in OpenAI
|
|
55
|
+
"""
|
|
56
|
+
base_url: Optional[str] = field(repr=False, default=None)
|
|
57
|
+
"""
|
|
58
|
+
An optional base URL to use for the OpenAI API. If not provided, will default
|
|
59
|
+
to what's configured in OpenAI
|
|
60
|
+
"""
|
|
36
61
|
model_name: str = "gpt-4"
|
|
37
|
-
"""Model name to use."""
|
|
62
|
+
"""Model name to use. In of azure, this is the deployment name such as gpt-35-instant"""
|
|
38
63
|
temperature: float = 0.0
|
|
39
64
|
"""What sampling temperature to use."""
|
|
40
65
|
max_tokens: int = 256
|
|
@@ -63,6 +88,18 @@ class OpenAIModel(BaseEvalModel):
|
|
|
63
88
|
retry_max_seconds: int = 60
|
|
64
89
|
"""Maximum number of seconds to wait when retrying."""
|
|
65
90
|
|
|
91
|
+
# Azure options
|
|
92
|
+
api_version: Optional[str] = field(default=None)
|
|
93
|
+
"""https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning"""
|
|
94
|
+
azure_endpoint: Optional[str] = field(default=None)
|
|
95
|
+
"""
|
|
96
|
+
The endpoint to use for azure openai. Available in the azure portal.
|
|
97
|
+
https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
|
|
98
|
+
"""
|
|
99
|
+
azure_deployment: Optional[str] = field(default=None)
|
|
100
|
+
azure_ad_token: Optional[str] = field(default=None)
|
|
101
|
+
azure_ad_token_provider: Optional[Callable[[], str]] = field(default=None)
|
|
102
|
+
|
|
66
103
|
def __post_init__(self) -> None:
|
|
67
104
|
self._init_environment()
|
|
68
105
|
self._init_open_ai()
|
|
@@ -71,12 +108,10 @@ class OpenAIModel(BaseEvalModel):
|
|
|
71
108
|
def _init_environment(self) -> None:
|
|
72
109
|
try:
|
|
73
110
|
import openai
|
|
74
|
-
import openai.
|
|
75
|
-
from openai import error as openai_error
|
|
111
|
+
import openai._utils as openai_util
|
|
76
112
|
|
|
77
113
|
self._openai = openai
|
|
78
|
-
self.
|
|
79
|
-
self._openai_util = openai.util
|
|
114
|
+
self._openai_util = openai_util
|
|
80
115
|
except ImportError:
|
|
81
116
|
self._raise_import_error(
|
|
82
117
|
package_display_name="OpenAI",
|
|
@@ -93,52 +128,76 @@ class OpenAIModel(BaseEvalModel):
|
|
|
93
128
|
)
|
|
94
129
|
|
|
95
130
|
def _init_open_ai(self) -> None:
|
|
131
|
+
# For Azure, you need to provide the endpoint and the endpoint
|
|
132
|
+
self._is_azure = bool(self.azure_endpoint)
|
|
133
|
+
|
|
96
134
|
self._model_uses_legacy_completion_api = self.model_name.startswith(
|
|
97
135
|
LEGACY_COMPLETION_API_MODELS
|
|
98
136
|
)
|
|
99
|
-
if self.
|
|
137
|
+
if self.api_key is None:
|
|
100
138
|
api_key = os.getenv(OPENAI_API_KEY_ENVVAR_NAME)
|
|
101
139
|
if api_key is None:
|
|
102
140
|
# TODO: Create custom AuthenticationError
|
|
103
141
|
raise RuntimeError(
|
|
104
|
-
"OpenAI's API key not provided. Pass it as an argument to '
|
|
142
|
+
"OpenAI's API key not provided. Pass it as an argument to 'api_key' "
|
|
105
143
|
"or set it in your environment: 'export OPENAI_API_KEY=sk-****'"
|
|
106
144
|
)
|
|
107
|
-
self.
|
|
108
|
-
self.openai_api_base = self.openai_api_base or self._openai.api_base
|
|
109
|
-
self.openai_api_type = self.openai_api_type or self._openai.api_type
|
|
110
|
-
self.openai_api_version = self.openai_api_version or self._openai.api_version
|
|
111
|
-
self.openai_organization = self.openai_organization or self._openai.organization
|
|
112
|
-
# use enum to validate api type
|
|
113
|
-
self._openai_util.ApiType.from_str(self.openai_api_type) # type: ignore
|
|
114
|
-
self._is_azure = self.openai_api_type.lower().startswith("azure")
|
|
145
|
+
self.api_key = api_key
|
|
115
146
|
|
|
147
|
+
# Set the version, organization, and base_url - default to openAI
|
|
148
|
+
self.api_version = self.api_version or self._openai.api_version
|
|
149
|
+
self.organization = self.organization or self._openai.organization
|
|
150
|
+
|
|
151
|
+
# Initialize specific clients depending on the API backend
|
|
152
|
+
# Set the type first
|
|
153
|
+
self._client: Union[self._openai.OpenAI, self._openai.AzureOpenAI] # type: ignore
|
|
116
154
|
if self._is_azure:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
elif "gpt-4" in self.model_name:
|
|
128
|
-
self._openai_api_model_name = "gpt-4-0613"
|
|
129
|
-
else:
|
|
130
|
-
raise NotImplementedError(
|
|
131
|
-
f"openai_api_model_name not available for model {self.model_name}. "
|
|
155
|
+
# Validate the azure options and construct a client
|
|
156
|
+
azure_options = self._get_azure_options()
|
|
157
|
+
self._client = self._openai.AzureOpenAI(
|
|
158
|
+
azure_endpoint=azure_options.azure_endpoint,
|
|
159
|
+
azure_deployment=azure_options.azure_deployment,
|
|
160
|
+
api_version=azure_options.api_version,
|
|
161
|
+
azure_ad_token=azure_options.azure_ad_token,
|
|
162
|
+
azure_ad_token_provider=azure_options.azure_ad_token_provider,
|
|
163
|
+
api_key=self.api_key,
|
|
164
|
+
organization=self.organization,
|
|
132
165
|
)
|
|
166
|
+
# return early since we don't need to check the model
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# The client is not azure, so it must be openai
|
|
170
|
+
self._client = self._openai.OpenAI(
|
|
171
|
+
api_key=self.api_key,
|
|
172
|
+
organization=self.organization,
|
|
173
|
+
base_url=(self.base_url or self._openai.base_url),
|
|
174
|
+
)
|
|
133
175
|
|
|
134
176
|
def _init_tiktoken(self) -> None:
|
|
135
177
|
try:
|
|
136
|
-
encoding = self._tiktoken.encoding_for_model(self.
|
|
178
|
+
encoding = self._tiktoken.encoding_for_model(self.model_name)
|
|
137
179
|
except KeyError:
|
|
138
|
-
logger.warning("Warning: model not found. Using cl100k_base encoding.")
|
|
139
180
|
encoding = self._tiktoken.get_encoding("cl100k_base")
|
|
140
181
|
self._tiktoken_encoding = encoding
|
|
141
182
|
|
|
183
|
+
def _get_azure_options(self) -> AzureOptions:
|
|
184
|
+
options = {}
|
|
185
|
+
for option in fields(AzureOptions):
|
|
186
|
+
if (value := getattr(self, option.name)) is not None:
|
|
187
|
+
options[option.name] = value
|
|
188
|
+
else:
|
|
189
|
+
# raise ValueError if field is not optional
|
|
190
|
+
# See if the field is optional - e.g. get_origin(Optional[T]) = typing.Union
|
|
191
|
+
option_is_optional = get_origin(option.type) is Union and type(None) in get_args(
|
|
192
|
+
option.type
|
|
193
|
+
)
|
|
194
|
+
if not option_is_optional:
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"Option '{option.name}' must be set when using Azure OpenAI API"
|
|
197
|
+
)
|
|
198
|
+
options[option.name] = None
|
|
199
|
+
return AzureOptions(**options)
|
|
200
|
+
|
|
142
201
|
@staticmethod
|
|
143
202
|
def _build_messages(
|
|
144
203
|
prompt: str, system_instruction: Optional[str] = None
|
|
@@ -173,11 +232,11 @@ class OpenAIModel(BaseEvalModel):
|
|
|
173
232
|
def _generate_with_retry(self, **kwargs: Any) -> Any:
|
|
174
233
|
"""Use tenacity to retry the completion call."""
|
|
175
234
|
openai_retry_errors = [
|
|
176
|
-
self.
|
|
177
|
-
self.
|
|
178
|
-
self.
|
|
179
|
-
self.
|
|
180
|
-
self.
|
|
235
|
+
self._openai.APITimeoutError,
|
|
236
|
+
self._openai.APIError,
|
|
237
|
+
self._openai.APIConnectionError,
|
|
238
|
+
self._openai.RateLimitError,
|
|
239
|
+
self._openai.InternalServerError,
|
|
181
240
|
]
|
|
182
241
|
|
|
183
242
|
@self.retry(
|
|
@@ -193,17 +252,22 @@ class OpenAIModel(BaseEvalModel):
|
|
|
193
252
|
(message.get("content") or "")
|
|
194
253
|
for message in (kwargs.pop("messages", None) or ())
|
|
195
254
|
)
|
|
196
|
-
|
|
197
|
-
|
|
255
|
+
# OpenAI 1.0.0 API responses are pydantic objects, not dicts
|
|
256
|
+
# We must dump the model to get the dict
|
|
257
|
+
return self._client.completions.create(**kwargs).model_dump()
|
|
258
|
+
return self._client.chat.completions.create(**kwargs).model_dump()
|
|
198
259
|
|
|
199
260
|
return _completion_with_retry(**kwargs)
|
|
200
261
|
|
|
201
262
|
@property
|
|
202
263
|
def max_context_size(self) -> int:
|
|
203
|
-
model_name = self.
|
|
264
|
+
model_name = self.model_name
|
|
204
265
|
# handling finetuned models
|
|
205
266
|
if "ft-" in model_name:
|
|
206
267
|
model_name = self.model_name.split(":")[0]
|
|
268
|
+
if model_name == "gpt-4":
|
|
269
|
+
# Map gpt-4 to the current default
|
|
270
|
+
model_name = "gpt-4-0613"
|
|
207
271
|
|
|
208
272
|
context_size = MODEL_TOKEN_LIMIT_MAPPING.get(model_name, None)
|
|
209
273
|
|
|
@@ -219,7 +283,7 @@ class OpenAIModel(BaseEvalModel):
|
|
|
219
283
|
@property
|
|
220
284
|
def public_invocation_params(self) -> Dict[str, Any]:
|
|
221
285
|
return {
|
|
222
|
-
**({"
|
|
286
|
+
**({"model": self.model_name}),
|
|
223
287
|
**self._default_params,
|
|
224
288
|
**self.model_kwargs,
|
|
225
289
|
}
|
|
@@ -228,18 +292,6 @@ class OpenAIModel(BaseEvalModel):
|
|
|
228
292
|
def invocation_params(self) -> Dict[str, Any]:
|
|
229
293
|
return {
|
|
230
294
|
**self.public_invocation_params,
|
|
231
|
-
**self._credentials,
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
@property
|
|
235
|
-
def _credentials(self) -> Dict[str, Any]:
|
|
236
|
-
"""Get the default parameters for calling OpenAI API."""
|
|
237
|
-
return {
|
|
238
|
-
"api_key": self.openai_api_key,
|
|
239
|
-
"api_base": self.openai_api_base,
|
|
240
|
-
"api_type": self.openai_api_type,
|
|
241
|
-
"api_version": self.openai_api_version,
|
|
242
|
-
"organization": self.openai_organization,
|
|
243
295
|
}
|
|
244
296
|
|
|
245
297
|
@property
|
|
@@ -252,13 +304,9 @@ class OpenAIModel(BaseEvalModel):
|
|
|
252
304
|
"presence_penalty": self.presence_penalty,
|
|
253
305
|
"top_p": self.top_p,
|
|
254
306
|
"n": self.n,
|
|
255
|
-
"
|
|
307
|
+
"timeout": self.request_timeout,
|
|
256
308
|
}
|
|
257
309
|
|
|
258
|
-
@property
|
|
259
|
-
def openai_api_model_name(self) -> str:
|
|
260
|
-
return self._openai_api_model_name
|
|
261
|
-
|
|
262
310
|
@property
|
|
263
311
|
def encoder(self) -> "Encoding":
|
|
264
312
|
return self._tiktoken_encoding
|
|
@@ -268,7 +316,7 @@ class OpenAIModel(BaseEvalModel):
|
|
|
268
316
|
|
|
269
317
|
Official documentation: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb
|
|
270
318
|
""" # noqa
|
|
271
|
-
model_name = self.
|
|
319
|
+
model_name = self.model_name
|
|
272
320
|
if model_name == "gpt-3.5-turbo-0301":
|
|
273
321
|
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
|
|
274
322
|
tokens_per_name = -1 # if there's a name, the role is omitted
|
|
@@ -297,10 +345,10 @@ class OpenAIModel(BaseEvalModel):
|
|
|
297
345
|
def supports_function_calling(self) -> bool:
|
|
298
346
|
if (
|
|
299
347
|
self._is_azure
|
|
300
|
-
and self.
|
|
348
|
+
and self.api_version
|
|
301
349
|
# The first api version supporting function calling is 2023-07-01-preview.
|
|
302
350
|
# See https://github.com/Azure/azure-rest-api-specs/blob/58e92dd03733bc175e6a9540f4bc53703b57fcc9/specification/cognitiveservices/data-plane/AzureOpenAI/inference/preview/2023-07-01-preview/inference.json#L895 # noqa E501
|
|
303
|
-
and self.
|
|
351
|
+
and self.api_version[:10] < "2023-07-01"
|
|
304
352
|
):
|
|
305
353
|
return False
|
|
306
354
|
if self._model_uses_legacy_completion_api:
|
|
@@ -75,19 +75,22 @@ def classify_relevance(query: str, document: str, model_name: str) -> Optional[b
|
|
|
75
75
|
unparseable output.
|
|
76
76
|
"""
|
|
77
77
|
|
|
78
|
-
from openai import
|
|
78
|
+
from openai import OpenAI
|
|
79
|
+
|
|
80
|
+
client = OpenAI()
|
|
79
81
|
|
|
80
82
|
prompt = _QUERY_CONTEXT_PROMPT_TEMPLATE.format(
|
|
81
83
|
query=query,
|
|
82
84
|
reference=document,
|
|
83
85
|
)
|
|
84
|
-
response =
|
|
86
|
+
response = client.chat.completions.create(
|
|
85
87
|
messages=[
|
|
86
88
|
{"role": "system", "content": _EVALUATION_SYSTEM_MESSAGE},
|
|
87
89
|
{"role": "user", "content": prompt},
|
|
88
90
|
],
|
|
89
91
|
model=model_name,
|
|
90
92
|
)
|
|
91
|
-
|
|
93
|
+
|
|
94
|
+
raw_response_text = str(response.choices[0].message.content).strip()
|
|
92
95
|
relevance_classification = {"relevant": True, "irrelevant": False}.get(raw_response_text)
|
|
93
96
|
return relevance_classification
|
|
@@ -10,9 +10,10 @@ from typing import (
|
|
|
10
10
|
List,
|
|
11
11
|
Mapping,
|
|
12
12
|
Optional,
|
|
13
|
-
cast,
|
|
14
13
|
)
|
|
15
14
|
|
|
15
|
+
from typing_extensions import TypeGuard
|
|
16
|
+
|
|
16
17
|
from phoenix.trace.schemas import (
|
|
17
18
|
SpanAttributes,
|
|
18
19
|
SpanEvent,
|
|
@@ -44,7 +45,7 @@ from phoenix.trace.utils import get_stacktrace, import_package
|
|
|
44
45
|
from ..tracer import Tracer
|
|
45
46
|
|
|
46
47
|
if TYPE_CHECKING:
|
|
47
|
-
from openai.
|
|
48
|
+
from openai.types.chat import ChatCompletion
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
Parameters = Mapping[str, Any]
|
|
@@ -75,21 +76,21 @@ class OpenAIInstrumentor:
|
|
|
75
76
|
"""
|
|
76
77
|
openai = import_package("openai")
|
|
77
78
|
is_instrumented = hasattr(
|
|
78
|
-
openai.
|
|
79
|
+
openai.OpenAI,
|
|
79
80
|
INSTRUMENTED_ATTRIBUTE_NAME,
|
|
80
81
|
)
|
|
81
82
|
if not is_instrumented:
|
|
82
|
-
openai.
|
|
83
|
-
openai.
|
|
83
|
+
openai.OpenAI.request = _wrapped_openai_client_request_function(
|
|
84
|
+
openai.OpenAI.request, self._tracer
|
|
84
85
|
)
|
|
85
86
|
setattr(
|
|
86
|
-
openai.
|
|
87
|
+
openai.OpenAI,
|
|
87
88
|
INSTRUMENTED_ATTRIBUTE_NAME,
|
|
88
89
|
True,
|
|
89
90
|
)
|
|
90
91
|
|
|
91
92
|
|
|
92
|
-
def
|
|
93
|
+
def _wrapped_openai_client_request_function(
|
|
93
94
|
request_fn: Callable[..., Any], tracer: Tracer
|
|
94
95
|
) -> Callable[..., Any]:
|
|
95
96
|
"""Wraps the OpenAI APIRequestor.request method to create spans for each API call.
|
|
@@ -105,9 +106,10 @@ def _wrap_openai_api_requestor(
|
|
|
105
106
|
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
|
106
107
|
call_signature = signature(request_fn)
|
|
107
108
|
bound_arguments = call_signature.bind(*args, **kwargs)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
is_streaming = bound_arguments.arguments["stream"]
|
|
110
|
+
options = bound_arguments.arguments["options"]
|
|
111
|
+
parameters = options.json_data
|
|
112
|
+
url = options.url
|
|
111
113
|
current_status_code = SpanStatusCode.UNSET
|
|
112
114
|
events: List[SpanEvent] = []
|
|
113
115
|
attributes: SpanAttributes = dict()
|
|
@@ -118,13 +120,13 @@ def _wrap_openai_api_requestor(
|
|
|
118
120
|
) in _PARAMETER_ATTRIBUTE_FUNCTIONS.items():
|
|
119
121
|
if (attribute_value := get_parameter_attribute_fn(parameters)) is not None:
|
|
120
122
|
attributes[attribute_name] = attribute_value
|
|
121
|
-
|
|
123
|
+
response = None
|
|
122
124
|
try:
|
|
123
125
|
start_time = datetime.now()
|
|
124
|
-
|
|
126
|
+
response = request_fn(*args, **kwargs)
|
|
125
127
|
end_time = datetime.now()
|
|
126
128
|
current_status_code = SpanStatusCode.OK
|
|
127
|
-
return
|
|
129
|
+
return response
|
|
128
130
|
except Exception as error:
|
|
129
131
|
end_time = datetime.now()
|
|
130
132
|
current_status_code = SpanStatusCode.ERROR
|
|
@@ -138,16 +140,17 @@ def _wrap_openai_api_requestor(
|
|
|
138
140
|
)
|
|
139
141
|
raise
|
|
140
142
|
finally:
|
|
141
|
-
if
|
|
142
|
-
response = outputs[0]
|
|
143
|
+
if _is_chat_completion(response):
|
|
143
144
|
for (
|
|
144
145
|
attribute_name,
|
|
145
|
-
|
|
146
|
-
) in
|
|
147
|
-
if (
|
|
146
|
+
get_chat_completion_attribute_fn,
|
|
147
|
+
) in _CHAT_COMPLETION_ATTRIBUTE_FUNCTIONS.items():
|
|
148
|
+
if (
|
|
149
|
+
attribute_value := get_chat_completion_attribute_fn(response)
|
|
150
|
+
) is not None:
|
|
148
151
|
attributes[attribute_name] = attribute_value
|
|
149
152
|
tracer.create_span(
|
|
150
|
-
name="
|
|
153
|
+
name="OpenAI Chat Completion",
|
|
151
154
|
span_kind=SpanKind.LLM,
|
|
152
155
|
start_time=start_time,
|
|
153
156
|
end_time=end_time,
|
|
@@ -182,48 +185,46 @@ def _llm_invocation_parameters(
|
|
|
182
185
|
return json.dumps(parameters)
|
|
183
186
|
|
|
184
187
|
|
|
185
|
-
def _output_value(
|
|
186
|
-
return json
|
|
188
|
+
def _output_value(chat_completion: "ChatCompletion") -> str:
|
|
189
|
+
return chat_completion.json()
|
|
187
190
|
|
|
188
191
|
|
|
189
192
|
def _output_mime_type(_: Any) -> MimeType:
|
|
190
193
|
return MimeType.JSON
|
|
191
194
|
|
|
192
195
|
|
|
193
|
-
def _llm_output_messages(
|
|
196
|
+
def _llm_output_messages(chat_completion: "ChatCompletion") -> List[OpenInferenceMessage]:
|
|
194
197
|
return [
|
|
195
|
-
_to_openinference_message(choice
|
|
196
|
-
for choice in
|
|
198
|
+
_to_openinference_message(choice.message.dict(), expects_name=False)
|
|
199
|
+
for choice in chat_completion.choices
|
|
197
200
|
]
|
|
198
201
|
|
|
199
202
|
|
|
200
|
-
def _llm_token_count_prompt(
|
|
201
|
-
if
|
|
202
|
-
return
|
|
203
|
+
def _llm_token_count_prompt(chat_completion: "ChatCompletion") -> Optional[int]:
|
|
204
|
+
if completion_usage := chat_completion.usage:
|
|
205
|
+
return completion_usage.prompt_tokens
|
|
203
206
|
return None
|
|
204
207
|
|
|
205
208
|
|
|
206
|
-
def _llm_token_count_completion(
|
|
207
|
-
if
|
|
208
|
-
return
|
|
209
|
+
def _llm_token_count_completion(chat_completion: "ChatCompletion") -> Optional[int]:
|
|
210
|
+
if completion_usage := chat_completion.usage:
|
|
211
|
+
return completion_usage.completion_tokens
|
|
209
212
|
return None
|
|
210
213
|
|
|
211
214
|
|
|
212
|
-
def _llm_token_count_total(
|
|
213
|
-
if
|
|
214
|
-
return
|
|
215
|
+
def _llm_token_count_total(chat_completion: "ChatCompletion") -> Optional[int]:
|
|
216
|
+
if completion_usage := chat_completion.usage:
|
|
217
|
+
return completion_usage.total_tokens
|
|
215
218
|
return None
|
|
216
219
|
|
|
217
220
|
|
|
218
221
|
def _llm_function_call(
|
|
219
|
-
|
|
222
|
+
chat_completion: "ChatCompletion",
|
|
220
223
|
) -> Optional[str]:
|
|
221
|
-
choices =
|
|
224
|
+
choices = chat_completion.choices
|
|
222
225
|
choice = choices[0]
|
|
223
|
-
if choice.
|
|
224
|
-
|
|
225
|
-
):
|
|
226
|
-
return json.dumps(function_call_data)
|
|
226
|
+
if choice.finish_reason == "function_call" and (function_call := choice.message.function_call):
|
|
227
|
+
return function_call.json()
|
|
227
228
|
return None
|
|
228
229
|
|
|
229
230
|
|
|
@@ -274,7 +275,7 @@ _PARAMETER_ATTRIBUTE_FUNCTIONS: Dict[str, Callable[[Parameters], Any]] = {
|
|
|
274
275
|
LLM_INPUT_MESSAGES: _llm_input_messages,
|
|
275
276
|
LLM_INVOCATION_PARAMETERS: _llm_invocation_parameters,
|
|
276
277
|
}
|
|
277
|
-
|
|
278
|
+
_CHAT_COMPLETION_ATTRIBUTE_FUNCTIONS: Dict[str, Callable[["ChatCompletion"], Any]] = {
|
|
278
279
|
OUTPUT_VALUE: _output_value,
|
|
279
280
|
OUTPUT_MIME_TYPE: _output_mime_type,
|
|
280
281
|
LLM_OUTPUT_MESSAGES: _llm_output_messages,
|
|
@@ -283,3 +284,11 @@ _RESPONSE_ATTRIBUTE_FUNCTIONS: Dict[str, Callable[["OpenAIResponse"], Any]] = {
|
|
|
283
284
|
LLM_TOKEN_COUNT_TOTAL: _llm_token_count_total,
|
|
284
285
|
LLM_FUNCTION_CALL: _llm_function_call,
|
|
285
286
|
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _is_chat_completion(response: Any) -> TypeGuard["ChatCompletion"]:
|
|
290
|
+
"""
|
|
291
|
+
Type guard for ChatCompletion.
|
|
292
|
+
"""
|
|
293
|
+
openai = import_package("openai")
|
|
294
|
+
return isinstance(response, openai.types.chat.ChatCompletion)
|
|
File without changes
|