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
@@ -2,8 +2,8 @@ from datetime import datetime
2
2
  from typing import TYPE_CHECKING, Annotated, Optional
3
3
 
4
4
  import strawberry
5
- from sqlalchemy import select
6
- from sqlalchemy.orm import load_only
5
+ from sqlalchemy import func, select
6
+ from sqlalchemy.sql.functions import coalesce
7
7
  from strawberry import UNSET
8
8
  from strawberry.relay import Connection, GlobalID, Node, NodeID
9
9
  from strawberry.scalars import JSON
@@ -11,30 +11,112 @@ from strawberry.types import Info
11
11
 
12
12
  from phoenix.db import models
13
13
  from phoenix.server.api.context import Context
14
- from phoenix.server.api.types.ExperimentRunAnnotation import (
15
- ExperimentRunAnnotation,
16
- to_gql_experiment_run_annotation,
17
- )
14
+ from phoenix.server.api.types.CostBreakdown import CostBreakdown
15
+ from phoenix.server.api.types.ExperimentRunAnnotation import ExperimentRunAnnotation
18
16
  from phoenix.server.api.types.pagination import (
19
17
  ConnectionArgs,
20
18
  CursorString,
21
19
  connection_from_list,
22
20
  )
21
+ from phoenix.server.api.types.SpanCostDetailSummaryEntry import SpanCostDetailSummaryEntry
22
+ from phoenix.server.api.types.SpanCostSummary import SpanCostSummary
23
23
  from phoenix.server.api.types.Trace import Trace
24
24
 
25
25
  if TYPE_CHECKING:
26
- from phoenix.server.api.types.DatasetExample import DatasetExample
26
+ from .DatasetExample import DatasetExample
27
+ from .Trace import Trace
27
28
 
28
29
 
29
30
  @strawberry.type
30
31
  class ExperimentRun(Node):
31
- id_attr: NodeID[int]
32
- experiment_id: GlobalID
33
- trace_id: Optional[str]
34
- output: Optional[JSON]
35
- start_time: datetime
36
- end_time: datetime
37
- error: Optional[str]
32
+ id: NodeID[int]
33
+ db_record: strawberry.Private[Optional[models.ExperimentRun]] = None
34
+
35
+ def __post_init__(self) -> None:
36
+ if self.db_record and self.id != self.db_record.id:
37
+ raise ValueError("ExperimentRun ID mismatch")
38
+
39
+ @strawberry.field
40
+ async def experiment_id(self, info: Info[Context, None]) -> GlobalID:
41
+ from .Experiment import Experiment
42
+
43
+ if self.db_record:
44
+ experiment_id = self.db_record.experiment_id
45
+ else:
46
+ experiment_id = await info.context.data_loaders.experiment_run_fields.load(
47
+ (self.id, models.ExperimentRun.experiment_id),
48
+ )
49
+ return GlobalID(Experiment.__name__, str(experiment_id))
50
+
51
+ @strawberry.field
52
+ async def repetition_number(self, info: Info[Context, None]) -> int:
53
+ if self.db_record:
54
+ val = self.db_record.repetition_number
55
+ else:
56
+ val = await info.context.data_loaders.experiment_run_fields.load(
57
+ (self.id, models.ExperimentRun.repetition_number),
58
+ )
59
+ return val
60
+
61
+ @strawberry.field
62
+ async def trace_id(self, info: Info[Context, None]) -> Optional[str]:
63
+ if self.db_record:
64
+ val = self.db_record.trace_id
65
+ else:
66
+ val = await info.context.data_loaders.experiment_run_fields.load(
67
+ (self.id, models.ExperimentRun.trace_id),
68
+ )
69
+ return val
70
+
71
+ @strawberry.field
72
+ async def output(self, info: Info[Context, None]) -> Optional[JSON]:
73
+ if self.db_record:
74
+ output_dict = self.db_record.output
75
+ else:
76
+ output_dict = await info.context.data_loaders.experiment_run_fields.load(
77
+ (self.id, models.ExperimentRun.output),
78
+ )
79
+ return output_dict.get("task_output") if output_dict else None
80
+
81
+ @strawberry.field
82
+ async def start_time(self, info: Info[Context, None]) -> datetime:
83
+ if self.db_record:
84
+ val = self.db_record.start_time
85
+ else:
86
+ val = await info.context.data_loaders.experiment_run_fields.load(
87
+ (self.id, models.ExperimentRun.start_time),
88
+ )
89
+ return val
90
+
91
+ @strawberry.field
92
+ async def end_time(self, info: Info[Context, None]) -> datetime:
93
+ if self.db_record:
94
+ val = self.db_record.end_time
95
+ else:
96
+ val = await info.context.data_loaders.experiment_run_fields.load(
97
+ (self.id, models.ExperimentRun.end_time),
98
+ )
99
+ return val
100
+
101
+ @strawberry.field
102
+ async def error(self, info: Info[Context, None]) -> Optional[str]:
103
+ if self.db_record:
104
+ val = self.db_record.error
105
+ else:
106
+ val = await info.context.data_loaders.experiment_run_fields.load(
107
+ (self.id, models.ExperimentRun.error),
108
+ )
109
+ return val
110
+
111
+ @strawberry.field
112
+ async def latency_ms(self, info: Info[Context, None]) -> float:
113
+ if self.db_record:
114
+ val = self.db_record.latency_ms
115
+ else:
116
+ val = await info.context.data_loaders.experiment_run_fields.load(
117
+ (self.id, models.ExperimentRun.latency_ms),
118
+ )
119
+ return val
38
120
 
