arize-phoenix 4.4.4rc4__py3-none-any.whl → 4.4.4rc5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arize-phoenix might be problematic. Click here for more details.

Files changed (31) hide show
  1. {arize_phoenix-4.4.4rc4.dist-info → arize_phoenix-4.4.4rc5.dist-info}/METADATA +2 -2
  2. {arize_phoenix-4.4.4rc4.dist-info → arize_phoenix-4.4.4rc5.dist-info}/RECORD +30 -28
  3. phoenix/datasets/evaluators/code_evaluators.py +25 -53
  4. phoenix/datasets/evaluators/llm_evaluators.py +63 -32
  5. phoenix/datasets/evaluators/utils.py +292 -0
  6. phoenix/datasets/experiments.py +147 -82
  7. phoenix/datasets/tracing.py +19 -0
  8. phoenix/datasets/types.py +18 -52
  9. phoenix/db/insertion/dataset.py +19 -16
  10. phoenix/db/migrations/versions/10460e46d750_datasets.py +2 -2
  11. phoenix/db/models.py +8 -3
  12. phoenix/server/api/context.py +2 -0
  13. phoenix/server/api/dataloaders/__init__.py +2 -0
  14. phoenix/server/api/dataloaders/experiment_run_counts.py +42 -0
  15. phoenix/server/api/helpers/dataset_helpers.py +8 -7
  16. phoenix/server/api/input_types/ClearProjectInput.py +15 -0
  17. phoenix/server/api/mutations/project_mutations.py +9 -4
  18. phoenix/server/api/routers/v1/datasets.py +146 -42
  19. phoenix/server/api/routers/v1/experiment_evaluations.py +1 -0
  20. phoenix/server/api/routers/v1/experiment_runs.py +2 -2
  21. phoenix/server/api/types/Experiment.py +5 -0
  22. phoenix/server/api/types/ExperimentRun.py +1 -1
  23. phoenix/server/api/types/ExperimentRunAnnotation.py +1 -1
  24. phoenix/server/app.py +2 -0
  25. phoenix/server/static/index.js +610 -564
  26. phoenix/session/client.py +124 -2
  27. phoenix/version.py +1 -1
  28. phoenix/datasets/evaluators/_utils.py +0 -13
  29. {arize_phoenix-4.4.4rc4.dist-info → arize_phoenix-4.4.4rc5.dist-info}/WHEEL +0 -0
  30. {arize_phoenix-4.4.4rc4.dist-info → arize_phoenix-4.4.4rc5.dist-info}/licenses/IP_NOTICE +0 -0
  31. {arize_phoenix-4.4.4rc4.dist-info → arize_phoenix-4.4.4rc5.dist-info}/licenses/LICENSE +0 -0
