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.

Files changed (113) hide show
  1. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/METADATA +26 -4
  2. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/RECORD +80 -75
  3. phoenix/__init__.py +9 -5
  4. phoenix/config.py +109 -53
  5. phoenix/datetime_utils.py +18 -1
  6. phoenix/db/README.md +25 -0
  7. phoenix/db/__init__.py +4 -0
  8. phoenix/db/alembic.ini +119 -0
  9. phoenix/db/bulk_inserter.py +206 -0
  10. phoenix/db/engines.py +152 -0
  11. phoenix/db/helpers.py +47 -0
  12. phoenix/db/insertion/evaluation.py +209 -0
  13. phoenix/db/insertion/helpers.py +51 -0
  14. phoenix/db/insertion/span.py +142 -0
  15. phoenix/db/migrate.py +71 -0
  16. phoenix/db/migrations/env.py +121 -0
  17. phoenix/db/migrations/script.py.mako +26 -0
  18. phoenix/db/migrations/versions/cf03bd6bae1d_init.py +280 -0
  19. phoenix/db/models.py +371 -0
  20. phoenix/exceptions.py +5 -1
  21. phoenix/server/api/context.py +40 -3
  22. phoenix/server/api/dataloaders/__init__.py +97 -0
  23. phoenix/server/api/dataloaders/cache/__init__.py +3 -0
  24. phoenix/server/api/dataloaders/cache/two_tier_cache.py +67 -0
  25. phoenix/server/api/dataloaders/document_evaluation_summaries.py +152 -0
  26. phoenix/server/api/dataloaders/document_evaluations.py +37 -0
  27. phoenix/server/api/dataloaders/document_retrieval_metrics.py +98 -0
  28. phoenix/server/api/dataloaders/evaluation_summaries.py +151 -0
  29. phoenix/server/api/dataloaders/latency_ms_quantile.py +198 -0
  30. phoenix/server/api/dataloaders/min_start_or_max_end_times.py +93 -0
  31. phoenix/server/api/dataloaders/record_counts.py +125 -0
  32. phoenix/server/api/dataloaders/span_descendants.py +64 -0
  33. phoenix/server/api/dataloaders/span_evaluations.py +37 -0
  34. phoenix/server/api/dataloaders/token_counts.py +138 -0
  35. phoenix/server/api/dataloaders/trace_evaluations.py +37 -0
  36. phoenix/server/api/input_types/SpanSort.py +138 -68
  37. phoenix/server/api/routers/v1/__init__.py +11 -0
  38. phoenix/server/api/routers/v1/evaluations.py +275 -0
  39. phoenix/server/api/routers/v1/spans.py +126 -0
  40. phoenix/server/api/routers/v1/traces.py +82 -0
  41. phoenix/server/api/schema.py +112 -48
  42. phoenix/server/api/types/DocumentEvaluationSummary.py +1 -1
  43. phoenix/server/api/types/Evaluation.py +29 -12
  44. phoenix/server/api/types/EvaluationSummary.py +29 -44
  45. phoenix/server/api/types/MimeType.py +2 -2
  46. phoenix/server/api/types/Model.py +9 -9
  47. phoenix/server/api/types/Project.py +240 -171
  48. phoenix/server/api/types/Span.py +87 -131
  49. phoenix/server/api/types/Trace.py +29 -20
  50. phoenix/server/api/types/pagination.py +151 -10
  51. phoenix/server/app.py +263 -35
  52. phoenix/server/grpc_server.py +93 -0
  53. phoenix/server/main.py +75 -60
  54. phoenix/server/openapi/docs.py +218 -0
  55. phoenix/server/prometheus.py +23 -7
  56. phoenix/server/static/index.js +662 -643
  57. phoenix/server/telemetry.py +68 -0
  58. phoenix/services.py +4 -0
  59. phoenix/session/client.py +34 -30
  60. phoenix/session/data_extractor.py +8 -3
  61. phoenix/session/session.py +176 -155
  62. phoenix/settings.py +13 -0
  63. phoenix/trace/attributes.py +349 -0
  64. phoenix/trace/dsl/README.md +116 -0
  65. phoenix/trace/dsl/filter.py +660 -192
  66. phoenix/trace/dsl/helpers.py +24 -5
  67. phoenix/trace/dsl/query.py +562 -185
  68. phoenix/trace/fixtures.py +69 -7
  69. phoenix/trace/otel.py +44 -200
  70. phoenix/trace/schemas.py +14 -8
  71. phoenix/trace/span_evaluations.py +5 -2
  72. phoenix/utilities/__init__.py +0 -26
  73. phoenix/utilities/span_store.py +0 -23
  74. phoenix/version.py +1 -1
  75. phoenix/core/project.py +0 -773
  76. phoenix/core/traces.py +0 -96
  77. phoenix/datasets/dataset.py +0 -214
  78. phoenix/datasets/fixtures.py +0 -24
  79. phoenix/datasets/schema.py +0 -31
  80. phoenix/experimental/evals/__init__.py +0 -73
  81. phoenix/experimental/evals/evaluators.py +0 -413
  82. phoenix/experimental/evals/functions/__init__.py +0 -4
  83. phoenix/experimental/evals/functions/classify.py +0 -453
  84. phoenix/experimental/evals/functions/executor.py +0 -353
  85. phoenix/experimental/evals/functions/generate.py +0 -138
  86. phoenix/experimental/evals/functions/processing.py +0 -76
  87. phoenix/experimental/evals/models/__init__.py +0 -14
  88. phoenix/experimental/evals/models/anthropic.py +0 -175
  89. phoenix/experimental/evals/models/base.py +0 -170
  90. phoenix/experimental/evals/models/bedrock.py +0 -221
  91. phoenix/experimental/evals/models/litellm.py +0 -134
  92. phoenix/experimental/evals/models/openai.py +0 -453
  93. phoenix/experimental/evals/models/rate_limiters.py +0 -246
  94. phoenix/experimental/evals/models/vertex.py +0 -173
  95. phoenix/experimental/evals/models/vertexai.py +0 -186
  96. phoenix/experimental/evals/retrievals.py +0 -96
  97. phoenix/experimental/evals/templates/__init__.py +0 -50
  98. phoenix/experimental/evals/templates/default_templates.py +0 -472
  99. phoenix/experimental/evals/templates/template.py +0 -195
  100. phoenix/experimental/evals/utils/__init__.py +0 -172
  101. phoenix/experimental/evals/utils/threads.py +0 -27
  102. phoenix/server/api/routers/evaluation_handler.py +0 -110
  103. phoenix/server/api/routers/span_handler.py +0 -70
  104. phoenix/server/api/routers/trace_handler.py +0 -60
  105. phoenix/storage/span_store/__init__.py +0 -23
  106. phoenix/storage/span_store/text_file.py +0 -85
  107. phoenix/trace/dsl/missing.py +0 -60
  108. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/WHEEL +0 -0
  109. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/licenses/IP_NOTICE +0 -0
  110. {arize_phoenix-3.25.0.dist-info → arize_phoenix-4.0.1.dist-info}/licenses/LICENSE +0 -0
  111. /phoenix/{datasets → db/insertion}/__init__.py +0 -0
  112. /phoenix/{experimental → db/migrations}/__init__.py +0 -0
  113. /phoenix/{storage → server/openapi}/__init__.py +0 -0
