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.

Files changed (81) hide show
  1. {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/METADATA +31 -28
  2. {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/RECORD +71 -48
  3. phoenix/config.py +61 -36
  4. phoenix/db/migrations/versions/bc8fea3c2bc8_add_prompt_tables.py +197 -0
  5. phoenix/db/models.py +307 -0
  6. phoenix/db/types/__init__.py +0 -0
  7. phoenix/db/types/identifier.py +7 -0
  8. phoenix/db/types/model_provider.py +8 -0
  9. phoenix/server/api/context.py +2 -0
  10. phoenix/server/api/dataloaders/__init__.py +2 -0
  11. phoenix/server/api/dataloaders/prompt_version_sequence_number.py +35 -0
  12. phoenix/server/api/helpers/jsonschema.py +135 -0
  13. phoenix/server/api/helpers/playground_clients.py +23 -27
  14. phoenix/server/api/helpers/playground_spans.py +9 -0
  15. phoenix/server/api/helpers/prompts/__init__.py +0 -0
  16. phoenix/server/api/helpers/prompts/conversions/__init__.py +0 -0
  17. phoenix/server/api/helpers/prompts/conversions/anthropic.py +87 -0
  18. phoenix/server/api/helpers/prompts/conversions/openai.py +78 -0
  19. phoenix/server/api/helpers/prompts/models.py +575 -0
  20. phoenix/server/api/input_types/ChatCompletionInput.py +9 -4
  21. phoenix/server/api/input_types/PromptTemplateOptions.py +10 -0
  22. phoenix/server/api/input_types/PromptVersionInput.py +133 -0
  23. phoenix/server/api/mutations/__init__.py +6 -0
  24. phoenix/server/api/mutations/chat_mutations.py +18 -16
  25. phoenix/server/api/mutations/prompt_label_mutations.py +191 -0
  26. phoenix/server/api/mutations/prompt_mutations.py +312 -0
  27. phoenix/server/api/mutations/prompt_version_tag_mutations.py +148 -0
  28. phoenix/server/api/mutations/user_mutations.py +7 -6
  29. phoenix/server/api/openapi/schema.py +1 -0
  30. phoenix/server/api/queries.py +84 -31
  31. phoenix/server/api/routers/oauth2.py +3 -2
  32. phoenix/server/api/routers/v1/__init__.py +2 -0
  33. phoenix/server/api/routers/v1/datasets.py +1 -1
  34. phoenix/server/api/routers/v1/experiment_evaluations.py +1 -1
  35. phoenix/server/api/routers/v1/experiment_runs.py +1 -1
  36. phoenix/server/api/routers/v1/experiments.py +1 -1
  37. phoenix/server/api/routers/v1/models.py +45 -0
  38. phoenix/server/api/routers/v1/prompts.py +412 -0
  39. phoenix/server/api/routers/v1/spans.py +1 -1
  40. phoenix/server/api/routers/v1/traces.py +1 -1
  41. phoenix/server/api/routers/v1/utils.py +1 -1
  42. phoenix/server/api/subscriptions.py +21 -24
  43. phoenix/server/api/types/GenerativeProvider.py +6 -6
  44. phoenix/server/api/types/Identifier.py +15 -0
  45. phoenix/server/api/types/Project.py +5 -7
  46. phoenix/server/api/types/Prompt.py +134 -0
  47. phoenix/server/api/types/PromptLabel.py +41 -0
  48. phoenix/server/api/types/PromptVersion.py +148 -0
  49. phoenix/server/api/types/PromptVersionTag.py +27 -0
  50. phoenix/server/api/types/PromptVersionTemplate.py +148 -0
  51. phoenix/server/api/types/ResponseFormat.py +9 -0
  52. phoenix/server/api/types/ToolDefinition.py +9 -0
  53. phoenix/server/app.py +3 -0
  54. phoenix/server/static/.vite/manifest.json +45 -45
  55. phoenix/server/static/assets/components-B-qgPyHv.js +2699 -0
  56. phoenix/server/static/assets/index-D4KO1IcF.js +1125 -0
  57. phoenix/server/static/assets/pages-DdcuL3Rh.js +5634 -0
  58. phoenix/server/static/assets/vendor-DQp7CrDA.js +894 -0
  59. phoenix/server/static/assets/vendor-arizeai-C1nEIEQq.js +657 -0
  60. phoenix/server/static/assets/vendor-codemirror-BZXYUIkP.js +24 -0
  61. phoenix/server/static/assets/vendor-recharts-BUFpwCVD.js +59 -0
  62. phoenix/server/static/assets/{vendor-shiki-Cl9QBraO.js → vendor-shiki-C8L-c9jT.js} +2 -2
  63. phoenix/server/static/assets/{vendor-three-DwGkEfCM.js → vendor-three-C-AGeJYv.js} +1 -1
  64. phoenix/session/client.py +25 -21
  65. phoenix/utilities/client.py +6 -0
  66. phoenix/version.py +1 -1
  67. phoenix/server/api/input_types/TemplateOptions.py +0 -10
  68. phoenix/server/api/routers/v1/pydantic_compat.py +0 -78
  69. phoenix/server/api/types/TemplateLanguage.py +0 -10
  70. phoenix/server/static/assets/components-DckIzNmE.js +0 -2125
  71. phoenix/server/static/assets/index-Bf25Ogon.js +0 -113
  72. phoenix/server/static/assets/pages-DL7J9q9w.js +0 -4463
  73. phoenix/server/static/assets/vendor-DvC8cT4X.js +0 -894
  74. phoenix/server/static/assets/vendor-arizeai-Do1793cv.js +0 -662
  75. phoenix/server/static/assets/vendor-codemirror-BzwZPyJM.js +0 -24
  76. phoenix/server/static/assets/vendor-recharts-_Jb7JjhG.js +0 -59
  77. {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/WHEEL +0 -0
  78. {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/entry_points.txt +0 -0
  79. {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/licenses/IP_NOTICE +0 -0
  80. {arize_phoenix-7.12.2.dist-info → arize_phoenix-8.0.0.dist-info}/licenses/LICENSE +0 -0
  81. /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
@@ -0,0 +1,7 @@
1
+ from typing import Annotated
2
+
3
+ from pydantic import Field, RootModel
4
+
5
+
6
+ class Identifier(RootModel[str]):
7
+ root: Annotated[str, Field(pattern=r"^[a-z0-9]([_a-z0-9-]*[a-z0-9])?$")]
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ModelProvider(Enum):
5
+ OPENAI = "OPENAI"
6
+ AZURE_OPENAI = "AZURE_OPENAI"
7
+ ANTHROPIC = "ANTHROPIC"
8
+ GOOGLE = "GOOGLE"
@@ -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
+ ]