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.

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 FileResponse, PlainTextResponse, Response
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 starlette.websockets import WebSocket
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.openapi.schema import OPENAPI_SCHEMA_GENERATOR
86
- from phoenix.server.api.routers.v1 import V1_ROUTES
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
- class GraphQLWithContext(GraphQL): # type: ignore
183
- def __init__(
184
- self,
185
- schema: BaseSchema,
186
- db: Callable[[], AsyncContextManager[AsyncSession]],
187
- model: Model,
188
- export_path: Path,
189
- graphiql: bool = False,
190
- corpus: Optional[Model] = None,
191
- streaming_last_updated_at: Callable[[ProjectRowId], Optional[datetime]] = lambda _: None,
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
- async def version(_: Request) -> PlainTextResponse:
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[Starlette]:
214
+ ) -> StatefulLifespan[FastAPI]:
314
215
  @contextlib.asynccontextmanager
315
- async def lifespan(_: Starlette) -> AsyncIterator[Dict[str, Any]]:
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
- async def openapi_schema(request: Request) -> Response:
342
- return OPENAPI_SCHEMA_GENERATOR.OpenAPIResponse(request=request)
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
- async def api_docs(request: Request) -> Response:
346
- return get_swagger_ui_html(openapi_url="/schema", title="arize-phoenix API")
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
- ) -> Starlette:
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
- graphql = GraphQLWithContext(
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 = Starlette(
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
- routes=V1_ROUTES
490
- + [
491
- Route("/schema", endpoint=openapi_schema, include_in_schema=False),
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.starlette import StarletteInstrumentor
508
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
538
509
 
539
- StarletteInstrumentor().instrument(tracer_provider=tracer_provider)
540
- StarletteInstrumentor.instrument_app(app, tracer_provider=tracer_provider)
541
- clean_ups.append(StarletteInstrumentor().uninstrument)
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
- {% set rendered_files = [] %}
2
- {% set rendered_chunks = [] %}
3
-
4
- {% macro render_chunk(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
-
9
- {% if chunk.css %}
10
- {% for css_file in chunk.css %}
11
- {% if css_file not in rendered_files %}
12
- <link rel="stylesheet" href="{{ basename }}/{{ css_file }}">
13
- {% set _ = rendered_files.append(css_file) %}
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
- {% endif %}
39
- {% endmacro %}
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
- {% else %}
96
- {{ render_chunk('index.tsx') }}
97
- {% endif %}
98
- </body>
103
+ {%- else -%}
104
+ {{- render_js() -}}
105
+ {%- endif -%}
106
+ </body>
99
107
  </html>
@@ -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 starlette.applications import Starlette
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: Starlette,
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
- ).raise_for_status()
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.0rc1"
1
+ __version__ = "4.12.1rc1"