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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arize-phoenix
3
- Version: 11.15.0
3
+ Version: 11.16.0
4
4
  Summary: AI Observability and Evaluation
5
5
  Project-URL: Documentation, https://arize.com/docs/phoenix/
6
6
  Project-URL: Issues, https://github.com/Arize-ai/phoenix/issues
@@ -6,7 +6,7 @@ phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
6
6
  phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
7
7
  phoenix/services.py,sha256=ngkyKGVatX3cO2WJdo2hKdaVKP-xJCMvqthvga6kJss,5196
8
8
  phoenix/settings.py,sha256=2kHfT3BNOVd4dAO1bq-syEQbHSG8oX2-7NhOwK2QREk,896
9
- phoenix/version.py,sha256=W6kx40mj0SUHyBQQqw1zvDl_2qQBQ-TkvJWoEy2ezRk,24
9
+ phoenix/version.py,sha256=1jPwVRFknFjr1Z2-4KMQGv_ONw2Eybch5xZM1zNP1rg,24
10
10
  phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
12
12
  phoenix/core/model.py,sha256=qBFraOtmwCCnWJltKNP18DDG0mULXigytlFsa6YOz6k,4837
@@ -22,7 +22,7 @@ phoenix/db/enums.py,sha256=w3O5YuJEEzVTwVDZb8b2UUFhU8yK_GosF081VVrrno0,188
22
22
  phoenix/db/facilitator.py,sha256=aRbkIJkIDP2zMsLKbO7Y8jJq4U2HbV7Lf6GYVWXVImU,20151
23
23
  phoenix/db/helpers.py,sha256=dsGONSgkhmVtjMpJh-84KRVTf5uPdQ5c8O2AhUgHkRg,14150
24
24
  phoenix/db/migrate.py,sha256=oUrXH8yEbcpL4eh09aSCuUiSrhFli0eT5D_j4ZmYChY,2797
25
- phoenix/db/models.py,sha256=0avhbUmDEBZ1_NgBSr9Ck9BYxXtJTraPfQqDGZFE2-Y,60388
25
+ phoenix/db/models.py,sha256=bxyBRSST8rqBKcAyPyyDHmkv9AadaE3XmQnpcaMvvnk,61588
26
26
  phoenix/db/pg_config.py,sha256=h6mB7qF7t4Zk6VGvAiyefHGVu74o-yJynaWzeE39k9Y,6001
27
27
  phoenix/db/insertion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  phoenix/db/insertion/constants.py,sha256=8wifm7X-1XvroZ__R2Gc96NsgLhTDn0zXl4lehlXtcA,70
@@ -95,7 +95,7 @@ phoenix/server/app.py,sha256=00un2qg4eUmHr37TtZlccmPgRnI1D5FB5r2zwIfI9W4,48047
95
95
  phoenix/server/authorization.py,sha256=OxROn7ibpKtCTrcgDkzWuNxVaQcSQ8MAx7zbjZiliK0,3201
96
96
  phoenix/server/bearer_auth.py,sha256=f4v4W94KyTdGGCPsK1tXOe0vouPuvanAEa03XSdCvPE,6650
97
97
  phoenix/server/dml_event.py,sha256=8UciN7W8X_IqQfAnAeAh68BezNmfxSxuTeD6IUerTW8,2911
98
- phoenix/server/dml_event_handler.py,sha256=gkDIONyTz9sLbSA6qOZCigiO5val-fvVcLzDrWNcVcg,8306
98
+ phoenix/server/dml_event_handler.py,sha256=71_iPcHiJ4E-8Z7sGL2j0vx_RpkmcMVEUFIBsWrdp-E,8483
99
99
  phoenix/server/grpc_server.py,sha256=ahHC394gFZYM3h4FmjQxZwL-a4x3mWmV2EdXYFlNEC8,4676
100
100
  phoenix/server/jwt_store.py,sha256=B6uVildN_dQDTG_-aHHvuVSI7wIVK1yvED-_y6se2GU,16905
101
101
  phoenix/server/main.py,sha256=UBwxrQIEE7ci-SbE6GAlRYmbMHooI6JYG6sG-UpBFFs,18905
@@ -266,7 +266,7 @@ phoenix/server/api/routers/v1/models.py,sha256=p3gJN-9SWiUYTUTft4bZMsZVCBNTb4nN1
266
266
  phoenix/server/api/routers/v1/projects.py,sha256=32GwTLsaFgQLVNdjrlrGe90XT3pIX1N7-zX9D9_J_4w,12701
267
267
  phoenix/server/api/routers/v1/prompts.py,sha256=chRYcLkOYDJdJfVZVukVTUyIRnLPvsJCg41CuPxOIU8,26695
268
268
  phoenix/server/api/routers/v1/spans.py,sha256=ETH6I14O_zY9IW69Fo-LxL796BR3xgt8qdzwqzYAvbE,44208
269
- phoenix/server/api/routers/v1/traces.py,sha256=63T-WYiwh8X3Sp6u_OFfA9zLLKk6cNsnciiDyUzKLVk,8561
269
+ phoenix/server/api/routers/v1/traces.py,sha256=uLASCHMgU13tUhuWXnXqaom1crrQVpXi9PUtsyDXU9Y,10318
270
270
  phoenix/server/api/routers/v1/users.py,sha256=hUZCe7ctJqEkSJBe046a0OAFMLZodtyO7NLP7U6S8Pg,11986
271
271
  phoenix/server/api/routers/v1/utils.py,sha256=oXIOGPzPTkE0ZWUTRCoRIQQ7wTzoSwtWFaUSjlGBqts,4960
272
272
  phoenix/server/api/types/Annotation.py,sha256=gsl8CwjIbDUbZRj4d9USwZ_w_Tkz4i7zuZh9ftV80jA,1132
@@ -321,7 +321,7 @@ phoenix/server/api/types/ModelInterface.py,sha256=Qe7H23wDb_Q2-HmeY2t0R5Jsn4aAfY
321
321
  phoenix/server/api/types/NumericRange.py,sha256=afEjgF97Go_OvmjMggbPBt-zGM8IONewAyEiKEHRds0,192
322
322
  phoenix/server/api/types/PerformanceMetric.py,sha256=KFkmJDqP43eDUtARQOUqR7NYcxvL6Vh2uisHWU6H3ko,387
323
323
  phoenix/server/api/types/PlaygroundModel.py,sha256=IqJFxsAAJMRyaFI9ryI3GQrpFOJ5Llf6kIutEO-tFvM,321
324
- phoenix/server/api/types/Project.py,sha256=u_Rlk61xrv5ZoJdOl5ghG1Wo7TK1eqydDZ7GeNPDgLM,69630
324
+ phoenix/server/api/types/Project.py,sha256=AxKlA7FHlI48uRKx5-MRzMzEyyWPZPffX_iZOV0jeJs,69652
325
325
  phoenix/server/api/types/ProjectSession.py,sha256=uwqTsDTfSGz13AvP-cwS_mJR5JZ1lHqu10ungbl7g5s,6245
326
326
  phoenix/server/api/types/ProjectTraceRetentionPolicy.py,sha256=tYy2kgalPDyuaYZr0VUHjH0YpXaiF_QOzg5yfaV_c7c,3782
327
327
  phoenix/server/api/types/Prompt.py,sha256=ccP4eq1e38xbF0afclGWLOuDpBVpNbJ3AOSRClF8yFQ,4955
@@ -439,9 +439,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
439
439
  phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
440
440
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
441
441
  phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
