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,9 +1,251 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import TYPE_CHECKING, Optional
4
+
1
5
  import strawberry
6
+ from openinference.semconv.trace import OpenInferenceLLMProviderValues
7
+ from strawberry.relay import Node, NodeID
8
+ from strawberry.relay.types import GlobalID
9
+ from strawberry.types import Info
10
+ from strawberry.types.unset import UNSET
11
+ from typing_extensions import TypeAlias, assert_never
2
12
 
13
+ from phoenix.db import models
14
+ from phoenix.server.api.context import Context
15
+ from phoenix.server.api.exceptions import BadRequest
16
+ from phoenix.server.api.input_types.TimeRange import TimeRange
17
+ from phoenix.server.api.types.CostBreakdown import CostBreakdown
3
18
  from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
19
+ from phoenix.server.api.types.ModelInterface import ModelInterface
20
+ from phoenix.server.api.types.node import from_global_id
21
+ from phoenix.server.api.types.SpanCostDetailSummaryEntry import SpanCostDetailSummaryEntry
22
+ from phoenix.server.api.types.SpanCostSummary import SpanCostSummary
23
+ from phoenix.server.api.types.TokenPrice import TokenKind, TokenPrice
24
+
25
+
26
+ @strawberry.enum
27
+ class GenerativeModelKind(Enum):
28
+ CUSTOM = "CUSTOM"
29
+ BUILT_IN = "BUILT_IN"
30
+
31
+
32
+ ProjectId: TypeAlias = int
33
+ TimeRangeKey: TypeAlias = tuple[Optional[datetime], Optional[datetime]]
34
+ CachedCostSummaryKey: TypeAlias = tuple[Optional[ProjectId], TimeRangeKey]
4
35
 
5
36
 
6
37
  @strawberry.type