39
121
  @strawberry.field
40
122
  async def annotations(
@@ -51,69 +133,91 @@ class ExperimentRun(Node):
51
133
  last=last,
52
134
  before=before if isinstance(before, CursorString) else None,
53
135
  )
54
- run_id = self.id_attr
55
- annotations = await info.context.data_loaders.experiment_run_annotations.load(run_id)
136
+ annotations = await info.context.data_loaders.experiment_run_annotations.load(self.id)
56
137
  return connection_from_list(
57
- [to_gql_experiment_run_annotation(annotation) for annotation in annotations], args
138
+ [
139
+ ExperimentRunAnnotation(id=annotation.id, db_record=annotation)
140
+ for annotation in annotations
141
+ ],
142
+ args,
58
143
  )
59
144
 
60
145
  @strawberry.field
61
- async def trace(self, info: Info) -> Optional[Trace]:
62
- if not self.trace_id:
146
+ async def trace(
147
+ self, info: Info[Context, None]
148
+ ) -> Optional[Annotated["Trace", strawberry.lazy(".Trace")]]:
149
+ if self.db_record:
150
+ trace_id = self.db_record.trace_id
151
+ else:
152
+ trace_id = await info.context.data_loaders.experiment_run_fields.load(
153
+ (self.id, models.ExperimentRun.trace_id),
154
+ )
155
+ if not trace_id:
63
156
  return None
64
- dataloader = info.context.data_loaders.trace_by_trace_ids
65
- if (trace := await dataloader.load(self.trace_id)) is None:
157
+ loader = info.context.data_loaders.trace_by_trace_ids
158
+ if (trace := await loader.load(trace_id)) is None:
66
159
  return None
67
- return Trace(trace_rowid=trace.id, db_trace=trace)
160
+ from .Trace import Trace
161
+
162
+ return Trace(id=trace.id, db_record=trace)
68
163
 
69
164
  @strawberry.field
70
165
  async def example(
71
- self, info: Info
166
+ self, info: Info[Context, None]
72
167
  ) -> Annotated[
73
- "DatasetExample", strawberry.lazy("phoenix.server.api.types.DatasetExample")
168
+ "DatasetExample", strawberry.lazy(".DatasetExample")
74
169
  ]: # use lazy types to avoid circular import: https://strawberry.rocks/docs/types/lazy
75
- from phoenix.server.api.types.DatasetExample import DatasetExample
170
+ from .DatasetExample import DatasetExample
76
171
 