442
- arize_phoenix-11.15.0.dist-info/METADATA,sha256=fTesMufMP3UOzOvqbdz-tSZUGvICm1OpUgz3TMf1Bsg,30851
443
- arize_phoenix-11.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
444
- arize_phoenix-11.15.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
445
- arize_phoenix-11.15.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
446
- arize_phoenix-11.15.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
447
- arize_phoenix-11.15.0.dist-info/RECORD,,
442
+ arize_phoenix-11.16.0.dist-info/METADATA,sha256=Vhoaw8m0XPZKoPzmUJxOrEfcQ-UZhTdIGXceg5fEiuU,30851
443
+ arize_phoenix-11.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
444
+ arize_phoenix-11.16.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
445
+ arize_phoenix-11.16.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
446
+ arize_phoenix-11.16.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
447
+ arize_phoenix-11.16.0.dist-info/RECORD,,
phoenix/db/models.py CHANGED
@@ -841,6 +841,41 @@ def _(element: Any, compiler: Any, **kw: Any) -> Any:
841
841
  return compiler.process(func.text_contains(string, substring) > 0, **kw)
842
842
 
843
843
 
844
+ class CaseInsensitiveContains(expression.FunctionElement[bool]):
845
+ # See https://docs.sqlalchemy.org/en/20/core/compiler.html
846
+ inherit_cache = True
847
+ type = Boolean()
848
+ name = "case_insensitive_contains"
849
+
850
+
851
+ @compiles(CaseInsensitiveContains)
852
+ def _(element: Any, compiler: Any, **kw: Any) -> Any:
853
+ string, substring = list(element.clauses)
854
+ result = compiler.process(func.lower(string).contains(func.lower(substring)), **kw)
855
+ return result
856
+
857
+
858
+ @compiles(CaseInsensitiveContains, "postgresql")
859
+ def _(element: Any, compiler: Any, **kw: Any) -> Any:
860
+ string, substring = list(element.clauses)
861
+ escaped = func.replace(
862
+ func.replace(func.replace(substring, "\\", "\\\\"), "%", "\\%"), "_", "\\_"
863
+ )
864
+ pattern = func.concat("%", escaped, "%")
865
+ result = compiler.process(string.ilike(pattern), **kw)
866
+ return result
867
+
868
+
869
+ @compiles(CaseInsensitiveContains, "sqlite")
870
+ def _(element: Any, compiler: Any, **kw: Any) -> Any:
871
+ # Use sqlean's `text_lower` to handle non-ASCII characters
872
+ string, substring = list(element.clauses)
873
+ result = compiler.process(
874
+ func.text_contains(func.text_lower(string), func.text_lower(substring)), **kw
875
+ )
876
+ return result
877
+
878
+
844
879
  async def init_models(engine: AsyncEngine) -> None:
845
880
  async with engine.begin() as conn:
846
881
  await conn.run_sync(Base.metadata.create_all)
@@ -2,14 +2,14 @@ import gzip
2
2
  import zlib
3
3
  from typing import Any, Literal, Optional
4
4
 
5
- from fastapi import APIRouter, BackgroundTasks, Depends, Header, HTTPException, Query
5
+ from fastapi import APIRouter, BackgroundTasks, Depends, Header, HTTPException, Path, Query
6
6
  from google.protobuf.message import DecodeError
7
7
  from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import (
8
8
  ExportTraceServiceRequest,
9
9
  ExportTraceServiceResponse,
10
10
  )
11
11
  from pydantic import Field
12
- from sqlalchemy import insert, select
12
+ from sqlalchemy import delete, insert, select
13
13
  from starlette.concurrency import run_in_threadpool
14
14
  from starlette.datastructures import State
15
15
  from starlette.requests import Request
@@ -26,7 +26,7 @@ from phoenix.db.insertion.helpers import as_kv
26
26
  from phoenix.db.insertion.types import Precursors
27
27
  from phoenix.server.authorization import is_not_locked
28
28
  from phoenix.server.bearer_auth import PhoenixUser
