arize-phoenix 3.4.0__py3-none-any.whl → 3.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- {arize_phoenix-3.4.0.dist-info → arize_phoenix-3.5.0.dist-info}/METADATA +31 -31
- {arize_phoenix-3.4.0.dist-info → arize_phoenix-3.5.0.dist-info}/RECORD +39 -38
- phoenix/core/evals.py +9 -9
- phoenix/core/model.py +18 -18
- phoenix/core/model_schema.py +22 -44
- phoenix/experimental/evals/functions/executor.py +1 -2
- phoenix/experimental/evals/functions/processing.py +33 -1
- phoenix/experimental/evals/models/base.py +4 -8
- phoenix/experimental/evals/models/litellm.py +1 -1
- phoenix/experimental/evals/models/rate_limiters.py +1 -2
- phoenix/metrics/__init__.py +2 -4
- phoenix/metrics/binning.py +3 -6
- phoenix/metrics/mixins.py +1 -0
- phoenix/metrics/wrappers.py +1 -0
- phoenix/pointcloud/pointcloud.py +2 -4
- phoenix/server/api/input_types/SpanSort.py +1 -2
- phoenix/server/api/interceptor.py +1 -2
- phoenix/server/api/routers/trace_handler.py +1 -2
- phoenix/server/api/schema.py +20 -3
- phoenix/server/api/types/Project.py +72 -0
- phoenix/server/api/types/Segments.py +2 -4
- phoenix/server/api/types/Span.py +18 -0
- phoenix/server/app.py +4 -0
- phoenix/server/main.py +35 -2
- phoenix/server/static/index.js +534 -494
- phoenix/server/templates/index.html +2 -1
- phoenix/session/data_extractor.py +2 -4
- phoenix/session/evaluation.py +1 -0
- phoenix/trace/dsl/filter.py +1 -2
- phoenix/trace/dsl/helpers.py +3 -2
- phoenix/trace/dsl/query.py +3 -7
- phoenix/trace/langchain/tracer.py +1 -0
- phoenix/trace/span_evaluations.py +1 -2
- phoenix/trace/span_json_encoder.py +13 -3
- phoenix/trace/tracer.py +2 -2
- phoenix/version.py +1 -1
- {arize_phoenix-3.4.0.dist-info → arize_phoenix-3.5.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-3.4.0.dist-info → arize_phoenix-3.5.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-3.4.0.dist-info → arize_phoenix-3.5.0.dist-info}/licenses/LICENSE +0 -0
phoenix/metrics/__init__.py
CHANGED
|
@@ -33,12 +33,10 @@ class Metric(ABC):
|
|
|
33
33
|
return self.initial_value
|
|
34
34
|
|
|
35
35
|
@abstractmethod
|
|
36
|
-
def calc(self, dataframe: pd.DataFrame) -> Any:
|
|
37
|
-
...
|
|
36
|
+
def calc(self, dataframe: pd.DataFrame) -> Any: ...
|
|
38
37
|
|
|
39
38
|
@abstractmethod
|
|
40
|
-
def operands(self) -> List[Column]:
|
|
41
|
-
...
|
|
39
|
+
def operands(self) -> List[Column]: ...
|
|
42
40
|
|
|
43
41
|
def __call__(
|
|
44
42
|
self,
|
phoenix/metrics/binning.py
CHANGED
|
@@ -25,8 +25,7 @@ class BinningMethod(ABC):
|
|
|
25
25
|
missing values will be grouped into a bin of their own)"""
|
|
26
26
|
|
|
27
27
|
@abstractmethod
|
|
28
|
-
def histogram(self, data: "pd.Series[Any]") -> Histogram:
|
|
29
|
-
...
|
|
28
|
+
def histogram(self, data: "pd.Series[Any]") -> Histogram: ...
|
|
30
29
|
|
|
31
30
|
@abstractmethod
|
|
32
31
|
def segmented_summary(
|
|
@@ -34,8 +33,7 @@ class BinningMethod(ABC):
|
|
|
34
33
|
group_by: Column,
|
|
35
34
|
dataframe: pd.DataFrame,
|
|
36
35
|
metrics: Iterable[Metric],
|
|
37
|
-
) -> pd.DataFrame:
|
|
38
|
-
...
|
|
36
|
+
) -> pd.DataFrame: ...
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
NumericBin: TypeAlias = "pd.Interval[float]"
|
|
@@ -296,8 +294,7 @@ class Normalizer(ABC):
|
|
|
296
294
|
"""A function that normalizes counts/frequencies to probabilities."""
|
|
297
295
|
|
|
298
296
|
@abstractmethod
|
|
299
|
-
def __call__(self, counts: Histogram) -> Distribution:
|
|
300
|
-
...
|
|
297
|
+
def __call__(self, counts: Histogram) -> Distribution: ...
|
|
301
298
|
|
|
302
299
|
|
|
303
300
|
@dataclass(frozen=True)
|
phoenix/metrics/mixins.py
CHANGED
|
@@ -3,6 +3,7 @@ Mixins are behavioral building blocks of metrics. All metrics inherit from
|
|
|
3
3
|
BaseMetric. Other mixins provide specialized functionalities. Mixins rely
|
|
4
4
|
on cooperative multiple inheritance and method resolution order in Python.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import collections
|
|
7
8
|
import inspect
|
|
8
9
|
from abc import ABC, abstractmethod
|
phoenix/metrics/wrappers.py
CHANGED
|
@@ -12,6 +12,7 @@ function, the wrappers need to perform a variety of preprocessing steps, e.g.
|
|
|
12
12
|
So, after the missing values are removed, we still need to coerce the dtype
|
|
13
13
|
to that of the first non-missing value in the series.
|
|
14
14
|
"""
|
|
15
|
+
|
|
15
16
|
import inspect
|
|
16
17
|
from abc import ABC
|
|
17
18
|
from enum import Enum
|
phoenix/pointcloud/pointcloud.py
CHANGED
|
@@ -14,13 +14,11 @@ RowIndex: TypeAlias = int
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class DimensionalityReducer(Protocol):
|
|
17
|
-
def project(self, mat: Matrix, n_components: int) -> Matrix:
|
|
18
|
-
...
|
|
17
|
+
def project(self, mat: Matrix, n_components: int) -> Matrix: ...
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
class ClustersFinder(Protocol):
|
|
22
|
-
def find_clusters(self, mat: Matrix) -> List[RawCluster]:
|
|
23
|
-
...
|
|
21
|
+
def find_clusters(self, mat: Matrix) -> List[RawCluster]: ...
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
@dataclass(frozen=True)
|
|
@@ -43,8 +43,7 @@ class EvalResultKey:
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
class SupportsGetSpanEvaluation(Protocol):
|
|
46
|
-
def get_span_evaluation(self, span_id: SpanID, name: str) -> Optional[pb.Evaluation]:
|
|
47
|
-
...
|
|
46
|
+
def get_span_evaluation(self, span_id: SpanID, name: str) -> Optional[pb.Evaluation]: ...
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
@strawberry.input(
|
|
@@ -18,8 +18,7 @@ class Interceptor(ABC):
|
|
|
18
18
|
return self if obj is None else getattr(obj, self._name)
|
|
19
19
|
|
|
20
20
|
@abstractmethod
|
|
21
|
-
def __set__(self, obj: Any, value: Any) -> None:
|
|
22
|
-
...
|
|
21
|
+
def __set__(self, obj: Any, value: Any) -> None: ...
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class GqlValueMediator(Interceptor):
|
phoenix/server/api/schema.py
CHANGED
|
@@ -20,6 +20,7 @@ from phoenix.server.api.input_types.Coordinates import (
|
|
|
20
20
|
)
|
|
21
21
|
from phoenix.server.api.input_types.SpanSort import SpanSort
|
|
22
22
|
from phoenix.server.api.types.Cluster import Cluster, to_gql_clusters
|
|
23
|
+
from phoenix.server.api.types.Project import Project
|
|
23
24
|
from phoenix.trace.dsl import SpanFilter
|
|
24
25
|
from phoenix.trace.schemas import SpanID, TraceID
|
|
25
26
|
|
|
@@ -48,6 +49,22 @@ from .types.ValidationResult import ValidationResult
|
|
|
48
49
|
|
|
49
50
|
@strawberry.type
|
|
50
51
|
class Query:
|
|
52
|
+
@strawberry.field
|
|
53
|
+
def projects(
|
|
54
|
+
self,
|
|
55
|
+
first: Optional[int] = 50,
|
|
56
|
+
last: Optional[int] = UNSET,
|
|
57
|
+
after: Optional[Cursor] = UNSET,
|
|
58
|
+
before: Optional[Cursor] = UNSET,
|
|
59
|
+
) -> Connection[Project]:
|
|
60
|
+
args = ConnectionArgs(
|
|
61
|
+
first=first,
|
|
62
|
+
after=after if isinstance(after, Cursor) else None,
|
|
63
|
+
last=last,
|
|
64
|
+
before=before if isinstance(before, Cursor) else None,
|
|
65
|
+
)
|
|
66
|
+
return connection_from_list(data=[Project(id_attr=0, name="default")], args=args)
|
|
67
|
+
|
|
51
68
|
@strawberry.field
|
|
52
69
|
def functionality(self, info: Info[Context, None]) -> "Functionality":
|
|
53
70
|
has_model_inferences = not info.context.model.is_empty
|
|
@@ -70,7 +87,8 @@ class Query:
|
|
|
70
87
|
elif type_name == "EmbeddingDimension":
|
|
71
88
|
embedding_dimension = info.context.model.embedding_dimensions[node_id]
|
|
72
89
|
return to_gql_embedding_dimension(node_id, embedding_dimension)
|
|
73
|
-
|
|
90
|
+
elif type_name == "Project":
|
|
91
|
+
return Project(id_attr=0, name="default") # TODO: implement ID
|
|
74
92
|
raise Exception(f"Unknown node type: {type}")
|
|
75
93
|
|
|
76
94
|
@strawberry.field
|
|
@@ -431,8 +449,7 @@ class Query:
|
|
|
431
449
|
|
|
432
450
|
|
|
433
451
|
@strawberry.type
|
|
434
|
-
class Mutation(ExportEventsMutation):
|
|
435
|
-
...
|
|
452
|
+
class Mutation(ExportEventsMutation): ...
|
|
436
453
|
|
|
437
454
|
|
|
438
455
|
schema = strawberry.Schema(query=Query, mutation=Mutation)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import strawberry
|
|
5
|
+
from strawberry.types import Info
|
|
6
|
+
|
|
7
|
+
from phoenix.server.api.context import Context
|
|
8
|
+
|
|
9
|
+
from .node import Node
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@strawberry.type
|
|
13
|
+
class Project(Node):
|
|
14
|
+
name: str
|
|
15
|
+
|
|
16
|
+
@strawberry.field
|
|
17
|
+
def start_time(
|
|
18
|
+
self,
|
|
19
|
+
info: Info[Context, None],
|
|
20
|
+
) -> Optional[datetime]:
|
|
21
|
+
if (traces := info.context.traces) is None:
|
|
22
|
+
return None
|
|
23
|
+
start_time, _ = traces.right_open_time_range
|
|
24
|
+
return start_time
|
|
25
|
+
|
|
26
|
+
@strawberry.field
|
|
27
|
+
def end_time(
|
|
28
|
+
self,
|
|
29
|
+
info: Info[Context, None],
|
|
30
|
+
) -> Optional[datetime]:
|
|
31
|
+
if (traces := info.context.traces) is None:
|
|
32
|
+
return None
|
|
33
|
+
_, end_time = traces.right_open_time_range
|
|
34
|
+
return end_time
|
|
35
|
+
|
|
36
|
+
@strawberry.field
|
|
37
|
+
def record_count(
|
|
38
|
+
self,
|
|
39
|
+
info: Info[Context, None],
|
|
40
|
+
) -> int:
|
|
41
|
+
if (traces := info.context.traces) is None:
|
|
42
|
+
return 0
|
|
43
|
+
return traces.span_count
|
|
44
|
+
|
|
45
|
+
@strawberry.field
|
|
46
|
+
def token_count_total(
|
|
47
|
+
self,
|
|
48
|
+
info: Info[Context, None],
|
|
49
|
+
) -> int:
|
|
50
|
+
if (traces := info.context.traces) is None:
|
|
51
|
+
return 0
|
|
52
|
+
return traces.token_count_total
|
|
53
|
+
|
|
54
|
+
@strawberry.field
|
|
55
|
+
def latency_ms_p50(
|
|
56
|
+
self,
|
|
57
|
+
info: Info[Context, None],
|
|
58
|
+
) -> Optional[float]:
|
|
59
|
+
if (traces := info.context.traces) is None:
|
|
60
|
+
return None
|
|
61
|
+
(latency,) = traces.root_span_latency_ms_quantiles(0.50)
|
|
62
|
+
return latency
|
|
63
|
+
|
|
64
|
+
@strawberry.field
|
|
65
|
+
def latency_ms_p99(
|
|
66
|
+
self,
|
|
67
|
+
info: Info[Context, None],
|
|
68
|
+
) -> Optional[float]:
|
|
69
|
+
if (traces := info.context.traces) is None:
|
|
70
|
+
return None
|
|
71
|
+
(latency,) = traces.root_span_latency_ms_quantiles(0.99)
|
|
72
|
+
return latency
|
|
@@ -38,12 +38,10 @@ class GqlBinFactory:
|
|
|
38
38
|
numeric_ubound: float = np.inf
|
|
39
39
|
|
|
40
40
|
@overload
|
|
41
|
-
def __call__(self, bin: "pd.Interval[float]") -> IntervalBin:
|
|
42
|
-
...
|
|
41
|
+
def __call__(self, bin: "pd.Interval[float]") -> IntervalBin: ...
|
|
43
42
|
|
|
44
43
|
@overload
|
|
45
|
-
def __call__(self, bin: Union[str, int, float]) -> Union[NominalBin, MissingValueBin]:
|
|
46
|
-
...
|
|
44
|
+
def __call__(self, bin: Union[str, int, float]) -> Union[NominalBin, MissingValueBin]: ...
|
|
47
45
|
|
|
48
46
|
def __call__(self, bin: Any) -> Union[NominalBin, IntervalBin, MissingValueBin]:
|
|
49
47
|
if isinstance(bin, pd.Interval):
|
phoenix/server/api/types/Span.py
CHANGED
|
@@ -24,6 +24,7 @@ INPUT_VALUE = SpanAttributes.INPUT_VALUE
|
|
|
24
24
|
LLM_TOKEN_COUNT_COMPLETION = SpanAttributes.LLM_TOKEN_COUNT_COMPLETION
|
|
25
25
|
LLM_TOKEN_COUNT_PROMPT = SpanAttributes.LLM_TOKEN_COUNT_PROMPT
|
|
26
26
|
LLM_TOKEN_COUNT_TOTAL = SpanAttributes.LLM_TOKEN_COUNT_TOTAL
|
|
27
|
+
METADATA = SpanAttributes.METADATA
|
|
27
28
|
OUTPUT_MIME_TYPE = SpanAttributes.OUTPUT_MIME_TYPE
|
|
28
29
|
OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE
|
|
29
30
|
RETRIEVAL_DOCUMENTS = SpanAttributes.RETRIEVAL_DOCUMENTS
|
|
@@ -107,6 +108,9 @@ class Span:
|
|
|
107
108
|
attributes: str = strawberry.field(
|
|
108
109
|
description="Span attributes as a JSON string",
|
|
109
110
|
)
|
|
111
|
+
metadata: Optional[str] = strawberry.field(
|
|
112
|
+
description="Metadata as a JSON string",
|
|
113
|
+
)
|
|
110
114
|
num_documents: Optional[int]
|
|
111
115
|
token_count_total: Optional[int]
|
|
112
116
|
token_count_prompt: Optional[int]
|
|
@@ -240,6 +244,7 @@ def to_gql_span(span: trace_schema.Span) -> "Span":
|
|
|
240
244
|
_nested_attributes(_hide_embedding_vectors(span.attributes)),
|
|
241
245
|
default=_json_encode,
|
|
242
246
|
),
|
|
247
|
+
metadata=_convert_metadata_to_string(span.attributes.get(METADATA)),
|
|
243
248
|
num_documents=num_documents,
|
|
244
249
|
token_count_total=cast(
|
|
245
250
|
Optional[int],
|
|
@@ -327,3 +332,16 @@ def _hide_embedding_vectors(
|
|
|
327
332
|
_embeddings.append(_embedding)
|
|
328
333
|
_attributes[EMBEDDING_EMBEDDINGS] = _embeddings
|
|
329
334
|
return _attributes
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _convert_metadata_to_string(metadata: Any) -> Optional[str]:
|
|
338
|
+
"""
|
|
339
|
+
Converts metadata to a string representation.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
if metadata is None or isinstance(metadata, str):
|
|
343
|
+
return metadata
|
|
344
|
+
try:
|
|
345
|
+
return json.dumps(metadata)
|
|
346
|
+
except Exception:
|
|
347
|
+
return str(metadata)
|
phoenix/server/app.py
CHANGED
|
@@ -36,6 +36,8 @@ templates = Jinja2Templates(directory=SERVER_DIR / "templates")
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class AppConfig(NamedTuple):
|
|
39
|
+
has_inferences: bool
|
|
40
|
+
""" Whether the model has inferences (e.g. a primary dataset) """
|
|
39
41
|
has_corpus: bool
|
|
40
42
|
min_dist: float
|
|
41
43
|
n_neighbors: int
|
|
@@ -64,6 +66,7 @@ class Static(StaticFiles):
|
|
|
64
66
|
response = templates.TemplateResponse(
|
|
65
67
|
"index.html",
|
|
66
68
|
context={
|
|
69
|
+
"has_inferences": self._app_config.has_inferences,
|
|
67
70
|
"has_corpus": self._app_config.has_corpus,
|
|
68
71
|
"min_dist": self._app_config.min_dist,
|
|
69
72
|
"n_neighbors": self._app_config.n_neighbors,
|
|
@@ -209,6 +212,7 @@ def create_app(
|
|
|
209
212
|
app=Static(
|
|
210
213
|
directory=SERVER_DIR / "static",
|
|
211
214
|
app_config=AppConfig(
|
|
215
|
+
has_inferences=model.is_empty is not True,
|
|
212
216
|
has_corpus=corpus is not None,
|
|
213
217
|
min_dist=umap_params.min_dist,
|
|
214
218
|
n_neighbors=umap_params.n_neighbors,
|
phoenix/server/main.py
CHANGED
|
@@ -8,6 +8,7 @@ from threading import Thread
|
|
|
8
8
|
from time import sleep, time
|
|
9
9
|
from typing import Iterable, Optional, Protocol, TypeVar
|
|
10
10
|
|
|
11
|
+
import pkg_resources
|
|
11
12
|
from uvicorn import Config, Server
|
|
12
13
|
|
|
13
14
|
from phoenix.config import EXPORT_DIR, get_env_host, get_env_port, get_pids_path
|
|
@@ -34,6 +35,31 @@ from phoenix.trace.span_json_decoder import json_string_to_span
|
|
|
34
35
|
|
|
35
36
|
logger = logging.getLogger(__name__)
|
|
36
37
|
|
|
38
|
+
_WELCOME_MESSAGE = """
|
|
39
|
+
|
|
40
|
+
██████╗ ██╗ ██╗ ██████╗ ███████╗███╗ ██╗██╗██╗ ██╗
|
|
41
|
+
██╔══██╗██║ ██║██╔═══██╗██╔════╝████╗ ██║██║╚██╗██╔╝
|
|
42
|
+
██████╔╝███████║██║ ██║█████╗ ██╔██╗ ██║██║ ╚███╔╝
|
|
43
|
+
██╔═══╝ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔██╗
|
|
44
|
+
██║ ██║ ██║╚██████╔╝███████╗██║ ╚████║██║██╔╝ ██╗
|
|
45
|
+
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{0}
|
|
46
|
+
|
|
47
|
+
|
|
|
48
|
+
| 🌎 Join our Community 🌎
|
|
49
|
+
| https://join.slack.com/t/arize-ai/shared_invite/zt-1px8dcmlf-fmThhDFD_V_48oU7ALan4Q
|
|
50
|
+
|
|
|
51
|
+
| ⭐️ Leave us a Star ⭐️
|
|
52
|
+
| https://github.com/Arize-ai/phoenix
|
|
53
|
+
|
|
|
54
|
+
| 📚 Documentation 📚
|
|
55
|
+
| https://docs.arize.com/phoenix
|
|
56
|
+
|
|
|
57
|
+
| 🚀 Phoenix Server 🚀
|
|
58
|
+
| Phoenix UI: http://{1}:{2}
|
|
59
|
+
| Log traces: /v1/traces over HTTP
|
|
60
|
+
|
|
|
61
|
+
"""
|
|
62
|
+
|
|
37
63
|
|
|
38
64
|
def _write_pid_file_when_ready(
|
|
39
65
|
server: Server,
|
|
@@ -60,8 +86,7 @@ _Item = TypeVar("_Item", contravariant=True)
|
|
|
60
86
|
|
|
61
87
|
|
|
62
88
|
class _SupportsPut(Protocol[_Item]):
|
|
63
|
-
def put(self, item: _Item) -> None:
|
|
64
|
-
...
|
|
89
|
+
def put(self, item: _Item) -> None: ...
|
|
65
90
|
|
|
66
91
|
|
|
67
92
|
def _load_items(
|
|
@@ -202,4 +227,12 @@ if __name__ == "__main__":
|
|
|
202
227
|
port = args.port or get_env_port()
|
|
203
228
|
server = Server(config=Config(app, host=host, port=port))
|
|
204
229
|
Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
|
|
230
|
+
|
|
231
|
+
# Print information about the server
|
|
232
|
+
phoenix_version = pkg_resources.get_distribution("arize-phoenix").version
|
|
233
|
+
print(
|
|
234
|
+
_WELCOME_MESSAGE.format(phoenix_version, host if host != "0.0.0.0" else "localhost", port)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Start the server
|
|
205
238
|
server.run()
|