77
- async with info.context.db() as session:
78
- assert (
79
- result := await session.execute(
80
- select(models.DatasetExample, models.Experiment.dataset_version_id)
81
- .select_from(models.ExperimentRun)
82
- .join(
83
- models.DatasetExample,
84
- models.DatasetExample.id == models.ExperimentRun.dataset_example_id,
85
- )
86
- .join(
87
- models.Experiment,
88
- models.Experiment.id == models.ExperimentRun.experiment_id,
89
- )
90
- .where(models.ExperimentRun.id == self.id_attr)
91
- .options(load_only(models.DatasetExample.id, models.DatasetExample.created_at))
92
- )
93
- ) is not None
94
- example, version_id = result.first()
95
- return DatasetExample(
96
- id_attr=example.id,
97
- created_at=example.created_at,
98
- version_id=version_id,
172
+ loader = info.context.data_loaders.dataset_examples_and_versions_by_experiment_run
173
+ (example, version_id) = await loader.load(self.id)
174
+ return DatasetExample(id=example.id, db_record=example, version_id=version_id)
175
+
176
+ @strawberry.field
177
+ async def cost_summary(self, info: Info[Context, None]) -> SpanCostSummary:
178
+ summary = await info.context.data_loaders.span_cost_summary_by_experiment_run.load(self.id)
179
+ return SpanCostSummary(
180
+ prompt=CostBreakdown(
181
+ tokens=summary.prompt.tokens,
182
+ cost=summary.prompt.cost,
183
+ ),
184
+ completion=CostBreakdown(
185
+ tokens=summary.completion.tokens,
186
+ cost=summary.completion.cost,
187
+ ),
188
+ total=CostBreakdown(
189
+ tokens=summary.total.tokens,
190
+ cost=summary.total.cost,
191
+ ),
99
192
  )
100
193
 
194
+ @strawberry.field
195
+ async def cost_detail_summary_entries(
196
+ self, info: Info[Context, None]
197
+ ) -> list[SpanCostDetailSummaryEntry]:
198
+ stmt = (
199
+ select(
200
+ models.SpanCostDetail.token_type,
201
+ models.SpanCostDetail.is_prompt,
202
+ coalesce(func.sum(models.SpanCostDetail.cost), 0).label("cost"),
203
+ coalesce(func.sum(models.SpanCostDetail.tokens), 0).label("tokens"),
204
+ )
205
+ .select_from(models.SpanCostDetail)
206
+ .join(models.SpanCost, models.SpanCostDetail.span_cost_id == models.SpanCost.id)
207
+ .join(models.Span, models.SpanCost.span_rowid == models.Span.id)
208
+ .join(models.Trace, models.Span.trace_rowid == models.Trace.id)
209
+ .join(models.ExperimentRun, models.ExperimentRun.trace_id == models.Trace.trace_id)
210
+ .where(models.ExperimentRun.id == self.id)
211
+ .group_by(models.SpanCostDetail.token_type, models.SpanCostDetail.is_prompt)
212
+ )
101
213
 
102
- def to_gql_experiment_run(run: models.ExperimentRun) -> ExperimentRun:
103
- """
104
- Converts an ORM experiment run to a GraphQL ExperimentRun.
105
- """
106
-
107
- from phoenix.server.api.types.Experiment import Experiment
108
-
109
- return ExperimentRun(
110
- id_attr=run.id,
111
- experiment_id=GlobalID(Experiment.__name__, str(run.experiment_id)),
112
- trace_id=trace_id
113
- if (trace := run.trace) and (trace_id := trace.trace_id) is not None
114
- else None,
115
- output=run.output.get("task_output"),
116
- start_time=run.start_time,
117
- end_time=run.end_time,
118
- error=run.error,
119
- )
214
+ async with info.context.db() as session:
215
+ data = await session.stream(stmt)
216
+ return [
217
+ SpanCostDetailSummaryEntry(
218
+ token_type=token_type,
219
+ is_prompt=is_prompt,
220
+ value=CostBreakdown(tokens=tokens, cost=cost),
221
+ )
222
+ async for token_type, is_prompt, cost, tokens in data
223
+ ]
@@ -1,56 +1,175 @@
1
1
  from datetime import datetime
2
+ from math import isfinite
2
3
  from typing import Optional
3
4
 
4
5
  import strawberry
5
6
  from strawberry import Info
6
- from strawberry.relay import Node, NodeID
7
+ from strawberry.relay import GlobalID, Node, NodeID
7
8
  from strawberry.scalars import JSON
8
9
 
9
10
  from phoenix.db import models
11
+ from phoenix.server.api.context import Context
10
12
  from phoenix.server.api.types.AnnotatorKind import ExperimentRunAnnotatorKind
11
13
  from phoenix.server.api.types.Trace import Trace
12
14
 
13
15
 
14
16
  @strawberry.type
15
17
  class ExperimentRunAnnotation(Node):
16
- id_attr: NodeID[int]
17
- name: str
18
- annotator_kind: ExperimentRunAnnotatorKind
19
- label: Optional[str]
20
- score: Optional[float]
21
- explanation: Optional[str]
22
- error: Optional[str]
23
- metadata: JSON
24
- start_time: datetime
25
- end_time: datetime
26
- trace_id: Optional[str]
27
-
28
- @strawberry.field
29
- async def trace(self, info: Info) -> Optional[Trace]:
30
- if not self.trace_id:
18
+ id: NodeID[int]
19
+ db_record: strawberry.Private[Optional[models.ExperimentRunAnnotation]] = None
20
+
21
+ def __post_init__(self) -> None:
22
+ if self.db_record and self.id != self.db_record.id:
23
+ raise ValueError("ExperimentRunAnnotation ID mismatch")
24
+
25
+ @strawberry.field(description="Name of the annotation, e.g. 'helpfulness' or 'relevance'.") # type: ignore
26
+ async def name(
27
+ self,
28
+ info: Info[Context, None],
29
+ ) -> str:
30
+ if self.db_record:
31
+ val = self.db_record.name
32
+ else:
33
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
34
+ (self.id, models.ExperimentRunAnnotation.name),
35
+ )
36
+ return val
37
+
38
+ @strawberry.field(description="The kind of annotator that produced the annotation.") # type: ignore
39
+ async def annotator_kind(
40
+ self,
41
+ info: Info[Context, None],
42
+ ) -> ExperimentRunAnnotatorKind:
43
+ if self.db_record:
44
+ val = self.db_record.annotator_kind
45
+ else:
46
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
47
+ (self.id, models.ExperimentRunAnnotation.annotator_kind),
48
+ )
49
+ return ExperimentRunAnnotatorKind(val)
50
+
51
+ @strawberry.field(
52
+ description="Value of the annotation in the form of a string, e.g. 'helpful' or 'not helpful'. Note that the label is not necessarily binary." # noqa: E501
53
+ ) # type: ignore
54
+ async def label(
55
+ self,
56
+ info: Info[Context, None],
57
+ ) -> Optional[str]:
58
+ if self.db_record:
59
+ val = self.db_record.label
60
+ else:
61
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
62
+ (self.id, models.ExperimentRunAnnotation.label),
63
+ )
64
+ return val
65
+
66
+ @strawberry.field(description="Value of the annotation in the form of a numeric score.") # type: ignore
67
+ async def score(
68
+ self,
69
+ info: Info[Context, None],
70
+ ) -> Optional[float]:
71
+ if self.db_record:
72
+ val = self.db_record.score
73
+ else:
74
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
75
+ (self.id, models.ExperimentRunAnnotation.score),
76
+ )
77
+ return val if val is not None and isfinite(val) else None
78
+
79
+ @strawberry.field(
80
+ description="The annotator's explanation for the annotation result (i.e. score or label, or both) given to the subject." # noqa: E501
81
+ ) # type: ignore
82
+ async def explanation(
83
+ self,
84
+ info: Info[Context, None],
85
+ ) -> Optional[str]:
86
+ if self.db_record:
87
+ val = self.db_record.explanation
88
+ else:
89
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
90
+ (self.id, models.ExperimentRunAnnotation.explanation),
91
+ )
92
+ return val
93
+
94
+ @strawberry.field(description="Error message if the annotation failed to produce a result.") # type: ignore
95
+ async def error(
96
+ self,
97
+ info: Info[Context, None],
98
+ ) -> Optional[str]:
99
+ if self.db_record:
100
+ val = self.db_record.error
101
+ else:
102
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
103
+ (self.id, models.ExperimentRunAnnotation.error),
104
+ )
105
+ return val
106
+
107
+ @strawberry.field(description="Metadata about the annotation.") # type: ignore
108
+ async def metadata(
109
+ self,
110
+ info: Info[Context, None],
111
+ ) -> JSON:
112
+ if self.db_record:
113
+ val = self.db_record.metadata_
114
+ else:
115
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
116
+ (self.id, models.ExperimentRunAnnotation.metadata_),
117
+ )
118
+ return val
119
+
120
+ @strawberry.field(description="The date and time when the annotation was created.") # type: ignore
121
+ async def start_time(
122
+ self,
123
+ info: Info[Context, None],
124
+ ) -> datetime:
125
+ if self.db_record:
126
+ val = self.db_record.start_time
127
+ else:
128
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
129
+ (self.id, models.ExperimentRunAnnotation.start_time),
130
+ )
131
+ return val
132
+
133
+ @strawberry.field(description="The date and time when the annotation was last updated.") # type: ignore
134
+ async def end_time(
135
+ self,
136
+ info: Info[Context, None],
137
+ ) -> datetime:
138
+ if self.db_record:
139
+ val = self.db_record.end_time
140
+ else:
141
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
142
+ (self.id, models.ExperimentRunAnnotation.end_time),
143
+ )
144
+ return val
145
+
146
+ @strawberry.field(description="The identifier of the trace associated with the annotation.") # type: ignore
147
+ async def trace_id(
148
+ self,
149
+ info: Info[Context, None],
150
+ ) -> Optional[GlobalID]:
151
+ if self.db_record:
152
+ val = self.db_record.trace_id
153
+ else:
154
+ val = await info.context.data_loaders.experiment_run_annotation_fields.load(
155
+ (self.id, models.ExperimentRunAnnotation.trace_id),
156
+ )
157
+ return None if val is None else GlobalID(type_name=Trace.__name__, node_id=val)
158
+
159
+ @strawberry.field(description="The trace associated with the annotation.") # type: ignore
160
+ async def trace(
161
+ self,
162
+ info: Info[Context, None],
163
+ ) -> Optional[Trace]:
164
+ if self.db_record:
165
+ trace_id = self.db_record.trace_id
166
+ else:
167
+ trace_id = await info.context.data_loaders.experiment_run_annotation_fields.load(
168
+ (self.id, models.ExperimentRunAnnotation.trace_id),
169
+ )
170
+ if not trace_id:
31
171
  return None
32
172
  dataloader = info.context.data_loaders.trace_by_trace_ids
33
- if (trace := await dataloader.load(self.trace_id)) is None:
173
+ if (trace := await dataloader.load(trace_id)) is None:
34
174
  return None
35
- return Trace(trace_rowid=trace.id, db_trace=trace)
36
-
37
-
38
- def to_gql_experiment_run_annotation(
39
- annotation: models.ExperimentRunAnnotation,
40
- ) -> ExperimentRunAnnotation:
41
- """
42
- Converts an ORM experiment run annotation to a GraphQL ExperimentRunAnnotation.
43
- """
44
- return ExperimentRunAnnotation(
45
- id_attr=annotation.id,
46
- name=annotation.name,
47
- annotator_kind=ExperimentRunAnnotatorKind(annotation.annotator_kind),
48
- label=annotation.label,
49
- score=annotation.score,
50
- explanation=annotation.explanation,
51
- error=annotation.error,
52
- metadata=annotation.metadata_,
53
- start_time=annotation.start_time,
54
- end_time=annotation.end_time,
55
- trace_id=annotation.trace_id,
56
- )
175
+ return Trace(id=trace.id, db_record=trace)