llama-index

Форк
0
383 строки · 12.0 Кб
1
import logging
2
import os
3
import time
4
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
5

6
import openai
7
from deprecated import deprecated
8
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionMessageToolCall
9
from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
10
from openai.types.chat.chat_completion_message import ChatCompletionMessage
11
from tenacity import (
12
    before_sleep_log,
13
    retry,
14
    retry_if_exception_type,
15
    stop_after_attempt,
16
    stop_after_delay,
17
    wait_exponential,
18
    wait_random_exponential,
19
)
20
from tenacity.stop import stop_base
21

22
from llama_index.legacy.bridge.pydantic import BaseModel
23
from llama_index.legacy.core.llms.types import ChatMessage
24
from llama_index.legacy.llms.generic_utils import get_from_param_or_env
25

26
DEFAULT_OPENAI_API_TYPE = "open_ai"
27
DEFAULT_OPENAI_API_BASE = "https://api.openai.com/v1"
28
DEFAULT_OPENAI_API_VERSION = ""
29

30

31
GPT4_MODELS: Dict[str, int] = {
32
    # stable model names:
33
    #   resolves to gpt-4-0314 before 2023-06-27,
34
    #   resolves to gpt-4-0613 after
35
    "gpt-4": 8192,
36
    "gpt-4-32k": 32768,
37
    # turbo models (Turbo, JSON mode)
38
    "gpt-4-1106-preview": 128000,
39
    "gpt-4-0125-preview": 128000,
40
    "gpt-4-turbo-preview": 128000,
41
    # multimodal model
42
    "gpt-4-vision-preview": 128000,
43
    # 0613 models (function calling):
44
    #   https://openai.com/blog/function-calling-and-other-api-updates
45
    "gpt-4-0613": 8192,
46
    "gpt-4-32k-0613": 32768,
47
    # 0314 models
48
    "gpt-4-0314": 8192,
49
    "gpt-4-32k-0314": 32768,
50
}
51

52
AZURE_TURBO_MODELS: Dict[str, int] = {
53
    "gpt-35-turbo-16k": 16384,
54
    "gpt-35-turbo": 4096,
55
    # 1106 model (JSON mode)
56
    "gpt-35-turbo-1106": 16384,
57
    # 0613 models (function calling):
58
    "gpt-35-turbo-0613": 4096,
59
    "gpt-35-turbo-16k-0613": 16384,
60
}
61

62
TURBO_MODELS: Dict[str, int] = {
63
    # stable model names:
64
    #   resolves to gpt-3.5-turbo-0301 before 2023-06-27,
65
    #   resolves to gpt-3.5-turbo-0613 until 2023-12-11,
66
    #   resolves to gpt-3.5-turbo-1106 after
67
    "gpt-3.5-turbo": 4096,
68
    # resolves to gpt-3.5-turbo-16k-0613 until 2023-12-11
69
    # resolves to gpt-3.5-turbo-1106 after
70
    "gpt-3.5-turbo-16k": 16384,
71
    # 0125 (2024) model (JSON mode)
72
    "gpt-3.5-turbo-0125": 16385,
73
    # 1106 model (JSON mode)
74
    "gpt-3.5-turbo-1106": 16384,
75
    # 0613 models (function calling):
76
    #   https://openai.com/blog/function-calling-and-other-api-updates
77
    "gpt-3.5-turbo-0613": 4096,
78
    "gpt-3.5-turbo-16k-0613": 16384,
79
    # 0301 models
80
    "gpt-3.5-turbo-0301": 4096,
81
}
82

83
GPT3_5_MODELS: Dict[str, int] = {
84
    "text-davinci-003": 4097,
85
    "text-davinci-002": 4097,
86
    # instruct models
87
    "gpt-3.5-turbo-instruct": 4096,
88
}
89

90
GPT3_MODELS: Dict[str, int] = {
91
    "text-ada-001": 2049,
92
    "text-babbage-001": 2040,
93
    "text-curie-001": 2049,
94
    "ada": 2049,
95
    "babbage": 2049,
96
    "curie": 2049,
97
    "davinci": 2049,
98
}
99

100
ALL_AVAILABLE_MODELS = {
101
    **GPT4_MODELS,
102
    **TURBO_MODELS,
103
    **GPT3_5_MODELS,
104
    **GPT3_MODELS,
105
    **AZURE_TURBO_MODELS,
106
}
107

108
CHAT_MODELS = {
109
    **GPT4_MODELS,
110
    **TURBO_MODELS,
111
    **AZURE_TURBO_MODELS,
112
}
113

114

115
DISCONTINUED_MODELS = {
116
    "code-davinci-002": 8001,
117
    "code-davinci-001": 8001,
118
    "code-cushman-002": 2048,
119
    "code-cushman-001": 2048,
120
}
121

