arize-phoenix 5.9.0__py3-none-any.whl → 5.10.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.0.dist-info → arize_phoenix-5.10.0.dist-info}/METADATA +1 -1
- {arize_phoenix-5.9.0.dist-info → arize_phoenix-5.10.0.dist-info}/RECORD +22 -22
- phoenix/config.py +16 -2
- phoenix/server/api/helpers/playground_clients.py +44 -6
- phoenix/server/api/helpers/playground_spans.py +34 -8
- phoenix/server/api/routers/oauth2.py +55 -23
- phoenix/server/api/subscriptions.py +1 -1
- 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-DU-8CYbi.js → components-BXIz9ZO8.js} +124 -124
- phoenix/server/static/assets/{index-D9E16vvV.js → index-DTut7g1y.js} +2 -2
- phoenix/server/static/assets/{pages-t09OI1rC.js → pages-B8FpJuXu.js} +291 -263
- phoenix/server/static/assets/{vendor-D04tenE6.js → vendor-BX8_Znqy.js} +146 -146
- phoenix/server/static/assets/{vendor-arizeai-D3NxMQw0.js → vendor-arizeai-CtHir-Ua.js} +1 -1
- phoenix/server/static/assets/{vendor-codemirror-XTiZSlqq.js → vendor-codemirror-DLlGiguX.js} +2 -2
- phoenix/server/static/assets/{vendor-recharts-p0L0neVs.js → vendor-recharts-CJRple0d.js} +1 -1
- phoenix/version.py +1 -1
- {arize_phoenix-5.9.0.dist-info → arize_phoenix-5.10.0.dist-info}/WHEEL +0 -0
- {arize_phoenix-5.9.0.dist-info → arize_phoenix-5.10.0.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-5.9.0.dist-info → arize_phoenix-5.10.0.dist-info}/licenses/IP_NOTICE +0 -0
- {arize_phoenix-5.9.0.dist-info → arize_phoenix-5.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
phoenix/__init__.py,sha256=X3eUEwd2rG8KKWWYVNNDJoqo08ihfjgHhlP29dcdNJE,5481
|
|
2
2
|
phoenix/auth.py,sha256=JpkwJbis2INlIXWcQ-M_Nk5Ln9LBgHMdWNnaAQp0D2w,10940
|
|
3
|
-
phoenix/config.py,sha256=
|
|
3
|
+
phoenix/config.py,sha256=xOM5eupLzXXCfZ4dzCYW-4pKG1xcLDNuu7vxvyiGfoM,25591
|
|
4
4
|
phoenix/datetime_utils.py,sha256=iJzNG6YJ6V7_u8B2iA7P2Z26FyxYbOPtx0dhJ7kNDHA,3398
|
|
5
5
|
phoenix/exceptions.py,sha256=n2L2KKuecrdflB9MsCdAYCiSEvGJptIsfRkXMoJle7A,169
|
|
6
6
|
phoenix/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
7
7
|
phoenix/services.py,sha256=kpW1WL0kiB8XJsO6XycvZVJ-lBkNoenhQ7atCvBoSe8,5365
|
|
8
8
|
phoenix/settings.py,sha256=ht-0oN-sMV6SPXrk7Tu1EZlngpAYkGNLYPhO8DyrdQI,661
|
|
9
|
-
phoenix/version.py,sha256=
|
|
9
|
+
phoenix/version.py,sha256=PtYE7255x6FNDU5KsxORw9DOsNBQOKOzcRu8NtdUMBk,23
|
|
10
10
|
phoenix/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
phoenix/core/embedding_dimension.py,sha256=zKGbcvwOXgLf-yrJBpQyKtd-LEOPRKHnUToyAU8Owis,87
|
|
12
12
|
phoenix/core/model.py,sha256=qBFraOtmwCCnWJltKNP18DDG0mULXigytlFsa6YOz6k,4837
|
|
@@ -94,7 +94,7 @@ phoenix/server/api/exceptions.py,sha256=TA0JuY2YRnj35qGuMSQ8d0ToHum9gWm9W--3fSKH
|
|
|
94
94
|
phoenix/server/api/interceptor.py,sha256=ykDnoC_apUd-llVli3m1CW18kNSIgjz2qZ6m5JmPDu8,1294
|
|
95
95
|
phoenix/server/api/queries.py,sha256=4KJz8TUz3VUTup9MDjr_GoKX0SttWSvHBq2ncWZGxf8,27343
|
|
96
96
|
phoenix/server/api/schema.py,sha256=tHyw2jTbue_-gu0fe9Sw7LUYtzJUCwp9SvccDgOkNPw,1696
|
|
97
|
-
phoenix/server/api/subscriptions.py,sha256=
|
|
97
|
+
phoenix/server/api/subscriptions.py,sha256=uTGlf6WKrnC6Nogsx_GtIVSMOzgIKMJwyP1oe47QCHg,19688
|
|
98
98
|
phoenix/server/api/utils.py,sha256=quCBRcusc6PUq9tJq7M8PgwFZp7nXgVAxtbw8feribY,833
|
|
99
99
|
phoenix/server/api/dataloaders/__init__.py,sha256=jNYvfXjnZzgA2HWTG7AZdqWGla3ZysBUDUei8Zkz6N8,3290
|
|
100
100
|
phoenix/server/api/dataloaders/annotation_summaries.py,sha256=2sHmIDX7n8tuPeBTs9bMKtlMKWn_Ph9awTZqmwn2Owc,5505
|
|
@@ -125,9 +125,9 @@ phoenix/server/api/dataloaders/cache/__init__.py,sha256=SYoOM9n8FJaMdQarma5d1blu
|
|
|
125
125
|
phoenix/server/api/dataloaders/cache/two_tier_cache.py,sha256=cmo8FUT3E91R139IEzh4yCga-6nTamc5KPXAfMrzNDM,2315
|
|
126
126
|
phoenix/server/api/helpers/__init__.py,sha256=m2-xaSPqUiSs91k62JaRDjFNfl-1byxBfY-m_Vxw16U,272
|
|
127
127
|
phoenix/server/api/helpers/dataset_helpers.py,sha256=14mldZp9to3rr9BdvvoFqEwZHHV_k2e7jPm8q9z2OdQ,6896
|
|
128
|
-
phoenix/server/api/helpers/playground_clients.py,sha256=
|
|
128
|
+
phoenix/server/api/helpers/playground_clients.py,sha256=Ah-f8jDr3uFC-MtofG1k9f4WYP_Lpb5eHxYaK154eRA,36471
|
|
129
129
|
phoenix/server/api/helpers/playground_registry.py,sha256=CPLMziFB2wmr-dfbx7VbzO2f8YIG_k5RftzvGXYGQ1w,2570
|
|
130
|
-
phoenix/server/api/helpers/playground_spans.py,sha256=
|
|
130
|
+
phoenix/server/api/helpers/playground_spans.py,sha256=ecQv7lTBue_vRW_2giIcAyYOEWIwU06C2371Gd92gK4,16253
|
|
131
131
|
phoenix/server/api/input_types/AddExamplesToDatasetInput.py,sha256=mIQz0S_z8YdrktKIY6RCvtNJ2yZF9pYvTGgasUsI-54,430
|
|
132
132
|
phoenix/server/api/input_types/AddSpansToDatasetInput.py,sha256=-StSstyMAVrba3tG1U30b-srkKCtu_svflQuSM19iJA,362
|
|
133
133
|
phoenix/server/api/input_types/ChatCompletionInput.py,sha256=g_5ARuwylt-uCVAsGyZPEVtidEQiOhbKakvDQsZumzw,1451
|
|
@@ -178,7 +178,7 @@ phoenix/server/api/openapi/schema.py,sha256=S1nPq4iR578fPESWDAHNv9nlgh_go6zwTalL
|
|
|
178
178
|
phoenix/server/api/routers/__init__.py,sha256=YIzHsIFOOXuCRbDkMUHx-McrANFJK5UfUn6a4BNIzmo,277
|
|
179
179
|
phoenix/server/api/routers/auth.py,sha256=T774FE5mqrfRSSYo1snpR5NIp3YzAJnsLsY9FJB9GCA,11164
|
|
180
180
|
phoenix/server/api/routers/embeddings.py,sha256=BpZGJee0pdL0W5Rp1L0b30dEtZTgJeVqXky8LgZ0ZXw,898
|
|
181
|
-
phoenix/server/api/routers/oauth2.py,sha256=
|
|
181
|
+
phoenix/server/api/routers/oauth2.py,sha256=bSrTZAAWW4WgZVwkr39xbo5jZEYL4w4wCbEe280M6f0,17157
|
|
182
182
|
phoenix/server/api/routers/utils.py,sha256=M41BoH-fl37izhRuN2aX7lWm7jOC20A_3uClv9TVUUY,583
|
|
183
183
|
phoenix/server/api/routers/v1/__init__.py,sha256=aLEHzzU8kQo4Oqsv2an35lH5VYUxAZQrcG7CXZA_Lx4,2214
|
|
184
184
|
phoenix/server/api/routers/v1/datasets.py,sha256=tNh0CxAvSkWh-_5AwisGN1degQlUNGU3uufGa7MIbOw,36985
|
|
@@ -227,7 +227,7 @@ phoenix/server/api/types/ExperimentRunAnnotation.py,sha256=iBxDaD9DgiF-Qymp5QyxW
|
|
|
227
227
|
phoenix/server/api/types/ExportedFile.py,sha256=e3GTn7B5LgsTbqiwjhMCQH7VsiqXitrBO4aCMS1lHsg,163
|
|
228
228
|
phoenix/server/api/types/Functionality.py,sha256=tzV9xdhB8zqfsjWxP66NDC7EZsplYkYO7jRbLWJIeeg,382
|
|
229
229
|
phoenix/server/api/types/GenerativeModel.py,sha256=P7eBUMXbeqaLwSSGBKdZy3a5gOLd9I0fuP8o1st6H08,193
|
|
230
|
-
phoenix/server/api/types/GenerativeProvider.py,sha256=
|
|
230
|
+
phoenix/server/api/types/GenerativeProvider.py,sha256=3zOR7SssR3mkGAaj8j-_qVzN6H7Qll52J_i98jVyEpA,3127
|
|
231
231
|
phoenix/server/api/types/Inferences.py,sha256=wv88PjcK-KwnzmTdukiAX9EV2KX4GqsKXVAUm1JtnDA,3383
|
|
232
232
|
phoenix/server/api/types/InferencesRole.py,sha256=mLfeHpyhGUVX1-tWzT9IwC_cD18BZrD3RA4YsHYuSpA,595
|
|
233
233
|
phoenix/server/api/types/LabelFraction.py,sha256=zsDxdFALrNiGA1eNykeP8o65gbA0HOhRp54MPH_iRAM,93
|
|
@@ -241,7 +241,7 @@ phoenix/server/api/types/Retrieval.py,sha256=OhMK2ncjoyp5h1yjKhjlKpoTbQrMHuxmgSF
|
|
|
241
241
|
phoenix/server/api/types/ScalarDriftMetricEnum.py,sha256=IUAcRPpgL41WdoIgK6cNk2Te38SspXGyEs-S1fY23_A,232
|
|
242
242
|
phoenix/server/api/types/Segments.py,sha256=vT2v0efoa5cuBKxLtxTnsUP5YJJCZfTloM71Spu0tMI,2915
|
|
243
243
|
phoenix/server/api/types/SortDir.py,sha256=OUpXhlCzCxPoXSDkJJygEs9Rw9pMymfaZUG5zPTrw4Y,152
|
|
244
|
-
phoenix/server/api/types/Span.py,sha256=
|
|
244
|
+
phoenix/server/api/types/Span.py,sha256=6GS6MpJ3f8P2LrQUe2TWPrPf7ENxmde_wisQkJguphw,16919
|
|
245
245
|
phoenix/server/api/types/SpanAnnotation.py,sha256=6b5G-b_OoRvDL2ayWk7MkbqarLK-F-pQMx21CpUuNGY,1168
|
|
246
246
|
phoenix/server/api/types/SystemApiKey.py,sha256=2ym8EgsTBIvxx1l9xZ-2YMovz58ZwYb_MaHBTJ9NH2E,166
|
|
247
247
|
phoenix/server/api/types/TemplateLanguage.py,sha256=9yxW3zGXgHPnA35svT4tznDyRKGuaz_WlbcpiUtC7Ec,142
|
|
@@ -273,15 +273,15 @@ phoenix/server/static/apple-touch-icon-76x76.png,sha256=CT_xT12I0u2i0WU8JzBZBuOQ
|
|
|
273
273
|
phoenix/server/static/apple-touch-icon.png,sha256=fOfpjqGpWYbJ0eAurKsyoZP1EAs6ZVooBJ_SGk2ZkDs,3801
|
|
274
274
|
phoenix/server/static/favicon.ico,sha256=bY0vvCKRftemZfPShwZtE93DiiQdaYaozkPGwNFr6H8,34494
|
|
275
275
|
phoenix/server/static/modernizr.js,sha256=mvK-XtkNqjOral-QvzoqsyOMECXIMu5BQwSVN_wcU9c,2564
|
|
276
|
-
phoenix/server/static/.vite/manifest.json,sha256=
|
|
277
|
-
phoenix/server/static/assets/components-
|
|
278
|
-
phoenix/server/static/assets/index-
|
|
279
|
-
phoenix/server/static/assets/pages-
|
|
280
|
-
phoenix/server/static/assets/vendor-
|
|
276
|
+
phoenix/server/static/.vite/manifest.json,sha256=uCPxIoF7qBxFkaV0H9ur1eYr2sV8go_LqjcguNA6CbQ,1929
|
|
277
|
+
phoenix/server/static/assets/components-BXIz9ZO8.js,sha256=clW5yRW2_5BRWWn3FLFSPhCAVqTsGXJmfUTJpHflHf0,306000
|
|
278
|
+
phoenix/server/static/assets/index-DTut7g1y.js,sha256=iM98orZiM2-8YVnpvZgBgVkteOcvDGZgJo3tWQCxkqo,7290
|
|
279
|
+
phoenix/server/static/assets/pages-B8FpJuXu.js,sha256=y7Q9_wYtuaTanNRZk-OvN1ffiyN_6wCsERnHuqOHHUY,629525
|
|
280
|
+
phoenix/server/static/assets/vendor-BX8_Znqy.js,sha256=M5d1J040pAmV0PhGLJLz3-QQ1m3bogn54ekym9iaAPg,10898641
|
|
281
281
|
phoenix/server/static/assets/vendor-DxkFTwjz.css,sha256=nZrkr0u6NNElFGvpWHk9GTHeGoibCXCli1bE7mXZGZg,1816
|
|
282
|
-
phoenix/server/static/assets/vendor-arizeai-
|
|
283
|
-
phoenix/server/static/assets/vendor-codemirror-
|
|
284
|
-
phoenix/server/static/assets/vendor-recharts-
|
|
282
|
+
phoenix/server/static/assets/vendor-arizeai-CtHir-Ua.js,sha256=mb8CF7PUkSisMt5HjNC2_HZd48HST1LLCOrUnxZ63iI,307000
|
|
283
|
+
phoenix/server/static/assets/vendor-codemirror-DLlGiguX.js,sha256=0bqHkVJOI4zakkaJLSppKz87jnbPhu3c7my6qqYFycE,392709
|
|
284
|
+
phoenix/server/static/assets/vendor-recharts-CJRple0d.js,sha256=YiimiK6M9fdba3tItYH1sl1-Ye61dM67qfAdWpnBjq8,282859
|
|
285
285
|
phoenix/server/static/assets/vendor-three-DwGkEfCM.js,sha256=0D12ZgKzfKCTSdSTKJBFR2RZO_xxeMXrqDp0AszZqHY,620972
|
|
286
286
|
phoenix/server/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
287
287
|
phoenix/server/templates/index.html,sha256=ram6sfy2obf_F053ay35V30v-mnRWZ86rK-PstXLy1c,4457
|
|
@@ -322,9 +322,9 @@ phoenix/utilities/project.py,sha256=auVpARXkDb-JgeX5f2aStyFIkeKvGwN9l7qrFeJMVxI,
|
|
|
322
322
|
phoenix/utilities/re.py,sha256=x8Xbk-Wa6qDMAtUd_7JtZvKtrYEuMY-bchB0n163_5c,2006
|
|
323
323
|
phoenix/utilities/span_store.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
324
324
|
phoenix/utilities/template_formatters.py,sha256=JuOyvukMPLDHa1uVNw0kCFBUnIxy02dwAWNZimdIZU4,2423
|
|
325
|
-
arize_phoenix-5.
|
|
326
|
-
arize_phoenix-5.
|
|
327
|
-
arize_phoenix-5.
|
|
328
|
-
arize_phoenix-5.
|
|
329
|
-
arize_phoenix-5.
|
|
330
|
-
arize_phoenix-5.
|
|
325
|
+
arize_phoenix-5.10.0.dist-info/METADATA,sha256=MYCUDgCZbLC0v04dTkj7SxbQ_hbJZ2FHJS42vzg-Kv4,22614
|
|
326
|
+
arize_phoenix-5.10.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
327
|
+
arize_phoenix-5.10.0.dist-info/entry_points.txt,sha256=Pgpn8Upxx9P8z8joPXZWl2LlnAlGc3gcQoVchb06X1Q,94
|
|
328
|
+
arize_phoenix-5.10.0.dist-info/licenses/IP_NOTICE,sha256=JBqyyCYYxGDfzQ0TtsQgjts41IJoa-hiwDrBjCb9gHM,469
|
|
329
|
+
arize_phoenix-5.10.0.dist-info/licenses/LICENSE,sha256=HFkW9REuMOkvKRACuwLPT0hRydHb3zNg-fdFt94td18,3794
|
|
330
|
+
arize_phoenix-5.10.0.dist-info/RECORD,,
|
phoenix/config.py
CHANGED
|
@@ -424,7 +424,9 @@ class OAuth2ClientConfig:
|
|
|
424
424
|
f"An OpenID Connect configuration URL must be set for the {idp_name} OAuth2 IDP "
|
|
425
425
|
f"via the {oidc_config_url_env_var} environment variable"
|
|
426
426
|
)
|
|
427
|
-
|
|
427
|
+
parsed_oidc_config_url = urlparse(oidc_config_url)
|
|
428
|
+
is_local_oidc_config_url = parsed_oidc_config_url.hostname in ("localhost", "127.0.0.1")
|
|
429
|
+
if parsed_oidc_config_url.scheme != "https" and not is_local_oidc_config_url:
|
|
428
430
|
raise ValueError(
|
|
429
431
|
f"Server metadata URL for {idp_name} OAuth2 IDP "
|
|
430
432
|
"must be a valid URL using the https protocol"
|
|
@@ -559,7 +561,19 @@ def get_env_host() -> str:
|
|
|
559
561
|
|
|
560
562
|
|
|
561
563
|
def get_env_host_root_path() -> str:
|
|
562
|
-
|
|
564
|
+
if (host_root_path := os.getenv(ENV_PHOENIX_HOST_ROOT_PATH)) is None:
|
|
565
|
+
return HOST_ROOT_PATH
|
|
566
|
+
if not host_root_path.startswith("/"):
|
|
567
|
+
raise ValueError(
|
|
568
|
+
f"Invalid value for environment variable {ENV_PHOENIX_HOST_ROOT_PATH}: "
|
|
569
|
+
f"{host_root_path}. Value must start with '/'"
|
|
570
|
+
)
|
|
571
|
+
if host_root_path.endswith("/"):
|
|
572
|
+
raise ValueError(
|
|
573
|
+
f"Invalid value for environment variable {ENV_PHOENIX_HOST_ROOT_PATH}: "
|
|
574
|
+
f"{host_root_path}. Value cannot end with '/'"
|
|
575
|
+
)
|
|
576
|
+
return host_root_path
|
|
563
577
|
|
|
564
578
|
|
|
565
579
|
def get_env_collector_endpoint() -> Optional[str]:
|
|
@@ -9,7 +9,11 @@ from functools import wraps
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Hashable, Mapping, Optional, Union
|
|
10
10
|
|
|
11
11
|
from openinference.instrumentation import safe_json_dumps
|
|
12
|
-
from openinference.semconv.trace import
|
|
12
|
+
from openinference.semconv.trace import (
|
|
13
|
+
OpenInferenceLLMProviderValues,
|
|
14
|
+
OpenInferenceLLMSystemValues,
|
|
15
|
+
SpanAttributes,
|
|
16
|
+
)
|
|
13
17
|
from strawberry import UNSET
|
|
14
18
|
from strawberry.scalars import JSON as JSONScalarType
|
|
15
19
|
from typing_extensions import TypeAlias, assert_never
|
|
@@ -44,7 +48,7 @@ from phoenix.server.api.types.ChatCompletionSubscriptionPayload import (
|
|
|
44
48
|
from phoenix.server.api.types.GenerativeProvider import GenerativeProviderKey
|
|
45
49
|
|
|
46
50
|
if TYPE_CHECKING:
|
|
47
|
-
from anthropic.types import MessageParam
|
|
51
|
+
from anthropic.types import MessageParam, TextBlockParam, ToolResultBlockParam
|
|
48
52
|
from google.generativeai.types import ContentType
|
|
49
53
|
from openai.types import CompletionUsage
|
|
50
54
|
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionMessageToolCallParam
|
|
@@ -255,6 +259,8 @@ class OpenAIStreamingClient(PlaygroundStreamingClient):
|
|
|
255
259
|
from openai import RateLimitError as OpenAIRateLimitError
|
|
256
260
|
|
|
257
261
|
super().__init__(model=model, api_key=api_key)
|
|
262
|
+
self._attributes[LLM_PROVIDER] = OpenInferenceLLMProviderValues.OPENAI.value
|
|
263
|
+
self._attributes[LLM_SYSTEM] = OpenInferenceLLMSystemValues.OPENAI.value
|
|
258
264
|
self.client = AsyncOpenAI(api_key=api_key)
|
|
259
265
|
self.model_name = model.name
|
|
260
266
|
self.rate_limiter = PlaygroundRateLimiter(model.provider_key, OpenAIRateLimitError)
|
|
@@ -610,6 +616,8 @@ class AzureOpenAIStreamingClient(OpenAIStreamingClient):
|
|
|
610
616
|
from openai import AsyncAzureOpenAI
|
|
611
617
|
|
|
612
618
|
super().__init__(model=model, api_key=api_key)
|
|
619
|
+
self._attributes[LLM_PROVIDER] = OpenInferenceLLMProviderValues.AZURE.value
|
|
620
|
+
self._attributes[LLM_SYSTEM] = OpenInferenceLLMSystemValues.OPENAI.value
|
|
613
621
|
if model.endpoint is None or model.api_version is None:
|
|
614
622
|
raise ValueError("endpoint and api_version are required for Azure OpenAI models")
|
|
615
623
|
self.client = AsyncAzureOpenAI(
|
|
@@ -638,6 +646,8 @@ class AnthropicStreamingClient(PlaygroundStreamingClient):
|
|
|
638
646
|
import anthropic
|
|
639
647
|
|
|
640
648
|
super().__init__(model=model, api_key=api_key)
|
|
649
|
+
self._attributes[LLM_PROVIDER] = OpenInferenceLLMProviderValues.ANTHROPIC.value
|
|
650
|
+
self._attributes[LLM_SYSTEM] = OpenInferenceLLMSystemValues.ANTHROPIC.value
|
|
641
651
|
self.client = anthropic.AsyncAnthropic(api_key=api_key)
|
|
642
652
|
self.model_name = model.name
|
|
643
653
|
self.rate_limiter = PlaygroundRateLimiter(model.provider_key, anthropic.RateLimitError)
|
|
@@ -693,7 +703,6 @@ class AnthropicStreamingClient(PlaygroundStreamingClient):
|
|
|
693
703
|
import anthropic.types as anthropic_types
|
|
694
704
|
|
|
695
705
|
anthropic_messages, system_prompt = self._build_anthropic_messages(messages)
|
|
696
|
-
|
|
697
706
|
anthropic_params = {
|
|
698
707
|
"messages": anthropic_messages,
|
|
699
708
|
"model": self.model_name,
|
|
@@ -751,19 +760,44 @@ class AnthropicStreamingClient(PlaygroundStreamingClient):
|
|
|
751
760
|
anthropic_messages: list["MessageParam"] = []
|
|
752
761
|
system_prompt = ""
|
|
753
762
|
for role, content, _tool_call_id, _tool_calls in messages:
|
|
763
|
+
tool_aware_content = self._anthropic_message_content(content, _tool_calls)
|
|
754
764
|
if role == ChatCompletionMessageRole.USER:
|
|
755
|
-
anthropic_messages.append({"role": "user", "content":
|
|
765
|
+
anthropic_messages.append({"role": "user", "content": tool_aware_content})
|
|
756
766
|
elif role == ChatCompletionMessageRole.AI:
|
|
757
|
-
anthropic_messages.append({"role": "assistant", "content":
|
|
767
|
+
anthropic_messages.append({"role": "assistant", "content": tool_aware_content})
|
|
758
768
|
elif role == ChatCompletionMessageRole.SYSTEM:
|
|
759
769
|
system_prompt += content + "\n"
|
|
760
770
|
elif role == ChatCompletionMessageRole.TOOL:
|
|
761
|
-
|
|
771
|
+
anthropic_messages.append(
|
|
772
|
+
{
|
|
773
|
+
"role": "user",
|
|
774
|
+
"content": [
|
|
775
|
+
{
|
|
776
|
+
"type": "tool_result",
|
|
777
|
+
"tool_use_id": _tool_call_id or "",
|
|
778
|
+
"content": content or "",
|
|
779
|
+
}
|
|
780
|
+
],
|
|
781
|
+
}
|
|
782
|
+
)
|
|
762
783
|
else:
|
|
763
784
|
assert_never(role)
|
|
764
785
|
|
|
765
786
|
return anthropic_messages, system_prompt
|
|
766
787
|
|
|
788
|
+
def _anthropic_message_content(
|
|
789
|
+
self, content: str, tool_calls: Optional[list[JSONScalarType]]
|
|
790
|
+
) -> Union[str, list[Union["ToolResultBlockParam", "TextBlockParam"]]]:
|
|
791
|
+
if tool_calls:
|
|
792
|
+
# Anthropic combines tool calls and the reasoning text into a single message object
|
|
793
|
+
tool_use_content: list[Union["ToolResultBlockParam", "TextBlockParam"]] = []
|
|
794
|
+
if content:
|
|
795
|
+
tool_use_content.append({"type": "text", "text": content})
|
|
796
|
+
tool_use_content.extend(tool_calls)
|
|
797
|
+
return tool_use_content
|
|
798
|
+
|
|
799
|
+
return content
|
|
800
|
+
|
|
767
801
|
|
|
768
802
|
@register_llm_client(
|
|
769
803
|
provider_key=GenerativeProviderKey.GEMINI,
|
|
@@ -784,6 +818,8 @@ class GeminiStreamingClient(PlaygroundStreamingClient):
|
|
|
784
818
|
import google.generativeai as google_genai
|
|
785
819
|
|
|
786
820
|
super().__init__(model=model, api_key=api_key)
|
|
821
|
+
self._attributes[LLM_PROVIDER] = OpenInferenceLLMProviderValues.GOOGLE.value
|
|
822
|
+
self._attributes[LLM_SYSTEM] = OpenInferenceLLMSystemValues.VERTEXAI.value
|
|
787
823
|
google_genai.configure(api_key=api_key)
|
|
788
824
|
self.model_name = model.name
|
|
789
825
|
|
|
@@ -905,6 +941,8 @@ def initialize_playground_clients() -> None:
|
|
|
905
941
|
pass
|
|
906
942
|
|
|
907
943
|
|
|
944
|
+
LLM_PROVIDER = SpanAttributes.LLM_PROVIDER
|
|
945
|
+
LLM_SYSTEM = SpanAttributes.LLM_SYSTEM
|
|
908
946
|
LLM_TOKEN_COUNT_PROMPT = SpanAttributes.LLM_TOKEN_COUNT_PROMPT
|
|
909
947
|
LLM_TOKEN_COUNT_COMPLETION = SpanAttributes.LLM_TOKEN_COUNT_COMPLETION
|
|
910
948
|
LLM_TOKEN_COUNT_TOTAL = SpanAttributes.LLM_TOKEN_COUNT_TOTAL
|
|
@@ -330,20 +330,45 @@ def llm_input_messages(
|
|
|
330
330
|
tuple[ChatCompletionMessageRole, str, Optional[str], Optional[list[JSONScalarType]]]
|
|
331
331
|
],
|
|
332
332
|
) -> Iterator[tuple[str, Any]]:
|
|
333
|
-
for i, (role, content,
|
|
333
|
+
for i, (role, content, tool_call_id, tool_calls) in enumerate(messages):
|
|
334
334
|
yield f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_ROLE}", role.value.lower()
|
|
335
335
|
yield f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_CONTENT}", content
|
|
336
|
+
if role == ChatCompletionMessageRole.TOOL and tool_call_id:
|
|
337
|
+
# Anthropic tool result spans
|
|
338
|
+
yield f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALL_ID}", tool_call_id
|
|
339
|
+
|
|
336
340
|
if tool_calls is not None:
|
|
337
341
|
for tool_call_index, tool_call in enumerate(tool_calls):
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
342
|
+
if tool_call.get("type") == "tool_use":
|
|
343
|
+
# Anthropic tool call spans
|
|
344
|
+
yield (
|
|
345
|
+
f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_NAME}",
|
|
346
|
+
tool_call["name"],
|
|
347
|
+
)
|
|
343
348
|
yield (
|
|
344
349
|
f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_ARGUMENTS_JSON}",
|
|
345
|
-
safe_json_dumps(jsonify(
|
|
350
|
+
safe_json_dumps(jsonify(tool_call["input"])),
|
|
346
351
|
)
|
|
352
|
+
yield (
|
|
353
|
+
f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_ID}",
|
|
354
|
+
tool_call["id"],
|
|
355
|
+
)
|
|
356
|
+
elif tool_call_function := tool_call.get("function"):
|
|
357
|
+
# OpenAI tool call spans
|
|
358
|
+
yield (
|
|
359
|
+
f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_NAME}",
|
|
360
|
+
tool_call_function["name"],
|
|
361
|
+
)
|
|
362
|
+
if arguments := tool_call_function["arguments"]:
|
|
363
|
+
yield (
|
|
364
|
+
f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_FUNCTION_ARGUMENTS_JSON}",
|
|
365
|
+
safe_json_dumps(jsonify(arguments)),
|
|
366
|
+
)
|
|
367
|
+
if tool_call_id := tool_call.get("id"):
|
|
368
|
+
yield (
|
|
369
|
+
f"{LLM_INPUT_MESSAGES}.{i}.{MESSAGE_TOOL_CALLS}.{tool_call_index}.{TOOL_CALL_ID}",
|
|
370
|
+
tool_call_id,
|
|
371
|
+
)
|
|
347
372
|
|
|
348
373
|
|
|
349
374
|
def _llm_output_messages(
|
|
@@ -418,5 +443,6 @@ MESSAGE_TOOL_CALLS = MessageAttributes.MESSAGE_TOOL_CALLS
|
|
|
418
443
|
|
|
419
444
|
TOOL_CALL_FUNCTION_NAME = ToolCallAttributes.TOOL_CALL_FUNCTION_NAME
|
|
420
445
|
TOOL_CALL_FUNCTION_ARGUMENTS_JSON = ToolCallAttributes.TOOL_CALL_FUNCTION_ARGUMENTS_JSON
|
|
421
|
-
|
|
446
|
+
TOOL_CALL_ID = ToolCallAttributes.TOOL_CALL_ID
|
|
447
|
+
MESSAGE_TOOL_CALL_ID = MessageAttributes.MESSAGE_TOOL_CALL_ID
|
|
422
448
|
TOOL_JSON_SCHEMA = ToolAttributes.TOOL_JSON_SCHEMA
|
|
@@ -14,7 +14,7 @@ from sqlalchemy import Boolean, and_, case, cast, func, insert, or_, select, upd
|
|
|
14
14
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
15
15
|
from sqlalchemy.orm import joinedload
|
|
16
16
|
from sqlean.dbapi2 import IntegrityError # type: ignore[import-untyped]
|
|
17
|
-
from starlette.datastructures import URL
|
|
17
|
+
from starlette.datastructures import URL, URLPath
|
|
18
18
|
from starlette.responses import RedirectResponse
|
|
19
19
|
from starlette.routing import Router
|
|
20
20
|
from starlette.status import HTTP_302_FOUND
|
|
@@ -86,8 +86,16 @@ async def login(
|
|
|
86
86
|
if not isinstance(
|
|
87
87
|
oauth2_client := request.app.state.oauth2_clients.get_client(idp_name), OAuth2Client
|
|
88
88
|
):
|
|
89
|
-
return _redirect_to_login(error=f"Unknown IDP: {idp_name}.")
|
|
90
|
-
|
|
89
|
+
return _redirect_to_login(request=request, error=f"Unknown IDP: {idp_name}.")
|
|
90
|
+
if (referer := request.headers.get("referer")) is not None:
|
|
91
|
+
# if the referer header is present, use it as the origin URL
|
|
92
|
+
parsed_url = urlparse(referer)
|
|
93
|
+
origin_url = _append_root_path_if_exists(
|
|
94
|
+
request=request, base_url=f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
# fall back to the base url as the origin URL
|
|
98
|
+
origin_url = str(request.base_url)
|
|
91
99
|
authorization_url_data = await oauth2_client.create_authorization_url(
|
|
92
100
|
redirect_uri=_get_create_tokens_endpoint(
|
|
93
101
|
request=request, origin_url=origin_url, idp_name=idp_name
|
|
@@ -124,22 +132,22 @@ async def create_tokens(
|
|
|
124
132
|
) -> RedirectResponse:
|
|
125
133
|
secret = request.app.state.get_secret()
|
|
126
134
|
if state != stored_state:
|
|
127
|
-
return _redirect_to_login(error=_INVALID_OAUTH2_STATE_MESSAGE)
|
|
135
|
+
return _redirect_to_login(request=request, error=_INVALID_OAUTH2_STATE_MESSAGE)
|
|
128
136
|
try:
|
|
129
137
|
payload = _parse_state_payload(secret=secret, state=state)
|
|
130
138
|
except JoseError:
|
|
131
|
-
return _redirect_to_login(error=_INVALID_OAUTH2_STATE_MESSAGE)
|
|
139
|
+
return _redirect_to_login(request=request, error=_INVALID_OAUTH2_STATE_MESSAGE)
|
|
132
140
|
if (return_url := payload.get("return_url")) is not None and not _is_relative_url(
|
|
133
141
|
unquote(return_url)
|
|
134
142
|
):
|
|
135
|
-
return _redirect_to_login(error="Attempting login with unsafe return URL.")
|
|
143
|
+
return _redirect_to_login(request=request, error="Attempting login with unsafe return URL.")
|
|
136
144
|
assert isinstance(access_token_expiry := request.app.state.access_token_expiry, timedelta)
|
|
137
145
|
assert isinstance(refresh_token_expiry := request.app.state.refresh_token_expiry, timedelta)
|
|
138
146
|
token_store: TokenStore = request.app.state.get_token_store()
|
|
139
147
|
if not isinstance(
|
|
140
148
|
oauth2_client := request.app.state.oauth2_clients.get_client(idp_name), OAuth2Client
|
|
141
149
|
):
|
|
142
|
-
return _redirect_to_login(error=f"Unknown IDP: {idp_name}.")
|
|
150
|
+
return _redirect_to_login(request=request, error=f"Unknown IDP: {idp_name}.")
|
|
143
151
|
try:
|
|
144
152
|
token_data = await oauth2_client.fetch_access_token(
|
|
145
153
|
state=state,
|
|
@@ -149,11 +157,12 @@ async def create_tokens(
|
|
|
149
157
|
),
|
|
150
158
|
)
|
|
151
159
|
except OAuthError as error:
|
|
152
|
-
return _redirect_to_login(error=str(error))
|
|
160
|
+
return _redirect_to_login(request=request, error=str(error))
|
|
153
161
|
_validate_token_data(token_data)
|
|
154
162
|
if "id_token" not in token_data:
|
|
155
163
|
return _redirect_to_login(
|
|
156
|
-
|
|
164
|
+
request=request,
|
|
165
|
+
error=f"OAuth2 IDP {idp_name} does not appear to support OpenID Connect.",
|
|
157
166
|
)
|
|
158
167
|
user_info = await oauth2_client.parse_id_token(token_data, nonce=stored_nonce)
|
|
159
168
|
user_info = _parse_user_info(user_info)
|
|
@@ -165,14 +174,18 @@ async def create_tokens(
|
|
|
165
174
|
user_info=user_info,
|
|
166
175
|
)
|
|
167
176
|
except EmailAlreadyInUse as error:
|
|
168
|
-
return _redirect_to_login(error=str(error))
|
|
177
|
+
return _redirect_to_login(request=request, error=str(error))
|
|
169
178
|
access_token, refresh_token = await create_access_and_refresh_tokens(
|
|
170
179
|
user=user,
|
|
171
180
|
token_store=token_store,
|
|
172
181
|
access_token_expiry=access_token_expiry,
|
|
173
182
|
refresh_token_expiry=refresh_token_expiry,
|
|
174
183
|
)
|
|
175
|
-
|
|
184
|
+
redirect_path = _prepend_root_path_if_exists(request=request, path=return_url or "/")
|
|
185
|
+
response = RedirectResponse(
|
|
186
|
+
url=redirect_path,
|
|
187
|
+
status_code=HTTP_302_FOUND,
|
|
188
|
+
)
|
|
176
189
|
response = set_access_token_cookie(
|
|
177
190
|
response=response, access_token=access_token, max_age=access_token_expiry
|
|
178
191
|
)
|
|
@@ -352,17 +365,46 @@ class EmailAlreadyInUse(Exception):
|
|
|
352
365
|
pass
|
|
353
366
|
|
|
354
367
|
|
|
355
|
-
def _redirect_to_login(*, error: str) -> RedirectResponse:
|
|
368
|
+
def _redirect_to_login(*, request: Request, error: str) -> RedirectResponse:
|
|
356
369
|
"""
|
|
357
370
|
Creates a RedirectResponse to the login page to display an error message.
|
|
358
371
|
"""
|
|
359
|
-
|
|
372
|
+
login_path = _prepend_root_path_if_exists(request=request, path="/login")
|
|
373
|
+
url = URL(login_path).include_query_params(error=error)
|
|
360
374
|
response = RedirectResponse(url=url)
|
|
361
375
|
response = delete_oauth2_state_cookie(response)
|
|
362
376
|
response = delete_oauth2_nonce_cookie(response)
|
|
363
377
|
return response
|
|
364
378
|
|
|
365
379
|
|
|
380
|
+
def _prepend_root_path_if_exists(*, request: Request, path: str) -> str:
|
|
381
|
+
"""
|
|
382
|
+
If a root path is configured, prepends it to the input path.
|
|
383
|
+
"""
|
|
384
|
+
if not path.startswith("/"):
|
|
385
|
+
raise ValueError("path must start with a forward slash")
|
|
386
|
+
root_path = _get_root_path(request=request)
|
|
387
|
+
if root_path.endswith("/"):
|
|
388
|
+
root_path = root_path.rstrip("/")
|
|
389
|
+
return root_path + path
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _append_root_path_if_exists(*, request: Request, base_url: str) -> str:
|
|
393
|
+
"""
|
|
394
|
+
If a root path is configured, appends it to the input base url.
|
|
395
|
+
"""
|
|
396
|
+
if not (root_path := _get_root_path(request=request)):
|
|
397
|
+
return base_url
|
|
398
|
+
return str(URLPath(root_path).make_absolute_url(base_url=base_url))
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _get_root_path(*, request: Request) -> str:
|
|
402
|
+
"""
|
|
403
|
+
Gets the root path from the request.
|
|
404
|
+
"""
|
|
405
|
+
return str(request.scope.get("root_path", ""))
|
|
406
|
+
|
|
407
|
+
|
|
366
408
|
def _get_create_tokens_endpoint(*, request: Request, origin_url: str, idp_name: str) -> str:
|
|
367
409
|
"""
|
|
368
410
|
Gets the endpoint for create tokens route.
|
|
@@ -427,16 +469,6 @@ def _with_random_suffix(string: str) -> str:
|
|
|
427
469
|
return f"{string}-{randrange(10_000, 100_000)}"
|
|
428
470
|
|
|
429
471
|
|
|
430
|
-
def _get_origin_url(request: Request) -> str:
|
|
431
|
-
"""
|
|
432
|
-
Infers the origin URL from the request.
|
|
433
|
-
"""
|
|
434
|
-
if (referer := request.headers.get("referer")) is None:
|
|
435
|
-
return str(request.base_url)
|
|
436
|
-
parsed_url = urlparse(referer)
|
|
437
|
-
return f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
438
|
-
|
|
439
|
-
|
|
440
472
|
def _is_oauth2_state_payload(maybe_state_payload: Any) -> TypeGuard[_OAuth2StatePayload]:
|
|
441
473
|
"""
|
|
442
474
|
Determines whether the given object is an OAuth2 state payload.
|
|
@@ -127,7 +127,7 @@ class Subscription:
|
|
|
127
127
|
):
|
|
128
128
|
span.add_response_chunk(chunk)
|
|
129
129
|
yield chunk
|
|
130
|
-
|
|
130
|
+
span.set_attributes(llm_client.attributes)
|
|
131
131
|
if span.status_message is not None:
|
|
132
132
|
yield ChatCompletionSubscriptionError(message=span.status_message)
|
|
133
133
|
async with info.context.db() as session:
|
|
@@ -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
|