arize-phoenix 4.23.0__py3-none-any.whl → 4.25.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.

Files changed (49) hide show
  1. {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/METADATA +11 -7
  2. {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/RECORD +49 -42
  3. phoenix/auth/__init__.py +0 -0
  4. phoenix/auth/utils.py +15 -0
  5. phoenix/db/engines.py +15 -2
  6. phoenix/db/insertion/dataset.py +1 -0
  7. phoenix/db/migrate.py +21 -10
  8. phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py +7 -6
  9. phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +4 -12
  10. phoenix/db/models.py +5 -1
  11. phoenix/experiments/functions.py +8 -0
  12. phoenix/inferences/fixtures.py +1 -0
  13. phoenix/inferences/inferences.py +1 -0
  14. phoenix/metrics/__init__.py +1 -0
  15. phoenix/server/api/context.py +14 -0
  16. phoenix/server/api/mutations/__init__.py +2 -0
  17. phoenix/server/api/mutations/api_key_mutations.py +119 -0
  18. phoenix/server/api/mutations/auth.py +7 -0
  19. phoenix/server/api/queries.py +76 -1
  20. phoenix/server/api/routers/v1/datasets.py +1 -0
  21. phoenix/server/api/routers/v1/spans.py +1 -1
  22. phoenix/server/api/types/ApiKey.py +16 -0
  23. phoenix/server/api/types/SystemApiKey.py +9 -0
  24. phoenix/server/api/types/User.py +3 -0
  25. phoenix/server/api/types/UserApiKey.py +11 -0
  26. phoenix/server/api/types/UserRole.py +8 -0
  27. phoenix/server/app.py +36 -7
  28. phoenix/server/main.py +24 -19
  29. phoenix/server/static/.vite/manifest.json +31 -31
  30. phoenix/server/static/assets/{components-DBYPF96c.js → components-1Ahruijo.js} +4 -4
  31. phoenix/server/static/assets/{index-DNxu4viw.js → index-BEE_RWJx.js} +2 -2
  32. phoenix/server/static/assets/{pages-BhOnrUmC.js → pages-CFS6mPnW.js} +265 -219
  33. phoenix/server/static/assets/{vendor-CIqy43_9.js → vendor-aSQri0vz.js} +58 -58
  34. phoenix/server/static/assets/{vendor-arizeai-B1YgcWL8.js → vendor-arizeai-CsdcB1NH.js} +1 -1
  35. phoenix/server/static/assets/{vendor-codemirror-_bcwCA1C.js → vendor-codemirror-CYHkhs7D.js} +1 -1
  36. phoenix/server/static/assets/{vendor-recharts-C3pM_Wlg.js → vendor-recharts-B0sannek.js} +1 -1
  37. phoenix/server/types.py +12 -4
  38. phoenix/services.py +1 -0
  39. phoenix/session/client.py +1 -1
  40. phoenix/session/evaluation.py +1 -0
  41. phoenix/session/session.py +2 -1
  42. phoenix/trace/fixtures.py +37 -0
  43. phoenix/trace/langchain/instrumentor.py +1 -1
  44. phoenix/trace/llama_index/callback.py +1 -0
  45. phoenix/trace/openai/instrumentor.py +1 -0
  46. phoenix/version.py +1 -1
  47. {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/WHEEL +0 -0
  48. {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/licenses/IP_NOTICE +0 -0
  49. {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,6 @@
1
1
  import strawberry
2
2
 
3
+ from phoenix.server.api.mutations.api_key_mutations import ApiKeyMutationMixin
3
4
  from phoenix.server.api.mutations.dataset_mutations import DatasetMutationMixin
4
5
  from phoenix.server.api.mutations.experiment_mutations import ExperimentMutationMixin
5
6
  from phoenix.server.api.mutations.export_events_mutations import ExportEventsMutationMixin
@@ -16,5 +17,6 @@ class Mutation(
16
17
  ExportEventsMutationMixin,
17
18
  SpanAnnotationMutationMixin,
18
19
  TraceAnnotationMutationMixin,
20
+ ApiKeyMutationMixin,
19
21
  ):
20
22
  pass
@@ -0,0 +1,119 @@
1
+ from datetime import datetime
2
+ from typing import Any, Dict, Optional
3
+
4
+ import jwt
5
+ import strawberry
6
+ from sqlalchemy import insert, select
7
+ from strawberry import UNSET
8
+ from strawberry.types import Info
9
+
10
+ from phoenix.db import models
11
+ from phoenix.server.api.context import Context
12
+ from phoenix.server.api.mutations.auth import HasSecret, IsAuthenticated
13
+ from phoenix.server.api.queries import Query
14
+ from phoenix.server.api.types.SystemApiKey import SystemApiKey
15
+
16
+
17
+ @strawberry.type
18
+ class CreateSystemApiKeyMutationPayload:
19
+ jwt: str
20
+ api_key: SystemApiKey
21
+ query: Query
22
+
23
+
24
+ @strawberry.input
25
+ class CreateApiKeyInput:
26
+ name: str
27
+ description: Optional[str] = UNSET
28
+ expires_at: Optional[datetime] = UNSET
29
+
30
+
31
+ @strawberry.type
32
+ class ApiKeyMutationMixin:
33
+ @strawberry.mutation(permission_classes=[HasSecret, IsAuthenticated]) # type: ignore
34
+ async def create_system_api_key(
35
+ self, info: Info[Context, None], input: CreateApiKeyInput
36
+ ) -> CreateSystemApiKeyMutationPayload:
37
+ # TODO(auth): safe guard against auth being disabled and secret not being set
38
+ async with info.context.db() as session:
39
+ # Get the system user - note this could be pushed into a dataloader
40
+ system_user = await session.scalar(
41
+ select(models.User)
42
+ .join(models.UserRole) # Join User with UserRole
43
+ .where(models.UserRole.name == "SYSTEM") # Filter where role is SYSTEM
44
+ .limit(1)
45
+ )
46
+ if system_user is None:
47
+ raise ValueError("System user not found")
48
+
49
+ insert_stmt = (
50
+ insert(models.APIKey)
51
+ .values(
52
+ user_id=system_user.id,
53
+ name=input.name,
54
+ description=input.description or None,
55
+ expires_at=input.expires_at or None,
56
+ )
57
+ .returning(models.APIKey)
58
+ )
59
+ api_key = await session.scalar(insert_stmt)
60
+ assert api_key is not None
61
+
62
+ encoded_jwt = create_jwt(
63
+ secret=info.context.get_secret(),
64
+ name=api_key.name,
65
+ id=api_key.id,
66
+ description=api_key.description,
67
+ iat=api_key.created_at,
68
+ exp=api_key.expires_at,
69
+ )
70
+ return CreateSystemApiKeyMutationPayload(
71
+ jwt=encoded_jwt,
72
+ api_key=SystemApiKey(
73
+ id_attr=api_key.id,
74
+ name=api_key.name,
75
+ description=api_key.description,
76
+ created_at=api_key.created_at,
77
+ expires_at=api_key.expires_at,
78
+ ),
79
+ query=Query(),
80
+ )
81
+
82
+
83
+ def create_jwt(
84
+ *,
85
+ secret: str,
86
+ algorithm: str = "HS256",
87
+ name: str,
88
+ description: Optional[str],
89
+ iat: datetime,
90
+ exp: Optional[datetime],
91
+ id: int,
92
+ ) -> str:
93
+ """Create a signed JSON Web Token for authentication
94
+
95
+ Args:
96
+ secret (str): the secret to sign with
97
+ name (str): name of the key / token
98
+ description (Optional[str]): description of the token
99
+ iat (datetime): the issued at time
100
+ exp (Optional[datetime]): the expiry, if set
101
+ id (int): the id of the key
102
+ algorithm (str, optional): the algorithm to use. Defaults to "HS256".
103
+
104
+ Returns:
105
+ str: The encoded JWT
106
+ """
107
+ payload: Dict[str, Any] = {
108
+ "name": name,
109
+ "description": description,
110
+ "iat": iat.utcnow(),
111
+ "id": id,
112
+ }
113
+ if exp is not None:
114
+ payload["exp"] = exp.utcnow()
115
+
116
+ # Encode the payload to create the JWT
117
+ token = jwt.encode(payload, secret, algorithm=algorithm)
118
+
119
+ return token
@@ -9,3 +9,10 @@ class IsAuthenticated(BasePermission):
9
9
 
10
10
  async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
11
11
  return not info.context.read_only
12
+
13
+
14
+ class HasSecret(BasePermission):
15
+ message = "Application secret is not set"
16
+
17
+ async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
18
+ return info.context.secret is not None
@@ -66,8 +66,11 @@ from phoenix.server.api.types.pagination import (
66
66
  from phoenix.server.api.types.Project import Project
67
67
  from phoenix.server.api.types.SortDir import SortDir
68
68
  from phoenix.server.api.types.Span import Span, to_gql_span
69
+ from phoenix.server.api.types.SystemApiKey import SystemApiKey
69
70
  from phoenix.server.api.types.Trace import Trace
70
71
  from phoenix.server.api.types.User import User
72
+ from phoenix.server.api.types.UserApiKey import UserApiKey
73
+ from phoenix.server.api.types.UserRole import UserRole
71
74
 
72
75
 
73
76
  @strawberry.type
@@ -87,7 +90,13 @@ class Query:
87
90
  last=last,
88
91
  before=before if isinstance(before, CursorString) else None,
89
92
  )
90
- stmt = select(models.User).order_by(models.User.email)
93
+ stmt = (
94
+ select(models.User)
95
+ .join(models.UserRole)
96
+ .where(models.UserRole.name != "SYSTEM")
97
+ .order_by(models.User.email)
98
+ .options(joinedload(models.User.role))
99
+ )
91
100
  async with info.context.db() as session:
92
101
  users = await session.stream_scalars(stmt)
93
102
  data = [
@@ -96,11 +105,77 @@ class Query:
96
105
  email=user.email,
97
106
  username=user.username,
98
107
  created_at=user.created_at,
108
+ role=UserRole(
109
+ id_attr=user.role.id,
110
+ name=user.role.name,
111
+ ),
99
112
  )
100
113
  async for user in users
101
114
  ]
102
115
  return connection_from_list(data=data, args=args)
103
116
 
117
+ @strawberry.field
118
+ async def user_roles(
119
+ self,
120
+ info: Info[Context, None],
121
+ ) -> List[UserRole]:
122
+ async with info.context.db() as session:
123
+ roles = await session.scalars(
124
+ select(models.UserRole).where(models.UserRole.name != "SYSTEM")
125
+ )
126
+ return [
127
+ UserRole(
128
+ id_attr=role.id,
129
+ name=role.name,
130
+ )
131
+ for role in roles
132
+ ]
133
+
134
+ @strawberry.field
135
+ async def user_api_keys(self, info: Info[Context, None]) -> List[UserApiKey]:
136
+ # TODO(auth): add access control
137
+ stmt = (
138
+ select(models.APIKey)
139
+ .join(models.User)
140
+ .join(models.UserRole)
141
+ .where(models.UserRole.name != "SYSTEM")
142
+ )
143
+ async with info.context.db() as session:
144
+ api_keys = await session.scalars(stmt)
145
+ return [
146
+ UserApiKey(
147
+ id_attr=api_key.id,
148
+ user_id=api_key.user_id,
149
+ name=api_key.name,
150
+ description=api_key.description,
151
+ created_at=api_key.created_at,
152
+ expires_at=api_key.expires_at,
153
+ )
154
+ for api_key in api_keys
155
+ ]
156
+
157
+ @strawberry.field
158
+ async def system_api_keys(self, info: Info[Context, None]) -> List[SystemApiKey]:
159
+ # TODO(auth): add access control
160
+ stmt = (
161
+ select(models.APIKey)
162
+ .join(models.User)
163
+ .join(models.UserRole)
164
+ .where(models.UserRole.name == "SYSTEM")
165
+ )
166
+ async with info.context.db() as session:
167
+ api_keys = await session.scalars(stmt)
168
+ return [
169
+ SystemApiKey(
170
+ id_attr=api_key.id,
171
+ name=api_key.name,
172
+ description=api_key.description,
173
+ created_at=api_key.created_at,
174
+ expires_at=api_key.expires_at,
175
+ )
176
+ for api_key in api_keys
177
+ ]
178
+
104
179
  @strawberry.field
105
180
  async def projects(
106
181
  self,
@@ -71,6 +71,7 @@ from .utils import (
71
71
  )
72
72
 
73
73
  logger = logging.getLogger(__name__)
74
+ logger.addHandler(logging.NullHandler())
74
75
 
75
76
  DATASET_NODE_NAME = DatasetNodeType.__name__
76
77
  DATASET_VERSION_NODE_NAME = DatasetVersionNodeType.__name__
@@ -196,7 +196,7 @@ class AnnotateSpansResponseBody(ResponseBody[List[InsertedSpanAnnotation]]):
196
196
  async def annotate_spans(
197
197
  request: Request,
198
198
  request_body: AnnotateSpansRequestBody,
199
- sync: bool = Query(default=True, description="If true, fulfill request synchronously."),
199
+ sync: bool = Query(default=False, description="If true, fulfill request synchronously."),
200
200
  ) -> AnnotateSpansResponseBody:
201
201
  if not request_body.data:
202
202
  return AnnotateSpansResponseBody(data=[])
@@ -0,0 +1,16 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ import strawberry
5
+
6
+
7
+ @strawberry.interface
8
+ class ApiKey:
9
+ name: str = strawberry.field(description="Name of the API key.")
10
+ description: Optional[str] = strawberry.field(description="Description of the API key.")
11
+ created_at: datetime = strawberry.field(
12
+ description="The date and time the API key was created."
13
+ )
14
+ expires_at: Optional[datetime] = strawberry.field(
15
+ description="The date and time the API key will expire."
16
+ )
@@ -0,0 +1,9 @@
1
+ import strawberry
2
+ from strawberry.relay import Node, NodeID
3
+
4
+ from .ApiKey import ApiKey
5
+
6
+
7
+ @strawberry.type
8
+ class SystemApiKey(ApiKey, Node):
9
+ id_attr: NodeID[int]
@@ -4,6 +4,8 @@ from typing import Optional
4
4
  import strawberry
5
5
  from strawberry.relay import Node, NodeID
6
6
 
7
+ from .UserRole import UserRole
8
+
7
9
 
8
10
  @strawberry.type
9
11
  class User(Node):
@@ -11,3 +13,4 @@ class User(Node):
11
13
  email: str
12
14
  username: Optional[str]
13
15
  created_at: datetime
16
+ role: UserRole
@@ -0,0 +1,11 @@
1
+ import strawberry
2
+ from strawberry import Private
3
+ from strawberry.relay.types import Node, NodeID
4
+
5
+ from .ApiKey import ApiKey
6
+
7
+
8
+ @strawberry.type
9
+ class UserApiKey(ApiKey, Node):
10
+ id_attr: NodeID[int]
11
+ user_id: Private[int]
@@ -0,0 +1,8 @@
1
+ import strawberry
2
+ from strawberry.relay import Node, NodeID
3
+
4
+
5
+ @strawberry.type
6
+ class UserRole(Node):
7
+ id_attr: NodeID[int]
8
+ name: str
phoenix/server/app.py CHANGED
@@ -100,6 +100,7 @@ if TYPE_CHECKING:
100
100
  from opentelemetry.trace import TracerProvider
101
101
 
102
102
  logger = logging.getLogger(__name__)
103
+ logger.addHandler(logging.NullHandler())
103
104
 
104
105
  router = APIRouter(include_in_schema=False)
105
106
 
@@ -229,7 +230,8 @@ def _lifespan(
229
230
  dml_event_handler: DmlEventHandler,
230
231
  tracer_provider: Optional["TracerProvider"] = None,
231
232
  enable_prometheus: bool = False,
232
- clean_ups: Iterable[Callable[[], None]] = (),
233
+ startup_callbacks: Iterable[Callable[[], None]] = (),
234
+ shutdown_callbacks: Iterable[Callable[[], None]] = (),
233
235
  read_only: bool = False,
234
236
  ) -> StatefulLifespan[FastAPI]:
235
237
  @contextlib.asynccontextmanager
@@ -247,6 +249,8 @@ def _lifespan(
247
249
  tracer_provider=tracer_provider,
248
250
  enable_prometheus=enable_prometheus,
249
251
  ), dml_event_handler:
252
+ for callback in startup_callbacks:
253
+ callback()
250
254
  yield {
251
255
  "event_queue": dml_event_handler,
252
256
  "enqueue": enqueue,
@@ -254,8 +258,8 @@ def _lifespan(
254
258
  "queue_evaluation_for_bulk_insert": queue_evaluation,
255
259
  "enqueue_operation": enqueue_operation,
256
260
  }
257
- for clean_up in clean_ups:
258
- clean_up()
261
+ for callback in shutdown_callbacks:
262
+ callback()
259
263
 
260
264
  return lifespan
261
265
 
@@ -276,7 +280,26 @@ def create_graphql_router(
276
280
  cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
277
281
  event_queue: CanPutItem[DmlEvent],
278
282
  read_only: bool = False,
283
+ secret: Optional[str] = None,
279
284
  ) -> GraphQLRouter: # type: ignore[type-arg]
285
+ """Creates the GraphQL router.
286
+
287
+ Args:
288
+ schema (BaseSchema): The GraphQL schema.
289
+ db (DbSessionFactory): The database session factory pointing to a SQL database.
290
+ model (Model): The Model representing inferences (legacy)
291
+ export_path (Path): the file path to export data to for download (legacy)
292
+ last_updated_at (CanGetLastUpdatedAt): How to get the last updated timestamp for updates.
293
+ event_queue (CanPutItem[DmlEvent]): The event queue for DML events.
294
+ corpus (Optional[Model], optional): the corpus for UMAP projection. Defaults to None.
295
+ cache_for_dataloaders (Optional[CacheForDataLoaders], optional): GraphQL data loaders.
296
+ read_only (bool, optional): Marks the app as read-only. Defaults to False.
297
+ secret (Optional[str], optional): The application secret for auth. Defaults to None.
298
+
299
+ Returns:
300
+ GraphQLRouter: The router mounted at /graphql
301
+ """
302
+
280
303
  def get_context() -> Context:
281
304
  return Context(
282
305
  db=db,
@@ -336,6 +359,7 @@ def create_graphql_router(
336
359
  ),
337
360
  cache_for_dataloaders=cache_for_dataloaders,
338
361
  read_only=read_only,
362
+ secret=secret,
339
363
  )
340
364
 
341
365
  return GraphQLRouter(
@@ -408,9 +432,12 @@ def create_app(
408
432
  initial_spans: Optional[Iterable[Union[Span, Tuple[Span, str]]]] = None,
409
433
  initial_evaluations: Optional[Iterable[pb.Evaluation]] = None,
410
434
  serve_ui: bool = True,
411
- clean_up_callbacks: List[Callable[[], None]] = [],
435
+ startup_callbacks: Iterable[Callable[[], None]] = (),
436
+ shutdown_callbacks: Iterable[Callable[[], None]] = (),
437
+ secret: Optional[str] = None,
412
438
  ) -> FastAPI:
413
- clean_ups: List[Callable[[], None]] = clean_up_callbacks # To be called at app shutdown.
439
+ startup_callbacks_list: List[Callable[[], None]] = list(startup_callbacks)
440
+ shutdown_callbacks_list: List[Callable[[], None]] = list(shutdown_callbacks)
414
441
  initial_batch_of_spans: Iterable[Tuple[Span, str]] = (
415
442
  ()
416
443
  if initial_spans is None
@@ -472,6 +499,7 @@ def create_app(
472
499
  event_queue=dml_event_handler,
473
500
  cache_for_dataloaders=cache_for_dataloaders,
474
501
  read_only=read_only,
502
+ secret=secret,
475
503
  )
476
504
  if enable_prometheus:
477
505
  from phoenix.server.prometheus import PrometheusMiddleware
@@ -489,7 +517,8 @@ def create_app(
489
517
  dml_event_handler=dml_event_handler,
490
518
  tracer_provider=tracer_provider,
491
519
  enable_prometheus=enable_prometheus,
492
- clean_ups=clean_ups,
520
+ shutdown_callbacks=shutdown_callbacks_list,
521
+ startup_callbacks=startup_callbacks_list,
493
522
  ),
494
523
  middleware=[
495
524
  Middleware(HeadersMiddleware),
@@ -532,5 +561,5 @@ def create_app(
532
561
 
533
562
  FastAPIInstrumentor().instrument(tracer_provider=tracer_provider)
534
563
  FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider)
535
- clean_ups.append(FastAPIInstrumentor().uninstrument)
564
+ shutdown_callbacks_list.append(FastAPIInstrumentor().uninstrument)
536
565
  return app
phoenix/server/main.py CHANGED
@@ -1,13 +1,16 @@
1
1
  import atexit
2
+ import codecs
2
3
  import logging
3
4
  import os
5
+ import sys
4
6
  from argparse import ArgumentParser
5
- from pathlib import Path, PosixPath
7
+ from importlib.metadata import version
8
+ from pathlib import Path
6
9
  from threading import Thread
7
10
  from time import sleep, time
8
11
  from typing import List, Optional
12
+ from urllib.parse import urljoin
9
13
 
10
- import pkg_resources
11
14
  from uvicorn import Config, Server
12
15
 
13
16
  import phoenix.trace.v1 as pb
@@ -53,6 +56,7 @@ from phoenix.trace.otel import decode_otlp_span, encode_span_to_otlp
53
56
  from phoenix.trace.schemas import Span
54
57
 
55
58
  logger = logging.getLogger(__name__)
59
+ logger.addHandler(logging.NullHandler())
56
60
 
57
61
  _WELCOME_MESSAGE = """
58
62
 
@@ -137,6 +141,7 @@ if __name__ == "__main__":
137
141
  parser.add_argument("--debug", action="store_true")
138
142
  # Whether the app is running in a development environment
139
143
  parser.add_argument("--dev", action="store_true")
144
+ parser.add_argument("--no-ui", action="store_true")
140
145
  subparsers = parser.add_subparsers(dest="command", required=True)
141
146
  serve_parser = subparsers.add_parser("serve")
142
147
  datasets_parser = subparsers.add_parser("datasets")
@@ -218,7 +223,7 @@ if __name__ == "__main__":
218
223
  reference_inferences,
219
224
  )
220
225
 
221
- authentication_enabled, auth_secret = get_auth_settings()
226
+ authentication_enabled, secret = get_auth_settings()
222
227
 
223
228
  fixture_spans: List[Span] = []
224
229
  fixture_evals: List[pb.Evaluation] = []
@@ -255,6 +260,18 @@ if __name__ == "__main__":
255
260
  engine = create_engine_and_run_migrations(db_connection_str)
256
261
  instrumentation_cleanups = instrument_engine_if_enabled(engine)
257
262
  factory = DbSessionFactory(db=_db(engine), dialect=engine.dialect.name)
263
+ # Print information about the server
264
+ msg = _WELCOME_MESSAGE.format(
265
+ version=version("arize-phoenix"),
266
+ ui_path=urljoin(f"http://{host}:{port}", host_root_path),
267
+ grpc_path=f"http://{host}:{get_env_grpc_port()}",
268
+ http_path=urljoin(urljoin(f"http://{host}:{port}", host_root_path), "v1/traces"),
269
+ storage=get_printable_db_url(db_connection_str),
270
+ )
271
+ if authentication_enabled:
272
+ msg += _EXPERIMENTAL_WARNING.format(auth_enabled=True)
273
+ if sys.platform.startswith("win"):
274
+ msg = codecs.encode(msg, "ascii", errors="ignore").decode("ascii").strip()
258
275
  app = create_app(
259
276
  db=factory,
260
277
  export_path=export_path,
@@ -266,29 +283,17 @@ if __name__ == "__main__":
266
283
  else create_model_from_inferences(corpus_inferences),
267
284
  debug=args.debug,
268
285
  dev=args.dev,
286
+ serve_ui=not args.no_ui,
269
287
  read_only=read_only,
270
288
  enable_prometheus=enable_prometheus,
271
289
  initial_spans=fixture_spans,
272
290
  initial_evaluations=fixture_evals,
273
- clean_up_callbacks=instrumentation_cleanups,
291
+ startup_callbacks=[lambda: print(msg)],
292
+ shutdown_callbacks=instrumentation_cleanups,
293
+ secret=secret,
274
294
  )
275
295
  server = Server(config=Config(app, host=host, port=port, root_path=host_root_path)) # type: ignore
276
296
  Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
277
297
 
278
- # Print information about the server
279
- phoenix_version = pkg_resources.get_distribution("arize-phoenix").version
280
- print(
281
- _WELCOME_MESSAGE.format(
282
- version=phoenix_version,
283
- ui_path=PosixPath(f"http://{host}:{port}", host_root_path),
284
- grpc_path=f"http://{host}:{get_env_grpc_port()}",
285
- http_path=PosixPath(f"http://{host}:{port}", host_root_path, "v1/traces"),
286
- storage=get_printable_db_url(db_connection_str),
287
- )
288
- )
289
-
290
- if authentication_enabled:
291
- print(_EXPERIMENTAL_WARNING.format(auth_enabled=authentication_enabled))
292
-
293
298
  # Start the server
294
299
  server.run()
@@ -1,32 +1,32 @@
1
1
  {
2
- "_components-DBYPF96c.js": {
3
- "file": "assets/components-DBYPF96c.js",
2
+ "_components-1Ahruijo.js": {
3
+ "file": "assets/components-1Ahruijo.js",
4
4
  "name": "components",
5
5
  "imports": [
6
- "_vendor-CIqy43_9.js",
7
- "_vendor-arizeai-B1YgcWL8.js",
8
- "_pages-BhOnrUmC.js",
6
+ "_vendor-aSQri0vz.js",
7
+ "_vendor-arizeai-CsdcB1NH.js",
8
+ "_pages-CFS6mPnW.js",
9
9
  "_vendor-three-DwGkEfCM.js",
10
- "_vendor-codemirror-_bcwCA1C.js"
10
+ "_vendor-codemirror-CYHkhs7D.js"
11
11
  ]
12
12
  },
13
- "_pages-BhOnrUmC.js": {
14
- "file": "assets/pages-BhOnrUmC.js",
13
+ "_pages-CFS6mPnW.js": {
14
+ "file": "assets/pages-CFS6mPnW.js",
15
15
  "name": "pages",
16
16
  "imports": [
17
- "_vendor-CIqy43_9.js",
18
- "_components-DBYPF96c.js",
19
- "_vendor-arizeai-B1YgcWL8.js",
20
- "_vendor-recharts-C3pM_Wlg.js",
21
- "_vendor-codemirror-_bcwCA1C.js"
17
+ "_vendor-aSQri0vz.js",
18
+ "_components-1Ahruijo.js",
19
+ "_vendor-arizeai-CsdcB1NH.js",
20
+ "_vendor-recharts-B0sannek.js",
21
+ "_vendor-codemirror-CYHkhs7D.js"
22
22
  ]
23
23
  },
24
24
  "_vendor-!~{003}~.js": {
25
25
  "file": "assets/vendor-DxkFTwjz.css",
26
26
  "src": "_vendor-!~{003}~.js"
27
27
  },
28
- "_vendor-CIqy43_9.js": {
29
- "file": "assets/vendor-CIqy43_9.js",
28
+ "_vendor-aSQri0vz.js": {
29
+ "file": "assets/vendor-aSQri0vz.js",
30
30
  "name": "vendor",
31
31
  "imports": [
32
32
  "_vendor-three-DwGkEfCM.js"
@@ -35,25 +35,25 @@
35
35
  "assets/vendor-DxkFTwjz.css"
36
36
  ]
37
37
  },
38
- "_vendor-arizeai-B1YgcWL8.js": {
39
- "file": "assets/vendor-arizeai-B1YgcWL8.js",
38
+ "_vendor-arizeai-CsdcB1NH.js": {
39
+ "file": "assets/vendor-arizeai-CsdcB1NH.js",
40
40
  "name": "vendor-arizeai",
41
41
  "imports": [
42
- "_vendor-CIqy43_9.js"
42
+ "_vendor-aSQri0vz.js"
43
43
  ]
44
44
  },
45
- "_vendor-codemirror-_bcwCA1C.js": {
46
- "file": "assets/vendor-codemirror-_bcwCA1C.js",
45
+ "_vendor-codemirror-CYHkhs7D.js": {
46
+ "file": "assets/vendor-codemirror-CYHkhs7D.js",
47
47
  "name": "vendor-codemirror",
48
48
  "imports": [
49
- "_vendor-CIqy43_9.js"
49
+ "_vendor-aSQri0vz.js"
50
50
  ]
51
51
  },
52
- "_vendor-recharts-C3pM_Wlg.js": {
53
- "file": "assets/vendor-recharts-C3pM_Wlg.js",
52
+ "_vendor-recharts-B0sannek.js": {
53
+ "file": "assets/vendor-recharts-B0sannek.js",
54
54
  "name": "vendor-recharts",
55
55
  "imports": [
56
- "_vendor-CIqy43_9.js"
56
+ "_vendor-aSQri0vz.js"
57
57
  ]
58
58
  },
59
59
  "_vendor-three-DwGkEfCM.js": {
@@ -61,18 +61,18 @@
61
61
  "name": "vendor-three"
62
62
  },
63
63
  "index.tsx": {
64
- "file": "assets/index-DNxu4viw.js",
64
+ "file": "assets/index-BEE_RWJx.js",
65
65
  "name": "index",
66
66
  "src": "index.tsx",
67
67
  "isEntry": true,
68
68
  "imports": [
69
- "_vendor-CIqy43_9.js",
70
- "_vendor-arizeai-B1YgcWL8.js",
71
- "_pages-BhOnrUmC.js",
72
- "_components-DBYPF96c.js",
69
+ "_vendor-aSQri0vz.js",
70
+ "_vendor-arizeai-CsdcB1NH.js",
71
+ "_pages-CFS6mPnW.js",
72
+ "_components-1Ahruijo.js",
73
73
  "_vendor-three-DwGkEfCM.js",
74
- "_vendor-recharts-C3pM_Wlg.js",
75
- "_vendor-codemirror-_bcwCA1C.js"
74
+ "_vendor-recharts-B0sannek.js",
75
+ "_vendor-codemirror-CYHkhs7D.js"
76
76
  ]
77
77
  }
78
78
  }