@@ -1,134 +0,0 @@
1
- import logging
2
- import warnings
3
- from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
5
-
6
- from phoenix.experimental.evals.models.base import BaseEvalModel
7
-
8
- if TYPE_CHECKING:
9
- from tiktoken import Encoding
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- @dataclass
15
- class LiteLLMModel(BaseEvalModel):
16
- model: str = "gpt-3.5-turbo"
17
- """The model name to use."""
18
- temperature: float = 0.0
19
- """What sampling temperature to use."""
20
- max_tokens: int = 256
21
- """The maximum number of tokens to generate in the completion."""
22
- top_p: float = 1
23
- """Total probability mass of tokens to consider at each step."""
24
- num_retries: int = 6
25
- """Maximum number to retry a model if an RateLimitError, OpenAIError, or
26
- ServiceUnavailableError occurs."""
27
- request_timeout: int = 60
28
- """Maximum number of seconds to wait when retrying."""
29
- model_kwargs: Dict[str, Any] = field(default_factory=dict)
30
- """Model specific params"""
31
-
32
- # non-LiteLLM params
33
- retry_min_seconds: int = 10
34
- """Minimum number of seconds to wait when retrying."""
35
- max_content_size: Optional[int] = None
36
- """If you're using a fine-tuned model, set this to the maximum content size"""
37
-
38
- # Deprecated fields
39
- model_name: Optional[str] = None
40
- """
41
- .. deprecated:: 3.0.0
42
- use `model` instead. This will be removed in a future release.
43
- """
44
-
45
- def __post_init__(self) -> None:
46
- self._migrate_model_name()
47
- self._init_environment()
48
- self._init_model_encoding()
49
-
50
- def _migrate_model_name(self) -> None:
51
- if self.model_name is not None:
52
- warning_message = "The `model_name` field is deprecated. Use `model` instead. \
53
- This will be removed in a future release."
54
- warnings.warn(
55
- warning_message,
56
- DeprecationWarning,
57
- )
58
- print(warning_message)
59
- self.model = self.model_name
60
- self.model_name = None
61
-
62
- def _init_environment(self) -> None:
63
- try:
64
- import litellm
65
- from litellm import validate_environment
66
-
67
- self._litellm = litellm
68
- env_info = validate_environment(self._litellm.utils.get_llm_provider(self.model))
69
-
70
- if not env_info["keys_in_environment"] and env_info["missing_keys"]:
71
- raise RuntimeError(
72
- f"Missing environment variable(s): '{str(env_info['missing_keys'])}', for "
73
- f"model: {self.model}. \nFor additional information about the right "
74
- "environment variables for specific model providers:\n"
75
- "https://docs.litellm.ai/docs/completion/input#provider-specific-params."
76
- )
77
- except ImportError:
78
- self._raise_import_error(
79
- package_display_name="LiteLLM",
80
- package_name="litellm",
81
- )
82
-
83
- def _init_model_encoding(self) -> None:
84
- from litellm import decode, encode
85
-
86
- self._encoding = encode
87
- self._decoding = decode
88
-
89
- @property
90
- def max_context_size(self) -> int:
91
- context_size = self.max_content_size or self._litellm.get_max_tokens(self.model).get(
92
- "max_tokens", None
93
- )
94
-
95
- if context_size is None:
96
- raise ValueError(
97
- "Can't determine maximum context size. An unknown model was "
98
- + f"used: {self.model}."
99
- )
100
-
101
- return context_size
102
-
103
- @property
104
- def encoder(self) -> "Encoding":
105
- raise NotImplementedError
106
-
107
- def get_tokens_from_text(self, text: str) -> List[int]:
108
- result: List[int] = self._encoding(model=self.model, text=text)
109
- return result
110
-
111
- def get_text_from_tokens(self, tokens: List[int]) -> str:
112
- return str(self._decoding(model=self.model, tokens=tokens))
113
-
114
- async def _async_generate(self, prompt: str, **kwargs: Dict[str, Any]) -> str:
115
- return self._generate(prompt, **kwargs)
116
-
117
- def _generate(self, prompt: str, **kwargs: Dict[str, Any]) -> str:
118
- messages = self._get_messages_from_prompt(prompt)
119
- response = self._litellm.completion(
120
- model=self.model,
121
- messages=messages,
122
- temperature=self.temperature,
123
- max_tokens=self.max_tokens,
124
- top_p=self.top_p,
125
- num_retries=self.num_retries,
126
- request_timeout=self.request_timeout,
127
- **self.model_kwargs,
128
- )
129
- return str(response.choices[0].message.content)
130
-
131
- def _get_messages_from_prompt(self, prompt: str) -> List[Dict[str, str]]:
132
- # LiteLLM requires prompts in the format of messages
133
- # messages=[{"content": "ABC?","role": "user"}]
134
- return [{"content": prompt, "role": "user"}]
@@ -1,453 +0,0 @@
1
- import logging
2
- import os
3
- import warnings
4
- from dataclasses import dataclass, field, fields
5
- from typing import (
6
- TYPE_CHECKING,
7
- Any,
8
- Callable,
9
- Dict,
10
- List,
11
- Mapping,
12
- Optional,
13
- Tuple,
14
- Union,
15
- get_args,
16
- get_origin,
17
- )
18
-
19
- from phoenix.exceptions import PhoenixContextLimitExceeded
20
- from phoenix.experimental.evals.models.base import BaseEvalModel
21
- from phoenix.experimental.evals.models.rate_limiters import RateLimiter
22
-
23
- if TYPE_CHECKING:
24
- from tiktoken import Encoding
25
-
26
- OPENAI_API_KEY_ENVVAR_NAME = "OPENAI_API_KEY"
27
- MINIMUM_OPENAI_VERSION = "1.0.0"
28
- MODEL_TOKEN_LIMIT_MAPPING = {
29
- "gpt-3.5-turbo-instruct": 4096,
30
- "gpt-3.5-turbo-0301": 4096,
31
- "gpt-3.5-turbo-0613": 4096, # Current gpt-3.5-turbo default
32
- "gpt-3.5-turbo-16k-0613": 16385,
33
- "gpt-4-0314": 8192,
34
- "gpt-4-0613": 8192, # Current gpt-4 default
35
- "gpt-4-32k-0314": 32768,
36
- "gpt-4-32k-0613": 32768,
37
- "gpt-4-1106-preview": 128000,
38
- "gpt-4-0125-preview": 128000,
39
- "gpt-4-turbo-preview": 128000,
40
- "gpt-4-vision-preview": 128000,
41
- }
42
- LEGACY_COMPLETION_API_MODELS = ("gpt-3.5-turbo-instruct",)
43
- logger = logging.getLogger(__name__)
44
-
45
-
46
- @dataclass
47
- class AzureOptions:
48
- api_version: str
49
- azure_endpoint: str
50
- azure_deployment: Optional[str]
51
- azure_ad_token: Optional[str]
52
- azure_ad_token_provider: Optional[Callable[[], str]]
53
-
54
-
55
- @dataclass
56
- class OpenAIModel(BaseEvalModel):
57
- api_key: Optional[str] = field(repr=False, default=None)
58
- """Your OpenAI key. If not provided, will be read from the environment variable"""
59
- organization: Optional[str] = field(repr=False, default=None)
60
- """
61
- The organization to use for the OpenAI API. If not provided, will default
62
- to what's configured in OpenAI
63
- """
64
- base_url: Optional[str] = field(repr=False, default=None)
65
- """
66
- An optional base URL to use for the OpenAI API. If not provided, will default
67
- to what's configured in OpenAI
68
- """
69
- model: str = "gpt-4"
70
- """
71
- Model name to use. In of azure, this is the deployment name such as gpt-35-instant
72
- """
73
- temperature: float = 0.0
74
- """What sampling temperature to use."""
75
- max_tokens: int = 256
76
- """The maximum number of tokens to generate in the completion.
77
- -1 returns as many tokens as possible given the prompt and
78
- the models maximal context size."""
79
- top_p: float = 1
80
- """Total probability mass of tokens to consider at each step."""
81
- frequency_penalty: float = 0
82
- """Penalizes repeated tokens according to frequency."""
83
- presence_penalty: float = 0
84
- """Penalizes repeated tokens."""
85
- n: int = 1
86
- """How many completions to generate for each prompt."""
87
- model_kwargs: Dict[str, Any] = field(default_factory=dict)
88
- """Holds any model parameters valid for `create` call not explicitly specified."""
89
- batch_size: int = 20
90
- # TODO: IMPLEMENT BATCHING
91
- """Batch size to use when passing multiple documents to generate."""
92
- request_timeout: Optional[Union[float, Tuple[float, float]]] = None
93
- """Timeout for requests to OpenAI completion API. Default is 600 seconds."""
94
- max_retries: int = 20
95
- """Maximum number of retries to make when generating."""
96
- retry_min_seconds: int = 10
97
- """Minimum number of seconds to wait when retrying."""
98
- retry_max_seconds: int = 60
99
- """Maximum number of seconds to wait when retrying."""
100
-
101
- # Azure options
102
- api_version: Optional[str] = field(default=None)
103
- """https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning"""
104
- azure_endpoint: Optional[str] = field(default=None)
105
- """
106
- The endpoint to use for azure openai. Available in the azure portal.
107
- https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
108
- """
109
- azure_deployment: Optional[str] = field(default=None)
110
- azure_ad_token: Optional[str] = field(default=None)
111
- azure_ad_token_provider: Optional[Callable[[], str]] = field(default=None)
112
- default_headers: Optional[Mapping[str, str]] = field(default=None)
113
- """Default headers required by AzureOpenAI"""
114
-
115
- # Deprecated fields
116
- model_name: Optional[str] = field(default=None)
117
- """
118
- .. deprecated:: 3.0.0
119
- use `model` instead. This will be removed
120
- """
121
-
122
- def __post_init__(self) -> None:
123
- self._migrate_model_name()
124
- self._init_environment()
125
- self._init_open_ai()
126
- self._init_tiktoken()
127
- self._init_rate_limiter()
128
-
129
- def reload_client(self) -> None:
130
- self._init_open_ai()
131
-
132
- def _migrate_model_name(self) -> None:
133
- if self.model_name:
134
- warning_message = "The `model_name` field is deprecated. Use `model` instead. \
135
- This will be removed in a future release."
136
- print(
137
- warning_message,
138
- )
139
- warnings.warn(warning_message, DeprecationWarning)
140
- self.model = self.model_name
141
- self.model_name = None
142
-
143
- def _init_environment(self) -> None:
144
- try:
145
- import openai
146
- import openai._utils as openai_util
147
-
148
- self._openai = openai
149
- self._openai_util = openai_util
150
- except ImportError:
151
- self._raise_import_error(
152
- package_display_name="OpenAI",
153
- package_name="openai",
154
- package_min_version=MINIMUM_OPENAI_VERSION,
155
- )
156
- try:
157
- import tiktoken
158
-
159
- self._tiktoken = tiktoken
160
- except ImportError:
161
- self._raise_import_error(
162
- package_name="tiktoken",
163
- )
164
-
165
- def _init_open_ai(self) -> None:
166
- # For Azure, you need to provide the endpoint and the endpoint
167
- self._is_azure = bool(self.azure_endpoint)
168
-
169
- self._model_uses_legacy_completion_api = self.model.startswith(LEGACY_COMPLETION_API_MODELS)
170
- if self.api_key is None:
171
- api_key = os.getenv(OPENAI_API_KEY_ENVVAR_NAME)
172
- if api_key is None:
173
- # TODO: Create custom AuthenticationError
174
- raise RuntimeError(
175
- "OpenAI's API key not provided. Pass it as an argument to 'api_key' "
176
- "or set it in your environment: 'export OPENAI_API_KEY=sk-****'"
177
- )
178
- self.api_key = api_key
179
-
180
- # Set the version, organization, and base_url - default to openAI
181
- self.api_version = self.api_version or self._openai.api_version
182
- self.organization = self.organization or self._openai.organization
183
-
184
- # Initialize specific clients depending on the API backend
185
- # Set the type first
186
- self._client: Union[self._openai.OpenAI, self._openai.AzureOpenAI] # type: ignore
187
- self._async_client: Union[self._openai.AsyncOpenAI, self._openai.AsyncAzureOpenAI] # type: ignore
188
- if self._is_azure:
189
- # Validate the azure options and construct a client
190
- azure_options = self._get_azure_options()
191
- self._client = self._openai.AzureOpenAI(
192
- azure_endpoint=azure_options.azure_endpoint,
193
- azure_deployment=azure_options.azure_deployment,
194
- api_version=azure_options.api_version,
195
- azure_ad_token=azure_options.azure_ad_token,
196
- azure_ad_token_provider=azure_options.azure_ad_token_provider,
197
- api_key=self.api_key,
198
- organization=self.organization,
199
- default_headers=self.default_headers,
200
- )
201
- self._async_client = self._openai.AsyncAzureOpenAI(
202
- azure_endpoint=azure_options.azure_endpoint,
203
- azure_deployment=azure_options.azure_deployment,
204
- api_version=azure_options.api_version,
205
- azure_ad_token=azure_options.azure_ad_token,
206
- azure_ad_token_provider=azure_options.azure_ad_token_provider,
207
- api_key=self.api_key,
208
- organization=self.organization,
209
- default_headers=self.default_headers,
210
- )
211
- # return early since we don't need to check the model
212
- return
213
-
214
- # The client is not azure, so it must be openai
215
- self._client = self._openai.OpenAI(
216
- api_key=self.api_key,
217
- organization=self.organization,
218
- base_url=(self.base_url or self._openai.base_url),
219
- )
220
-
221
- # The client is not azure, so it must be openai
222
- self._async_client = self._openai.AsyncOpenAI(
223
- api_key=self.api_key,
224
- organization=self.organization,
225
- base_url=(self.base_url or self._openai.base_url),
226
- max_retries=0,
227
- )
228
-
229
- def _init_tiktoken(self) -> None:
230
- try:
231
- encoding = self._tiktoken.encoding_for_model(self.model)
232
- except KeyError:
233
- encoding = self._tiktoken.get_encoding("cl100k_base")
234
- self._tiktoken_encoding = encoding
235
-
236
- def _get_azure_options(self) -> AzureOptions:
237
- options = {}
238
- for option in fields(AzureOptions):
239
- if (value := getattr(self, option.name)) is not None:
240
- options[option.name] = value
241
- else:
242
- # raise ValueError if field is not optional
243
- # See if the field is optional - e.g. get_origin(Optional[T]) = typing.Union
244
- option_is_optional = get_origin(option.type) is Union and type(None) in get_args(
245
- option.type
246
- )
247
- if not option_is_optional:
248
- raise ValueError(
249
- f"Option '{option.name}' must be set when using Azure OpenAI API"
250
- )
251
- options[option.name] = None
252
- return AzureOptions(**options)
253
-
254
- def _init_rate_limiter(self) -> None:
255
- self._rate_limiter = RateLimiter(
256
- rate_limit_error=self._openai.RateLimitError,
257
- max_rate_limit_retries=10,
258
- initial_per_second_request_rate=5,
259
- maximum_per_second_request_rate=20,
260
- enforcement_window_minutes=1,
261
- )
262
-
263
- @staticmethod
264
- def _build_messages(
265
- prompt: str, system_instruction: Optional[str] = None
266
- ) -> List[Dict[str, str]]:
267
- messages = [{"role": "user", "content": prompt}]
268
- if system_instruction:
269
- messages.insert(0, {"role": "system", "content": str(system_instruction)})
270
- return messages
271
-
272
- def verbose_generation_info(self) -> str:
273
- return f"OpenAI invocation parameters: {self.public_invocation_params}"
274
-
275
- async def _async_generate(self, prompt: str, **kwargs: Any) -> str:
276
- invoke_params = self.invocation_params
277
- messages = self._build_messages(prompt, kwargs.get("instruction"))
278
- if functions := kwargs.get("functions"):
279
- invoke_params["functions"] = functions
280
- if function_call := kwargs.get("function_call"):
281
- invoke_params["function_call"] = function_call
282
- response = await self._async_rate_limited_completion(
283
- messages=messages,
284
- **invoke_params,
285
- )
286
- choice = response["choices"][0]
287
- if self._model_uses_legacy_completion_api:
288
- return str(choice["text"])
289
- message = choice["message"]
290
- if function_call := message.get("function_call"):
291
- return str(function_call.get("arguments") or "")
292
- return str(message["content"])
293
-
294
- def _generate(self, prompt: str, **kwargs: Any) -> str:
295
- invoke_params = self.invocation_params
296
- messages = self._build_messages(prompt, kwargs.get("instruction"))
297
- if functions := kwargs.get("functions"):
298
- invoke_params["functions"] = functions
299
- if function_call := kwargs.get("function_call"):
300
- invoke_params["function_call"] = function_call
301
- response = self._rate_limited_completion(
302
- messages=messages,
303
- **invoke_params,
304
- )
305
- choice = response["choices"][0]
306
- if self._model_uses_legacy_completion_api:
307
- return str(choice["text"])
308
- message = choice["message"]
309
- if function_call := message.get("function_call"):
310
- return str(function_call.get("arguments") or "")
311
- return str(message["content"])
312
-
313
- async def _async_rate_limited_completion(self, **kwargs: Any) -> Any:
314
- @self._rate_limiter.alimit
315
- async def _async_completion(**kwargs: Any) -> Any:
316
- try:
317
- if self._model_uses_legacy_completion_api:
318
- if "prompt" not in kwargs:
319
- kwargs["prompt"] = "\n\n".join(
320
- (message.get("content") or "")
321
- for message in (kwargs.pop("messages", None) or ())
322
- )
323
- # OpenAI 1.0.0 API responses are pydantic objects, not dicts
324
- # We must dump the model to get the dict
325
- res = await self._async_client.completions.create(**kwargs)
326
- else:
327
- res = await self._async_client.chat.completions.create(**kwargs)
328
- return res.model_dump()
329
- except self._openai._exceptions.BadRequestError as e:
330
- exception_message = e.args[0]
331
- if exception_message and "maximum context length" in exception_message:
332
- raise PhoenixContextLimitExceeded(exception_message) from e
333
- raise e
334
-
335
- return await _async_completion(**kwargs)
336
-
337
- def _rate_limited_completion(self, **kwargs: Any) -> Any:
338
- @self._rate_limiter.limit
339
- def _completion(**kwargs: Any) -> Any:
340
- try:
341
- if self._model_uses_legacy_completion_api:
342
- if "prompt" not in kwargs:
343
- kwargs["prompt"] = "\n\n".join(
344
- (message.get("content") or "")
345
- for message in (kwargs.pop("messages", None) or ())
346
- )
347
- # OpenAI 1.0.0 API responses are pydantic objects, not dicts
348
- # We must dump the model to get the dict
349
- return self._client.completions.create(**kwargs).model_dump()
350
- return self._client.chat.completions.create(**kwargs).model_dump()
351
- except self._openai._exceptions.BadRequestError as e:
352
- exception_message = e.args[0]
353
- if exception_message and "maximum context length" in exception_message:
354
- raise PhoenixContextLimitExceeded(exception_message) from e
355
- raise e
356
-
357
- return _completion(**kwargs)
358
-
359
- @property
360
- def max_context_size(self) -> int:
361
- model = self.model
362
- # handling finetuned models
363
- if "ft-" in model:
364
- model = self.model.split(":")[0]
365
- if model == "gpt-4":
366
- # Map gpt-4 to the current default
367
- model = "gpt-4-0613"
368
-
369
- context_size = MODEL_TOKEN_LIMIT_MAPPING.get(model, None)
370
-
371
- if context_size is None:
372
- raise ValueError(
373
- "Can't determine maximum context size. An unknown model name was "
374
- f"used: {model}. Please provide a valid OpenAI model name. "
375
- "Known models are: " + ", ".join(MODEL_TOKEN_LIMIT_MAPPING.keys())
376
- )
377
-
378
- return context_size
379
-
380
- @property
381
- def public_invocation_params(self) -> Dict[str, Any]:
382
- return {
383
- **({"model": self.model}),
384
- **self._default_params,
385
- **self.model_kwargs,
386
- }
387
-
388
- @property
389
- def invocation_params(self) -> Dict[str, Any]:
390
- return {
391
- **self.public_invocation_params,
392
- }
393
-
394
- @property
395
- def _default_params(self) -> Dict[str, Any]:
396
- """Get the default parameters for calling OpenAI API."""
397
- return {
398
- "temperature": self.temperature,
399
- "max_tokens": self.max_tokens,
400
- "frequency_penalty": self.frequency_penalty,
401
- "presence_penalty": self.presence_penalty,
402
- "top_p": self.top_p,
403
- "n": self.n,
404
- "timeout": self.request_timeout,
405
- }
406
-
407
- @property
408
- def encoder(self) -> "Encoding":
409
- return self._tiktoken_encoding
410
-
411
- def get_token_count_from_messages(self, messages: List[Dict[str, str]]) -> int:
412
- """Return the number of tokens used by a list of messages.
413
-
414
- Official documentation: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb
415
- """ # noqa
416
- model = self.model
417
- if model == "gpt-3.5-turbo-0301":
418
- tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
419
- tokens_per_name = -1 # if there's a name, the role is omitted
420
- else:
421
- tokens_per_message = 3
422
- tokens_per_name = 1
423
-
424
- token_count = 0
425
- for message in messages:
426
- token_count += tokens_per_message
427
- for key, text in message.items():
428
- token_count += len(self.get_tokens_from_text(text))
429
- if key == "name":
430
- token_count += tokens_per_name
431
- # every reply is primed with <|start|>assistant<|message|>
432
- token_count += 3
433
- return token_count
434
-
435
- def get_tokens_from_text(self, text: str) -> List[int]:
436
- return self.encoder.encode(text)
437
-
438
- def get_text_from_tokens(self, tokens: List[int]) -> str:
439
- return self.encoder.decode(tokens)
440
-
441
- @property
442
- def supports_function_calling(self) -> bool:
443
- if (
444
- self._is_azure
445
- and self.api_version
446
- # The first api version supporting function calling is 2023-07-01-preview.
447
- # See https://github.com/Azure/azure-rest-api-specs/blob/58e92dd03733bc175e6a9540f4bc53703b57fcc9/specification/cognitiveservices/data-plane/AzureOpenAI/inference/preview/2023-07-01-preview/inference.json#L895 # noqa E501
448
- and self.api_version[:10] < "2023-07-01"
449
- ):
450
- return False
451
- if self._model_uses_legacy_completion_api:
452
- return False
453
- return True