arize-phoenix 4.35.2__py3-none-any.whl → 5.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-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/METADATA +10 -12
- {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/RECORD +92 -79
- phoenix/__init__.py +86 -0
- phoenix/auth.py +275 -14
- phoenix/config.py +369 -27
- phoenix/db/alembic.ini +0 -34
- phoenix/db/engines.py +27 -10
- phoenix/db/enums.py +20 -0
- phoenix/db/facilitator.py +112 -0
- phoenix/db/insertion/dataset.py +0 -1
- phoenix/db/insertion/types.py +1 -1
- phoenix/db/migrate.py +3 -3
- phoenix/db/migrations/env.py +0 -7
- phoenix/db/migrations/versions/cd164e83824f_users_and_tokens.py +157 -0
- phoenix/db/models.py +145 -60
- phoenix/experiments/evaluators/code_evaluators.py +9 -3
- phoenix/experiments/functions.py +1 -4
- phoenix/inferences/fixtures.py +0 -1
- phoenix/inferences/inferences.py +0 -1
- phoenix/logging/__init__.py +3 -0
- phoenix/logging/_config.py +90 -0
- phoenix/logging/_filter.py +6 -0
- phoenix/logging/_formatter.py +69 -0
- phoenix/metrics/__init__.py +0 -1
- phoenix/otel/settings.py +4 -4
- phoenix/server/api/README.md +28 -0
- phoenix/server/api/auth.py +32 -0
- phoenix/server/api/context.py +50 -2
- phoenix/server/api/dataloaders/__init__.py +4 -0
- phoenix/server/api/dataloaders/user_roles.py +30 -0
- phoenix/server/api/dataloaders/users.py +33 -0
- phoenix/server/api/exceptions.py +7 -0
- phoenix/server/api/mutations/__init__.py +0 -2
- phoenix/server/api/mutations/api_key_mutations.py +104 -86
- phoenix/server/api/mutations/dataset_mutations.py +8 -8
- phoenix/server/api/mutations/experiment_mutations.py +2 -2
- phoenix/server/api/mutations/export_events_mutations.py +3 -3
- phoenix/server/api/mutations/project_mutations.py +3 -3
- phoenix/server/api/mutations/span_annotations_mutations.py +4 -4
- phoenix/server/api/mutations/trace_annotations_mutations.py +4 -4
- phoenix/server/api/mutations/user_mutations.py +282 -42
- phoenix/server/api/openapi/schema.py +2 -2
- phoenix/server/api/queries.py +48 -39
- phoenix/server/api/routers/__init__.py +11 -0
- phoenix/server/api/routers/auth.py +284 -0
- phoenix/server/api/routers/embeddings.py +26 -0
- phoenix/server/api/routers/oauth2.py +456 -0
- phoenix/server/api/routers/v1/__init__.py +38 -16
- phoenix/server/api/routers/v1/datasets.py +0 -1
- phoenix/server/api/types/ApiKey.py +11 -0
- phoenix/server/api/types/AuthMethod.py +9 -0
- phoenix/server/api/types/User.py +48 -4
- phoenix/server/api/types/UserApiKey.py +35 -1
- phoenix/server/api/types/UserRole.py +7 -0
- phoenix/server/app.py +105 -34
- phoenix/server/bearer_auth.py +161 -0
- phoenix/server/email/__init__.py +0 -0
- phoenix/server/email/sender.py +26 -0
- phoenix/server/email/templates/__init__.py +0 -0
- phoenix/server/email/templates/password_reset.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/grpc_server.py +6 -0
- phoenix/server/jwt_store.py +504 -0
- phoenix/server/main.py +61 -30
- phoenix/server/oauth2.py +51 -0
- phoenix/server/prometheus.py +20 -0
- phoenix/server/rate_limiters.py +191 -0
- phoenix/server/static/.vite/manifest.json +31 -31
- phoenix/server/static/assets/{components-Dte7_KRd.js → components-REunxTt6.js} +348 -286
- phoenix/server/static/assets/index-DAPJxlCw.js +101 -0
- phoenix/server/static/assets/{pages-CnTvEGEN.js → pages-1VrMk2pW.js} +559 -291
- phoenix/server/static/assets/{vendor-BC3OPQuM.js → vendor-B5IC0ivG.js} +5 -5
- phoenix/server/static/assets/{vendor-arizeai-NjB3cZzD.js → vendor-arizeai-aFbT4kl1.js} +2 -2
- phoenix/server/static/assets/{vendor-codemirror-gE_JCOgX.js → vendor-codemirror-BEGorXSV.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-BXLYwcXF.js → vendor-recharts-6nUU7gU_.js} +1 -1
- phoenix/server/telemetry.py +2 -2
- phoenix/server/templates/index.html +1 -0
- phoenix/server/types.py +157 -1
- phoenix/services.py +0 -1
- phoenix/session/client.py +7 -3
- phoenix/session/evaluation.py +0 -1
- phoenix/session/session.py +0 -1
- phoenix/settings.py +9 -0
- phoenix/trace/exporter.py +0 -1
- phoenix/trace/fixtures.py +0 -2
- phoenix/utilities/client.py +16 -0
- phoenix/utilities/logging.py +9 -1
- phoenix/utilities/re.py +3 -3
- phoenix/version.py +1 -1
- phoenix/db/migrations/future_versions/README.md +0 -4
- phoenix/db/migrations/future_versions/cd164e83824f_users_and_tokens.py +0 -293
- phoenix/db/migrations/versions/.gitignore +0 -1
- phoenix/server/api/mutations/auth.py +0 -18
- phoenix/server/api/mutations/auth_mutations.py +0 -65
- phoenix/server/static/assets/index-fq1-hCK4.js +0 -100
- phoenix/trace/langchain/__init__.py +0 -3
- phoenix/trace/langchain/instrumentor.py +0 -35
- phoenix/trace/llama_index/__init__.py +0 -3
- phoenix/trace/llama_index/callback.py +0 -103
- phoenix/trace/openai/__init__.py +0 -3
- phoenix/trace/openai/instrumentor.py +0 -31
- {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-4.35.2.dist-info → arize_phoenix-5.0.0.dist-info}/licenses/LICENSE +0 -0
phoenix/settings.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from dataclasses import dataclass, field
|
|
2
3
|
|
|
4
|
+
from phoenix.config import LoggingMode
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
@dataclass
|
|
5
8
|
class _Settings:
|
|
@@ -7,6 +10,12 @@ class _Settings:
|
|
|
7
10
|
|
|
8
11
|
# By default, don't log migrations
|
|
9
12
|
log_migrations: bool = field(default=False)
|
|
13
|
+
# By default, Phoenix does not configure its loggers and acts as a library
|
|
14
|
+
logging_mode: LoggingMode = field(default=LoggingMode.DEFAULT)
|
|
15
|
+
# By default, log level is INFO
|
|
16
|
+
logging_level: int = field(default=logging.INFO)
|
|
17
|
+
# By default, log level is WARNING
|
|
18
|
+
db_logging_level: int = field(default=logging.WARNING)
|
|
10
19
|
|
|
11
20
|
|
|
12
21
|
# Singleton instance of the settings
|
phoenix/trace/exporter.py
CHANGED
phoenix/trace/fixtures.py
CHANGED
phoenix/utilities/client.py
CHANGED
|
@@ -3,6 +3,8 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
import httpx
|
|
5
5
|
|
|
6
|
+
from phoenix.config import get_env_client_headers, get_env_phoenix_api_key
|
|
7
|
+
|
|
6
8
|
PHOENIX_SERVER_VERSION_HEADER = "x-phoenix-server-version"
|
|
7
9
|
|
|
8
10
|
|
|
@@ -15,6 +17,13 @@ class VersionedClient(httpx.Client):
|
|
|
15
17
|
from phoenix import __version__ as phoenix_version
|
|
16
18
|
|
|
17
19
|
super().__init__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
if env_headers := get_env_client_headers():
|
|
22
|
+
self.headers.update(env_headers)
|
|
23
|
+
if "authorization" not in [k.lower() for k in self.headers]:
|
|
24
|
+
if api_key := get_env_phoenix_api_key():
|
|
25
|
+
self.headers["Authorization"] = f"Bearer {api_key}"
|
|
26
|
+
|
|
18
27
|
self._client_phoenix_version = phoenix_version
|
|
19
28
|
self._warned_on_minor_version_mismatch = False
|
|
20
29
|
|
|
@@ -72,6 +81,13 @@ class VersionedAsyncClient(httpx.AsyncClient):
|
|
|
72
81
|
from phoenix import __version__ as phoenix_version
|
|
73
82
|
|
|
74
83
|
super().__init__(*args, **kwargs)
|
|
84
|
+
|
|
85
|
+
if env_headers := get_env_client_headers():
|
|
86
|
+
self.headers.update(env_headers)
|
|
87
|
+
if "authorization" not in [k.lower() for k in self.headers]:
|
|
88
|
+
if api_key := get_env_phoenix_api_key():
|
|
89
|
+
self.headers["Authorization"] = f"Bearer {api_key}"
|
|
90
|
+
|
|
75
91
|
self._client_phoenix_version = phoenix_version
|
|
76
92
|
self._warned_on_minor_version_mismatch = False
|
|
77
93
|
|
phoenix/utilities/logging.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# A collection of printing and logging utilities
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, List
|
|
4
4
|
|
|
5
5
|
from tqdm.auto import tqdm
|
|
6
6
|
|
|
@@ -8,3 +8,11 @@ from tqdm.auto import tqdm
|
|
|
8
8
|
def printif(condition: bool, *args: Any, **kwargs: Any) -> None:
|
|
9
9
|
if condition:
|
|
10
10
|
tqdm.write(*args, **kwargs)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def log_a_list(list_of_str: List[str], join_word: str) -> str:
|
|
14
|
+
if list_of_str is None or len(list_of_str) == 0:
|
|
15
|
+
return ""
|
|
16
|
+
if len(list_of_str) == 1:
|
|
17
|
+
return list_of_str[0]
|
|
18
|
+
return f"{', '.join(map(str, list_of_str[:-1]))} {join_word} {list_of_str[-1]}"
|
phoenix/utilities/re.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
2
|
from re import compile, split
|
|
3
3
|
from typing import Dict, List
|
|
4
4
|
from urllib.parse import unquote
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
8
8
|
# Optional whitespace
|
|
9
9
|
_OWS = r"[ \t]*"
|
|
@@ -35,7 +35,7 @@ def parse_env_headers(s: str) -> Dict[str, str]:
|
|
|
35
35
|
continue
|
|
36
36
|
match = _HEADER_PATTERN.fullmatch(header.strip())
|
|
37
37
|
if not match:
|
|
38
|
-
|
|
38
|
+
logger.warning(
|
|
39
39
|
"Header format invalid! Header values in environment variables must be "
|
|
40
40
|
"URL encoded: %s",
|
|
41
41
|
header,
|
phoenix/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "5.0.0"
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
"""users and tokens
|
|
2
|
-
|
|
3
|
-
Revision ID: cd164e83824f
|
|
4
|
-
Revises: 10460e46d750
|
|
5
|
-
Create Date: 2024-08-01 18:36:52.157604
|
|
6
|
-
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from datetime import datetime, timezone
|
|
10
|
-
from typing import Any, Dict, List, Optional, Sequence, TypedDict, Union
|
|
11
|
-
|
|
12
|
-
import sqlalchemy as sa
|
|
13
|
-
from alembic import op
|
|
14
|
-
from sqlalchemy import (
|
|
15
|
-
JSON,
|
|
16
|
-
TIMESTAMP,
|
|
17
|
-
CheckConstraint,
|
|
18
|
-
Dialect,
|
|
19
|
-
ForeignKey,
|
|
20
|
-
MetaData,
|
|
21
|
-
TypeDecorator,
|
|
22
|
-
func,
|
|
23
|
-
insert,
|
|
24
|
-
)
|
|
25
|
-
from sqlalchemy.dialects import postgresql
|
|
26
|
-
from sqlalchemy.ext.asyncio.engine import AsyncConnection
|
|
27
|
-
from sqlalchemy.ext.compiler import compiles
|
|
28
|
-
from sqlalchemy.orm import (
|
|
29
|
-
DeclarativeBase,
|
|
30
|
-
Mapped,
|
|
31
|
-
mapped_column,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
from phoenix.datetime_utils import normalize_datetime
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class JSONB(JSON):
|
|
38
|
-
# See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
39
|
-
__visit_name__ = "JSONB"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@compiles(JSONB, "sqlite") # type: ignore
|
|
43
|
-
def _(*args: Any, **kwargs: Any) -> str:
|
|
44
|
-
# See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
45
|
-
return "JSONB"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
JSON_ = (
|
|
49
|
-
JSON()
|
|
50
|
-
.with_variant(
|
|
51
|
-
postgresql.JSONB(), # type: ignore
|
|
52
|
-
"postgresql",
|
|
53
|
-
)
|
|
54
|
-
.with_variant(
|
|
55
|
-
JSONB(),
|
|
56
|
-
"sqlite",
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class JsonDict(TypeDecorator[Dict[str, Any]]):
|
|
62
|
-
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
63
|
-
cache_ok = True
|
|
64
|
-
impl = JSON_
|
|
65
|
-
|
|
66
|
-
def process_bind_param(self, value: Optional[Dict[str, Any]], _: Dialect) -> Dict[str, Any]:
|
|
67
|
-
return value if isinstance(value, dict) else {}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class JsonList(TypeDecorator[List[Any]]):
|
|
71
|
-
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
72
|
-
cache_ok = True
|
|
73
|
-
impl = JSON_
|
|
74
|
-
|
|
75
|
-
def process_bind_param(self, value: Optional[List[Any]], _: Dialect) -> List[Any]:
|
|
76
|
-
return value if isinstance(value, list) else []
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class UtcTimeStamp(TypeDecorator[datetime]):
|
|
80
|
-
# See # See https://docs.sqlalchemy.org/en/20/core/custom_types.html
|
|
81
|
-
cache_ok = True
|
|
82
|
-
impl = TIMESTAMP(timezone=True)
|
|
83
|
-
|
|
84
|
-
def process_bind_param(self, value: Optional[datetime], _: Dialect) -> Optional[datetime]:
|
|
85
|
-
return normalize_datetime(value)
|
|
86
|
-
|
|
87
|
-
def process_result_value(self, value: Optional[Any], _: Dialect) -> Optional[datetime]:
|
|
88
|
-
return normalize_datetime(value, timezone.utc)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class ExperimentRunOutput(TypedDict, total=False):
|
|
92
|
-
task_output: Any
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class Base(DeclarativeBase):
|
|
96
|
-
# Enforce best practices for naming constraints
|
|
97
|
-
# https://alembic.sqlalchemy.org/en/latest/naming.html#integration-of-naming-conventions-into-operations-autogenerate
|
|
98
|
-
metadata = MetaData(
|
|
99
|
-
naming_convention={
|
|
100
|
-
"ix": "ix_%(table_name)s_%(column_0_N_name)s",
|
|
101
|
-
"uq": "uq_%(table_name)s_%(column_0_N_name)s",
|
|
102
|
-
"ck": "ck_%(table_name)s_`%(constraint_name)s`",
|
|
103
|
-
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
|
104
|
-
"pk": "pk_%(table_name)s",
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
type_annotation_map = {
|
|
108
|
-
Dict[str, Any]: JsonDict,
|
|
109
|
-
List[Dict[str, Any]]: JsonList,
|
|
110
|
-
ExperimentRunOutput: JsonDict,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
class UserRole(Base):
|
|
115
|
-
__tablename__ = "user_roles"
|
|
116
|
-
id: Mapped[int] = mapped_column(primary_key=True)
|
|
117
|
-
name: Mapped[str] = mapped_column(unique=True)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class User(Base):
|
|
121
|
-
__tablename__ = "users"
|
|
122
|
-
id: Mapped[int] = mapped_column(primary_key=True)
|
|
123
|
-
user_role_id: Mapped[int] = mapped_column(
|
|
124
|
-
ForeignKey("user_roles.id"),
|
|
125
|
-
index=True,
|
|
126
|
-
)
|
|
127
|
-
username: Mapped[Optional[str]] = mapped_column(nullable=True, unique=True, index=True)
|
|
128
|
-
email: Mapped[str] = mapped_column(nullable=False, unique=True, index=True)
|
|
129
|
-
auth_method: Mapped[str] = mapped_column(
|
|
130
|
-
CheckConstraint("auth_method IN ('LOCAL')", name="valid_auth_method")
|
|
131
|
-
)
|
|
132
|
-
password_hash: Mapped[Optional[str]]
|
|
133
|
-
reset_password: Mapped[bool]
|
|
134
|
-
created_at: Mapped[datetime] = mapped_column(UtcTimeStamp, server_default=func.now())
|
|
135
|
-
updated_at: Mapped[datetime] = mapped_column(
|
|
136
|
-
UtcTimeStamp, server_default=func.now(), onupdate=func.now()
|
|
137
|
-
)
|
|
138
|
-
deleted_at: Mapped[Optional[datetime]] = mapped_column(UtcTimeStamp)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# revision identifiers, used by Alembic.
|
|
142
|
-
revision: str = "cd164e83824f"
|
|
143
|
-
down_revision: Union[str, None] = "3be8647b87d8"
|
|
144
|
-
branch_labels: Union[str, Sequence[str], None] = None
|
|
145
|
-
depends_on: Union[str, Sequence[str], None] = None
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
async def insert_roles_and_users(connection: AsyncConnection) -> None:
|
|
149
|
-
"""
|
|
150
|
-
Populates the `user_roles` table and adds a system user and initial admin
|
|
151
|
-
user to the `users` table.
|
|
152
|
-
"""
|
|
153
|
-
await connection.execute(
|
|
154
|
-
insert(UserRole).values([{"name": "SYSTEM"}, {"name": "ADMIN"}, {"name": "MEMBER"}])
|
|
155
|
-
)
|
|
156
|
-
system_user_role_id = sa.select(UserRole.id).where(UserRole.name == "SYSTEM").scalar_subquery()
|
|
157
|
-
admin_user_role_id = sa.select(UserRole.id).where(UserRole.name == "ADMIN").scalar_subquery()
|
|
158
|
-
await connection.execute(
|
|
159
|
-
insert(User).values(
|
|
160
|
-
[
|
|
161
|
-
{
|
|
162
|
-
"user_role_id": system_user_role_id,
|
|
163
|
-
"username": None,
|
|
164
|
-
"email": "system@localhost",
|
|
165
|
-
"auth_method": "LOCAL",
|
|
166
|
-
"password_hash": None,
|
|
167
|
-
"reset_password": False,
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
"user_role_id": admin_user_role_id,
|
|
171
|
-
"username": "admin",
|
|
172
|
-
"email": "admin@localhost",
|
|
173
|
-
"auth_method": "LOCAL",
|
|
174
|
-
"password_hash": None, # todo: replace this with the hashed PHOENIX_SECRET
|
|
175
|
-
"reset_password": True,
|
|
176
|
-
},
|
|
177
|
-
]
|
|
178
|
-
)
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def upgrade() -> None:
|
|
183
|
-
op.create_table(
|
|
184
|
-
"user_roles",
|
|
185
|
-
sa.Column("id", sa.Integer, primary_key=True),
|
|
186
|
-
sa.Column(
|
|
187
|
-
"name",
|
|
188
|
-
sa.String,
|
|
189
|
-
nullable=False,
|
|
190
|
-
unique=True,
|
|
191
|
-
),
|
|
192
|
-
)
|
|
193
|
-
op.create_table(
|
|
194
|
-
"users",
|
|
195
|
-
sa.Column("id", sa.Integer, primary_key=True),
|
|
196
|
-
sa.Column(
|
|
197
|
-
"user_role_id",
|
|
198
|
-
sa.Integer,
|
|
199
|
-
sa.ForeignKey("user_roles.id"),
|
|
200
|
-
nullable=False,
|
|
201
|
-
index=True,
|
|
202
|
-
),
|
|
203
|
-
sa.Column("username", sa.String, nullable=True, unique=True, index=True),
|
|
204
|
-
sa.Column("email", sa.String, nullable=False, unique=True, index=True),
|
|
205
|
-
sa.Column(
|
|
206
|
-
"auth_method",
|
|
207
|
-
sa.String,
|
|
208
|
-
sa.CheckConstraint("auth_method IN ('LOCAL')", "valid_auth_method"),
|
|
209
|
-
nullable=False,
|
|
210
|
-
),
|
|
211
|
-
sa.Column("password_hash", sa.String, nullable=True),
|
|
212
|
-
sa.Column("reset_password", sa.Boolean, nullable=False),
|
|
213
|
-
sa.Column(
|
|
214
|
-
"created_at",
|
|
215
|
-
sa.TIMESTAMP(timezone=True),
|
|
216
|
-
nullable=False,
|
|
217
|
-
server_default=sa.func.now(),
|
|
218
|
-
),
|
|
219
|
-
sa.Column(
|
|
220
|
-
"updated_at",
|
|
221
|
-
sa.TIMESTAMP(timezone=True),
|
|
222
|
-
nullable=False,
|
|
223
|
-
server_default=sa.func.now(),
|
|
224
|
-
onupdate=sa.func.now(),
|
|
225
|
-
),
|
|
226
|
-
sa.Column(
|
|
227
|
-
"deleted_at",
|
|
228
|
-
sa.TIMESTAMP(timezone=True),
|
|
229
|
-
nullable=True,
|
|
230
|
-
),
|
|
231
|
-
)
|
|
232
|
-
op.create_table(
|
|
233
|
-
"api_keys",
|
|
234
|
-
sa.Column("id", sa.Integer, primary_key=True),
|
|
235
|
-
sa.Column(
|
|
236
|
-
"user_id",
|
|
237
|
-
sa.Integer,
|
|
238
|
-
sa.ForeignKey("users.id"),
|
|
239
|
-
nullable=False,
|
|
240
|
-
index=True,
|
|
241
|
-
),
|
|
242
|
-
sa.Column("name", sa.String, nullable=False),
|
|
243
|
-
sa.Column("description", sa.String, nullable=True),
|
|
244
|
-
sa.Column(
|
|
245
|
-
"created_at",
|
|
246
|
-
sa.TIMESTAMP(timezone=True),
|
|
247
|
-
nullable=False,
|
|
248
|
-
server_default=sa.func.now(),
|
|
249
|
-
),
|
|
250
|
-
sa.Column(
|
|
251
|
-
"expires_at",
|
|
252
|
-
sa.TIMESTAMP(timezone=True),
|
|
253
|
-
nullable=True,
|
|
254
|
-
),
|
|
255
|
-
)
|
|
256
|
-
op.create_table(
|
|
257
|
-
"audit_api_keys",
|
|
258
|
-
sa.Column("id", sa.Integer, primary_key=True),
|
|
259
|
-
sa.Column(
|
|
260
|
-
"api_key_id",
|
|
261
|
-
sa.Integer,
|
|
262
|
-
sa.ForeignKey("api_keys.id"),
|
|
263
|
-
nullable=False,
|
|
264
|
-
index=True,
|
|
265
|
-
),
|
|
266
|
-
sa.Column(
|
|
267
|
-
"user_id",
|
|
268
|
-
sa.Integer,
|
|
269
|
-
sa.ForeignKey("users.id"),
|
|
270
|
-
nullable=False,
|
|
271
|
-
index=True,
|
|
272
|
-
),
|
|
273
|
-
sa.Column(
|
|
274
|
-
"action",
|
|
275
|
-
sa.String,
|
|
276
|
-
sa.CheckConstraint("action IN ('CREATE', 'DELETE')", "valid_action"),
|
|
277
|
-
nullable=False,
|
|
278
|
-
),
|
|
279
|
-
sa.Column(
|
|
280
|
-
"created_at",
|
|
281
|
-
sa.TIMESTAMP(timezone=True),
|
|
282
|
-
nullable=False,
|
|
283
|
-
server_default=sa.func.now(),
|
|
284
|
-
),
|
|
285
|
-
)
|
|
286
|
-
op.run_async(insert_roles_and_users)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def downgrade() -> None:
|
|
290
|
-
op.drop_table("audit_api_keys")
|
|
291
|
-
op.drop_table("api_keys")
|
|
292
|
-
op.drop_table("users")
|
|
293
|
-
op.drop_table("user_roles")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
cd164e83824f_users_and_tokens.py
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from strawberry import Info
|
|
4
|
-
from strawberry.permission import BasePermission
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class IsAuthenticated(BasePermission):
|
|
8
|
-
message = "User is not authenticated"
|
|
9
|
-
|
|
10
|
-
async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
|
|
11
|
-
return not info.context.read_only
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class HasSecret(BasePermission):
|
|
15
|
-
message = "Application secret is not set"
|
|
16
|
-
|
|
17
|
-
async def has_permission(self, source: Any, info: Info, **kwargs: Any) -> bool:
|
|
18
|
-
return info.context.secret is not None
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from datetime import timedelta
|
|
3
|
-
|
|
4
|
-
import strawberry
|
|
5
|
-
from sqlalchemy import select
|
|
6
|
-
from strawberry.types import Info
|
|
7
|
-
|
|
8
|
-
from phoenix.auth import is_valid_password
|
|
9
|
-
from phoenix.db import models
|
|
10
|
-
from phoenix.server.api.context import Context
|
|
11
|
-
from phoenix.server.api.exceptions import Unauthorized
|
|
12
|
-
from phoenix.server.api.mutations.auth import HasSecret
|
|
13
|
-
|
|
14
|
-
PHOENIX_ACCESS_TOKEN_COOKIE_NAME = "phoenix-access-token"
|
|
15
|
-
PHOENIX_ACCESS_TOKEN_COOKIE_MAX_AGE_IN_SECONDS = int(timedelta(days=31).total_seconds())
|
|
16
|
-
FAILED_LOGIN_MESSAGE = "login failed"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@strawberry.input
|
|
20
|
-
class LoginMutationInput:
|
|
21
|
-
email: str
|
|
22
|
-
password: str
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@strawberry.type
|
|
26
|
-
class AuthMutationMixin:
|
|
27
|
-
@strawberry.mutation(permission_classes=[HasSecret]) # type: ignore
|
|
28
|
-
async def login(
|
|
29
|
-
self,
|
|
30
|
-
info: Info[Context, None],
|
|
31
|
-
input: LoginMutationInput,
|
|
32
|
-
) -> None:
|
|
33
|
-
async with info.context.db() as session:
|
|
34
|
-
if (
|
|
35
|
-
user := await session.scalar(
|
|
36
|
-
select(models.User).where(models.User.email == input.email)
|
|
37
|
-
)
|
|
38
|
-
) is None or (password_hash := user.password_hash) is None:
|
|
39
|
-
raise Unauthorized(FAILED_LOGIN_MESSAGE)
|
|
40
|
-
secret = info.context.get_secret()
|
|
41
|
-
loop = asyncio.get_running_loop()
|
|
42
|
-
if not await loop.run_in_executor(
|
|
43
|
-
executor=None,
|
|
44
|
-
func=lambda: is_valid_password(
|
|
45
|
-
password=input.password, salt=secret, password_hash=password_hash
|
|
46
|
-
),
|
|
47
|
-
):
|
|
48
|
-
raise Unauthorized(FAILED_LOGIN_MESSAGE)
|
|
49
|
-
response = info.context.get_response()
|
|
50
|
-
response.set_cookie(
|
|
51
|
-
key=PHOENIX_ACCESS_TOKEN_COOKIE_NAME,
|
|
52
|
-
value="token", # todo: compute access token
|
|
53
|
-
secure=True,
|
|
54
|
-
httponly=True,
|
|
55
|
-
samesite="strict",
|
|
56
|
-
max_age=PHOENIX_ACCESS_TOKEN_COOKIE_MAX_AGE_IN_SECONDS,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
@strawberry.mutation(permission_classes=[HasSecret]) # type: ignore
|
|
60
|
-
async def logout(
|
|
61
|
-
self,
|
|
62
|
-
info: Info[Context, None],
|
|
63
|
-
) -> None:
|
|
64
|
-
response = info.context.get_response()
|
|
65
|
-
response.delete_cookie(key=PHOENIX_ACCESS_TOKEN_COOKIE_NAME)
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import{r as d,j as e,d5 as P,v as s,F,R as v,w as E,aQ as S,d6 as L,d7 as R,d8 as a,d9 as w,da as z,b as A,db as j}from"./vendor-BC3OPQuM.js";import{S as C,j as k,Z as $,U as _,t as I,a4 as O}from"./vendor-arizeai-NjB3cZzD.js";import{E as T,L as D,a as N,P as G,h as M,M as U,b as m,D as B,d as q,c as J,e as K,f as W,g as H,T as Q,p as V,i as g,j as Y,k as Z,l as u,m as X,n as h,o as b,q as ee,r as re,s as ae,t as te,v as oe,A as ne,S as se,F as le}from"./pages-CnTvEGEN.js";import{b9 as ie,d as ce,R as de,ba as pe,bb as me}from"./components-Dte7_KRd.js";import"./vendor-three-DwGkEfCM.js";import"./vendor-recharts-BXLYwcXF.js";import"./vendor-codemirror-gE_JCOgX.js";(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))c(t);new MutationObserver(t=>{for(const o of t)if(o.type==="childList")for(const l of o.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&c(l)}).observe(document,{childList:!0,subtree:!0});function i(t){const o={};return t.integrity&&(o.integrity=t.integrity),t.referrerPolicy&&(o.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?o.credentials="include":t.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function c(t){if(t.ep)return;t.ep=!0;const o=i(t);fetch(t.href,o)}})();const f="arize-phoenix-feature-flags",p={__CLEAR__:!0};function ge(){const r=localStorage.getItem(f);if(!r)return p;try{const n=JSON.parse(r);return Object.assign({},p,n)}catch{return p}}const x=d.createContext(null);function ue(){const r=v.useContext(x);if(r===null)throw new Error("useFeatureFlags must be used within a FeatureFlagsProvider");return r}function he(r){const[n,i]=d.useState(ge()),c=t=>{localStorage.setItem(f,JSON.stringify(t)),i(t)};return e(x.Provider,{value:{featureFlags:n,setFeatureFlags:c},children:e(be,{children:r.children})})}function be(r){const{children:n}=r,{featureFlags:i,setFeatureFlags:c}=ue(),[t,o]=d.useState(!1);return P("ctrl+shift+f",()=>o(!0)),s(F,{children:[n,e(_,{type:"modal",isDismissable:!0,onDismiss:()=>o(!1),children:t&&e(C,{title:"Feature Flags",children:e(k,{height:"size-1000",padding:"size-100",children:Object.keys(i).map(l=>e($,{isSelected:i[l],onChange:y=>c({...i,[l]:y}),children:l},l))})})})]})}function fe(){return e(S,{styles:r=>E`
|
|
2
|
-
body {
|
|
3
|
-
background-color: var(--ac-global-color-grey-75);
|
|
4
|
-
color: var(--ac-global-text-color-900);
|
|
5
|
-
font-family: "Roboto";
|
|
6
|
-
font-size: ${r.typography.sizes.medium.fontSize}px;
|
|
7
|
-
margin: 0;
|
|
8
|
-
#root,
|
|
9
|
-
#root > div[data-overlay-container="true"],
|
|
10
|
-
#root > div[data-overlay-container="true"] > .ac-theme {
|
|
11
|
-
height: 100vh;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* Remove list styling */
|
|
16
|
-
ul {
|
|
17
|
-
display: block;
|
|
18
|
-
list-style-type: none;
|
|
19
|
-
margin-block-start: none;
|
|
20
|
-
margin-block-end: 0;
|
|
21
|
-
padding-inline-start: 0;
|
|
22
|
-
margin-block-start: 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* A reset style for buttons */
|
|
26
|
-
.button--reset {
|
|
27
|
-
background: none;
|
|
28
|
-
border: none;
|
|
29
|
-
padding: 0;
|
|
30
|
-
}
|
|
31
|
-
/* this css class is added to html via modernizr @see modernizr.js */
|
|
32
|
-
.no-hiddenscroll {
|
|
33
|
-
/* Works on Firefox */
|
|
34
|
-
* {
|
|
35
|
-
scrollbar-width: thin;
|
|
36
|
-
scrollbar-color: var(--ac-global-color-grey-300)
|
|
37
|
-
var(--ac-global-color-grey-400);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Works on Chrome, Edge, and Safari */
|
|
41
|
-
*::-webkit-scrollbar {
|
|
42
|
-
width: 14px;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
*::-webkit-scrollbar-track {
|
|
46
|
-
background: var(--ac-global-color-grey-100);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
*::-webkit-scrollbar-thumb {
|
|
50
|
-
background-color: var(--ac-global-color-grey-75);
|
|
51
|
-
border-radius: 8px;
|
|
52
|
-
border: 1px solid var(--ac-global-color-grey-300);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
:root {
|
|
57
|
-
--px-blue-color: ${r.colors.arizeBlue};
|
|
58
|
-
|
|
59
|
-
--px-flex-gap-sm: ${r.spacing.margin4}px;
|
|
60
|
-
--px-flex-gap-sm: ${r.spacing.margin8}px;
|
|
61
|
-
|
|
62
|
-
--px-section-background-color: ${r.colors.gray500};
|
|
63
|
-
|
|
64
|
-
/* An item is a typically something in a list */
|
|
65
|
-
--px-item-background-color: ${r.colors.gray800};
|
|
66
|
-
--px-item-border-color: ${r.colors.gray600};
|
|
67
|
-
|
|
68
|
-
--px-spacing-sm: ${r.spacing.padding4}px;
|
|
69
|
-
--px-spacing-med: ${r.spacing.padding8}px;
|
|
70
|
-
--px-spacing-lg: ${r.spacing.padding16}px;
|
|
71
|
-
|
|
72
|
-
--px-border-radius-med: ${r.borderRadius.medium}px;
|
|
73
|
-
|
|
74
|
-
--px-font-size-sm: ${r.typography.sizes.small.fontSize}px;
|
|
75
|
-
--px-font-size-med: ${r.typography.sizes.medium.fontSize}px;
|
|
76
|
-
--px-font-size-lg: ${r.typography.sizes.large.fontSize}px;
|
|
77
|
-
|
|
78
|
-
--px-gradient-bar-height: 8px;
|
|
79
|
-
|
|
80
|
-
--px-nav-collapsed-width: 45px;
|
|
81
|
-
--px-nav-expanded-width: 200px;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.ac-theme--dark {
|
|
85
|
-
--px-primary-color: #9efcfd;
|
|
86
|
-
--px-primary-color--transparent: rgb(158, 252, 253, 0.2);
|
|
87
|
-
--px-reference-color: #baa1f9;
|
|
88
|
-
--px-reference-color--transparent: #baa1f982;
|
|
89
|
-
--px-corpus-color: #92969c;
|
|
90
|
-
--px-corpus-color--transparent: #92969c63;
|
|
91
|
-
}
|
|
92
|
-
.ac-theme--light {
|
|
93
|
-
--px-primary-color: #00add0;
|
|
94
|
-
--px-primary-color--transparent: rgba(0, 173, 208, 0.2);
|
|
95
|
-
--px-reference-color: #4500d9;
|
|
96
|
-
--px-reference-color--transparent: rgba(69, 0, 217, 0.2);
|
|
97
|
-
--px-corpus-color: #92969c;
|
|
98
|
-
--px-corpus-color--transparent: #92969c63;
|
|
99
|
-
}
|
|
100
|
-
`})}const xe=L(R(s(a,{path:"/",errorElement:e(T,{}),children:[e(a,{path:"/login",element:e(D,{})}),s(a,{element:e(N,{}),children:[e(a,{path:"/profile",handle:{crumb:()=>"profile"},element:e(G,{})}),e(a,{index:!0,loader:M}),s(a,{path:"/model",handle:{crumb:()=>"model"},element:e(U,{}),children:[e(a,{index:!0,element:e(m,{})}),e(a,{element:e(m,{}),children:e(a,{path:"dimensions",children:e(a,{path:":dimensionId",element:e(B,{}),loader:q})})}),e(a,{path:"embeddings",children:e(a,{path:":embeddingDimensionId",element:e(J,{}),loader:K,handle:{crumb:r=>r.embedding.name}})})]}),s(a,{path:"/projects",handle:{crumb:()=>"projects"},element:e(W,{}),children:[e(a,{index:!0,element:e(H,{})}),s(a,{path:":projectId",element:e(Q,{}),loader:V,handle:{crumb:r=>r.project.name},children:[e(a,{index:!0,element:e(g,{})}),e(a,{element:e(g,{}),children:e(a,{path:"traces/:traceId",element:e(Y,{})})})]})]}),s(a,{path:"/datasets",handle:{crumb:()=>"datasets"},children:[e(a,{index:!0,element:e(Z,{})}),s(a,{path:":datasetId",loader:u,handle:{crumb:r=>r.dataset.name},children:[s(a,{element:e(X,{}),loader:u,children:[e(a,{index:!0,element:e(h,{}),loader:b}),e(a,{path:"experiments",element:e(h,{}),loader:b}),e(a,{path:"examples",element:e(ee,{}),loader:re,children:e(a,{path:":exampleId",element:e(ae,{})})})]}),e(a,{path:"compare",handle:{crumb:()=>"compare"},loader:te,element:e(oe,{})})]})]}),e(a,{path:"/apis",element:e(ne,{}),handle:{crumb:()=>"APIs"}}),e(a,{path:"/settings",element:e(se,{}),handle:{crumb:()=>"Settings"}})]})]})),{basename:window.Config.basename});function ye(){return e(w,{router:xe})}function Pe(){return e(le,{children:e(ie,{children:e(Fe,{})})})}function Fe(){const{theme:r}=ce();return e(O,{theme:r,children:e(z,{theme:I,children:s(A.RelayEnvironmentProvider,{environment:de,children:[e(fe,{}),e(he,{children:e(pe,{children:e(d.Suspense,{children:e(me,{children:e(ye,{})})})})})]})})})}const ve=document.getElementById("root"),Ee=j.createRoot(ve);Ee.render(e(Pe,{}));
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from importlib.metadata import PackageNotFoundError
|
|
3
|
-
from importlib.util import find_spec
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from openinference.instrumentation.langchain import LangChainInstrumentor as Instrumentor
|
|
7
|
-
from openinference.semconv.resource import ResourceAttributes
|
|
8
|
-
from opentelemetry.sdk import trace as trace_sdk
|
|
9
|
-
from opentelemetry.sdk.resources import Resource
|
|
10
|
-
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
11
|
-
|
|
12
|
-
from phoenix.config import get_env_project_name
|
|
13
|
-
from phoenix.trace.exporter import _OpenInferenceExporter
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
logger.addHandler(logging.NullHandler())
|
|
17
|
-
|
|
18
|
-
__all__ = ("LangChainInstrumentor",)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class LangChainInstrumentor(Instrumentor):
|
|
22
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
23
|
-
if find_spec("langchain_core") is None:
|
|
24
|
-
raise PackageNotFoundError(
|
|
25
|
-
"Missing `langchain-core`. Install with `pip install langchain-core`."
|
|
26
|
-
)
|
|
27
|
-
super().__init__()
|
|
28
|
-
|
|
29
|
-
def instrument(self) -> None:
|
|
30
|
-
tracer_provider = trace_sdk.TracerProvider(
|
|
31
|
-
resource=Resource({ResourceAttributes.PROJECT_NAME: get_env_project_name()}),
|
|
32
|
-
span_limits=trace_sdk.SpanLimits(max_attributes=10_000),
|
|
33
|
-
)
|
|
34
|
-
tracer_provider.add_span_processor(SimpleSpanProcessor(_OpenInferenceExporter()))
|
|
35
|
-
super().instrument(skip_dep_check=True, tracer_provider=tracer_provider)
|