7
- class GenerativeModel:
8
- name: str
9
- provider_key: GenerativeProviderKey
38
+ class GenerativeModel(Node, ModelInterface):
39
+ id: NodeID[int]
40
+ db_record: strawberry.Private[Optional[models.GenerativeModel]] = None
41
+ cached_cost_summary: strawberry.Private[
42
+ Optional[dict[CachedCostSummaryKey, SpanCostSummary]]
43
+ ] = None
44
+
45
+ def __post_init__(self) -> None:
46
+ if self.db_record and self.id != self.db_record.id:
47
+ raise ValueError("GenerativeModel ID mismatch")
48
+
49
+ @strawberry.field
50
+ async def name(self, info: Info[Context, None]) -> str:
51
+ if self.db_record:
52
+ val = self.db_record.name
53
+ else:
54
+ val = await info.context.data_loaders.generative_model_fields.load(
55
+ (self.id, models.GenerativeModel.name),
56
+ )
57
+ return val
58
+
59
+ @strawberry.field
60
+ async def provider(self, info: Info[Context, None]) -> Optional[str]:
61
+ if self.db_record:
62
+ provider = self.db_record.provider
63
+ else:
64
+ provider = await info.context.data_loaders.generative_model_fields.load(
65
+ (self.id, models.GenerativeModel.provider),
66
+ )
67
+ return provider or None
68
+
69
+ @strawberry.field
70
+ async def name_pattern(self, info: Info[Context, None]) -> str:
71
+ if self.db_record:
72
+ pattern = self.db_record.name_pattern.pattern
73
+ else:
74
+ name_pattern_obj = await info.context.data_loaders.generative_model_fields.load(
75
+ (self.id, models.GenerativeModel.name_pattern),
76
+ )
77
+ pattern = name_pattern_obj.pattern
78
+ assert isinstance(pattern, str)
79
+ return pattern
80
+
81
+ @strawberry.field
82
+ async def kind(self, info: Info[Context, None]) -> GenerativeModelKind:
83
+ if self.db_record:
84
+ is_built_in = self.db_record.is_built_in
85
+ else:
86
+ is_built_in = await info.context.data_loaders.generative_model_fields.load(
87
+ (self.id, models.GenerativeModel.is_built_in),
88
+ )
89
+ return GenerativeModelKind.BUILT_IN if is_built_in else GenerativeModelKind.CUSTOM
90
+
91
+ @strawberry.field
92
+ async def created_at(self, info: Info[Context, None]) -> datetime:
93
+ if self.db_record:
94
+ val = self.db_record.created_at
95
+ else:
96
+ val = await info.context.data_loaders.generative_model_fields.load(
97
+ (self.id, models.GenerativeModel.created_at),
98
+ )
99
+ return val
100
+
101
+ @strawberry.field
102
+ async def updated_at(self, info: Info[Context, None]) -> datetime:
103
+ if self.db_record:
104
+ val = self.db_record.updated_at
105
+ else:
106
+ val = await info.context.data_loaders.generative_model_fields.load(
107
+ (self.id, models.GenerativeModel.updated_at),
108
+ )
109
+ return val
110
+
111
+ @strawberry.field
112
+ async def provider_key(self, info: Info[Context, None]) -> Optional[GenerativeProviderKey]:
113
+ if self.db_record:
114
+ provider = self.db_record.provider
115
+ else:
116
+ provider = await info.context.data_loaders.generative_model_fields.load(
117
+ (self.id, models.GenerativeModel.provider),
118
+ )
119
+ return _semconv_provider_to_gql_generative_provider_key(provider) if provider else None
120
+
121
+ @strawberry.field
122
+ async def start_time(self, info: Info[Context, None]) -> Optional[datetime]:
123
+ if self.db_record:
124
+ val = self.db_record.start_time
125
+ else:
126
+ val = await info.context.data_loaders.generative_model_fields.load(
127
+ (self.id, models.GenerativeModel.start_time),
128
+ )
129
+ return val
130
+
131
+ def add_cached_cost_summary(
132
+ self, project_id: Optional[int], time_range: TimeRange, cost_summary: SpanCostSummary
133
+ ) -> None:
134
+ if self.cached_cost_summary is None:
135
+ self.cached_cost_summary = {}
136
+ time_range_key = (time_range.start, time_range.end) if time_range else (None, None)
137
+ cache_key = (project_id, time_range_key)
138
+ self.cached_cost_summary[cache_key] = cost_summary
139
+
140
+ @strawberry.field
141
+ async def token_prices(self, info: Info[Context, None]) -> list[TokenPrice]:
142
+ costs = await info.context.data_loaders.token_prices_by_model.load(self.id)
143
+ token_prices: list[TokenPrice] = []
144
+ for cost in costs:
145
+ token_prices.append(
146
+ TokenPrice(
147
+ token_type=cost.token_type,
148
+ kind=TokenKind.PROMPT if cost.is_prompt else TokenKind.COMPLETION,
149
+ cost_per_million_tokens=cost.base_rate * 1_000_000,
150
+ cost_per_token=cost.base_rate,
151
+ )
152
+ )
153
+ return token_prices
154
+
155
+ @strawberry.field
156
+ async def cost_summary(
157
+ self,
158
+ info: Info[Context, None],
159
+ project_id: Optional[GlobalID] = UNSET,
160
+ time_range: Optional[TimeRange] = UNSET,
161
+ ) -> SpanCostSummary:
162
+ if self.cached_cost_summary is not None:
163
+ time_range_key = (time_range.start, time_range.end) if time_range else (None, None)
164
+ project_rowid: Optional[int] = None
165
+ if project_id:
166
+ type_name, project_rowid = from_global_id(project_id)
167
+ if type_name != models.Project.__name__:
168
+ raise BadRequest("Invalid Project ID")
169
+ cache_key = (project_rowid, time_range_key)
170
+ if cache_key in self.cached_cost_summary:
171
+ return self.cached_cost_summary[cache_key]
172
+
173
+ if time_range or project_id:
174
+ raise BadRequest(
175
+ "Cost summaries for specific projects or time ranges are not yet implemented"
176
+ )
177
+
178
+ loader = info.context.data_loaders.span_cost_summary_by_generative_model
179
+ summary = await loader.load(self.id)
180
+ return SpanCostSummary(
181
+ prompt=CostBreakdown(
182
+ tokens=summary.prompt.tokens,
183
+ cost=summary.prompt.cost,
184
+ ),
185
+ completion=CostBreakdown(
186
+ tokens=summary.completion.tokens,
187
+ cost=summary.completion.cost,
188
+ ),
189
+ total=CostBreakdown(
190
+ tokens=summary.total.tokens,
191
+ cost=summary.total.cost,
192
+ ),
193
+ )
194
+
195
+ @strawberry.field
196
+ async def cost_detail_summary_entries(
197
+ self,
198
+ info: Info[Context, None],
199
+ ) -> list[SpanCostDetailSummaryEntry]:
200
+ loader = info.context.data_loaders.span_cost_detail_summary_entries_by_generative_model
201
+ summary = await loader.load(self.id)
202
+ return [
203
+ SpanCostDetailSummaryEntry(
204
+ token_type=entry.token_type,
205
+ is_prompt=entry.is_prompt,
206
+ value=CostBreakdown(
207
+ tokens=entry.value.tokens,
208
+ cost=entry.value.cost,
209
+ ),
210
+ )
211
+ for entry in summary
212
+ ]
213
+
214
+ @strawberry.field
215
+ async def last_used_at(self, info: Info[Context, None]) -> Optional[datetime]:
216
+ return await info.context.data_loaders.last_used_times_by_generative_model_id.load(self.id)
217
+
218
+
219
+ def _semconv_provider_to_gql_generative_provider_key(
220
+ semconv_provider_str: str,
221
+ ) -> Optional[GenerativeProviderKey]:
222
+ """
223
+ Translates a semconv provider string to a GQL GenerativeProviderKey.
224
+ """
225
+
226
+ try:
227
+ semconv_provider = OpenInferenceLLMProviderValues(semconv_provider_str)
228
+ except Exception:
229
+ return None
230
+ if semconv_provider == OpenInferenceLLMProviderValues.OPENAI:
231
+ return GenerativeProviderKey.OPENAI
232
+ if semconv_provider == OpenInferenceLLMProviderValues.ANTHROPIC:
233
+ return GenerativeProviderKey.ANTHROPIC
234
+ if semconv_provider == OpenInferenceLLMProviderValues.AZURE:
235
+ return GenerativeProviderKey.AZURE_OPENAI
236
+ if semconv_provider == OpenInferenceLLMProviderValues.GOOGLE:
237
+ return GenerativeProviderKey.GOOGLE
238
+ if semconv_provider == OpenInferenceLLMProviderValues.DEEPSEEK:
239
+ return GenerativeProviderKey.DEEPSEEK
240
+ if semconv_provider == OpenInferenceLLMProviderValues.XAI:
241
+ return GenerativeProviderKey.XAI
242
+ if semconv_provider == OpenInferenceLLMProviderValues.AWS:
243
+ return GenerativeProviderKey.AWS
244
+ if semconv_provider == OpenInferenceLLMProviderValues.COHERE:
245
+ return None # TODO
246
+ if semconv_provider == OpenInferenceLLMProviderValues.MISTRALAI:
247
+ return None # TODO
248
+ if TYPE_CHECKING:
249
+ assert_never(semconv_provider)
250
+ else:
251
+ return None
@@ -13,7 +13,17 @@ class GenerativeProviderKey(Enum):
13
13
  OPENAI = "OpenAI"
