arize-phoenix 5.10.0__py3-none-any.whl → 5.11.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-5.10.0.dist-info → arize_phoenix-5.11.0.dist-info}/METADATA +2 -1
- {arize_phoenix-5.10.0.dist-info → arize_phoenix-5.11.0.dist-info}/RECORD +23 -23
- phoenix/config.py +13 -0
- phoenix/db/facilitator.py +3 -2
- phoenix/server/api/helpers/playground_clients.py +29 -13
- phoenix/server/api/helpers/playground_spans.py +6 -0
- phoenix/server/api/mutations/chat_mutations.py +75 -34
- phoenix/server/api/subscriptions.py +108 -49
- phoenix/server/static/.vite/manifest.json +31 -31
- phoenix/server/static/assets/{components-BXIz9ZO8.js → components-C_HASv83.js} +90 -90
- phoenix/server/static/assets/{index-DTut7g1y.js → index-D7UiCRtr.js} +2 -2
- phoenix/server/static/assets/{pages-B8FpJuXu.js → pages-DYHcAdjT.js} +313 -264
- phoenix/server/static/assets/{vendor-BX8_Znqy.js → vendor-BCxsh5i3.js} +150 -150
- phoenix/server/static/assets/{vendor-arizeai-CtHir-Ua.js → vendor-arizeai-C2CDZgMz.js} +28 -28
- phoenix/server/static/assets/{vendor-codemirror-DLlGiguX.js → vendor-codemirror-DYbtnCTn.js} +1 -1
- phoenix/server/static/assets/{vendor-recharts-CJRple0d.js → vendor-recharts-P6W8G0Mb.js} +1 -1
- phoenix/trace/span_evaluations.py +4 -3
- phoenix/utilities/json.py +7 -1
- phoenix/version.py +1 -1
- {arize_phoenix-5.10.0.dist-info → arize_phoenix-5.11.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-5.10.0.dist-info → arize_phoenix-5.11.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-5.10.0.dist-info → arize_phoenix-5.11.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-5.10.0.dist-info → arize_phoenix-5.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,9 +2,10 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
from asyncio import FIRST_COMPLETED, Queue, QueueEmpty, Task, create_task, wait, wait_for
|
|
4
4
|
from collections.abc import AsyncIterator, Iterator
|
|
5
|
-
from datetime import datetime, timezone
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
6
|
from typing import (
|
|
7
7
|
Any,
|
|
8
|
+
AsyncGenerator,
|
|
8
9
|
Iterable,
|
|
9
10
|
Mapping,
|
|
10
11
|
Optional,
|
|
@@ -24,7 +25,7 @@ from typing_extensions import TypeAlias, assert_never
|
|
|
24
25
|
from phoenix.datetime_utils import local_now, normalize_datetime
|
|
25
26
|
from phoenix.db import models
|
|
26
27
|
from phoenix.server.api.context import Context
|
|
27
|
-
from phoenix.server.api.exceptions import BadRequest, NotFound
|
|
28
|
+
from phoenix.server.api.exceptions import BadRequest, CustomGraphQLError, NotFound
|
|
28
29
|
from phoenix.server.api.helpers.playground_clients import (
|
|
29
30
|
PlaygroundStreamingClient,
|
|
30
31
|
initialize_playground_clients,
|
|
@@ -79,6 +80,7 @@ DatasetExampleID: TypeAlias = GlobalID
|
|
|
79
80
|
ChatCompletionResult: TypeAlias = tuple[
|
|
80
81
|
DatasetExampleID, Optional[models.Span], models.ExperimentRun
|
|
81
82
|
]
|
|
83
|
+
ChatStream: TypeAlias = AsyncGenerator[ChatCompletionSubscriptionPayload, None]
|
|
82
84
|
PLAYGROUND_PROJECT_NAME = "playground"
|
|
83
85
|
|
|
84
86
|
|
|
@@ -91,11 +93,19 @@ class Subscription:
|
|
|
91
93
|
provider_key = input.model.provider_key
|
|
92
94
|
llm_client_class = PLAYGROUND_CLIENT_REGISTRY.get_client(provider_key, input.model.name)
|
|
93
95
|
if llm_client_class is None:
|
|
94
|
-
raise BadRequest(f"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
raise BadRequest(f"Unknown LLM provider: '{provider_key.value}'")
|
|
97
|
+
try:
|
|
98
|
+
llm_client = llm_client_class(
|
|
99
|
+
model=input.model,
|
|
100
|
+
api_key=input.api_key,
|
|
101
|
+
)
|
|
102
|
+
except CustomGraphQLError:
|
|
103
|
+
raise
|
|
104
|
+
except Exception as error:
|
|
105
|
+
raise BadRequest(
|
|
106
|
+
f"Failed to connect to LLM API for {provider_key.value} {input.model.name}: "
|
|
107
|
+
f"{str(error)}"
|
|
108
|
+
)
|
|
99
109
|
|
|
100
110
|
messages = [
|
|
101
111
|
(
|
|
@@ -158,7 +168,19 @@ class Subscription:
|
|
|
158
168
|
provider_key = input.model.provider_key
|
|
159
169
|
llm_client_class = PLAYGROUND_CLIENT_REGISTRY.get_client(provider_key, input.model.name)
|
|
160
170
|
if llm_client_class is None:
|
|
161
|
-
raise BadRequest(f"
|
|
171
|
+
raise BadRequest(f"Unknown LLM provider: '{provider_key.value}'")
|
|
172
|
+
try:
|
|
173
|
+
llm_client = llm_client_class(
|
|
174
|
+
model=input.model,
|
|
175
|
+
api_key=input.api_key,
|
|
176
|
+
)
|
|
177
|
+
except CustomGraphQLError:
|
|
178
|
+
raise
|
|
179
|
+
except Exception as error:
|
|
180
|
+
raise BadRequest(
|
|
181
|
+
f"Failed to connect to LLM API for {provider_key.value} {input.model.name}: "
|
|
182
|
+
f"{str(error)}"
|
|
183
|
+
)
|
|
162
184
|
|
|
163
185
|
dataset_id = from_global_id_with_expected_type(input.dataset_id, Dataset.__name__)
|
|
164
186
|
version_id = (
|
|
@@ -264,45 +286,76 @@ class Subscription:
|
|
|
264
286
|
experiment=to_gql_experiment(experiment)
|
|
265
287
|
) # eagerly yields experiment so it can be linked by consumers of the subscription
|
|
266
288
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
289
|
+
results: Queue[ChatCompletionResult] = Queue()
|
|
290
|
+
not_started: list[tuple[DatasetExampleID, ChatStream]] = [
|
|
291
|
+
(
|
|
292
|
+
GlobalID(DatasetExample.__name__, str(revision.dataset_example_id)),
|
|
293
|
+
_stream_chat_completion_over_dataset_example(
|
|
294
|
+
input=input,
|
|
295
|
+
llm_client=llm_client,
|
|
296
|
+
revision=revision,
|
|
297
|
+
results=results,
|
|
298
|
+
experiment_id=experiment.id,
|
|
299
|
+
project_id=playground_project_id,
|
|
300
|
+
),
|
|
276
301
|
)
|
|
277
302
|
for revision in revisions
|
|
278
303
|
]
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
304
|
+
in_progress: list[
|
|
305
|
+
tuple[Optional[DatasetExampleID], ChatStream, Task[ChatCompletionSubscriptionPayload]]
|
|
306
|
+
] = []
|
|
307
|
+
max_in_progress = 3
|
|
308
|
+
write_batch_size = 10
|
|
309
|
+
write_interval = timedelta(seconds=10)
|
|
310
|
+
last_write_time = datetime.now()
|
|
311
|
+
while not_started or in_progress:
|
|
312
|
+
while not_started and len(in_progress) < max_in_progress:
|
|
313
|
+
ex_id, stream = not_started.pop()
|
|
314
|
+
task = _create_task_with_timeout(stream)
|
|
315
|
+
in_progress.append((ex_id, stream, task))
|
|
316
|
+
async_tasks_to_run = [task for _, _, task in in_progress]
|
|
286
317
|
completed_tasks, _ = await wait(async_tasks_to_run, return_when=FIRST_COMPLETED)
|
|
287
|
-
for
|
|
288
|
-
|
|
318
|
+
for completed_task in completed_tasks:
|
|
319
|
+
idx = [task for _, _, task in in_progress].index(completed_task)
|
|
320
|
+
example_id, stream, _ = in_progress[idx]
|
|
289
321
|
try:
|
|
290
|
-
yield
|
|
291
|
-
except
|
|
292
|
-
del
|
|
322
|
+
yield completed_task.result()
|
|
323
|
+
except StopAsyncIteration:
|
|
324
|
+
del in_progress[idx] # removes exhausted stream
|
|
325
|
+
except asyncio.TimeoutError:
|
|
326
|
+
del in_progress[idx] # removes timed-out stream
|
|
327
|
+
if example_id is not None:
|
|
328
|
+
yield ChatCompletionSubscriptionError(
|
|
329
|
+
message="Timed out", dataset_example_id=example_id
|
|
330
|
+
)
|
|
293
331
|
except Exception as error:
|
|
294
|
-
del
|
|
332
|
+
del in_progress[idx] # removes failed stream
|
|
333
|
+
if example_id is not None:
|
|
334
|
+
yield ChatCompletionSubscriptionError(
|
|
335
|
+
message="An unexpected error occurred", dataset_example_id=example_id
|
|
336
|
+
)
|
|
295
337
|
logger.exception(error)
|
|
296
338
|
else:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
339
|
+
task = _create_task_with_timeout(stream)
|
|
340
|
+
in_progress[idx] = (example_id, stream, task)
|
|
341
|
+
|
|
342
|
+
exceeded_write_batch_size = results.qsize() >= write_batch_size
|
|
343
|
+
exceeded_write_interval = datetime.now() - last_write_time > write_interval
|
|
344
|
+
write_already_in_progress = any(
|
|
345
|
+
_is_result_payloads_stream(stream) for _, stream, _ in in_progress
|
|
346
|
+
)
|
|
347
|
+
if (
|
|
348
|
+
not results.empty()
|
|
349
|
+
and (exceeded_write_batch_size or exceeded_write_interval)
|
|
350
|
+
and not write_already_in_progress
|
|
351
|
+
):
|
|
352
|
+
result_payloads_stream = _chat_completion_result_payloads(
|
|
353
|
+
db=info.context.db, results=_drain_no_wait(results)
|
|
304
354
|
)
|
|
305
|
-
|
|
355
|
+
task = _create_task_with_timeout(result_payloads_stream)
|
|
356
|
+
in_progress.append((None, result_payloads_stream, task))
|
|
357
|
+
last_write_time = datetime.now()
|
|
358
|
+
if remaining_results := await _drain(results):
|
|
306
359
|
async for result_payload in _chat_completion_result_payloads(
|
|
307
360
|
db=info.context.db, results=remaining_results
|
|
308
361
|
):
|
|
@@ -312,17 +365,13 @@ class Subscription:
|
|
|
312
365
|
async def _stream_chat_completion_over_dataset_example(
|
|
313
366
|
*,
|
|
314
367
|
input: ChatCompletionOverDatasetInput,
|
|
315
|
-
|
|
368
|
+
llm_client: PlaygroundStreamingClient,
|
|
316
369
|
revision: models.DatasetExampleRevision,
|
|
317
|
-
|
|
370
|
+
results: Queue[ChatCompletionResult],
|
|
318
371
|
experiment_id: int,
|
|
319
372
|
project_id: int,
|
|
320
|
-
) ->
|
|
373
|
+
) -> ChatStream:
|
|
321
374
|
example_id = GlobalID(DatasetExample.__name__, str(revision.dataset_example_id))
|
|
322
|
-
llm_client = llm_client_class(
|
|
323
|
-
model=input.model,
|
|
324
|
-
api_key=input.api_key,
|
|
325
|
-
)
|
|
326
375
|
invocation_parameters = llm_client.construct_invocation_parameters(input.invocation_parameters)
|
|
327
376
|
messages = [
|
|
328
377
|
(
|
|
@@ -345,7 +394,7 @@ async def _stream_chat_completion_over_dataset_example(
|
|
|
345
394
|
except TemplateFormatterError as error:
|
|
346
395
|
format_end_time = cast(datetime, normalize_datetime(dt=local_now(), tz=timezone.utc))
|
|
347
396
|
yield ChatCompletionSubscriptionError(message=str(error), dataset_example_id=example_id)
|
|
348
|
-
await
|
|
397
|
+
await results.put(
|
|
349
398
|
(
|
|
350
399
|
example_id,
|
|
351
400
|
None,
|
|
@@ -380,7 +429,7 @@ async def _stream_chat_completion_over_dataset_example(
|
|
|
380
429
|
db_run = get_db_experiment_run(
|
|
381
430
|
db_span, db_trace, experiment_id=experiment_id, example_id=revision.dataset_example_id
|
|
382
431
|
)
|
|
383
|
-
await
|
|
432
|
+
await results.put((example_id, db_span, db_run))
|
|
384
433
|
if span.status_message is not None:
|
|
385
434
|
yield ChatCompletionSubscriptionError(
|
|
386
435
|
message=span.status_message, dataset_example_id=example_id
|
|
@@ -391,7 +440,7 @@ async def _chat_completion_result_payloads(
|
|
|
391
440
|
*,
|
|
392
441
|
db: DbSessionFactory,
|
|
393
442
|
results: Sequence[ChatCompletionResult],
|
|
394
|
-
) ->
|
|
443
|
+
) -> ChatStream:
|
|
395
444
|
if not results:
|
|
396
445
|
return
|
|
397
446
|
async with db() as session:
|
|
@@ -408,8 +457,18 @@ async def _chat_completion_result_payloads(
|
|
|
408
457
|
)
|
|
409
458
|
|
|
410
459
|
|
|
460
|
+
def _is_result_payloads_stream(
|
|
461
|
+
stream: ChatStream,
|
|
462
|
+
) -> bool:
|
|
463
|
+
"""
|
|
464
|
+
Checks if the given generator was instantiated from
|
|
465
|
+
`_chat_completion_result_payloads`
|
|
466
|
+
"""
|
|
467
|
+
return stream.ag_code == _chat_completion_result_payloads.__code__
|
|
468
|
+
|
|
469
|
+
|
|
411
470
|
def _create_task_with_timeout(
|
|
412
|
-
iterable: AsyncIterator[GenericType], timeout_in_seconds: int =
|
|
471
|
+
iterable: AsyncIterator[GenericType], timeout_in_seconds: int = 90
|
|
413
472
|
) -> Task[GenericType]:
|
|
414
473
|
return create_task(wait_for(_as_coroutine(iterable), timeout=timeout_in_seconds))
|
|
415
474
|
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_components-
|
|
3
|
-
"file": "assets/components-
|
|
2
|
+
"_components-C_HASv83.js": {
|
|
3
|
+
"file": "assets/components-C_HASv83.js",
|
|
4
4
|
"name": "components",
|
|
5
5
|
"imports": [
|
|
6
|
-
"_vendor-
|
|
7
|
-
"_pages-
|
|
8
|
-
"_vendor-arizeai-
|
|
9
|
-
"_vendor-codemirror-
|
|
6
|
+
"_vendor-BCxsh5i3.js",
|
|
7
|
+
"_pages-DYHcAdjT.js",
|
|
8
|
+
"_vendor-arizeai-C2CDZgMz.js",
|
|
9
|
+
"_vendor-codemirror-DYbtnCTn.js",
|
|
10
10
|
"_vendor-three-DwGkEfCM.js"
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
|
-
"_pages-
|
|
14
|
-
"file": "assets/pages-
|
|
13
|
+
"_pages-DYHcAdjT.js": {
|
|
14
|
+
"file": "assets/pages-DYHcAdjT.js",
|
|
15
15
|
"name": "pages",
|
|
16
16
|
"imports": [
|
|
17
|
-
"_vendor-
|
|
18
|
-
"_vendor-arizeai-
|
|
19
|
-
"_components-
|
|
20
|
-
"_vendor-recharts-
|
|
21
|
-
"_vendor-codemirror-
|
|
17
|
+
"_vendor-BCxsh5i3.js",
|
|
18
|
+
"_vendor-arizeai-C2CDZgMz.js",
|
|
19
|
+
"_components-C_HASv83.js",
|
|
20
|
+
"_vendor-recharts-P6W8G0Mb.js",
|
|
21
|
+
"_vendor-codemirror-DYbtnCTn.js"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
24
|
"_vendor-!~{003}~.js": {
|
|
25
25
|
"file": "assets/vendor-DxkFTwjz.css",
|
|
26
26
|
"src": "_vendor-!~{003}~.js"
|
|
27
27
|
},
|
|
28
|
-
"_vendor-
|
|
29
|
-
"file": "assets/vendor-
|
|
28
|
+
"_vendor-BCxsh5i3.js": {
|
|
29
|
+
"file": "assets/vendor-BCxsh5i3.js",
|
|
30
30
|
"name": "vendor",
|
|
31
31
|
"imports": [
|
|
32
32
|
"_vendor-three-DwGkEfCM.js"
|
|
@@ -35,25 +35,25 @@
|
|
|
35
35
|
"assets/vendor-DxkFTwjz.css"
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
-
"_vendor-arizeai-
|
|
39
|
-
"file": "assets/vendor-arizeai-
|
|
38
|
+
"_vendor-arizeai-C2CDZgMz.js": {
|
|
39
|
+
"file": "assets/vendor-arizeai-C2CDZgMz.js",
|
|
40
40
|
"name": "vendor-arizeai",
|
|
41
41
|
"imports": [
|
|
42
|
-
"_vendor-
|
|
42
|
+
"_vendor-BCxsh5i3.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
-
"_vendor-codemirror-
|
|
46
|
-
"file": "assets/vendor-codemirror-
|
|
45
|
+
"_vendor-codemirror-DYbtnCTn.js": {
|
|
46
|
+
"file": "assets/vendor-codemirror-DYbtnCTn.js",
|
|
47
47
|
"name": "vendor-codemirror",
|
|
48
48
|
"imports": [
|
|
49
|
-
"_vendor-
|
|
49
|
+
"_vendor-BCxsh5i3.js"
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
|
-
"_vendor-recharts-
|
|
53
|
-
"file": "assets/vendor-recharts-
|
|
52
|
+
"_vendor-recharts-P6W8G0Mb.js": {
|
|
53
|
+
"file": "assets/vendor-recharts-P6W8G0Mb.js",
|
|
54
54
|
"name": "vendor-recharts",
|
|
55
55
|
"imports": [
|
|
56
|
-
"_vendor-
|
|
56
|
+
"_vendor-BCxsh5i3.js"
|
|
57
57
|
]
|
|
58
58
|
},
|
|
59
59
|
"_vendor-three-DwGkEfCM.js": {
|
|
@@ -61,18 +61,18 @@
|
|
|
61
61
|
"name": "vendor-three"
|
|
62
62
|
},
|
|
63
63
|
"index.tsx": {
|
|
64
|
-
"file": "assets/index-
|
|
64
|
+
"file": "assets/index-D7UiCRtr.js",
|
|
65
65
|
"name": "index",
|
|
66
66
|
"src": "index.tsx",
|
|
67
67
|
"isEntry": true,
|
|
68
68
|
"imports": [
|
|
69
|
-
"_vendor-
|
|
70
|
-
"_vendor-arizeai-
|
|
71
|
-
"_pages-
|
|
72
|
-
"_components-
|
|
69
|
+
"_vendor-BCxsh5i3.js",
|
|
70
|
+
"_vendor-arizeai-C2CDZgMz.js",
|
|
71
|
+
"_pages-DYHcAdjT.js",
|
|
72
|
+
"_components-C_HASv83.js",
|
|
73
73
|
"_vendor-three-DwGkEfCM.js",
|
|
74
|
-
"_vendor-recharts-
|
|
75
|
-
"_vendor-codemirror-
|
|
74
|
+
"_vendor-recharts-P6W8G0Mb.js",
|
|
75
|
+
"_vendor-codemirror-DYbtnCTn.js"
|
|
76
76
|
]
|
|
77
77
|
}
|
|
78
78
|
}
|