phoenix/session/client.py CHANGED
@@ -15,6 +15,7 @@ from typing import (
15
15
  Literal,
16
16
  Mapping,
17
17
  Optional,
18
+ Sequence,
18
19
  Tuple,
19
20
  Union,
20
21
  cast,
@@ -24,6 +25,7 @@ from urllib.parse import quote, urljoin
24
25
  import httpx
25
26
  import pandas as pd
26
27
  import pyarrow as pa
28
+ from httpx import HTTPStatusError
27
29
  from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ExportTraceServiceRequest
28
30
  from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue
29
31
  from opentelemetry.proto.resource.v1.resource_pb2 import Resource
@@ -48,6 +50,8 @@ from phoenix.trace.otel import encode_span_to_otlp
48
50
 
49
51
  logger = logging.getLogger(__name__)
50
52
 
53
+ DatasetAction: TypeAlias = Literal["create", "append"]
54
+
51
55
 
52
56
  class Client(TraceDataExtractor):
53
57
  def __init__(
@@ -88,6 +92,23 @@ class Client(TraceDataExtractor):
88
92
  if warn_if_server_not_running:
89
93
  self._warn_if_phoenix_is_not_running()
90
94
 
95
+ @property
96
+ def web_url(self) -> str:
97
+ """
98
+ Return the web URL of the Phoenix UI. This is different from the base
99
+ URL in the cases where there is a proxy like colab
100
+
101
+
102
+ Returns:
103
+ str: A fully qualified URL to the Phoenix UI.
104
+ """
105
+ # Avoid circular import
106
+ from phoenix.session.session import active_session
107
+
108
+ if session := active_session():
109
+ return session.url
110
+ return self._base_url
111
+
91
112
  def query_spans(
92
113
  self,
93
114
  *queries: SpanQuery,
@@ -407,6 +428,91 @@ class Client(TraceDataExtractor):
407
428
  index_col="example_id",
408
429
  )
409
430
 
431
+ def create_examples(
432
+ self,
433
+ *,
434
+ dataset_name: str,
435
+ inputs: Iterable[Mapping[str, Any]],
436
+ outputs: Iterable[Mapping[str, Any]] = (),
437
+ metadata: Iterable[Mapping[str, Any]] = (),
438
+ dataset_description: Optional[str] = None,
439
+ ) -> Dataset:
440
+ """
441
+ Upload examples as dataset to the Phoenix server.
442
+
443
+ Args:
444
+ dataset_name: (str): Name of the dataset
445
+ inputs (Iterable[Mapping[str, Any]]): List of dictionaries object each
446
+ corresponding to an example in the dataset.
447
+ outputs (Iterable[Mapping[str, Any]]): List of dictionaries object each
448
+ corresponding to an example in the dataset.
449
+ metadata (Iterable[Mapping[str, Any]]): List of dictionaries object each
450
+ corresponding to an example in the dataset.
451
+ dataset_description: (Optional[str]): Description of the dataset.
452
+
453
+ Returns:
454
+ A Dataset object with the uploaded examples.
455
+ """
456
+ # convert to list to avoid issues with pandas Series
457
+ inputs, outputs, metadata = list(inputs), list(outputs), list(metadata)
458
+ if not inputs or not _is_all_dict(inputs):
459
+ raise ValueError(
460
+ "`inputs` should be a non-empty sequence containing only dictionary objects"
461
+ )
462
+ for name, seq in {"outputs": outputs, "metadata": metadata}.items():
463
+ if seq and not (len(seq) == len(inputs) and _is_all_dict(seq)):
464
+ raise ValueError(
465
+ f"`{name}` should be a sequence of the same length as `inputs` "
466
+ "containing only dictionary objects"
467
+ )
468
+ action: DatasetAction = "create"
469
+ print("📤 Uploading dataset...")
470
+ response = self._client.post(
471
+ url=urljoin(self._base_url, "v1/datasets/upload"),
472
+ headers={"Content-Encoding": "gzip"},
473
+ json={
474
+ "action": action,
475
+ "name": dataset_name,
476
+ "description": dataset_description,
477
+ "inputs": inputs,
478
+ "outputs": outputs,
479
+ "metadata": metadata,
480
+ },
481
+ params={"sync": True},
482
+ )
483
+ try:
484
+ response.raise_for_status()
485
+ except HTTPStatusError as e:
486
+ if msg := response.text:
487
+ raise DatasetUploadError(msg) from e
488
+ raise
489
+ data = response.json()["data"]
490
+ dataset_id = data["dataset_id"]
491
+ response = self._client.get(
492
+ url=urljoin(self._base_url, f"v1/datasets/{dataset_id}/examples")
493
+ )
494
+ response.raise_for_status()
495
+ data = response.json()["data"]
496
+ version_id = data["version_id"]
497
+ examples = data["examples"]
498
+ print(f"💾 Examples uploaded: {self.web_url}datasets/{dataset_id}/examples")
499
+ print(f"🗄️ Dataset version ID: {version_id}")
500
+
501
+ return Dataset(
502
+ id=dataset_id,
503
+ version_id=version_id,
504
+ examples=[
505
+ Example(
506
+ id=example["id"],
507
+ input=example["input"],
508
+ output=example["output"],
509
+ metadata=example["metadata"],
510
+ updated_at=datetime.fromisoformat(example["updated_at"]),
511
+ )
512
+ for example in examples
513
+ ],
514
+ )
515
+
410
516
  def upload_dataset(
411
517
  self,
412
518
  table: Union[str, Path, pd.DataFrame],
@@ -414,7 +520,7 @@ class Client(TraceDataExtractor):
414
520
  *,
415
521
  name: str,
416
522
  input_keys: Iterable[str],
417
- output_keys: Iterable[str],
523
+ output_keys: Iterable[str] = (),
418
524
  metadata_keys: Iterable[str] = (),
419
525
  description: Optional[str] = None,
420
526
  action: Literal["create", "append"] = "create",
@@ -457,6 +563,7 @@ class Client(TraceDataExtractor):
457
563
  file = _prepare_csv(Path(table), keys)
458
564
  else:
459
565
  assert_never(table)
566
+ print("📤 Uploading dataset...")
460
567
  response = self._client.post(
461
568
  url=urljoin(self._base_url, "v1/datasets/upload"),
462
569
  files={"file": file},
@@ -470,7 +577,12 @@ class Client(TraceDataExtractor):
470
577
  },
471
578
  params={"sync": True},
472
579
  )
473
- response.raise_for_status()
580
+ try:
581
+ response.raise_for_status()
582
+ except HTTPStatusError as e:
583
+ if msg := response.text:
584
+ raise DatasetUploadError(msg) from e
585
+ raise
474
586
  data = response.json()["data"]
475
587
  dataset_id = data["dataset_id"]
476
588
  response = self._client.get(
@@ -480,6 +592,9 @@ class Client(TraceDataExtractor):
480
592
  data = response.json()["data"]
481
593
  version_id = data["version_id"]
482
594
  examples = data["examples"]
595
+ print(f"💾 Examples uploaded: {self.web_url}datasets/{dataset_id}/examples")
596
+ print(f"🗄️ Dataset version ID: {version_id}")
597
+
483
598
  return Dataset(
484
599
  id=dataset_id,
485
600
  version_id=version_id,
@@ -547,3 +662,10 @@ def _prepare_pyarrow(
547
662
 
548
663
  def _to_iso_format(value: Optional[datetime]) -> Optional[str]:
549
664
  return value.isoformat() if value else None
665
+
666
+
667
+ def _is_all_dict(seq: Sequence[Any]) -> bool:
668
+ return all(map(lambda obj: isinstance(obj, dict), seq))
669
+
670
+
671
+ class DatasetUploadError(Exception): ...
phoenix/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "4.4.4rc4"
1
+ __version__ = "4.4.4rc5"
@@ -1,13 +0,0 @@
1
- from phoenix.datasets.types import JSONSerializable
2
-
3
-
4
- def _unwrap_json(obj: JSONSerializable) -> JSONSerializable:
5
- if isinstance(obj, dict):
6
- if len(obj) == 1:
7
- key = next(iter(obj.keys()))
8
- output = obj[key]
9
- assert isinstance(
10
- output, (dict, list, str, int, float, bool, type(None))
11
- ), "Output must be JSON serializable"
12
- return output
13
- return obj