diff --git a/.circleci/config.yml b/.circleci/config.yml
index 47339bfb8..dfaf479f2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,6 +8,16 @@ jobs:
steps:
- checkout
+ - run:
+ name: Check if litellm dir was updated or if pyproject.toml was modified
+ command: |
+ if [ -n "$(git diff --name-only $CIRCLE_SHA1^..$CIRCLE_SHA1 | grep -E 'pyproject\.toml|litellm/')" ]; then
+ echo "litellm updated"
+ else
+ echo "No changes to litellm or pyproject.toml. Skipping tests."
+ circleci step halt
+ fi
+
- run:
name: Install Dependencies
command: |
@@ -15,8 +25,20 @@ jobs:
python -m pip install -r .circleci/requirements.txt
pip install infisical
pip install pytest
+ pip install mypy
pip install openai[datalib]
pip install -Uq chromadb==0.3.29
+
+ - run:
+ name: Linting Testing
+ command: |
+ cd litellm
+ python -m pip install types-requests types-setuptools
+ if ! python -m mypy . --ignore-missing-imports; then
+ echo "mypy detected errors"
+ exit 1
+ fi
+ cd ..
# Run pytest and generate JUnit XML report
@@ -77,7 +99,3 @@ workflows:
- publish_to_pypi:
requires:
- local_testing
- filters:
- branches:
- only:
- - main
diff --git a/README.md b/README.md
index 57d23215d..40aa4fb2e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# *🚅 litellm*
[](https://pypi.org/project/litellm/)
-[](https://pypi.org/project/litellm/0.1.1/)
+[](https://pypi.org/project/litellm/0.1.1/)
[](https://dl.circleci.com/status-badge/redirect/gh/BerriAI/litellm/tree/main)

[](https://github.com/BerriAI/litellm)
@@ -35,13 +35,13 @@ messages = [{ "content": "Hello, how are you?","role": "user"}]
response = completion(model="gpt-3.5-turbo", messages=messages)
# cohere call
-response = completion("command-nightly", messages)
+response = completion(model="command-nightly", messages)
```
Code Sample: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing)
Stable version
```
-pip install litellm==0.1.345
+pip install litellm==0.1.424
```
## Streaming Queries
diff --git a/cookbook/Evaluating_LLMs.ipynb b/cookbook/Evaluating_LLMs.ipynb
new file mode 100644
index 000000000..ed1892506
--- /dev/null
+++ b/cookbook/Evaluating_LLMs.ipynb
@@ -0,0 +1,581 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Ys9n20Es2IzT"
+ },
+ "source": [
+ "# Evaluate Multiple LLM Providers with LiteLLM\n",
+ "\n",
+ "\n",
+ "\n",
+ "* Quality Testing\n",
+ "* Load Testing\n",
+ "* Duration Testing\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ZXOXl23PIIP6"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install litellm==0.1.404 python-dotenv"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "id": "LINuBzXDItq2"
+ },
+ "outputs": [],
+ "source": [
+ "import litellm\n",
+ "from litellm import load_test_model, testing_batch_completion\n",
+ "import time"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "EkxMhsWdJdu4"
+ },
+ "outputs": [],
+ "source": [
+ "import os \n",
+ "os.environ[\"OPENAI_API_KEY\"] = \"...\"\n",
+ "os.environ[\"ANTHROPIC_API_KEY\"] = \"...\"\n",
+ "os.environ[\"REPLICATE_API_KEY\"] = \"...\""
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "mv5XdnqeW5I_"
+ },
+ "source": [
+ "# Quality Test endpoint\n",
+ "\n",
+ "## Test the same prompt across multiple LLM providers\n",
+ "\n",
+ "In this example, let's ask some questions about Paul Graham"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "id": "XpzrR5m4W_Us"
+ },
+ "outputs": [],
+ "source": [
+ "models = [\"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\", \"gpt-4\", \"claude-instant-1\", {\"model\": \"replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781\", \"custom_llm_provider\": \"replicate\"}]\n",
+ "context = \"\"\"Paul Graham (/ɡræm/; born 1964)[3] is an English computer scientist, essayist, entrepreneur, venture capitalist, and author. He is best known for his work on the programming language Lisp, his former startup Viaweb (later renamed Yahoo! Store), cofounding the influential startup accelerator and seed capital firm Y Combinator, his essays, and Hacker News. He is the author of several computer programming books, including: On Lisp,[4] ANSI Common Lisp,[5] and Hackers & Painters.[6] Technology journalist Steven Levy has described Graham as a \"hacker philosopher\".[7] Graham was born in England, where he and his family maintain permanent residence. However he is also a citizen of the United States, where he was educated, lived, and worked until 2016.\"\"\"\n",
+ "prompts = [\"Who is Paul Graham?\", \"What is Paul Graham known for?\" , \"Is paul graham a writer?\" , \"Where does Paul Graham live?\", \"What has Paul Graham done?\"]\n",
+ "messages = [[{\"role\": \"user\", \"content\": context + \"\\n\" + prompt}] for prompt in prompts] # pass in a list of messages we want to test\n",
+ "result = testing_batch_completion(models=models, messages=messages)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9nzeLySnvIIW"
+ },
+ "source": [
+ "## Visualize the data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 403
+ },
+ "id": "X-2n7hdAuVAY",
+ "outputId": "69cc0de1-68e3-4c12-a8ea-314880010d94"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " Model Name | \n",
+ " claude-instant-1 | \n",
+ " gpt-3.5-turbo-0613 | \n",
+ " gpt-3.5-turbo-16k-0613 | \n",
+ " gpt-4-0613 | \n",
+ " replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781 | \n",
+ "
\n",
+ " \n",
+ " Prompt | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " \\nIs paul graham a writer? | \n",
+ " Yes, Paul Graham is considered a writer in ad... | \n",
+ " Yes, Paul Graham is a writer. He has written s... | \n",
+ " Yes, Paul Graham is a writer. He has authored ... | \n",
+ " Yes, Paul Graham is a writer. He is an essayis... | \n",
+ " Yes, Paul Graham is an author. According to t... | \n",
+ "
\n",
+ " \n",
+ " \\nWhat has Paul Graham done? | \n",
+ " Paul Graham has made significant contribution... | \n",
+ " Paul Graham has achieved several notable accom... | \n",
+ " Paul Graham has made significant contributions... | \n",
+ " Paul Graham is known for his work on the progr... | \n",
+ " Paul Graham has had a diverse career in compu... | \n",
+ "
\n",
+ " \n",
+ " \\nWhat is Paul Graham known for? | \n",
+ " Paul Graham is known for several things:\\n\\n-... | \n",
+ " Paul Graham is known for his work on the progr... | \n",
+ " Paul Graham is known for his work on the progr... | \n",
+ " Paul Graham is known for his work on the progr... | \n",
+ " Paul Graham is known for many things, includi... | \n",
+ "
\n",
+ " \n",
+ " \\nWhere does Paul Graham live? | \n",
+ " Based on the information provided:\\n\\n- Paul ... | \n",
+ " According to the given information, Paul Graha... | \n",
+ " Paul Graham currently lives in England, where ... | \n",
+ " The text does not provide a current place of r... | \n",
+ " Based on the information provided, Paul Graha... | \n",
+ "
\n",
+ " \n",
+ " \\nWho is Paul Graham? | \n",
+ " Paul Graham is an influential computer scient... | \n",
+ " Paul Graham is an English computer scientist, ... | \n",
+ " Paul Graham is an English computer scientist, ... | \n",
+ " Paul Graham is an English computer scientist, ... | \n",
+ " Paul Graham is an English computer scientist,... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "\n",
+ "\n",
+ "\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ "
\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "Model Name claude-instant-1 \\\n",
+ "Prompt \n",
+ "\\nIs paul graham a writer? Yes, Paul Graham is considered a writer in ad... \n",
+ "\\nWhat has Paul Graham done? Paul Graham has made significant contribution... \n",
+ "\\nWhat is Paul Graham known for? Paul Graham is known for several things:\\n\\n-... \n",
+ "\\nWhere does Paul Graham live? Based on the information provided:\\n\\n- Paul ... \n",
+ "\\nWho is Paul Graham? Paul Graham is an influential computer scient... \n",
+ "\n",
+ "Model Name gpt-3.5-turbo-0613 \\\n",
+ "Prompt \n",
+ "\\nIs paul graham a writer? Yes, Paul Graham is a writer. He has written s... \n",
+ "\\nWhat has Paul Graham done? Paul Graham has achieved several notable accom... \n",
+ "\\nWhat is Paul Graham known for? Paul Graham is known for his work on the progr... \n",
+ "\\nWhere does Paul Graham live? According to the given information, Paul Graha... \n",
+ "\\nWho is Paul Graham? Paul Graham is an English computer scientist, ... \n",
+ "\n",
+ "Model Name gpt-3.5-turbo-16k-0613 \\\n",
+ "Prompt \n",
+ "\\nIs paul graham a writer? Yes, Paul Graham is a writer. He has authored ... \n",
+ "\\nWhat has Paul Graham done? Paul Graham has made significant contributions... \n",
+ "\\nWhat is Paul Graham known for? Paul Graham is known for his work on the progr... \n",
+ "\\nWhere does Paul Graham live? Paul Graham currently lives in England, where ... \n",
+ "\\nWho is Paul Graham? Paul Graham is an English computer scientist, ... \n",
+ "\n",
+ "Model Name gpt-4-0613 \\\n",
+ "Prompt \n",
+ "\\nIs paul graham a writer? Yes, Paul Graham is a writer. He is an essayis... \n",
+ "\\nWhat has Paul Graham done? Paul Graham is known for his work on the progr... \n",
+ "\\nWhat is Paul Graham known for? Paul Graham is known for his work on the progr... \n",
+ "\\nWhere does Paul Graham live? The text does not provide a current place of r... \n",
+ "\\nWho is Paul Graham? Paul Graham is an English computer scientist, ... \n",
+ "\n",
+ "Model Name replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781 \n",
+ "Prompt \n",
+ "\\nIs paul graham a writer? Yes, Paul Graham is an author. According to t... \n",
+ "\\nWhat has Paul Graham done? Paul Graham has had a diverse career in compu... \n",
+ "\\nWhat is Paul Graham known for? Paul Graham is known for many things, includi... \n",
+ "\\nWhere does Paul Graham live? Based on the information provided, Paul Graha... \n",
+ "\\nWho is Paul Graham? Paul Graham is an English computer scientist,... "
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "# Create an empty list to store the row data\n",
+ "table_data = []\n",
+ "\n",
+ "# Iterate through the list and extract the required data\n",
+ "for item in result:\n",
+ " prompt = item['prompt'][0]['content'].replace(context, \"\") # clean the prompt for easy comparison\n",
+ " model = item['response']['model']\n",
+ " response = item['response']['choices'][0]['message']['content']\n",
+ " table_data.append([prompt, model, response])\n",
+ "\n",
+ "# Create a DataFrame from the table data\n",
+ "df = pd.DataFrame(table_data, columns=['Prompt', 'Model Name', 'Response'])\n",
+ "\n",
+ "# Pivot the DataFrame to get the desired table format\n",
+ "table = df.pivot(index='Prompt', columns='Model Name', values='Response')\n",
+ "table"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "zOxUM40PINDC"
+ },
+ "source": [
+ "# Load Test endpoint\n",
+ "\n",
+ "Run 100+ simultaneous queries across multiple providers to see when they fail + impact on latency"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ZkQf_wbcIRQ9"
+ },
+ "outputs": [],
+ "source": [
+ "models=[\"gpt-3.5-turbo\", \"replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781\", \"claude-instant-1\"]\n",
+ "context = \"\"\"Paul Graham (/ɡræm/; born 1964)[3] is an English computer scientist, essayist, entrepreneur, venture capitalist, and author. He is best known for his work on the programming language Lisp, his former startup Viaweb (later renamed Yahoo! Store), cofounding the influential startup accelerator and seed capital firm Y Combinator, his essays, and Hacker News. He is the author of several computer programming books, including: On Lisp,[4] ANSI Common Lisp,[5] and Hackers & Painters.[6] Technology journalist Steven Levy has described Graham as a \"hacker philosopher\".[7] Graham was born in England, where he and his family maintain permanent residence. However he is also a citizen of the United States, where he was educated, lived, and worked until 2016.\"\"\"\n",
+ "prompt = \"Where does Paul Graham live?\"\n",
+ "final_prompt = context + prompt\n",
+ "result = load_test_model(models=models, prompt=final_prompt, num_calls=5)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "8vSNBFC06aXY"
+ },
+ "source": [
+ "## Visualize the data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 552
+ },
+ "id": "SZfiKjLV3-n8",
+ "outputId": "00f7f589-b3da-43ed-e982-f9420f074b8d"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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WwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWwwqREREJFsMKkRERCRbDCpEREQkWz8UVB4+fIhx48ahXbt2CA8PBwAcOHAAt2/f1mhxRERElLmpHVSOHz+O4sWL4/z58wgICEBsbCwA4Pr165g4caLGCyQiIqLMS+2gMmrUKEybNg1BQUEwMDCQ2mvWrIlz585ptDgiIiLK3NQOKjdv3kSLFi3StNva2uLt27caKYqIiIgI+IGgYmFhgZcvX6Zpv3r1KnLmzKmRooiIiIiAHwgqbdu2xciRI/Hq1SsoFAoolUqcPn0aw4YNQ+fOndOjRiIiIsqk1A4qM2bMQKFCheDo6IjY2FgUKVIEVatWRcWKFTFu3Lj0qJGIiIgyKT11b2BgYICVK1di/PjxuHXrFmJjY1GqVCnkz58/PeojIiKiTEztoJIqV65cyJUrlyZrISIiIlKhdlARQmD79u0IDg5GeHg4lEqlyvKAgACNFUdERESZm9pBZdCgQVixYgVq1KiB7NmzQ6FQpEddREREROoHFT8/PwQEBKBhw4bpUQ8RERGRRO1ZP+bm5sibN2961EJERESkQu2gMmnSJEyePBnx8fHpUQ8RERGRRO1dP25ubti0aRNsbW3h5OQEfX19leVXrlzRWHFERESUuakdVNzd3XH58mV07NiRg2mJiIgoXakdVPbv34/AwEBUrlw5PeohIiIikqg9RsXR0RFmZmbpUQsRERGRCrWDyvz58zFixAiEhoamQzlERERE/6P2rp+OHTviw4cPyJcvH0xMTNIMpo2IiNBYcUSZndOo/dougbQsdFYjbZdApFVqBxUvL690KIOIiIgorR+a9UNERET0K3xXUImOjpYG0EZHR//ruhxoS0RERJryXUHF0tISL1++hK2tLSwsLL567BQhBBQKBVJSUjReJBEREWVO3xVUjh49CisrKwBAcHBwuhZERERElOq7gkq1atWQN29eXLx4EdWqVUvvmoiIiIgAqHEcldDQUO7WISIiol9K7QO+aZK3tzdcXFxgZmYGMzMzVKhQAQcOHNBmSURERCQjak1PDgwMhLm5+b+u07Rp0+++PwcHB8yaNQv58+eHEALr169Hs2bNcPXqVRQtWlSd0oiIiCgDUiuo/NcxVNSd9dOkSROV69OnT4e3tzfOnTvHoEJERETqBZVXr17B1tY2XQpJSUnBtm3bEBcXhwoVKnx1ncTERCQmJkrX/+uYLkRERPR7++4xKl87doom3Lx5E1myZIGhoSF69+6NnTt3okiRIl9dd+bMmTA3N5cujo6O6VITERERycN3BxUhRLoUULBgQVy7dg3nz59Hnz594O7ujn/++eer644ePRpRUVHS5enTp+lSExEREcnDd+/6cXd3h7GxscYLMDAwgLOzMwCgTJkyuHjxIhYtWoQVK1akWdfQ0BCGhoYar4GIiIjk6buDytq1a9OzDolSqVQZh0JERESZl9pnT9ak0aNHo0GDBsiVKxdiYmKwceNGHDt2DIGBgdosi4iIiGRCq0ElPDwcnTt3xsuXL2Fubg4XFxcEBgaiTp062iyLiIiIZEKrQWX16tXafHgiIiKSuR8+hH5ISAgCAwMRHx8PIP1mBREREVHmpXZQeffuHWrXro0CBQqgYcOGePnyJQCge/fuGDp0qMYLJCIiosxL7aAyePBg6Onp4cmTJzAxMZHa27Rpg4MHD2q0OCIiIsrc1B6jcujQIQQGBsLBwUGlPX/+/AgLC9NYYURERERq96jExcWp9KSkioiI4MHYiIiISKPUDipVqlTBhg0bpOsKhQJKpRJz5sxBjRo1NFocERERZW5q7/qZM2cOatWqhUuXLiEpKQkjRozA7du3ERERgdOnT6dHjURERJRJqd2jUqxYMdy/fx+VK1dGs2bNEBcXh5YtW+Lq1avIly9fetRIREREmdQPHfDN3NwcY8eO1XQtRERERCrU7lE5ePAgTp06JV1ftmwZSpYsifbt2+P9+/caLY6IiIgyN7WDyvDhwxEdHQ0AuHnzJoYMGYKGDRvi8ePHGDJkiMYLJCIiosxL7V0/jx8/RpEiRQAAO3bsQJMmTTBjxgxcuXIFDRs21HiBRERElHmp3aNiYGCADx8+AAAOHz6MunXrAgCsrKyknhYiIiIiTVC7R6Vy5coYMmQIKlWqhAsXLmDLli0AgPv376c5Wi0RERHRz1C7R2Xp0qXQ09PD9u3b4e3tjZw5cwIADhw4gPr162u8QCIiIsq81O5RyZUrF/bt25emfeHChRopiIiIiCjVDx1HRalUIiQkBOHh4VAqlSrLqlatqpHCiIiIiNQOKufOnUP79u0RFhYGIYTKMoVCgZSUFI0VR0RERJmb2kGld+/eKFu2LPbv348cOXJAoVCkR11ERERE6geVBw8eYPv27XB2dk6PeoiIiIgkas/6KV++PEJCQtKjFiIiIiIVaveo9O/fH0OHDsWrV69QvHhx6Ovrqyx3cXHRWHFERESUuakdVFq1agUA6Natm9SmUCgghOBgWiIiItKoHzrXDxEREdGvoHZQyZ07d3rUQURERJTGDx3w7eHDh/Dy8sKdO3cAAEWKFMHAgQORL18+jRZHREREmZvaQSUwMBBNmzZFyZIlUalSJQDA6dOnUbRoUezduxd16tTReJHa4jRqv7ZLIC0LndVI2yUQEWVqageVUaNGYfDgwZg1a1aa9pEjR2aooEJERETapfZxVO7cuYPu3bunae/WrRv++ecfjRRFREREBPxAULGxscG1a9fStF+7dg22traaqImIiIgIwA/s+unZsyd69eqFR48eoWLFigA+jVGZPXs2hgwZovECiYiIKPNSO6iMHz8eWbNmxfz58zF69GgAgL29PSZNmoQBAwZovEAiIiLKvNQOKgqFAoMHD8bgwYMRExMDAMiaNavGCyMiIiL6oeOoAEB4eDju3bsHAChUqBBsbGw0VhQRERER8AODaWNiYtCpUyfY29ujWrVqqFatGuzt7dGxY0dERUWlR41ERESUSakdVHr06IHz589j//79iIyMRGRkJPbt24dLly7Bw8MjPWokIiKiTErtXT/79u1DYGAgKleuLLXVq1cPK1euRP369TVaHBEREWVuaveoZMuWDebm5mnazc3NYWlpqZGiiIiIiIAfCCrjxo3DkCFD8OrVK6nt1atXGD58OMaPH6/R4oiIiChzU3vXj7e3N0JCQpArVy7kypULAPDkyRMYGhrizZs3WLFihbTulStXNFcpERERZTpqB5XmzZunQxlEREREaakdVCZOnJgedRARERGlofYYladPn+LZs2fS9QsXLmDQoEHw9fXVaGFEREREageV9u3bIzg4GMCnQbS1a9fGhQsXMHbsWEyZMkXjBRIREVHmpXZQuXXrFv744w8AwNatW1G8eHGcOXMG/v7+WLdunabrIyIiokxM7aCSnJwMQ0NDAMDhw4fRtGlTAJ/O9/Py5UvNVkdERESZmtpBpWjRovDx8cHJkycRFBQkHY32xYsXyJYtm8YLJCIiosxL7aAye/ZsrFixAtWrV0e7du1QokQJAMCePXukXUJEREREmqD29OTq1avj7du3iI6OVjlkfq9evWBiYqLR4oiIiChzU7tHBQCEELh8+TJWrFiBmJgYAICBgQGDChEREWmU2j0qYWFhqF+/Pp48eYLExETUqVMHWbNmxezZs5GYmAgfH5/0qJOIiIgyIbV7VAYOHIiyZcvi/fv3MDY2ltpbtGiBI0eOaLQ4IiIiytzU7lE5efIkzpw5AwMDA5V2JycnPH/+XGOFEREREando6JUKpGSkpKm/dmzZ8iaNatGiiIiIiICfiCo1K1bF15eXtJ1hUKB2NhYTJw4EQ0bNtRkbURERJTJqb3rZ/78+ahXrx6KFCmChIQEtG/fHg8ePIC1tTU2bdqUHjUSERFRJqV2UHFwcMD169exZcsWXL9+HbGxsejevTs6dOigMriWiIiI6GepHVQAQE9PDx06dECHDh2ktpcvX2L48OFYunSpxoojIiKizE2toHL79m0EBwfDwMAAbm5usLCwwNu3bzF9+nT4+Pggb9686VUnERERZULfPZh2z549KFWqFAYMGIDevXujbNmyCA4ORuHChXHnzh3s3LkTt2/fTs9aiYiIKJP57qAybdo0eHp6Ijo6GgsWLMCjR48wYMAA/P333zh48KB0FmUiIiIiTfnuoHLv3j14enoiS5Ys6N+/P3R0dLBw4UKUK1cuPesjIiKiTOy7g0pMTAzMzMwAALq6ujA2NuaYFCIiIkpXag2mDQwMhLm5OYBPR6g9cuQIbt26pbJO06ZNNVcdERERZWpqBRV3d3eV6x4eHirXFQrFVw+vT0RERPQjvjuoKJXK9KyDiIiIKA21z/VDRERE9KtoNajMnDkT5cqVQ9asWWFra4vmzZvj3r172iyJiIiIZESrQeX48ePw9PTEuXPnEBQUhOTkZNStWxdxcXHaLIuIiIhk4ofO9aMpBw8eVLm+bt062Nra4vLly6hataqWqiIiIiK50GpQ+VJUVBQAwMrK6qvLExMTkZiYKF2Pjo7+JXURERGRdvzQrp/IyEisWrUKo0ePRkREBADgypUreP78+Q8XolQqMWjQIFSqVAnFihX76jozZ86Eubm5dHF0dPzhxyMiIiL5Uzuo3LhxAwUKFMDs2bMxb948REZGAgACAgIwevToHy7E09MTt27dwubNm7+5zujRoxEVFSVdnj59+sOPR0RERPKndlAZMmQIunTpggcPHsDIyEhqb9iwIU6cOPFDRfTr1w/79u1DcHAwHBwcvrmeoaEhzMzMVC5ERESUcak9RuXixYtYsWJFmvacOXPi1atXat2XEAL9+/fHzp07cezYMeTJk0fdcoiIiCgDUzuoGBoafnUQ6/3792FjY6PWfXl6emLjxo3YvXs3smbNKgUdc3NzGBsbq1saERERZTBq7/pp2rQppkyZguTkZACfzu/z5MkTjBw5Eq1atVLrvry9vREVFYXq1asjR44c0mXLli3qlkVEREQZkNpBZf78+YiNjYWtrS3i4+NRrVo1ODs7I2vWrJg+fbpa9yWE+OqlS5cu6pZFREREGZDau37Mzc0RFBSEU6dO4caNG4iNjUXp0qVRu3bt9KiPiIiIMrEfPuBb5cqVUblyZU3WQkRERKRC7aCyePHir7YrFAoYGRnB2dkZVatWha6u7k8XR0RERJmb2kFl4cKFePPmDT58+ABLS0sAwPv372FiYoIsWbIgPDwcefPmRXBwMI8cS0RERD9F7cG0M2bMQLly5fDgwQO8e/cO7969w/3791G+fHksWrQIT548gZ2dHQYPHpwe9RIREVEmonaPyrhx47Bjxw7ky5dPanN2dsa8efPQqlUrPHr0CHPmzFF7qjIRERHRl9TuUXn58iU+fvyYpv3jx4/SAdvs7e0RExPz89URERFRpqZ2UKlRowY8PDxw9epVqe3q1avo06cPatasCQC4efMmD4dPREREP03toLJ69WpYWVmhTJkyMDQ0hKGhIcqWLQsrKyusXr0aAJAlSxbMnz9f48USERFR5qL2GBU7OzsEBQXh7t27uH//PgCgYMGCKFiwoLROjRo1NFchERERZVo/fMC3QoUKoVChQpqshYiIiEjFDwWVZ8+eYc+ePXjy5AmSkpJUli1YsEAjhRERERGpHVSOHDmCpk2b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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "## calculate avg response time\n",
+ "unique_models = set(result[\"response\"]['model'] for result in result[\"results\"])\n",
+ "model_dict = {model: {\"response_time\": []} for model in unique_models}\n",
+ "for completion_result in result[\"results\"]:\n",
+ " model_dict[completion_result[\"response\"][\"model\"]][\"response_time\"].append(completion_result[\"response_time\"])\n",
+ "\n",
+ "avg_response_time = {}\n",
+ "for model, data in model_dict.items():\n",
+ " avg_response_time[model] = sum(data[\"response_time\"]) / len(data[\"response_time\"])\n",
+ "\n",
+ "models = list(avg_response_time.keys())\n",
+ "response_times = list(avg_response_time.values())\n",
+ "\n",
+ "plt.bar(models, response_times)\n",
+ "plt.xlabel('Model', fontsize=10)\n",
+ "plt.ylabel('Average Response Time')\n",
+ "plt.title('Average Response Times for each Model')\n",
+ "\n",
+ "plt.xticks(models, [model[:15]+'...' if len(model) > 15 else model for model in models], rotation=45)\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "inSDIE3_IRds"
+ },
+ "source": [
+ "# Duration Test endpoint\n",
+ "\n",
+ "Run load testing for 2 mins. Hitting endpoints with 100+ queries every 15 seconds."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "id": "ePIqDx2EIURH"
+ },
+ "outputs": [],
+ "source": [
+ "models=[\"gpt-3.5-turbo\", \"replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781\", \"claude-instant-1\"]\n",
+ "context = \"\"\"Paul Graham (/ɡræm/; born 1964)[3] is an English computer scientist, essayist, entrepreneur, venture capitalist, and author. He is best known for his work on the programming language Lisp, his former startup Viaweb (later renamed Yahoo! Store), cofounding the influential startup accelerator and seed capital firm Y Combinator, his essays, and Hacker News. He is the author of several computer programming books, including: On Lisp,[4] ANSI Common Lisp,[5] and Hackers & Painters.[6] Technology journalist Steven Levy has described Graham as a \"hacker philosopher\".[7] Graham was born in England, where he and his family maintain permanent residence. However he is also a citizen of the United States, where he was educated, lived, and worked until 2016.\"\"\"\n",
+ "prompt = \"Where does Paul Graham live?\"\n",
+ "final_prompt = context + prompt\n",
+ "result = load_test_model(models=models, prompt=final_prompt, num_calls=100, interval=15, duration=120)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 552
+ },
+ "id": "k6rJoELM6t1K",
+ "outputId": "f4968b59-3bca-4f78-a88b-149ad55e3cf7"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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DaxZswYFCxaU/ua9fPkS4eHhcHNzU3mMd+/eyTrgcLcU5VkZuTsuLg5JSUlQKBQ4cOAAwsPDoaurCz09PURERMDV1RX//POPFGwSExOhr68PPz8/vH79msGGNEIIIQWbX3/9Fd27d8fcuXPRt29fjBgxAufPnwfwftdASkoKJkyYgMqVK+PVq1cqj8NgQ5/r/PnzaN68Obp374727dtj1KhRCAkJwbVr19ClSxcA708zkJaWhnXr1qFGjRo4ceKEymPIOdgADDeUx0VGRqJixYo4evQoNmzYgCZNmuDvv/8G8H5XU/ny5dGmTRtMnz5dWidfvnwYNWoU7t27B0tLSwYb0oiM7Wju3Ln4448/sHHjRty4cQNt27bFn3/+ifHjx+P06dMAAAMDA7i6uqJhw4YwNzfXYtUkRw8fPsTz589V2r7//nvs3r0bFy9eRJ8+fQC8P/VFixYtMH369EwjN7IniPK4Hj16CFNTU6GjoyOWL18utaemporNmzeL9PR0qU2pVGqjRPpGREVFibZt24pVq1YJIYTYvXu3MDU1Ff369ROurq6iYcOG4syZM0II1W3x3bt3WqmX5CkkJETY2NiITZs2SW0Z29v69euFk5OTOH/+fKb10tLSvliN2saRG8qzMk4J7uPjg/j4eBgYGMDW1hbJyckA3n8rad++vcrETY7SUG6ytrbGqFGj0KRJE1y+fBk+Pj6YNm0ali5dip9++glnzpyBj48PLl68qLItclcUaVLZsmVRr149rFu3DocOHQLwv799Li4uiI6Oli4z8yG574r6EMMN5VkZocXe3h4nTpyAl5cXOnbsiF27diEpKSlTfzleH4W051Pbk6urKwoXLox9+/bB2dkZffv2BQBYWlrCzc0NLVu2hKur65cslb4x9vb26NevH2JiYuDn54fdu3dLywoXLgxHR0ctVpc3fDsxjr4a4v8nAD9//hxpaWkoVqwYrK2tUaNGDSQnJ6NXr14ICgpCixYtYGRkhICAADRq1AhOTk7aLp1kQnwweXjFihWIjo6GgYEBRowYIV26IyUlBc+ePcOjR49QunRpHDhwAK1atcLAgQP/9bQERJ8j42i7evXqYcmSJRg7dixGjx6NkJAQODs7Y8uWLVAoFPjxxx+1XapW8VBwypOCg4MxadIkREVFoXnz5mjTpg1atmwJAOjRowd27NiB4cOH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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "## calculate avg response time\n",
+ "unique_models = set(unique_result[\"response\"]['model'] for unique_result in result[0][\"results\"])\n",
+ "model_dict = {model: {\"response_time\": []} for model in unique_models}\n",
+ "for iteration in result:\n",
+ " for completion_result in iteration[\"results\"]:\n",
+ " model_dict[completion_result[\"response\"][\"model\"]][\"response_time\"].append(completion_result[\"response_time\"])\n",
+ "\n",
+ "avg_response_time = {}\n",
+ "for model, data in model_dict.items():\n",
+ " avg_response_time[model] = sum(data[\"response_time\"]) / len(data[\"response_time\"])\n",
+ "\n",
+ "models = list(avg_response_time.keys())\n",
+ "response_times = list(avg_response_time.values())\n",
+ "\n",
+ "plt.bar(models, response_times)\n",
+ "plt.xlabel('Model', fontsize=10)\n",
+ "plt.ylabel('Average Response Time')\n",
+ "plt.title('Average Response Times for each Model')\n",
+ "\n",
+ "plt.xticks(models, [model[:15]+'...' if len(model) > 15 else model for model in models], rotation=45)\n",
+ "plt.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/cookbook/TogetherAI_liteLLM.ipynb b/cookbook/TogetherAI_liteLLM.ipynb
index b81659576..db029da3d 100644
--- a/cookbook/TogetherAI_liteLLM.ipynb
+++ b/cookbook/TogetherAI_liteLLM.ipynb
@@ -19,12 +19,12 @@
},
"outputs": [],
"source": [
- "!pip install litellm==0.1.371"
+ "!pip install litellm==0.1.419"
]
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 3,
"metadata": {
"id": "TMI3739_9q97"
},
@@ -32,7 +32,7 @@
"source": [
"import os\n",
"from litellm import completion\n",
- "os.environ[\"TOGETHER_AI_TOKEN\"] = \"\" #@param\n",
+ "os.environ[\"TOGETHERAI_API_KEY\"] = \"\" #@param\n",
"user_message = \"Hello, whats the weather in San Francisco??\"\n",
"messages = [{ \"content\": user_message,\"role\": \"user\"}]"
]
@@ -50,26 +50,47 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 4,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Jrrt8puj523f",
- "outputId": "5a5b5beb-cda3-413e-8e83-4423d392cb44"
+ "outputId": "24494dea-816f-47a6-ade4-1b04f2e9085b"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "{'choices': [{'finish_reason': 'stop', 'index': 0, 'message': {'role': 'assistant', 'content': \"\\n\\nI'm not able to provide real-time weather information. However, I can suggest\"}}], 'created': 1691629657.9288375, 'model': 'togethercomputer/llama-2-70b-chat', 'usage': {'prompt_tokens': 9, 'completion_tokens': 17, 'total_tokens': 26}}\n"
+ "{\n",
+ " 'choices': [\n",
+ "{\n",
+ " 'finish_reason': 'stop',\n",
+ " 'index': 0,\n",
+ " 'message': {\n",
+ " 'role': 'assistant',\n",
+ " 'content': \"\n",
+ "\n",
+ "I'm not able to provide real-time weather information. However, I can suggest some ways for you to find out the current weather in San Francisco.\n",
+ "\n",
+ "1. Check online weather websites: There are many websites that provide up-to-date weather information, such as AccuWeather, Weather.com, or the National Weather Service. You can enter \"San Francisco\" in the search bar and get the current weather conditions, forecast, and radar imagery.\n",
+ "2. Use a weather app: You can download a weather app on your smartphone that provides real-time weather information. Some popular weather apps include Dark Sky, Weather Underground, and The Weather Channel.\n",
+ "3. Tune into local news: You can watch local news channels or listen to local radio stations to get the latest weather forecast and current conditions.\n",
+ "4. Check social media: Follow local weather accounts on social media platforms like Twitter or Facebook to\"\n",
+ "}\n",
+ "}\n",
+ " ],\n",
+ " 'created': 1692323365.8261144,\n",
+ " 'model': 'togethercomputer/llama-2-70b-chat',\n",
+ " 'usage': {'prompt_tokens': 9, 'completion_tokens': 176, 'total_tokens': 185}\n",
+ "}\n"
]
}
],
"source": [
"model_name = \"togethercomputer/llama-2-70b-chat\"\n",
- "response = completion(model=model_name, messages=messages, custom_llm_provider=\"together_ai\")\n",
+ "response = completion(model=model_name, messages=messages, max_tokens=200)\n",
"print(response)"
]
},
@@ -85,46 +106,569 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "wuBhlZtC6MH5",
- "outputId": "fcb82177-6494-4963-8e37-8716d3b9e616"
+ "outputId": "1bedc981-4ab1-4abd-9b81-a9727223b66a"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "\n",
- "{'role': 'assistant', 'content': '\\\\n'}\n",
- "{'role': 'assistant', 'content': '\\\\n'}\n",
- "{'role': 'assistant', 'content': 'I'}\n",
- "{'role': 'assistant', 'content': 'm'}\n",
- "{'role': 'assistant', 'content': ' not'}\n",
- "{'role': 'assistant', 'content': ' able'}\n",
- "{'role': 'assistant', 'content': ' to'}\n",
- "{'role': 'assistant', 'content': ' provide'}\n",
- "{'role': 'assistant', 'content': ' real'}\n",
- "{'role': 'assistant', 'content': '-'}\n",
- "{'role': 'assistant', 'content': 'time'}\n",
- "{'role': 'assistant', 'content': ' weather'}\n",
- "{'role': 'assistant', 'content': ' information'}\n",
- "{'role': 'assistant', 'content': '.'}\n",
- "{'role': 'assistant', 'content': ' However'}\n",
- "{'role': 'assistant', 'content': ','}\n",
- "{'role': 'assistant', 'content': ' I'}\n",
- "{'role': 'assistant', 'content': ' can'}\n"
+ "\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Com'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'bin'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ator'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' ('}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ')'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' are'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' two'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' popular'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' startup'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' acceler'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ators'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' have'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' gained'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' recognition'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' for'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' their'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' effect'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'iveness'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'urt'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'uring'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' scaling'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' early'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '-'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'stage'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' companies'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ities'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' they'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' also'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' have'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' distinct'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' differences'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' set'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' them'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' apart'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' In'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' this'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' ess'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ay'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' we'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' will'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' explore'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' key'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' features'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' discuss'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' which'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' program'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' might'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' be'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' better'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fit'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' for'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' your'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' startup'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Com'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'bin'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ator'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' one'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' most'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' successful'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' startup'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' acceler'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ators'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' world'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' with'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' port'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'folio'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' includes'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Air'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'b'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'nb'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Drop'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'box'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Red'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'dit'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' F'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ounded'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' '}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '2'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '5'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' has'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fund'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ed'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' over'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' '}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '1'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '9'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' start'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ups'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' with'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' combined'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' valu'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ation'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' over'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' $'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '1'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' billion'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' The'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' program'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' known'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' for'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' its'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' inten'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'se'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' three'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '-'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'month'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' boot'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' camp'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '-'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'style'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' format'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' where'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' found'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ers'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' work'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' closely'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' with'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' experienced'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' ment'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ors'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' develop'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' their'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' products'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' ref'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ine'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' their'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' business'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' models'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' prepare'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' for'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fund'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ra'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ising'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': \"'\"}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 's'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' focus'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' on'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' software'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' technology'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' internet'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' start'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ups'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' program'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' has'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' strong'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' track'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' record'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' ident'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ifying'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'urt'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'uring'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' successful'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' companies'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' these'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' spaces'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' on'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' other'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' hand'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' relatively'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' new'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' acceler'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ator'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' program'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' was'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' founded'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' '}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '2'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '1'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '7'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' While'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' it'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' may'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' not'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' have'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' same'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' level'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' brand'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' recognition'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' as'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' has'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' quickly'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' gained'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' reputation'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' for'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' its'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' unique'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' approach'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' startup'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' acceleration'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' The'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' program'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' focus'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'es'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' on'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' supporting'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' under'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 're'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'present'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ed'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' found'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ers'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' particularly'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' women'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' people'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' color'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' provides'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' range'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' resources'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' support'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' help'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' these'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' found'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ers'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' succeed'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': \"'\"}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 's'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' program'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' designed'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' be'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' more'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' flexible'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' personal'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ized'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' than'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' traditional'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' acceler'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ators'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' with'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' focus'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' on'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' connecting'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' found'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ers'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' with'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' ment'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ors'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' resources'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' are'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' tail'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ored'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' their'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' specific'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' needs'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'One'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' key'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' difference'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' between'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' type'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' companies'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' they'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' support'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' focus'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'es'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' primarily'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' on'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' software'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' technology'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' internet'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' start'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ups'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' while'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' has'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' bro'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ader'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' focus'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' includes'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' range'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' indust'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ries'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' such'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' as'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' health'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'care'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fin'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ance'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' and'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' consumer'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' products'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' This'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' means'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' that'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' if'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' your'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' startup'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' non'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '-'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'tech'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' industry'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' may'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' be'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' a'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' better'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fit'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '\\n'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'An'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'other'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' difference'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' between'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' two'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' programs'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' is'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' their'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' approach'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fund'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ing'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' Y'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'C'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' provides'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' seed'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fund'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ing'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' all'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' its'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' port'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'folio'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' companies'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' typically'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' in'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' the'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' range'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' of'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' $'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '1'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' $'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '2'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '0'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': '.'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' In'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' contrast'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ','}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' l'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ite'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'LL'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'M'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' does'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' not'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' provide'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' fund'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': 'ing'}}]}\n",
+ "{'choices': [{'delta': {'role': 'assistant', 'content': ' to'}}]}\n"
]
}
],
"source": [
- "response = completion(model=model_name, messages=messages, stream=True, custom_llm_provider=\"together_ai\")\n",
- "print(response)\n",
- "for chunk in response:\n",
- " print(chunk['choices'][0]['delta']) # same as openai format"
+ "user_message = \"Write 1page essay on YC + liteLLM\"\n",
+ "messages = [{ \"content\": user_message,\"role\": \"user\"}]\n",
+ "\n",
+ "\n",
+ "import asyncio\n",
+ "async def parse_stream(stream):\n",
+ " async for elem in stream:\n",
+ " print(elem)\n",
+ " return\n",
+ "\n",
+ "stream = completion(model=\"togethercomputer/llama-2-70b-chat\", messages=messages, stream=True, max_tokens=800)\n",
+ "print(stream)\n",
+ "\n",
+ "# Await the asynchronous function directly in the notebook cell\n",
+ "await parse_stream(stream)\n"
]
}
],
diff --git a/cookbook/liteLLM_Langchain_Demo.ipynb b/cookbook/liteLLM_Langchain_Demo.ipynb
new file mode 100644
index 000000000..0f6364a14
--- /dev/null
+++ b/cookbook/liteLLM_Langchain_Demo.ipynb
@@ -0,0 +1,201 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "# Langchain liteLLM Demo Notebook\n",
+ "## Use `ChatLiteLLM()` to instantly support 50+ LLM models\n",
+ "Langchain Docs: https://python.langchain.com/docs/integrations/chat/litellm\n",
+ "\n",
+ "Call all LLM models using the same I/O interface\n",
+ "\n",
+ "Example usage\n",
+ "```python\n",
+ "ChatLiteLLM(model=\"gpt-3.5-turbo\")\n",
+ "ChatLiteLLM(model=\"claude-2\", temperature=0.3)\n",
+ "ChatLiteLLM(model=\"command-nightly\")\n",
+ "ChatLiteLLM(model=\"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\")\n",
+ "```"
+ ],
+ "metadata": {
+ "id": "5hwntUxTMxEk"
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "aPNAUsCvB6Sv"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install litellm langchain"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "import os\n",
+ "from langchain.chat_models import ChatLiteLLM\n",
+ "from langchain.prompts.chat import (\n",
+ " ChatPromptTemplate,\n",
+ " SystemMessagePromptTemplate,\n",
+ " AIMessagePromptTemplate,\n",
+ " HumanMessagePromptTemplate,\n",
+ ")\n",
+ "from langchain.schema import AIMessage, HumanMessage, SystemMessage"
+ ],
+ "metadata": {
+ "id": "MOhRaVnhB-0J"
+ },
+ "execution_count": 2,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "os.environ['OPENAI_API_KEY'] = \"\"\n",
+ "chat = ChatLiteLLM(model=\"gpt-3.5-turbo\")\n",
+ "messages = [\n",
+ " HumanMessage(\n",
+ " content=\"what model are you\"\n",
+ " )\n",
+ "]\n",
+ "chat(messages)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "TahkCtlmCD65",
+ "outputId": "5ddda40f-f252-4830-a8d6-bd3fa68ae487"
+ },
+ "execution_count": 17,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "AIMessage(content='I am an AI model known as GPT-3, developed by OpenAI.', additional_kwargs={}, example=False)"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 17
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "os.environ['ANTHROPIC_API_KEY'] = \"\"\n",
+ "chat = ChatLiteLLM(model=\"claude-2\", temperature=0.3)\n",
+ "messages = [\n",
+ " HumanMessage(\n",
+ " content=\"what model are you\"\n",
+ " )\n",
+ "]\n",
+ "chat(messages)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "uXNDyU4jChcs",
+ "outputId": "bd74b4c6-f9fb-42dc-fdc3-9240d50503ba"
+ },
+ "execution_count": 23,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "AIMessage(content=\" I'm Claude, an AI assistant created by Anthropic.\", additional_kwargs={}, example=False)"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 23
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "os.environ['REPLICATE_API_TOKEN'] = \"\"\n",
+ "chat = ChatLiteLLM(model=\"replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1\")\n",
+ "messages = [\n",
+ " HumanMessage(\n",
+ " content=\"what model are you?\"\n",
+ " )\n",
+ "]\n",
+ "chat(messages)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "czbDJRKcC7BV",
+ "outputId": "892e147d-831e-4884-dc71-040f92c3fb8e"
+ },
+ "execution_count": 27,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "AIMessage(content=\" I'm an AI based based on LLaMA models (LLaMA: Open and Efficient Foundation Language Models, Touvron et al. 2023), my knowledge was built from a massive corpus of text, including books, articles, and websites, and I was trained using a variety of machine learning algorithms. My model architecture is based on the transformer architecture, which is particularly well-suited for natural language processing tasks. My team of developers and I are constantly working to improve and fine-tune my performance, and I am always happy to help with any questions you may have!\", additional_kwargs={}, example=False)"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 27
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "os.environ['COHERE_API_KEY'] = \"\"\n",
+ "chat = ChatLiteLLM(model=\"command-nightly\")\n",
+ "messages = [\n",
+ " HumanMessage(\n",
+ " content=\"what model are you?\"\n",
+ " )\n",
+ "]\n",
+ "chat(messages)"
+ ],
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "tZxpq5PDDY9Y",
+ "outputId": "7e86f4ed-ac7a-45e1-87d0-217da6cad666"
+ },
+ "execution_count": 30,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "AIMessage(content=' I am an AI-based large language model, or Chatbot, built by the company Cohere. I am designed to have polite, helpful, inclusive conversations with users. I am always learning and improving, and I am constantly being updated with new information and improvements.\\n\\nI am currently in the development phase, and I am not yet available to the general public. However, I am currently being used by a select group of users for testing and feedback.\\n\\nI am a large language model, which means that I am trained on a massive amount of data and can understand and respond to a wide range of requests and questions. I am also designed to be flexible and adaptable, so I can be customized to suit the needs of different users and use cases.\\n\\nI am currently being used to develop a range of applications, including customer service chatbots, content generation tools, and language translation services. I am also being used to train other language models and to develop new ways of using large language models.\\n\\nI am constantly being updated with new information and improvements, so I am always learning and improving. I am also being used to develop new ways of using large language models, so I am always evolving and adapting to new use cases and requirements.', additional_kwargs={}, example=False)"
+ ]
+ },
+ "metadata": {},
+ "execution_count": 30
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cookbook/proxy-server/readme.md b/cookbook/proxy-server/readme.md
index e0a0ad6c6..4f735f38c 100644
--- a/cookbook/proxy-server/readme.md
+++ b/cookbook/proxy-server/readme.md
@@ -8,6 +8,8 @@
[](https://railway.app/template/DYqQAW?referralCode=t3ukrU)
+
+
## What does liteLLM proxy do
- Make `/chat/completions` requests for 50+ LLM models **Azure, OpenAI, Replicate, Anthropic, Hugging Face**
@@ -156,3 +158,11 @@ This project includes a `Dockerfile` allowing you to build and deploy a Docker P
- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
+## Roadmap
+- [ ] Support hosted db (e.g. Supabase)
+- [ ] Easily send data to places like posthog and sentry.
+- [ ] Add a hot-cache for project spend logs - enables fast checks for user + project limitings
+- [ ] Implement user-based rate-limiting
+- [ ] Spending controls per project - expose key creation endpoint
+- [ ] Need to store a keys db -> mapping created keys to their alias (i.e. project name)
+- [ ] Easily add new models as backups / as the entry-point (add this to the available model list)
diff --git a/dist/litellm-0.1.401-py3-none-any.whl b/dist/litellm-0.1.401-py3-none-any.whl
new file mode 100644
index 000000000..a412dff57
Binary files /dev/null and b/dist/litellm-0.1.401-py3-none-any.whl differ
diff --git a/dist/litellm-0.1.401.tar.gz b/dist/litellm-0.1.401.tar.gz
new file mode 100644
index 000000000..fff7c7da6
Binary files /dev/null and b/dist/litellm-0.1.401.tar.gz differ
diff --git a/dist/litellm-0.1.432-py3-none-any.whl b/dist/litellm-0.1.432-py3-none-any.whl
new file mode 100644
index 000000000..caa311d5a
Binary files /dev/null and b/dist/litellm-0.1.432-py3-none-any.whl differ
diff --git a/dist/litellm-0.1.432.tar.gz b/dist/litellm-0.1.432.tar.gz
new file mode 100644
index 000000000..7506ce00a
Binary files /dev/null and b/dist/litellm-0.1.432.tar.gz differ
diff --git a/dist/litellm-0.1.434-py3-none-any.whl b/dist/litellm-0.1.434-py3-none-any.whl
new file mode 100644
index 000000000..41e852f70
Binary files /dev/null and b/dist/litellm-0.1.434-py3-none-any.whl differ
diff --git a/dist/litellm-0.1.434.tar.gz b/dist/litellm-0.1.434.tar.gz
new file mode 100644
index 000000000..433c504e9
Binary files /dev/null and b/dist/litellm-0.1.434.tar.gz differ
diff --git a/dist/litellm-0.1.435-py3-none-any.whl b/dist/litellm-0.1.435-py3-none-any.whl
new file mode 100644
index 000000000..68c3e446c
Binary files /dev/null and b/dist/litellm-0.1.435-py3-none-any.whl differ
diff --git a/dist/litellm-0.1.435.tar.gz b/dist/litellm-0.1.435.tar.gz
new file mode 100644
index 000000000..cdf40650f
Binary files /dev/null and b/dist/litellm-0.1.435.tar.gz differ
diff --git a/docs/my-website/blog/2019-05-28-first-blog-post.md b/docs/my-website/blog/2019-05-28-first-blog-post.md
deleted file mode 100644
index 02f3f81bd..000000000
--- a/docs/my-website/blog/2019-05-28-first-blog-post.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-slug: first-blog-post
-title: First Blog Post
-authors:
- name: Gao Wei
- title: Docusaurus Core Team
- url: https://github.com/wgao19
- image_url: https://github.com/wgao19.png
-tags: [hola, docusaurus]
----
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
diff --git a/docs/my-website/blog/2019-05-29-long-blog-post.md b/docs/my-website/blog/2019-05-29-long-blog-post.md
deleted file mode 100644
index 26ffb1b1f..000000000
--- a/docs/my-website/blog/2019-05-29-long-blog-post.md
+++ /dev/null
@@ -1,44 +0,0 @@
----
-slug: long-blog-post
-title: Long Blog Post
-authors: endi
-tags: [hello, docusaurus]
----
-
-This is the summary of a very long blog post,
-
-Use a `` comment to limit blog post size in the list view.
-
-
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
diff --git a/docs/my-website/blog/2021-08-01-mdx-blog-post.mdx b/docs/my-website/blog/2021-08-01-mdx-blog-post.mdx
deleted file mode 100644
index c04ebe323..000000000
--- a/docs/my-website/blog/2021-08-01-mdx-blog-post.mdx
+++ /dev/null
@@ -1,20 +0,0 @@
----
-slug: mdx-blog-post
-title: MDX Blog Post
-authors: [slorber]
-tags: [docusaurus]
----
-
-Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
-
-:::tip
-
-Use the power of React to create interactive blog posts.
-
-```js
-
-```
-
-
-
-:::
diff --git a/docs/my-website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg b/docs/my-website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg
deleted file mode 100644
index 11bda0928..000000000
Binary files a/docs/my-website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg and /dev/null differ
diff --git a/docs/my-website/blog/2021-08-26-welcome/index.md b/docs/my-website/blog/2021-08-26-welcome/index.md
index 9455168f1..4ca470335 100644
--- a/docs/my-website/blog/2021-08-26-welcome/index.md
+++ b/docs/my-website/blog/2021-08-26-welcome/index.md
@@ -1,25 +1,43 @@
----
-slug: welcome
-title: Welcome
-authors: [slorber, yangshun]
-tags: [facebook, hello, docusaurus]
----
+# 🚅 litellm
+a light 100 line package to simplify calling OpenAI, Azure, Cohere, Anthropic APIs
-[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
+###### litellm manages:
+* Calling all LLM APIs using the OpenAI format - `completion(model, messages)`
+* Consistent output for all LLM APIs, text responses will always be available at `['choices'][0]['message']['content']`
+* Consistent Exceptions for all LLM APIs, we map RateLimit, Context Window, and Authentication Error exceptions across all providers to their OpenAI equivalents. [see Code](https://github.com/BerriAI/litellm/blob/ba1079ff6698ef238c5c7f771dd2b698ec76f8d9/litellm/utils.py#L250)
-Simply add Markdown files (or folders) to the `blog` directory.
+###### observability:
+* Logging - see exactly what the raw model request/response is by plugging in your own function `completion(.., logger_fn=your_logging_fn)` and/or print statements from the package `litellm.set_verbose=True`
+* Callbacks - automatically send your data to Helicone, Sentry, Posthog, Slack - `litellm.success_callbacks`, `litellm.failure_callbacks` [see Callbacks](https://litellm.readthedocs.io/en/latest/advanced/)
-Regular blog authors can be added to `authors.yml`.
+## Quick Start
+Go directly to code: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing)
+### Installation
+```
+pip install litellm
+```
-The blog post date can be extracted from filenames, such as:
+### Usage
+```python
+from litellm import completion
-- `2019-05-30-welcome.md`
-- `2019-05-30-welcome/index.md`
+## set ENV variables
+os.environ["OPENAI_API_KEY"] = "openai key"
+os.environ["COHERE_API_KEY"] = "cohere key"
-A blog post folder can be convenient to co-locate blog post images:
+messages = [{ "content": "Hello, how are you?","role": "user"}]
-
+# openai call
+response = completion(model="gpt-3.5-turbo", messages=messages)
-The blog supports tags as well!
+# cohere call
+response = completion("command-nightly", messages)
+```
+Need Help / Support : [see troubleshooting](https://litellm.readthedocs.io/en/latest/troubleshoot)
-**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
+## Why did we build liteLLM
+- **Need for simplicity**: Our code started to get extremely complicated managing & translating calls between Azure, OpenAI, Cohere
+
+## Support
+* [Meet with us 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
+* Contact us at ishaan@berri.ai / krrish@berri.ai
diff --git a/docs/my-website/blog/authors.yml b/docs/my-website/blog/authors.yml
deleted file mode 100644
index bcb299156..000000000
--- a/docs/my-website/blog/authors.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-endi:
- name: Endilie Yacop Sucipto
- title: Maintainer of Docusaurus
- url: https://github.com/endiliey
- image_url: https://github.com/endiliey.png
-
-yangshun:
- name: Yangshun Tay
- title: Front End Engineer @ Facebook
- url: https://github.com/yangshun
- image_url: https://github.com/yangshun.png
-
-slorber:
- name: Sébastien Lorber
- title: Docusaurus maintainer
- url: https://sebastienlorber.com
- image_url: https://github.com/slorber.png
diff --git a/docs/my-website/docs/caching.md b/docs/my-website/docs/caching.md
new file mode 100644
index 000000000..16c8e686f
--- /dev/null
+++ b/docs/my-website/docs/caching.md
@@ -0,0 +1,42 @@
+# Caching Completion() Responses
+
+liteLLM implements exact match caching. It can be enabled by setting
+1. `litellm.caching`: When set to `True`, enables caching for all responses. Keys are the input `messages` and values store in the cache is the corresponding `response`
+
+2. `litellm.caching_with_models`: When set to `True`, enables caching on a per-model basis.Keys are the input `messages + model` and values store in the cache is the corresponding `response`
+
+## Usage
+1. Caching - cache
+Keys in the cache are `model`, the following example will lead to a cache hit
+```python
+litellm.caching = True
+
+# Make completion calls
+response1 = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Tell me a joke."}])
+response2 = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Tell me a joke."}])
+
+# response1 == response2, response 1 is cached
+
+# with a diff model
+response3 = completion(model="command-nightly", messages=[{"role": "user", "content": "Tell me a joke."}])
+
+# response3 == response1 == response2, since keys are messages
+```
+
+
+2. Caching with Models - caching_with_models
+Keys in the cache are `messages + model`, the following example will not lead to a cache hit
+```python
+litellm.caching_with_models = True
+
+# Make completion calls
+response1 = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Tell me a joke."}])
+response2 = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Tell me a joke."}])
+# response1 == response2, response 1 is cached
+
+# with a diff model, this will call the API since the key is not cached
+response3 = completion(model="command-nightly", messages=[{"role": "user", "content": "Tell me a joke."}])
+
+# response3 != response1, since keys are messages + model
+```
+
diff --git a/docs/my-website/docs/completion/input.md b/docs/my-website/docs/completion/input.md
index 86546bbba..c1ae22c82 100644
--- a/docs/my-website/docs/completion/input.md
+++ b/docs/my-website/docs/completion/input.md
@@ -1,6 +1,6 @@
-# Completion Function - completion()
+# Input Format - completion()
The Input params are **exactly the same** as the
-OpenAI Create chat completion, and let you call **Azure OpenAI, Anthropic, Cohere, Replicate, OpenRouter** models in the same format.
+OpenAI Create chat completion, and let you call Azure OpenAI, Anthropic, Cohere, Replicate, OpenRouter models in the same format.
In addition, liteLLM allows you to pass in the following **Optional** liteLLM args:
`force_timeout`, `azure`, `logger_fn`, `verbose`
diff --git a/docs/my-website/docs/completion/output.md b/docs/my-website/docs/completion/output.md
index a82023771..4fb12c91d 100644
--- a/docs/my-website/docs/completion/output.md
+++ b/docs/my-website/docs/completion/output.md
@@ -1,12 +1,50 @@
-# Completion Function - completion()
-Here's the exact json output you can expect from a litellm `completion` call:
+# Output Format - completion()
+Here's the exact json output and type you can expect from all litellm `completion` calls for all models
```python
-{'choices': [{'finish_reason': 'stop',
- 'index': 0,
- 'message': {'role': 'assistant',
- 'content': " I'm doing well, thank you for asking. I am Claude, an AI assistant created by Anthropic."}}],
+{
+ 'choices': [
+ {
+ 'finish_reason': str, # String: 'stop'
+ 'index': int, # Integer: 0
+ 'message': { # Dictionary [str, str]
+ 'role': str, # String: 'assistant'
+ 'content': str # String: "default message"
+ }
+ }
+ ],
+ 'created': str, # String: None
+ 'model': str, # String: None
+ 'usage': { # Dictionary [str, int]
+ 'prompt_tokens': int, # Integer
+ 'completion_tokens': int, # Integer
+ 'total_tokens': int # Integer
+ }
+}
+
+```
+
+You can access the response as a dictionary or as a class object, just as OpenAI allows you
+```python
+print(response.choices[0].message.content)
+print(response['choices'][0]['message']['content'])
+```
+
+Here's what an example response looks like
+```python
+{
+ 'choices': [
+ {
+ 'finish_reason': 'stop',
+ 'index': 0,
+ 'message': {
+ 'role': 'assistant',
+ 'content': " I'm doing well, thank you for asking. I am Claude, an AI assistant created by Anthropic."
+ }
+ }
+ ],
'created': 1691429984.3852863,
'model': 'claude-instant-1',
- 'usage': {'prompt_tokens': 18, 'completion_tokens': 23, 'total_tokens': 41}}
+ 'usage': {'prompt_tokens': 18, 'completion_tokens': 23, 'total_tokens': 41}
+}
```
\ No newline at end of file
diff --git a/docs/my-website/docs/completion/supported.md b/docs/my-website/docs/completion/supported.md
index 7ae64024a..935a68b84 100644
--- a/docs/my-website/docs/completion/supported.md
+++ b/docs/my-website/docs/completion/supported.md
@@ -1,4 +1,12 @@
-# Generation/Completion/Chat Completion Models
+# Supported Chat, Completion Models
+
+## API Keys
+liteLLM reads key naming, all keys should be named in the following format:
+`_API_KEY` for example
+* `OPENAI_API_KEY` Provider = OpenAI
+* `TOGETHERAI_API_KEY` Provider = TogetherAI
+* `HUGGINGFACE_API_KEY` Provider = HuggingFace
+
### OpenAI Chat Completion Models
@@ -49,6 +57,7 @@ VertexAI requires you to set `application_default_credentials.json`, this can be
| Model Name | Function Call | Required OS Variables |
|------------------|--------------------------------------------|--------------------------------------|
| claude-instant-1 | `completion('claude-instant-1', messages)` | `os.environ['ANTHROPIC_API_KEY']` |
+| claude-instant-1.2 | `completion('claude-instant-1.2', messages)` | `os.environ['ANTHROPIC_API_KEY']` |
| claude-2 | `completion('claude-2', messages)` | `os.environ['ANTHROPIC_API_KEY']` |
### Hugging Face Inference API
@@ -64,10 +73,10 @@ Here are some examples of supported models:
| Model Name | Function Call | Required OS Variables |
|------------------|-------------------------------------------------------------------------------------|--------------------------------------|
-| [stabilityai/stablecode-completion-alpha-3b-4k](https://huggingface.co/stabilityai/stablecode-completion-alpha-3b-4k) | `completion(model="stabilityai/stablecode-completion-alpha-3b-4k", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HF_TOKEN']` |
-| [bigcode/starcoder](https://huggingface.co/bigcode/starcoder) | `completion(model="bigcode/starcoder", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HF_TOKEN']` |
-| [google/flan-t5-xxl](https://huggingface.co/google/flan-t5-xxl) | `completion(model="google/flan-t5-xxl", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HF_TOKEN']` |
-| [google/flan-t5-large](https://huggingface.co/google/flan-t5-large) | `completion(model="google/flan-t5-large", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HF_TOKEN']` |
+| [stabilityai/stablecode-completion-alpha-3b-4k](https://huggingface.co/stabilityai/stablecode-completion-alpha-3b-4k) | `completion(model="stabilityai/stablecode-completion-alpha-3b-4k", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HUGGINGFACE_API_KEY']` |
+| [bigcode/starcoder](https://huggingface.co/bigcode/starcoder) | `completion(model="bigcode/starcoder", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HUGGINGFACE_API_KEY']` |
+| [google/flan-t5-xxl](https://huggingface.co/google/flan-t5-xxl) | `completion(model="google/flan-t5-xxl", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HUGGINGFACE_API_KEY']` |
+| [google/flan-t5-large](https://huggingface.co/google/flan-t5-large) | `completion(model="google/flan-t5-large", messages=messages, custom_llm_provider="huggingface")` | `os.environ['HUGGINGFACE_API_KEY']` |
### AI21 Models
| Model Name | Function Call | Required OS Variables |
@@ -82,9 +91,24 @@ Here are some examples of supported models:
|------------------|--------------------------------------------|--------------------------------------|
| command-nightly | `completion('command-nightly', messages)` | `os.environ['COHERE_API_KEY']` |
-### BaseTen Models
+### Together AI Models
+liteLLM supports `non-streaming` and `streaming` requests to all models on https://api.together.xyz/
+
+Example TogetherAI Usage - Note: liteLLM supports all models deployed on TogetherAI
+
+| Model Name | Function Call | Required OS Variables |
+|-----------------------------------|------------------------------------------------------------------------|---------------------------------|
+| togethercomputer/llama-2-70b-chat | `completion('togethercomputer/llama-2-70b-chat', messages)` | `os.environ['TOGETHERAI_API_KEY']` |
+| togethercomputer/LLaMA-2-13b-chat | `completion('togethercomputer/LLaMA-2-13b-chat', messages)` | `os.environ['TOGETHERAI_API_KEY']` |
+| togethercomputer/code-and-talk-v1 | `completion('togethercomputer/code-and-talk-v1', messages)` | `os.environ['TOGETHERAI_API_KEY']` |
+| togethercomputer/creative-v1 | `completion('togethercomputer/creative-v1', messages)` | `os.environ['TOGETHERAI_API_KEY']` |
+| togethercomputer/yourmodel | `completion('togethercomputer/yourmodel', messages)` | `os.environ['TOGETHERAI_API_KEY']` |
+
+
+### Baseten Models
Baseten provides infrastructure to deploy and serve ML models https://www.baseten.co/. Use liteLLM to easily call models deployed on Baseten.
+Example Baseten Usage - Note: liteLLM supports all models deployed on Basten
| Model Name | Function Call | Required OS Variables |
|------------------|--------------------------------------------|------------------------------------|
@@ -99,13 +123,37 @@ All the text models from [OpenRouter](https://openrouter.ai/docs) are supported
| Model Name | Function Call | Required OS Variables |
|------------------|--------------------------------------------|--------------------------------------|
-| openai/gpt-3.5-turbo | `completion('openai/gpt-3.5-turbo', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| openai/gpt-3.5-turbo-16k | `completion('openai/gpt-3.5-turbo-16k', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| openai/gpt-4 | `completion('openai/gpt-4', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| openai/gpt-4-32k | `completion('openai/gpt-4-32k', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| anthropic/claude-2 | `completion('anthropic/claude-2', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| anthropic/claude-instant-v1 | `completion('anthropic/claude-instant-v1', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| google/palm-2-chat-bison | `completion('google/palm-2-chat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| google/palm-2-codechat-bison | `completion('google/palm-2-codechat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| meta-llama/llama-2-13b-chat | `completion('meta-llama/llama-2-13b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
-| meta-llama/llama-2-70b-chat | `completion('meta-llama/llama-2-70b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OR_API_KEY']` |
\ No newline at end of file
+| openai/gpt-3.5-turbo | `completion('openai/gpt-3.5-turbo', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| openai/gpt-3.5-turbo-16k | `completion('openai/gpt-3.5-turbo-16k', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| openai/gpt-4 | `completion('openai/gpt-4', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| openai/gpt-4-32k | `completion('openai/gpt-4-32k', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| anthropic/claude-2 | `completion('anthropic/claude-2', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| anthropic/claude-instant-v1 | `completion('anthropic/claude-instant-v1', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| google/palm-2-chat-bison | `completion('google/palm-2-chat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| google/palm-2-codechat-bison | `completion('google/palm-2-codechat-bison', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| meta-llama/llama-2-13b-chat | `completion('meta-llama/llama-2-13b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+| meta-llama/llama-2-70b-chat | `completion('meta-llama/llama-2-70b-chat', messages)` | `os.environ['OR_SITE_URL']`,`os.environ['OR_APP_NAME']`,`os.environ['OPENROUTER_API_KEY']` |
+
+### Petals Models
+Supported models on https://chat.petals.dev/
+
+| Model Name | Function Call | Required OS Variables |
+|----------------------|------------------------------------------------------------------------|--------------------------------|
+| stabilityai/StableBeluga2 | `completion(model='stabilityai/StableBeluga2', messages, custom_llm_provider="petals")` | No API Key required |
+| enoch/llama-65b-hf | `completion(model='enoch/llama-65b-hf', messages, custom_llm_provider="petals")` | No API Key required |
+| bigscience/bloomz | `completion(model='bigscience/bloomz', messages, custom_llm_provider="petals")` | No API Key required |
+
+### Ollama Models
+Ollama supported models: https://github.com/jmorganca/ollama
+
+| Model Name | Function Call | Required OS Variables |
+|----------------------|-----------------------------------------------------------------------------------|--------------------------------|
+| Llama2 7B | `completion(model='llama2', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Llama2 13B | `completion(model='llama2:13b', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Llama2 70B | `completion(model='llama2:70b', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Llama2 Uncensored | `completion(model='llama2-uncensored', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Orca Mini | `completion(model='orca-mini', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Vicuna | `completion(model='vicuna', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Nous-Hermes | `completion(model='nous-hermes', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Nous-Hermes 13B | `completion(model='nous-hermes:13b', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
+| Wizard Vicuna Uncensored | `completion(model='wizard-vicuna', messages, custom_api_base="http://localhost:11434", custom_llm_provider="ollama", stream=True)` | No API Key required |
diff --git a/docs/my-website/docs/index.md b/docs/my-website/docs/index.md
index 8fbce1e29..54f6fa869 100644
--- a/docs/my-website/docs/index.md
+++ b/docs/my-website/docs/index.md
@@ -1,30 +1,32 @@
-# 🚅 litellm
+# litellm
-a light 100 line package to simplify calling OpenAI, Azure, Cohere, Anthropic APIs
+[](https://pypi.org/project/litellm/)
+[](https://pypi.org/project/litellm/0.1.1/)
+[](https://dl.circleci.com/status-badge/redirect/gh/BerriAI/litellm/tree/main)
+
+[](https://github.com/BerriAI/litellm)
-###### litellm manages:
+[](https://discord.gg/wuPM9dRgDw)
-- Calling all LLM APIs using the OpenAI format - `completion(model, messages)`
-- Consistent output for all LLM APIs, text responses will always be available at `['choices'][0]['message']['content']`
-- Consistent Exceptions for all LLM APIs, we map RateLimit, Context Window, and Authentication Error exceptions across all providers to their OpenAI equivalents. [see Code](https://github.com/BerriAI/litellm/blob/ba1079ff6698ef238c5c7f771dd2b698ec76f8d9/litellm/utils.py#L250)
+a light package to simplify calling OpenAI, Azure, Cohere, Anthropic, Huggingface API Endpoints. It manages:
-###### observability:
+- translating inputs to the provider's completion and embedding endpoints
+- guarantees [consistent output](https://litellm.readthedocs.io/en/latest/output/), text responses will always be available at `['choices'][0]['message']['content']`
+- exception mapping - common exceptions across providers are mapped to the [OpenAI exception types](https://help.openai.com/en/articles/6897213-openai-library-error-types-guidance)
-- Logging - see exactly what the raw model request/response is by plugging in your own function `completion(.., logger_fn=your_logging_fn)` and/or print statements from the package `litellm.set_verbose=True`
-- Callbacks - automatically send your data to Helicone, LLMonitor, Sentry, Posthog, Slack - `litellm.success_callbacks`, `litellm.failure_callbacks` [see Callbacks](https://litellm.readthedocs.io/en/latest/advanced/)
+# usage
-## Quick Start
+
-Go directly to code: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing)
+Demo - https://litellm.ai/playground \
+Read the docs - https://docs.litellm.ai/docs/
-### Installation
+## quick start
```
pip install litellm
```
-### Usage
-
```python
from litellm import completion
@@ -41,13 +43,37 @@ response = completion(model="gpt-3.5-turbo", messages=messages)
response = completion("command-nightly", messages)
```
-Need Help / Support : [see troubleshooting](https://litellm.readthedocs.io/en/latest/troubleshoot)
+Code Sample: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing)
-## Why did we build liteLLM
+Stable version
+
+```
+pip install litellm==0.1.345
+```
+
+## Streaming Queries
+
+liteLLM supports streaming the model response back, pass `stream=True` to get a streaming iterator in response.
+Streaming is supported for OpenAI, Azure, Anthropic, Huggingface models
+
+```python
+response = completion(model="gpt-3.5-turbo", messages=messages, stream=True)
+for chunk in response:
+ print(chunk['choices'][0]['delta'])
+
+# claude 2
+result = completion('claude-2', messages, stream=True)
+for chunk in result:
+ print(chunk['choices'][0]['delta'])
+```
+
+# support / talk with founders
+
+- [Our calendar 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
+- [Community Discord 💭](https://discord.gg/wuPM9dRgDw)
+- Our numbers 📞 +1 (770) 8783-106 / +1 (412) 618-6238
+- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
+
+# why did we build this
- **Need for simplicity**: Our code started to get extremely complicated managing & translating calls between Azure, OpenAI, Cohere
-
-## Support
-
-- [Meet with us 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
-- Contact us at ishaan@berri.ai / krrish@berri.ai
diff --git a/docs/my-website/docs/observability/supabase_integration.md b/docs/my-website/docs/observability/supabase_integration.md
index 6ae4f65da..d9fbc2b5a 100644
--- a/docs/my-website/docs/observability/supabase_integration.md
+++ b/docs/my-website/docs/observability/supabase_integration.md
@@ -22,11 +22,13 @@ create table
messages json null default '{}'::json,
response json null default '{}'::json,
end_user text null default ''::text,
+ status text null default ''::text,
error json null default '{}'::json,
response_time real null default '0'::real,
total_cost real null,
additional_details json null default '{}'::json,
- constraint request_logs_pkey primary key (id)
+ litellm_call_id text unique,
+ primary key (id)
) tablespace pg_default;
```
diff --git a/docs/my-website/docusaurus.config.js b/docs/my-website/docusaurus.config.js
index e1e5cbb01..4af0e6f3b 100644
--- a/docs/my-website/docusaurus.config.js
+++ b/docs/my-website/docusaurus.config.js
@@ -6,8 +6,9 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
- title: 'LiteLLM',
+ title: 'liteLLM',
tagline: 'Simplify LLM API Calls',
+ favicon: '/img/favicon.ico',
// Set the production url of your site here
url: 'https://litellm.vercel.app/',
@@ -80,35 +81,27 @@ const config = {
{
title: 'Community',
items: [
- {
- label: 'Stack Overflow',
- href: 'https://stackoverflow.com/questions/tagged/docusaurus',
- },
{
label: 'Discord',
- href: 'https://discordapp.com/invite/docusaurus',
+ href: 'https://discord.com/invite/wuPM9dRgDw',
},
{
label: 'Twitter',
- href: 'https://twitter.com/docusaurus',
+ href: 'https://twitter.com/LiteLLM',
},
],
},
{
title: 'More',
items: [
- {
- label: 'Blog',
- to: '/blog',
- },
{
label: 'GitHub',
- href: 'https://github.com/facebook/docusaurus',
+ href: 'https://github.com/BerriAI/litellm/',
},
],
},
],
- copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
+ copyright: `Copyright © ${new Date().getFullYear()} liteLLM`,
},
prism: {
theme: lightCodeTheme,
diff --git a/docs/my-website/index.md b/docs/my-website/index.md
new file mode 100644
index 000000000..7d0698afe
--- /dev/null
+++ b/docs/my-website/index.md
@@ -0,0 +1,25 @@
+---
+slug: welcome
+title: Welcome
+authors: [slorber, yangshun]
+tags: [facebook, hello, docusaurus]
+---
+
+[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
+
+Simply add Markdown files (or folders) to the `blog` directory.
+
+Regular blog authors can be added to `authors.yml`.
+
+The blog post date can be extracted from filenames, such as:
+
+- `2019-05-30-welcome.md`
+- `2019-05-30-welcome/index.md`
+
+A blog post folder can be convenient to co-locate blog post images:
+
+
+
+The blog supports tags as well!
+
+**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
\ No newline at end of file
diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js
index 09978e440..4b5344382 100644
--- a/docs/my-website/sidebars.js
+++ b/docs/my-website/sidebars.js
@@ -21,14 +21,15 @@ const sidebars = {
'index',
{
type: 'category',
- label: 'completion_function',
- items: ['completion/input', 'completion/supported','completion/output'],
+ label: 'Completion()',
+ items: ['completion/input','completion/output'],
},
{
type: 'category',
- label: 'embedding_function',
+ label: 'Embedding()',
items: ['embedding/supported_embedding'],
},
+ 'completion/supported',
{
type: 'category',
label: 'Tutorials',
@@ -37,6 +38,7 @@ const sidebars = {
'token_usage',
'stream',
'secret',
+ 'caching',
{
type: 'category',
label: 'Logging & Observability',
diff --git a/docs/my-website/src/pages/index.md b/docs/my-website/src/pages/index.md
index 4ca470335..57d23215d 100644
--- a/docs/my-website/src/pages/index.md
+++ b/docs/my-website/src/pages/index.md
@@ -1,23 +1,27 @@
-# 🚅 litellm
-a light 100 line package to simplify calling OpenAI, Azure, Cohere, Anthropic APIs
+# *🚅 litellm*
+[](https://pypi.org/project/litellm/)
+[](https://pypi.org/project/litellm/0.1.1/)
+[](https://dl.circleci.com/status-badge/redirect/gh/BerriAI/litellm/tree/main)
+
+[](https://github.com/BerriAI/litellm)
-###### litellm manages:
-* Calling all LLM APIs using the OpenAI format - `completion(model, messages)`
-* Consistent output for all LLM APIs, text responses will always be available at `['choices'][0]['message']['content']`
-* Consistent Exceptions for all LLM APIs, we map RateLimit, Context Window, and Authentication Error exceptions across all providers to their OpenAI equivalents. [see Code](https://github.com/BerriAI/litellm/blob/ba1079ff6698ef238c5c7f771dd2b698ec76f8d9/litellm/utils.py#L250)
+[](https://discord.gg/wuPM9dRgDw)
-###### observability:
-* Logging - see exactly what the raw model request/response is by plugging in your own function `completion(.., logger_fn=your_logging_fn)` and/or print statements from the package `litellm.set_verbose=True`
-* Callbacks - automatically send your data to Helicone, Sentry, Posthog, Slack - `litellm.success_callbacks`, `litellm.failure_callbacks` [see Callbacks](https://litellm.readthedocs.io/en/latest/advanced/)
+a light package to simplify calling OpenAI, Azure, Cohere, Anthropic, Huggingface API Endpoints. It manages:
+- translating inputs to the provider's completion and embedding endpoints
+- guarantees [consistent output](https://litellm.readthedocs.io/en/latest/output/), text responses will always be available at `['choices'][0]['message']['content']`
+- exception mapping - common exceptions across providers are mapped to the [OpenAI exception types](https://help.openai.com/en/articles/6897213-openai-library-error-types-guidance)
+# usage
+
-## Quick Start
-Go directly to code: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing)
-### Installation
+Demo - https://litellm.ai/playground \
+Read the docs - https://docs.litellm.ai/docs/
+
+## quick start
```
pip install litellm
```
-### Usage
```python
from litellm import completion
@@ -33,11 +37,32 @@ response = completion(model="gpt-3.5-turbo", messages=messages)
# cohere call
response = completion("command-nightly", messages)
```
-Need Help / Support : [see troubleshooting](https://litellm.readthedocs.io/en/latest/troubleshoot)
+Code Sample: [Getting Started Notebook](https://colab.research.google.com/drive/1gR3pY-JzDZahzpVdbGBtrNGDBmzUNJaJ?usp=sharing)
-## Why did we build liteLLM
+Stable version
+```
+pip install litellm==0.1.345
+```
+
+## Streaming Queries
+liteLLM supports streaming the model response back, pass `stream=True` to get a streaming iterator in response.
+Streaming is supported for OpenAI, Azure, Anthropic, Huggingface models
+```python
+response = completion(model="gpt-3.5-turbo", messages=messages, stream=True)
+for chunk in response:
+ print(chunk['choices'][0]['delta'])
+
+# claude 2
+result = completion('claude-2', messages, stream=True)
+for chunk in result:
+ print(chunk['choices'][0]['delta'])
+```
+
+# support / talk with founders
+- [Our calendar 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
+- [Community Discord 💭](https://discord.gg/wuPM9dRgDw)
+- Our numbers 📞 +1 (770) 8783-106 / +1 (412) 618-6238
+- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
+
+# why did we build this
- **Need for simplicity**: Our code started to get extremely complicated managing & translating calls between Azure, OpenAI, Cohere
-
-## Support
-* [Meet with us 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
-* Contact us at ishaan@berri.ai / krrish@berri.ai
diff --git a/docs/my-website/static/img/favicon.ico b/docs/my-website/static/img/favicon.ico
index c01d54bcd..88caa2b83 100644
Binary files a/docs/my-website/static/img/favicon.ico and b/docs/my-website/static/img/favicon.ico differ
diff --git a/litellm/__init__.py b/litellm/__init__.py
index 6b8240678..7cbb0e996 100644
--- a/litellm/__init__.py
+++ b/litellm/__init__.py
@@ -1,50 +1,120 @@
import threading
-success_callback = []
-failure_callback = []
-set_verbose=False
-telemetry=True
-max_tokens = 256 # OpenAI Defaults
+from typing import Callable, List, Optional
+input_callback: List[str] = []
+success_callback: List[str] = []
+failure_callback: List[str] = []
+set_verbose = False
+telemetry = True
+max_tokens = 256 # OpenAI Defaults
retry = True
-api_key = None
-openai_key = None
-azure_key = None
-anthropic_key = None
-replicate_key = None
-cohere_key = None
-openrouter_key = None
-huggingface_key = None
-vertex_project = None
-vertex_location = None
-
-hugging_api_token = None
+api_key: Optional[str] = None
+openai_key: Optional[str] = None
+azure_key: Optional[str] = None
+anthropic_key: Optional[str] = None
+replicate_key: Optional[str] = None
+cohere_key: Optional[str] = None
+openrouter_key: Optional[str] = None
+huggingface_key: Optional[str] = None
+vertex_project: Optional[str] = None
+vertex_location: Optional[str] = None
+hugging_api_token: Optional[str] = None
+togetherai_api_key: Optional[str] = None
+caching = False
+caching_with_models = False # if you want the caching key to be model + prompt
model_cost = {
- "gpt-3.5-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-35-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002}, # azure model name
- "gpt-3.5-turbo-0613": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-0301": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-35-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004}, # azure model name
- "gpt-3.5-turbo-16k-0613": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-4": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-0613": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-32k": {"max_tokens": 8000, "input_cost_per_token": 0.00006, "output_cost_per_token": 0.00012},
- "claude-instant-1": {"max_tokens": 100000, "input_cost_per_token": 0.00000163, "output_cost_per_token": 0.00000551},
- "claude-2": {"max_tokens": 100000, "input_cost_per_token": 0.00001102, "output_cost_per_token": 0.00003268},
- "text-bison-001": {"max_tokens": 8192, "input_cost_per_token": 0.000004, "output_cost_per_token": 0.000004},
- "chat-bison-001": {"max_tokens": 4096, "input_cost_per_token": 0.000002, "output_cost_per_token": 0.000002},
- "command-nightly": {"max_tokens": 4096, "input_cost_per_token": 0.000015, "output_cost_per_token": 0.000015},
+ "gpt-3.5-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-35-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ }, # azure model name
+ "gpt-3.5-turbo-0613": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-0301": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-35-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ }, # azure model name
+ "gpt-3.5-turbo-16k-0613": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-4": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-0613": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-32k": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.00006,
+ "output_cost_per_token": 0.00012,
+ },
+ "claude-instant-1": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00000163,
+ "output_cost_per_token": 0.00000551,
+ },
+ "claude-2": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00001102,
+ "output_cost_per_token": 0.00003268,
+ },
+ "text-bison-001": {
+ "max_tokens": 8192,
+ "input_cost_per_token": 0.000004,
+ "output_cost_per_token": 0.000004,
+ },
+ "chat-bison-001": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000002,
+ "output_cost_per_token": 0.000002,
+ },
+ "command-nightly": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000015,
+ "output_cost_per_token": 0.000015,
+ },
}
+
####### THREAD-SPECIFIC DATA ###################
class MyLocal(threading.local):
def __init__(self):
self.user = "Hello World"
+
_thread_context = MyLocal()
+
+
def identify(event_details):
# Store user in thread local data
if "user" in event_details:
_thread_context.user = event_details["user"]
+
+
####### ADDITIONAL PARAMS ################### configurable params if you use proxy models like Helicone, map spend to org id, etc.
api_base = None
headers = None
@@ -55,60 +125,48 @@ config_path = None
secret_manager_client = None
####### COMPLETION MODELS ###################
open_ai_chat_completion_models = [
- "gpt-4",
- "gpt-4-0613",
- "gpt-4-32k",
- "gpt-4-32k-0613",
- #################
- "gpt-3.5-turbo",
- "gpt-3.5-turbo-16k",
- "gpt-3.5-turbo-0613",
- "gpt-3.5-turbo-16k-0613",
-]
-open_ai_text_completion_models = [
- 'text-davinci-003'
+ "gpt-4",
+ "gpt-4-0613",
+ "gpt-4-32k",
+ "gpt-4-32k-0613",
+ #################
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-16k",
+ "gpt-3.5-turbo-0613",
+ "gpt-3.5-turbo-16k-0613",
]
+open_ai_text_completion_models = ["text-davinci-003"]
cohere_models = [
- 'command-nightly',
- "command",
- "command-light",
- "command-medium-beta",
- "command-xlarge-beta"
+ "command-nightly",
+ "command",
+ "command-light",
+ "command-medium-beta",
+ "command-xlarge-beta",
]
-anthropic_models = [
- "claude-2",
- "claude-instant-1",
- "claude-instant-1.2"
-]
+anthropic_models = ["claude-2", "claude-instant-1", "claude-instant-1.2"]
replicate_models = [
"replicate/"
-] # placeholder, to make sure we accept any replicate model in our model_list
+] # placeholder, to make sure we accept any replicate model in our model_list
openrouter_models = [
- 'google/palm-2-codechat-bison',
- 'google/palm-2-chat-bison',
- 'openai/gpt-3.5-turbo',
- 'openai/gpt-3.5-turbo-16k',
- 'openai/gpt-4-32k',
- 'anthropic/claude-2',
- 'anthropic/claude-instant-v1',
- 'meta-llama/llama-2-13b-chat',
- 'meta-llama/llama-2-70b-chat'
+ "google/palm-2-codechat-bison",
+ "google/palm-2-chat-bison",
+ "openai/gpt-3.5-turbo",
+ "openai/gpt-3.5-turbo-16k",
+ "openai/gpt-4-32k",
+ "anthropic/claude-2",
+ "anthropic/claude-instant-v1",
+ "meta-llama/llama-2-13b-chat",
+ "meta-llama/llama-2-70b-chat",
]
-vertex_chat_models = [
- "chat-bison",
- "chat-bison@001"
-]
+vertex_chat_models = ["chat-bison", "chat-bison@001"]
-vertex_text_models = [
- "text-bison",
- "text-bison@001"
-]
+vertex_text_models = ["text-bison", "text-bison@001"]
huggingface_models = [
"meta-llama/Llama-2-7b-hf",
@@ -123,24 +181,56 @@ huggingface_models = [
"meta-llama/Llama-2-13b-chat",
"meta-llama/Llama-2-70b",
"meta-llama/Llama-2-70b-chat",
-] # these have been tested on extensively. But by default all text2text-generation and text-generation models are supported by liteLLM. - https://docs.litellm.ai/docs/completion/supported
+] # these have been tested on extensively. But by default all text2text-generation and text-generation models are supported by liteLLM. - https://docs.litellm.ai/docs/completion/supported
-ai21_models = [
- "j2-ultra",
- "j2-mid",
- "j2-light"
+ai21_models = ["j2-ultra", "j2-mid", "j2-light"]
+
+model_list = (
+ open_ai_chat_completion_models
+ + open_ai_text_completion_models
+ + cohere_models
+ + anthropic_models
+ + replicate_models
+ + openrouter_models
+ + huggingface_models
+ + vertex_chat_models
+ + vertex_text_models
+ + ai21_models
+)
+
+provider_list = [
+ "openai",
+ "cohere",
+ "anthropic",
+ "replicate",
+ "huggingface",
+ "together_ai",
+ "openrouter",
+ "vertex_ai",
+ "ai21",
]
-
-model_list = open_ai_chat_completion_models + open_ai_text_completion_models + cohere_models + anthropic_models + replicate_models + openrouter_models + huggingface_models + vertex_chat_models + vertex_text_models + ai21_models
-
-
####### EMBEDDING MODELS ###################
-open_ai_embedding_models = [
- 'text-embedding-ada-002'
-]
+open_ai_embedding_models = ["text-embedding-ada-002"]
from .timeout import timeout
-from .utils import client, logging, exception_type, get_optional_params, modify_integration, token_counter, cost_per_token, completion_cost, load_test_model, get_litellm_params
-from .main import * # Import all the symbols from main.py
+from .testing import *
+from .utils import (
+ client,
+ exception_type,
+ get_optional_params,
+ modify_integration,
+ token_counter,
+ cost_per_token,
+ completion_cost,
+ get_litellm_params,
+ Logging
+)
+from .main import * # type: ignore
from .integrations import *
-from openai.error import AuthenticationError, InvalidRequestError, RateLimitError, ServiceUnavailableError, OpenAIError
\ No newline at end of file
+from openai.error import (
+ AuthenticationError,
+ InvalidRequestError,
+ RateLimitError,
+ ServiceUnavailableError,
+ OpenAIError,
+)
diff --git a/litellm/__pycache__/__init__.cpython-311.pyc b/litellm/__pycache__/__init__.cpython-311.pyc
index 3a9b2741d..c998bff4a 100644
Binary files a/litellm/__pycache__/__init__.cpython-311.pyc and b/litellm/__pycache__/__init__.cpython-311.pyc differ
diff --git a/litellm/__pycache__/main.cpython-311.pyc b/litellm/__pycache__/main.cpython-311.pyc
index afc8469fd..f9e652468 100644
Binary files a/litellm/__pycache__/main.cpython-311.pyc and b/litellm/__pycache__/main.cpython-311.pyc differ
diff --git a/litellm/__pycache__/timeout.cpython-311.pyc b/litellm/__pycache__/timeout.cpython-311.pyc
index 09f976993..68f0223aa 100644
Binary files a/litellm/__pycache__/timeout.cpython-311.pyc and b/litellm/__pycache__/timeout.cpython-311.pyc differ
diff --git a/litellm/__pycache__/utils.cpython-311.pyc b/litellm/__pycache__/utils.cpython-311.pyc
index 92c3af615..05731d21d 100644
Binary files a/litellm/__pycache__/utils.cpython-311.pyc and b/litellm/__pycache__/utils.cpython-311.pyc differ
diff --git a/litellm/exceptions.py b/litellm/exceptions.py
new file mode 100644
index 000000000..7b48a343d
--- /dev/null
+++ b/litellm/exceptions.py
@@ -0,0 +1,62 @@
+## LiteLLM versions of the OpenAI Exception Types
+from openai.error import (
+ AuthenticationError,
+ InvalidRequestError,
+ RateLimitError,
+ ServiceUnavailableError,
+ OpenAIError,
+)
+
+
+class AuthenticationError(AuthenticationError): # type: ignore
+ def __init__(self, message, llm_provider):
+ self.status_code = 401
+ self.message = message
+ self.llm_provider = llm_provider
+ super().__init__(
+ self.message
+ ) # Call the base class constructor with the parameters it needs
+
+
+class InvalidRequestError(InvalidRequestError): # type: ignore
+ def __init__(self, message, model, llm_provider):
+ self.status_code = 400
+ self.message = message
+ self.model = model
+ self.llm_provider = llm_provider
+ super().__init__(
+ self.message, f"{self.model}"
+ ) # Call the base class constructor with the parameters it needs
+
+
+class RateLimitError(RateLimitError): # type: ignore
+ def __init__(self, message, llm_provider):
+ self.status_code = 429
+ self.message = message
+ self.llm_provider = llm_provider
+ super().__init__(
+ self.message
+ ) # Call the base class constructor with the parameters it needs
+
+
+class ServiceUnavailableError(ServiceUnavailableError): # type: ignore
+ def __init__(self, message, llm_provider):
+ self.status_code = 500
+ self.message = message
+ self.llm_provider = llm_provider
+ super().__init__(
+ self.message
+ ) # Call the base class constructor with the parameters it needs
+
+
+class OpenAIError(OpenAIError): # type: ignore
+ def __init__(self, original_exception):
+ self.status_code = original_exception.http_status
+ super().__init__(
+ http_body=original_exception.http_body,
+ http_status=original_exception.http_status,
+ json_body=original_exception.json_body,
+ headers=original_exception.headers,
+ code=original_exception.code,
+ )
+ self.llm_provider = "openai"
diff --git a/litellm/integrations/__init__.py b/litellm/integrations/__init__.py
index b9742821a..b6e690fd5 100644
--- a/litellm/integrations/__init__.py
+++ b/litellm/integrations/__init__.py
@@ -1 +1 @@
-from . import *
\ No newline at end of file
+from . import *
diff --git a/litellm/integrations/__pycache__/__init__.cpython-311.pyc b/litellm/integrations/__pycache__/__init__.cpython-311.pyc
index e951c50a3..5bd4bfeb8 100644
Binary files a/litellm/integrations/__pycache__/__init__.cpython-311.pyc and b/litellm/integrations/__pycache__/__init__.cpython-311.pyc differ
diff --git a/litellm/integrations/__pycache__/aispend.cpython-311.pyc b/litellm/integrations/__pycache__/aispend.cpython-311.pyc
index 9e2d468cb..111b4eba1 100644
Binary files a/litellm/integrations/__pycache__/aispend.cpython-311.pyc and b/litellm/integrations/__pycache__/aispend.cpython-311.pyc differ
diff --git a/litellm/integrations/__pycache__/berrispend.cpython-311.pyc b/litellm/integrations/__pycache__/berrispend.cpython-311.pyc
index 87b3f5e36..616f227eb 100644
Binary files a/litellm/integrations/__pycache__/berrispend.cpython-311.pyc and b/litellm/integrations/__pycache__/berrispend.cpython-311.pyc differ
diff --git a/litellm/integrations/__pycache__/helicone.cpython-311.pyc b/litellm/integrations/__pycache__/helicone.cpython-311.pyc
index 03de753b4..972c339ed 100644
Binary files a/litellm/integrations/__pycache__/helicone.cpython-311.pyc and b/litellm/integrations/__pycache__/helicone.cpython-311.pyc differ
diff --git a/litellm/integrations/__pycache__/supabase.cpython-311.pyc b/litellm/integrations/__pycache__/supabase.cpython-311.pyc
index c3f60037e..26cdede8f 100644
Binary files a/litellm/integrations/__pycache__/supabase.cpython-311.pyc and b/litellm/integrations/__pycache__/supabase.cpython-311.pyc differ
diff --git a/litellm/integrations/aispend.py b/litellm/integrations/aispend.py
index 6723a6227..2015d45dd 100644
--- a/litellm/integrations/aispend.py
+++ b/litellm/integrations/aispend.py
@@ -1,53 +1,121 @@
#### What this does ####
-# On success + failure, log events to aispend.io
+# On success + failure, log events to aispend.io
import dotenv, os
import requests
-dotenv.load_dotenv() # Loading env variables using dotenv
+
+dotenv.load_dotenv() # Loading env variables using dotenv
import traceback
import datetime
model_cost = {
- "gpt-3.5-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-35-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002}, # azure model name
- "gpt-3.5-turbo-0613": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-0301": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-35-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004}, # azure model name
- "gpt-3.5-turbo-16k-0613": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-4": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-0613": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-32k": {"max_tokens": 8000, "input_cost_per_token": 0.00006, "output_cost_per_token": 0.00012},
- "claude-instant-1": {"max_tokens": 100000, "input_cost_per_token": 0.00000163, "output_cost_per_token": 0.00000551},
- "claude-2": {"max_tokens": 100000, "input_cost_per_token": 0.00001102, "output_cost_per_token": 0.00003268},
- "text-bison-001": {"max_tokens": 8192, "input_cost_per_token": 0.000004, "output_cost_per_token": 0.000004},
- "chat-bison-001": {"max_tokens": 4096, "input_cost_per_token": 0.000002, "output_cost_per_token": 0.000002},
- "command-nightly": {"max_tokens": 4096, "input_cost_per_token": 0.000015, "output_cost_per_token": 0.000015},
+ "gpt-3.5-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-35-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ }, # azure model name
+ "gpt-3.5-turbo-0613": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-0301": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-35-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ }, # azure model name
+ "gpt-3.5-turbo-16k-0613": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-4": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-0613": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-32k": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.00006,
+ "output_cost_per_token": 0.00012,
+ },
+ "claude-instant-1": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00000163,
+ "output_cost_per_token": 0.00000551,
+ },
+ "claude-2": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00001102,
+ "output_cost_per_token": 0.00003268,
+ },
+ "text-bison-001": {
+ "max_tokens": 8192,
+ "input_cost_per_token": 0.000004,
+ "output_cost_per_token": 0.000004,
+ },
+ "chat-bison-001": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000002,
+ "output_cost_per_token": 0.000002,
+ },
+ "command-nightly": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000015,
+ "output_cost_per_token": 0.000015,
+ },
}
+
class AISpendLogger:
# Class variables or attributes
def __init__(self):
# Instance variables
self.account_id = os.getenv("AISPEND_ACCOUNT_ID")
self.api_key = os.getenv("AISPEND_API_KEY")
-
+
def price_calculator(self, model, response_obj, start_time, end_time):
# try and find if the model is in the model_cost map
# else default to the average of the costs
prompt_tokens_cost_usd_dollar = 0
completion_tokens_cost_usd_dollar = 0
if model in model_cost:
- prompt_tokens_cost_usd_dollar = model_cost[model]["input_cost_per_token"] * response_obj["usage"]["prompt_tokens"]
- completion_tokens_cost_usd_dollar = model_cost[model]["output_cost_per_token"] * response_obj["usage"]["completion_tokens"]
- elif "replicate" in model:
+ prompt_tokens_cost_usd_dollar = (
+ model_cost[model]["input_cost_per_token"]
+ * response_obj["usage"]["prompt_tokens"]
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost[model]["output_cost_per_token"]
+ * response_obj["usage"]["completion_tokens"]
+ )
+ elif "replicate" in model:
# replicate models are charged based on time
# llama 2 runs on an nvidia a100 which costs $0.0032 per second - https://replicate.com/replicate/llama-2-70b-chat
- model_run_time = end_time - start_time # assuming time in seconds
+ model_run_time = end_time - start_time # assuming time in seconds
cost_usd_dollar = model_run_time * 0.0032
prompt_tokens_cost_usd_dollar = cost_usd_dollar / 2
completion_tokens_cost_usd_dollar = cost_usd_dollar / 2
else:
- # calculate average input cost
+ # calculate average input cost
input_cost_sum = 0
output_cost_sum = 0
for model in model_cost:
@@ -55,37 +123,52 @@ class AISpendLogger:
output_cost_sum += model_cost[model]["output_cost_per_token"]
avg_input_cost = input_cost_sum / len(model_cost.keys())
avg_output_cost = output_cost_sum / len(model_cost.keys())
- prompt_tokens_cost_usd_dollar = model_cost[model]["input_cost_per_token"] * response_obj["usage"]["prompt_tokens"]
- completion_tokens_cost_usd_dollar = model_cost[model]["output_cost_per_token"] * response_obj["usage"]["completion_tokens"]
+ prompt_tokens_cost_usd_dollar = (
+ model_cost[model]["input_cost_per_token"]
+ * response_obj["usage"]["prompt_tokens"]
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost[model]["output_cost_per_token"]
+ * response_obj["usage"]["completion_tokens"]
+ )
return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
-
+
def log_event(self, model, response_obj, start_time, end_time, print_verbose):
# Method definition
try:
- print_verbose(f"AISpend Logging - Enters logging function for model {model}")
+ print_verbose(
+ f"AISpend Logging - Enters logging function for model {model}"
+ )
url = f"https://aispend.io/api/v1/accounts/{self.account_id}/data"
headers = {
- 'Authorization': f'Bearer {self.api_key}',
- 'Content-Type': 'application/json'
+ "Authorization": f"Bearer {self.api_key}",
+ "Content-Type": "application/json",
}
- response_timestamp = datetime.datetime.fromtimestamp(int(response_obj["created"])).strftime('%Y-%m-%d')
+ response_timestamp = datetime.datetime.fromtimestamp(
+ int(response_obj["created"])
+ ).strftime("%Y-%m-%d")
- prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = self.price_calculator(model, response_obj, start_time, end_time)
+ (
+ prompt_tokens_cost_usd_dollar,
+ completion_tokens_cost_usd_dollar,
+ ) = self.price_calculator(model, response_obj, start_time, end_time)
prompt_tokens_cost_usd_cent = prompt_tokens_cost_usd_dollar * 100
completion_tokens_cost_usd_cent = completion_tokens_cost_usd_dollar * 100
- data = [{
- "requests": 1,
- "requests_context": 1,
- "context_tokens": response_obj["usage"]["prompt_tokens"],
- "requests_generated": 1,
- "generated_tokens": response_obj["usage"]["completion_tokens"],
- "recorded_date": response_timestamp,
- "model_id": response_obj["model"],
- "generated_tokens_cost_usd_cent": prompt_tokens_cost_usd_cent,
- "context_tokens_cost_usd_cent": completion_tokens_cost_usd_cent
- }]
+ data = [
+ {
+ "requests": 1,
+ "requests_context": 1,
+ "context_tokens": response_obj["usage"]["prompt_tokens"],
+ "requests_generated": 1,
+ "generated_tokens": response_obj["usage"]["completion_tokens"],
+ "recorded_date": response_timestamp,
+ "model_id": response_obj["model"],
+ "generated_tokens_cost_usd_cent": prompt_tokens_cost_usd_cent,
+ "context_tokens_cost_usd_cent": completion_tokens_cost_usd_cent,
+ }
+ ]
print_verbose(f"AISpend Logging - final data object: {data}")
except:
diff --git a/litellm/integrations/berrispend.py b/litellm/integrations/berrispend.py
index 1742bfed7..7d91ffca7 100644
--- a/litellm/integrations/berrispend.py
+++ b/litellm/integrations/berrispend.py
@@ -1,52 +1,120 @@
#### What this does ####
-# On success + failure, log events to aispend.io
+# On success + failure, log events to aispend.io
import dotenv, os
import requests
-dotenv.load_dotenv() # Loading env variables using dotenv
+
+dotenv.load_dotenv() # Loading env variables using dotenv
import traceback
import datetime
model_cost = {
- "gpt-3.5-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-35-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002}, # azure model name
- "gpt-3.5-turbo-0613": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-0301": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-35-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004}, # azure model name
- "gpt-3.5-turbo-16k-0613": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-4": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-0613": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-32k": {"max_tokens": 8000, "input_cost_per_token": 0.00006, "output_cost_per_token": 0.00012},
- "claude-instant-1": {"max_tokens": 100000, "input_cost_per_token": 0.00000163, "output_cost_per_token": 0.00000551},
- "claude-2": {"max_tokens": 100000, "input_cost_per_token": 0.00001102, "output_cost_per_token": 0.00003268},
- "text-bison-001": {"max_tokens": 8192, "input_cost_per_token": 0.000004, "output_cost_per_token": 0.000004},
- "chat-bison-001": {"max_tokens": 4096, "input_cost_per_token": 0.000002, "output_cost_per_token": 0.000002},
- "command-nightly": {"max_tokens": 4096, "input_cost_per_token": 0.000015, "output_cost_per_token": 0.000015},
+ "gpt-3.5-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-35-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ }, # azure model name
+ "gpt-3.5-turbo-0613": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-0301": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-35-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ }, # azure model name
+ "gpt-3.5-turbo-16k-0613": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-4": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-0613": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-32k": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.00006,
+ "output_cost_per_token": 0.00012,
+ },
+ "claude-instant-1": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00000163,
+ "output_cost_per_token": 0.00000551,
+ },
+ "claude-2": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00001102,
+ "output_cost_per_token": 0.00003268,
+ },
+ "text-bison-001": {
+ "max_tokens": 8192,
+ "input_cost_per_token": 0.000004,
+ "output_cost_per_token": 0.000004,
+ },
+ "chat-bison-001": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000002,
+ "output_cost_per_token": 0.000002,
+ },
+ "command-nightly": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000015,
+ "output_cost_per_token": 0.000015,
+ },
}
+
class BerriSpendLogger:
# Class variables or attributes
def __init__(self):
# Instance variables
self.account_id = os.getenv("BERRISPEND_ACCOUNT_ID")
-
+
def price_calculator(self, model, response_obj, start_time, end_time):
# try and find if the model is in the model_cost map
# else default to the average of the costs
prompt_tokens_cost_usd_dollar = 0
completion_tokens_cost_usd_dollar = 0
if model in model_cost:
- prompt_tokens_cost_usd_dollar = model_cost[model]["input_cost_per_token"] * response_obj["usage"]["prompt_tokens"]
- completion_tokens_cost_usd_dollar = model_cost[model]["output_cost_per_token"] * response_obj["usage"]["completion_tokens"]
- elif "replicate" in model:
+ prompt_tokens_cost_usd_dollar = (
+ model_cost[model]["input_cost_per_token"]
+ * response_obj["usage"]["prompt_tokens"]
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost[model]["output_cost_per_token"]
+ * response_obj["usage"]["completion_tokens"]
+ )
+ elif "replicate" in model:
# replicate models are charged based on time
# llama 2 runs on an nvidia a100 which costs $0.0032 per second - https://replicate.com/replicate/llama-2-70b-chat
- model_run_time = end_time - start_time # assuming time in seconds
+ model_run_time = end_time - start_time # assuming time in seconds
cost_usd_dollar = model_run_time * 0.0032
prompt_tokens_cost_usd_dollar = cost_usd_dollar / 2
completion_tokens_cost_usd_dollar = cost_usd_dollar / 2
else:
- # calculate average input cost
+ # calculate average input cost
input_cost_sum = 0
output_cost_sum = 0
for model in model_cost:
@@ -54,42 +122,59 @@ class BerriSpendLogger:
output_cost_sum += model_cost[model]["output_cost_per_token"]
avg_input_cost = input_cost_sum / len(model_cost.keys())
avg_output_cost = output_cost_sum / len(model_cost.keys())
- prompt_tokens_cost_usd_dollar = model_cost[model]["input_cost_per_token"] * response_obj["usage"]["prompt_tokens"]
- completion_tokens_cost_usd_dollar = model_cost[model]["output_cost_per_token"] * response_obj["usage"]["completion_tokens"]
+ prompt_tokens_cost_usd_dollar = (
+ model_cost[model]["input_cost_per_token"]
+ * response_obj["usage"]["prompt_tokens"]
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost[model]["output_cost_per_token"]
+ * response_obj["usage"]["completion_tokens"]
+ )
return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
-
- def log_event(self, model, messages, response_obj, start_time, end_time, print_verbose):
+
+ def log_event(
+ self, model, messages, response_obj, start_time, end_time, print_verbose
+ ):
# Method definition
try:
- print_verbose(f"BerriSpend Logging - Enters logging function for model {model}")
+ print_verbose(
+ f"BerriSpend Logging - Enters logging function for model {model}"
+ )
url = f"https://berrispend.berri.ai/spend"
- headers = {
- 'Content-Type': 'application/json'
- }
+ headers = {"Content-Type": "application/json"}
- prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = self.price_calculator(model, response_obj, start_time, end_time)
- total_cost = prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
+ (
+ prompt_tokens_cost_usd_dollar,
+ completion_tokens_cost_usd_dollar,
+ ) = self.price_calculator(model, response_obj, start_time, end_time)
+ total_cost = (
+ prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
+ )
- response_time = (end_time-start_time).total_seconds()
+ response_time = (end_time - start_time).total_seconds()
if "response" in response_obj:
- data = [{
- "response_time": response_time,
- "model_id": response_obj["model"],
- "total_cost": total_cost,
- "messages": messages,
- "response": response_obj['choices'][0]['message']['content'],
- "account_id": self.account_id
- }]
+ data = [
+ {
+ "response_time": response_time,
+ "model_id": response_obj["model"],
+ "total_cost": total_cost,
+ "messages": messages,
+ "response": response_obj["choices"][0]["message"]["content"],
+ "account_id": self.account_id,
+ }
+ ]
elif "error" in response_obj:
- data = [{
- "response_time": response_time,
- "model_id": response_obj["model"],
- "total_cost": total_cost,
- "messages": messages,
- "error": response_obj['error'],
- "account_id": self.account_id
- }]
+ data = [
+ {
+ "response_time": response_time,
+ "model_id": response_obj["model"],
+ "total_cost": total_cost,
+ "messages": messages,
+ "error": response_obj["error"],
+ "account_id": self.account_id,
+ }
+ ]
print_verbose(f"BerriSpend Logging - final data object: {data}")
response = requests.post(url, headers=headers, json=data)
diff --git a/litellm/integrations/helicone.py b/litellm/integrations/helicone.py
index 9e74b246f..f9dff85db 100644
--- a/litellm/integrations/helicone.py
+++ b/litellm/integrations/helicone.py
@@ -2,19 +2,24 @@
# On success, logs events to Helicone
import dotenv, os
import requests
-dotenv.load_dotenv() # Loading env variables using dotenv
+
+dotenv.load_dotenv() # Loading env variables using dotenv
import traceback
+
+
class HeliconeLogger:
# Class variables or attributes
helicone_model_list = ["gpt", "claude"]
+
def __init__(self):
# Instance variables
self.provider_url = "https://api.openai.com/v1"
- self.key = os.getenv('HELICONE_API_KEY')
+ self.key = os.getenv("HELICONE_API_KEY")
def claude_mapping(self, model, messages, response_obj):
from anthropic import HUMAN_PROMPT, AI_PROMPT
- prompt = f"{HUMAN_PROMPT}"
+
+ prompt = f"{HUMAN_PROMPT}"
for message in messages:
if "role" in message:
if message["role"] == "user":
@@ -26,48 +31,84 @@ class HeliconeLogger:
prompt += f"{AI_PROMPT}"
claude_provider_request = {"model": model, "prompt": prompt}
- claude_response_obj = {"completion": response_obj['choices'][0]['message']['content'], "model": model, "stop_reason": "stop_sequence"}
+ claude_response_obj = {
+ "completion": response_obj["choices"][0]["message"]["content"],
+ "model": model,
+ "stop_reason": "stop_sequence",
+ }
return claude_provider_request, claude_response_obj
-
- def log_success(self, model, messages, response_obj, start_time, end_time, print_verbose):
+
+ def log_success(
+ self, model, messages, response_obj, start_time, end_time, print_verbose
+ ):
# Method definition
try:
- print_verbose(f"Helicone Logging - Enters logging function for model {model}")
- model = model if any(accepted_model in model for accepted_model in self.helicone_model_list) else "gpt-3.5-turbo"
+ print_verbose(
+ f"Helicone Logging - Enters logging function for model {model}"
+ )
+ model = (
+ model
+ if any(
+ accepted_model in model
+ for accepted_model in self.helicone_model_list
+ )
+ else "gpt-3.5-turbo"
+ )
provider_request = {"model": model, "messages": messages}
- if "claude" in model:
- provider_request, response_obj = self.claude_mapping(model=model, messages=messages, response_obj=response_obj)
+ if "claude" in model:
+ provider_request, response_obj = self.claude_mapping(
+ model=model, messages=messages, response_obj=response_obj
+ )
providerResponse = {
- "json": response_obj,
- "headers": {"openai-version": "2020-10-01"},
- "status": 200
+ "json": response_obj,
+ "headers": {"openai-version": "2020-10-01"},
+ "status": 200,
}
# Code to be executed
url = "https://api.hconeai.com/oai/v1/log"
headers = {
- 'Authorization': f'Bearer {self.key}',
- 'Content-Type': 'application/json'
+ "Authorization": f"Bearer {self.key}",
+ "Content-Type": "application/json",
}
start_time_seconds = int(start_time.timestamp())
- start_time_milliseconds = int((start_time.timestamp() - start_time_seconds) * 1000)
+ start_time_milliseconds = int(
+ (start_time.timestamp() - start_time_seconds) * 1000
+ )
end_time_seconds = int(end_time.timestamp())
- end_time_milliseconds = int((end_time.timestamp() - end_time_seconds) * 1000)
+ end_time_milliseconds = int(
+ (end_time.timestamp() - end_time_seconds) * 1000
+ )
data = {
- "providerRequest": {"url": self.provider_url, "json": provider_request, "meta": {"Helicone-Auth": f"Bearer {self.key}"}},
+ "providerRequest": {
+ "url": self.provider_url,
+ "json": provider_request,
+ "meta": {"Helicone-Auth": f"Bearer {self.key}"},
+ },
"providerResponse": providerResponse,
- "timing": {"startTime": {"seconds": start_time_seconds, "milliseconds": start_time_milliseconds}, "endTime": {"seconds": end_time_seconds, "milliseconds": end_time_milliseconds}} # {"seconds": .., "milliseconds": ..}
+ "timing": {
+ "startTime": {
+ "seconds": start_time_seconds,
+ "milliseconds": start_time_milliseconds,
+ },
+ "endTime": {
+ "seconds": end_time_seconds,
+ "milliseconds": end_time_milliseconds,
+ },
+ }, # {"seconds": .., "milliseconds": ..}
}
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
print_verbose("Helicone Logging - Success!")
else:
- print_verbose(f"Helicone Logging - Error Request was not successful. Status Code: {response.status_code}")
+ print_verbose(
+ f"Helicone Logging - Error Request was not successful. Status Code: {response.status_code}"
+ )
print_verbose(f"Helicone Logging - Error {response.text}")
except:
# traceback.print_exc()
print_verbose(f"Helicone Logging Error - {traceback.format_exc()}")
- pass
\ No newline at end of file
+ pass
diff --git a/litellm/integrations/litedebugger.py b/litellm/integrations/litedebugger.py
new file mode 100644
index 000000000..a42692a3e
--- /dev/null
+++ b/litellm/integrations/litedebugger.py
@@ -0,0 +1,74 @@
+import requests, traceback, json
+class LiteDebugger:
+ def __init__(self):
+ self.api_url = "https://api.litellm.ai/debugger"
+ pass
+
+ def input_log_event(self, model, messages, end_user, litellm_call_id, print_verbose):
+ try:
+ print_verbose(
+ f"LiteLLMDebugger: Logging - Enters input logging function for model {model}"
+ )
+ litellm_data_obj = {
+ "model": model,
+ "messages": messages,
+ "end_user": end_user,
+ "status": "initiated",
+ "litellm_call_id": litellm_call_id
+ }
+ response = requests.post(url=self.api_url, headers={"content-type": "application/json"}, data=json.dumps(litellm_data_obj))
+ print_verbose(f"LiteDebugger: api response - {response.text}")
+ except:
+ print_verbose(f"LiteDebugger: Logging Error - {traceback.format_exc()}")
+ pass
+
+ def log_event(self, model,
+ messages,
+ end_user,
+ response_obj,
+ start_time,
+ end_time,
+ litellm_call_id,
+ print_verbose,):
+ try:
+ print_verbose(
+ f"LiteLLMDebugger: Logging - Enters input logging function for model {model}"
+ )
+ total_cost = 0 # [TODO] implement cost tracking
+ response_time = (end_time - start_time).total_seconds()
+ if "choices" in response_obj:
+ litellm_data_obj = {
+ "response_time": response_time,
+ "model": response_obj["model"],
+ "total_cost": total_cost,
+ "messages": messages,
+ "response": response_obj["choices"][0]["message"]["content"],
+ "end_user": end_user,
+ "litellm_call_id": litellm_call_id,
+ "status": "success"
+ }
+ print_verbose(
+ f"LiteDebugger: Logging - final data object: {litellm_data_obj}"
+ )
+ response = requests.post(url=self.api_url, headers={"content-type": "application/json"}, data=json.dumps(litellm_data_obj))
+ elif "error" in response_obj:
+ if "Unable to map your input to a model." in response_obj["error"]:
+ total_cost = 0
+ litellm_data_obj = {
+ "response_time": response_time,
+ "model": response_obj["model"],
+ "total_cost": total_cost,
+ "messages": messages,
+ "error": response_obj["error"],
+ "end_user": end_user,
+ "litellm_call_id": litellm_call_id,
+ "status": "failure"
+ }
+ print_verbose(
+ f"LiteDebugger: Logging - final data object: {litellm_data_obj}"
+ )
+ response = requests.post(url=self.api_url, headers={"content-type": "application/json"}, data=json.dumps(litellm_data_obj))
+ print_verbose(f"LiteDebugger: api response - {response.text}")
+ except:
+ print_verbose(f"LiteDebugger: Logging Error - {traceback.format_exc()}")
+ pass
\ No newline at end of file
diff --git a/litellm/integrations/supabase.py b/litellm/integrations/supabase.py
index 1ac28763f..3ea63f5c6 100644
--- a/litellm/integrations/supabase.py
+++ b/litellm/integrations/supabase.py
@@ -3,31 +3,94 @@
import dotenv, os
import requests
-dotenv.load_dotenv() # Loading env variables using dotenv
+
+dotenv.load_dotenv() # Loading env variables using dotenv
import traceback
import datetime, subprocess, sys
model_cost = {
- "gpt-3.5-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-35-turbo": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002}, # azure model name
- "gpt-3.5-turbo-0613": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-0301": {"max_tokens": 4000, "input_cost_per_token": 0.0000015, "output_cost_per_token": 0.000002},
- "gpt-3.5-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-35-turbo-16k": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004}, # azure model name
- "gpt-3.5-turbo-16k-0613": {"max_tokens": 16000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.000004},
- "gpt-4": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-0613": {"max_tokens": 8000, "input_cost_per_token": 0.000003, "output_cost_per_token": 0.00006},
- "gpt-4-32k": {"max_tokens": 8000, "input_cost_per_token": 0.00006, "output_cost_per_token": 0.00012},
- "claude-instant-1": {"max_tokens": 100000, "input_cost_per_token": 0.00000163, "output_cost_per_token": 0.00000551},
- "claude-2": {"max_tokens": 100000, "input_cost_per_token": 0.00001102, "output_cost_per_token": 0.00003268},
- "text-bison-001": {"max_tokens": 8192, "input_cost_per_token": 0.000004, "output_cost_per_token": 0.000004},
- "chat-bison-001": {"max_tokens": 4096, "input_cost_per_token": 0.000002, "output_cost_per_token": 0.000002},
- "command-nightly": {"max_tokens": 4096, "input_cost_per_token": 0.000015, "output_cost_per_token": 0.000015},
+ "gpt-3.5-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-35-turbo": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ }, # azure model name
+ "gpt-3.5-turbo-0613": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-0301": {
+ "max_tokens": 4000,
+ "input_cost_per_token": 0.0000015,
+ "output_cost_per_token": 0.000002,
+ },
+ "gpt-3.5-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-35-turbo-16k": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ }, # azure model name
+ "gpt-3.5-turbo-16k-0613": {
+ "max_tokens": 16000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.000004,
+ },
+ "gpt-4": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-0613": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.000003,
+ "output_cost_per_token": 0.00006,
+ },
+ "gpt-4-32k": {
+ "max_tokens": 8000,
+ "input_cost_per_token": 0.00006,
+ "output_cost_per_token": 0.00012,
+ },
+ "claude-instant-1": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00000163,
+ "output_cost_per_token": 0.00000551,
+ },
+ "claude-2": {
+ "max_tokens": 100000,
+ "input_cost_per_token": 0.00001102,
+ "output_cost_per_token": 0.00003268,
+ },
+ "text-bison-001": {
+ "max_tokens": 8192,
+ "input_cost_per_token": 0.000004,
+ "output_cost_per_token": 0.000004,
+ },
+ "chat-bison-001": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000002,
+ "output_cost_per_token": 0.000002,
+ },
+ "command-nightly": {
+ "max_tokens": 4096,
+ "input_cost_per_token": 0.000015,
+ "output_cost_per_token": 0.000015,
+ },
}
+
class Supabase:
# Class variables or attributes
supabase_table_name = "request_logs"
+
def __init__(self):
# Instance variables
self.supabase_url = os.getenv("SUPABASE_URL")
@@ -35,9 +98,11 @@ class Supabase:
try:
import supabase
except ImportError:
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'supabase'])
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "supabase"])
import supabase
- self.supabase_client = supabase.create_client(self.supabase_url, self.supabase_key)
+ self.supabase_client = supabase.create_client(
+ self.supabase_url, self.supabase_key
+ )
def price_calculator(self, model, response_obj, start_time, end_time):
# try and find if the model is in the model_cost map
@@ -45,17 +110,23 @@ class Supabase:
prompt_tokens_cost_usd_dollar = 0
completion_tokens_cost_usd_dollar = 0
if model in model_cost:
- prompt_tokens_cost_usd_dollar = model_cost[model]["input_cost_per_token"] * response_obj["usage"]["prompt_tokens"]
- completion_tokens_cost_usd_dollar = model_cost[model]["output_cost_per_token"] * response_obj["usage"]["completion_tokens"]
- elif "replicate" in model:
+ prompt_tokens_cost_usd_dollar = (
+ model_cost[model]["input_cost_per_token"]
+ * response_obj["usage"]["prompt_tokens"]
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost[model]["output_cost_per_token"]
+ * response_obj["usage"]["completion_tokens"]
+ )
+ elif "replicate" in model:
# replicate models are charged based on time
# llama 2 runs on an nvidia a100 which costs $0.0032 per second - https://replicate.com/replicate/llama-2-70b-chat
- model_run_time = end_time - start_time # assuming time in seconds
+ model_run_time = end_time - start_time # assuming time in seconds
cost_usd_dollar = model_run_time * 0.0032
prompt_tokens_cost_usd_dollar = cost_usd_dollar / 2
completion_tokens_cost_usd_dollar = cost_usd_dollar / 2
else:
- # calculate average input cost
+ # calculate average input cost
input_cost_sum = 0
output_cost_sum = 0
for model in model_cost:
@@ -63,41 +134,104 @@ class Supabase:
output_cost_sum += model_cost[model]["output_cost_per_token"]
avg_input_cost = input_cost_sum / len(model_cost.keys())
avg_output_cost = output_cost_sum / len(model_cost.keys())
- prompt_tokens_cost_usd_dollar = model_cost[model]["input_cost_per_token"] * response_obj["usage"]["prompt_tokens"]
- completion_tokens_cost_usd_dollar = model_cost[model]["output_cost_per_token"] * response_obj["usage"]["completion_tokens"]
+ prompt_tokens_cost_usd_dollar = (
+ model_cost[model]["input_cost_per_token"]
+ * response_obj["usage"]["prompt_tokens"]
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost[model]["output_cost_per_token"]
+ * response_obj["usage"]["completion_tokens"]
+ )
return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
-
- def log_event(self, model, messages, end_user, response_obj, start_time, end_time, print_verbose):
+
+ def input_log_event(self, model, messages, end_user, litellm_call_id, print_verbose):
try:
- print_verbose(f"Supabase Logging - Enters logging function for model {model}, response_obj: {response_obj}")
+ print_verbose(
+ f"Supabase Logging - Enters input logging function for model {model}"
+ )
+ supabase_data_obj = {
+ "model": model,
+ "messages": messages,
+ "end_user": end_user,
+ "status": "initiated",
+ "litellm_call_id": litellm_call_id
+ }
+ data, count = (
+ self.supabase_client.table(self.supabase_table_name)
+ .insert(supabase_data_obj)
+ .execute()
+ )
+ print(f"data: {data}")
+ except:
+ print_verbose(f"Supabase Logging Error - {traceback.format_exc()}")
+ pass
- prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = self.price_calculator(model, response_obj, start_time, end_time)
- total_cost = prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
+ def log_event(
+ self,
+ model,
+ messages,
+ end_user,
+ response_obj,
+ start_time,
+ end_time,
+ litellm_call_id,
+ print_verbose,
+ ):
+ try:
+ print_verbose(
+ f"Supabase Logging - Enters logging function for model {model}, response_obj: {response_obj}"
+ )
- response_time = (end_time-start_time).total_seconds()
+ (
+ prompt_tokens_cost_usd_dollar,
+ completion_tokens_cost_usd_dollar,
+ ) = self.price_calculator(model, response_obj, start_time, end_time)
+ total_cost = (
+ prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
+ )
+
+ response_time = (end_time - start_time).total_seconds()
if "choices" in response_obj:
supabase_data_obj = {
"response_time": response_time,
"model": response_obj["model"],
- "total_cost": total_cost,
+ "total_cost": total_cost,
"messages": messages,
- "response": response_obj['choices'][0]['message']['content'],
- "end_user": end_user
+ "response": response_obj["choices"][0]["message"]["content"],
+ "end_user": end_user,
+ "litellm_call_id": litellm_call_id,
+ "status": "success"
}
- print_verbose(f"Supabase Logging - final data object: {supabase_data_obj}")
- data, count = self.supabase_client.table(self.supabase_table_name).insert(supabase_data_obj).execute()
+ print_verbose(
+ f"Supabase Logging - final data object: {supabase_data_obj}"
+ )
+ data, count = (
+ self.supabase_client.table(self.supabase_table_name)
+ .upsert(supabase_data_obj)
+ .execute()
+ )
elif "error" in response_obj:
+ if "Unable to map your input to a model." in response_obj["error"]:
+ total_cost = 0
supabase_data_obj = {
"response_time": response_time,
"model": response_obj["model"],
- "total_cost": total_cost,
+ "total_cost": total_cost,
"messages": messages,
- "error": response_obj['error'],
- "end_user": end_user
+ "error": response_obj["error"],
+ "end_user": end_user,
+ "litellm_call_id": litellm_call_id,
+ "status": "failure"
}
- print_verbose(f"Supabase Logging - final data object: {supabase_data_obj}")
- data, count = self.supabase_client.table(self.supabase_table_name).insert(supabase_data_obj).execute()
-
+ print_verbose(
+ f"Supabase Logging - final data object: {supabase_data_obj}"
+ )
+ data, count = (
+ self.supabase_client.table(self.supabase_table_name)
+ .upsert(supabase_data_obj)
+ .execute()
+ )
+
except:
# traceback.print_exc()
print_verbose(f"Supabase Logging Error - {traceback.format_exc()}")
diff --git a/litellm/llms/__init__.py b/litellm/llms/__init__.py
index b9742821a..b6e690fd5 100644
--- a/litellm/llms/__init__.py
+++ b/litellm/llms/__init__.py
@@ -1 +1 @@
-from . import *
\ No newline at end of file
+from . import *
diff --git a/litellm/llms/anthropic.py b/litellm/llms/anthropic.py
index c617a0ae0..fecc655f2 100644
--- a/litellm/llms/anthropic.py
+++ b/litellm/llms/anthropic.py
@@ -1,59 +1,78 @@
import os, json
from enum import Enum
import requests
-from litellm import logging
-import time
+import time
from typing import Callable
+from litellm.utils import ModelResponse
+
class AnthropicConstants(Enum):
HUMAN_PROMPT = "\n\nHuman:"
AI_PROMPT = "\n\nAssistant:"
+
class AnthropicError(Exception):
def __init__(self, status_code, message):
self.status_code = status_code
self.message = message
- super().__init__(self.message) # Call the base class constructor with the parameters it needs
+ super().__init__(
+ self.message
+ ) # Call the base class constructor with the parameters it needs
-class AnthropicLLM:
-
- def __init__(self, encoding, default_max_tokens_to_sample, api_key=None):
+
+class AnthropicLLM:
+ def __init__(self, encoding, default_max_tokens_to_sample, logging_obj, api_key=None):
self.encoding = encoding
self.default_max_tokens_to_sample = default_max_tokens_to_sample
self.completion_url = "https://api.anthropic.com/v1/complete"
+ self.api_key = api_key
+ self.logging_obj = logging_obj
self.validate_environment(api_key=api_key)
-
- def validate_environment(self, api_key): # set up the environment required to run the model
- # set the api key
- try:
- self.api_key = os.getenv("ANTHROPIC_API_KEY") if "ANTHROPIC_API_KEY" in os.environ else api_key
- if self.api_key == None:
- raise Exception
-
- self.headers = {
- "accept": "application/json",
- "anthropic-version": "2023-06-01",
- "content-type": "application/json",
- "x-api-key": self.api_key
- }
- except:
- raise ValueError("Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params")
- pass
+ def validate_environment(
+ self, api_key
+ ): # set up the environment required to run the model
+ # set the api key
+ if self.api_key == None:
+ raise ValueError(
+ "Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params"
+ )
+ self.api_key = api_key
+ self.headers = {
+ "accept": "application/json",
+ "anthropic-version": "2023-06-01",
+ "content-type": "application/json",
+ "x-api-key": self.api_key,
+ }
- def completion(self, model: str, messages: list, model_response: dict, print_verbose: Callable, optional_params=None, litellm_params=None, logger_fn=None): # logic for parsing in - calling - parsing out model completion calls
+ def completion(
+ self,
+ model: str,
+ messages: list,
+ model_response: ModelResponse,
+ print_verbose: Callable,
+ optional_params=None,
+ litellm_params=None,
+ logger_fn=None,
+ ): # logic for parsing in - calling - parsing out model completion calls
model = model
prompt = f"{AnthropicConstants.HUMAN_PROMPT.value}"
for message in messages:
if "role" in message:
if message["role"] == "user":
- prompt += f"{AnthropicConstants.HUMAN_PROMPT.value}{message['content']}"
+ prompt += (
+ f"{AnthropicConstants.HUMAN_PROMPT.value}{message['content']}"
+ )
else:
- prompt += f"{AnthropicConstants.AI_PROMPT.value}{message['content']}"
+ prompt += (
+ f"{AnthropicConstants.AI_PROMPT.value}{message['content']}"
+ )
else:
prompt += f"{AnthropicConstants.HUMAN_PROMPT.value}{message['content']}"
prompt += f"{AnthropicConstants.AI_PROMPT.value}"
- if "max_tokens" in optional_params and optional_params["max_tokens"] != float('inf'):
+ if "max_tokens" in optional_params and optional_params["max_tokens"] != float(
+ "inf"
+ ):
max_tokens = optional_params["max_tokens"]
else:
max_tokens = self.default_max_tokens_to_sample
@@ -61,39 +80,51 @@ class AnthropicLLM:
"model": model,
"prompt": prompt,
"max_tokens_to_sample": max_tokens,
- **optional_params
+ **optional_params,
}
## LOGGING
- logging(model=model, input=prompt, additional_args={"litellm_params": litellm_params, "optional_params": optional_params}, logger_fn=logger_fn)
+ self.logging_obj.pre_call(input=prompt, api_key=self.api_key, additional_args={"complete_input_dict": data})
## COMPLETION CALL
- response = requests.post(self.completion_url, headers=self.headers, data=json.dumps(data))
+ response = requests.post(
+ self.completion_url, headers=self.headers, data=json.dumps(data)
+ )
if "stream" in optional_params and optional_params["stream"] == True:
return response.iter_lines()
else:
## LOGGING
- logging(model=model, input=prompt, additional_args={"litellm_params": litellm_params, "optional_params": optional_params, "original_response": response.text}, logger_fn=logger_fn)
+ self.logging_obj.post_call(input=prompt, api_key=self.api_key, original_response=response.text, additional_args={"complete_input_dict": data})
print_verbose(f"raw model_response: {response.text}")
## RESPONSE OBJECT
completion_response = response.json()
if "error" in completion_response:
- raise AnthropicError(message=completion_response["error"], status_code=response.status_code)
+ raise AnthropicError(
+ message=completion_response["error"],
+ status_code=response.status_code,
+ )
else:
- model_response["choices"][0]["message"]["content"] = completion_response["completion"]
-
+ model_response["choices"][0]["message"][
+ "content"
+ ] = completion_response["completion"]
+
## CALCULATING USAGE
- prompt_tokens = len(self.encoding.encode(prompt)) ##[TODO] use the anthropic tokenizer here
- completion_tokens = len(self.encoding.encode(model_response["choices"][0]["message"]["content"])) ##[TODO] use the anthropic tokenizer here
-
-
+ prompt_tokens = len(
+ self.encoding.encode(prompt)
+ ) ##[TODO] use the anthropic tokenizer here
+ completion_tokens = len(
+ self.encoding.encode(model_response["choices"][0]["message"]["content"])
+ ) ##[TODO] use the anthropic tokenizer here
+
model_response["created"] = time.time()
model_response["model"] = model
model_response["usage"] = {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
- "total_tokens": prompt_tokens + completion_tokens
- }
+ "total_tokens": prompt_tokens + completion_tokens,
+ }
return model_response
-
- def embedding(): # logic for parsing in - calling - parsing out model embedding calls
- pass
\ No newline at end of file
+
+ def embedding(
+ self,
+ ): # logic for parsing in - calling - parsing out model embedding calls
+ pass
diff --git a/litellm/llms/base.py b/litellm/llms/base.py
index 368df9624..2c05a00c1 100644
--- a/litellm/llms/base.py
+++ b/litellm/llms/base.py
@@ -1,11 +1,16 @@
## This is a template base class to be used for adding new LLM providers via API calls
-class BaseLLM():
- def validate_environment(): # set up the environment required to run the model
- pass
- def completion(): # logic for parsing in - calling - parsing out model completion calls
+class BaseLLM:
+ def validate_environment(self): # set up the environment required to run the model
pass
- def embedding(): # logic for parsing in - calling - parsing out model embedding calls
- pass
\ No newline at end of file
+ def completion(
+ self,
+ ): # logic for parsing in - calling - parsing out model completion calls
+ pass
+
+ def embedding(
+ self,
+ ): # logic for parsing in - calling - parsing out model embedding calls
+ pass
diff --git a/litellm/llms/huggingface_restapi.py b/litellm/llms/huggingface_restapi.py
index 50bb9451a..624fb4f05 100644
--- a/litellm/llms/huggingface_restapi.py
+++ b/litellm/llms/huggingface_restapi.py
@@ -2,39 +2,60 @@
import os, json
from enum import Enum
import requests
-from litellm import logging
-import time
+import time
from typing import Callable
+from litellm.utils import ModelResponse
+from typing import Optional
+
class HuggingfaceError(Exception):
def __init__(self, status_code, message):
self.status_code = status_code
self.message = message
- super().__init__(self.message) # Call the base class constructor with the parameters it needs
+ super().__init__(
+ self.message
+ ) # Call the base class constructor with the parameters it needs
-class HuggingfaceRestAPILLM():
- def __init__(self, encoding, api_key=None) -> None:
+
+class HuggingfaceRestAPILLM:
+ def __init__(self, encoding, logging_obj, api_key=None) -> None:
self.encoding = encoding
+ self.logging_obj = logging_obj
self.validate_environment(api_key=api_key)
- def validate_environment(self, api_key): # set up the environment required to run the model
+ def validate_environment(
+ self, api_key
+ ): # set up the environment required to run the model
self.headers = {
"content-type": "application/json",
}
# get the api key if it exists in the environment or is passed in, but don't require it
- self.api_key = os.getenv("HF_TOKEN") if "HF_TOKEN" in os.environ else api_key
+ self.api_key = api_key
if self.api_key != None:
- self.headers["Authorization"] = f"Bearer {self.api_key}"
+ self.headers["Authorization"] = f"Bearer {self.api_key}"
- def completion(self, model: str, messages: list, custom_api_base: str, model_response: dict, print_verbose: Callable, optional_params=None, litellm_params=None, logger_fn=None): # logic for parsing in - calling - parsing out model completion calls
+ def completion(
+ self,
+ model: str,
+ messages: list,
+ custom_api_base: str,
+ model_response: ModelResponse,
+ print_verbose: Callable,
+ optional_params=None,
+ litellm_params=None,
+ logger_fn=None,
+ ): # logic for parsing in - calling - parsing out model completion calls
+ completion_url: str = ""
if custom_api_base:
completion_url = custom_api_base
elif "HF_API_BASE" in os.environ:
- completion_url = os.getenv("HF_API_BASE")
+ completion_url = os.getenv("HF_API_BASE", "")
else:
completion_url = f"https://api-inference.huggingface.co/models/{model}"
prompt = ""
- if "meta-llama" in model and "chat" in model: # use the required special tokens for meta-llama - https://huggingface.co/blog/llama2#how-to-prompt-llama-2
+ if (
+ "meta-llama" in model and "chat" in model
+ ): # use the required special tokens for meta-llama - https://huggingface.co/blog/llama2#how-to-prompt-llama-2
prompt = ""
for message in messages:
if message["role"] == "system":
@@ -46,49 +67,60 @@ class HuggingfaceRestAPILLM():
else:
for message in messages:
prompt += f"{message['content']}"
- ### MAP INPUT PARAMS
- # max tokens
+ ### MAP INPUT PARAMS
+ # max tokens
if "max_tokens" in optional_params:
value = optional_params.pop("max_tokens")
optional_params["max_new_tokens"] = value
data = {
"inputs": prompt,
- # "parameters": optional_params
+ "parameters": optional_params
}
## LOGGING
- logging(model=model, input=prompt, additional_args={"litellm_params": litellm_params, "optional_params": optional_params}, logger_fn=logger_fn)
+ self.logging_obj.pre_call(input=prompt, api_key=self.api_key, additional_args={"complete_input_dict": data})
## COMPLETION CALL
- response = requests.post(completion_url, headers=self.headers, data=json.dumps(data))
+ response = requests.post(
+ completion_url, headers=self.headers, data=json.dumps(data)
+ )
if "stream" in optional_params and optional_params["stream"] == True:
return response.iter_lines()
else:
## LOGGING
- logging(model=model, input=prompt, additional_args={"litellm_params": litellm_params, "optional_params": optional_params, "original_response": response.text}, logger_fn=logger_fn)
- print_verbose(f"raw model_response: {response.text}")
+ self.logging_obj.post_call(input=prompt, api_key=self.api_key, original_response=response.text, additional_args={"complete_input_dict": data})
## RESPONSE OBJECT
completion_response = response.json()
print_verbose(f"response: {completion_response}")
if isinstance(completion_response, dict) and "error" in completion_response:
print_verbose(f"completion error: {completion_response['error']}")
print_verbose(f"response.status_code: {response.status_code}")
- raise HuggingfaceError(message=completion_response["error"], status_code=response.status_code)
+ raise HuggingfaceError(
+ message=completion_response["error"],
+ status_code=response.status_code,
+ )
else:
- model_response["choices"][0]["message"]["content"] = completion_response[0]["generated_text"]
-
+ model_response["choices"][0]["message"][
+ "content"
+ ] = completion_response[0]["generated_text"]
+
## CALCULATING USAGE
- prompt_tokens = len(self.encoding.encode(prompt)) ##[TODO] use the llama2 tokenizer here
- completion_tokens = len(self.encoding.encode(model_response["choices"][0]["message"]["content"])) ##[TODO] use the llama2 tokenizer here
-
-
+ prompt_tokens = len(
+ self.encoding.encode(prompt)
+ ) ##[TODO] use the llama2 tokenizer here
+ completion_tokens = len(
+ self.encoding.encode(model_response["choices"][0]["message"]["content"])
+ ) ##[TODO] use the llama2 tokenizer here
+
model_response["created"] = time.time()
model_response["model"] = model
model_response["usage"] = {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
- "total_tokens": prompt_tokens + completion_tokens
- }
+ "total_tokens": prompt_tokens + completion_tokens,
+ }
return model_response
pass
- def embedding(): # logic for parsing in - calling - parsing out model embedding calls
- pass
\ No newline at end of file
+ def embedding(
+ self,
+ ): # logic for parsing in - calling - parsing out model embedding calls
+ pass
diff --git a/litellm/main.py b/litellm/main.py
index 18453e0c7..63f4e88fe 100644
--- a/litellm/main.py
+++ b/litellm/main.py
@@ -4,557 +4,730 @@ from functools import partial
import dotenv, traceback, random, asyncio, time
from copy import deepcopy
import litellm
-from litellm import client, logging, exception_type, timeout, get_optional_params, get_litellm_params
-from litellm.utils import get_secret, install_and_import, CustomStreamWrapper, read_config_args
+from litellm import ( # type: ignore
+ client,
+ exception_type,
+ timeout,
+ get_optional_params,
+ get_litellm_params,
+ Logging
+)
+from litellm.utils import (
+ get_secret,
+ install_and_import,
+ CustomStreamWrapper,
+ read_config_args,
+)
from .llms.anthropic import AnthropicLLM
from .llms.huggingface_restapi import HuggingfaceRestAPILLM
import tiktoken
from concurrent.futures import ThreadPoolExecutor
+
encoding = tiktoken.get_encoding("cl100k_base")
-from litellm.utils import get_secret, install_and_import, CustomStreamWrapper, read_config_args
-from litellm.utils import get_ollama_response_stream, stream_to_string
+from litellm.utils import (
+ get_secret,
+ install_and_import,
+ CustomStreamWrapper,
+ ModelResponse,
+ read_config_args,
+)
+from litellm.utils import (
+ get_ollama_response_stream,
+ stream_to_string,
+ together_ai_completion_streaming,
+)
+
####### ENVIRONMENT VARIABLES ###################
-dotenv.load_dotenv() # Loading env variables using dotenv
-new_response = {
- "choices": [
- {
- "finish_reason": "stop",
- "index": 0,
- "message": {
- "role": "assistant"
- }
- }
- ]
- }
-# TODO add translations
+dotenv.load_dotenv() # Loading env variables using dotenv
+
+
####### COMPLETION ENDPOINTS ################
#############################################
async def acompletion(*args, **kwargs):
- loop = asyncio.get_event_loop()
-
- # Use a partial function to pass your keyword arguments
- func = partial(completion, *args, **kwargs)
+ loop = asyncio.get_event_loop()
+
+ # Use a partial function to pass your keyword arguments
+ func = partial(completion, *args, **kwargs)
+
+ # Call the synchronous function using run_in_executor
+ return await loop.run_in_executor(None, func)
- # Call the synchronous function using run_in_executor
- return await loop.run_in_executor(None, func)
@client
# @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(2), reraise=True, retry_error_callback=lambda retry_state: setattr(retry_state.outcome, 'retry_variable', litellm.retry)) # retry call, turn this off by setting `litellm.retry = False`
-@timeout(600) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout`
+@timeout( # type: ignore
+ 600
+) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout`
def completion(
- model, messages,# required params
+ model,
+ messages, # required params
# Optional OpenAI params: see https://platform.openai.com/docs/api-reference/chat/create
- functions=[], function_call="", # optional params
- temperature=1, top_p=1, n=1, stream=False, stop=None, max_tokens=float('inf'),
- presence_penalty=0, frequency_penalty=0, logit_bias={}, user="", deployment_id=None,
+ functions=[],
+ function_call="", # optional params
+ temperature=1,
+ top_p=1,
+ n=1,
+ stream=False,
+ stop=None,
+ max_tokens=float("inf"),
+ presence_penalty=0,
+ frequency_penalty=0,
+ logit_bias={},
+ user="",
+ deployment_id=None,
# Optional liteLLM function params
- *, return_async=False, api_key=None, force_timeout=600, logger_fn=None, verbose=False, azure=False, custom_llm_provider=None, custom_api_base=None,
+ *,
+ return_async=False,
+ api_key=None,
+ force_timeout=600,
+ logger_fn=None,
+ verbose=False,
+ azure=False,
+ custom_llm_provider=None,
+ custom_api_base=None,
+ litellm_call_id=None,
# model specific optional params
# used by text-bison only
- top_k=40, request_timeout=0, # unused var for old version of OpenAI API
- ):
- try:
- global new_response
- if azure: # this flag is deprecated, remove once notebooks are also updated.
- custom_llm_provider="azure"
- args = locals()
- model_response = deepcopy(new_response) # deep copy the default response format so we can mutate it and it's thread-safe.
- # check if user passed in any of the OpenAI optional params
- optional_params = get_optional_params(
- functions=functions, function_call=function_call,
- temperature=temperature, top_p=top_p, n=n, stream=stream, stop=stop, max_tokens=max_tokens,
- presence_penalty=presence_penalty, frequency_penalty=frequency_penalty, logit_bias=logit_bias, user=user, deployment_id=deployment_id,
- # params to identify the model
- model=model, custom_llm_provider=custom_llm_provider, top_k=top_k,
- )
- # For logging - save the values of the litellm-specific params passed in
- litellm_params = get_litellm_params(
- return_async=return_async, api_key=api_key, force_timeout=force_timeout,
- logger_fn=logger_fn, verbose=verbose, custom_llm_provider=custom_llm_provider,
- custom_api_base=custom_api_base)
-
- if custom_llm_provider == "azure":
- # azure configs
- openai.api_type = "azure"
- openai.api_base = litellm.api_base if litellm.api_base is not None else get_secret("AZURE_API_BASE")
- openai.api_version = litellm.api_version if litellm.api_version is not None else get_secret("AZURE_API_VERSION")
- # set key
- if api_key:
- openai.api_key = api_key
- elif litellm.azure_key:
- openai.api_key = litellm.azure_key
- else:
- openai.api_key = get_secret("AZURE_API_KEY")
- ## LOGGING
- logging(model=model, input=messages, additional_args=optional_params, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- ## COMPLETION CALL
- if litellm.headers:
- response = openai.ChatCompletion.create(
- engine=model,
- messages = messages,
- headers = litellm.headers,
- **optional_params,
- )
- else:
- response = openai.ChatCompletion.create(
- model=model,
- messages = messages,
- **optional_params
- )
- elif model in litellm.open_ai_chat_completion_models or custom_llm_provider == "custom_openai": # allow user to make an openai call with a custom base
- openai.api_type = "openai"
- # note: if a user sets a custom base - we should ensure this works
- api_base = custom_api_base if custom_api_base is not None else litellm.api_base # allow for the setting of dynamic and stateful api-bases
- openai.api_base = api_base if api_base is not None else "https://api.openai.com/v1"
- openai.api_version = None
- if litellm.organization:
- openai.organization = litellm.organization
- if api_key:
- openai.api_key = api_key
- elif litellm.openai_key:
- openai.api_key = litellm.openai_key
- else:
- openai.api_key = get_secret("OPENAI_API_KEY")
- ## LOGGING
- logging(model=model, input=messages, additional_args=args, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- ## COMPLETION CALL
- if litellm.headers:
- response = openai.ChatCompletion.create(
- model=model,
- messages = messages,
- headers = litellm.headers,
- **optional_params
- )
- else:
- response = openai.ChatCompletion.create(
- model=model,
- messages = messages,
- **optional_params
- )
- elif model in litellm.open_ai_text_completion_models:
- openai.api_type = "openai"
- openai.api_base = litellm.api_base if litellm.api_base is not None else "https://api.openai.com/v1"
- openai.api_version = None
- if api_key:
- openai.api_key = api_key
- elif litellm.openai_key:
- openai.api_key = litellm.openai_key
- else:
- openai.api_key = get_secret("OPENAI_API_KEY")
- if litellm.organization:
- openai.organization = litellm.organization
- prompt = " ".join([message["content"] for message in messages])
- ## LOGGING
- logging(model=model, input=prompt, additional_args=optional_params, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- ## COMPLETION CALL
- if litellm.headers:
- response = openai.Completion.create(
- model=model,
- prompt = prompt,
- headers = litellm.headers,
- )
- else:
- response = openai.Completion.create(
+ top_k=40,
+ request_timeout=0, # unused var for old version of OpenAI API
+) -> ModelResponse:
+ try:
+ model_response = ModelResponse()
+ if azure: # this flag is deprecated, remove once notebooks are also updated.
+ custom_llm_provider = "azure"
+ elif model.split("/", 1)[0] in litellm.provider_list: # allow custom provider to be passed in via the model name "azure/chatgpt-test"
+ custom_llm_provider = model.split("/", 1)[0]
+ model = model.split("/", 1)[1]
+ if "replicate" == custom_llm_provider and "/" not in model: # handle the "replicate/llama2..." edge-case
+ model = custom_llm_provider + "/" + model
+ args = locals()
+ # check if user passed in any of the OpenAI optional params
+ optional_params = get_optional_params(
+ functions=functions,
+ function_call=function_call,
+ temperature=temperature,
+ top_p=top_p,
+ n=n,
+ stream=stream,
+ stop=stop,
+ max_tokens=max_tokens,
+ presence_penalty=presence_penalty,
+ frequency_penalty=frequency_penalty,
+ logit_bias=logit_bias,
+ user=user,
+ deployment_id=deployment_id,
+ # params to identify the model
model=model,
- prompt = prompt
+ custom_llm_provider=custom_llm_provider,
+ top_k=top_k,
)
- completion_response = response["choices"]["text"]
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = response["created"]
- model_response["model"] = model
- model_response["usage"] = response["usage"]
- response = model_response
- elif "replicate" in model or custom_llm_provider == "replicate":
- # import replicate/if it fails then pip install replicate
- install_and_import("replicate")
- import replicate
- # replicate defaults to os.environ.get("REPLICATE_API_TOKEN")
- # checking in case user set it to REPLICATE_API_KEY instead
- if not get_secret("REPLICATE_API_TOKEN") and get_secret("REPLICATE_API_KEY"):
- replicate_api_token = get_secret("REPLICATE_API_KEY")
- os.environ["REPLICATE_API_TOKEN"] = replicate_api_token
- elif api_key:
- os.environ["REPLICATE_API_TOKEN"] = api_key
- elif litellm.replicate_key:
- os.environ["REPLICATE_API_TOKEN"] = litellm.replicate_key
- prompt = " ".join([message["content"] for message in messages])
- input = {"prompt": prompt}
- if "max_tokens" in optional_params:
- input["max_length"] = max_tokens # for t5 models
- input["max_new_tokens"] = max_tokens # for llama2 models
- ## LOGGING
- logging(model=model, input=input, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn)
- ## COMPLETION CALL
- output = replicate.run(
- model,
- input=input)
- if 'stream' in optional_params and optional_params['stream'] == True:
- # don't try to access stream object,
- # let the stream handler know this is replicate
- response = CustomStreamWrapper(output, "replicate")
- return response
- response = ""
- for item in output:
- response += item
- completion_response = response
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
- prompt_tokens = len(encoding.encode(prompt))
- completion_tokens = len(encoding.encode(completion_response))
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- model_response["usage"] = {
- "prompt_tokens": prompt_tokens,
- "completion_tokens": completion_tokens,
- "total_tokens": prompt_tokens + completion_tokens
- }
- response = model_response
- elif model in litellm.anthropic_models:
- anthropic_key = api_key if api_key is not None else litellm.anthropic_key
- anthropic_client = AnthropicLLM(encoding=encoding, default_max_tokens_to_sample=litellm.max_tokens, api_key=anthropic_key)
- model_response = anthropic_client.completion(model=model, messages=messages, model_response=model_response, print_verbose=print_verbose, optional_params=optional_params, litellm_params=litellm_params, logger_fn=logger_fn)
- if 'stream' in optional_params and optional_params['stream'] == True:
- # don't try to access stream object,
- response = CustomStreamWrapper(model_response, model)
- return response
- response = model_response
- elif model in litellm.openrouter_models or custom_llm_provider == "openrouter":
- openai.api_type = "openai"
- # not sure if this will work after someone first uses another API
- openai.api_base = litellm.api_base if litellm.api_base is not None else "https://openrouter.ai/api/v1"
- openai.api_version = None
- if litellm.organization:
- openai.organization = litellm.organization
- if api_key:
- openai.api_key = api_key
- elif litellm.openrouter_key:
- openai.api_key = litellm.openrouter_key
- else:
- openai.api_key = get_secret("OPENROUTER_API_KEY")
- ## LOGGING
- logging(model=model, input=messages, additional_args=optional_params, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- ## COMPLETION CALL
- if litellm.headers:
- response = openai.ChatCompletion.create(
- model=model,
- messages = messages,
- headers = litellm.headers,
- **optional_params
+ # For logging - save the values of the litellm-specific params passed in
+ litellm_params = get_litellm_params(
+ return_async=return_async,
+ api_key=api_key,
+ force_timeout=force_timeout,
+ logger_fn=logger_fn,
+ verbose=verbose,
+ custom_llm_provider=custom_llm_provider,
+ custom_api_base=custom_api_base,
+ litellm_call_id=litellm_call_id
)
- else:
- openrouter_site_url = get_secret("OR_SITE_URL")
- openrouter_app_name = get_secret("OR_APP_NAME")
- # if openrouter_site_url is None, set it to https://litellm.ai
- if openrouter_site_url is None:
- openrouter_site_url = "https://litellm.ai"
- # if openrouter_app_name is None, set it to liteLLM
- if openrouter_app_name is None:
- openrouter_app_name = "liteLLM"
- response = openai.ChatCompletion.create(
- model=model,
- messages = messages,
- headers =
- {
- "HTTP-Referer": openrouter_site_url, # To identify your site
- "X-Title": openrouter_app_name # To identify your app
- },
- **optional_params
+ logging = Logging(model=model, messages=messages, optional_params=optional_params, litellm_params=litellm_params)
+ if custom_llm_provider == "azure":
+ # azure configs
+ openai.api_type = "azure"
+ openai.api_base = (
+ litellm.api_base
+ if litellm.api_base is not None
+ else get_secret("AZURE_API_BASE")
+ )
+ openai.api_version = (
+ litellm.api_version
+ if litellm.api_version is not None
+ else get_secret("AZURE_API_VERSION")
+ )
+ if not api_key and litellm.azure_key:
+ api_key = litellm.azure_key
+ elif not api_key and get_secret("AZURE_API_KEY"):
+ api_key = get_secret("AZURE_API_KEY")
+ # set key
+ openai.api_key = api_key
+ ## LOGGING
+ logging.pre_call(input=messages, api_key=openai.api_key, additional_args={"headers": litellm.headers, "api_version": openai.api_version, "api_base": openai.api_base})
+ ## COMPLETION CALL
+ if litellm.headers:
+ response = openai.ChatCompletion.create(
+ engine=model,
+ messages=messages,
+ headers=litellm.headers,
+ **optional_params,
+ )
+ else:
+ response = openai.ChatCompletion.create(
+ model=model, messages=messages, **optional_params
+ )
+ ## LOGGING
+ logging.post_call(input=messages, api_key=openai.api_key, original_response=response, additional_args={"headers": litellm.headers, "api_version": openai.api_version, "api_base": openai.api_base})
+ elif (
+ model in litellm.open_ai_chat_completion_models
+ or custom_llm_provider == "custom_openai"
+ ): # allow user to make an openai call with a custom base
+ openai.api_type = "openai"
+ # note: if a user sets a custom base - we should ensure this works
+ api_base = (
+ custom_api_base if custom_api_base is not None else litellm.api_base
+ ) # allow for the setting of dynamic and stateful api-bases
+ openai.api_base = (
+ api_base if api_base is not None else "https://api.openai.com/v1"
+ )
+ openai.api_version = None
+ if litellm.organization:
+ openai.organization = litellm.organization
+ # set API KEY
+ if not api_key and litellm.openai_key:
+ api_key = litellm.openai_key
+ elif not api_key and get_secret("AZURE_API_KEY"):
+ api_key = get_secret("OPENAI_API_KEY")
+
+ openai.api_key = api_key
+
+ ## LOGGING
+ logging.pre_call(input=messages, api_key=api_key, additional_args={"headers": litellm.headers, "api_base": api_base})
+ ## COMPLETION CALL
+ if litellm.headers:
+ response = openai.ChatCompletion.create(
+ model=model,
+ messages=messages,
+ headers=litellm.headers,
+ **optional_params,
+ )
+ else:
+ response = openai.ChatCompletion.create(
+ model=model, messages=messages, **optional_params
+ )
+ ## LOGGING
+ logging.post_call(input=messages, api_key=api_key, original_response=response, additional_args={"headers": litellm.headers})
+ elif model in litellm.open_ai_text_completion_models:
+ openai.api_type = "openai"
+ openai.api_base = (
+ litellm.api_base
+ if litellm.api_base is not None
+ else "https://api.openai.com/v1"
+ )
+ openai.api_version = None
+ # set API KEY
+ if not api_key and litellm.openai_key:
+ api_key = litellm.openai_key
+ elif not api_key and get_secret("AZURE_API_KEY"):
+ api_key = get_secret("OPENAI_API_KEY")
+
+ openai.api_key = api_key
+
+ if litellm.organization:
+ openai.organization = litellm.organization
+ prompt = " ".join([message["content"] for message in messages])
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=api_key, additional_args={"openai_organization": litellm.organization, "headers": litellm.headers, "api_base": openai.api_base, "api_type": openai.api_type})
+ ## COMPLETION CALL
+ if litellm.headers:
+ response = openai.Completion.create(
+ model=model,
+ prompt=prompt,
+ headers=litellm.headers,
+ )
+ else:
+ response = openai.Completion.create(model=model, prompt=prompt)
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=api_key, original_response=response, additional_args={"openai_organization": litellm.organization, "headers": litellm.headers, "api_base": openai.api_base, "api_type": openai.api_type})
+ ## RESPONSE OBJECT
+ completion_response = response["choices"][0]["text"]
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = response["created"]
+ model_response["model"] = model
+ model_response["usage"] = response["usage"]
+ response = model_response
+ elif "replicate" in model or custom_llm_provider == "replicate":
+ # import replicate/if it fails then pip install replicate
+ install_and_import("replicate")
+ import replicate
+
+ # Setting the relevant API KEY for replicate, replicate defaults to using os.environ.get("REPLICATE_API_TOKEN")
+ replicate_key = os.environ.get("REPLICATE_API_TOKEN")
+ if replicate_key == None:
+ # user did not set REPLICATE_API_TOKEN in .env
+ replicate_key = (
+ get_secret("REPLICATE_API_KEY")
+ or get_secret("REPLICATE_API_TOKEN")
+ or api_key
+ or litellm.replicate_key
+ )
+ # set replicate key
+ os.environ["REPLICATE_API_TOKEN"] = str(replicate_key)
+ prompt = " ".join([message["content"] for message in messages])
+ input = {"prompt": prompt}
+ if "max_tokens" in optional_params:
+ input["max_length"] = max_tokens # for t5 models
+ input["max_new_tokens"] = max_tokens # for llama2 models
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=replicate_key, additional_args={"complete_input_dict": input, "max_tokens": max_tokens})
+ ## COMPLETION CALL
+ output = replicate.run(model, input=input)
+ if "stream" in optional_params and optional_params["stream"] == True:
+ # don't try to access stream object,
+ # let the stream handler know this is replicate
+ response = CustomStreamWrapper(output, "replicate")
+ return response
+ response = ""
+ for item in output:
+ response += item
+ completion_response = response
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=replicate_key, original_response=completion_response, additional_args={"complete_input_dict": input, "max_tokens": max_tokens})
+ ## USAGE
+ prompt_tokens = len(encoding.encode(prompt))
+ completion_tokens = len(encoding.encode(completion_response))
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ model_response["usage"] = {
+ "prompt_tokens": prompt_tokens,
+ "completion_tokens": completion_tokens,
+ "total_tokens": prompt_tokens + completion_tokens,
+ }
+ response = model_response
+ elif model in litellm.anthropic_models:
+ anthropic_key = (
+ api_key or litellm.anthropic_key or os.environ.get("ANTHROPIC_API_KEY")
+ )
+ anthropic_client = AnthropicLLM(
+ encoding=encoding,
+ default_max_tokens_to_sample=litellm.max_tokens,
+ api_key=anthropic_key,
+ logging_obj = logging # model call logging done inside the class as we make need to modify I/O to fit anthropic's requirements
+ )
+ model_response = anthropic_client.completion(
+ model=model,
+ messages=messages,
+ model_response=model_response,
+ print_verbose=print_verbose,
+ optional_params=optional_params,
+ litellm_params=litellm_params,
+ logger_fn=logger_fn,
+ )
+ if "stream" in optional_params and optional_params["stream"] == True:
+ # don't try to access stream object,
+ response = CustomStreamWrapper(model_response, model)
+ return response
+ response = model_response
+ elif model in litellm.openrouter_models or custom_llm_provider == "openrouter":
+ openai.api_type = "openai"
+ # not sure if this will work after someone first uses another API
+ openai.api_base = (
+ litellm.api_base
+ if litellm.api_base is not None
+ else "https://openrouter.ai/api/v1"
+ )
+ openai.api_version = None
+ if litellm.organization:
+ openai.organization = litellm.organization
+ if api_key:
+ openai.api_key = api_key
+ elif litellm.openrouter_key:
+ openai.api_key = litellm.openrouter_key
+ else:
+ openai.api_key = get_secret("OPENROUTER_API_KEY") or get_secret(
+ "OR_API_KEY"
+ )
+ ## LOGGING
+ logging.pre_call(input=messages, api_key=openai.api_key)
+ ## COMPLETION CALL
+ if litellm.headers:
+ response = openai.ChatCompletion.create(
+ model=model,
+ messages=messages,
+ headers=litellm.headers,
+ **optional_params,
+ )
+ else:
+ openrouter_site_url = get_secret("OR_SITE_URL")
+ openrouter_app_name = get_secret("OR_APP_NAME")
+ # if openrouter_site_url is None, set it to https://litellm.ai
+ if openrouter_site_url is None:
+ openrouter_site_url = "https://litellm.ai"
+ # if openrouter_app_name is None, set it to liteLLM
+ if openrouter_app_name is None:
+ openrouter_app_name = "liteLLM"
+ response = openai.ChatCompletion.create(
+ model=model,
+ messages=messages,
+ headers={
+ "HTTP-Referer": openrouter_site_url, # To identify your site
+ "X-Title": openrouter_app_name, # To identify your app
+ },
+ **optional_params,
+ )
+ ## LOGGING
+ logging.post_call(input=messages, api_key=openai.api_key, original_response=response)
+ elif model in litellm.cohere_models:
+ # import cohere/if it fails then pip install cohere
+ install_and_import("cohere")
+ import cohere
+
+ cohere_key = (
+ api_key
+ or litellm.cohere_key
+ or get_secret("COHERE_API_KEY")
+ or get_secret("CO_API_KEY")
+ )
+ co = cohere.Client(cohere_key)
+ prompt = " ".join([message["content"] for message in messages])
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=cohere_key)
+ ## COMPLETION CALL
+ response = co.generate(model=model, prompt=prompt, **optional_params)
+ if "stream" in optional_params and optional_params["stream"] == True:
+ # don't try to access stream object,
+ response = CustomStreamWrapper(response, model)
+ return response
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=cohere_key, original_response=response)
+ ## USAGE
+ completion_response = response[0].text
+ prompt_tokens = len(encoding.encode(prompt))
+ completion_tokens = len(encoding.encode(completion_response))
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ model_response["usage"] = {
+ "prompt_tokens": prompt_tokens,
+ "completion_tokens": completion_tokens,
+ "total_tokens": prompt_tokens + completion_tokens,
+ }
+ response = model_response
+ elif (
+ model in litellm.huggingface_models or custom_llm_provider == "huggingface"
+ ):
+ custom_llm_provider = "huggingface"
+ huggingface_key = (
+ api_key
+ or litellm.huggingface_key
+ or os.environ.get("HF_TOKEN")
+ or os.environ.get("HUGGINGFACE_API_KEY")
+ )
+ huggingface_client = HuggingfaceRestAPILLM(
+ encoding=encoding, api_key=huggingface_key, logging_obj=logging
+ )
+ model_response = huggingface_client.completion(
+ model=model,
+ messages=messages,
+ custom_api_base=custom_api_base,
+ model_response=model_response,
+ print_verbose=print_verbose,
+ optional_params=optional_params,
+ litellm_params=litellm_params,
+ logger_fn=logger_fn,
+ )
+ if "stream" in optional_params and optional_params["stream"] == True:
+ # don't try to access stream object,
+ response = CustomStreamWrapper(
+ model_response, model, custom_llm_provider="huggingface"
+ )
+ return response
+ response = model_response
+ elif custom_llm_provider == "together_ai" or ("togethercomputer" in model):
+ import requests
+
+ TOGETHER_AI_TOKEN = (
+ get_secret("TOGETHER_AI_TOKEN")
+ or get_secret("TOGETHERAI_API_KEY")
+ or api_key
+ or litellm.togetherai_api_key
+ )
+ headers = {"Authorization": f"Bearer {TOGETHER_AI_TOKEN}"}
+ endpoint = "https://api.together.xyz/inference"
+ prompt = " ".join(
+ [message["content"] for message in messages]
+ ) # TODO: Add chat support for together AI
+
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=TOGETHER_AI_TOKEN)
+ if stream == True:
+ return together_ai_completion_streaming(
+ {
+ "model": model,
+ "prompt": prompt,
+ "request_type": "language-model-inference",
+ **optional_params,
+ },
+ headers=headers,
+ )
+ res = requests.post(
+ endpoint,
+ json={
+ "model": model,
+ "prompt": prompt,
+ "request_type": "language-model-inference",
+ **optional_params,
+ },
+ headers=headers,
+ )
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=TOGETHER_AI_TOKEN, original_response=res.text)
+ # make this safe for reading, if output does not exist raise an error
+ json_response = res.json()
+ if "output" not in json_response:
+ raise Exception(
+ f"liteLLM: Error Making TogetherAI request, JSON Response {json_response}"
+ )
+ completion_response = json_response["output"]["choices"][0]["text"]
+ prompt_tokens = len(encoding.encode(prompt))
+ completion_tokens = len(encoding.encode(completion_response))
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ model_response["usage"] = {
+ "prompt_tokens": prompt_tokens,
+ "completion_tokens": completion_tokens,
+ "total_tokens": prompt_tokens + completion_tokens,
+ }
+ response = model_response
+ elif model in litellm.vertex_chat_models:
+ # import vertexai/if it fails then pip install vertexai# import cohere/if it fails then pip install cohere
+ install_and_import("vertexai")
+ import vertexai
+ from vertexai.preview.language_models import ChatModel, InputOutputTextPair
+
+ vertexai.init(
+ project=litellm.vertex_project, location=litellm.vertex_location
+ )
+ # vertexai does not use an API key, it looks for credentials.json in the environment
+
+ prompt = " ".join([message["content"] for message in messages])
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=None)
+
+ chat_model = ChatModel.from_pretrained(model)
+
+ chat = chat_model.start_chat()
+ completion_response = chat.send_message(prompt, **optional_params)
+
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=None, original_response=completion_response)
+
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ elif model in litellm.vertex_text_models:
+ # import vertexai/if it fails then pip install vertexai# import cohere/if it fails then pip install cohere
+ install_and_import("vertexai")
+ import vertexai
+ from vertexai.language_models import TextGenerationModel
+
+ vertexai.init(
+ project=litellm.vertex_project, location=litellm.vertex_location
+ )
+ # vertexai does not use an API key, it looks for credentials.json in the environment
+
+ prompt = " ".join([message["content"] for message in messages])
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=None)
+
+ vertex_model = TextGenerationModel.from_pretrained(model)
+ completion_response = vertex_model.predict(prompt, **optional_params)
+
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=None, original_response=completion_response)
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ response = model_response
+ elif model in litellm.ai21_models:
+ install_and_import("ai21")
+ import ai21
+
+ ai21.api_key = get_secret("AI21_API_KEY")
+
+ prompt = " ".join([message["content"] for message in messages])
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=ai21.api_key)
+
+ ai21_response = ai21.Completion.execute(
+ model=model,
+ prompt=prompt,
+ )
+ completion_response = ai21_response["completions"][0]["data"]["text"]
+
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=ai21.api_key, original_response=completion_response)
+
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ response = model_response
+ elif custom_llm_provider == "ollama":
+ endpoint = (
+ litellm.api_base if litellm.api_base is not None else custom_api_base
+ )
+ prompt = " ".join([message["content"] for message in messages])
+
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=None, additional_args={"endpoint": endpoint})
+
+ generator = get_ollama_response_stream(endpoint, model, prompt)
+ # assume all responses are streamed
+ return generator
+ elif (
+ custom_llm_provider == "baseten"
+ or litellm.api_base == "https://app.baseten.co"
+ ):
+ import baseten
+
+ base_ten_key = get_secret("BASETEN_API_KEY")
+ baseten.login(base_ten_key)
+
+ prompt = " ".join([message["content"] for message in messages])
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=base_ten_key)
+
+ base_ten__model = baseten.deployed_model_version_id(model)
+
+ completion_response = base_ten__model.predict({"prompt": prompt})
+ if type(completion_response) == dict:
+ completion_response = completion_response["data"]
+ if type(completion_response) == dict:
+ completion_response = completion_response["generated_text"]
+
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=base_ten_key, original_response=completion_response)
+
+ ## RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ response = model_response
+
+ elif custom_llm_provider == "petals" or (
+ litellm.api_base and "chat.petals.dev" in litellm.api_base
+ ):
+ url = "https://chat.petals.dev/api/v1/generate"
+ import requests
+
+ prompt = " ".join([message["content"] for message in messages])
+
+ ## LOGGING
+ logging.pre_call(input=prompt, api_key=None, additional_args={"url": url, "max_new_tokens": 100})
+
+ response = requests.post(
+ url, data={"inputs": prompt, "max_new_tokens": 100, "model": model}
+ )
+ ## LOGGING
+ logging.post_call(input=prompt, api_key=None, original_response=response.text, additional_args={"url": url, "max_new_tokens": 100})
+
+ completion_response = response.json()["outputs"]
+
+ # RESPONSE OBJECT
+ model_response["choices"][0]["message"]["content"] = completion_response
+ model_response["created"] = time.time()
+ model_response["model"] = model
+ response = model_response
+ else:
+ args = locals()
+ raise ValueError(
+ f"Unable to map your input to a model. Check your input - {args}"
+ )
+ return response
+ except Exception as e:
+ ## LOGGING
+ logging.post_call(input=messages, api_key=api_key, original_response=e)
+ ## Map to OpenAI Exception
+ raise exception_type(
+ model=model, custom_llm_provider=custom_llm_provider, original_exception=e
)
- elif model in litellm.cohere_models:
- # import cohere/if it fails then pip install cohere
- install_and_import("cohere")
- import cohere
- if api_key:
- cohere_key = api_key
- elif litellm.cohere_key:
- cohere_key = litellm.cohere_key
- else:
- cohere_key = get_secret("COHERE_API_KEY")
- co = cohere.Client(cohere_key)
- prompt = " ".join([message["content"] for message in messages])
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- ## COMPLETION CALL
- response = co.generate(
- model=model,
- prompt = prompt,
- **optional_params
- )
- if 'stream' in optional_params and optional_params['stream'] == True:
- # don't try to access stream object,
- response = CustomStreamWrapper(response, model)
- return response
- completion_response = response[0].text
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
- prompt_tokens = len(encoding.encode(prompt))
- completion_tokens = len(encoding.encode(completion_response))
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- model_response["usage"] = {
- "prompt_tokens": prompt_tokens,
- "completion_tokens": completion_tokens,
- "total_tokens": prompt_tokens + completion_tokens
- }
- response = model_response
- elif model in litellm.huggingface_models or custom_llm_provider == "huggingface":
- custom_llm_provider = "huggingface"
- huggingface_key = api_key if api_key is not None else litellm.huggingface_key
- huggingface_client = HuggingfaceRestAPILLM(encoding=encoding, api_key=huggingface_key)
- model_response = huggingface_client.completion(model=model, messages=messages, custom_api_base=custom_api_base, model_response=model_response, print_verbose=print_verbose, optional_params=optional_params, litellm_params=litellm_params, logger_fn=logger_fn)
- if 'stream' in optional_params and optional_params['stream'] == True:
- # don't try to access stream object,
- response = CustomStreamWrapper(model_response, model, custom_llm_provider="huggingface")
- return response
- response = model_response
- elif custom_llm_provider == "together_ai":
- import requests
- TOGETHER_AI_TOKEN = get_secret("TOGETHER_AI_TOKEN")
- headers = {"Authorization": f"Bearer {TOGETHER_AI_TOKEN}"}
- endpoint = 'https://api.together.xyz/inference'
- prompt = " ".join([message["content"] for message in messages]) # TODO: Add chat support for together AI
-
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- res = requests.post(endpoint, json={
- "model": model,
- "prompt": prompt,
- "request_type": "language-model-inference",
- **optional_params
- },
- headers=headers
- )
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": res.text}, logger_fn=logger_fn)
- if stream == True:
- response = CustomStreamWrapper(res, "together_ai")
- return response
-
- completion_response = res.json()['output']['choices'][0]['text']
- prompt_tokens = len(encoding.encode(prompt))
- completion_tokens = len(encoding.encode(completion_response))
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- model_response["usage"] = {
- "prompt_tokens": prompt_tokens,
- "completion_tokens": completion_tokens,
- "total_tokens": prompt_tokens + completion_tokens
- }
- response = model_response
- elif model in litellm.vertex_chat_models:
- # import vertexai/if it fails then pip install vertexai# import cohere/if it fails then pip install cohere
- install_and_import("vertexai")
- import vertexai
- from vertexai.preview.language_models import ChatModel, InputOutputTextPair
- vertexai.init(project=litellm.vertex_project, location=litellm.vertex_location)
- # vertexai does not use an API key, it looks for credentials.json in the environment
-
- prompt = " ".join([message["content"] for message in messages])
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"litellm_params": litellm_params, "optional_params": optional_params}, logger_fn=logger_fn)
-
- chat_model = ChatModel.from_pretrained(model)
-
-
- chat = chat_model.start_chat()
- completion_response = chat.send_message(prompt, **optional_params)
-
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
-
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- elif model in litellm.vertex_text_models:
- # import vertexai/if it fails then pip install vertexai# import cohere/if it fails then pip install cohere
- install_and_import("vertexai")
- import vertexai
- from vertexai.language_models import TextGenerationModel
-
- vertexai.init(project=litellm.vertex_project, location=litellm.vertex_location)
- # vertexai does not use an API key, it looks for credentials.json in the environment
-
- prompt = " ".join([message["content"] for message in messages])
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- vertex_model = TextGenerationModel.from_pretrained(model)
- completion_response= vertex_model.predict(prompt, **optional_params)
-
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
-
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- response = model_response
- elif model in litellm.ai21_models:
- install_and_import("ai21")
- import ai21
- ai21.api_key = get_secret("AI21_API_KEY")
-
- prompt = " ".join([message["content"] for message in messages])
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
-
- ai21_response = ai21.Completion.execute(
- model=model,
- prompt=prompt,
- )
- completion_response = ai21_response['completions'][0]['data']['text']
-
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
-
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- response = model_response
- elif custom_llm_provider == "ollama":
- endpoint = litellm.api_base if litellm.api_base is not None else custom_api_base
- prompt = " ".join([message["content"] for message in messages])
-
- ## LOGGING
- logging(model=model, input=prompt, azure=azure, logger_fn=logger_fn)
- generator = get_ollama_response_stream(endpoint, model, prompt)
- # assume all responses are streamed
- return generator
- elif custom_llm_provider == "baseten" or litellm.api_base=="https://app.baseten.co":
- import baseten
- base_ten_key = get_secret('BASETEN_API_KEY')
- baseten.login(base_ten_key)
-
- prompt = " ".join([message["content"] for message in messages])
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
-
- base_ten__model = baseten.deployed_model_version_id(model)
-
- completion_response = base_ten__model.predict({"prompt": prompt})
- if type(completion_response) == dict:
- completion_response = completion_response["data"]
- if type(completion_response) == dict:
- completion_response = completion_response["generated_text"]
-
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": completion_response}, logger_fn=logger_fn)
-
- ## RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- response = model_response
-
- elif custom_llm_provider == "petals" or "chat.petals.dev" in litellm.api_base:
- url = "https://chat.petals.dev/api/v1/generate"
- import requests
- prompt = " ".join([message["content"] for message in messages])
-
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- response = requests.post(url, data={"inputs": prompt, "max_new_tokens": 100, "model": model})
- ## LOGGING
- logging(model=model, input=prompt, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens, "original_response": response}, logger_fn=logger_fn)
- completion_response = response.json()["outputs"]
-
- # RESPONSE OBJECT
- model_response["choices"][0]["message"]["content"] = completion_response
- model_response["created"] = time.time()
- model_response["model"] = model
- response = model_response
- else:
- ## LOGGING
- logging(model=model, input=messages, custom_llm_provider=custom_llm_provider, logger_fn=logger_fn)
- args = locals()
- raise ValueError(f"Unable to map your input to a model. Check your input - {args}")
- return response
- except Exception as e:
- ## LOGGING
- logging(model=model, input=messages, custom_llm_provider=custom_llm_provider, additional_args={"max_tokens": max_tokens}, logger_fn=logger_fn, exception=e)
- ## Map to OpenAI Exception
- raise exception_type(model=model, custom_llm_provider=custom_llm_provider, original_exception=e)
def batch_completion(*args, **kwargs):
- batch_messages = args[1] if len(args) > 1 else kwargs.get("messages")
- completions = []
- with ThreadPoolExecutor() as executor:
- for message_list in batch_messages:
- if len(args) > 1:
- args_modified = list(args)
- args_modified[1] = message_list
- future = executor.submit(completion, *args_modified)
- else:
- kwargs_modified = dict(kwargs)
- kwargs_modified["messages"] = message_list
- future = executor.submit(completion, *args, **kwargs_modified)
- completions.append(future)
-
- # Retrieve the results from the futures
- results = [future.result() for future in completions]
- return results
+ batch_messages = args[1] if len(args) > 1 else kwargs.get("messages")
+ completions = []
+ with ThreadPoolExecutor() as executor:
+ for message_list in batch_messages:
+ if len(args) > 1:
+ args_modified = list(args)
+ args_modified[1] = message_list
+ future = executor.submit(completion, *args_modified)
+ else:
+ kwargs_modified = dict(kwargs)
+ kwargs_modified["messages"] = message_list
+ future = executor.submit(completion, *args, **kwargs_modified)
+ completions.append(future)
+
+ # Retrieve the results from the futures
+ results = [future.result() for future in completions]
+ return results
+
### EMBEDDING ENDPOINTS ####################
@client
-@timeout(60) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout`
-def embedding(model, input=[], azure=False, force_timeout=60, logger_fn=None):
- try:
- response = None
- if azure == True:
- # azure configs
- openai.api_type = "azure"
- openai.api_base = get_secret("AZURE_API_BASE")
- openai.api_version = get_secret("AZURE_API_VERSION")
- openai.api_key = get_secret("AZURE_API_KEY")
- ## LOGGING
- logging(model=model, input=input, azure=azure, logger_fn=logger_fn)
- ## EMBEDDING CALL
- response = openai.Embedding.create(input=input, engine=model)
- print_verbose(f"response_value: {str(response)[:50]}")
- elif model in litellm.open_ai_embedding_models:
- openai.api_type = "openai"
- openai.api_base = "https://api.openai.com/v1"
- openai.api_version = None
- openai.api_key = get_secret("OPENAI_API_KEY")
- ## LOGGING
- logging(model=model, input=input, azure=azure, logger_fn=logger_fn)
- ## EMBEDDING CALL
- response = openai.Embedding.create(input=input, model=model)
- print_verbose(f"response_value: {str(response)[:50]}")
- else:
- logging(model=model, input=input, azure=azure, logger_fn=logger_fn)
- args = locals()
- raise ValueError(f"No valid embedding model args passed in - {args}")
-
- return response
- except Exception as e:
- # log the original exception
- logging(model=model, input=input, azure=azure, logger_fn=logger_fn, exception=e)
- ## Map to OpenAI Exception
- raise exception_type(model=model, original_exception=e)
- raise e
+@timeout( # type: ignore
+ 60
+) ## set timeouts, in case calls hang (e.g. Azure) - default is 60s, override with `force_timeout`
+def embedding(model, input=[], azure=False, force_timeout=60, litellm_call_id=None, logger_fn=None):
+ try:
+ response = None
+ logging = Logging(model=model, messages=input, optional_params={}, litellm_params={"azure": azure, "force_timeout": force_timeout, "logger_fn": logger_fn, "litellm_call_id": litellm_call_id})
+ if azure == True:
+ # azure configs
+ openai.api_type = "azure"
+ openai.api_base = get_secret("AZURE_API_BASE")
+ openai.api_version = get_secret("AZURE_API_VERSION")
+ openai.api_key = get_secret("AZURE_API_KEY")
+ ## LOGGING
+ logging.pre_call(input=input, api_key=openai.api_key, additional_args={"api_type": openai.api_type, "api_base": openai.api_base, "api_version": openai.api_version})
+ ## EMBEDDING CALL
+ response = openai.Embedding.create(input=input, engine=model)
+ print_verbose(f"response_value: {str(response)[:50]}")
+ elif model in litellm.open_ai_embedding_models:
+ openai.api_type = "openai"
+ openai.api_base = "https://api.openai.com/v1"
+ openai.api_version = None
+ openai.api_key = get_secret("OPENAI_API_KEY")
+ ## LOGGING
+ logging.pre_call(input=input, api_key=openai.api_key, additional_args={"api_type": openai.api_type, "api_base": openai.api_base, "api_version": openai.api_version})
+ ## EMBEDDING CALL
+ response = openai.Embedding.create(input=input, model=model)
+ print_verbose(f"response_value: {str(response)[:50]}")
+ else:
+ args = locals()
+ raise ValueError(f"No valid embedding model args passed in - {args}")
+
+ return response
+ except Exception as e:
+ ## LOGGING
+ logging.post_call(input=input, api_key=openai.api_key, original_response=e)
+ ## Map to OpenAI Exception
+ raise exception_type(model=model, original_exception=e, custom_llm_provider="azure" if azure==True else None)
+
+
####### HELPER FUNCTIONS ################
-## Set verbose to true -> ```litellm.set_verbose = True```
+## Set verbose to true -> ```litellm.set_verbose = True```
def print_verbose(print_statement):
- if litellm.set_verbose:
- print(f"LiteLLM: {print_statement}")
- if random.random() <= 0.3:
- print("Get help - https://discord.com/invite/wuPM9dRgDw")
+ if litellm.set_verbose:
+ print(f"LiteLLM: {print_statement}")
+ if random.random() <= 0.3:
+ print("Get help - https://discord.com/invite/wuPM9dRgDw")
+
def config_completion(**kwargs):
- if litellm.config_path != None:
- config_args = read_config_args(litellm.config_path)
- # overwrite any args passed in with config args
- return completion(**kwargs, **config_args)
- else:
- raise ValueError("No config path set, please set a config path using `litellm.config_path = 'path/to/config.json'`")
\ No newline at end of file
+ if litellm.config_path != None:
+ config_args = read_config_args(litellm.config_path)
+ # overwrite any args passed in with config args
+ return completion(**kwargs, **config_args)
+ else:
+ raise ValueError(
+ "No config path set, please set a config path using `litellm.config_path = 'path/to/config.json'`"
+ )
diff --git a/litellm/testing.py b/litellm/testing.py
new file mode 100644
index 000000000..5db01d182
--- /dev/null
+++ b/litellm/testing.py
@@ -0,0 +1,137 @@
+import litellm
+import time
+from concurrent.futures import ThreadPoolExecutor
+import traceback
+
+
+def testing_batch_completion(*args, **kwargs):
+ try:
+ batch_models = (
+ args[0] if len(args) > 0 else kwargs.pop("models")
+ ) ## expected input format- ["gpt-3.5-turbo", {"model": "qvv0xeq", "custom_llm_provider"="baseten"}...]
+ batch_messages = args[1] if len(args) > 1 else kwargs.pop("messages")
+ results = []
+ completions = []
+ exceptions = []
+ times = []
+ with ThreadPoolExecutor() as executor:
+ for model in batch_models:
+ kwargs_modified = dict(kwargs)
+ args_modified = list(args)
+ if len(args) > 0:
+ args_modified[0] = model["model"]
+ else:
+ kwargs_modified["model"] = (
+ model["model"]
+ if isinstance(model, dict) and "model" in model
+ else model
+ ) # if model is a dictionary get it's value else assume it's a string
+ kwargs_modified["custom_llm_provider"] = (
+ model["custom_llm_provider"]
+ if isinstance(model, dict) and "custom_llm_provider" in model
+ else None
+ )
+ kwargs_modified["custom_api_base"] = (
+ model["custom_api_base"]
+ if isinstance(model, dict) and "custom_api_base" in model
+ else None
+ )
+ for message_list in batch_messages:
+ if len(args) > 1:
+ args_modified[1] = message_list
+ future = executor.submit(
+ litellm.completion, *args_modified, **kwargs_modified
+ )
+ else:
+ kwargs_modified["messages"] = message_list
+ future = executor.submit(
+ litellm.completion, *args_modified, **kwargs_modified
+ )
+ completions.append((future, message_list))
+
+ # Retrieve the results and calculate elapsed time for each completion call
+ for completion in completions:
+ future, message_list = completion
+ start_time = time.time()
+ try:
+ result = future.result()
+ end_time = time.time()
+ elapsed_time = end_time - start_time
+ result_dict = {
+ "status": "succeeded",
+ "response": future.result(),
+ "prompt": message_list,
+ "response_time": elapsed_time,
+ }
+ results.append(result_dict)
+ except Exception as e:
+ end_time = time.time()
+ elapsed_time = end_time - start_time
+ result_dict = {
+ "status": "failed",
+ "response": e,
+ "response_time": elapsed_time,
+ }
+ results.append(result_dict)
+ return results
+ except:
+ traceback.print_exc()
+
+
+def duration_test_model(original_function):
+ def wrapper_function(*args, **kwargs):
+ # Code to be executed before the original function
+ duration = kwargs.pop("duration", None)
+ interval = kwargs.pop("interval", None)
+ results = []
+ if duration and interval:
+ start_time = time.time()
+ end_time = start_time + duration # default to 1hr duration
+ while time.time() < end_time:
+ result = original_function(*args, **kwargs)
+ results.append(result)
+ time.sleep(interval)
+ else:
+ result = original_function(*args, **kwargs)
+ results = result
+ return results
+
+ # Return the wrapper function
+ return wrapper_function
+
+
+@duration_test_model
+def load_test_model(models: list, prompt: str = "", num_calls: int = 0):
+ test_calls = 100
+ if num_calls:
+ test_calls = num_calls
+ input_prompt = prompt if prompt else "Hey, how's it going?"
+ messages = (
+ [{"role": "user", "content": prompt}]
+ if prompt
+ else [{"role": "user", "content": input_prompt}]
+ )
+ full_message_list = [
+ messages for _ in range(test_calls)
+ ] # call it as many times as set by user to load test models
+ start_time = time.time()
+ try:
+ results = testing_batch_completion(models=models, messages=full_message_list)
+ end_time = time.time()
+ response_time = end_time - start_time
+ return {
+ "total_response_time": response_time,
+ "calls_made": test_calls,
+ "prompt": input_prompt,
+ "results": results,
+ }
+ except Exception as e:
+ traceback.print_exc()
+ end_time = time.time()
+ response_time = end_time - start_time
+ return {
+ "total_response_time": response_time,
+ "calls_made": test_calls,
+ "prompt": input_prompt,
+ "exception": e,
+ }
diff --git a/litellm/tests/test_api_key_param.py b/litellm/tests/test_api_key_param.py
index 6213730f5..40f7a12b0 100644
--- a/litellm/tests/test_api_key_param.py
+++ b/litellm/tests/test_api_key_param.py
@@ -3,39 +3,51 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
litellm.set_verbose = False
+
def logger_fn(model_call_object: dict):
print(f"model call details: {model_call_object}")
+
user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
## Test 1: Setting key dynamically
-temp_key = os.environ.get("ANTHROPIC_API_KEY")
+temp_key = os.environ.get("ANTHROPIC_API_KEY", "")
os.environ["ANTHROPIC_API_KEY"] = "bad-key"
-# test on openai completion call
+# test on openai completion call
try:
- response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn, api_key=temp_key)
+ response = completion(
+ model="claude-instant-1",
+ messages=messages,
+ logger_fn=logger_fn,
+ api_key=temp_key,
+ )
print(f"response: {response}")
except:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
pass
os.environ["ANTHROPIC_API_KEY"] = temp_key
## Test 2: Setting key via __init__ params
-litellm.anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
+litellm.anthropic_key = os.environ.get("ANTHROPIC_API_KEY", "")
os.environ.pop("ANTHROPIC_API_KEY")
-# test on openai completion call
+# test on openai completion call
try:
- response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn)
+ response = completion(
+ model="claude-instant-1", messages=messages, logger_fn=logger_fn
+ )
print(f"response: {response}")
except:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
pass
os.environ["ANTHROPIC_API_KEY"] = temp_key
diff --git a/litellm/tests/test_async_fn.py b/litellm/tests/test_async_fn.py
index b0925c4b5..c20c5cde6 100644
--- a/litellm/tests/test_async_fn.py
+++ b/litellm/tests/test_async_fn.py
@@ -5,17 +5,22 @@ import sys, os
import pytest
import traceback
import asyncio
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
from litellm import acompletion
+
async def test_get_response():
user_message = "Hello, how are you?"
- messages = [{ "content": user_message,"role": "user"}]
+ messages = [{"content": user_message, "role": "user"}]
try:
response = await acompletion(model="gpt-3.5-turbo", messages=messages)
except Exception as e:
pytest.fail(f"error occurred: {e}")
return response
+
response = asyncio.run(test_get_response())
-print(response)
\ No newline at end of file
+print(response)
diff --git a/litellm/tests/test_bad_params.py b/litellm/tests/test_bad_params.py
index 0a2313c78..bf05de8bd 100644
--- a/litellm/tests/test_bad_params.py
+++ b/litellm/tests/test_bad_params.py
@@ -1,16 +1,17 @@
#### What this tests ####
# This tests chaos monkeys - if random parts of the system are broken / things aren't sent correctly - what happens.
-# Expect to add more edge cases to this over time.
+# Expect to add more edge cases to this over time.
import sys, os
import traceback
from dotenv import load_dotenv
+
load_dotenv()
# Get the current directory of the script
current_dir = os.path.dirname(os.path.abspath(__file__))
# Get the parent directory by joining the current directory with '..'
-parent_dir = os.path.join(current_dir, '../..')
+parent_dir = os.path.join(current_dir, "../..")
# Add the parent directory to the system path
sys.path.append(parent_dir)
@@ -26,7 +27,7 @@ litellm.failure_callback = ["slack", "sentry", "posthog"]
user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
model_val = None
@@ -35,18 +36,18 @@ def test_completion_with_empty_model():
try:
response = completion(model=model_val, messages=messages)
except Exception as e:
- print(f"error occurred: {e}")
+ print(f"error occurred: {e}")
pass
-#bad key
+# bad key
temp_key = os.environ.get("OPENAI_API_KEY")
os.environ["OPENAI_API_KEY"] = "bad-key"
-# test on openai completion call
+# test on openai completion call
try:
response = completion(model="gpt-3.5-turbo", messages=messages)
print(f"response: {response}")
except:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
pass
-os.environ["OPENAI_API_KEY"] = temp_key
\ No newline at end of file
+os.environ["OPENAI_API_KEY"] = str(temp_key) # this passes linting#5
diff --git a/litellm/tests/test_batch_completions.py b/litellm/tests/test_batch_completions.py
index d15628f56..a136351ba 100644
--- a/litellm/tests/test_batch_completions.py
+++ b/litellm/tests/test_batch_completions.py
@@ -3,7 +3,10 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import batch_completion
@@ -14,4 +17,4 @@ model = "gpt-3.5-turbo"
result = batch_completion(model=model, messages=messages)
print(result)
-print(len(result))
\ No newline at end of file
+print(len(result))
diff --git a/litellm/tests/test_berrispend_integration.py b/litellm/tests/test_berrispend_integration.py
index 122c9201d..500285b85 100644
--- a/litellm/tests/test_berrispend_integration.py
+++ b/litellm/tests/test_berrispend_integration.py
@@ -19,7 +19,7 @@
# #openai call
-# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
+# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
# #bad request call
-# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}])
\ No newline at end of file
+# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}])
diff --git a/litellm/tests/test_caching.py b/litellm/tests/test_caching.py
new file mode 100644
index 000000000..87ae02b58
--- /dev/null
+++ b/litellm/tests/test_caching.py
@@ -0,0 +1,52 @@
+import sys, os
+import traceback
+from dotenv import load_dotenv
+
+load_dotenv()
+import os
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
+import pytest
+import litellm
+from litellm import embedding, completion
+
+messages = [{"role": "user", "content": "who is ishaan Github? "}]
+
+
+# test if response cached
+def test_caching():
+ try:
+ litellm.caching = True
+ response1 = completion(model="gpt-3.5-turbo", messages=messages)
+ response2 = completion(model="gpt-3.5-turbo", messages=messages)
+ print(f"response1: {response1}")
+ print(f"response2: {response2}")
+ litellm.caching = False
+ if response2 != response1:
+ print(f"response1: {response1}")
+ print(f"response2: {response2}")
+ pytest.fail(f"Error occurred: {e}")
+ except Exception as e:
+ litellm.caching = False
+ print(f"error occurred: {traceback.format_exc()}")
+ pytest.fail(f"Error occurred: {e}")
+
+
+
+def test_caching_with_models():
+ litellm.caching_with_models = True
+ response2 = completion(model="gpt-3.5-turbo", messages=messages)
+ response3 = completion(model="command-nightly", messages=messages)
+ print(f"response2: {response2}")
+ print(f"response3: {response3}")
+ litellm.caching_with_models = False
+ if response3 == response2:
+ # if models are different, it should not return cached response
+ print(f"response2: {response2}")
+ print(f"response3: {response3}")
+ pytest.fail(f"Error occurred: {e}")
+
+
+
diff --git a/litellm/tests/test_client.py b/litellm/tests/test_client.py
index b329e4c65..f29ae5a94 100644
--- a/litellm/tests/test_client.py
+++ b/litellm/tests/test_client.py
@@ -5,7 +5,9 @@ import sys, os
import traceback
import pytest
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
@@ -14,53 +16,71 @@ litellm.failure_callback = ["slack", "sentry", "posthog"]
litellm.set_verbose = True
+
def logger_fn(model_call_object: dict):
# print(f"model call details: {model_call_object}")
pass
+
user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
+
def test_completion_openai():
try:
print("running query")
- response = completion(model="gpt-3.5-turbo", messages=messages, logger_fn=logger_fn)
+ response = completion(
+ model="gpt-3.5-turbo", messages=messages, logger_fn=logger_fn
+ )
print(f"response: {response}")
# Add any assertions here to check the response
except Exception as e:
traceback.print_exc()
pytest.fail(f"Error occurred: {e}")
-test_completion_openai()
+
def test_completion_claude():
try:
- response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn)
+ response = completion(
+ model="claude-instant-1", messages=messages, logger_fn=logger_fn
+ )
# Add any assertions here to check the response
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-test_completion_claude()
+
+
def test_completion_non_openai():
try:
- response = completion(model="command-nightly", messages=messages, logger_fn=logger_fn)
+ response = completion(
+ model="command-nightly", messages=messages, logger_fn=logger_fn
+ )
# Add any assertions here to check the response
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-test_completion_non_openai()
+
+
def test_embedding_openai():
try:
- response = embedding(model='text-embedding-ada-002', input=[user_message], logger_fn=logger_fn)
+ response = embedding(
+ model="text-embedding-ada-002", input=[user_message], logger_fn=logger_fn
+ )
# Add any assertions here to check the response
print(f"response: {str(response)[:50]}")
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_bad_azure_embedding():
try:
- response = embedding(model='chatgpt-test', input=[user_message], logger_fn=logger_fn)
+ response = embedding(
+ model="chatgpt-test", input=[user_message], logger_fn=logger_fn
+ )
# Add any assertions here to check the response
print(f"response: {str(response)[:50]}")
except Exception as e:
pass
+
+
# def test_good_azure_embedding():
# try:
# response = embedding(model='azure-embedding-model', input=[user_message], azure=True, logger_fn=logger_fn)
@@ -68,4 +88,3 @@ def test_bad_azure_embedding():
# print(f"response: {str(response)[:50]}")
# except Exception as e:
# pytest.fail(f"Error occurred: {e}")
-
diff --git a/litellm/tests/test_completion.py b/litellm/tests/test_completion.py
index a73599cbe..fc9954459 100644
--- a/litellm/tests/test_completion.py
+++ b/litellm/tests/test_completion.py
@@ -1,53 +1,79 @@
import sys, os
import traceback
from dotenv import load_dotenv
+
load_dotenv()
import os
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import pytest
import litellm
from litellm import embedding, completion
+
# from infisical import InfisicalClient
# litellm.set_verbose = True
# litellm.secret_manager_client = InfisicalClient(token=os.environ["INFISICAL_TOKEN"])
user_message = "Hello, whats the weather in San Francisco??"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
+
def logger_fn(user_model_dict):
print(f"user_model_dict: {user_model_dict}")
-def test_completion_claude():
+
+def test_completion_custom_provider_model_name():
try:
- response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn)
+ response = completion(
+ model="together_ai/togethercomputer/llama-2-70b-chat", messages=messages, logger_fn=logger_fn
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+test_completion_custom_provider_model_name()
+
+def test_completion_claude():
+ try:
+ response = completion(
+ model="claude-instant-1", messages=messages, logger_fn=logger_fn
+ )
+ # Add any assertions here to check the response
+ print(response)
+ except Exception as e:
+ pytest.fail(f"Error occurred: {e}")
+
+
def test_completion_claude_stream():
try:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
- {"role": "user", "content": "how does a court case get to the Supreme Court?"}
+ {
+ "role": "user",
+ "content": "how does a court case get to the Supreme Court?",
+ },
]
response = completion(model="claude-2", messages=messages, stream=True)
# Add any assertions here to check the response
for chunk in response:
- print(chunk['choices'][0]['delta']) # same as openai format
+ print(chunk["choices"][0]["delta"]) # same as openai format
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-def test_completion_hf_api():
- try:
- user_message = "write some code to find the sum of two numbers"
- messages = [{ "content": user_message,"role": "user"}]
- response = completion(model="stabilityai/stablecode-completion-alpha-3b-4k", messages=messages, custom_llm_provider="huggingface")
- # Add any assertions here to check the response
- print(response)
- except Exception as e:
- pytest.fail(f"Error occurred: {e}")
+
+# def test_completion_hf_api():
+# try:
+# user_message = "write some code to find the sum of two numbers"
+# messages = [{ "content": user_message,"role": "user"}]
+# response = completion(model="stabilityai/stablecode-completion-alpha-3b-4k", messages=messages, custom_llm_provider="huggingface")
+# # Add any assertions here to check the response
+# print(response)
+# except Exception as e:
+# pytest.fail(f"Error occurred: {e}")
# def test_completion_hf_deployed_api():
# try:
@@ -62,65 +88,140 @@ def test_completion_hf_api():
def test_completion_cohere():
try:
- response = completion(model="command-nightly", messages=messages, max_tokens=500)
+ response = completion(
+ model="command-nightly", messages=messages, max_tokens=100, logit_bias={40: 10}
+ )
# Add any assertions here to check the response
print(response)
+ response_str = response["choices"][0]["message"]["content"]
+ print(f"str response{response_str}")
+ response_str_2 = response.choices[0].message.content
+ if type(response_str) != str:
+ pytest.fail(f"Error occurred: {e}")
+ if type(response_str_2) != str:
+ pytest.fail(f"Error occurred: {e}")
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-
def test_completion_cohere_stream():
try:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
- {"role": "user", "content": "how does a court case get to the Supreme Court?"}
+ {
+ "role": "user",
+ "content": "how does a court case get to the Supreme Court?",
+ },
]
- response = completion(model="command-nightly", messages=messages, stream=True, max_tokens=50)
+ response = completion(
+ model="command-nightly", messages=messages, stream=True, max_tokens=50
+ )
# Add any assertions here to check the response
for chunk in response:
- print(chunk['choices'][0]['delta']) # same as openai format
+ print(chunk["choices"][0]["delta"]) # same as openai format
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_openai():
try:
response = completion(model="gpt-3.5-turbo", messages=messages)
+
+ response_str = response["choices"][0]["message"]["content"]
+ response_str_2 = response.choices[0].message.content
+ assert response_str == response_str_2
+ assert type(response_str) == str
+ assert len(response_str) > 1
+ except Exception as e:
+ pytest.fail(f"Error occurred: {e}")
+
+
+def test_completion_text_openai():
+ try:
+ response = completion(model="text-davinci-003", messages=messages)
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_openai_with_optional_params():
try:
- response = completion(model="gpt-3.5-turbo", messages=messages, temperature=0.5, top_p=0.1, user="ishaan_dev@berri.ai")
+ response = completion(
+ model="gpt-3.5-turbo",
+ messages=messages,
+ temperature=0.5,
+ top_p=0.1,
+ user="ishaan_dev@berri.ai",
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_openrouter():
try:
- response = completion(model="google/palm-2-chat-bison", messages=messages, temperature=0.5, top_p=0.1, user="ishaan_dev@berri.ai")
+ response = completion(
+ model="google/palm-2-chat-bison",
+ messages=messages,
+ temperature=0.5,
+ top_p=0.1,
+ user="ishaan_dev@berri.ai",
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_openai_with_more_optional_params():
try:
- response = completion(model="gpt-3.5-turbo", messages=messages, temperature=0.5, top_p=0.1, n=2, max_tokens=150, presence_penalty=0.5, frequency_penalty=-0.5, logit_bias={123: 5}, user="ishaan_dev@berri.ai")
+ response = completion(
+ model="gpt-3.5-turbo",
+ messages=messages,
+ temperature=0.5,
+ top_p=0.1,
+ n=2,
+ max_tokens=150,
+ presence_penalty=0.5,
+ frequency_penalty=-0.5,
+ logit_bias={123: 5},
+ user="ishaan_dev@berri.ai",
+ )
+ # Add any assertions here to check the response
+ print(response)
+ response_str = response["choices"][0]["message"]["content"]
+ response_str_2 = response.choices[0].message.content
+ print(response["choices"][0]["message"]["content"])
+ print(response.choices[0].message.content)
+ if type(response_str) != str:
+ pytest.fail(f"Error occurred: {e}")
+ if type(response_str_2) != str:
+ pytest.fail(f"Error occurred: {e}")
+ except Exception as e:
+ pytest.fail(f"Error occurred: {e}")
+
+
+def test_completion_openai_with_stream():
+ try:
+ response = completion(
+ model="gpt-3.5-turbo",
+ messages=messages,
+ temperature=0.5,
+ top_p=0.1,
+ n=2,
+ max_tokens=150,
+ presence_penalty=0.5,
+ stream=True,
+ frequency_penalty=-0.5,
+ logit_bias={27000: 5},
+ user="ishaan_dev@berri.ai",
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-def test_completion_openai_with_stream():
- try:
- response = completion(model="gpt-3.5-turbo", messages=messages, temperature=0.5, top_p=0.1, n=2, max_tokens=150, presence_penalty=0.5, stream=True, frequency_penalty=-0.5, logit_bias={27000: 5}, user="ishaan_dev@berri.ai")
- # Add any assertions here to check the response
- print(response)
- except Exception as e:
- pytest.fail(f"Error occurred: {e}")
def test_completion_openai_with_functions():
function1 = [
@@ -132,33 +233,39 @@ def test_completion_openai_with_functions():
"properties": {
"location": {
"type": "string",
- "description": "The city and state, e.g. San Francisco, CA"
+ "description": "The city and state, e.g. San Francisco, CA",
},
- "unit": {
- "type": "string",
- "enum": ["celsius", "fahrenheit"]
- }
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
- "required": ["location"]
- }
+ "required": ["location"],
+ },
}
]
try:
- response = completion(model="gpt-3.5-turbo", messages=messages, functions=function1)
+ response = completion(
+ model="gpt-3.5-turbo", messages=messages, functions=function1
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_azure():
try:
- response = completion(model="gpt-3.5-turbo", deployment_id="chatgpt-test", messages=messages, custom_llm_provider="azure")
+ response = completion(
+ model="gpt-3.5-turbo",
+ deployment_id="chatgpt-test",
+ messages=messages,
+ custom_llm_provider="azure",
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-# Replicate API endpoints are unstable -> throw random CUDA errors -> this means our tests can fail even if our tests weren't incorrect.
+
+# Replicate API endpoints are unstable -> throw random CUDA errors -> this means our tests can fail even if our tests weren't incorrect.
def test_completion_replicate_llama_stream():
model_name = "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"
try:
@@ -170,59 +277,69 @@ def test_completion_replicate_llama_stream():
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_replicate_stability_stream():
model_name = "stability-ai/stablelm-tuned-alpha-7b:c49dae362cbaecd2ceabb5bd34fdb68413c4ff775111fea065d259d577757beb"
try:
- response = completion(model=model_name, messages=messages, stream=True, custom_llm_provider="replicate")
+ response = completion(
+ model=model_name,
+ messages=messages,
+ stream=True,
+ custom_llm_provider="replicate",
+ )
# Add any assertions here to check the response
for chunk in response:
- print(chunk['choices'][0]['delta'])
+ print(chunk["choices"][0]["delta"])
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
def test_completion_replicate_stability():
model_name = "stability-ai/stablelm-tuned-alpha-7b:c49dae362cbaecd2ceabb5bd34fdb68413c4ff775111fea065d259d577757beb"
try:
- response = completion(model=model_name, messages=messages, custom_llm_provider="replicate")
+ response = completion(
+ model=model_name, messages=messages, custom_llm_provider="replicate"
+ )
# Add any assertions here to check the response
- for result in response:
- print(result)
- print(response)
+ response_str = response["choices"][0]["message"]["content"]
+ response_str_2 = response.choices[0].message.content
+ print(response_str)
+ print(response_str_2)
+ if type(response_str) != str:
+ pytest.fail(f"Error occurred: {e}")
+ if type(response_str_2) != str:
+ pytest.fail(f"Error occurred: {e}")
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
######## Test TogetherAI ########
def test_completion_together_ai():
model_name = "togethercomputer/llama-2-70b-chat"
try:
- response = completion(model=model_name, messages=messages, custom_llm_provider="together_ai")
+ response = completion(model=model_name, messages=messages)
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
-def test_completion_together_ai_stream():
- model_name = "togethercomputer/llama-2-70b-chat"
- try:
- response = completion(model=model_name, messages=messages, custom_llm_provider="together_ai", stream=True)
- # Add any assertions here to check the response
- print(response)
- for chunk in response:
- print(chunk['choices'][0]['delta']) # same as openai format
- except Exception as e:
- pytest.fail(f"Error occurred: {e}")
-
def test_petals():
model_name = "stabilityai/StableBeluga2"
try:
- response = completion(model=model_name, messages=messages, custom_llm_provider="petals", force_timeout=120)
+ response = completion(
+ model=model_name,
+ messages=messages,
+ custom_llm_provider="petals",
+ force_timeout=120,
+ )
# Add any assertions here to check the response
print(response)
except Exception as e:
pytest.fail(f"Error occurred: {e}")
+
# def test_baseten_falcon_7bcompletion():
# model_name = "qvv0xeq"
# try:
@@ -270,7 +387,6 @@ def test_petals():
# pytest.fail(f"Error occurred: {e}")
-
#### Test A121 ###################
# def test_completion_ai21():
# model_name = "j2-light"
@@ -281,7 +397,7 @@ def test_petals():
# except Exception as e:
# pytest.fail(f"Error occurred: {e}")
-# test config file with completion #
+# test config file with completion #
# def test_completion_openai_config():
# try:
# litellm.config_path = "../config.json"
@@ -294,4 +410,22 @@ def test_petals():
# pytest.fail(f"Error occurred: {e}")
+# import asyncio
+# def test_completion_together_ai_stream():
+# user_message = "Write 1pg about YC & litellm"
+# messages = [{ "content": user_message,"role": "user"}]
+# try:
+# response = completion(model="togethercomputer/llama-2-70b-chat", messages=messages, stream=True, max_tokens=800)
+# print(response)
+# asyncio.run(get_response(response))
+# # print(string_response)
+# except Exception as e:
+# pytest.fail(f"Error occurred: {e}")
+
+# async def get_response(generator):
+# async for elem in generator:
+# print(elem)
+# return
+
+# test_completion_together_ai_stream()
diff --git a/litellm/tests/test_custom_api_base.py b/litellm/tests/test_custom_api_base.py
index 966fff954..70a477eab 100644
--- a/litellm/tests/test_custom_api_base.py
+++ b/litellm/tests/test_custom_api_base.py
@@ -1,20 +1,33 @@
import sys, os
import traceback
from dotenv import load_dotenv
+
load_dotenv()
import os
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
-import litellm
-from litellm import completion
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
+import litellm
+from litellm import completion
+
def logging_fn(model_call_dict):
print(f"model call details: {model_call_dict}")
+
+
models = ["gorilla-7b-hf-v1", "gpt-4"]
custom_llm_provider = None
messages = [{"role": "user", "content": "Hey, how's it going?"}]
-for model in models: # iterate through list
+for model in models: # iterate through list
custom_api_base = None
- if model == "gorilla-7b-hf-v1":
+ if model == "gorilla-7b-hf-v1":
custom_llm_provider = "custom_openai"
custom_api_base = "http://zanino.millennium.berkeley.edu:8000/v1"
- completion(model=model, messages=messages, custom_llm_provider=custom_llm_provider, custom_api_base=custom_api_base, logger_fn=logging_fn)
+ completion(
+ model=model,
+ messages=messages,
+ custom_llm_provider=custom_llm_provider,
+ custom_api_base=custom_api_base,
+ logger_fn=logging_fn,
+ )
diff --git a/litellm/tests/test_embedding.py b/litellm/tests/test_embedding.py
index ce83ffc70..a9b3f2b79 100644
--- a/litellm/tests/test_embedding.py
+++ b/litellm/tests/test_embedding.py
@@ -1,20 +1,24 @@
-
import sys, os
import traceback
import pytest
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
from infisical import InfisicalClient
-# litellm.set_verbose = True
-litellm.secret_manager_client = InfisicalClient(token=os.environ["INFISICAL_TOKEN"])
+# # litellm.set_verbose = True
+# litellm.secret_manager_client = InfisicalClient(token=os.environ["INFISICAL_TOKEN"])
+
def test_openai_embedding():
try:
- response = embedding(model='text-embedding-ada-002', input=["good morning from litellm"])
+ response = embedding(
+ model="text-embedding-ada-002", input=["good morning from litellm"]
+ )
# Add any assertions here to check the response
print(f"response: {str(response)}")
except Exception as e:
- pytest.fail(f"Error occurred: {e}")
\ No newline at end of file
+ pytest.fail(f"Error occurred: {e}")
diff --git a/litellm/tests/test_exceptions.py b/litellm/tests/test_exceptions.py
index e224fdf7a..6620eb2ae 100644
--- a/litellm/tests/test_exceptions.py
+++ b/litellm/tests/test_exceptions.py
@@ -1,10 +1,21 @@
# from openai.error import AuthenticationError, InvalidRequestError, RateLimitError, OpenAIError
-import os
+import os
import sys
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
-from litellm import embedding, completion, AuthenticationError, InvalidRequestError, RateLimitError, ServiceUnavailableError, OpenAIError
+from litellm import (
+ embedding,
+ completion,
+ AuthenticationError,
+ InvalidRequestError,
+ RateLimitError,
+ ServiceUnavailableError,
+ OpenAIError,
+)
from concurrent.futures import ThreadPoolExecutor
import pytest
@@ -23,8 +34,10 @@ litellm.failure_callback = ["sentry"]
# models = ["gpt-3.5-turbo", "chatgpt-test", "claude-instant-1", "command-nightly"]
test_model = "claude-instant-1"
models = ["claude-instant-1"]
+
+
def logging_fn(model_call_dict):
- if "model" in model_call_dict:
+ if "model" in model_call_dict:
print(f"model_call_dict: {model_call_dict['model']}")
else:
print(f"model_call_dict: {model_call_dict}")
@@ -38,13 +51,18 @@ def test_context_window(model):
try:
model = "chatgpt-test"
print(f"model: {model}")
- response = completion(model=model, messages=messages, custom_llm_provider="azure", logger_fn=logging_fn)
+ response = completion(
+ model=model,
+ messages=messages,
+ custom_llm_provider="azure",
+ logger_fn=logging_fn,
+ )
print(f"response: {response}")
- except InvalidRequestError:
- print("InvalidRequestError")
+ except InvalidRequestError as e:
+ print(f"InvalidRequestError: {e.llm_provider}")
return
- except OpenAIError:
- print("OpenAIError")
+ except OpenAIError as e:
+ print(f"OpenAIError: {e.llm_provider}")
return
except Exception as e:
print("Uncaught Error in test_context_window")
@@ -52,14 +70,17 @@ def test_context_window(model):
print(f"Uncaught Exception - {e}")
pytest.fail(f"Error occurred: {e}")
return
+
+
test_context_window(test_model)
+
# Test 2: InvalidAuth Errors
@pytest.mark.parametrize("model", models)
-def invalid_auth(model): # set the model key to an invalid key, depending on the model
- messages = [{ "content": "Hello, how are you?","role": "user"}]
+def invalid_auth(model): # set the model key to an invalid key, depending on the model
+ messages = [{"content": "Hello, how are you?", "role": "user"}]
temporary_key = None
- try:
+ try:
custom_llm_provider = None
if model == "gpt-3.5-turbo":
temporary_key = os.environ["OPENAI_API_KEY"]
@@ -74,22 +95,29 @@ def invalid_auth(model): # set the model key to an invalid key, depending on the
elif model == "command-nightly":
temporary_key = os.environ["COHERE_API_KEY"]
os.environ["COHERE_API_KEY"] = "bad-key"
- elif model == "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1":
- temporary_key = os.environ["REPLICATE_API_KEY"]
+ elif (
+ model
+ == "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"
+ ):
+ temporary_key = os.environ["REPLICATE_API_KEY"]
os.environ["REPLICATE_API_KEY"] = "bad-key"
print(f"model: {model}")
- response = completion(model=model, messages=messages, custom_llm_provider=custom_llm_provider)
+ response = completion(
+ model=model, messages=messages, custom_llm_provider=custom_llm_provider
+ )
print(f"response: {response}")
except AuthenticationError as e:
- print(f"AuthenticationError Caught Exception - {e}")
- except OpenAIError: # is at least an openai error -> in case of random model errors - e.g. overloaded server
+ print(f"AuthenticationError Caught Exception - {e.llm_provider}")
+ except (
+ OpenAIError
+ ): # is at least an openai error -> in case of random model errors - e.g. overloaded server
print(f"OpenAIError Caught Exception - {e}")
except Exception as e:
print(type(e))
print(e.__class__.__name__)
print(f"Uncaught Exception - {e}")
pytest.fail(f"Error occurred: {e}")
- if temporary_key != None: # reset the key
+ if temporary_key != None: # reset the key
if model == "gpt-3.5-turbo":
os.environ["OPENAI_API_KEY"] = temporary_key
elif model == "chatgpt-test":
@@ -99,13 +127,18 @@ def invalid_auth(model): # set the model key to an invalid key, depending on the
os.environ["ANTHROPIC_API_KEY"] = temporary_key
elif model == "command-nightly":
os.environ["COHERE_API_KEY"] = temporary_key
- elif model == "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1":
+ elif (
+ model
+ == "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"
+ ):
os.environ["REPLICATE_API_KEY"] = temporary_key
return
+
+
invalid_auth(test_model)
-# # Test 3: Rate Limit Errors
+# # Test 3: Rate Limit Errors
# def test_model(model):
-# try:
+# try:
# sample_text = "how does a court case get to the Supreme Court?" * 50000
# messages = [{ "content": sample_text,"role": "user"}]
# custom_llm_provider = None
@@ -142,5 +175,3 @@ invalid_auth(test_model)
# accuracy_score = counts[True]/(counts[True] + counts[False])
# print(f"accuracy_score: {accuracy_score}")
-
-
diff --git a/litellm/tests/test_helicone_integration.py b/litellm/tests/test_helicone_integration.py
index 0b1d6ce8a..66e375d17 100644
--- a/litellm/tests/test_helicone_integration.py
+++ b/litellm/tests/test_helicone_integration.py
@@ -5,7 +5,9 @@ import sys, os
import traceback
import pytest
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
@@ -14,11 +16,15 @@ litellm.success_callback = ["helicone"]
litellm.set_verbose = True
user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
-#openai call
-response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
+# openai call
+response = completion(
+ model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}]
+)
-#cohere call
-response = completion(model="command-nightly", messages=[{"role": "user", "content": "Hi 👋 - i'm cohere"}])
\ No newline at end of file
+# cohere call
+response = completion(
+ model="command-nightly", messages=[{"role": "user", "content": "Hi 👋 - i'm cohere"}]
+)
diff --git a/litellm/tests/test_litedebugger_integration.py b/litellm/tests/test_litedebugger_integration.py
new file mode 100644
index 000000000..7fc9e3069
--- /dev/null
+++ b/litellm/tests/test_litedebugger_integration.py
@@ -0,0 +1,26 @@
+# #### What this tests ####
+# # This tests if logging to the litedebugger integration actually works
+# # pytest mistakes intentional bad calls as failed tests -> [TODO] fix this
+# import sys, os
+# import traceback
+# import pytest
+
+# sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+# import litellm
+# from litellm import embedding, completion
+
+# litellm.input_callback = ["lite_debugger"]
+# litellm.success_callback = ["lite_debugger"]
+# litellm.failure_callback = ["lite_debugger"]
+
+# litellm.set_verbose = True
+
+# user_message = "Hello, how are you?"
+# messages = [{ "content": user_message,"role": "user"}]
+
+
+# #openai call
+# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
+
+# #bad request call
+# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}])
diff --git a/litellm/tests/test_load_test_model.py b/litellm/tests/test_load_test_model.py
index 373da1a6d..0820990c2 100644
--- a/litellm/tests/test_load_test_model.py
+++ b/litellm/tests/test_load_test_model.py
@@ -1,9 +1,37 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
-import litellm
-from litellm import load_test_model
-model="gpt-3.5-turbo"
-result = load_test_model(model=model, num_calls=5)
-print(result)
\ No newline at end of file
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
+import litellm
+from litellm import load_test_model, testing_batch_completion
+
+# ## Load Test Model
+# model="gpt-3.5-turbo"
+# result = load_test_model(model=model, num_calls=5)
+# print(result)
+# print(len(result["results"]))
+
+# ## Duration Test Model
+# model="gpt-3.5-turbo"
+# result = load_test_model(model=model, num_calls=5, duration=15, interval=15) # duration test the model for 2 minutes, sending 5 calls every 15s
+# print(result)
+
+## Quality Test across Model
+models = [
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-16k",
+ "gpt-4",
+ "claude-instant-1",
+ {
+ "model": "replicate/llama-2-70b-chat:58d078176e02c219e11eb4da5a02a7830a283b14cf8f94537af893ccff5ee781",
+ "custom_llm_provider": "replicate",
+ },
+]
+messages = [
+ [{"role": "user", "content": "What is your name?"}],
+ [{"role": "user", "content": "Hey, how's it going?"}],
+]
+result = testing_batch_completion(models=models, messages=messages)
+print(result)
diff --git a/litellm/tests/test_logging.py b/litellm/tests/test_logging.py
index 3174083ef..37caeffa9 100644
--- a/litellm/tests/test_logging.py
+++ b/litellm/tests/test_logging.py
@@ -3,7 +3,10 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
@@ -11,49 +14,53 @@ litellm.set_verbose = False
score = 0
+
def logger_fn(model_call_object: dict):
print(f"model call details: {model_call_object}")
-user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
-# test on openai completion call
+user_message = "Hello, how are you?"
+messages = [{"content": user_message, "role": "user"}]
+
+# test on openai completion call
try:
response = completion(model="gpt-3.5-turbo", messages=messages, logger_fn=logger_fn)
- score +=1
+ score += 1
except:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
pass
-# test on non-openai completion call
+# test on non-openai completion call
try:
- response = completion(model="claude-instant-1", messages=messages, logger_fn=logger_fn)
+ response = completion(
+ model="claude-instant-1", messages=messages, logger_fn=logger_fn
+ )
print(f"claude response: {response}")
- score +=1
+ score += 1
except:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
pass
-# # test on openai embedding call
-# try:
+# # test on openai embedding call
+# try:
# response = embedding(model='text-embedding-ada-002', input=[user_message], logger_fn=logger_fn)
-# score +=1
+# score +=1
# except:
# traceback.print_exc()
# # test on bad azure openai embedding call -> missing azure flag and this isn't an embedding model
-# try:
+# try:
# response = embedding(model='chatgpt-test', input=[user_message], logger_fn=logger_fn)
# except:
# score +=1 # expect this to fail
# traceback.print_exc()
-# # test on good azure openai embedding call
-# try:
+# # test on good azure openai embedding call
+# try:
# response = embedding(model='azure-embedding-model', input=[user_message], azure=True, logger_fn=logger_fn)
-# score +=1
+# score +=1
# except:
# traceback.print_exc()
-# print(f"Score: {score}, Overall score: {score/5}")
\ No newline at end of file
+# print(f"Score: {score}, Overall score: {score/5}")
diff --git a/litellm/tests/test_model_fallback.py b/litellm/tests/test_model_fallback.py
index 69dc1f68d..82535f77a 100644
--- a/litellm/tests/test_model_fallback.py
+++ b/litellm/tests/test_model_fallback.py
@@ -3,7 +3,10 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
@@ -15,11 +18,11 @@ litellm.set_verbose = True
model_fallback_list = ["claude-instant-1", "gpt-3.5-turbo", "chatgpt-test"]
user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
for model in model_fallback_list:
try:
response = embedding(model="text-embedding-ada-002", input=[user_message])
response = completion(model=model, messages=messages)
except Exception as e:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
diff --git a/litellm/tests/test_model_response_typing/server.py b/litellm/tests/test_model_response_typing/server.py
new file mode 100644
index 000000000..80dbc33af
--- /dev/null
+++ b/litellm/tests/test_model_response_typing/server.py
@@ -0,0 +1,23 @@
+# #### What this tests ####
+# # This tests if the litellm model response type is returnable in a flask app
+
+# import sys, os
+# import traceback
+# from flask import Flask, request, jsonify, abort, Response
+# sys.path.insert(0, os.path.abspath('../../..')) # Adds the parent directory to the system path
+
+# import litellm
+# from litellm import completion
+
+# litellm.set_verbose = False
+
+# app = Flask(__name__)
+
+# @app.route('/')
+# def hello():
+# data = request.json
+# return completion(**data)
+
+# if __name__ == '__main__':
+# from waitress import serve
+# serve(app, host='localhost', port=8080, threads=10)
diff --git a/litellm/tests/test_model_response_typing/test.py b/litellm/tests/test_model_response_typing/test.py
new file mode 100644
index 000000000..95d404809
--- /dev/null
+++ b/litellm/tests/test_model_response_typing/test.py
@@ -0,0 +1,14 @@
+# import requests, json
+
+# BASE_URL = 'http://localhost:8080'
+
+# def test_hello_route():
+# data = {"model": "claude-instant-1", "messages": [{"role": "user", "content": "hey, how's it going?"}]}
+# headers = {'Content-Type': 'application/json'}
+# response = requests.get(BASE_URL, headers=headers, data=json.dumps(data))
+# print(response.text)
+# assert response.status_code == 200
+# print("Hello route test passed!")
+
+# if __name__ == '__main__':
+# test_hello_route()
diff --git a/litellm/tests/test_no_client.py b/litellm/tests/test_no_client.py
index 79c47d0da..05badddb6 100644
--- a/litellm/tests/test_no_client.py
+++ b/litellm/tests/test_no_client.py
@@ -4,7 +4,10 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
@@ -13,11 +16,11 @@ litellm.set_verbose = True
model_fallback_list = ["claude-instant-1", "gpt-3.5-turbo", "chatgpt-test"]
user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
for model in model_fallback_list:
try:
response = embedding(model="text-embedding-ada-002", input=[user_message])
response = completion(model=model, messages=messages)
except Exception as e:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
diff --git a/litellm/tests/test_ollama.py b/litellm/tests/test_ollama.py
index d95414560..8e0732a2c 100644
--- a/litellm/tests/test_ollama.py
+++ b/litellm/tests/test_ollama.py
@@ -53,7 +53,6 @@
# # # return this generator to the client for streaming requests
-
# # async def get_response():
# # global generator
# # async for elem in generator:
diff --git a/litellm/tests/test_ollama_local.py b/litellm/tests/test_ollama_local.py
index 22544f4cf..a9431a932 100644
--- a/litellm/tests/test_ollama_local.py
+++ b/litellm/tests/test_ollama_local.py
@@ -12,7 +12,6 @@
# import asyncio
-
# user_message = "respond in 20 words. who are you?"
# messages = [{ "content": user_message,"role": "user"}]
@@ -45,8 +44,3 @@
# pytest.fail(f"Error occurred: {e}")
# test_completion_ollama_stream()
-
-
-
-
-
diff --git a/litellm/tests/test_secrets.py b/litellm/tests/test_secrets.py
index 72e1bfb08..9b9757015 100644
--- a/litellm/tests/test_secrets.py
+++ b/litellm/tests/test_secrets.py
@@ -4,7 +4,10 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import embedding, completion
from infisical import InfisicalClient
@@ -15,15 +18,8 @@ infisical_token = os.environ["INFISICAL_TOKEN"]
litellm.secret_manager_client = InfisicalClient(token=infisical_token)
user_message = "Hello, whats the weather in San Francisco??"
-messages = [{ "content": user_message,"role": "user"}]
+messages = [{"content": user_message, "role": "user"}]
-def test_completion_azure():
- try:
- response = completion(model="gpt-3.5-turbo", deployment_id="chatgpt-test", messages=messages, custom_llm_provider="azure")
- # Add any assertions here to check the response
- print(response)
- except Exception as e:
- pytest.fail(f"Error occurred: {e}")
def test_completion_openai():
try:
@@ -31,12 +27,9 @@ def test_completion_openai():
# Add any assertions here to check the response
print(response)
except Exception as e:
+ litellm.secret_manager_client = None
pytest.fail(f"Error occurred: {e}")
+ litellm.secret_manager_client = None
-def test_completion_openai_with_optional_params():
- try:
- response = completion(model="gpt-3.5-turbo", messages=messages, temperature=0.5, top_p=0.1, user="ishaan_dev@berri.ai")
- # Add any assertions here to check the response
- print(response)
- except Exception as e:
- pytest.fail(f"Error occurred: {e}")
\ No newline at end of file
+
+test_completion_openai()
diff --git a/litellm/tests/test_streaming.py b/litellm/tests/test_streaming.py
index 317dea904..ef2063828 100644
--- a/litellm/tests/test_streaming.py
+++ b/litellm/tests/test_streaming.py
@@ -3,7 +3,10 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import litellm
from litellm import completion
@@ -11,29 +14,40 @@ litellm.set_verbose = False
score = 0
+
def logger_fn(model_call_object: dict):
print(f"model call details: {model_call_object}")
-user_message = "Hello, how are you?"
-messages = [{ "content": user_message,"role": "user"}]
-# test on anthropic completion call
+user_message = "Hello, how are you?"
+messages = [{"content": user_message, "role": "user"}]
+
+# test on anthropic completion call
try:
- response = completion(model="claude-instant-1", messages=messages, stream=True, logger_fn=logger_fn)
+ response = completion(
+ model="claude-instant-1", messages=messages, stream=True, logger_fn=logger_fn
+ )
for chunk in response:
- print(chunk['choices'][0]['delta'])
- score +=1
+ print(chunk["choices"][0]["delta"])
+ score += 1
except:
- print(f"error occurred: {traceback.format_exc()}")
+ print(f"error occurred: {traceback.format_exc()}")
pass
-# test on anthropic completion call
+# test on anthropic completion call
try:
- response = completion(model="meta-llama/Llama-2-7b-chat-hf", messages=messages, custom_llm_provider="huggingface", custom_api_base="https://s7c7gytn18vnu4tw.us-east-1.aws.endpoints.huggingface.cloud", stream=True, logger_fn=logger_fn)
+ response = completion(
+ model="meta-llama/Llama-2-7b-chat-hf",
+ messages=messages,
+ custom_llm_provider="huggingface",
+ custom_api_base="https://s7c7gytn18vnu4tw.us-east-1.aws.endpoints.huggingface.cloud",
+ stream=True,
+ logger_fn=logger_fn,
+ )
for chunk in response:
- print(chunk['choices'][0]['delta'])
- score +=1
+ print(chunk["choices"][0]["delta"])
+ score += 1
except:
- print(f"error occurred: {traceback.format_exc()}")
- pass
\ No newline at end of file
+ print(f"error occurred: {traceback.format_exc()}")
+ pass
diff --git a/litellm/tests/test_supabase_integration.py b/litellm/tests/test_supabase_integration.py
index ac4e31b58..abfa4be18 100644
--- a/litellm/tests/test_supabase_integration.py
+++ b/litellm/tests/test_supabase_integration.py
@@ -1,5 +1,5 @@
# #### What this tests ####
-# # This tests if logging to the helicone integration actually works
+# # This tests if logging to the supabase integration actually works
# # pytest mistakes intentional bad calls as failed tests -> [TODO] fix this
# import sys, os
# import traceback
@@ -9,10 +9,11 @@
# import litellm
# from litellm import embedding, completion
+# litellm.input_callback = ["supabase"]
# litellm.success_callback = ["supabase"]
# litellm.failure_callback = ["supabase"]
-# litellm.modify_integration("supabase",{"table_name": "litellm_logs"})
+# # litellm.modify_integration("supabase",{"table_name": "test_table"})
# litellm.set_verbose = True
@@ -21,7 +22,7 @@
# #openai call
-# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
+# response = completion(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
# #bad request call
-# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}])
\ No newline at end of file
+# response = completion(model="chatgpt-test", messages=[{"role": "user", "content": "Hi 👋 - i'm a bad request"}])
diff --git a/litellm/tests/test_timeout.py b/litellm/tests/test_timeout.py
index 31f27e12b..b2bc43ed8 100644
--- a/litellm/tests/test_timeout.py
+++ b/litellm/tests/test_timeout.py
@@ -3,10 +3,14 @@
import sys, os
import traceback
-sys.path.insert(0, os.path.abspath('../..')) # Adds the parent directory to the system path
+
+sys.path.insert(
+ 0, os.path.abspath("../..")
+) # Adds the parent directory to the system path
import time
from litellm import timeout
+
@timeout(10)
def stop_after_10_s(force_timeout=60):
print("Stopping after 10 seconds")
@@ -14,14 +18,14 @@ def stop_after_10_s(force_timeout=60):
return
-start_time = time.time()
+start_time = time.time()
try:
- stop_after_10_s(force_timeout=1)
+ stop_after_10_s(force_timeout=1)
except Exception as e:
- print(e)
- pass
+ print(e)
+ pass
end_time = time.time()
-print(f"total time: {end_time-start_time}")
\ No newline at end of file
+print(f"total time: {end_time-start_time}")
diff --git a/litellm/tests/test_vertex.py b/litellm/tests/test_vertex.py
index 468ba8d32..01088ec89 100644
--- a/litellm/tests/test_vertex.py
+++ b/litellm/tests/test_vertex.py
@@ -49,4 +49,4 @@
# # chat = chat_model.start_chat()
# # response = chat.send_message("who are u? write a sentence", **parameters)
-# # print(f"Response from Model: {response.text}")
\ No newline at end of file
+# # print(f"Response from Model: {response.text}")
diff --git a/litellm/timeout.py b/litellm/timeout.py
index 81d99e7de..d88e16c96 100644
--- a/litellm/timeout.py
+++ b/litellm/timeout.py
@@ -11,9 +11,7 @@ from threading import Thread
from openai.error import Timeout
-def timeout(
- timeout_duration: float = None, exception_to_raise = Timeout
-):
+def timeout(timeout_duration: float = 0.0, exception_to_raise=Timeout):
"""
Wraps a function to raise the specified exception if execution time
is greater than the specified timeout.
@@ -44,7 +42,9 @@ def timeout(
result = future.result(timeout=local_timeout_duration)
except futures.TimeoutError:
thread.stop_loop()
- raise exception_to_raise(f"A timeout error occurred. The function call took longer than {local_timeout_duration} second(s).")
+ raise exception_to_raise(
+ f"A timeout error occurred. The function call took longer than {local_timeout_duration} second(s)."
+ )
thread.stop_loop()
return result
@@ -59,7 +59,9 @@ def timeout(
)
return value
except asyncio.TimeoutError:
- raise exception_to_raise(f"A timeout error occurred. The function call took longer than {local_timeout_duration} second(s).")
+ raise exception_to_raise(
+ f"A timeout error occurred. The function call took longer than {local_timeout_duration} second(s)."
+ )
if iscoroutinefunction(func):
return async_wrapper
@@ -80,4 +82,4 @@ class _LoopWrapper(Thread):
def stop_loop(self):
for task in asyncio.all_tasks(self.loop):
task.cancel()
- self.loop.call_soon_threadsafe(self.loop.stop)
\ No newline at end of file
+ self.loop.call_soon_threadsafe(self.loop.stop)
diff --git a/litellm/utils.py b/litellm/utils.py
index 545138313..e47b55978 100644
--- a/litellm/utils.py
+++ b/litellm/utils.py
@@ -1,20 +1,31 @@
import sys
import dotenv, json, traceback, threading
-import subprocess, os
-import litellm, openai
+import subprocess, os
+import litellm, openai
import random, uuid, requests
import datetime, time
import tiktoken
-import pkg_resources
-from pkg_resources import DistributionNotFound, VersionConflict
+
encoding = tiktoken.get_encoding("cl100k_base")
+import pkg_resources
from .integrations.helicone import HeliconeLogger
from .integrations.aispend import AISpendLogger
from .integrations.berrispend import BerriSpendLogger
from .integrations.supabase import Supabase
-from openai.error import AuthenticationError, InvalidRequestError, RateLimitError, ServiceUnavailableError, OpenAIError
+from .integrations.litedebugger import LiteDebugger
+from openai.error import OpenAIError as OriginalError
+from openai.openai_object import OpenAIObject
+from .exceptions import (
+ AuthenticationError,
+ InvalidRequestError,
+ RateLimitError,
+ ServiceUnavailableError,
+ OpenAIError,
+)
+from typing import List, Dict, Union, Optional
+
####### ENVIRONMENT VARIABLES ###################
-dotenv.load_dotenv() # Loading env variables using dotenv
+dotenv.load_dotenv() # Loading env variables using dotenv
sentry_sdk_instance = None
capture_exception = None
add_breadcrumb = None
@@ -25,198 +36,454 @@ heliconeLogger = None
aispendLogger = None
berrispendLogger = None
supabaseClient = None
-callback_list = []
+liteDebuggerClient = None
+callback_list: Optional[List[str]] = []
user_logger_fn = None
-additional_details = {}
+additional_details: Optional[Dict[str, str]] = {}
+local_cache: Optional[Dict[str, str]] = {}
+######## Model Response #########################
+# All liteLLM Model responses will be in this format, Follows the OpenAI Format
+# https://docs.litellm.ai/docs/completion/output
+# {
+# 'choices': [
+# {
+# 'finish_reason': 'stop',
+# 'index': 0,
+# 'message': {
+# 'role': 'assistant',
+# 'content': " I'm doing well, thank you for asking. I am Claude, an AI assistant created by Anthropic."
+# }
+# }
+# ],
+# 'created': 1691429984.3852863,
+# 'model': 'claude-instant-1',
+# 'usage': {'prompt_tokens': 18, 'completion_tokens': 23, 'total_tokens': 41}
+# }
+
+
+class Message(OpenAIObject):
+ def __init__(self, content="default", role="assistant", **params):
+ super(Message, self).__init__(**params)
+ self.content = content
+ self.role = role
+
+
+class Choices(OpenAIObject):
+ def __init__(self, finish_reason="stop", index=0, message=Message(), **params):
+ super(Choices, self).__init__(**params)
+ self.finish_reason = finish_reason
+ self.index = index
+ self.message = message
+
+
+class ModelResponse(OpenAIObject):
+ def __init__(self, choices=None, created=None, model=None, usage=None, **params):
+ super(ModelResponse, self).__init__(**params)
+ self.choices = choices if choices else [Choices()]
+ self.created = created
+ self.model = model
+ self.usage = (
+ usage
+ if usage
+ else {
+ "prompt_tokens": None,
+ "completion_tokens": None,
+ "total_tokens": None,
+ }
+ )
+
+ def to_dict_recursive(self):
+ d = super().to_dict_recursive()
+ d["choices"] = [choice.to_dict_recursive() for choice in self.choices]
+ return d
+
+
+############################################################
def print_verbose(print_statement):
- if litellm.set_verbose:
- print(f"LiteLLM: {print_statement}")
- if random.random() <= 0.3:
- print("Get help - https://discord.com/invite/wuPM9dRgDw")
+ if litellm.set_verbose:
+ print(f"LiteLLM: {print_statement}")
+ if random.random() <= 0.3:
+ print("Get help - https://discord.com/invite/wuPM9dRgDw")
+
####### Package Import Handler ###################
import importlib
import subprocess
+
+
def install_and_import(package: str):
if package in globals().keys():
- print_verbose(f"{package} has already been imported.")
- return
+ print_verbose(f"{package} has already been imported.")
+ return
try:
- # Import the module
+ # Import the module
module = importlib.import_module(package)
- except (ModuleNotFoundError, ImportError):
+ except ImportError:
print_verbose(f"{package} is not installed. Installing...")
subprocess.call([sys.executable, "-m", "pip", "install", package])
globals()[package] = importlib.import_module(package)
- except (DistributionNotFound, ImportError):
- print_verbose(f"{package} is not installed. Installing...")
- subprocess.call([sys.executable, "-m", "pip", "install", package])
- globals()[package] = importlib.import_module(package)
- except VersionConflict as vc:
- print_verbose(f"Detected version conflict for {package}. Upgrading...")
- subprocess.call([sys.executable, "-m", "pip", "install", "--upgrade", package])
- globals()[package] = importlib.import_module(package)
+ # except VersionConflict as vc:
+ # print_verbose(f"Detected version conflict for {package}. Upgrading...")
+ # subprocess.call([sys.executable, "-m", "pip", "install", "--upgrade", package])
+ # globals()[package] = importlib.import_module(package)
finally:
if package not in globals().keys():
globals()[package] = importlib.import_module(package)
+
+
##################################################
-####### LOGGING ###################
-#Logging function -> log the exact model details + what's being sent | Non-Blocking
-def logging(model=None, input=None, custom_llm_provider=None, azure=False, additional_args={}, logger_fn=None, exception=None):
- try:
- model_call_details = {}
- if model:
- model_call_details["model"] = model
- if azure:
- model_call_details["azure"] = azure
- if custom_llm_provider:
- model_call_details["custom_llm_provider"] = custom_llm_provider
- if exception:
- model_call_details["exception"] = exception
- if input:
- model_call_details["input"] = input
-
- if len(additional_args):
- model_call_details["additional_args"] = additional_args
- # log additional call details -> api key, etc.
- if model:
- if azure == True or model in litellm.open_ai_chat_completion_models or model in litellm.open_ai_chat_completion_models or model in litellm.open_ai_embedding_models:
- model_call_details["api_type"] = openai.api_type
- model_call_details["api_base"] = openai.api_base
- model_call_details["api_version"] = openai.api_version
- model_call_details["api_key"] = openai.api_key
- elif "replicate" in model:
- model_call_details["api_key"] = os.environ.get("REPLICATE_API_TOKEN")
- elif model in litellm.anthropic_models:
- model_call_details["api_key"] = os.environ.get("ANTHROPIC_API_KEY")
- elif model in litellm.cohere_models:
- model_call_details["api_key"] = os.environ.get("COHERE_API_KEY")
- ## User Logging -> if you pass in a custom logging function or want to use sentry breadcrumbs
- print_verbose(f"Logging Details: logger_fn - {logger_fn} | callable(logger_fn) - {callable(logger_fn)}")
- if logger_fn and callable(logger_fn):
- try:
- logger_fn(model_call_details) # Expectation: any logger function passed in by the user should accept a dict object
- except Exception as e:
- print(f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}")
- except Exception as e:
- print(f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}")
- pass
-####### CLIENT ###################
+####### LOGGING ###################
+# Logging function -> log the exact model details + what's being sent | Non-Blocking
+class Logging:
+ global supabaseClient, liteDebuggerClient
+ def __init__(self, model, messages, optional_params, litellm_params):
+ self.model = model
+ self.messages = messages
+ self.optional_params = optional_params
+ self.litellm_params = litellm_params
+ self.logger_fn = litellm_params["logger_fn"]
+ self.model_call_details = {
+ "model": model,
+ "messages": messages,
+ "optional_params": self.optional_params,
+ "litellm_params": self.litellm_params,
+ }
+
+ def pre_call(self, input, api_key, additional_args={}):
+ try:
+ print(f"logging pre call for model: {self.model}")
+ self.model_call_details["input"] = input
+ self.model_call_details["api_key"] = api_key
+ self.model_call_details["additional_args"] = additional_args
+
+ ## User Logging -> if you pass in a custom logging function
+ print_verbose(
+ f"Logging Details: logger_fn - {self.logger_fn} | callable(logger_fn) - {callable(self.logger_fn)}"
+ )
+ if self.logger_fn and callable(self.logger_fn):
+ try:
+ self.logger_fn(
+ self.model_call_details
+ ) # Expectation: any logger function passed in by the user should accept a dict object
+ except Exception as e:
+ print_verbose(
+ f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}"
+ )
+
+ ## Input Integration Logging -> If you want to log the fact that an attempt to call the model was made
+ for callback in litellm.input_callback:
+ try:
+ if callback == "supabase":
+ print_verbose("reaches supabase for logging!")
+ model = self.model
+ messages = self.messages
+ print(f"supabaseClient: {supabaseClient}")
+ supabaseClient.input_log_event(
+ model=model,
+ messages=messages,
+ end_user=litellm._thread_context.user,
+ litellm_call_id=self.litellm_params["litellm_call_id"],
+ print_verbose=print_verbose,
+ )
+ elif callback == "lite_debugger":
+ print_verbose("reaches litedebugger for logging!")
+ model = self.model
+ messages = self.messages
+ print(f"liteDebuggerClient: {liteDebuggerClient}")
+ liteDebuggerClient.input_log_event(
+ model=model,
+ messages=messages,
+ end_user=litellm._thread_context.user,
+ litellm_call_id=self.litellm_params["litellm_call_id"],
+ print_verbose=print_verbose,
+ )
+ except Exception as e:
+ print_verbose(f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while input logging with integrations {traceback.format_exc()}")
+ print_verbose(
+ f"LiteLLM.Logging: is sentry capture exception initialized {capture_exception}"
+ )
+ if capture_exception: # log this error to sentry for debugging
+ capture_exception(e)
+ except:
+ print_verbose(
+ f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}"
+ )
+ print_verbose(
+ f"LiteLLM.Logging: is sentry capture exception initialized {capture_exception}"
+ )
+ if capture_exception: # log this error to sentry for debugging
+ capture_exception(e)
+
+ def post_call(self, input, api_key, original_response, additional_args={}):
+ # Do something here
+ try:
+ self.model_call_details["input"] = input
+ self.model_call_details["api_key"] = api_key
+ self.model_call_details["original_response"] = original_response
+ self.model_call_details["additional_args"] = additional_args
+
+ ## User Logging -> if you pass in a custom logging function
+ print_verbose(
+ f"Logging Details: logger_fn - {self.logger_fn} | callable(logger_fn) - {callable(self.logger_fn)}"
+ )
+ if self.logger_fn and callable(self.logger_fn):
+ try:
+ self.logger_fn(
+ self.model_call_details
+ ) # Expectation: any logger function passed in by the user should accept a dict object
+ except Exception as e:
+ print_verbose(
+ f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}"
+ )
+ except:
+ print_verbose(
+ f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}"
+ )
+ pass
+
+ # Add more methods as needed
+
+
+def exception_logging(
+ additional_args={},
+ logger_fn=None,
+ exception=None,
+):
+ try:
+ model_call_details = {}
+ if exception:
+ model_call_details["exception"] = exception
+ model_call_details["additional_args"] = additional_args
+ ## User Logging -> if you pass in a custom logging function or want to use sentry breadcrumbs
+ print_verbose(
+ f"Logging Details: logger_fn - {logger_fn} | callable(logger_fn) - {callable(logger_fn)}"
+ )
+ if logger_fn and callable(logger_fn):
+ try:
+ logger_fn(
+ model_call_details
+ ) # Expectation: any logger function passed in by the user should accept a dict object
+ except Exception as e:
+ print(
+ f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}"
+ )
+ except Exception as e:
+ print(
+ f"LiteLLM.LoggingError: [Non-Blocking] Exception occurred while logging {traceback.format_exc()}"
+ )
+ pass
+
+
+####### CLIENT ###################
# make it easy to log if completion/embedding runs succeeded or failed + see what happened | Non-Blocking
def client(original_function):
- def function_setup(*args, **kwargs): #just run once to check if user wants to send their data anywhere - PostHog/Sentry/Slack/etc.
- try:
- global callback_list, add_breadcrumb, user_logger_fn
- if (len(litellm.success_callback) > 0 or len(litellm.failure_callback) > 0) and len(callback_list) == 0:
- callback_list = list(set(litellm.success_callback + litellm.failure_callback))
- set_callbacks(callback_list=callback_list,)
- if add_breadcrumb:
- add_breadcrumb(
- category="litellm.llm_call",
- message=f"Positional Args: {args}, Keyword Args: {kwargs}",
- level="info",
- )
- if "logger_fn" in kwargs:
- user_logger_fn = kwargs["logger_fn"]
- except: # DO NOT BLOCK running the function because of this
- print_verbose(f"[Non-Blocking] {traceback.format_exc()}")
- pass
+ def function_setup(
+ *args, **kwargs
+ ): # just run once to check if user wants to send their data anywhere - PostHog/Sentry/Slack/etc.
+ try:
+ global callback_list, add_breadcrumb, user_logger_fn
+ if (
+ len(litellm.input_callback) > 0 or len(litellm.success_callback) > 0 or len(litellm.failure_callback) > 0
+ ) and len(callback_list) == 0:
+ callback_list = list(
+ set(litellm.input_callback + litellm.success_callback + litellm.failure_callback)
+ )
+ set_callbacks(
+ callback_list=callback_list,
+ )
+ if add_breadcrumb:
+ add_breadcrumb(
+ category="litellm.llm_call",
+ message=f"Positional Args: {args}, Keyword Args: {kwargs}",
+ level="info",
+ )
+ if "logger_fn" in kwargs:
+ user_logger_fn = kwargs["logger_fn"]
+ except: # DO NOT BLOCK running the function because of this
+ print_verbose(f"[Non-Blocking] {traceback.format_exc()}")
+ pass
def crash_reporting(*args, **kwargs):
- if litellm.telemetry:
- try:
- model = args[0] if len(args) > 0 else kwargs["model"]
- exception = kwargs["exception"] if "exception" in kwargs else None
- custom_llm_provider = kwargs["custom_llm_provider"] if "custom_llm_provider" in kwargs else None
- safe_crash_reporting(model=model, exception=exception, custom_llm_provider=custom_llm_provider) # log usage-crash details. Do not log any user details. If you want to turn this off, set `litellm.telemetry=False`.
+ if litellm.telemetry:
+ try:
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ exception = kwargs["exception"] if "exception" in kwargs else None
+ custom_llm_provider = (
+ kwargs["custom_llm_provider"]
+ if "custom_llm_provider" in kwargs
+ else None
+ )
+ safe_crash_reporting(
+ model=model,
+ exception=exception,
+ custom_llm_provider=custom_llm_provider,
+ ) # log usage-crash details. Do not log any user details. If you want to turn this off, set `litellm.telemetry=False`.
+ except:
+ # [Non-Blocking Error]
+ pass
+
+ def get_prompt(*args, **kwargs):
+ # make this safe checks, it should not throw any exceptions
+ if len(args) > 1:
+ messages = args[1]
+ prompt = " ".join(message["content"] for message in messages)
+ return prompt
+ if "messages" in kwargs:
+ messages = kwargs["messages"]
+ prompt = " ".join(message["content"] for message in messages)
+ return prompt
+ return None
+
+ def check_cache(*args, **kwargs):
+ try: # never block execution
+ prompt = get_prompt(*args, **kwargs)
+ if (
+ prompt != None and prompt in local_cache
+ ): # check if messages / prompt exists
+ if litellm.caching_with_models:
+ # if caching with model names is enabled, key is prompt + model name
+ if (
+ "model" in kwargs
+ and kwargs["model"] in local_cache[prompt]["models"]
+ ):
+ cache_key = prompt + kwargs["model"]
+ return local_cache[cache_key]
+ else: # caching only with prompts
+ result = local_cache[prompt]
+ return result
+ else:
+ return None
except:
- #[Non-Blocking Error]
- pass
+ return None
+
+ def add_cache(result, *args, **kwargs):
+ try: # never block execution
+ prompt = get_prompt(*args, **kwargs)
+ if litellm.caching_with_models: # caching with model + prompt
+ if (
+ "model" in kwargs
+ and kwargs["model"] in local_cache[prompt]["models"]
+ ):
+ cache_key = prompt + kwargs["model"]
+ local_cache[cache_key] = result
+ else: # caching based only on prompts
+ local_cache[prompt] = result
+ except:
+ pass
def wrapper(*args, **kwargs):
start_time = None
+ result = None
try:
- function_setup(*args, **kwargs)
- ## MODEL CALL
- start_time = datetime.datetime.now()
- result = original_function(*args, **kwargs)
- end_time = datetime.datetime.now()
- ## LOG SUCCESS
- crash_reporting(*args, **kwargs)
- my_thread = threading.Thread(target=handle_success, args=(args, kwargs, result, start_time, end_time)) # don't interrupt execution of main thread
- my_thread.start()
- return result
+ function_setup(*args, **kwargs)
+ litellm_call_id = str(uuid.uuid4())
+ kwargs["litellm_call_id"] = litellm_call_id
+ ## [OPTIONAL] CHECK CACHE
+ start_time = datetime.datetime.now()
+ if (litellm.caching or litellm.caching_with_models) and (
+ cached_result := check_cache(*args, **kwargs)
+ ) is not None:
+ result = cached_result
+ else:
+ ## MODEL CALL
+ result = original_function(*args, **kwargs)
+ end_time = datetime.datetime.now()
+ ## Add response to CACHE
+ if litellm.caching:
+ add_cache(result, *args, **kwargs)
+ ## LOG SUCCESS
+ crash_reporting(*args, **kwargs)
+ my_thread = threading.Thread(
+ target=handle_success, args=(args, kwargs, result, start_time, end_time)
+ ) # don't interrupt execution of main thread
+ my_thread.start()
+ return result
except Exception as e:
- traceback_exception = traceback.format_exc()
- crash_reporting(*args, **kwargs, exception=traceback_exception)
- end_time = datetime.datetime.now()
- my_thread = threading.Thread(target=handle_failure, args=(e, traceback_exception, start_time, end_time, args, kwargs)) # don't interrupt execution of main thread
- my_thread.start()
- raise e
+ traceback_exception = traceback.format_exc()
+ crash_reporting(*args, **kwargs, exception=traceback_exception)
+ end_time = datetime.datetime.now()
+ my_thread = threading.Thread(
+ target=handle_failure,
+ args=(e, traceback_exception, start_time, end_time, args, kwargs),
+ ) # don't interrupt execution of main thread
+ my_thread.start()
+ raise e
+
return wrapper
+
####### USAGE CALCULATOR ################
+
def token_counter(model, text):
- # use tiktoken or anthropic's tokenizer depending on the model
- num_tokens = 0
- if "claude" in model:
- install_and_import('anthropic')
- from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
- anthropic = Anthropic()
- num_tokens = anthropic.count_tokens(text)
- else:
- num_tokens = len(encoding.encode(text))
- return num_tokens
+ # use tiktoken or anthropic's tokenizer depending on the model
+ num_tokens = 0
+ if "claude" in model:
+ install_and_import("anthropic")
+ from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
+
+ anthropic = Anthropic()
+ num_tokens = anthropic.count_tokens(text)
+ else:
+ num_tokens = len(encoding.encode(text))
+ return num_tokens
-def cost_per_token(model="gpt-3.5-turbo", prompt_tokens = 0, completion_tokens = 0):
- ## given
- prompt_tokens_cost_usd_dollar = 0
- completion_tokens_cost_usd_dollar = 0
- model_cost_ref = litellm.model_cost
- if model in model_cost_ref:
- prompt_tokens_cost_usd_dollar = model_cost_ref[model]["input_cost_per_token"] * prompt_tokens
- completion_tokens_cost_usd_dollar = model_cost_ref[model]["output_cost_per_token"] * completion_tokens
- return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
- else:
- # calculate average input cost
- input_cost_sum = 0
- output_cost_sum = 0
+def cost_per_token(model="gpt-3.5-turbo", prompt_tokens=0, completion_tokens=0):
+ ## given
+ prompt_tokens_cost_usd_dollar = 0
+ completion_tokens_cost_usd_dollar = 0
model_cost_ref = litellm.model_cost
- for model in model_cost_ref:
- input_cost_sum += model_cost_ref[model]["input_cost_per_token"]
- output_cost_sum += model_cost_ref[model]["output_cost_per_token"]
- avg_input_cost = input_cost_sum / len(model_cost_ref.keys())
- avg_output_cost = output_cost_sum / len(model_cost_ref.keys())
- prompt_tokens_cost_usd_dollar = avg_input_cost * prompt_tokens
- completion_tokens_cost_usd_dollar = avg_output_cost * completion_tokens
- return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
-
+ if model in model_cost_ref:
+ prompt_tokens_cost_usd_dollar = (
+ model_cost_ref[model]["input_cost_per_token"] * prompt_tokens
+ )
+ completion_tokens_cost_usd_dollar = (
+ model_cost_ref[model]["output_cost_per_token"] * completion_tokens
+ )
+ return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
+ else:
+ # calculate average input cost
+ input_cost_sum = 0
+ output_cost_sum = 0
+ model_cost_ref = litellm.model_cost
+ for model in model_cost_ref:
+ input_cost_sum += model_cost_ref[model]["input_cost_per_token"]
+ output_cost_sum += model_cost_ref[model]["output_cost_per_token"]
+ avg_input_cost = input_cost_sum / len(model_cost_ref.keys())
+ avg_output_cost = output_cost_sum / len(model_cost_ref.keys())
+ prompt_tokens_cost_usd_dollar = avg_input_cost * prompt_tokens
+ completion_tokens_cost_usd_dollar = avg_output_cost * completion_tokens
+ return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar
+
def completion_cost(model="gpt-3.5-turbo", prompt="", completion=""):
- prompt_tokens = token_counter(model=model, text=prompt)
- completion_tokens = token_counter(model=model, text=completion)
- prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = cost_per_token(model=model, prompt_tokens = prompt_tokens, completion_tokens = completion_tokens)
- return prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
+ prompt_tokens = token_counter(model=model, text=prompt)
+ completion_tokens = token_counter(model=model, text=completion)
+ prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = cost_per_token(
+ model=model, prompt_tokens=prompt_tokens, completion_tokens=completion_tokens
+ )
+ return prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar
+
####### HELPER FUNCTIONS ################
def get_litellm_params(
return_async=False,
- api_key=None,
- force_timeout=600,
- azure=False,
- logger_fn=None,
+ api_key=None,
+ force_timeout=600,
+ azure=False,
+ logger_fn=None,
verbose=False,
- hugging_face=False,
+ hugging_face=False,
replicate=False,
- together_ai=False,
- custom_llm_provider=None,
- custom_api_base=None
-):
+ together_ai=False,
+ custom_llm_provider=None,
+ custom_api_base=None,
+ litellm_call_id=None,
+):
litellm_params = {
"return_async": return_async,
"api_key": api_key,
@@ -224,458 +491,697 @@ def get_litellm_params(
"logger_fn": logger_fn,
"verbose": verbose,
"custom_llm_provider": custom_llm_provider,
- "custom_api_base": custom_api_base
+ "custom_api_base": custom_api_base,
+ "litellm_call_id": litellm_call_id
}
-
+
return litellm_params
def get_optional_params(
# 12 optional params
- functions = [],
- function_call = "",
- temperature = 1,
- top_p = 1,
- n = 1,
- stream = False,
- stop = None,
- max_tokens = float('inf'),
- presence_penalty = 0,
- frequency_penalty = 0,
- logit_bias = {},
- user = "",
- deployment_id = None,
- model = None,
- custom_llm_provider = "",
- top_k = 40,
+ functions=[],
+ function_call="",
+ temperature=1,
+ top_p=1,
+ n=1,
+ stream=False,
+ stop=None,
+ max_tokens=float("inf"),
+ presence_penalty=0,
+ frequency_penalty=0,
+ logit_bias={},
+ user="",
+ deployment_id=None,
+ model=None,
+ custom_llm_provider="",
+ top_k=40,
):
- optional_params = {}
- if model in litellm.anthropic_models:
- # handle anthropic params
- if stream:
- optional_params["stream"] = stream
- if stop != None:
- optional_params["stop_sequences"] = stop
- if temperature != 1:
+ optional_params = {}
+ if model in litellm.anthropic_models:
+ # handle anthropic params
+ if stream:
+ optional_params["stream"] = stream
+ if stop != None:
+ optional_params["stop_sequences"] = stop
+ if temperature != 1:
+ optional_params["temperature"] = temperature
+ if top_p != 1:
+ optional_params["top_p"] = top_p
+ return optional_params
+ elif model in litellm.cohere_models:
+ # handle cohere params
+ if stream:
+ optional_params["stream"] = stream
+ if temperature != 1:
+ optional_params["temperature"] = temperature
+ if max_tokens != float("inf"):
+ optional_params["max_tokens"] = max_tokens
+ if logit_bias != {}:
+ optional_params["logit_bias"] = logit_bias
+ return optional_params
+ elif custom_llm_provider == "replicate":
+ # any replicate models
+ # TODO: handle translating remaining replicate params
+ if stream:
+ optional_params["stream"] = stream
+ return optional_params
+ elif custom_llm_provider == "together_ai" or ("togethercomputer" in model):
+ if stream:
+ optional_params["stream_tokens"] = stream
+ if temperature != 1:
+ optional_params["temperature"] = temperature
+ if top_p != 1:
+ optional_params["top_p"] = top_p
+ if max_tokens != float("inf"):
+ optional_params["max_tokens"] = max_tokens
+ if frequency_penalty != 0:
+ optional_params["frequency_penalty"] = frequency_penalty
+ elif (
+ model == "chat-bison"
+ ): # chat-bison has diff args from chat-bison@001 ty Google
+ if temperature != 1:
+ optional_params["temperature"] = temperature
+ if top_p != 1:
+ optional_params["top_p"] = top_p
+ if max_tokens != float("inf"):
+ optional_params["max_output_tokens"] = max_tokens
+ elif model in litellm.vertex_text_models:
+ # required params for all text vertex calls
+ # temperature=0.2, top_p=0.1, top_k=20
+ # always set temperature, top_p, top_k else, text bison fails
optional_params["temperature"] = temperature
- if top_p != 1:
optional_params["top_p"] = top_p
- return optional_params
- elif model in litellm.cohere_models:
- # handle cohere params
- if stream:
- optional_params["stream"] = stream
- if temperature != 1:
- optional_params["temperature"] = temperature
- if max_tokens != float('inf'):
- optional_params["max_tokens"] = max_tokens
- return optional_params
- elif custom_llm_provider == "replicate":
- # any replicate models
- # TODO: handle translating remaining replicate params
- if stream:
- optional_params["stream"] = stream
- return optional_params
- elif custom_llm_provider == "together_ai":
- if stream:
- optional_params["stream_tokens"] = stream
- if temperature != 1:
- optional_params["temperature"] = temperature
- if top_p != 1:
- optional_params["top_p"] = top_p
- if max_tokens != float('inf'):
- optional_params["max_tokens"] = max_tokens
- if frequency_penalty != 0:
- optional_params["frequency_penalty"] = frequency_penalty
- elif model == "chat-bison": # chat-bison has diff args from chat-bison@001 ty Google
- if temperature != 1:
- optional_params["temperature"] = temperature
- if top_p != 1:
- optional_params["top_p"] = top_p
- if max_tokens != float('inf'):
- optional_params["max_output_tokens"] = max_tokens
- elif model in litellm.vertex_text_models:
- # required params for all text vertex calls
- # temperature=0.2, top_p=0.1, top_k=20
- # always set temperature, top_p, top_k else, text bison fails
- optional_params["temperature"] = temperature
- optional_params["top_p"] = top_p
- optional_params["top_k"] = top_k
+ optional_params["top_k"] = top_k
- else:# assume passing in params for openai/azure openai
- if functions != []:
- optional_params["functions"] = functions
- if function_call != "":
- optional_params["function_call"] = function_call
- if temperature != 1:
- optional_params["temperature"] = temperature
- if top_p != 1:
- optional_params["top_p"] = top_p
- if n != 1:
- optional_params["n"] = n
- if stream:
- optional_params["stream"] = stream
- if stop != None:
- optional_params["stop"] = stop
- if max_tokens != float('inf'):
- optional_params["max_tokens"] = max_tokens
- if presence_penalty != 0:
- optional_params["presence_penalty"] = presence_penalty
- if frequency_penalty != 0:
- optional_params["frequency_penalty"] = frequency_penalty
- if logit_bias != {}:
- optional_params["logit_bias"] = logit_bias
- if user != "":
- optional_params["user"] = user
- if deployment_id != None:
- optional_params["deployment_id"] = deployment_id
+ else: # assume passing in params for openai/azure openai
+ if functions != []:
+ optional_params["functions"] = functions
+ if function_call != "":
+ optional_params["function_call"] = function_call
+ if temperature != 1:
+ optional_params["temperature"] = temperature
+ if top_p != 1:
+ optional_params["top_p"] = top_p
+ if n != 1:
+ optional_params["n"] = n
+ if stream:
+ optional_params["stream"] = stream
+ if stop != None:
+ optional_params["stop"] = stop
+ if max_tokens != float("inf"):
+ optional_params["max_tokens"] = max_tokens
+ if presence_penalty != 0:
+ optional_params["presence_penalty"] = presence_penalty
+ if frequency_penalty != 0:
+ optional_params["frequency_penalty"] = frequency_penalty
+ if logit_bias != {}:
+ optional_params["logit_bias"] = logit_bias
+ if user != "":
+ optional_params["user"] = user
+ if deployment_id != None:
+ optional_params["deployment_id"] = deployment_id
+ return optional_params
return optional_params
- return optional_params
-def load_test_model(model: str, custom_llm_provider: str = None, custom_api_base: str = None, prompt: str = None, num_calls: int = None, force_timeout: int = None):
- test_prompt = "Hey, how's it going"
- test_calls = 100
- if prompt:
- test_prompt = prompt
- if num_calls:
- test_calls = num_calls
- messages = [[{"role": "user", "content": test_prompt}] for _ in range(test_calls)]
- start_time = time.time()
- try:
- litellm.batch_completion(model=model, messages=messages, custom_llm_provider=custom_llm_provider, custom_api_base = custom_api_base, force_timeout=force_timeout)
- end_time = time.time()
- response_time = end_time - start_time
- return {"total_response_time": response_time, "calls_made": 100, "status": "success", "exception": None}
- except Exception as e:
- end_time = time.time()
- response_time = end_time - start_time
- return {"total_response_time": response_time, "calls_made": 100, "status": "failed", "exception": e}
+
+def load_test_model(
+ model: str,
+ custom_llm_provider: str = "",
+ custom_api_base: str = "",
+ prompt: str = "",
+ num_calls: int = 0,
+ force_timeout: int = 0,
+):
+ test_prompt = "Hey, how's it going"
+ test_calls = 100
+ if prompt:
+ test_prompt = prompt
+ if num_calls:
+ test_calls = num_calls
+ messages = [[{"role": "user", "content": test_prompt}] for _ in range(test_calls)]
+ start_time = time.time()
+ try:
+ litellm.batch_completion(
+ model=model,
+ messages=messages,
+ custom_llm_provider=custom_llm_provider,
+ custom_api_base=custom_api_base,
+ force_timeout=force_timeout,
+ )
+ end_time = time.time()
+ response_time = end_time - start_time
+ return {
+ "total_response_time": response_time,
+ "calls_made": 100,
+ "status": "success",
+ "exception": None,
+ }
+ except Exception as e:
+ end_time = time.time()
+ response_time = end_time - start_time
+ return {
+ "total_response_time": response_time,
+ "calls_made": 100,
+ "status": "failed",
+ "exception": e,
+ }
+
def set_callbacks(callback_list):
- global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel, heliconeLogger, aispendLogger, berrispendLogger, supabaseClient
- try:
- for callback in callback_list:
- if callback == "sentry":
- try:
- import sentry_sdk
- except ImportError:
- print_verbose("Package 'sentry_sdk' is missing. Installing it...")
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'sentry_sdk'])
- import sentry_sdk
- sentry_sdk_instance = sentry_sdk
- sentry_trace_rate = os.environ.get("SENTRY_API_TRACE_RATE") if "SENTRY_API_TRACE_RATE" in os.environ else "1.0"
- sentry_sdk_instance.init(dsn=os.environ.get("SENTRY_API_URL"), traces_sample_rate=float(os.environ.get("SENTRY_API_TRACE_RATE")))
- capture_exception = sentry_sdk_instance.capture_exception
- add_breadcrumb = sentry_sdk_instance.add_breadcrumb
- elif callback == "posthog":
- try:
- from posthog import Posthog
- except ImportError:
- print_verbose("Package 'posthog' is missing. Installing it...")
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'posthog'])
- from posthog import Posthog
- posthog = Posthog(
- project_api_key=os.environ.get("POSTHOG_API_KEY"),
- host=os.environ.get("POSTHOG_API_URL"))
- elif callback == "slack":
- try:
- from slack_bolt import App
- except ImportError:
- print_verbose("Package 'slack_bolt' is missing. Installing it...")
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'slack_bolt'])
- from slack_bolt import App
- slack_app = App(
- token=os.environ.get("SLACK_API_TOKEN"),
- signing_secret=os.environ.get("SLACK_API_SECRET")
- )
- alerts_channel = os.environ["SLACK_API_CHANNEL"]
- print_verbose(f"Initialized Slack App: {slack_app}")
- elif callback == "helicone":
- heliconeLogger = HeliconeLogger()
- elif callback == "aispend":
- aispendLogger = AISpendLogger()
- elif callback == "berrispend":
- berrispendLogger = BerriSpendLogger()
- elif callback == "supabase":
- supabaseClient = Supabase()
- except Exception as e:
- raise e
+ global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel, heliconeLogger, aispendLogger, berrispendLogger, supabaseClient, liteDebuggerClient
+ try:
+ for callback in callback_list:
+ print(f"callback: {callback}")
+ if callback == "sentry":
+ try:
+ import sentry_sdk
+ except ImportError:
+ print_verbose("Package 'sentry_sdk' is missing. Installing it...")
+ subprocess.check_call(
+ [sys.executable, "-m", "pip", "install", "sentry_sdk"]
+ )
+ import sentry_sdk
+ sentry_sdk_instance = sentry_sdk
+ sentry_trace_rate = (
+ os.environ.get("SENTRY_API_TRACE_RATE")
+ if "SENTRY_API_TRACE_RATE" in os.environ
+ else "1.0"
+ )
+ sentry_sdk_instance.init(
+ dsn=os.environ.get("SENTRY_API_URL"),
+ traces_sample_rate=float(sentry_trace_rate),
+ )
+ capture_exception = sentry_sdk_instance.capture_exception
+ add_breadcrumb = sentry_sdk_instance.add_breadcrumb
+ elif callback == "posthog":
+ try:
+ from posthog import Posthog
+ except ImportError:
+ print_verbose("Package 'posthog' is missing. Installing it...")
+ subprocess.check_call(
+ [sys.executable, "-m", "pip", "install", "posthog"]
+ )
+ from posthog import Posthog
+ posthog = Posthog(
+ project_api_key=os.environ.get("POSTHOG_API_KEY"),
+ host=os.environ.get("POSTHOG_API_URL"),
+ )
+ elif callback == "slack":
+ try:
+ from slack_bolt import App
+ except ImportError:
+ print_verbose("Package 'slack_bolt' is missing. Installing it...")
+ subprocess.check_call(
+ [sys.executable, "-m", "pip", "install", "slack_bolt"]
+ )
+ from slack_bolt import App
+ slack_app = App(
+ token=os.environ.get("SLACK_API_TOKEN"),
+ signing_secret=os.environ.get("SLACK_API_SECRET"),
+ )
+ alerts_channel = os.environ["SLACK_API_CHANNEL"]
+ print_verbose(f"Initialized Slack App: {slack_app}")
+ elif callback == "helicone":
+ heliconeLogger = HeliconeLogger()
+ elif callback == "aispend":
+ aispendLogger = AISpendLogger()
+ elif callback == "berrispend":
+ berrispendLogger = BerriSpendLogger()
+ elif callback == "supabase":
+ print(f"instantiating supabase")
+ supabaseClient = Supabase()
+ elif callback == "lite_debugger":
+ print(f"instantiating lite_debugger")
+ liteDebuggerClient = LiteDebugger()
+ except Exception as e:
+ raise e
def handle_failure(exception, traceback_exception, start_time, end_time, args, kwargs):
- global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel, aispendLogger, berrispendLogger
+ global sentry_sdk_instance, capture_exception, add_breadcrumb, posthog, slack_app, alerts_channel, aispendLogger, berrispendLogger, supabaseClient, liteDebuggerClient
try:
- # print_verbose(f"handle_failure args: {args}")
- # print_verbose(f"handle_failure kwargs: {kwargs}")
-
- success_handler = additional_details.pop("success_handler", None)
- failure_handler = additional_details.pop("failure_handler", None)
-
- additional_details["Event_Name"] = additional_details.pop("failed_event_name", "litellm.failed_query")
- print_verbose(f"self.failure_callback: {litellm.failure_callback}")
+ # print_verbose(f"handle_failure args: {args}")
+ # print_verbose(f"handle_failure kwargs: {kwargs}")
+ success_handler = additional_details.pop("success_handler", None)
+ failure_handler = additional_details.pop("failure_handler", None)
- # print_verbose(f"additional_details: {additional_details}")
- for callback in litellm.failure_callback:
- try:
- if callback == "slack":
- slack_msg = ""
- if len(kwargs) > 0:
- for key in kwargs:
- slack_msg += f"{key}: {kwargs[key]}\n"
- if len(args) > 0:
- for i, arg in enumerate(args):
- slack_msg += f"LiteLLM_Args_{str(i)}: {arg}"
- for detail in additional_details:
- slack_msg += f"{detail}: {additional_details[detail]}\n"
- slack_msg += f"Traceback: {traceback_exception}"
- slack_app.client.chat_postMessage(channel=alerts_channel, text=slack_msg)
- elif callback == "sentry":
- capture_exception(exception)
- elif callback == "posthog":
- print_verbose(f"inside posthog, additional_details: {len(additional_details.keys())}")
- ph_obj = {}
- if len(kwargs) > 0:
- ph_obj = kwargs
- if len(args) > 0:
- for i, arg in enumerate(args):
- ph_obj["litellm_args_" + str(i)] = arg
- for detail in additional_details:
- ph_obj[detail] = additional_details[detail]
- event_name = additional_details["Event_Name"]
- print_verbose(f"ph_obj: {ph_obj}")
- print_verbose(f"PostHog Event Name: {event_name}")
- if "user_id" in additional_details:
- posthog.capture(additional_details["user_id"], event_name, ph_obj)
- else: # PostHog calls require a unique id to identify a user - https://posthog.com/docs/libraries/python
- unique_id = str(uuid.uuid4())
- posthog.capture(unique_id, event_name)
- print_verbose(f"successfully logged to PostHog!")
- elif callback == "berrispend":
- print_verbose("reaches berrispend for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- messages = args[1] if len(args) > 1 else kwargs["messages"]
- result = {
- "model": model,
- "created": time.time(),
- "error": traceback_exception,
- "usage": {
- "prompt_tokens": prompt_token_calculator(model, messages=messages),
- "completion_tokens": 0
- }
- }
- berrispendLogger.log_event(model=model, messages=messages, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
- elif callback == "aispend":
- print_verbose("reaches aispend for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- messages = args[1] if len(args) > 1 else kwargs["messages"]
- result = {
- "model": model,
- "created": time.time(),
- "usage": {
- "prompt_tokens": prompt_token_calculator(model, messages=messages),
- "completion_tokens": 0
- }
- }
- aispendLogger.log_event(model=model, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
- elif callback == "supabase":
- print_verbose("reaches supabase for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- messages = args[1] if len(args) > 1 else kwargs["messages"]
- result = {
- "model": model,
- "created": time.time(),
- "error": traceback_exception,
- "usage": {
- "prompt_tokens": prompt_token_calculator(model, messages=messages),
- "completion_tokens": 0
- }
- }
- print(f"litellm._thread_context: {litellm._thread_context}")
- supabaseClient.log_event(model=model, messages=messages, end_user=litellm._thread_context.user, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
+ additional_details["Event_Name"] = additional_details.pop(
+ "failed_event_name", "litellm.failed_query"
+ )
+ print_verbose(f"self.failure_callback: {litellm.failure_callback}")
- except:
- print_verbose(f"Error Occurred while logging failure: {traceback.format_exc()}")
- pass
-
- if failure_handler and callable(failure_handler):
- call_details = {
- "exception": exception,
- "additional_details": additional_details
- }
- failure_handler(call_details)
- pass
+ # print_verbose(f"additional_details: {additional_details}")
+ for callback in litellm.failure_callback:
+ try:
+ if callback == "slack":
+ slack_msg = ""
+ if len(kwargs) > 0:
+ for key in kwargs:
+ slack_msg += f"{key}: {kwargs[key]}\n"
+ if len(args) > 0:
+ for i, arg in enumerate(args):
+ slack_msg += f"LiteLLM_Args_{str(i)}: {arg}"
+ for detail in additional_details:
+ slack_msg += f"{detail}: {additional_details[detail]}\n"
+ slack_msg += f"Traceback: {traceback_exception}"
+ slack_app.client.chat_postMessage(
+ channel=alerts_channel, text=slack_msg
+ )
+ elif callback == "sentry":
+ capture_exception(exception)
+ elif callback == "posthog":
+ print_verbose(
+ f"inside posthog, additional_details: {len(additional_details.keys())}"
+ )
+ ph_obj = {}
+ if len(kwargs) > 0:
+ ph_obj = kwargs
+ if len(args) > 0:
+ for i, arg in enumerate(args):
+ ph_obj["litellm_args_" + str(i)] = arg
+ for detail in additional_details:
+ ph_obj[detail] = additional_details[detail]
+ event_name = additional_details["Event_Name"]
+ print_verbose(f"ph_obj: {ph_obj}")
+ print_verbose(f"PostHog Event Name: {event_name}")
+ if "user_id" in additional_details:
+ posthog.capture(
+ additional_details["user_id"], event_name, ph_obj
+ )
+ else: # PostHog calls require a unique id to identify a user - https://posthog.com/docs/libraries/python
+ unique_id = str(uuid.uuid4())
+ posthog.capture(unique_id, event_name)
+ print_verbose(f"successfully logged to PostHog!")
+ elif callback == "berrispend":
+ print_verbose("reaches berrispend for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ result = {
+ "model": model,
+ "created": time.time(),
+ "error": traceback_exception,
+ "usage": {
+ "prompt_tokens": prompt_token_calculator(
+ model, messages=messages
+ ),
+ "completion_tokens": 0,
+ },
+ }
+ berrispendLogger.log_event(
+ model=model,
+ messages=messages,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ print_verbose=print_verbose,
+ )
+ elif callback == "aispend":
+ print_verbose("reaches aispend for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ result = {
+ "model": model,
+ "created": time.time(),
+ "usage": {
+ "prompt_tokens": prompt_token_calculator(
+ model, messages=messages
+ ),
+ "completion_tokens": 0,
+ },
+ }
+ aispendLogger.log_event(
+ model=model,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ print_verbose=print_verbose,
+ )
+ elif callback == "supabase":
+ print_verbose("reaches supabase for logging!")
+ print_verbose(f"supabaseClient: {supabaseClient}")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ result = {
+ "model": model,
+ "created": time.time(),
+ "error": traceback_exception,
+ "usage": {
+ "prompt_tokens": prompt_token_calculator(
+ model, messages=messages
+ ),
+ "completion_tokens": 0,
+ },
+ }
+ supabaseClient.log_event(
+ model=model,
+ messages=messages,
+ end_user=litellm._thread_context.user,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ litellm_call_id=kwargs["litellm_call_id"],
+ print_verbose=print_verbose,
+ )
+ elif callback == "lite_debugger":
+ print_verbose("reaches lite_debugger for logging!")
+ print_verbose(f"liteDebuggerClient: {liteDebuggerClient}")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ result = {
+ "model": model,
+ "created": time.time(),
+ "error": traceback_exception,
+ "usage": {
+ "prompt_tokens": prompt_token_calculator(
+ model, messages=messages
+ ),
+ "completion_tokens": 0,
+ },
+ }
+ liteDebuggerClient.log_event(
+ model=model,
+ messages=messages,
+ end_user=litellm._thread_context.user,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ litellm_call_id=kwargs["litellm_call_id"],
+ print_verbose=print_verbose,
+ )
+ except:
+ print_verbose(
+ f"Error Occurred while logging failure: {traceback.format_exc()}"
+ )
+ pass
+
+ if failure_handler and callable(failure_handler):
+ call_details = {
+ "exception": exception,
+ "additional_details": additional_details,
+ }
+ failure_handler(call_details)
+ pass
except Exception as e:
- ## LOGGING
- logging(logger_fn=user_logger_fn, exception=e)
- pass
-
-def handle_success(args, kwargs, result, start_time, end_time):
- global heliconeLogger, aispendLogger
- try:
- success_handler = additional_details.pop("success_handler", None)
- failure_handler = additional_details.pop("failure_handler", None)
- additional_details["Event_Name"] = additional_details.pop("successful_event_name", "litellm.succes_query")
- for callback in litellm.success_callback:
- try:
- if callback == "posthog":
- ph_obj = {}
- for detail in additional_details:
- ph_obj[detail] = additional_details[detail]
- event_name = additional_details["Event_Name"]
- if "user_id" in additional_details:
- posthog.capture(additional_details["user_id"], event_name, ph_obj)
- else: # PostHog calls require a unique id to identify a user - https://posthog.com/docs/libraries/python
- unique_id = str(uuid.uuid4())
- posthog.capture(unique_id, event_name, ph_obj)
- pass
- elif callback == "slack":
- slack_msg = ""
- for detail in additional_details:
- slack_msg += f"{detail}: {additional_details[detail]}\n"
- slack_app.client.chat_postMessage(channel=alerts_channel, text=slack_msg)
- elif callback == "helicone":
- print_verbose("reaches helicone for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- messages = args[1] if len(args) > 1 else kwargs["messages"]
- heliconeLogger.log_success(model=model, messages=messages, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
- elif callback == "aispend":
- print_verbose("reaches aispend for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- aispendLogger.log_event(model=model, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
- elif callback == "berrispend":
- print_verbose("reaches berrispend for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- messages = args[1] if len(args) > 1 else kwargs["messages"]
- berrispendLogger.log_event(model=model, messages=messages, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
- elif callback == "supabase":
- print_verbose("reaches supabase for logging!")
- model = args[0] if len(args) > 0 else kwargs["model"]
- messages = args[1] if len(args) > 1 else kwargs["messages"]
- print(f"litellm._thread_context: {litellm._thread_context}")
- supabaseClient.log_event(model=model, messages=messages, end_user=litellm._thread_context.user, response_obj=result, start_time=start_time, end_time=end_time, print_verbose=print_verbose)
- except Exception as e:
## LOGGING
- logging(logger_fn=user_logger_fn, exception=e)
- print_verbose(f"[Non-Blocking] Success Callback Error - {traceback.format_exc()}")
+ exception_logging(logger_fn=user_logger_fn, exception=e)
+ pass
+
+
+def handle_success(args, kwargs, result, start_time, end_time):
+ global heliconeLogger, aispendLogger, supabaseClient, liteDebuggerClient
+ try:
+ success_handler = additional_details.pop("success_handler", None)
+ failure_handler = additional_details.pop("failure_handler", None)
+ additional_details["Event_Name"] = additional_details.pop(
+ "successful_event_name", "litellm.succes_query"
+ )
+ for callback in litellm.success_callback:
+ try:
+ if callback == "posthog":
+ ph_obj = {}
+ for detail in additional_details:
+ ph_obj[detail] = additional_details[detail]
+ event_name = additional_details["Event_Name"]
+ if "user_id" in additional_details:
+ posthog.capture(
+ additional_details["user_id"], event_name, ph_obj
+ )
+ else: # PostHog calls require a unique id to identify a user - https://posthog.com/docs/libraries/python
+ unique_id = str(uuid.uuid4())
+ posthog.capture(unique_id, event_name, ph_obj)
+ pass
+ elif callback == "slack":
+ slack_msg = ""
+ for detail in additional_details:
+ slack_msg += f"{detail}: {additional_details[detail]}\n"
+ slack_app.client.chat_postMessage(
+ channel=alerts_channel, text=slack_msg
+ )
+ elif callback == "helicone":
+ print_verbose("reaches helicone for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ heliconeLogger.log_success(
+ model=model,
+ messages=messages,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ print_verbose=print_verbose,
+ )
+ elif callback == "aispend":
+ print_verbose("reaches aispend for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ aispendLogger.log_event(
+ model=model,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ print_verbose=print_verbose,
+ )
+ elif callback == "berrispend":
+ print_verbose("reaches berrispend for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ berrispendLogger.log_event(
+ model=model,
+ messages=messages,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ print_verbose=print_verbose,
+ )
+ elif callback == "supabase":
+ print_verbose("reaches supabase for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ print(f"supabaseClient: {supabaseClient}")
+ supabaseClient.log_event(
+ model=model,
+ messages=messages,
+ end_user=litellm._thread_context.user,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ litellm_call_id=kwargs["litellm_call_id"],
+ print_verbose=print_verbose,
+ )
+ elif callback == "lite_debugger":
+ print_verbose("reaches lite_debugger for logging!")
+ model = args[0] if len(args) > 0 else kwargs["model"]
+ messages = args[1] if len(args) > 1 else kwargs["messages"]
+ print(f"liteDebuggerClient: {liteDebuggerClient}")
+ liteDebuggerClient.log_event(
+ model=model,
+ messages=messages,
+ end_user=litellm._thread_context.user,
+ response_obj=result,
+ start_time=start_time,
+ end_time=end_time,
+ litellm_call_id=kwargs["litellm_call_id"],
+ print_verbose=print_verbose,
+ )
+ except Exception as e:
+ ## LOGGING
+ exception_logging(logger_fn=user_logger_fn, exception=e)
+ print_verbose(
+ f"[Non-Blocking] Success Callback Error - {traceback.format_exc()}"
+ )
+ pass
+
+ if success_handler and callable(success_handler):
+ success_handler(args, kwargs)
+ pass
+ except Exception as e:
+ ## LOGGING
+ exception_logging(logger_fn=user_logger_fn, exception=e)
+ print_verbose(
+ f"[Non-Blocking] Success Callback Error - {traceback.format_exc()}"
+ )
pass
- if success_handler and callable(success_handler):
- success_handler(args, kwargs)
- pass
- except Exception as e:
- ## LOGGING
- logging(logger_fn=user_logger_fn, exception=e)
- print_verbose(f"[Non-Blocking] Success Callback Error - {traceback.format_exc()}")
- pass
def prompt_token_calculator(model, messages):
- # use tiktoken or anthropic's tokenizer depending on the model
- text = " ".join(message["content"] for message in messages)
- num_tokens = 0
- if "claude" in model:
- install_and_import('anthropic')
- from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
- anthropic = Anthropic()
- num_tokens = anthropic.count_tokens(text)
- else:
- num_tokens = len(encoding.encode(text))
- return num_tokens
+ # use tiktoken or anthropic's tokenizer depending on the model
+ text = " ".join(message["content"] for message in messages)
+ num_tokens = 0
+ if "claude" in model:
+ install_and_import("anthropic")
+ from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
-# integration helper function
+ anthropic = Anthropic()
+ num_tokens = anthropic.count_tokens(text)
+ else:
+ num_tokens = len(encoding.encode(text))
+ return num_tokens
+
+
+# integration helper function
def modify_integration(integration_name, integration_params):
- global supabaseClient
- if integration_name == "supabase":
- if "table_name" in integration_params:
- Supabase.supabase_table_name = integration_params["table_name"]
+ global supabaseClient
+ if integration_name == "supabase":
+ if "table_name" in integration_params:
+ Supabase.supabase_table_name = integration_params["table_name"]
+
def exception_type(model, original_exception, custom_llm_provider):
global user_logger_fn
exception_mapping_worked = False
try:
- if isinstance(original_exception, OpenAIError):
- # Handle the OpenAIError
- raise original_exception
- elif model:
- error_str = str(original_exception)
- if isinstance(original_exception, BaseException):
- exception_type = type(original_exception).__name__
+ if isinstance(original_exception, OriginalError):
+ # Handle the OpenAIError
+ exception_mapping_worked = True
+ if custom_llm_provider == "azure":
+ original_exception.llm_provider = "azure"
+ else:
+ original_exception.llm_provider = "openai"
+ raise original_exception
+ elif model:
+ error_str = str(original_exception)
+ if isinstance(original_exception, BaseException):
+ exception_type = type(original_exception).__name__
+ else:
+ exception_type = ""
+ if "claude" in model: # one of the anthropics
+ if hasattr(original_exception, "status_code"):
+ print_verbose(f"status_code: {original_exception.status_code}")
+ if original_exception.status_code == 401:
+ exception_mapping_worked = True
+ raise AuthenticationError(
+ message=f"AnthropicException - {original_exception.message}",
+ llm_provider="anthropic",
+ )
+ elif original_exception.status_code == 400:
+ exception_mapping_worked = True
+ raise InvalidRequestError(
+ message=f"AnthropicException - {original_exception.message}",
+ model=model,
+ llm_provider="anthropic",
+ )
+ elif original_exception.status_code == 429:
+ exception_mapping_worked = True
+ raise RateLimitError(
+ message=f"AnthropicException - {original_exception.message}",
+ llm_provider="anthropic",
+ )
+ elif (
+ "Could not resolve authentication method. Expected either api_key or auth_token to be set."
+ in error_str
+ ):
+ exception_mapping_worked = True
+ raise AuthenticationError(
+ message=f"AnthropicException - {original_exception.message}",
+ llm_provider="anthropic",
+ )
+ elif "replicate" in model:
+ if "Incorrect authentication token" in error_str:
+ exception_mapping_worked = True
+ raise AuthenticationError(
+ message=f"ReplicateException - {error_str}",
+ llm_provider="replicate",
+ )
+ elif exception_type == "ModelError":
+ exception_mapping_worked = True
+ raise InvalidRequestError(
+ message=f"ReplicateException - {error_str}",
+ model=model,
+ llm_provider="replicate",
+ )
+ elif "Request was throttled" in error_str:
+ exception_mapping_worked = True
+ raise RateLimitError(
+ message=f"ReplicateException - {error_str}",
+ llm_provider="replicate",
+ )
+ elif (
+ exception_type == "ReplicateError"
+ ): ## ReplicateError implies an error on Replicate server side, not user side
+ raise ServiceUnavailableError(
+ message=f"ReplicateException - {error_str}",
+ llm_provider="replicate",
+ )
+ elif model == "command-nightly": # Cohere
+ if (
+ "invalid api token" in error_str
+ or "No API key provided." in error_str
+ ):
+ exception_mapping_worked = True
+ raise AuthenticationError(
+ message=f"CohereException - {original_exception.message}",
+ llm_provider="cohere",
+ )
+ elif "too many tokens" in error_str:
+ exception_mapping_worked = True
+ raise InvalidRequestError(
+ message=f"CohereException - {original_exception.message}",
+ model=model,
+ llm_provider="cohere",
+ )
+ elif (
+ "CohereConnectionError" in exception_type
+ ): # cohere seems to fire these errors when we load test it (1k+ messages / min)
+ exception_mapping_worked = True
+ raise RateLimitError(
+ message=f"CohereException - {original_exception.message}",
+ llm_provider="cohere",
+ )
+ elif custom_llm_provider == "huggingface":
+ if hasattr(original_exception, "status_code"):
+ if original_exception.status_code == 401:
+ exception_mapping_worked = True
+ raise AuthenticationError(
+ message=f"HuggingfaceException - {original_exception.message}",
+ llm_provider="huggingface",
+ )
+ elif original_exception.status_code == 400:
+ exception_mapping_worked = True
+ raise InvalidRequestError(
+ message=f"HuggingfaceException - {original_exception.message}",
+ model=model,
+ llm_provider="huggingface",
+ )
+ elif original_exception.status_code == 429:
+ exception_mapping_worked = True
+ raise RateLimitError(
+ message=f"HuggingfaceException - {original_exception.message}",
+ llm_provider="huggingface",
+ )
+ raise original_exception # base case - return the original exception
else:
- exception_type = ""
- logging(model=model, additional_args={"error_str": error_str, "exception_type": exception_type, "original_exception": original_exception}, logger_fn=user_logger_fn)
- if "claude" in model: #one of the anthropics
- if hasattr(original_exception, "status_code"):
- print_verbose(f"status_code: {original_exception.status_code}")
- if original_exception.status_code == 401:
- exception_mapping_worked = True
- raise AuthenticationError(f"AnthropicException - {original_exception.message}")
- elif original_exception.status_code == 400:
- exception_mapping_worked = True
- raise InvalidRequestError(f"AnthropicException - {original_exception.message}", f"{model}")
- elif original_exception.status_code == 429:
- exception_mapping_worked = True
- raise RateLimitError(f"AnthropicException - {original_exception.message}")
- elif "Could not resolve authentication method. Expected either api_key or auth_token to be set." in error_str:
- exception_mapping_worked = True
- raise AuthenticationError(f"AnthropicException - {error_str}")
- elif "replicate" in model:
- if "Incorrect authentication token" in error_str:
- exception_mapping_worked = True
- raise AuthenticationError(f"ReplicateException - {error_str}")
- elif exception_type == "ModelError":
- exception_mapping_worked = True
- raise InvalidRequestError(f"ReplicateException - {error_str}", f"{model}")
- elif "Request was throttled" in error_str:
- exception_mapping_worked = True
- raise RateLimitError(f"ReplicateException - {error_str}")
- elif exception_type == "ReplicateError": ## ReplicateError implies an error on Replicate server side, not user side
- raise ServiceUnavailableError(f"ReplicateException - {error_str}")
- elif model == "command-nightly": #Cohere
- if "invalid api token" in error_str or "No API key provided." in error_str:
- exception_mapping_worked = True
- raise AuthenticationError(f"CohereException - {error_str}")
- elif "too many tokens" in error_str:
- exception_mapping_worked = True
- raise InvalidRequestError(f"CohereException - {error_str}", f"{model}")
- elif "CohereConnectionError" in exception_type: # cohere seems to fire these errors when we load test it (1k+ messages / min)
- exception_mapping_worked = True
- raise RateLimitError(f"CohereException - {original_exception.message}")
- elif custom_llm_provider == "huggingface":
- if hasattr(original_exception, "status_code"):
- if original_exception.status_code == 401:
- exception_mapping_worked = True
- raise AuthenticationError(f"HuggingfaceException - {original_exception.message}")
- elif original_exception.status_code == 400:
- exception_mapping_worked = True
- raise InvalidRequestError(f"HuggingfaceException - {original_exception.message}", f"{model}")
- elif original_exception.status_code == 429:
- exception_mapping_worked = True
- raise RateLimitError(f"HuggingfaceException - {original_exception.message}")
- raise original_exception # base case - return the original exception
- else:
- raise original_exception
+ raise original_exception
except Exception as e:
- ## LOGGING
- logging(logger_fn=user_logger_fn, additional_args={"exception_mapping_worked": exception_mapping_worked, "original_exception": original_exception}, exception=e)
- if exception_mapping_worked:
- raise e
- else: # don't let an error with mapping interrupt the user from receiving an error from the llm api calls
- raise original_exception
+ ## LOGGING
+ exception_logging(
+ logger_fn=user_logger_fn,
+ additional_args={
+ "exception_mapping_worked": exception_mapping_worked,
+ "original_exception": original_exception,
+ },
+ exception=e,
+ )
+ if exception_mapping_worked:
+ raise e
+ else: # don't let an error with mapping interrupt the user from receiving an error from the llm api calls
+ raise original_exception
+
def safe_crash_reporting(model=None, exception=None, custom_llm_provider=None):
data = {
- "model": model,
- "exception": str(exception),
- "custom_llm_provider": custom_llm_provider
+ "model": model,
+ "exception": str(exception),
+ "custom_llm_provider": custom_llm_provider,
}
threading.Thread(target=litellm_telemetry, args=(data,)).start()
+
def litellm_telemetry(data):
# Load or generate the UUID
- uuid_file = 'litellm_uuid.txt'
+ uuid_file = "litellm_uuid.txt"
try:
# Try to open the file and load the UUID
- with open(uuid_file, 'r') as file:
+ with open(uuid_file, "r") as file:
uuid_value = file.read()
if uuid_value:
uuid_value = uuid_value.strip()
@@ -685,42 +1191,48 @@ def litellm_telemetry(data):
# Generate a new UUID if the file doesn't exist or is empty
new_uuid = uuid.uuid4()
uuid_value = str(new_uuid)
- with open(uuid_file, 'w') as file:
+ with open(uuid_file, "w") as file:
file.write(uuid_value)
- except:
- # [Non-Blocking Error]
- return
-
- try:
- # Prepare the data to send to litellm logging api
- payload = {
- 'uuid': uuid_value,
- 'data': data,
- 'version': pkg_resources.get_distribution("litellm").version
- }
- # Make the POST request to litellm logging api
- response = requests.post('https://litellm.berri.ai/logging', headers={"Content-Type": "application/json"}, json=payload)
- response.raise_for_status() # Raise an exception for HTTP errors
except:
# [Non-Blocking Error]
return
+ try:
+ # Prepare the data to send to litellm logging api
+ payload = {
+ "uuid": uuid_value,
+ "data": data,
+ "version": pkg_resources.get_distribution("litellm").version,
+ }
+ # Make the POST request to litellm logging api
+ response = requests.post(
+ "https://litellm.berri.ai/logging",
+ headers={"Content-Type": "application/json"},
+ json=payload,
+ )
+ response.raise_for_status() # Raise an exception for HTTP errors
+ except:
+ # [Non-Blocking Error]
+ return
+
+
######### Secret Manager ############################
# checks if user has passed in a secret manager client
# if passed in then checks the secret there
def get_secret(secret_name):
- if litellm.secret_manager_client != None:
- # TODO: check which secret manager is being used
- # currently only supports Infisical
- secret = litellm.secret_manager_client.get_secret(secret_name).secret_value
- if secret != None: # failsafe when secret manager fails
- # if secret manager fails default to using .env variables
- os.environ[secret_name] = secret # set to env to be safe
- return secret
- elif litellm.api_key != None: # if users use litellm default key
- return litellm.api_key
- else:
- return os.environ.get(secret_name)
+ if litellm.secret_manager_client != None:
+ # TODO: check which secret manager is being used
+ # currently only supports Infisical
+ secret = litellm.secret_manager_client.get_secret(secret_name).secret_value
+ if secret != None:
+ return secret # if secret found in secret manager return it
+ else:
+ raise ValueError(f"Secret '{secret_name}' not found in secret manager")
+ elif litellm.api_key != None: # if users use litellm default key
+ return litellm.api_key
+ else:
+ return os.environ.get(secret_name)
+
######## Streaming Class ############################
# wraps the completion stream to return the correct format for the model
@@ -730,73 +1242,73 @@ class CustomStreamWrapper:
self.model = model
self.custom_llm_provider = custom_llm_provider
if model in litellm.cohere_models:
- # cohere does not return an iterator, so we need to wrap it in one
- self.completion_stream = iter(completion_stream)
+ # cohere does not return an iterator, so we need to wrap it in one
+ self.completion_stream = iter(completion_stream)
elif model == "together_ai":
self.completion_stream = iter(completion_stream)
- else:
- self.completion_stream = completion_stream
+ else:
+ self.completion_stream = completion_stream
def __iter__(self):
return self
def handle_anthropic_chunk(self, chunk):
- str_line = chunk.decode('utf-8') # Convert bytes to string
- if str_line.startswith('data:'):
- data_json = json.loads(str_line[5:])
- return data_json.get("completion", "")
- return ""
+ str_line = chunk.decode("utf-8") # Convert bytes to string
+ if str_line.startswith("data:"):
+ data_json = json.loads(str_line[5:])
+ return data_json.get("completion", "")
+ return ""
- def handle_together_ai_chunk(self, chunk):
- chunk = chunk.decode("utf-8")
- text_index = chunk.find('"text":"') # this checks if text: exists
- text_start = text_index + len('"text":"')
- text_end = chunk.find('"}', text_start)
- if text_index != -1 and text_end != -1:
- extracted_text = chunk[text_start:text_end]
- return extracted_text
- else:
- return ""
-
- def handle_huggingface_chunk(self, chunk):
- chunk = chunk.decode("utf-8")
- if chunk.startswith('data:'):
- data_json = json.loads(chunk[5:])
- if "token" in data_json and "text" in data_json["token"]:
- return data_json["token"]["text"]
- else:
- return ""
- return ""
+ def handle_together_ai_chunk(self, chunk):
+ chunk = chunk.decode("utf-8")
+ text_index = chunk.find('"text":"') # this checks if text: exists
+ text_start = text_index + len('"text":"')
+ text_end = chunk.find('"}', text_start)
+ if text_index != -1 and text_end != -1:
+ extracted_text = chunk[text_start:text_end]
+ return extracted_text
+ else:
+ return ""
+
+ def handle_huggingface_chunk(self, chunk):
+ chunk = chunk.decode("utf-8")
+ if chunk.startswith("data:"):
+ data_json = json.loads(chunk[5:])
+ if "token" in data_json and "text" in data_json["token"]:
+ return data_json["token"]["text"]
+ else:
+ return ""
+ return ""
def __next__(self):
- completion_obj ={ "role": "assistant", "content": ""}
+ completion_obj = {"role": "assistant", "content": ""}
if self.model in litellm.anthropic_models:
- chunk = next(self.completion_stream)
- completion_obj["content"] = self.handle_anthropic_chunk(chunk)
+ chunk = next(self.completion_stream)
+ completion_obj["content"] = self.handle_anthropic_chunk(chunk)
elif self.model == "replicate":
- chunk = next(self.completion_stream)
- completion_obj["content"] = chunk
- elif self.model == "together_ai":
- chunk = next(self.completion_stream)
- text_data = self.handle_together_ai_chunk(chunk)
- if text_data == "":
- return self.__next__()
- completion_obj["content"] = text_data
+ chunk = next(self.completion_stream)
+ completion_obj["content"] = chunk
+ elif (self.model == "together_ai") or ("togethercomputer" in self.model):
+ chunk = next(self.completion_stream)
+ text_data = self.handle_together_ai_chunk(chunk)
+ if text_data == "":
+ return self.__next__()
+ completion_obj["content"] = text_data
elif self.model in litellm.cohere_models:
- chunk = next(self.completion_stream)
- completion_obj["content"] = chunk.text
+ chunk = next(self.completion_stream)
+ completion_obj["content"] = chunk.text
elif self.custom_llm_provider and self.custom_llm_provider == "huggingface":
- chunk = next(self.completion_stream)
- completion_obj["content"] = self.handle_huggingface_chunk(chunk)
+ chunk = next(self.completion_stream)
+ completion_obj["content"] = self.handle_huggingface_chunk(chunk)
# return this for all models
return {"choices": [{"delta": completion_obj}]}
-
########## Reading Config File ############################
def read_config_args(config_path):
try:
import os
+
current_path = os.getcwd()
with open(config_path, "r") as config_file:
config = json.load(config_file)
@@ -810,9 +1322,13 @@ def read_config_args(config_path):
########## ollama implementation ############################
import aiohttp
-async def get_ollama_response_stream(api_base="http://localhost:11434", model="llama2", prompt="Why is the sky blue?"):
+
+
+async def get_ollama_response_stream(
+ api_base="http://localhost:11434", model="llama2", prompt="Why is the sky blue?"
+):
session = aiohttp.ClientSession()
- url = f'{api_base}/api/generate'
+ url = f"{api_base}/api/generate"
data = {
"model": model,
"prompt": prompt,
@@ -828,7 +1344,10 @@ async def get_ollama_response_stream(api_base="http://localhost:11434", model="l
if chunk.strip() != "":
j = json.loads(chunk)
if "response" in j:
- completion_obj ={ "role": "assistant", "content": ""}
+ completion_obj = {
+ "role": "assistant",
+ "content": "",
+ }
completion_obj["content"] = j["response"]
yield {"choices": [{"delta": completion_obj}]}
# self.responses.append(j["response"])
@@ -840,9 +1359,46 @@ async def get_ollama_response_stream(api_base="http://localhost:11434", model="l
async def stream_to_string(generator):
- response = ""
- async for chunk in generator:
- response += chunk["content"]
- return response
+ response = ""
+ async for chunk in generator:
+ response += chunk["content"]
+ return response
-
+
+########## Together AI streaming #############################
+async def together_ai_completion_streaming(json_data, headers):
+ session = aiohttp.ClientSession()
+ url = "https://api.together.xyz/inference"
+ # headers = {
+ # 'Authorization': f'Bearer {together_ai_token}',
+ # 'Content-Type': 'application/json'
+ # }
+
+ # data = {
+ # "model": "togethercomputer/llama-2-70b-chat",
+ # "prompt": "write 1 page on the topic of the history of the united state",
+ # "max_tokens": 1000,
+ # "temperature": 0.7,
+ # "top_p": 0.7,
+ # "top_k": 50,
+ # "repetition_penalty": 1,
+ # "stream_tokens": True
+ # }
+ try:
+ async with session.post(url, json=json_data, headers=headers) as resp:
+ async for line in resp.content.iter_any():
+ # print(line)
+ if line:
+ try:
+ json_chunk = line.decode("utf-8")
+ json_string = json_chunk.split("data: ")[1]
+ # Convert the JSON string to a dictionary
+ data_dict = json.loads(json_string)
+ completion_response = data_dict["choices"][0]["text"]
+ completion_obj = {"role": "assistant", "content": ""}
+ completion_obj["content"] = completion_response
+ yield {"choices": [{"delta": completion_obj}]}
+ except:
+ pass
+ finally:
+ await session.close()
diff --git a/poetry.lock b/poetry.lock
index 8f839788d..3edf35bb2 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,10 +1,9 @@
-# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
[[package]]
name = "aiohttp"
version = "3.8.5"
description = "Async http client/server framework (asyncio)"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -113,7 +112,6 @@ speedups = ["Brotli", "aiodns", "cchardet"]
name = "aiosignal"
version = "1.3.1"
description = "aiosignal: a list of registered asynchronous callbacks"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -125,64 +123,20 @@ files = [
frozenlist = ">=1.1.0"
[[package]]
-name = "anthropic"
-version = "0.3.8"
-description = "Client library for the anthropic API"
-category = "main"
-optional = false
-python-versions = ">=3.7,<4.0"
-files = [
- {file = "anthropic-0.3.8-py3-none-any.whl", hash = "sha256:97ffe1bacc4214dc89b19f496cf2769746971e86f7c835a05aa21b76f260d279"},
- {file = "anthropic-0.3.8.tar.gz", hash = "sha256:6651099807456c3b95b3879f5ad7d00f7e7e4f7649a2394d18032ab8be54ef16"},
-]
-
-[package.dependencies]
-anyio = ">=3.5.0,<4"
-distro = ">=1.7.0,<2"
-httpx = ">=0.23.0,<1"
-pydantic = ">=1.9.0,<2.0.0"
-tokenizers = ">=0.13.0"
-typing-extensions = ">=4.1.1,<5"
-
-[[package]]
-name = "anyio"
-version = "3.7.1"
-description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "main"
+name = "async-timeout"
+version = "4.0.3"
+description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
files = [
- {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
- {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
-]
-
-[package.dependencies]
-exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
-idna = ">=2.8"
-sniffio = ">=1.1"
-
-[package.extras]
-doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
-test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
-trio = ["trio (<0.22)"]
-
-[[package]]
-name = "async-timeout"
-version = "4.0.2"
-description = "Timeout context manager for asyncio programs"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
- {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
+ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
+ {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
name = "attrs"
version = "23.1.0"
description = "Classes Without Boilerplate"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -197,23 +151,10 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-[[package]]
-name = "backoff"
-version = "2.2.1"
-description = "Function decoration for backoff and retry"
-category = "main"
-optional = false
-python-versions = ">=3.7,<4.0"
-files = [
- {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"},
- {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"},
-]
-
[[package]]
name = "certifi"
version = "2023.7.22"
description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -225,7 +166,6 @@ files = [
name = "charset-normalizer"
version = "3.2.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
@@ -306,31 +246,10 @@ files = [
{file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"},
]
-[[package]]
-name = "cohere"
-version = "4.19.2"
-description = ""
-category = "main"
-optional = false
-python-versions = ">=3.7,<4.0"
-files = [
- {file = "cohere-4.19.2-py3-none-any.whl", hash = "sha256:0b6a4fe04380a481a8e975ebcc9bb6433febe4d3eb583b6d6e04342a5e998345"},
- {file = "cohere-4.19.2.tar.gz", hash = "sha256:a0b0fa698b3d3983fb328bb90d68fcf08faaa2268f3772ebc6bfea6ba55acf27"},
-]
-
-[package.dependencies]
-aiohttp = ">=3.0,<4.0"
-backoff = ">=2.0,<3.0"
-fastavro = {version = "1.8.2", markers = "python_version >= \"3.8\""}
-importlib_metadata = ">=6.0,<7.0"
-requests = ">=2.25.0,<3.0.0"
-urllib3 = ">=1.26,<3"
-
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@@ -338,91 +257,10 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
-[[package]]
-name = "distro"
-version = "1.8.0"
-description = "Distro - an OS platform information API"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "distro-1.8.0-py3-none-any.whl", hash = "sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"},
- {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"},
-]
-
-[[package]]
-name = "et-xmlfile"
-version = "1.1.0"
-description = "An implementation of lxml.xmlfile for the standard library"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"},
- {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"},
-]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.1.2"
-description = "Backport of PEP 654 (exception groups)"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
- {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
-]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
-[[package]]
-name = "fastavro"
-version = "1.8.2"
-description = "Fast read/write of AVRO files"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "fastavro-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:0e08964b2e9a455d831f2557402a683d4c4d45206f2ab9ade7c69d3dc14e0e58"},
- {file = "fastavro-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:401a70b1e5c7161420c6019e0c8afa88f7c8a373468591f5ec37639a903c2509"},
- {file = "fastavro-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef1ed3eaa4240c05698d02d8d0c010b9a03780eda37b492da6cd4c9d37e04ec"},
- {file = "fastavro-1.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:543185a672ff6306beb329b57a7b8a3a2dd1eb21a5ccc530150623d58d48bb98"},
- {file = "fastavro-1.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ffbf8bae1edb50fe7beeffc3afa8e684686550c2e5d31bf01c25cfa213f581e1"},
- {file = "fastavro-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:bb545eb9d876bc7b785e27e98e7720ada7eee7d7a1729798d2ed51517f13500a"},
- {file = "fastavro-1.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b837d3038c651046252bc92c1b9899bf21c7927a148a1ff89599c36c2a331ca"},
- {file = "fastavro-1.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3510e96c0a47e4e914bd1a29c954eb662bfa24849ad92e597cb97cc79f21af7"},
- {file = "fastavro-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccc0e74f2c2ab357f39bb73d67fcdb6dc10e23fdbbd399326139f72ec0fb99a3"},
- {file = "fastavro-1.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:add51c70d0ab1175601c75cd687bbe9d16ae312cd8899b907aafe0d79ee2bc1d"},
- {file = "fastavro-1.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d9e2662f57e6453e9a2c9fb4f54b2a9e62e3e46f5a412ac00558112336d23883"},
- {file = "fastavro-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:fea75cf53a93c56dd56e68abce8d314ef877b27451c870cd7ede7582d34c08a7"},
- {file = "fastavro-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:f489020bb8664c2737c03457ad5dbd490579ddab6f0a7b5c17fecfe982715a89"},
- {file = "fastavro-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a547625c138efd5e61300119241041906ee8cb426fc7aa789900f87af7ed330d"},
- {file = "fastavro-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53beb458f30c9ad4aa7bff4a42243ff990ffb713b6ce0cd9b360cbc3d648fe52"},
- {file = "fastavro-1.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7b1b2cbd2dd851452306beed0ab9bdaeeab1cc8ad46f84b47cd81eeaff6dd6b8"},
- {file = "fastavro-1.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29e9baee0b2f37ecd09bde3b487cf900431fd548c85be3e4fe1b9a0b2a917f1"},
- {file = "fastavro-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:66e132c710663230292bc63e2cb79cf95b16ccb94a5fc99bb63694b24e312fc5"},
- {file = "fastavro-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:38aca63ce604039bcdf2edd14912d00287bdbf8b76f9aa42b28e6ca0bf950092"},
- {file = "fastavro-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9787835f6449ee94713e7993a700432fce3763024791ffa8a58dc91ef9d1f950"},
- {file = "fastavro-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536cb448bc83811056be02749fd9df37a69621678f02597d272970a769e9b40c"},
- {file = "fastavro-1.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e9d5027cf7d9968f8f819958b41bfedb933323ea6d6a0485eefacaa1afd91f54"},
- {file = "fastavro-1.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:792adfc0c80c7f1109e0ab4b0decef20691fdf0a45091d397a0563872eb56d42"},
- {file = "fastavro-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:650b22766259f7dd7519dfa4e4658f0e233c319efa130b9cf0c36a500e09cc57"},
- {file = "fastavro-1.8.2.tar.gz", hash = "sha256:ab9d9226d4b66b6b3d0661a57cd45259b0868fed1c0cd4fac95249b9e0973320"},
-]
-
-[package.extras]
-codecs = ["lz4", "python-snappy", "zstandard"]
-lz4 = ["lz4"]
-snappy = ["python-snappy"]
-zstandard = ["zstandard"]
-
[[package]]
name = "frozenlist"
version = "1.4.0"
description = "A list-like structure which implements collections.abc.MutableSequence"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -489,69 +327,10 @@ files = [
{file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
]
-[[package]]
-name = "h11"
-version = "0.14.0"
-description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
- {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
-]
-
-[[package]]
-name = "httpcore"
-version = "0.17.3"
-description = "A minimal low-level HTTP client."
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"},
- {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"},
-]
-
-[package.dependencies]
-anyio = ">=3.0,<5.0"
-certifi = "*"
-h11 = ">=0.13,<0.15"
-sniffio = ">=1.0.0,<2.0.0"
-
-[package.extras]
-http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
-
-[[package]]
-name = "httpx"
-version = "0.24.1"
-description = "The next generation HTTP client."
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"},
- {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"},
-]
-
-[package.dependencies]
-certifi = "*"
-httpcore = ">=0.15.0,<0.18.0"
-idna = "*"
-sniffio = "*"
-
-[package.extras]
-brotli = ["brotli", "brotlicffi"]
-cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
-http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
-
[[package]]
name = "idna"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
optional = false
python-versions = ">=3.5"
files = [
@@ -559,43 +338,10 @@ files = [
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
-[[package]]
-name = "importlib-metadata"
-version = "6.8.0"
-description = "Read metadata from Python packages"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"},
- {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"},
-]
-
-[package.dependencies]
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
-
-[[package]]
-name = "iniconfig"
-version = "2.0.0"
-description = "brain-dead simple config-ini parsing"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
- {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
-]
-
[[package]]
name = "multidict"
version = "6.0.4"
description = "multidict implementation"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -675,49 +421,10 @@ files = [
{file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
]
-[[package]]
-name = "numpy"
-version = "1.24.4"
-description = "Fundamental package for array computing in Python"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"},
- {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"},
- {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"},
- {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"},
- {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"},
- {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"},
- {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"},
- {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"},
- {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"},
- {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"},
- {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"},
- {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"},
- {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"},
- {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"},
- {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"},
- {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"},
- {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"},
- {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"},
- {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"},
- {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"},
- {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"},
- {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"},
- {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"},
- {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"},
- {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"},
- {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"},
-]
-
[[package]]
name = "openai"
version = "0.27.8"
description = "Python client library for the OpenAI API"
-category = "main"
optional = false
python-versions = ">=3.7.1"
files = [
@@ -727,242 +434,19 @@ files = [
[package.dependencies]
aiohttp = "*"
-numpy = {version = "*", optional = true, markers = "extra == \"datalib\""}
-openpyxl = {version = ">=3.0.7", optional = true, markers = "extra == \"datalib\""}
-pandas = {version = ">=1.2.3", optional = true, markers = "extra == \"datalib\""}
-pandas-stubs = {version = ">=1.1.0.11", optional = true, markers = "extra == \"datalib\""}
requests = ">=2.20"
tqdm = "*"
[package.extras]
datalib = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
-dev = ["black (>=21.6b0,<22.0)", "pytest (>=6.0.0,<7.0.0)", "pytest-asyncio", "pytest-mock"]
+dev = ["black (>=21.6b0,<22.0)", "pytest (==6.*)", "pytest-asyncio", "pytest-mock"]
embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"]
wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"]
-[[package]]
-name = "openpyxl"
-version = "3.1.2"
-description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"},
- {file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"},
-]
-
-[package.dependencies]
-et-xmlfile = "*"
-
-[[package]]
-name = "packaging"
-version = "23.1"
-description = "Core utilities for Python packages"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
- {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
-]
-
-[[package]]
-name = "pandas"
-version = "2.0.3"
-description = "Powerful data structures for data analysis, time series, and statistics"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"},
- {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"},
- {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"},
- {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"},
- {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"},
- {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"},
- {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"},
- {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"},
- {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"},
- {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"},
- {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"},
- {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"},
- {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"},
- {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"},
- {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"},
- {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"},
- {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"},
- {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"},
- {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"},
- {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"},
- {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"},
- {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"},
- {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"},
- {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"},
- {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"},
-]
-
-[package.dependencies]
-numpy = [
- {version = ">=1.20.3", markers = "python_version < \"3.10\""},
- {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
- {version = ">=1.23.2", markers = "python_version >= \"3.11\""},
-]
-python-dateutil = ">=2.8.2"
-pytz = ">=2020.1"
-tzdata = ">=2022.1"
-
-[package.extras]
-all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"]
-aws = ["s3fs (>=2021.08.0)"]
-clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"]
-compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"]
-computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"]
-excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"]
-feather = ["pyarrow (>=7.0.0)"]
-fss = ["fsspec (>=2021.07.0)"]
-gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"]
-hdf5 = ["tables (>=3.6.1)"]
-html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"]
-mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"]
-output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"]
-parquet = ["pyarrow (>=7.0.0)"]
-performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"]
-plot = ["matplotlib (>=3.6.1)"]
-postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"]
-spss = ["pyreadstat (>=1.1.2)"]
-sql-other = ["SQLAlchemy (>=1.4.16)"]
-test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
-xml = ["lxml (>=4.6.3)"]
-
-[[package]]
-name = "pandas-stubs"
-version = "2.0.2.230605"
-description = "Type annotations for pandas"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "pandas_stubs-2.0.2.230605-py3-none-any.whl", hash = "sha256:39106b602f3cb6dc5f728b84e1b32bde6ecf41ee34ee714c66228009609fbada"},
- {file = "pandas_stubs-2.0.2.230605.tar.gz", hash = "sha256:624c7bb06d38145a44b61be459ccd19b038e0bf20364a025ecaab78fea65e858"},
-]
-
-[package.dependencies]
-numpy = ">=1.24.3"
-types-pytz = ">=2022.1.1"
-
-[[package]]
-name = "pluggy"
-version = "1.2.0"
-description = "plugin and hook calling mechanisms for python"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"},
- {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"},
-]
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
-
-[[package]]
-name = "pydantic"
-version = "1.10.12"
-description = "Data validation and settings management using python type hints"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"},
- {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"},
- {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"},
- {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"},
- {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"},
- {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"},
- {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"},
- {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"},
- {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"},
- {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"},
- {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"},
- {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"},
- {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"},
- {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"},
- {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"},
- {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"},
- {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"},
- {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"},
- {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"},
- {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"},
- {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"},
- {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"},
- {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"},
- {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"},
- {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"},
- {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"},
- {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"},
- {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"},
- {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"},
- {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"},
- {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"},
- {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"},
- {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"},
- {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"},
- {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"},
- {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"},
-]
-
-[package.dependencies]
-typing-extensions = ">=4.2.0"
-
-[package.extras]
-dotenv = ["python-dotenv (>=0.10.4)"]
-email = ["email-validator (>=1.0.3)"]
-
-[[package]]
-name = "pytest"
-version = "7.4.0"
-description = "pytest: simple powerful testing with Python"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"},
- {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"},
-]
-
-[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=0.12,<2.0"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-
-[package.extras]
-testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
-
-[[package]]
-name = "python-dateutil"
-version = "2.8.2"
-description = "Extensions to the standard Python datetime module"
-category = "main"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-files = [
- {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
- {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
-]
-
-[package.dependencies]
-six = ">=1.5"
-
[[package]]
name = "python-dotenv"
version = "1.0.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -973,141 +457,107 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
-[[package]]
-name = "pytz"
-version = "2023.3"
-description = "World timezone definitions, modern and historical"
-category = "main"
-optional = false
-python-versions = "*"
-files = [
- {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"},
- {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
-]
-
[[package]]
name = "regex"
-version = "2023.6.3"
+version = "2023.8.8"
description = "Alternative regular expression module, to replace re."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
- {file = "regex-2023.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:824bf3ac11001849aec3fa1d69abcb67aac3e150a933963fb12bda5151fe1bfd"},
- {file = "regex-2023.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05ed27acdf4465c95826962528f9e8d41dbf9b1aa8531a387dee6ed215a3e9ef"},
- {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b49c764f88a79160fa64f9a7b425620e87c9f46095ef9c9920542ab2495c8bc"},
- {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e3f1316c2293e5469f8f09dc2d76efb6c3982d3da91ba95061a7e69489a14ef"},
- {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43e1dd9d12df9004246bacb79a0e5886b3b6071b32e41f83b0acbf293f820ee8"},
- {file = "regex-2023.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959e8bcbfda5146477d21c3a8ad81b185cd252f3d0d6e4724a5ef11c012fb06"},
- {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af4dd387354dc83a3bff67127a124c21116feb0d2ef536805c454721c5d7993d"},
- {file = "regex-2023.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2239d95d8e243658b8dbb36b12bd10c33ad6e6933a54d36ff053713f129aa536"},
- {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:890e5a11c97cf0d0c550eb661b937a1e45431ffa79803b942a057c4fb12a2da2"},
- {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a8105e9af3b029f243ab11ad47c19b566482c150c754e4c717900a798806b222"},
- {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:25be746a8ec7bc7b082783216de8e9473803706723b3f6bef34b3d0ed03d57e2"},
- {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3676f1dd082be28b1266c93f618ee07741b704ab7b68501a173ce7d8d0d0ca18"},
- {file = "regex-2023.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10cb847aeb1728412c666ab2e2000ba6f174f25b2bdc7292e7dd71b16db07568"},
- {file = "regex-2023.6.3-cp310-cp310-win32.whl", hash = "sha256:dbbbfce33cd98f97f6bffb17801b0576e653f4fdb1d399b2ea89638bc8d08ae1"},
- {file = "regex-2023.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:c5f8037000eb21e4823aa485149f2299eb589f8d1fe4b448036d230c3f4e68e0"},
- {file = "regex-2023.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c123f662be8ec5ab4ea72ea300359023a5d1df095b7ead76fedcd8babbedf969"},
- {file = "regex-2023.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9edcbad1f8a407e450fbac88d89e04e0b99a08473f666a3f3de0fd292badb6aa"},
- {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcba6dae7de533c876255317c11f3abe4907ba7d9aa15d13e3d9710d4315ec0e"},
- {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29cdd471ebf9e0f2fb3cac165efedc3c58db841d83a518b082077e612d3ee5df"},
- {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b74fbbf6cbbf9dbce20eb9b5879469e97aeeaa874145517563cca4029db65c"},
- {file = "regex-2023.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c29ca1bd61b16b67be247be87390ef1d1ef702800f91fbd1991f5c4421ebae8"},
- {file = "regex-2023.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77f09bc4b55d4bf7cc5eba785d87001d6757b7c9eec237fe2af57aba1a071d9"},
- {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ea353ecb6ab5f7e7d2f4372b1e779796ebd7b37352d290096978fea83c4dba0c"},
- {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:10590510780b7541969287512d1b43f19f965c2ece6c9b1c00fc367b29d8dce7"},
- {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e2fbd6236aae3b7f9d514312cdb58e6494ee1c76a9948adde6eba33eb1c4264f"},
- {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:6b2675068c8b56f6bfd5a2bda55b8accbb96c02fd563704732fd1c95e2083461"},
- {file = "regex-2023.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74419d2b50ecb98360cfaa2974da8689cb3b45b9deff0dcf489c0d333bcc1477"},
- {file = "regex-2023.6.3-cp311-cp311-win32.whl", hash = "sha256:fb5ec16523dc573a4b277663a2b5a364e2099902d3944c9419a40ebd56a118f9"},
- {file = "regex-2023.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:09e4a1a6acc39294a36b7338819b10baceb227f7f7dbbea0506d419b5a1dd8af"},
- {file = "regex-2023.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0654bca0cdf28a5956c83839162692725159f4cda8d63e0911a2c0dc76166525"},
- {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463b6a3ceb5ca952e66550a4532cef94c9a0c80dc156c4cc343041951aec1697"},
- {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87b2a5bb5e78ee0ad1de71c664d6eb536dc3947a46a69182a90f4410f5e3f7dd"},
- {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6343c6928282c1f6a9db41f5fd551662310e8774c0e5ebccb767002fcf663ca9"},
- {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6192d5af2ccd2a38877bfef086d35e6659566a335b1492786ff254c168b1693"},
- {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390d18c75054947e4194019077e243c06fbb62e541d8817a0fa822ea310c14"},
- {file = "regex-2023.6.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:742e19a90d9bb2f4a6cf2862b8b06dea5e09b96c9f2df1779e53432d7275331f"},
- {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8abbc5d54ea0ee80e37fef009e3cec5dafd722ed3c829126253d3e22f3846f1e"},
- {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c2b867c17a7a7ae44c43ebbeb1b5ff406b3e8d5b3e14662683e5e66e6cc868d3"},
- {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d831c2f8ff278179705ca59f7e8524069c1a989e716a1874d6d1aab6119d91d1"},
- {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ee2d1a9a253b1729bb2de27d41f696ae893507c7db224436abe83ee25356f5c1"},
- {file = "regex-2023.6.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:61474f0b41fe1a80e8dfa70f70ea1e047387b7cd01c85ec88fa44f5d7561d787"},
- {file = "regex-2023.6.3-cp36-cp36m-win32.whl", hash = "sha256:0b71e63226e393b534105fcbdd8740410dc6b0854c2bfa39bbda6b0d40e59a54"},
- {file = "regex-2023.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bbb02fd4462f37060122e5acacec78e49c0fbb303c30dd49c7f493cf21fc5b27"},
- {file = "regex-2023.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b862c2b9d5ae38a68b92e215b93f98d4c5e9454fa36aae4450f61dd33ff48487"},
- {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976d7a304b59ede34ca2921305b57356694f9e6879db323fd90a80f865d355a3"},
- {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:83320a09188e0e6c39088355d423aa9d056ad57a0b6c6381b300ec1a04ec3d16"},
- {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9427a399501818a7564f8c90eced1e9e20709ece36be701f394ada99890ea4b3"},
- {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178bbc1b2ec40eaca599d13c092079bf529679bf0371c602edaa555e10b41c3"},
- {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:837328d14cde912af625d5f303ec29f7e28cdab588674897baafaf505341f2fc"},
- {file = "regex-2023.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d44dc13229905ae96dd2ae2dd7cebf824ee92bc52e8cf03dcead37d926da019"},
- {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d54af539295392611e7efbe94e827311eb8b29668e2b3f4cadcfe6f46df9c777"},
- {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7117d10690c38a622e54c432dfbbd3cbd92f09401d622902c32f6d377e2300ee"},
- {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bb60b503ec8a6e4e3e03a681072fa3a5adcbfa5479fa2d898ae2b4a8e24c4591"},
- {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:65ba8603753cec91c71de423a943ba506363b0e5c3fdb913ef8f9caa14b2c7e0"},
- {file = "regex-2023.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:271f0bdba3c70b58e6f500b205d10a36fb4b58bd06ac61381b68de66442efddb"},
- {file = "regex-2023.6.3-cp37-cp37m-win32.whl", hash = "sha256:9beb322958aaca059f34975b0df135181f2e5d7a13b84d3e0e45434749cb20f7"},
- {file = "regex-2023.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fea75c3710d4f31389eed3c02f62d0b66a9da282521075061ce875eb5300cf23"},
- {file = "regex-2023.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f56fcb7ff7bf7404becdfc60b1e81a6d0561807051fd2f1860b0d0348156a07"},
- {file = "regex-2023.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d2da3abc88711bce7557412310dfa50327d5769a31d1c894b58eb256459dc289"},
- {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a99b50300df5add73d307cf66abea093304a07eb017bce94f01e795090dea87c"},
- {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5708089ed5b40a7b2dc561e0c8baa9535b77771b64a8330b684823cfd5116036"},
- {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:687ea9d78a4b1cf82f8479cab23678aff723108df3edeac098e5b2498879f4a7"},
- {file = "regex-2023.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3850beab9f527f06ccc94b446c864059c57651b3f911fddb8d9d3ec1d1b25d"},
- {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8915cc96abeb8983cea1df3c939e3c6e1ac778340c17732eb63bb96247b91d2"},
- {file = "regex-2023.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:841d6e0e5663d4c7b4c8099c9997be748677d46cbf43f9f471150e560791f7ff"},
- {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9edce5281f965cf135e19840f4d93d55b3835122aa76ccacfd389e880ba4cf82"},
- {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b956231ebdc45f5b7a2e1f90f66a12be9610ce775fe1b1d50414aac1e9206c06"},
- {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:36efeba71c6539d23c4643be88295ce8c82c88bbd7c65e8a24081d2ca123da3f"},
- {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cf67ca618b4fd34aee78740bea954d7c69fdda419eb208c2c0c7060bb822d747"},
- {file = "regex-2023.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b4598b1897837067a57b08147a68ac026c1e73b31ef6e36deeeb1fa60b2933c9"},
- {file = "regex-2023.6.3-cp38-cp38-win32.whl", hash = "sha256:f415f802fbcafed5dcc694c13b1292f07fe0befdb94aa8a52905bd115ff41e88"},
- {file = "regex-2023.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:d4f03bb71d482f979bda92e1427f3ec9b220e62a7dd337af0aa6b47bf4498f72"},
- {file = "regex-2023.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccf91346b7bd20c790310c4147eee6ed495a54ddb6737162a36ce9dbef3e4751"},
- {file = "regex-2023.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b28f5024a3a041009eb4c333863d7894d191215b39576535c6734cd88b0fcb68"},
- {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0bb18053dfcfed432cc3ac632b5e5e5c5b7e55fb3f8090e867bfd9b054dbcbf"},
- {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5bfb3004f2144a084a16ce19ca56b8ac46e6fd0651f54269fc9e230edb5e4a"},
- {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6b48d0fa50d8f4df3daf451be7f9689c2bde1a52b1225c5926e3f54b6a9ed1"},
- {file = "regex-2023.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051da80e6eeb6e239e394ae60704d2b566aa6a7aed6f2890a7967307267a5dc6"},
- {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4c3b7fa4cdaa69268748665a1a6ff70c014d39bb69c50fda64b396c9116cf77"},
- {file = "regex-2023.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:457b6cce21bee41ac292d6753d5e94dcbc5c9e3e3a834da285b0bde7aa4a11e9"},
- {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aad51907d74fc183033ad796dd4c2e080d1adcc4fd3c0fd4fd499f30c03011cd"},
- {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0385e73da22363778ef2324950e08b689abdf0b108a7d8decb403ad7f5191938"},
- {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a57b742133830eec44d9b2290daf5cbe0a2f1d6acee1b3c7b1c7b2f3606df7"},
- {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3e5219bf9e75993d73ab3d25985c857c77e614525fac9ae02b1bebd92f7cecac"},
- {file = "regex-2023.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e5087a3c59eef624a4591ef9eaa6e9a8d8a94c779dade95d27c0bc24650261cd"},
- {file = "regex-2023.6.3-cp39-cp39-win32.whl", hash = "sha256:20326216cc2afe69b6e98528160b225d72f85ab080cbdf0b11528cbbaba2248f"},
- {file = "regex-2023.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:bdff5eab10e59cf26bc479f565e25ed71a7d041d1ded04ccf9aee1d9f208487a"},
- {file = "regex-2023.6.3.tar.gz", hash = "sha256:72d1a25bf36d2050ceb35b517afe13864865268dfb45910e2e17a84be6cbfeb0"},
+ {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"},
+ {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"},
+ {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"},
+ {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"},
+ {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"},
+ {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"},
+ {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"},
+ {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"},
+ {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"},
+ {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"},
+ {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"},
+ {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"},
+ {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"},
+ {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"},
+ {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"},
+ {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"},
+ {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"},
+ {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"},
+ {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"},
+ {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"},
+ {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"},
+ {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"},
+ {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"},
+ {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"},
+ {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"},
+ {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"},
+ {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"},
+ {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"},
+ {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"},
+ {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"},
+ {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"},
+ {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"},
+ {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"},
+ {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"},
+ {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"},
+ {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"},
+ {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"},
+ {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"},
+ {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"},
+ {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"},
+ {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"},
+ {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"},
+ {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"},
+ {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"},
+ {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"},
+ {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"},
+ {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"},
+ {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"},
+ {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"},
+ {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"},
+ {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"},
+ {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"},
+ {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"},
+ {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"},
+ {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"},
+ {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"},
+ {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"},
+ {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"},
+ {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"},
+ {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"},
+ {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"},
+ {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"},
+ {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"},
+ {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"},
+ {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"},
+ {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"},
+ {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"},
+ {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"},
+ {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"},
+ {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"},
+ {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"},
+ {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"},
+ {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"},
+ {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"},
+ {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"},
+ {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"},
+ {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"},
+ {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"},
+ {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"},
+ {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"},
+ {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"},
+ {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"},
+ {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"},
+ {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"},
+ {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"},
+ {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"},
+ {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"},
+ {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"},
]
-[[package]]
-name = "replicate"
-version = "0.10.0"
-description = "Python client for Replicate"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "replicate-0.10.0-py3-none-any.whl", hash = "sha256:71464d62259b22a17f5383e83ebaf158cace69ba0153a9de21061283ac2c3a4b"},
- {file = "replicate-0.10.0.tar.gz", hash = "sha256:f714944be0c65eef7b3ebf740eb132d573a52e811a02f0031b9f1ae077a50696"},
-]
-
-[package.dependencies]
-packaging = "*"
-pydantic = ">1"
-requests = ">2"
-
-[package.extras]
-dev = ["black", "mypy", "pytest", "responses", "ruff"]
-
[[package]]
name = "requests"
version = "2.31.0"
description = "Python HTTP for Humans."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1125,50 +575,10 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
-[[package]]
-name = "six"
-version = "1.16.0"
-description = "Python 2 and 3 compatibility utilities"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-files = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-
-[[package]]
-name = "sniffio"
-version = "1.3.0"
-description = "Sniff out which async library your code is running under"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
- {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
-]
-
-[[package]]
-name = "tenacity"
-version = "8.2.2"
-description = "Retry code until it succeeds"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"},
- {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"},
-]
-
-[package.extras]
-doc = ["reno", "sphinx", "tornado (>=4.5)"]
-
[[package]]
name = "tiktoken"
version = "0.4.0"
description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -1210,135 +620,30 @@ requests = ">=2.26.0"
[package.extras]
blobfile = ["blobfile (>=2)"]
-[[package]]
-name = "tokenizers"
-version = "0.13.3"
-description = "Fast and Customizable Tokenizers"
-category = "main"
-optional = false
-python-versions = "*"
-files = [
- {file = "tokenizers-0.13.3-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:f3835c5be51de8c0a092058a4d4380cb9244fb34681fd0a295fbf0a52a5fdf33"},
- {file = "tokenizers-0.13.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4ef4c3e821730f2692489e926b184321e887f34fb8a6b80b8096b966ba663d07"},
- {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5fd1a6a25353e9aa762e2aae5a1e63883cad9f4e997c447ec39d071020459bc"},
- {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee0b1b311d65beab83d7a41c56a1e46ab732a9eed4460648e8eb0bd69fc2d059"},
- {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ef4215284df1277dadbcc5e17d4882bda19f770d02348e73523f7e7d8b8d396"},
- {file = "tokenizers-0.13.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4d53976079cff8a033f778fb9adca2d9d69d009c02fa2d71a878b5f3963ed30"},
- {file = "tokenizers-0.13.3-cp310-cp310-win32.whl", hash = "sha256:1f0e3b4c2ea2cd13238ce43548959c118069db7579e5d40ec270ad77da5833ce"},
- {file = "tokenizers-0.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:89649c00d0d7211e8186f7a75dfa1db6996f65edce4b84821817eadcc2d3c79e"},
- {file = "tokenizers-0.13.3-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:56b726e0d2bbc9243872b0144515ba684af5b8d8cd112fb83ee1365e26ec74c8"},
- {file = "tokenizers-0.13.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:cc5c022ce692e1f499d745af293ab9ee6f5d92538ed2faf73f9708c89ee59ce6"},
- {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55c981ac44ba87c93e847c333e58c12abcbb377a0c2f2ef96e1a266e4184ff2"},
- {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f247eae99800ef821a91f47c5280e9e9afaeed9980fc444208d5aa6ba69ff148"},
- {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e3215d048e94f40f1c95802e45dcc37c5b05eb46280fc2ccc8cd351bff839"},
- {file = "tokenizers-0.13.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ba2b0bf01777c9b9bc94b53764d6684554ce98551fec496f71bc5be3a03e98b"},
- {file = "tokenizers-0.13.3-cp311-cp311-win32.whl", hash = "sha256:cc78d77f597d1c458bf0ea7c2a64b6aa06941c7a99cb135b5969b0278824d808"},
- {file = "tokenizers-0.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:ecf182bf59bd541a8876deccf0360f5ae60496fd50b58510048020751cf1724c"},
- {file = "tokenizers-0.13.3-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:0527dc5436a1f6bf2c0327da3145687d3bcfbeab91fed8458920093de3901b44"},
- {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07cbb2c307627dc99b44b22ef05ff4473aa7c7cc1fec8f0a8b37d8a64b1a16d2"},
- {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4560dbdeaae5b7ee0d4e493027e3de6d53c991b5002d7ff95083c99e11dd5ac0"},
- {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64064bd0322405c9374305ab9b4c07152a1474370327499911937fd4a76d004b"},
- {file = "tokenizers-0.13.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8c6e2ab0f2e3d939ca66aa1d596602105fe33b505cd2854a4c1717f704c51de"},
- {file = "tokenizers-0.13.3-cp37-cp37m-win32.whl", hash = "sha256:6cc29d410768f960db8677221e497226e545eaaea01aa3613fa0fdf2cc96cff4"},
- {file = "tokenizers-0.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fc2a7fdf864554a0dacf09d32e17c0caa9afe72baf9dd7ddedc61973bae352d8"},
- {file = "tokenizers-0.13.3-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:8791dedba834c1fc55e5f1521be325ea3dafb381964be20684b92fdac95d79b7"},
- {file = "tokenizers-0.13.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:d607a6a13718aeb20507bdf2b96162ead5145bbbfa26788d6b833f98b31b26e1"},
- {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3791338f809cd1bf8e4fee6b540b36822434d0c6c6bc47162448deee3f77d425"},
- {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2f35f30e39e6aab8716f07790f646bdc6e4a853816cc49a95ef2a9016bf9ce6"},
- {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310204dfed5aa797128b65d63538a9837cbdd15da2a29a77d67eefa489edda26"},
- {file = "tokenizers-0.13.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0f9b92ea052305166559f38498b3b0cae159caea712646648aaa272f7160963"},
- {file = "tokenizers-0.13.3-cp38-cp38-win32.whl", hash = "sha256:9a3fa134896c3c1f0da6e762d15141fbff30d094067c8f1157b9fdca593b5806"},
- {file = "tokenizers-0.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:8e7b0cdeace87fa9e760e6a605e0ae8fc14b7d72e9fc19c578116f7287bb873d"},
- {file = "tokenizers-0.13.3-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:00cee1e0859d55507e693a48fa4aef07060c4bb6bd93d80120e18fea9371c66d"},
- {file = "tokenizers-0.13.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a23ff602d0797cea1d0506ce69b27523b07e70f6dda982ab8cf82402de839088"},
- {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ce07445050b537d2696022dafb115307abdffd2a5c106f029490f84501ef97"},
- {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:280ffe95f50eaaf655b3a1dc7ff1d9cf4777029dbbc3e63a74e65a056594abc3"},
- {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97acfcec592f7e9de8cadcdcda50a7134423ac8455c0166b28c9ff04d227b371"},
- {file = "tokenizers-0.13.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7730c98a3010cd4f523465867ff95cd9d6430db46676ce79358f65ae39797b"},
- {file = "tokenizers-0.13.3-cp39-cp39-win32.whl", hash = "sha256:48625a108029cb1ddf42e17a81b5a3230ba6888a70c9dc14e81bc319e812652d"},
- {file = "tokenizers-0.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:bc0a6f1ba036e482db6453571c9e3e60ecd5489980ffd95d11dc9f960483d783"},
- {file = "tokenizers-0.13.3.tar.gz", hash = "sha256:2e546dbb68b623008a5442353137fbb0123d311a6d7ba52f2667c8862a75af2e"},
-]
-
-[package.extras]
-dev = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"]
-docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"]
-testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"]
-
-[[package]]
-name = "tomli"
-version = "2.0.1"
-description = "A lil' TOML parser"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
- {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
-]
-
[[package]]
name = "tqdm"
-version = "4.65.0"
+version = "4.66.1"
description = "Fast, Extensible Progress Meter"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"},
- {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"},
+ {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"},
+ {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
-dev = ["py-make (>=0.1.0)", "twine", "wheel"]
+dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"]
notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
-[[package]]
-name = "types-pytz"
-version = "2023.3.0.0"
-description = "Typing stubs for pytz"
-category = "main"
-optional = false
-python-versions = "*"
-files = [
- {file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"},
- {file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"},
-]
-
-[[package]]
-name = "typing-extensions"
-version = "4.7.1"
-description = "Backported and Experimental Type Hints for Python 3.7+"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
- {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
-]
-
-[[package]]
-name = "tzdata"
-version = "2023.3"
-description = "Provider of IANA time zone data"
-category = "main"
-optional = false
-python-versions = ">=2"
-files = [
- {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"},
- {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"},
-]
-
[[package]]
name = "urllib3"
version = "2.0.4"
description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1356,7 +661,6 @@ zstd = ["zstandard (>=0.18.0)"]
name = "yarl"
version = "1.9.2"
description = "Yet another URL library"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -1440,23 +744,7 @@ files = [
idna = ">=2.0"
multidict = ">=4.0"
-[[package]]
-name = "zipp"
-version = "3.16.2"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"},
- {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
-
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "59b35d97e5b8fd2fb7e10a769ea08062bb54636e3c8a0278c5e30ca1a692aab8"
+content-hash = "fe7d88d91250950917244f8a6ffc8eba7bfc9aa84314ed6498131172ae4ef3cf"
diff --git a/cookbook/proxy-server/.DS_Store b/proxy-server/.DS_Store
similarity index 100%
rename from cookbook/proxy-server/.DS_Store
rename to proxy-server/.DS_Store
diff --git a/cookbook/proxy-server/Dockerfile b/proxy-server/Dockerfile
similarity index 100%
rename from cookbook/proxy-server/Dockerfile
rename to proxy-server/Dockerfile
diff --git a/cookbook/proxy-server/LICENSE b/proxy-server/LICENSE
similarity index 100%
rename from cookbook/proxy-server/LICENSE
rename to proxy-server/LICENSE
diff --git a/proxy-server/main.py b/proxy-server/main.py
new file mode 100644
index 000000000..f44dd1025
--- /dev/null
+++ b/proxy-server/main.py
@@ -0,0 +1,86 @@
+from flask import Flask, request, jsonify, abort, Response
+from flask_cors import CORS
+import traceback
+import litellm
+
+from litellm import completion
+import openai
+from utils import handle_error, get_cache, add_cache
+import os, dotenv
+import logging
+import json
+dotenv.load_dotenv()
+
+# TODO: set your keys in .env or here:
+# os.environ["OPENAI_API_KEY"] = "" # set your openai key here
+# see supported models / keys here: https://litellm.readthedocs.io/en/latest/supported/
+
+######### LOGGING ###################
+# log your data to slack, supabase
+litellm.success_callback=["slack", "supabase"] # set .env SLACK_API_TOKEN, SLACK_API_SECRET, SLACK_API_CHANNEL, SUPABASE
+
+######### ERROR MONITORING ##########
+# log errors to slack, sentry, supabase
+litellm.failure_callback=["slack", "sentry", "supabase"] # .env SENTRY_API_URL
+
+app = Flask(__name__)
+CORS(app)
+
+@app.route('/')
+def index():
+ return 'received!', 200
+
+def data_generator(response):
+ for chunk in response:
+ yield f"data: {json.dumps(chunk)}\n\n"
+
+@app.route('/chat/completions', methods=["POST"])
+def api_completion():
+ data = request.json
+ if data.get('stream') == "True":
+ data['stream'] = True # convert to boolean
+ try:
+ # pass in data to completion function, unpack data
+ response = completion(**data)
+ if 'stream' in data and data['stream'] == True: # use generate_responses to stream responses
+ return Response(data_generator(response), mimetype='text/event-stream')
+ except Exception as e:
+ # call handle_error function
+ print(f"got error{e}")
+ return handle_error(data)
+ return response, 200 # non streaming responses
+
+@app.route('/get_models', methods=["POST"])
+def get_models():
+ try:
+ return litellm.model_list
+ except Exception as e:
+ traceback.print_exc()
+ response = {"error": str(e)}
+ return response, 200
+
+if __name__ == "__main__":
+ from waitress import serve
+ serve(app, host="0.0.0.0", port=5000, threads=500)
+
+############### Advanced ##########################
+
+############ Caching ###################################
+# make a new endpoint with caching
+# This Cache is built using ChromaDB
+# it has two functions add_cache() and get_cache()
+@app.route('/chat/completions_with_cache', methods=["POST"])
+def api_completion_with_cache():
+ data = request.json
+ try:
+ cache_response = get_cache(data['messages'])
+ if cache_response!=None:
+ return cache_response
+ # pass in data to completion function, unpack data
+ response = completion(**data)
+
+ # add to cache
+ except Exception as e:
+ # call handle_error function
+ return handle_error(data)
+ return response, 200
\ No newline at end of file
diff --git a/cookbook/proxy-server/models_info.json b/proxy-server/models_info.json
similarity index 100%
rename from cookbook/proxy-server/models_info.json
rename to proxy-server/models_info.json
diff --git a/proxy-server/readme.md b/proxy-server/readme.md
new file mode 100644
index 000000000..4f735f38c
--- /dev/null
+++ b/proxy-server/readme.md
@@ -0,0 +1,168 @@
+
+# liteLLM Proxy Server: 50+ LLM Models, Error Handling, Caching
+### Azure, Llama2, OpenAI, Claude, Hugging Face, Replicate Models
+[](https://pypi.org/project/litellm/)
+[](https://pypi.org/project/litellm/0.1.1/)
+
+[](https://github.com/BerriAI/litellm)
+
+[](https://railway.app/template/DYqQAW?referralCode=t3ukrU)
+
+
+
+## What does liteLLM proxy do
+- Make `/chat/completions` requests for 50+ LLM models **Azure, OpenAI, Replicate, Anthropic, Hugging Face**
+
+ Example: for `model` use `claude-2`, `gpt-3.5`, `gpt-4`, `command-nightly`, `stabilityai/stablecode-completion-alpha-3b-4k`
+ ```json
+ {
+ "model": "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",
+ "messages": [
+ {
+ "content": "Hello, whats the weather in San Francisco??",
+ "role": "user"
+ }
+ ]
+ }
+ ```
+- **Consistent Input/Output** Format
+ - Call all models using the OpenAI format - `completion(model, messages)`
+ - Text responses will always be available at `['choices'][0]['message']['content']`
+- **Error Handling** Using Model Fallbacks (if `GPT-4` fails, try `llama2`)
+- **Logging** - Log Requests, Responses and Errors to `Supabase`, `Posthog`, `Mixpanel`, `Sentry`, `Helicone` (Any of the supported providers here: https://litellm.readthedocs.io/en/latest/advanced/
+
+ **Example: Logs sent to Supabase**
+
+
+- **Token Usage & Spend** - Track Input + Completion tokens used + Spend/model
+- **Caching** - Implementation of Semantic Caching
+- **Streaming & Async Support** - Return generators to stream text responses
+
+
+## API Endpoints
+
+### `/chat/completions` (POST)
+
+This endpoint is used to generate chat completions for 50+ support LLM API Models. Use llama2, GPT-4, Claude2 etc
+
+#### Input
+This API endpoint accepts all inputs in raw JSON and expects the following inputs
+- `model` (string, required): ID of the model to use for chat completions. See all supported models [here]: (https://litellm.readthedocs.io/en/latest/supported/):
+ eg `gpt-3.5-turbo`, `gpt-4`, `claude-2`, `command-nightly`, `stabilityai/stablecode-completion-alpha-3b-4k`
+- `messages` (array, required): A list of messages representing the conversation context. Each message should have a `role` (system, user, assistant, or function), `content` (message text), and `name` (for function role).
+- Additional Optional parameters: `temperature`, `functions`, `function_call`, `top_p`, `n`, `stream`. See the full list of supported inputs here: https://litellm.readthedocs.io/en/latest/input/
+
+
+#### Example JSON body
+For claude-2
+```json
+{
+ "model": "claude-2",
+ "messages": [
+ {
+ "content": "Hello, whats the weather in San Francisco??",
+ "role": "user"
+ }
+ ]
+
+}
+```
+
+### Making an API request to the Proxy Server
+```python
+import requests
+import json
+
+# TODO: use your URL
+url = "http://localhost:5000/chat/completions"
+
+payload = json.dumps({
+ "model": "gpt-3.5-turbo",
+ "messages": [
+ {
+ "content": "Hello, whats the weather in San Francisco??",
+ "role": "user"
+ }
+ ]
+})
+headers = {
+ 'Content-Type': 'application/json'
+}
+response = requests.request("POST", url, headers=headers, data=payload)
+print(response.text)
+
+```
+
+### Output [Response Format]
+Responses from the server are given in the following format.
+All responses from the server are returned in the following format (for all LLM models). More info on output here: https://litellm.readthedocs.io/en/latest/output/
+```json
+{
+ "choices": [
+ {
+ "finish_reason": "stop",
+ "index": 0,
+ "message": {
+ "content": "I'm sorry, but I don't have the capability to provide real-time weather information. However, you can easily check the weather in San Francisco by searching online or using a weather app on your phone.",
+ "role": "assistant"
+ }
+ }
+ ],
+ "created": 1691790381,
+ "id": "chatcmpl-7mUFZlOEgdohHRDx2UpYPRTejirzb",
+ "model": "gpt-3.5-turbo-0613",
+ "object": "chat.completion",
+ "usage": {
+ "completion_tokens": 41,
+ "prompt_tokens": 16,
+ "total_tokens": 57
+ }
+}
+```
+
+## Installation & Usage
+### Running Locally
+1. Clone liteLLM repository to your local machine:
+ ```
+ git clone https://github.com/BerriAI/liteLLM-proxy
+ ```
+2. Install the required dependencies using pip
+ ```
+ pip install requirements.txt
+ ```
+3. Set your LLM API keys
+ ```
+ os.environ['OPENAI_API_KEY]` = "YOUR_API_KEY"
+ or
+ set OPENAI_API_KEY in your .env file
+ ```
+4. Run the server:
+ ```
+ python main.py
+ ```
+
+
+
+## Deploying
+1. Quick Start: Deploy on Railway
+
+ [](https://railway.app/template/DYqQAW?referralCode=t3ukrU)
+
+2. `GCP`, `AWS`, `Azure`
+This project includes a `Dockerfile` allowing you to build and deploy a Docker Project on your providers
+
+# Support / Talk with founders
+- [Our calendar 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
+- [Community Discord 💭](https://discord.gg/wuPM9dRgDw)
+- Our numbers 📞 +1 (770) 8783-106 / +1 (412) 618-6238
+- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
+
+
+## Roadmap
+- [ ] Support hosted db (e.g. Supabase)
+- [ ] Easily send data to places like posthog and sentry.
+- [ ] Add a hot-cache for project spend logs - enables fast checks for user + project limitings
+- [ ] Implement user-based rate-limiting
+- [ ] Spending controls per project - expose key creation endpoint
+- [ ] Need to store a keys db -> mapping created keys to their alias (i.e. project name)
+- [ ] Easily add new models as backups / as the entry-point (add this to the available model list)
diff --git a/cookbook/proxy-server/requirements.txt b/proxy-server/requirements.txt
similarity index 100%
rename from cookbook/proxy-server/requirements.txt
rename to proxy-server/requirements.txt
diff --git a/cookbook/proxy-server/test_proxy.py b/proxy-server/test_proxy.py
similarity index 100%
rename from cookbook/proxy-server/test_proxy.py
rename to proxy-server/test_proxy.py
diff --git a/proxy-server/test_proxy_stream.py b/proxy-server/test_proxy_stream.py
new file mode 100644
index 000000000..8b358f058
--- /dev/null
+++ b/proxy-server/test_proxy_stream.py
@@ -0,0 +1,21 @@
+# import openai
+# import os
+
+# os.environ["OPENAI_API_KEY"] = ""
+
+# openai.api_key = os.environ["OPENAI_API_KEY"]
+# openai.api_base ="http://localhost:5000"
+
+# messages = [
+# {
+# "role": "user",
+# "content": "write a 1 pg essay in liteLLM"
+# }
+# ]
+
+# response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=messages, stream=True)
+# print("got response", response)
+# # response is a generator
+
+# for chunk in response:
+# print(chunk)
diff --git a/cookbook/proxy-server/main.py b/proxy-server/utils.py
similarity index 63%
rename from cookbook/proxy-server/main.py
rename to proxy-server/utils.py
index be770afb7..f6cd94942 100644
--- a/cookbook/proxy-server/main.py
+++ b/proxy-server/utils.py
@@ -1,53 +1,15 @@
-from flask import Flask, request, jsonify, abort
-from flask_cors import CORS
-import traceback
-import litellm
from litellm import completion
import os, dotenv
+import json
dotenv.load_dotenv()
-
-######### LOGGING ###################
-# log your data to slack, supabase
-litellm.success_callback=["slack", "supabase"] # set .env SLACK_API_TOKEN, SLACK_API_SECRET, SLACK_API_CHANNEL, SUPABASE
-
-######### ERROR MONITORING ##########
-# log errors to slack, sentry, supabase
-litellm.failure_callback=["slack", "sentry", "supabase"] # .env SENTRY_API_URL
-
-app = Flask(__name__)
-CORS(app)
-
-@app.route('/')
-def index():
- return 'received!', 200
-
-@app.route('/chat/completions', methods=["POST"])
-def api_completion():
- data = request.json
- try:
- # pass in data to completion function, unpack data
- response = completion(**data)
- except Exception as e:
- # call handle_error function
- return handle_error(data)
- return response, 200
-
-@app.route('/get_models', methods=["POST"])
-def get_models():
- try:
- return litellm.model_list
- except Exception as e:
- traceback.print_exc()
- response = {"error": str(e)}
- return response, 200
-
-if __name__ == "__main__":
- from waitress import serve
- serve(app, host="0.0.0.0", port=5000, threads=500)
-
############### Advanced ##########################
+########### streaming ############################
+def generate_responses(response):
+ for chunk in response:
+ yield json.dumps({"response": chunk}) + "\n"
+
################ ERROR HANDLING #####################
# implement model fallbacks, cooldowns, and retries
# if a model fails assume it was rate limited and let it cooldown for 60s
@@ -82,26 +44,6 @@ def handle_error(data):
-############ Caching ###################################
-# make a new endpoint with caching
-# This Cache is built using ChromaDB
-# it has two functions add_cache() and get_cache()
-@app.route('/chat/completions', methods=["POST"])
-def api_completion_with_cache():
- data = request.json
- try:
- cache_response = get_cache(data['messages'])
- if cache_response!=None:
- return cache_response
- # pass in data to completion function, unpack data
- response = completion(**data)
-
- # add to cache
- except Exception as e:
- # call handle_error function
- return handle_error(data)
- return response, 200
-
import uuid
cache_collection = None
# Add a response to the cache
diff --git a/pyproject.toml b/pyproject.toml
index 6d676f2a9..58ec89de7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "litellm"
-version = "0.1.400"
+version = "0.1.436"
description = "Library to easily interface with LLM API providers"
authors = ["BerriAI"]
license = "MIT License"