122
MISSING_API_KEY_ERROR_MESSAGE = """No API key found for OpenAI.
123
Please set either the OPENAI_API_KEY environment variable or \
124
openai.api_key prior to initialization.
125
API keys can be found or created at \
126
https://platform.openai.com/account/api-keys
127
"""
128

129
logger = logging.getLogger(__name__)
130

131
OpenAIToolCall = Union[ChatCompletionMessageToolCall, ChoiceDeltaToolCall]
132

133

134
def create_retry_decorator(
135
    max_retries: int,
136
    random_exponential: bool = False,
137
    stop_after_delay_seconds: Optional[float] = None,
138
    min_seconds: float = 4,
139
    max_seconds: float = 10,
140
) -> Callable[[Any], Any]:
141
    wait_strategy = (
142
        wait_random_exponential(min=min_seconds, max=max_seconds)
143
        if random_exponential
144
        else wait_exponential(multiplier=1, min=min_seconds, max=max_seconds)
145
    )
146

147
    stop_strategy: stop_base = stop_after_attempt(max_retries)
148
    if stop_after_delay_seconds is not None:
149
        stop_strategy = stop_strategy | stop_after_delay(stop_after_delay_seconds)
150

151
    return retry(
152
        reraise=True,
153
        stop=stop_strategy,
154
        wait=wait_strategy,
155
        retry=(
156
            retry_if_exception_type(
157
                (
158
                    openai.APITimeoutError,
159
                    openai.APIError,
160
                    openai.APIConnectionError,
161
                    openai.RateLimitError,
162
                    openai.APIStatusError,
163
                )
164
            )
165
        ),
166
        before_sleep=before_sleep_log(logger, logging.WARNING),
167
    )
168

169

170
def openai_modelname_to_contextsize(modelname: str) -> int:
171
    """Calculate the maximum number of tokens possible to generate for a model.
172

173
    Args:
174
        modelname: The modelname we want to know the context size for.
175

176
    Returns:
177
        The maximum context size
178

179
    Example:
180
        .. code-block:: python
181

182
            max_tokens = openai.modelname_to_contextsize("text-davinci-003")
183

184
    Modified from:
185
        https://github.com/hwchase17/langchain/blob/master/langchain/llms/openai.py
186
    """
187
    # handling finetuned models
188
    if modelname.startswith("ft:"):
189
        modelname = modelname.split(":")[1]
190
    elif ":ft-" in modelname:  # legacy fine-tuning
191
        modelname = modelname.split(":")[0]
192

193
    if modelname in DISCONTINUED_MODELS:
194
        raise ValueError(
195
            f"OpenAI model {modelname} has been discontinued. "
196
            "Please choose another model."
197
        )
198
    if modelname not in ALL_AVAILABLE_MODELS:
199
        raise ValueError(
200
            f"Unknown model {modelname!r}. Please provide a valid OpenAI model name in:"
201
            f" {', '.join(ALL_AVAILABLE_MODELS.keys())}"
202
        )
203
    return ALL_AVAILABLE_MODELS[modelname]
204

205

206
def is_chat_model(model: str) -> bool:
207
    return model in CHAT_MODELS
208

209

210
def is_function_calling_model(model: str) -> bool:
211
    is_chat_model_ = is_chat_model(model)
212
    is_old = "0314" in model or "0301" in model
213
    return is_chat_model_ and not is_old
214

215

216
def to_openai_message_dict(
217
    message: ChatMessage, drop_none: bool = False
218
) -> ChatCompletionMessageParam:
219
    """Convert generic message to OpenAI message dict."""
220
    message_dict = {
221
        "role": message.role.value,
222
        "content": message.content,
223
    }
224

225
    # NOTE: openai messages have additional arguments:
226
    # - function messages have `name`
227
    # - assistant messages have optional `function_call`
228
    message_dict.update(message.additional_kwargs)
229

230
    null_keys = [key for key, value in message_dict.items() if value is None]
231
    # if drop_none is True, remove keys with None values
232
    if drop_none:
233
        for key in null_keys:
234
            message_dict.pop(key)
235

236
    return message_dict  # type: ignore
237

238

239
def to_openai_message_dicts(
240
    messages: Sequence[ChatMessage], drop_none: bool = False
241
) -> List[ChatCompletionMessageParam]:
242
    """Convert generic messages to OpenAI message dicts."""
243
    return [
244
        to_openai_message_dict(message, drop_none=drop_none) for message in messages
245
    ]
246

247

248
def from_openai_message(openai_message: ChatCompletionMessage) -> ChatMessage:
249
    """Convert openai message dict to generic message."""
250
    role = openai_message.role
251
    # NOTE: Azure OpenAI returns function calling messages without a content key
252
    content = openai_message.content
253

254
    function_call = None  # deprecated in OpenAI v 1.1.0
255

256
    additional_kwargs: Dict[str, Any] = {}
257
    if openai_message.tool_calls is not None:
258
        tool_calls: List[ChatCompletionMessageToolCall] = openai_message.tool_calls
