arize-phoenix 11.23.1__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-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +61 -36
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/RECORD +212 -162
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-11.23.1.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 +2 -1
- phoenix/auth.py +27 -2
- phoenix/config.py +1594 -81
- phoenix/db/README.md +546 -28
- phoenix/db/bulk_inserter.py +119 -116
- phoenix/db/engines.py +140 -33
- phoenix/db/facilitator.py +22 -1
- phoenix/db/helpers.py +818 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +133 -1
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +2 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +41 -18
- 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/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 +364 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/trace_retention.py +7 -6
- phoenix/experiments/functions.py +69 -19
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +9 -0
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +60 -0
- phoenix/server/api/dataloaders/__init__.py +36 -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/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_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +28 -14
- phoenix/server/api/dataloaders/span_costs.py +3 -9
- 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/exceptions.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +263 -83
- phoenix/server/api/helpers/playground_spans.py +2 -1
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +61 -19
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +3 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +5 -2
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +3 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +158 -1
- phoenix/server/api/input_types/PromptVersionInput.py +47 -1
- phoenix/server/api/input_types/SpanSort.py +3 -2
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +8 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +15 -20
- phoenix/server/api/mutations/chat_mutations.py +106 -37
- 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 +11 -9
- phoenix/server/api/mutations/project_mutations.py +4 -4
- 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 +13 -8
- phoenix/server/api/mutations/trace_mutations.py +3 -3
- phoenix/server/api/mutations/user_mutations.py +55 -26
- phoenix/server/api/queries.py +501 -617
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +141 -87
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +349 -101
- phoenix/server/api/routers/v1/__init__.py +22 -4
- phoenix/server/api/routers/v1/annotation_configs.py +19 -30
- phoenix/server/api/routers/v1/annotations.py +455 -13
- phoenix/server/api/routers/v1/datasets.py +355 -68
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +20 -28
- phoenix/server/api/routers/v1/experiment_evaluations.py +16 -6
- phoenix/server/api/routers/v1/experiment_runs.py +335 -59
- phoenix/server/api/routers/v1/experiments.py +475 -47
- phoenix/server/api/routers/v1/projects.py +16 -50
- phoenix/server/api/routers/v1/prompts.py +50 -39
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +156 -96
- phoenix/server/api/routers/v1/traces.py +51 -77
- phoenix/server/api/routers/v1/users.py +64 -24
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +257 -93
- 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/Dataset.py +199 -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 +215 -68
- phoenix/server/api/types/ExperimentComparison.py +3 -9
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +120 -70
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +95 -42
- phoenix/server/api/types/GenerativeProvider.py +1 -1
- phoenix/server/api/types/ModelInterface.py +7 -2
- phoenix/server/api/types/PlaygroundModel.py +12 -2
- phoenix/server/api/types/Project.py +218 -185
- phoenix/server/api/types/ProjectSession.py +146 -29
- 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/Span.py +130 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/Trace.py +184 -53
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +128 -33
- 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 +154 -36
- phoenix/server/authorization.py +5 -4
- phoenix/server/bearer_auth.py +13 -5
- phoenix/server/cost_tracking/cost_model_lookup.py +42 -14
- phoenix/server/cost_tracking/model_cost_manifest.json +1085 -194
- phoenix/server/daemons/generative_model_store.py +61 -9
- phoenix/server/daemons/span_cost_calculator.py +10 -8
- phoenix/server/dml_event.py +13 -0
- phoenix/server/email/sender.py +29 -2
- phoenix/server/grpc_server.py +9 -9
- phoenix/server/jwt_store.py +8 -6
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +9 -3
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +43 -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 +51 -53
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-BPCwGQr8.js → index-CTQoemZv.js} +42 -35
- 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-Bw30oz1A.js → vendor-recharts-V9cwpXsm.js} +7 -7
- phoenix/server/static/assets/{vendor-shiki-DZajAPeq.js → vendor-shiki-Do--csgv.js} +1 -1
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +7 -1
- phoenix/server/thread_server.py +1 -2
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +55 -1
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +8 -4
- phoenix/session/session.py +44 -8
- phoenix/settings.py +2 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/query.py +2 -0
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/static/assets/components-D0DWAf0l.js +0 -5650
- phoenix/server/static/assets/pages-Creyamao.js +0 -8612
- phoenix/server/static/assets/vendor-CU36oj8y.js +0 -905
- phoenix/server/static/assets/vendor-CqDb5u4o.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Ctgw0e1G.js +0 -168
- phoenix/server/static/assets/vendor-codemirror-Cojjzqb9.js +0 -25
- phoenix/server/static/assets/vendor-three-BLWp5bic.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-11.23.1.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,32 +1,41 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import Literal, Optional
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Literal, Optional
|
|
6
6
|
|
|
7
7
|
from fastapi import APIRouter, HTTPException, Path, Query
|
|
8
|
+
from pydantic import Field
|
|
8
9
|
from sqlalchemy import exists, select
|
|
9
10
|
from starlette.requests import Request
|
|
10
|
-
from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND, HTTP_422_UNPROCESSABLE_ENTITY
|
|
11
11
|
from strawberry.relay import GlobalID
|
|
12
12
|
|
|
13
13
|
from phoenix.db import models
|
|
14
|
+
from phoenix.db.insertion.types import Precursors
|
|
15
|
+
from phoenix.server.api.routers.v1.models import V1RoutesBaseModel
|
|
16
|
+
from phoenix.server.api.types.ProjectSessionAnnotation import (
|
|
17
|
+
ProjectSessionAnnotation as SessionAnnotationNodeType,
|
|
18
|
+
)
|
|
14
19
|
from phoenix.server.api.types.SpanAnnotation import SpanAnnotation as SpanAnnotationNodeType
|
|
20
|
+
from phoenix.server.api.types.TraceAnnotation import TraceAnnotation as TraceAnnotationNodeType
|
|
15
21
|
from phoenix.server.api.types.User import User as UserNodeType
|
|
16
22
|
|
|
17
|
-
from .spans import SpanAnnotationData, SpanAnnotationResult
|
|
18
23
|
from .utils import PaginatedResponseBody, _get_project_by_identifier, add_errors_to_responses
|
|
19
24
|
|
|
20
25
|
logger = logging.getLogger(__name__)
|
|
21
26
|
|
|
22
27
|
SPAN_ANNOTATION_NODE_NAME = SpanAnnotationNodeType.__name__
|
|
28
|
+
TRACE_ANNOTATION_NODE_NAME = TraceAnnotationNodeType.__name__
|
|
29
|
+
SESSION_ANNOTATION_NODE_NAME = SessionAnnotationNodeType.__name__
|
|
30
|
+
MAX_TRACE_IDS = 1_000
|
|
23
31
|
USER_NODE_NAME = UserNodeType.__name__
|
|
24
32
|
MAX_SPAN_IDS = 1_000
|
|
33
|
+
MAX_SESSION_IDS = 1_000
|
|
25
34
|
|
|
26
35
|
router = APIRouter(tags=["annotations"])
|
|
27
36
|
|
|
28
37
|
|
|
29
|
-
class
|
|
38
|
+
class Annotation(V1RoutesBaseModel):
|
|
30
39
|
id: str
|
|
31
40
|
created_at: datetime
|
|
32
41
|
updated_at: datetime
|
|
@@ -34,19 +43,165 @@ class SpanAnnotation(SpanAnnotationData):
|
|
|
34
43
|
user_id: Optional[str]
|
|
35
44
|
|
|
36
45
|
|
|
46
|
+
class AnnotationResult(V1RoutesBaseModel):
|
|
47
|
+
label: Optional[str] = Field(default=None, description="The label assigned by the annotation")
|
|
48
|
+
score: Optional[float] = Field(default=None, description="The score assigned by the annotation")
|
|
49
|
+
explanation: Optional[str] = Field(
|
|
50
|
+
default=None, description="Explanation of the annotation result"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AnnotationData(V1RoutesBaseModel):
|
|
55
|
+
name: str = Field(description="The name of the annotation")
|
|
56
|
+
annotator_kind: Literal["LLM", "CODE", "HUMAN"] = Field(
|
|
57
|
+
description="The kind of annotator used for the annotation"
|
|
58
|
+
)
|
|
59
|
+
result: Optional[AnnotationResult] = Field(
|
|
60
|
+
default=None, description="The result of the annotation"
|
|
61
|
+
)
|
|
62
|
+
metadata: Optional[dict[str, Any]] = Field(
|
|
63
|
+
default=None, description="Metadata for the annotation"
|
|
64
|
+
)
|
|
65
|
+
identifier: str = Field(
|
|
66
|
+
default="",
|
|
67
|
+
description=(
|
|
68
|
+
"The identifier of the annotation. "
|
|
69
|
+
"If provided, the annotation will be updated if it already exists."
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SpanAnnotationData(AnnotationData):
|
|
75
|
+
span_id: str = Field(description="OpenTelemetry Span ID (hex format w/o 0x prefix)")
|
|
76
|
+
|
|
77
|
+
def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.SpanAnnotation:
|
|
78
|
+
return Precursors.SpanAnnotation(
|
|
79
|
+
datetime.now(timezone.utc),
|
|
80
|
+
self.span_id,
|
|
81
|
+
models.SpanAnnotation(
|
|
82
|
+
name=self.name,
|
|
83
|
+
annotator_kind=self.annotator_kind,
|
|
84
|
+
score=self.result.score if self.result else None,
|
|
85
|
+
label=self.result.label if self.result else None,
|
|
86
|
+
explanation=self.result.explanation if self.result else None,
|
|
87
|
+
metadata_=self.metadata or {},
|
|
88
|
+
identifier=self.identifier,
|
|
89
|
+
source="API",
|
|
90
|
+
user_id=user_id,
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class SpanAnnotation(SpanAnnotationData, Annotation):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
37
99
|
class SpanAnnotationsResponseBody(PaginatedResponseBody[SpanAnnotation]):
|
|
38
100
|
pass
|
|
39
101
|
|
|
40
102
|
|
|
103
|
+
class SpanDocumentAnnotationData(AnnotationData):
|
|
104
|
+
span_id: str = Field(description="OpenTelemetry Span ID (hex format w/o 0x prefix)")
|
|
105
|
+
document_position: int = Field(
|
|
106
|
+
description="A 0 based index of the document. E.x. the first document during retrieval is 0"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Precursor here means a value to add to a queue for processing async
|
|
110
|
+
def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.DocumentAnnotation:
|
|
111
|
+
return Precursors.DocumentAnnotation(
|
|
112
|
+
datetime.now(timezone.utc),
|
|
113
|
+
self.span_id,
|
|
114
|
+
self.document_position,
|
|
115
|
+
models.DocumentAnnotation(
|
|
116
|
+
name=self.name,
|
|
117
|
+
annotator_kind=self.annotator_kind,
|
|
118
|
+
document_position=self.document_position,
|
|
119
|
+
score=self.result.score if self.result else None,
|
|
120
|
+
label=self.result.label if self.result else None,
|
|
121
|
+
explanation=self.result.explanation if self.result else None,
|
|
122
|
+
metadata_=self.metadata or {},
|
|
123
|
+
identifier=self.identifier,
|
|
124
|
+
source="API",
|
|
125
|
+
user_id=user_id,
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class SpanDocumentAnnotation(SpanDocumentAnnotationData, Annotation):
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SpanDocumentAnnotationsResponseBody(PaginatedResponseBody[SpanDocumentAnnotation]):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TraceAnnotationData(AnnotationData):
|
|
139
|
+
trace_id: str = Field(description="OpenTelemetry Trace ID (hex format w/o 0x prefix)")
|
|
140
|
+
|
|
141
|
+
def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.TraceAnnotation:
|
|
142
|
+
return Precursors.TraceAnnotation(
|
|
143
|
+
datetime.now(timezone.utc),
|
|
144
|
+
self.trace_id,
|
|
145
|
+
models.TraceAnnotation(
|
|
146
|
+
name=self.name,
|
|
147
|
+
annotator_kind=self.annotator_kind,
|
|
148
|
+
score=self.result.score if self.result else None,
|
|
149
|
+
label=self.result.label if self.result else None,
|
|
150
|
+
explanation=self.result.explanation if self.result else None,
|
|
151
|
+
metadata_=self.metadata or {},
|
|
152
|
+
identifier=self.identifier,
|
|
153
|
+
source="API",
|
|
154
|
+
user_id=user_id,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TraceAnnotation(TraceAnnotationData, Annotation):
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TraceAnnotationsResponseBody(PaginatedResponseBody[TraceAnnotation]):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class SessionAnnotationData(AnnotationData):
|
|
168
|
+
session_id: str = Field(description="Session ID")
|
|
169
|
+
|
|
170
|
+
def as_precursor(self, *, user_id: Optional[int] = None) -> Precursors.SessionAnnotation:
|
|
171
|
+
return Precursors.SessionAnnotation(
|
|
172
|
+
datetime.now(timezone.utc),
|
|
173
|
+
self.session_id,
|
|
174
|
+
models.ProjectSessionAnnotation(
|
|
175
|
+
name=self.name,
|
|
176
|
+
annotator_kind=self.annotator_kind,
|
|
177
|
+
score=self.result.score if self.result else None,
|
|
178
|
+
label=self.result.label if self.result else None,
|
|
179
|
+
explanation=self.result.explanation if self.result else None,
|
|
180
|
+
metadata_=self.metadata or {},
|
|
181
|
+
identifier=self.identifier,
|
|
182
|
+
source="API",
|
|
183
|
+
user_id=user_id,
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SessionAnnotation(SessionAnnotationData, Annotation):
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class SessionAnnotationsResponseBody(PaginatedResponseBody[SessionAnnotation]):
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
|
|
41
196
|
@router.get(
|
|
42
197
|
"/projects/{project_identifier}/span_annotations",
|
|
43
198
|
operation_id="listSpanAnnotationsBySpanIds",
|
|
44
199
|
summary="Get span annotations for a list of span_ids.",
|
|
45
|
-
status_code=
|
|
200
|
+
status_code=200,
|
|
46
201
|
responses=add_errors_to_responses(
|
|
47
202
|
[
|
|
48
|
-
{"status_code":
|
|
49
|
-
{"status_code":
|
|
203
|
+
{"status_code": 404, "description": "Project or spans not found"},
|
|
204
|
+
{"status_code": 422, "description": "Invalid parameters"},
|
|
50
205
|
]
|
|
51
206
|
),
|
|
52
207
|
)
|
|
@@ -84,7 +239,7 @@ async def list_span_annotations(
|
|
|
84
239
|
span_ids = list({*span_ids})
|
|
85
240
|
if len(span_ids) > MAX_SPAN_IDS:
|
|
86
241
|
raise HTTPException(
|
|
87
|
-
status_code=
|
|
242
|
+
status_code=422,
|
|
88
243
|
detail=f"Too many span_ids supplied: {len(span_ids)} (max {MAX_SPAN_IDS})",
|
|
89
244
|
)
|
|
90
245
|
|
|
@@ -92,7 +247,7 @@ async def list_span_annotations(
|
|
|
92
247
|
project = await _get_project_by_identifier(session, project_identifier)
|
|
93
248
|
if not project:
|
|
94
249
|
raise HTTPException(
|
|
95
|
-
status_code=
|
|
250
|
+
status_code=404,
|
|
96
251
|
detail=f"Project with identifier {project_identifier} not found",
|
|
97
252
|
)
|
|
98
253
|
|
|
@@ -124,7 +279,7 @@ async def list_span_annotations(
|
|
|
124
279
|
cursor_id = int(GlobalID.from_id(cursor).node_id)
|
|
125
280
|
except ValueError:
|
|
126
281
|
raise HTTPException(
|
|
127
|
-
status_code=
|
|
282
|
+
status_code=422,
|
|
128
283
|
detail="Invalid cursor value",
|
|
129
284
|
)
|
|
130
285
|
stmt = stmt.where(models.SpanAnnotation.id <= cursor_id)
|
|
@@ -154,7 +309,7 @@ async def list_span_annotations(
|
|
|
154
309
|
if not spans_exist:
|
|
155
310
|
raise HTTPException(
|
|
156
311
|
detail="None of the supplied span_ids exist in this project",
|
|
157
|
-
status_code=
|
|
312
|
+
status_code=404,
|
|
158
313
|
)
|
|
159
314
|
|
|
160
315
|
return SpanAnnotationsResponseBody(data=[], next_cursor=None)
|
|
@@ -164,7 +319,7 @@ async def list_span_annotations(
|
|
|
164
319
|
id=str(GlobalID(SPAN_ANNOTATION_NODE_NAME, str(anno.id))),
|
|
165
320
|
span_id=span_id,
|
|
166
321
|
name=anno.name,
|
|
167
|
-
result=
|
|
322
|
+
result=AnnotationResult(
|
|
168
323
|
label=anno.label,
|
|
169
324
|
score=anno.score,
|
|
170
325
|
explanation=anno.explanation,
|
|
@@ -181,3 +336,290 @@ async def list_span_annotations(
|
|
|
181
336
|
]
|
|
182
337
|
|
|
183
338
|
return SpanAnnotationsResponseBody(data=data, next_cursor=next_cursor)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@router.get(
|
|
342
|
+
"/projects/{project_identifier}/trace_annotations",
|
|
343
|
+
operation_id="listTraceAnnotationsByTraceIds",
|
|
344
|
+
summary="Get trace annotations for a list of trace_ids.",
|
|
345
|
+
status_code=200,
|
|
346
|
+
responses=add_errors_to_responses(
|
|
347
|
+
[
|
|
348
|
+
{"status_code": 404, "description": "Project or traces not found"},
|
|
349
|
+
{"status_code": 422, "description": "Invalid parameters"},
|
|
350
|
+
]
|
|
351
|
+
),
|
|
352
|
+
)
|
|
353
|
+
async def list_trace_annotations(
|
|
354
|
+
request: Request,
|
|
355
|
+
project_identifier: str = Path(
|
|
356
|
+
description=(
|
|
357
|
+
"The project identifier: either project ID or project name. If using a project name as "
|
|
358
|
+
"the identifier, it cannot contain slash (/), question mark (?), or pound sign (#) "
|
|
359
|
+
"characters."
|
|
360
|
+
)
|
|
361
|
+
),
|
|
362
|
+
trace_ids: list[str] = Query(
|
|
363
|
+
..., min_length=1, description="One or more trace id to fetch annotations for"
|
|
364
|
+
),
|
|
365
|
+
include_annotation_names: Optional[list[str]] = Query(
|
|
366
|
+
default=None,
|
|
367
|
+
description=(
|
|
368
|
+
"Optional list of annotation names to include. If provided, only annotations with "
|
|
369
|
+
"these names will be returned. 'note' annotations are excluded by default unless "
|
|
370
|
+
"explicitly included in this list."
|
|
371
|
+
),
|
|
372
|
+
),
|
|
373
|
+
exclude_annotation_names: Optional[list[str]] = Query(
|
|
374
|
+
default=None, description="Optional list of annotation names to exclude from results."
|
|
375
|
+
),
|
|
376
|
+
cursor: Optional[str] = Query(default=None, description="A cursor for pagination"),
|
|
377
|
+
limit: int = Query(
|
|
378
|
+
default=10,
|
|
379
|
+
gt=0,
|
|
380
|
+
le=10000,
|
|
381
|
+
description="The maximum number of annotations to return in a single request",
|
|
382
|
+
),
|
|
383
|
+
) -> TraceAnnotationsResponseBody:
|
|
384
|
+
trace_ids = list({*trace_ids})
|
|
385
|
+
if len(trace_ids) > MAX_TRACE_IDS:
|
|
386
|
+
raise HTTPException(
|
|
387
|
+
status_code=422,
|
|
388
|
+
detail=f"Too many trace_ids supplied: {len(trace_ids)} (max {MAX_TRACE_IDS})",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
async with request.app.state.db() as session:
|
|
392
|
+
project = await _get_project_by_identifier(session, project_identifier)
|
|
393
|
+
if not project:
|
|
394
|
+
raise HTTPException(
|
|
395
|
+
status_code=404,
|
|
396
|
+
detail=f"Project with identifier {project_identifier} not found",
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Build the base query
|
|
400
|
+
where_conditions = [
|
|
401
|
+
models.Project.id == project.id,
|
|
402
|
+
models.Trace.trace_id.in_(trace_ids),
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
# Add annotation name filtering
|
|
406
|
+
if include_annotation_names:
|
|
407
|
+
where_conditions.append(models.TraceAnnotation.name.in_(include_annotation_names))
|
|
408
|
+
|
|
409
|
+
if exclude_annotation_names:
|
|
410
|
+
where_conditions.append(models.TraceAnnotation.name.not_in(exclude_annotation_names))
|
|
411
|
+
|
|
412
|
+
stmt = (
|
|
413
|
+
select(models.Trace.trace_id, models.TraceAnnotation)
|
|
414
|
+
.join(models.Project, models.Trace.project_rowid == models.Project.id)
|
|
415
|
+
.join(models.TraceAnnotation, models.TraceAnnotation.trace_rowid == models.Trace.id)
|
|
416
|
+
.where(*where_conditions)
|
|
417
|
+
.order_by(models.TraceAnnotation.id.desc())
|
|
418
|
+
.limit(limit + 1)
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if cursor:
|
|
422
|
+
try:
|
|
423
|
+
cursor_id = int(GlobalID.from_id(cursor).node_id)
|
|
424
|
+
except ValueError:
|
|
425
|
+
raise HTTPException(
|
|
426
|
+
status_code=422,
|
|
427
|
+
detail="Invalid cursor value",
|
|
428
|
+
)
|
|
429
|
+
stmt = stmt.where(models.TraceAnnotation.id <= cursor_id)
|
|
430
|
+
|
|
431
|
+
rows: list[tuple[str, models.TraceAnnotation]] = [
|
|
432
|
+
r async for r in (await session.stream(stmt))
|
|
433
|
+
]
|
|
434
|
+
|
|
435
|
+
next_cursor: Optional[str] = None
|
|
436
|
+
if len(rows) == limit + 1:
|
|
437
|
+
*rows, extra = rows
|
|
438
|
+
next_cursor = str(GlobalID(TRACE_ANNOTATION_NODE_NAME, str(extra[1].id)))
|
|
439
|
+
|
|
440
|
+
if not rows:
|
|
441
|
+
traces_exist = await session.scalar(
|
|
442
|
+
select(
|
|
443
|
+
exists().where(
|
|
444
|
+
models.Trace.trace_id.in_(trace_ids),
|
|
445
|
+
models.Trace.project_rowid == project.id,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
if not traces_exist:
|
|
450
|
+
raise HTTPException(
|
|
451
|
+
detail="None of the supplied trace_ids exist in this project",
|
|
452
|
+
status_code=404,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
return TraceAnnotationsResponseBody(data=[], next_cursor=None)
|
|
456
|
+
|
|
457
|
+
data = [
|
|
458
|
+
TraceAnnotation(
|
|
459
|
+
id=str(GlobalID(TRACE_ANNOTATION_NODE_NAME, str(anno.id))),
|
|
460
|
+
trace_id=trace_id,
|
|
461
|
+
name=anno.name,
|
|
462
|
+
result=AnnotationResult(
|
|
463
|
+
label=anno.label,
|
|
464
|
+
score=anno.score,
|
|
465
|
+
explanation=anno.explanation,
|
|
466
|
+
),
|
|
467
|
+
metadata=anno.metadata_,
|
|
468
|
+
annotator_kind=anno.annotator_kind,
|
|
469
|
+
created_at=anno.created_at,
|
|
470
|
+
updated_at=anno.updated_at,
|
|
471
|
+
identifier=anno.identifier,
|
|
472
|
+
source=anno.source,
|
|
473
|
+
user_id=str(GlobalID("User", str(anno.user_id))) if anno.user_id else None,
|
|
474
|
+
)
|
|
475
|
+
for trace_id, anno in rows
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
return TraceAnnotationsResponseBody(data=data, next_cursor=next_cursor)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@router.get(
|
|
482
|
+
"/projects/{project_identifier}/session_annotations",
|
|
483
|
+
operation_id="listSessionAnnotationsBySessionIds",
|
|
484
|
+
summary="Get session annotations for a list of session_ids.",
|
|
485
|
+
status_code=200,
|
|
486
|
+
responses=add_errors_to_responses(
|
|
487
|
+
[
|
|
488
|
+
{"status_code": 404, "description": "Project or sessions not found"},
|
|
489
|
+
{"status_code": 422, "description": "Invalid parameters"},
|
|
490
|
+
]
|
|
491
|
+
),
|
|
492
|
+
)
|
|
493
|
+
async def list_session_annotations(
|
|
494
|
+
request: Request,
|
|
495
|
+
project_identifier: str = Path(
|
|
496
|
+
description=(
|
|
497
|
+
"The project identifier: either project ID or project name. If using a project name as "
|
|
498
|
+
"the identifier, it cannot contain slash (/), question mark (?), or pound sign (#) "
|
|
499
|
+
"characters."
|
|
500
|
+
)
|
|
501
|
+
),
|
|
502
|
+
session_ids: list[str] = Query(
|
|
503
|
+
..., min_length=1, description="One or more session id to fetch annotations for"
|
|
504
|
+
),
|
|
505
|
+
include_annotation_names: Optional[list[str]] = Query(
|
|
506
|
+
default=None,
|
|
507
|
+
description=(
|
|
508
|
+
"Optional list of annotation names to include. If provided, only annotations with "
|
|
509
|
+
"these names will be returned. 'note' annotations are excluded by default unless "
|
|
510
|
+
"explicitly included in this list."
|
|
511
|
+
),
|
|
512
|
+
),
|
|
513
|
+
exclude_annotation_names: Optional[list[str]] = Query(
|
|
514
|
+
default=None, description="Optional list of annotation names to exclude from results."
|
|
515
|
+
),
|
|
516
|
+
cursor: Optional[str] = Query(default=None, description="A cursor for pagination"),
|
|
517
|
+
limit: int = Query(
|
|
518
|
+
default=10,
|
|
519
|
+
gt=0,
|
|
520
|
+
le=10000,
|
|
521
|
+
description="The maximum number of annotations to return in a single request",
|
|
522
|
+
),
|
|
523
|
+
) -> SessionAnnotationsResponseBody:
|
|
524
|
+
session_ids = list({*session_ids})
|
|
525
|
+
if len(session_ids) > MAX_SESSION_IDS:
|
|
526
|
+
raise HTTPException(
|
|
527
|
+
status_code=422,
|
|
528
|
+
detail=f"Too many session_ids supplied: {len(session_ids)} (max {MAX_SESSION_IDS})",
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
async with request.app.state.db() as session:
|
|
532
|
+
project = await _get_project_by_identifier(session, project_identifier)
|
|
533
|
+
if not project:
|
|
534
|
+
raise HTTPException(
|
|
535
|
+
status_code=404,
|
|
536
|
+
detail=f"Project with identifier {project_identifier} not found",
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
# Build the base query
|
|
540
|
+
where_conditions = [
|
|
541
|
+
models.Project.id == project.id,
|
|
542
|
+
models.ProjectSession.session_id.in_(session_ids),
|
|
543
|
+
]
|
|
544
|
+
|
|
545
|
+
# Add annotation name filtering
|
|
546
|
+
if include_annotation_names:
|
|
547
|
+
where_conditions.append(
|
|
548
|
+
models.ProjectSessionAnnotation.name.in_(include_annotation_names)
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
if exclude_annotation_names:
|
|
552
|
+
where_conditions.append(
|
|
553
|
+
models.ProjectSessionAnnotation.name.not_in(exclude_annotation_names)
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
stmt = (
|
|
557
|
+
select(models.ProjectSession.session_id, models.ProjectSessionAnnotation)
|
|
558
|
+
.join(models.Project, models.ProjectSession.project_id == models.Project.id)
|
|
559
|
+
.join(
|
|
560
|
+
models.ProjectSessionAnnotation,
|
|
561
|
+
models.ProjectSessionAnnotation.project_session_id == models.ProjectSession.id,
|
|
562
|
+
)
|
|
563
|
+
.where(*where_conditions)
|
|
564
|
+
.order_by(models.ProjectSessionAnnotation.id.desc())
|
|
565
|
+
.limit(limit + 1)
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
if cursor:
|
|
569
|
+
try:
|
|
570
|
+
cursor_id = int(GlobalID.from_id(cursor).node_id)
|
|
571
|
+
except ValueError:
|
|
572
|
+
raise HTTPException(
|
|
573
|
+
status_code=422,
|
|
574
|
+
detail="Invalid cursor value",
|
|
575
|
+
)
|
|
576
|
+
stmt = stmt.where(models.ProjectSessionAnnotation.id <= cursor_id)
|
|
577
|
+
|
|
578
|
+
rows: list[tuple[str, models.ProjectSessionAnnotation]] = [
|
|
579
|
+
r async for r in (await session.stream(stmt))
|
|
580
|
+
]
|
|
581
|
+
|
|
582
|
+
next_cursor: Optional[str] = None
|
|
583
|
+
if len(rows) == limit + 1:
|
|
584
|
+
*rows, extra = rows
|
|
585
|
+
next_cursor = str(GlobalID(SESSION_ANNOTATION_NODE_NAME, str(extra[1].id)))
|
|
586
|
+
|
|
587
|
+
if not rows:
|
|
588
|
+
sessions_exist = await session.scalar(
|
|
589
|
+
select(
|
|
590
|
+
exists().where(
|
|
591
|
+
models.ProjectSession.session_id.in_(session_ids),
|
|
592
|
+
models.ProjectSession.project_id == project.id,
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
)
|
|
596
|
+
if not sessions_exist:
|
|
597
|
+
raise HTTPException(
|
|
598
|
+
detail="None of the supplied session_ids exist in this project",
|
|
599
|
+
status_code=404,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
return SessionAnnotationsResponseBody(data=[], next_cursor=None)
|
|
603
|
+
|
|
604
|
+
data = [
|
|
605
|
+
SessionAnnotation(
|
|
606
|
+
id=str(GlobalID(SESSION_ANNOTATION_NODE_NAME, str(anno.id))),
|
|
607
|
+
session_id=session_id,
|
|
608
|
+
name=anno.name,
|
|
609
|
+
result=AnnotationResult(
|
|
610
|
+
label=anno.label,
|
|
611
|
+
score=anno.score,
|
|
612
|
+
explanation=anno.explanation,
|
|
613
|
+
),
|
|
614
|
+
metadata=anno.metadata_,
|
|
615
|
+
annotator_kind=anno.annotator_kind,
|
|
616
|
+
created_at=anno.created_at,
|
|
617
|
+
updated_at=anno.updated_at,
|
|
618
|
+
identifier=anno.identifier,
|
|
619
|
+
source=anno.source,
|
|
620
|
+
user_id=str(GlobalID(USER_NODE_NAME, str(anno.user_id))) if anno.user_id else None,
|
|
621
|
+
)
|
|
622
|
+
for session_id, anno in rows
|
|
623
|
+
]
|
|
624
|
+
|
|
625
|
+
return SessionAnnotationsResponseBody(data=data, next_cursor=next_cursor)
|