arize-phoenix 5.9.1__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.9.1.dist-info → arize_phoenix-5.11.0.dist-info}/METADATA +2 -1
- {arize_phoenix-5.9.1.dist-info → arize_phoenix-5.11.0.dist-info}/RECORD +25 -25
- phoenix/config.py +16 -1
- phoenix/db/facilitator.py +3 -2
- phoenix/server/api/helpers/playground_clients.py +73 -19
- phoenix/server/api/helpers/playground_spans.py +40 -8
- phoenix/server/api/mutations/chat_mutations.py +75 -34
- phoenix/server/api/subscriptions.py +109 -50
- phoenix/server/api/types/GenerativeProvider.py +44 -0
- phoenix/server/api/types/Span.py +4 -5
- phoenix/server/static/.vite/manifest.json +31 -31
- phoenix/server/static/assets/{components-BcvRmBnN.js → components-C_HASv83.js} +131 -131
- phoenix/server/static/assets/{index-BF4RUiOz.js → index-D7UiCRtr.js} +2 -2
- phoenix/server/static/assets/{pages-CM_Zho_x.js → pages-DYHcAdjT.js} +315 -266
- phoenix/server/static/assets/{vendor-Bjm5T3cE.js → vendor-BCxsh5i3.js} +153 -153
- phoenix/server/static/assets/{vendor-arizeai-CQhWGEdL.js → vendor-arizeai-C2CDZgMz.js} +28 -28
- phoenix/server/static/assets/{vendor-codemirror-CdtiO80y.js → vendor-codemirror-DYbtnCTn.js} +2 -2
- phoenix/server/static/assets/{vendor-recharts-BqWon6Py.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.9.1.dist-info → arize_phoenix-5.11.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-5.9.1.dist-info → arize_phoenix-5.11.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-5.9.1.dist-info → arize_phoenix-5.11.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-5.9.1.dist-info → arize_phoenix-5.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from dataclasses import asdict, field
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
from itertools import chain
|
|
4
|
+
from itertools import chain, islice
|
|
5
5
|
from traceback import format_exc
|
|
6
|
-
from typing import Any, Iterable, Iterator, List, Optional, Union
|
|
6
|
+
from typing import Any, Iterable, Iterator, List, Optional, TypeVar, Union
|
|
7
7
|
|
|
8
8
|
import strawberry
|
|
9
9
|
from openinference.instrumentation import safe_json_dumps
|
|
@@ -26,7 +26,7 @@ from phoenix.datetime_utils import local_now, normalize_datetime
|
|
|
26
26
|
from phoenix.db import models
|
|
27
27
|
from phoenix.db.helpers import get_dataset_example_revisions
|
|
28
28
|
from phoenix.server.api.context import Context
|
|
29
|
-
from phoenix.server.api.exceptions import BadRequest, NotFound
|
|
29
|
+
from phoenix.server.api.exceptions import BadRequest, CustomGraphQLError, NotFound
|
|
30
30
|
from phoenix.server.api.helpers.playground_clients import (
|
|
31
31
|
PlaygroundStreamingClient,
|
|
32
32
|
initialize_playground_clients,
|
|
@@ -127,11 +127,19 @@ class ChatCompletionMutationMixin:
|
|
|
127
127
|
provider_key = input.model.provider_key
|
|
128
128
|
llm_client_class = PLAYGROUND_CLIENT_REGISTRY.get_client(provider_key, input.model.name)
|
|
129
129
|
if llm_client_class is None:
|
|
130
|
-
raise BadRequest(f"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
raise BadRequest(f"Unknown LLM provider: '{provider_key.value}'")
|
|
131
|
+
try:
|
|
132
|
+
llm_client = llm_client_class(
|
|
133
|
+
model=input.model,
|
|
134
|
+
api_key=input.api_key,
|
|
135
|
+
)
|
|
136
|
+
except CustomGraphQLError:
|
|
137
|
+
raise
|
|
138
|
+
except Exception as error:
|
|
139
|
+
raise BadRequest(
|
|
140
|
+
f"Failed to connect to LLM API for {provider_key.value} {input.model.name}: "
|
|
141
|
+
f"{str(error)}"
|
|
142
|
+
)
|
|
135
143
|
dataset_id = from_global_id_with_expected_type(input.dataset_id, Dataset.__name__)
|
|
136
144
|
dataset_version_id = (
|
|
137
145
|
from_global_id_with_expected_type(
|
|
@@ -158,7 +166,9 @@ class ChatCompletionMutationMixin:
|
|
|
158
166
|
revisions = [
|
|
159
167
|
revision
|
|
160
168
|
async for revision in await session.stream_scalars(
|
|
161
|
-
get_dataset_example_revisions(resolved_version_id)
|
|
169
|
+
get_dataset_example_revisions(resolved_version_id).order_by(
|
|
170
|
+
models.DatasetExampleRevision.id
|
|
171
|
+
)
|
|
162
172
|
)
|
|
163
173
|
]
|
|
164
174
|
if not revisions:
|
|
@@ -181,28 +191,32 @@ class ChatCompletionMutationMixin:
|
|
|
181
191
|
session.add(experiment)
|
|
182
192
|
await session.flush()
|
|
183
193
|
|
|
194
|
+
results = []
|
|
195
|
+
batch_size = 3
|
|
184
196
|
start_time = datetime.now(timezone.utc)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
for batch in _get_batches(revisions, batch_size):
|
|
198
|
+
batch_results = await asyncio.gather(
|
|
199
|
+
*(
|
|
200
|
+
cls._chat_completion(
|
|
201
|
+
info,
|
|
202
|
+
llm_client,
|
|
203
|
+
ChatCompletionInput(
|
|
204
|
+
model=input.model,
|
|
205
|
+
api_key=input.api_key,
|
|
206
|
+
messages=input.messages,
|
|
207
|
+
tools=input.tools,
|
|
208
|
+
invocation_parameters=input.invocation_parameters,
|
|
209
|
+
template=TemplateOptions(
|
|
210
|
+
language=input.template_language,
|
|
211
|
+
variables=revision.input,
|
|
212
|
+
),
|
|
199
213
|
),
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
)
|
|
215
|
+
for revision in batch
|
|
216
|
+
),
|
|
217
|
+
return_exceptions=True,
|
|
218
|
+
)
|
|
219
|
+
results.extend(batch_results)
|
|
206
220
|
|
|
207
221
|
payload = ChatCompletionOverDatasetMutationPayload(
|
|
208
222
|
dataset_id=GlobalID(models.Dataset.__name__, str(dataset.id)),
|
|
@@ -266,11 +280,19 @@ class ChatCompletionMutationMixin:
|
|
|
266
280
|
provider_key = input.model.provider_key
|
|
267
281
|
llm_client_class = PLAYGROUND_CLIENT_REGISTRY.get_client(provider_key, input.model.name)
|
|
268
282
|
if llm_client_class is None:
|
|
269
|
-
raise BadRequest(f"
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
283
|
+
raise BadRequest(f"Unknown LLM provider: '{provider_key.value}'")
|
|
284
|
+
try:
|
|
285
|
+
llm_client = llm_client_class(
|
|
286
|
+
model=input.model,
|
|
287
|
+
api_key=input.api_key,
|
|
288
|
+
)
|
|
289
|
+
except CustomGraphQLError:
|
|
290
|
+
raise
|
|
291
|
+
except Exception as error:
|
|
292
|
+
raise BadRequest(
|
|
293
|
+
f"Failed to connect to LLM API for {provider_key.value} {input.model.name}: "
|
|
294
|
+
f"{str(error)}"
|
|
295
|
+
)
|
|
274
296
|
return await cls._chat_completion(info, llm_client, input)
|
|
275
297
|
|
|
276
298
|
@classmethod
|
|
@@ -486,6 +508,11 @@ def _llm_output_messages(
|
|
|
486
508
|
if text_content:
|
|
487
509
|
yield f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_CONTENT}", text_content
|
|
488
510
|
for tool_call_index, tool_call in enumerate(tool_calls.values()):
|
|
511
|
+
if tool_call_id := tool_call.id:
|
|
512
|
+
yield (
|
|
513
|
+
f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_ID}",
|
|
514
|
+
tool_call_id,
|
|
515
|
+
)
|
|
489
516
|
yield (
|
|
490
517
|
f"{LLM_OUTPUT_MESSAGES}.0.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_NAME}",
|
|
491
518
|
tool_call.function.name,
|
|
@@ -513,6 +540,19 @@ def _serialize_event(event: SpanException) -> dict[str, Any]:
|
|
|
513
540
|
return {k: (v.isoformat() if isinstance(v, datetime) else v) for k, v in asdict(event).items()}
|
|
514
541
|
|
|
515
542
|
|
|
543
|
+
_AnyT = TypeVar("_AnyT")
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _get_batches(
|
|
547
|
+
iterable: Iterable[_AnyT],
|
|
548
|
+
batch_size: int,
|
|
549
|
+
) -> Iterator[list[_AnyT]]:
|
|
550
|
+
"""Splits an iterable into batches not exceeding a specified size."""
|
|
551
|
+
iterator = iter(iterable)
|
|
552
|
+
while batch := list(islice(iterator, batch_size)):
|
|
553
|
+
yield batch
|
|
554
|
+
|
|
555
|
+
|
|
516
556
|
JSON = OpenInferenceMimeTypeValues.JSON.value
|
|
517
557
|
TEXT = OpenInferenceMimeTypeValues.TEXT.value
|
|
518
558
|
LLM = OpenInferenceSpanKindValues.LLM.value
|
|
@@ -534,6 +574,7 @@ MESSAGE_CONTENT = MessageAttributes.MESSAGE_CONTENT
|
|
|
534
574
|
MESSAGE_ROLE = MessageAttributes.MESSAGE_ROLE
|
|
535
575
|
MESSAGE_TOOL_CALLS = MessageAttributes.MESSAGE_TOOL_CALLS
|
|
536
576
|
|
|
577
|
+
TOOL_CALL_ID = ToolCallAttributes.TOOL_CALL_ID
|
|
537
578
|
TOOL_CALL_FUNCTION_NAME = ToolCallAttributes.TOOL_CALL_FUNCTION_NAME
|
|
538
579
|
TOOL_CALL_FUNCTION_ARGUMENTS_JSON = ToolCallAttributes.TOOL_CALL_FUNCTION_ARGUMENTS_JSON
|
|
539
580
|
|
|
@@ -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
|
(
|
|
@@ -127,7 +137,7 @@ class Subscription:
|
|
|
127
137
|
):
|
|
128
138
|
span.add_response_chunk(chunk)
|
|
129
139
|
yield chunk
|
|
130
|
-
|
|
140
|
+
span.set_attributes(llm_client.attributes)
|
|
131
141
|
if span.status_message is not None:
|
|
132
142
|
yield ChatCompletionSubscriptionError(message=span.status_message)
|
|
133
143
|
async with info.context.db() as session:
|
|
@@ -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,6 +1,10 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from typing import Any, ClassVar, Optional, Union
|
|
2
3
|
|
|
3
4
|
import strawberry
|
|
5
|
+
from openinference.semconv.trace import OpenInferenceLLMProviderValues, SpanAttributes
|
|
6
|
+
|
|
7
|
+
from phoenix.trace.attributes import get_attribute_value
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
@strawberry.enum
|
|
@@ -16,6 +20,20 @@ class GenerativeProvider:
|
|
|
16
20
|
name: str
|
|
17
21
|
key: GenerativeProviderKey
|
|
18
22
|
|
|
23
|
+
model_provider_to_model_prefix_map: ClassVar[dict[GenerativeProviderKey, list[str]]] = {
|
|
24
|
+
GenerativeProviderKey.AZURE_OPENAI: [],
|
|
25
|
+
GenerativeProviderKey.ANTHROPIC: ["claude"],
|
|
26
|
+
GenerativeProviderKey.OPENAI: ["gpt", "o1"],
|
|
27
|
+
GenerativeProviderKey.GEMINI: ["gemini"],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
attribute_provider_to_generative_provider_map: ClassVar[dict[str, GenerativeProviderKey]] = {
|
|
31
|
+
OpenInferenceLLMProviderValues.OPENAI.value: GenerativeProviderKey.OPENAI,
|
|
32
|
+
OpenInferenceLLMProviderValues.ANTHROPIC.value: GenerativeProviderKey.ANTHROPIC,
|
|
33
|
+
OpenInferenceLLMProviderValues.AZURE.value: GenerativeProviderKey.AZURE_OPENAI,
|
|
34
|
+
OpenInferenceLLMProviderValues.GOOGLE.value: GenerativeProviderKey.GEMINI,
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
@strawberry.field
|
|
20
38
|
async def dependencies(self) -> list[str]:
|
|
21
39
|
from phoenix.server.api.helpers.playground_registry import (
|
|
@@ -39,3 +57,29 @@ class GenerativeProvider:
|
|
|
39
57
|
if default_client:
|
|
40
58
|
return default_client.dependencies_are_installed()
|
|
41
59
|
return False
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def _infer_model_provider_from_model_name(
|
|
63
|
+
cls,
|
|
64
|
+
model_name: str,
|
|
65
|
+
) -> Union[GenerativeProviderKey, None]:
|
|
66
|
+
for provider, prefixes in cls.model_provider_to_model_prefix_map.items():
|
|
67
|
+
if any(prefix.lower() in model_name.lower() for prefix in prefixes):
|
|
68
|
+
return provider
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def get_model_provider_from_attributes(
|
|
73
|
+
cls,
|
|
74
|
+
attributes: dict[str, Any],
|
|
75
|
+
) -> Union[GenerativeProviderKey, None]:
|
|
76
|
+
llm_provider: Optional[str] = get_attribute_value(attributes, SpanAttributes.LLM_PROVIDER)
|
|
77
|
+
|
|
78
|
+
if isinstance(llm_provider, str) and (
|
|
79
|
+
provider := cls.attribute_provider_to_generative_provider_map.get(llm_provider)
|
|
80
|
+
):
|
|
81
|
+
return provider
|
|
82
|
+
llm_model = get_attribute_value(attributes, SpanAttributes.LLM_MODEL_NAME)
|
|
83
|
+
if isinstance(llm_model, str):
|
|
84
|
+
return cls._infer_model_provider_from_model_name(llm_model)
|
|
85
|
+
return None
|
phoenix/server/api/types/Span.py
CHANGED
|
@@ -25,7 +25,7 @@ from phoenix.server.api.input_types.SpanAnnotationSort import (
|
|
|
25
25
|
SpanAnnotationColumn,
|
|
26
26
|
SpanAnnotationSort,
|
|
27
27
|
)
|
|
28
|
-
from phoenix.server.api.types.GenerativeProvider import
|
|
28
|
+
from phoenix.server.api.types.GenerativeProvider import GenerativeProvider
|
|
29
29
|
from phoenix.server.api.types.SortDir import SortDir
|
|
30
30
|
from phoenix.server.api.types.SpanAnnotation import to_gql_span_annotation
|
|
31
31
|
from phoenix.trace.attributes import get_attribute_value
|
|
@@ -300,10 +300,9 @@ class Span(Node):
|
|
|
300
300
|
|
|
301
301
|
db_span = self.db_span
|
|
302
302
|
attributes = db_span.attributes
|
|
303
|
-
llm_provider
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
)
|
|
303
|
+
llm_provider = GenerativeProvider.get_model_provider_from_attributes(attributes)
|
|
304
|
+
if llm_provider is None:
|
|
305
|
+
return []
|
|
307
306
|
llm_model = get_attribute_value(attributes, SpanAttributes.LLM_MODEL_NAME)
|
|
308
307
|
invocation_parameters = get_attribute_value(
|
|
309
308
|
attributes, SpanAttributes.LLM_INVOCATION_PARAMETERS
|
|
@@ -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
|
-
"
|
|
8
|
-
"_vendor-
|
|
9
|
-
"
|
|
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
|
}
|