259
        additional_kwargs.update(tool_calls=tool_calls)
260

261
    return ChatMessage(role=role, content=content, additional_kwargs=additional_kwargs)
262

263

264
def from_openai_messages(
265
    openai_messages: Sequence[ChatCompletionMessage],
266
) -> List[ChatMessage]:
267
    """Convert openai message dicts to generic messages."""
268
    return [from_openai_message(message) for message in openai_messages]
269

270

271
def from_openai_message_dict(message_dict: dict) -> ChatMessage:
272
    """Convert openai message dict to generic message."""
273
    role = message_dict["role"]
274
    # NOTE: Azure OpenAI returns function calling messages without a content key
275
    content = message_dict.get("content", None)
276

277
    additional_kwargs = message_dict.copy()
278
    additional_kwargs.pop("role")
279
    additional_kwargs.pop("content", None)
280

281
    return ChatMessage(role=role, content=content, additional_kwargs=additional_kwargs)
282

283

284
def from_openai_message_dicts(message_dicts: Sequence[dict]) -> List[ChatMessage]:
285
    """Convert openai message dicts to generic messages."""
286
    return [from_openai_message_dict(message_dict) for message_dict in message_dicts]
287

288

289
@deprecated("Deprecated in favor of `to_openai_tool`, which should be used instead.")
290
def to_openai_function(pydantic_class: Type[BaseModel]) -> Dict[str, Any]:
291
    """Deprecated in favor of `to_openai_tool`.
292

293
    Convert pydantic class to OpenAI function.
294
    """
295
    return to_openai_tool(pydantic_class, description=None)
296

297

298
def to_openai_tool(
299
    pydantic_class: Type[BaseModel], description: Optional[str] = None
300
) -> Dict[str, Any]:
301
    """Convert pydantic class to OpenAI tool."""
302
    schema = pydantic_class.schema()
303
    schema_description = schema.get("description", None) or description
304
    function = {
305
        "name": schema["title"],
306
        "description": schema_description,
307
        "parameters": pydantic_class.schema(),
308
    }
309
    return {"type": "function", "function": function}
310

311

312
def resolve_openai_credentials(
313
    api_key: Optional[str] = None,
314
    api_base: Optional[str] = None,
315
    api_version: Optional[str] = None,
316
) -> Tuple[Optional[str], str, str]:
317
    """ "Resolve OpenAI credentials.
318

319
    The order of precedence is:
320
    1. param
321
    2. env
322
    3. openai module
323
    4. default
324
    """
325
    # resolve from param or env
326
    api_key = get_from_param_or_env("api_key", api_key, "OPENAI_API_KEY", "")
327
    api_base = get_from_param_or_env("api_base", api_base, "OPENAI_API_BASE", "")
328
    api_version = get_from_param_or_env(
329
        "api_version", api_version, "OPENAI_API_VERSION", ""
330
    )
331

332
    # resolve from openai module or default
333
    final_api_key = api_key or openai.api_key or ""
334
    final_api_base = api_base or openai.base_url or DEFAULT_OPENAI_API_BASE
335
    final_api_version = api_version or openai.api_version or DEFAULT_OPENAI_API_VERSION
336

337
    return final_api_key, str(final_api_base), final_api_version
338

339

340
def refresh_openai_azuread_token(
341
    azure_ad_token: Any = None,
342
) -> Any:
343
    """
344
    Checks the validity of the associated token, if any, and tries to refresh it
345
    using the credentials available in the current context. Different authentication
346
    methods are tried, in order, until a successful one is found as defined at the
347
    package `azure-indentity`.
348
    """
349
    try:
350
        from azure.core.exceptions import ClientAuthenticationError
351
        from azure.identity import DefaultAzureCredential
352
    except ImportError as ex:
353
        raise ValueError(
354
            "Using API type `azure_ad` or `azuread` requires the package"
355
            " `azure-identity` to be installed."
356
        ) from ex
357

358
    if not azure_ad_token or azure_ad_token.expires_on < time.time() + 60:
359
        try:
360
            credential = DefaultAzureCredential()
361
            azure_ad_token = credential.get_token(
362
                "https://cognitiveservices.azure.com/.default"
363
            )
364
        except ClientAuthenticationError as err:
365
            raise ValueError(
366
                "Unable to acquire a valid Microsoft Entra ID (former Azure AD) token for "
367
                f"the resource due to the following error: {err.message}"
368
            ) from err
369
    return azure_ad_token
370

371

372
def resolve_from_aliases(*args: Optional[str]) -> Optional[str]:
373
    for arg in args:
374
        if arg is not None:
375
            return arg
376
    return None
377

378

379
def validate_openai_api_key(api_key: Optional[str] = None) -> None:
380
    openai_api_key = api_key or os.environ.get("OPENAI_API_KEY", "")
381

382
    if not openai_api_key:
383
        raise ValueError(MISSING_API_KEY_ERROR_MESSAGE)
384

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.