arize-phoenix 3.25.0__py3-none-any.whl → 4.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-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/METADATA +26 -4
- {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/RECORD +80 -75
- phoenix/__init__.py +9 -5
- phoenix/config.py +109 -53
- phoenix/datetime_utils.py +18 -1
- phoenix/db/README.md +25 -0
- phoenix/db/__init__.py +4 -0
- phoenix/db/alembic.ini +119 -0
- phoenix/db/bulk_inserter.py +206 -0
- phoenix/db/engines.py +152 -0
- phoenix/db/helpers.py +47 -0
- phoenix/db/insertion/evaluation.py +209 -0
- phoenix/db/insertion/helpers.py +51 -0
- phoenix/db/insertion/span.py +142 -0
- phoenix/db/migrate.py +71 -0
- phoenix/db/migrations/env.py +121 -0
- phoenix/db/migrations/script.py.mako +26 -0
- phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
- phoenix/db/models.py +371 -0
- phoenix/exceptions.py +5 -1
- phoenix/server/api/context.py +40 -3
- phoenix/server/api/dataloaders/__init__.py +97 -0
- phoenix/server/api/dataloaders/cache/__init__.py +3 -0
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +67 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +152 -0
- phoenix/server/api/dataloaders/document_evaluations.py +37 -0
- phoenix/server/api/dataloaders/document_retrieval_metrics.py +98 -0
- phoenix/server/api/dataloaders/evaluation_summaries.py +151 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +198 -0
- phoenix/server/api/dataloaders/min_start_or_max_end_times.py +93 -0
- phoenix/server/api/dataloaders/record_counts.py +125 -0
- phoenix/server/api/dataloaders/span_descendants.py +64 -0
- phoenix/server/api/dataloaders/span_evaluations.py +37 -0
- phoenix/server/api/dataloaders/token_counts.py +138 -0
- phoenix/server/api/dataloaders/trace_evaluations.py +37 -0
- phoenix/server/api/input_types/SpanSort.py +138 -68
- phoenix/server/api/routers/v1/__init__.py +11 -0
- phoenix/server/api/routers/v1/evaluations.py +275 -0
- phoenix/server/api/routers/v1/spans.py +126 -0
- phoenix/server/api/routers/v1/traces.py +82 -0
- phoenix/server/api/schema.py +112 -48
- phoenix/server/api/types/DocumentEvaluationSummary.py +1 -1
- phoenix/server/api/types/Evaluation.py +29 -12
- phoenix/server/api/types/EvaluationSummary.py +29 -44
- phoenix/server/api/types/MimeType.py +2 -2
- phoenix/server/api/types/Model.py +9 -9
- phoenix/server/api/types/Project.py +240 -171
- phoenix/server/api/types/Span.py +87 -131
- phoenix/server/api/types/Trace.py +29 -20
- phoenix/server/api/types/pagination.py +151 -10
- phoenix/server/app.py +263 -35
- phoenix/server/grpc_server.py +93 -0
- phoenix/server/main.py +75 -60
- phoenix/server/openapi/docs.py +218 -0
- phoenix/server/prometheus.py +23 -7
- phoenix/server/static/index.js +662 -643
- phoenix/server/telemetry.py +68 -0
- phoenix/services.py +4 -0
- phoenix/session/client.py +34 -30
- phoenix/session/data_extractor.py +8 -3
- phoenix/session/session.py +176 -155
- phoenix/settings.py +13 -0
- phoenix/trace/attributes.py +349 -0
- phoenix/trace/dsl/README.md +116 -0
- phoenix/trace/dsl/filter.py +660 -192
- phoenix/trace/dsl/helpers.py +24 -5
- phoenix/trace/dsl/query.py +562 -185
- phoenix/trace/fixtures.py +69 -7
- phoenix/trace/otel.py +44 -200
- phoenix/trace/schemas.py +14 -8
- phoenix/trace/span_evaluations.py +5 -2
- phoenix/utilities/__init__.py +0 -26
- phoenix/utilities/span_store.py +0 -23
- phoenix/version.py +1 -1
- phoenix/core/project.py +0 -773
- phoenix/core/traces.py +0 -96
- phoenix/datasets/dataset.py +0 -214
- phoenix/datasets/fixtures.py +0 -24
- phoenix/datasets/schema.py +0 -31
- phoenix/experimental/evals/__init__.py +0 -73
- phoenix/experimental/evals/evaluators.py +0 -413
- phoenix/experimental/evals/functions/__init__.py +0 -4
- phoenix/experimental/evals/functions/classify.py +0 -453
- phoenix/experimental/evals/functions/executor.py +0 -353
- phoenix/experimental/evals/functions/generate.py +0 -138
- phoenix/experimental/evals/functions/processing.py +0 -76
- phoenix/experimental/evals/models/__init__.py +0 -14
- phoenix/experimental/evals/models/anthropic.py +0 -175
- phoenix/experimental/evals/models/base.py +0 -170
- phoenix/experimental/evals/models/bedrock.py +0 -221
- phoenix/experimental/evals/models/litellm.py +0 -134
- phoenix/experimental/evals/models/openai.py +0 -453
- phoenix/experimental/evals/models/rate_limiters.py +0 -246
- phoenix/experimental/evals/models/vertex.py +0 -173
- phoenix/experimental/evals/models/vertexai.py +0 -186
- phoenix/experimental/evals/retrievals.py +0 -96
- phoenix/experimental/evals/templates/__init__.py +0 -50
- phoenix/experimental/evals/templates/default_templates.py +0 -472
- phoenix/experimental/evals/templates/template.py +0 -195
- phoenix/experimental/evals/utils/__init__.py +0 -172
- phoenix/experimental/evals/utils/threads.py +0 -27
- phoenix/server/api/routers/evaluation_handler.py +0 -110
- phoenix/server/api/routers/span_handler.py +0 -70
- phoenix/server/api/routers/trace_handler.py +0 -60
- phoenix/storage/span_store/__init__.py +0 -23
- phoenix/storage/span_store/text_file.py +0 -85
- phoenix/trace/dsl/missing.py +0 -60
- {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/WHEEL +0 -0
- {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/licenses/LICENSE +0 -0
- /phoenix/{datasets → db/insertion}/__init__.py +0 -0
- /phoenix/{experimental → db/migrations}/__init__.py +0 -0
- /phoenix/{storage → server/openapi}/__init__.py +0 -0
phoenix/server/main.py
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import atexit
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
import warnings
|
|
4
5
|
from argparse import ArgumentParser
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from random import random
|
|
7
7
|
from threading import Thread
|
|
8
8
|
from time import sleep, time
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import List, Optional
|
|
10
10
|
|
|
11
11
|
import pkg_resources
|
|
12
12
|
from uvicorn import Config, Server
|
|
13
13
|
|
|
14
|
+
import phoenix.trace.v1 as pb
|
|
14
15
|
from phoenix.config import (
|
|
15
16
|
EXPORT_DIR,
|
|
17
|
+
get_env_database_connection_str,
|
|
18
|
+
get_env_enable_prometheus,
|
|
19
|
+
get_env_grpc_port,
|
|
16
20
|
get_env_host,
|
|
17
21
|
get_env_port,
|
|
18
22
|
get_pids_path,
|
|
23
|
+
get_working_dir,
|
|
19
24
|
)
|
|
20
25
|
from phoenix.core.model_schema_adapter import create_model_from_datasets
|
|
21
|
-
from phoenix.
|
|
26
|
+
from phoenix.db import get_printable_db_url
|
|
22
27
|
from phoenix.inferences.fixtures import FIXTURES, get_datasets
|
|
23
28
|
from phoenix.inferences.inferences import EMPTY_INFERENCES, Inferences
|
|
24
29
|
from phoenix.pointcloud.umap_parameters import (
|
|
@@ -28,16 +33,17 @@ from phoenix.pointcloud.umap_parameters import (
|
|
|
28
33
|
UMAPParameters,
|
|
29
34
|
)
|
|
30
35
|
from phoenix.server.app import create_app
|
|
31
|
-
from phoenix.
|
|
36
|
+
from phoenix.settings import Settings
|
|
32
37
|
from phoenix.trace.fixtures import (
|
|
33
38
|
TRACES_FIXTURES,
|
|
34
|
-
|
|
35
|
-
_get_trace_fixture_by_name,
|
|
39
|
+
download_traces_fixture,
|
|
36
40
|
get_evals_from_fixture,
|
|
41
|
+
get_trace_fixture_by_name,
|
|
42
|
+
reset_fixture_span_ids_and_timestamps,
|
|
37
43
|
)
|
|
38
44
|
from phoenix.trace.otel import decode_otlp_span, encode_span_to_otlp
|
|
45
|
+
from phoenix.trace.schemas import Span
|
|
39
46
|
from phoenix.trace.span_json_decoder import json_string_to_span
|
|
40
|
-
from phoenix.utilities.span_store import get_span_store, load_traces_data_from_store
|
|
41
47
|
|
|
42
48
|
logger = logging.getLogger(__name__)
|
|
43
49
|
|
|
@@ -48,7 +54,7 @@ _WELCOME_MESSAGE = """
|
|
|
48
54
|
██████╔╝███████║██║ ██║█████╗ ██╔██╗ ██║██║ ╚███╔╝
|
|
49
55
|
██╔═══╝ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔██╗
|
|
50
56
|
██║ ██║ ██║╚██████╔╝███████╗██║ ╚████║██║██╔╝ ██╗
|
|
51
|
-
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{
|
|
57
|
+
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝ v{version}
|
|
52
58
|
|
|
53
59
|
|
|
|
54
60
|
| 🌎 Join our Community 🌎
|
|
@@ -61,9 +67,11 @@ _WELCOME_MESSAGE = """
|
|
|
61
67
|
| https://docs.arize.com/phoenix
|
|
62
68
|
|
|
|
63
69
|
| 🚀 Phoenix Server 🚀
|
|
64
|
-
| Phoenix UI: http://{
|
|
65
|
-
| Log traces:
|
|
66
|
-
|
|
|
70
|
+
| Phoenix UI: http://{host}:{port}
|
|
71
|
+
| Log traces:
|
|
72
|
+
| - gRPC: http://{host}:{grpc_port}
|
|
73
|
+
| - HTTP: http://{host}:{port}/v1/traces
|
|
74
|
+
| Storage: {storage}
|
|
67
75
|
"""
|
|
68
76
|
|
|
69
77
|
|
|
@@ -88,24 +96,6 @@ def _get_pid_file() -> Path:
|
|
|
88
96
|
return get_pids_path() / str(os.getpid())
|
|
89
97
|
|
|
90
98
|
|
|
91
|
-
_Item = TypeVar("_Item", contravariant=True)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class _SupportsPut(Protocol[_Item]):
|
|
95
|
-
def put(self, item: _Item) -> None: ...
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _load_items(
|
|
99
|
-
queue: _SupportsPut[_Item],
|
|
100
|
-
items: Iterable[_Item],
|
|
101
|
-
simulate_streaming: Optional[bool] = False,
|
|
102
|
-
) -> None:
|
|
103
|
-
for item in items:
|
|
104
|
-
if simulate_streaming:
|
|
105
|
-
sleep(random())
|
|
106
|
-
queue.put(item)
|
|
107
|
-
|
|
108
|
-
|
|
109
99
|
DEFAULT_UMAP_PARAMS_STR = f"{DEFAULT_MIN_DIST},{DEFAULT_N_NEIGHBORS},{DEFAULT_N_SAMPLES}"
|
|
110
100
|
|
|
111
101
|
if __name__ == "__main__":
|
|
@@ -118,10 +108,14 @@ if __name__ == "__main__":
|
|
|
118
108
|
reference_dataset: Optional[Inferences] = None
|
|
119
109
|
corpus_dataset: Optional[Inferences] = None
|
|
120
110
|
|
|
111
|
+
# Initialize the settings for the Server
|
|
112
|
+
Settings.log_migrations = True
|
|
113
|
+
|
|
121
114
|
# automatically remove the pid file when the process is being gracefully terminated
|
|
122
115
|
atexit.register(_remove_pid_file)
|
|
123
116
|
|
|
124
117
|
parser = ArgumentParser()
|
|
118
|
+
parser.add_argument("--database-url", required=False)
|
|
125
119
|
parser.add_argument("--export_path")
|
|
126
120
|
parser.add_argument("--host", type=str, required=False)
|
|
127
121
|
parser.add_argument("--port", type=int, required=False)
|
|
@@ -152,8 +146,10 @@ if __name__ == "__main__":
|
|
|
152
146
|
)
|
|
153
147
|
demo_parser.add_argument("--simulate-streaming", action="store_true")
|
|
154
148
|
args = parser.parse_args()
|
|
149
|
+
db_connection_str = (
|
|
150
|
+
args.database_url if args.database_url else get_env_database_connection_str()
|
|
151
|
+
)
|
|
155
152
|
export_path = Path(args.export_path) if args.export_path else EXPORT_DIR
|
|
156
|
-
span_store: Optional[SpanStore] = None
|
|
157
153
|
if args.command == "datasets":
|
|
158
154
|
primary_dataset_name = args.primary
|
|
159
155
|
reference_dataset_name = args.reference
|
|
@@ -189,33 +185,38 @@ if __name__ == "__main__":
|
|
|
189
185
|
trace_dataset_name = args.trace_fixture
|
|
190
186
|
simulate_streaming = args.simulate_streaming
|
|
191
187
|
|
|
188
|
+
host: Optional[str] = args.host or get_env_host()
|
|
189
|
+
display_host = host or "localhost"
|
|
190
|
+
# If the host is "::", the convention is to bind to all interfaces. However, uvicorn
|
|
191
|
+
# does not support this directly unless the host is set to None.
|
|
192
|
+
if host and ":" in host:
|
|
193
|
+
# format IPv6 hosts in brackets
|
|
194
|
+
display_host = f"[{host}]"
|
|
195
|
+
if host == "::":
|
|
196
|
+
# TODO(dustin): why is this necessary? it's not type compliant
|
|
197
|
+
host = None
|
|
198
|
+
|
|
199
|
+
port = args.port or get_env_port()
|
|
200
|
+
|
|
192
201
|
model = create_model_from_datasets(
|
|
193
202
|
primary_dataset,
|
|
194
203
|
reference_dataset,
|
|
195
204
|
)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
|
|
206
|
+
fixture_spans: List[Span] = []
|
|
207
|
+
fixture_evals: List[pb.Evaluation] = []
|
|
199
208
|
if trace_dataset_name is not None:
|
|
200
|
-
fixture_spans =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
fixture_spans, fixture_evals = reset_fixture_span_ids_and_timestamps(
|
|
210
|
+
(
|
|
211
|
+
# Apply `encode` here because legacy jsonl files contains UUIDs as strings.
|
|
212
|
+
# `encode` removes the hyphens in the UUIDs.
|
|
213
|
+
decode_otlp_span(encode_span_to_otlp(json_string_to_span(json_span)))
|
|
214
|
+
for json_span in download_traces_fixture(
|
|
215
|
+
get_trace_fixture_by_name(trace_dataset_name)
|
|
216
|
+
)
|
|
217
|
+
),
|
|
218
|
+
get_evals_from_fixture(trace_dataset_name),
|
|
207
219
|
)
|
|
208
|
-
Thread(
|
|
209
|
-
target=_load_items,
|
|
210
|
-
args=(traces, fixture_spans, simulate_streaming),
|
|
211
|
-
daemon=True,
|
|
212
|
-
).start()
|
|
213
|
-
fixture_evals = list(get_evals_from_fixture(trace_dataset_name))
|
|
214
|
-
Thread(
|
|
215
|
-
target=_load_items,
|
|
216
|
-
args=(traces, fixture_evals, simulate_streaming),
|
|
217
|
-
daemon=True,
|
|
218
|
-
).start()
|
|
219
220
|
umap_params_list = args.umap_params.split(",")
|
|
220
221
|
umap_params = UMAPParameters(
|
|
221
222
|
min_dist=float(umap_params_list[0]),
|
|
@@ -224,31 +225,45 @@ if __name__ == "__main__":
|
|
|
224
225
|
)
|
|
225
226
|
read_only = args.read_only
|
|
226
227
|
logger.info(f"Server umap params: {umap_params}")
|
|
227
|
-
if enable_prometheus := args.enable_prometheus:
|
|
228
|
+
if enable_prometheus := (get_env_enable_prometheus() or args.enable_prometheus):
|
|
229
|
+
if args.enable_prometheus:
|
|
230
|
+
warnings.warn(
|
|
231
|
+
"The --enable-prometheus command line argument is being deprecated "
|
|
232
|
+
"and will be removed in an upcoming release. "
|
|
233
|
+
"Please set the PHOENIX_ENABLE_PROMETHEUS environment variable to TRUE.",
|
|
234
|
+
DeprecationWarning,
|
|
235
|
+
stacklevel=2,
|
|
236
|
+
)
|
|
228
237
|
from phoenix.server.prometheus import start_prometheus
|
|
229
238
|
|
|
230
239
|
start_prometheus()
|
|
240
|
+
|
|
241
|
+
working_dir = get_working_dir().resolve()
|
|
231
242
|
app = create_app(
|
|
243
|
+
database_url=db_connection_str,
|
|
232
244
|
export_path=export_path,
|
|
233
245
|
model=model,
|
|
234
246
|
umap_params=umap_params,
|
|
235
|
-
traces=traces,
|
|
236
247
|
corpus=None if corpus_dataset is None else create_model_from_datasets(corpus_dataset),
|
|
237
248
|
debug=args.debug,
|
|
238
249
|
read_only=read_only,
|
|
239
|
-
span_store=span_store,
|
|
240
250
|
enable_prometheus=enable_prometheus,
|
|
251
|
+
initial_spans=fixture_spans,
|
|
252
|
+
initial_evaluations=fixture_evals,
|
|
241
253
|
)
|
|
242
|
-
|
|
243
|
-
port = args.port or get_env_port()
|
|
244
|
-
server = Server(config=Config(app, host=host, port=port))
|
|
254
|
+
server = Server(config=Config(app, host=host, port=port)) # type: ignore
|
|
245
255
|
Thread(target=_write_pid_file_when_ready, args=(server,), daemon=True).start()
|
|
246
256
|
|
|
247
257
|
# Print information about the server
|
|
248
258
|
phoenix_version = pkg_resources.get_distribution("arize-phoenix").version
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
259
|
+
config = {
|
|
260
|
+
"version": phoenix_version,
|
|
261
|
+
"host": display_host,
|
|
262
|
+
"port": port,
|
|
263
|
+
"grpc_port": get_env_grpc_port(),
|
|
264
|
+
"storage": get_printable_db_url(db_connection_str),
|
|
265
|
+
}
|
|
266
|
+
print(_WELCOME_MESSAGE.format(**config))
|
|
252
267
|
|
|
253
268
|
# Start the server
|
|
254
269
|
server.run()
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
from starlette.responses import HTMLResponse
|
|
5
|
+
|
|
6
|
+
swagger_ui_default_parameters: Dict[str, Any] = {
|
|
7
|
+
"dom_id": "#swagger-ui",
|
|
8
|
+
"layout": "BaseLayout",
|
|
9
|
+
"deepLinking": True,
|
|
10
|
+
"showExtensions": True,
|
|
11
|
+
"showCommonExtensions": True,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_swagger_ui_html(
|
|
16
|
+
*,
|
|
17
|
+
openapi_url: str = "/schema",
|
|
18
|
+
title: str,
|
|
19
|
+
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js",
|
|
20
|
+
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css",
|
|
21
|
+
swagger_favicon_url: str = "/favicon.ico",
|
|
22
|
+
oauth2_redirect_url: Optional[str] = None,
|
|
23
|
+
init_oauth: Optional[str] = None,
|
|
24
|
+
swagger_ui_parameters: Optional[Dict[str, Any]] = None,
|
|
25
|
+
) -> HTMLResponse:
|
|
26
|
+
"""
|
|
27
|
+
Generate and return the HTML that loads Swagger UI for the interactive API
|
|
28
|
+
docs (normally served at `/docs`).
|
|
29
|
+
"""
|
|
30
|
+
current_swagger_ui_parameters = swagger_ui_default_parameters.copy()
|
|
31
|
+
if swagger_ui_parameters:
|
|
32
|
+
current_swagger_ui_parameters.update(swagger_ui_parameters)
|
|
33
|
+
|
|
34
|
+
html = f"""
|
|
35
|
+
<!DOCTYPE html>
|
|
36
|
+
<html>
|
|
37
|
+
<head>
|
|
38
|
+
<link type="text/css" rel="stylesheet" href="{swagger_css_url}">
|
|
39
|
+
<link rel="shortcut icon" href="{swagger_favicon_url}">
|
|
40
|
+
<title>{title}</title>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div id="swagger-ui">
|
|
44
|
+
</div>
|
|
45
|
+
<script src="{swagger_js_url}"></script>
|
|
46
|
+
<!-- `SwaggerUIBundle` is now available on the page -->
|
|
47
|
+
<script>
|
|
48
|
+
const ui = SwaggerUIBundle({{
|
|
49
|
+
url: '{openapi_url}',
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
for key, value in current_swagger_ui_parameters.items():
|
|
53
|
+
html += f"{json.dumps(key)}: {json.dumps(value)},\n"
|
|
54
|
+
|
|
55
|
+
if oauth2_redirect_url:
|
|
56
|
+
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
|
|
57
|
+
|
|
58
|
+
html += """
|
|
59
|
+
presets: [
|
|
60
|
+
SwaggerUIBundle.presets.apis,
|
|
61
|
+
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
62
|
+
],
|
|
63
|
+
})"""
|
|
64
|
+
|
|
65
|
+
if init_oauth:
|
|
66
|
+
html += f"""
|
|
67
|
+
ui.initOAuth({json.dumps(init_oauth)})
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
html += """
|
|
71
|
+
</script>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
74
|
+
"""
|
|
75
|
+
return HTMLResponse(html)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_redoc_html(
|
|
79
|
+
*,
|
|
80
|
+
openapi_url: str,
|
|
81
|
+
title: str,
|
|
82
|
+
redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
|
|
83
|
+
redoc_favicon_url: str = "/favicon.ico",
|
|
84
|
+
with_google_fonts: bool = True,
|
|
85
|
+
) -> HTMLResponse:
|
|
86
|
+
"""
|
|
87
|
+
Generate and return the HTML response that loads ReDoc for the alternative
|
|
88
|
+
API docs (normally served at `/redoc`).
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
html = f"""
|
|
93
|
+
<!DOCTYPE html>
|
|
94
|
+
<html>
|
|
95
|
+
<head>
|
|
96
|
+
<title>{title}</title>
|
|
97
|
+
<!-- needed for adaptive design -->
|
|
98
|
+
<meta charset="utf-8"/>
|
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
100
|
+
"""
|
|
101
|
+
if with_google_fonts:
|
|
102
|
+
html += """
|
|
103
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
104
|
+
""" # noqa: E501
|
|
105
|
+
html += f"""
|
|
106
|
+
<link rel="shortcut icon" href="{redoc_favicon_url}">
|
|
107
|
+
<!--
|
|
108
|
+
ReDoc doesn't change outer page styles
|
|
109
|
+
-->
|
|
110
|
+
<style>
|
|
111
|
+
body {{
|
|
112
|
+
margin: 0;
|
|
113
|
+
padding: 0;
|
|
114
|
+
}}
|
|
115
|
+
</style>
|
|
116
|
+
</head>
|
|
117
|
+
<body>
|
|
118
|
+
<noscript>
|
|
119
|
+
ReDoc requires Javascript to function. Please enable it to browse the documentation.
|
|
120
|
+
</noscript>
|
|
121
|
+
<redoc spec-url="{openapi_url}"></redoc>
|
|
122
|
+
<script src="{redoc_js_url}"> </script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
125
|
+
"""
|
|
126
|
+
return HTMLResponse(html)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Not needed now but copy-pasting for future reference
|
|
130
|
+
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
|
|
131
|
+
"""
|
|
132
|
+
Generate the HTML response with the OAuth2 redirection for Swagger UI.
|
|
133
|
+
|
|
134
|
+
You normally don't need to use or change this.
|
|
135
|
+
"""
|
|
136
|
+
# copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html
|
|
137
|
+
html = """
|
|
138
|
+
<!doctype html>
|
|
139
|
+
<html lang="en-US">
|
|
140
|
+
<head>
|
|
141
|
+
<title>Swagger UI: OAuth2 Redirect</title>
|
|
142
|
+
</head>
|
|
143
|
+
<body>
|
|
144
|
+
<script>
|
|
145
|
+
'use strict';
|
|
146
|
+
function run () {
|
|
147
|
+
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
|
148
|
+
var sentState = oauth2.state;
|
|
149
|
+
var redirectUrl = oauth2.redirectUrl;
|
|
150
|
+
var isValid, qp, arr;
|
|
151
|
+
|
|
152
|
+
if (/code|token|error/.test(window.location.hash)) {
|
|
153
|
+
qp = window.location.hash.substring(1).replace('?', '&');
|
|
154
|
+
} else {
|
|
155
|
+
qp = location.search.substring(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
arr = qp.split("&");
|
|
159
|
+
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
|
160
|
+
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
|
161
|
+
function (key, value) {
|
|
162
|
+
return key === "" ? value : decodeURIComponent(value);
|
|
163
|
+
}
|
|
164
|
+
) : {};
|
|
165
|
+
|
|
166
|
+
isValid = qp.state === sentState;
|
|
167
|
+
|
|
168
|
+
if ((
|
|
169
|
+
oauth2.auth.schema.get("flow") === "accessCode" ||
|
|
170
|
+
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
|
171
|
+
oauth2.auth.schema.get("flow") === "authorization_code"
|
|
172
|
+
) && !oauth2.auth.code) {
|
|
173
|
+
if (!isValid) {
|
|
174
|
+
oauth2.errCb({
|
|
175
|
+
authId: oauth2.auth.name,
|
|
176
|
+
source: "auth",
|
|
177
|
+
level: "warning",
|
|
178
|
+
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (qp.code) {
|
|
183
|
+
delete oauth2.state;
|
|
184
|
+
oauth2.auth.code = qp.code;
|
|
185
|
+
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
|
186
|
+
} else {
|
|
187
|
+
let oauthErrorMsg;
|
|
188
|
+
if (qp.error) {
|
|
189
|
+
oauthErrorMsg = "["+qp.error+"]: " +
|
|
190
|
+
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
|
191
|
+
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
oauth2.errCb({
|
|
195
|
+
authId: oauth2.auth.name,
|
|
196
|
+
source: "auth",
|
|
197
|
+
level: "error",
|
|
198
|
+
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
|
203
|
+
}
|
|
204
|
+
window.close();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (document.readyState !== 'loading') {
|
|
208
|
+
run();
|
|
209
|
+
} else {
|
|
210
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
211
|
+
run();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
</script>
|
|
215
|
+
</body>
|
|
216
|
+
</html>
|
|
217
|
+
""" # noqa: E501
|
|
218
|
+
return HTMLResponse(content=html)
|
phoenix/server/prometheus.py
CHANGED
|
@@ -5,7 +5,7 @@ import psutil
|
|
|
5
5
|
from prometheus_client import (
|
|
6
6
|
Counter,
|
|
7
7
|
Gauge,
|
|
8
|
-
|
|
8
|
+
Summary,
|
|
9
9
|
start_http_server,
|
|
10
10
|
)
|
|
11
11
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
@@ -13,9 +13,9 @@ from starlette.requests import Request
|
|
|
13
13
|
from starlette.responses import Response
|
|
14
14
|
from starlette.routing import Match
|
|
15
15
|
|
|
16
|
-
REQUESTS_PROCESSING_TIME =
|
|
17
|
-
name="
|
|
18
|
-
documentation="
|
|
16
|
+
REQUESTS_PROCESSING_TIME = Summary(
|
|
17
|
+
name="starlette_requests_processing_time_seconds_summary",
|
|
18
|
+
documentation="Summary of requests processing time by method and path (in seconds)",
|
|
19
19
|
labelnames=["method", "path"],
|
|
20
20
|
)
|
|
21
21
|
EXCEPTIONS = Counter(
|
|
@@ -33,6 +33,22 @@ CPU_METRIC = Gauge(
|
|
|
33
33
|
documentation="CPU usage percent",
|
|
34
34
|
labelnames=["core"],
|
|
35
35
|
)
|
|
36
|
+
BULK_LOADER_INSERTION_TIME = Summary(
|
|
37
|
+
name="bulk_loader_insertion_time_seconds_summary",
|
|
38
|
+
documentation="Summary of database insertion time (seconds)",
|
|
39
|
+
)
|
|
40
|
+
BULK_LOADER_SPAN_INSERTIONS = Counter(
|
|
41
|
+
name="bulk_loader_span_insertions_total",
|
|
42
|
+
documentation="Total count of bulk loader span insertions",
|
|
43
|
+
)
|
|
44
|
+
BULK_LOADER_EVALUATION_INSERTIONS = Counter(
|
|
45
|
+
name="bulk_loader_evaluation_insertions_total",
|
|
46
|
+
documentation="Total count of bulk loader evaluation insertions",
|
|
47
|
+
)
|
|
48
|
+
BULK_LOADER_EXCEPTIONS = Counter(
|
|
49
|
+
name="bulk_loader_exceptions_total",
|
|
50
|
+
documentation="Total count of bulk loader exceptions",
|
|
51
|
+
)
|
|
36
52
|
|
|
37
53
|
|
|
38
54
|
class PrometheusMiddleware(BaseHTTPMiddleware):
|
|
@@ -51,14 +67,14 @@ class PrometheusMiddleware(BaseHTTPMiddleware):
|
|
|
51
67
|
except BaseException as e:
|
|
52
68
|
EXCEPTIONS.labels(method=method, path=path, exception_type=type(e).__name__).inc()
|
|
53
69
|
raise
|
|
54
|
-
|
|
55
|
-
REQUESTS_PROCESSING_TIME.labels(method=method, path=path).observe(
|
|
70
|
+
end_time = time.perf_counter()
|
|
71
|
+
REQUESTS_PROCESSING_TIME.labels(method=method, path=path).observe(end_time - start_time)
|
|
56
72
|
return response
|
|
57
73
|
|
|
58
74
|
|
|
59
75
|
def start_prometheus() -> None:
|
|
60
76
|
Thread(target=gather_system_data, daemon=True).start()
|
|
61
|
-
start_http_server(9090)
|
|
77
|
+
start_http_server(9090, addr="::")
|
|
62
78
|
|
|
63
79
|
|
|
64
80
|
def gather_system_data() -> None:
|