diff --git a/.circleci/config.yml b/.circleci/config.yml index 7a982d74cbe..a8ccfcf7103 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -657,7 +657,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -2108,7 +2108,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -2250,7 +2250,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -2390,7 +2390,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -2551,7 +2551,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -2664,7 +2664,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -2800,7 +2800,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -3032,7 +3032,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 @@ -3549,7 +3549,7 @@ jobs: docker run -d \ --name postgres-db \ -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_PASSWORD=test-postgres \ -e POSTGRES_DB=circle_test \ -p 5432:5432 \ postgres:14 diff --git a/.gitguardian.yaml b/.gitguardian.yaml new file mode 100644 index 00000000000..861dd6e6d68 --- /dev/null +++ b/.gitguardian.yaml @@ -0,0 +1,84 @@ +version: 2 + +secret: + # Exclude files and paths by globbing + ignored_paths: + - "**/*.whl" + - "**/*.pyc" + - "**/__pycache__/**" + - "**/node_modules/**" + - "**/dist/**" + - "**/build/**" + - "**/.git/**" + - "**/venv/**" + - "**/.venv/**" + + # Large data/metadata files that don't need scanning + - "**/model_prices_and_context_window*.json" + - "**/*_metadata/*.txt" + - "**/tokenizers/*.json" + - "**/tokenizers/*" + - "miniconda.sh" + + # Build outputs and static assets + - "litellm/proxy/_experimental/out/**" + - "ui/litellm-dashboard/public/**" + - "**/swagger/*.js" + - "**/*.woff" + - "**/*.woff2" + - "**/*.avif" + - "**/*.webp" + + # Test data files + - "**/tests/**/data_map.txt" + - "tests/**/*.txt" + + # Documentation and other non-code files + - "docs/**" + - "**/*.md" + - "**/*.lock" + - "poetry.lock" + - "package-lock.json" + + # Ignore security incidents with the SHA256 of the occurrence (false positives) + ignored_matches: + # === Current detected false positives (SHA-based) === + + # gcs_pub_sub_body - folder name, not a password + - name: GCS pub/sub test folder name + match: 75f377c456eede69e5f6e47399ccee6016a2a93cc5dd11db09cc5b1359ae569a + + # os.environ/APORIA_API_KEY_1 - environment variable reference + - name: Environment variable reference APORIA_API_KEY_1 + match: e2ddeb8b88eca97a402559a2be2117764e11c074d86159ef9ad2375dea188094 + + # os.environ/APORIA_API_KEY_2 - environment variable reference + - name: Environment variable reference APORIA_API_KEY_2 + match: 09aa39a29e050b86603aa55138af1ff08fb86a4582aa965c1bd0672e1575e052 + + # oidc/circleci_v2/ - test authentication path, not a secret + - name: OIDC CircleCI test path + match: feb3475e1f89a65b7b7815ac4ec597e18a9ec1847742ad445c36ca617b536e15 + + # text-davinci-003 - OpenAI model identifier, not a secret + - name: OpenAI model identifier text-davinci-003 + match: c489000cf6c7600cee0eefb80ad0965f82921cfb47ece880930eb7e7635cf1f1 + + # === Preventive patterns for test keys (pattern-based) === + + # Test API keys (124 instances across 45 files) + - name: Test API keys with sk-test prefix + match: sk-test- + + # Mock API keys + - name: Mock API keys with sk-mock prefix + match: sk-mock- + + # Fake API keys + - name: Fake API keys with sk-fake prefix + match: sk-fake- + + # Generic test API key patterns + - name: Test API key patterns + match: test-api-key + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 8fbf1b3c5b4..39b46cba999 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,13 +23,15 @@ body: description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. render: shell - type: dropdown - id: ml-ops-team + id: component attributes: - label: Are you a ML Ops Team? - description: This helps us prioritize your requests correctly + label: What part of LiteLLM is this about? options: - - "No" - - "Yes" + - "SDK (litellm Python package)" + - "Proxy" + - "UI Dashboard" + - "Docs" + - "Other" validations: required: true - type: input diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 13a2132ec95..96b95cc7f02 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -22,6 +22,18 @@ body: description: Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., "I'm working on X and would like Y to be possible". If this is related to another GitHub issue, please link here too. validations: required: true + - type: dropdown + id: component + attributes: + label: What part of LiteLLM is this about? + options: + - "SDK (litellm Python package)" + - "Proxy" + - "UI Dashboard" + - "Docs" + - "Other" + validations: + required: true - type: dropdown id: hiring-interest attributes: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 85f1769b6f3..b91b16c955c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,3 @@ -## Title - - - ## Relevant issues @@ -11,10 +7,25 @@ **Please complete all items before asking a LiteLLM maintainer to review your PR** - [ ] I have Added testing in the [`tests/litellm/`](https://github.com/BerriAI/litellm/tree/main/tests/litellm) directory, **Adding at least 1 test is a hard requirement** - [see details](https://docs.litellm.ai/docs/extras/contributing_code) -- [ ] I have added a screenshot of my new test passing locally - [ ] My PR passes all unit tests on [`make test-unit`](https://docs.litellm.ai/docs/extras/contributing_code) - [ ] My PR's scope is as isolated as possible, it only solves 1 specific problem +## CI (LiteLLM team) + +> **CI status guideline:** +> +> - 50-55 passing tests: main is stable with minor issues. +> - 45-49 passing tests: acceptable but needs attention +> - <= 40 passing tests: unstable; be careful with your merges and assess the risk. + +- [ ] **Branch creation CI run** + Link: + +- [ ] **CI run for the last commit** + Link: + +- [ ] **Merge / cherry-pick CI run** + Links: ## Type @@ -29,5 +40,3 @@ ✅ Test ## Changes - - diff --git a/.github/workflows/create_daily_staging_branch.yml b/.github/workflows/create_daily_staging_branch.yml new file mode 100644 index 00000000000..a97cf6f9740 --- /dev/null +++ b/.github/workflows/create_daily_staging_branch.yml @@ -0,0 +1,43 @@ +name: Create Daily Staging Branch + +on: + schedule: + - cron: '0 0 * * *' # Runs daily at midnight UTC + workflow_dispatch: # Allow manual trigger + +jobs: + create-staging-branch: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Create daily staging branch + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Configure Git user + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Generate branch name with MM_DD_YYYY format + BRANCH_NAME="litellm_staging_$(date +'%m_%d_%Y')" + echo "Creating branch: $BRANCH_NAME" + + # Fetch all branches + git fetch --all + + # Check if the branch already exists + if git show-ref --verify --quiet refs/remotes/origin/$BRANCH_NAME; then + echo "Branch $BRANCH_NAME already exists. Skipping creation." + else + echo "Creating new branch: $BRANCH_NAME" + # Create the new branch from main + git checkout -b $BRANCH_NAME origin/main + # Push the new branch + git push origin $BRANCH_NAME + echo "Successfully created and pushed branch: $BRANCH_NAME" + fi diff --git a/.github/workflows/issue-keyword-labeler.yml b/.github/workflows/issue-keyword-labeler.yml index 60c18e3b9af..936f90f747f 100644 --- a/.github/workflows/issue-keyword-labeler.yml +++ b/.github/workflows/issue-keyword-labeler.yml @@ -19,7 +19,7 @@ jobs: id: scan env: PROVIDER_ISSUE_WEBHOOK_URL: ${{ secrets.PROVIDER_ISSUE_WEBHOOK_URL }} - KEYWORDS: azure,openai,bedrock,vertexai,vertex ai,anthropic + KEYWORDS: azure,openai,bedrock,vertexai,vertex ai,anthropic,gemini,cohere,mistral,groq,ollama,deepseek run: python3 .github/scripts/scan_keywords.py - name: Ensure label exists diff --git a/.github/workflows/label-component.yml b/.github/workflows/label-component.yml new file mode 100644 index 00000000000..c0f9436288c --- /dev/null +++ b/.github/workflows/label-component.yml @@ -0,0 +1,144 @@ +name: Label Component Issues + +on: + issues: + types: + - opened + +jobs: + add-component-label: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add SDK label + if: contains(github.event.issue.body, 'SDK (litellm Python package)') + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labelName = 'sdk'; + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + color: '0E7C86', + description: 'Issues related to the litellm Python SDK' + }); + } else { + throw error; + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [labelName] + }); + + - name: Add Proxy label + if: contains(github.event.issue.body, 'Proxy') + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labelName = 'proxy'; + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + color: '5319E7', + description: 'Issues related to the LiteLLM Proxy' + }); + } else { + throw error; + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [labelName] + }); + + - name: Add UI Dashboard label + if: contains(github.event.issue.body, 'UI Dashboard') + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labelName = 'ui-dashboard'; + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + color: 'D876E3', + description: 'Issues related to the LiteLLM UI Dashboard' + }); + } else { + throw error; + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [labelName] + }); + + - name: Add Docs label + if: contains(github.event.issue.body, 'Docs') + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labelName = 'docs'; + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + color: 'FBCA04', + description: 'Issues related to LiteLLM documentation' + }); + } else { + throw error; + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [labelName] + }); diff --git a/.github/workflows/label-mlops.yml b/.github/workflows/label-mlops.yml deleted file mode 100644 index 37789c1ea76..00000000000 --- a/.github/workflows/label-mlops.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Label ML Ops Team Issues - -on: - issues: - types: - - opened - -jobs: - add-mlops-label: - runs-on: ubuntu-latest - steps: - - name: Check if ML Ops Team is selected - uses: actions-ecosystem/action-add-labels@v1 - if: contains(github.event.issue.body, '### Are you a ML Ops Team?') && contains(github.event.issue.body, 'Yes') - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: "mlops user request" diff --git a/.github/workflows/locustfile.py b/.github/workflows/locustfile.py index 36dbeee9c48..65d0d56b3a6 100644 --- a/.github/workflows/locustfile.py +++ b/.github/workflows/locustfile.py @@ -8,7 +8,7 @@ class MyUser(HttpUser): def chat_completion(self): headers = { "Content-Type": "application/json", - "Authorization": "Bearer sk-8N1tLOOyH8TIxwOLahhIVg", + "Authorization": "Bearer sk-test-load-test-key-123", # Include any additional headers you may need for authentication, etc. } diff --git a/.github/workflows/publish-migrations.yml b/.github/workflows/publish-migrations.yml index 8e5a67bcf85..a81a64ab46a 100644 --- a/.github/workflows/publish-migrations.yml +++ b/.github/workflows/publish-migrations.yml @@ -20,7 +20,7 @@ jobs: env: POSTGRES_DB: temp_db POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_PASSWORD: test-postgres ports: - 5432:5432 options: >- @@ -35,7 +35,7 @@ jobs: env: POSTGRES_DB: shadow_db POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_PASSWORD: test-postgres ports: - 5433:5432 options: >- diff --git a/README.md b/README.md index 9fed1c6dbc7..a020bd80898 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ 🚅 LiteLLM
+
Call 100+ LLMs in OpenAI format. [Bedrock, Azure, OpenAI, VertexAI, Anthropic, Groq, etc.] +
-Call all LLM APIs using the OpenAI format [Bedrock, Huggingface, VertexAI, TogetherAI, Azure, OpenAI, Groq etc.]
-
| + | LiteLLM AI Gateway | +LiteLLM Python SDK | +
|---|---|---|
| Use Case | +Central service (LLM Gateway) to access multiple LLMs | +Use LiteLLM directly in your Python code | +
| Who Uses It? | +Gen AI Enablement / ML Platform Teams | +Developers building LLM projects | +
| Key Features | +Centralized API gateway with authentication and authorization, multi-tenant cost tracking and spend management per project/user, per-project customization (logging, guardrails, caching), virtual keys for secure access control, admin dashboard UI for monitoring and management | +Direct Python library integration in your codebase, Router with retry/fallback logic across multiple deployments (e.g. Azure/OpenAI) - Router, application-level load balancing and cost tracking, exception handling with OpenAI-compatible errors, observability callbacks (Lunary, MLflow, Langfuse, etc.) | +
+AICORE_AUTH_URL = "https://* * * .authentication.sap.hana.ondemand.com/oauth/token", +AICORE_CLIENT_ID = " *** ", +AICORE_CLIENT_SECRET = " *** ", +AICORE_RESOURCE_GROUP = " *** ", +AICORE_BASE_URL = "https://api.ai.***.cfapps.sap.hana.ondemand.com/v2" +## Usage - LiteLLM Python SDK ```python showLineNumbers title="SAP Chat Completion" @@ -55,16 +62,33 @@ for chunk in response: print(chunk.choices[0].delta.content or "", end="") ``` +```python showLineNumbers title="SAP Embedding" +from litellm import embedding +import os + +os.environ["AICORE_SERVICE_KEY"] = '{"clientid": "...", "clientsecret": "...", ...}' + +result = embedding( + model="sap/text-embedding-3-small", + input="Answer to the ultimate question of life, the universe, and everything is 42") +print(result.data[0]) +``` + ## Usage - LiteLLM Proxy Add to your LiteLLM Proxy config: ```yaml showLineNumbers title="config.yaml" model_list: - - model_name: sap-gpt4 + - model_name: "sap/*" litellm_params: - model: sap/gpt-4 - api_key: os.environ/AICORE_SERVICE_KEY + model: "sap/*" + +general_settings: + master_key: your-proxy-api-key + +environment_variables: + AICORE_SERVICE_KEY: '{"clientid": "...", "clientsecret": "...", ...}' ``` Start the proxy: @@ -81,7 +105,7 @@ curl http://localhost:4000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-proxy-api-key" \ -d '{ - "model": "sap-gpt4", + "model": "sap/gpt-4", "messages": [{"role": "user", "content": "Hello"}] }' ``` @@ -98,12 +122,29 @@ client = OpenAI( ) response = client.chat.completions.create( - model="sap-gpt4", + model="sap/gpt-4", messages=[{"role": "user", "content": "Hello"}] ) print(response.choices[0].message.content) ``` + +
Hi {recipient_email},
+
+ Your LiteLLM API key has crossed its soft budget limit of {soft_budget}.
+
+ Current Spend: {spend}
+ Soft Budget: {soft_budget}
+ {max_budget_info}
+
+
+ ⚠️ Note: Your API requests will continue to work, but you should monitor your usage closely. + If you reach your maximum budget, requests will be rejected. +
+ + You can view your usage and manage your budget in the LiteLLM Dashboard. Hi {recipient_email},
+
+ Your LiteLLM API key has reached {percentage}% of its maximum budget.
+
+ Current Spend: {spend}
+ Maximum Budget: {max_budget}
+ Alert Threshold: {alert_threshold} ({percentage}%)
+
+
+ ⚠️ Warning: You are approaching your maximum budget limit. + Once you reach your maximum budget of {max_budget}, all API requests will be rejected. +
+ + You can view your usage and manage your budget in the LiteLLM Dashboard.Simulation output for connection: {settings.connection_id}
+
+ {dryRunResult}
+
+
+ {previewYaml}
+
+ ) : (
+
+ {categoryYaml[category.category]}
+
+ ) : (
+
+ {JSON.stringify(info.metadata.secret_manager_settings, null, 2)}
+
+