arize-phoenix 7.12.2__py3-none-any.whl → 8.0.0__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.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/METADATA +31 -28
- {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/RECORD +71 -48
- phoenix/config.py +61 -36
- phoenix/db/migrations/versions/bc8fea3c2bc8_add_prompt_tables.py +197 -0
- phoenix/db/models.py +307 -0
- phoenix/db/types/__init__.py +0 -0
- phoenix/db/types/identifier.py +7 -0
- phoenix/db/types/model_provider.py +8 -0
- phoenix/server/api/context.py +2 -0
- phoenix/server/api/dataloaders/__init__.py +2 -0
- phoenix/server/api/dataloaders/prompt_version_sequence_number.py +35 -0
- phoenix/server/api/helpers/jsonschema.py +135 -0
- phoenix/server/api/helpers/playground_clients.py +23 -27
- phoenix/server/api/helpers/playground_spans.py +9 -0
- phoenix/server/api/helpers/prompts/__init__.py +0 -0
- phoenix/server/api/helpers/prompts/conversions/__init__.py +0 -0
- phoenix/server/api/helpers/prompts/conversions/anthropic.py +87 -0
- phoenix/server/api/helpers/prompts/conversions/openai.py +78 -0
- phoenix/server/api/helpers/prompts/models.py +575 -0
- phoenix/server/api/input_types/ChatCompletionInput.py +9 -4
- phoenix/server/api/input_types/PromptTemplateOptions.py +10 -0
- phoenix/server/api/input_types/PromptVersionInput.py +133 -0
- phoenix/server/api/mutations/__init__.py +6 -0
- phoenix/server/api/mutations/chat_mutations.py +18 -16
- phoenix/server/api/mutations/prompt_label_mutations.py +191 -0
- phoenix/server/api/mutations/prompt_mutations.py +312 -0
- phoenix/server/api/mutations/prompt_version_tag_mutations.py +148 -0
- phoenix/server/api/mutations/user_mutations.py +7 -6
- phoenix/server/api/openapi/schema.py +1 -0
- phoenix/server/api/queries.py +84 -31
- phoenix/server/api/routers/oauth2.py +3 -2
- phoenix/server/api/routers/v1/__init__.py +2 -0
- phoenix/server/api/routers/v1/datasets.py +1 -1
- phoenix/server/api/routers/v1/experiment_evaluations.py +1 -1
- phoenix/server/api/routers/v1/experiment_runs.py +1 -1
- phoenix/server/api/routers/v1/experiments.py +1 -1
- phoenix/server/api/routers/v1/models.py +45 -0
- phoenix/server/api/routers/v1/prompts.py +412 -0
- phoenix/server/api/routers/v1/spans.py +1 -1
- phoenix/server/api/routers/v1/traces.py +1 -1
- phoenix/server/api/routers/v1/utils.py +1 -1
- phoenix/server/api/subscriptions.py +21 -24
- phoenix/server/api/types/GenerativeProvider.py +6 -6
- phoenix/server/api/types/Identifier.py +15 -0
- phoenix/server/api/types/Project.py +5 -7
- phoenix/server/api/types/Prompt.py +134 -0
- phoenix/server/api/types/PromptLabel.py +41 -0
- phoenix/server/api/types/PromptVersion.py +148 -0
- phoenix/server/api/types/PromptVersionTag.py +27 -0
- phoenix/server/api/types/PromptVersionTemplate.py +148 -0
- phoenix/server/api/types/ResponseFormat.py +9 -0
- phoenix/server/api/types/ToolDefinition.py +9 -0
- phoenix/server/app.py +3 -0
- phoenix/server/static/.vite/manifest.json +45 -45
- phoenix/server/static/assets/components-B-qgPyHv.js +2699 -0
- phoenix/server/static/assets/index-D4KO1IcF.js +1125 -0
- phoenix/server/static/assets/pages-DdcuL3Rh.js +5634 -0
- phoenix/server/static/assets/vendor-DQp7CrDA.js +894 -0
- phoenix/server/static/assets/vendor-arizeai-C1nEIEQq.js +657 -0
- phoenix/server/static/assets/vendor-codemirror-BZXYUIkP.js +24 -0
- phoenix/server/static/assets/vendor-recharts-BUFpwCVD.js +59 -0
- phoenix/server/static/assets/{vendor-shiki-Cl9QBraO.js → vendor-shiki-C8L-c9jT.js} +2 -2
- phoenix/server/static/assets/{vendor-three-DwGkEfCM.js → vendor-three-C-AGeJYv.js} +1 -1
- phoenix/session/client.py +25 -21
- phoenix/utilities/client.py +6 -0
- phoenix/version.py +1 -1
- phoenix/server/api/input_types/TemplateOptions.py +0 -10
- phoenix/server/api/routers/v1/pydantic_compat.py +0 -78
- phoenix/server/api/types/TemplateLanguage.py +0 -10
- phoenix/server/static/assets/components-DckIzNmE.js +0 -2125
- phoenix/server/static/assets/index-Bf25Ogon.js +0 -113
- phoenix/server/static/assets/pages-DL7J9q9w.js +0 -4463
- phoenix/server/static/assets/vendor-DvC8cT4X.js +0 -894
- phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +0 -662
- phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +0 -24
- phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +0 -59
- {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/licenses/LICENSE +0 -0
- /phoenix/server/static/assets/{vendor-DxkFTwjz.css → vendor-Cg6lcjUC.css} +0 -0
phoenix/db/models.py
CHANGED
|
@@ -13,6 +13,7 @@ from sqlalchemy import (
|
|
|
13
13
|
ForeignKey,
|
|
14
14
|
Index,
|
|
15
15
|
MetaData,
|
|
16
|
+
Null,
|
|
16
17
|
String,
|
|
17
18
|
TypeDecorator,
|
|
18
19
|
UniqueConstraint,
|
|
@@ -38,6 +39,21 @@ from sqlalchemy.sql import expression
|
|
|
38
39
|
|
|
39
40
|
from phoenix.config import get_env_database_schema
|
|
40
41
|
from phoenix.datetime_utils import normalize_datetime
|
|
42
|
+
from phoenix.db.types.identifier import Identifier
|
|
43
|
+
from phoenix.db.types.model_provider import ModelProvider
|
|
44
|
+
from phoenix.server.api.helpers.prompts.models import (
|
|
45
|
+
PromptInvocationParameters,
|
|
46
|
+
PromptInvocationParametersRootModel,
|
|
47
|
+
PromptResponseFormat,
|
|
48
|
+
PromptResponseFormatRootModel,
|
|
49
|
+
PromptTemplate,
|
|
50
|
+
PromptTemplateFormat,
|
|
51
|
+
PromptTemplateRootModel,
|
|
52
|
+
PromptTemplateType,
|
|
53
|
+
PromptTools,
|
|
54
|
+
is_prompt_invocation_parameters,
|
|
55
|
+
is_prompt_template,
|
|
56
|
+
)
|
|
41
57
|
|
|
42
58
|
|
|
43
59
|
class AuthMethod(Enum):
|
|
@@ -99,6 +115,139 @@ class UtcTimeStamp(TypeDecorator[datetime]):
|
|
|
99
115
|
return normalize_datetime(value, timezone.utc)
|
|
100
116
|
|
|
101
117
|
|
|
118
|
+
class _Identifier(TypeDecorator[Identifier]):
|
|
119
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
120
|
+
cache_ok = True
|
|
121
|
+
impl = String
|
|
122
|
+
|
|
123
|
+
def process_bind_param(self, value: Optional[Identifier], _: Dialect) -> Optional[str]:
|
|
124
|
+
assert isinstance(value, Identifier) or value is None
|
|
125
|
+
return None if value is None else value.root
|
|
126
|
+
|
|
127
|
+
def process_result_value(self, value: Optional[str], _: Dialect) -> Optional[Identifier]:
|
|
128
|
+
return None if value is None else Identifier.model_validate(value)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class _ModelProvider(TypeDecorator[ModelProvider]):
|
|
132
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
133
|
+
cache_ok = True
|
|
134
|
+
impl = String
|
|
135
|
+
|
|
136
|
+
def process_bind_param(self, value: Optional[ModelProvider], _: Dialect) -> Optional[str]:
|
|
137
|
+
if isinstance(value, str):
|
|
138
|
+
return ModelProvider(value).value
|
|
139
|
+
return None if value is None else value.value
|
|
140
|
+
|
|
141
|
+
def process_result_value(self, value: Optional[str], _: Dialect) -> Optional[ModelProvider]:
|
|
142
|
+
return None if value is None else ModelProvider(value)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class _InvocationParameters(TypeDecorator[PromptInvocationParameters]):
|
|
146
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
147
|
+
cache_ok = True
|
|
148
|
+
impl = JSON_
|
|
149
|
+
|
|
150
|
+
def process_bind_param(
|
|
151
|
+
self, value: Optional[PromptInvocationParameters], _: Dialect
|
|
152
|
+
) -> Optional[dict[str, Any]]:
|
|
153
|
+
assert is_prompt_invocation_parameters(value)
|
|
154
|
+
invocation_parameters = value.model_dump()
|
|
155
|
+
assert isinstance(invocation_parameters, dict)
|
|
156
|
+
return invocation_parameters
|
|
157
|
+
|
|
158
|
+
def process_result_value(
|
|
159
|
+
self, value: Optional[dict[str, Any]], _: Dialect
|
|
160
|
+
) -> Optional[PromptInvocationParameters]:
|
|
161
|
+
assert isinstance(value, dict)
|
|
162
|
+
return PromptInvocationParametersRootModel.model_validate(value).root
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class _PromptTemplate(TypeDecorator[PromptTemplate]):
|
|
166
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
167
|
+
cache_ok = True
|
|
168
|
+
impl = JSON_
|
|
169
|
+
|
|
170
|
+
def process_bind_param(
|
|
171
|
+
self, value: Optional[PromptTemplate], _: Dialect
|
|
172
|
+
) -> Optional[dict[str, Any]]:
|
|
173
|
+
assert is_prompt_template(value)
|
|
174
|
+
return value.model_dump() if value is not None else None
|
|
175
|
+
|
|
176
|
+
def process_result_value(
|
|
177
|
+
self, value: Optional[dict[str, Any]], _: Dialect
|
|
178
|
+
) -> Optional[PromptTemplate]:
|
|
179
|
+
assert isinstance(value, dict)
|
|
180
|
+
return PromptTemplateRootModel.model_validate(value).root
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class _Tools(TypeDecorator[PromptTools]):
|
|
184
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
185
|
+
cache_ok = True
|
|
186
|
+
impl = JSON_
|
|
187
|
+
|
|
188
|
+
def process_bind_param(
|
|
189
|
+
self, value: Optional[PromptTools], _: Dialect
|
|
190
|
+
) -> Optional[dict[str, Any]]:
|
|
191
|
+
return value.model_dump() if value is not None else None
|
|
192
|
+
|
|
193
|
+
def process_result_value(
|
|
194
|
+
self, value: Optional[dict[str, Any]], _: Dialect
|
|
195
|
+
) -> Optional[PromptTools]:
|
|
196
|
+
return PromptTools.model_validate(value) if value is not None else None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class _PromptResponseFormat(TypeDecorator[PromptResponseFormat]):
|
|
200
|
+
# See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
201
|
+
cache_ok = True
|
|
202
|
+
impl = JSON_
|
|
203
|
+
|
|
204
|
+
def process_bind_param(
|
|
205
|
+
self, value: Optional[PromptResponseFormat], _: Dialect
|
|
206
|
+
) -> Optional[dict[str, Any]]:
|
|
207
|
+
return value.model_dump() if value is not None else None
|
|
208
|
+
|
|
209
|
+
def process_result_value(
|
|
210
|
+
self, value: Optional[dict[str, Any]], _: Dialect
|
|
211
|
+
) -> Optional[PromptResponseFormat]:
|
|
212
|
+
return (
|
|
213
|
+
PromptResponseFormatRootModel.model_validate(value).root if value is not None else None
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class _PromptTemplateType(TypeDecorator[PromptTemplateType]):
|
|
218
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
219
|
+
cache_ok = True
|
|
220
|
+
impl = String
|
|
221
|
+
|
|
222
|
+
def process_bind_param(self, value: Optional[PromptTemplateType], _: Dialect) -> Optional[str]:
|
|
223
|
+
if isinstance(value, str):
|
|
224
|
+
return PromptTemplateType(value).value
|
|
225
|
+
return None if value is None else value.value
|
|
226
|
+
|
|
227
|
+
def process_result_value(
|
|
228
|
+
self, value: Optional[str], _: Dialect
|
|
229
|
+
) -> Optional[PromptTemplateType]:
|
|
230
|
+
return None if value is None else PromptTemplateType(value)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class _TemplateFormat(TypeDecorator[PromptTemplateFormat]):
|
|
234
|
+
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
235
|
+
cache_ok = True
|
|
236
|
+
impl = String
|
|
237
|
+
|
|
238
|
+
def process_bind_param(
|
|
239
|
+
self, value: Optional[PromptTemplateFormat], _: Dialect
|
|
240
|
+
) -> Optional[str]:
|
|
241
|
+
if isinstance(value, str):
|
|
242
|
+
return PromptTemplateFormat(value).value
|
|
243
|
+
return None if value is None else value.value
|
|
244
|
+
|
|
245
|
+
def process_result_value(
|
|
246
|
+
self, value: Optional[str], _: Dialect
|
|
247
|
+
) -> Optional[PromptTemplateFormat]:
|
|
248
|
+
return None if value is None else PromptTemplateFormat(value)
|
|
249
|
+
|
|
250
|
+
|
|
102
251
|
class ExperimentRunOutput(TypedDict, total=False):
|
|
103
252
|
task_output: Any
|
|
104
253
|
|
|
@@ -805,3 +954,161 @@ class ApiKey(Base):
|
|
|
805
954
|
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
806
955
|
expires_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp, nullable=True, index=True)
|
|
807
956
|
__table_args__ = (dict(sqlite_autoincrement=True),)
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
class PromptLabel(Base):
|
|
960
|
+
__tablename__ = "prompt_labels"
|
|
961
|
+
|
|
962
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
963
|
+
name: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
|
|
964
|
+
description: Mapped[Optional[str]]
|
|
965
|
+
color: Mapped[str] = mapped_column(String, nullable=True)
|
|
966
|
+
|
|
967
|
+
prompts_prompt_labels: Mapped[list["PromptPromptLabel"]] = relationship(
|
|
968
|
+
"PromptPromptLabel",
|
|
969
|
+
back_populates="prompt_label",
|
|
970
|
+
cascade="all, delete-orphan",
|
|
971
|
+
uselist=True,
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
class Prompt(Base):
|
|
976
|
+
__tablename__ = "prompts"
|
|
977
|
+
|
|
978
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
979
|
+
source_prompt_id: Mapped[Optional[int]] = mapped_column(
|
|
980
|
+
ForeignKey("prompts.id", ondelete="SET NULL"),
|
|
981
|
+
index=True,
|
|
982
|
+
nullable=True,
|
|
983
|
+
)
|
|
984
|
+
name: Mapped[Identifier] = mapped_column(_Identifier, unique=True, index=True, nullable=False)
|
|
985
|
+
description: Mapped[Optional[str]]
|
|
986
|
+
metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
|
|
987
|
+
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
988
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
989
|
+
UtcTimeStamp, server_default=func.now(), onupdate=func.now()
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
prompts_prompt_labels: Mapped[list["PromptPromptLabel"]] = relationship(
|
|
993
|
+
"PromptPromptLabel",
|
|
994
|
+
back_populates="prompt",
|
|
995
|
+
cascade="all, delete-orphan",
|
|
996
|
+
uselist=True,
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
prompt_versions: Mapped[list["PromptVersion"]] = relationship(
|
|
1000
|
+
"PromptVersion",
|
|
1001
|
+
back_populates="prompt",
|
|
1002
|
+
cascade="all, delete-orphan",
|
|
1003
|
+
uselist=True,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
prompt_version_tags: Mapped[list["PromptVersionTag"]] = relationship(
|
|
1007
|
+
"PromptVersionTag",
|
|
1008
|
+
back_populates="prompt",
|
|
1009
|
+
cascade="all, delete-orphan",
|
|
1010
|
+
uselist=True,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
class PromptPromptLabel(Base):
|
|
1015
|
+
__tablename__ = "prompts_prompt_labels"
|
|
1016
|
+
|
|
1017
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
1018
|
+
prompt_label_id: Mapped[int] = mapped_column(
|
|
1019
|
+
ForeignKey("prompt_labels.id", ondelete="CASCADE"),
|
|
1020
|
+
index=True,
|
|
1021
|
+
nullable=False,
|
|
1022
|
+
)
|
|
1023
|
+
prompt_id: Mapped[int] = mapped_column(
|
|
1024
|
+
ForeignKey("prompts.id", ondelete="CASCADE"),
|
|
1025
|
+
index=True,
|
|
1026
|
+
nullable=False,
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
prompt_label: Mapped["PromptLabel"] = relationship(
|
|
1030
|
+
"PromptLabel", back_populates="prompts_prompt_labels"
|
|
1031
|
+
)
|
|
1032
|
+
prompt: Mapped["Prompt"] = relationship("Prompt", back_populates="prompts_prompt_labels")
|
|
1033
|
+
|
|
1034
|
+
__table_args__ = (UniqueConstraint("prompt_label_id", "prompt_id"),)
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
class PromptVersion(Base):
|
|
1038
|
+
__tablename__ = "prompt_versions"
|
|
1039
|
+
|
|
1040
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
1041
|
+
prompt_id: Mapped[int] = mapped_column(
|
|
1042
|
+
ForeignKey("prompts.id", ondelete="CASCADE"),
|
|
1043
|
+
index=True,
|
|
1044
|
+
nullable=False,
|
|
1045
|
+
)
|
|
1046
|
+
description: Mapped[Optional[str]] = mapped_column(String, nullable=True)
|
|
1047
|
+
user_id: Mapped[Optional[int]] = mapped_column(
|
|
1048
|
+
ForeignKey("users.id", ondelete="SET NULL"),
|
|
1049
|
+
index=True,
|
|
1050
|
+
nullable=True,
|
|
1051
|
+
)
|
|
1052
|
+
template_type: Mapped[PromptTemplateType] = mapped_column(
|
|
1053
|
+
_PromptTemplateType,
|
|
1054
|
+
CheckConstraint("template_type IN ('CHAT', 'STR')", name="template_type"),
|
|
1055
|
+
nullable=False,
|
|
1056
|
+
)
|
|
1057
|
+
template_format: Mapped[PromptTemplateFormat] = mapped_column(
|
|
1058
|
+
_TemplateFormat,
|
|
1059
|
+
CheckConstraint(
|
|
1060
|
+
"template_format IN ('F_STRING', 'MUSTACHE', 'NONE')", name="template_format"
|
|
1061
|
+
),
|
|
1062
|
+
nullable=False,
|
|
1063
|
+
)
|
|
1064
|
+
template: Mapped[PromptTemplate] = mapped_column(_PromptTemplate, nullable=False)
|
|
1065
|
+
invocation_parameters: Mapped[PromptInvocationParameters] = mapped_column(
|
|
1066
|
+
_InvocationParameters, nullable=False
|
|
1067
|
+
)
|
|
1068
|
+
tools: Mapped[Optional[PromptTools]] = mapped_column(_Tools, default=Null(), nullable=True)
|
|
1069
|
+
response_format: Mapped[Optional[PromptResponseFormat]] = mapped_column(
|
|
1070
|
+
_PromptResponseFormat, default=Null(), nullable=True
|
|
1071
|
+
)
|
|
1072
|
+
model_provider: Mapped[ModelProvider] = mapped_column(_ModelProvider)
|
|
1073
|
+
model_name: Mapped[str]
|
|
1074
|
+
metadata_: Mapped[dict[str, Any]] = mapped_column("metadata")
|
|
1075
|
+
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
1076
|
+
|
|
1077
|
+
prompt: Mapped["Prompt"] = relationship("Prompt", back_populates="prompt_versions")
|
|
1078
|
+
|
|
1079
|
+
prompt_version_tags: Mapped[list["PromptVersionTag"]] = relationship(
|
|
1080
|
+
"PromptVersionTag",
|
|
1081
|
+
back_populates="prompt_version",
|
|
1082
|
+
cascade="all, delete-orphan",
|
|
1083
|
+
uselist=True,
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
class PromptVersionTag(Base):
|
|
1088
|
+
__tablename__ = "prompt_version_tags"
|
|
1089
|
+
|
|
1090
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
1091
|
+
name: Mapped[Identifier] = mapped_column(_Identifier, nullable=False)
|
|
1092
|
+
description: Mapped[Optional[str]] = mapped_column(String, nullable=True)
|
|
1093
|
+
prompt_id: Mapped[int] = mapped_column(
|
|
1094
|
+
ForeignKey("prompts.id", ondelete="CASCADE"),
|
|
1095
|
+
index=True,
|
|
1096
|
+
nullable=False,
|
|
1097
|
+
)
|
|
1098
|
+
prompt_version_id: Mapped[int] = mapped_column(
|
|
1099
|
+
ForeignKey("prompt_versions.id", ondelete="CASCADE"),
|
|
1100
|
+
index=True,
|
|
1101
|
+
nullable=False,
|
|
1102
|
+
)
|
|
1103
|
+
user_id: Mapped[Optional[int]] = mapped_column(
|
|
1104
|
+
ForeignKey("users.id", ondelete="SET NULL"),
|
|
1105
|
+
index=True,
|
|
1106
|
+
nullable=True,
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
prompt: Mapped["Prompt"] = relationship("Prompt", back_populates="prompt_version_tags")
|
|
1110
|
+
prompt_version: Mapped["PromptVersion"] = relationship(
|
|
1111
|
+
"PromptVersion", back_populates="prompt_version_tags"
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
__table_args__ = (UniqueConstraint("name", "prompt_id"),)
|
|
File without changes
|
phoenix/server/api/context.py
CHANGED
|
@@ -30,6 +30,7 @@ from phoenix.server.api.dataloaders import (
|
|
|
30
30
|
LatencyMsQuantileDataLoader,
|
|
31
31
|
MinStartOrMaxEndTimeDataLoader,
|
|
32
32
|
ProjectByNameDataLoader,
|
|
33
|
+
PromptVersionSequenceNumberDataLoader,
|
|
33
34
|
RecordCountDataLoader,
|
|
34
35
|
SessionIODataLoader,
|
|
35
36
|
SessionNumTracesDataLoader,
|
|
@@ -73,6 +74,7 @@ class DataLoaders:
|
|
|
73
74
|
experiment_sequence_number: ExperimentSequenceNumberDataLoader
|
|
74
75
|
latency_ms_quantile: LatencyMsQuantileDataLoader
|
|
75
76
|
min_start_or_max_end_times: MinStartOrMaxEndTimeDataLoader
|
|
77
|
+
prompt_version_sequence_number: PromptVersionSequenceNumberDataLoader
|
|
76
78
|
record_counts: RecordCountDataLoader
|
|
77
79
|
session_first_inputs: SessionIODataLoader
|
|
78
80
|
session_last_outputs: SessionIODataLoader
|
|
@@ -18,6 +18,7 @@ from .experiment_sequence_number import ExperimentSequenceNumberDataLoader
|
|
|
18
18
|
from .latency_ms_quantile import LatencyMsQuantileCache, LatencyMsQuantileDataLoader
|
|
19
19
|
from .min_start_or_max_end_times import MinStartOrMaxEndTimeCache, MinStartOrMaxEndTimeDataLoader
|
|
20
20
|
from .project_by_name import ProjectByNameDataLoader
|
|
21
|
+
from .prompt_version_sequence_number import PromptVersionSequenceNumberDataLoader
|
|
21
22
|
from .record_counts import RecordCountCache, RecordCountDataLoader
|
|
22
23
|
from .session_io import SessionIODataLoader
|
|
23
24
|
from .session_num_traces import SessionNumTracesDataLoader
|
|
@@ -50,6 +51,7 @@ __all__ = [
|
|
|
50
51
|
"ExperimentSequenceNumberDataLoader",
|
|
51
52
|
"LatencyMsQuantileDataLoader",
|
|
52
53
|
"MinStartOrMaxEndTimeDataLoader",
|
|
54
|
+
"PromptVersionSequenceNumberDataLoader",
|
|
53
55
|
"RecordCountDataLoader",
|
|
54
56
|
"SessionIODataLoader",
|
|
55
57
|
"SessionNumTracesDataLoader",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import func, select
|
|
4
|
+
from strawberry.dataloader import DataLoader
|
|
5
|
+
from typing_extensions import TypeAlias
|
|
6
|
+
|
|
7
|
+
from phoenix.db import models
|
|
8
|
+
from phoenix.server.types import DbSessionFactory
|
|
9
|
+
|
|
10
|
+
PromptVersionId: TypeAlias = int
|
|
11
|
+
Key: TypeAlias = PromptVersionId
|
|
12
|
+
Result: TypeAlias = Optional[int]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PromptVersionSequenceNumberDataLoader(DataLoader[Key, Result]):
|
|
16
|
+
def __init__(self, db: DbSessionFactory) -> None:
|
|
17
|
+
super().__init__(load_fn=self._load_fn)
|
|
18
|
+
self._db = db
|
|
19
|
+
|
|
20
|
+
async def _load_fn(self, keys: list[Key]) -> list[Result]:
|
|
21
|
+
prompt_version_ids = keys
|
|
22
|
+
row_number = (
|
|
23
|
+
func.row_number().over(
|
|
24
|
+
partition_by=models.PromptVersion.prompt_id,
|
|
25
|
+
order_by=models.PromptVersion.id,
|
|
26
|
+
)
|
|
27
|
+
).label("sequence_number")
|
|
28
|
+
subq = select(models.PromptVersion.id.label("prompt_version_id"), row_number).subquery()
|
|
29
|
+
stmt = select(subq).where(subq.c.prompt_version_id.in_(prompt_version_ids))
|
|
30
|
+
async with self._db() as session:
|
|
31
|
+
result = {
|
|
32
|
+
prompt_version_id: seq_number
|
|
33
|
+
async for prompt_version_id, seq_number in await session.stream(stmt)
|
|
34
|
+
}
|
|
35
|
+
return [result.get(prompt_version_id) for prompt_version_id in keys]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Annotated, Any, Literal, Union
|
|
2
|
+
|
|
3
|
+
from jsonschema import Draft7Validator, ValidationError
|
|
4
|
+
from pydantic import AfterValidator, BaseModel, Field
|
|
5
|
+
from typing_extensions import TypeAlias
|
|
6
|
+
|
|
7
|
+
# This meta-schema describes valid JSON schemas according to the JSON Schema Draft 7 specification.
|
|
8
|
+
# It is copied from https://json-schema.org/draft-07/schema#
|
|
9
|
+
JSON_SCHEMA_DRAFT_7_META_SCHEMA = {
|
|
10
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
11
|
+
"$id": "http://json-schema.org/draft-07/schema#",
|
|
12
|
+
"title": "Core schema meta-schema",
|
|
13
|
+
"definitions": {
|
|
14
|
+
"schemaArray": {"type": "array", "minItems": 1, "items": {"$ref": "#"}},
|
|
15
|
+
"nonNegativeInteger": {"type": "integer", "minimum": 0},
|
|
16
|
+
"nonNegativeIntegerDefault0": {
|
|
17
|
+
"allOf": [{"$ref": "#/definitions/nonNegativeInteger"}, {"default": 0}]
|
|
18
|
+
},
|
|
19
|
+
"simpleTypes": {
|
|
20
|
+
"enum": ["array", "boolean", "integer", "null", "number", "object", "string"]
|
|
21
|
+
},
|
|
22
|
+
"stringArray": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": {"type": "string"},
|
|
25
|
+
"uniqueItems": True,
|
|
26
|
+
"default": [],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
"type": ["object", "boolean"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"$id": {"type": "string", "format": "uri-reference"},
|
|
32
|
+
"$schema": {"type": "string", "format": "uri"},
|
|
33
|
+
"$ref": {"type": "string", "format": "uri-reference"},
|
|
34
|
+
"$comment": {"type": "string"},
|
|
35
|
+
"title": {"type": "string"},
|
|
36
|
+
"description": {"type": "string"},
|
|
37
|
+
"default": True,
|
|
38
|
+
"readOnly": {"type": "boolean", "default": False},
|
|
39
|
+
"writeOnly": {"type": "boolean", "default": False},
|
|
40
|
+
"examples": {"type": "array", "items": True},
|
|
41
|
+
"multipleOf": {"type": "number", "exclusiveMinimum": 0},
|
|
42
|
+
"maximum": {"type": "number"},
|
|
43
|
+
"exclusiveMaximum": {"type": "number"},
|
|
44
|
+
"minimum": {"type": "number"},
|
|
45
|
+
"exclusiveMinimum": {"type": "number"},
|
|
46
|
+
"maxLength": {"$ref": "#/definitions/nonNegativeInteger"},
|
|
47
|
+
"minLength": {"$ref": "#/definitions/nonNegativeIntegerDefault0"},
|
|
48
|
+
"pattern": {"type": "string", "format": "regex"},
|
|
49
|
+
"additionalItems": {"$ref": "#"},
|
|
50
|
+
"items": {"anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/schemaArray"}], "default": True},
|
|
51
|
+
"maxItems": {"$ref": "#/definitions/nonNegativeInteger"},
|
|
52
|
+
"minItems": {"$ref": "#/definitions/nonNegativeIntegerDefault0"},
|
|
53
|
+
"uniqueItems": {"type": "boolean", "default": False},
|
|
54
|
+
"contains": {"$ref": "#"},
|
|
55
|
+
"maxProperties": {"$ref": "#/definitions/nonNegativeInteger"},
|
|
56
|
+
"minProperties": {"$ref": "#/definitions/nonNegativeIntegerDefault0"},
|
|
57
|
+
"required": {"$ref": "#/definitions/stringArray"},
|
|
58
|
+
"additionalProperties": {"$ref": "#"},
|
|
59
|
+
"definitions": {"type": "object", "additionalProperties": {"$ref": "#"}, "default": {}},
|
|
60
|
+
"properties": {"type": "object", "additionalProperties": {"$ref": "#"}, "default": {}},
|
|
61
|
+
"patternProperties": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": {"$ref": "#"},
|
|
64
|
+
"propertyNames": {"format": "regex"},
|
|
65
|
+
"default": {},
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": {
|
|
70
|
+
"anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/stringArray"}]
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
"propertyNames": {"$ref": "#"},
|
|
74
|
+
"const": True,
|
|
75
|
+
"enum": {"type": "array", "items": True, "minItems": 1, "uniqueItems": True},
|
|
76
|
+
"type": {
|
|
77
|
+
"anyOf": [
|
|
78
|
+
{"$ref": "#/definitions/simpleTypes"},
|
|
79
|
+
{
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": {"$ref": "#/definitions/simpleTypes"},
|
|
82
|
+
"minItems": 1,
|
|
83
|
+
"uniqueItems": True,
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"format": {"type": "string"},
|
|
88
|
+
"contentMediaType": {"type": "string"},
|
|
89
|
+
"contentEncoding": {"type": "string"},
|
|
90
|
+
"if": {"$ref": "#"},
|
|
91
|
+
"then": {"$ref": "#"},
|
|
92
|
+
"else": {"$ref": "#"},
|
|
93
|
+
"allOf": {"$ref": "#/definitions/schemaArray"},
|
|
94
|
+
"anyOf": {"$ref": "#/definitions/schemaArray"},
|
|
95
|
+
"oneOf": {"$ref": "#/definitions/schemaArray"},
|
|
96
|
+
"not": {"$ref": "#"},
|
|
97
|
+
},
|
|
98
|
+
"default": True,
|
|
99
|
+
}
|
|
100
|
+
Draft7Validator.check_schema(JSON_SCHEMA_DRAFT_7_META_SCHEMA) # ensure the schema is valid
|
|
101
|
+
JSON_SCHEMA_DRAFT_7_VALIDATOR = Draft7Validator(JSON_SCHEMA_DRAFT_7_META_SCHEMA)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def validate_json_schema_draft_7_object(schema: dict[str, Any]) -> dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Validates that a dictionary is a valid JSON schema according to the JSON
|
|
107
|
+
Schema Draft 7 specification.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
JSON_SCHEMA_DRAFT_7_VALIDATOR.validate(schema)
|
|
111
|
+
except ValidationError as error:
|
|
112
|
+
raise ValueError(str(error))
|
|
113
|
+
if schema.get("type") != "object":
|
|
114
|
+
raise ValueError("The 'type' property must be 'object'")
|
|
115
|
+
return schema
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
JSONSchemaDraft7ObjectSchemaContent: TypeAlias = Annotated[
|
|
119
|
+
dict[str, Any],
|
|
120
|
+
AfterValidator(validate_json_schema_draft_7_object),
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class JSONSchemaDraft7ObjectSchema(BaseModel):
|
|
125
|
+
type: Literal["json-schema-draft-7-object-schema"]
|
|
126
|
+
json_: JSONSchemaDraft7ObjectSchemaContent = Field(
|
|
127
|
+
...,
|
|
128
|
+
alias="json", # avoid conflict with pydantic json method
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
JSONSchemaObjectSchema: TypeAlias = Annotated[
|
|
133
|
+
Union[JSONSchemaDraft7ObjectSchema],
|
|
134
|
+
Field(discriminator="type"),
|
|
135
|
+
]
|