29
- from phoenix.server.dml_event import TraceAnnotationInsertEvent
29
+ from phoenix.server.dml_event import SpanDeleteEvent, TraceAnnotationInsertEvent
30
30
  from phoenix.trace.otel import decode_otlp_span
31
31
  from phoenix.utilities.project import get_project_name
32
32
 
@@ -225,3 +225,52 @@ async def _add_spans(req: ExportTraceServiceRequest, state: State) -> None:
225
225
  for otlp_span in scope_span.spans:
226
226
  span = await run_in_threadpool(decode_otlp_span, otlp_span)
227
227
  await state.queue_span_for_bulk_insert(span, project_name)
228
+
229
+
230
+ @router.delete(
231
+ "/traces/{trace_id}",
232
+ operation_id="deleteTrace",
233
+ summary="Delete a trace by trace_id",
234
+ description=(
235
+ "Delete an entire trace by its OpenTelemetry trace_id. "
236
+ "This will permanently remove all spans in the trace and their associated data."
237
+ ),
238
+ responses=add_errors_to_responses([HTTP_404_NOT_FOUND]),
239
+ status_code=204, # No Content for successful deletion
240
+ )
241
+ async def delete_trace(
242
+ request: Request,
243
+ trace_id: str = Path(description="The OpenTelemetry trace_id of the trace to delete"),
244
+ ) -> None:
245
+ """
246
+ Delete a trace by trace_id.
247
+
248
+ This endpoint will:
249
+ 1. Find and delete the trace with the given trace_id (and all its spans via CASCADE)
250
+ 2. Trigger cache invalidation events
251
+ 3. Return 204 No Content on success
252
+
253
+ Note: This deletes the entire trace, including all spans, which maintains data consistency
254
+ and avoids orphaned spans or inconsistent cached cumulative fields.
255
+ """
256
+ async with request.app.state.db() as session:
257
+ # Delete the trace directly and get project_id for cache invalidation
258
+ delete_stmt = (
259
+ delete(models.Trace)
260
+ .where(models.Trace.trace_id == trace_id)
261
+ .returning(models.Trace.project_rowid)
262
+ )
263
+
264
+ project_id = await session.scalar(delete_stmt)
265
+
266
+ if project_id is None:
267
+ raise HTTPException(
268
+ status_code=HTTP_404_NOT_FOUND,
269
+ detail=f"Trace with trace_id '{trace_id}' not found",
270
+ )
271
+
272
+ # Trigger cache invalidation event
273
+ request.state.event_queue.put(SpanDeleteEvent((project_id,)))
274
+
275
+ # Return 204 No Content (successful deletion with no response body)
276
+ return None
@@ -404,11 +404,11 @@ class Project(Node):
404
404
  .where(models.Span.parent_id.is_(None))
405
405
  .where(
406
406
  or_(
407
- models.TextContains(
407
+ models.CaseInsensitiveContains(
408
408
  models.Span.attributes[INPUT_VALUE].as_string(),
409
409
  filter_io_substring,
410
410
  ),
411
- models.TextContains(
411
+ models.CaseInsensitiveContains(
412
412
  models.Span.attributes[OUTPUT_VALUE].as_string(),
413
413
  filter_io_substring,
414
414
  ),
@@ -128,6 +128,10 @@ class _SpanDmlEventHandler(_DmlEventHandler[SpanDmlEvent]):
128
128
  class _SpanDeleteEventHandler(_SpanDmlEventHandler):
129
129
  @staticmethod
130
130
  def _clear(cache: CacheForDataLoaders, project_id: int) -> None:
131
+ # Call parent's cache invalidation first (core span caches)
132
+ _SpanDmlEventHandler._clear(cache, project_id)
133
+
134
+ # Then invalidate annotation-specific caches
131
135
  cache.annotation_summary.invalidate_project(project_id)
132
136
  cache.document_evaluation_summary.invalidate_project(project_id)
133
137
 
phoenix/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "11.15.0"
1
+ __version__ = "11.16.0"