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.
- {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/METADATA +11 -7
- {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/RECORD +49 -42
- phoenix/auth/__init__.py +0 -0
- phoenix/auth/utils.py +15 -0
- phoenix/db/engines.py +15 -2
- phoenix/db/insertion/dataset.py +1 -0
- phoenix/db/migrate.py +21 -10
- phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py +7 -6
- phoenix/db/migrations/versions/3be8647b87d8_add_token_columns_to_spans_table.py +4 -12
- phoenix/db/models.py +5 -1
- phoenix/experiments/functions.py +8 -0
- phoenix/inferences/fixtures.py +1 -0
- phoenix/inferences/inferences.py +1 -0
- phoenix/metrics/__init__.py +1 -0
- phoenix/server/api/context.py +14 -0
- phoenix/server/api/mutations/__init__.py +2 -0
- phoenix/server/api/mutations/api_key_mutations.py +119 -0
- phoenix/server/api/mutations/auth.py +7 -0
- phoenix/server/api/queries.py +76 -1
- phoenix/server/api/routers/v1/datasets.py +1 -0
- phoenix/server/api/routers/v1/spans.py +1 -1
- phoenix/server/api/types/ApiKey.py +16 -0
- phoenix/server/api/types/SystemApiKey.py +9 -0
- phoenix/server/api/types/User.py +3 -0
- phoenix/server/api/types/UserApiKey.py +11 -0
- phoenix/server/api/types/UserRole.py +8 -0
- phoenix/server/app.py +36 -7
- phoenix/server/main.py +24 -19
- phoenix/server/static/.vite/manifest.json +31 -31
- phoenix/server/static/assets/{components-DBYPF96c.js → components-1Ahruijo.js} +4 -4
- phoenix/server/static/assets/{index-DNxu4viw.js → index-BEE_RWJx.js} +2 -2
- phoenix/server/static/assets/{pages-BhOnrUmC.js → pages-CFS6mPnW.js} +265 -219
- phoenix/server/static/assets/{vendor-CIqy43_9.js → vendor-aSQri0vz.js} +58 -58
- phoenix/server/static/assets/{vendor-arizeai-B1YgcWL8.js → vendor-arizeai-CsdcB1NH.js} +1 -1
- phoenix/server/static/assets/{vendor-codemirror-_bcwCA1C.js → vendor-codemirror-CYHkhs7D.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-C3pM_Wlg.js → vendor-recharts-B0sannek.js} +1 -1
- phoenix/server/types.py +12 -4
- phoenix/services.py +1 -0
- phoenix/session/client.py +1 -1
- phoenix/session/evaluation.py +1 -0
- phoenix/session/session.py +2 -1
- phoenix/trace/fixtures.py +37 -0
- phoenix/trace/langchain/instrumentor.py +1 -1
- phoenix/trace/llama_index/callback.py +1 -0
- phoenix/trace/openai/instrumentor.py +1 -0
- phoenix/version.py +1 -1
- {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.23.0.dist-info → arize_phoenix-4.25.0.dist-info}/licenses/IP_NOTICE +0 -0
- {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
|
phoenix/server/api/queries.py
CHANGED
|
@@ -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 =
|
|
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,
|
|
@@ -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=
|
|
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
|
+
)
|
phoenix/server/api/types/User.py
CHANGED
|
@@ -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
|
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
|
-
|
|
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
|
|
258
|
-
|
|
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
|
-
|
|
435
|
+
startup_callbacks: Iterable[Callable[[], None]] = (),
|
|
436
|
+
shutdown_callbacks: Iterable[Callable[[], None]] = (),
|
|
437
|
+
secret: Optional[str] = None,
|
|
412
438
|
) -> FastAPI:
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-1Ahruijo.js": {
|
|
3
|
+
"file": "assets/components-1Ahruijo.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
|
-
"_vendor-
|
|
7
|
-
"_vendor-arizeai-
|
|
8
|
-
"_pages-
|
|
6
|
+
"_vendor-aSQri0vz.js",
|
|
7
|
+
"_vendor-arizeai-CsdcB1NH.js",
|
|
8
|
+
"_pages-CFS6mPnW.js",
|
|
9
9
|
"_vendor-three-DwGkEfCM.js",
|
|
10
|
-
"_vendor-codemirror-
|
|
10
|
+
"_vendor-codemirror-CYHkhs7D.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-CFS6mPnW.js": {
|
|
14
|
+
"file": "assets/pages-CFS6mPnW.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
|
-
"_vendor-
|
|
18
|
-
"_components-
|
|
19
|
-
"_vendor-arizeai-
|
|
20
|
-
"_vendor-recharts-
|
|
21
|
-
"_vendor-codemirror-
|
|
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-
|
|
29
|
-
"file": "assets/vendor-
|
|
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-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-CsdcB1NH.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-CsdcB1NH.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
|
-
"_vendor-
|
|
42
|
+
"_vendor-aSQri0vz.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
"_vendor-codemirror-
|
|
46
|
-
"file": "assets/vendor-codemirror-
|
|
45
|
+
"_vendor-codemirror-CYHkhs7D.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-CYHkhs7D.js",
|
|
47
47
|
"name": "vendor-codemirror",
|
|
48
48
|
"imports": [
|
|
49
|
-
"_vendor-
|
|
49
|
+
"_vendor-aSQri0vz.js"
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
|
-
"_vendor-recharts-
|
|
53
|
-
"file": "assets/vendor-recharts-
|
|
52
|
+
"_vendor-recharts-B0sannek.js": {
|
|
53
|
+
"file": "assets/vendor-recharts-B0sannek.js",
|
|
54
54
|
"name": "vendor-recharts",
|
|
55
55
|
"imports": [
|
|
56
|
-
"_vendor-
|
|
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-
|
|
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-
|
|
70
|
-
"_vendor-arizeai-
|
|
71
|
-
"_pages-
|
|
72
|
-
"_components-
|
|
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-
|
|
75
|
-
"_vendor-codemirror-
|
|
74
|
+
"_vendor-recharts-B0sannek.js",
|
|
75
|
+
"_vendor-codemirror-CYHkhs7D.js"
|
|
76
76
|
]
|
|
77
77
|
}
|
|
78
78
|
}
|