arize-phoenix 4.12.0rc1__py3-none-any.whl → 4.12.1rc1__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.12.0rc1.dist-info → arize_phoenix-4.12.1rc1.dist-info}/METADATA +4 -3
- {arize_phoenix-4.12.0rc1.dist-info → arize_phoenix-4.12.1rc1.dist-info}/RECORD +24 -23
- phoenix/server/api/context.py +3 -7
- phoenix/server/api/openapi/main.py +18 -2
- phoenix/server/api/openapi/schema.py +12 -12
- phoenix/server/api/routers/v1/__init__.py +36 -83
- phoenix/server/api/routers/v1/dataset_examples.py +102 -123
- phoenix/server/api/routers/v1/datasets.py +390 -506
- phoenix/server/api/routers/v1/evaluations.py +73 -66
- phoenix/server/api/routers/v1/experiment_evaluations.py +68 -91
- phoenix/server/api/routers/v1/experiment_runs.py +98 -155
- phoenix/server/api/routers/v1/experiments.py +132 -181
- phoenix/server/api/routers/v1/pydantic_compat.py +78 -0
- phoenix/server/api/routers/v1/spans.py +144 -173
- phoenix/server/api/routers/v1/traces.py +115 -128
- phoenix/server/api/routers/v1/utils.py +95 -0
- phoenix/server/app.py +154 -183
- phoenix/server/templates/index.html +51 -43
- phoenix/server/thread_server.py +2 -2
- phoenix/session/client.py +3 -2
- phoenix/version.py +1 -1
- phoenix/server/openapi/docs.py +0 -221
- {arize_phoenix-4.12.0rc1.dist-info → arize_phoenix-4.12.1rc1.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.12.0rc1.dist-info → arize_phoenix-4.12.1rc1.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.12.0rc1.dist-info → arize_phoenix-4.12.1rc1.dist-info}/licenses/LICENSE +0 -0
phoenix/server/app.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
-
from functools import cached_property
|
|
3
2
|
import json
|
|
4
3
|
import logging
|
|
5
4
|
from datetime import datetime
|
|
5
|
+
from functools import cached_property
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import (
|
|
8
8
|
TYPE_CHECKING,
|
|
@@ -21,25 +21,24 @@ from typing import (
|
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
import strawberry
|
|
24
|
+
from fastapi import APIRouter, FastAPI
|
|
25
|
+
from fastapi.middleware.gzip import GZipMiddleware
|
|
26
|
+
from fastapi.responses import FileResponse
|
|
27
|
+
from fastapi.utils import is_body_allowed_for_status_code
|
|
24
28
|
from sqlalchemy.ext.asyncio import (
|
|
25
29
|
AsyncEngine,
|
|
26
30
|
AsyncSession,
|
|
27
31
|
async_sessionmaker,
|
|
28
32
|
)
|
|
29
|
-
from starlette.applications import Starlette
|
|
30
|
-
from starlette.datastructures import QueryParams
|
|
31
|
-
from starlette.endpoints import HTTPEndpoint
|
|
32
33
|
from starlette.exceptions import HTTPException
|
|
33
34
|
from starlette.middleware import Middleware
|
|
34
35
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
35
36
|
from starlette.requests import Request
|
|
36
|
-
from starlette.responses import
|
|
37
|
-
from starlette.routing import Mount, Route
|
|
37
|
+
from starlette.responses import PlainTextResponse, Response
|
|
38
38
|
from starlette.staticfiles import StaticFiles
|
|
39
39
|
from starlette.templating import Jinja2Templates
|
|
40
40
|
from starlette.types import Scope, StatefulLifespan
|
|
41
|
-
from
|
|
42
|
-
from strawberry.asgi import GraphQL
|
|
41
|
+
from strawberry.fastapi import GraphQLRouter
|
|
43
42
|
from strawberry.schema import BaseSchema
|
|
44
43
|
from typing_extensions import TypeAlias
|
|
45
44
|
|
|
@@ -82,11 +81,10 @@ from phoenix.server.api.dataloaders import (
|
|
|
82
81
|
TraceEvaluationsDataLoader,
|
|
83
82
|
TraceRowIdsDataLoader,
|
|
84
83
|
)
|
|
85
|
-
from phoenix.server.api.
|
|
86
|
-
from phoenix.server.api.routers.v1 import
|
|
84
|
+
from phoenix.server.api.routers.v1 import REST_API_VERSION
|
|
85
|
+
from phoenix.server.api.routers.v1 import router as v1_router
|
|
87
86
|
from phoenix.server.api.schema import schema
|
|
88
87
|
from phoenix.server.grpc_server import GrpcServer
|
|
89
|
-
from phoenix.server.openapi.docs import get_swagger_ui_html
|
|
90
88
|
from phoenix.server.telemetry import initialize_opentelemetry_tracer_provider
|
|
91
89
|
from phoenix.trace.schemas import Span
|
|
92
90
|
|
|
@@ -95,6 +93,8 @@ if TYPE_CHECKING:
|
|
|
95
93
|
|
|
96
94
|
logger = logging.getLogger(__name__)
|
|
97
95
|
|
|
96
|
+
router = APIRouter(include_in_schema=False)
|
|
97
|
+
|
|
98
98
|
templates = Jinja2Templates(directory=SERVER_DIR / "templates")
|
|
99
99
|
|
|
100
100
|
|
|
@@ -125,12 +125,9 @@ class Static(StaticFiles):
|
|
|
125
125
|
return cast(Dict[str, Any], json.load(f))
|
|
126
126
|
except FileNotFoundError as e:
|
|
127
127
|
if self._app_config.is_development:
|
|
128
|
-
logger.warning(
|
|
129
|
-
f"Web manifest not found at {self._app_config.web_manifest_path}"
|
|
130
|
-
)
|
|
131
128
|
return {}
|
|
132
129
|
raise e
|
|
133
|
-
|
|
130
|
+
|
|
134
131
|
def _sanitize_basename(self, basename: str) -> str:
|
|
135
132
|
return basename[:-1] if basename.endswith("/") else basename
|
|
136
133
|
|
|
@@ -156,7 +153,7 @@ class Static(StaticFiles):
|
|
|
156
153
|
"platform_version": phoenix.__version__,
|
|
157
154
|
"request": request,
|
|
158
155
|
"is_development": self._app_config.is_development,
|
|
159
|
-
"manifest": self._web_manifest
|
|
156
|
+
"manifest": self._web_manifest,
|
|
160
157
|
},
|
|
161
158
|
)
|
|
162
159
|
except Exception as e:
|
|
@@ -179,116 +176,20 @@ class HeadersMiddleware(BaseHTTPMiddleware):
|
|
|
179
176
|
ProjectRowId: TypeAlias = int
|
|
180
177
|
|
|
181
178
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
|
|
193
|
-
read_only: bool = False,
|
|
194
|
-
) -> None:
|
|
195
|
-
self.db = db
|
|
196
|
-
self.model = model
|
|
197
|
-
self.corpus = corpus
|
|
198
|
-
self.export_path = export_path
|
|
199
|
-
self.streaming_last_updated_at = streaming_last_updated_at
|
|
200
|
-
self.cache_for_dataloaders = cache_for_dataloaders
|
|
201
|
-
self.read_only = read_only
|
|
202
|
-
super().__init__(schema, graphiql=graphiql)
|
|
203
|
-
|
|
204
|
-
async def get_context(
|
|
205
|
-
self,
|
|
206
|
-
request: Union[Request, WebSocket],
|
|
207
|
-
response: Optional[Response] = None,
|
|
208
|
-
) -> Context:
|
|
209
|
-
return Context(
|
|
210
|
-
request=request,
|
|
211
|
-
response=response,
|
|
212
|
-
db=self.db,
|
|
213
|
-
model=self.model,
|
|
214
|
-
corpus=self.corpus,
|
|
215
|
-
export_path=self.export_path,
|
|
216
|
-
streaming_last_updated_at=self.streaming_last_updated_at,
|
|
217
|
-
data_loaders=DataLoaders(
|
|
218
|
-
average_experiment_run_latency=AverageExperimentRunLatencyDataLoader(self.db),
|
|
219
|
-
dataset_example_revisions=DatasetExampleRevisionsDataLoader(self.db),
|
|
220
|
-
dataset_example_spans=DatasetExampleSpansDataLoader(self.db),
|
|
221
|
-
document_evaluation_summaries=DocumentEvaluationSummaryDataLoader(
|
|
222
|
-
self.db,
|
|
223
|
-
cache_map=self.cache_for_dataloaders.document_evaluation_summary
|
|
224
|
-
if self.cache_for_dataloaders
|
|
225
|
-
else None,
|
|
226
|
-
),
|
|
227
|
-
document_evaluations=DocumentEvaluationsDataLoader(self.db),
|
|
228
|
-
document_retrieval_metrics=DocumentRetrievalMetricsDataLoader(self.db),
|
|
229
|
-
evaluation_summaries=EvaluationSummaryDataLoader(
|
|
230
|
-
self.db,
|
|
231
|
-
cache_map=self.cache_for_dataloaders.evaluation_summary
|
|
232
|
-
if self.cache_for_dataloaders
|
|
233
|
-
else None,
|
|
234
|
-
),
|
|
235
|
-
experiment_annotation_summaries=ExperimentAnnotationSummaryDataLoader(self.db),
|
|
236
|
-
experiment_error_rates=ExperimentErrorRatesDataLoader(self.db),
|
|
237
|
-
experiment_run_counts=ExperimentRunCountsDataLoader(self.db),
|
|
238
|
-
experiment_sequence_number=ExperimentSequenceNumberDataLoader(self.db),
|
|
239
|
-
latency_ms_quantile=LatencyMsQuantileDataLoader(
|
|
240
|
-
self.db,
|
|
241
|
-
cache_map=self.cache_for_dataloaders.latency_ms_quantile
|
|
242
|
-
if self.cache_for_dataloaders
|
|
243
|
-
else None,
|
|
244
|
-
),
|
|
245
|
-
min_start_or_max_end_times=MinStartOrMaxEndTimeDataLoader(
|
|
246
|
-
self.db,
|
|
247
|
-
cache_map=self.cache_for_dataloaders.min_start_or_max_end_time
|
|
248
|
-
if self.cache_for_dataloaders
|
|
249
|
-
else None,
|
|
250
|
-
),
|
|
251
|
-
record_counts=RecordCountDataLoader(
|
|
252
|
-
self.db,
|
|
253
|
-
cache_map=self.cache_for_dataloaders.record_count
|
|
254
|
-
if self.cache_for_dataloaders
|
|
255
|
-
else None,
|
|
256
|
-
),
|
|
257
|
-
span_descendants=SpanDescendantsDataLoader(self.db),
|
|
258
|
-
span_evaluations=SpanEvaluationsDataLoader(self.db),
|
|
259
|
-
span_projects=SpanProjectsDataLoader(self.db),
|
|
260
|
-
token_counts=TokenCountDataLoader(
|
|
261
|
-
self.db,
|
|
262
|
-
cache_map=self.cache_for_dataloaders.token_count
|
|
263
|
-
if self.cache_for_dataloaders
|
|
264
|
-
else None,
|
|
265
|
-
),
|
|
266
|
-
trace_evaluations=TraceEvaluationsDataLoader(self.db),
|
|
267
|
-
trace_row_ids=TraceRowIdsDataLoader(self.db),
|
|
268
|
-
project_by_name=ProjectByNameDataLoader(self.db),
|
|
269
|
-
span_annotations=SpanAnnotationsDataLoader(self.db),
|
|
270
|
-
),
|
|
271
|
-
cache_for_dataloaders=self.cache_for_dataloaders,
|
|
272
|
-
read_only=self.read_only,
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
class Download(HTTPEndpoint):
|
|
277
|
-
path: Path
|
|
278
|
-
|
|
279
|
-
async def get(self, request: Request) -> FileResponse:
|
|
280
|
-
params = QueryParams(request.query_params)
|
|
281
|
-
file = self.path / (params.get("filename", "") + ".parquet")
|
|
282
|
-
if not file.is_file():
|
|
283
|
-
raise HTTPException(status_code=404)
|
|
284
|
-
return FileResponse(
|
|
285
|
-
path=file,
|
|
286
|
-
filename=file.name,
|
|
287
|
-
media_type="application/x-octet-stream",
|
|
288
|
-
)
|
|
179
|
+
@router.get("/exports")
|
|
180
|
+
async def download_exported_file(request: Request, filename: str) -> FileResponse:
|
|
181
|
+
file = request.app.state.export_path / (filename + ".parquet")
|
|
182
|
+
if not file.is_file():
|
|
183
|
+
raise HTTPException(status_code=404)
|
|
184
|
+
return FileResponse(
|
|
185
|
+
path=file,
|
|
186
|
+
filename=file.name,
|
|
187
|
+
media_type="application/x-octet-stream",
|
|
188
|
+
)
|
|
289
189
|
|
|
290
190
|
|
|
291
|
-
|
|
191
|
+
@router.get("/arize_phoenix_version")
|
|
192
|
+
async def version() -> PlainTextResponse:
|
|
292
193
|
return PlainTextResponse(f"{phoenix.__version__}")
|
|
293
194
|
|
|
294
195
|
|
|
@@ -310,9 +211,9 @@ def _lifespan(
|
|
|
310
211
|
enable_prometheus: bool = False,
|
|
311
212
|
clean_ups: Iterable[Callable[[], None]] = (),
|
|
312
213
|
read_only: bool = False,
|
|
313
|
-
) -> StatefulLifespan[
|
|
214
|
+
) -> StatefulLifespan[FastAPI]:
|
|
314
215
|
@contextlib.asynccontextmanager
|
|
315
|
-
async def lifespan(_:
|
|
216
|
+
async def lifespan(_: FastAPI) -> AsyncIterator[Dict[str, Any]]:
|
|
316
217
|
async with bulk_inserter as (
|
|
317
218
|
queue_span,
|
|
318
219
|
queue_evaluation,
|
|
@@ -334,16 +235,90 @@ def _lifespan(
|
|
|
334
235
|
return lifespan
|
|
335
236
|
|
|
336
237
|
|
|
238
|
+
@router.get("/healthz")
|
|
337
239
|
async def check_healthz(_: Request) -> PlainTextResponse:
|
|
338
240
|
return PlainTextResponse("OK")
|
|
339
241
|
|
|
340
242
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
243
|
+
def create_graphql_router(
|
|
244
|
+
*,
|
|
245
|
+
schema: BaseSchema,
|
|
246
|
+
db: Callable[[], AsyncContextManager[AsyncSession]],
|
|
247
|
+
model: Model,
|
|
248
|
+
export_path: Path,
|
|
249
|
+
corpus: Optional[Model] = None,
|
|
250
|
+
streaming_last_updated_at: Callable[[ProjectRowId], Optional[datetime]] = lambda _: None,
|
|
251
|
+
cache_for_dataloaders: Optional[CacheForDataLoaders] = None,
|
|
252
|
+
read_only: bool = False,
|
|
253
|
+
) -> GraphQLRouter: # type: ignore[type-arg]
|
|
254
|
+
def get_context() -> Context:
|
|
255
|
+
return Context(
|
|
256
|
+
db=db,
|
|
257
|
+
model=model,
|
|
258
|
+
corpus=corpus,
|
|
259
|
+
export_path=export_path,
|
|
260
|
+
streaming_last_updated_at=streaming_last_updated_at,
|
|
261
|
+
data_loaders=DataLoaders(
|
|
262
|
+
average_experiment_run_latency=AverageExperimentRunLatencyDataLoader(db),
|
|
263
|
+
dataset_example_revisions=DatasetExampleRevisionsDataLoader(db),
|
|
264
|
+
dataset_example_spans=DatasetExampleSpansDataLoader(db),
|
|
265
|
+
document_evaluation_summaries=DocumentEvaluationSummaryDataLoader(
|
|
266
|
+
db,
|
|
267
|
+
cache_map=cache_for_dataloaders.document_evaluation_summary
|
|
268
|
+
if cache_for_dataloaders
|
|
269
|
+
else None,
|
|
270
|
+
),
|
|
271
|
+
document_evaluations=DocumentEvaluationsDataLoader(db),
|
|
272
|
+
document_retrieval_metrics=DocumentRetrievalMetricsDataLoader(db),
|
|
273
|
+
evaluation_summaries=EvaluationSummaryDataLoader(
|
|
274
|
+
db,
|
|
275
|
+
cache_map=cache_for_dataloaders.evaluation_summary
|
|
276
|
+
if cache_for_dataloaders
|
|
277
|
+
else None,
|
|
278
|
+
),
|
|
279
|
+
experiment_annotation_summaries=ExperimentAnnotationSummaryDataLoader(db),
|
|
280
|
+
experiment_error_rates=ExperimentErrorRatesDataLoader(db),
|
|
281
|
+
experiment_run_counts=ExperimentRunCountsDataLoader(db),
|
|
282
|
+
experiment_sequence_number=ExperimentSequenceNumberDataLoader(db),
|
|
283
|
+
latency_ms_quantile=LatencyMsQuantileDataLoader(
|
|
284
|
+
db,
|
|
285
|
+
cache_map=cache_for_dataloaders.latency_ms_quantile
|
|
286
|
+
if cache_for_dataloaders
|
|
287
|
+
else None,
|
|
288
|
+
),
|
|
289
|
+
min_start_or_max_end_times=MinStartOrMaxEndTimeDataLoader(
|
|
290
|
+
db,
|
|
291
|
+
cache_map=cache_for_dataloaders.min_start_or_max_end_time
|
|
292
|
+
if cache_for_dataloaders
|
|
293
|
+
else None,
|
|
294
|
+
),
|
|
295
|
+
record_counts=RecordCountDataLoader(
|
|
296
|
+
db,
|
|
297
|
+
cache_map=cache_for_dataloaders.record_count if cache_for_dataloaders else None,
|
|
298
|
+
),
|
|
299
|
+
span_annotations=SpanAnnotationsDataLoader(db),
|
|
300
|
+
span_descendants=SpanDescendantsDataLoader(db),
|
|
301
|
+
span_evaluations=SpanEvaluationsDataLoader(db),
|
|
302
|
+
span_projects=SpanProjectsDataLoader(db),
|
|
303
|
+
token_counts=TokenCountDataLoader(
|
|
304
|
+
db,
|
|
305
|
+
cache_map=cache_for_dataloaders.token_count if cache_for_dataloaders else None,
|
|
306
|
+
),
|
|
307
|
+
trace_evaluations=TraceEvaluationsDataLoader(db),
|
|
308
|
+
trace_row_ids=TraceRowIdsDataLoader(db),
|
|
309
|
+
project_by_name=ProjectByNameDataLoader(db),
|
|
310
|
+
),
|
|
311
|
+
cache_for_dataloaders=cache_for_dataloaders,
|
|
312
|
+
read_only=read_only,
|
|
313
|
+
)
|
|
344
314
|
|
|
345
|
-
|
|
346
|
-
|
|
315
|
+
return GraphQLRouter(
|
|
316
|
+
schema,
|
|
317
|
+
graphiql=True,
|
|
318
|
+
context_getter=get_context,
|
|
319
|
+
include_in_schema=False,
|
|
320
|
+
prefix="/graphql",
|
|
321
|
+
)
|
|
347
322
|
|
|
348
323
|
|
|
349
324
|
class SessionFactory:
|
|
@@ -394,6 +369,18 @@ def instrument_engine_if_enabled(engine: AsyncEngine) -> List[Callable[[], None]
|
|
|
394
369
|
return instrumentation_cleanups
|
|
395
370
|
|
|
396
371
|
|
|
372
|
+
async def plain_text_http_exception_handler(request: Request, exc: HTTPException) -> Response:
|
|
373
|
+
"""
|
|
374
|
+
Overrides the default handler for HTTPExceptions to return a plain text
|
|
375
|
+
response instead of a JSON response. For the original source code, see
|
|
376
|
+
https://github.com/tiangolo/fastapi/blob/d3cdd3bbd14109f3b268df7ca496e24bb64593aa/fastapi/exception_handlers.py#L11
|
|
377
|
+
"""
|
|
378
|
+
headers = getattr(exc, "headers", None)
|
|
379
|
+
if not is_body_allowed_for_status_code(exc.status_code):
|
|
380
|
+
return Response(status_code=exc.status_code, headers=headers)
|
|
381
|
+
return PlainTextResponse(str(exc.detail), status_code=exc.status_code, headers=headers)
|
|
382
|
+
|
|
383
|
+
|
|
397
384
|
def create_app(
|
|
398
385
|
db: SessionFactory,
|
|
399
386
|
export_path: Path,
|
|
@@ -408,7 +395,7 @@ def create_app(
|
|
|
408
395
|
initial_evaluations: Optional[Iterable[pb.Evaluation]] = None,
|
|
409
396
|
serve_ui: bool = True,
|
|
410
397
|
clean_up_callbacks: List[Callable[[], None]] = [],
|
|
411
|
-
) ->
|
|
398
|
+
) -> FastAPI:
|
|
412
399
|
clean_ups: List[Callable[[], None]] = clean_up_callbacks # To be called at app shutdown.
|
|
413
400
|
initial_batch_of_spans: Iterable[Tuple[Span, str]] = (
|
|
414
401
|
()
|
|
@@ -451,7 +438,7 @@ def create_app(
|
|
|
451
438
|
|
|
452
439
|
strawberry_extensions.append(_OpenTelemetryExtension)
|
|
453
440
|
|
|
454
|
-
|
|
441
|
+
graphql_router = create_graphql_router(
|
|
455
442
|
db=db,
|
|
456
443
|
schema=strawberry.Schema(
|
|
457
444
|
query=schema.query,
|
|
@@ -462,7 +449,6 @@ def create_app(
|
|
|
462
449
|
model=model,
|
|
463
450
|
corpus=corpus,
|
|
464
451
|
export_path=export_path,
|
|
465
|
-
graphiql=True,
|
|
466
452
|
streaming_last_updated_at=bulk_inserter.last_updated_at,
|
|
467
453
|
cache_for_dataloaders=cache_for_dataloaders,
|
|
468
454
|
read_only=read_only,
|
|
@@ -473,7 +459,9 @@ def create_app(
|
|
|
473
459
|
prometheus_middlewares = [Middleware(PrometheusMiddleware)]
|
|
474
460
|
else:
|
|
475
461
|
prometheus_middlewares = []
|
|
476
|
-
app =
|
|
462
|
+
app = FastAPI(
|
|
463
|
+
title="Arize-Phoenix REST API",
|
|
464
|
+
version=REST_API_VERSION,
|
|
477
465
|
lifespan=_lifespan(
|
|
478
466
|
read_only=read_only,
|
|
479
467
|
bulk_inserter=bulk_inserter,
|
|
@@ -485,58 +473,41 @@ def create_app(
|
|
|
485
473
|
Middleware(HeadersMiddleware),
|
|
486
474
|
*prometheus_middlewares,
|
|
487
475
|
],
|
|
476
|
+
exception_handlers={HTTPException: plain_text_http_exception_handler},
|
|
488
477
|
debug=debug,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
Route("/arize_phoenix_version", version),
|
|
493
|
-
Route("/healthz", check_healthz),
|
|
494
|
-
Route(
|
|
495
|
-
"/exports",
|
|
496
|
-
type(
|
|
497
|
-
"DownloadExports",
|
|
498
|
-
(Download,),
|
|
499
|
-
{"path": export_path},
|
|
500
|
-
),
|
|
501
|
-
),
|
|
502
|
-
Route(
|
|
503
|
-
"/docs",
|
|
504
|
-
api_docs,
|
|
505
|
-
),
|
|
506
|
-
Route(
|
|
507
|
-
"/graphql",
|
|
508
|
-
graphql,
|
|
509
|
-
),
|
|
510
|
-
]
|
|
511
|
-
+ (
|
|
512
|
-
[
|
|
513
|
-
Mount(
|
|
514
|
-
"/",
|
|
515
|
-
app=Static(
|
|
516
|
-
directory=SERVER_DIR / "static",
|
|
517
|
-
app_config=AppConfig(
|
|
518
|
-
has_inferences=model.is_empty is not True,
|
|
519
|
-
has_corpus=corpus is not None,
|
|
520
|
-
min_dist=umap_params.min_dist,
|
|
521
|
-
n_neighbors=umap_params.n_neighbors,
|
|
522
|
-
n_samples=umap_params.n_samples,
|
|
523
|
-
is_development=dev,
|
|
524
|
-
web_manifest_path=SERVER_DIR / "static" / ".vite" / "manifest.json",
|
|
525
|
-
),
|
|
526
|
-
),
|
|
527
|
-
name="static",
|
|
528
|
-
),
|
|
529
|
-
]
|
|
530
|
-
if serve_ui
|
|
531
|
-
else []
|
|
532
|
-
),
|
|
478
|
+
swagger_ui_parameters={
|
|
479
|
+
"defaultModelsExpandDepth": -1, # hides the schema section in the Swagger UI
|
|
480
|
+
},
|
|
533
481
|
)
|
|
534
482
|
app.state.read_only = read_only
|
|
483
|
+
app.state.export_path = export_path
|
|
484
|
+
app.include_router(v1_router)
|
|
485
|
+
app.include_router(router)
|
|
486
|
+
app.include_router(graphql_router)
|
|
487
|
+
app.add_middleware(GZipMiddleware)
|
|
488
|
+
if serve_ui:
|
|
489
|
+
app.mount(
|
|
490
|
+
"/",
|
|
491
|
+
app=Static(
|
|
492
|
+
directory=SERVER_DIR / "static",
|
|
493
|
+
app_config=AppConfig(
|
|
494
|
+
has_inferences=model.is_empty is not True,
|
|
495
|
+
has_corpus=corpus is not None,
|
|
496
|
+
min_dist=umap_params.min_dist,
|
|
497
|
+
n_neighbors=umap_params.n_neighbors,
|
|
498
|
+
n_samples=umap_params.n_samples,
|
|
499
|
+
is_development=dev,
|
|
500
|
+
web_manifest_path=SERVER_DIR / "static" / ".vite" / "manifest.json",
|
|
501
|
+
),
|
|
502
|
+
),
|
|
503
|
+
name="static",
|
|
504
|
+
)
|
|
505
|
+
|
|
535
506
|
app.state.db = db
|
|
536
507
|
if tracer_provider:
|
|
537
|
-
from opentelemetry.instrumentation.
|
|
508
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
538
509
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
clean_ups.append(
|
|
510
|
+
FastAPIInstrumentor().instrument(tracer_provider=tracer_provider)
|
|
511
|
+
FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider)
|
|
512
|
+
clean_ups.append(FastAPIInstrumentor().uninstrument)
|
|
542
513
|
return app
|
|
@@ -1,43 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
{
|
|
3
|
-
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
{%- set rendered_chunks = [] -%}
|
|
2
|
+
{%- set css_links = [] -%}
|
|
3
|
+
{%- set js_scripts = [] -%}
|
|
4
|
+
{%- macro collect_assets(chunk_name, level=0) -%}
|
|
5
|
+
{% if chunk_name not in rendered_chunks %}
|
|
6
|
+
{% set _ = rendered_chunks.append(chunk_name) %}
|
|
7
|
+
{% set chunk = manifest[chunk_name] %}
|
|
8
|
+
{% if chunk.css %}
|
|
9
|
+
{% for css_file in chunk.css %}
|
|
10
|
+
{% if css_file not in css_links %}
|
|
11
|
+
{% set _ = css_links.append((basename, css_file)) %}
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% endfor %}
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if chunk.imports %}
|
|
16
|
+
{% for import in chunk.imports %}
|
|
17
|
+
{% set _ = collect_assets(import, level + 1) %}
|
|
18
|
+
{% endfor %}
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% if chunk.file.endswith('.js') %}
|
|
21
|
+
{% if chunk.file not in js_scripts %}
|
|
22
|
+
{% set _ = js_scripts.append((basename, chunk.file, level == 0)) %}
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% elif chunk.file.endswith('.css') %}
|
|
25
|
+
{% if chunk.file not in css_links %}
|
|
26
|
+
{% set _ = css_links.append((basename, chunk.file)) %}
|
|
27
|
+
{% endif %}
|
|
14
28
|
{% endif %}
|
|
15
|
-
{% endfor %}
|
|
16
|
-
{% endif %}
|
|
17
|
-
|
|
18
|
-
{% if chunk.imports %}
|
|
19
|
-
{% for import in chunk.imports %}
|
|
20
|
-
{{ render_chunk(import, level + 1) }}
|
|
21
|
-
{% endfor %}
|
|
22
|
-
{% endif %}
|
|
23
|
-
|
|
24
|
-
{% if level == 0 %}
|
|
25
|
-
{% if chunk.file.endswith('.js') and chunk.file not in rendered_files %}
|
|
26
|
-
<script type="module" src="{{ basename }}/{{ chunk.file }}"></script>
|
|
27
|
-
{% set _ = rendered_files.append(chunk.file) %}
|
|
28
|
-
{% elif chunk.file.endswith('.css') and chunk.file not in rendered_files %}
|
|
29
|
-
<link rel="stylesheet" href="{{ basename }}/{{ chunk.file }}">
|
|
30
|
-
{% set _ = rendered_files.append(chunk.file) %}
|
|
31
|
-
{% endif %}
|
|
32
|
-
{% endif %}
|
|
33
|
-
|
|
34
|
-
{% if chunk.file.endswith('.js') and chunk.file not in rendered_files %}
|
|
35
|
-
<link rel="modulepreload" href="{{ basename }}/{{ chunk.file }}">
|
|
36
|
-
{% set _ = rendered_files.append(chunk.file) %}
|
|
37
29
|
{% endif %}
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
30
|
+
{%- endmacro -%}
|
|
31
|
+
{%- macro render_css() -%}
|
|
32
|
+
{%- for basename, css_file in css_links -%}
|
|
33
|
+
<link rel="stylesheet" href="{{ basename }}/{{ css_file }}">
|
|
34
|
+
{% endfor -%}
|
|
35
|
+
{%- endmacro -%}
|
|
36
|
+
{%- macro render_js() -%}
|
|
37
|
+
{%- for basename, js_file, is_entry in js_scripts -%}
|
|
38
|
+
{%- if is_entry -%}
|
|
39
|
+
<script type="module" src="{{ basename }}/{{ js_file }}"></script>
|
|
40
|
+
{% else -%}
|
|
41
|
+
<link rel="modulepreload" href="{{ basename }}/{{ js_file }}">
|
|
42
|
+
{% endif -%}
|
|
43
|
+
{%- endfor -%}
|
|
44
|
+
{%- endmacro -%}
|
|
41
45
|
<!DOCTYPE html>
|
|
42
46
|
<html>
|
|
43
47
|
<head>
|
|
@@ -55,6 +59,10 @@
|
|
|
55
59
|
rel="stylesheet"
|
|
56
60
|
href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700;900&display=swap"
|
|
57
61
|
/>
|
|
62
|
+
{% if not is_development -%}
|
|
63
|
+
{% set _ = collect_assets('index.tsx') -%}
|
|
64
|
+
{{- render_css() -}}
|
|
65
|
+
{%- endif -%}
|
|
58
66
|
<script src="{{basename}}/modernizr.js"></script>
|
|
59
67
|
</head>
|
|
60
68
|
<body>
|
|
@@ -82,7 +90,7 @@
|
|
|
82
90
|
writable: false
|
|
83
91
|
});
|
|
84
92
|
})()</script>
|
|
85
|
-
{% if is_development
|
|
93
|
+
{% if is_development -%}
|
|
86
94
|
<script type="module">
|
|
87
95
|
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
|
|
88
96
|
RefreshRuntime.injectIntoGlobalHook(window)
|
|
@@ -92,8 +100,8 @@
|
|
|
92
100
|
</script>
|
|
93
101
|
<script type="module" src="http://localhost:5173/@vite/client"></script>
|
|
94
102
|
<script type="module" src="http://localhost:5173/index.tsx"></script>
|
|
95
|
-
{
|
|
96
|
-
{{
|
|
97
|
-
{
|
|
98
|
-
|
|
103
|
+
{%- else -%}
|
|
104
|
+
{{- render_js() -}}
|
|
105
|
+
{%- endif -%}
|
|
106
|
+
</body>
|
|
99
107
|
</html>
|
phoenix/server/thread_server.py
CHANGED
|
@@ -4,7 +4,7 @@ from threading import Thread
|
|
|
4
4
|
from time import sleep, time
|
|
5
5
|
from typing import Generator
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from fastapi import FastAPI
|
|
8
8
|
from uvicorn import Config, Server
|
|
9
9
|
from uvicorn.config import LoopSetupType
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ class ThreadServer(Server):
|
|
|
24
24
|
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
27
|
-
app:
|
|
27
|
+
app: FastAPI,
|
|
28
28
|
host: str,
|
|
29
29
|
port: int,
|
|
30
30
|
root_path: str,
|
phoenix/session/client.py
CHANGED
|
@@ -300,14 +300,15 @@ class Client(TraceDataExtractor):
|
|
|
300
300
|
for otlp_span in otlp_spans:
|
|
301
301
|
serialized = otlp_span.SerializeToString()
|
|
302
302
|
content = gzip.compress(serialized)
|
|
303
|
-
self._client.post(
|
|
303
|
+
response = self._client.post(
|
|
304
304
|
url=urljoin(self._base_url, "v1/traces"),
|
|
305
305
|
content=content,
|
|
306
306
|
headers={
|
|
307
307
|
"content-type": "application/x-protobuf",
|
|
308
308
|
"content-encoding": "gzip",
|
|
309
309
|
},
|
|
310
|
-
)
|
|
310
|
+
)
|
|
311
|
+
response.raise_for_status()
|
|
311
312
|
|
|
312
313
|
def _get_dataset_id_by_name(self, name: str) -> str:
|
|
313
314
|
"""
|
phoenix/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "4.12.
|
|
1
|
+
__version__ = "4.12.1rc1"
|