diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc41d85f14..e8bb1ff66a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,11 @@ repos: name: Check if files match entry: python3 ci_cd/check_files_match.py language: system -- repo: local - hooks: - - id: mypy - name: mypy - entry: python3 -m mypy --ignore-missing-imports - language: system - types: [python] - files: ^litellm/ \ No newline at end of file +# - repo: local +# hooks: +# - id: mypy +# name: mypy +# entry: python3 -m mypy --ignore-missing-imports +# language: system +# types: [python] +# files: ^litellm/ \ No newline at end of file diff --git a/enterprise/utils.py b/enterprise/utils.py index 4a42dc996f..05bd7dac6e 100644 --- a/enterprise/utils.py +++ b/enterprise/utils.py @@ -291,7 +291,7 @@ def _create_clickhouse_aggregate_tables(client=None, table_names=[]): def _forecast_daily_cost(data: list): - import requests + import requests # type: ignore from datetime import datetime, timedelta if len(data) == 0: diff --git a/litellm/_redis.py b/litellm/_redis.py index d7789472c1..d72016dcd9 100644 --- a/litellm/_redis.py +++ b/litellm/_redis.py @@ -10,8 +10,8 @@ # s/o [@Frank Colson](https://www.linkedin.com/in/frank-colson-422b9b183/) for this redis implementation import os import inspect -import redis, litellm -import redis.asyncio as async_redis +import redis, litellm # type: ignore +import redis.asyncio as async_redis # type: ignore from typing import List, Optional diff --git a/litellm/budget_manager.py b/litellm/budget_manager.py index 8410157537..9ef4bfafa9 100644 --- a/litellm/budget_manager.py +++ b/litellm/budget_manager.py @@ -10,7 +10,7 @@ import os, json, time import litellm from litellm.utils import ModelResponse -import requests, threading +import requests, threading # type: ignore from typing import Optional, Union, Literal diff --git a/litellm/integrations/aispend.py b/litellm/integrations/aispend.py index 2015d45ddd..a893f8923b 100644 --- a/litellm/integrations/aispend.py +++ b/litellm/integrations/aispend.py @@ -1,7 +1,6 @@ #### What this does #### # On success + failure, log events to aispend.io import dotenv, os -import requests dotenv.load_dotenv() # Loading env variables using dotenv import traceback diff --git a/litellm/integrations/athina.py b/litellm/integrations/athina.py index 897cf6c8d5..660dd51efb 100644 --- a/litellm/integrations/athina.py +++ b/litellm/integrations/athina.py @@ -4,18 +4,30 @@ import datetime class AthinaLogger: def __init__(self): import os + self.athina_api_key = os.getenv("ATHINA_API_KEY") self.headers = { "athina-api-key": self.athina_api_key, - "Content-Type": "application/json" + "Content-Type": "application/json", } self.athina_logging_url = "https://log.athina.ai/api/v1/log/inference" - self.additional_keys = ["environment", "prompt_slug", "customer_id", "customer_user_id", "session_id", "external_reference_id", "context", "expected_response", "user_query"] + self.additional_keys = [ + "environment", + "prompt_slug", + "customer_id", + "customer_user_id", + "session_id", + "external_reference_id", + "context", + "expected_response", + "user_query", + ] def log_event(self, kwargs, response_obj, start_time, end_time, print_verbose): - import requests + import requests # type: ignore import json import traceback + try: response_json = response_obj.model_dump() if response_obj else {} data = { @@ -23,32 +35,51 @@ class AthinaLogger: "request": kwargs, "response": response_json, "prompt_tokens": response_json.get("usage", {}).get("prompt_tokens"), - "completion_tokens": response_json.get("usage", {}).get("completion_tokens"), + "completion_tokens": response_json.get("usage", {}).get( + "completion_tokens" + ), "total_tokens": response_json.get("usage", {}).get("total_tokens"), } - - if type(end_time) == datetime.datetime and type(start_time) == datetime.datetime: - data["response_time"] = int((end_time - start_time).total_seconds() * 1000) + + if ( + type(end_time) == datetime.datetime + and type(start_time) == datetime.datetime + ): + data["response_time"] = int( + (end_time - start_time).total_seconds() * 1000 + ) if "messages" in kwargs: data["prompt"] = kwargs.get("messages", None) # Directly add tools or functions if present optional_params = kwargs.get("optional_params", {}) - data.update((k, v) for k, v in optional_params.items() if k in ["tools", "functions"]) + data.update( + (k, v) + for k, v in optional_params.items() + if k in ["tools", "functions"] + ) # Add additional metadata keys - metadata = kwargs.get("litellm_params", {}).get("metadata", {}) + metadata = kwargs.get("litellm_params", {}).get("metadata", {}) if metadata: for key in self.additional_keys: if key in metadata: data[key] = metadata[key] - response = requests.post(self.athina_logging_url, headers=self.headers, data=json.dumps(data, default=str)) + response = requests.post( + self.athina_logging_url, + headers=self.headers, + data=json.dumps(data, default=str), + ) if response.status_code != 200: - print_verbose(f"Athina Logger Error - {response.text}, {response.status_code}") + print_verbose( + f"Athina Logger Error - {response.text}, {response.status_code}" + ) else: print_verbose(f"Athina Logger Succeeded - {response.text}") except Exception as e: - print_verbose(f"Athina Logger Error - {e}, Stack trace: {traceback.format_exc()}") - pass \ No newline at end of file + print_verbose( + f"Athina Logger Error - {e}, Stack trace: {traceback.format_exc()}" + ) + pass diff --git a/litellm/integrations/berrispend.py b/litellm/integrations/berrispend.py index 7d91ffca7f..1f0ae4581f 100644 --- a/litellm/integrations/berrispend.py +++ b/litellm/integrations/berrispend.py @@ -1,7 +1,7 @@ #### What this does #### # On success + failure, log events to aispend.io import dotenv, os -import requests +import requests # type: ignore dotenv.load_dotenv() # Loading env variables using dotenv import traceback diff --git a/litellm/integrations/clickhouse.py b/litellm/integrations/clickhouse.py index d5000e5c46..7d1fb37d94 100644 --- a/litellm/integrations/clickhouse.py +++ b/litellm/integrations/clickhouse.py @@ -3,7 +3,6 @@ #### What this does #### # On success, logs events to Promptlayer import dotenv, os -import requests from litellm.proxy._types import UserAPIKeyAuth from litellm.caching import DualCache diff --git a/litellm/integrations/custom_logger.py b/litellm/integrations/custom_logger.py index b288036ad5..8a3e0f4673 100644 --- a/litellm/integrations/custom_logger.py +++ b/litellm/integrations/custom_logger.py @@ -1,7 +1,6 @@ #### What this does #### # On success, logs events to Promptlayer import dotenv, os -import requests from litellm.proxy._types import UserAPIKeyAuth from litellm.caching import DualCache diff --git a/litellm/integrations/datadog.py b/litellm/integrations/datadog.py index f5db5bf1f7..d969341fc4 100644 --- a/litellm/integrations/datadog.py +++ b/litellm/integrations/datadog.py @@ -2,7 +2,7 @@ # On success + failure, log events to Supabase import dotenv, os -import requests +import requests # type: ignore dotenv.load_dotenv() # Loading env variables using dotenv import traceback diff --git a/litellm/integrations/dynamodb.py b/litellm/integrations/dynamodb.py index 2ed6c3f9f7..b5462ee7fa 100644 --- a/litellm/integrations/dynamodb.py +++ b/litellm/integrations/dynamodb.py @@ -2,7 +2,7 @@ # On success + failure, log events to Supabase import dotenv, os -import requests +import requests # type: ignore dotenv.load_dotenv() # Loading env variables using dotenv import traceback diff --git a/litellm/integrations/greenscale.py b/litellm/integrations/greenscale.py index 3ff808ddbb..78190d69dc 100644 --- a/litellm/integrations/greenscale.py +++ b/litellm/integrations/greenscale.py @@ -1,15 +1,17 @@ -import requests +import requests # type: ignore import json import traceback from datetime import datetime, timezone + class GreenscaleLogger: def __init__(self): import os + self.greenscale_api_key = os.getenv("GREENSCALE_API_KEY") self.headers = { "api-key": self.greenscale_api_key, - "Content-Type": "application/json" + "Content-Type": "application/json", } self.greenscale_logging_url = os.getenv("GREENSCALE_ENDPOINT") @@ -19,33 +21,48 @@ class GreenscaleLogger: data = { "modelId": kwargs.get("model"), "inputTokenCount": response_json.get("usage", {}).get("prompt_tokens"), - "outputTokenCount": response_json.get("usage", {}).get("completion_tokens"), + "outputTokenCount": response_json.get("usage", {}).get( + "completion_tokens" + ), } - data["timestamp"] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') - - if type(end_time) == datetime and type(start_time) == datetime: - data["invocationLatency"] = int((end_time - start_time).total_seconds() * 1000) + data["timestamp"] = datetime.now(timezone.utc).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + if type(end_time) == datetime and type(start_time) == datetime: + data["invocationLatency"] = int( + (end_time - start_time).total_seconds() * 1000 + ) # Add additional metadata keys to tags tags = [] metadata = kwargs.get("litellm_params", {}).get("metadata", {}) for key, value in metadata.items(): - if key.startswith("greenscale"): + if key.startswith("greenscale"): if key == "greenscale_project": data["project"] = value elif key == "greenscale_application": data["application"] = value else: - tags.append({"key": key.replace("greenscale_", ""), "value": str(value)}) - + tags.append( + {"key": key.replace("greenscale_", ""), "value": str(value)} + ) + data["tags"] = tags - response = requests.post(self.greenscale_logging_url, headers=self.headers, data=json.dumps(data, default=str)) + response = requests.post( + self.greenscale_logging_url, + headers=self.headers, + data=json.dumps(data, default=str), + ) if response.status_code != 200: - print_verbose(f"Greenscale Logger Error - {response.text}, {response.status_code}") + print_verbose( + f"Greenscale Logger Error - {response.text}, {response.status_code}" + ) else: print_verbose(f"Greenscale Logger Succeeded - {response.text}") except Exception as e: - print_verbose(f"Greenscale Logger Error - {e}, Stack trace: {traceback.format_exc()}") - pass \ No newline at end of file + print_verbose( + f"Greenscale Logger Error - {e}, Stack trace: {traceback.format_exc()}" + ) + pass diff --git a/litellm/integrations/helicone.py b/litellm/integrations/helicone.py index cb86637732..c8c1075419 100644 --- a/litellm/integrations/helicone.py +++ b/litellm/integrations/helicone.py @@ -1,7 +1,7 @@ #### What this does #### # On success, logs events to Helicone import dotenv, os -import requests +import requests # type: ignore import litellm dotenv.load_dotenv() # Loading env variables using dotenv diff --git a/litellm/integrations/langsmith.py b/litellm/integrations/langsmith.py index 415f3d2d20..8a0fb38522 100644 --- a/litellm/integrations/langsmith.py +++ b/litellm/integrations/langsmith.py @@ -1,15 +1,14 @@ #### What this does #### # On success, logs events to Langsmith -import dotenv, os -import requests -import requests +import dotenv, os # type: ignore +import requests # type: ignore from datetime import datetime dotenv.load_dotenv() # Loading env variables using dotenv import traceback import asyncio import types -from pydantic import BaseModel +from pydantic import BaseModel # type: ignore def is_serializable(value): @@ -79,8 +78,6 @@ class LangsmithLogger: except: response_obj = response_obj.dict() # type: ignore - print(f"response_obj: {response_obj}") - data = { "name": run_name, "run_type": "llm", # this should always be llm, since litellm always logs llm calls. Langsmith allow us to log "chain" @@ -90,7 +87,6 @@ class LangsmithLogger: "start_time": start_time, "end_time": end_time, } - print(f"data: {data}") response = requests.post( "https://api.smith.langchain.com/runs", diff --git a/litellm/integrations/openmeter.py b/litellm/integrations/openmeter.py index 237a40eb8d..a454739d54 100644 --- a/litellm/integrations/openmeter.py +++ b/litellm/integrations/openmeter.py @@ -2,7 +2,6 @@ ## On Success events log cost to OpenMeter - https://github.com/BerriAI/litellm/issues/1268 import dotenv, os, json -import requests import litellm dotenv.load_dotenv() # Loading env variables using dotenv @@ -60,7 +59,7 @@ class OpenMeterLogger(CustomLogger): "total_tokens": response_obj["usage"].get("total_tokens"), } - subject = kwargs.get("user", None), # end-user passed in via 'user' param + subject = (kwargs.get("user", None),) # end-user passed in via 'user' param if not subject: raise Exception("OpenMeter: user is required") diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index e3c6e8e774..577946ce18 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -3,7 +3,7 @@ # On success, log events to Prometheus import dotenv, os -import requests +import requests # type: ignore dotenv.load_dotenv() # Loading env variables using dotenv import traceback @@ -19,7 +19,6 @@ class PrometheusLogger: **kwargs, ): try: - print(f"in init prometheus metrics") from prometheus_client import Counter self.litellm_llm_api_failed_requests_metric = Counter( diff --git a/litellm/integrations/prometheus_services.py b/litellm/integrations/prometheus_services.py index 0249a71d0f..d276bb85ba 100644 --- a/litellm/integrations/prometheus_services.py +++ b/litellm/integrations/prometheus_services.py @@ -4,7 +4,7 @@ import dotenv, os -import requests +import requests # type: ignore dotenv.load_dotenv() # Loading env variables using dotenv import traceback @@ -183,7 +183,6 @@ class PrometheusServicesLogger: ) async def async_service_failure_hook(self, payload: ServiceLoggerPayload): - print(f"received error payload: {payload.error}") if self.mock_testing: self.mock_testing_failure_calls += 1 diff --git a/litellm/integrations/prompt_layer.py b/litellm/integrations/prompt_layer.py index 39a80940b7..ce610e1ef1 100644 --- a/litellm/integrations/prompt_layer.py +++ b/litellm/integrations/prompt_layer.py @@ -1,12 +1,13 @@ #### What this does #### # On success, logs events to Promptlayer import dotenv, os -import requests +import requests # type: ignore from pydantic import BaseModel dotenv.load_dotenv() # Loading env variables using dotenv import traceback + class PromptLayerLogger: # Class variables or attributes def __init__(self): @@ -32,7 +33,11 @@ class PromptLayerLogger: tags = kwargs["litellm_params"]["metadata"]["pl_tags"] # Remove "pl_tags" from metadata - metadata = {k:v for k, v in kwargs["litellm_params"]["metadata"].items() if k != "pl_tags"} + metadata = { + k: v + for k, v in kwargs["litellm_params"]["metadata"].items() + if k != "pl_tags" + } print_verbose( f"Prompt Layer Logging - Enters logging function for model kwargs: {new_kwargs}\n, response: {response_obj}" diff --git a/litellm/integrations/s3.py b/litellm/integrations/s3.py index dc35430bc1..d31b158402 100644 --- a/litellm/integrations/s3.py +++ b/litellm/integrations/s3.py @@ -2,7 +2,6 @@ # On success + failure, log events to Supabase import dotenv, os -import requests dotenv.load_dotenv() # Loading env variables using dotenv import traceback diff --git a/litellm/integrations/supabase.py b/litellm/integrations/supabase.py index a99e4abc4b..58beba8a3d 100644 --- a/litellm/integrations/supabase.py +++ b/litellm/integrations/supabase.py @@ -2,7 +2,7 @@ # On success + failure, log events to Supabase import dotenv, os -import requests +import requests # type: ignore dotenv.load_dotenv() # Loading env variables using dotenv import traceback diff --git a/litellm/llms/ai21.py b/litellm/llms/ai21.py index 73d5afebe8..a39a83f157 100644 --- a/litellm/llms/ai21.py +++ b/litellm/llms/ai21.py @@ -1,8 +1,8 @@ import os, types, traceback import json from enum import Enum -import requests -import time, httpx +import requests # type: ignore +import time, httpx # type: ignore from typing import Callable, Optional from litellm.utils import ModelResponse, Choices, Message import litellm diff --git a/litellm/llms/aleph_alpha.py b/litellm/llms/aleph_alpha.py index 86a30a9ec9..7edd11964b 100644 --- a/litellm/llms/aleph_alpha.py +++ b/litellm/llms/aleph_alpha.py @@ -1,12 +1,12 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional import litellm from litellm.utils import ModelResponse, Choices, Message, Usage -import httpx +import httpx # type: ignore class AlephAlphaError(Exception): diff --git a/litellm/llms/anthropic.py b/litellm/llms/anthropic.py index 3fc374dce7..818c4ecb3a 100644 --- a/litellm/llms/anthropic.py +++ b/litellm/llms/anthropic.py @@ -1,7 +1,7 @@ import os, types import json from enum import Enum -import requests, copy +import requests, copy # type: ignore import time from typing import Callable, Optional, List from litellm.utils import ModelResponse, Usage, map_finish_reason, CustomStreamWrapper @@ -9,7 +9,7 @@ import litellm from .prompt_templates.factory import prompt_factory, custom_prompt from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler from .base import BaseLLM -import httpx +import httpx # type: ignore class AnthropicConstants(Enum): diff --git a/litellm/llms/azure.py b/litellm/llms/azure.py index c3e0245251..f416d14377 100644 --- a/litellm/llms/azure.py +++ b/litellm/llms/azure.py @@ -12,7 +12,7 @@ from litellm.utils import ( from typing import Callable, Optional, BinaryIO from litellm import OpenAIConfig import litellm, json -import httpx +import httpx # type: ignore from .custom_httpx.azure_dall_e_2 import CustomHTTPTransport, AsyncCustomHTTPTransport from openai import AzureOpenAI, AsyncAzureOpenAI import uuid diff --git a/litellm/llms/azure_text.py b/litellm/llms/azure_text.py index e0d5474774..640ab82223 100644 --- a/litellm/llms/azure_text.py +++ b/litellm/llms/azure_text.py @@ -1,5 +1,5 @@ from typing import Optional, Union, Any -import types, requests +import types, requests # type: ignore from .base import BaseLLM from litellm.utils import ( ModelResponse, diff --git a/litellm/llms/baseten.py b/litellm/llms/baseten.py index 75db9ab465..643dae5304 100644 --- a/litellm/llms/baseten.py +++ b/litellm/llms/baseten.py @@ -1,7 +1,7 @@ import os import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable from litellm.utils import ModelResponse, Usage diff --git a/litellm/llms/bedrock.py b/litellm/llms/bedrock.py index 2f26ae4a9a..08433ba18b 100644 --- a/litellm/llms/bedrock.py +++ b/litellm/llms/bedrock.py @@ -163,10 +163,9 @@ class AmazonAnthropicClaude3Config: "stop", "temperature", "top_p", - "extra_headers" + "extra_headers", ] - def map_openai_params(self, non_default_params: dict, optional_params: dict): for param, value in non_default_params.items(): if param == "max_tokens": @@ -534,10 +533,12 @@ class AmazonStabilityConfig: def add_custom_header(headers): """Closure to capture the headers and add them.""" + def callback(request, **kwargs): """Actual callback function that Boto3 will call.""" for header_name, header_value in headers.items(): request.headers.add_header(header_name, header_value) + return callback @@ -672,7 +673,9 @@ def init_bedrock_client( config=config, ) if extra_headers: - client.meta.events.register('before-sign.bedrock-runtime.*', add_custom_header(extra_headers)) + client.meta.events.register( + "before-sign.bedrock-runtime.*", add_custom_header(extra_headers) + ) return client @@ -1224,7 +1227,7 @@ def _embedding_func_single( "input_type", "search_document" ) # aws bedrock example default - https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/providers?model=cohere.embed-english-v3 data = {"texts": [input], **inference_params} # type: ignore - body = json.dumps(data).encode("utf-8") + body = json.dumps(data).encode("utf-8") # type: ignore ## LOGGING request_str = f""" response = client.invoke_model( @@ -1416,7 +1419,7 @@ def image_generation( ## LOGGING request_str = f""" response = client.invoke_model( - body={body}, + body={body}, # type: ignore modelId={modelId}, accept="application/json", contentType="application/json", diff --git a/litellm/llms/cloudflare.py b/litellm/llms/cloudflare.py index b8187cbc94..5a24b3b443 100644 --- a/litellm/llms/cloudflare.py +++ b/litellm/llms/cloudflare.py @@ -1,11 +1,11 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional import litellm -import httpx +import httpx # type: ignore from litellm.utils import ModelResponse, Usage from .prompt_templates.factory import prompt_factory, custom_prompt diff --git a/litellm/llms/cohere.py b/litellm/llms/cohere.py index b867559c3b..0ebdf38f19 100644 --- a/litellm/llms/cohere.py +++ b/litellm/llms/cohere.py @@ -1,12 +1,12 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time, traceback from typing import Callable, Optional from litellm.utils import ModelResponse, Choices, Message, Usage import litellm -import httpx +import httpx # type: ignore class CohereError(Exception): diff --git a/litellm/llms/cohere_chat.py b/litellm/llms/cohere_chat.py index 2a9bc320b9..e4de6ddcb0 100644 --- a/litellm/llms/cohere_chat.py +++ b/litellm/llms/cohere_chat.py @@ -1,12 +1,12 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time, traceback from typing import Callable, Optional from litellm.utils import ModelResponse, Choices, Message, Usage import litellm -import httpx +import httpx # type: ignore from .prompt_templates.factory import cohere_message_pt diff --git a/litellm/llms/maritalk.py b/litellm/llms/maritalk.py index 4c6b86d3cd..dfe53e9df0 100644 --- a/litellm/llms/maritalk.py +++ b/litellm/llms/maritalk.py @@ -1,7 +1,7 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time, traceback from typing import Callable, Optional, List from litellm.utils import ModelResponse, Choices, Message, Usage diff --git a/litellm/llms/nlp_cloud.py b/litellm/llms/nlp_cloud.py index 86648118f9..cd5f17a90b 100644 --- a/litellm/llms/nlp_cloud.py +++ b/litellm/llms/nlp_cloud.py @@ -1,7 +1,7 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional import litellm diff --git a/litellm/llms/ollama.py b/litellm/llms/ollama.py index 5180cfebe7..9c9b5e8981 100644 --- a/litellm/llms/ollama.py +++ b/litellm/llms/ollama.py @@ -1,10 +1,10 @@ from itertools import chain -import requests, types, time +import requests, types, time # type: ignore import json, uuid import traceback from typing import Optional import litellm -import httpx, aiohttp, asyncio +import httpx, aiohttp, asyncio # type: ignore from .prompt_templates.factory import prompt_factory, custom_prompt @@ -220,7 +220,10 @@ def get_ollama_response( tool_calls=[ { "id": f"call_{str(uuid.uuid4())}", - "function": {"name": function_call["name"], "arguments": json.dumps(function_call["arguments"])}, + "function": { + "name": function_call["name"], + "arguments": json.dumps(function_call["arguments"]), + }, "type": "function", } ], @@ -232,7 +235,9 @@ def get_ollama_response( model_response["created"] = int(time.time()) model_response["model"] = "ollama/" + model prompt_tokens = response_json.get("prompt_eval_count", len(encoding.encode(prompt, disallowed_special=()))) # type: ignore - completion_tokens = response_json.get("eval_count", len(response_json.get("message",dict()).get("content", ""))) + completion_tokens = response_json.get( + "eval_count", len(response_json.get("message", dict()).get("content", "")) + ) model_response["usage"] = litellm.Usage( prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, @@ -273,7 +278,10 @@ def ollama_completion_stream(url, data, logging_obj): tool_calls=[ { "id": f"call_{str(uuid.uuid4())}", - "function": {"name": function_call["name"], "arguments": json.dumps(function_call["arguments"])}, + "function": { + "name": function_call["name"], + "arguments": json.dumps(function_call["arguments"]), + }, "type": "function", } ], @@ -314,9 +322,10 @@ async def ollama_async_streaming(url, data, model_response, encoding, logging_ob first_chunk_content = first_chunk.choices[0].delta.content or "" response_content = first_chunk_content + "".join( [ - chunk.choices[0].delta.content - async for chunk in streamwrapper - if chunk.choices[0].delta.content] + chunk.choices[0].delta.content + async for chunk in streamwrapper + if chunk.choices[0].delta.content + ] ) function_call = json.loads(response_content) delta = litellm.utils.Delta( @@ -324,7 +333,10 @@ async def ollama_async_streaming(url, data, model_response, encoding, logging_ob tool_calls=[ { "id": f"call_{str(uuid.uuid4())}", - "function": {"name": function_call["name"], "arguments": json.dumps(function_call["arguments"])}, + "function": { + "name": function_call["name"], + "arguments": json.dumps(function_call["arguments"]), + }, "type": "function", } ], @@ -373,7 +385,10 @@ async def ollama_acompletion(url, data, model_response, encoding, logging_obj): tool_calls=[ { "id": f"call_{str(uuid.uuid4())}", - "function": {"name": function_call["name"], "arguments": json.dumps(function_call["arguments"])}, + "function": { + "name": function_call["name"], + "arguments": json.dumps(function_call["arguments"]), + }, "type": "function", } ], @@ -387,7 +402,10 @@ async def ollama_acompletion(url, data, model_response, encoding, logging_obj): model_response["created"] = int(time.time()) model_response["model"] = "ollama/" + data["model"] prompt_tokens = response_json.get("prompt_eval_count", len(encoding.encode(data["prompt"], disallowed_special=()))) # type: ignore - completion_tokens = response_json.get("eval_count", len(response_json.get("message",dict()).get("content", ""))) + completion_tokens = response_json.get( + "eval_count", + len(response_json.get("message", dict()).get("content", "")), + ) model_response["usage"] = litellm.Usage( prompt_tokens=prompt_tokens, completion_tokens=completion_tokens, @@ -475,6 +493,7 @@ async def ollama_aembeddings( } return model_response + def ollama_embeddings( api_base: str, model: str, @@ -492,5 +511,6 @@ def ollama_embeddings( optional_params, logging_obj, model_response, - encoding) + encoding, ) + ) diff --git a/litellm/llms/oobabooga.py b/litellm/llms/oobabooga.py index b166c9069e..f8f32e0fe4 100644 --- a/litellm/llms/oobabooga.py +++ b/litellm/llms/oobabooga.py @@ -1,7 +1,7 @@ import os import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional from litellm.utils import ModelResponse, Usage diff --git a/litellm/llms/openai.py b/litellm/llms/openai.py index f007507c9b..d516334ac1 100644 --- a/litellm/llms/openai.py +++ b/litellm/llms/openai.py @@ -22,7 +22,6 @@ from litellm.utils import ( TextCompletionResponse, ) from typing import Callable, Optional -import aiohttp, requests import litellm from .prompt_templates.factory import prompt_factory, custom_prompt from openai import OpenAI, AsyncOpenAI diff --git a/litellm/llms/petals.py b/litellm/llms/petals.py index 25403f5980..334b80d388 100644 --- a/litellm/llms/petals.py +++ b/litellm/llms/petals.py @@ -1,7 +1,7 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional import litellm diff --git a/litellm/llms/prompt_templates/factory.py b/litellm/llms/prompt_templates/factory.py index 0820303685..3ae2db7013 100644 --- a/litellm/llms/prompt_templates/factory.py +++ b/litellm/llms/prompt_templates/factory.py @@ -487,7 +487,7 @@ def format_prompt_togetherai(messages, prompt_format, chat_template): def ibm_granite_pt(messages: list): """ - IBM's Granite models uses the template: + IBM's Granite chat models uses the template: <|system|> {system_message} <|user|> {user_message} <|assistant|> {assistant_message} See: https://www.ibm.com/docs/en/watsonx-as-a-service?topic=solutions-supported-foundation-models @@ -503,12 +503,13 @@ def ibm_granite_pt(messages: list): "pre_message": "<|user|>\n", "post_message": "\n", }, - "assistant": { - "pre_message": "<|assistant|>\n", - "post_message": "\n", + 'assistant': { + 'pre_message': '<|assistant|>\n', + 'post_message': '\n', }, }, - ).strip() + final_prompt_value='<|assistant|>\n', + ) ### ANTHROPIC ### @@ -981,7 +982,7 @@ def anthropic_messages_pt(messages: list): # add role=tool support to allow function call result/error submission user_message_types = {"user", "tool", "function"} # reformat messages to ensure user/assistant are alternating, if there's either 2 consecutive 'user' messages or 2 consecutive 'assistant' message, merge them. - new_messages = [] + new_messages: list = [] msg_i = 0 tool_use_param = False while msg_i < len(messages): @@ -1524,24 +1525,9 @@ def prompt_factory( return mistral_instruct_pt(messages=messages) elif "meta-llama/llama-3" in model and "instruct" in model: # https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3/ - return custom_prompt( - role_dict={ - "system": { - "pre_message": "<|start_header_id|>system<|end_header_id|>\n", - "post_message": "<|eot_id|>", - }, - "user": { - "pre_message": "<|start_header_id|>user<|end_header_id|>\n", - "post_message": "<|eot_id|>", - }, - "assistant": { - "pre_message": "<|start_header_id|>assistant<|end_header_id|>\n", - "post_message": "<|eot_id|>", - }, - }, + return hf_chat_template( + model="meta-llama/Meta-Llama-3-8B-Instruct", messages=messages, - initial_prompt_value="<|begin_of_text|>", - final_prompt_value="<|start_header_id|>assistant<|end_header_id|>\n", ) try: if "meta-llama/llama-2" in model and "chat" in model: diff --git a/litellm/llms/replicate.py b/litellm/llms/replicate.py index 65052e3179..c297281347 100644 --- a/litellm/llms/replicate.py +++ b/litellm/llms/replicate.py @@ -1,11 +1,11 @@ import os, types import json -import requests +import requests # type: ignore import time from typing import Callable, Optional from litellm.utils import ModelResponse, Usage import litellm -import httpx +import httpx # type: ignore from .prompt_templates.factory import prompt_factory, custom_prompt diff --git a/litellm/llms/sagemaker.py b/litellm/llms/sagemaker.py index 27d3ff72a9..8e75428bb7 100644 --- a/litellm/llms/sagemaker.py +++ b/litellm/llms/sagemaker.py @@ -1,14 +1,14 @@ import os, types, traceback from enum import Enum import json -import requests +import requests # type: ignore import time from typing import Callable, Optional, Any import litellm from litellm.utils import ModelResponse, EmbeddingResponse, get_secret, Usage import sys from copy import deepcopy -import httpx +import httpx # type: ignore from .prompt_templates.factory import prompt_factory, custom_prompt @@ -295,7 +295,7 @@ def completion( EndpointName={model}, InferenceComponentName={model_id}, ContentType="application/json", - Body={data}, + Body={data}, # type: ignore CustomAttributes="accept_eula=true", ) """ # type: ignore @@ -321,7 +321,7 @@ def completion( response = client.invoke_endpoint( EndpointName={model}, ContentType="application/json", - Body={data}, + Body={data}, # type: ignore CustomAttributes="accept_eula=true", ) """ # type: ignore @@ -688,7 +688,7 @@ def embedding( response = client.invoke_endpoint( EndpointName={model}, ContentType="application/json", - Body={data}, + Body={data}, # type: ignore CustomAttributes="accept_eula=true", )""" # type: ignore logging_obj.pre_call( diff --git a/litellm/llms/together_ai.py b/litellm/llms/together_ai.py index 3f9d3b9dee..47453ca885 100644 --- a/litellm/llms/together_ai.py +++ b/litellm/llms/together_ai.py @@ -6,11 +6,11 @@ Reference: https://docs.together.ai/docs/openai-api-compatibility import os, types import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional import litellm -import httpx +import httpx # type: ignore from litellm.utils import ModelResponse, Usage from .prompt_templates.factory import prompt_factory, custom_prompt diff --git a/litellm/llms/vertex_ai.py b/litellm/llms/vertex_ai.py index ce0ccc73a0..cab7ae19f2 100644 --- a/litellm/llms/vertex_ai.py +++ b/litellm/llms/vertex_ai.py @@ -1,12 +1,12 @@ import os, types import json from enum import Enum -import requests +import requests # type: ignore import time from typing import Callable, Optional, Union, List from litellm.utils import ModelResponse, Usage, CustomStreamWrapper, map_finish_reason import litellm, uuid -import httpx, inspect +import httpx, inspect # type: ignore class VertexAIError(Exception): diff --git a/litellm/llms/vertex_ai_anthropic.py b/litellm/llms/vertex_ai_anthropic.py index e73545f992..3bdcf4fd61 100644 --- a/litellm/llms/vertex_ai_anthropic.py +++ b/litellm/llms/vertex_ai_anthropic.py @@ -3,7 +3,7 @@ import os, types import json from enum import Enum -import requests, copy +import requests, copy # type: ignore import time, uuid from typing import Callable, Optional, List from litellm.utils import ModelResponse, Usage, map_finish_reason, CustomStreamWrapper @@ -17,7 +17,7 @@ from .prompt_templates.factory import ( extract_between_tags, parse_xml_params, ) -import httpx +import httpx # type: ignore class VertexAIError(Exception): diff --git a/litellm/llms/vllm.py b/litellm/llms/vllm.py index 15f18cbdca..b2a9dd54db 100644 --- a/litellm/llms/vllm.py +++ b/litellm/llms/vllm.py @@ -1,8 +1,8 @@ import os import json from enum import Enum -import requests -import time, httpx +import requests # type: ignore +import time, httpx # type: ignore from typing import Callable, Any from litellm.utils import ModelResponse, Usage from .prompt_templates.factory import prompt_factory, custom_prompt diff --git a/litellm/llms/watsonx.py b/litellm/llms/watsonx.py index ac38a2a8fe..082cdb3255 100644 --- a/litellm/llms/watsonx.py +++ b/litellm/llms/watsonx.py @@ -1,12 +1,13 @@ from enum import Enum import json, types, time # noqa: E401 -from contextlib import contextmanager -from typing import Callable, Dict, Optional, Any, Union, List +from contextlib import asynccontextmanager, contextmanager +from typing import AsyncGenerator, Callable, Dict, Generator, Optional, Any, Union, List -import httpx -import requests +import httpx # type: ignore +import requests # type: ignore import litellm -from litellm.utils import ModelResponse, get_secret, Usage +from litellm.utils import Logging, ModelResponse, Usage, get_secret +from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler from .base import BaseLLM from .prompt_templates import factory as ptf @@ -192,7 +193,7 @@ class WatsonXAIEndpoint(str, Enum): class IBMWatsonXAI(BaseLLM): """ - Class to interface with IBM Watsonx.ai API for text generation and embeddings. + Class to interface with IBM watsonx.ai API for text generation and embeddings. Reference: https://cloud.ibm.com/apidocs/watsonx-ai """ @@ -343,7 +344,7 @@ class IBMWatsonXAI(BaseLLM): ) if token is None and api_key is not None: # generate the auth token - if print_verbose: + if print_verbose is not None: print_verbose("Generating IAM token for Watsonx.ai") token = self.generate_iam_token(api_key) elif token is None and api_key is None: @@ -377,8 +378,9 @@ class IBMWatsonXAI(BaseLLM): model_response: ModelResponse, print_verbose: Callable, encoding, - logging_obj, - optional_params: dict, + logging_obj: Logging, + optional_params: Optional[dict] = None, + acompletion: bool = None, litellm_params: Optional[dict] = None, logger_fn=None, timeout: Optional[float] = None, @@ -401,13 +403,15 @@ class IBMWatsonXAI(BaseLLM): prompt = convert_messages_to_prompt( model, messages, provider, custom_prompt_dict ) + + manage_response = self._make_response_manager(async_=(acompletion is True), logging_obj=logging_obj) - def process_text_request(request_params: dict) -> ModelResponse: - with self._manage_response( - request_params, logging_obj=logging_obj, input=prompt, timeout=timeout - ) as resp: - json_resp = resp.json() - + def process_text_gen_response(json_resp: dict) -> ModelResponse: + if "results" not in json_resp: + raise WatsonXAIError( + status_code=500, + message=f"Error: Invalid response from Watsonx.ai API: {json_resp}", + ) generated_text = json_resp["results"][0]["generated_text"] prompt_tokens = json_resp["results"][0]["input_token_count"] completion_tokens = json_resp["results"][0]["generated_token_count"] @@ -426,25 +430,52 @@ class IBMWatsonXAI(BaseLLM): ) return model_response - def process_stream_request( + def handle_text_request(request_params: dict) -> ModelResponse: + with manage_response( + request_params, input=prompt, timeout=timeout, + ) as resp: + json_resp = resp.json() + + return process_text_gen_response(json_resp) + + async def handle_text_request_async(request_params: dict) -> ModelResponse: + async with manage_response( + request_params, input=prompt, timeout=timeout, + ) as resp: + json_resp = resp.json() + return process_text_gen_response(json_resp) + + def handle_stream_request( request_params: dict, ) -> litellm.CustomStreamWrapper: # stream the response - generated chunks will be handled # by litellm.utils.CustomStreamWrapper.handle_watsonx_stream - with self._manage_response( - request_params, - logging_obj=logging_obj, - stream=True, - input=prompt, - timeout=timeout, + with manage_response( + request_params, stream=True, input=prompt, timeout=timeout, ) as resp: - response = litellm.CustomStreamWrapper( + streamwrapper = litellm.CustomStreamWrapper( resp.iter_lines(), model=model, custom_llm_provider="watsonx", logging_obj=logging_obj, ) - return response + return streamwrapper + + async def handle_stream_request_async( + request_params: dict, + ) -> litellm.CustomStreamWrapper: + # stream the response - generated chunks will be handled + # by litellm.utils.CustomStreamWrapper.handle_watsonx_stream + async with manage_response( + request_params, stream=True, input=prompt, timeout=timeout, + ) as resp: + streamwrapper = litellm.CustomStreamWrapper( + resp.aiter_lines(), + model=model, + custom_llm_provider="watsonx", + logging_obj=logging_obj, + ) + return streamwrapper try: ## Get the response from the model @@ -455,10 +486,18 @@ class IBMWatsonXAI(BaseLLM): optional_params=optional_params, print_verbose=print_verbose, ) - if stream: - return process_stream_request(req_params) + if stream and acompletion: + # stream and async text generation + return handle_stream_request_async(req_params) + elif stream: + # streaming text generation + return handle_stream_request(req_params) + elif acompletion: + # async text generation + return handle_text_request_async(req_params) else: - return process_text_request(req_params) + # regular text generation + return handle_text_request(req_params) except WatsonXAIError as e: raise e except Exception as e: @@ -473,6 +512,7 @@ class IBMWatsonXAI(BaseLLM): model_response=None, optional_params=None, encoding=None, + aembedding=None, ): """ Send a text embedding request to the IBM Watsonx.ai API. @@ -507,9 +547,6 @@ class IBMWatsonXAI(BaseLLM): } request_params = dict(version=api_params["api_version"]) url = api_params["url"].rstrip("/") + WatsonXAIEndpoint.EMBEDDINGS - # request = httpx.Request( - # "POST", url, headers=headers, json=payload, params=request_params - # ) req_params = { "method": "POST", "url": url, @@ -517,25 +554,47 @@ class IBMWatsonXAI(BaseLLM): "json": payload, "params": request_params, } - with self._manage_response( - req_params, logging_obj=logging_obj, input=input - ) as resp: - json_resp = resp.json() - - results = json_resp.get("results", []) - embedding_response = [] - for idx, result in enumerate(results): - embedding_response.append( - {"object": "embedding", "index": idx, "embedding": result["embedding"]} + manage_response = self._make_response_manager(async_=(aembedding is True), logging_obj=logging_obj) + + def process_embedding_response(json_resp: dict) -> ModelResponse: + results = json_resp.get("results", []) + embedding_response = [] + for idx, result in enumerate(results): + embedding_response.append( + {"object": "embedding", "index": idx, "embedding": result["embedding"]} + ) + model_response["object"] = "list" + model_response["data"] = embedding_response + model_response["model"] = model + input_tokens = json_resp.get("input_token_count", 0) + model_response.usage = Usage( + prompt_tokens=input_tokens, completion_tokens=0, total_tokens=input_tokens ) - model_response["object"] = "list" - model_response["data"] = embedding_response - model_response["model"] = model - input_tokens = json_resp.get("input_token_count", 0) - model_response.usage = Usage( - prompt_tokens=input_tokens, completion_tokens=0, total_tokens=input_tokens - ) - return model_response + return model_response + + def handle_embedding_request(request_params: dict) -> ModelResponse: + with manage_response( + request_params, input=input + ) as resp: + json_resp = resp.json() + return process_embedding_response(json_resp) + + async def handle_embedding_request_async(request_params: dict) -> ModelResponse: + async with manage_response( + request_params, input=input + ) as resp: + json_resp = resp.json() + return process_embedding_response(json_resp) + + try: + if aembedding: + return handle_embedding_request_async(req_params) + else: + return handle_embedding_request(req_params) + except WatsonXAIError as e: + raise e + except Exception as e: + raise WatsonXAIError(status_code=500, message=str(e)) def generate_iam_token(self, api_key=None, **params): headers = {} @@ -557,53 +616,116 @@ class IBMWatsonXAI(BaseLLM): iam_access_token = json_data["access_token"] self.token = iam_access_token return iam_access_token + + def _make_response_manager( + self, + async_: bool, + logging_obj: Logging + ) -> Callable[..., Generator[Union[requests.Response, httpx.Response], None, None]]: + """ + Returns a context manager that manages the response from the request. + if async_ is True, returns an async context manager, otherwise returns a regular context manager. - @contextmanager - def _manage_response( - self, - request_params: dict, - logging_obj: Any, - stream: bool = False, - input: Optional[Any] = None, - timeout: Optional[float] = None, - ): - request_str = ( - f"response = {request_params['method']}(\n" - f"\turl={request_params['url']},\n" - f"\tjson={request_params['json']},\n" - f")" - ) - logging_obj.pre_call( - input=input, - api_key=request_params["headers"].get("Authorization"), - additional_args={ - "complete_input_dict": request_params["json"], - "request_str": request_str, - }, - ) - if timeout: - request_params["timeout"] = timeout - try: - if stream: - resp = requests.request( - **request_params, - stream=True, - ) - resp.raise_for_status() - yield resp - else: - resp = requests.request(**request_params) - resp.raise_for_status() - yield resp - except Exception as e: - raise WatsonXAIError(status_code=500, message=str(e)) - if not stream: + Usage: + ```python + manage_response = self._make_response_manager(async_=True, logging_obj=logging_obj) + async with manage_response(request_params) as resp: + ... + # or + manage_response = self._make_response_manager(async_=False, logging_obj=logging_obj) + with manage_response(request_params) as resp: + ... + ``` + """ + + def pre_call( + request_params: dict, + input:Optional[Any]=None, + ): + request_str = ( + f"response = {'await ' if async_ else ''}{request_params['method']}(\n" + f"\turl={request_params['url']},\n" + f"\tjson={request_params['json']},\n" + f")" + ) + logging_obj.pre_call( + input=input, + api_key=request_params["headers"].get("Authorization"), + additional_args={ + "complete_input_dict": request_params["json"], + "request_str": request_str, + }, + ) + + def post_call(resp, request_params): logging_obj.post_call( input=input, api_key=request_params["headers"].get("Authorization"), original_response=json.dumps(resp.json()), additional_args={ "status_code": resp.status_code, - "complete_input_dict": request_params["json"], + "complete_input_dict": request_params.get("data", request_params.get("json")), }, ) + + @contextmanager + def _manage_response( + request_params: dict, + stream: bool = False, + input: Optional[Any] = None, + timeout: float = None, + ) -> Generator[requests.Response, None, None]: + """ + Returns a context manager that yields the response from the request. + """ + pre_call(request_params, input) + if timeout: + request_params["timeout"] = timeout + if stream: + request_params["stream"] = stream + try: + resp = requests.request(**request_params) + resp.raise_for_status() + yield resp + except Exception as e: + raise WatsonXAIError(status_code=500, message=str(e)) + if not stream: + post_call(resp, request_params) + + + @asynccontextmanager + async def _manage_response_async( + request_params: dict, + stream: bool = False, + input: Optional[Any] = None, + timeout: float = None, + ) -> AsyncGenerator[httpx.Response, None]: + pre_call(request_params, input) + if timeout: + request_params["timeout"] = timeout + if stream: + request_params["stream"] = stream + try: + # async with AsyncHTTPHandler(timeout=timeout) as client: + self.async_handler = AsyncHTTPHandler( + timeout=httpx.Timeout(timeout=request_params.pop("timeout", 600.0), connect=5.0), + ) + # async_handler.client.verify = False + if "json" in request_params: + request_params['data'] = json.dumps(request_params.pop("json", {})) + method = request_params.pop("method") + if method.upper() == "POST": + resp = await self.async_handler.post(**request_params) + else: + resp = await self.async_handler.get(**request_params) + yield resp + # await async_handler.close() + except Exception as e: + raise WatsonXAIError(status_code=500, message=str(e)) + if not stream: + post_call(resp, request_params) + + if async_: + return _manage_response_async + else: + return _manage_response diff --git a/litellm/main.py b/litellm/main.py index 99e5ec2241..1298dec4c7 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -73,6 +73,7 @@ from .llms.azure_text import AzureTextCompletion from .llms.anthropic import AnthropicChatCompletion from .llms.anthropic_text import AnthropicTextCompletion from .llms.huggingface_restapi import Huggingface +from .llms.watsonx import IBMWatsonXAI from .llms.prompt_templates.factory import ( prompt_factory, custom_prompt, @@ -109,6 +110,7 @@ anthropic_text_completions = AnthropicTextCompletion() azure_chat_completions = AzureChatCompletion() azure_text_completions = AzureTextCompletion() huggingface = Huggingface() +watsonxai = IBMWatsonXAI() ####### COMPLETION ENDPOINTS ################ @@ -313,6 +315,7 @@ async def acompletion( or custom_llm_provider == "gemini" or custom_llm_provider == "sagemaker" or custom_llm_provider == "anthropic" + or custom_llm_provider == "watsonx" or custom_llm_provider in litellm.openai_compatible_providers ): # currently implemented aiohttp calls for just azure, openai, hf, ollama, vertex ai soon all. init_response = await loop.run_in_executor(None, func_with_context) @@ -1908,7 +1911,7 @@ def completion( response = response elif custom_llm_provider == "watsonx": custom_prompt_dict = custom_prompt_dict or litellm.custom_prompt_dict - response = watsonx.IBMWatsonXAI().completion( + response = watsonxai.completion( model=model, messages=messages, custom_prompt_dict=custom_prompt_dict, @@ -1919,7 +1922,8 @@ def completion( logger_fn=logger_fn, encoding=encoding, logging_obj=logging, - timeout=timeout, # type: ignore + acompletion=acompletion, + timeout=timeout, ) if ( "stream" in optional_params @@ -2572,6 +2576,7 @@ async def aembedding(*args, **kwargs): or custom_llm_provider == "fireworks_ai" or custom_llm_provider == "ollama" or custom_llm_provider == "vertex_ai" + or custom_llm_provider == "watsonx" ): # currently implemented aiohttp calls for just azure and openai, soon all. # Await normally init_response = await loop.run_in_executor(None, func_with_context) @@ -3029,13 +3034,14 @@ def embedding( aembedding=aembedding, ) elif custom_llm_provider == "watsonx": - response = watsonx.IBMWatsonXAI().embedding( + response = watsonxai.embedding( model=model, input=input, encoding=encoding, logging_obj=logging, optional_params=optional_params, model_response=EmbeddingResponse(), + aembedding=aembedding, ) else: args = locals() diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html index 8a47f9984e..448d7cf877 100644 --- a/litellm/proxy/_experimental/out/404.html +++ b/litellm/proxy/_experimental/out/404.html @@ -1 +1 @@ -
0?a=a.charAt(0)+"."+a.slice(1)+x(r):i>1&&(a=a.charAt(0)+"."+a.slice(1)),a=a+(o<0?"e":"e+")+o):o<0?(a="0."+x(-o-1)+a,n&&(r=n-i)>0&&(a+=x(r))):o>=i?(a+=x(o+1-i),n&&(r=n-o-1)>0&&(a=a+"."+x(r))):((r=o+1)0&&(o+1===i&&(a+="."),a+=x(r))),e.s<0?"-"+a:a}function N(e,t){if(e.length>t)return e.length=t,!0}function I(e){if(!e||"object"!=typeof e)throw Error(s+"Object expected");var t,n,r,o=["precision",1,1e9,"rounding",0,8,"toExpNeg",-1/0,0,"toExpPos",0,1/0];for(t=0;t239?4:c>223?3:c>191?2:1;if(o+d<=n)switch(d){case 1:c<128&&(u=c);break;case 2:(192&(a=e[o+1]))==128&&(s=(31&c)<<6|63&a)>127&&(u=s);break;case 3:a=e[o+1],i=e[o+2],(192&a)==128&&(192&i)==128&&(s=(15&c)<<12|(63&a)<<6|63&i)>2047&&(s<55296||s>57343)&&(u=s);break;case 4:a=e[o+1],i=e[o+2],l=e[o+3],(192&a)==128&&(192&i)==128&&(192&l)==128&&(s=(15&c)<<18|(63&a)<<12|(63&i)<<6|63&l)>65535&&s<1114112&&(u=s)}null===u?(u=65533,d=1):u>65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u),o+=d}return function(e){var t=e.length;if(t<=4096)return String.fromCharCode.apply(String,e);for(var n="",r=0;r>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0);else throw Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");var o,a,i,l,s,c,u,d,p,f,m,g,h=this.length-t;if((void 0===n||n>h)&&(n=h),e.length>0&&(n<0||t<0)||t>this.length)throw RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var b=!1;;)switch(r){case"hex":return function(e,t,n,r){n=Number(n)||0;var o=e.length-n;r?(r=Number(r))>o&&(r=o):r=o;var a=t.length;r>a/2&&(r=a/2);for(var i=0;i