arize-phoenix 10.0.4__py3-none-any.whl → 12.28.1__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.
Files changed (276) hide show
  1. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
  2. arize_phoenix-12.28.1.dist-info/RECORD +499 -0
  3. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
  4. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
  5. phoenix/__generated__/__init__.py +0 -0
  6. phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
  7. phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
  8. phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
  9. phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
  10. phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
  11. phoenix/__init__.py +5 -4
  12. phoenix/auth.py +39 -2
  13. phoenix/config.py +1763 -91
  14. phoenix/datetime_utils.py +120 -2
  15. phoenix/db/README.md +595 -25
  16. phoenix/db/bulk_inserter.py +145 -103
  17. phoenix/db/engines.py +140 -33
  18. phoenix/db/enums.py +3 -12
  19. phoenix/db/facilitator.py +302 -35
  20. phoenix/db/helpers.py +1000 -65
  21. phoenix/db/iam_auth.py +64 -0
  22. phoenix/db/insertion/dataset.py +135 -2
  23. phoenix/db/insertion/document_annotation.py +9 -6
  24. phoenix/db/insertion/evaluation.py +2 -3
  25. phoenix/db/insertion/helpers.py +17 -2
  26. phoenix/db/insertion/session_annotation.py +176 -0
  27. phoenix/db/insertion/span.py +15 -11
  28. phoenix/db/insertion/span_annotation.py +3 -4
  29. phoenix/db/insertion/trace_annotation.py +3 -4
  30. phoenix/db/insertion/types.py +50 -20
  31. phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
  32. phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
  33. phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
  34. phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
  35. phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
  36. phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
  37. phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
  38. phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
  39. phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
  40. phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
  41. phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
  42. phoenix/db/models.py +669 -56
  43. phoenix/db/pg_config.py +10 -0
  44. phoenix/db/types/model_provider.py +4 -0
  45. phoenix/db/types/token_price_customization.py +29 -0
  46. phoenix/db/types/trace_retention.py +23 -15
  47. phoenix/experiments/evaluators/utils.py +3 -3
  48. phoenix/experiments/functions.py +160 -52
  49. phoenix/experiments/tracing.py +2 -2
  50. phoenix/experiments/types.py +1 -1
  51. phoenix/inferences/inferences.py +1 -2
  52. phoenix/server/api/auth.py +38 -7
  53. phoenix/server/api/auth_messages.py +46 -0
  54. phoenix/server/api/context.py +100 -4
  55. phoenix/server/api/dataloaders/__init__.py +79 -5
  56. phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
  57. phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
  58. phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
  59. phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
  60. phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
  61. phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
  62. phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
  63. phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
  64. phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
  65. phoenix/server/api/dataloaders/dataset_labels.py +36 -0
  66. phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
  67. phoenix/server/api/dataloaders/document_evaluations.py +6 -9
  68. phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
  69. phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
  70. phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
  71. phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
  72. phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
  73. phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
  74. phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
  75. phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
  76. phoenix/server/api/dataloaders/record_counts.py +37 -10
  77. phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
  78. phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
  79. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
  80. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
  81. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
  82. phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
  83. phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
  84. phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
  85. phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
  86. phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
  87. phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
  88. phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
  89. phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
  90. phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
  91. phoenix/server/api/dataloaders/span_costs.py +29 -0
  92. phoenix/server/api/dataloaders/table_fields.py +2 -2
  93. phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
  94. phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
  95. phoenix/server/api/dataloaders/types.py +29 -0
  96. phoenix/server/api/exceptions.py +11 -1
  97. phoenix/server/api/helpers/dataset_helpers.py +5 -1
  98. phoenix/server/api/helpers/playground_clients.py +1243 -292
  99. phoenix/server/api/helpers/playground_registry.py +2 -2
  100. phoenix/server/api/helpers/playground_spans.py +8 -4
  101. phoenix/server/api/helpers/playground_users.py +26 -0
  102. phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
  103. phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
  104. phoenix/server/api/helpers/prompts/models.py +205 -22
  105. phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
  106. phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
  107. phoenix/server/api/input_types/CreateProjectInput.py +27 -0
  108. phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
  109. phoenix/server/api/input_types/DatasetFilter.py +17 -0
  110. phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
  111. phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
  112. phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
  113. phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
  114. phoenix/server/api/input_types/PromptFilter.py +14 -0
  115. phoenix/server/api/input_types/PromptVersionInput.py +52 -1
  116. phoenix/server/api/input_types/SpanSort.py +44 -7
  117. phoenix/server/api/input_types/TimeBinConfig.py +23 -0
  118. phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
  119. phoenix/server/api/input_types/UserRoleInput.py +1 -0
  120. phoenix/server/api/mutations/__init__.py +10 -0
  121. phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
  122. phoenix/server/api/mutations/api_key_mutations.py +19 -23
  123. phoenix/server/api/mutations/chat_mutations.py +154 -47
  124. phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
  125. phoenix/server/api/mutations/dataset_mutations.py +21 -16
  126. phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
  127. phoenix/server/api/mutations/experiment_mutations.py +2 -2
  128. phoenix/server/api/mutations/export_events_mutations.py +3 -3
  129. phoenix/server/api/mutations/model_mutations.py +210 -0
  130. phoenix/server/api/mutations/project_mutations.py +49 -10
  131. phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
  132. phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
  133. phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
  134. phoenix/server/api/mutations/prompt_mutations.py +65 -129
  135. phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
  136. phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
  137. phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
  138. phoenix/server/api/mutations/trace_mutations.py +47 -3
  139. phoenix/server/api/mutations/user_mutations.py +66 -41
  140. phoenix/server/api/queries.py +768 -293
  141. phoenix/server/api/routers/__init__.py +2 -2
  142. phoenix/server/api/routers/auth.py +154 -88
  143. phoenix/server/api/routers/ldap.py +229 -0
  144. phoenix/server/api/routers/oauth2.py +369 -106
  145. phoenix/server/api/routers/v1/__init__.py +24 -4
  146. phoenix/server/api/routers/v1/annotation_configs.py +23 -31
  147. phoenix/server/api/routers/v1/annotations.py +481 -17
  148. phoenix/server/api/routers/v1/datasets.py +395 -81
  149. phoenix/server/api/routers/v1/documents.py +142 -0
  150. phoenix/server/api/routers/v1/evaluations.py +24 -31
  151. phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
  152. phoenix/server/api/routers/v1/experiment_runs.py +337 -59
  153. phoenix/server/api/routers/v1/experiments.py +479 -48
  154. phoenix/server/api/routers/v1/models.py +7 -0
  155. phoenix/server/api/routers/v1/projects.py +18 -49
  156. phoenix/server/api/routers/v1/prompts.py +54 -40
  157. phoenix/server/api/routers/v1/sessions.py +108 -0
  158. phoenix/server/api/routers/v1/spans.py +1091 -81
  159. phoenix/server/api/routers/v1/traces.py +132 -78
  160. phoenix/server/api/routers/v1/users.py +389 -0
  161. phoenix/server/api/routers/v1/utils.py +3 -7
  162. phoenix/server/api/subscriptions.py +305 -88
  163. phoenix/server/api/types/Annotation.py +90 -23
  164. phoenix/server/api/types/ApiKey.py +13 -17
  165. phoenix/server/api/types/AuthMethod.py +1 -0
  166. phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
  167. phoenix/server/api/types/CostBreakdown.py +12 -0
  168. phoenix/server/api/types/Dataset.py +226 -72
  169. phoenix/server/api/types/DatasetExample.py +88 -18
  170. phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
  171. phoenix/server/api/types/DatasetLabel.py +57 -0
  172. phoenix/server/api/types/DatasetSplit.py +98 -0
  173. phoenix/server/api/types/DatasetVersion.py +49 -4
  174. phoenix/server/api/types/DocumentAnnotation.py +212 -0
  175. phoenix/server/api/types/Experiment.py +264 -59
  176. phoenix/server/api/types/ExperimentComparison.py +5 -10
  177. phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
  178. phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
  179. phoenix/server/api/types/ExperimentRun.py +169 -65
  180. phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
  181. phoenix/server/api/types/GenerativeModel.py +245 -3
  182. phoenix/server/api/types/GenerativeProvider.py +70 -11
  183. phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
  184. phoenix/server/api/types/ModelInterface.py +16 -0
  185. phoenix/server/api/types/PlaygroundModel.py +20 -0
  186. phoenix/server/api/types/Project.py +1278 -216
  187. phoenix/server/api/types/ProjectSession.py +188 -28
  188. phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
  189. phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
  190. phoenix/server/api/types/Prompt.py +119 -39
  191. phoenix/server/api/types/PromptLabel.py +42 -25
  192. phoenix/server/api/types/PromptVersion.py +11 -8
  193. phoenix/server/api/types/PromptVersionTag.py +65 -25
  194. phoenix/server/api/types/ServerStatus.py +6 -0
  195. phoenix/server/api/types/Span.py +167 -123
  196. phoenix/server/api/types/SpanAnnotation.py +189 -42
  197. phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
  198. phoenix/server/api/types/SpanCostSummary.py +10 -0
  199. phoenix/server/api/types/SystemApiKey.py +65 -1
  200. phoenix/server/api/types/TokenPrice.py +16 -0
  201. phoenix/server/api/types/TokenUsage.py +3 -3
  202. phoenix/server/api/types/Trace.py +223 -51
  203. phoenix/server/api/types/TraceAnnotation.py +149 -50
  204. phoenix/server/api/types/User.py +137 -32
  205. phoenix/server/api/types/UserApiKey.py +73 -26
  206. phoenix/server/api/types/node.py +10 -0
  207. phoenix/server/api/types/pagination.py +11 -2
  208. phoenix/server/app.py +290 -45
  209. phoenix/server/authorization.py +38 -3
  210. phoenix/server/bearer_auth.py +34 -24
  211. phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
  212. phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
  213. phoenix/server/cost_tracking/helpers.py +68 -0
  214. phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
  215. phoenix/server/cost_tracking/regex_specificity.py +397 -0
  216. phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
  217. phoenix/server/daemons/__init__.py +0 -0
  218. phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
  219. phoenix/server/daemons/generative_model_store.py +103 -0
  220. phoenix/server/daemons/span_cost_calculator.py +99 -0
  221. phoenix/server/dml_event.py +17 -0
  222. phoenix/server/dml_event_handler.py +5 -0
  223. phoenix/server/email/sender.py +56 -3
  224. phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
  225. phoenix/server/email/types.py +11 -0
  226. phoenix/server/experiments/__init__.py +0 -0
  227. phoenix/server/experiments/utils.py +14 -0
  228. phoenix/server/grpc_server.py +11 -11
  229. phoenix/server/jwt_store.py +17 -15
  230. phoenix/server/ldap.py +1449 -0
  231. phoenix/server/main.py +26 -10
  232. phoenix/server/oauth2.py +330 -12
  233. phoenix/server/prometheus.py +66 -6
  234. phoenix/server/rate_limiters.py +4 -9
  235. phoenix/server/retention.py +33 -20
  236. phoenix/server/session_filters.py +49 -0
  237. phoenix/server/static/.vite/manifest.json +55 -51
  238. phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
  239. phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
  240. phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
  241. phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
  242. phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
  243. phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
  244. phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
  245. phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
  246. phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
  247. phoenix/server/templates/index.html +40 -6
  248. phoenix/server/thread_server.py +1 -2
  249. phoenix/server/types.py +14 -4
  250. phoenix/server/utils.py +74 -0
  251. phoenix/session/client.py +56 -3
  252. phoenix/session/data_extractor.py +5 -0
  253. phoenix/session/evaluation.py +14 -5
  254. phoenix/session/session.py +45 -9
  255. phoenix/settings.py +5 -0
  256. phoenix/trace/attributes.py +80 -13
  257. phoenix/trace/dsl/helpers.py +90 -1
  258. phoenix/trace/dsl/query.py +8 -6
  259. phoenix/trace/projects.py +5 -0
  260. phoenix/utilities/template_formatters.py +1 -1
  261. phoenix/version.py +1 -1
  262. arize_phoenix-10.0.4.dist-info/RECORD +0 -405
  263. phoenix/server/api/types/Evaluation.py +0 -39
  264. phoenix/server/cost_tracking/cost_lookup.py +0 -255
  265. phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
  266. phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
  267. phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
  268. phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
  269. phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
  270. phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
  271. phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
  272. phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
  273. phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
  274. phoenix/utilities/deprecation.py +0 -31
  275. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
  276. {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,11 @@
1
1
  import json
2
- from typing import Optional, cast
2
+ from typing import Any, Optional, Union, cast
3
3
 
4
4
  import strawberry
5
5
  from strawberry import UNSET
6
6
  from strawberry.scalars import JSON
7
7
 
8
+ from phoenix.db import models
8
9
  from phoenix.db.types.model_provider import ModelProvider
9
10
  from phoenix.server.api.helpers.prompts.models import (
10
11
  ContentPart,
@@ -12,11 +13,15 @@ from phoenix.server.api.helpers.prompts.models import (
12
13
  PromptMessage,
13
14
  PromptMessageRole,
14
15
  PromptTemplateFormat,
16
+ PromptTemplateType,
15
17
  RoleConversion,
16
18
  TextContentPart,
17
19
  ToolCallContentPart,
18
20
  ToolCallFunction,
19
21
  ToolResultContentPart,
22
+ normalize_response_format,
23
+ normalize_tools,
24
+ validate_invocation_parameters,
20
25
  )
21
26
 
22
27
 
@@ -83,6 +88,52 @@ class ChatPromptVersionInput:
83
88
  model_provider: ModelProvider
84
89
  model_name: str
85
90
 
91
+ def __post_init__(self) -> None:
92
+ self.invocation_parameters = {
93
+ k: v for k, v in self.invocation_parameters.items() if v is not None
94
+ }
95
+
96
+ def to_orm_prompt_version(
97
+ self,
98
+ user_id: Optional[int],
99
+ ) -> models.PromptVersion:
100
+ tool_definitions = [tool.definition for tool in self.tools]
101
+ tool_choice = cast(
102
+ Optional[Union[str, dict[str, Any]]],
103
+ cast(dict[str, Any], self.invocation_parameters).pop("tool_choice", None),
104
+ )
105
+ model_provider = ModelProvider(self.model_provider)
106
+ tools = (
107
+ normalize_tools(tool_definitions, model_provider, tool_choice)
108
+ if tool_definitions
109
+ else None
110
+ )
111
+ template = to_pydantic_prompt_chat_template_v1(self.template)
112
+ response_format = (
113
+ normalize_response_format(
114
+ self.response_format.definition,
115
+ model_provider,
116
+ )
117
+ if self.response_format
118
+ else None
119
+ )
120
+ invocation_parameters = validate_invocation_parameters(
121
+ self.invocation_parameters,
122
+ model_provider,
123
+ )
124
+ return models.PromptVersion(
125
+ description=self.description,
126
+ user_id=user_id,
127
+ template_type=PromptTemplateType.CHAT,
128
+ template_format=self.template_format,
129
+ template=template,
130
+ invocation_parameters=invocation_parameters,
131
+ tools=tools,
132
+ response_format=response_format,
133
+ model_provider=ModelProvider(self.model_provider),
134
+ model_name=self.model_name,
135
+ )
136
+
86
137
 
87
138
  def to_pydantic_prompt_chat_template_v1(
88
139
  prompt_chat_template_input: PromptChatTemplateInput,
@@ -3,7 +3,7 @@ from enum import Enum, auto
3
3
  from typing import Any, Optional, Protocol
4
4
 
5
5
  import strawberry
6
- from sqlalchemy import and_, desc, nulls_last
6
+ from sqlalchemy import and_, desc, func, nulls_last, select
7
7
  from sqlalchemy.orm import InstrumentedAttribute
8
8
  from sqlalchemy.sql.expression import Select
9
9
  from strawberry import UNSET
@@ -11,6 +11,7 @@ from typing_extensions import assert_never
11
11
 
12
12
  import phoenix.trace.v1 as pb
13
13
  from phoenix.db import models
14
+ from phoenix.db.helpers import truncate_name
14
15
  from phoenix.server.api.types.pagination import CursorSortColumnDataType
15
16
  from phoenix.server.api.types.SortDir import SortDir
16
17
  from phoenix.trace.schemas import SpanID
@@ -27,13 +28,14 @@ class SpanColumn(Enum):
27
28
  cumulativeTokenCountTotal = auto()
28
29
  cumulativeTokenCountPrompt = auto()
29
30
  cumulativeTokenCountCompletion = auto()
31
+ cumulativeTokenCostTotal = auto()
32
+ tokenCostTotal = auto()
30
33
 
31
34
  @property
32
35
  def column_name(self) -> str:
33
- return f"{self.name}_span_sort_column"
36
+ return truncate_name(f"{self.name}_span_sort_column")
34
37
 
35
- @property
36
- def orm_expression(self) -> Any:
38
+ def as_orm_expression(self, joined_table: Optional[Any] = None) -> Any:
37
39
  expr: Any
38
40
  if self is SpanColumn.startTime:
39
41
  expr = models.Span.start_time
@@ -56,6 +58,11 @@ class SpanColumn(Enum):
56
58
  expr = models.Span.cumulative_llm_token_count_prompt
57
59
  elif self is SpanColumn.cumulativeTokenCountCompletion:
58
60
  expr = models.Span.cumulative_llm_token_count_completion
61
+ elif self is SpanColumn.tokenCostTotal:
62
+ expr = models.SpanCost.total_cost
63
+ elif self is SpanColumn.cumulativeTokenCostTotal:
64
+ assert joined_table is not None
65
+ expr = joined_table.c.cumulative_total_cost
59
66
  else:
60
67
  assert_never(self)
61
68
  return expr.label(self.column_name)
@@ -73,12 +80,41 @@ class SpanColumn(Enum):
73
80
  or self is SpanColumn.tokenCountTotal
74
81
  or self is SpanColumn.tokenCountPrompt
75
82
  or self is SpanColumn.tokenCountCompletion
83
+ or self is SpanColumn.tokenCostTotal
84
+ or self is SpanColumn.cumulativeTokenCostTotal
76
85
  ):
77
86
  return CursorSortColumnDataType.FLOAT
78
87
  if self is SpanColumn.startTime or self is SpanColumn.endTime:
79
88
  return CursorSortColumnDataType.DATETIME
80
89
  assert_never(self)
81
90
 
91
+ def join_tables(self, stmt: Select[Any]) -> tuple[Select[Any], Any]:
92
+ """
93
+ If needed, joins tables required for the sort column.
94
+ """
95
+ if self is SpanColumn.tokenCostTotal:
96
+ return stmt.join_from(
97
+ models.Span,
98
+ models.SpanCost,
99
+ onclause=models.SpanCost.span_rowid == models.Span.id,
100
+ ), models.SpanCost
101
+ if self is SpanColumn.cumulativeTokenCostTotal:
102
+ trace_costs = (
103
+ select(
104
+ func.sum(models.SpanCost.total_cost).label("cumulative_total_cost"),
105
+ models.SpanCost.trace_rowid,
106
+ )
107
+ .select_from(models.SpanCost)
108
+ .group_by(models.SpanCost.trace_rowid)
109
+ .subquery()
110
+ )
111
+ stmt = stmt.join(
112
+ trace_costs,
113
+ onclause=models.Span.trace_rowid == trace_costs.c.trace_rowid,
114
+ )
115
+ return stmt, trace_costs
116
+ return stmt, None
117
+
82
118
 
83
119
  @strawberry.enum
84
120
  class EvalAttr(Enum):
@@ -139,13 +175,14 @@ class SpanSort:
139
175
 
140
176
  def update_orm_expr(self, stmt: Select[Any]) -> SpanSortConfig:
141
177
  if (col := self.col) and not self.eval_result_key:
142
- expr = col.orm_expression
178
+ stmt, joined_table = col.join_tables(stmt)
179
+ expr = col.as_orm_expression(joined_table)
143
180
  stmt = stmt.add_columns(expr)
144
181
  if self.dir == SortDir.desc:
145
182
  expr = desc(expr)
146
183
  return SpanSortConfig(
147
184
  stmt=stmt.order_by(nulls_last(expr)),
148
- orm_expression=col.orm_expression,
185
+ orm_expression=col.as_orm_expression(joined_table),
149
186
  dir=self.dir,
150
187
  column_name=col.column_name,
151
188
  column_data_type=col.data_type,
@@ -163,7 +200,7 @@ class SpanSort:
163
200
  models.SpanAnnotation.span_rowid == models.Span.id,
164
201
  models.SpanAnnotation.name == eval_name,
165
202
  ),
166
- ).order_by(expr)
203
+ ).order_by(nulls_last(expr))
167
204
  return SpanSortConfig(
168
205
  stmt=stmt,
169
206
  orm_expression=eval_result_key.attr.orm_expression,
@@ -0,0 +1,23 @@
1
+ from enum import Enum
2
+
3
+ import strawberry
4
+
5
+
6
+ @strawberry.enum
7
+ class TimeBinScale(Enum):
8
+ MINUTE = "minute"
9
+ HOUR = "hour"
10
+ DAY = "day"
11
+ WEEK = "week"
12
+ MONTH = "month"
13
+ YEAR = "year"
14
+
15
+
16
+ @strawberry.input
17
+ class TimeBinConfig:
18
+ scale: TimeBinScale = strawberry.field(
19
+ default=TimeBinScale.HOUR, description="The scale of time bins for aggregation."
20
+ )
21
+ utc_offset_minutes: int = strawberry.field(
22
+ default=0, description="Offset in minutes from UTC for local time binning."
23
+ )
@@ -0,0 +1,34 @@
1
+ from typing import Optional
2
+
3
+ import strawberry
4
+ from strawberry.relay import GlobalID
5
+ from strawberry.scalars import JSON
6
+
7
+ from phoenix.server.api.exceptions import BadRequest
8
+ from phoenix.server.api.types.AnnotationSource import AnnotationSource
9
+ from phoenix.server.api.types.AnnotatorKind import AnnotatorKind
10
+
11
+
12
+ @strawberry.input
13
+ class UpdateAnnotationInput:
14
+ id: GlobalID
15
+ name: str
16
+ annotator_kind: AnnotatorKind = AnnotatorKind.HUMAN
17
+ label: Optional[str] = None
18
+ score: Optional[float] = None
19
+ explanation: Optional[str] = None
20
+ metadata: JSON = strawberry.field(default_factory=dict)
21
+ source: AnnotationSource = AnnotationSource.APP
22
+
23
+ def __post_init__(self) -> None:
24
+ self.name = self.name.strip()
25
+ if isinstance(self.label, str):
26
+ self.label = self.label.strip()
27
+ if not self.label:
28
+ self.label = None
29
+ if isinstance(self.explanation, str):
30
+ self.explanation = self.explanation.strip()
31
+ if not self.explanation:
32
+ self.explanation = None
33
+ if self.score is None and not self.label and not self.explanation:
34
+ raise BadRequest("At least one of score, label, or explanation must be not null/empty.")
@@ -7,3 +7,4 @@ import strawberry
7
7
  class UserRoleInput(Enum):
8
8
  ADMIN = "ADMIN"
9
9
  MEMBER = "MEMBER"
10
+ VIEWER = "VIEWER"
@@ -5,10 +5,16 @@ from phoenix.server.api.mutations.api_key_mutations import ApiKeyMutationMixin
5
5
  from phoenix.server.api.mutations.chat_mutations import (
6
6
  ChatCompletionMutationMixin,
7
7
  )
8
+ from phoenix.server.api.mutations.dataset_label_mutations import DatasetLabelMutationMixin
8
9
  from phoenix.server.api.mutations.dataset_mutations import DatasetMutationMixin
10
+ from phoenix.server.api.mutations.dataset_split_mutations import DatasetSplitMutationMixin
9
11
  from phoenix.server.api.mutations.experiment_mutations import ExperimentMutationMixin
10
12
  from phoenix.server.api.mutations.export_events_mutations import ExportEventsMutationMixin
13
+ from phoenix.server.api.mutations.model_mutations import ModelMutationMixin
11
14
  from phoenix.server.api.mutations.project_mutations import ProjectMutationMixin
15
+ from phoenix.server.api.mutations.project_session_annotations_mutations import (
16
+ ProjectSessionAnnotationMutationMixin,
17
+ )
12
18
  from phoenix.server.api.mutations.project_trace_retention_policy_mutations import (
13
19
  ProjectTraceRetentionPolicyMutationMixin,
14
20
  )
@@ -26,15 +32,19 @@ class Mutation(
26
32
  AnnotationConfigMutationMixin,
27
33
  ApiKeyMutationMixin,
28
34
  ChatCompletionMutationMixin,
35
+ DatasetLabelMutationMixin,
29
36
  DatasetMutationMixin,
37
+ DatasetSplitMutationMixin,
30
38
  ExperimentMutationMixin,
31
39
  ExportEventsMutationMixin,
40
+ ModelMutationMixin,
32
41
  ProjectMutationMixin,
33
42
  ProjectTraceRetentionPolicyMutationMixin,
34
43
  PromptMutationMixin,
35
44
  PromptVersionTagMutationMixin,
36
45
  PromptLabelMutationMixin,
37
46
  SpanAnnotationMutationMixin,
47
+ ProjectSessionAnnotationMutationMixin,
38
48
  TraceAnnotationMutationMixin,
39
49
  TraceMutationMixin,
40
50
  UserMutationMixin,
@@ -23,7 +23,7 @@ from phoenix.db.types.annotation_configs import (
23
23
  from phoenix.db.types.annotation_configs import (
24
24
  FreeformAnnotationConfig as FreeformAnnotationConfigModel,
25
25
  )
26
- from phoenix.server.api.auth import IsNotReadOnly
26
+ from phoenix.server.api.auth import IsNotReadOnly, IsNotViewer
27
27
  from phoenix.server.api.context import Context
28
28
  from phoenix.server.api.exceptions import BadRequest, Conflict, NotFound
29
29
  from phoenix.server.api.queries import Query
@@ -197,7 +197,7 @@ def _to_pydantic_freeform_annotation_config(
197
197
 
198
198
  @strawberry.type
199
199
  class AnnotationConfigMutationMixin:
200
- @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
200
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
201
201
  async def create_annotation_config(
202
202
  self,
203
203
  info: Info[Context, None],
@@ -236,7 +236,7 @@ class AnnotationConfigMutationMixin:
236
236
  annotation_config=to_gql_annotation_config(annotation_config),
237
237
  )
238
238
 
239
- @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
239
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
240
240
  async def update_annotation_config(
241
241
  self,
242
242
  info: Info[Context, None],
@@ -285,7 +285,7 @@ class AnnotationConfigMutationMixin:
285
285
  annotation_config=to_gql_annotation_config(annotation_config),
286
286
  )
287
287
 
288
- @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
288
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
289
289
  async def delete_annotation_configs(
290
290
  self,
291
291
  info: Info[Context, None],
@@ -317,7 +317,7 @@ class AnnotationConfigMutationMixin:
317
317
  ],
318
318
  )
319
319
 
320
- @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
320
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
321
321
  async def add_annotation_config_to_project(
322
322
  self,
323
323
  info: Info[Context, None],
@@ -374,10 +374,10 @@ class AnnotationConfigMutationMixin:
374
374
  )
375
375
  return AddAnnotationConfigToProjectPayload(
376
376
  query=Query(),
377
- project=Project(project_rowid=project_id),
377
+ project=Project(id=project_id),
378
378
  )
379
379
 
380
- @strawberry.mutation(permission_classes=[IsNotReadOnly]) # type: ignore[misc]
380
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer]) # type: ignore[misc]
381
381
  async def remove_annotation_config_from_project(
382
382
  self,
383
383
  info: Info[Context, None],
@@ -409,5 +409,5 @@ class AnnotationConfigMutationMixin:
409
409
  raise NotFound("Could not find one or more input project annotation configs")
410
410
  return RemoveAnnotationConfigFromProjectPayload(
411
411
  query=Query(),
412
- project=Project(project_rowid=project_id),
412
+ project=Project(id=project_id),
413
413
  )
@@ -1,5 +1,5 @@
1
1
  from datetime import datetime, timezone
2
- from typing import Optional
2
+ from typing import Literal, Optional
3
3
 
4
4
  import strawberry
5
5
  from sqlalchemy import select
@@ -7,8 +7,9 @@ from strawberry import UNSET
7
7
  from strawberry.relay import GlobalID
8
8
  from strawberry.types import Info
9
9
 
10
- from phoenix.db import enums, models
11
- from phoenix.server.api.auth import IsAdmin, IsLocked, IsNotReadOnly
10
+ from phoenix.db import models
11
+ from phoenix.db.models import UserRoleName
12
+ from phoenix.server.api.auth import IsAdmin, IsLocked, IsNotReadOnly, IsNotViewer
12
13
  from phoenix.server.api.context import Context
13
14
  from phoenix.server.api.exceptions import Unauthorized
14
15
  from phoenix.server.api.queries import Query
@@ -60,18 +61,18 @@ class DeleteApiKeyMutationPayload:
60
61
 
61
62
  @strawberry.type
62
63
  class ApiKeyMutationMixin:
63
- @strawberry.mutation(permission_classes=[IsNotReadOnly, IsAdmin, IsLocked]) # type: ignore
64
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsAdmin, IsLocked]) # type: ignore
64
65
  async def create_system_api_key(
65
66
  self, info: Info[Context, None], input: CreateApiKeyInput
66
67
  ) -> CreateSystemApiKeyMutationPayload:
67
68
  assert (token_store := info.context.token_store) is not None
68
- user_role = enums.UserRole.SYSTEM
69
+ user_role: UserRoleName = "SYSTEM"
69
70
  async with info.context.db() as session:
70
71
  # Get the system user - note this could be pushed into a dataloader
71
72
  system_user = await session.scalar(
72
73
  select(models.User)
73
74
  .join(models.UserRole) # Join User with UserRole
74
- .where(models.UserRole.name == user_role.value) # Filter where role is SYSTEM
75
+ .where(models.UserRole.name == user_role) # Filter where role is SYSTEM
75
76
  .order_by(models.User.id)
76
77
  .limit(1)
77
78
  )
@@ -91,13 +92,7 @@ class ApiKeyMutationMixin:
91
92
  token, token_id = await token_store.create_api_key(claims)
92
93
  return CreateSystemApiKeyMutationPayload(
93
94
  jwt=token,
94
- api_key=SystemApiKey(
95
- id_attr=int(token_id),
96
- name=input.name,
97
- description=input.description or None,
98
- created_at=issued_at,
99
- expires_at=input.expires_at or None,
100
- ),
95
+ api_key=SystemApiKey(id=int(token_id)),
101
96
  query=Query(),
102
97
  )
103
98
 
@@ -112,12 +107,20 @@ class ApiKeyMutationMixin:
112
107
  except AttributeError:
113
108
  raise ValueError("User not found")
114
109
  issued_at = datetime.now(timezone.utc)
110
+ # Determine user role for API key
111
+ user_role: Literal["ADMIN", "MEMBER", "VIEWER"]
112
+ if user.is_admin:
113
+ user_role = "ADMIN"
114
+ elif user.is_viewer:
115
+ user_role = "VIEWER"
116
+ else:
117
+ user_role = "MEMBER"
115
118
  claims = ApiKeyClaims(
116
119
  subject=user.identity,
117
120
  issued_at=issued_at,
118
121
  expiration_time=input.expires_at or None,
119
122
  attributes=ApiKeyAttributes(
120
- user_role=enums.UserRole.MEMBER,
123
+ user_role=user_role,
121
124
  name=input.name,
122
125
  description=input.description,
123
126
  ),
@@ -125,18 +128,11 @@ class ApiKeyMutationMixin:
125
128
  token, token_id = await token_store.create_api_key(claims)
126
129
  return CreateUserApiKeyMutationPayload(
127
130
  jwt=token,
128
- api_key=UserApiKey(
129
- id_attr=int(token_id),
130
- name=input.name,
131
- description=input.description or None,
132
- created_at=issued_at,
133
- expires_at=input.expires_at or None,
134
- user_id=int(user.identity),
135
- ),
131
+ api_key=UserApiKey(id=int(token_id)),
136
132
  query=Query(),
137
133
  )
138
134
 
139
- @strawberry.mutation(permission_classes=[IsNotReadOnly, IsAdmin]) # type: ignore
135
+ @strawberry.mutation(permission_classes=[IsNotReadOnly, IsNotViewer, IsAdmin]) # type: ignore
140
136
  async def delete_system_api_key(
141
137
  self, info: Info[Context, None], input: DeleteApiKeyInput
142
138
  ) -> DeleteApiKeyMutationPayload: