arize-phoenix 7.12.3__py3-none-any.whl → 8.0.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.
Potentially problematic release.
This version of arize-phoenix might be problematic. Click here for more details.
- {arize_phoenix-7.12.3.dist-info → arize_phoenix-8.0.1.dist-info}/METADATA +31 -28
- {arize_phoenix-7.12.3.dist-info → arize_phoenix-8.0.1.dist-info}/RECORD +70 -47
- 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 +15 -15
- 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 +415 -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 +4 -4
- 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.3.dist-info → arize_phoenix-8.0.1.dist-info}/WHEEL +0 -0
- {arize_phoenix-7.12.3.dist-info → arize_phoenix-8.0.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-7.12.3.dist-info → arize_phoenix-8.0.1.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-7.12.3.dist-info → arize_phoenix-8.0.1.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
|
+
]
|
|
@@ -856,7 +856,7 @@ class AnthropicStreamingClient(PlaygroundStreamingClient):
|
|
|
856
856
|
|
|
857
857
|
|
|
858
858
|
@register_llm_client(
|
|
859
|
-
provider_key=GenerativeProviderKey.
|
|
859
|
+
provider_key=GenerativeProviderKey.GOOGLE,
|
|
860
860
|
model_names=[
|
|
861
861
|
PROVIDER_DEFAULT,
|
|
862
862
|
"gemini-2.0-flash-exp",
|
|
@@ -866,7 +866,7 @@ class AnthropicStreamingClient(PlaygroundStreamingClient):
|
|
|
866
866
|
"gemini-1.0-pro",
|
|
867
867
|
],
|
|
868
868
|
)
|
|
869
|
-
class
|
|
869
|
+
class GoogleStreamingClient(PlaygroundStreamingClient):
|
|
870
870
|
def __init__(
|
|
871
871
|
self,
|
|
872
872
|
model: GenerativeModelInput,
|
|
@@ -941,7 +941,7 @@ class GeminiStreamingClient(PlaygroundStreamingClient):
|
|
|
941
941
|
) -> AsyncIterator[ChatCompletionChunk]:
|
|
942
942
|
import google.generativeai as google_genai
|
|
943
943
|
|
|
944
|
-
|
|
944
|
+
google_message_history, current_message, system_prompt = self._build_google_messages(
|
|
945
945
|
messages
|
|
946
946
|
)
|
|
947
947
|
|
|
@@ -950,17 +950,17 @@ class GeminiStreamingClient(PlaygroundStreamingClient):
|
|
|
950
950
|
model_args["system_instruction"] = system_prompt
|
|
951
951
|
client = google_genai.GenerativeModel(**model_args)
|
|
952
952
|
|
|
953
|
-
|
|
953
|
+
google_config = google_genai.GenerationConfig(
|
|
954
954
|
**invocation_parameters,
|
|
955
955
|
)
|
|
956
|
-
|
|
956
|
+
google_params = {
|
|
957
957
|
"content": current_message,
|
|
958
|
-
"generation_config":
|
|
958
|
+
"generation_config": google_config,
|
|
959
959
|
"stream": True,
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
-
chat = client.start_chat(history=
|
|
963
|
-
stream = await chat.send_message_async(**
|
|
962
|
+
chat = client.start_chat(history=google_message_history)
|
|
963
|
+
stream = await chat.send_message_async(**google_params)
|
|
964
964
|
async for event in stream:
|
|
965
965
|
self._attributes.update(
|
|
966
966
|
{
|
|
@@ -971,29 +971,29 @@ class GeminiStreamingClient(PlaygroundStreamingClient):
|
|
|
971
971
|
)
|
|
972
972
|
yield TextChunk(content=event.text)
|
|
973
973
|
|
|
974
|
-
def
|
|
974
|
+
def _build_google_messages(
|
|
975
975
|
self,
|
|
976
976
|
messages: list[tuple[ChatCompletionMessageRole, str, Optional[str], Optional[list[str]]]],
|
|
977
977
|
) -> tuple[list["ContentType"], str, str]:
|
|
978
|
-
|
|
978
|
+
google_message_history: list["ContentType"] = []
|
|
979
979
|
system_prompts = []
|
|
980
980
|
for role, content, _tool_call_id, _tool_calls in messages:
|
|
981
981
|
if role == ChatCompletionMessageRole.USER:
|
|
982
|
-
|
|
982
|
+
google_message_history.append({"role": "user", "parts": content})
|
|
983
983
|
elif role == ChatCompletionMessageRole.AI:
|
|
984
|
-
|
|
984
|
+
google_message_history.append({"role": "model", "parts": content})
|
|
985
985
|
elif role == ChatCompletionMessageRole.SYSTEM:
|
|
986
986
|
system_prompts.append(content)
|
|
987
987
|
elif role == ChatCompletionMessageRole.TOOL:
|
|
988
988
|
raise NotImplementedError
|
|
989
989
|
else:
|
|
990
990
|
assert_never(role)
|
|
991
|
-
if
|
|
992
|
-
prompt =
|
|
991
|
+
if google_message_history:
|
|
992
|
+
prompt = google_message_history.pop()["parts"]
|
|
993
993
|
else:
|
|
994
994
|
prompt = ""
|
|
995
995
|
|
|
996
|
-
return
|
|
996
|
+
return google_message_history, prompt, "\n".join(system_prompts)
|
|
997
997
|
|
|
998
998
|
|
|
999
999
|
def initialize_playground_clients() -> None:
|
|
@@ -41,6 +41,7 @@ from phoenix.server.api.types.ChatCompletionSubscriptionPayload import (
|
|
|
41
41
|
TextChunk,
|
|
42
42
|
ToolCallChunk,
|
|
43
43
|
)
|
|
44
|
+
from phoenix.server.api.types.Identifier import Identifier
|
|
44
45
|
from phoenix.trace.attributes import get_attribute_value, unflatten
|
|
45
46
|
from phoenix.trace.schemas import (
|
|
46
47
|
SpanEvent,
|
|
@@ -70,6 +71,8 @@ class streaming_llm_span:
|
|
|
70
71
|
) -> None:
|
|
71
72
|
self._input = input
|
|
72
73
|
self._attributes: dict[str, Any] = attributes if attributes is not None else {}
|
|
74
|
+
self._attributes.update(dict(prompt_metadata(input.prompt_name)))
|
|
75
|
+
|
|
73
76
|
self._attributes.update(
|
|
74
77
|
chain(
|
|
75
78
|
llm_span_kind(),
|
|
@@ -264,6 +267,11 @@ def input_value_and_mime_type(
|
|
|
264
267
|
yield INPUT_VALUE, safe_json_dumps(input_data)
|
|
265
268
|
|
|
266
269
|
|
|
270
|
+
def prompt_metadata(prompt_name: Optional[Identifier]) -> Iterator[tuple[str, Any]]:
|
|
271
|
+
if prompt_name:
|
|
272
|
+
yield METADATA, {"phoenix_prompt_id": prompt_name}
|
|
273
|
+
|
|
274
|
+
|
|
267
275
|
def _merge_tool_call_chunks(
|
|
268
276
|
chunks_by_id: defaultdict[str, list[ToolCallChunk]],
|
|
269
277
|
) -> list[dict[str, Any]]:
|
|
@@ -442,6 +450,7 @@ LLM_INVOCATION_PARAMETERS = SpanAttributes.LLM_INVOCATION_PARAMETERS
|
|
|
442
450
|
LLM_TOOLS = SpanAttributes.LLM_TOOLS
|
|
443
451
|
LLM_TOKEN_COUNT_PROMPT = SpanAttributes.LLM_TOKEN_COUNT_PROMPT
|
|
444
452
|
LLM_TOKEN_COUNT_COMPLETION = SpanAttributes.LLM_TOKEN_COUNT_COMPLETION
|
|
453
|
+
METADATA = SpanAttributes.METADATA
|
|
445
454
|
|
|
446
455
|
MESSAGE_CONTENT = MessageAttributes.MESSAGE_CONTENT
|
|
447
456
|
MESSAGE_ROLE = MessageAttributes.MESSAGE_ROLE
|
|
File without changes
|
|
File without changes
|