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.
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
- arize_phoenix-12.28.1.dist-info/RECORD +499 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
- phoenix/__generated__/__init__.py +0 -0
- phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
- phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
- phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
- phoenix/__init__.py +5 -4
- phoenix/auth.py +39 -2
- phoenix/config.py +1763 -91
- phoenix/datetime_utils.py +120 -2
- phoenix/db/README.md +595 -25
- phoenix/db/bulk_inserter.py +145 -103
- phoenix/db/engines.py +140 -33
- phoenix/db/enums.py +3 -12
- phoenix/db/facilitator.py +302 -35
- phoenix/db/helpers.py +1000 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +135 -2
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +17 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span.py +15 -11
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +50 -20
- phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
- phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
- phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
- phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
- phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
- phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
- phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
- phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
- phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
- phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
- phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
- phoenix/db/models.py +669 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/model_provider.py +4 -0
- phoenix/db/types/token_price_customization.py +29 -0
- phoenix/db/types/trace_retention.py +23 -15
- phoenix/experiments/evaluators/utils.py +3 -3
- phoenix/experiments/functions.py +160 -52
- phoenix/experiments/tracing.py +2 -2
- phoenix/experiments/types.py +1 -1
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +38 -7
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +100 -4
- phoenix/server/api/dataloaders/__init__.py +79 -5
- phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
- phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
- phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
- phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
- phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
- phoenix/server/api/dataloaders/dataset_labels.py +36 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
- phoenix/server/api/dataloaders/document_evaluations.py +6 -9
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
- phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
- phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
- phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
- phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
- phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
- phoenix/server/api/dataloaders/record_counts.py +37 -10
- phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
- phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
- phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
- phoenix/server/api/dataloaders/span_costs.py +29 -0
- phoenix/server/api/dataloaders/table_fields.py +2 -2
- phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
- phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
- phoenix/server/api/dataloaders/types.py +29 -0
- phoenix/server/api/exceptions.py +11 -1
- phoenix/server/api/helpers/dataset_helpers.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +1243 -292
- phoenix/server/api/helpers/playground_registry.py +2 -2
- phoenix/server/api/helpers/playground_spans.py +8 -4
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +205 -22
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
- phoenix/server/api/input_types/CreateProjectInput.py +27 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +17 -0
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
- phoenix/server/api/input_types/PromptFilter.py +14 -0
- phoenix/server/api/input_types/PromptVersionInput.py +52 -1
- phoenix/server/api/input_types/SpanSort.py +44 -7
- phoenix/server/api/input_types/TimeBinConfig.py +23 -0
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +10 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +19 -23
- phoenix/server/api/mutations/chat_mutations.py +154 -47
- phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
- phoenix/server/api/mutations/dataset_mutations.py +21 -16
- phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
- phoenix/server/api/mutations/experiment_mutations.py +2 -2
- phoenix/server/api/mutations/export_events_mutations.py +3 -3
- phoenix/server/api/mutations/model_mutations.py +210 -0
- phoenix/server/api/mutations/project_mutations.py +49 -10
- phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
- phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
- phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
- phoenix/server/api/mutations/prompt_mutations.py +65 -129
- phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
- phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
- phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
- phoenix/server/api/mutations/trace_mutations.py +47 -3
- phoenix/server/api/mutations/user_mutations.py +66 -41
- phoenix/server/api/queries.py +768 -293
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +154 -88
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +369 -106
- phoenix/server/api/routers/v1/__init__.py +24 -4
- phoenix/server/api/routers/v1/annotation_configs.py +23 -31
- phoenix/server/api/routers/v1/annotations.py +481 -17
- phoenix/server/api/routers/v1/datasets.py +395 -81
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +24 -31
- phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
- phoenix/server/api/routers/v1/experiment_runs.py +337 -59
- phoenix/server/api/routers/v1/experiments.py +479 -48
- phoenix/server/api/routers/v1/models.py +7 -0
- phoenix/server/api/routers/v1/projects.py +18 -49
- phoenix/server/api/routers/v1/prompts.py +54 -40
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +1091 -81
- phoenix/server/api/routers/v1/traces.py +132 -78
- phoenix/server/api/routers/v1/users.py +389 -0
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +305 -88
- phoenix/server/api/types/Annotation.py +90 -23
- phoenix/server/api/types/ApiKey.py +13 -17
- phoenix/server/api/types/AuthMethod.py +1 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
- phoenix/server/api/types/CostBreakdown.py +12 -0
- phoenix/server/api/types/Dataset.py +226 -72
- phoenix/server/api/types/DatasetExample.py +88 -18
- phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
- phoenix/server/api/types/DatasetLabel.py +57 -0
- phoenix/server/api/types/DatasetSplit.py +98 -0
- phoenix/server/api/types/DatasetVersion.py +49 -4
- phoenix/server/api/types/DocumentAnnotation.py +212 -0
- phoenix/server/api/types/Experiment.py +264 -59
- phoenix/server/api/types/ExperimentComparison.py +5 -10
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +169 -65
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +245 -3
- phoenix/server/api/types/GenerativeProvider.py +70 -11
- phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
- phoenix/server/api/types/ModelInterface.py +16 -0
- phoenix/server/api/types/PlaygroundModel.py +20 -0
- phoenix/server/api/types/Project.py +1278 -216
- phoenix/server/api/types/ProjectSession.py +188 -28
- phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
- phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
- phoenix/server/api/types/Prompt.py +119 -39
- phoenix/server/api/types/PromptLabel.py +42 -25
- phoenix/server/api/types/PromptVersion.py +11 -8
- phoenix/server/api/types/PromptVersionTag.py +65 -25
- phoenix/server/api/types/ServerStatus.py +6 -0
- phoenix/server/api/types/Span.py +167 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
- phoenix/server/api/types/SpanCostSummary.py +10 -0
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/TokenPrice.py +16 -0
- phoenix/server/api/types/TokenUsage.py +3 -3
- phoenix/server/api/types/Trace.py +223 -51
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +137 -32
- phoenix/server/api/types/UserApiKey.py +73 -26
- phoenix/server/api/types/node.py +10 -0
- phoenix/server/api/types/pagination.py +11 -2
- phoenix/server/app.py +290 -45
- phoenix/server/authorization.py +38 -3
- phoenix/server/bearer_auth.py +34 -24
- phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
- phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
- phoenix/server/cost_tracking/helpers.py +68 -0
- phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
- phoenix/server/cost_tracking/regex_specificity.py +397 -0
- phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
- phoenix/server/daemons/__init__.py +0 -0
- phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
- phoenix/server/daemons/generative_model_store.py +103 -0
- phoenix/server/daemons/span_cost_calculator.py +99 -0
- phoenix/server/dml_event.py +17 -0
- phoenix/server/dml_event_handler.py +5 -0
- phoenix/server/email/sender.py +56 -3
- phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/experiments/__init__.py +0 -0
- phoenix/server/experiments/utils.py +14 -0
- phoenix/server/grpc_server.py +11 -11
- phoenix/server/jwt_store.py +17 -15
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +26 -10
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +66 -6
- phoenix/server/rate_limiters.py +4 -9
- phoenix/server/retention.py +33 -20
- phoenix/server/session_filters.py +49 -0
- phoenix/server/static/.vite/manifest.json +55 -51
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
- phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
- phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
- phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
- phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
- phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
- phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +40 -6
- phoenix/server/thread_server.py +1 -2
- phoenix/server/types.py +14 -4
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +56 -3
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +14 -5
- phoenix/session/session.py +45 -9
- phoenix/settings.py +5 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/helpers.py +90 -1
- phoenix/trace/dsl/query.py +8 -6
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- arize_phoenix-10.0.4.dist-info/RECORD +0 -405
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/cost_tracking/cost_lookup.py +0 -255
- phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
- phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
- phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
- phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
- phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
- phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
- phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
- phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
70
|
-
async def
|
|
71
|
-
|
|
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
|
|
75
|
-
|
|
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(
|
|
@@ -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
|