14
14
  ANTHROPIC = "Anthropic"
15
15
  AZURE_OPENAI = "Azure OpenAI"
16
- GOOGLE = "Google AI Studio"
16
+ GOOGLE = "Google Gemini"
17
+ DEEPSEEK = "DeepSeek"
18
+ XAI = "xAI"
19
+ OLLAMA = "Ollama"
20
+ AWS = "AWS Bedrock"
21
+
22
+
23
+ @strawberry.type
24
+ class GenerativeProviderCredentialConfig:
25
+ env_var_name: str
26
+ is_required: bool
17
27
 
18
28
 
19
29
  @strawberry.type
@@ -26,6 +36,10 @@ class GenerativeProvider:
26
36
  GenerativeProviderKey.ANTHROPIC: ["claude"],
27
37
  GenerativeProviderKey.OPENAI: ["gpt", "o1"],
28
38
  GenerativeProviderKey.GOOGLE: ["gemini"],
39
+ GenerativeProviderKey.DEEPSEEK: ["deepseek"],
40
+ GenerativeProviderKey.XAI: ["grok"],
41
+ GenerativeProviderKey.OLLAMA: ["llama", "mistral", "codellama", "phi", "qwen", "gemma"],
42
+ GenerativeProviderKey.AWS: ["nova", "titan"],
29
43
  }
