Merge branch 'BerriAI:main' into ollama-image-handling
This commit is contained in:
commit
6b55bd0dee
@ -23,6 +23,14 @@ response = completion(model="gpt-3.5-turbo", messages=messages)
|
||||
response = completion("command-nightly", messages)
|
||||
```
|
||||
|
||||
## JSON Logs
|
||||
|
||||
If you need to store the logs as JSON, just set the `litellm.json_logs = True`.
|
||||
|
||||
We currently just log the raw POST request from litellm as a JSON - [**See Code**].
|
||||
|
||||
[Share feedback here](https://github.com/BerriAI/litellm/issues)
|
||||
|
||||
## Logger Function
|
||||
But sometimes all you care about is seeing exactly what's getting sent to your api call and what's being returned - e.g. if the api call is failing, why is that happening? what are the exact params being set?
|
||||
|
||||
|
||||
@ -213,3 +213,349 @@ asyncio.run(loadtest_fn())
|
||||
|
||||
```
|
||||
|
||||
## Multi-Instance TPM/RPM Load Test (Router)
|
||||
|
||||
Test if your defined tpm/rpm limits are respected across multiple instances of the Router object.
|
||||
|
||||
In our test:
|
||||
- Max RPM per deployment is = 100 requests per minute
|
||||
- Max Throughput / min on router = 200 requests per minute (2 deployments)
|
||||
- Load we'll send through router = 600 requests per minute
|
||||
|
||||
:::info
|
||||
|
||||
If you don't want to call a real LLM API endpoint, you can setup a fake openai server. [See code](#extra---setup-fake-openai-server)
|
||||
|
||||
:::
|
||||
|
||||
### Code
|
||||
|
||||
Let's hit the router with 600 requests per minute.
|
||||
|
||||
Copy this script 👇. Save it as `test_loadtest_router.py` AND run it with `python3 test_loadtest_router.py`
|
||||
|
||||
|
||||
```python
|
||||
from litellm import Router
|
||||
import litellm
|
||||
litellm.suppress_debug_info = True
|
||||
litellm.set_verbose = False
|
||||
import logging
|
||||
logging.basicConfig(level=logging.CRITICAL)
|
||||
import os, random, uuid, time, asyncio
|
||||
|
||||
# Model list for OpenAI and Anthropic models
|
||||
model_list = [
|
||||
{
|
||||
"model_name": "fake-openai-endpoint",
|
||||
"litellm_params": {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"api_key": "my-fake-key",
|
||||
"api_base": "http://0.0.0.0:8080",
|
||||
"rpm": 100
|
||||
},
|
||||
},
|
||||
{
|
||||
"model_name": "fake-openai-endpoint",
|
||||
"litellm_params": {
|
||||
"model": "gpt-3.5-turbo",
|
||||
"api_key": "my-fake-key",
|
||||
"api_base": "http://0.0.0.0:8081",
|
||||
"rpm": 100
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
router_1 = Router(model_list=model_list, num_retries=0, enable_pre_call_checks=True, routing_strategy="usage-based-routing-v2", redis_host=os.getenv("REDIS_HOST"), redis_port=os.getenv("REDIS_PORT"), redis_password=os.getenv("REDIS_PASSWORD"))
|
||||
router_2 = Router(model_list=model_list, num_retries=0, routing_strategy="usage-based-routing-v2", enable_pre_call_checks=True, redis_host=os.getenv("REDIS_HOST"), redis_port=os.getenv("REDIS_PORT"), redis_password=os.getenv("REDIS_PASSWORD"))
|
||||
|
||||
|
||||
|
||||
async def router_completion_non_streaming():
|
||||
try:
|
||||
client: Router = random.sample([router_1, router_2], 1)[0] # randomly pick b/w clients
|
||||
# print(f"client={client}")
|
||||
response = await client.acompletion(
|
||||
model="fake-openai-endpoint", # [CHANGE THIS] (if you call it something else on your proxy)
|
||||
messages=[{"role": "user", "content": f"This is a test: {uuid.uuid4()}"}],
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
# print(e)
|
||||
return None
|
||||
|
||||
async def loadtest_fn():
|
||||
start = time.time()
|
||||
n = 600 # Number of concurrent tasks
|
||||
tasks = [router_completion_non_streaming() for _ in range(n)]
|
||||
chat_completions = await asyncio.gather(*tasks)
|
||||
successful_completions = [c for c in chat_completions if c is not None]
|
||||
print(n, time.time() - start, len(successful_completions))
|
||||
|
||||
def get_utc_datetime():
|
||||
import datetime as dt
|
||||
from datetime import datetime
|
||||
|
||||
if hasattr(dt, "UTC"):
|
||||
return datetime.now(dt.UTC) # type: ignore
|
||||
else:
|
||||
return datetime.utcnow() # type: ignore
|
||||
|
||||
|
||||
# Run the event loop to execute the async function
|
||||
async def parent_fn():
|
||||
for _ in range(10):
|
||||
dt = get_utc_datetime()
|
||||
current_minute = dt.strftime("%H-%M")
|
||||
print(f"triggered new batch - {current_minute}")
|
||||
await loadtest_fn()
|
||||
await asyncio.sleep(10)
|
||||
|
||||
asyncio.run(parent_fn())
|
||||
```
|
||||
## Multi-Instance TPM/RPM Load Test (Proxy)
|
||||
|
||||
Test if your defined tpm/rpm limits are respected across multiple instances.
|
||||
|
||||
The quickest way to do this is by testing the [proxy](./proxy/quick_start.md). The proxy uses the [router](./routing.md) under the hood, so if you're using either of them, this test should work for you.
|
||||
|
||||
In our test:
|
||||
- Max RPM per deployment is = 100 requests per minute
|
||||
- Max Throughput / min on proxy = 200 requests per minute (2 deployments)
|
||||
- Load we'll send to proxy = 600 requests per minute
|
||||
|
||||
|
||||
So we'll send 600 requests per minute, but expect only 200 requests per minute to succeed.
|
||||
|
||||
:::info
|
||||
|
||||
If you don't want to call a real LLM API endpoint, you can setup a fake openai server. [See code](#extra---setup-fake-openai-server)
|
||||
|
||||
:::
|
||||
|
||||
### 1. Setup config
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
- litellm_params:
|
||||
api_base: http://0.0.0.0:8080
|
||||
api_key: my-fake-key
|
||||
model: openai/my-fake-model
|
||||
rpm: 100
|
||||
model_name: fake-openai-endpoint
|
||||
- litellm_params:
|
||||
api_base: http://0.0.0.0:8081
|
||||
api_key: my-fake-key
|
||||
model: openai/my-fake-model-2
|
||||
rpm: 100
|
||||
model_name: fake-openai-endpoint
|
||||
router_settings:
|
||||
num_retries: 0
|
||||
enable_pre_call_checks: true
|
||||
redis_host: os.environ/REDIS_HOST ## 👈 IMPORTANT! Setup the proxy w/ redis
|
||||
redis_password: os.environ/REDIS_PASSWORD
|
||||
redis_port: os.environ/REDIS_PORT
|
||||
routing_strategy: usage-based-routing-v2
|
||||
```
|
||||
|
||||
### 2. Start proxy 2 instances
|
||||
|
||||
**Instance 1**
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml --port 4000
|
||||
|
||||
## RUNNING on http://0.0.0.0:4000
|
||||
```
|
||||
|
||||
**Instance 2**
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml --port 4001
|
||||
|
||||
## RUNNING on http://0.0.0.0:4001
|
||||
```
|
||||
|
||||
### 3. Run Test
|
||||
|
||||
Let's hit the proxy with 600 requests per minute.
|
||||
|
||||
Copy this script 👇. Save it as `test_loadtest_proxy.py` AND run it with `python3 test_loadtest_proxy.py`
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI, AsyncAzureOpenAI
|
||||
import random, uuid
|
||||
import time, asyncio, litellm
|
||||
# import logging
|
||||
# logging.basicConfig(level=logging.DEBUG)
|
||||
#### LITELLM PROXY ####
|
||||
litellm_client = AsyncOpenAI(
|
||||
api_key="sk-1234", # [CHANGE THIS]
|
||||
base_url="http://0.0.0.0:4000"
|
||||
)
|
||||
litellm_client_2 = AsyncOpenAI(
|
||||
api_key="sk-1234", # [CHANGE THIS]
|
||||
base_url="http://0.0.0.0:4001"
|
||||
)
|
||||
|
||||
async def proxy_completion_non_streaming():
|
||||
try:
|
||||
client = random.sample([litellm_client, litellm_client_2], 1)[0] # randomly pick b/w clients
|
||||
# print(f"client={client}")
|
||||
response = await client.chat.completions.create(
|
||||
model="fake-openai-endpoint", # [CHANGE THIS] (if you call it something else on your proxy)
|
||||
messages=[{"role": "user", "content": f"This is a test: {uuid.uuid4()}"}],
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
# print(e)
|
||||
return None
|
||||
|
||||
async def loadtest_fn():
|
||||
start = time.time()
|
||||
n = 600 # Number of concurrent tasks
|
||||
tasks = [proxy_completion_non_streaming() for _ in range(n)]
|
||||
chat_completions = await asyncio.gather(*tasks)
|
||||
successful_completions = [c for c in chat_completions if c is not None]
|
||||
print(n, time.time() - start, len(successful_completions))
|
||||
|
||||
def get_utc_datetime():
|
||||
import datetime as dt
|
||||
from datetime import datetime
|
||||
|
||||
if hasattr(dt, "UTC"):
|
||||
return datetime.now(dt.UTC) # type: ignore
|
||||
else:
|
||||
return datetime.utcnow() # type: ignore
|
||||
|
||||
|
||||
# Run the event loop to execute the async function
|
||||
async def parent_fn():
|
||||
for _ in range(10):
|
||||
dt = get_utc_datetime()
|
||||
current_minute = dt.strftime("%H-%M")
|
||||
print(f"triggered new batch - {current_minute}")
|
||||
await loadtest_fn()
|
||||
await asyncio.sleep(10)
|
||||
|
||||
asyncio.run(parent_fn())
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Extra - Setup Fake OpenAI Server
|
||||
|
||||
Let's setup a fake openai server with a RPM limit of 100.
|
||||
|
||||
Let's call our file `fake_openai_server.py`.
|
||||
|
||||
```
|
||||
# import sys, os
|
||||
# sys.path.insert(
|
||||
# 0, os.path.abspath("../")
|
||||
# ) # Adds the parent directory to the system path
|
||||
from fastapi import FastAPI, Request, status, HTTPException, Depends
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import FastAPI, Request, HTTPException, UploadFile, File
|
||||
import httpx, os, json
|
||||
from openai import AsyncOpenAI
|
||||
from typing import Optional
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import PlainTextResponse
|
||||
|
||||
|
||||
class ProxyException(Exception):
|
||||
# NOTE: DO NOT MODIFY THIS
|
||||
# This is used to map exactly to OPENAI Exceptions
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
type: str,
|
||||
param: Optional[str],
|
||||
code: Optional[int],
|
||||
):
|
||||
self.message = message
|
||||
self.type = type
|
||||
self.param = param
|
||||
self.code = code
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Converts the ProxyException instance to a dictionary."""
|
||||
return {
|
||||
"message": self.message,
|
||||
"type": self.type,
|
||||
"param": self.param,
|
||||
"code": self.code,
|
||||
}
|
||||
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
app = FastAPI()
|
||||
app.state.limiter = limiter
|
||||
|
||||
@app.exception_handler(RateLimitExceeded)
|
||||
async def _rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded):
|
||||
return JSONResponse(status_code=429,
|
||||
content={"detail": "Rate Limited!"})
|
||||
|
||||
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# for completion
|
||||
@app.post("/chat/completions")
|
||||
@app.post("/v1/chat/completions")
|
||||
@limiter.limit("100/minute")
|
||||
async def completion(request: Request):
|
||||
# raise HTTPException(status_code=429, detail="Rate Limited!")
|
||||
return {
|
||||
"id": "chatcmpl-123",
|
||||
"object": "chat.completion",
|
||||
"created": 1677652288,
|
||||
"model": None,
|
||||
"system_fingerprint": "fp_44709d6fcb",
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "\n\nHello there, how may I assist you today?",
|
||||
},
|
||||
"logprobs": None,
|
||||
"finish_reason": "stop"
|
||||
}],
|
||||
"usage": {
|
||||
"prompt_tokens": 9,
|
||||
"completion_tokens": 12,
|
||||
"total_tokens": 21
|
||||
}
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import socket
|
||||
import uvicorn
|
||||
port = 8080
|
||||
while True:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
result = sock.connect_ex(('0.0.0.0', port))
|
||||
if result != 0:
|
||||
print(f"Port {port} is available, starting server...")
|
||||
break
|
||||
else:
|
||||
port += 1
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
```
|
||||
|
||||
```bash
|
||||
python3 fake_openai_server.py
|
||||
```
|
||||
|
||||
@ -331,49 +331,25 @@ response = litellm.completion(model="gpt-3.5-turbo", messages=messages, metadata
|
||||
## Examples
|
||||
|
||||
### Custom Callback to track costs for Streaming + Non-Streaming
|
||||
By default, the response cost is accessible in the logging object via `kwargs["response_cost"]` on success (sync + async)
|
||||
```python
|
||||
|
||||
# Step 1. Write your custom callback function
|
||||
def track_cost_callback(
|
||||
kwargs, # kwargs to completion
|
||||
completion_response, # response from completion
|
||||
start_time, end_time # start/end time
|
||||
):
|
||||
try:
|
||||
# init logging config
|
||||
logging.basicConfig(
|
||||
filename='cost.log',
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# check if it has collected an entire stream response
|
||||
if "complete_streaming_response" in kwargs:
|
||||
# for tracking streaming cost we pass the "messages" and the output_text to litellm.completion_cost
|
||||
completion_response=kwargs["complete_streaming_response"]
|
||||
input_text = kwargs["messages"]
|
||||
output_text = completion_response["choices"][0]["message"]["content"]
|
||||
response_cost = litellm.completion_cost(
|
||||
model = kwargs["model"],
|
||||
messages = input_text,
|
||||
completion=output_text
|
||||
)
|
||||
print("streaming response_cost", response_cost)
|
||||
logging.info(f"Model {kwargs['model']} Cost: ${response_cost:.8f}")
|
||||
|
||||
# for non streaming responses
|
||||
else:
|
||||
# we pass the completion_response obj
|
||||
if kwargs["stream"] != True:
|
||||
response_cost = litellm.completion_cost(completion_response=completion_response)
|
||||
print("regular response_cost", response_cost)
|
||||
logging.info(f"Model {completion_response.model} Cost: ${response_cost:.8f}")
|
||||
response_cost = kwargs["response_cost"] # litellm calculates response cost for you
|
||||
print("regular response_cost", response_cost)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Assign the custom callback function
|
||||
# Step 2. Assign the custom callback function
|
||||
litellm.success_callback = [track_cost_callback]
|
||||
|
||||
# Step 3. Make litellm.completion call
|
||||
response = completion(
|
||||
model="gpt-3.5-turbo",
|
||||
messages=[
|
||||
|
||||
@ -121,10 +121,12 @@ response = completion(
|
||||
metadata={
|
||||
"generation_name": "ishaan-test-generation", # set langfuse Generation Name
|
||||
"generation_id": "gen-id22", # set langfuse Generation ID
|
||||
"trace_id": "trace-id22", # set langfuse Trace ID
|
||||
"trace_user_id": "user-id2", # set langfuse Trace User ID
|
||||
"session_id": "session-1", # set langfuse Session ID
|
||||
"tags": ["tag1", "tag2"] # set langfuse Tags
|
||||
"trace_id": "trace-id22", # set langfuse Trace ID
|
||||
### OR ###
|
||||
"existing_trace_id": "trace-id22", # if generation is continuation of past trace. This prevents default behaviour of setting a trace name
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -62,9 +62,11 @@ model_list:
|
||||
|
||||
litellm_settings: # module level litellm settings - https://github.com/BerriAI/litellm/blob/main/litellm/__init__.py
|
||||
drop_params: True
|
||||
success_callback: ["langfuse"] # OPTIONAL - if you want to start sending LLM Logs to Langfuse. Make sure to set `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` in your env
|
||||
|
||||
general_settings:
|
||||
master_key: sk-1234 # [OPTIONAL] Only use this if you to require all calls to contain this key (Authorization: Bearer sk-1234)
|
||||
alerting: ["slack"] # [OPTIONAL] If you want Slack Alerts for Hanging LLM requests, Slow llm responses, Budget Alerts. Make sure to set `SLACK_WEBHOOK_URL` in your env
|
||||
```
|
||||
:::info
|
||||
|
||||
|
||||
@ -272,26 +272,63 @@ Your OpenAI proxy server is now running on `http://0.0.0.0:4000`.
|
||||
#### Step 1. Create deployment.yaml
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: litellm-deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: litellm
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: litellm
|
||||
spec:
|
||||
containers:
|
||||
- name: litellm-container
|
||||
image: ghcr.io/berriai/litellm-database:main-latest
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
value: postgresql://<user>:<password>@<host>:<port>/<dbname>
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: litellm-deployment
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: litellm
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: litellm
|
||||
spec:
|
||||
containers:
|
||||
- name: litellm-container
|
||||
image: ghcr.io/berriai/litellm:main-latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: AZURE_API_KEY
|
||||
value: "d6******"
|
||||
- name: AZURE_API_BASE
|
||||
value: "https://ope******"
|
||||
- name: LITELLM_MASTER_KEY
|
||||
value: "sk-1234"
|
||||
- name: DATABASE_URL
|
||||
value: "po**********"
|
||||
args:
|
||||
- "--config"
|
||||
- "/app/proxy_config.yaml" # Update the path to mount the config file
|
||||
volumeMounts: # Define volume mount for proxy_config.yaml
|
||||
- name: config-volume
|
||||
mountPath: /app
|
||||
readOnly: true
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/liveliness
|
||||
port: 4000
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 15
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/readiness
|
||||
port: 4000
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 15
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
timeoutSeconds: 10
|
||||
volumes: # Define volume to mount proxy_config.yaml
|
||||
- name: config-volume
|
||||
configMap:
|
||||
name: litellm-config
|
||||
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
@ -401,7 +401,7 @@ litellm_settings:
|
||||
Start the LiteLLM Proxy and make a test request to verify the logs reached your callback API
|
||||
|
||||
## Logging Proxy Input/Output - Langfuse
|
||||
We will use the `--config` to set `litellm.success_callback = ["langfuse"]` this will log all successfull LLM calls to langfuse
|
||||
We will use the `--config` to set `litellm.success_callback = ["langfuse"]` this will log all successfull LLM calls to langfuse. Make sure to set `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` in your environment
|
||||
|
||||
**Step 1** Install langfuse
|
||||
|
||||
@ -419,7 +419,13 @@ litellm_settings:
|
||||
success_callback: ["langfuse"]
|
||||
```
|
||||
|
||||
**Step 3**: Start the proxy, make a test request
|
||||
**Step 3**: Set required env variables for logging to langfuse
|
||||
```shell
|
||||
export LANGFUSE_PUBLIC_KEY="pk_kk"
|
||||
export LANGFUSE_SECRET_KEY="sk_ss
|
||||
```
|
||||
|
||||
**Step 4**: Start the proxy, make a test request
|
||||
|
||||
Start proxy
|
||||
```shell
|
||||
|
||||
@ -443,6 +443,35 @@ asyncio.run(router_acompletion())
|
||||
|
||||
## Basic Reliability
|
||||
|
||||
### Max Parallel Requests (ASYNC)
|
||||
|
||||
Used in semaphore for async requests on router. Limit the max concurrent calls made to a deployment. Useful in high-traffic scenarios.
|
||||
|
||||
If tpm/rpm is set, and no max parallel request limit given, we use the RPM or calculated RPM (tpm/1000/6) as the max parallel request limit.
|
||||
|
||||
|
||||
```python
|
||||
from litellm import Router
|
||||
|
||||
model_list = [{
|
||||
"model_name": "gpt-4",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-4",
|
||||
...
|
||||
"max_parallel_requests": 10 # 👈 SET PER DEPLOYMENT
|
||||
}
|
||||
}]
|
||||
|
||||
### OR ###
|
||||
|
||||
router = Router(model_list=model_list, default_max_parallel_requests=20) # 👈 SET DEFAULT MAX PARALLEL REQUESTS
|
||||
|
||||
|
||||
# deployment max parallel requests > default max parallel requests
|
||||
```
|
||||
|
||||
[**See Code**](https://github.com/BerriAI/litellm/blob/a978f2d8813c04dad34802cb95e0a0e35a3324bc/litellm/utils.py#L5605)
|
||||
|
||||
### Timeouts
|
||||
|
||||
The timeout set in router is for the entire length of the call, and is passed down to the completion() call level as well.
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import threading, requests, os
|
||||
from typing import Callable, List, Optional, Dict, Union, Any, Literal
|
||||
from litellm.caching import Cache
|
||||
from litellm._logging import set_verbose, _turn_on_debug, verbose_logger
|
||||
from litellm._logging import set_verbose, _turn_on_debug, verbose_logger, json_logs
|
||||
from litellm.proxy._types import (
|
||||
KeyManagementSystem,
|
||||
KeyManagementSettings,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import logging
|
||||
|
||||
set_verbose = False
|
||||
|
||||
json_logs = False
|
||||
# Create a handler for the logger (you may need to adapt this based on your needs)
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(logging.DEBUG)
|
||||
|
||||
@ -265,15 +265,18 @@ class LangFuseLogger:
|
||||
tags = metadata_tags
|
||||
|
||||
trace_name = metadata.get("trace_name", None)
|
||||
if trace_name is None:
|
||||
trace_id = metadata.get("trace_id", None)
|
||||
existing_trace_id = metadata.get("existing_trace_id", None)
|
||||
if trace_name is None and existing_trace_id is None:
|
||||
# just log `litellm-{call_type}` as the trace name
|
||||
## DO NOT SET TRACE_NAME if trace-id set. this can lead to overwriting of past traces.
|
||||
trace_name = f"litellm-{kwargs.get('call_type', 'completion')}"
|
||||
|
||||
trace_params = {
|
||||
"name": trace_name,
|
||||
"input": input,
|
||||
"user_id": metadata.get("trace_user_id", user_id),
|
||||
"id": metadata.get("trace_id", None),
|
||||
"id": trace_id or existing_trace_id,
|
||||
"session_id": metadata.get("session_id", None),
|
||||
}
|
||||
|
||||
|
||||
@ -73,10 +73,6 @@ class LangsmithLogger:
|
||||
elif type(value) != dict and is_serializable(value=value):
|
||||
new_kwargs[key] = value
|
||||
|
||||
print(f"type of response: {type(response_obj)}")
|
||||
for k, v in new_kwargs.items():
|
||||
print(f"key={k}, type of arg: {type(v)}, value={v}")
|
||||
|
||||
if isinstance(response_obj, BaseModel):
|
||||
try:
|
||||
response_obj = response_obj.model_dump()
|
||||
|
||||
@ -3,8 +3,14 @@ import requests, traceback
|
||||
import json, re, xml.etree.ElementTree as ET
|
||||
from jinja2 import Template, exceptions, meta, BaseLoader
|
||||
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
||||
from typing import Optional, Any
|
||||
from typing import List
|
||||
from typing import (
|
||||
Any,
|
||||
List,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
)
|
||||
import litellm
|
||||
|
||||
|
||||
@ -430,8 +436,10 @@ def format_prompt_togetherai(messages, prompt_format, chat_template):
|
||||
prompt = default_pt(messages)
|
||||
return prompt
|
||||
|
||||
|
||||
### IBM Granite
|
||||
|
||||
|
||||
def ibm_granite_pt(messages: list):
|
||||
"""
|
||||
IBM's Granite models uses the template:
|
||||
@ -440,23 +448,24 @@ def ibm_granite_pt(messages: list):
|
||||
See: https://www.ibm.com/docs/en/watsonx-as-a-service?topic=solutions-supported-foundation-models
|
||||
"""
|
||||
return custom_prompt(
|
||||
messages=messages,
|
||||
messages=messages,
|
||||
role_dict={
|
||||
'system': {
|
||||
'pre_message': '<|system|>\n',
|
||||
'post_message': '\n',
|
||||
"system": {
|
||||
"pre_message": "<|system|>\n",
|
||||
"post_message": "\n",
|
||||
},
|
||||
'user': {
|
||||
'pre_message': '<|user|>\n',
|
||||
'post_message': '\n',
|
||||
"user": {
|
||||
"pre_message": "<|user|>\n",
|
||||
"post_message": "\n",
|
||||
},
|
||||
'assistant': {
|
||||
'pre_message': '<|assistant|>\n',
|
||||
'post_message': '\n',
|
||||
}
|
||||
}
|
||||
"assistant": {
|
||||
"pre_message": "<|assistant|>\n",
|
||||
"post_message": "\n",
|
||||
},
|
||||
},
|
||||
).strip()
|
||||
|
||||
|
||||
### ANTHROPIC ###
|
||||
|
||||
|
||||
@ -1043,6 +1052,30 @@ def get_system_prompt(messages):
|
||||
return system_prompt, messages
|
||||
|
||||
|
||||
def convert_to_documents(
|
||||
observations: Any,
|
||||
) -> List[MutableMapping]:
|
||||
"""Converts observations into a 'document' dict"""
|
||||
documents: List[MutableMapping] = []
|
||||
if isinstance(observations, str):
|
||||
# strings are turned into a key/value pair and a key of 'output' is added.
|
||||
observations = [{"output": observations}]
|
||||
elif isinstance(observations, Mapping):
|
||||
# single mappings are transformed into a list to simplify the rest of the code.
|
||||
observations = [observations]
|
||||
elif not isinstance(observations, Sequence):
|
||||
# all other types are turned into a key/value pair within a list
|
||||
observations = [{"output": observations}]
|
||||
|
||||
for doc in observations:
|
||||
if not isinstance(doc, Mapping):
|
||||
# types that aren't Mapping are turned into a key/value pair.
|
||||
doc = {"output": doc}
|
||||
documents.append(doc)
|
||||
|
||||
return documents
|
||||
|
||||
|
||||
def convert_openai_message_to_cohere_tool_result(message):
|
||||
"""
|
||||
OpenAI message with a tool result looks like:
|
||||
@ -1084,7 +1117,7 @@ def convert_openai_message_to_cohere_tool_result(message):
|
||||
"parameters": {"location": "San Francisco, CA"},
|
||||
"generation_id": tool_call_id,
|
||||
},
|
||||
"outputs": [content],
|
||||
"outputs": convert_to_documents(content),
|
||||
}
|
||||
return cohere_tool_result
|
||||
|
||||
@ -1097,7 +1130,7 @@ def cohere_message_pt(messages: list):
|
||||
if message["role"] == "tool":
|
||||
tool_result = convert_openai_message_to_cohere_tool_result(message)
|
||||
tool_results.append(tool_result)
|
||||
else:
|
||||
elif message.get("content"):
|
||||
prompt += message["content"] + "\n\n"
|
||||
prompt = prompt.rstrip()
|
||||
return prompt, tool_results
|
||||
@ -1396,9 +1429,18 @@ def prompt_factory(
|
||||
# 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|>"},
|
||||
"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|>",
|
||||
},
|
||||
},
|
||||
messages=messages,
|
||||
initial_prompt_value="<|begin_of_text|>",
|
||||
|
||||
@ -1418,6 +1418,123 @@
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-13b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.0000001,
|
||||
"output_cost_per_token": 0.0000005,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-13b-chat": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.0000001,
|
||||
"output_cost_per_token": 0.0000005,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-70b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-70b-chat": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-7b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-7b-chat": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-70b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-70b-instruct": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-8b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-8b-instruct": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/mistralai/mistral-7b-v0.1": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/mistralai/mistral-7b-instruct-v0.2": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/mistralai/mixtral-8x7b-instruct-v0.1": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.0000003,
|
||||
"output_cost_per_token": 0.000001,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/openai/gpt-3.5-turbo": {
|
||||
"max_tokens": 4095,
|
||||
"input_cost_per_token": 0.0000015,
|
||||
@ -2379,6 +2496,24 @@
|
||||
"litellm_provider": "bedrock",
|
||||
"mode": "chat"
|
||||
},
|
||||
"meta.llama3-8b-instruct-v1:0": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
"max_output_tokens": 8192,
|
||||
"input_cost_per_token": 0.0000004,
|
||||
"output_cost_per_token": 0.0000006,
|
||||
"litellm_provider": "bedrock",
|
||||
"mode": "chat"
|
||||
},
|
||||
"meta.llama3-70b-instruct-v1:0": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
"max_output_tokens": 8192,
|
||||
"input_cost_per_token": 0.00000265,
|
||||
"output_cost_per_token": 0.0000035,
|
||||
"litellm_provider": "bedrock",
|
||||
"mode": "chat"
|
||||
},
|
||||
"512-x-512/50-steps/stability.stable-diffusion-xl-v0": {
|
||||
"max_tokens": 77,
|
||||
"max_input_tokens": 77,
|
||||
|
||||
@ -3,21 +3,17 @@ model_list:
|
||||
api_base: http://0.0.0.0:8080
|
||||
api_key: my-fake-key
|
||||
model: openai/my-fake-model
|
||||
rpm: 100
|
||||
model_name: fake-openai-endpoint
|
||||
- litellm_params:
|
||||
api_base: http://0.0.0.0:8080
|
||||
api_base: http://0.0.0.0:8081
|
||||
api_key: my-fake-key
|
||||
model: openai/my-fake-model-2
|
||||
model_name: fake-openai-endpoint
|
||||
- litellm_params:
|
||||
api_base: http://0.0.0.0:8080
|
||||
api_key: my-fake-key
|
||||
model: openai/my-fake-model-3
|
||||
model_name: fake-openai-endpoint
|
||||
- litellm_params:
|
||||
api_base: http://0.0.0.0:8080
|
||||
api_key: my-fake-key
|
||||
model: openai/my-fake-model-4
|
||||
rpm: 100
|
||||
model_name: fake-openai-endpoint
|
||||
router_settings:
|
||||
num_retries: 0
|
||||
num_retries: 0
|
||||
enable_pre_call_checks: true
|
||||
redis_host: os.environ/REDIS_HOST
|
||||
redis_password: os.environ/REDIS_PASSWORD
|
||||
redis_port: os.environ/REDIS_PORT
|
||||
@ -422,6 +422,9 @@ class LiteLLM_ModelTable(LiteLLMBase):
|
||||
created_by: str
|
||||
updated_by: str
|
||||
|
||||
class Config:
|
||||
protected_namespaces = ()
|
||||
|
||||
|
||||
class NewUserRequest(GenerateKeyRequest):
|
||||
max_budget: Optional[float] = None
|
||||
@ -485,6 +488,9 @@ class TeamBase(LiteLLMBase):
|
||||
class NewTeamRequest(TeamBase):
|
||||
model_aliases: Optional[dict] = None
|
||||
|
||||
class Config:
|
||||
protected_namespaces = ()
|
||||
|
||||
|
||||
class GlobalEndUsersSpend(LiteLLMBase):
|
||||
api_key: Optional[str] = None
|
||||
@ -534,6 +540,9 @@ class LiteLLM_TeamTable(TeamBase):
|
||||
budget_reset_at: Optional[datetime] = None
|
||||
model_id: Optional[int] = None
|
||||
|
||||
class Config:
|
||||
protected_namespaces = ()
|
||||
|
||||
@root_validator(pre=True)
|
||||
def set_model_info(cls, values):
|
||||
dict_fields = [
|
||||
@ -570,6 +579,9 @@ class LiteLLM_BudgetTable(LiteLLMBase):
|
||||
model_max_budget: Optional[dict] = None
|
||||
budget_duration: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
protected_namespaces = ()
|
||||
|
||||
|
||||
class NewOrganizationRequest(LiteLLM_BudgetTable):
|
||||
organization_id: Optional[str] = None
|
||||
@ -900,5 +912,18 @@ class LiteLLM_SpendLogs(LiteLLMBase):
|
||||
request_tags: Optional[Json] = None
|
||||
|
||||
|
||||
class LiteLLM_ErrorLogs(LiteLLMBase):
|
||||
request_id: Optional[str] = str(uuid.uuid4())
|
||||
api_base: Optional[str] = ""
|
||||
model_group: Optional[str] = ""
|
||||
model_id: Optional[str] = ""
|
||||
request_kwargs: Optional[dict] = {}
|
||||
exception_type: Optional[str] = ""
|
||||
status_code: Optional[str] = ""
|
||||
exception_string: Optional[str] = ""
|
||||
startTime: Union[str, datetime, None]
|
||||
endTime: Union[str, datetime, None]
|
||||
|
||||
|
||||
class LiteLLM_SpendLogs_ResponseObject(LiteLLMBase):
|
||||
response: Optional[List[Union[LiteLLM_SpendLogs, Any]]] = None
|
||||
|
||||
@ -1059,8 +1059,18 @@ async def user_api_key_auth(
|
||||
):
|
||||
pass
|
||||
else:
|
||||
user_role = "unknown"
|
||||
user_id = "unknown"
|
||||
if user_id_information is not None and isinstance(
|
||||
user_id_information, list
|
||||
):
|
||||
_user = user_id_information[0]
|
||||
user_role = _user.get("user_role", {}).get(
|
||||
"user_role", "unknown"
|
||||
)
|
||||
user_id = _user.get("user_id", "unknown")
|
||||
raise Exception(
|
||||
f"Only master key can be used to generate, delete, update info for new keys/users/teams. Route={route}"
|
||||
f"Only proxy admin can be used to generate, delete, update info for new keys/users/teams. Route={route}. Your role={user_role}. Your user_id={user_id}"
|
||||
)
|
||||
|
||||
# check if token is from litellm-ui, litellm ui makes keys to allow users to login with sso. These keys can only be used for LiteLLM UI functions
|
||||
@ -1207,6 +1217,67 @@ def cost_tracking():
|
||||
litellm.success_callback.append(_PROXY_track_cost_callback) # type: ignore
|
||||
|
||||
|
||||
async def _PROXY_failure_handler(
|
||||
kwargs, # kwargs to completion
|
||||
completion_response: litellm.ModelResponse, # response from completion
|
||||
start_time=None,
|
||||
end_time=None, # start/end time for completion
|
||||
):
|
||||
global prisma_client
|
||||
if prisma_client is not None:
|
||||
verbose_proxy_logger.debug(
|
||||
"inside _PROXY_failure_handler kwargs=", extra=kwargs
|
||||
)
|
||||
|
||||
_exception = kwargs.get("exception")
|
||||
_exception_type = _exception.__class__.__name__
|
||||
_model = kwargs.get("model", None)
|
||||
|
||||
_optional_params = kwargs.get("optional_params", {})
|
||||
_optional_params = copy.deepcopy(_optional_params)
|
||||
|
||||
for k, v in _optional_params.items():
|
||||
v = str(v)
|
||||
v = v[:100]
|
||||
|
||||
_status_code = "500"
|
||||
try:
|
||||
_status_code = str(_exception.status_code)
|
||||
except:
|
||||
# Don't let this fail logging the exception to the dB
|
||||
pass
|
||||
|
||||
_litellm_params = kwargs.get("litellm_params", {}) or {}
|
||||
_metadata = _litellm_params.get("metadata", {}) or {}
|
||||
_model_id = _metadata.get("model_info", {}).get("id", "")
|
||||
_model_group = _metadata.get("model_group", "")
|
||||
api_base = litellm.get_api_base(model=_model, optional_params=_litellm_params)
|
||||
_exception_string = str(_exception)[:500]
|
||||
|
||||
error_log = LiteLLM_ErrorLogs(
|
||||
request_id=str(uuid.uuid4()),
|
||||
model_group=_model_group,
|
||||
model_id=_model_id,
|
||||
request_kwargs=_optional_params,
|
||||
api_base=api_base,
|
||||
exception_type=_exception_type,
|
||||
status_code=_status_code,
|
||||
exception_string=_exception_string,
|
||||
startTime=kwargs.get("start_time"),
|
||||
endTime=kwargs.get("end_time"),
|
||||
)
|
||||
|
||||
# helper function to convert to dict on pydantic v2 & v1
|
||||
error_log_dict = _get_pydantic_json_dict(error_log)
|
||||
error_log_dict["request_kwargs"] = json.dumps(error_log_dict["request_kwargs"])
|
||||
|
||||
await prisma_client.db.litellm_errorlogs.create(
|
||||
data=error_log_dict # type: ignore
|
||||
)
|
||||
|
||||
pass
|
||||
|
||||
|
||||
async def _PROXY_track_cost_callback(
|
||||
kwargs, # kwargs to completion
|
||||
completion_response: litellm.ModelResponse, # response from completion
|
||||
@ -1292,6 +1363,15 @@ async def _PROXY_track_cost_callback(
|
||||
verbose_proxy_logger.debug("error in tracking cost callback - %s", e)
|
||||
|
||||
|
||||
def error_tracking():
|
||||
global prisma_client, custom_db_client
|
||||
if prisma_client is not None or custom_db_client is not None:
|
||||
if isinstance(litellm.failure_callback, list):
|
||||
verbose_proxy_logger.debug("setting litellm failure callback to track cost")
|
||||
if (_PROXY_failure_handler) not in litellm.failure_callback: # type: ignore
|
||||
litellm.failure_callback.append(_PROXY_failure_handler) # type: ignore
|
||||
|
||||
|
||||
def _set_spend_logs_payload(
|
||||
payload: dict, prisma_client: PrismaClient, spend_logs_url: Optional[str] = None
|
||||
):
|
||||
@ -3184,6 +3264,9 @@ async def startup_event():
|
||||
## COST TRACKING ##
|
||||
cost_tracking()
|
||||
|
||||
## Error Tracking ##
|
||||
error_tracking()
|
||||
|
||||
db_writer_client = HTTPHandler()
|
||||
|
||||
proxy_logging_obj._init_litellm_callbacks() # INITIALIZE LITELLM CALLBACKS ON SERVER STARTUP <- do this to catch any logging errors on startup, not when calls are being made
|
||||
|
||||
@ -1450,7 +1450,9 @@ class Router:
|
||||
raise original_exception
|
||||
### RETRY
|
||||
#### check if it should retry + back-off if required
|
||||
if "No models available" in str(e):
|
||||
if "No models available" in str(
|
||||
e
|
||||
) or RouterErrors.no_deployments_available.value in str(e):
|
||||
timeout = litellm._calculate_retry_after(
|
||||
remaining_retries=num_retries,
|
||||
max_retries=num_retries,
|
||||
@ -2779,7 +2781,10 @@ class Router:
|
||||
self.cache.get_cache(key=model_id, local_only=True) or 0
|
||||
)
|
||||
### get usage based cache ###
|
||||
if isinstance(model_group_cache, dict):
|
||||
if (
|
||||
isinstance(model_group_cache, dict)
|
||||
and self.routing_strategy != "usage-based-routing-v2"
|
||||
):
|
||||
model_group_cache[model_id] = model_group_cache.get(model_id, 0)
|
||||
|
||||
current_request = max(
|
||||
@ -2807,7 +2812,7 @@ class Router:
|
||||
|
||||
if _rate_limit_error == True: # allow generic fallback logic to take place
|
||||
raise ValueError(
|
||||
f"No deployments available for selected model, passed model={model}"
|
||||
f"{RouterErrors.no_deployments_available.value}, passed model={model}"
|
||||
)
|
||||
elif _context_window_error == True:
|
||||
raise litellm.ContextWindowExceededError(
|
||||
@ -2945,6 +2950,11 @@ class Router:
|
||||
model=model, healthy_deployments=healthy_deployments, messages=messages
|
||||
)
|
||||
|
||||
if len(healthy_deployments) == 0:
|
||||
raise ValueError(
|
||||
f"{RouterErrors.no_deployments_available.value}, passed model={model}"
|
||||
)
|
||||
|
||||
if (
|
||||
self.routing_strategy == "usage-based-routing-v2"
|
||||
and self.lowesttpm_logger_v2 is not None
|
||||
@ -3000,7 +3010,7 @@ class Router:
|
||||
f"get_available_deployment for model: {model}, No deployment available"
|
||||
)
|
||||
raise ValueError(
|
||||
f"No deployments available for selected model, passed model={model}"
|
||||
f"{RouterErrors.no_deployments_available.value}, passed model={model}"
|
||||
)
|
||||
verbose_router_logger.info(
|
||||
f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment)} for model: {model}"
|
||||
@ -3130,7 +3140,7 @@ class Router:
|
||||
f"get_available_deployment for model: {model}, No deployment available"
|
||||
)
|
||||
raise ValueError(
|
||||
f"No deployments available for selected model, passed model={model}"
|
||||
f"{RouterErrors.no_deployments_available.value}, passed model={model}"
|
||||
)
|
||||
verbose_router_logger.info(
|
||||
f"get_available_deployment for model: {model}, Selected deployment: {self.print_deployment(deployment)} for model: {model}"
|
||||
|
||||
@ -312,6 +312,10 @@ class LowestLatencyLoggingHandler(CustomLogger):
|
||||
except:
|
||||
input_tokens = 0
|
||||
|
||||
# randomly sample from all_deployments, incase all deployments have latency=0.0
|
||||
_items = all_deployments.items()
|
||||
all_deployments = random.sample(list(_items), len(_items))
|
||||
all_deployments = dict(all_deployments)
|
||||
for item, item_map in all_deployments.items():
|
||||
## get the item from model list
|
||||
_deployment = None
|
||||
|
||||
@ -206,7 +206,7 @@ class LowestTPMLoggingHandler(CustomLogger):
|
||||
if item_tpm + input_tokens > _deployment_tpm:
|
||||
continue
|
||||
elif (rpm_dict is not None and item in rpm_dict) and (
|
||||
rpm_dict[item] + 1 > _deployment_rpm
|
||||
rpm_dict[item] + 1 >= _deployment_rpm
|
||||
):
|
||||
continue
|
||||
elif item_tpm < lowest_tpm:
|
||||
|
||||
@ -333,7 +333,7 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
tpm_dict[tpm_key] = 0
|
||||
|
||||
all_deployments = tpm_dict
|
||||
deployment = None
|
||||
potential_deployments = [] # if multiple deployments have the same low value
|
||||
for item, item_tpm in all_deployments.items():
|
||||
## get the item from model list
|
||||
_deployment = None
|
||||
@ -343,6 +343,8 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
_deployment = m
|
||||
if _deployment is None:
|
||||
continue # skip to next one
|
||||
elif item_tpm is None:
|
||||
continue # skip if unhealthy deployment
|
||||
|
||||
_deployment_tpm = None
|
||||
if _deployment_tpm is None:
|
||||
@ -366,14 +368,20 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
if item_tpm + input_tokens > _deployment_tpm:
|
||||
continue
|
||||
elif (rpm_dict is not None and item in rpm_dict) and (
|
||||
rpm_dict[item] + 1 > _deployment_rpm
|
||||
rpm_dict[item] + 1 >= _deployment_rpm
|
||||
):
|
||||
continue
|
||||
elif item_tpm == lowest_tpm:
|
||||
potential_deployments.append(_deployment)
|
||||
elif item_tpm < lowest_tpm:
|
||||
lowest_tpm = item_tpm
|
||||
deployment = _deployment
|
||||
potential_deployments = [_deployment]
|
||||
print_verbose("returning picked lowest tpm/rpm deployment.")
|
||||
return deployment
|
||||
|
||||
if len(potential_deployments) > 0:
|
||||
return random.choice(potential_deployments)
|
||||
else:
|
||||
return None
|
||||
|
||||
async def async_get_available_deployments(
|
||||
self,
|
||||
@ -394,6 +402,7 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
|
||||
dt = get_utc_datetime()
|
||||
current_minute = dt.strftime("%H-%M")
|
||||
|
||||
tpm_keys = []
|
||||
rpm_keys = []
|
||||
for m in healthy_deployments:
|
||||
@ -416,7 +425,7 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
tpm_values = combined_tpm_rpm_values[: len(tpm_keys)]
|
||||
rpm_values = combined_tpm_rpm_values[len(tpm_keys) :]
|
||||
|
||||
return self._common_checks_available_deployment(
|
||||
deployment = self._common_checks_available_deployment(
|
||||
model_group=model_group,
|
||||
healthy_deployments=healthy_deployments,
|
||||
tpm_keys=tpm_keys,
|
||||
@ -427,6 +436,61 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
input=input,
|
||||
)
|
||||
|
||||
try:
|
||||
assert deployment is not None
|
||||
return deployment
|
||||
except Exception as e:
|
||||
### GET THE DICT OF TPM / RPM + LIMITS PER DEPLOYMENT ###
|
||||
deployment_dict = {}
|
||||
for index, _deployment in enumerate(healthy_deployments):
|
||||
if isinstance(_deployment, dict):
|
||||
id = _deployment.get("model_info", {}).get("id")
|
||||
### GET DEPLOYMENT TPM LIMIT ###
|
||||
_deployment_tpm = None
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = _deployment.get("tpm", None)
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = _deployment.get("litellm_params", {}).get(
|
||||
"tpm", None
|
||||
)
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = _deployment.get("model_info", {}).get(
|
||||
"tpm", None
|
||||
)
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = float("inf")
|
||||
|
||||
### GET CURRENT TPM ###
|
||||
current_tpm = tpm_values[index]
|
||||
|
||||
### GET DEPLOYMENT TPM LIMIT ###
|
||||
_deployment_rpm = None
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = _deployment.get("rpm", None)
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = _deployment.get("litellm_params", {}).get(
|
||||
"rpm", None
|
||||
)
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = _deployment.get("model_info", {}).get(
|
||||
"rpm", None
|
||||
)
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = float("inf")
|
||||
|
||||
### GET CURRENT RPM ###
|
||||
current_rpm = rpm_values[index]
|
||||
|
||||
deployment_dict[id] = {
|
||||
"current_tpm": current_tpm,
|
||||
"tpm_limit": _deployment_tpm,
|
||||
"current_rpm": current_rpm,
|
||||
"rpm_limit": _deployment_rpm,
|
||||
}
|
||||
raise ValueError(
|
||||
f"{RouterErrors.no_deployments_available.value}. Passed model={model_group}. Deployments={deployment_dict}"
|
||||
)
|
||||
|
||||
def get_available_deployments(
|
||||
self,
|
||||
model_group: str,
|
||||
@ -464,7 +528,7 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
keys=rpm_keys
|
||||
) # [1, 2, None, ..]
|
||||
|
||||
return self._common_checks_available_deployment(
|
||||
deployment = self._common_checks_available_deployment(
|
||||
model_group=model_group,
|
||||
healthy_deployments=healthy_deployments,
|
||||
tpm_keys=tpm_keys,
|
||||
@ -474,3 +538,58 @@ class LowestTPMLoggingHandler_v2(CustomLogger):
|
||||
messages=messages,
|
||||
input=input,
|
||||
)
|
||||
|
||||
try:
|
||||
assert deployment is not None
|
||||
return deployment
|
||||
except Exception as e:
|
||||
### GET THE DICT OF TPM / RPM + LIMITS PER DEPLOYMENT ###
|
||||
deployment_dict = {}
|
||||
for index, _deployment in enumerate(healthy_deployments):
|
||||
if isinstance(_deployment, dict):
|
||||
id = _deployment.get("model_info", {}).get("id")
|
||||
### GET DEPLOYMENT TPM LIMIT ###
|
||||
_deployment_tpm = None
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = _deployment.get("tpm", None)
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = _deployment.get("litellm_params", {}).get(
|
||||
"tpm", None
|
||||
)
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = _deployment.get("model_info", {}).get(
|
||||
"tpm", None
|
||||
)
|
||||
if _deployment_tpm is None:
|
||||
_deployment_tpm = float("inf")
|
||||
|
||||
### GET CURRENT TPM ###
|
||||
current_tpm = tpm_values[index]
|
||||
|
||||
### GET DEPLOYMENT TPM LIMIT ###
|
||||
_deployment_rpm = None
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = _deployment.get("rpm", None)
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = _deployment.get("litellm_params", {}).get(
|
||||
"rpm", None
|
||||
)
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = _deployment.get("model_info", {}).get(
|
||||
"rpm", None
|
||||
)
|
||||
if _deployment_rpm is None:
|
||||
_deployment_rpm = float("inf")
|
||||
|
||||
### GET CURRENT RPM ###
|
||||
current_rpm = rpm_values[index]
|
||||
|
||||
deployment_dict[id] = {
|
||||
"current_tpm": current_tpm,
|
||||
"tpm_limit": _deployment_tpm,
|
||||
"current_rpm": current_rpm,
|
||||
"rpm_limit": _deployment_rpm,
|
||||
}
|
||||
raise ValueError(
|
||||
f"{RouterErrors.no_deployments_available.value}. Passed model={model_group}. Deployments={deployment_dict}"
|
||||
)
|
||||
|
||||
@ -231,6 +231,76 @@ def test_completion_claude_3_function_call():
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
def test_completion_cohere_command_r_plus_function_call():
|
||||
litellm.set_verbose = True
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_current_weather",
|
||||
"description": "Get the current weather in a given location",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "The city and state, e.g. San Francisco, CA",
|
||||
},
|
||||
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
||||
},
|
||||
"required": ["location"],
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What's the weather like in Boston today in Fahrenheit?",
|
||||
}
|
||||
]
|
||||
try:
|
||||
# test without max tokens
|
||||
response = completion(
|
||||
model="command-r-plus",
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
)
|
||||
# Add any assertions, here to check response args
|
||||
print(response)
|
||||
assert isinstance(response.choices[0].message.tool_calls[0].function.name, str)
|
||||
assert isinstance(
|
||||
response.choices[0].message.tool_calls[0].function.arguments, str
|
||||
)
|
||||
|
||||
messages.append(
|
||||
response.choices[0].message.model_dump()
|
||||
) # Add assistant tool invokes
|
||||
tool_result = (
|
||||
'{"location": "Boston", "temperature": "72", "unit": "fahrenheit"}'
|
||||
)
|
||||
# Add user submitted tool results in the OpenAI format
|
||||
messages.append(
|
||||
{
|
||||
"tool_call_id": response.choices[0].message.tool_calls[0].id,
|
||||
"role": "tool",
|
||||
"name": response.choices[0].message.tool_calls[0].function.name,
|
||||
"content": tool_result,
|
||||
}
|
||||
)
|
||||
# In the second response, Cohere should deduce answer from tool results
|
||||
second_response = completion(
|
||||
model="command-r-plus",
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
tool_choice="auto",
|
||||
)
|
||||
print(second_response)
|
||||
except Exception as e:
|
||||
pytest.fail(f"Error occurred: {e}")
|
||||
|
||||
|
||||
def test_parse_xml_params():
|
||||
from litellm.llms.prompt_templates.factory import parse_xml_params
|
||||
|
||||
|
||||
@ -328,3 +328,56 @@ def test_dalle_3_azure_cost_tracking():
|
||||
completion_response=response, call_type="image_generation"
|
||||
)
|
||||
assert cost > 0
|
||||
|
||||
|
||||
def test_replicate_llama3_cost_tracking():
|
||||
litellm.set_verbose = True
|
||||
model = "replicate/meta/meta-llama-3-8b-instruct"
|
||||
litellm.register_model(
|
||||
{
|
||||
"replicate/meta/meta-llama-3-8b-instruct": {
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
}
|
||||
}
|
||||
)
|
||||
response = litellm.ModelResponse(
|
||||
id="chatcmpl-cad7282f-7f68-41e7-a5ab-9eb33ae301dc",
|
||||
choices=[
|
||||
litellm.utils.Choices(
|
||||
finish_reason="stop",
|
||||
index=0,
|
||||
message=litellm.utils.Message(
|
||||
content="I'm doing well, thanks for asking! I'm here to help you with any questions or tasks you may have. How can I assist you today?",
|
||||
role="assistant",
|
||||
),
|
||||
)
|
||||
],
|
||||
created=1714401369,
|
||||
model="replicate/meta/meta-llama-3-8b-instruct",
|
||||
object="chat.completion",
|
||||
system_fingerprint=None,
|
||||
usage=litellm.utils.Usage(
|
||||
prompt_tokens=48, completion_tokens=31, total_tokens=79
|
||||
),
|
||||
)
|
||||
cost = litellm.completion_cost(
|
||||
completion_response=response,
|
||||
messages=[{"role": "user", "content": "Hey, how's it going?"}],
|
||||
)
|
||||
|
||||
print(f"cost: {cost}")
|
||||
cost = round(cost, 5)
|
||||
expected_cost = round(
|
||||
litellm.model_cost["replicate/meta/meta-llama-3-8b-instruct"][
|
||||
"input_cost_per_token"
|
||||
]
|
||||
* 48
|
||||
+ litellm.model_cost["replicate/meta/meta-llama-3-8b-instruct"][
|
||||
"output_cost_per_token"
|
||||
]
|
||||
* 31,
|
||||
5,
|
||||
)
|
||||
assert cost == expected_cost
|
||||
|
||||
@ -26,6 +26,9 @@ class DBModel(BaseModel):
|
||||
model_info: dict
|
||||
litellm_params: dict
|
||||
|
||||
class Config:
|
||||
protected_namespaces = ()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_deployment():
|
||||
|
||||
@ -136,8 +136,8 @@ def test_image_generation_bedrock():
|
||||
litellm.set_verbose = True
|
||||
response = litellm.image_generation(
|
||||
prompt="A cute baby sea otter",
|
||||
model="bedrock/stability.stable-diffusion-xl-v0",
|
||||
aws_region_name="us-east-1",
|
||||
model="bedrock/stability.stable-diffusion-xl-v1",
|
||||
aws_region_name="us-west-2",
|
||||
)
|
||||
print(f"response: {response}")
|
||||
except litellm.RateLimitError as e:
|
||||
@ -156,8 +156,8 @@ async def test_aimage_generation_bedrock_with_optional_params():
|
||||
try:
|
||||
response = await litellm.aimage_generation(
|
||||
prompt="A cute baby sea otter",
|
||||
model="bedrock/stability.stable-diffusion-xl-v0",
|
||||
size="128x128",
|
||||
model="bedrock/stability.stable-diffusion-xl-v1",
|
||||
size="256x256",
|
||||
)
|
||||
print(f"response: {response}")
|
||||
except litellm.RateLimitError as e:
|
||||
|
||||
@ -555,3 +555,79 @@ async def test_lowest_latency_routing_with_timeouts():
|
||||
|
||||
# ALL the Requests should have been routed to the fast-endpoint
|
||||
assert deployments["fast-endpoint"] == 10
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_lowest_latency_routing_first_pick():
|
||||
"""
|
||||
PROD Test:
|
||||
- When all deployments are latency=0, it should randomly pick a deployment
|
||||
- IT SHOULD NEVER PICK THE Very First deployment everytime all deployment latencies are 0
|
||||
- This ensures that after the ttl window resets it randomly picks a deployment
|
||||
"""
|
||||
import litellm
|
||||
|
||||
litellm.set_verbose = True
|
||||
|
||||
router = Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "azure-model",
|
||||
"litellm_params": {
|
||||
"model": "openai/fast-endpoint",
|
||||
"api_base": "https://exampleopenaiendpoint-production.up.railway.app/",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {"id": "fast-endpoint"},
|
||||
},
|
||||
{
|
||||
"model_name": "azure-model",
|
||||
"litellm_params": {
|
||||
"model": "openai/fast-endpoint-2",
|
||||
"api_base": "https://exampleopenaiendpoint-production.up.railway.app/",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {"id": "fast-endpoint-2"},
|
||||
},
|
||||
{
|
||||
"model_name": "azure-model",
|
||||
"litellm_params": {
|
||||
"model": "openai/fast-endpoint-2",
|
||||
"api_base": "https://exampleopenaiendpoint-production.up.railway.app/",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {"id": "fast-endpoint-3"},
|
||||
},
|
||||
{
|
||||
"model_name": "azure-model",
|
||||
"litellm_params": {
|
||||
"model": "openai/fast-endpoint-2",
|
||||
"api_base": "https://exampleopenaiendpoint-production.up.railway.app/",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
"model_info": {"id": "fast-endpoint-4"},
|
||||
},
|
||||
],
|
||||
routing_strategy="latency-based-routing",
|
||||
routing_strategy_args={"ttl": 0.0000000001},
|
||||
set_verbose=True,
|
||||
debug_level="DEBUG",
|
||||
) # type: ignore
|
||||
|
||||
deployments = {}
|
||||
for _ in range(5):
|
||||
response = await router.acompletion(
|
||||
model="azure-model", messages=[{"role": "user", "content": "hello"}]
|
||||
)
|
||||
print(response)
|
||||
_picked_model_id = response._hidden_params["model_id"]
|
||||
if _picked_model_id not in deployments:
|
||||
deployments[_picked_model_id] = 1
|
||||
else:
|
||||
deployments[_picked_model_id] += 1
|
||||
await asyncio.sleep(0.000000000005)
|
||||
|
||||
print("deployments", deployments)
|
||||
|
||||
# assert that len(deployments) >1
|
||||
assert len(deployments) > 1
|
||||
|
||||
10
litellm/tests/test_pydantic_namespaces.py
Normal file
10
litellm/tests/test_pydantic_namespaces.py
Normal file
@ -0,0 +1,10 @@
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
def test_namespace_conflict_warning():
|
||||
with warnings.catch_warnings(record=True) as recorded_warnings:
|
||||
warnings.simplefilter("always") # Capture all warnings
|
||||
import litellm
|
||||
|
||||
# Check that no warning with the specific message was raised
|
||||
assert not any("conflict with protected namespace" in str(w.message) for w in recorded_warnings), "Test failed: 'conflict with protected namespace' warning was encountered!"
|
||||
@ -282,6 +282,64 @@ def test_router_skip_rate_limited_deployments():
|
||||
print(f"An exception occurred! {str(e)}")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sync_mode", [True, False])
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_potential_deployments(sync_mode):
|
||||
"""
|
||||
If multiple deployments have the same tpm value
|
||||
|
||||
call 5 times, test if deployments are shuffled.
|
||||
|
||||
-> prevents single deployment from being overloaded in high-concurrency scenario
|
||||
"""
|
||||
|
||||
model_list = [
|
||||
{
|
||||
"model_name": "azure-model",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-turbo",
|
||||
"api_key": "os.environ/AZURE_FRANCE_API_KEY",
|
||||
"api_base": "https://openai-france-1234.openai.azure.com",
|
||||
"tpm": 1440,
|
||||
},
|
||||
},
|
||||
{
|
||||
"model_name": "azure-model",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-turbo-2",
|
||||
"api_key": "os.environ/AZURE_FRANCE_API_KEY",
|
||||
"api_base": "https://openai-france-1234.openai.azure.com",
|
||||
"tpm": 1440,
|
||||
},
|
||||
},
|
||||
]
|
||||
router = Router(
|
||||
model_list=model_list,
|
||||
routing_strategy="usage-based-routing-v2",
|
||||
set_verbose=False,
|
||||
num_retries=3,
|
||||
) # type: ignore
|
||||
|
||||
model_ids = set()
|
||||
for _ in range(1000):
|
||||
if sync_mode:
|
||||
deployment = router.get_available_deployment(
|
||||
model="azure-model",
|
||||
messages=[{"role": "user", "content": "Hey, how's it going?"}],
|
||||
)
|
||||
else:
|
||||
deployment = await router.async_get_available_deployment(
|
||||
model="azure-model",
|
||||
messages=[{"role": "user", "content": "Hey, how's it going?"}],
|
||||
)
|
||||
|
||||
## get id ##
|
||||
id = deployment.get("model_info", {}).get("id")
|
||||
model_ids.add(id)
|
||||
|
||||
assert len(model_ids) == 2
|
||||
|
||||
|
||||
def test_single_deployment_tpm_zero():
|
||||
import litellm
|
||||
import os
|
||||
|
||||
@ -202,6 +202,9 @@ class updateDeployment(BaseModel):
|
||||
litellm_params: Optional[updateLiteLLMParams] = None
|
||||
model_info: Optional[ModelInfo] = None
|
||||
|
||||
class Config:
|
||||
protected_namespaces = ()
|
||||
|
||||
|
||||
class Deployment(BaseModel):
|
||||
model_name: str
|
||||
@ -260,3 +263,4 @@ class RouterErrors(enum.Enum):
|
||||
"""
|
||||
|
||||
user_defined_ratelimit_error = "Deployment over user-defined ratelimit."
|
||||
no_deployments_available = "No deployments available for selected model"
|
||||
|
||||
@ -1202,7 +1202,14 @@ class Logging:
|
||||
if verbose_logger.level == 0:
|
||||
# this means verbose logger was not switched on - user is in litellm.set_verbose=True
|
||||
print_verbose(f"\033[92m{curl_command}\033[0m\n")
|
||||
verbose_logger.info(f"\033[92m{curl_command}\033[0m\n")
|
||||
|
||||
if litellm.json_logs:
|
||||
verbose_logger.info(
|
||||
"POST Request Sent from LiteLLM",
|
||||
extra={"api_base": {api_base}, **masked_headers},
|
||||
)
|
||||
else:
|
||||
verbose_logger.info(f"\033[92m{curl_command}\033[0m\n")
|
||||
if self.logger_fn and callable(self.logger_fn):
|
||||
try:
|
||||
self.logger_fn(
|
||||
@ -3641,12 +3648,12 @@ def get_replicate_completion_pricing(completion_response=None, total_time=0.0):
|
||||
a100_80gb_price_per_second_public = (
|
||||
0.001400 # assume all calls sent to A100 80GB for now
|
||||
)
|
||||
if total_time == 0.0:
|
||||
if total_time == 0.0: # total time is in ms
|
||||
start_time = completion_response["created"]
|
||||
end_time = completion_response["ended"]
|
||||
total_time = end_time - start_time
|
||||
|
||||
return a100_80gb_price_per_second_public * total_time
|
||||
return a100_80gb_price_per_second_public * total_time / 1000
|
||||
|
||||
|
||||
def _select_tokenizer(model: str):
|
||||
@ -3668,7 +3675,7 @@ def _select_tokenizer(model: str):
|
||||
tokenizer = Tokenizer.from_str(json_str)
|
||||
return {"type": "huggingface_tokenizer", "tokenizer": tokenizer}
|
||||
# llama2
|
||||
elif "llama-2" in model.lower():
|
||||
elif "llama-2" in model.lower() or "replicate" in model.lower():
|
||||
tokenizer = Tokenizer.from_pretrained("hf-internal-testing/llama-tokenizer")
|
||||
return {"type": "huggingface_tokenizer", "tokenizer": tokenizer}
|
||||
# default - tiktoken
|
||||
@ -4269,7 +4276,10 @@ def completion_cost(
|
||||
model = get_model_params_and_category(model)
|
||||
# replicate llms are calculate based on time for request running
|
||||
# see https://replicate.com/pricing
|
||||
elif model in litellm.replicate_models or "replicate" in model:
|
||||
elif (
|
||||
model in litellm.replicate_models or "replicate" in model
|
||||
) and model not in litellm.model_cost:
|
||||
# for unmapped replicate model, default to replicate's time tracking logic
|
||||
return get_replicate_completion_pricing(completion_response, total_time)
|
||||
|
||||
(
|
||||
@ -10154,21 +10164,6 @@ class CustomStreamWrapper:
|
||||
elif self.custom_llm_provider == "watsonx":
|
||||
response_obj = self.handle_watsonx_stream(chunk)
|
||||
completion_obj["content"] = response_obj["text"]
|
||||
print_verbose(f"completion obj content: {completion_obj['content']}")
|
||||
if response_obj.get("prompt_tokens") is not None:
|
||||
prompt_token_count = getattr(
|
||||
model_response.usage, "prompt_tokens", 0
|
||||
)
|
||||
model_response.usage.prompt_tokens = (
|
||||
prompt_token_count + response_obj["prompt_tokens"]
|
||||
)
|
||||
if response_obj.get("completion_tokens") is not None:
|
||||
model_response.usage.completion_tokens = response_obj[
|
||||
"completion_tokens"
|
||||
]
|
||||
model_response.usage.total_tokens = getattr(
|
||||
model_response.usage, "prompt_tokens", 0
|
||||
) + getattr(model_response.usage, "completion_tokens", 0)
|
||||
if response_obj["is_finished"]:
|
||||
self.received_finish_reason = response_obj["finish_reason"]
|
||||
elif self.custom_llm_provider == "text-completion-openai":
|
||||
|
||||
@ -1418,6 +1418,123 @@
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-13b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.0000001,
|
||||
"output_cost_per_token": 0.0000005,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-13b-chat": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.0000001,
|
||||
"output_cost_per_token": 0.0000005,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-70b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-70b-chat": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-7b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-2-7b-chat": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-70b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-70b-instruct": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000065,
|
||||
"output_cost_per_token": 0.00000275,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-8b": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/meta/llama-3-8b-instruct": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/mistralai/mistral-7b-v0.1": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/mistralai/mistral-7b-instruct-v0.2": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.00000005,
|
||||
"output_cost_per_token": 0.00000025,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"replicate/mistralai/mixtral-8x7b-instruct-v0.1": {
|
||||
"max_tokens": 4096,
|
||||
"max_input_tokens": 4096,
|
||||
"max_output_tokens": 4096,
|
||||
"input_cost_per_token": 0.0000003,
|
||||
"output_cost_per_token": 0.000001,
|
||||
"litellm_provider": "replicate",
|
||||
"mode": "chat"
|
||||
},
|
||||
"openrouter/openai/gpt-3.5-turbo": {
|
||||
"max_tokens": 4095,
|
||||
"input_cost_per_token": 0.0000015,
|
||||
@ -2379,6 +2496,24 @@
|
||||
"litellm_provider": "bedrock",
|
||||
"mode": "chat"
|
||||
},
|
||||
"meta.llama3-8b-instruct-v1:0": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
"max_output_tokens": 8192,
|
||||
"input_cost_per_token": 0.0000004,
|
||||
"output_cost_per_token": 0.0000006,
|
||||
"litellm_provider": "bedrock",
|
||||
"mode": "chat"
|
||||
},
|
||||
"meta.llama3-70b-instruct-v1:0": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
"max_output_tokens": 8192,
|
||||
"input_cost_per_token": 0.00000265,
|
||||
"output_cost_per_token": 0.0000035,
|
||||
"litellm_provider": "bedrock",
|
||||
"mode": "chat"
|
||||
},
|
||||
"512-x-512/50-steps/stability.stable-diffusion-xl-v0": {
|
||||
"max_tokens": 77,
|
||||
"max_input_tokens": 77,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "litellm"
|
||||
version = "1.35.31"
|
||||
version = "1.35.33"
|
||||
description = "Library to easily interface with LLM API providers"
|
||||
authors = ["BerriAI"]
|
||||
license = "MIT"
|
||||
@ -80,7 +80,7 @@ requires = ["poetry-core", "wheel"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.commitizen]
|
||||
version = "1.35.31"
|
||||
version = "1.35.33"
|
||||
version_files = [
|
||||
"pyproject.toml:^version"
|
||||
]
|
||||
|
||||
@ -183,6 +183,20 @@ model LiteLLM_SpendLogs {
|
||||
end_user String?
|
||||
}
|
||||
|
||||
// View spend, model, api_key per request
|
||||
model LiteLLM_ErrorLogs {
|
||||
request_id String @id @default(uuid())
|
||||
startTime DateTime // Assuming start_time is a DateTime field
|
||||
endTime DateTime // Assuming end_time is a DateTime field
|
||||
api_base String @default("")
|
||||
model_group String @default("") // public model_name / model_group
|
||||
model_id String @default("") // ID of model in ProxyModelTable
|
||||
request_kwargs Json @default("{}")
|
||||
exception_type String @default("")
|
||||
exception_string String @default("")
|
||||
status_code String @default("")
|
||||
}
|
||||
|
||||
// Beta - allow team members to request access to a model
|
||||
model LiteLLM_UserNotifications {
|
||||
request_id String @id
|
||||
|
||||
Loading…
Reference in New Issue
Block a user