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

@@ -159,9 +159,9 @@ class Session(ABC):
159
159
  )
160
160
  if predicate:
161
161
  spans = filter(predicate, spans)
162
- if not (data := list(map(json.loads, map(span_to_json, spans)))):
162
+ if not (data := [json.loads(span_to_json(span)) for span in spans]):
163
163
  return None
164
- return pd.json_normalize(data).set_index("context.span_id", drop=False)
164
+ return pd.json_normalize(data, max_level=1).set_index("context.span_id", drop=False)
165
165
 
166
166
 
167
167
  _session: Optional[Session] = None
@@ -319,6 +319,9 @@ def launch_app(
319
319
  )
320
320
  _session.end()
321
321
 
322
+ host = host or get_env_host()
323
+ port = port or get_env_port()
324
+
322
325
  if run_in_thread:
323
326
  _session = ThreadSession(primary, reference, corpus, trace, host=host, port=port)
324
327
  # TODO: catch exceptions from thread
phoenix/trace/fixtures.py CHANGED
@@ -22,7 +22,7 @@ llama_index_rag_fixture = TracesFixture(
22
22
  llama_index_calculator_agent = TracesFixture(
23
23
  name="llama_index_calculator_agent",
24
24
  description="Traces from running the llama_index with calculator tools.",
25
- file_name="llama_index_calculator_agent.jsonl",
25
+ file_name="llama_index_calculator_agent_v2.jsonl",
26
26
  )
27
27
 
28
28
  llama_index_rag_fixture_with_davinci = TracesFixture(
@@ -37,6 +37,12 @@ langchain_rag_stuff_document_chain_fixture = TracesFixture(
37
37
  file_name="langchain_rag.jsonl",
38
38
  )
39
39
 
40
+ langchain_titanic_csv_agent_evaluator_fixture = TracesFixture(
41
+ name="lc_titanic",
42
+ description="LangChain titanic.csv Agent Evaluator",
43
+ file_name="lc_titanic.jsonl",
44
+ )
45
+
40
46
  random_fixture = TracesFixture(
41
47
  name="random",
42
48
  description="Randomly generated traces",
@@ -47,6 +53,7 @@ TRACES_FIXTURES: List[TracesFixture] = [
47
53
  llama_index_rag_fixture,
48
54
  llama_index_rag_fixture_with_davinci,
49
55
  langchain_rag_stuff_document_chain_fixture,
56
+ langchain_titanic_csv_agent_evaluator_fixture,
50
57
  random_fixture,
51
58
  llama_index_calculator_agent,
52
59
  ]
@@ -79,7 +86,6 @@ def _download_traces_fixture(
79
86
  Downloads the traces fixture from the phoenix bucket.
80
87
  """
81
88
  url = f"{host}{bucket}/{prefix}{fixture.file_name}"
82
- print(url)
83
89
  with request.urlopen(url) as f:
84
90
  return cast(List[str], f.readlines())
85
91
 
@@ -2,10 +2,20 @@ import json
2
2
  import logging
3
3
  from copy import deepcopy
4
4
  from datetime import datetime
5
- from typing import Any, Dict, Iterator, List, Optional, Tuple
5
+ from typing import (
6
+ Any,
7
+ Dict,
8
+ Iterator,
9
+ List,
10
+ Optional,
11
+ Tuple,
12
+ )
13
+ from uuid import UUID
6
14
 
7
15
  from langchain.callbacks.tracers.base import BaseTracer
8
16
  from langchain.callbacks.tracers.schemas import Run
17
+ from langchain.load.dump import dumpd
18
+ from langchain.schema.messages import BaseMessage
9
19
 
10
20
  from phoenix.trace.exporter import HttpExporter
11
21
  from phoenix.trace.schemas import (
@@ -22,6 +32,7 @@ from phoenix.trace.semantic_conventions import (
22
32
  INPUT_VALUE,
23
33
  LLM_FUNCTION_CALL,
24
34
  LLM_INVOCATION_PARAMETERS,
35
+ LLM_MESSAGES,
25
36
  LLM_MODEL_NAME,
26
37
  LLM_PROMPT_TEMPLATE,
27
38
  LLM_PROMPT_TEMPLATE_VARIABLES,
@@ -30,6 +41,8 @@ from phoenix.trace.semantic_conventions import (
30
41
  LLM_TOKEN_COUNT_COMPLETION,
31
42
  LLM_TOKEN_COUNT_PROMPT,
32
43
  LLM_TOKEN_COUNT_TOTAL,
44
+ MESSAGE_CONTENT,
45
+ MESSAGE_ROLE,
33
46
  OUTPUT_MIME_TYPE,
34
47
  OUTPUT_VALUE,
35
48
  RETRIEVAL_DOCUMENTS,
@@ -42,6 +55,9 @@ from phoenix.trace.tracer import Tracer
42
55
  logger = logging.getLogger(__name__)
43
56
 
44
57
 
58
+ Message = Dict[str, Any]
59
+
60
+
45
61
  def _langchain_run_type_to_span_kind(run_type: str) -> SpanKind:
46
62
  # TODO: LangChain is moving away from enums and to arbitrary strings
47
63
  # for the run_type variable, so we may need to do the same
@@ -75,6 +91,36 @@ def _prompts(run_inputs: Dict[str, Any]) -> Iterator[Tuple[str, List[str]]]:
75
91
  yield LLM_PROMPTS, run_inputs["prompts"]
76
92
 
77
93
 
94
+ def _messages(run_inputs: Dict[str, Any]) -> Iterator[Tuple[str, List[Message]]]:
95
+ """Yields chat messages if present."""
96
+ if "messages" in run_inputs:
97
+ yield LLM_MESSAGES, [
98
+ _parse_message_data(message_data) for message_data in run_inputs["messages"][0]
99
+ ]
100
+
101
+
102
+ def _parse_message_data(message_data: Dict[str, Any]) -> Message:
103
+ """Parses message data to grab message role, content, etc."""
104
+ message_class_name = message_data["id"][-1]
105
+ if message_class_name == "HumanMessage":
106
+ role = "user"
107
+ elif message_class_name == "AIMessage":
108
+ role = "assistant"
109
+ elif message_class_name == "SystemMessage":
110
+ role = "system"
111
+ elif message_class_name == "FunctionMessage":
112
+ role = "function"
113
+ elif message_class_name == "ChatMessage":
114
+ role = message_data["kwargs"]["role"]
115
+ else:
116
+ raise ValueError(f"Cannot parse message of type: {message_class_name}")
117
+ parsed_message_data = {
118
+ MESSAGE_ROLE: role,
119
+ MESSAGE_CONTENT: message_data["kwargs"]["content"],
120
+ }
121
+ return parsed_message_data
122
+
123
+
78
124
  def _prompt_template(run_serialized: Dict[str, Any]) -> Iterator[Tuple[str, Any]]:
79
125
  """
80
126
  A best-effort attempt to locate the PromptTemplate object among the
@@ -187,6 +233,7 @@ class OpenInferenceTracer(Tracer, BaseTracer):
187
233
  }.items():
188
234
  attributes.update(zip(io_attributes, _convert_io(run.get(io_key))))
189
235
  attributes.update(_prompts(run["inputs"]))
236
+ attributes.update(_messages(run["inputs"]))
190
237
  attributes.update(_prompt_template(run["serialized"]))
191
238
  attributes.update(_invocation_parameters(run))
192
239
  attributes.update(_model_name(run["extra"]))
@@ -213,9 +260,14 @@ class OpenInferenceTracer(Tracer, BaseTracer):
213
260
  timestamp=error_event["time"],
214
261
  )
215
262
  )
263
+ span_kind = (
264
+ SpanKind.AGENT
265
+ if "agent" in run["name"].lower()
266
+ else _langchain_run_type_to_span_kind(run["run_type"])
267
+ )
216
268
  span = self.create_span(
217
269
  name=run["name"],
218
- span_kind=_langchain_run_type_to_span_kind(run["run_type"]),
270
+ span_kind=span_kind,
219
271
  parent_id=None if parent is None else parent.context.span_id,
220
272
  trace_id=None if parent is None else parent.context.trace_id,
221
273
  start_time=run["start_time"],
@@ -234,3 +286,43 @@ class OpenInferenceTracer(Tracer, BaseTracer):
234
286
  self._convert_run_to_spans(run.dict())
235
287
  except Exception:
236
288
  logger.exception("Failed to convert run to spans")
289
+
290
+ def on_chat_model_start(
291
+ self,
292
+ serialized: Dict[str, Any],
293
+ messages: List[List[BaseMessage]],
294
+ *,
295
+ run_id: UUID,
296
+ tags: Optional[List[str]] = None,
297
+ parent_run_id: Optional[UUID] = None,
298
+ metadata: Optional[Dict[str, Any]] = None,
299
+ **kwargs: Any,
300
+ ) -> None:
301
+ """
302
+ Adds chat messages to the run inputs.
303
+
304
+ LangChain's BaseTracer class does not implement hooks for chat models and hence does not
305
+ record data such as the list of messages that were passed to the chat model.
306
+
307
+ For reference, see https://github.com/langchain-ai/langchain/pull/4499.
308
+ """
309
+
310
+ parent_run_id_ = str(parent_run_id) if parent_run_id else None
311
+ execution_order = self._get_execution_order(parent_run_id_)
312
+ start_time = datetime.utcnow()
313
+ if metadata:
314
+ kwargs.update({"metadata": metadata})
315
+ run = Run(
316
+ id=run_id,
317
+ parent_run_id=parent_run_id,
318
+ serialized=serialized,
319
+ inputs={"messages": [[dumpd(message) for message in batch] for batch in messages]},
320
+ extra=kwargs,
321
+ events=[{"name": "start", "time": start_time}],
322
+ start_time=start_time,
323
+ execution_order=execution_order,
324
+ child_execution_order=execution_order,
325
+ run_type="llm",
326
+ tags=tags,
327
+ )
328
+ self._start_trace(run)
@@ -25,7 +25,6 @@ from llama_index.callbacks.schema import (
25
25
  )
26
26
  from llama_index.llms.base import ChatMessage, ChatResponse
27
27
  from llama_index.tools import ToolMetadata
28
- from openai.openai_object import OpenAIObject
29
28
 
30
29
  from phoenix.trace.exporter import HttpExporter
31
30
  from phoenix.trace.schemas import Span, SpanID, SpanKind, SpanStatusCode
@@ -43,6 +42,8 @@ from phoenix.trace.semantic_conventions import (
43
42
  LLM_INVOCATION_PARAMETERS,
44
43
  LLM_MESSAGES,
45
44
  LLM_MODEL_NAME,
45
+ LLM_PROMPT_TEMPLATE,
46
+ LLM_PROMPT_TEMPLATE_VARIABLES,
46
47
  LLM_PROMPTS,
47
48
  LLM_TOKEN_COUNT_COMPLETION,
48
49
  LLM_TOKEN_COUNT_PROMPT,
@@ -88,6 +89,14 @@ def payload_to_semantic_attributes(
88
89
  if event_type in (CBEventType.NODE_PARSING, CBEventType.CHUNKING):
89
90
  # TODO(maybe): handle these events
90
91
  return attributes
92
+ if event_type == CBEventType.TEMPLATING:
93
+ if template := payload.get(EventPayload.TEMPLATE):
94
+ attributes[LLM_PROMPT_TEMPLATE] = template
95
+ if template_vars := payload.get(EventPayload.TEMPLATE_VARS):
96
+ attributes[LLM_PROMPT_TEMPLATE_VARIABLES] = template_vars
97
+ # TODO(maybe): other keys in the same payload
98
+ # EventPayload.SYSTEM_PROMPT
99
+ # EventPayload.QUERY_WRAPPER_PROMPT
91
100
  if EventPayload.CHUNKS in payload and EventPayload.EMBEDDINGS in payload:
92
101
  attributes[EMBEDDING_EMBEDDINGS] = [
93
102
  {EMBEDDING_TEXT: text, EMBEDDING_VECTOR: vector}
@@ -120,14 +129,14 @@ def payload_to_semantic_attributes(
120
129
  # akin to the query_str
121
130
  attributes[INPUT_VALUE] = _message_payload_to_str(messages[0])
122
131
  if response := (payload.get(EventPayload.RESPONSE) or payload.get(EventPayload.COMPLETION)):
123
- attributes[OUTPUT_VALUE] = _get_response_content(response)
124
- attributes[OUTPUT_MIME_TYPE] = MimeType.TEXT
125
- if raw := getattr(response, "raw", None):
126
- if isinstance(raw, OpenAIObject):
127
- usage = raw.usage
128
- attributes[LLM_TOKEN_COUNT_PROMPT] = usage.prompt_tokens
129
- attributes[LLM_TOKEN_COUNT_COMPLETION] = usage.completion_tokens
130
- attributes[LLM_TOKEN_COUNT_TOTAL] = usage.total_tokens
132
+ attributes.update(_get_response_output(response))
133
+ if (raw := getattr(response, "raw", None)) and (usage := getattr(raw, "usage", None)):
134
+ if prompt_tokens := getattr(usage, "prompt_tokens", None):
135
+ attributes[LLM_TOKEN_COUNT_PROMPT] = prompt_tokens
136
+ if completion_tokens := getattr(usage, "completion_tokens", None):
137
+ attributes[LLM_TOKEN_COUNT_COMPLETION] = completion_tokens
138
+ if total_tokens := getattr(usage, "total_tokens", None):
139
+ attributes[LLM_TOKEN_COUNT_TOTAL] = total_tokens
131
140
  if EventPayload.TEMPLATE in payload:
132
141
  ...
133
142
  if event_type is CBEventType.RERANKING:
@@ -354,11 +363,21 @@ def _message_payload_to_str(message: Any) -> Optional[str]:
354
363
  return str(message)
355
364
 
356
365
 
357
- def _get_response_content(response: Any) -> str:
366
+ def _get_response_output(response: Any) -> Iterator[Tuple[str, Any]]:
358
367
  """
359
- Gets content from response objects. This is needed since the string representation of some
360
- response objects includes extra information in addition to the content itself.
368
+ Gets output from response objects. This is needed since the string representation of some
369
+ response objects includes extra information in addition to the content itself. In the
370
+ case of an agent's ChatResponse the output may be a `function_call` object specifying
371
+ the name of the function to call and the arguments to call it with.
361
372
  """
362
373
  if isinstance(response, ChatResponse):
363
- return response.message.content or ""
364
- return str(response)
374
+ message = response.message
375
+ if content := message.content:
376
+ yield OUTPUT_VALUE, content
377
+ yield OUTPUT_MIME_TYPE, MimeType.TEXT
378
+ else:
379
+ yield OUTPUT_VALUE, json.dumps(message.additional_kwargs)
380
+ yield OUTPUT_MIME_TYPE, MimeType.JSON
381
+ else:
382
+ yield OUTPUT_VALUE, str(response)
383
+ yield OUTPUT_MIME_TYPE, MimeType.TEXT
@@ -70,7 +70,12 @@ class TraceDataset:
70
70
  Returns:
71
71
  TraceDataset: A TraceDataset containing the spans.
72
72
  """
73
- return cls(pd.json_normalize(map(json.loads, map(span_to_json, spans)))) # type: ignore
73
+ return cls(
74
+ pd.json_normalize(
75
+ (json.loads(span_to_json(span)) for span in spans), # type: ignore
76
+ max_level=1,
77
+ )
78
+ )
74
79
 
75
80
  def to_spans(self) -> Iterator[Span]:
76
81
  for _, row in self.dataframe.iterrows():
phoenix/trace/utils.py CHANGED
@@ -15,5 +15,5 @@ def json_lines_to_df(lines: List[str]) -> pd.DataFrame:
15
15
  data.append(json.loads(line))
16
16
 
17
17
  # Normalize data to a flat structure
18
- df = pd.concat([pd.json_normalize(item) for item in data], ignore_index=True)
18
+ df = pd.concat([pd.json_normalize(item, max_level=1) for item in data], ignore_index=True)
19
19
  return df
@@ -1,222 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: arize-phoenix
3
- Version: 0.0.37
4
- Summary: ML Observability in your notebook
5
- Project-URL: Documentation, https://docs.arize.com/phoenix/
6
- Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
7
- Project-URL: Source, https://github.com/Arize-ai/phoenix
8
- Author-email: Arize AI <phoenix-devs@arize.com>
9
- License-Expression: Elastic-2.0
10
- License-File: IP_NOTICE
11
- License-File: LICENSE
12
- Keywords: Explainability,Monitoring,Observability
13
- Classifier: Programming Language :: Python
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Requires-Python: <3.12,>=3.8
19
- Requires-Dist: hdbscan<1.0.0,>=0.8.33
20
- Requires-Dist: numpy
21
- Requires-Dist: pandas
22
- Requires-Dist: protobuf<5.0,>=3.20
23
- Requires-Dist: psutil
24
- Requires-Dist: pyarrow
25
- Requires-Dist: scikit-learn<1.3.0
26
- Requires-Dist: scipy
27
- Requires-Dist: sortedcontainers
28
- Requires-Dist: starlette
29
- Requires-Dist: strawberry-graphql==0.205.0
30
- Requires-Dist: typing-extensions
31
- Requires-Dist: umap-learn
32
- Requires-Dist: uvicorn
33
- Requires-Dist: wrapt
34
- Provides-Extra: dev
35
- Requires-Dist: arize[autoembeddings,llm-evaluation]; extra == 'dev'
36
- Requires-Dist: black[jupyter]; extra == 'dev'
37
- Requires-Dist: gcsfs; extra == 'dev'
38
- Requires-Dist: hatch; extra == 'dev'
39
- Requires-Dist: jupyter; extra == 'dev'
40
- Requires-Dist: nbqa; extra == 'dev'
41
- Requires-Dist: pandas-stubs<=2.0.2.230605; extra == 'dev'
42
- Requires-Dist: pre-commit; extra == 'dev'
43
- Requires-Dist: pytest; extra == 'dev'
44
- Requires-Dist: pytest-cov; extra == 'dev'
45
- Requires-Dist: pytest-lazy-fixture; extra == 'dev'
46
- Requires-Dist: ruff==0.0.290; extra == 'dev'
47
- Requires-Dist: strawberry-graphql[debug-server]==0.205.0; extra == 'dev'
48
- Provides-Extra: experimental
49
- Requires-Dist: openai; extra == 'experimental'
50
- Requires-Dist: tenacity; extra == 'experimental'
51
- Provides-Extra: langchain
52
- Requires-Dist: langchain>=0.0.257; extra == 'langchain'
53
- Provides-Extra: llama-index
54
- Requires-Dist: llama-index>=0.8.25; extra == 'llama-index'
55
- Requires-Dist: openai; extra == 'llama-index'
56
- Description-Content-Type: text/markdown
57
-
58
- <p align="center">
59
- <a target="_blank" href="https://phoenix.arize.com" style="background:none">
60
- <img alt="phoenix logo" src="https://storage.googleapis.com/arize-assets/phoenix/assets/phoenix-logo-light.svg" width="auto" height="200"></img>
61
- </a>
62
- <br/>
63
- <br/>
64
- <a href="https://docs.arize.com/phoenix/">
65
- <img src="https://img.shields.io/static/v1?message=Docs&logo=data:image/png;base64,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&labelColor=grey&color=blue&logoColor=white&label=%20"/>
66
- </a>
67
- <a target="_blank" href="https://join.slack.com/t/arize-ai/shared_invite/zt-1px8dcmlf-fmThhDFD_V_48oU7ALan4Q">
68
- <img src="https://img.shields.io/static/v1?message=Community&logo=slack&labelColor=grey&color=blue&logoColor=white&label=%20"/>
69
- </a>
70
- <a target="_blank" href="https://twitter.com/ArizePhoenix">
71
- <img src="https://img.shields.io/badge/-ArizePhoenix-blue.svg?color=blue&labelColor=gray&logo=twitter">
72
- </a>
73
- <a target="_blank" href="https://pypi.org/project/arize-phoenix/">
74
- <img src="https://img.shields.io/pypi/v/arize-phoenix?color=blue">
75
- </a>
76
- <a target="_blank" href="https://anaconda.org/conda-forge/arize-phoenix">
77
- <img src="https://img.shields.io/conda/vn/conda-forge/arize-phoenix.svg?color=blue">
78
- </a>
79
- <a target="_blank" href="https://pypi.org/project/arize-phoenix/">
80
- <img src="https://img.shields.io/pypi/pyversions/arize-phoenix">
81
- </a>
82
- </p>
83
-
84
- Phoenix provides MLOps insights at lightning speed with zero-config observability for model drift, performance, and data quality. Phoenix is notebook-first python library that leverages embeddings to uncover problematic cohorts of your LLM, CV, NLP and tabular models.
85
-
86
- <!-- EXCLUDE -->
87
-
88
- ![a rotating UMAP point cloud of a computer vision model](https://github.com/Arize-ai/phoenix-assets/blob/main/gifs/image_classification_10mb.gif?raw=true)
89
-
90
- <!-- /EXCLUDE -->
91
-
92
- ## Installation
93
-
94
- ```shell
95
- pip install arize-phoenix
96
- ```
97
-
98
- ## Quickstart
99
-
100
- [![Open in Colab](https://img.shields.io/static/v1?message=Open%20in%20Colab&logo=googlecolab&labelColor=grey&color=blue&logoColor=orange&label=%20)](https://colab.research.google.com/github/Arize-ai/phoenix/blob/main/tutorials/image_classification_tutorial.ipynb) [![Open in GitHub](https://img.shields.io/static/v1?message=Open%20in%20GitHub&logo=github&labelColor=grey&color=blue&logoColor=white&label=%20)](https://github.com/Arize-ai/phoenix/blob/main/tutorials/image_classification_tutorial.ipynb)
101
-
102
- Import libraries.
103
-
104
- ```python
105
- from dataclasses import replace
106
- import pandas as pd
107
- import phoenix as px
108
- ```
109
-
110
- Download curated datasets and load them into pandas DataFrames.
111
-
112
- ```python
113
- train_df = pd.read_parquet(
114
- "https://storage.googleapis.com/arize-assets/phoenix/datasets/unstructured/cv/human-actions/human_actions_training.parquet"
115
- )
116
- prod_df = pd.read_parquet(
117
- "https://storage.googleapis.com/arize-assets/phoenix/datasets/unstructured/cv/human-actions/human_actions_production.parquet"
118
- )
119
- ```
120
-
121
- Define schemas that tell Phoenix which columns of your DataFrames correspond to features, predictions, actuals (i.e., ground truth), embeddings, etc.
122
-
123
- ```python
124
- train_schema = px.Schema(
125
- prediction_id_column_name="prediction_id",
126
- timestamp_column_name="prediction_ts",
127
- prediction_label_column_name="predicted_action",
128
- actual_label_column_name="actual_action",
129
- embedding_feature_column_names={
130
- "image_embedding": px.EmbeddingColumnNames(
131
- vector_column_name="image_vector",
132
- link_to_data_column_name="url",
133
- ),
134
- },
135
- )
136
- prod_schema = replace(train_schema, actual_label_column_name=None)
137
- ```
138
-
139
- Define your production and training datasets.
140
-
141
- ```python
142
- prod_ds = px.Dataset(prod_df, prod_schema)
143
- train_ds = px.Dataset(train_df, train_schema)
144
- ```
145
-
146
- Launch the app.
147
-
148
- ```python
149
- session = px.launch_app(prod_ds, train_ds)
150
- ```
151
-
152
- You can open Phoenix by copying and pasting the output of `session.url` into a new browser tab.
153
-
154
- ```python
155
- session.url
156
- ```
157
-
158
- Alternatively, you can open the Phoenix UI in your notebook with
159
-
160
- ```python
161
- session.view()
162
- ```
163
-
164
- When you're done, don't forget to close the app.
165
-
166
- ```python
167
- px.close_app()
168
- ```
169
-
170
- ## Features
171
-
172
- ### Embedding Drift Analysis
173
-
174
- Explore UMAP point-clouds at times of high euclidean distance and identify clusters of drift.
175
-
176
- ![Euclidean distance drift analysis](https://storage.googleapis.com/arize-assets/phoenix/assets/images/ner_color_by_correctness.png)
177
-
178
- ### UMAP-based Exploratory Data Analysis
179
-
180
- Color your UMAP point-clouds by your model's dimensions, drift, and performance to identify problematic cohorts.
181
-
182
- ![UMAP-based EDA](https://storage.googleapis.com/arize-assets/phoenix/assets/images/cv_eda_selection.png)
183
-
184
- ### Cluster-driven Drift and Performance Analysis
185
-
186
- Break-apart your data into clusters of high drift or bad performance using HDBSCAN
187
-
188
- ![HDBSCAN clusters sorted by drift](https://storage.googleapis.com/arize-assets/phoenix/assets/images/HDBSCAN_drift_analysis.png)
189
-
190
- ### Exportable Clusters
191
-
192
- Export your clusters to `parquet` files or dataframes for further analysis and fine-tuning.
193
-
194
- ## Documentation
195
-
196
- For in-depth examples and explanations, read the [docs](https://docs.arize.com/phoenix).
197
-
198
- ## Community
199
-
200
- Join our community to connect with thousands of machine learning practitioners and ML observability enthusiasts.
201
-
202
- - 🌍 Join our [Slack community](https://join.slack.com/t/arize-ai/shared_invite/zt-1px8dcmlf-fmThhDFD_V_48oU7ALan4Q).
203
- - 💡 Ask questions and provide feedback in the _#phoenix-support_ channel.
204
- - 🌟 Leave a star on our [GitHub](https://github.com/Arize-ai/phoenix).
205
- - 🐞 Report bugs with [GitHub Issues](https://github.com/Arize-ai/phoenix/issues).
206
- - 🐣 Follow us on [twitter](https://twitter.com/ArizePhoenix).
207
- - 💌️ Sign up for our [mailing list](https://phoenix.arize.com/#updates).
208
- - 🗺️ Check out our [roadmap](https://github.com/orgs/Arize-ai/projects/45) to see where we're heading next.
209
- - 🎓 Learn the fundamentals of ML observability with our [introductory](https://arize.com/ml-observability-fundamentals/) and [advanced](https://arize.com/blog-course/) courses.
210
-
211
- ## Thanks
212
-
213
- - [UMAP](https://github.com/lmcinnes/umap) For unlocking the ability to visualize and reason about embeddings
214
- - [HDBSCAN](https://github.com/scikit-learn-contrib/hdbscan) For providing a clustering algorithm to aid in the discovery of drift and performance degradation
215
-
216
- ## Copyright, Patent, and License
217
-
218
- Copyright 2023 Arize AI, Inc. All Rights Reserved.
219
-
220
- Portions of this code are patent protected by one or more U.S. Patents. See [IP_NOTICE](https://github.com/Arize-ai/phoenix/blob/main/IP_NOTICE).
221
-
222
- This software is licensed under the terms of the Elastic License 2.0 (ELv2). See [LICENSE](https://github.com/Arize-ai/phoenix/blob/main/LICENSE).