30
44
 
31
45
  attribute_provider_to_generative_provider_map: ClassVar[dict[str, GenerativeProviderKey]] = {
@@ -33,13 +47,50 @@ class GenerativeProvider:
33
47
  OpenInferenceLLMProviderValues.ANTHROPIC.value: GenerativeProviderKey.ANTHROPIC,
34
48
  OpenInferenceLLMProviderValues.AZURE.value: GenerativeProviderKey.AZURE_OPENAI,
35
49
  OpenInferenceLLMProviderValues.GOOGLE.value: GenerativeProviderKey.GOOGLE,
50
+ OpenInferenceLLMProviderValues.AWS.value: GenerativeProviderKey.AWS,
51
+ # Note: DeepSeek uses OpenAI compatibility but we can't duplicate the key in the dict
52
+ # The provider will be determined through model name prefix matching instead
53
+ # Note: xAI uses OpenAI compatibility but we can't duplicate the key in the dict
54
+ # The provider will be determined through model name prefix matching instead
55
+ # Note: Ollama uses OpenAI compatibility but we can't duplicate the key in the dict
56
+ # The provider will be determined through model name prefix matching instead
36
57
  }
37
58
 
38
- model_provider_to_api_key_env_var_map: ClassVar[dict[GenerativeProviderKey, str]] = {
39
- GenerativeProviderKey.AZURE_OPENAI: "AZURE_OPENAI_API_KEY",
40
- GenerativeProviderKey.ANTHROPIC: "ANTHROPIC_API_KEY",
41
- GenerativeProviderKey.OPENAI: "OPENAI_API_KEY",
42
- GenerativeProviderKey.GOOGLE: "GEMINI_API_KEY",
59
+ """
60
+ A map of model provider keys to their credential requirements.
61
+ E.x. OpenAI requires a single API key
62
+ """
63
+ model_provider_to_credential_requirements_map: ClassVar[
64
+ dict[GenerativeProviderKey, list[GenerativeProviderCredentialConfig]]
65
+ ] = {
66
+ GenerativeProviderKey.AZURE_OPENAI: [
67
+ GenerativeProviderCredentialConfig(
68
+ env_var_name="AZURE_OPENAI_API_KEY", is_required=True
69
+ )
70
+ ],
71
+ GenerativeProviderKey.ANTHROPIC: [
72
+ GenerativeProviderCredentialConfig(env_var_name="ANTHROPIC_API_KEY", is_required=True)
73
+ ],
74
+ GenerativeProviderKey.OPENAI: [
75
+ GenerativeProviderCredentialConfig(env_var_name="OPENAI_API_KEY", is_required=True)
76
+ ],
77
+ GenerativeProviderKey.GOOGLE: [
78
+ GenerativeProviderCredentialConfig(env_var_name="GEMINI_API_KEY", is_required=True)
79
+ ],
80
+ GenerativeProviderKey.DEEPSEEK: [
81
+ GenerativeProviderCredentialConfig(env_var_name="DEEPSEEK_API_KEY", is_required=True)
82
+ ],
83
+ GenerativeProviderKey.XAI: [
84
+ GenerativeProviderCredentialConfig(env_var_name="XAI_API_KEY", is_required=True)
85
+ ],
86
+ GenerativeProviderKey.OLLAMA: [],
87
+ GenerativeProviderKey.AWS: [
88
+ GenerativeProviderCredentialConfig(env_var_name="AWS_ACCESS_KEY_ID", is_required=True),
89
+ GenerativeProviderCredentialConfig(
90
+ env_var_name="AWS_SECRET_ACCESS_KEY", is_required=True
91
+ ),
92
+ GenerativeProviderCredentialConfig(env_var_name="AWS_SESSION_TOKEN", is_required=False),
93
+ ],
43
94
  }
44
95
 
45
96
  @strawberry.field
@@ -66,13 +117,21 @@ class GenerativeProvider:
66
117
  return default_client.dependencies_are_installed()
67
118
  return False
68
119
 
69
- @strawberry.field(description="The API key for the provider") # type: ignore
70
- async def api_key_env_var(self) -> str:
71
- return self.model_provider_to_api_key_env_var_map[self.key]
120
+ @strawberry.field(description="The credential requirements for the provider") # type: ignore
121
+ async def credential_requirements(self) -> list[GenerativeProviderCredentialConfig]:
122
+ # Handle providers that don't require credentials
123
+ credential_requirements = self.model_provider_to_credential_requirements_map.get(self.key)
124
+ if credential_requirements is None:
125
+ return []
126
+ return self.model_provider_to_credential_requirements_map[self.key]
72
127
 
73
128
  @strawberry.field(description="Whether the credentials are set on the server for the provider") # type: ignore
74
- async def api_key_set(self) -> bool:
75
- return getenv(self.model_provider_to_api_key_env_var_map[self.key]) is not None
129
+ async def credentials_set(self) -> bool:
130
+ # Check if every required credential is set
131
+ return all(
132
+ getenv(credential_config.env_var_name) is not None or not credential_config.is_required
133
+ for credential_config in await self.credential_requirements()
134
+ )
76
135
 
77
136
  @classmethod
78
137
  def _infer_model_provider_from_model_name(
@@ -28,7 +28,7 @@ from .TimeSeries import (
28
28
 
29
29
 
30
30
  @strawberry.type
31
- class Model:
31
+ class InferenceModel:
32
32
  @strawberry.field
33
33
  def dimensions(
34
34
  self,
@@ -0,0 +1,16 @@
1
+ from typing import Optional
2
+
3
+ import strawberry
4
+
5
+ from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
6
+
7
+
8
+ @strawberry.interface
9
+ class ModelInterface:
10
+ @strawberry.field
11
+ async def name(self) -> str:
12
+ raise NotImplementedError
13
+
14
+ @strawberry.field
15
+ async def provider_key(self) -> Optional[GenerativeProviderKey]:
16
+ raise NotImplementedError
@@ -0,0 +1,20 @@
1
+ import strawberry
2
+ from strawberry.types import Info
3
+
4
+ from phoenix.server.api.context import Context
5
+ from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
6
+ from phoenix.server.api.types.ModelInterface import ModelInterface
7
+
8
+
9
+ @strawberry.type
10
+ class PlaygroundModel(ModelInterface):
11
+ name_value: strawberry.Private[str]
12
+ provider_key_value: strawberry.Private[GenerativeProviderKey]
13
+
14
+ @strawberry.field
15
+ async def name(self, info: Info[Context, None]) -> str:
16
+ return self.name_value
17
+
18
+ @strawberry.field
19
+ async def provider_key(self, info: Info[Context, None]) -> GenerativeProviderKey:
20
+ return self.provider_key_value