arize-phoenix 11.30.0__py3-none-any.whl → 11.31.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 (25) hide show
  1. {arize_phoenix-11.30.0.dist-info → arize_phoenix-11.31.0.dist-info}/METADATA +17 -17
  2. {arize_phoenix-11.30.0.dist-info → arize_phoenix-11.31.0.dist-info}/RECORD +24 -23
  3. phoenix/db/types/trace_retention.py +1 -1
  4. phoenix/server/api/dataloaders/document_evaluations.py +6 -9
  5. phoenix/server/api/routers/v1/annotations.py +128 -5
  6. phoenix/server/api/routers/v1/documents.py +47 -79
  7. phoenix/server/api/routers/v1/spans.py +2 -48
  8. phoenix/server/api/routers/v1/traces.py +19 -55
  9. phoenix/server/api/types/Dataset.py +8 -66
  10. phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
  11. phoenix/server/api/types/DocumentAnnotation.py +92 -0
  12. phoenix/server/api/types/Span.py +8 -2
  13. phoenix/server/api/types/TraceAnnotation.py +8 -5
  14. phoenix/server/cost_tracking/model_cost_manifest.json +91 -0
  15. phoenix/server/static/.vite/manifest.json +9 -9
  16. phoenix/server/static/assets/{components-BBwXqJXQ.js → components-BjW5gAwL.js} +1 -1
  17. phoenix/server/static/assets/{index-C_gU3x10.js → index-3OI8VV_W.js} +1 -1
  18. phoenix/server/static/assets/{pages-YmQb55Uo.js → pages-CQfUODtD.js} +57 -56
  19. phoenix/trace/projects.py +6 -0
  20. phoenix/version.py +1 -1
  21. phoenix/server/api/types/Evaluation.py +0 -40
  22. {arize_phoenix-11.30.0.dist-info → arize_phoenix-11.31.0.dist-info}/WHEEL +0 -0
  23. {arize_phoenix-11.30.0.dist-info → arize_phoenix-11.31.0.dist-info}/entry_points.txt +0 -0
  24. {arize_phoenix-11.30.0.dist-info → arize_phoenix-11.31.0.dist-info}/licenses/IP_NOTICE +0 -0
  25. {arize_phoenix-11.30.0.dist-info → arize_phoenix-11.31.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arize-phoenix
3
- Version: 11.30.0
3
+ Version: 11.31.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
@@ -201,14 +201,14 @@ The `arize-phoenix` package includes the entire Phoenix platfom. However if you
201
201
 
202
202
  ### Subpackages
203
203
 
204
- | Package | Language | Description |
205
- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
206
- | [arize-phoenix-otel](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-otel) | Python [![PyPI Version](https://img.shields.io/pypi/v/arize-phoenix-otel)](https://pypi.org/project/arize-phoenix-otel/) | Provides a lightweight wrapper around OpenTelemetry primitives with Phoenix-aware defaults |
207
- | [arize-phoenix-client](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-client) | Python [![PyPI Version](https://img.shields.io/pypi/v/arize-phoenix-client)](https://pypi.org/project/arize-phoenix-client/) | Lightweight client for interacting with the Phoenix server via its OpenAPI REST interface |
208
- | [arize-phoenix-evals](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-evals) | Python [![PyPI Version](https://img.shields.io/pypi/v/arize-phoenix-evals)](https://pypi.org/project/arize-phoenix-evals/) | Tooling to evaluate LLM applications including RAG relevance, answer relevance, and more |
209
- | [@arizeai/phoenix-client](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-client) | JavaScript [![NPM Version](https://img.shields.io/npm/v/%40arizeai%2Fphoenix-client)](https://www.npmjs.com/package/@arizeai/phoenix-client) | Client for the Arize Phoenix API |
210
- | [@arizeai/phoenix-evals](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-evals) | TypeScript [![NPM Version](https://img.shields.io/npm/v/%40arizeai%2Fphoenix-evals)](https://www.npmjs.com/package/@arizeai/phoenix-evals) | TypeScript evaluation library for LLM applications (alpha release) |
211
- | [@arizeai/phoenix-mcp](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-mcp) | JavaScript [![NPM Version](https://img.shields.io/npm/v/%40arizeai%2Fphoenix-mcp)](https://www.npmjs.com/package/@arizeai/phoenix-mcp) | MCP server implementation for Arize Phoenix providing unified interface to Phoenix's capabilities |
204
+ | Package | Version & Docs | Description |
205
+ | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
206
+ | [arize-phoenix-otel](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-otel) | [![PyPI Version](https://img.shields.io/pypi/v/arize-phoenix-otel)](https://pypi.org/project/arize-phoenix-otel/) [![Docs](https://img.shields.io/badge/docs-blue?logo=readthedocs&logoColor=white)](https://arize-phoenix.readthedocs.io/projects/otel/en/latest/index.html) | Provides a lightweight wrapper around OpenTelemetry primitives with Phoenix-aware defaults |
207
+ | [arize-phoenix-client](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-client) | [![PyPI Version](https://img.shields.io/pypi/v/arize-phoenix-client)](https://pypi.org/project/arize-phoenix-client/) [![Docs](https://img.shields.io/badge/docs-blue?logo=readthedocs&logoColor=white)](https://arize-phoenix.readthedocs.io/projects/client/en/latest/index.html) | Lightweight client for interacting with the Phoenix server via its OpenAPI REST interface |
208
+ | [arize-phoenix-evals](https://github.com/Arize-ai/phoenix/tree/main/packages/phoenix-evals) | [![PyPI Version](https://img.shields.io/pypi/v/arize-phoenix-evals)](https://pypi.org/project/arize-phoenix-evals/) [![Docs](https://img.shields.io/badge/docs-blue?logo=readthedocs&logoColor=white)](https://arize-phoenix.readthedocs.io/projects/evals/en/latest/index.html) | Tooling to evaluate LLM applications including RAG relevance, answer relevance, and more |
209
+ | [@arizeai/phoenix-client](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-client) | [![NPM Version](https://img.shields.io/npm/v/%40arizeai%2Fphoenix-client)](https://www.npmjs.com/package/@arizeai/phoenix-client) [![Docs](https://img.shields.io/badge/docs-blue?logo=typescript&logoColor=white)](https://arize-ai.github.io/phoenix/) | Client for the Arize Phoenix API |
210
+ | [@arizeai/phoenix-evals](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-evals) | [![NPM Version](https://img.shields.io/npm/v/%40arizeai%2Fphoenix-evals)](https://www.npmjs.com/package/@arizeai/phoenix-evals) [![Docs](https://img.shields.io/badge/docs-blue?logo=typescript&logoColor=white)](https://arize-ai.github.io/phoenix/) | TypeScript evaluation library for LLM applications (alpha release) |
211
+ | [@arizeai/phoenix-mcp](https://github.com/Arize-ai/phoenix/tree/main/js/packages/phoenix-mcp) | [![NPM Version](https://img.shields.io/npm/v/%40arizeai%2Fphoenix-mcp)](https://www.npmjs.com/package/@arizeai/phoenix-mcp) [![Docs](https://img.shields.io/badge/docs-blue?logo=markdown&logoColor=white)](./js/packages/phoenix-mcp/README.md) | MCP server implementation for Arize Phoenix providing unified interface to Phoenix's capabilities |
212
212
 
213
213
  ## Tracing Integrations
214
214
 
@@ -256,17 +256,17 @@ Phoenix is built on top of OpenTelemetry and is vendor, language, and framework
256
256
  | Integration | Package | Version Badge |
257
257
  | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
258
258
  | [LangChain4j](https://github.com/Arize-ai/openinference/tree/main/java/instrumentation/openinference-instrumentation-langchain4j) | `openinference-instrumentation-langchain4j` | [![Maven Central](https://img.shields.io/maven-central/v/com.arize/openinference-instrumentation-langchain4j.svg)](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-langchain4j) |
259
- | [SpringAI](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-springAI) | `openinference-instrumentation-springAI` | [![Maven Central](https://img.shields.io/maven-central/v/com.arize/openinference-instrumentation-springAI.svg)](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-springAI) |
259
+ | [SpringAI](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-springAI) | `openinference-instrumentation-springAI` | [![Maven Central](https://img.shields.io/maven-central/v/com.arize/openinference-instrumentation-springAI.svg)](https://central.sonatype.com/artifact/com.arize/openinference-instrumentation-springAI) |
260
260
 
261
261
  ### Platforms
262
262
 
263
- | Platform | Description | Docs |
264
- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
265
- | [BeeAI](https://docs.beeai.dev/observability/agents-traceability) | AI agent framework with built-in observability | [Integration Guide](https://docs.beeai.dev/observability/agents-traceability) |
266
- | [Dify](https://docs.dify.ai/en/guides/monitoring/integrate-external-ops-tools/integrate-phoenix) | Open-source LLM app development platform | [Integration Guide](https://docs.dify.ai/en/guides/monitoring/integrate-external-ops-tools/integrate-phoenix) |
267
- | [Envoy AI Gateway](https://github.com/envoyproxy/ai-gateway) | AI Gateway built on Envoy Proxy for AI workloads | [Integration Guide](https://github.com/envoyproxy/ai-gateway/tree/main/cmd/aigw#opentelemetry-setup-with-phoenix) |
268
- | [LangFlow](https://arize.com/docs/phoenix/tracing/integrations-tracing/langflow) | Visual framework for building multi-agent and RAG applications | [Integration Guide](https://arize.com/docs/phoenix/tracing/integrations-tracing/langflow) |
269
- | [LiteLLM Proxy](https://docs.litellm.ai/docs/observability/phoenix_integration#using-with-litellm-proxy) | Proxy server for LLMs | [Integration Guide](https://docs.litellm.ai/docs/observability/phoenix_integration#using-with-litellm-proxy) |
263
+ | Platform | Description | Docs |
264
+ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
265
+ | [BeeAI](https://docs.beeai.dev/observability/agents-traceability) | AI agent framework with built-in observability | [Integration Guide](https://docs.beeai.dev/observability/agents-traceability) |
266
+ | [Dify](https://docs.dify.ai/en/guides/monitoring/integrate-external-ops-tools/integrate-phoenix) | Open-source LLM app development platform | [Integration Guide](https://docs.dify.ai/en/guides/monitoring/integrate-external-ops-tools/integrate-phoenix) |
267
+ | [Envoy AI Gateway](https://github.com/envoyproxy/ai-gateway) | AI Gateway built on Envoy Proxy for AI workloads | [Integration Guide](https://github.com/envoyproxy/ai-gateway/tree/main/cmd/aigw#opentelemetry-setup-with-phoenix) |
268
+ | [LangFlow](https://arize.com/docs/phoenix/tracing/integrations-tracing/langflow) | Visual framework for building multi-agent and RAG applications | [Integration Guide](https://arize.com/docs/phoenix/tracing/integrations-tracing/langflow) |
269
+ | [LiteLLM Proxy](https://docs.litellm.ai/docs/observability/phoenix_integration#using-with-litellm-proxy) | Proxy server for LLMs | [Integration Guide](https://docs.litellm.ai/docs/observability/phoenix_integration#using-with-litellm-proxy) |
270
270
 
271
271
  ## Community
272
272
 
@@ -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=JHzYn5SQjWvqEZWixlgmU5X8oFvAzqJf2SdpbNxVVK4,24
9
+ phoenix/version.py,sha256=np-y_W3enAyHJplYVsNoeOIf4b0r7heOgEKw1jPc6io,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
@@ -56,7 +56,7 @@ phoenix/db/types/db_models.py,sha256=nMSd9gWHwObnVO3_slztlHqoeh04czS-Jxu-omS6M6E
56
56
  phoenix/db/types/identifier.py,sha256=Opr3_1di6e5ncrBDn30WfBSr-jN_VGBnkkA4BMuSoyc,244
57
57
  phoenix/db/types/model_provider.py,sha256=zKYGcEQqbAxtPwnq5dL0fYPgDC8nrh_ABLBMR94___4,237
58
58
  phoenix/db/types/token_price_customization.py,sha256=LAb8IwyFJGDCje7CvcPcxNp4NOU8si5hSYaf1fyi624,857
59
- phoenix/db/types/trace_retention.py,sha256=fyqAQCvDiD7mpJ_WUqbPyQvuSdERof4DpKpHLJsdROk,9897
59
+ phoenix/db/types/trace_retention.py,sha256=Y97xfqNa4AO2j6RhhUJIbO6duQ1oX7Vu5bC9laydf1g,9899
60
60
  phoenix/experiments/__init__.py,sha256=6JGwgUd7xCbGpuHqYZlsmErmYvVgv7N_j43bn3dUqsk,123
61
61
  phoenix/experiments/functions.py,sha256=J9Bfp7ptDB2g6DpSqgf2iw4cDL09P_h4UWAAtJNS9Mg,39971
62
62
  phoenix/experiments/tracing.py,sha256=X-wlgbWEzP1oHkLmjop3fGjONo6JK5a0kPXc9YYD014,2856
@@ -124,7 +124,7 @@ phoenix/server/api/dataloaders/average_experiment_run_latency.py,sha256=_wEcC47z
124
124
  phoenix/server/api/dataloaders/dataset_example_revisions.py,sha256=xF7M2dg3UmjhdCrscnztCIBBI0cg3RF48IIqvilpc18,4623
125
125
  phoenix/server/api/dataloaders/dataset_example_spans.py,sha256=z_MFquqAcJ9wat7BBp7MVeJ9BYuu2EZEdaog52iWDno,1390
126
126
  phoenix/server/api/dataloaders/document_evaluation_summaries.py,sha256=9fdROnzp-mymggHwNvpRkCk93LUFxxLy55-j3HP_2HY,5565
127
- phoenix/server/api/dataloaders/document_evaluations.py,sha256=W1b7TIlmPG61vR7kEcLZ5hAQYfkSKZAgrJOXYkA9-Ko,1246
127
+ phoenix/server/api/dataloaders/document_evaluations.py,sha256=1bpBDydd-9YvhgkNoE5w-FGrjtnjUztcnjZTdMAazws,1072
128
128
  phoenix/server/api/dataloaders/document_retrieval_metrics.py,sha256=37EcAW7oYQuWYHMDHb0wcqbWj9lhSskvzDO7NJbT5Js,4136
129
129
  phoenix/server/api/dataloaders/experiment_annotation_summaries.py,sha256=CFVj7DwFYj330FLU5w3zEr12AGUX1e8ZX0X5buxMuEk,5643
130
130
  phoenix/server/api/dataloaders/experiment_error_rates.py,sha256=06IZF07qt2y167DBM49QkSNdnphPArhcsgYFcunaL-U,1992
@@ -257,9 +257,9 @@ phoenix/server/api/routers/oauth2.py,sha256=rPcKFvfijzBYLjfwbCNzCn0ihn4wGWh4xh6B
257
257
  phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
258
258
  phoenix/server/api/routers/v1/__init__.py,sha256=_CxVCs26dPuC2KygV3VzYqmoAECeFwiSTava9mxIJTE,2790
259
259
  phoenix/server/api/routers/v1/annotation_configs.py,sha256=xp5lJmKYlRsINCUrRD9-lTAElw2v4hdFndS5BWrxICA,16048
260
- phoenix/server/api/routers/v1/annotations.py,sha256=fVl2qeh_ZbWXGvFBTZgeL7aGkkINIScdjuyxnOoSzNM,6817
260
+ phoenix/server/api/routers/v1/annotations.py,sha256=V_Cm_XOJaSVpMsTCfxBd1H39sk17b2y6Lyk5qwwDHvI,11433
261
261
  phoenix/server/api/routers/v1/datasets.py,sha256=9iPORLmbOrPKgUUcRDMs6ZczSIz7hvc6bngJy3IbdR0,38331
262
- phoenix/server/api/routers/v1/documents.py,sha256=D8Pg6lEBHzSuPEDVts__X0ArIKBdQs_3gtIgoZXk_eU,6930
262
+ phoenix/server/api/routers/v1/documents.py,sha256=iA_vYU6_p2-pazh_Rp930kiOsiHYFIPPqsZSSXwPgVI,5733
263
263
  phoenix/server/api/routers/v1/evaluations.py,sha256=aBrPO-xCAWyTxydaHq7W2wQFm65k89uVR-H3VWsd6WQ,13062
264
264
  phoenix/server/api/routers/v1/experiment_evaluations.py,sha256=DZ3UK9OoYKElpRcEER7559-KiAqWr-1IXpZ27FbfP3k,5249
265
265
  phoenix/server/api/routers/v1/experiment_runs.py,sha256=LZeCQWQIEOZ9jK5Gp_C4JbiYY6AmnnWe85cVcvdkCLE,7107
@@ -267,8 +267,8 @@ phoenix/server/api/routers/v1/experiments.py,sha256=hIBecGACzGZEgl93ap_JV52pUv-I
267
267
  phoenix/server/api/routers/v1/models.py,sha256=p3gJN-9SWiUYTUTft4bZMsZVCBNTb4nN1Foy68eRZzQ,1997
268
268
  phoenix/server/api/routers/v1/projects.py,sha256=XR6uJxHXXtC1q8LNyS9W6iaj440sv1OKCu-OSBfxEys,12824
269
269
  phoenix/server/api/routers/v1/prompts.py,sha256=chRYcLkOYDJdJfVZVukVTUyIRnLPvsJCg41CuPxOIU8,26695
270
- phoenix/server/api/routers/v1/spans.py,sha256=6wu8nUQNp9ma_k5XGvcVx3fq5xPuaN5sbv15ouBWcVc,49438
271
- phoenix/server/api/routers/v1/traces.py,sha256=ur4qVh8NDHDfwXKUNlAQoZhe4xAWe1Dv2ODixR5qroE,11418
270
+ phoenix/server/api/routers/v1/spans.py,sha256=9xWj5RZEGCjOUVCx9bl-tHdQrIS6VvaUROfo3ROg7J4,47583
271
+ phoenix/server/api/routers/v1/traces.py,sha256=-_g-Imy31J_jWyCOMIOAHMEdMB6KxeLRNW56r9-6REI,9849
272
272
  phoenix/server/api/routers/v1/users.py,sha256=eO8zMtGU33Td2_G1l9D7Z0a4CG1CwBUCj_Z9z2uk7wg,12089
273
273
  phoenix/server/api/routers/v1/utils.py,sha256=oXIOGPzPTkE0ZWUTRCoRIQQ7wTzoSwtWFaUSjlGBqts,4960
274
274
  phoenix/server/api/types/Annotation.py,sha256=gsl8CwjIbDUbZRj4d9USwZ_w_Tkz4i7zuZh9ftV80jA,1132
@@ -285,9 +285,10 @@ phoenix/server/api/types/CostBreakdown.py,sha256=yw9dlb0blGIB_dWNP8yEvDHJztHjpiV
285
285
  phoenix/server/api/types/CreateDatasetPayload.py,sha256=R-6zCmuD0f76RU9Giu78xwTHlASQs6Aq8yzvX1Kxc3g,140
286
286
  phoenix/server/api/types/CronExpression.py,sha256=R7oxuSSX_eTUHQWaoaSueQqWDmkkHr5dBKRN6q-6ROk,331
287
287
  phoenix/server/api/types/DataQualityMetric.py,sha256=Aieg3bHeBFaAf4mqeRcH1zT04sXAtQD8ATSHJt7FaBQ,1538
288
- phoenix/server/api/types/Dataset.py,sha256=23dst_glr7kFNC62-q6D9H2hJgrfZnGe7V-Bg72SJgg,15303
288
+ phoenix/server/api/types/Dataset.py,sha256=OvCgbqqNF-bj0iaQldIeEfZiMjyXGFsozIIBsbJJwq4,12648
289
289
  phoenix/server/api/types/DatasetExample.py,sha256=_9byxGpXfYb-hmFMUJeG7Bw1wsRKSJaHwF2IPAbFpFw,3115
290
290
  phoenix/server/api/types/DatasetExampleRevision.py,sha256=c-jWR6dTguEZTm54IMlFr0Ic84I3nefyDnZb7nF5hnI,874
291
+ phoenix/server/api/types/DatasetExperimentAnnotationSummary.py,sha256=EVXz6zfbdYsU5SZ6FUrAA_VQqQCnBE_mbKgmAmFoDLI,195
291
292
  phoenix/server/api/types/DatasetValues.py,sha256=7VbCOLlzOXpZN80-zYF2UGuafRcPsZF-8WQNc0YsKFc,1119
292
293
  phoenix/server/api/types/DatasetVersion.py,sha256=NnDriqQUE20N_Jhyu3-IxHWJQE9wmdHEJIQAKyTVJpo,302
293
294
  phoenix/server/api/types/Dimension.py,sha256=VjW6kOMhFslGSSHlsylpF1hYY3wuhopC2LM2eOcIhRI,10665
@@ -295,11 +296,11 @@ phoenix/server/api/types/DimensionDataType.py,sha256=o0QQRpUzgXbhd10drR2LthxrjB8
295
296
  phoenix/server/api/types/DimensionShape.py,sha256=LNsRt3Uyx1OmwMa5EXk1JGRXPLVV2EEgYXz-6wnLJn4,562
296
297
  phoenix/server/api/types/DimensionType.py,sha256=JLikZUBVqbHlUWcYYd8d60gB1_hmAcuFuZZsjCXpIwc,801
297
298
  phoenix/server/api/types/DimensionWithValue.py,sha256=4_koirnDrZBBdFkIWka2f-OeqZ6IzQXwqz3ccjdgHrc,484
299
+ phoenix/server/api/types/DocumentAnnotation.py,sha256=CMNcODXnKYzAUBITp_iYOoCGpXPEFGnelxmWySqDyn4,3076
298
300
  phoenix/server/api/types/DocumentEvaluationSummary.py,sha256=dx4Btlfw9_XsfmibjfWvMOvUfrvr9oGJq8jYmIL2o-Q,3494
299
301
  phoenix/server/api/types/DocumentRetrievalMetrics.py,sha256=amkpC3H5IU5-9GvO0telpbq00m6lIcv_2v446OpwFwc,1822
300
302
  phoenix/server/api/types/EmbeddingDimension.py,sha256=AYvpZ1nWINAgN4BAZsA_xI_2TNFK6h5jmqzvkPs651M,19428
301
303
  phoenix/server/api/types/EmbeddingMetadata.py,sha256=fJvNNYCbkf3SJalArLy9rcBq9Uj1SNac60zjqe1PFnM,461
302
- phoenix/server/api/types/Evaluation.py,sha256=qkMmq5G9pgwUNOPSTRwR-nNAoqLT3LlXrq7O8cAgXQ8,1356
303
304
  phoenix/server/api/types/EvaluationSummary.py,sha256=vILYejnfPvMwWEXOwhQZsANvYe3AdO2OkMR2rcgp1H4,1512
304
305
  phoenix/server/api/types/Event.py,sha256=iYt_Jx1Roioo0vZ0iPeJTHcTu6NSm4ilVMJ-IMUHAKk,3970
305
306
  phoenix/server/api/types/EventMetadata.py,sha256=-J0tYF9eZTHwCjwxQHY7Gckr2_MNW5OoWT1mydweZNM,635
@@ -338,7 +339,7 @@ phoenix/server/api/types/ScalarDriftMetricEnum.py,sha256=IUAcRPpgL41WdoIgK6cNk2T
338
339
  phoenix/server/api/types/Segments.py,sha256=vT2v0efoa5cuBKxLtxTnsUP5YJJCZfTloM71Spu0tMI,2915
339
340
  phoenix/server/api/types/ServerStatus.py,sha256=t92OHuVhK9DXDk2vsBuHceQNKqYGpHwUp8DNGKz2wOk,88
340
341
  phoenix/server/api/types/SortDir.py,sha256=OUpXhlCzCxPoXSDkJJygEs9Rw9pMymfaZUG5zPTrw4Y,152
341
- phoenix/server/api/types/Span.py,sha256=ag5qWOEbWDCNUAkSUd0K3biE_PuGT2oweL4gVKElNis,32000
342
+ phoenix/server/api/types/Span.py,sha256=wJEEReo38Qsc0nFuBF0LDq9kcfGwmYerA86FXC4GqzE,32130
342
343
  phoenix/server/api/types/SpanAnnotation.py,sha256=uPWu7Z8rmpfKhaaxbged4_o00pPCR3nkn7Gji9vB8jY,1959
343
344
  phoenix/server/api/types/SpanCostDetailSummaryEntry.py,sha256=RXAdOC6MFyR9mwaoj8lMMdI3_9r3z6mR2izJvlsj12U,252
344
345
  phoenix/server/api/types/SpanCostSummary.py,sha256=wo03FCMcFzB5m4P5kvA5jzi9ACLbht38ozQbDJUh94g,357
@@ -350,7 +351,7 @@ phoenix/server/api/types/TokenPrice.py,sha256=OyCZgXCKDIihmDalHvkUDvhvl2Vsxx1J2U
350
351
  phoenix/server/api/types/TokenUsage.py,sha256=aeMPwEDqu8MWT2Jc5sq6Sfb_7kcR_KTzZi6UB9OK7Yg,207
351
352
  phoenix/server/api/types/ToolDefinition.py,sha256=T6UH2vcbuPBDy7jKYOqMth2NdqxMPgDBf11Tpbt5Yb8,187
352
353
  phoenix/server/api/types/Trace.py,sha256=iaSAicxMHTrnUTYxNfMwFMglVeptqu7nF_AHS6k-QGw,9718
353
- phoenix/server/api/types/TraceAnnotation.py,sha256=qUO9DhTIAOKIYqNd5uHqLAsVTsJLSddkXo-gmyjnPBI,1964
354
+ phoenix/server/api/types/TraceAnnotation.py,sha256=NAZ5HXH31x6zjXlh-1CUiXjbiCo0iF6hqIrgts2h3Lo,2022
354
355
  phoenix/server/api/types/UMAPPoints.py,sha256=49sWnxjcAJKRzqUY71Fa0tOPti5XjIIFT5cSg6oNu_U,1650
355
356
  phoenix/server/api/types/User.py,sha256=20q4K_dwWHKdCTLFOmpdbt_OjdGv-98z2dHhwh7RmnI,2412
356
357
  phoenix/server/api/types/UserApiKey.py,sha256=EcsPNxV_PtgpqwA_bPTj_aRYt15l6X24YDy20hUPywU,1251
@@ -364,7 +365,7 @@ phoenix/server/cost_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
364
365
  phoenix/server/cost_tracking/cost_details_calculator.py,sha256=Tt0YcuLhgPuXKWJemWVmYQfG0xQUvH4VziIj6KcDnoA,8945
365
366
  phoenix/server/cost_tracking/cost_model_lookup.py,sha256=jhtVdnQBzrTUHeOGPWgOebk-Io5hpJ1vAgWOu8ojeJ4,6801
366
367
  phoenix/server/cost_tracking/helpers.py,sha256=Pk6ECjnYreTxrldtRwxnwFcxIPVsvDq_yAwDA_spkOc,2122
367
- phoenix/server/cost_tracking/model_cost_manifest.json,sha256=kE8VrBbvdqDy1ijk8KWWs_76U-L_vcRUT5hVT418evY,63488
368
+ phoenix/server/cost_tracking/model_cost_manifest.json,sha256=hOe6WLOd4t99iMCwCVbOya9kERKb7Ci_NR2opiNQY5M,65570
368
369
  phoenix/server/cost_tracking/regex_specificity.py,sha256=9kqWuQ68C-hlwW25hr7BhFlRt5y2Nnpy0Ax3n9UN6Xk,11622
369
370
  phoenix/server/cost_tracking/token_cost_calculator.py,sha256=2JEZnvusx2-xbhp8krp9EarjWuyGH2KO4e-ZwJX-K0s,1598
370
371
  phoenix/server/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -393,10 +394,10 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
393
394
  phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
394
395
  phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
395
396
  phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
396
- phoenix/server/static/.vite/manifest.json,sha256=podIkdilPnaQGOQjbnF7zpOylgP4MXrSImpB5UGvNBc,2328
397
- phoenix/server/static/assets/components-BBwXqJXQ.js,sha256=U8GMAX0TEjXRzhmqCMXjnVaQ9trJDICWTq9IcjqcNhE,664533
398
- phoenix/server/static/assets/index-C_gU3x10.js,sha256=ouZyJslt5vOJGBHCHiO0ziM9Y5lTwAdB86lGFUbr-Ec,63396
399
- phoenix/server/static/assets/pages-YmQb55Uo.js,sha256=tPGWJ9VTxJJSVoyQj_7VutaInS_SuhuXSal0nuJQTKQ,1269456
397
+ phoenix/server/static/.vite/manifest.json,sha256=-oRiGNtI_bi_1cZQ95g-a5uptY0yHoFkRH9om8VpChY,2328
398
+ phoenix/server/static/assets/components-BjW5gAwL.js,sha256=VHX557qhyBcOfSYPZtR1TtOW1AMeAaJiqvYZ1cDaOf4,664533
399
+ phoenix/server/static/assets/index-3OI8VV_W.js,sha256=K7HPjzp6bjscIw5R4KHQS32wo6oJhcrRTA7myP9dKlM,63396
400
+ phoenix/server/static/assets/pages-CQfUODtD.js,sha256=7iODh8TLYLYZGeEAl3dW9jJcCyP1EkKS_OluUdNVMpc,1269654
400
401
  phoenix/server/static/assets/vendor-CqDb5u4o.css,sha256=zIyFiNJKxMaQk8AvtLgt1rR01oO10d1MFndSDKH9Clw,5517
401
402
  phoenix/server/static/assets/vendor-RdRDaQiR.js,sha256=oTxLetZZXJ20yoKNAYExto9V73y8X5zjddWV46K9CWM,2595492
402
403
  phoenix/server/static/assets/vendor-arizeai-DsYDNOqt.js,sha256=0HIkPJXbKTh85nqphdAXYeStRzdaim0IQxRXiXxa21U,121514
@@ -418,7 +419,7 @@ phoenix/trace/evaluation_conventions.py,sha256=t8jydM3U0-T5YpiQKRJ3tWdWGlHtzKytt
418
419
  phoenix/trace/exporter.py,sha256=bUXh8fjJIbHurrnt4bAm-cCWqUN5FqNsIc8DZzzklkQ,4695
419
420
  phoenix/trace/fixtures.py,sha256=1c7fsyvmxC53Fib9T_Qxp_Ly3OZdDbkLQ0XpFzikEjk,20298
420
421
  phoenix/trace/otel.py,sha256=RJSbAuzS4KBS0t-fntXQaaYwv7FmIXRMrw65DI67z8k,10622
421
- phoenix/trace/projects.py,sha256=9dKv1aiKL4IYMFsg2xnC6EOIRO0YHtkR5o9ALHbMK9g,2178
422
+ phoenix/trace/projects.py,sha256=2wVg_UaNiXuhn60qJhgqJiaQ0OnbOWMnB10QeTGZGwQ,2420
422
423
  phoenix/trace/schemas.py,sha256=Su6e567Bei9oo6PsWO2srTcPAj9C2bMgbGtx64Sgqeg,6332
423
424
  phoenix/trace/span_evaluations.py,sha256=x3nye9r2SQk1mrR3N05YbuWsgUKpMWwTRBtJTDBSj3Y,13156
424
425
  phoenix/trace/span_json_decoder.py,sha256=J1_oDViuUoC4vxPg61U4EOZC1uEMcCzoj-kVjOFEE8k,3224
@@ -443,9 +444,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
443
444
  phoenix/utilities/re.py,sha256=6YyUWIkv0zc2SigsxfOWIHzdpjKA_TZo2iqKq7zJKvw,2081
444
445
  phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
445
446
  phoenix/utilities/template_formatters.py,sha256=gh9PJD6WEGw7TEYXfSst1UR4pWWwmjxMLrDVQ_CkpkQ,2779
446
- arize_phoenix-11.30.0.dist-info/METADATA,sha256=p2LDUgcsaZ4l5qRVqP5FVFjrpVpZTvzTN60j913wTtM,31733
447
- arize_phoenix-11.30.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
448
- arize_phoenix-11.30.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
449
- arize_phoenix-11.30.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
450
- arize_phoenix-11.30.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
451
- arize_phoenix-11.30.0.dist-info/RECORD,,
447
+ arize_phoenix-11.31.0.dist-info/METADATA,sha256=E2fweFHv4hduXa1oIQzqu4LX29_zspyxGa8bdGq13zs,32877
448
+ arize_phoenix-11.31.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
449
+ arize_phoenix-11.31.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
450
+ arize_phoenix-11.31.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
451
+ arize_phoenix-11.31.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
452
+ arize_phoenix-11.31.0.dist-info/RECORD,,
@@ -199,7 +199,7 @@ class TraceRetentionCronExpression(RootModel[str]):
199
199
 
200
200
  def _parse_field(field: str, min_val: int, max_val: int) -> set[int]:
201
201
  """
202
- Parse a cron field and return the set of matching values.
202
+ Parses a cron field and returns the set of matching values.
203
203
 
204
204
  Args:
205
205
  field (str): The cron field to parse
@@ -5,11 +5,10 @@ from strawberry.dataloader import DataLoader
5
5
  from typing_extensions import TypeAlias
6
6
 
7
7
  from phoenix.db import models
8
- from phoenix.server.api.types.Evaluation import DocumentAnnotation
9
8
  from phoenix.server.types import DbSessionFactory
10
9
 
11
10
  Key: TypeAlias = int
12
- Result: TypeAlias = list[DocumentAnnotation]
11
+ Result: TypeAlias = list[models.DocumentAnnotation]
13
12
 
14
13
 
15
14
  class DocumentEvaluationsDataLoader(DataLoader[Key, Result]):
@@ -18,14 +17,12 @@ class DocumentEvaluationsDataLoader(DataLoader[Key, Result]):
18
17
  self._db = db
19
18
 
20
19
  async def _load_fn(self, keys: list[Key]) -> list[Result]:
21
- document_evaluations_by_id: defaultdict[Key, Result] = defaultdict(list)
20
+ document_annotations_by_id: defaultdict[Key, Result] = defaultdict(list)
22
21
  mda = models.DocumentAnnotation
23
22
  async with self._db() as session:
24
- data = await session.stream_scalars(
25
- select(mda).where(mda.span_rowid.in_(keys)).where(mda.annotator_kind == "LLM")
26
- )
23
+ data = await session.stream_scalars(select(mda).where(mda.span_rowid.in_(keys)))
27
24
  async for document_evaluation in data:
28
- document_evaluations_by_id[document_evaluation.span_rowid].append(
29
- DocumentAnnotation.from_sql_document_annotation(document_evaluation)
25
+ document_annotations_by_id[document_evaluation.span_rowid].append(
26
+ document_evaluation
30
27
  )
31
- return [document_evaluations_by_id[key] for key in keys]
28
+ return [document_annotations_by_id[key] for key in keys]
@@ -1,32 +1,38 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- from datetime import datetime
5
- from typing import Literal, Optional
4
+ from datetime import datetime, timezone
5
+ from typing import Any, Literal, Optional
6
6
 
7
7
  from fastapi import APIRouter, HTTPException, Path, Query
8
+ from pydantic import Field
8
9
  from sqlalchemy import exists, select
9
10
  from starlette.requests import Request
10
11
  from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
11
12
  from strawberry.relay import GlobalID
12
13
 
13
14
  from phoenix.db import models
15
+ from phoenix.db.insertion.types import Precursors
16
+ from phoenix.server.api.routers.v1.models import V1RoutesBaseModel
14
17
  from phoenix.server.api.types.SpanAnnotation import SpanAnnotation as SpanAnnotationNodeType
18
+ from phoenix.server.api.types.TraceAnnotation import TraceAnnotation as TraceAnnotationNodeType
15
19
  from phoenix.server.api.types.User import User as UserNodeType
16
20
 
17
- from .spans import SpanAnnotationData, SpanAnnotationResult
18
21
  from .utils import PaginatedResponseBody, _get_project_by_identifier, add_errors_to_responses
19
22
 
20
23
  logger = logging.getLogger(__name__)
21
24
 
22
25
  SPAN_ANNOTATION_NODE_NAME = SpanAnnotationNodeType.__name__
26
+ TRACE_ANNOTATION_NODE_NAME = TraceAnnotationNodeType.__name__
27
+ MAX_TRACE_IDS = 1_000
23
28
  USER_NODE_NAME = UserNodeType.__name__
24
29
  MAX_SPAN_IDS = 1_000
30
+ MAX_SESSION_IDS = 1_000
25
31
 
26
32
  router = APIRouter(tags=["annotations"])
27
33
 
28
34
 
29
- class SpanAnnotation(SpanAnnotationData):
35
+ class Annotation(V1RoutesBaseModel):
30
36
  id: str
31
37
  created_at: datetime
32
38
  updated_at: datetime
@@ -34,10 +40,127 @@ class SpanAnnotation(SpanAnnotationData):
34
40
  user_id: Optional[str]
35
41
 
36
42
 
43
+ class AnnotationResult(V1RoutesBaseModel):
44
+ label: Optional[str] = Field(default=None, description="The label assigned by the annotation")
45
+ score: Optional[float] = Field(default=None, description="The score assigned by the annotation")
46
+ explanation: Optional[str] = Field(
47
+ default=None, description="Explanation of the annotation result"
48
+ )
49
+
50
+
51
+ class AnnotationData(V1RoutesBaseModel):
52
+ name: str = Field(description="The name of the annotation")
53
+ annotator_kind: Literal["LLM", "CODE", "HUMAN"] = Field(
54
+ description="The kind of annotator used for the annotation"
55
+ )
56
+ result: Optional[AnnotationResult] = Field(
57
+ default=None, description="The result of the annotation"
58
+ )
59
+ metadata: Optional[dict[str, Any]] = Field(
60
+ default=None, description="Metadata for the annotation"
61
+ )
62
+ identifier: str = Field(
63
+ default="",
64
+ description=(
65
+ "The identifier of the annotation. "
66
+ "If provided, the annotation will be updated if it already exists."
67
+ ),
68
+ )
69
+
70
+
71
+ class SpanAnnotationData(AnnotationData):
72
+ span_id: str = Field(description="OpenTelemetry Span ID (hex format w/o 0x prefix)")
73
+
74
+ def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.SpanAnnotation:
75
+ return Precursors.SpanAnnotation(
76
+ datetime.now(timezone.utc),
77
+ self.span_id,
78
+ models.SpanAnnotation(
79
+ name=self.name,
80
+ annotator_kind=self.annotator_kind,
81
+ score=self.result.score if self.result else None,
82
+ label=self.result.label if self.result else None,
83
+ explanation=self.result.explanation if self.result else None,
84
+ metadata_=self.metadata or {},
85
+ identifier=self.identifier,
86
+ source="API",
87
+ user_id=user_id,
88
+ ),
89
+ )
90
+
91
+
92
+ class SpanAnnotation(SpanAnnotationData, Annotation):
93
+ pass
94
+
95
+
37
96
  class SpanAnnotationsResponseBody(PaginatedResponseBody[SpanAnnotation]):
38
97
  pass
39
98
 
40
99
 
100
+ class SpanDocumentAnnotationData(AnnotationData):
101
+ span_id: str = Field(description="OpenTelemetry Span ID (hex format w/o 0x prefix)")
102
+ document_position: int = Field(
103
+ description="A 0 based index of the document. E.x. the first document during retrieval is 0"
104
+ )
105
+
106
+ # Precursor here means a value to add to a queue for processing async
107
+ def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.DocumentAnnotation:
108
+ return Precursors.DocumentAnnotation(
109
+ datetime.now(timezone.utc),
110
+ self.span_id,
111
+ self.document_position,
112
+ models.DocumentAnnotation(
113
+ name=self.name,
114
+ annotator_kind=self.annotator_kind,
115
+ document_position=self.document_position,
116
+ score=self.result.score if self.result else None,
117
+ label=self.result.label if self.result else None,
118
+ explanation=self.result.explanation if self.result else None,
119
+ metadata_=self.metadata or {},
120
+ identifier=self.identifier,
121
+ source="API",
122
+ user_id=user_id,
123
+ ),
124
+ )
125
+
126
+
127
+ class SpanDocumentAnnotation(SpanDocumentAnnotationData, Annotation):
128
+ pass
129
+
130
+
131
+ class SpanDocumentAnnotationsResponseBody(PaginatedResponseBody[SpanDocumentAnnotation]):
132
+ pass
133
+
134
+
135
+ class TraceAnnotationData(AnnotationData):
136
+ trace_id: str = Field(description="OpenTelemetry Trace ID (hex format w/o 0x prefix)")
137
+
138
+ def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.TraceAnnotation:
139
+ return Precursors.TraceAnnotation(
140
+ datetime.now(timezone.utc),
141
+ self.trace_id,
142
+ models.TraceAnnotation(
143
+ name=self.name,
144
+ annotator_kind=self.annotator_kind,
145
+ score=self.result.score if self.result else None,
146
+ label=self.result.label if self.result else None,
147
+ explanation=self.result.explanation if self.result else None,
148
+ metadata_=self.metadata or {},
149
+ identifier=self.identifier,
150
+ source="API",
151
+ user_id=user_id,
152
+ ),
153
+ )
154
+
155
+
156
+ class TraceAnnotation(TraceAnnotationData, Annotation):
157
+ pass
158
+
159
+
160
+ class TraceAnnotationsResponseBody(PaginatedResponseBody[TraceAnnotation]):
161
+ pass
162
+
163
+
41
164
  @router.get(
42
165
  "/projects/{project_identifier}/span_annotations",
43
166
  operation_id="listSpanAnnotationsBySpanIds",
@@ -164,7 +287,7 @@ async def list_span_annotations(
164
287
  id=str(GlobalID(SPAN_ANNOTATION_NODE_NAME, str(anno.id))),
165
288
  span_id=span_id,
166
289
  name=anno.name,
167
- result=SpanAnnotationResult(
290
+ result=AnnotationResult(
168
291
  label=anno.label,
169
292
  score=anno.score,
170
293
  explanation=anno.explanation,
@@ -1,5 +1,4 @@
1
- from datetime import datetime, timezone
2
- from typing import Any, Literal, Optional
1
+ from typing import Optional
3
2
 
4
3
  from fastapi import APIRouter, Depends, HTTPException, Query
5
4
  from pydantic import Field
@@ -11,64 +10,19 @@ from strawberry.relay import GlobalID
11
10
  from phoenix.db import models
12
11
  from phoenix.db.helpers import SupportedSQLDialect
13
12
  from phoenix.db.insertion.helpers import as_kv, insert_on_conflict
14
- from phoenix.db.insertion.types import Precursors
15
- from phoenix.server.api.types.Evaluation import DocumentAnnotation
13
+ from phoenix.server.api.routers.v1.annotations import SpanDocumentAnnotationData
14
+ from phoenix.server.api.types.DocumentAnnotation import DocumentAnnotation
16
15
  from phoenix.server.authorization import is_not_locked
17
16
  from phoenix.server.bearer_auth import PhoenixUser
18
17
  from phoenix.server.dml_event import DocumentAnnotationInsertEvent
19
18
 
20
19
  from .models import V1RoutesBaseModel
21
- from .spans import SpanAnnotationResult
22
20
  from .utils import RequestBody, ResponseBody, add_errors_to_responses
23
21
 
24
22
  # Since the document annotations are spans related, we place it under spans
25
23
  router = APIRouter(tags=["spans"])
26
24
 
27
25
 
28
- class SpanDocumentAnnotationData(V1RoutesBaseModel):
29
- span_id: str = Field(description="OpenTelemetry Span ID (hex format w/o 0x prefix)")
30
- name: str = Field(description="The name of the document annotation. E.x. relevance")
31
- annotator_kind: Literal["LLM", "CODE", "HUMAN"] = Field(
32
- description="The kind of annotator. E.g. llm judge, a heuristic piece of code, or a human"
33
- )
34
- document_position: int = Field(
35
- description="A 0 based index of the document. E.x. the first document during retrieval is 0"
36
- )
37
- result: Optional[SpanAnnotationResult] = Field(
38
- default=None, description="The score and or label of the annotation"
39
- )
40
- metadata: Optional[dict[str, Any]] = Field(
41
- default=None, description="Metadata for custom values of the annotation"
42
- )
43
- identifier: str = Field(
44
- default="",
45
- description=(
46
- "An custom ID for the annotation. "
47
- "If provided, the annotation will be updated if it already exists."
48
- ),
49
- )
50
-
51
- # Precursor here means a value to add to a queue for processing async
52
- def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.DocumentAnnotation:
53
- return Precursors.DocumentAnnotation(
54
- datetime.now(timezone.utc),
55
- self.span_id,
56
- self.document_position,
57
- models.DocumentAnnotation(
58
- name=self.name,
59
- annotator_kind=self.annotator_kind,
60
- document_position=self.document_position,
61
- score=self.result.score if self.result else None,
62
- label=self.result.label if self.result else None,
63
- explanation=self.result.explanation if self.result else None,
64
- metadata_=self.metadata or {},
65
- identifier=self.identifier,
66
- source="API",
67
- user_id=user_id,
68
- ),
69
- )
70
-
71
-
72
26
  class AnnotateSpanDocumentsRequestBody(RequestBody[list[SpanDocumentAnnotationData]]):
73
27
  pass
74
28
 
@@ -90,7 +44,11 @@ class AnnotateSpanDocumentsResponseBody(ResponseBody[list[InsertedSpanDocumentAn
90
44
  {
91
45
  "status_code": HTTP_404_NOT_FOUND,
92
46
  "description": "Span not found",
93
- }
47
+ },
48
+ {
49
+ "status_code": 422,
50
+ "description": "Invalid request - non-empty identifier not supported",
51
+ },
94
52
  ]
95
53
  ),
96
54
  response_description="Span document annotation inserted successfully",
@@ -106,6 +64,14 @@ async def annotate_span_documents(
106
64
  if not request_body.data:
107
65
  return AnnotateSpanDocumentsResponseBody(data=[])
108
66
 
67
+ # Validate that identifiers are empty or only whitespace
68
+ for annotation in request_body.data:
69
+ if annotation.identifier.strip():
70
+ raise HTTPException(
71
+ detail=f"Non-empty identifier '{annotation.identifier}' is not supported",
72
+ status_code=422, # Unprocessable Entity
73
+ )
74
+
109
75
  user_id: Optional[int] = None
110
76
  if request.app.state.authentication_enabled and isinstance(request.user, PhoenixUser):
111
77
  user_id = int(request.user.identity)
@@ -117,6 +83,7 @@ async def annotate_span_documents(
117
83
  ]
118
84
  if not sync:
119
85
  await request.state.enqueue(*precursors)
86
+ return AnnotateSpanDocumentsResponseBody(data=[])
120
87
 
121
88
  span_ids = {p.span_id for p in precursors}
122
89
  # Account for the fact that the spans could arrive after the annotation
@@ -130,38 +97,39 @@ async def annotate_span_documents(
130
97
  )
131
98
  }
132
99
 
133
- missing_span_ids = span_ids - set(existing_spans.keys())
134
- # We prefer to fail the entire operation if there are missing spans in sync mode
135
- if missing_span_ids:
136
- raise HTTPException(
137
- detail=f"Spans with IDs {', '.join(missing_span_ids)} do not exist.",
138
- status_code=HTTP_404_NOT_FOUND,
139
- )
140
-
141
- # Validate that document positions are within bounds
142
- for annotation in span_document_annotations:
143
- _, num_docs = existing_spans[annotation.span_id]
144
- if annotation.document_position not in range(num_docs):
100
+ missing_span_ids = span_ids - set(existing_spans.keys())
101
+ # We prefer to fail the entire operation if there are missing spans in sync mode
102
+ if missing_span_ids:
145
103
  raise HTTPException(
146
- detail=f"Document position {annotation.document_position} is out of bounds for "
147
- f"span {annotation.span_id} (max: {num_docs - 1})",
148
- status_code=422, # Unprocessable Entity
104
+ detail=f"Spans with IDs {', '.join(missing_span_ids)} do not exist.",
105
+ status_code=HTTP_404_NOT_FOUND,
149
106
  )
150
107
 
151
- inserted_document_annotation_ids = []
152
- dialect = SupportedSQLDialect(session.bind.dialect.name)
153
- for anno in precursors:
154
- span_rowid, _ = existing_spans[anno.span_id]
155
- values = dict(as_kv(anno.as_insertable(span_rowid).row))
156
- span_document_annotation_id = await session.scalar(
157
- insert_on_conflict(
158
- values,
159
- dialect=dialect,
160
- table=models.DocumentAnnotation,
161
- unique_by=("name", "span_rowid", "identifier", "document_position"),
162
- ).returning(models.DocumentAnnotation.id)
163
- )
164
- inserted_document_annotation_ids.append(span_document_annotation_id)
108
+ # Validate that document positions are within bounds
109
+ for annotation in span_document_annotations:
110
+ _, num_docs = existing_spans[annotation.span_id]
111
+ if annotation.document_position not in range(num_docs):
112
+ raise HTTPException(
113
+ detail=f"Document position {annotation.document_position} is out of bounds for "
114
+ f"span {annotation.span_id} (max: {num_docs - 1})",
115
+ status_code=422, # Unprocessable Entity
116
+ )
117
+
118
+ inserted_document_annotation_ids = []
119
+ dialect = SupportedSQLDialect(session.bind.dialect.name)
120
+ for anno in precursors:
121
+ span_rowid, _ = existing_spans[anno.span_id]
122
+ values = dict(as_kv(anno.as_insertable(span_rowid).row))
123
+ span_document_annotation_id = await session.scalar(
124
+ insert_on_conflict(
125
+ values,
126
+ dialect=dialect,
127
+ table=models.DocumentAnnotation,
128
+ unique_by=("name", "span_rowid", "identifier", "document_position"),
129
+ constraint_name="uq_document_annotations_name_span_rowid_document_pos_identifier",
130
+ ).returning(models.DocumentAnnotation.id)
131
+ )
132
+ inserted_document_annotation_ids.append(span_document_annotation_id)
165
133
 
166
134
  # We queue an event to let the application know that annotations have changed
167
135
  request.state.event_queue.put(