Merge branch 'BerriAI:main' into docs/elasticsearch-logging-tutorial
157
.github/workflows/llm-translation-testing.yml
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
name: LLM Translation Test Results for Release Candidates
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_candidate_tag:
|
||||
description: 'Release candidate tag/version'
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
tags:
|
||||
- 'v*-rc*' # Triggers on release candidate tags like v1.0.0-rc1
|
||||
|
||||
jobs:
|
||||
fetch-llm-translation-results:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.release_candidate_tag || github.ref }}
|
||||
|
||||
- name: Install CircleCI CLI
|
||||
run: |
|
||||
curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/main/install.sh | sudo bash
|
||||
|
||||
- name: Create test results directory
|
||||
run: mkdir -p test-results
|
||||
|
||||
- name: Fetch LLM Translation Test Results from CircleCI
|
||||
env:
|
||||
CIRCLE_TOKEN: ${{ secrets.CIRCLE_TOKEN }}
|
||||
run: |
|
||||
# Get the latest CircleCI pipeline for this commit
|
||||
COMMIT_SHA="${{ github.sha }}"
|
||||
echo "Fetching CircleCI results for commit: $COMMIT_SHA"
|
||||
|
||||
# Get pipeline info
|
||||
PIPELINE_INFO=$(curl -s -H "Circle-Token: $CIRCLE_TOKEN" \
|
||||
"https://circleci.com/api/v2/project/github/BerriAI/litellm/pipeline?branch=main" | \
|
||||
jq -r ".items[] | select(.vcs.revision == \"$COMMIT_SHA\") | .id" | head -1)
|
||||
|
||||
if [ -z "$PIPELINE_INFO" ]; then
|
||||
echo "No CircleCI pipeline found for commit $COMMIT_SHA"
|
||||
echo "Creating placeholder test results..."
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>' > test-results/junit.xml
|
||||
echo '<testsuite name="llm_translation" tests="0" failures="0" errors="0" skipped="0" time="0">' >> test-results/junit.xml
|
||||
echo '<system-out>No CircleCI results found for this commit</system-out>' >> test-results/junit.xml
|
||||
echo '</testsuite>' >> test-results/junit.xml
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found pipeline: $PIPELINE_INFO"
|
||||
|
||||
# Get workflow info
|
||||
WORKFLOW_ID=$(curl -s -H "Circle-Token: $CIRCLE_TOKEN" \
|
||||
"https://circleci.com/api/v2/pipeline/$PIPELINE_INFO/workflow" | \
|
||||
jq -r '.items[] | select(.name | contains("test")) | .id' | head -1)
|
||||
|
||||
if [ -z "$WORKFLOW_ID" ]; then
|
||||
echo "No test workflow found in pipeline"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found workflow: $WORKFLOW_ID"
|
||||
|
||||
# Get job info for llm_translation tests
|
||||
JOB_INFO=$(curl -s -H "Circle-Token: $CIRCLE_TOKEN" \
|
||||
"https://circleci.com/api/v2/workflow/$WORKFLOW_ID/job" | \
|
||||
jq -r '.items[] | select(.name | contains("llm_translation")) | select(.status == "success" or .status == "failed") | .job_number' | head -1)
|
||||
|
||||
if [ -z "$JOB_INFO" ]; then
|
||||
echo "No completed llm_translation job found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found job: $JOB_INFO"
|
||||
|
||||
# Download artifacts
|
||||
curl -s -H "Circle-Token: $CIRCLE_TOKEN" \
|
||||
"https://circleci.com/api/v2/project/github/BerriAI/litellm/$JOB_INFO/artifacts" | \
|
||||
jq -r '.items[] | select(.path | contains("junit") or contains("coverage") or contains("report")) | .url' | \
|
||||
while read -r artifact_url; do
|
||||
filename=$(basename "$artifact_url" | sed 's/[?&].*//')
|
||||
echo "Downloading artifact: $filename"
|
||||
curl -s -H "Circle-Token: $CIRCLE_TOKEN" -o "test-results/$filename" "$artifact_url"
|
||||
done
|
||||
|
||||
# If no artifacts found, create placeholder
|
||||
if [ ! -f "test-results/junit.xml" ]; then
|
||||
echo "No test artifacts found, creating placeholder..."
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>' > test-results/junit.xml
|
||||
echo '<testsuite name="llm_translation" tests="0" failures="0" errors="0" skipped="0" time="0">' >> test-results/junit.xml
|
||||
echo '<system-out>Test artifacts not available from CircleCI</system-out>' >> test-results/junit.xml
|
||||
echo '</testsuite>' >> test-results/junit.xml
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Generate test summary
|
||||
run: |
|
||||
echo "# LLM Translation Testing Results" > test-results/summary.md
|
||||
echo "" >> test-results/summary.md
|
||||
echo "**Release Candidate:** ${{ github.event.inputs.release_candidate_tag || github.ref_name }}" >> test-results/summary.md
|
||||
echo "**Fetched Date:** $(date)" >> test-results/summary.md
|
||||
echo "**Commit:** ${{ github.sha }}" >> test-results/summary.md
|
||||
echo "**Source:** CircleCI Pipeline" >> test-results/summary.md
|
||||
echo "" >> test-results/summary.md
|
||||
|
||||
# Parse junit.xml for test statistics if it exists
|
||||
if [ -f "test-results/junit.xml" ]; then
|
||||
python -c "
|
||||
import xml.etree.ElementTree as ET
|
||||
try:
|
||||
tree = ET.parse('test-results/junit.xml')
|
||||
root = tree.getroot()
|
||||
tests = root.get('tests', '0')
|
||||
failures = root.get('failures', '0')
|
||||
errors = root.get('errors', '0')
|
||||
skipped = root.get('skipped', '0')
|
||||
time = root.get('time', '0')
|
||||
|
||||
print(f'**Total Tests:** {tests}')
|
||||
print(f'**Passed:** {int(tests) - int(failures) - int(errors) - int(skipped)}')
|
||||
print(f'**Failed:** {failures}')
|
||||
print(f'**Errors:** {errors}')
|
||||
print(f'**Skipped:** {skipped}')
|
||||
print(f'**Duration:** {time} seconds')
|
||||
except Exception as e:
|
||||
print(f'Could not parse test results: {e}')
|
||||
" >> test-results/summary.md
|
||||
fi
|
||||
|
||||
echo "" >> test-results/summary.md
|
||||
echo "## Test Files Covered" >> test-results/summary.md
|
||||
ls tests/llm_translation/*.py | sed 's/^/- /' >> test-results/summary.md
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: llm-translation-test-results-${{ github.event.inputs.release_candidate_tag || github.ref_name }}
|
||||
path: |
|
||||
test-results/
|
||||
coverage.xml
|
||||
htmlcov/
|
||||
.coverage
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload JUnit test results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: junit-xml-${{ github.event.inputs.release_candidate_tag || github.ref_name }}
|
||||
path: test-results/junit.xml
|
||||
retention-days: 30
|
||||
89
CLAUDE.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Installation
|
||||
- `make install-dev` - Install core development dependencies
|
||||
- `make install-proxy-dev` - Install proxy development dependencies with full feature set
|
||||
- `make install-test-deps` - Install all test dependencies
|
||||
|
||||
### Testing
|
||||
- `make test` - Run all tests
|
||||
- `make test-unit` - Run unit tests (tests/test_litellm) with 4 parallel workers
|
||||
- `make test-integration` - Run integration tests (excludes unit tests)
|
||||
- `pytest tests/` - Direct pytest execution
|
||||
|
||||
### Code Quality
|
||||
- `make lint` - Run all linting (Ruff, MyPy, Black, circular imports, import safety)
|
||||
- `make format` - Apply Black code formatting
|
||||
- `make lint-ruff` - Run Ruff linting only
|
||||
- `make lint-mypy` - Run MyPy type checking only
|
||||
|
||||
### Single Test Files
|
||||
- `poetry run pytest tests/path/to/test_file.py -v` - Run specific test file
|
||||
- `poetry run pytest tests/path/to/test_file.py::test_function -v` - Run specific test
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
LiteLLM is a unified interface for 100+ LLM providers with two main components:
|
||||
|
||||
### Core Library (`litellm/`)
|
||||
- **Main entry point**: `litellm/main.py` - Contains core completion() function
|
||||
- **Provider implementations**: `litellm/llms/` - Each provider has its own subdirectory
|
||||
- **Router system**: `litellm/router.py` + `litellm/router_utils/` - Load balancing and fallback logic
|
||||
- **Type definitions**: `litellm/types/` - Pydantic models and type hints
|
||||
- **Integrations**: `litellm/integrations/` - Third-party observability, caching, logging
|
||||
- **Caching**: `litellm/caching/` - Multiple cache backends (Redis, in-memory, S3, etc.)
|
||||
|
||||
### Proxy Server (`litellm/proxy/`)
|
||||
- **Main server**: `proxy_server.py` - FastAPI application
|
||||
- **Authentication**: `auth/` - API key management, JWT, OAuth2
|
||||
- **Database**: `db/` - Prisma ORM with PostgreSQL/SQLite support
|
||||
- **Management endpoints**: `management_endpoints/` - Admin APIs for keys, teams, models
|
||||
- **Pass-through endpoints**: `pass_through_endpoints/` - Provider-specific API forwarding
|
||||
- **Guardrails**: `guardrails/` - Safety and content filtering hooks
|
||||
- **UI Dashboard**: Served from `_experimental/out/` (Next.js build)
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Provider Implementation
|
||||
- Providers inherit from base classes in `litellm/llms/base.py`
|
||||
- Each provider has transformation functions for input/output formatting
|
||||
- Support both sync and async operations
|
||||
- Handle streaming responses and function calling
|
||||
|
||||
### Error Handling
|
||||
- Provider-specific exceptions mapped to OpenAI-compatible errors
|
||||
- Fallback logic handled by Router system
|
||||
- Comprehensive logging through `litellm/_logging.py`
|
||||
|
||||
### Configuration
|
||||
- YAML config files for proxy server (see `proxy/example_config_yaml/`)
|
||||
- Environment variables for API keys and settings
|
||||
- Database schema managed via Prisma (`proxy/schema.prisma`)
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Code Style
|
||||
- Uses Black formatter, Ruff linter, MyPy type checker
|
||||
- Pydantic v2 for data validation
|
||||
- Async/await patterns throughout
|
||||
- Type hints required for all public APIs
|
||||
|
||||
### Testing Strategy
|
||||
- Unit tests in `tests/test_litellm/`
|
||||
- Integration tests for each provider in `tests/llm_translation/`
|
||||
- Proxy tests in `tests/proxy_unit_tests/`
|
||||
- Load tests in `tests/load_tests/`
|
||||
|
||||
### Database Migrations
|
||||
- Prisma handles schema migrations
|
||||
- Migration files auto-generated with `prisma migrate dev`
|
||||
- Always test migrations against both PostgreSQL and SQLite
|
||||
|
||||
### Enterprise Features
|
||||
- Enterprise-specific code in `enterprise/` directory
|
||||
- Optional features enabled via environment variables
|
||||
- Separate licensing and authentication for enterprise features
|
||||
|
|
@ -39,31 +39,32 @@ This is a list of openai params we translate across providers.
|
|||
|
||||
Use `litellm.get_supported_openai_params()` for an updated list of params for each model + provider
|
||||
|
||||
| Provider | temperature | max_completion_tokens | max_tokens | top_p | stream | stream_options | stop | n | presence_penalty | frequency_penalty | functions | function_call | logit_bias | user | response_format | seed | tools | tool_choice | logprobs | top_logprobs | extra_headers |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
|Anthropic| ✅ | ✅ | ✅ |✅ | ✅ | ✅ | ✅ | | | | | | |✅ | ✅ | | ✅ | ✅ | | | ✅ |
|
||||
|OpenAI| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |✅ | ✅ | ✅ | ✅ |✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|Azure OpenAI| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |✅ | ✅ | ✅ | ✅ |✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|xAI| ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
|Replicate | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | |
|
||||
|Anyscale | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|Cohere| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | |
|
||||
|Huggingface| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
|
||||
|Openrouter| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |✅ | | | |
|
||||
|AI21| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | |
|
||||
|VertexAI| ✅ | ✅ | ✅ | | ✅ | ✅ | | | | | | | | | ✅ | ✅ | | |
|
||||
|Bedrock| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | ✅ (model dependent) | |
|
||||
|Sagemaker| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
|
||||
|TogetherAI| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | ✅ | | | ✅ | | ✅ | ✅ | | | |
|
||||
|Sambanova| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | ✅ | ✅ | | | |
|
||||
|AlephAlpha| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
|
||||
|NLP Cloud| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | |
|
||||
|Petals| ✅ | ✅ | | ✅ | ✅ | | | | | |
|
||||
|Ollama| ✅ | ✅ | ✅ |✅ | ✅ | ✅ | | | ✅ | | | | | ✅ | | |✅| | | | | | |
|
||||
|Databricks| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | |
|
||||
|ClarifAI| ✅ | ✅ | ✅ | |✅ | ✅ | | | | | | | | | | |
|
||||
|Github| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | ✅ |✅ (model dependent)|✅ (model dependent)| | |
|
||||
|Novita AI| ✅ | ✅ | | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | | | ✅ | | | | | | | |
|
||||
| Provider | temperature | max_completion_tokens | max_tokens | top_p | stream | stream_options | stop | n | presence_penalty | frequency_penalty | functions | function_call | logit_bias | user | response_format | seed| tools | tool_choice | logprobs | top_logprobs | extra_headers |
|
||||
|--------------|-------------|------------------------|------------|-------|--------|----------------|------|-----|------------------|-------------------|-----------|----------------|-------------|------|------------------|-------------------|--------|--------------|----------|---------------|----------------------|
|
||||
| Anthropic| ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | || | || | ✅ | ✅ | | ✅ | ✅ || | ✅|
|
||||
| OpenAI | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅ | ✅| ✅| ✅ | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅|
|
||||
| Azure OpenAI | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅ | ✅| ✅| ✅ | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅|
|
||||
| xAI| ✅|| ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅ | ✅| || ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅||
|
||||
| Replicate| ✅| ✅ | ✅ | ✅| ✅ | ✅ || || | || ||| |||| ||
|
||||
| Anyscale | ✅| ✅ | ✅ | ✅| ✅ | ✅ || || | || ||| |||| ||
|
||||
| Cohere | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅|| | || ||| |||| ||
|
||||
| Huggingface| ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | || | || ||| |||| ||
|
||||
| Openrouter | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅ | ✅| ✅|| ||| ✅| ✅ ||| ||
|
||||
| AI21 | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅|| | || ||| |||| ||
|
||||
| VertexAI | ✅| ✅ | ✅ | | ✅ | ✅ || || | || || ✅ | ✅|||| ||
|
||||
| Bedrock| ✅| ✅ | ✅ | ✅| ✅ | ✅ || || | || || ✅ (model dependent) | |||| ||
|
||||
| Sagemaker| ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | || | || ||| |||| ||
|
||||
| TogetherAI | ✅| ✅ | ✅ | ✅| ✅ | ✅ || || | ✅|| || ✅ | | ✅ | ✅ || ||
|
||||
| Sambanova| ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | || | || || ✅ | | ✅ | ✅ || ||
|
||||
| AlephAlpha | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | || | || ||| |||| ||
|
||||
| NLP Cloud| ✅| ✅ | ✅ | ✅| ✅ | ✅ || || | || ||| |||| ||
|
||||
| Petals | ✅| ✅ || ✅| ✅ ||| || | || ||| |||| ||
|
||||
| Ollama | ✅| ✅ | ✅ | ✅| ✅ | ✅ || ✅|| | || ✅||| | ✅ ||| ||
|
||||
| Databricks | ✅| ✅ | ✅ | ✅| ✅ | ✅ || || | || ||| |||| ||
|
||||
| ClarifAI | ✅| ✅ | ✅ | | ✅ | ✅ || || | || ||| |||| ||
|
||||
| Github | ✅| ✅ | ✅ | ✅| ✅ | ✅ | ✅ | ✅| ✅ | ✅| ✅|| || ✅ | ✅ (model dependent) | ✅ (model dependent) || ||
|
||||
| Novita AI| ✅| ✅ || ✅| ✅ | ✅ | ✅ | ✅| ✅ | ✅| || ✅||| |||| ||
|
||||
|
||||
:::note
|
||||
|
||||
By default, LiteLLM raises an exception if the openai param being passed in isn't supported.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ LiteLLM integrates with vector stores, allowing your models to access your organ
|
|||
|
||||
## Supported Vector Stores
|
||||
- [Bedrock Knowledge Bases](https://aws.amazon.com/bedrock/knowledge-bases/)
|
||||
- [OpenAI Vector Stores](https://platform.openai.com/docs/api-reference/vector-stores/search)
|
||||
- [Azure Vector Stores](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/file-search?tabs=python#vector-stores
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Use web search with litellm
|
|||
| Feature | Details |
|
||||
|---------|---------|
|
||||
| Supported Endpoints | - `/chat/completions` <br/> - `/responses` |
|
||||
| Supported Providers | `openai`, `xai`, `vertex_ai`, `gemini` |
|
||||
| Supported Providers | `openai`, `xai`, `vertex_ai`, `gemini`, `perplexity` |
|
||||
| LiteLLM Cost Tracking | ✅ Supported |
|
||||
| LiteLLM Version | `v1.71.0+` |
|
||||
|
||||
|
|
|
|||
|
|
@ -233,3 +233,63 @@ for event in response:
|
|||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Calling via `/chat/completions`
|
||||
|
||||
You can also call the Azure Responses API via the `/chat/completions` endpoint.
|
||||
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="litellm-sdk" label="LiteLLM SDK">
|
||||
|
||||
```python showLineNumbers
|
||||
from litellm import completion
|
||||
import os
|
||||
|
||||
os.environ["AZURE_API_BASE"] = "https://my-endpoint-sweden-berri992.openai.azure.com/"
|
||||
os.environ["AZURE_API_VERSION"] = "2023-03-15-preview"
|
||||
os.environ["AZURE_API_KEY"] = "my-api-key"
|
||||
|
||||
response = completion(
|
||||
model="azure/responses/my-custom-o1-pro",
|
||||
messages=[{"role": "user", "content": "Hello world"}],
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="proxy" label="OpenAI SDK with LiteLLM Proxy">
|
||||
|
||||
1. Setup config.yaml
|
||||
|
||||
```yaml showLineNumbers
|
||||
model_list:
|
||||
- model_name: my-custom-o1-pro
|
||||
litellm_params:
|
||||
model: azure/responses/my-custom-o1-pro
|
||||
api_key: os.environ/AZURE_API_KEY
|
||||
api_base: https://my-endpoint-sweden-berri992.openai.azure.com/
|
||||
api_version: 2023-03-15-preview
|
||||
```
|
||||
|
||||
2. Start LiteLLM proxy
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
|
||||
# RUNNING on http://0.0.0.0:4000
|
||||
```
|
||||
|
||||
3. Test it!
|
||||
|
||||
```bash
|
||||
curl http://localhost:4000/v1/chat/completions \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $LITELLM_API_KEY" \
|
||||
-d '{
|
||||
"model": "my-custom-o1-pro",
|
||||
"messages": [{"role": "user", "content": "Hello world"}]
|
||||
}'
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
|
@ -13,11 +13,12 @@ Call your custom torch-serve / internal LLM APIs via LiteLLM
|
|||
:::
|
||||
|
||||
Supported Routes:
|
||||
- `/v1/chat/completions` -> `litellm.completion`
|
||||
- `/v1/completions` -> `litellm.text_completion`
|
||||
- `/v1/embeddings` -> `litellm.embedding`
|
||||
- `/v1/images/generations` -> `litellm.image_generation`
|
||||
- `/v1/chat/completions` -> `litellm.acompletion`
|
||||
- `/v1/completions` -> `litellm.atext_completion`
|
||||
- `/v1/embeddings` -> `litellm.aembedding`
|
||||
- `/v1/images/generations` -> `litellm.aimage_generation`
|
||||
|
||||
- `/v1/messages` -> `litellm.acompletion`
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
@ -262,6 +263,102 @@ Expected Response
|
|||
}
|
||||
```
|
||||
|
||||
## Anthropic `/v1/messages`
|
||||
|
||||
- Write the integration for .acompletion
|
||||
- litellm will transform it to /v1/messages
|
||||
|
||||
1. Setup your `custom_handler.py` file
|
||||
|
||||
```python
|
||||
import litellm
|
||||
from litellm import CustomLLM, completion, get_llm_provider
|
||||
|
||||
|
||||
class MyCustomLLM(CustomLLM):
|
||||
async def acompletion(self, *args, **kwargs) -> litellm.ModelResponse:
|
||||
return litellm.completion(
|
||||
model="gpt-3.5-turbo",
|
||||
messages=[{"role": "user", "content": "Hello world"}],
|
||||
mock_response="Hi!",
|
||||
) # type: ignore
|
||||
|
||||
|
||||
my_custom_llm = MyCustomLLM()
|
||||
```
|
||||
|
||||
2. Add to `config.yaml`
|
||||
|
||||
In the config below, we pass
|
||||
|
||||
python_filename: `custom_handler.py`
|
||||
custom_handler_instance_name: `my_custom_llm`. This is defined in Step 1
|
||||
|
||||
custom_handler: `custom_handler.my_custom_llm`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: "test-model"
|
||||
litellm_params:
|
||||
model: "openai/text-embedding-ada-002"
|
||||
- model_name: "my-custom-model"
|
||||
litellm_params:
|
||||
model: "my-custom-llm/my-model"
|
||||
|
||||
litellm_settings:
|
||||
custom_provider_map:
|
||||
- {"provider": "my-custom-llm", "custom_handler": custom_handler.my_custom_llm}
|
||||
```
|
||||
|
||||
```bash
|
||||
litellm --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
3. Test it!
|
||||
|
||||
```bash
|
||||
curl -L -X POST 'http://0.0.0.0:4000/v1/messages' \
|
||||
-H 'anthropic-version: 2023-06-01' \
|
||||
-H 'content-type: application/json' \
|
||||
-H 'Authorization: Bearer sk-1234' \
|
||||
-d '{
|
||||
"model": "my-custom-model",
|
||||
"max_tokens": 1024,
|
||||
"messages": [{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "What are the key findings in this document 12?"
|
||||
}]
|
||||
}]
|
||||
}'
|
||||
```
|
||||
|
||||
Expected Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-Bm4qEp4h4vCe7Zi4Gud1MAxTWgibO",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "gpt-3.5-turbo-0125",
|
||||
"stop_sequence": null,
|
||||
"usage": {
|
||||
"input_tokens": 18,
|
||||
"output_tokens": 44
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Without the specific document being provided, it is not possible to determine the key findings within it. If you can provide the content or a summary of document 12, I would be happy to help identify the key findings."
|
||||
}
|
||||
],
|
||||
"stop_reason": "end_turn"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Additional Parameters
|
||||
|
||||
Additional parameters are passed inside `optional_params` key in the `completion` or `image_generation` function.
|
||||
|
|
|
|||
214
docs/my-website/docs/proxy/dynamic_logging.md
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import Image from '@theme/IdealImage';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
# Dynamic Callback Management
|
||||
|
||||
:::info
|
||||
|
||||
✨ This is an enterprise feature.
|
||||
|
||||
[Get started with LiteLLM Enterprise](https://www.litellm.ai/enterprise)
|
||||
|
||||
:::
|
||||
|
||||
LiteLLM's dynamic callback management enables teams to control logging behavior on a per-request basis without requiring central infrastructure changes. This is essential for organizations managing large-scale service ecosystems where:
|
||||
|
||||
- **Teams manage their own compliance** - Services can handle sensitive data appropriately without central oversight
|
||||
- **Decentralized responsibility** - Each team controls their data handling while using shared infrastructure
|
||||
|
||||
You can disable callbacks by passing the `x-litellm-disable-callbacks` header with your requests, giving teams granular control over where their data is logged.
|
||||
|
||||
## Getting Started: List and Disable Callbacks
|
||||
|
||||
Managing callbacks is a two-step process:
|
||||
|
||||
1. **First, list your active callbacks** to see what's currently enabled
|
||||
2. **Then, disable specific callbacks** as needed for your requests
|
||||
|
||||
|
||||
|
||||
## 1. List Active Callbacks
|
||||
|
||||
Start by viewing all currently enabled callbacks on your proxy to see what's available to disable.
|
||||
|
||||
#### Request
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'http://localhost:4000/callbacks/list' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-litellm-api-key: sk-1234'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": [
|
||||
"deployment_callback_on_success",
|
||||
"sync_deployment_callback_on_success"
|
||||
],
|
||||
"failure": [
|
||||
"async_deployment_callback_on_failure",
|
||||
"deployment_callback_on_failure"
|
||||
],
|
||||
"success_and_failure": [
|
||||
"langfuse",
|
||||
"datadog"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Response Fields
|
||||
|
||||
The response contains three arrays that categorize your active callbacks:
|
||||
- **`success`** - Callbacks that only execute when requests complete successfully. These callbacks receive data from successful LLM responses.
|
||||
- **`failure`** - Callbacks that only execute when requests fail or encounter errors. These callbacks receive error information and failed request data.
|
||||
- **`success_and_failure`** - Callbacks that execute for both successful and failed requests. These are typically logging/observability tools that need to capture all request data regardless of outcome.
|
||||
|
||||
---
|
||||
|
||||
## 2. Disable Callbacks
|
||||
|
||||
Now that you know which callbacks are active, you can selectively disable them using the `x-litellm-disable-callbacks` header. You can reference any callback name from the list response above.
|
||||
|
||||
### Disable a Single Callback
|
||||
|
||||
Use the `x-litellm-disable-callbacks` header to disable specific callbacks for individual requests.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Curl" label="Curl Request">
|
||||
|
||||
```bash
|
||||
curl --location 'http://0.0.0.0:4000/chat/completions' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer sk-1234' \
|
||||
--header 'x-litellm-disable-callbacks: langfuse' \
|
||||
--data '{
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "what llm are you"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="OpenAI" label="OpenAI Python SDK">
|
||||
|
||||
```python
|
||||
import openai
|
||||
|
||||
client = openai.OpenAI(
|
||||
api_key="sk-1234",
|
||||
base_url="http://0.0.0.0:4000"
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "what llm are you"
|
||||
}
|
||||
],
|
||||
extra_headers={
|
||||
"x-litellm-disable-callbacks": "langfuse"
|
||||
}
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Disable Multiple Callbacks
|
||||
|
||||
You can disable multiple callbacks by providing a comma-separated list in the header. Use any combination of callback names from your `/callbacks/list` response.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Curl" label="Curl Request">
|
||||
|
||||
```bash
|
||||
curl --location 'http://0.0.0.0:4000/chat/completions' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer sk-1234' \
|
||||
--header 'x-litellm-disable-callbacks: langfuse,datadog,prometheus' \
|
||||
--data '{
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "what llm are you"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="OpenAI" label="OpenAI Python SDK">
|
||||
|
||||
```python
|
||||
import openai
|
||||
|
||||
client = openai.OpenAI(
|
||||
api_key="sk-1234",
|
||||
base_url="http://0.0.0.0:4000"
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "what llm are you"
|
||||
}
|
||||
],
|
||||
extra_headers={
|
||||
"x-litellm-disable-callbacks": "langfuse,datadog,prometheus"
|
||||
}
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Header Format and Case Sensitivity
|
||||
|
||||
### Expected Header Format
|
||||
|
||||
The `x-litellm-disable-callbacks` header accepts callback names in the following formats (use the exact names returned by `/callbacks/list`):
|
||||
|
||||
- **Single callback**: `x-litellm-disable-callbacks: langfuse`
|
||||
- **Multiple callbacks**: `x-litellm-disable-callbacks: langfuse,datadog,prometheus`
|
||||
|
||||
When specifying multiple callbacks, use comma-separated values without spaces around the commas.
|
||||
|
||||
### Case Sensitivity
|
||||
|
||||
**Callback name checks are case insensitive.** This means all of the following are equivalent:
|
||||
|
||||
```bash
|
||||
# These are all equivalent
|
||||
x-litellm-disable-callbacks: langfuse
|
||||
x-litellm-disable-callbacks: LANGFUSE
|
||||
x-litellm-disable-callbacks: LangFuse
|
||||
x-litellm-disable-callbacks: langFUSE
|
||||
```
|
||||
|
||||
This applies to both single and multiple callback specifications:
|
||||
|
||||
```bash
|
||||
# Case insensitive for multiple callbacks
|
||||
x-litellm-disable-callbacks: LANGFUSE,datadog,PROMETHEUS
|
||||
x-litellm-disable-callbacks: langfuse,DATADOG,prometheus
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -56,27 +56,6 @@ components in your system, including in logging tools.
|
|||
|
||||
## Logging Features
|
||||
|
||||
### Conditional Logging by Virtual Keys, Teams
|
||||
|
||||
Use this to:
|
||||
1. Conditionally enable logging for some virtual keys/teams
|
||||
2. Set different logging providers for different virtual keys/teams
|
||||
|
||||
[👉 **Get Started** - Team/Key Based Logging](team_logging)
|
||||
|
||||
|
||||
### Redacting UserAPIKeyInfo
|
||||
|
||||
Redact information about the user api key (hashed token, user_id, team id, etc.), from logs.
|
||||
|
||||
Currently supported for Langfuse, OpenTelemetry, Logfire, ArizeAI logging.
|
||||
|
||||
```yaml
|
||||
litellm_settings:
|
||||
callbacks: ["langfuse"]
|
||||
redact_user_api_key_info: true
|
||||
```
|
||||
|
||||
|
||||
### Redact Messages, Response Content
|
||||
|
||||
|
|
@ -172,6 +151,18 @@ curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
|
|||
<Image img={require('../../img/message_redaction_spend_logs.png')} />
|
||||
|
||||
|
||||
### Redacting UserAPIKeyInfo
|
||||
|
||||
Redact information about the user api key (hashed token, user_id, team id, etc.), from logs.
|
||||
|
||||
Currently supported for Langfuse, OpenTelemetry, Logfire, ArizeAI logging.
|
||||
|
||||
```yaml
|
||||
litellm_settings:
|
||||
callbacks: ["langfuse"]
|
||||
redact_user_api_key_info: true
|
||||
```
|
||||
|
||||
### Disable Message Redaction
|
||||
|
||||
If you have `litellm.turn_on_message_logging` turned on, you can override it for specific requests by
|
||||
|
|
@ -269,6 +260,81 @@ print(response)
|
|||
LiteLLM.Info: "no-log request, skipping logging"
|
||||
```
|
||||
|
||||
### ✨ Dynamically Disable specific callbacks
|
||||
|
||||
:::info
|
||||
|
||||
This is an enterprise feature.
|
||||
|
||||
[Proceed with LiteLLM Enterprise](https://www.litellm.ai/enterprise)
|
||||
|
||||
:::
|
||||
|
||||
For some use cases, you may want to disable specific callbacks for a request. You can do this by passing `x-litellm-disable-callbacks: <callback_name>` in the request headers.
|
||||
|
||||
Send the list of callbacks to disable in the request header `x-litellm-disable-callbacks`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="Curl" label="Curl Request">
|
||||
|
||||
```bash
|
||||
curl --location 'http://0.0.0.0:4000/chat/completions' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer sk-1234' \
|
||||
--header 'x-litellm-disable-callbacks: langfuse' \
|
||||
--data '{
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "what llm are you"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="OpenAI" label="OpenAI Python SDK">
|
||||
|
||||
```python
|
||||
import openai
|
||||
|
||||
client = openai.OpenAI(
|
||||
api_key="sk-1234",
|
||||
base_url="http://0.0.0.0:4000"
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "what llm are you"
|
||||
}
|
||||
],
|
||||
extra_headers={
|
||||
"x-litellm-disable-callbacks": "langfuse"
|
||||
}
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
### ✨ Conditional Logging by Virtual Keys, Teams
|
||||
|
||||
Use this to:
|
||||
1. Conditionally enable logging for some virtual keys/teams
|
||||
2. Set different logging providers for different virtual keys/teams
|
||||
|
||||
[👉 **Get Started** - Team/Key Based Logging](team_logging)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## What gets logged?
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,21 @@ Need Help or want dedicated support ? Talk to a founder [here]: (https://calendl
|
|||
:::
|
||||
|
||||
|
||||
## 2. On Kubernetes - Use 1 Uvicorn worker [Suggested CMD]
|
||||
## 2. Recommended Machine Specifications
|
||||
|
||||
For optimal performance in production, we recommend the following minimum machine specifications:
|
||||
|
||||
| Resource | Recommended Value |
|
||||
|----------|------------------|
|
||||
| CPU | 2 vCPU |
|
||||
| Memory | 4 GB RAM |
|
||||
|
||||
These specifications provide:
|
||||
- Sufficient compute power for handling concurrent requests
|
||||
- Adequate memory for request processing and caching
|
||||
|
||||
|
||||
## 3. On Kubernetes - Use 1 Uvicorn worker [Suggested CMD]
|
||||
|
||||
Use this Docker `CMD`. This will start the proxy with 1 Uvicorn Async Worker
|
||||
|
||||
|
|
@ -59,7 +73,7 @@ CMD ["--port", "4000", "--config", "./proxy_server_config.yaml"]
|
|||
```
|
||||
|
||||
|
||||
## 3. Use Redis 'port','host', 'password'. NOT 'redis_url'
|
||||
## 4. Use Redis 'port','host', 'password'. NOT 'redis_url'
|
||||
|
||||
If you decide to use Redis, DO NOT use 'redis_url'. We recommend using redis port, host, and password params.
|
||||
|
||||
|
|
@ -92,13 +106,13 @@ litellm_settings:
|
|||
password: os.environ/REDIS_PASSWORD
|
||||
```
|
||||
|
||||
## 4. Disable 'load_dotenv'
|
||||
## 5. Disable 'load_dotenv'
|
||||
|
||||
Set `export LITELLM_MODE="PRODUCTION"`
|
||||
|
||||
This disables the load_dotenv() functionality, which will automatically load your environment credentials from the local `.env`.
|
||||
|
||||
## 5. If running LiteLLM on VPC, gracefully handle DB unavailability
|
||||
## 6. If running LiteLLM on VPC, gracefully handle DB unavailability
|
||||
|
||||
When running LiteLLM on a VPC (and inaccessible from the public internet), you can enable graceful degradation so that request processing continues even if the database is temporarily unavailable.
|
||||
|
||||
|
|
@ -125,7 +139,7 @@ When `allow_requests_on_db_unavailable` is set to `true`, LiteLLM will handle er
|
|||
| LiteLLM Budget Errors or Model Errors | ❌ Request will be blocked | Triggered when the DB is reachable but the authentication token is invalid, lacks access, or exceeds budget limits. |
|
||||
|
||||
|
||||
## 6. Disable spend_logs & error_logs if not using the LiteLLM UI
|
||||
## 7. Disable spend_logs & error_logs if not using the LiteLLM UI
|
||||
|
||||
By default, LiteLLM writes several types of logs to the database:
|
||||
- Every LLM API request to the `LiteLLM_SpendLogs` table
|
||||
|
|
@ -141,7 +155,7 @@ general_settings:
|
|||
|
||||
[More information about what the Database is used for here](db_info)
|
||||
|
||||
## 7. Use Helm PreSync Hook for Database Migrations [BETA]
|
||||
## 8. Use Helm PreSync Hook for Database Migrations [BETA]
|
||||
|
||||
To ensure only one service manages database migrations, use our [Helm PreSync hook for Database Migrations](https://github.com/BerriAI/litellm/blob/main/deploy/charts/litellm-helm/templates/migrations-job.yaml). This ensures migrations are handled during `helm upgrade` or `helm install`, while LiteLLM pods explicitly disable migrations.
|
||||
|
||||
|
|
@ -169,7 +183,7 @@ To ensure only one service manages database migrations, use our [Helm PreSync ho
|
|||
```
|
||||
|
||||
|
||||
## 8. Set LiteLLM Salt Key
|
||||
## 9. Set LiteLLM Salt Key
|
||||
|
||||
If you plan on using the DB, set a salt key for encrypting/decrypting variables in the DB.
|
||||
|
||||
|
|
@ -184,7 +198,7 @@ export LITELLM_SALT_KEY="sk-1234"
|
|||
[**See Code**](https://github.com/BerriAI/litellm/blob/036a6821d588bd36d170713dcf5a72791a694178/litellm/proxy/common_utils/encrypt_decrypt_utils.py#L15)
|
||||
|
||||
|
||||
## 9. Use `prisma migrate deploy`
|
||||
## 10. Use `prisma migrate deploy`
|
||||
|
||||
Use this to handle db migrations across LiteLLM versions in production
|
||||
|
||||
|
|
|
|||
|
|
@ -161,6 +161,11 @@ Here's the available UI roles for a LiteLLM Internal User:
|
|||
- `internal_user`: can login, view/create/delete their own keys, view their spend. **Cannot** add new users.
|
||||
- `internal_user_viewer`: can login, view their own keys, view their own spend. **Cannot** create/delete keys, add new users.
|
||||
|
||||
**Team Roles:**
|
||||
- `admin`: can add new members to the team, can control Team Permissions, can add team-only models (useful for onboarding a team's finetuned models).
|
||||
- `user`: can login, view their own keys, view their own spend. **Cannot** create/delete keys (controllable via Team Permissions), add new users.
|
||||
|
||||
|
||||
## Auto-add SSO users to teams
|
||||
|
||||
This walks through setting up sso auto-add for **Okta, Google SSO**
|
||||
|
|
@ -273,6 +278,65 @@ This budget does not apply to keys created under non-default teams.
|
|||
|
||||
[**Go Here**](./team_budgets.md)
|
||||
|
||||
### Default Team
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="ui" label="UI">
|
||||
|
||||
Go to `Internal Users` -> `Default User Settings` and set the default team to the team you just created.
|
||||
|
||||
Let's also set the default models to `no-default-models`. This means a user can only create keys within a team.
|
||||
|
||||
<Image img={require('../../img/default_user_settings_with_default_team.png')} style={{ width: '1000px', height: 'auto' }} />
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
:::info
|
||||
Team must be created before setting it as the default team.
|
||||
:::
|
||||
|
||||
```yaml
|
||||
litellm_settings:
|
||||
default_internal_user_params: # Default Params used when a new user signs in Via SSO
|
||||
user_role: "internal_user" # one of "internal_user", "internal_user_viewer",
|
||||
models: ["no-default-models"] # Optional[List[str]], optional): models to be used by the user
|
||||
teams: # Optional[List[NewUserRequestTeam]], optional): teams to be used by the user
|
||||
- team_id: "team_id_1" # Required[str]: team_id to be used by the user
|
||||
user_role: "user" # Optional[str], optional): Default role in the team. Values: "user" or "admin". Defaults to "user"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Team Member Budgets
|
||||
|
||||
Set a max budget for a team member.
|
||||
|
||||
You can do this when creating a new team, or by updating an existing team.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="ui" label="UI">
|
||||
|
||||
<Image img={require('../../img/create_default_team.png')} style={{ width: '600px', height: 'auto' }} />
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```bash
|
||||
curl -X POST '<PROXY_BASE_URL>/team/new' \
|
||||
-H 'Authorization: Bearer <PROXY_MASTER_KEY>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-D '{
|
||||
"team_alias": "team_1",
|
||||
"budget_duration": "10d",
|
||||
"team_member_budget": 10
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Set default params for new teams
|
||||
|
||||
When you connect litellm to your SSO provider, litellm can auto-create teams. Use this to set the default `models`, `max_budget`, `budget_duration` for these auto-created teams.
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ curl -X POST http://0.0.0.0:4000/v1/messages \
|
|||
- Setup environment variables
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_BASE="http://0.0.0.0:4000"
|
||||
export ANTHROPIC_BASE_URL="http://0.0.0.0:4000"
|
||||
export ANTHROPIC_API_KEY="sk-1234" # replace with your LiteLLM key
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ const sidebars = {
|
|||
items: [
|
||||
"proxy/logging",
|
||||
"proxy/logging_spec",
|
||||
"proxy/team_logging"
|
||||
"proxy/team_logging",
|
||||
"proxy/dynamic_logging"
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
|||
BIN
enterprise/dist/litellm_enterprise-0.1.9-py3-none-any.whl
vendored
Normal file
BIN
enterprise/dist/litellm_enterprise-0.1.9.tar.gz
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.constants import X_LITELLM_DISABLE_CALLBACKS
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.litellm_core_utils.llm_request_utils import (
|
||||
get_proxy_server_request_headers,
|
||||
)
|
||||
from litellm.proxy._types import CommonProxyErrors
|
||||
|
||||
|
||||
class EnterpriseCallbackControls:
|
||||
@staticmethod
|
||||
def is_callback_disabled_via_headers(
|
||||
callback: litellm.CALLBACK_TYPES, litellm_params: dict
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a callback is disabled via the x-litellm-disable-callbacks header.
|
||||
|
||||
Args:
|
||||
callback: The callback to check (can be string, CustomLogger instance, or callable)
|
||||
litellm_params: Parameters containing proxy server request info
|
||||
|
||||
Returns:
|
||||
bool: True if the callback should be disabled, False otherwise
|
||||
"""
|
||||
from litellm.litellm_core_utils.custom_logger_registry import (
|
||||
CustomLoggerRegistry,
|
||||
)
|
||||
|
||||
try:
|
||||
request_headers = get_proxy_server_request_headers(litellm_params)
|
||||
disabled_callbacks = request_headers.get(X_LITELLM_DISABLE_CALLBACKS, None)
|
||||
verbose_logger.debug(f"Dynamically disabled callbacks from {X_LITELLM_DISABLE_CALLBACKS}: {disabled_callbacks}")
|
||||
verbose_logger.debug(f"Checking if {callback} is disabled via headers. Disable callbacks from headers: {disabled_callbacks}")
|
||||
if disabled_callbacks is not None:
|
||||
#########################################################
|
||||
# premium user check
|
||||
#########################################################
|
||||
if not EnterpriseCallbackControls._premium_user_check():
|
||||
return False
|
||||
#########################################################
|
||||
disabled_callbacks = set([cb.strip().lower() for cb in disabled_callbacks.split(",")])
|
||||
if isinstance(callback, str):
|
||||
if callback.lower() in disabled_callbacks:
|
||||
verbose_logger.debug(f"Not logging to {callback} because it is disabled via {X_LITELLM_DISABLE_CALLBACKS}")
|
||||
return True
|
||||
elif isinstance(callback, CustomLogger):
|
||||
# get the string name of the callback
|
||||
callback_str = CustomLoggerRegistry.get_callback_str_from_class_type(callback.__class__)
|
||||
if callback_str is not None and callback_str.lower() in disabled_callbacks:
|
||||
verbose_logger.debug(f"Not logging to {callback_str} because it is disabled via {X_LITELLM_DISABLE_CALLBACKS}")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
verbose_logger.debug(
|
||||
f"Error checking disabled callbacks header: {str(e)}"
|
||||
)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _premium_user_check():
|
||||
from litellm.proxy.proxy_server import premium_user
|
||||
if premium_user:
|
||||
return True
|
||||
verbose_logger.warning(f"Disabling callbacks using request headers is an enterprise feature. {CommonProxyErrors.not_premium_user.value}")
|
||||
return False
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from typing import Optional
|
||||
|
||||
from litellm.proxy._types import GenerateKeyRequest, LiteLLM_TeamTable
|
||||
|
||||
|
||||
def add_team_member_key_duration(
|
||||
team_table: Optional[LiteLLM_TeamTable],
|
||||
data: GenerateKeyRequest,
|
||||
) -> GenerateKeyRequest:
|
||||
if team_table is None:
|
||||
return data
|
||||
|
||||
if data.user_id is None: # only apply for team member keys, not service accounts
|
||||
return data
|
||||
|
||||
if (
|
||||
team_table.metadata is not None
|
||||
and team_table.metadata.get("team_member_key_duration") is not None
|
||||
):
|
||||
data.duration = team_table.metadata["team_member_key_duration"]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def apply_enterprise_key_management_params(
|
||||
data: GenerateKeyRequest,
|
||||
team_table: Optional[LiteLLM_TeamTable],
|
||||
) -> GenerateKeyRequest:
|
||||
data = add_team_member_key_duration(team_table, data)
|
||||
return data
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "litellm-enterprise"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
description = "Package for LiteLLM Enterprise features"
|
||||
authors = ["BerriAI"]
|
||||
readme = "README.md"
|
||||
|
|
@ -22,7 +22,7 @@ requires = ["poetry-core"]
|
|||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.commitizen]
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
version_files = [
|
||||
"pyproject.toml:version",
|
||||
"../requirements.txt:litellm-enterprise==",
|
||||
|
|
|
|||
|
|
@ -324,6 +324,7 @@ use_aiohttp_transport: bool = (
|
|||
True # Older variable, aiohttp is now the default. use disable_aiohttp_transport instead.
|
||||
)
|
||||
disable_aiohttp_transport: bool = False # Set this to true to use httpx instead
|
||||
disable_aiohttp_trust_env: bool = False # When False, aiohttp will respect HTTP(S)_PROXY env vars
|
||||
force_ipv4: bool = (
|
||||
False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6.
|
||||
)
|
||||
|
|
@ -1140,6 +1141,7 @@ from .router import Router
|
|||
from .assistants.main import *
|
||||
from .batches.main import *
|
||||
from .images.main import *
|
||||
from .vector_stores import *
|
||||
from .batch_completion.main import * # type: ignore
|
||||
from .rerank_api.main import *
|
||||
from .llms.anthropic.experimental_pass_through.messages.handler import *
|
||||
|
|
|
|||
|
|
@ -693,6 +693,9 @@ PROMETHEUS_BUDGET_METRICS_REFRESH_INTERVAL_MINUTES = int(
|
|||
MCP_TOOL_NAME_PREFIX = "mcp_tool"
|
||||
MAXIMUM_TRACEBACK_LINES_TO_LOG = int(os.getenv("MAXIMUM_TRACEBACK_LINES_TO_LOG", 100))
|
||||
|
||||
# Headers to control callbacks
|
||||
X_LITELLM_DISABLE_CALLBACKS = "x-litellm-disable-callbacks"
|
||||
|
||||
########################### LiteLLM Proxy Specific Constants ###########################
|
||||
########################################################################################
|
||||
MAX_SPENDLOG_ROWS_TO_QUERY = int(
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ from litellm.llms.openai.cost_calculation import (
|
|||
cost_per_second as openai_cost_per_second,
|
||||
)
|
||||
from litellm.llms.openai.cost_calculation import cost_per_token as openai_cost_per_token
|
||||
from litellm.llms.perplexity.cost_calculator import (
|
||||
cost_per_token as perplexity_cost_per_token,
|
||||
)
|
||||
from litellm.llms.together_ai.cost_calculator import get_model_params_and_category
|
||||
from litellm.llms.vertex_ai.cost_calculator import (
|
||||
cost_per_character as google_cost_per_character,
|
||||
|
|
@ -329,6 +332,8 @@ def cost_per_token( # noqa: PLR0915
|
|||
return gemini_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "deepseek":
|
||||
return deepseek_cost_per_token(model=model, usage=usage_block)
|
||||
elif custom_llm_provider == "perplexity":
|
||||
return perplexity_cost_per_token(model=model, usage=usage_block)
|
||||
else:
|
||||
model_info = _cached_get_model_info_helper(
|
||||
model=model, custom_llm_provider=custom_llm_provider
|
||||
|
|
@ -661,9 +666,9 @@ def completion_cost( # noqa: PLR0915
|
|||
or isinstance(completion_response, dict)
|
||||
): # tts returns a custom class
|
||||
if isinstance(completion_response, dict):
|
||||
usage_obj: Optional[
|
||||
Union[dict, Usage]
|
||||
] = completion_response.get("usage", {})
|
||||
usage_obj: Optional[Union[dict, Usage]] = (
|
||||
completion_response.get("usage", {})
|
||||
)
|
||||
else:
|
||||
usage_obj = getattr(completion_response, "usage", {})
|
||||
if isinstance(usage_obj, BaseModel) and not _is_known_usage_objects(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,409 @@
|
|||
# +-------------------------------------------------------------+
|
||||
#
|
||||
# Add Bedrock Knowledge Base Context to your LLM calls
|
||||
#
|
||||
# +-------------------------------------------------------------+
|
||||
# Thank you users! We ❤️ you! - Krrish & Ishaan
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger, verbose_proxy_logger
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.integrations.vector_store_integrations.base_vector_store import (
|
||||
BaseVectorStore,
|
||||
)
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
from litellm.types.integrations.rag.bedrock_knowledgebase import (
|
||||
BedrockKBContent,
|
||||
BedrockKBGuardrailConfiguration,
|
||||
BedrockKBRequest,
|
||||
BedrockKBResponse,
|
||||
BedrockKBRetrievalConfiguration,
|
||||
BedrockKBRetrievalQuery,
|
||||
BedrockKBRetrievalResult,
|
||||
)
|
||||
from litellm.types.llms.openai import AllMessageValues, ChatCompletionUserMessage
|
||||
from litellm.types.utils import StandardLoggingVectorStoreRequest
|
||||
from litellm.types.vector_stores import (
|
||||
VectorStoreResultContent,
|
||||
VectorStoreSearchResponse,
|
||||
VectorStoreSearchResult,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import StandardCallbackDynamicParams
|
||||
else:
|
||||
StandardCallbackDynamicParams = Any
|
||||
|
||||
|
||||
class BedrockVectorStore(BaseVectorStore, BaseAWSLLM):
|
||||
CONTENT_PREFIX_STRING = "Context: \n\n"
|
||||
CUSTOM_LLM_PROVIDER = "bedrock"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
):
|
||||
self.async_handler = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.LoggingCallback
|
||||
)
|
||||
|
||||
# store kwargs as optional_params
|
||||
self.optional_params = kwargs
|
||||
|
||||
super().__init__(**kwargs)
|
||||
BaseAWSLLM.__init__(self)
|
||||
|
||||
async def async_get_chat_completion_prompt(
|
||||
self,
|
||||
model: str,
|
||||
messages: List[AllMessageValues],
|
||||
non_default_params: dict,
|
||||
prompt_id: Optional[str],
|
||||
prompt_variables: Optional[dict],
|
||||
dynamic_callback_params: StandardCallbackDynamicParams,
|
||||
litellm_logging_obj: LiteLLMLoggingObj,
|
||||
tools: Optional[List[Dict]] = None,
|
||||
prompt_label: Optional[str] = None,
|
||||
) -> Tuple[str, List[AllMessageValues], dict]:
|
||||
"""
|
||||
Retrieves the context from the Bedrock Knowledge Base and appends it to the messages.
|
||||
"""
|
||||
if litellm.vector_store_registry is None:
|
||||
return model, messages, non_default_params
|
||||
|
||||
vector_store_ids = litellm.vector_store_registry.pop_vector_store_ids_to_run(
|
||||
non_default_params=non_default_params, tools=tools
|
||||
)
|
||||
vector_store_request_metadata: List[StandardLoggingVectorStoreRequest] = []
|
||||
if vector_store_ids:
|
||||
for vector_store_id in vector_store_ids:
|
||||
start_time = datetime.now()
|
||||
query = self._get_kb_query_from_messages(messages)
|
||||
bedrock_kb_response = await self.make_bedrock_kb_retrieve_request(
|
||||
knowledge_base_id=vector_store_id,
|
||||
query=query,
|
||||
non_default_params=non_default_params,
|
||||
)
|
||||
verbose_logger.debug(
|
||||
f"Bedrock Knowledge Base Response: {bedrock_kb_response}"
|
||||
)
|
||||
|
||||
(
|
||||
context_message,
|
||||
context_string,
|
||||
) = self.get_chat_completion_message_from_bedrock_kb_response(
|
||||
bedrock_kb_response
|
||||
)
|
||||
if context_message is not None:
|
||||
messages.append(context_message)
|
||||
|
||||
#################################################################################################
|
||||
########## LOGGING for Standard Logging Payload, Langfuse, s3, LiteLLM DB etc. ##################
|
||||
#################################################################################################
|
||||
vector_store_search_response: VectorStoreSearchResponse = (
|
||||
self.transform_bedrock_kb_response_to_vector_store_search_response(
|
||||
bedrock_kb_response=bedrock_kb_response, query=query
|
||||
)
|
||||
)
|
||||
vector_store_request_metadata.append(
|
||||
StandardLoggingVectorStoreRequest(
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
vector_store_search_response=vector_store_search_response,
|
||||
custom_llm_provider=self.CUSTOM_LLM_PROVIDER,
|
||||
start_time=start_time.timestamp(),
|
||||
end_time=datetime.now().timestamp(),
|
||||
)
|
||||
)
|
||||
|
||||
litellm_logging_obj.model_call_details[
|
||||
"vector_store_request_metadata"
|
||||
] = vector_store_request_metadata
|
||||
|
||||
return model, messages, non_default_params
|
||||
|
||||
def transform_bedrock_kb_response_to_vector_store_search_response(
|
||||
self,
|
||||
bedrock_kb_response: BedrockKBResponse,
|
||||
query: str,
|
||||
) -> VectorStoreSearchResponse:
|
||||
"""
|
||||
Transform a BedrockKBResponse to a VectorStoreSearchResponse
|
||||
"""
|
||||
retrieval_results: Optional[
|
||||
List[BedrockKBRetrievalResult]
|
||||
] = bedrock_kb_response.get("retrievalResults", None)
|
||||
vector_store_search_response: VectorStoreSearchResponse = (
|
||||
VectorStoreSearchResponse(search_query=query, data=[])
|
||||
)
|
||||
if retrieval_results is None:
|
||||
return vector_store_search_response
|
||||
|
||||
vector_search_response_data: List[VectorStoreSearchResult] = []
|
||||
for retrieval_result in retrieval_results:
|
||||
content: Optional[BedrockKBContent] = retrieval_result.get("content", None)
|
||||
if content is None:
|
||||
continue
|
||||
content_text: Optional[str] = content.get("text", None)
|
||||
if content_text is None:
|
||||
continue
|
||||
vector_store_search_result: VectorStoreSearchResult = (
|
||||
VectorStoreSearchResult(
|
||||
score=retrieval_result.get("score", None),
|
||||
content=[VectorStoreResultContent(text=content_text, type="text")],
|
||||
)
|
||||
)
|
||||
vector_search_response_data.append(vector_store_search_result)
|
||||
vector_store_search_response["data"] = vector_search_response_data
|
||||
return vector_store_search_response
|
||||
|
||||
def _get_kb_query_from_messages(self, messages: List[AllMessageValues]) -> str:
|
||||
"""
|
||||
Uses the text `content` field of the last message in the list of messages
|
||||
"""
|
||||
if len(messages) == 0:
|
||||
return ""
|
||||
last_message = messages[-1]
|
||||
last_message_content = last_message.get("content", None)
|
||||
if last_message_content is None:
|
||||
return ""
|
||||
if isinstance(last_message_content, str):
|
||||
return last_message_content
|
||||
elif isinstance(last_message_content, list):
|
||||
return "\n".join([item.get("text", "") for item in last_message_content])
|
||||
return ""
|
||||
|
||||
def _prepare_request(
|
||||
self,
|
||||
credentials: Any,
|
||||
data: BedrockKBRequest,
|
||||
optional_params: dict,
|
||||
aws_region_name: str,
|
||||
api_base: str,
|
||||
extra_headers: Optional[dict] = None,
|
||||
) -> Any:
|
||||
"""
|
||||
Prepare a signed AWS request.
|
||||
|
||||
Args:
|
||||
credentials: AWS credentials
|
||||
data: Request data
|
||||
optional_params: Additional parameters
|
||||
aws_region_name: AWS region name
|
||||
api_base: Base API URL
|
||||
extra_headers: Additional headers
|
||||
|
||||
Returns:
|
||||
AWSRequest: A signed AWS request
|
||||
"""
|
||||
try:
|
||||
from botocore.auth import SigV4Auth
|
||||
from botocore.awsrequest import AWSRequest
|
||||
except ImportError:
|
||||
raise ImportError("Missing boto3 to call bedrock. Run 'pip install boto3'.")
|
||||
|
||||
sigv4 = SigV4Auth(credentials, "bedrock", aws_region_name)
|
||||
|
||||
encoded_data = json.dumps(data).encode("utf-8")
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if extra_headers is not None:
|
||||
headers = {"Content-Type": "application/json", **extra_headers}
|
||||
|
||||
request = AWSRequest(
|
||||
method="POST", url=api_base, data=encoded_data, headers=headers
|
||||
)
|
||||
sigv4.add_auth(request)
|
||||
if extra_headers is not None and "Authorization" in extra_headers:
|
||||
# prevent sigv4 from overwriting the auth header
|
||||
request.headers["Authorization"] = extra_headers["Authorization"]
|
||||
|
||||
return request.prepare()
|
||||
|
||||
async def make_bedrock_kb_retrieve_request(
|
||||
self,
|
||||
knowledge_base_id: str,
|
||||
query: str,
|
||||
guardrail_id: Optional[str] = None,
|
||||
guardrail_version: Optional[str] = None,
|
||||
next_token: Optional[str] = None,
|
||||
retrieval_configuration: Optional[BedrockKBRetrievalConfiguration] = None,
|
||||
non_default_params: Optional[dict] = None,
|
||||
) -> BedrockKBResponse:
|
||||
"""
|
||||
Make a Bedrock Knowledge Base retrieve request.
|
||||
|
||||
Args:
|
||||
knowledge_base_id (str): The unique identifier of the knowledge base to query
|
||||
query (str): The query text to search for
|
||||
guardrail_id (Optional[str]): The guardrail ID to apply
|
||||
guardrail_version (Optional[str]): The version of the guardrail to apply
|
||||
next_token (Optional[str]): Token for pagination
|
||||
retrieval_configuration (Optional[BedrockKBRetrievalConfiguration]): Configuration for the retrieval process
|
||||
|
||||
Returns:
|
||||
BedrockKBRetrievalResponse: A typed response object containing the retrieval results
|
||||
"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
non_default_params = non_default_params or {}
|
||||
credentials_dict: Dict[str, Any] = {}
|
||||
if litellm.vector_store_registry is not None:
|
||||
credentials_dict = (
|
||||
litellm.vector_store_registry.get_credentials_for_vector_store(
|
||||
knowledge_base_id
|
||||
)
|
||||
)
|
||||
|
||||
credentials = self.get_credentials(
|
||||
aws_access_key_id=credentials_dict.get(
|
||||
"aws_access_key_id", non_default_params.get("aws_access_key_id", None)
|
||||
),
|
||||
aws_secret_access_key=credentials_dict.get(
|
||||
"aws_secret_access_key",
|
||||
non_default_params.get("aws_secret_access_key", None),
|
||||
),
|
||||
aws_session_token=credentials_dict.get(
|
||||
"aws_session_token", non_default_params.get("aws_session_token", None)
|
||||
),
|
||||
aws_region_name=credentials_dict.get(
|
||||
"aws_region_name", non_default_params.get("aws_region_name", None)
|
||||
),
|
||||
aws_session_name=credentials_dict.get(
|
||||
"aws_session_name", non_default_params.get("aws_session_name", None)
|
||||
),
|
||||
aws_profile_name=credentials_dict.get(
|
||||
"aws_profile_name", non_default_params.get("aws_profile_name", None)
|
||||
),
|
||||
aws_role_name=credentials_dict.get(
|
||||
"aws_role_name", non_default_params.get("aws_role_name", None)
|
||||
),
|
||||
aws_web_identity_token=credentials_dict.get(
|
||||
"aws_web_identity_token",
|
||||
non_default_params.get("aws_web_identity_token", None),
|
||||
),
|
||||
aws_sts_endpoint=credentials_dict.get(
|
||||
"aws_sts_endpoint", non_default_params.get("aws_sts_endpoint", None)
|
||||
),
|
||||
)
|
||||
aws_region_name = self.get_aws_region_name_for_non_llm_api_calls(
|
||||
aws_region_name=credentials_dict.get(
|
||||
"aws_region_name", non_default_params.get("aws_region_name", None)
|
||||
),
|
||||
)
|
||||
|
||||
# Prepare request data
|
||||
request_data: BedrockKBRequest = BedrockKBRequest(
|
||||
retrievalQuery=BedrockKBRetrievalQuery(text=query),
|
||||
)
|
||||
if next_token:
|
||||
request_data["nextToken"] = next_token
|
||||
if retrieval_configuration:
|
||||
request_data["retrievalConfiguration"] = retrieval_configuration
|
||||
if guardrail_id and guardrail_version:
|
||||
request_data["guardrailConfiguration"] = BedrockKBGuardrailConfiguration(
|
||||
guardrailId=guardrail_id, guardrailVersion=guardrail_version
|
||||
)
|
||||
verbose_logger.debug(
|
||||
f"Request Data: {json.dumps(request_data, indent=4, default=str)}"
|
||||
)
|
||||
|
||||
# Prepare the request
|
||||
api_base = f"https://bedrock-agent-runtime.{aws_region_name}.amazonaws.com/knowledgebases/{knowledge_base_id}/retrieve"
|
||||
|
||||
prepared_request = self._prepare_request(
|
||||
credentials=credentials,
|
||||
data=request_data,
|
||||
optional_params=self.optional_params,
|
||||
aws_region_name=aws_region_name,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
verbose_proxy_logger.debug(
|
||||
"Bedrock Knowledge Base request body: %s, url %s, headers: %s",
|
||||
request_data,
|
||||
prepared_request.url,
|
||||
prepared_request.headers,
|
||||
)
|
||||
|
||||
response = await self.async_handler.post(
|
||||
url=prepared_request.url,
|
||||
data=prepared_request.body, # type: ignore
|
||||
headers=prepared_request.headers, # type: ignore
|
||||
)
|
||||
|
||||
verbose_proxy_logger.debug("Bedrock Knowledge Base response: %s", response.text)
|
||||
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
return BedrockKBResponse(**response_data)
|
||||
else:
|
||||
verbose_proxy_logger.error(
|
||||
"Bedrock Knowledge Base: error in response. Status code: %s, response: %s",
|
||||
response.status_code,
|
||||
response.text,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=response.status_code,
|
||||
detail={
|
||||
"error": "Error calling Bedrock Knowledge Base",
|
||||
"response": response.text,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_initialized_custom_logger() -> Optional[CustomLogger]:
|
||||
from litellm.litellm_core_utils.litellm_logging import (
|
||||
_init_custom_logger_compatible_class,
|
||||
)
|
||||
|
||||
return _init_custom_logger_compatible_class(
|
||||
logging_integration="bedrock_vector_store",
|
||||
internal_usage_cache=None,
|
||||
llm_router=None,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_chat_completion_message_from_bedrock_kb_response(
|
||||
response: BedrockKBResponse,
|
||||
) -> Tuple[Optional[ChatCompletionUserMessage], str]:
|
||||
"""
|
||||
Retrieves the context from the Bedrock Knowledge Base response and returns a ChatCompletionUserMessage object.
|
||||
"""
|
||||
retrieval_results: Optional[List[BedrockKBRetrievalResult]] = response.get(
|
||||
"retrievalResults", None
|
||||
)
|
||||
if retrieval_results is None:
|
||||
return None, ""
|
||||
|
||||
# string to combine the context from the knowledge base
|
||||
context_string: str = BedrockVectorStore.CONTENT_PREFIX_STRING
|
||||
for retrieval_result in retrieval_results:
|
||||
retrieval_result_content: Optional[BedrockKBContent] = (
|
||||
retrieval_result.get("content", None) or {}
|
||||
)
|
||||
if retrieval_result_content is None:
|
||||
continue
|
||||
retrieval_result_text: Optional[str] = retrieval_result_content.get(
|
||||
"text", None
|
||||
)
|
||||
if retrieval_result_text is None:
|
||||
continue
|
||||
context_string += retrieval_result_text
|
||||
message = ChatCompletionUserMessage(
|
||||
role="user",
|
||||
content=context_string,
|
||||
)
|
||||
return message, context_string
|
||||
|
|
@ -12,7 +12,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
|||
import litellm
|
||||
from litellm._logging import verbose_logger, verbose_proxy_logger
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.integrations.vector_stores.base_vector_store import BaseVectorStore
|
||||
from litellm.integrations.vector_store_integrations.base_vector_store import (
|
||||
BaseVectorStore,
|
||||
)
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
get_async_httpx_client,
|
||||
|
|
|
|||
135
litellm/litellm_core_utils/custom_logger_registry.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
"""
|
||||
Registry mapping the callback class string to the class type.
|
||||
|
||||
This is used to get the class type from the callback class string.
|
||||
|
||||
Example:
|
||||
"datadog" -> DataDogLogger
|
||||
"prometheus" -> PrometheusLogger
|
||||
"""
|
||||
|
||||
from litellm.integrations.agentops import AgentOps
|
||||
from litellm.integrations.anthropic_cache_control_hook import AnthropicCacheControlHook
|
||||
from litellm.integrations.argilla import ArgillaLogger
|
||||
from litellm.integrations.azure_storage.azure_storage import AzureBlobStorageLogger
|
||||
from litellm.integrations.braintrust_logging import BraintrustLogger
|
||||
from litellm.integrations.datadog.datadog import DataDogLogger
|
||||
from litellm.integrations.datadog.datadog_llm_obs import DataDogLLMObsLogger
|
||||
from litellm.integrations.deepeval import DeepEvalLogger
|
||||
from litellm.integrations.galileo import GalileoObserve
|
||||
from litellm.integrations.gcs_bucket.gcs_bucket import GCSBucketLogger
|
||||
from litellm.integrations.gcs_pubsub.pub_sub import GcsPubSubLogger
|
||||
from litellm.integrations.humanloop import HumanloopLogger
|
||||
from litellm.integrations.lago import LagoLogger
|
||||
from litellm.integrations.langfuse.langfuse_prompt_management import (
|
||||
LangfusePromptManagement,
|
||||
)
|
||||
from litellm.integrations.langsmith import LangsmithLogger
|
||||
from litellm.integrations.literal_ai import LiteralAILogger
|
||||
from litellm.integrations.mlflow import MlflowLogger
|
||||
from litellm.integrations.openmeter import OpenMeterLogger
|
||||
from litellm.integrations.opentelemetry import OpenTelemetry
|
||||
from litellm.integrations.opik.opik import OpikLogger
|
||||
from litellm.integrations.prometheus import PrometheusLogger
|
||||
from litellm.integrations.s3_v2 import S3Logger
|
||||
from litellm.integrations.vector_store_integrations.bedrock_vector_store import (
|
||||
BedrockVectorStore,
|
||||
)
|
||||
from litellm.proxy.hooks.dynamic_rate_limiter import _PROXY_DynamicRateLimitHandler
|
||||
|
||||
|
||||
class CustomLoggerRegistry:
|
||||
"""
|
||||
Registry mapping the callback class string to the class type.
|
||||
"""
|
||||
CALLBACK_CLASS_STR_TO_CLASS_TYPE = {
|
||||
"lago": LagoLogger,
|
||||
"openmeter": OpenMeterLogger,
|
||||
"braintrust": BraintrustLogger,
|
||||
"galileo": GalileoObserve,
|
||||
"langsmith": LangsmithLogger,
|
||||
"literalai": LiteralAILogger,
|
||||
"prometheus": PrometheusLogger,
|
||||
"datadog": DataDogLogger,
|
||||
"datadog_llm_observability": DataDogLLMObsLogger,
|
||||
"gcs_bucket": GCSBucketLogger,
|
||||
"opik": OpikLogger,
|
||||
"argilla": ArgillaLogger,
|
||||
"opentelemetry": OpenTelemetry,
|
||||
"azure_storage": AzureBlobStorageLogger,
|
||||
"humanloop": HumanloopLogger,
|
||||
# OTEL compatible loggers
|
||||
"logfire": OpenTelemetry,
|
||||
"arize": OpenTelemetry,
|
||||
"langfuse_otel": OpenTelemetry,
|
||||
"arize_phoenix": OpenTelemetry,
|
||||
"langtrace": OpenTelemetry,
|
||||
"mlflow": MlflowLogger,
|
||||
"langfuse": LangfusePromptManagement,
|
||||
"otel": OpenTelemetry,
|
||||
"gcs_pubsub": GcsPubSubLogger,
|
||||
"anthropic_cache_control_hook": AnthropicCacheControlHook,
|
||||
"agentops": AgentOps,
|
||||
"bedrock_vector_store": BedrockVectorStore,
|
||||
"deepeval": DeepEvalLogger,
|
||||
"s3_v2": S3Logger,
|
||||
"dynamic_rate_limiter": _PROXY_DynamicRateLimitHandler,
|
||||
}
|
||||
|
||||
try:
|
||||
from litellm_enterprise.enterprise_callbacks.generic_api_callback import (
|
||||
GenericAPILogger,
|
||||
)
|
||||
from litellm_enterprise.enterprise_callbacks.pagerduty.pagerduty import (
|
||||
PagerDutyAlerting,
|
||||
)
|
||||
from litellm_enterprise.enterprise_callbacks.send_emails.resend_email import (
|
||||
ResendEmailLogger,
|
||||
)
|
||||
from litellm_enterprise.enterprise_callbacks.send_emails.smtp_email import (
|
||||
SMTPEmailLogger,
|
||||
)
|
||||
|
||||
enterprise_loggers = {
|
||||
"pagerduty": PagerDutyAlerting,
|
||||
"generic_api": GenericAPILogger,
|
||||
"resend_email": ResendEmailLogger,
|
||||
"smtp_email": SMTPEmailLogger,
|
||||
}
|
||||
CALLBACK_CLASS_STR_TO_CLASS_TYPE.update(enterprise_loggers)
|
||||
except ImportError:
|
||||
pass # enterprise not installed
|
||||
|
||||
@classmethod
|
||||
def get_callback_str_from_class_type(cls, class_type: type) -> str | None:
|
||||
"""
|
||||
Get the callback string from the class type.
|
||||
|
||||
Args:
|
||||
class_type: The class type to find the string for
|
||||
|
||||
Returns:
|
||||
str: The callback string, or None if not found
|
||||
"""
|
||||
for callback_str, callback_class in cls.CALLBACK_CLASS_STR_TO_CLASS_TYPE.items():
|
||||
if callback_class == class_type:
|
||||
return callback_str
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_all_callback_strs_from_class_type(cls, class_type: type) -> list[str]:
|
||||
"""
|
||||
Get all callback strings that map to the same class type.
|
||||
Some class types (like OpenTelemetry) have multiple string mappings.
|
||||
|
||||
Args:
|
||||
class_type: The class type to find all strings for
|
||||
|
||||
Returns:
|
||||
list: List of callback strings that map to the class type
|
||||
"""
|
||||
callback_strs: list[str] = []
|
||||
for callback_str, callback_class in cls.CALLBACK_CLASS_STR_TO_CLASS_TYPE.items():
|
||||
if callback_class == class_type:
|
||||
callback_strs.append(callback_str)
|
||||
return callback_strs
|
||||
|
|
@ -54,7 +54,9 @@ from litellm.integrations.custom_guardrail import CustomGuardrail
|
|||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.integrations.deepeval.deepeval import DeepEvalLogger
|
||||
from litellm.integrations.mlflow import MlflowLogger
|
||||
from litellm.integrations.vector_stores.bedrock_vector_store import BedrockVectorStore
|
||||
from litellm.integrations.vector_store_integrations.bedrock_vector_store import (
|
||||
BedrockVectorStore,
|
||||
)
|
||||
from litellm.litellm_core_utils.get_litellm_params import get_litellm_params
|
||||
from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
|
||||
StandardBuiltInToolCostTracking,
|
||||
|
|
@ -147,6 +149,9 @@ from .initialize_dynamic_callback_params import (
|
|||
from .specialty_caches.dynamic_logging_cache import DynamicLoggingCache
|
||||
|
||||
try:
|
||||
from litellm_enterprise.enterprise_callbacks.callback_controls import (
|
||||
EnterpriseCallbackControls,
|
||||
)
|
||||
from litellm_enterprise.enterprise_callbacks.generic_api_callback import (
|
||||
GenericAPILogger,
|
||||
)
|
||||
|
|
@ -174,6 +179,7 @@ except Exception as e:
|
|||
ResendEmailLogger = CustomLogger # type: ignore
|
||||
SMTPEmailLogger = CustomLogger # type: ignore
|
||||
PagerDutyAlerting = CustomLogger # type: ignore
|
||||
EnterpriseCallbackControls = None # type: ignore
|
||||
EnterpriseStandardLoggingPayloadSetupVAR = None
|
||||
_in_memory_loggers: List[Any] = []
|
||||
|
||||
|
|
@ -1079,6 +1085,18 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
used for consistent cost calculation across response headers + logging integrations.
|
||||
"""
|
||||
|
||||
if isinstance(result, BaseModel) and hasattr(result, "_hidden_params"):
|
||||
hidden_params = getattr(result, "_hidden_params", {})
|
||||
if (
|
||||
"response_cost" in hidden_params
|
||||
and hidden_params["response_cost"] is not None
|
||||
): # use cost if already calculated
|
||||
return hidden_params["response_cost"]
|
||||
elif (
|
||||
router_model_id is None and "model_id" in hidden_params
|
||||
): # use model_id if not already set
|
||||
router_model_id = hidden_params["model_id"]
|
||||
|
||||
## RESPONSE COST ##
|
||||
custom_pricing = use_custom_pricing_for_model(
|
||||
litellm_params=(
|
||||
|
|
@ -1217,6 +1235,14 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
f"no-log request, skipping logging for {event_hook} event"
|
||||
)
|
||||
return False
|
||||
|
||||
# Check for dynamically disabled callbacks via headers
|
||||
if EnterpriseCallbackControls is not None and EnterpriseCallbackControls.is_callback_disabled_via_headers(callback, litellm_params):
|
||||
verbose_logger.debug(
|
||||
f"Callback {callback} disabled via x-litellm-disable-callbacks header for {event_hook} event"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _update_completion_start_time(self, completion_start_time: datetime.datetime):
|
||||
|
|
@ -2246,6 +2272,14 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
self.has_run_logging(event_type="sync_failure")
|
||||
for callback in callbacks:
|
||||
try:
|
||||
litellm_params = self.model_call_details.get("litellm_params", {})
|
||||
should_run = self.should_run_callback(
|
||||
callback=callback,
|
||||
litellm_params=litellm_params,
|
||||
event_hook="failure_handler",
|
||||
)
|
||||
if not should_run:
|
||||
continue
|
||||
if callback == "lunary" and lunaryLogger is not None:
|
||||
print_verbose("reaches lunary for logging error!")
|
||||
|
||||
|
|
@ -2427,6 +2461,14 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
self.has_run_logging(event_type="async_failure")
|
||||
for callback in callbacks:
|
||||
try:
|
||||
litellm_params = self.model_call_details.get("litellm_params", {})
|
||||
should_run = self.should_run_callback(
|
||||
callback=callback,
|
||||
litellm_params=litellm_params,
|
||||
event_hook="async_failure_handler",
|
||||
)
|
||||
if not should_run:
|
||||
continue
|
||||
if isinstance(callback, CustomLogger): # custom logger class
|
||||
await callback.async_log_failure_event(
|
||||
kwargs=self.model_call_details,
|
||||
|
|
|
|||
|
|
@ -66,3 +66,18 @@ def pick_cheapest_chat_models_from_llm_provider(custom_llm_provider: str, n=1):
|
|||
|
||||
# Return the top n cheapest models
|
||||
return [model for model, _ in model_costs[:n]]
|
||||
|
||||
def get_proxy_server_request_headers(litellm_params: Optional[dict]) -> dict:
|
||||
"""
|
||||
Get the `proxy_server_request` headers from the litellm_params.\
|
||||
|
||||
Use this if you want to access the request headers made to LiteLLM proxy server.
|
||||
"""
|
||||
if litellm_params is None:
|
||||
return {}
|
||||
|
||||
proxy_request_headers = (
|
||||
litellm_params.get("proxy_server_request", {}).get("headers", {}) or {}
|
||||
)
|
||||
|
||||
return proxy_request_headers
|
||||
|
|
@ -40,6 +40,34 @@ from litellm.types.utils import (
|
|||
from .get_headers import get_response_headers
|
||||
|
||||
|
||||
def _safe_convert_created_field(created_value) -> int:
|
||||
"""
|
||||
Safely convert a 'created' field value to an integer.
|
||||
|
||||
Some providers (like SambaNova) return the 'created' field as a float
|
||||
(Unix timestamp with fractional seconds), but LiteLLM expects an integer.
|
||||
|
||||
Args:
|
||||
created_value: The value from response_object["created"]
|
||||
|
||||
Returns:
|
||||
int: Unix timestamp as integer
|
||||
"""
|
||||
if created_value is None:
|
||||
return int(time.time())
|
||||
elif isinstance(created_value, int):
|
||||
return created_value
|
||||
elif isinstance(created_value, float):
|
||||
return int(created_value)
|
||||
else:
|
||||
# for strings, etc
|
||||
try:
|
||||
return int(float(created_value))
|
||||
except (ValueError, TypeError):
|
||||
# Fallback to current time if conversion fails
|
||||
return int(time.time())
|
||||
|
||||
|
||||
def convert_tool_call_to_json_mode(
|
||||
tool_calls: List[ChatCompletionMessageToolCall],
|
||||
convert_tool_call_to_json_mode: bool,
|
||||
|
|
@ -133,7 +161,7 @@ async def convert_to_streaming_response_async(response_object: Optional[dict] =
|
|||
model_response_object.id = response_object["id"]
|
||||
|
||||
if "created" in response_object:
|
||||
model_response_object.created = response_object["created"]
|
||||
model_response_object.created = _safe_convert_created_field(response_object["created"])
|
||||
|
||||
if "system_fingerprint" in response_object:
|
||||
model_response_object.system_fingerprint = response_object["system_fingerprint"]
|
||||
|
|
@ -181,7 +209,7 @@ def convert_to_streaming_response(response_object: Optional[dict] = None):
|
|||
model_response_object.id = response_object["id"]
|
||||
|
||||
if "created" in response_object:
|
||||
model_response_object.created = response_object["created"]
|
||||
model_response_object.created = _safe_convert_created_field(response_object["created"])
|
||||
|
||||
if "system_fingerprint" in response_object:
|
||||
model_response_object.system_fingerprint = response_object["system_fingerprint"]
|
||||
|
|
@ -578,9 +606,7 @@ def convert_to_model_response_object( # noqa: PLR0915
|
|||
usage_object = litellm.Usage(**response_object["usage"])
|
||||
setattr(model_response_object, "usage", usage_object)
|
||||
if "created" in response_object:
|
||||
model_response_object.created = response_object["created"] or int(
|
||||
time.time()
|
||||
)
|
||||
model_response_object.created = _safe_convert_created_field(response_object["created"])
|
||||
|
||||
if "id" in response_object:
|
||||
model_response_object.id = response_object["id"] or str(uuid.uuid4())
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import litellm
|
|||
from litellm._logging import verbose_logger
|
||||
from litellm.integrations.additional_logging_utils import AdditionalLoggingUtils
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.types.utils import CallbacksByType
|
||||
|
||||
|
||||
class LoggingCallbackManager:
|
||||
|
|
@ -275,3 +276,65 @@ class LoggingCallbackManager:
|
|||
isinstance(callback, callback_type)
|
||||
for callback in self._get_all_callbacks()
|
||||
)
|
||||
|
||||
def get_callbacks_by_type(self) -> CallbacksByType:
|
||||
"""
|
||||
Get all active callbacks categorized by their type (success, failure, success_and_failure).
|
||||
|
||||
Returns:
|
||||
CallbacksByType: Dict with keys 'success', 'failure', 'success_and_failure' containing lists of callback strings
|
||||
"""
|
||||
# Get callback lists
|
||||
success_callbacks = set(litellm.success_callback + litellm._async_success_callback)
|
||||
failure_callbacks = set(litellm.failure_callback + litellm._async_failure_callback)
|
||||
general_callbacks = set(litellm.callbacks)
|
||||
|
||||
# Get all unique callbacks
|
||||
all_callbacks = success_callbacks | failure_callbacks | general_callbacks
|
||||
|
||||
result: CallbacksByType = CallbacksByType(
|
||||
success=[],
|
||||
failure=[],
|
||||
success_and_failure=[]
|
||||
)
|
||||
|
||||
for callback in all_callbacks:
|
||||
callback_str = self._get_callback_string(callback)
|
||||
|
||||
is_in_success = callback in success_callbacks
|
||||
is_in_failure = callback in failure_callbacks
|
||||
is_in_general = callback in general_callbacks
|
||||
|
||||
if is_in_general or (is_in_success and is_in_failure):
|
||||
result["success_and_failure"].append(callback_str)
|
||||
elif is_in_success:
|
||||
result["success"].append(callback_str)
|
||||
elif is_in_failure:
|
||||
result["failure"].append(callback_str)
|
||||
|
||||
|
||||
|
||||
# final de-duplication
|
||||
result["success"] = list(set(result["success"]))
|
||||
result["failure"] = list(set(result["failure"]))
|
||||
result["success_and_failure"] = list(set(result["success_and_failure"]))
|
||||
|
||||
return result
|
||||
|
||||
def _get_callback_string(
|
||||
self,
|
||||
callback: Union[CustomLogger, Callable, str]
|
||||
) -> str:
|
||||
from litellm.litellm_core_utils.custom_logger_registry import (
|
||||
CustomLoggerRegistry,
|
||||
)
|
||||
"""Convert a callback to its string representation"""
|
||||
if isinstance(callback, str):
|
||||
return callback
|
||||
elif isinstance(callback, CustomLogger):
|
||||
# Try to get the string representation from the registry
|
||||
callback_str = CustomLoggerRegistry.get_callback_str_from_class_type(type(callback))
|
||||
return callback_str if callback_str is not None else type(callback).__name__
|
||||
elif callable(callback):
|
||||
return getattr(callback, '__name__', str(callback))
|
||||
return str(callback)
|
||||
|
|
|
|||
|
|
@ -489,38 +489,100 @@ def extract_file_data(file_data: FileTypes) -> ExtractedFileData:
|
|||
)
|
||||
|
||||
|
||||
def unpack_defs(schema, defs):
|
||||
properties = schema.get("properties", None)
|
||||
if properties is None:
|
||||
return
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic, dependency-free implementation of `unpack_defs`
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
for name, value in properties.items():
|
||||
ref_key = value.get("$ref", None)
|
||||
if ref_key is not None:
|
||||
ref = defs[ref_key.split("defs/")[-1]]
|
||||
unpack_defs(ref, defs)
|
||||
properties[name] = ref
|
||||
|
||||
def unpack_defs(schema: dict, defs: dict) -> None:
|
||||
"""Expand *all* ``$ref`` entries pointing into ``$defs`` / ``definitions``.
|
||||
|
||||
This utility walks the entire schema tree (dicts and lists) so it naturally
|
||||
resolves references hidden under any keyword – ``items``, ``allOf``,
|
||||
``anyOf``, ``oneOf``, ``additionalProperties``, etc.
|
||||
|
||||
It mutates *schema* in-place and does **not** return anything. The helper
|
||||
keeps memory overhead low by resolving nodes as it encounters them rather
|
||||
than materialising a fully dereferenced copy first.
|
||||
"""
|
||||
|
||||
import copy
|
||||
from collections import deque
|
||||
|
||||
# Combine the defs handed down by the caller with defs/definitions found on
|
||||
# the current node. Local keys shadow parent keys to match JSON-schema
|
||||
# scoping rules.
|
||||
root_defs: dict = {
|
||||
**defs,
|
||||
**schema.get("$defs", {}),
|
||||
**schema.get("definitions", {}),
|
||||
}
|
||||
|
||||
# Use iterative approach with queue to avoid recursion
|
||||
# Each item in queue is (node, parent_container, key/index, active_defs, seen_ids)
|
||||
queue: deque[tuple[Any, Union[dict, list, None], Union[str, int, None], dict, set]] = deque([(schema, None, None, root_defs, set())])
|
||||
|
||||
while queue:
|
||||
node, parent, key, active_defs, seen = queue.popleft()
|
||||
|
||||
# Avoid infinite loops on self-referential schemas
|
||||
if id(node) in seen:
|
||||
continue
|
||||
seen = seen.copy() # Create new set for this branch
|
||||
seen.add(id(node))
|
||||
|
||||
anyof = value.get("anyOf", None)
|
||||
if anyof is not None:
|
||||
for i, atype in enumerate(anyof):
|
||||
ref_key = atype.get("$ref", None)
|
||||
if ref_key is not None:
|
||||
ref = defs[ref_key.split("defs/")[-1]]
|
||||
unpack_defs(ref, defs)
|
||||
anyof[i] = ref
|
||||
continue
|
||||
# ----------------------------- dict -----------------------------
|
||||
if isinstance(node, dict):
|
||||
# --- Case 1: this node *is* a reference ---
|
||||
if "$ref" in node:
|
||||
ref_name = node["$ref"].split("/")[-1]
|
||||
target_schema = active_defs.get(ref_name)
|
||||
# Unknown reference – leave untouched
|
||||
if target_schema is None:
|
||||
continue
|
||||
|
||||
items = value.get("items", None)
|
||||
if items is not None:
|
||||
ref_key = items.get("$ref", None)
|
||||
if ref_key is not None:
|
||||
ref = defs[ref_key.split("defs/")[-1]]
|
||||
unpack_defs(ref, defs)
|
||||
value["items"] = ref
|
||||
# Merge defs from the target to capture nested definitions
|
||||
child_defs = {
|
||||
**active_defs,
|
||||
**target_schema.get("$defs", {}),
|
||||
**target_schema.get("definitions", {}),
|
||||
}
|
||||
|
||||
# Replace the reference with resolved copy
|
||||
resolved = copy.deepcopy(target_schema)
|
||||
if parent is not None and key is not None:
|
||||
if isinstance(parent, dict) and isinstance(key, str):
|
||||
parent[key] = resolved
|
||||
elif isinstance(parent, list) and isinstance(key, int):
|
||||
parent[key] = resolved
|
||||
else:
|
||||
# This is the root schema itself
|
||||
schema.clear()
|
||||
schema.update(resolved)
|
||||
resolved = schema
|
||||
|
||||
# Add resolved node to queue for further processing
|
||||
queue.append((resolved, parent, key, child_defs, seen))
|
||||
continue
|
||||
|
||||
# --- Case 2: regular dict – process its values ---
|
||||
# Update defs with any nested $defs/definitions present *here*.
|
||||
current_defs = {
|
||||
**active_defs,
|
||||
**node.get("$defs", {}),
|
||||
**node.get("definitions", {}),
|
||||
}
|
||||
|
||||
# Add all dict values to queue
|
||||
for k, v in node.items():
|
||||
queue.append((v, node, k, current_defs, seen))
|
||||
|
||||
# ---------------------------- list ------------------------------
|
||||
elif isinstance(node, list):
|
||||
# Add all list items to queue
|
||||
for idx, item in enumerate(node):
|
||||
queue.append((item, node, idx, active_defs, seen))
|
||||
|
||||
|
||||
def _get_image_mime_type_from_url(url: str) -> Optional[str]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ class CustomStreamWrapper:
|
|||
|
||||
self.system_fingerprint: Optional[str] = None
|
||||
self.received_finish_reason: Optional[str] = None
|
||||
self.intermittent_finish_reason: Optional[
|
||||
str
|
||||
] = None # finish reasons that show up mid-stream
|
||||
self.intermittent_finish_reason: Optional[str] = (
|
||||
None # finish reasons that show up mid-stream
|
||||
)
|
||||
self.special_tokens = [
|
||||
"<|assistant|>",
|
||||
"<|system|>",
|
||||
|
|
@ -643,6 +643,7 @@ class CustomStreamWrapper:
|
|||
model_response._hidden_params = {
|
||||
**model_response._hidden_params,
|
||||
**self._hidden_params,
|
||||
"response_cost": None,
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
@ -1322,9 +1323,9 @@ class CustomStreamWrapper:
|
|||
_json_delta = delta.model_dump()
|
||||
print_verbose(f"_json_delta: {_json_delta}")
|
||||
if "role" not in _json_delta or _json_delta["role"] is None:
|
||||
_json_delta[
|
||||
"role"
|
||||
] = "assistant" # mistral's api returns role as None
|
||||
_json_delta["role"] = (
|
||||
"assistant" # mistral's api returns role as None
|
||||
)
|
||||
if "tool_calls" in _json_delta and isinstance(
|
||||
_json_delta["tool_calls"], list
|
||||
):
|
||||
|
|
@ -1715,9 +1716,9 @@ class CustomStreamWrapper:
|
|||
chunk = next(self.completion_stream)
|
||||
if chunk is not None and chunk != b"":
|
||||
print_verbose(f"PROCESSED CHUNK PRE CHUNK CREATOR: {chunk}")
|
||||
processed_chunk: Optional[
|
||||
ModelResponseStream
|
||||
] = self.chunk_creator(chunk=chunk)
|
||||
processed_chunk: Optional[ModelResponseStream] = (
|
||||
self.chunk_creator(chunk=chunk)
|
||||
)
|
||||
print_verbose(
|
||||
f"PROCESSED CHUNK POST CHUNK CREATOR: {processed_chunk}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ def anthropic_messages_handler(
|
|||
"""
|
||||
Makes Anthropic `/v1/messages` API calls In the Anthropic API Spec
|
||||
"""
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
local_vars = locals()
|
||||
is_async = kwargs.pop("is_async", False)
|
||||
# Use provided client or create a new one
|
||||
|
|
@ -141,12 +143,17 @@ def anthropic_messages_handler(
|
|||
api_key=litellm_params.api_key,
|
||||
)
|
||||
|
||||
anthropic_messages_provider_config: Optional[
|
||||
BaseAnthropicMessagesConfig
|
||||
] = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model=model,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
anthropic_messages_provider_config: Optional[BaseAnthropicMessagesConfig] = None
|
||||
|
||||
if custom_llm_provider is not None and custom_llm_provider in [
|
||||
provider.value for provider in LlmProviders
|
||||
]:
|
||||
anthropic_messages_provider_config = (
|
||||
ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model=model,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
)
|
||||
if anthropic_messages_provider_config is None:
|
||||
# Handle non-Anthropic models using the adapter
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from typing import List, Optional
|
|||
import litellm
|
||||
from litellm import verbose_logger
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
from litellm.utils import get_model_info
|
||||
from litellm.utils import get_model_info, supports_reasoning
|
||||
|
||||
from ...openai.chat.o_series_transformation import OpenAIOSeriesConfig
|
||||
|
||||
|
|
@ -38,7 +38,9 @@ class AzureOpenAIO1Config(OpenAIOSeriesConfig):
|
|||
"top_logprobs",
|
||||
]
|
||||
|
||||
o_series_only_param = ["reasoning_effort"]
|
||||
o_series_only_param = []
|
||||
if supports_reasoning(model):
|
||||
o_series_only_param.append("reasoning_effort")
|
||||
all_openai_params.extend(o_series_only_param)
|
||||
return [
|
||||
param for param in all_openai_params if param not in non_supported_params
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
from typing import Any, Callable, Dict, Optional, Union
|
||||
from typing import Any, Callable, Dict, Literal, Optional, Union, cast
|
||||
|
||||
import httpx
|
||||
from openai import AsyncAzureOpenAI, AzureOpenAI
|
||||
|
|
@ -14,6 +14,8 @@ from litellm.secret_managers.get_azure_ad_token_provider import (
|
|||
get_azure_ad_token_provider,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.utils import _add_path_to_api_base
|
||||
|
||||
azure_ad_cache = DualCache()
|
||||
|
||||
|
|
@ -264,6 +266,126 @@ def select_azure_base_url_or_endpoint(azure_client_params: dict):
|
|||
return azure_client_params
|
||||
|
||||
|
||||
def get_azure_ad_token(
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Get Azure AD token from various authentication methods.
|
||||
|
||||
This function tries different methods to obtain an Azure AD token:
|
||||
1. From an existing token provider
|
||||
2. From Entra ID using tenant_id, client_id, and client_secret
|
||||
3. From username and password
|
||||
4. From OIDC token
|
||||
5. From a service principal with secret workflow
|
||||
|
||||
Args:
|
||||
litellm_params: Dictionary containing authentication parameters
|
||||
- azure_ad_token_provider: Optional callable that returns a token
|
||||
- azure_ad_token: Optional existing token
|
||||
- tenant_id: Optional Azure tenant ID
|
||||
- client_id: Optional Azure client ID
|
||||
- client_secret: Optional Azure client secret
|
||||
- azure_username: Optional Azure username
|
||||
- azure_password: Optional Azure password
|
||||
|
||||
Returns:
|
||||
Azure AD token as string if successful, None otherwise
|
||||
"""
|
||||
# Extract parameters
|
||||
azure_ad_token_provider = litellm_params.get("azure_ad_token_provider")
|
||||
azure_ad_token = litellm_params.get("azure_ad_token", None) or get_secret_str(
|
||||
"AZURE_AD_TOKEN"
|
||||
)
|
||||
tenant_id = litellm_params.get("tenant_id", os.getenv("AZURE_TENANT_ID"))
|
||||
client_id = litellm_params.get("client_id", os.getenv("AZURE_CLIENT_ID"))
|
||||
client_secret = litellm_params.get(
|
||||
"client_secret", os.getenv("AZURE_CLIENT_SECRET")
|
||||
)
|
||||
azure_username = litellm_params.get("azure_username", os.getenv("AZURE_USERNAME"))
|
||||
azure_password = litellm_params.get("azure_password", os.getenv("AZURE_PASSWORD"))
|
||||
scope = litellm_params.get(
|
||||
"azure_scope",
|
||||
os.getenv("AZURE_SCOPE", "https://cognitiveservices.azure.com/.default"),
|
||||
)
|
||||
if scope is None:
|
||||
scope = "https://cognitiveservices.azure.com/.default"
|
||||
|
||||
# Try to get token provider from Entra ID
|
||||
if azure_ad_token_provider is None and tenant_id and client_id and client_secret:
|
||||
verbose_logger.debug(
|
||||
"Using Azure AD Token Provider from Entra ID for Azure Auth"
|
||||
)
|
||||
azure_ad_token_provider = get_azure_ad_token_from_entra_id(
|
||||
tenant_id=tenant_id,
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
# Try to get token provider from username and password
|
||||
if (
|
||||
azure_ad_token_provider is None
|
||||
and azure_username
|
||||
and azure_password
|
||||
and client_id
|
||||
):
|
||||
verbose_logger.debug("Using Azure Username and Password for Azure Auth")
|
||||
azure_ad_token_provider = get_azure_ad_token_from_username_password(
|
||||
azure_username=azure_username,
|
||||
azure_password=azure_password,
|
||||
client_id=client_id,
|
||||
scope=scope,
|
||||
)
|
||||
|
||||
# Try to get token from OIDC
|
||||
if (
|
||||
client_id
|
||||
and tenant_id
|
||||
and azure_ad_token
|
||||
and azure_ad_token.startswith("oidc/")
|
||||
):
|
||||
verbose_logger.debug("Using Azure OIDC Token for Azure Auth")
|
||||
azure_ad_token = get_azure_ad_token_from_oidc(
|
||||
azure_ad_token=azure_ad_token,
|
||||
azure_client_id=client_id,
|
||||
azure_tenant_id=tenant_id,
|
||||
scope=scope,
|
||||
)
|
||||
# Try to get token provider from service principal
|
||||
elif (
|
||||
azure_ad_token_provider is None
|
||||
and litellm.enable_azure_ad_token_refresh is True
|
||||
):
|
||||
verbose_logger.debug(
|
||||
"Using Azure AD token provider based on Service Principal with Secret workflow for Azure Auth"
|
||||
)
|
||||
try:
|
||||
azure_ad_token_provider = get_azure_ad_token_provider(azure_scope=scope)
|
||||
except ValueError:
|
||||
verbose_logger.debug("Azure AD Token Provider could not be used.")
|
||||
|
||||
# Execute the token provider to get the token if available
|
||||
if azure_ad_token_provider and callable(azure_ad_token_provider):
|
||||
try:
|
||||
token = azure_ad_token_provider()
|
||||
if not isinstance(token, str):
|
||||
verbose_logger.error(
|
||||
f"Azure AD token provider returned non-string value: {type(token)}"
|
||||
)
|
||||
raise TypeError(f"Azure AD token must be a string, got {type(token)}")
|
||||
else:
|
||||
azure_ad_token = token
|
||||
except TypeError:
|
||||
# Re-raise TypeError directly
|
||||
raise
|
||||
except Exception as e:
|
||||
verbose_logger.error(f"Error calling Azure AD token provider: {str(e)}")
|
||||
raise RuntimeError(f"Failed to get Azure AD token: {str(e)}") from e
|
||||
|
||||
return azure_ad_token
|
||||
|
||||
|
||||
class BaseAzureLLM(BaseOpenAILLM):
|
||||
def get_azure_openai_client(
|
||||
self,
|
||||
|
|
@ -492,3 +614,78 @@ class BaseAzureLLM(BaseOpenAILLM):
|
|||
else:
|
||||
client = AzureOpenAI(**azure_client_params) # type: ignore
|
||||
return client
|
||||
|
||||
@staticmethod
|
||||
def _base_validate_azure_environment(
|
||||
headers: dict, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
litellm_params = litellm_params or GenericLiteLLMParams()
|
||||
api_key = (
|
||||
litellm_params.api_key
|
||||
or litellm.api_key
|
||||
or litellm.azure_key
|
||||
or get_secret_str("AZURE_OPENAI_API_KEY")
|
||||
or get_secret_str("AZURE_API_KEY")
|
||||
)
|
||||
|
||||
if api_key:
|
||||
headers["api-key"] = api_key
|
||||
return headers
|
||||
|
||||
### Fallback to Azure AD token-based authentication if no API key is available
|
||||
### Retrieves Azure AD token and adds it to the Authorization header
|
||||
azure_ad_token = get_azure_ad_token(litellm_params)
|
||||
if azure_ad_token:
|
||||
headers["Authorization"] = f"Bearer {azure_ad_token}"
|
||||
|
||||
return headers
|
||||
|
||||
@staticmethod
|
||||
def _get_base_azure_url(
|
||||
api_base: Optional[str],
|
||||
litellm_params: Optional[Union[GenericLiteLLMParams, Dict[str, Any]]],
|
||||
route: Literal["/openai/responses", "/openai/vector_stores"]
|
||||
) -> str:
|
||||
api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE")
|
||||
if api_base is None:
|
||||
raise ValueError(
|
||||
f"api_base is required for Azure AI Studio. Please set the api_base parameter. Passed `api_base={api_base}`"
|
||||
)
|
||||
original_url = httpx.URL(api_base)
|
||||
|
||||
# Extract api_version or use default
|
||||
litellm_params = litellm_params or {}
|
||||
api_version = cast(Optional[str], litellm_params.get("api_version"))
|
||||
|
||||
# Create a new dictionary with existing params
|
||||
query_params = dict(original_url.params)
|
||||
|
||||
# Add api_version if needed
|
||||
if "api-version" not in query_params and api_version:
|
||||
query_params["api-version"] = api_version
|
||||
|
||||
# Add the path to the base URL
|
||||
if route not in api_base:
|
||||
new_url = _add_path_to_api_base(
|
||||
api_base=api_base, ending_path=route
|
||||
)
|
||||
else:
|
||||
new_url = api_base
|
||||
|
||||
if BaseAzureLLM._is_azure_v1_api_version(api_version):
|
||||
# ensure the request go to /openai/v1 and not just /openai
|
||||
if "/openai/v1" not in new_url:
|
||||
parsed_url = httpx.URL(new_url)
|
||||
new_url = str(parsed_url.copy_with(path=parsed_url.path.replace("/openai", "/openai/v1")))
|
||||
|
||||
|
||||
# Use the new query_params dictionary
|
||||
final_url = httpx.URL(new_url).copy_with(params=query_params)
|
||||
|
||||
return str(final_url)
|
||||
|
||||
@staticmethod
|
||||
def _is_azure_v1_api_version(api_version: Optional[str]) -> bool:
|
||||
if api_version is None:
|
||||
return False
|
||||
return api_version == "preview" or api_version == "latest"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, cast
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple
|
||||
|
||||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.openai import *
|
||||
from litellm.types.responses.main import *
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.utils import _add_path_to_api_base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||||
|
|
@ -21,26 +17,13 @@ else:
|
|||
|
||||
class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
api_key: Optional[str] = None,
|
||||
self, headers: dict, model: str, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
api_key = (
|
||||
api_key
|
||||
or litellm.api_key
|
||||
or litellm.azure_key
|
||||
or get_secret_str("AZURE_OPENAI_API_KEY")
|
||||
or get_secret_str("AZURE_API_KEY")
|
||||
return BaseAzureLLM._base_validate_azure_environment(
|
||||
headers=headers,
|
||||
litellm_params=litellm_params
|
||||
)
|
||||
|
||||
headers.update(
|
||||
{
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
}
|
||||
)
|
||||
return headers
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
|
|
@ -62,47 +45,12 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
- A complete URL string, e.g.,
|
||||
"https://litellm8397336933.openai.azure.com/openai/responses?api-version=2024-05-01-preview"
|
||||
"""
|
||||
api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE")
|
||||
if api_base is None:
|
||||
raise ValueError(
|
||||
f"api_base is required for Azure AI Studio. Please set the api_base parameter. Passed `api_base={api_base}`"
|
||||
)
|
||||
original_url = httpx.URL(api_base)
|
||||
|
||||
# Extract api_version or use default
|
||||
api_version = cast(Optional[str], litellm_params.get("api_version"))
|
||||
|
||||
# Create a new dictionary with existing params
|
||||
query_params = dict(original_url.params)
|
||||
|
||||
# Add api_version if needed
|
||||
if "api-version" not in query_params and api_version:
|
||||
query_params["api-version"] = api_version
|
||||
|
||||
# Add the path to the base URL
|
||||
if "/openai/responses" not in api_base:
|
||||
new_url = _add_path_to_api_base(
|
||||
api_base=api_base, ending_path="/openai/responses"
|
||||
)
|
||||
else:
|
||||
new_url = api_base
|
||||
|
||||
if self._is_azure_v1_api_version(api_version):
|
||||
# ensure the request go to /openai/v1 and not just /openai
|
||||
if "/openai/v1" not in new_url:
|
||||
parsed_url = httpx.URL(new_url)
|
||||
new_url = str(parsed_url.copy_with(path=parsed_url.path.replace("/openai", "/openai/v1")))
|
||||
|
||||
|
||||
# Use the new query_params dictionary
|
||||
final_url = httpx.URL(new_url).copy_with(params=query_params)
|
||||
|
||||
return str(final_url)
|
||||
return BaseAzureLLM._get_base_azure_url(
|
||||
api_base=api_base,
|
||||
litellm_params=litellm_params,
|
||||
route="/openai/responses"
|
||||
)
|
||||
|
||||
def _is_azure_v1_api_version(self, api_version: Optional[str]) -> bool:
|
||||
if api_version is None:
|
||||
return False
|
||||
return api_version == "preview" or api_version == "latest"
|
||||
|
||||
#########################################################
|
||||
########## DELETE RESPONSE API TRANSFORMATION ##############
|
||||
|
|
|
|||
27
litellm/llms/azure/vector_stores/transformation.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from typing import Optional
|
||||
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.openai.vector_stores.transformation import OpenAIVectorStoreConfig
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
|
||||
class AzureOpenAIVectorStoreConfig(OpenAIVectorStoreConfig):
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
litellm_params: dict,
|
||||
) -> str:
|
||||
return BaseAzureLLM._get_base_azure_url(
|
||||
api_base=api_base,
|
||||
litellm_params=litellm_params,
|
||||
route="/openai/vector_stores"
|
||||
)
|
||||
|
||||
|
||||
def validate_environment(
|
||||
self, headers: dict, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
return BaseAzureLLM._base_validate_azure_environment(
|
||||
headers=headers,
|
||||
litellm_params=litellm_params
|
||||
)
|
||||
|
|
@ -63,10 +63,7 @@ class BaseResponsesAPIConfig(ABC):
|
|||
|
||||
@abstractmethod
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
api_key: Optional[str] = None,
|
||||
self, headers: dict, model: str, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
return {}
|
||||
|
||||
|
|
|
|||
86
litellm/llms/base_llm/vector_store/transformation.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.vector_stores import (
|
||||
VectorStoreCreateOptionalRequestParams,
|
||||
VectorStoreCreateResponse,
|
||||
VectorStoreSearchOptionalRequestParams,
|
||||
VectorStoreSearchResponse,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||||
|
||||
from ..chat.transformation import BaseLLMException as _BaseLLMException
|
||||
|
||||
LiteLLMLoggingObj = _LiteLLMLoggingObj
|
||||
BaseLLMException = _BaseLLMException
|
||||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
BaseLLMException = Any
|
||||
|
||||
class BaseVectorStoreConfig:
|
||||
@abstractmethod
|
||||
def transform_search_vector_store_request(
|
||||
self,
|
||||
vector_store_id: str,
|
||||
query: Union[str, List[str]],
|
||||
vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transform_search_vector_store_response(self, response: httpx.Response) -> VectorStoreSearchResponse:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transform_create_vector_store_request(
|
||||
self,
|
||||
vector_store_create_optional_params: VectorStoreCreateOptionalRequestParams,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transform_create_vector_store_response(self, response: httpx.Response) -> VectorStoreCreateResponse:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def validate_environment(
|
||||
self, headers: dict, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
return {}
|
||||
|
||||
@abstractmethod
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
litellm_params: dict,
|
||||
) -> str:
|
||||
"""
|
||||
OPTIONAL
|
||||
|
||||
Get the complete url for the request
|
||||
|
||||
Some providers need `model` in `api_base`
|
||||
"""
|
||||
if api_base is None:
|
||||
raise ValueError("api_base is required")
|
||||
return api_base
|
||||
|
||||
|
||||
def get_error_class(
|
||||
self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers]
|
||||
) -> BaseLLMException:
|
||||
from ..chat.transformation import BaseLLMException
|
||||
|
||||
raise BaseLLMException(
|
||||
status_code=status_code,
|
||||
message=error_message,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import asyncio
|
||||
import contextlib
|
||||
import os
|
||||
import typing
|
||||
import urllib.request
|
||||
from typing import Callable, Dict, Union
|
||||
|
||||
import aiohttp
|
||||
|
|
@ -9,7 +11,9 @@ import aiohttp.http_exceptions
|
|||
import httpx
|
||||
from aiohttp.client import ClientResponse, ClientSession
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.secret_managers.main import str_to_bool
|
||||
|
||||
AIOHTTP_EXC_MAP: Dict = {
|
||||
# Order matters here, most specific exception first
|
||||
|
|
@ -182,7 +186,7 @@ class LiteLLMAiohttpTransport(AiohttpTransport):
|
|||
self.client = ClientSession()
|
||||
|
||||
return self.client
|
||||
|
||||
|
||||
async def handle_async_request(
|
||||
self,
|
||||
request: httpx.Request,
|
||||
|
|
@ -196,6 +200,9 @@ class LiteLLMAiohttpTransport(AiohttpTransport):
|
|||
# Use helper to ensure we have a valid session for the current event loop
|
||||
client_session = self._get_valid_client_session()
|
||||
|
||||
# Resolve proxy settings from environment variables
|
||||
proxy = await self._get_proxy_settings(request)
|
||||
|
||||
with map_aiohttp_exceptions():
|
||||
try:
|
||||
data = request.content
|
||||
|
|
@ -215,6 +222,7 @@ class LiteLLMAiohttpTransport(AiohttpTransport):
|
|||
sock_read=timeout.get("read"),
|
||||
connect=timeout.get("pool"),
|
||||
),
|
||||
proxy=proxy,
|
||||
server_hostname=sni_hostname,
|
||||
).__aenter__()
|
||||
|
||||
|
|
@ -224,3 +232,29 @@ class LiteLLMAiohttpTransport(AiohttpTransport):
|
|||
content=AiohttpResponseStream(response),
|
||||
request=request,
|
||||
)
|
||||
|
||||
|
||||
async def _get_proxy_settings(self, request: httpx.Request):
|
||||
proxy = None
|
||||
if not (
|
||||
litellm.disable_aiohttp_trust_env
|
||||
or str_to_bool(os.getenv("DISABLE_AIOHTTP_TRUST_ENV", "False"))
|
||||
):
|
||||
try:
|
||||
proxy = self._proxy_from_env(request.url)
|
||||
except Exception as e: # pragma: no cover - best effort
|
||||
verbose_logger.debug(f"Error reading proxy env: {e}")
|
||||
|
||||
return proxy
|
||||
|
||||
|
||||
def _proxy_from_env(self, url: httpx.URL) -> typing.Optional[str]:
|
||||
"""Return proxy URL from env for the given request URL."""
|
||||
proxies = urllib.request.getproxies()
|
||||
if urllib.request.proxy_bypass(url.host):
|
||||
return None
|
||||
|
||||
proxy = proxies.get(url.scheme) or proxies.get("all")
|
||||
if proxy and "://" not in proxy:
|
||||
proxy = f"http://{proxy}"
|
||||
return proxy
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
|
|||
from litellm.llms.base_llm.realtime.transformation import BaseRealtimeConfig
|
||||
from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig
|
||||
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
AsyncHTTPHandler,
|
||||
HTTPHandler,
|
||||
|
|
@ -60,6 +61,12 @@ from litellm.types.rerank import OptionalRerankParams, RerankResponse
|
|||
from litellm.types.responses.main import DeleteResponseResult
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.utils import EmbeddingResponse, FileTypes, TranscriptionResponse
|
||||
from litellm.types.vector_stores import (
|
||||
VectorStoreCreateOptionalRequestParams,
|
||||
VectorStoreCreateResponse,
|
||||
VectorStoreSearchOptionalRequestParams,
|
||||
VectorStoreSearchResponse,
|
||||
)
|
||||
from litellm.utils import (
|
||||
CustomStreamWrapper,
|
||||
ImageResponse,
|
||||
|
|
@ -1108,18 +1115,21 @@ class BaseLLMHTTPHandler:
|
|||
)
|
||||
|
||||
# Prepare the request
|
||||
headers, complete_url, binary_data, json_data = (
|
||||
self._prepare_audio_transcription_request(
|
||||
model=model,
|
||||
audio_file=audio_file,
|
||||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=logging_obj,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
headers=headers,
|
||||
provider_config=provider_config,
|
||||
)
|
||||
(
|
||||
headers,
|
||||
complete_url,
|
||||
binary_data,
|
||||
json_data,
|
||||
) = self._prepare_audio_transcription_request(
|
||||
model=model,
|
||||
audio_file=audio_file,
|
||||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=logging_obj,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
headers=headers,
|
||||
provider_config=provider_config,
|
||||
)
|
||||
|
||||
if client is None or not isinstance(client, HTTPHandler):
|
||||
|
|
@ -1170,18 +1180,21 @@ class BaseLLMHTTPHandler:
|
|||
)
|
||||
|
||||
# Prepare the request
|
||||
headers, complete_url, binary_data, json_data = (
|
||||
self._prepare_audio_transcription_request(
|
||||
model=model,
|
||||
audio_file=audio_file,
|
||||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=logging_obj,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
headers=headers,
|
||||
provider_config=provider_config,
|
||||
)
|
||||
(
|
||||
headers,
|
||||
complete_url,
|
||||
binary_data,
|
||||
json_data,
|
||||
) = self._prepare_audio_transcription_request(
|
||||
model=model,
|
||||
audio_file=audio_file,
|
||||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=logging_obj,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
headers=headers,
|
||||
provider_config=provider_config,
|
||||
)
|
||||
|
||||
if client is None or not isinstance(client, AsyncHTTPHandler):
|
||||
|
|
@ -1437,9 +1450,9 @@ class BaseLLMHTTPHandler:
|
|||
sync_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=response_api_optional_request_params.get("extra_headers", {}) or {},
|
||||
model=model,
|
||||
litellm_params=litellm_params,
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -1557,9 +1570,9 @@ class BaseLLMHTTPHandler:
|
|||
async_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=response_api_optional_request_params.get("extra_headers", {}) or {},
|
||||
model=model,
|
||||
litellm_params=litellm_params,
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -1678,9 +1691,7 @@ class BaseLLMHTTPHandler:
|
|||
async_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=extra_headers or {},
|
||||
model="None",
|
||||
headers=extra_headers or {}, model="None", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -1762,9 +1773,7 @@ class BaseLLMHTTPHandler:
|
|||
sync_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=extra_headers or {},
|
||||
model="None",
|
||||
headers=extra_headers or {}, model="None", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -1847,9 +1856,7 @@ class BaseLLMHTTPHandler:
|
|||
sync_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=extra_headers or {},
|
||||
model="None",
|
||||
headers=extra_headers or {}, model="None", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -1915,9 +1922,7 @@ class BaseLLMHTTPHandler:
|
|||
async_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=extra_headers or {},
|
||||
model="None",
|
||||
headers=extra_headers or {}, model="None", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -2008,9 +2013,7 @@ class BaseLLMHTTPHandler:
|
|||
sync_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=extra_headers or {},
|
||||
model="None",
|
||||
headers=extra_headers or {}, model="None", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -2078,9 +2081,7 @@ class BaseLLMHTTPHandler:
|
|||
async_httpx_client = client
|
||||
|
||||
headers = responses_api_provider_config.validate_environment(
|
||||
api_key=litellm_params.api_key,
|
||||
headers=extra_headers or {},
|
||||
model="None",
|
||||
headers=extra_headers or {}, model="None", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
|
|
@ -2348,7 +2349,7 @@ class BaseLLMHTTPHandler:
|
|||
self,
|
||||
e: Exception,
|
||||
provider_config: Union[
|
||||
BaseConfig, BaseRerankConfig, BaseResponsesAPIConfig, BaseImageEditConfig
|
||||
BaseConfig, BaseRerankConfig, BaseResponsesAPIConfig, BaseImageEditConfig, BaseVectorStoreConfig
|
||||
],
|
||||
):
|
||||
status_code = getattr(e, "status_code", 500)
|
||||
|
|
@ -2447,10 +2448,7 @@ class BaseLLMHTTPHandler:
|
|||
_is_async: bool = False,
|
||||
fake_stream: bool = False,
|
||||
litellm_metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> Union[
|
||||
ImageResponse,
|
||||
Coroutine[Any, Any, ImageResponse],
|
||||
]:
|
||||
) -> Union[ImageResponse, Coroutine[Any, Any, ImageResponse],]:
|
||||
"""
|
||||
|
||||
Handles image edit requests.
|
||||
|
|
@ -2622,3 +2620,271 @@ class BaseLLMHTTPHandler:
|
|||
raw_response=response,
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
###### VECTOR STORE HANDLER ######
|
||||
async def async_vector_store_search_handler(
|
||||
self,
|
||||
vector_store_id: str,
|
||||
query: Union[str, List[str]],
|
||||
vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams,
|
||||
vector_store_provider_config: BaseVectorStoreConfig,
|
||||
custom_llm_provider: str,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
|
||||
_is_async: bool = False,
|
||||
) -> VectorStoreSearchResponse:
|
||||
if client is None or not isinstance(client, AsyncHTTPHandler):
|
||||
async_httpx_client = get_async_httpx_client(
|
||||
llm_provider=litellm.LlmProviders(custom_llm_provider),
|
||||
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
|
||||
)
|
||||
else:
|
||||
async_httpx_client = client
|
||||
|
||||
headers = vector_store_provider_config.validate_environment(
|
||||
headers=extra_headers or {},
|
||||
litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
headers.update(extra_headers)
|
||||
|
||||
api_base = vector_store_provider_config.get_complete_url(
|
||||
api_base=litellm_params.api_base,
|
||||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url, request_body = vector_store_provider_config.transform_search_vector_store_request(
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
vector_store_search_optional_params=vector_store_search_optional_params,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
api_key="",
|
||||
additional_args={
|
||||
"complete_input_dict": request_body,
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
response = await async_httpx_client.post(url=url, headers=headers, json=request_body, timeout=timeout)
|
||||
except Exception as e:
|
||||
raise self._handle_error(e=e, provider_config=vector_store_provider_config)
|
||||
|
||||
return vector_store_provider_config.transform_search_vector_store_response(
|
||||
response=response,
|
||||
)
|
||||
|
||||
def vector_store_search_handler(
|
||||
self,
|
||||
vector_store_id: str,
|
||||
query: Union[str, List[str]],
|
||||
vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams,
|
||||
vector_store_provider_config: BaseVectorStoreConfig,
|
||||
custom_llm_provider: str,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
|
||||
_is_async: bool = False,
|
||||
) -> Union[VectorStoreSearchResponse, Coroutine[Any, Any, VectorStoreSearchResponse]]:
|
||||
if _is_async:
|
||||
return self.async_vector_store_search_handler(
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
vector_store_search_optional_params=vector_store_search_optional_params,
|
||||
vector_store_provider_config=vector_store_provider_config,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=logging_obj,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
extra_headers=extra_headers,
|
||||
extra_body=extra_body,
|
||||
timeout=timeout,
|
||||
client=client,
|
||||
)
|
||||
|
||||
if client is None or not isinstance(client, HTTPHandler):
|
||||
sync_httpx_client = _get_httpx_client(
|
||||
params={"ssl_verify": litellm_params.get("ssl_verify", None)}
|
||||
)
|
||||
else:
|
||||
sync_httpx_client = client
|
||||
|
||||
headers = vector_store_provider_config.validate_environment(
|
||||
headers=extra_headers or {},
|
||||
litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
headers.update(extra_headers)
|
||||
|
||||
api_base = vector_store_provider_config.get_complete_url(
|
||||
api_base=litellm_params.api_base,
|
||||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url, request_body = vector_store_provider_config.transform_search_vector_store_request(
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
vector_store_search_optional_params=vector_store_search_optional_params,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
api_key="",
|
||||
additional_args={
|
||||
"complete_input_dict": request_body,
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
response = sync_httpx_client.post(url=url, headers=headers, json=request_body)
|
||||
except Exception as e:
|
||||
raise self._handle_error(e=e, provider_config=vector_store_provider_config)
|
||||
|
||||
return vector_store_provider_config.transform_search_vector_store_response(
|
||||
response=response,
|
||||
)
|
||||
|
||||
async def async_vector_store_create_handler(
|
||||
self,
|
||||
vector_store_create_optional_params: VectorStoreCreateOptionalRequestParams,
|
||||
vector_store_provider_config: BaseVectorStoreConfig,
|
||||
custom_llm_provider: str,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
|
||||
_is_async: bool = False,
|
||||
) -> VectorStoreCreateResponse:
|
||||
if client is None or not isinstance(client, AsyncHTTPHandler):
|
||||
async_httpx_client = get_async_httpx_client(
|
||||
llm_provider=litellm.LlmProviders(custom_llm_provider),
|
||||
params={"ssl_verify": litellm_params.get("ssl_verify", None)},
|
||||
)
|
||||
else:
|
||||
async_httpx_client = client
|
||||
|
||||
headers = vector_store_provider_config.validate_environment(
|
||||
headers=extra_headers or {},
|
||||
litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
headers.update(extra_headers)
|
||||
|
||||
api_base = vector_store_provider_config.get_complete_url(
|
||||
api_base=litellm_params.api_base,
|
||||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url, request_body = vector_store_provider_config.transform_create_vector_store_request(
|
||||
vector_store_create_optional_params=vector_store_create_optional_params,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
api_key="",
|
||||
additional_args={
|
||||
"complete_input_dict": request_body,
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
response = await async_httpx_client.post(url=url, headers=headers, json=request_body, timeout=timeout)
|
||||
except Exception as e:
|
||||
raise self._handle_error(e=e, provider_config=vector_store_provider_config)
|
||||
|
||||
return vector_store_provider_config.transform_create_vector_store_response(
|
||||
response=response,
|
||||
)
|
||||
|
||||
def vector_store_create_handler(
|
||||
self,
|
||||
vector_store_create_optional_params: VectorStoreCreateOptionalRequestParams,
|
||||
vector_store_provider_config: BaseVectorStoreConfig,
|
||||
custom_llm_provider: str,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
|
||||
_is_async: bool = False,
|
||||
) -> Union[VectorStoreCreateResponse, Coroutine[Any, Any, VectorStoreCreateResponse]]:
|
||||
if _is_async:
|
||||
return self.async_vector_store_create_handler(
|
||||
vector_store_create_optional_params=vector_store_create_optional_params,
|
||||
vector_store_provider_config=vector_store_provider_config,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=logging_obj,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
extra_headers=extra_headers,
|
||||
extra_body=extra_body,
|
||||
timeout=timeout,
|
||||
client=client,
|
||||
)
|
||||
|
||||
if client is None or not isinstance(client, HTTPHandler):
|
||||
sync_httpx_client = _get_httpx_client(
|
||||
params={"ssl_verify": litellm_params.get("ssl_verify", None)}
|
||||
)
|
||||
else:
|
||||
sync_httpx_client = client
|
||||
|
||||
headers = vector_store_provider_config.validate_environment(
|
||||
headers=extra_headers or {},
|
||||
litellm_params=litellm_params
|
||||
)
|
||||
|
||||
if extra_headers:
|
||||
headers.update(extra_headers)
|
||||
|
||||
api_base = vector_store_provider_config.get_complete_url(
|
||||
api_base=litellm_params.api_base,
|
||||
litellm_params=dict(litellm_params),
|
||||
)
|
||||
|
||||
url, request_body = vector_store_provider_config.transform_create_vector_store_request(
|
||||
vector_store_create_optional_params=vector_store_create_optional_params,
|
||||
api_base=api_base,
|
||||
)
|
||||
|
||||
logging_obj.pre_call(
|
||||
input="",
|
||||
api_key="",
|
||||
additional_args={
|
||||
"complete_input_dict": request_body,
|
||||
"api_base": api_base,
|
||||
"headers": headers,
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
response = sync_httpx_client.post(url=url, headers=headers, json=request_body)
|
||||
except Exception as e:
|
||||
raise self._handle_error(e=e, provider_config=vector_store_provider_config)
|
||||
|
||||
return vector_store_provider_config.transform_create_vector_store_response(
|
||||
response=response,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Why separate file? Make it easy to see how transformation works
|
|||
Docs - https://docs.mistral.ai/api/
|
||||
"""
|
||||
|
||||
from typing import Any, Coroutine, List, Literal, Optional, Tuple, Union, overload, cast
|
||||
from typing import Any, Coroutine, List, Literal, Optional, Tuple, Union, cast, overload
|
||||
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
handle_messages_with_content_list_to_str_conversion,
|
||||
|
|
@ -107,15 +107,28 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
def _get_mistral_reasoning_system_prompt() -> str:
|
||||
"""
|
||||
Returns the system prompt for Mistral reasoning models.
|
||||
Based on Mistral's documentation: https://docs.mistral.ai/capabilities/reasoning/
|
||||
Based on Mistral's documentation: https://huggingface.co/mistralai/Magistral-Small-2506
|
||||
|
||||
Mistral recommends the following system prompt for reasoning:
|
||||
"""
|
||||
return """When solving problems, think step-by-step in <think> tags before providing your final answer. Use the following format:
|
||||
return """
|
||||
<s>[SYSTEM_PROMPT]system_prompt
|
||||
A user will ask you to solve a task. You should first draft your thinking process (inner monologue) until you have derived the final answer. Afterwards, write a self-contained summary of your thoughts (i.e. your summary should be succinct but contain all the critical steps you needed to reach the conclusion). You should use Markdown to format your response. Write both your thoughts and summary in the same language as the task posed by the user. NEVER use \boxed{} in your response.
|
||||
|
||||
<think>
|
||||
Your step-by-step reasoning process. Be thorough and work through the problem carefully.
|
||||
</think>
|
||||
Your thinking process must follow the template below:
|
||||
<think>
|
||||
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct answer.
|
||||
</think>
|
||||
|
||||
Then provide a clear, concise answer based on your reasoning."""
|
||||
Here, provide a concise summary that reflects your reasoning and presents a clear final answer to the user. Don't mention that this is a summary.
|
||||
|
||||
Problem:
|
||||
|
||||
[/SYSTEM_PROMPT][INST]user_message[/INST]<think>
|
||||
reasoning_traces
|
||||
</think>
|
||||
assistant_response</s>[INST]user_message[/INST]
|
||||
"""
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ class NvidiaNimConfig(OpenAIGPTConfig):
|
|||
"tools",
|
||||
"tool_choice",
|
||||
"parallel_tool_calls",
|
||||
"response_format",
|
||||
]
|
||||
|
||||
def map_openai_params(
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ Ollama /chat/completion calls handled in llm_http_handler.py
|
|||
"""
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import litellm
|
||||
from litellm.types.utils import EmbeddingResponse
|
||||
|
||||
|
||||
def _prepare_ollama_embedding_payload(
|
||||
model: str,
|
||||
prompts: List[str],
|
||||
optional_params: Dict[str, Any]
|
||||
model: str, prompts: List[str], optional_params: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
|
||||
|
||||
data: Dict[str, Any] = {"model": model, "input": prompts}
|
||||
special_optional_params = ["truncate", "options", "keep_alive"]
|
||||
|
||||
|
|
@ -26,13 +26,14 @@ def _prepare_ollama_embedding_payload(
|
|||
data["options"].update({k: v})
|
||||
return data
|
||||
|
||||
|
||||
def _process_ollama_embedding_response(
|
||||
response_json: dict,
|
||||
prompts: List[str],
|
||||
model: str,
|
||||
model_response: EmbeddingResponse,
|
||||
logging_obj: Any,
|
||||
encoding: Any
|
||||
encoding: Any,
|
||||
) -> EmbeddingResponse:
|
||||
output_data = []
|
||||
embeddings: List[List[float]] = response_json["embeddings"]
|
||||
|
|
@ -46,11 +47,15 @@ def _process_ollama_embedding_response(
|
|||
if encoding is not None:
|
||||
input_tokens = len(encoding.encode("".join(prompts)))
|
||||
if logging_obj:
|
||||
logging_obj.debug("Ollama response missing prompt_eval_count; estimated with encoding.")
|
||||
logging_obj.debug(
|
||||
"Ollama response missing prompt_eval_count; estimated with encoding."
|
||||
)
|
||||
else:
|
||||
input_tokens = 0
|
||||
if logging_obj:
|
||||
logging_obj.warning("Missing prompt_eval_count and no encoding provided; defaulted to 0.")
|
||||
logging_obj.warning(
|
||||
"Missing prompt_eval_count and no encoding provided; defaulted to 0."
|
||||
)
|
||||
|
||||
model_response.object = "list"
|
||||
model_response.data = output_data
|
||||
|
|
@ -64,6 +69,7 @@ def _process_ollama_embedding_response(
|
|||
)
|
||||
return model_response
|
||||
|
||||
|
||||
async def ollama_aembeddings(
|
||||
api_base: str,
|
||||
model: str,
|
||||
|
|
@ -79,7 +85,7 @@ async def ollama_aembeddings(
|
|||
data = _prepare_ollama_embedding_payload(model, prompts, optional_params)
|
||||
|
||||
response = await litellm.module_level_aclient.post(url=api_base, json=data)
|
||||
response_json = await response.json()
|
||||
response_json = response.json()
|
||||
|
||||
return _process_ollama_embedding_response(
|
||||
response_json=response_json,
|
||||
|
|
@ -87,9 +93,10 @@ async def ollama_aembeddings(
|
|||
model=model,
|
||||
model_response=model_response,
|
||||
logging_obj=logging_obj,
|
||||
encoding=encoding
|
||||
encoding=encoding,
|
||||
)
|
||||
|
||||
|
||||
def ollama_embeddings(
|
||||
api_base: str,
|
||||
model: str,
|
||||
|
|
@ -113,5 +120,5 @@ def ollama_embeddings(
|
|||
model=model,
|
||||
model_response=model_response,
|
||||
logging_obj=logging_obj,
|
||||
encoding=encoding
|
||||
encoding=encoding,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -92,13 +92,11 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
return ResponsesAPIResponse(**raw_response_json)
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
api_key: Optional[str] = None,
|
||||
self, headers: dict, model: str, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
litellm_params = litellm_params or GenericLiteLLMParams()
|
||||
api_key = (
|
||||
api_key
|
||||
litellm_params.api_key
|
||||
or litellm.api_key
|
||||
or litellm.openai_key
|
||||
or get_secret_str("OPENAI_API_KEY")
|
||||
|
|
|
|||
140
litellm/llms/openai/vector_stores/transformation.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
from typing import Dict, List, Optional, Tuple, Union, cast
|
||||
|
||||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.vector_stores import (
|
||||
VectorStoreCreateOptionalRequestParams,
|
||||
VectorStoreCreateRequest,
|
||||
VectorStoreCreateResponse,
|
||||
VectorStoreSearchOptionalRequestParams,
|
||||
VectorStoreSearchRequest,
|
||||
VectorStoreSearchResponse,
|
||||
)
|
||||
|
||||
|
||||
class OpenAIVectorStoreConfig(BaseVectorStoreConfig):
|
||||
ASSISTANTS_HEADER_KEY = "OpenAI-Beta"
|
||||
ASSISTANTS_HEADER_VALUE = "assistants=v2"
|
||||
|
||||
def validate_environment(
|
||||
self, headers: dict, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
litellm_params = litellm_params or GenericLiteLLMParams()
|
||||
api_key = (
|
||||
litellm_params.api_key
|
||||
or litellm.api_key
|
||||
or litellm.openai_key
|
||||
or get_secret_str("OPENAI_API_KEY")
|
||||
)
|
||||
headers.update(
|
||||
{
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
}
|
||||
)
|
||||
|
||||
#########################################################
|
||||
# Ensure OpenAI Assistants header is includes
|
||||
#########################################################
|
||||
if self.ASSISTANTS_HEADER_KEY not in headers:
|
||||
headers.update(
|
||||
{
|
||||
self.ASSISTANTS_HEADER_KEY: self.ASSISTANTS_HEADER_VALUE,
|
||||
}
|
||||
)
|
||||
|
||||
return headers
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
litellm_params: dict,
|
||||
) -> str:
|
||||
"""
|
||||
Get the Base endpoint for OpenAI Vector Stores API
|
||||
"""
|
||||
api_base = (
|
||||
api_base
|
||||
or litellm.api_base
|
||||
or get_secret_str("OPENAI_BASE_URL")
|
||||
or get_secret_str("OPENAI_API_BASE")
|
||||
or "https://api.openai.com/v1"
|
||||
)
|
||||
|
||||
# Remove trailing slashes
|
||||
api_base = api_base.rstrip("/")
|
||||
|
||||
return f"{api_base}/vector_stores"
|
||||
|
||||
|
||||
def transform_search_vector_store_request(
|
||||
self,
|
||||
vector_store_id: str,
|
||||
query: Union[str, List[str]],
|
||||
vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = f"{api_base}/{vector_store_id}/search"
|
||||
typed_request_body = VectorStoreSearchRequest(
|
||||
query=query,
|
||||
filters=vector_store_search_optional_params.get("filters", None),
|
||||
max_num_results=vector_store_search_optional_params.get("max_num_results", None),
|
||||
ranking_options=vector_store_search_optional_params.get("ranking_options", None),
|
||||
rewrite_query=vector_store_search_optional_params.get("rewrite_query", None),
|
||||
)
|
||||
|
||||
dict_request_body = cast(dict, typed_request_body)
|
||||
return url, dict_request_body
|
||||
|
||||
|
||||
|
||||
def transform_search_vector_store_response(self, response: httpx.Response) -> VectorStoreSearchResponse:
|
||||
try:
|
||||
response_json = response.json()
|
||||
return VectorStoreSearchResponse(
|
||||
**response_json
|
||||
)
|
||||
except Exception as e:
|
||||
raise self.get_error_class(
|
||||
error_message=str(e),
|
||||
status_code=response.status_code,
|
||||
headers=response.headers
|
||||
)
|
||||
|
||||
def transform_create_vector_store_request(
|
||||
self,
|
||||
vector_store_create_optional_params: VectorStoreCreateOptionalRequestParams,
|
||||
api_base: str,
|
||||
) -> Tuple[str, Dict]:
|
||||
url = api_base # Base URL for creating vector stores
|
||||
typed_request_body = VectorStoreCreateRequest(
|
||||
name=vector_store_create_optional_params.get("name", None),
|
||||
file_ids=vector_store_create_optional_params.get("file_ids", None),
|
||||
expires_after=vector_store_create_optional_params.get("expires_after", None),
|
||||
chunking_strategy=vector_store_create_optional_params.get("chunking_strategy", None),
|
||||
metadata=vector_store_create_optional_params.get("metadata", None),
|
||||
)
|
||||
|
||||
dict_request_body = cast(dict, typed_request_body)
|
||||
return url, dict_request_body
|
||||
|
||||
def transform_create_vector_store_response(self, response: httpx.Response) -> VectorStoreCreateResponse:
|
||||
try:
|
||||
response_json = response.json()
|
||||
return VectorStoreCreateResponse(
|
||||
**response_json
|
||||
)
|
||||
except Exception as e:
|
||||
raise self.get_error_class(
|
||||
error_message=str(e),
|
||||
status_code=response.status_code,
|
||||
headers=response.headers
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -2,13 +2,17 @@
|
|||
Translate from OpenAI's `/v1/chat/completions` to Perplexity's `/v1/chat/completions`
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
import httpx
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
from litellm.types.utils import Usage, PromptTokensDetailsWrapper
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
|
||||
class PerplexityChatConfig(OpenAIGPTConfig):
|
||||
|
|
@ -55,4 +59,105 @@ class PerplexityChatConfig(OpenAIGPTConfig):
|
|||
base_openai_params.append("reasoning_effort")
|
||||
except Exception as e:
|
||||
verbose_logger.debug(f"Error checking if model supports reasoning: {e}")
|
||||
|
||||
try:
|
||||
if litellm.supports_web_search(
|
||||
model=model, custom_llm_provider=self.custom_llm_provider
|
||||
):
|
||||
base_openai_params.append("web_search_options")
|
||||
except Exception as e:
|
||||
verbose_logger.debug(f"Error checking if model supports web search: {e}")
|
||||
|
||||
return base_openai_params
|
||||
|
||||
def transform_response(
|
||||
self,
|
||||
model: str,
|
||||
raw_response: httpx.Response,
|
||||
model_response: ModelResponse,
|
||||
logging_obj: LiteLLMLoggingObj,
|
||||
request_data: dict,
|
||||
messages: List[AllMessageValues],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
encoding: Any,
|
||||
api_key: Optional[str] = None,
|
||||
json_mode: Optional[bool] = None,
|
||||
) -> ModelResponse:
|
||||
# Call the parent transform_response first to handle the standard transformation
|
||||
model_response = super().transform_response(
|
||||
model=model,
|
||||
raw_response=raw_response,
|
||||
model_response=model_response,
|
||||
logging_obj=logging_obj,
|
||||
request_data=request_data,
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
encoding=encoding,
|
||||
api_key=api_key,
|
||||
json_mode=json_mode,
|
||||
)
|
||||
|
||||
# Extract and enhance usage with Perplexity-specific fields
|
||||
try:
|
||||
raw_response_json = raw_response.json()
|
||||
self._enhance_usage_with_perplexity_fields(model_response, raw_response_json)
|
||||
except Exception as e:
|
||||
verbose_logger.debug(f"Error extracting Perplexity-specific usage fields: {e}")
|
||||
|
||||
return model_response
|
||||
|
||||
def _enhance_usage_with_perplexity_fields(
|
||||
self, model_response: ModelResponse, raw_response_json: dict
|
||||
) -> None:
|
||||
"""
|
||||
Extract citation tokens and search queries from Perplexity API response
|
||||
and add them to the usage object using standard LiteLLM fields.
|
||||
"""
|
||||
if not hasattr(model_response, "usage") or model_response.usage is None:
|
||||
# Create a usage object if it doesn't exist (when usage was None)
|
||||
model_response.usage = Usage( # type: ignore[attr-defined]
|
||||
prompt_tokens=0,
|
||||
completion_tokens=0,
|
||||
total_tokens=0
|
||||
)
|
||||
|
||||
usage = model_response.usage # type: ignore[attr-defined]
|
||||
|
||||
# Extract citation tokens count
|
||||
citations = raw_response_json.get("citations", [])
|
||||
citation_tokens = 0
|
||||
if citations:
|
||||
# Count total characters in citations as a proxy for citation tokens
|
||||
# This is an estimation - in practice, you might want to use proper tokenization
|
||||
total_citation_chars = sum(len(str(citation)) for citation in citations if citation)
|
||||
# Rough estimation: ~4 characters per token (OpenAI's general rule)
|
||||
if total_citation_chars > 0:
|
||||
citation_tokens = max(1, total_citation_chars // 4)
|
||||
|
||||
# Extract search queries count from usage or response metadata
|
||||
# Perplexity might include this in the usage object or as separate metadata
|
||||
perplexity_usage = raw_response_json.get("usage", {})
|
||||
|
||||
# Try to extract search queries from usage field first, then root level
|
||||
num_search_queries = perplexity_usage.get("num_search_queries")
|
||||
if num_search_queries is None:
|
||||
num_search_queries = raw_response_json.get("num_search_queries")
|
||||
if num_search_queries is None:
|
||||
num_search_queries = perplexity_usage.get("search_queries")
|
||||
if num_search_queries is None:
|
||||
num_search_queries = raw_response_json.get("search_queries")
|
||||
|
||||
# Create or update prompt_tokens_details to include web search requests and citation tokens
|
||||
if citation_tokens > 0 or (num_search_queries is not None and num_search_queries > 0):
|
||||
if usage.prompt_tokens_details is None:
|
||||
usage.prompt_tokens_details = PromptTokensDetailsWrapper()
|
||||
|
||||
# Store citation tokens count for cost calculation
|
||||
if citation_tokens > 0:
|
||||
setattr(usage, "citation_tokens", citation_tokens)
|
||||
|
||||
# Store search queries count in the standard web_search_requests field
|
||||
if num_search_queries is not None and num_search_queries > 0:
|
||||
usage.prompt_tokens_details.web_search_requests = num_search_queries
|
||||
|
|
|
|||
79
litellm/llms/perplexity/cost_calculator.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""
|
||||
Helper util for handling perplexity-specific cost calculation
|
||||
- e.g.: citation tokens, search queries
|
||||
"""
|
||||
|
||||
from typing import Tuple, Union
|
||||
|
||||
from litellm.types.utils import Usage
|
||||
from litellm.utils import get_model_info
|
||||
|
||||
|
||||
def cost_per_token(model: str, usage: Usage) -> Tuple[float, float]:
|
||||
"""
|
||||
Calculates the cost per token for a given model, prompt tokens, and completion tokens.
|
||||
|
||||
Input:
|
||||
- model: str, the model name without provider prefix
|
||||
- usage: LiteLLM Usage block, containing perplexity-specific usage information
|
||||
|
||||
Returns:
|
||||
Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd
|
||||
"""
|
||||
## GET MODEL INFO
|
||||
model_info = get_model_info(model=model, custom_llm_provider="perplexity")
|
||||
|
||||
def _safe_float_cast(value: Union[str, int, float, None, object], default: float = 0.0) -> float:
|
||||
"""Safely cast a value to float with proper type handling for mypy."""
|
||||
if value is None:
|
||||
return default
|
||||
try:
|
||||
return float(value) # type: ignore
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
## CALCULATE INPUT COST
|
||||
input_cost_per_token = _safe_float_cast(model_info.get("input_cost_per_token"))
|
||||
prompt_cost: float = (usage.prompt_tokens or 0) * input_cost_per_token
|
||||
|
||||
## ADD CITATION TOKENS COST (if present)
|
||||
citation_tokens = getattr(usage, "citation_tokens", 0) or 0
|
||||
citation_cost_value = model_info.get("citation_cost_per_token")
|
||||
if citation_tokens > 0 and citation_cost_value is not None:
|
||||
citation_cost_per_token = _safe_float_cast(citation_cost_value)
|
||||
prompt_cost += citation_tokens * citation_cost_per_token
|
||||
|
||||
## CALCULATE OUTPUT COST
|
||||
output_cost_per_token = _safe_float_cast(model_info.get("output_cost_per_token"))
|
||||
completion_cost: float = (usage.completion_tokens or 0) * output_cost_per_token
|
||||
|
||||
## ADD REASONING TOKENS COST (if present)
|
||||
reasoning_tokens = getattr(usage, "reasoning_tokens", 0) or 0
|
||||
# Also check completion_tokens_details if reasoning_tokens is not directly available
|
||||
if reasoning_tokens == 0 and hasattr(usage, "completion_tokens_details") and usage.completion_tokens_details:
|
||||
reasoning_tokens = getattr(usage.completion_tokens_details, "reasoning_tokens", 0) or 0
|
||||
|
||||
reasoning_cost_value = model_info.get("output_cost_per_reasoning_token")
|
||||
if reasoning_tokens > 0 and reasoning_cost_value is not None:
|
||||
reasoning_cost_per_token = _safe_float_cast(reasoning_cost_value)
|
||||
completion_cost += reasoning_tokens * reasoning_cost_per_token
|
||||
|
||||
## ADD SEARCH QUERIES COST (if present)
|
||||
num_search_queries = 0
|
||||
if hasattr(usage, "prompt_tokens_details") and usage.prompt_tokens_details:
|
||||
num_search_queries = getattr(usage.prompt_tokens_details, "web_search_requests", 0) or 0
|
||||
|
||||
# Check both possible keys for search cost (legacy and current)
|
||||
search_cost_value = model_info.get("search_queries_cost_per_query") or model_info.get("search_context_cost_per_query")
|
||||
if num_search_queries > 0 and search_cost_value is not None:
|
||||
# Handle both dict and float formats
|
||||
if isinstance(search_cost_value, dict):
|
||||
# Use the "low" size as default - tests expect 0.005 / 1000
|
||||
search_cost_per_query = _safe_float_cast(search_cost_value.get("search_context_size_low", 0)) / 1000
|
||||
else:
|
||||
search_cost_per_query = _safe_float_cast(search_cost_value)
|
||||
search_cost = num_search_queries * search_cost_per_query
|
||||
# Add search cost to completion cost (similar to how other providers handle it)
|
||||
completion_cost += search_cost
|
||||
|
||||
return prompt_cost, completion_cost
|
||||
|
|
@ -204,6 +204,7 @@ def _build_vertex_schema(parameters: dict, add_property_ordering: bool = False):
|
|||
add_object_type(parameters)
|
||||
# Postprocessing
|
||||
# Filter out fields that don't exist in Schema
|
||||
|
||||
parameters = filter_schema_fields(parameters, valid_schema_fields)
|
||||
|
||||
if add_property_ordering:
|
||||
|
|
@ -318,6 +319,11 @@ def filter_schema_fields(
|
|||
k: filter_schema_fields(v, valid_fields, processed)
|
||||
for k, v in value.items()
|
||||
}
|
||||
elif key == "format":
|
||||
if value in {"enum", "date-time"}:
|
||||
result[key] = value
|
||||
else:
|
||||
continue
|
||||
elif key == "items" and isinstance(value, dict):
|
||||
result[key] = filter_schema_fields(value, valid_fields, processed)
|
||||
elif key == "anyOf" and isinstance(value, list):
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ class ContextCachingEndpoints(VertexBase):
|
|||
def check_and_create_cache(
|
||||
self,
|
||||
messages: List[AllMessageValues], # receives openai format messages
|
||||
optional_params: dict, # cache the tools if present, in case cache content exists in messages
|
||||
api_key: str,
|
||||
api_base: Optional[str],
|
||||
model: str,
|
||||
|
|
@ -213,7 +214,7 @@ class ContextCachingEndpoints(VertexBase):
|
|||
logging_obj: Logging,
|
||||
extra_headers: Optional[dict] = None,
|
||||
cached_content: Optional[str] = None,
|
||||
) -> Tuple[List[AllMessageValues], Optional[str]]:
|
||||
) -> Tuple[List[AllMessageValues], dict, Optional[str]]:
|
||||
"""
|
||||
Receives
|
||||
- messages: List of dict - messages in the openai format
|
||||
|
|
@ -225,7 +226,16 @@ class ContextCachingEndpoints(VertexBase):
|
|||
Follows - https://ai.google.dev/api/caching#request-body
|
||||
"""
|
||||
if cached_content is not None:
|
||||
return messages, cached_content
|
||||
return messages, optional_params, cached_content
|
||||
|
||||
cached_messages, non_cached_messages = separate_cached_messages(
|
||||
messages=messages
|
||||
)
|
||||
|
||||
if len(cached_messages) == 0:
|
||||
return messages, optional_params, None
|
||||
|
||||
tools = optional_params.pop("tools", None)
|
||||
|
||||
## AUTHORIZATION ##
|
||||
token, url = self._get_token_and_url_context_caching(
|
||||
|
|
@ -252,15 +262,10 @@ class ContextCachingEndpoints(VertexBase):
|
|||
else:
|
||||
client = client
|
||||
|
||||
cached_messages, non_cached_messages = separate_cached_messages(
|
||||
messages=messages
|
||||
)
|
||||
|
||||
if len(cached_messages) == 0:
|
||||
return messages, None
|
||||
|
||||
## CHECK IF CACHED ALREADY
|
||||
generated_cache_key = local_cache_obj.get_cache_key(messages=cached_messages)
|
||||
generated_cache_key = local_cache_obj.get_cache_key(
|
||||
messages=cached_messages, tools=tools
|
||||
)
|
||||
google_cache_name = self.check_cache(
|
||||
cache_key=generated_cache_key,
|
||||
client=client,
|
||||
|
|
@ -270,7 +275,7 @@ class ContextCachingEndpoints(VertexBase):
|
|||
logging_obj=logging_obj,
|
||||
)
|
||||
if google_cache_name:
|
||||
return non_cached_messages, google_cache_name
|
||||
return non_cached_messages, optional_params, google_cache_name
|
||||
|
||||
## TRANSFORM REQUEST
|
||||
cached_content_request_body = (
|
||||
|
|
@ -279,6 +284,8 @@ class ContextCachingEndpoints(VertexBase):
|
|||
)
|
||||
)
|
||||
|
||||
cached_content_request_body["tools"] = tools
|
||||
|
||||
## LOGGING
|
||||
logging_obj.pre_call(
|
||||
input=messages,
|
||||
|
|
@ -305,11 +312,16 @@ class ContextCachingEndpoints(VertexBase):
|
|||
cached_content_response_obj = VertexAICachedContentResponseObject(
|
||||
name=raw_response_cached.get("name"), model=raw_response_cached.get("model")
|
||||
)
|
||||
return (non_cached_messages, cached_content_response_obj["name"])
|
||||
return (
|
||||
non_cached_messages,
|
||||
optional_params,
|
||||
cached_content_response_obj["name"],
|
||||
)
|
||||
|
||||
async def async_check_and_create_cache(
|
||||
self,
|
||||
messages: List[AllMessageValues], # receives openai format messages
|
||||
optional_params: dict, # cache the tools if present, in case cache content exists in messages
|
||||
api_key: str,
|
||||
api_base: Optional[str],
|
||||
model: str,
|
||||
|
|
@ -318,7 +330,7 @@ class ContextCachingEndpoints(VertexBase):
|
|||
logging_obj: Logging,
|
||||
extra_headers: Optional[dict] = None,
|
||||
cached_content: Optional[str] = None,
|
||||
) -> Tuple[List[AllMessageValues], Optional[str]]:
|
||||
) -> Tuple[List[AllMessageValues], dict, Optional[str]]:
|
||||
"""
|
||||
Receives
|
||||
- messages: List of dict - messages in the openai format
|
||||
|
|
@ -330,14 +342,16 @@ class ContextCachingEndpoints(VertexBase):
|
|||
Follows - https://ai.google.dev/api/caching#request-body
|
||||
"""
|
||||
if cached_content is not None:
|
||||
return messages, cached_content
|
||||
return messages, optional_params, cached_content
|
||||
|
||||
cached_messages, non_cached_messages = separate_cached_messages(
|
||||
messages=messages
|
||||
)
|
||||
|
||||
if len(cached_messages) == 0:
|
||||
return messages, None
|
||||
return messages, optional_params, None
|
||||
|
||||
tools = optional_params.pop("tools", None)
|
||||
|
||||
## AUTHORIZATION ##
|
||||
token, url = self._get_token_and_url_context_caching(
|
||||
|
|
@ -362,7 +376,9 @@ class ContextCachingEndpoints(VertexBase):
|
|||
client = client
|
||||
|
||||
## CHECK IF CACHED ALREADY
|
||||
generated_cache_key = local_cache_obj.get_cache_key(messages=cached_messages)
|
||||
generated_cache_key = local_cache_obj.get_cache_key(
|
||||
messages=cached_messages, tools=tools
|
||||
)
|
||||
google_cache_name = await self.async_check_cache(
|
||||
cache_key=generated_cache_key,
|
||||
client=client,
|
||||
|
|
@ -371,8 +387,9 @@ class ContextCachingEndpoints(VertexBase):
|
|||
api_base=api_base,
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
if google_cache_name:
|
||||
return non_cached_messages, google_cache_name
|
||||
return non_cached_messages, optional_params, google_cache_name
|
||||
|
||||
## TRANSFORM REQUEST
|
||||
cached_content_request_body = (
|
||||
|
|
@ -381,6 +398,8 @@ class ContextCachingEndpoints(VertexBase):
|
|||
)
|
||||
)
|
||||
|
||||
cached_content_request_body["tools"] = tools
|
||||
|
||||
## LOGGING
|
||||
logging_obj.pre_call(
|
||||
input=messages,
|
||||
|
|
@ -407,7 +426,11 @@ class ContextCachingEndpoints(VertexBase):
|
|||
cached_content_response_obj = VertexAICachedContentResponseObject(
|
||||
name=raw_response_cached.get("name"), model=raw_response_cached.get("model")
|
||||
)
|
||||
return (non_cached_messages, cached_content_response_obj["name"])
|
||||
return (
|
||||
non_cached_messages,
|
||||
optional_params,
|
||||
cached_content_response_obj["name"],
|
||||
)
|
||||
|
||||
def get_cache(self):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Transformation logic from OpenAI format to Gemini format.
|
||||
Transformation logic from OpenAI format to Gemini format.
|
||||
|
||||
Why separate file? Make it easy to see how transformation works
|
||||
"""
|
||||
|
|
@ -402,16 +402,19 @@ def sync_transform_request_body(
|
|||
context_caching_endpoints = ContextCachingEndpoints()
|
||||
|
||||
if gemini_api_key is not None:
|
||||
messages, cached_content = context_caching_endpoints.check_and_create_cache(
|
||||
messages=messages,
|
||||
api_key=gemini_api_key,
|
||||
api_base=api_base,
|
||||
model=model,
|
||||
client=client,
|
||||
timeout=timeout,
|
||||
extra_headers=extra_headers,
|
||||
cached_content=optional_params.pop("cached_content", None),
|
||||
logging_obj=logging_obj,
|
||||
messages, optional_params, cached_content = (
|
||||
context_caching_endpoints.check_and_create_cache(
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
api_key=gemini_api_key,
|
||||
api_base=api_base,
|
||||
model=model,
|
||||
client=client,
|
||||
timeout=timeout,
|
||||
extra_headers=extra_headers,
|
||||
cached_content=optional_params.pop("cached_content", None),
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
)
|
||||
else: # [TODO] implement context caching for gemini as well
|
||||
cached_content = optional_params.pop("cached_content", None)
|
||||
|
|
@ -446,9 +449,11 @@ async def async_transform_request_body(
|
|||
if gemini_api_key is not None:
|
||||
(
|
||||
messages,
|
||||
optional_params,
|
||||
cached_content,
|
||||
) = await context_caching_endpoints.async_check_and_create_cache(
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
api_key=gemini_api_key,
|
||||
api_base=api_base,
|
||||
model=model,
|
||||
|
|
|
|||
|
|
@ -63,3 +63,26 @@ class VolcEngineConfig(OpenAILikeChatConfig):
|
|||
"extra_headers",
|
||||
"thinking",
|
||||
] # works across all models
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
replace_max_completion_tokens_with_max_tokens: bool = True,
|
||||
) -> dict:
|
||||
optional_params = super().map_openai_params(
|
||||
non_default_params,
|
||||
optional_params,
|
||||
model,
|
||||
drop_params,
|
||||
replace_max_completion_tokens_with_max_tokens,
|
||||
)
|
||||
|
||||
if "thinking" in optional_params:
|
||||
optional_params.setdefault("extra_body", {})["thinking"] = (
|
||||
optional_params.pop("thinking")
|
||||
)
|
||||
|
||||
return optional_params
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class WatsonXChatHandler(OpenAILikeChatHandler):
|
|||
litellm_params=litellm_params,
|
||||
)
|
||||
|
||||
## UPDATE PAYLOAD (optional params)
|
||||
## UPDATE PAYLOAD (optional params and special cases for models deployed in spaces)
|
||||
watsonx_auth_payload = watsonx_chat_transformation._prepare_payload(
|
||||
model=model,
|
||||
api_params=api_params,
|
||||
|
|
@ -70,7 +70,7 @@ class WatsonXChatHandler(OpenAILikeChatHandler):
|
|||
)
|
||||
|
||||
return super().completion(
|
||||
model=model,
|
||||
model=watsonx_auth_payload.get("model_id", None),
|
||||
messages=messages,
|
||||
api_base=api_base,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Docs: https://cloud.ibm.com/apidocs/watsonx-ai#text-chat
|
|||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.llms.watsonx import WatsonXAIEndpoint
|
||||
from litellm.types.llms.watsonx import WatsonXAIEndpoint, WatsonXAPIParams
|
||||
|
||||
from ....utils import _remove_additional_properties, _remove_strict_from_schema
|
||||
from ...openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
|
|
@ -108,3 +108,15 @@ class IBMWatsonXChatConfig(IBMWatsonXMixin, OpenAIGPTConfig):
|
|||
url=url, api_version=optional_params.pop("api_version", None)
|
||||
)
|
||||
return url
|
||||
|
||||
def _prepare_payload(self, model: str, api_params: WatsonXAPIParams) -> dict:
|
||||
"""
|
||||
Prepare payload for deployment models.
|
||||
Deployment models cannot have 'model_id' or 'model' in the request body.
|
||||
"""
|
||||
payload: dict = {}
|
||||
payload["model_id"] = None if model.startswith("deployment/") else model
|
||||
payload["project_id"] = (
|
||||
None if model.startswith("deployment/") else api_params["project_id"]
|
||||
)
|
||||
return payload
|
||||
|
|
|
|||
|
|
@ -1297,6 +1297,12 @@ def completion( # type: ignore # noqa: PLR0915
|
|||
except Exception as e:
|
||||
verbose_logger.debug("Error getting model info: {}".format(e))
|
||||
model_info = {}
|
||||
if model.startswith(
|
||||
"responses/"
|
||||
): # handle azure models - `azure/responses/<deployment-name>`
|
||||
model = model.split("/")[1]
|
||||
mode = "responses"
|
||||
model_info["mode"] = mode
|
||||
|
||||
if model_info.get("mode") == "responses":
|
||||
from litellm.completion_extras import responses_api_bridge
|
||||
|
|
|
|||
|
|
@ -2156,6 +2156,66 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true
|
||||
},
|
||||
"azure/o3-pro": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 100000,
|
||||
"input_cost_per_token": 2e-05,
|
||||
"output_cost_per_token": 8e-05,
|
||||
"input_cost_per_token_batches": 1e-05,
|
||||
"output_cost_per_token_batches": 4e-05,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"azure/o3-pro-2025-06-10": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 100000,
|
||||
"input_cost_per_token": 2e-05,
|
||||
"output_cost_per_token": 8e-05,
|
||||
"input_cost_per_token_batches": 1e-05,
|
||||
"output_cost_per_token_batches": 4e-05,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"azure/o3": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
|
|
@ -3986,7 +4046,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-small": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -3998,7 +4059,8 @@
|
|||
"supports_function_calling": true,
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-small-latest": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4010,7 +4072,8 @@
|
|||
"supports_function_calling": true,
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4021,7 +4084,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium-latest": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4033,7 +4097,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium-2505": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4045,7 +4110,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium-2312": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4056,7 +4122,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-latest": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4068,7 +4135,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-2411": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4080,7 +4148,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-2402": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4092,7 +4161,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-2407": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4104,7 +4174,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/pixtral-large-latest": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4117,7 +4188,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_vision": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/pixtral-large-2411": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4130,7 +4202,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_vision": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/pixtral-12b-2409": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4143,7 +4216,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_vision": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mistral-7b": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4154,7 +4228,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mixtral-8x7b": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4166,7 +4241,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mixtral-8x22b": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4178,7 +4254,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/codestral-latest": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4189,7 +4266,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/codestral-2405": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4200,7 +4278,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mistral-nemo": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4212,7 +4291,8 @@
|
|||
"mode": "chat",
|
||||
"source": "https://mistral.ai/technology/",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mistral-nemo-2407": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4224,7 +4304,8 @@
|
|||
"mode": "chat",
|
||||
"source": "https://mistral.ai/technology/",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-codestral-mamba": {
|
||||
"max_tokens": 256000,
|
||||
|
|
@ -4261,7 +4342,8 @@
|
|||
"source": "https://mistral.ai/news/devstral",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-medium-latest": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4275,7 +4357,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-medium-2506": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4289,7 +4372,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-small-latest": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4303,7 +4387,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-small-2506": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4317,7 +4402,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-embed": {
|
||||
"max_tokens": 8192,
|
||||
|
|
@ -9995,7 +10081,15 @@
|
|||
"mode": "chat",
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"mistralai/mistral-small-3.1-24b-instruct": {
|
||||
"openrouter/mistralai/mistral-small-3.1-24b-instruct": {
|
||||
"max_tokens": 32000,
|
||||
"input_cost_per_token": 1e-07,
|
||||
"output_cost_per_token": 3e-07,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat",
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"openrouter/mistralai/mistral-small-3.2-24b-instruct": {
|
||||
"max_tokens": 32000,
|
||||
"input_cost_per_token": 1e-07,
|
||||
"output_cost_per_token": 3e-07,
|
||||
|
|
@ -13647,13 +13741,14 @@
|
|||
"input_cost_per_token": 2e-06,
|
||||
"output_cost_per_token": 8e-06,
|
||||
"output_cost_per_reasoning_token": 3e-06,
|
||||
"litellm_provider": "perplexity",
|
||||
"mode": "chat",
|
||||
"citation_cost_per_token": 2e-06,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.005,
|
||||
"search_context_size_medium": 0.005,
|
||||
"search_context_size_high": 0.005
|
||||
},
|
||||
"litellm_provider": "perplexity",
|
||||
"mode": "chat",
|
||||
"supports_reasoning": true,
|
||||
"supports_web_search": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/litellm-asset-prefix/_next/",i={272:0,919:0,986:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|919|986)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
|
||||
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={id:e,loaded:!1,exports:{}},r=!0;try{a[e].call(n.exports,n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.loaded=!0,n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/_next/",i={272:0,919:0,986:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|919|986)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();
|
||||
|
|
@ -1 +1 @@
|
|||
@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/55c55f0601d81cf3-s.woff2) format("woff2");unicode-range:u+0460-052f,u+1c80-1c8a,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/26a46d62cd723877-s.woff2) format("woff2");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/97e0cb1ae144a2a9-s.woff2) format("woff2");unicode-range:u+1f??}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/581909926a08bbc8-s.woff2) format("woff2");unicode-range:u+0370-0377,u+037a-037f,u+0384-038a,u+038c,u+038e-03a1,u+03a3-03ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/df0a9ae256c0569c-s.woff2) format("woff2");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/8e9860b6e62d6359-s.woff2) format("woff2");unicode-range:u+0100-02ba,u+02bd-02c5,u+02c7-02cc,u+02ce-02d7,u+02dd-02ff,u+0304,u+0308,u+0329,u+1d00-1dbf,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20c0,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/litellm-asset-prefix/_next/static/media/e4af272ccee01ff0-s.p.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:__Inter_Fallback_b0dd8a;src:local("Arial");ascent-override:90.49%;descent-override:22.56%;line-gap-override:0.00%;size-adjust:107.06%}.__className_b0dd8a{font-family:__Inter_b0dd8a,__Inter_Fallback_b0dd8a;font-style:normal}
|
||||
@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/55c55f0601d81cf3-s.woff2) format("woff2");unicode-range:u+0460-052f,u+1c80-1c8a,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/26a46d62cd723877-s.woff2) format("woff2");unicode-range:u+0301,u+0400-045f,u+0490-0491,u+04b0-04b1,u+2116}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/97e0cb1ae144a2a9-s.woff2) format("woff2");unicode-range:u+1f??}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/581909926a08bbc8-s.woff2) format("woff2");unicode-range:u+0370-0377,u+037a-037f,u+0384-038a,u+038c,u+038e-03a1,u+03a3-03ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/df0a9ae256c0569c-s.woff2) format("woff2");unicode-range:u+0102-0103,u+0110-0111,u+0128-0129,u+0168-0169,u+01a0-01a1,u+01af-01b0,u+0300-0301,u+0303-0304,u+0308-0309,u+0323,u+0329,u+1ea0-1ef9,u+20ab}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/8e9860b6e62d6359-s.woff2) format("woff2");unicode-range:u+0100-02ba,u+02bd-02c5,u+02c7-02cc,u+02ce-02d7,u+02dd-02ff,u+0304,u+0308,u+0329,u+1d00-1dbf,u+1e00-1e9f,u+1ef2-1eff,u+2020,u+20a0-20ab,u+20ad-20c0,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:__Inter_b0dd8a;font-style:normal;font-weight:100 900;font-display:swap;src:url(/_next/static/media/e4af272ccee01ff0-s.p.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+0304,u+0308,u+0329,u+2000-206f,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:__Inter_Fallback_b0dd8a;src:local("Arial");ascent-override:90.49%;descent-override:22.56%;line-gap-override:0.00%;size-adjust:107.06%}.__className_b0dd8a{font-family:__Inter_b0dd8a,__Inter_Fallback_b0dd8a;font-style:normal}
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 450" style="enable-background:new 0 0 600 450;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#343B45;}
|
||||
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F4981A;}
|
||||
</style>
|
||||
<g id="_x31__stroke">
|
||||
<g id="Amazon_1_">
|
||||
<rect x="161.2" y="86.5" class="st0" width="277.8" height="277.8"/>
|
||||
<g id="Amazon">
|
||||
<path class="st1" d="M315,163.7c-8,0.6-17.2,1.2-26.4,2.4c-14.1,1.9-28.2,4.3-39.8,9.8c-22.7,9.2-38,28.8-38,57.6
|
||||
c0,36.2,23.3,54.6,52.7,54.6c9.8,0,17.8-1.2,25.1-3.1c11.7-3.7,21.5-10.4,33.1-22.7c6.7,9.2,8.6,13.5,20.2,23.3
|
||||
c3.1,1.2,6.1,1.2,8.6-0.6c7.4-6.1,20.3-17.2,27-23.3c3.1-2.5,2.5-6.1,0.6-9.2c-6.7-8.6-13.5-16-13.5-32.5V165
|
||||
c0-23.3,1.9-44.8-15.3-60.7c-14.1-12.9-36.2-17.8-53.4-17.8h-7.4c-31.2,1.8-64.3,15.3-71.7,54c-1.2,4.9,2.5,6.8,4.9,7.4l34.3,4.3
|
||||
c3.7-0.6,5.5-3.7,6.1-6.7c3.1-13.5,14.1-20.2,26.3-21.5h2.5c7.4,0,15.3,3.1,19.6,9.2c4.9,7.4,4.3,17.2,4.3,25.8L315,163.7
|
||||
L315,163.7z M308.2,236.7c-4.3,8.6-11.7,14.1-19.6,16c-1.2,0-3.1,0.6-4.9,0.6c-13.5,0-21.4-10.4-21.4-25.8
|
||||
c0-19.6,11.6-28.8,26.3-33.1c8-1.8,17.2-2.5,26.4-2.5v7.4C315,213.4,315.6,224.4,308.2,236.7z"/>
|
||||
<path class="st2" d="M398.8,311.4c-1.4,0-2.8,0.3-4.1,0.9c-1.5,0.6-3,1.3-4.4,1.9l-2.1,0.9l-2.7,1.1v0
|
||||
c-29.8,12.1-61.1,19.2-90.1,19.8c-1.1,0-2.1,0-3.2,0c-45.6,0-82.8-21.1-120.3-42c-1.3-0.7-2.7-1-4-1c-1.7,0-3.4,0.6-4.7,1.8
|
||||
c-1.3,1.2-2,2.9-2,4.7c0,2.3,1.2,4.4,2.9,5.7c35.2,30.6,73.8,59,125.7,59c1,0,2,0,3.1,0c33-0.7,70.3-11.9,99.3-30.1l0.2-0.1
|
||||
c3.8-2.3,7.6-4.9,11.2-7.7c2.2-1.6,3.8-4.2,3.8-6.9C407.2,314.6,403.2,311.4,398.8,311.4z M439,294.5L439,294.5
|
||||
c-0.1-2.9-0.7-5.1-1.9-6.9l-0.1-0.2l-0.1-0.2c-1.2-1.3-2.4-1.8-3.7-2.4c-3.8-1.5-9.3-2.3-16-2.3c-4.8,0-10.1,0.5-15.4,1.6l0-0.4
|
||||
l-5.3,1.8l-0.1,0l-3,1v0.1c-3.5,1.5-6.8,3.3-9.8,5.5c-1.9,1.4-3.4,3.2-3.5,6.1c0,1.5,0.7,3.3,2,4.3c1.3,1,2.8,1.4,4.1,1.4
|
||||
c0.3,0,0.6,0,0.9-0.1l0.3,0l0.2,0c2.6-0.6,6.4-0.9,10.9-1.6c3.8-0.4,7.9-0.7,11.4-0.7c2.5,0,4.7,0.2,6.3,0.5
|
||||
c0.8,0.2,1.3,0.4,1.6,0.5c0.1,0,0.2,0.1,0.2,0.1c0.1,0.2,0.2,0.8,0.1,1.5c0,2.9-1.2,8.4-2.9,13.7c-1.7,5.3-3.7,10.7-5,14.2
|
||||
c-0.3,0.8-0.5,1.7-0.5,2.7c0,1.4,0.6,3.2,1.8,4.3c1.2,1.1,2.8,1.6,4.1,1.6h0.1c2,0,3.6-0.8,5.1-1.9
|
||||
c13.6-12.2,18.3-31.7,18.5-42.6L439,294.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 600 450" style="enable-background:new 0 0 600 450;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#343B45;}
|
||||
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#F4981A;}
|
||||
</style>
|
||||
<g id="_x31__stroke">
|
||||
<g id="Amazon_1_">
|
||||
<rect x="161.2" y="86.5" class="st0" width="277.8" height="277.8"/>
|
||||
<g id="Amazon">
|
||||
<path class="st1" d="M315,163.7c-8,0.6-17.2,1.2-26.4,2.4c-14.1,1.9-28.2,4.3-39.8,9.8c-22.7,9.2-38,28.8-38,57.6
|
||||
c0,36.2,23.3,54.6,52.7,54.6c9.8,0,17.8-1.2,25.1-3.1c11.7-3.7,21.5-10.4,33.1-22.7c6.7,9.2,8.6,13.5,20.2,23.3
|
||||
c3.1,1.2,6.1,1.2,8.6-0.6c7.4-6.1,20.3-17.2,27-23.3c3.1-2.5,2.5-6.1,0.6-9.2c-6.7-8.6-13.5-16-13.5-32.5V165
|
||||
c0-23.3,1.9-44.8-15.3-60.7c-14.1-12.9-36.2-17.8-53.4-17.8h-7.4c-31.2,1.8-64.3,15.3-71.7,54c-1.2,4.9,2.5,6.8,4.9,7.4l34.3,4.3
|
||||
c3.7-0.6,5.5-3.7,6.1-6.7c3.1-13.5,14.1-20.2,26.3-21.5h2.5c7.4,0,15.3,3.1,19.6,9.2c4.9,7.4,4.3,17.2,4.3,25.8L315,163.7
|
||||
L315,163.7z M308.2,236.7c-4.3,8.6-11.7,14.1-19.6,16c-1.2,0-3.1,0.6-4.9,0.6c-13.5,0-21.4-10.4-21.4-25.8
|
||||
c0-19.6,11.6-28.8,26.3-33.1c8-1.8,17.2-2.5,26.4-2.5v7.4C315,213.4,315.6,224.4,308.2,236.7z"/>
|
||||
<path class="st2" d="M398.8,311.4c-1.4,0-2.8,0.3-4.1,0.9c-1.5,0.6-3,1.3-4.4,1.9l-2.1,0.9l-2.7,1.1v0
|
||||
c-29.8,12.1-61.1,19.2-90.1,19.8c-1.1,0-2.1,0-3.2,0c-45.6,0-82.8-21.1-120.3-42c-1.3-0.7-2.7-1-4-1c-1.7,0-3.4,0.6-4.7,1.8
|
||||
c-1.3,1.2-2,2.9-2,4.7c0,2.3,1.2,4.4,2.9,5.7c35.2,30.6,73.8,59,125.7,59c1,0,2,0,3.1,0c33-0.7,70.3-11.9,99.3-30.1l0.2-0.1
|
||||
c3.8-2.3,7.6-4.9,11.2-7.7c2.2-1.6,3.8-4.2,3.8-6.9C407.2,314.6,403.2,311.4,398.8,311.4z M439,294.5L439,294.5
|
||||
c-0.1-2.9-0.7-5.1-1.9-6.9l-0.1-0.2l-0.1-0.2c-1.2-1.3-2.4-1.8-3.7-2.4c-3.8-1.5-9.3-2.3-16-2.3c-4.8,0-10.1,0.5-15.4,1.6l0-0.4
|
||||
l-5.3,1.8l-0.1,0l-3,1v0.1c-3.5,1.5-6.8,3.3-9.8,5.5c-1.9,1.4-3.4,3.2-3.5,6.1c0,1.5,0.7,3.3,2,4.3c1.3,1,2.8,1.4,4.1,1.4
|
||||
c0.3,0,0.6,0,0.9-0.1l0.3,0l0.2,0c2.6-0.6,6.4-0.9,10.9-1.6c3.8-0.4,7.9-0.7,11.4-0.7c2.5,0,4.7,0.2,6.3,0.5
|
||||
c0.8,0.2,1.3,0.4,1.6,0.5c0.1,0,0.2,0.1,0.2,0.1c0.1,0.2,0.2,0.8,0.1,1.5c0,2.9-1.2,8.4-2.9,13.7c-1.7,5.3-3.7,10.7-5,14.2
|
||||
c-0.3,0.8-0.5,1.7-0.5,2.7c0,1.4,0.6,3.2,1.8,4.3c1.2,1.1,2.8,1.6,4.1,1.6h0.1c2,0,3.6-0.8,5.1-1.9
|
||||
c13.6-12.2,18.3-31.7,18.5-42.6L439,294.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
|
@ -1,89 +1,89 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F05A28;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
|
||||
</style>
|
||||
<g id="Contact">
|
||||
<g id="Contact-us" transform="translate(-234.000000, -1114.000000)">
|
||||
<g id="map" transform="translate(-6.000000, 1027.000000)">
|
||||
<g id="Contact-box" transform="translate(190.000000, 36.000000)">
|
||||
<g id="Group-26" transform="translate(50.000000, 51.000000)">
|
||||
<g id="Group-3">
|
||||
<path id="Fill-1" class="st0" d="M220.9,421c-17,0-33.1-3.4-47.8-9.5c-22-9.2-40.8-24.6-54.1-44c-13.3-19.4-21-42.7-21-67.9
|
||||
c0-16.8,3.4-32.7,9.7-47.3c9.3-21.8,24.9-40.3,44.5-53.4c19.6-13.1,43.2-20.7,68.7-20.7v-18.3c-19.5,0-38.1,3.9-55.1,11
|
||||
c-25.4,10.6-47,28.3-62.2,50.6c-15.3,22.3-24.2,49.2-24.2,78.1c0,19.3,4,37.7,11.1,54.4c10.7,25.1,28.7,46.4,51.2,61.5
|
||||
c22.6,15.1,49.8,23.9,79.1,23.9V421z"/>
|
||||
<path id="Fill-4" class="st0" d="M157.9,374.1c-11.5-9.6-20.1-21.2-25.9-33.9c-5.8-12.7-8.8-26.4-8.8-40.2
|
||||
c0-11,1.9-22,5.6-32.5c3.8-10.5,9.4-20.5,17.1-29.6c9.6-11.4,21.3-20,34-25.8c12.7-5.8,26.6-8.7,40.4-8.7
|
||||
c11,0,22.1,1.9,32.6,5.6c10.6,3.8,20.6,9.4,29.7,17l11.9-14.1c-10.8-9-22.8-15.8-35.4-20.2c-12.6-4.5-25.7-6.7-38.8-6.7
|
||||
c-16.5,0-32.9,3.5-48.1,10.4c-15.2,6.9-29.1,17.2-40.5,30.7c-9.1,10.8-15.8,22.7-20.3,35.2c-4.5,12.5-6.7,25.6-6.7,38.7
|
||||
c0,16.4,3.5,32.8,10.4,47.9c6.9,15.1,17.3,29,30.9,40.3L157.9,374.1z"/>
|
||||
<path id="Fill-6" class="st0" d="M186.4,362.2c-12.1-6.4-21.6-15.7-28.1-26.6c-6.5-10.9-9.9-23.5-9.9-36.2
|
||||
c0-11.2,2.6-22.5,8.3-33c6.4-12.1,15.8-21.5,26.8-27.9c11-6.5,23.6-9.9,36.4-9.9c11.2,0,22.6,2.6,33.2,8.2l8.6-16.3
|
||||
c-13.3-7-27.7-10.4-41.9-10.3c-16.1,0-32,4.3-45.8,12.4c-13.8,8.1-25.7,20.1-33.7,35.2c-7,13.3-10.4,27.6-10.4,41.6
|
||||
c0,16,4.3,31.8,12.5,45.5c8.2,13.8,20.2,25.5,35.4,33.5L186.4,362.2z"/>
|
||||
<path id="Fill-8" class="st0" d="M221,344.6c-6.3,0-12.3-1.3-17.7-3.6c-8.2-3.4-15.1-9.2-20-16.5c-4.9-7.3-7.8-16-7.8-25.4
|
||||
c0-6.3,1.3-12.3,3.6-17.7c3.4-8.1,9.2-15.1,16.5-20c7.3-4.9,16-7.8,25.4-7.8v-18.4c-8.8,0-17.2,1.8-24.9,5
|
||||
c-11.5,4.9-21.2,12.9-28.1,23.1C161,273.6,157,286,157,299.2c0,8.8,1.8,17.2,5,24.9c4.9,11.5,13,21.2,23.2,28.1
|
||||
C195.4,359,207.7,363,221,363V344.6z"/>
|
||||
</g>
|
||||
<g id="Group" transform="translate(22.000000, 13.000000)">
|
||||
<path id="Fill-10" class="st1" d="M214,271.6c-2.1-2.2-4.4-4-6.7-5.3c-2.3-1.3-4.7-2-7.2-2c-3.4,0-6.3,0.6-9,1.8
|
||||
c-2.6,1.2-4.9,2.8-6.8,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.4,5.4-1.4,8.2c0,2.8,0.5,5.6,1.4,8.2c1,2.6,2.4,5,4.3,7
|
||||
c1.9,2,4.1,3.7,6.8,4.9c2.6,1.2,5.6,1.8,9,1.8c2.8,0,5.5-0.6,7.9-1.7c2.4-1.2,4.5-2.9,6.2-5.1l12.2,13.1
|
||||
c-1.8,1.8-3.9,3.4-6.3,4.7c-2.4,1.3-4.8,2.4-7.2,3.2s-4.8,1.4-7,1.7c-2.2,0.4-4.2,0.5-5.8,0.5c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c4.7,0,9.4,0.9,14.1,2.7
|
||||
c4.7,1.8,8.9,4.6,12.4,8.4L214,271.6z"/>
|
||||
<path id="Fill-12" class="st1" d="M280.4,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H280.4z M243,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H243z"/>
|
||||
<path id="Fill-14" class="st1" d="M306.5,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-16" class="st1" d="M409,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H409z M371.6,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H371.6z"/>
|
||||
<path id="Fill-18" class="st1" d="M494.6,286.2c0-2.8-0.5-5.6-1.5-8.2c-1-2.6-2.4-5-4.3-7c-1.9-2-4.2-3.7-6.9-4.9
|
||||
c-2.7-1.2-5.7-1.8-9.1-1.8c-3.4,0-6.4,0.6-9.1,1.8c-2.7,1.2-5,2.8-6.9,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.5,5.4-1.5,8.2
|
||||
c0,2.8,0.5,5.6,1.5,8.2c1,2.6,2.4,5,4.3,7c1.9,2,4.2,3.7,6.9,4.9c2.7,1.2,5.7,1.8,9.1,1.8c3.4,0,6.4-0.6,9.1-1.8
|
||||
c2.7-1.2,5-2.8,6.9-4.9c1.9-2,3.3-4.4,4.3-7C494.1,291.8,494.6,289,494.6,286.2L494.6,286.2z M433.2,207.6h18.5v51.3h0.5
|
||||
c0.9-1.2,2.1-2.5,3.5-3.7c1.4-1.3,3.2-2.5,5.2-3.6c2.1-1.1,4.4-2,7.1-2.7c2.7-0.7,5.8-1.1,9.3-1.1c5.2,0,10.1,1,14.5,3
|
||||
c4.4,2,8.2,4.7,11.3,8.1c3.1,3.5,5.6,7.5,7.3,12.2c1.7,4.7,2.6,9.7,2.6,15.1c0,5.4-0.8,10.4-2.5,15.1
|
||||
c-1.6,4.7-4.1,8.7-7.2,12.2c-3.2,3.5-7,6.2-11.6,8.1c-4.5,2-9.6,3-15.3,3c-5.2,0-10.1-1-14.7-3c-4.5-2-8.1-5.3-10.8-9.7h-0.3
|
||||
v11h-17.6V207.6z"/>
|
||||
<path id="Fill-20" class="st1" d="M520.9,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-22" class="st1" d="M616,290h-3.9c-2.6,0-5.5,0.1-8.7,0.3c-3.2,0.2-6.2,0.7-9.1,1.4c-2.8,0.8-5.2,1.9-7.2,3.3
|
||||
c-2,1.5-2.9,3.5-2.9,6.2c0,1.7,0.4,3.2,1.2,4.3c0.8,1.2,1.8,2.2,3,3c1.2,0.8,2.6,1.4,4.2,1.8c1.5,0.4,3.1,0.5,4.6,0.5
|
||||
c6.4,0,11.1-1.5,14.2-4.5c3-3,4.6-7.1,4.6-12.2V290z M617.1,312.7h-0.5c-2.7,4.2-6.1,7.2-10.2,9.1c-4.1,1.9-8.7,2.8-13.6,2.8
|
||||
c-3.4,0-6.7-0.5-10-1.4s-6.1-2.3-8.7-4.1c-2.5-1.8-4.6-4.1-6.1-6.8s-2.3-5.9-2.3-9.6c0-4,0.7-7.3,2.2-10.1
|
||||
c1.4-2.8,3.4-5.1,5.8-7c2.4-1.9,5.2-3.4,8.4-4.5c3.2-1.1,6.5-2,10-2.5c3.5-0.6,6.9-0.9,10.5-1.1c3.5-0.2,6.8-0.2,9.9-0.2h4.6
|
||||
v-2c0-4.6-1.6-8-4.8-10.3c-3.2-2.3-7.3-3.4-12.2-3.4c-3.9,0-7.6,0.7-11,2.1c-3.4,1.4-6.4,3.2-8.8,5.6l-9.8-9.6
|
||||
c4.1-4.2,9-7.1,14.5-9c5.5-1.8,11.2-2.7,17.1-2.7c5.3,0,9.7,0.6,13.3,1.7c3.6,1.2,6.6,2.7,9,4.5c2.4,1.8,4.2,3.9,5.5,6.3
|
||||
c1.3,2.4,2.2,4.8,2.8,7.2c0.6,2.4,0.9,4.8,1,7.1c0.1,2.3,0.2,4.3,0.2,6v42h-16.7V312.7z"/>
|
||||
<path id="Fill-24" class="st1" d="M683.6,269.9c-3.6-5-8.4-7.5-14.4-7.5c-2.5,0-4.9,0.6-7.2,1.8c-2.4,1.2-3.5,3.2-3.5,5.9
|
||||
c0,2.2,1,3.9,2.9,4.9c1.9,1,4.4,1.9,7.4,2.6c3,0.7,6.2,1.4,9.6,2.2c3.4,0.8,6.6,1.9,9.6,3.5c3,1.6,5.4,3.7,7.4,6.5
|
||||
c1.9,2.7,2.9,6.5,2.9,11.3c0,4.4-0.9,8-2.8,11c-1.9,3-4.3,5.4-7.4,7.2c-3,1.8-6.4,3.1-10.2,4c-3.8,0.8-7.6,1.2-11.3,1.2
|
||||
c-5.7,0-11-0.8-15.8-2.4c-4.8-1.6-9.1-4.6-12.9-8.8l12.3-11.4c2.4,2.6,4.9,4.8,7.6,6.5c2.7,1.7,6,2.5,9.9,2.5
|
||||
c1.3,0,2.7-0.2,4.1-0.5c1.4-0.3,2.8-0.8,4-1.5c1.2-0.7,2.2-1.6,3-2.7c0.8-1.1,1.1-2.3,1.1-3.7c0-2.5-1-4.4-2.9-5.6
|
||||
c-1.9-1.2-4.4-2.2-7.4-3c-3-0.8-6.2-1.5-9.6-2.1c-3.4-0.7-6.6-1.7-9.6-3.2c-3-1.5-5.4-3.5-7.4-6.2c-1.9-2.6-2.9-6.3-2.9-11
|
||||
c0-4.1,0.8-7.6,2.5-10.6c1.7-3,3.9-5.4,6.7-7.4c2.8-1.9,5.9-3.3,9.5-4.3c3.6-0.9,7.2-1.4,10.9-1.4c4.9,0,9.8,0.8,14.6,2.5
|
||||
c4.8,1.7,8.7,4.5,11.7,8.6L683.6,269.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F05A28;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#231F20;}
|
||||
</style>
|
||||
<g id="Contact">
|
||||
<g id="Contact-us" transform="translate(-234.000000, -1114.000000)">
|
||||
<g id="map" transform="translate(-6.000000, 1027.000000)">
|
||||
<g id="Contact-box" transform="translate(190.000000, 36.000000)">
|
||||
<g id="Group-26" transform="translate(50.000000, 51.000000)">
|
||||
<g id="Group-3">
|
||||
<path id="Fill-1" class="st0" d="M220.9,421c-17,0-33.1-3.4-47.8-9.5c-22-9.2-40.8-24.6-54.1-44c-13.3-19.4-21-42.7-21-67.9
|
||||
c0-16.8,3.4-32.7,9.7-47.3c9.3-21.8,24.9-40.3,44.5-53.4c19.6-13.1,43.2-20.7,68.7-20.7v-18.3c-19.5,0-38.1,3.9-55.1,11
|
||||
c-25.4,10.6-47,28.3-62.2,50.6c-15.3,22.3-24.2,49.2-24.2,78.1c0,19.3,4,37.7,11.1,54.4c10.7,25.1,28.7,46.4,51.2,61.5
|
||||
c22.6,15.1,49.8,23.9,79.1,23.9V421z"/>
|
||||
<path id="Fill-4" class="st0" d="M157.9,374.1c-11.5-9.6-20.1-21.2-25.9-33.9c-5.8-12.7-8.8-26.4-8.8-40.2
|
||||
c0-11,1.9-22,5.6-32.5c3.8-10.5,9.4-20.5,17.1-29.6c9.6-11.4,21.3-20,34-25.8c12.7-5.8,26.6-8.7,40.4-8.7
|
||||
c11,0,22.1,1.9,32.6,5.6c10.6,3.8,20.6,9.4,29.7,17l11.9-14.1c-10.8-9-22.8-15.8-35.4-20.2c-12.6-4.5-25.7-6.7-38.8-6.7
|
||||
c-16.5,0-32.9,3.5-48.1,10.4c-15.2,6.9-29.1,17.2-40.5,30.7c-9.1,10.8-15.8,22.7-20.3,35.2c-4.5,12.5-6.7,25.6-6.7,38.7
|
||||
c0,16.4,3.5,32.8,10.4,47.9c6.9,15.1,17.3,29,30.9,40.3L157.9,374.1z"/>
|
||||
<path id="Fill-6" class="st0" d="M186.4,362.2c-12.1-6.4-21.6-15.7-28.1-26.6c-6.5-10.9-9.9-23.5-9.9-36.2
|
||||
c0-11.2,2.6-22.5,8.3-33c6.4-12.1,15.8-21.5,26.8-27.9c11-6.5,23.6-9.9,36.4-9.9c11.2,0,22.6,2.6,33.2,8.2l8.6-16.3
|
||||
c-13.3-7-27.7-10.4-41.9-10.3c-16.1,0-32,4.3-45.8,12.4c-13.8,8.1-25.7,20.1-33.7,35.2c-7,13.3-10.4,27.6-10.4,41.6
|
||||
c0,16,4.3,31.8,12.5,45.5c8.2,13.8,20.2,25.5,35.4,33.5L186.4,362.2z"/>
|
||||
<path id="Fill-8" class="st0" d="M221,344.6c-6.3,0-12.3-1.3-17.7-3.6c-8.2-3.4-15.1-9.2-20-16.5c-4.9-7.3-7.8-16-7.8-25.4
|
||||
c0-6.3,1.3-12.3,3.6-17.7c3.4-8.1,9.2-15.1,16.5-20c7.3-4.9,16-7.8,25.4-7.8v-18.4c-8.8,0-17.2,1.8-24.9,5
|
||||
c-11.5,4.9-21.2,12.9-28.1,23.1C161,273.6,157,286,157,299.2c0,8.8,1.8,17.2,5,24.9c4.9,11.5,13,21.2,23.2,28.1
|
||||
C195.4,359,207.7,363,221,363V344.6z"/>
|
||||
</g>
|
||||
<g id="Group" transform="translate(22.000000, 13.000000)">
|
||||
<path id="Fill-10" class="st1" d="M214,271.6c-2.1-2.2-4.4-4-6.7-5.3c-2.3-1.3-4.7-2-7.2-2c-3.4,0-6.3,0.6-9,1.8
|
||||
c-2.6,1.2-4.9,2.8-6.8,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.4,5.4-1.4,8.2c0,2.8,0.5,5.6,1.4,8.2c1,2.6,2.4,5,4.3,7
|
||||
c1.9,2,4.1,3.7,6.8,4.9c2.6,1.2,5.6,1.8,9,1.8c2.8,0,5.5-0.6,7.9-1.7c2.4-1.2,4.5-2.9,6.2-5.1l12.2,13.1
|
||||
c-1.8,1.8-3.9,3.4-6.3,4.7c-2.4,1.3-4.8,2.4-7.2,3.2s-4.8,1.4-7,1.7c-2.2,0.4-4.2,0.5-5.8,0.5c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c4.7,0,9.4,0.9,14.1,2.7
|
||||
c4.7,1.8,8.9,4.6,12.4,8.4L214,271.6z"/>
|
||||
<path id="Fill-12" class="st1" d="M280.4,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H280.4z M243,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H243z"/>
|
||||
<path id="Fill-14" class="st1" d="M306.5,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-16" class="st1" d="M409,278.9c-0.1-5.4-1.8-9.6-5-12.7c-3.3-3.1-7.8-4.6-13.6-4.6c-5.5,0-9.8,1.6-13,4.7
|
||||
c-3.2,3.1-5.2,7.4-5.9,12.6H409z M371.6,292.6c0.6,5.5,2.7,9.7,6.4,12.8c3.7,3,8.1,4.6,13.3,4.6c4.6,0,8.4-0.9,11.5-2.8
|
||||
c3.1-1.9,5.8-4.2,8.2-7.1l13.1,9.9c-4.3,5.3-9,9-14.3,11.3c-5.3,2.2-10.8,3.3-16.6,3.3c-5.5,0-10.7-0.9-15.5-2.7
|
||||
c-4.9-1.8-9.1-4.4-12.6-7.8c-3.6-3.3-6.4-7.4-8.5-12.1c-2.1-4.7-3.1-10-3.1-15.7c0-5.8,1-11,3.1-15.7
|
||||
c2.1-4.7,4.9-8.7,8.5-12.1c3.6-3.3,7.8-5.9,12.6-7.8c4.9-1.8,10.1-2.7,15.5-2.7c5.1,0,9.7,0.9,13.9,2.7
|
||||
c4.2,1.8,7.8,4.3,10.8,7.7c3,3.3,5.3,7.5,7,12.4c1.7,4.9,2.5,10.6,2.5,17v5H371.6z"/>
|
||||
<path id="Fill-18" class="st1" d="M494.6,286.2c0-2.8-0.5-5.6-1.5-8.2c-1-2.6-2.4-5-4.3-7c-1.9-2-4.2-3.7-6.9-4.9
|
||||
c-2.7-1.2-5.7-1.8-9.1-1.8c-3.4,0-6.4,0.6-9.1,1.8c-2.7,1.2-5,2.8-6.9,4.9c-1.9,2-3.3,4.4-4.3,7c-1,2.6-1.5,5.4-1.5,8.2
|
||||
c0,2.8,0.5,5.6,1.5,8.2c1,2.6,2.4,5,4.3,7c1.9,2,4.2,3.7,6.9,4.9c2.7,1.2,5.7,1.8,9.1,1.8c3.4,0,6.4-0.6,9.1-1.8
|
||||
c2.7-1.2,5-2.8,6.9-4.9c1.9-2,3.3-4.4,4.3-7C494.1,291.8,494.6,289,494.6,286.2L494.6,286.2z M433.2,207.6h18.5v51.3h0.5
|
||||
c0.9-1.2,2.1-2.5,3.5-3.7c1.4-1.3,3.2-2.5,5.2-3.6c2.1-1.1,4.4-2,7.1-2.7c2.7-0.7,5.8-1.1,9.3-1.1c5.2,0,10.1,1,14.5,3
|
||||
c4.4,2,8.2,4.7,11.3,8.1c3.1,3.5,5.6,7.5,7.3,12.2c1.7,4.7,2.6,9.7,2.6,15.1c0,5.4-0.8,10.4-2.5,15.1
|
||||
c-1.6,4.7-4.1,8.7-7.2,12.2c-3.2,3.5-7,6.2-11.6,8.1c-4.5,2-9.6,3-15.3,3c-5.2,0-10.1-1-14.7-3c-4.5-2-8.1-5.3-10.8-9.7h-0.3
|
||||
v11h-17.6V207.6z"/>
|
||||
<path id="Fill-20" class="st1" d="M520.9,249.7h18.3v11.5h0.3c2-4.3,4.9-7.5,8.7-9.9c3.8-2.3,8.1-3.5,12.9-3.5
|
||||
c1.1,0,2.2,0.1,3.3,0.3c1.1,0.2,2.2,0.5,3.3,0.8v17.6c-1.5-0.4-3-0.7-4.5-1c-1.5-0.3-2.9-0.4-4.3-0.4c-4.3,0-7.7,0.8-10.3,2.4
|
||||
c-2.6,1.6-4.6,3.4-5.9,5.4c-1.4,2-2.3,4.1-2.7,6.1c-0.5,2-0.7,3.5-0.7,4.6v39h-18.3V249.7z"/>
|
||||
<path id="Fill-22" class="st1" d="M616,290h-3.9c-2.6,0-5.5,0.1-8.7,0.3c-3.2,0.2-6.2,0.7-9.1,1.4c-2.8,0.8-5.2,1.9-7.2,3.3
|
||||
c-2,1.5-2.9,3.5-2.9,6.2c0,1.7,0.4,3.2,1.2,4.3c0.8,1.2,1.8,2.2,3,3c1.2,0.8,2.6,1.4,4.2,1.8c1.5,0.4,3.1,0.5,4.6,0.5
|
||||
c6.4,0,11.1-1.5,14.2-4.5c3-3,4.6-7.1,4.6-12.2V290z M617.1,312.7h-0.5c-2.7,4.2-6.1,7.2-10.2,9.1c-4.1,1.9-8.7,2.8-13.6,2.8
|
||||
c-3.4,0-6.7-0.5-10-1.4s-6.1-2.3-8.7-4.1c-2.5-1.8-4.6-4.1-6.1-6.8s-2.3-5.9-2.3-9.6c0-4,0.7-7.3,2.2-10.1
|
||||
c1.4-2.8,3.4-5.1,5.8-7c2.4-1.9,5.2-3.4,8.4-4.5c3.2-1.1,6.5-2,10-2.5c3.5-0.6,6.9-0.9,10.5-1.1c3.5-0.2,6.8-0.2,9.9-0.2h4.6
|
||||
v-2c0-4.6-1.6-8-4.8-10.3c-3.2-2.3-7.3-3.4-12.2-3.4c-3.9,0-7.6,0.7-11,2.1c-3.4,1.4-6.4,3.2-8.8,5.6l-9.8-9.6
|
||||
c4.1-4.2,9-7.1,14.5-9c5.5-1.8,11.2-2.7,17.1-2.7c5.3,0,9.7,0.6,13.3,1.7c3.6,1.2,6.6,2.7,9,4.5c2.4,1.8,4.2,3.9,5.5,6.3
|
||||
c1.3,2.4,2.2,4.8,2.8,7.2c0.6,2.4,0.9,4.8,1,7.1c0.1,2.3,0.2,4.3,0.2,6v42h-16.7V312.7z"/>
|
||||
<path id="Fill-24" class="st1" d="M683.6,269.9c-3.6-5-8.4-7.5-14.4-7.5c-2.5,0-4.9,0.6-7.2,1.8c-2.4,1.2-3.5,3.2-3.5,5.9
|
||||
c0,2.2,1,3.9,2.9,4.9c1.9,1,4.4,1.9,7.4,2.6c3,0.7,6.2,1.4,9.6,2.2c3.4,0.8,6.6,1.9,9.6,3.5c3,1.6,5.4,3.7,7.4,6.5
|
||||
c1.9,2.7,2.9,6.5,2.9,11.3c0,4.4-0.9,8-2.8,11c-1.9,3-4.3,5.4-7.4,7.2c-3,1.8-6.4,3.1-10.2,4c-3.8,0.8-7.6,1.2-11.3,1.2
|
||||
c-5.7,0-11-0.8-15.8-2.4c-4.8-1.6-9.1-4.6-12.9-8.8l12.3-11.4c2.4,2.6,4.9,4.8,7.6,6.5c2.7,1.7,6,2.5,9.9,2.5
|
||||
c1.3,0,2.7-0.2,4.1-0.5c1.4-0.3,2.8-0.8,4-1.5c1.2-0.7,2.2-1.6,3-2.7c0.8-1.1,1.1-2.3,1.1-3.7c0-2.5-1-4.4-2.9-5.6
|
||||
c-1.9-1.2-4.4-2.2-7.4-3c-3-0.8-6.2-1.5-9.6-2.1c-3.4-0.7-6.6-1.7-9.6-3.2c-3-1.5-5.4-3.5-7.4-6.2c-1.9-2.6-2.9-6.3-2.9-11
|
||||
c0-4.1,0.8-7.6,2.5-10.6c1.7-3,3.9-5.4,6.7-7.4c2.8-1.9,5.9-3.3,9.5-4.3c3.6-0.9,7.2-1.4,10.9-1.4c4.9,0,9.8,0.8,14.6,2.5
|
||||
c4.8,1.7,8.7,4.5,11.7,8.6L683.6,269.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
|
|
@ -1,25 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 292.6 215.3" style="enable-background:new 0 0 292.6 215.3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#566AB2;}
|
||||
</style>
|
||||
<path class="st0" d="M191.3,123.7c-2.4,1-4.9,1.8-7.2,1.9c-3.6,0.2-7.6-1.3-9.7-3.1c-3.3-2.8-5.7-4.4-6.7-9.2
|
||||
c-0.4-2.1-0.2-5.3,0.2-7.2c0.9-4-0.1-6.5-2.9-8.9c-2.3-1.9-5.2-2.4-8.4-2.4s-2.3-0.5-3.1-1c-1.3-0.7-2.4-2.3-1.4-4.4
|
||||
c0.3-0.7,2-2.3,2.3-2.5c4.3-2.5,9.4-1.7,14,0.2c4.3,1.7,7.5,5,12.2,9.5c4.8,5.5,5.6,7,8.4,11.1c2.1,3.2,4.1,6.6,5.4,10.4
|
||||
C195.2,120.5,194.2,122.4,191.3,123.7L191.3,123.7z M153.4,104.3c0-2.1,1.7-3.7,3.8-3.7s0.9,0.1,1.3,0.2c0.5,0.2,1,0.5,1.4,0.9
|
||||
c0.7,0.7,1.1,1.6,1.1,2.6c0,2.1-1.7,3.8-3.8,3.8s-3.7-1.7-3.7-3.8H153.4z M141.2,182.8c-25.5-20-37.8-26.6-42.9-26.3
|
||||
c-4.8,0.3-3.9,5.7-2.8,9.3c1.1,3.5,2.5,5.9,4.5,9c1.4,2,2.3,5.1-1.4,7.3c-8.2,5.1-22.5-1.7-23.1-2c-16.6-9.8-30.5-22.7-40.2-40.3
|
||||
c-9.5-17-14.9-35.2-15.8-54.6c-0.2-4.7,1.1-6.4,5.8-7.2c6.2-1.1,12.5-1.4,18.7-0.5c26,3.8,48.1,15.4,66.7,33.8
|
||||
c10.6,10.5,18.6,23,26.8,35.2c8.8,13,18.2,25.4,30.2,35.5c4.3,3.6,7.6,6.3,10.9,8.2c-9.8,1.1-26.1,1.3-37.2-7.5L141.2,182.8z
|
||||
M289.5,18c-3.1-1.5-4.4,1.4-6.3,2.8c-0.6,0.5-1.1,1.1-1.7,1.7c-4.5,4.8-9.8,8-16.8,7.6c-10.1-0.6-18.7,2.6-26.4,10.4
|
||||
c-1.6-9.5-7-15.2-15.2-18.9c-4.3-1.9-8.6-3.8-11.6-7.9c-2.1-2.9-2.7-6.2-3.7-9.4c-0.7-2-1.3-3.9-3.6-4.3c-2.4-0.4-3.4,1.7-4.3,3.4
|
||||
c-3.8,7-5.3,14.6-5.2,22.4c0.3,17.5,7.7,31.5,22.4,41.4c1.7,1.1,2.1,2.3,1.6,3.9c-1,3.4-2.2,6.7-3.3,10.1c-0.7,2.2-1.7,2.7-4,1.7
|
||||
c-8.1-3.4-15-8.4-21.2-14.4c-10.4-10.1-19.9-21.2-31.6-30c-2.8-2.1-5.5-4-8.4-5.7c-12-11.7,1.6-21.3,4.7-22.4
|
||||
c3.3-1.2,1.2-5.3-9.5-5.2c-10.6,0-20.3,3.6-32.8,8.4c-1.8,0.7-3.7,1.2-5.7,1.7c-11.3-2.1-22.9-2.6-35.1-1.2
|
||||
c-23,2.5-41.4,13.4-54.8,32C1,68.3-2.8,93.6,1.9,120c4.9,27.8,19.1,50.9,41,68.9c22.6,18.7,48.7,27.8,78.5,26.1
|
||||
c18.1-1,38.2-3.5,60.9-22.7c5.7,2.8,11.7,4,21.7,4.8c7.7,0.7,15.1-0.4,20.8-1.5c9-1.9,8.4-10.2,5.1-11.7
|
||||
c-26.3-12.3-20.5-7.3-25.7-11.3c13.3-15.8,33.5-32.2,41.3-85.4c0.6-4.2,0.1-6.9,0-10.3c0-2.1,0.4-2.9,2.8-3.1
|
||||
c6.6-0.8,13-2.6,18.8-5.8c17-9.3,23.9-24.6,25.5-42.9c0.2-2.8,0-5.7-3-7.2L289.5,18z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 292.6 215.3" style="enable-background:new 0 0 292.6 215.3;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#566AB2;}
|
||||
</style>
|
||||
<path class="st0" d="M191.3,123.7c-2.4,1-4.9,1.8-7.2,1.9c-3.6,0.2-7.6-1.3-9.7-3.1c-3.3-2.8-5.7-4.4-6.7-9.2
|
||||
c-0.4-2.1-0.2-5.3,0.2-7.2c0.9-4-0.1-6.5-2.9-8.9c-2.3-1.9-5.2-2.4-8.4-2.4s-2.3-0.5-3.1-1c-1.3-0.7-2.4-2.3-1.4-4.4
|
||||
c0.3-0.7,2-2.3,2.3-2.5c4.3-2.5,9.4-1.7,14,0.2c4.3,1.7,7.5,5,12.2,9.5c4.8,5.5,5.6,7,8.4,11.1c2.1,3.2,4.1,6.6,5.4,10.4
|
||||
C195.2,120.5,194.2,122.4,191.3,123.7L191.3,123.7z M153.4,104.3c0-2.1,1.7-3.7,3.8-3.7s0.9,0.1,1.3,0.2c0.5,0.2,1,0.5,1.4,0.9
|
||||
c0.7,0.7,1.1,1.6,1.1,2.6c0,2.1-1.7,3.8-3.8,3.8s-3.7-1.7-3.7-3.8H153.4z M141.2,182.8c-25.5-20-37.8-26.6-42.9-26.3
|
||||
c-4.8,0.3-3.9,5.7-2.8,9.3c1.1,3.5,2.5,5.9,4.5,9c1.4,2,2.3,5.1-1.4,7.3c-8.2,5.1-22.5-1.7-23.1-2c-16.6-9.8-30.5-22.7-40.2-40.3
|
||||
c-9.5-17-14.9-35.2-15.8-54.6c-0.2-4.7,1.1-6.4,5.8-7.2c6.2-1.1,12.5-1.4,18.7-0.5c26,3.8,48.1,15.4,66.7,33.8
|
||||
c10.6,10.5,18.6,23,26.8,35.2c8.8,13,18.2,25.4,30.2,35.5c4.3,3.6,7.6,6.3,10.9,8.2c-9.8,1.1-26.1,1.3-37.2-7.5L141.2,182.8z
|
||||
M289.5,18c-3.1-1.5-4.4,1.4-6.3,2.8c-0.6,0.5-1.1,1.1-1.7,1.7c-4.5,4.8-9.8,8-16.8,7.6c-10.1-0.6-18.7,2.6-26.4,10.4
|
||||
c-1.6-9.5-7-15.2-15.2-18.9c-4.3-1.9-8.6-3.8-11.6-7.9c-2.1-2.9-2.7-6.2-3.7-9.4c-0.7-2-1.3-3.9-3.6-4.3c-2.4-0.4-3.4,1.7-4.3,3.4
|
||||
c-3.8,7-5.3,14.6-5.2,22.4c0.3,17.5,7.7,31.5,22.4,41.4c1.7,1.1,2.1,2.3,1.6,3.9c-1,3.4-2.2,6.7-3.3,10.1c-0.7,2.2-1.7,2.7-4,1.7
|
||||
c-8.1-3.4-15-8.4-21.2-14.4c-10.4-10.1-19.9-21.2-31.6-30c-2.8-2.1-5.5-4-8.4-5.7c-12-11.7,1.6-21.3,4.7-22.4
|
||||
c3.3-1.2,1.2-5.3-9.5-5.2c-10.6,0-20.3,3.6-32.8,8.4c-1.8,0.7-3.7,1.2-5.7,1.7c-11.3-2.1-22.9-2.6-35.1-1.2
|
||||
c-23,2.5-41.4,13.4-54.8,32C1,68.3-2.8,93.6,1.9,120c4.9,27.8,19.1,50.9,41,68.9c22.6,18.7,48.7,27.8,78.5,26.1
|
||||
c18.1-1,38.2-3.5,60.9-22.7c5.7,2.8,11.7,4,21.7,4.8c7.7,0.7,15.1-0.4,20.8-1.5c9-1.9,8.4-10.2,5.1-11.7
|
||||
c-26.3-12.3-20.5-7.3-25.7-11.3c13.3-15.8,33.5-32.2,41.3-85.4c0.6-4.2,0.1-6.9,0-10.3c0-2.1,0.4-2.9,2.8-3.1
|
||||
c6.6-0.8,13-2.6,18.8-5.8c17-9.3,23.9-24.6,25.5-42.9c0.2-2.8,0-5.7-3-7.2L289.5,18z"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
|
@ -1,16 +1,16 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.5862" y1="1.61" x2="36.0543" y2="44.1206">
|
||||
<stop offset="0.002" style="stop-color:#9C55D4"/>
|
||||
<stop offset="0.003" style="stop-color:#20808D"/>
|
||||
<stop offset="0.3731" style="stop-color:#218F9B"/>
|
||||
<stop offset="1" style="stop-color:#22B1BC"/>
|
||||
</linearGradient>
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);" d="M11.469,4l11.39,10.494v-0.002V4.024h2.217v10.517
|
||||
L36.518,4v11.965h4.697v17.258h-4.683v10.654L25.077,33.813v10.18h-2.217V33.979L11.482,44V33.224H6.785V15.965h4.685V4z
|
||||
M21.188,18.155H9.002v12.878h2.477v-4.062L21.188,18.155z M13.699,27.943v11.17l9.16-8.068V19.623L13.699,27.943z M25.141,30.938
|
||||
V19.612l9.163,8.321v5.291h0.012v5.775L25.141,30.938z M36.532,31.033h2.466V18.155H26.903l9.629,8.725V31.033z M34.301,15.965
|
||||
V9.038l-7.519,6.927H34.301z M21.205,15.965h-7.519V9.038L21.205,15.965z"/>
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="10.5862" y1="1.61" x2="36.0543" y2="44.1206">
|
||||
<stop offset="0.002" style="stop-color:#9C55D4"/>
|
||||
<stop offset="0.003" style="stop-color:#20808D"/>
|
||||
<stop offset="0.3731" style="stop-color:#218F9B"/>
|
||||
<stop offset="1" style="stop-color:#22B1BC"/>
|
||||
</linearGradient>
|
||||
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);" d="M11.469,4l11.39,10.494v-0.002V4.024h2.217v10.517
|
||||
L36.518,4v11.965h4.697v17.258h-4.683v10.654L25.077,33.813v10.18h-2.217V33.979L11.482,44V33.224H6.785V15.965h4.685V4z
|
||||
M21.188,18.155H9.002v12.878h2.477v-4.062L21.188,18.155z M13.699,27.943v11.17l9.16-8.068V19.623L13.699,27.943z M25.141,30.938
|
||||
V19.612l9.163,8.321v5.291h0.012v5.775L25.141,30.938z M36.532,31.033h2.466V18.155H26.903l9.629,8.725V31.033z M34.301,15.965
|
||||
V9.038l-7.519,6.927H34.301z M21.205,15.965h-7.519V9.038L21.205,15.965z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -2,6 +2,6 @@
|
|||
3:I[21718,["665","static/chunks/3014691f-b7b79b78e27792f3.js","990","static/chunks/13b76428-ebdf3012af0e4489.js","402","static/chunks/402-239a4ed70dc393da.js","313","static/chunks/313-cf4a28394ee560d6.js","899","static/chunks/899-0459bccb48a6666d.js","539","static/chunks/539-fadc69b2ec7728b1.js","250","static/chunks/250-f07ccab57fe599d4.js","699","static/chunks/699-2e0b76ba9cd1d301.js","931","static/chunks/app/page-2905d62702267b5c.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["1Uk4UFrWV9mxVjloiprrd",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/c1c21001170a99e0.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["1Uk4UFrWV9mxVjloiprrd",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/c1c21001170a99e0.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
3:I[52829,["402","static/chunks/402-239a4ed70dc393da.js","313","static/chunks/313-cf4a28394ee560d6.js","250","static/chunks/250-f07ccab57fe599d4.js","699","static/chunks/699-2e0b76ba9cd1d301.js","418","static/chunks/app/model_hub/page-ce40c5a05f3174ca.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["1Uk4UFrWV9mxVjloiprrd",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/c1c21001170a99e0.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["1Uk4UFrWV9mxVjloiprrd",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/c1c21001170a99e0.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
3:I[12011,["665","static/chunks/3014691f-b7b79b78e27792f3.js","402","static/chunks/402-239a4ed70dc393da.js","899","static/chunks/899-0459bccb48a6666d.js","250","static/chunks/250-f07ccab57fe599d4.js","461","static/chunks/app/onboarding/page-e05c770288debeda.js"],"default",1]
|
||||
4:I[4707,[],""]
|
||||
5:I[36423,[],""]
|
||||
0:["1Uk4UFrWV9mxVjloiprrd",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/c1c21001170a99e0.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
0:["1Uk4UFrWV9mxVjloiprrd",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/31b7f215e119031e.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/_next/static/css/c1c21001170a99e0.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_b0dd8a","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
|
||||
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]]
|
||||
1:null
|
||||
|
|
|
|||
|
|
@ -1,119 +1,4 @@
|
|||
model_list:
|
||||
- model_name: codex-mini
|
||||
- model_name: gemini-2.5-pro
|
||||
litellm_params:
|
||||
model: codex-mini-latest
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
- model_name: bedrock/*
|
||||
litellm_params:
|
||||
model: bedrock/*
|
||||
- model_name: eu.anthropic.claude-3-5-sonnet-20240620-v1:0
|
||||
litellm_params:
|
||||
model: eu.anthropic.claude-3-5-sonnet-20240620-v1:0
|
||||
- model_name: "gpt-4o-mini-openai"
|
||||
litellm_params:
|
||||
model: gpt-4o-mini
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
model_info:
|
||||
access_groups: ["beta-models"] # 👈 Model Access Group
|
||||
- model_name: azure_ai/Phi-3-medium
|
||||
litellm_params:
|
||||
model: azure_ai/Phi-3-medium
|
||||
api_key: os.environ/AZURE_AI_PHI_3_MEDIUM_API_KEY
|
||||
api_base: os.environ/AZURE_AI_PHI_3_MEDIUM_API_BASE
|
||||
- model_name: "bedrock-nova"
|
||||
litellm_params:
|
||||
model: us.amazon.nova-pro-v1:0
|
||||
- model_name: openrouter_model
|
||||
litellm_params:
|
||||
model: openrouter/openrouter_model
|
||||
api_key: os.environ/OPENROUTER_API_KEY
|
||||
api_base: http://0.0.0.0:8090
|
||||
- model_name: dall-e-3-azure
|
||||
litellm_params:
|
||||
model: azure/dall-e-3-test
|
||||
api_version: "2023-12-01-preview"
|
||||
api_base: os.environ/AZURE_SWEDEN_API_BASE
|
||||
api_key: os.environ/AZURE_SWEDEN_API_KEY
|
||||
model_info:
|
||||
input_cost_per_pixel: 10
|
||||
- model_name: "claude-3-7-sonnet"
|
||||
litellm_params:
|
||||
model: databricks/databricks-claude-3-7-sonnet
|
||||
api_key: os.environ/DATABRICKS_API_KEY
|
||||
api_base: os.environ/DATABRICKS_API_BASE
|
||||
- model_name: "gpt-4.1"
|
||||
litellm_params:
|
||||
model: azure/gpt-4.1
|
||||
api_key: os.environ/AZURE_API_KEY_REALTIME
|
||||
api_base: https://krris-m2f9a9i7-eastus2.openai.azure.com/
|
||||
- model_name: "xai/*"
|
||||
litellm_params:
|
||||
model: xai/*
|
||||
api_key: os.environ/XAI_API_KEY
|
||||
- model_name: "text-embedding-ada-002"
|
||||
litellm_params:
|
||||
model: text-embedding-ada-002
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
- model_name: gemini/*
|
||||
litellm_params:
|
||||
model: gemini/*
|
||||
- model_name: llama-qwen
|
||||
litellm_params:
|
||||
model: ollama/qwen2:0.5b
|
||||
model_info:
|
||||
input_cost_per_token: 0.75
|
||||
output_cost_per_token: 3
|
||||
- model_name: gpt-image-1
|
||||
litellm_params:
|
||||
model: gpt-image-1
|
||||
api_key: os.environ/OPENAI_API_KEY
|
||||
# drop_params: true
|
||||
- model_name: "gpt-4o-batch"
|
||||
litellm_params:
|
||||
model: azure/gpt-4o-mini
|
||||
api_base: os.environ/AZURE_API_BASE
|
||||
api_key: os.environ/AZURE_API_KEY
|
||||
model_info:
|
||||
id: my-general-azure-deployment
|
||||
mode: batch
|
||||
- model_name: "gpt-4o-batch"
|
||||
litellm_params:
|
||||
model: azure/gpt-4o-mini
|
||||
api_base: https://krris-m2f9a9i7-eastus2.openai.azure.com
|
||||
api_key: 04d22fb7e9ad4d9c8afe7c6abf97a6fc
|
||||
model_info:
|
||||
id: my-unique-azure-deployment
|
||||
mode: batch
|
||||
- model_name: fake-openai-endpoint
|
||||
litellm_params:
|
||||
model: openai/fake
|
||||
api_key: fake-key
|
||||
api_base: https://exampleopenaiendpoint-production.up.railway.app/
|
||||
- model_name: "anthropic-claude-vertex"
|
||||
litellm_params:
|
||||
model: vertex_ai/claude-3-5-sonnet@20240620
|
||||
vertex_project: internal-litellm-local-dev
|
||||
- model_name: "openai-custom/*"
|
||||
litellm_params:
|
||||
model: "openai/*"
|
||||
api_key: os.environ/OPENAI_API_KEY_TEST
|
||||
- model_name: "anthropic-claude"
|
||||
litellm_params:
|
||||
model: "anthropic/claude-3-5-sonnet-latest"
|
||||
api_key: os.environ/ANTHROPIC_API_KEY
|
||||
|
||||
general_settings:
|
||||
store_model_in_db: true
|
||||
store_prompts_in_spend_logs: true
|
||||
token_rate_limit_type: "output"
|
||||
# master_key: os.environ/PROXY_MASTER_KEY
|
||||
|
||||
litellm_settings:
|
||||
# cache: true
|
||||
# cache_params:
|
||||
# type: redis
|
||||
# ttl: 600
|
||||
# password: os.environ/REDIS_PASSWORD
|
||||
# supported_call_types: ["acompletion", "completion"]
|
||||
callbacks: ["prometheus", "langfuse"]
|
||||
|
||||
model: gemini/gemini-2.5-pro
|
||||
|
|
|
|||
|
|
@ -1115,6 +1115,7 @@ class NewTeamRequest(TeamBase):
|
|||
team_member_budget: Optional[float] = (
|
||||
None # allow user to set a budget for all team members
|
||||
)
|
||||
team_member_key_duration: Optional[str] = None # e.g. "1d", "1w", "1m"
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
||||
|
|
@ -1157,6 +1158,7 @@ class UpdateTeamRequest(LiteLLMPydanticObjectBase):
|
|||
guardrails: Optional[List[str]] = None
|
||||
object_permission: Optional[LiteLLM_ObjectPermissionBase] = None
|
||||
team_member_budget: Optional[float] = None
|
||||
team_member_key_duration: Optional[str] = None
|
||||
|
||||
|
||||
class ResetTeamBudgetRequest(LiteLLMPydanticObjectBase):
|
||||
|
|
@ -2792,6 +2794,7 @@ LiteLLM_ManagementEndpoint_MetadataFields = [
|
|||
LiteLLM_ManagementEndpoint_MetadataFields_Premium = [
|
||||
"guardrails",
|
||||
"tags",
|
||||
"team_member_key_duration",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,9 @@ class JWTHandler:
|
|||
self.litellm_jwtauth.team_ids_jwt_field is not None
|
||||
and token.get(self.litellm_jwtauth.team_ids_jwt_field) is not None
|
||||
):
|
||||
|
||||
return token[self.litellm_jwtauth.team_ids_jwt_field]
|
||||
|
||||
return []
|
||||
|
||||
def get_end_user_id(
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
|
||||
if messages:
|
||||
for message in messages:
|
||||
message_text_content: Optional[
|
||||
List[str]
|
||||
] = self.get_content_for_message(message=message)
|
||||
message_text_content: Optional[List[str]] = (
|
||||
self.get_content_for_message(message=message)
|
||||
)
|
||||
if message_text_content is None:
|
||||
continue
|
||||
for text_content in message_text_content:
|
||||
|
|
@ -241,7 +241,7 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
self, response: BedrockGuardrailResponse
|
||||
) -> bool:
|
||||
"""
|
||||
By default always raise an exception when a guardrail intervention is detected.
|
||||
Only raise exception for "BLOCKED" actions, not for "ANONYMIZED" actions.
|
||||
|
||||
If `self.mask_request_content` or `self.mask_response_content` is set to `True`, then use the output from the guardrail to mask the request or response content.
|
||||
"""
|
||||
|
|
@ -250,11 +250,68 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
if self.mask_request_content or self.mask_response_content:
|
||||
return False
|
||||
|
||||
# if intervention, return True
|
||||
if response.get("action") == "GUARDRAIL_INTERVENED":
|
||||
return True
|
||||
|
||||
# if no intervention, return False
|
||||
if response.get("action") != "GUARDRAIL_INTERVENED":
|
||||
return False
|
||||
|
||||
# Check assessments to determine if any actions were BLOCKED (vs ANONYMIZED)
|
||||
assessments = response.get("assessments", [])
|
||||
if not assessments:
|
||||
return False
|
||||
|
||||
for assessment in assessments:
|
||||
# Check topic policy
|
||||
topic_policy = assessment.get("topicPolicy")
|
||||
if topic_policy:
|
||||
topics = topic_policy.get("topics", [])
|
||||
for topic in topics:
|
||||
if topic.get("action") == "BLOCKED":
|
||||
return True
|
||||
|
||||
# Check content policy
|
||||
content_policy = assessment.get("contentPolicy")
|
||||
if content_policy:
|
||||
filters = content_policy.get("filters", [])
|
||||
for filter_item in filters:
|
||||
if filter_item.get("action") == "BLOCKED":
|
||||
return True
|
||||
|
||||
# Check word policy
|
||||
word_policy = assessment.get("wordPolicy")
|
||||
if word_policy:
|
||||
custom_words = word_policy.get("customWords", [])
|
||||
for custom_word in custom_words:
|
||||
if custom_word.get("action") == "BLOCKED":
|
||||
return True
|
||||
managed_words = word_policy.get("managedWordLists", [])
|
||||
for managed_word in managed_words:
|
||||
if managed_word.get("action") == "BLOCKED":
|
||||
return True
|
||||
|
||||
# Check sensitive information policy
|
||||
sensitive_info_policy = assessment.get("sensitiveInformationPolicy")
|
||||
if sensitive_info_policy:
|
||||
pii_entities = sensitive_info_policy.get("piiEntities", [])
|
||||
if pii_entities:
|
||||
for pii_entity in pii_entities:
|
||||
if pii_entity.get("action") == "BLOCKED":
|
||||
return True
|
||||
regexes = sensitive_info_policy.get("regexes", [])
|
||||
if regexes:
|
||||
for regex in regexes:
|
||||
if regex.get("action") == "BLOCKED":
|
||||
return True
|
||||
|
||||
# Check contextual grounding policy
|
||||
contextual_grounding_policy = assessment.get("contextualGroundingPolicy")
|
||||
if contextual_grounding_policy:
|
||||
grounding_filters = contextual_grounding_policy.get("filters", [])
|
||||
for grounding_filter in grounding_filters:
|
||||
if grounding_filter.get("action") == "BLOCKED":
|
||||
return True
|
||||
|
||||
# If we got here, intervention occurred but no BLOCKED actions found
|
||||
# This means all actions were ANONYMIZED or NONE, so don't raise exception
|
||||
return False
|
||||
|
||||
@log_guardrail_information
|
||||
|
|
@ -300,11 +357,11 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
#########################################################
|
||||
########## 2. Update the messages with the guardrail response ##########
|
||||
#########################################################
|
||||
data[
|
||||
"messages"
|
||||
] = self._update_messages_with_updated_bedrock_guardrail_response(
|
||||
messages=new_messages,
|
||||
bedrock_guardrail_response=bedrock_guardrail_response,
|
||||
data["messages"] = (
|
||||
self._update_messages_with_updated_bedrock_guardrail_response(
|
||||
messages=new_messages,
|
||||
bedrock_guardrail_response=bedrock_guardrail_response,
|
||||
)
|
||||
)
|
||||
|
||||
#########################################################
|
||||
|
|
@ -354,11 +411,11 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
#########################################################
|
||||
########## 2. Update the messages with the guardrail response ##########
|
||||
#########################################################
|
||||
data[
|
||||
"messages"
|
||||
] = self._update_messages_with_updated_bedrock_guardrail_response(
|
||||
messages=new_messages,
|
||||
bedrock_guardrail_response=bedrock_guardrail_response,
|
||||
data["messages"] = (
|
||||
self._update_messages_with_updated_bedrock_guardrail_response(
|
||||
messages=new_messages,
|
||||
bedrock_guardrail_response=bedrock_guardrail_response,
|
||||
)
|
||||
)
|
||||
|
||||
#########################################################
|
||||
|
|
@ -408,11 +465,11 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
#########################################################
|
||||
########## 2. Update the messages with the guardrail response ##########
|
||||
#########################################################
|
||||
data[
|
||||
"messages"
|
||||
] = self._update_messages_with_updated_bedrock_guardrail_response(
|
||||
messages=new_messages,
|
||||
bedrock_guardrail_response=bedrock_guardrail_response,
|
||||
data["messages"] = (
|
||||
self._update_messages_with_updated_bedrock_guardrail_response(
|
||||
messages=new_messages,
|
||||
bedrock_guardrail_response=bedrock_guardrail_response,
|
||||
)
|
||||
)
|
||||
|
||||
#########################################################
|
||||
|
|
@ -440,21 +497,29 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
Returns:
|
||||
List of messages with content masked according to guardrail response
|
||||
"""
|
||||
# Skip processing if masking is not enabled
|
||||
if not (self.mask_request_content or self.mask_response_content):
|
||||
return messages
|
||||
|
||||
# Get masked texts from guardrail response
|
||||
masked_texts = self._extract_masked_texts_from_response(
|
||||
bedrock_guardrail_response
|
||||
)
|
||||
if not masked_texts:
|
||||
return messages
|
||||
|
||||
# Apply masking to messages using index tracking
|
||||
return self._apply_masking_to_messages(
|
||||
messages=messages, masked_texts=masked_texts
|
||||
)
|
||||
# If guardrail provided masked output, use it regardless of masking flags
|
||||
# because the guardrail has already determined this content needs anonymization
|
||||
if masked_texts:
|
||||
verbose_proxy_logger.debug(
|
||||
"Bedrock guardrail provided masked output, applying to messages"
|
||||
)
|
||||
return self._apply_masking_to_messages(
|
||||
messages=messages, masked_texts=masked_texts
|
||||
)
|
||||
|
||||
# If masking is enabled but no masked texts available, still try to apply
|
||||
# (this maintains backward compatibility for edge cases)
|
||||
if self.mask_request_content or self.mask_response_content:
|
||||
verbose_proxy_logger.debug(
|
||||
"Masking enabled but no masked output from guardrail, returning original messages"
|
||||
)
|
||||
|
||||
return messages
|
||||
|
||||
async def async_post_call_streaming_iterator_hook(
|
||||
self,
|
||||
|
|
|
|||
357
litellm/proxy/litellm.log
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
[92m18:10:09 - LiteLLM Router:INFO[0m: router.py:660 - Routing strategy: simple-shuffle
|
||||
[92m18:10:11 - LiteLLM Proxy:INFO[0m: utils.py:1317 - All necessary views exist!
|
||||
[92m18:10:11 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:10:11 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:10:23 - LiteLLM Proxy:INFO[0m: ui_sso.py:129 - Redirecting to SSO login for http://localhost:4000/sso/callback
|
||||
[92m18:10:27 - LiteLLM Proxy:INFO[0m: ui_sso.py:495 - Starting SSO callback
|
||||
[92m18:10:27 - LiteLLM Proxy:INFO[0m: ui_sso.py:550 - Redirecting to http://localhost:4000/sso/callback
|
||||
[92m18:10:28 - LiteLLM Proxy:INFO[0m: ui_sso.py:581 - SSO callback result: id='krrishd' email='krrishdholakia@gmail.com' first_name=None last_name=None display_name='a3f1c107-04dc-4c93-ae60-7f32eb4b05ce' picture=None provider=None team_ids=[]
|
||||
[92m18:10:28 - LiteLLM Proxy:INFO[0m: ui_sso.py:671 - user_defined_values for creating ui key: {'models': [], 'user_id': 'krrishd', 'user_email': 'krrishdholakia@gmail.com', 'max_budget': None, 'user_role': 'proxy_admin', 'budget_duration': None}
|
||||
[92m18:10:28 - LiteLLM Proxy:INFO[0m: utils.py:1856 - Data Inserted into Keys Table
|
||||
[92m18:10:28 - LiteLLM Proxy:INFO[0m: ui_sso.py:761 - user_id: krrishd; jwt_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoia3JyaXNoZCIsImtleSI6InNrLTVvOXVVc0ZaaTVBRFBiWERoanhCZlEiLCJ1c2VyX2VtYWlsIjoia3JyaXNoZGhvbGFraWFAZ21haWwuY29tIiwidXNlcl9yb2xlIjoicHJveHlfYWRtaW4iLCJsb2dpbl9tZXRob2QiOiJzc28iLCJwcmVtaXVtX3VzZXIiOnRydWUsImF1dGhfaGVhZGVyX25hbWUiOiJBdXRob3JpemF0aW9uIiwiZGlzYWJsZWRfbm9uX2FkbWluX3BlcnNvbmFsX2tleV9jcmVhdGlvbiI6ZmFsc2UsInNlcnZlcl9yb290X3BhdGgiOiIvIn0.OiZdFjZ2wiMhFbMCwu2cZYXh7oV5BB8Vta-Ysk5JBQU
|
||||
[92m18:10:28 - LiteLLM Proxy:INFO[0m: ui_sso.py:764 - Redirecting to http://localhost:4000/ui/?login=success
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: key_management_endpoints.py:2275 - Error in list_keys: Server disconnected without sending a response.
|
||||
Traceback (most recent call last):
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 394, in handle_async_request
|
||||
resp = await self._pool.handle_async_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
|
||||
raise exc from None
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
|
||||
response = await connection.handle_async_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 101, in handle_async_request
|
||||
return await self._connection.handle_async_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 143, in handle_async_request
|
||||
raise exc
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 113, in handle_async_request
|
||||
) = await self._receive_response_headers(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 186, in _receive_response_headers
|
||||
event = await self._receive_event(timeout=timeout)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 238, in _receive_event
|
||||
raise RemoteProtocolError(msg)
|
||||
httpcore.RemoteProtocolError: Server disconnected without sending a response.
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/management_endpoints/key_management_endpoints.py", line 2255, in list_keys
|
||||
response = await _list_key_helper(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/management_endpoints/key_management_endpoints.py", line 2434, in _list_key_helper
|
||||
total_count = await prisma_client.db.litellm_verificationtoken.count(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/actions.py", line 10157, in count
|
||||
resp = await self._client._execute(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_base_client.py", line 543, in _execute
|
||||
return await self._engine.query(builder.build(), tx_id=self._tx_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_query.py", line 402, in query
|
||||
return await self.request(
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_http.py", line 217, in request
|
||||
response = await self.session.request(method, url, **kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_async_http.py", line 26, in request
|
||||
return Response(await self.session.request(method, url, **kwargs))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1540, in request
|
||||
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
||||
response = await self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
||||
response = await self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
||||
response = await self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
||||
response = await transport.handle_async_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
|
||||
with map_httpcore_exceptions():
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 155, in __exit__
|
||||
self.gen.throw(typ, value, traceback)
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.RemoteProtocolError: Server disconnected without sending a response.
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: proxy_server.py:2730 - litellm.proxy_server.py::add_deployment() - Error getting new models from DB - All connection attempts failed
|
||||
Traceback (most recent call last):
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 394, in handle_async_request
|
||||
resp = await self._pool.handle_async_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
|
||||
raise exc from None
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
|
||||
response = await connection.handle_async_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 99, in handle_async_request
|
||||
raise exc
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 76, in handle_async_request
|
||||
stream = await self._connect(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 122, in _connect
|
||||
stream = await self._network_backend.connect_tcp(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_backends/auto.py", line 30, in connect_tcp
|
||||
return await self._backend.connect_tcp(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_backends/anyio.py", line 112, in connect_tcp
|
||||
with map_exceptions(exc_map):
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 155, in __exit__
|
||||
self.gen.throw(typ, value, traceback)
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions
|
||||
raise to_exc(exc) from exc
|
||||
httpcore.ConnectError: All connection attempts failed
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/proxy_server.py", line 2728, in _get_models_from_db
|
||||
new_models = await prisma_client.db.litellm_proxymodeltable.find_many()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/actions.py", line 2540, in find_many
|
||||
resp = await self._client._execute(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_base_client.py", line 543, in _execute
|
||||
return await self._engine.query(builder.build(), tx_id=self._tx_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_query.py", line 402, in query
|
||||
return await self.request(
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_http.py", line 217, in request
|
||||
response = await self.session.request(method, url, **kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_async_http.py", line 26, in request
|
||||
return Response(await self.session.request(method, url, **kwargs))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1540, in request
|
||||
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
||||
response = await self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
||||
response = await self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
||||
response = await self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
||||
response = await transport.handle_async_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
|
||||
with map_httpcore_exceptions():
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 155, in __exit__
|
||||
self.gen.throw(typ, value, traceback)
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.ConnectError: All connection attempts failed
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: utils.py:1404 - LiteLLM Prisma Client Exception get_generic_data: All connection attempts failed
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: utils.py:1404 - LiteLLM Prisma Client Exception get_generic_data: All connection attempts failed
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: proxy_server.py:2778 - litellm.proxy.proxy_server.py::ProxyConfig:add_deployment - All connection attempts failed
|
||||
Traceback (most recent call last):
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 394, in handle_async_request
|
||||
resp = await self._pool.handle_async_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
|
||||
raise exc from None
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
|
||||
response = await connection.handle_async_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 99, in handle_async_request
|
||||
raise exc
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 76, in handle_async_request
|
||||
stream = await self._connect(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 122, in _connect
|
||||
stream = await self._network_backend.connect_tcp(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_backends/auto.py", line 30, in connect_tcp
|
||||
return await self._backend.connect_tcp(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_backends/anyio.py", line 112, in connect_tcp
|
||||
with map_exceptions(exc_map):
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 155, in __exit__
|
||||
self.gen.throw(typ, value, traceback)
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions
|
||||
raise to_exc(exc) from exc
|
||||
httpcore.ConnectError: All connection attempts failed
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/proxy_server.py", line 2760, in add_deployment
|
||||
await self._update_llm_router(
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/proxy_server.py", line 2418, in _update_llm_router
|
||||
config_data = await proxy_config.get_config()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/proxy_server.py", line 1584, in get_config
|
||||
config = await self._update_config_from_db(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/proxy_server.py", line 2706, in _update_config_from_db
|
||||
responses = await asyncio.gather(*_tasks)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/db/log_db_metrics.py", line 99, in wrapper
|
||||
raise e
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/db/log_db_metrics.py", line 42, in wrapper
|
||||
result = await func(*args, **kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/backoff/_async.py", line 151, in retry
|
||||
ret = await target(*args, **kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/utils.py", line 1418, in get_generic_data
|
||||
raise e
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/utils.py", line 1392, in get_generic_data
|
||||
response = await self.db.litellm_config.find_first( # type: ignore
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/actions.py", line 11822, in find_first
|
||||
resp = await self._client._execute(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_base_client.py", line 543, in _execute
|
||||
return await self._engine.query(builder.build(), tx_id=self._tx_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_query.py", line 402, in query
|
||||
return await self.request(
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_http.py", line 217, in request
|
||||
response = await self.session.request(method, url, **kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_async_http.py", line 26, in request
|
||||
return Response(await self.session.request(method, url, **kwargs))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1540, in request
|
||||
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
||||
response = await self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
||||
response = await self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
||||
response = await self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
||||
response = await transport.handle_async_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
|
||||
with map_httpcore_exceptions():
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 155, in __exit__
|
||||
self.gen.throw(typ, value, traceback)
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.ConnectError: All connection attempts failed
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: utils.py:1404 - LiteLLM Prisma Client Exception get_generic_data: All connection attempts failed
|
||||
[92m18:10:30 - LiteLLM Proxy:ERROR[0m: utils.py:1404 - LiteLLM Prisma Client Exception get_generic_data: All connection attempts failed
|
||||
[92m18:10:30 - LiteLLM Proxy:INFO[0m: proxy_server.py:490 - Shutting down LiteLLM Proxy Server
|
||||
[92m18:11:47 - LiteLLM Router:INFO[0m: router.py:660 - Routing strategy: simple-shuffle
|
||||
[92m18:11:49 - LiteLLM Proxy:INFO[0m: utils.py:1317 - All necessary views exist!
|
||||
[92m18:11:50 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:11:50 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:12:00 - LiteLLM Proxy:ERROR[0m: proxy_server.py:2925 - litellm.proxy_server.py::get_credentials() - Error getting credentials from DB - Server disconnected without sending a response.
|
||||
Traceback (most recent call last):
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
|
||||
yield
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 394, in handle_async_request
|
||||
resp = await self._pool.handle_async_request(req)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
|
||||
raise exc from None
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
|
||||
response = await connection.handle_async_request(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/connection.py", line 101, in handle_async_request
|
||||
return await self._connection.handle_async_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 143, in handle_async_request
|
||||
raise exc
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 113, in handle_async_request
|
||||
) = await self._receive_response_headers(**kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 186, in _receive_response_headers
|
||||
event = await self._receive_event(timeout=timeout)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpcore/_async/http11.py", line 238, in _receive_event
|
||||
raise RemoteProtocolError(msg)
|
||||
httpcore.RemoteProtocolError: Server disconnected without sending a response.
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "/Users/krrishdholakia/Documents/litellm/litellm/proxy/proxy_server.py", line 2916, in get_credentials
|
||||
credentials = await prisma_client.db.litellm_credentialstable.find_many()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/actions.py", line 1502, in find_many
|
||||
resp = await self._client._execute(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_base_client.py", line 543, in _execute
|
||||
return await self._engine.query(builder.build(), tx_id=self._tx_id)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_query.py", line 402, in query
|
||||
return await self.request(
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/engine/_http.py", line 217, in request
|
||||
response = await self.session.request(method, url, **kwargs)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/prisma/_async_http.py", line 26, in request
|
||||
return Response(await self.session.request(method, url, **kwargs))
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1540, in request
|
||||
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
||||
response = await self._send_handling_auth(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
||||
response = await self._send_handling_redirects(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
||||
response = await self._send_single_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
||||
response = await transport.handle_async_request(request)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
|
||||
with map_httpcore_exceptions():
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 155, in __exit__
|
||||
self.gen.throw(typ, value, traceback)
|
||||
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
|
||||
raise mapped_exc(message) from exc
|
||||
httpx.RemoteProtocolError: Server disconnected without sending a response.
|
||||
[92m18:12:01 - LiteLLM Proxy:INFO[0m: proxy_server.py:490 - Shutting down LiteLLM Proxy Server
|
||||
[92m18:12:14 - LiteLLM Router:INFO[0m: router.py:660 - Routing strategy: simple-shuffle
|
||||
[92m18:12:16 - LiteLLM Proxy:INFO[0m: utils.py:1317 - All necessary views exist!
|
||||
[92m18:12:16 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:12:16 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:12:21 - LiteLLM Proxy:INFO[0m: ui_sso.py:129 - Redirecting to SSO login for http://localhost:4000/sso/callback
|
||||
[92m18:12:26 - LiteLLM Proxy:INFO[0m: ui_sso.py:495 - Starting SSO callback
|
||||
[92m18:12:26 - LiteLLM Proxy:INFO[0m: ui_sso.py:550 - Redirecting to http://localhost:4000/sso/callback
|
||||
[92m18:12:26 - LiteLLM Proxy:INFO[0m: ui_sso.py:581 - SSO callback result: id='krrishd' email='krrishdholakia@gmail.com' first_name=None last_name=None display_name='a3f1c107-04dc-4c93-ae60-7f32eb4b05ce' picture=None provider=None team_ids=[]
|
||||
[92m18:12:27 - LiteLLM Proxy:INFO[0m: ui_sso.py:672 - user_defined_values for creating ui key: {'models': [], 'user_id': 'krrishd', 'user_email': 'krrishdholakia@gmail.com', 'max_budget': None, 'user_role': 'proxy_admin', 'budget_duration': None}
|
||||
[92m18:12:27 - LiteLLM Proxy:INFO[0m: utils.py:1856 - Data Inserted into Keys Table
|
||||
[92m18:12:27 - LiteLLM Proxy:INFO[0m: ui_sso.py:762 - user_id: krrishd; jwt_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoia3JyaXNoZCIsImtleSI6InNrLUQzMEFpdW9lckU3YlMyakFXWVFLd1EiLCJ1c2VyX2VtYWlsIjoia3JyaXNoZGhvbGFraWFAZ21haWwuY29tIiwidXNlcl9yb2xlIjoicHJveHlfYWRtaW4iLCJsb2dpbl9tZXRob2QiOiJzc28iLCJwcmVtaXVtX3VzZXIiOnRydWUsImF1dGhfaGVhZGVyX25hbWUiOiJBdXRob3JpemF0aW9uIiwiZGlzYWJsZWRfbm9uX2FkbWluX3BlcnNvbmFsX2tleV9jcmVhdGlvbiI6ZmFsc2UsInNlcnZlcl9yb290X3BhdGgiOiIvIn0.EzYP86hw12J4WHLe6ZZz4YgVNGPnxM_PHqLjINH2_-U
|
||||
[92m18:12:27 - LiteLLM Proxy:INFO[0m: ui_sso.py:765 - Redirecting to http://localhost:4000/ui/?login=success
|
||||
[92m18:12:31 - LiteLLM Proxy:INFO[0m: proxy_server.py:490 - Shutting down LiteLLM Proxy Server
|
||||
[92m18:15:07 - LiteLLM Router:INFO[0m: router.py:660 - Routing strategy: simple-shuffle
|
||||
[92m18:15:09 - LiteLLM Proxy:INFO[0m: utils.py:1317 - All necessary views exist!
|
||||
[92m18:15:09 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:15:09 - LiteLLM Router:WARNING[0m: router.py:4862 - Error upserting deployment: vertex_project, and vertex_location must be set in litellm_params for pass-through endpoints., ignoring and continuing with other deployments.
|
||||
[92m18:15:17 - LiteLLM Proxy:INFO[0m: utils.py:1916 - Data Inserted into Config Table
|
||||
[92m18:15:28 - LiteLLM Proxy:INFO[0m: ui_sso.py:129 - Redirecting to SSO login for http://localhost:4000/sso/callback
|
||||
[92m18:15:32 - LiteLLM Proxy:INFO[0m: ui_sso.py:495 - Starting SSO callback
|
||||
[92m18:15:32 - LiteLLM Proxy:INFO[0m: ui_sso.py:550 - Redirecting to http://localhost:4000/sso/callback
|
||||
[92m18:15:32 - LiteLLM Proxy:INFO[0m: ui_sso.py:581 - SSO callback result: id='krrishd' email='krrishdholakia@gmail.com' first_name=None last_name=None display_name='a3f1c107-04dc-4c93-ae60-7f32eb4b05ce' picture=None provider=None team_ids=[]
|
||||
[92m18:15:37 - LiteLLM Proxy:INFO[0m: proxy_server.py:490 - Shutting down LiteLLM Proxy Server
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
Endpoints for managing callbacks
|
||||
"""
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from litellm.litellm_core_utils.logging_callback_manager import CallbacksByType
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/callbacks/list",
|
||||
tags=["Logging Callbacks"],
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_model=CallbacksByType,
|
||||
)
|
||||
async def list_callbacks():
|
||||
"""
|
||||
View List of Active Logging Callbacks
|
||||
"""
|
||||
from litellm import logging_callback_manager
|
||||
|
||||
# Get callbacks organized by type using the callback manager utility
|
||||
callbacks_by_type = logging_callback_manager.get_callbacks_by_type()
|
||||
|
||||
return callbacks_by_type
|
||||
|
|
@ -512,6 +512,20 @@ async def generate_key_fn( # noqa: PLR0915
|
|||
},
|
||||
)
|
||||
|
||||
# APPLY ENTERPRISE KEY MANAGEMENT PARAMS
|
||||
try:
|
||||
from litellm_enterprise.proxy.management_endpoints.key_management_endpoints import (
|
||||
apply_enterprise_key_management_params,
|
||||
)
|
||||
|
||||
data = apply_enterprise_key_management_params(data, team_table)
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.info(
|
||||
"litellm.proxy.proxy_server.generate_key_fn(): Enterprise key management params not applied - {}".format(
|
||||
str(e)
|
||||
)
|
||||
)
|
||||
|
||||
# TODO: @ishaan-jaff: Migrate all budget tracking to use LiteLLM_BudgetTable
|
||||
_budget_id = data.budget_id
|
||||
if prisma_client is not None and data.soft_budget is not None:
|
||||
|
|
@ -536,7 +550,7 @@ async def generate_key_fn( # noqa: PLR0915
|
|||
# ADD METADATA FIELDS
|
||||
# Set Management Endpoint Metadata Fields
|
||||
for field in LiteLLM_ManagementEndpoint_MetadataFields_Premium:
|
||||
if getattr(data, field) is not None:
|
||||
if getattr(data, field, None) is not None:
|
||||
_set_object_metadata_field(
|
||||
object_data=data,
|
||||
field_name=field,
|
||||
|
|
@ -589,9 +603,9 @@ async def generate_key_fn( # noqa: PLR0915
|
|||
request_type="key", **data_json, table_name="key"
|
||||
)
|
||||
|
||||
response[
|
||||
"soft_budget"
|
||||
] = data.soft_budget # include the user-input soft budget in the response
|
||||
response["soft_budget"] = (
|
||||
data.soft_budget
|
||||
) # include the user-input soft budget in the response
|
||||
|
||||
response = GenerateKeyResponse(**response)
|
||||
|
||||
|
|
@ -667,9 +681,9 @@ async def _set_object_permission(
|
|||
data=data_json["object_permission"],
|
||||
)
|
||||
)
|
||||
data_json[
|
||||
"object_permission_id"
|
||||
] = created_object_permission.object_permission_id
|
||||
data_json["object_permission_id"] = (
|
||||
created_object_permission.object_permission_id
|
||||
)
|
||||
|
||||
# delete the object_permission from the data_json
|
||||
data_json.pop("object_permission")
|
||||
|
|
@ -1652,10 +1666,10 @@ async def delete_verification_tokens(
|
|||
try:
|
||||
if prisma_client:
|
||||
tokens = [_hash_token_if_needed(token=key) for key in tokens]
|
||||
_keys_being_deleted: List[
|
||||
LiteLLM_VerificationToken
|
||||
] = await prisma_client.db.litellm_verificationtoken.find_many(
|
||||
where={"token": {"in": tokens}}
|
||||
_keys_being_deleted: List[LiteLLM_VerificationToken] = (
|
||||
await prisma_client.db.litellm_verificationtoken.find_many(
|
||||
where={"token": {"in": tokens}}
|
||||
)
|
||||
)
|
||||
|
||||
if len(_keys_being_deleted) == 0:
|
||||
|
|
@ -1763,9 +1777,9 @@ async def _rotate_master_key(
|
|||
from litellm.proxy.proxy_server import proxy_config
|
||||
|
||||
try:
|
||||
models: Optional[
|
||||
List
|
||||
] = await prisma_client.db.litellm_proxymodeltable.find_many()
|
||||
models: Optional[List] = (
|
||||
await prisma_client.db.litellm_proxymodeltable.find_many()
|
||||
)
|
||||
except Exception:
|
||||
models = None
|
||||
# 2. process model table
|
||||
|
|
@ -2057,11 +2071,11 @@ async def validate_key_list_check(
|
|||
param="user_id",
|
||||
code=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
complete_user_info_db_obj: Optional[
|
||||
BaseModel
|
||||
] = await prisma_client.db.litellm_usertable.find_unique(
|
||||
where={"user_id": user_api_key_dict.user_id},
|
||||
include={"organization_memberships": True},
|
||||
complete_user_info_db_obj: Optional[BaseModel] = (
|
||||
await prisma_client.db.litellm_usertable.find_unique(
|
||||
where={"user_id": user_api_key_dict.user_id},
|
||||
include={"organization_memberships": True},
|
||||
)
|
||||
)
|
||||
|
||||
if complete_user_info_db_obj is None:
|
||||
|
|
@ -2147,10 +2161,10 @@ async def get_admin_team_ids(
|
|||
if complete_user_info is None:
|
||||
return []
|
||||
# Get all teams that user is an admin of
|
||||
teams: Optional[
|
||||
List[BaseModel]
|
||||
] = await prisma_client.db.litellm_teamtable.find_many(
|
||||
where={"team_id": {"in": complete_user_info.teams}}
|
||||
teams: Optional[List[BaseModel]] = (
|
||||
await prisma_client.db.litellm_teamtable.find_many(
|
||||
where={"team_id": {"in": complete_user_info.teams}}
|
||||
)
|
||||
)
|
||||
if teams is None:
|
||||
return []
|
||||
|
|
@ -2403,12 +2417,14 @@ async def _list_key_helper(
|
|||
where=where, # type: ignore
|
||||
skip=skip, # type: ignore
|
||||
take=size, # type: ignore
|
||||
order=order_by
|
||||
if order_by
|
||||
else [
|
||||
{"created_at": "desc"},
|
||||
{"token": "desc"}, # fallback sort
|
||||
],
|
||||
order=(
|
||||
order_by
|
||||
if order_by
|
||||
else [
|
||||
{"created_at": "desc"},
|
||||
{"token": "desc"}, # fallback sort
|
||||
]
|
||||
),
|
||||
include={"object_permission": True},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from fastapi import (
|
|||
Response,
|
||||
)
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.proxy._types import (
|
||||
|
|
@ -361,6 +362,18 @@ async def create_user(
|
|||
# Create user in database
|
||||
user_id = user.userName or str(uuid.uuid4())
|
||||
metadata = _build_scim_metadata(user_data["given_name"], user_data["family_name"])
|
||||
|
||||
default_role: Optional[
|
||||
Literal[
|
||||
LitellmUserRoles.PROXY_ADMIN,
|
||||
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
|
||||
LitellmUserRoles.INTERNAL_USER,
|
||||
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
|
||||
]
|
||||
] = LitellmUserRoles.INTERNAL_USER_VIEW_ONLY
|
||||
if litellm.default_internal_user_params:
|
||||
default_role = litellm.default_internal_user_params.get("user_role")
|
||||
|
||||
new_user_request = NewUserRequest(
|
||||
user_id=user_id,
|
||||
user_email=user_data["user_email"],
|
||||
|
|
@ -368,6 +381,7 @@ async def create_user(
|
|||
teams=user_data["teams"],
|
||||
metadata=metadata,
|
||||
auto_create_key=False,
|
||||
user_role=default_role,
|
||||
)
|
||||
|
||||
# Check if user with email already exists and update if found
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
from typing import Dict, Union
|
||||
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
||||
|
||||
def check_is_admin_only_access(ui_access_mode: str) -> bool:
|
||||
def check_is_admin_only_access(ui_access_mode: Union[str, Dict]) -> bool:
|
||||
"""Checks ui access mode is admin_only"""
|
||||
return ui_access_mode == "admin_only"
|
||||
if isinstance(ui_access_mode, str):
|
||||
return ui_access_mode == "admin_only"
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def has_admin_ui_access(user_role: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ async def new_team( # noqa: PLR0915
|
|||
- guardrails: Optional[List[str]] - Guardrails for the team. [Docs](https://docs.litellm.ai/docs/proxy/guardrails)
|
||||
- object_permission: Optional[LiteLLM_ObjectPermissionBase] - team-specific object permission. Example - {"vector_stores": ["vector_store_1", "vector_store_2"]}. IF null or {} then no object permission.
|
||||
- team_member_budget: Optional[float] - The maximum budget allocated to an individual team member.
|
||||
- team_member_key_duration: Optional[str] - The duration for a team member's key. e.g. "1d", "1w", "1mo"
|
||||
|
||||
Returns:
|
||||
- team_id: (str) Unique team id - used for tracking spend across multiple keys for same team id.
|
||||
|
|
@ -688,6 +689,7 @@ async def update_team(
|
|||
- guardrails: Optional[List[str]] - Guardrails for the team. [Docs](https://docs.litellm.ai/docs/proxy/guardrails)
|
||||
- object_permission: Optional[LiteLLM_ObjectPermissionBase] - team-specific object permission. Example - {"vector_stores": ["vector_store_1", "vector_store_2"]}. IF null or {} then no object permission.
|
||||
- team_member_budget: Optional[float] - The maximum budget allocated to an individual team member.
|
||||
- team_member_key_duration: Optional[str] - The duration for a team member's key. e.g. "1d", "1w", "1mo"
|
||||
Example - update team TPM Limit
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -145,7 +145,11 @@ async def google_login(request: Request): # noqa: PLR0915
|
|||
return HTMLResponse(content=html_form, status_code=200)
|
||||
|
||||
|
||||
def generic_response_convertor(response, jwt_handler: JWTHandler):
|
||||
def generic_response_convertor(
|
||||
response,
|
||||
jwt_handler: JWTHandler,
|
||||
sso_jwt_handler: Optional[JWTHandler] = None,
|
||||
):
|
||||
generic_user_id_attribute_name = os.getenv(
|
||||
"GENERIC_USER_ID_ATTRIBUTE", "preferred_username"
|
||||
)
|
||||
|
|
@ -171,6 +175,13 @@ def generic_response_convertor(response, jwt_handler: JWTHandler):
|
|||
f" generic_user_id_attribute_name: {generic_user_id_attribute_name}\n generic_user_email_attribute_name: {generic_user_email_attribute_name}"
|
||||
)
|
||||
|
||||
all_teams = []
|
||||
if sso_jwt_handler is not None:
|
||||
team_ids = sso_jwt_handler.get_team_ids_from_jwt(cast(dict, response))
|
||||
all_teams.extend(team_ids)
|
||||
|
||||
team_ids = jwt_handler.get_team_ids_from_jwt(cast(dict, response))
|
||||
all_teams.extend(team_ids)
|
||||
return CustomOpenID(
|
||||
id=response.get(generic_user_id_attribute_name),
|
||||
display_name=response.get(generic_user_display_name_attribute_name),
|
||||
|
|
@ -178,20 +189,24 @@ def generic_response_convertor(response, jwt_handler: JWTHandler):
|
|||
first_name=response.get(generic_user_first_name_attribute_name),
|
||||
last_name=response.get(generic_user_last_name_attribute_name),
|
||||
provider=response.get(generic_provider_attribute_name),
|
||||
team_ids=jwt_handler.get_team_ids_from_jwt(cast(dict, response)),
|
||||
team_ids=all_teams,
|
||||
)
|
||||
|
||||
|
||||
async def get_generic_sso_response(
|
||||
request: Request,
|
||||
jwt_handler: JWTHandler,
|
||||
sso_jwt_handler: Optional[
|
||||
JWTHandler
|
||||
], # sso specific jwt handler - used for restricted sso group access control
|
||||
generic_client_id: str,
|
||||
redirect_url: str,
|
||||
) -> Union[OpenID, dict]:
|
||||
) -> Tuple[Union[OpenID, dict], Optional[dict]]: # return received response
|
||||
# make generic sso provider
|
||||
from fastapi_sso.sso.base import DiscoveryDocument
|
||||
from fastapi_sso.sso.generic import create_provider
|
||||
|
||||
received_response: Optional[dict] = None
|
||||
generic_client_secret = os.getenv("GENERIC_CLIENT_SECRET", None)
|
||||
generic_scope = os.getenv("GENERIC_SCOPE", "openid email profile").split(" ")
|
||||
generic_authorization_endpoint = os.getenv("GENERIC_AUTHORIZATION_ENDPOINT", None)
|
||||
|
|
@ -242,9 +257,12 @@ async def get_generic_sso_response(
|
|||
)
|
||||
|
||||
def response_convertor(response, client):
|
||||
nonlocal received_response # return for user debugging
|
||||
received_response = response
|
||||
return generic_response_convertor(
|
||||
response=response,
|
||||
jwt_handler=jwt_handler,
|
||||
sso_jwt_handler=sso_jwt_handler,
|
||||
)
|
||||
|
||||
SSOProvider = create_provider(
|
||||
|
|
@ -284,7 +302,7 @@ async def get_generic_sso_response(
|
|||
)
|
||||
raise e
|
||||
verbose_proxy_logger.debug("generic result: %s", result)
|
||||
return result or {}
|
||||
return result or {}, received_response
|
||||
|
||||
|
||||
async def create_team_member_add_task(team_id, user_info):
|
||||
|
|
@ -480,6 +498,8 @@ async def check_and_update_if_proxy_admin_id(
|
|||
async def auth_callback(request: Request): # noqa: PLR0915
|
||||
"""Verify login"""
|
||||
verbose_proxy_logger.info("Starting SSO callback")
|
||||
from litellm.proxy._types import LiteLLM_JWTAuth
|
||||
from litellm.proxy.auth.handle_jwt import JWTHandler
|
||||
from litellm.proxy.management_endpoints.key_management_endpoints import (
|
||||
generate_key_helper_fn,
|
||||
)
|
||||
|
|
@ -490,7 +510,6 @@ async def auth_callback(request: Request): # noqa: PLR0915
|
|||
premium_user,
|
||||
prisma_client,
|
||||
proxy_logging_obj,
|
||||
ui_access_mode,
|
||||
user_api_key_cache,
|
||||
user_custom_sso,
|
||||
)
|
||||
|
|
@ -502,9 +521,25 @@ async def auth_callback(request: Request): # noqa: PLR0915
|
|||
status_code=500, detail=CommonProxyErrors.db_not_connected_error.value
|
||||
)
|
||||
|
||||
sso_jwt_handler: Optional[JWTHandler] = None
|
||||
ui_access_mode = general_settings.get("ui_access_mode", None)
|
||||
if ui_access_mode is not None and isinstance(ui_access_mode, dict):
|
||||
sso_jwt_handler = JWTHandler()
|
||||
sso_jwt_handler.update_environment(
|
||||
prisma_client=prisma_client,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
litellm_jwtauth=LiteLLM_JWTAuth(
|
||||
team_ids_jwt_field=general_settings.get("ui_access_mode", {}).get(
|
||||
"sso_group_jwt_field", None
|
||||
),
|
||||
),
|
||||
leeway=0,
|
||||
)
|
||||
|
||||
microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None)
|
||||
google_client_id = os.getenv("GOOGLE_CLIENT_ID", None)
|
||||
generic_client_id = os.getenv("GENERIC_CLIENT_ID", None)
|
||||
received_response: Optional[dict] = None
|
||||
# get url from request
|
||||
if master_key is None:
|
||||
raise ProxyException(
|
||||
|
|
@ -532,11 +567,12 @@ async def auth_callback(request: Request): # noqa: PLR0915
|
|||
redirect_url=redirect_url,
|
||||
)
|
||||
elif generic_client_id is not None:
|
||||
result = await get_generic_sso_response(
|
||||
result, received_response = await get_generic_sso_response(
|
||||
request=request,
|
||||
jwt_handler=jwt_handler,
|
||||
generic_client_id=generic_client_id,
|
||||
redirect_url=redirect_url,
|
||||
sso_jwt_handler=sso_jwt_handler,
|
||||
)
|
||||
|
||||
if result is None:
|
||||
|
|
@ -547,6 +583,7 @@ async def auth_callback(request: Request): # noqa: PLR0915
|
|||
|
||||
# User is Authe'd in - generate key for the UI to access Proxy
|
||||
verbose_proxy_logger.info(f"SSO callback result: {result}")
|
||||
|
||||
user_email: Optional[str] = getattr(result, "email", None)
|
||||
user_id: Optional[str] = getattr(result, "id", None) if result is not None else None
|
||||
|
||||
|
|
@ -612,6 +649,13 @@ async def auth_callback(request: Request): # noqa: PLR0915
|
|||
budget_duration=internal_user_budget_duration,
|
||||
)
|
||||
|
||||
# (IF SET) Verify user is in restricted SSO group
|
||||
SSOAuthenticationHandler.verify_user_in_restricted_sso_group(
|
||||
general_settings=general_settings,
|
||||
result=result,
|
||||
received_response=received_response,
|
||||
)
|
||||
|
||||
user_info = await get_user_info_from_db(
|
||||
result=result,
|
||||
prisma_client=prisma_client,
|
||||
|
|
@ -1055,6 +1099,44 @@ class SSOAuthenticationHandler:
|
|||
sso_teams = getattr(result, "team_ids", [])
|
||||
await add_missing_team_member(user_info=user_info, sso_teams=sso_teams)
|
||||
|
||||
@staticmethod
|
||||
def verify_user_in_restricted_sso_group(
|
||||
general_settings: Dict,
|
||||
result: Optional[Union[CustomOpenID, OpenID, dict]],
|
||||
received_response: Optional[dict],
|
||||
) -> Literal[True]:
|
||||
"""
|
||||
when ui_access_mode.type == "restricted_sso_group":
|
||||
|
||||
- result.team_ids should contain the restricted_sso_group
|
||||
- if not, raise a ProxyException
|
||||
- if so, return True
|
||||
- if result.team_ids is None, return False
|
||||
- if result.team_ids is an empty list, return False
|
||||
- if result.team_ids is a list, return True if the restricted_sso_group is in the list, otherwise return False
|
||||
"""
|
||||
|
||||
ui_access_mode = cast(
|
||||
Optional[Union[Dict, str]], general_settings.get("ui_access_mode")
|
||||
)
|
||||
|
||||
if ui_access_mode is None:
|
||||
return True
|
||||
if isinstance(ui_access_mode, str):
|
||||
return True
|
||||
team_ids = getattr(result, "team_ids", [])
|
||||
|
||||
if ui_access_mode.get("type") == "restricted_sso_group":
|
||||
restricted_sso_group = ui_access_mode.get("restricted_sso_group")
|
||||
if restricted_sso_group not in team_ids:
|
||||
raise ProxyException(
|
||||
message=f"User is not in the restricted SSO group: {restricted_sso_group}. User groups: {team_ids}. Received SSO response: {received_response}",
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param="restricted_sso_group",
|
||||
code=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def create_litellm_team_from_sso_group(
|
||||
litellm_team_id: str,
|
||||
|
|
@ -1551,7 +1633,29 @@ async def debug_sso_callback(request: Request):
|
|||
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from litellm.proxy.proxy_server import jwt_handler
|
||||
from litellm.proxy._types import LiteLLM_JWTAuth
|
||||
from litellm.proxy.auth.handle_jwt import JWTHandler
|
||||
from litellm.proxy.proxy_server import (
|
||||
general_settings,
|
||||
jwt_handler,
|
||||
prisma_client,
|
||||
user_api_key_cache,
|
||||
)
|
||||
|
||||
sso_jwt_handler: Optional[JWTHandler] = None
|
||||
ui_access_mode = general_settings.get("ui_access_mode", None)
|
||||
if ui_access_mode is not None and isinstance(ui_access_mode, dict):
|
||||
sso_jwt_handler = JWTHandler()
|
||||
sso_jwt_handler.update_environment(
|
||||
prisma_client=prisma_client,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
litellm_jwtauth=LiteLLM_JWTAuth(
|
||||
team_ids_jwt_field=general_settings.get("ui_access_mode", {}).get(
|
||||
"sso_group_jwt_field", None
|
||||
),
|
||||
),
|
||||
leeway=0,
|
||||
)
|
||||
|
||||
microsoft_client_id = os.getenv("MICROSOFT_CLIENT_ID", None)
|
||||
google_client_id = os.getenv("GOOGLE_CLIENT_ID", None)
|
||||
|
|
@ -1580,11 +1684,12 @@ async def debug_sso_callback(request: Request):
|
|||
)
|
||||
|
||||
elif generic_client_id is not None:
|
||||
result = await get_generic_sso_response(
|
||||
result, _ = await get_generic_sso_response(
|
||||
request=request,
|
||||
jwt_handler=jwt_handler,
|
||||
generic_client_id=generic_client_id,
|
||||
redirect_url=redirect_url,
|
||||
sso_jwt_handler=sso_jwt_handler,
|
||||
)
|
||||
|
||||
# If result is None, return a basic error message
|
||||
|
|
|
|||
|
|
@ -272,26 +272,11 @@ class VertexPassthroughLoggingHandler:
|
|||
return None
|
||||
if len(parsed_chunks) == 0:
|
||||
return None
|
||||
litellm_custom_stream_wrapper = litellm.CustomStreamWrapper(
|
||||
completion_stream=vertex_iterator,
|
||||
model=model,
|
||||
logging_obj=litellm_logging_obj,
|
||||
custom_llm_provider="vertex_ai",
|
||||
)
|
||||
all_openai_chunks = []
|
||||
for parsed_chunk in parsed_chunks:
|
||||
try:
|
||||
litellm_chunk = litellm_custom_stream_wrapper.chunk_creator(
|
||||
chunk=parsed_chunk
|
||||
)
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(
|
||||
"Error creating litellm chunk from vertex passthrough endpoint: %s",
|
||||
str(e),
|
||||
)
|
||||
if parsed_chunk is None:
|
||||
continue
|
||||
if litellm_chunk is not None:
|
||||
all_openai_chunks.append(litellm_chunk)
|
||||
all_openai_chunks.append(parsed_chunk)
|
||||
|
||||
complete_streaming_response = litellm.stream_chunk_builder(
|
||||
chunks=all_openai_chunks
|
||||
|
|
|
|||
|
|
@ -442,8 +442,18 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
|
|||
_parsed_body: Optional[dict] = None,
|
||||
litellm_call_id: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Filter out litellm params from the request body
|
||||
"""
|
||||
from litellm.types.utils import all_litellm_params
|
||||
|
||||
_parsed_body = _parsed_body or {}
|
||||
_litellm_metadata: Optional[dict] = _parsed_body.pop("litellm_metadata", None)
|
||||
|
||||
litellm_params_in_body = {}
|
||||
for k in all_litellm_params:
|
||||
if k in _parsed_body:
|
||||
litellm_params_in_body[k] = _parsed_body.pop(k, None)
|
||||
|
||||
_metadata = dict(
|
||||
StandardLoggingUserAPIKeyMetadata(
|
||||
user_api_key_hash=user_api_key_dict.api_key,
|
||||
|
|
@ -457,9 +467,15 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
|
|||
user_api_key_request_route=user_api_key_dict.request_route,
|
||||
)
|
||||
)
|
||||
|
||||
_metadata["user_api_key"] = user_api_key_dict.api_key
|
||||
if _litellm_metadata:
|
||||
_metadata.update(_litellm_metadata)
|
||||
|
||||
litellm_metadata = litellm_params_in_body.pop("litellm_metadata", None)
|
||||
metadata = litellm_params_in_body.pop("metadata", None)
|
||||
if litellm_metadata:
|
||||
_metadata.update(litellm_metadata)
|
||||
if metadata:
|
||||
_metadata.update(metadata)
|
||||
|
||||
_metadata = _update_metadata_with_tags_in_header(
|
||||
request=request,
|
||||
|
|
@ -468,12 +484,13 @@ class HttpPassThroughEndpointHelpers(BasePassthroughUtils):
|
|||
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
**litellm_params_in_body,
|
||||
"metadata": _metadata,
|
||||
"proxy_server_request": {
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"body": copy.copy(_parsed_body), # use copy instead of deepcopy
|
||||
}
|
||||
"url": str(request.url),
|
||||
"method": request.method,
|
||||
"body": copy.copy(_parsed_body), # use copy instead of deepcopy
|
||||
},
|
||||
},
|
||||
"call_type": "pass_through_endpoint",
|
||||
"litellm_call_id": litellm_call_id,
|
||||
|
|
@ -629,6 +646,7 @@ async def pass_through_request( # noqa: PLR0915
|
|||
request=request,
|
||||
logging_obj=logging_obj,
|
||||
)
|
||||
|
||||
# done for supporting 'parallel_request_limiter.py' with pass-through endpoints
|
||||
logging_obj.update_environment_variables(
|
||||
model="unknown",
|
||||
|
|
@ -962,7 +980,7 @@ class InitPassThroughEndpointHelpers:
|
|||
|
||||
app.add_api_route(
|
||||
path=path,
|
||||
endpoint=create_pass_through_route(
|
||||
endpoint=create_pass_through_route( # type: ignore
|
||||
path,
|
||||
target,
|
||||
custom_headers,
|
||||
|
|
@ -996,7 +1014,7 @@ class InitPassThroughEndpointHelpers:
|
|||
|
||||
app.add_api_route(
|
||||
path=wildcard_path,
|
||||
endpoint=create_pass_through_route(
|
||||
endpoint=create_pass_through_route( # type: ignore
|
||||
path,
|
||||
target,
|
||||
custom_headers,
|
||||
|
|
@ -1024,6 +1042,7 @@ async def initialize_pass_through_endpoints(
|
|||
None
|
||||
"""
|
||||
import uuid
|
||||
|
||||
verbose_proxy_logger.debug("initializing pass through endpoints")
|
||||
from litellm.proxy._types import CommonProxyErrors, LiteLLMRoutes
|
||||
from litellm.proxy.proxy_server import app, premium_user
|
||||
|
|
@ -1031,11 +1050,11 @@ async def initialize_pass_through_endpoints(
|
|||
for endpoint in pass_through_endpoints:
|
||||
if isinstance(endpoint, PassThroughGenericEndpoint):
|
||||
endpoint = endpoint.model_dump()
|
||||
|
||||
|
||||
# Auto-generate ID for backwards compatibility if not present
|
||||
if endpoint.get("id") is None:
|
||||
endpoint["id"] = str(uuid.uuid4())
|
||||
|
||||
|
||||
_target = endpoint.get("target", None)
|
||||
_path: Optional[str] = endpoint.get("path", None)
|
||||
if _path is None:
|
||||
|
|
@ -1062,7 +1081,9 @@ async def initialize_pass_through_endpoints(
|
|||
continue
|
||||
|
||||
# Add exact path route
|
||||
verbose_proxy_logger.debug("Initializing pass through endpoint: %s (ID: %s)", _path, endpoint.get("id"))
|
||||
verbose_proxy_logger.debug(
|
||||
"Initializing pass through endpoint: %s (ID: %s)", _path, endpoint.get("id")
|
||||
)
|
||||
InitPassThroughEndpointHelpers.add_exact_path_route(
|
||||
app=app,
|
||||
path=_path,
|
||||
|
|
@ -1087,7 +1108,9 @@ async def initialize_pass_through_endpoints(
|
|||
cost_per_request=endpoint.get("cost_per_request", None),
|
||||
)
|
||||
|
||||
verbose_proxy_logger.debug("Added new pass through endpoint: %s (ID: %s)", _path, endpoint.get("id"))
|
||||
verbose_proxy_logger.debug(
|
||||
"Added new pass through endpoint: %s (ID: %s)", _path, endpoint.get("id")
|
||||
)
|
||||
|
||||
|
||||
async def _get_pass_through_endpoints_from_db(
|
||||
|
|
@ -1117,12 +1140,10 @@ async def _get_pass_through_endpoints_from_db(
|
|||
returned_endpoints.append(endpoint)
|
||||
else:
|
||||
# Find specific endpoint by ID
|
||||
found_endpoint = _find_endpoint_by_id(
|
||||
pass_through_endpoint_data, endpoint_id
|
||||
)
|
||||
found_endpoint = _find_endpoint_by_id(pass_through_endpoint_data, endpoint_id)
|
||||
if found_endpoint is not None:
|
||||
returned_endpoints.append(found_endpoint)
|
||||
|
||||
|
||||
return returned_endpoints
|
||||
|
||||
|
||||
|
|
@ -1182,22 +1203,22 @@ async def update_pass_through_endpoints(
|
|||
)
|
||||
|
||||
# Find the endpoint to update
|
||||
found_endpoint = _find_endpoint_by_id(
|
||||
pass_through_endpoint_data, endpoint_id
|
||||
)
|
||||
|
||||
found_endpoint = _find_endpoint_by_id(pass_through_endpoint_data, endpoint_id)
|
||||
|
||||
if found_endpoint is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail={
|
||||
"error": f"Endpoint with ID '{endpoint_id}' not found"
|
||||
},
|
||||
detail={"error": f"Endpoint with ID '{endpoint_id}' not found"},
|
||||
)
|
||||
|
||||
# Find the index for updating the list
|
||||
endpoint_index = None
|
||||
for idx, endpoint in enumerate(pass_through_endpoint_data):
|
||||
_endpoint = PassThroughGenericEndpoint(**endpoint) if isinstance(endpoint, dict) else endpoint
|
||||
_endpoint = (
|
||||
PassThroughGenericEndpoint(**endpoint)
|
||||
if isinstance(endpoint, dict)
|
||||
else endpoint
|
||||
)
|
||||
if _endpoint.id == endpoint_id:
|
||||
endpoint_index = idx
|
||||
break
|
||||
|
|
@ -1212,20 +1233,20 @@ async def update_pass_through_endpoints(
|
|||
|
||||
# Get the update data as dict, excluding None values for partial updates
|
||||
update_data = data.model_dump(exclude_none=True)
|
||||
|
||||
|
||||
# Start with existing endpoint data
|
||||
endpoint_dict = found_endpoint.model_dump()
|
||||
|
||||
|
||||
# Update with new data (only non-None values)
|
||||
endpoint_dict.update(update_data)
|
||||
|
||||
|
||||
# Preserve existing ID if not provided in update and endpoint has ID
|
||||
if "id" not in update_data and found_endpoint.id is not None:
|
||||
endpoint_dict["id"] = found_endpoint.id
|
||||
|
||||
|
||||
# Create updated endpoint object
|
||||
updated_endpoint = PassThroughGenericEndpoint(**endpoint_dict)
|
||||
|
||||
|
||||
# Update the list
|
||||
pass_through_endpoint_data[endpoint_index] = endpoint_dict
|
||||
|
||||
|
|
@ -1239,7 +1260,9 @@ async def update_pass_through_endpoints(
|
|||
data=updated_data, user_api_key_dict=user_api_key_dict
|
||||
)
|
||||
|
||||
return PassThroughEndpointResponse(endpoints=[updated_endpoint] if updated_endpoint else [])
|
||||
return PassThroughEndpointResponse(
|
||||
endpoints=[updated_endpoint] if updated_endpoint else []
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
|
|
@ -1335,10 +1358,8 @@ async def delete_pass_through_endpoints(
|
|||
)
|
||||
|
||||
# Find the endpoint to delete
|
||||
found_endpoint = _find_endpoint_by_id(
|
||||
pass_through_endpoint_data, endpoint_id
|
||||
)
|
||||
|
||||
found_endpoint = _find_endpoint_by_id(pass_through_endpoint_data, endpoint_id)
|
||||
|
||||
if found_endpoint is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
|
|
@ -1348,11 +1369,15 @@ async def delete_pass_through_endpoints(
|
|||
)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# Find the index for deleting from the list
|
||||
endpoint_index = None
|
||||
for idx, endpoint in enumerate(pass_through_endpoint_data):
|
||||
_endpoint = PassThroughGenericEndpoint(**endpoint) if isinstance(endpoint, dict) else endpoint
|
||||
_endpoint = (
|
||||
PassThroughGenericEndpoint(**endpoint)
|
||||
if isinstance(endpoint, dict)
|
||||
else endpoint
|
||||
)
|
||||
if _endpoint.id == endpoint_id:
|
||||
endpoint_index = idx
|
||||
break
|
||||
|
|
@ -1364,7 +1389,7 @@ async def delete_pass_through_endpoints(
|
|||
"error": f"Could not find index for endpoint with ID '{endpoint_id}'"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# Remove the endpoint
|
||||
pass_through_endpoint_data.pop(endpoint_index)
|
||||
response_obj = found_endpoint
|
||||
|
|
@ -1388,11 +1413,11 @@ def _find_endpoint_by_id(
|
|||
) -> Optional[PassThroughGenericEndpoint]:
|
||||
"""
|
||||
Find an endpoint by ID.
|
||||
|
||||
|
||||
Args:
|
||||
endpoints_data: List of endpoint data (dicts or PassThroughGenericEndpoint objects)
|
||||
endpoint_id: ID to search for
|
||||
|
||||
|
||||
Returns:
|
||||
Found endpoint or None if not found
|
||||
"""
|
||||
|
|
@ -1406,7 +1431,7 @@ def _find_endpoint_by_id(
|
|||
# Only compare IDs to IDs
|
||||
if _endpoint is not None and _endpoint.id == endpoint_id:
|
||||
return _endpoint
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,4 +15,7 @@ mcp_servers:
|
|||
|
||||
general_settings:
|
||||
store_model_in_db: true
|
||||
store_prompts_in_spend_logs: true
|
||||
store_prompts_in_spend_logs: true
|
||||
|
||||
litellm_settings:
|
||||
callbacks: ["langfuse", "datadog"]
|
||||
|
|
@ -231,6 +231,9 @@ from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
|
|||
from litellm.proxy.management_endpoints.budget_management_endpoints import (
|
||||
router as budget_management_router,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.callback_management_endpoints import (
|
||||
router as callback_management_endpoints_router,
|
||||
)
|
||||
from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view
|
||||
from litellm.proxy.management_endpoints.customer_endpoints import (
|
||||
router as customer_router,
|
||||
|
|
@ -902,7 +905,7 @@ health_check_results: Dict[str, Union[int, List[Dict[str, Any]]]] = {}
|
|||
queue: List = []
|
||||
litellm_proxy_budget_name = "litellm-proxy-budget"
|
||||
litellm_proxy_admin_name = LITELLM_PROXY_ADMIN_NAME
|
||||
ui_access_mode: Literal["admin", "all"] = "all"
|
||||
ui_access_mode: Union[Literal["admin", "all"], Dict] = "all"
|
||||
proxy_budget_rescheduler_min_time = PROXY_BUDGET_RESCHEDULER_MIN_TIME
|
||||
proxy_budget_rescheduler_max_time = PROXY_BUDGET_RESCHEDULER_MAX_TIME
|
||||
proxy_batch_write_at = PROXY_BATCH_WRITE_AT
|
||||
|
|
@ -1432,11 +1435,13 @@ class ProxyConfig:
|
|||
- Do not write restricted params like 'api_key' to the database
|
||||
- if api_key is passed, save that to the local environment or connected secret manage (maybe expose `litellm.save_secret()`)
|
||||
"""
|
||||
|
||||
if prisma_client is not None and (
|
||||
general_settings.get("store_model_in_db", False) is True
|
||||
or store_model_in_db
|
||||
):
|
||||
# if using - db for config - models are in ModelTable
|
||||
|
||||
new_config.pop("model_list", None)
|
||||
await prisma_client.insert_data(data=new_config, table_name="config")
|
||||
else:
|
||||
|
|
@ -2622,6 +2627,10 @@ class ProxyConfig:
|
|||
pass_through_endpoints=general_settings["pass_through_endpoints"]
|
||||
)
|
||||
|
||||
## UI ACCESS MODE ##
|
||||
if "ui_access_mode" in _general_settings:
|
||||
general_settings["ui_access_mode"] = _general_settings["ui_access_mode"]
|
||||
|
||||
def _update_config_fields(
|
||||
self,
|
||||
current_config: dict,
|
||||
|
|
@ -8595,6 +8604,7 @@ app.include_router(spend_management_router)
|
|||
app.include_router(caching_router)
|
||||
app.include_router(analytics_router)
|
||||
app.include_router(guardrails_router)
|
||||
app.include_router(callback_management_endpoints_router)
|
||||
app.include_router(debugging_endpoints_router)
|
||||
app.include_router(ui_crud_endpoints_router)
|
||||
app.include_router(openai_files_router)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,10 @@ async def route_request(
|
|||
team_id = get_team_id_from_data(data)
|
||||
router_model_names = llm_router.model_names if llm_router is not None else []
|
||||
if "api_key" in data or "api_base" in data:
|
||||
return getattr(llm_router, f"{route_type}")(**data)
|
||||
if llm_router is not None:
|
||||
return getattr(llm_router, f"{route_type}")(**data)
|
||||
else:
|
||||
return getattr(litellm, f"{route_type}")(**data)
|
||||
|
||||
elif "user_config" in data:
|
||||
router_config = data.pop("user_config")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import litellm
|
|||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.proxy._types import *
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.types.proxy.management_endpoints.ui_sso import DefaultTeamSSOParams, SSOConfig
|
||||
from litellm.types.proxy.management_endpoints.ui_sso import (
|
||||
DefaultTeamSSOParams,
|
||||
SSOConfig,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -18,26 +21,29 @@ class IPAddress(BaseModel):
|
|||
|
||||
class SettingsResponse(BaseModel):
|
||||
"""Base response model for settings with values and schema information"""
|
||||
|
||||
|
||||
values: Dict[str, Any]
|
||||
"""The current configuration values"""
|
||||
|
||||
|
||||
field_schema: Dict[str, Any]
|
||||
"""Schema information including descriptions and property types for UI display"""
|
||||
|
||||
|
||||
class SSOSettingsResponse(SettingsResponse):
|
||||
"""Response model for SSO settings"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class InternalUserSettingsResponse(SettingsResponse):
|
||||
"""Response model for internal user settings"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DefaultTeamSettingsResponse(SettingsResponse):
|
||||
"""Response model for default team settings"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -166,7 +172,10 @@ async def _get_settings_with_schema(
|
|||
# Add descriptions to the response
|
||||
result = {
|
||||
"values": settings_dict,
|
||||
"field_schema": {"description": schema.get("description", ""), "properties": {}},
|
||||
"field_schema": {
|
||||
"description": schema.get("description", ""),
|
||||
"properties": {},
|
||||
},
|
||||
}
|
||||
|
||||
# Add property descriptions
|
||||
|
|
@ -322,20 +331,21 @@ async def get_sso_settings():
|
|||
Returns a structured object with values and descriptions for UI display.
|
||||
"""
|
||||
import os
|
||||
|
||||
from litellm.proxy.proxy_server import proxy_config
|
||||
|
||||
|
||||
# Load existing config to get both environment variables and general settings
|
||||
config = await proxy_config.get_config()
|
||||
general_settings = config.get("general_settings", {}) or {}
|
||||
environment_variables = config.get("environment_variables", {}) or {}
|
||||
|
||||
|
||||
# Get user_email from general_settings
|
||||
proxy_admin_email = general_settings.get("proxy_admin_email", None)
|
||||
|
||||
|
||||
# Helper function to get env var value (first from config, then from environment)
|
||||
def get_env_value(env_var_name: str):
|
||||
return environment_variables.get(env_var_name) or os.getenv(env_var_name)
|
||||
|
||||
|
||||
# Get current environment variables for SSO
|
||||
sso_config = SSOConfig(
|
||||
google_client_id=get_env_value("GOOGLE_CLIENT_ID"),
|
||||
|
|
@ -351,27 +361,31 @@ async def get_sso_settings():
|
|||
proxy_base_url=get_env_value("PROXY_BASE_URL"),
|
||||
user_email=proxy_admin_email, # Get from config instead of environment
|
||||
)
|
||||
|
||||
|
||||
# Get the schema for UI display
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
schema = TypeAdapter(SSOConfig).json_schema(by_alias=True)
|
||||
|
||||
|
||||
# Convert to dict for response
|
||||
sso_dict = sso_config.model_dump()
|
||||
|
||||
|
||||
# Add descriptions to the response
|
||||
result = {
|
||||
"values": sso_dict,
|
||||
"field_schema": {"description": schema.get("description", ""), "properties": {}},
|
||||
"field_schema": {
|
||||
"description": schema.get("description", ""),
|
||||
"properties": {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Add property descriptions
|
||||
for field_name, field_info in schema["properties"].items():
|
||||
result["field_schema"]["properties"][field_name] = {
|
||||
"description": field_info.get("description", ""),
|
||||
"type": field_info.get("type", "string"),
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -384,51 +398,56 @@ async def update_sso_settings(sso_config: SSOConfig):
|
|||
"""
|
||||
Update SSO configuration by saving to both environment variables and config file.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import proxy_config
|
||||
import os
|
||||
|
||||
|
||||
from litellm.proxy.proxy_server import proxy_config
|
||||
|
||||
# Update environment variables
|
||||
env_var_mapping = {
|
||||
'google_client_id': 'GOOGLE_CLIENT_ID',
|
||||
'google_client_secret': 'GOOGLE_CLIENT_SECRET',
|
||||
'microsoft_client_id': 'MICROSOFT_CLIENT_ID',
|
||||
'microsoft_client_secret': 'MICROSOFT_CLIENT_SECRET',
|
||||
'microsoft_tenant': 'MICROSOFT_TENANT',
|
||||
'generic_client_id': 'GENERIC_CLIENT_ID',
|
||||
'generic_client_secret': 'GENERIC_CLIENT_SECRET',
|
||||
'generic_authorization_endpoint': 'GENERIC_AUTHORIZATION_ENDPOINT',
|
||||
'generic_token_endpoint': 'GENERIC_TOKEN_ENDPOINT',
|
||||
'generic_userinfo_endpoint': 'GENERIC_USERINFO_ENDPOINT',
|
||||
'proxy_base_url': 'PROXY_BASE_URL',
|
||||
"google_client_id": "GOOGLE_CLIENT_ID",
|
||||
"google_client_secret": "GOOGLE_CLIENT_SECRET",
|
||||
"microsoft_client_id": "MICROSOFT_CLIENT_ID",
|
||||
"microsoft_client_secret": "MICROSOFT_CLIENT_SECRET",
|
||||
"microsoft_tenant": "MICROSOFT_TENANT",
|
||||
"generic_client_id": "GENERIC_CLIENT_ID",
|
||||
"generic_client_secret": "GENERIC_CLIENT_SECRET",
|
||||
"generic_authorization_endpoint": "GENERIC_AUTHORIZATION_ENDPOINT",
|
||||
"generic_token_endpoint": "GENERIC_TOKEN_ENDPOINT",
|
||||
"generic_userinfo_endpoint": "GENERIC_USERINFO_ENDPOINT",
|
||||
"proxy_base_url": "PROXY_BASE_URL",
|
||||
}
|
||||
|
||||
|
||||
# Load existing config
|
||||
config = await proxy_config.get_config()
|
||||
|
||||
|
||||
# Update config with new environment variables
|
||||
if "environment_variables" not in config:
|
||||
config["environment_variables"] = {}
|
||||
|
||||
|
||||
# Update general_settings for user_email (admin email)
|
||||
if "general_settings" not in config:
|
||||
config["general_settings"] = {}
|
||||
|
||||
|
||||
# Update environment variables in config and in memory
|
||||
sso_data = sso_config.model_dump(exclude_none=True)
|
||||
for field_name, value in sso_data.items():
|
||||
if field_name == 'user_email' and value is not None:
|
||||
|
||||
if field_name == "user_email" and value is not None:
|
||||
# Store user_email in general_settings instead of environment variables
|
||||
config["general_settings"]["proxy_admin_email"] = value
|
||||
elif field_name == "ui_access_mode" and value is not None:
|
||||
|
||||
config["general_settings"]["ui_access_mode"] = value
|
||||
elif field_name in env_var_mapping and value is not None:
|
||||
env_var_name = env_var_mapping[field_name]
|
||||
# Update in config
|
||||
config["environment_variables"][env_var_name] = value
|
||||
# Update in runtime environment
|
||||
os.environ[env_var_name] = value
|
||||
|
||||
|
||||
# Save the updated config
|
||||
await proxy_config.save_config(new_config=config)
|
||||
|
||||
|
||||
return {
|
||||
"message": "SSO settings updated successfully",
|
||||
"status": "success",
|
||||
|
|
|
|||
|
|
@ -4108,20 +4108,26 @@ class Router:
|
|||
original_exception=exception
|
||||
)
|
||||
|
||||
_time_to_cooldown = kwargs.get("litellm_params", {}).get(
|
||||
"cooldown_time", self.cooldown_time
|
||||
)
|
||||
|
||||
# Determine cooldown time with priority: deployment config > response header > router default
|
||||
deployment_cooldown = kwargs.get("litellm_params", {}).get("cooldown_time", None)
|
||||
|
||||
header_cooldown = None
|
||||
if exception_headers is not None:
|
||||
_time_to_cooldown = (
|
||||
litellm.utils._get_retry_after_from_exception_header(
|
||||
response_headers=exception_headers
|
||||
)
|
||||
header_cooldown = litellm.utils._get_retry_after_from_exception_header(
|
||||
response_headers=exception_headers
|
||||
)
|
||||
|
||||
if _time_to_cooldown is None or _time_to_cooldown < 0:
|
||||
# if the response headers did not read it -> set to default cooldown time
|
||||
_time_to_cooldown = self.cooldown_time
|
||||
##############################################
|
||||
# Logic to determine cooldown time
|
||||
# 1. Check if a cooldown time is set in the deployment config
|
||||
# 2. Check if a cooldown time is set in the response header
|
||||
# 3. If no cooldown time is set, use the router default cooldown time
|
||||
##############################################
|
||||
if deployment_cooldown is not None and deployment_cooldown >= 0:
|
||||
_time_to_cooldown = deployment_cooldown
|
||||
elif header_cooldown is not None and header_cooldown >= 0:
|
||||
_time_to_cooldown = header_cooldown
|
||||
else:
|
||||
_time_to_cooldown = self.cooldown_time
|
||||
|
||||
if isinstance(_model_info, dict):
|
||||
deployment_id = _model_info.get("id", None)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class SystemInstructions(TypedDict):
|
|||
|
||||
class Schema(TypedDict, total=False):
|
||||
type: Literal["STRING", "INTEGER", "BOOLEAN", "NUMBER", "ARRAY", "OBJECT"]
|
||||
format: str
|
||||
format: Literal["enum", "date-time"]
|
||||
title: str
|
||||
description: str
|
||||
nullable: bool
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import List, Literal, Optional, TypedDict
|
||||
from typing import List, Literal, Optional, TypedDict, Union
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
|
|
@ -31,6 +31,14 @@ class MicrosoftServicePrincipalTeam(TypedDict, total=False):
|
|||
principalId: Optional[str]
|
||||
|
||||
|
||||
class AccessControl_UI_AccessMode(LiteLLMPydanticObjectBase):
|
||||
"""Model for Controlling UI Access Mode via SSO Groups"""
|
||||
|
||||
type: Literal["restricted_sso_group"]
|
||||
restricted_sso_group: str
|
||||
sso_group_jwt_field: str
|
||||
|
||||
|
||||
class SSOConfig(LiteLLMPydanticObjectBase):
|
||||
"""
|
||||
Configuration for SSO environment variables and settings
|
||||
|
|
@ -45,7 +53,7 @@ class SSOConfig(LiteLLMPydanticObjectBase):
|
|||
default=None,
|
||||
description="Google OAuth Client Secret for SSO authentication",
|
||||
)
|
||||
|
||||
|
||||
# Microsoft SSO
|
||||
microsoft_client_id: Optional[str] = Field(
|
||||
default=None,
|
||||
|
|
@ -59,7 +67,7 @@ class SSOConfig(LiteLLMPydanticObjectBase):
|
|||
default=None,
|
||||
description="Microsoft Azure Tenant ID for SSO authentication",
|
||||
)
|
||||
|
||||
|
||||
# Generic/Okta SSO
|
||||
generic_client_id: Optional[str] = Field(
|
||||
default=None,
|
||||
|
|
@ -81,7 +89,7 @@ class SSOConfig(LiteLLMPydanticObjectBase):
|
|||
default=None,
|
||||
description="User info endpoint URL for generic OAuth provider",
|
||||
)
|
||||
|
||||
|
||||
# Common settings
|
||||
proxy_base_url: Optional[str] = Field(
|
||||
default=None,
|
||||
|
|
@ -92,6 +100,12 @@ class SSOConfig(LiteLLMPydanticObjectBase):
|
|||
description="Email of the proxy admin user",
|
||||
)
|
||||
|
||||
# Access Mode
|
||||
ui_access_mode: Optional[Union[AccessControl_UI_AccessMode, str]] = Field(
|
||||
default=None,
|
||||
description="Access mode for the UI",
|
||||
)
|
||||
|
||||
|
||||
class DefaultTeamSSOParams(LiteLLMPydanticObjectBase):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
|
|||
search_context_cost_per_query: Optional[
|
||||
SearchContextCostPerQuery
|
||||
] # Cost for using web search tool
|
||||
|
||||
citation_cost_per_token: Optional[float] # Cost per citation token for Perplexity
|
||||
litellm_provider: Required[str]
|
||||
mode: Required[
|
||||
Literal[
|
||||
|
|
@ -947,24 +947,6 @@ class Usage(CompletionUsage):
|
|||
elif isinstance(completion_tokens_details, CompletionTokensDetails):
|
||||
_completion_tokens_details = completion_tokens_details
|
||||
|
||||
## DEEPSEEK MAPPING ##
|
||||
if "prompt_cache_hit_tokens" in params and isinstance(
|
||||
params["prompt_cache_hit_tokens"], int
|
||||
):
|
||||
if prompt_tokens_details is None:
|
||||
prompt_tokens_details = PromptTokensDetailsWrapper(
|
||||
cached_tokens=params["prompt_cache_hit_tokens"]
|
||||
)
|
||||
|
||||
## ANTHROPIC MAPPING ##
|
||||
if "cache_read_input_tokens" in params and isinstance(
|
||||
params["cache_read_input_tokens"], int
|
||||
):
|
||||
if prompt_tokens_details is None:
|
||||
prompt_tokens_details = PromptTokensDetailsWrapper(
|
||||
cached_tokens=params["cache_read_input_tokens"]
|
||||
)
|
||||
|
||||
# handle prompt_tokens_details
|
||||
_prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None
|
||||
if prompt_tokens_details:
|
||||
|
|
@ -975,6 +957,28 @@ class Usage(CompletionUsage):
|
|||
elif isinstance(prompt_tokens_details, PromptTokensDetails):
|
||||
_prompt_tokens_details = prompt_tokens_details
|
||||
|
||||
## DEEPSEEK MAPPING ##
|
||||
if "prompt_cache_hit_tokens" in params and isinstance(
|
||||
params["prompt_cache_hit_tokens"], int
|
||||
):
|
||||
if _prompt_tokens_details is None:
|
||||
_prompt_tokens_details = PromptTokensDetailsWrapper(
|
||||
cached_tokens=params["prompt_cache_hit_tokens"]
|
||||
)
|
||||
else:
|
||||
_prompt_tokens_details.cached_tokens = params["prompt_cache_hit_tokens"]
|
||||
|
||||
## ANTHROPIC MAPPING ##
|
||||
if "cache_read_input_tokens" in params and isinstance(
|
||||
params["cache_read_input_tokens"], int
|
||||
):
|
||||
if _prompt_tokens_details is None:
|
||||
_prompt_tokens_details = PromptTokensDetailsWrapper(
|
||||
cached_tokens=params["cache_read_input_tokens"]
|
||||
)
|
||||
else:
|
||||
_prompt_tokens_details.cached_tokens = params["cache_read_input_tokens"]
|
||||
|
||||
super().__init__(
|
||||
prompt_tokens=prompt_tokens or 0,
|
||||
completion_tokens=completion_tokens or 0,
|
||||
|
|
@ -2489,3 +2493,9 @@ class DynamicPromptManagementParamLiteral(str, Enum):
|
|||
@classmethod
|
||||
def list_all_params(cls):
|
||||
return [param.value for param in cls]
|
||||
|
||||
|
||||
class CallbacksByType(TypedDict):
|
||||
success: List[str]
|
||||
failure: List[str]
|
||||
success_and_failure: List[str]
|
||||
|
|
|
|||
|
|
@ -85,3 +85,83 @@ class VectorStoreSearchResponse(TypedDict, total=False):
|
|||
] # Always "vector_store.search_results.page"
|
||||
search_query: Optional[str]
|
||||
data: Optional[List[VectorStoreSearchResult]]
|
||||
|
||||
class VectorStoreSearchOptionalRequestParams(TypedDict, total=False):
|
||||
"""TypedDict for Optional parameters supported by the vector store search API."""
|
||||
filters: Optional[Dict]
|
||||
max_num_results: Optional[int]
|
||||
ranking_options: Optional[Dict]
|
||||
rewrite_query: Optional[bool]
|
||||
|
||||
class VectorStoreSearchRequest(VectorStoreSearchOptionalRequestParams, total=False):
|
||||
"""Request body for searching a vector store"""
|
||||
query: Union[str, List[str]]
|
||||
|
||||
|
||||
# Vector Store Creation Types
|
||||
class VectorStoreExpirationPolicy(TypedDict, total=False):
|
||||
"""The expiration policy for a vector store"""
|
||||
anchor: Literal["last_active_at"] # Anchor timestamp after which the expiration policy applies
|
||||
days: int # Number of days after anchor time that the vector store will expire
|
||||
|
||||
|
||||
class VectorStoreAutoChunkingStrategy(TypedDict, total=False):
|
||||
"""Auto chunking strategy configuration"""
|
||||
type: Literal["auto"] # Always "auto"
|
||||
|
||||
|
||||
class VectorStoreStaticChunkingStrategyConfig(TypedDict, total=False):
|
||||
"""Static chunking strategy configuration"""
|
||||
max_chunk_size_tokens: int # Maximum number of tokens per chunk
|
||||
chunk_overlap_tokens: int # Number of tokens to overlap between chunks
|
||||
|
||||
|
||||
class VectorStoreStaticChunkingStrategy(TypedDict, total=False):
|
||||
"""Static chunking strategy"""
|
||||
type: Literal["static"] # Always "static"
|
||||
static: VectorStoreStaticChunkingStrategyConfig
|
||||
|
||||
|
||||
class VectorStoreChunkingStrategy(TypedDict, total=False):
|
||||
"""Union type for chunking strategies"""
|
||||
# This can be either auto or static
|
||||
type: Literal["auto", "static"]
|
||||
static: Optional[VectorStoreStaticChunkingStrategyConfig]
|
||||
|
||||
|
||||
class VectorStoreFileCounts(TypedDict, total=False):
|
||||
"""File counts for a vector store"""
|
||||
in_progress: int
|
||||
completed: int
|
||||
failed: int
|
||||
cancelled: int
|
||||
total: int
|
||||
|
||||
|
||||
class VectorStoreCreateOptionalRequestParams(TypedDict, total=False):
|
||||
"""TypedDict for Optional parameters supported by the vector store create API."""
|
||||
name: Optional[str] # Name of the vector store
|
||||
file_ids: Optional[List[str]] # List of File IDs that the vector store should use
|
||||
expires_after: Optional[VectorStoreExpirationPolicy] # Expiration policy for the vector store
|
||||
chunking_strategy: Optional[VectorStoreChunkingStrategy] # Chunking strategy for the files
|
||||
metadata: Optional[Dict[str, str]] # Set of key-value pairs for metadata
|
||||
|
||||
|
||||
class VectorStoreCreateRequest(VectorStoreCreateOptionalRequestParams, total=False):
|
||||
"""Request body for creating a vector store"""
|
||||
pass # All fields are optional for vector store creation
|
||||
|
||||
|
||||
class VectorStoreCreateResponse(TypedDict, total=False):
|
||||
"""Response after creating a vector store"""
|
||||
id: str # ID of the vector store
|
||||
object: Literal["vector_store"] # Always "vector_store"
|
||||
created_at: int # Unix timestamp of when the vector store was created
|
||||
name: Optional[str] # Name of the vector store
|
||||
bytes: int # Size of the vector store in bytes
|
||||
file_counts: VectorStoreFileCounts # File counts for the vector store
|
||||
status: Literal["expired", "in_progress", "completed"] # Status of the vector store
|
||||
expires_after: Optional[VectorStoreExpirationPolicy] # Expiration policy
|
||||
expires_at: Optional[int] # Unix timestamp of when the vector store expires
|
||||
last_active_at: Optional[int] # Unix timestamp of when the vector store was last active
|
||||
metadata: Optional[Dict[str, str]] # Metadata associated with the vector store
|
||||
|
|
@ -78,7 +78,9 @@ from litellm.constants import (
|
|||
)
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.integrations.custom_logger import CustomLogger
|
||||
from litellm.integrations.vector_stores.base_vector_store import BaseVectorStore
|
||||
from litellm.integrations.vector_store_integrations.base_vector_store import (
|
||||
BaseVectorStore,
|
||||
)
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
map_finish_reason,
|
||||
process_response_headers,
|
||||
|
|
@ -242,6 +244,7 @@ from litellm.llms.base_llm.image_variations.transformation import (
|
|||
from litellm.llms.base_llm.realtime.transformation import BaseRealtimeConfig
|
||||
from litellm.llms.base_llm.rerank.transformation import BaseRerankConfig
|
||||
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
|
||||
from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig
|
||||
|
||||
from ._logging import _is_debugging_on, verbose_logger
|
||||
from .caching.caching import (
|
||||
|
|
@ -4650,6 +4653,7 @@ def _get_model_info_helper( # noqa: PLR0915
|
|||
output_cost_per_second=_model_info.get("output_cost_per_second", None),
|
||||
output_cost_per_image=_model_info.get("output_cost_per_image", None),
|
||||
output_vector_size=_model_info.get("output_vector_size", None),
|
||||
citation_cost_per_token=_model_info.get("citation_cost_per_token", None),
|
||||
litellm_provider=_model_info.get(
|
||||
"litellm_provider", custom_llm_provider
|
||||
),
|
||||
|
|
@ -6901,13 +6905,33 @@ class ProviderConfigManager:
|
|||
def get_provider_vector_store_config(
|
||||
provider: LlmProviders,
|
||||
) -> Optional[CustomLogger]:
|
||||
from litellm.integrations.vector_stores.bedrock_vector_store import (
|
||||
from litellm.integrations.vector_store_integrations.bedrock_vector_store import (
|
||||
BedrockVectorStore,
|
||||
)
|
||||
|
||||
if LlmProviders.BEDROCK == provider:
|
||||
return BedrockVectorStore.get_initialized_custom_logger()
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_provider_vector_stores_config(
|
||||
provider: LlmProviders,
|
||||
) -> Optional[BaseVectorStoreConfig]:
|
||||
"""
|
||||
v2 vector store config, use this for new vector store integrations
|
||||
"""
|
||||
if litellm.LlmProviders.OPENAI == provider:
|
||||
from litellm.llms.openai.vector_stores.transformation import (
|
||||
OpenAIVectorStoreConfig,
|
||||
)
|
||||
return OpenAIVectorStoreConfig()
|
||||
elif litellm.LlmProviders.AZURE == provider:
|
||||
from litellm.llms.azure.vector_stores.transformation import (
|
||||
AzureOpenAIVectorStoreConfig,
|
||||
)
|
||||
return AzureOpenAIVectorStoreConfig()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_provider_image_generation_config(
|
||||
|
|
|
|||
4
litellm/vector_stores/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .main import acreate, asearch, create, search
|
||||
from .vector_store_registry import VectorStoreRegistry
|
||||
|
||||
__all__ = ["search", "asearch", "create", "acreate", "VectorStoreRegistry"]
|
||||
434
litellm/vector_stores/main.py
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
"""
|
||||
LiteLLM SDK Functions for Creating and Searching Vector Stores
|
||||
"""
|
||||
import asyncio
|
||||
import contextvars
|
||||
from functools import partial
|
||||
from typing import Any, Coroutine, Dict, List, Optional, Union
|
||||
|
||||
import httpx
|
||||
|
||||
import litellm
|
||||
from litellm.constants import request_timeout
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.vector_stores import (
|
||||
VectorStoreCreateOptionalRequestParams,
|
||||
VectorStoreCreateResponse,
|
||||
VectorStoreFileCounts,
|
||||
VectorStoreResultContent,
|
||||
VectorStoreSearchOptionalRequestParams,
|
||||
VectorStoreSearchResponse,
|
||||
VectorStoreSearchResult,
|
||||
)
|
||||
from litellm.utils import ProviderConfigManager, client
|
||||
from litellm.vector_stores.utils import VectorStoreRequestUtils
|
||||
|
||||
####### ENVIRONMENT VARIABLES ###################
|
||||
# Initialize any necessary instances or variables here
|
||||
base_llm_http_handler = BaseLLMHTTPHandler()
|
||||
#################################################
|
||||
|
||||
|
||||
def mock_vector_store_search_response(
|
||||
mock_results: Optional[List[VectorStoreSearchResult]] = None,
|
||||
):
|
||||
"""Mock response for vector store search"""
|
||||
if mock_results is None:
|
||||
mock_results = [
|
||||
VectorStoreSearchResult(
|
||||
score=0.95,
|
||||
content=[
|
||||
VectorStoreResultContent(
|
||||
text="This is a sample search result from the vector store.",
|
||||
type="text"
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
return VectorStoreSearchResponse(
|
||||
object="vector_store.search_results.page",
|
||||
search_query="sample query",
|
||||
data=mock_results,
|
||||
)
|
||||
|
||||
|
||||
def mock_vector_store_create_response(
|
||||
mock_response: Optional[VectorStoreCreateResponse] = None,
|
||||
):
|
||||
"""Mock response for vector store create"""
|
||||
if mock_response is None:
|
||||
mock_response = VectorStoreCreateResponse(
|
||||
id="vs_mock123",
|
||||
object="vector_store",
|
||||
created_at=1699061776,
|
||||
name="Mock Vector Store",
|
||||
bytes=0,
|
||||
file_counts=VectorStoreFileCounts(
|
||||
in_progress=0,
|
||||
completed=0,
|
||||
failed=0,
|
||||
cancelled=0,
|
||||
total=0,
|
||||
),
|
||||
status="completed",
|
||||
expires_after=None,
|
||||
expires_at=None,
|
||||
last_active_at=None,
|
||||
metadata=None,
|
||||
)
|
||||
|
||||
return mock_response
|
||||
|
||||
|
||||
@client
|
||||
async def acreate(
|
||||
name: Optional[str] = None,
|
||||
file_ids: Optional[List[str]] = None,
|
||||
expires_after: Optional[Dict] = None,
|
||||
chunking_strategy: Optional[Dict] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
||||
# The extra values given here take precedence over values defined on the client or passed to this method.
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_query: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
# LiteLLM specific params,
|
||||
custom_llm_provider: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> VectorStoreCreateResponse:
|
||||
"""
|
||||
Async: Create a vector store.
|
||||
"""
|
||||
local_vars = locals()
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
kwargs["acreate"] = True
|
||||
|
||||
# get custom llm provider so we can use this for mapping exceptions
|
||||
if custom_llm_provider is None:
|
||||
custom_llm_provider = "openai" # Default to OpenAI for vector stores
|
||||
|
||||
func = partial(
|
||||
create,
|
||||
name=name,
|
||||
file_ids=file_ids,
|
||||
expires_after=expires_after,
|
||||
chunking_strategy=chunking_strategy,
|
||||
metadata=metadata,
|
||||
extra_headers=extra_headers,
|
||||
extra_query=extra_query,
|
||||
extra_body=extra_body,
|
||||
timeout=timeout,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
ctx = contextvars.copy_context()
|
||||
func_with_context = partial(ctx.run, func)
|
||||
init_response = await loop.run_in_executor(None, func_with_context)
|
||||
|
||||
if asyncio.iscoroutine(init_response):
|
||||
response = await init_response
|
||||
else:
|
||||
response = init_response
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
raise litellm.exception_type(
|
||||
model=None,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
original_exception=e,
|
||||
completion_kwargs=local_vars,
|
||||
extra_kwargs=kwargs,
|
||||
)
|
||||
|
||||
|
||||
@client
|
||||
def create(
|
||||
name: Optional[str] = None,
|
||||
file_ids: Optional[List[str]] = None,
|
||||
expires_after: Optional[Dict] = None,
|
||||
chunking_strategy: Optional[Dict] = None,
|
||||
metadata: Optional[Dict[str, str]] = None,
|
||||
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
||||
# The extra values given here take precedence over values defined on the client or passed to this method.
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_query: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
# LiteLLM specific params,
|
||||
custom_llm_provider: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Union[VectorStoreCreateResponse, Coroutine[Any, Any, VectorStoreCreateResponse]]:
|
||||
"""
|
||||
Create a vector store.
|
||||
|
||||
Args:
|
||||
name: The name of the vector store.
|
||||
file_ids: A list of File IDs that the vector store should use.
|
||||
expires_after: The expiration policy for the vector store.
|
||||
chunking_strategy: The chunking strategy used to chunk the file(s).
|
||||
metadata: Set of 16 key-value pairs that can be attached to an object.
|
||||
|
||||
Returns:
|
||||
VectorStoreCreateResponse containing the created vector store details.
|
||||
"""
|
||||
local_vars = locals()
|
||||
try:
|
||||
litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore
|
||||
litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None)
|
||||
_is_async = kwargs.pop("acreate", False) is True
|
||||
|
||||
# get llm provider logic
|
||||
litellm_params = GenericLiteLLMParams(**kwargs)
|
||||
|
||||
## MOCK RESPONSE LOGIC
|
||||
if litellm_params.mock_response and isinstance(
|
||||
litellm_params.mock_response, dict
|
||||
):
|
||||
return mock_vector_store_create_response(
|
||||
mock_response=VectorStoreCreateResponse(**litellm_params.mock_response)
|
||||
)
|
||||
|
||||
# Default to OpenAI for vector stores
|
||||
if custom_llm_provider is None:
|
||||
custom_llm_provider = "openai"
|
||||
|
||||
# get provider config - using vector store custom logger for now
|
||||
vector_store_provider_config = ProviderConfigManager.get_provider_vector_stores_config(
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
|
||||
if vector_store_provider_config is None:
|
||||
raise ValueError(
|
||||
f"Vector store create is not supported for {custom_llm_provider}"
|
||||
)
|
||||
|
||||
local_vars.update(kwargs)
|
||||
|
||||
# Get VectorStoreCreateOptionalRequestParams with only valid parameters
|
||||
vector_store_create_optional_params: VectorStoreCreateOptionalRequestParams = (
|
||||
VectorStoreRequestUtils.get_requested_vector_store_create_optional_param(
|
||||
local_vars
|
||||
)
|
||||
)
|
||||
|
||||
# Pre Call logging
|
||||
litellm_logging_obj.update_environment_variables(
|
||||
model=None,
|
||||
optional_params={
|
||||
"name": name,
|
||||
**vector_store_create_optional_params,
|
||||
},
|
||||
litellm_params={
|
||||
"litellm_call_id": litellm_call_id,
|
||||
},
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
response = base_llm_http_handler.vector_store_create_handler(
|
||||
vector_store_create_optional_params=vector_store_create_optional_params,
|
||||
vector_store_provider_config=vector_store_provider_config,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=litellm_logging_obj,
|
||||
extra_headers=extra_headers,
|
||||
extra_body=extra_body,
|
||||
timeout=timeout or request_timeout,
|
||||
_is_async=_is_async,
|
||||
client=kwargs.get("client"),
|
||||
)
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
raise litellm.exception_type(
|
||||
model=None,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
original_exception=e,
|
||||
completion_kwargs=local_vars,
|
||||
extra_kwargs=kwargs,
|
||||
)
|
||||
|
||||
|
||||
@client
|
||||
async def asearch(
|
||||
vector_store_id: str,
|
||||
query: Union[str, List[str]],
|
||||
filters: Optional[Dict] = None,
|
||||
max_num_results: Optional[int] = None,
|
||||
ranking_options: Optional[Dict] = None,
|
||||
rewrite_query: Optional[bool] = None,
|
||||
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
||||
# The extra values given here take precedence over values defined on the client or passed to this method.
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_query: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
# LiteLLM specific params,
|
||||
custom_llm_provider: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> VectorStoreSearchResponse:
|
||||
"""
|
||||
Async: Search a vector store for relevant chunks based on a query and file attributes filter.
|
||||
"""
|
||||
local_vars = locals()
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
kwargs["asearch"] = True
|
||||
|
||||
# get custom llm provider so we can use this for mapping exceptions
|
||||
if custom_llm_provider is None:
|
||||
custom_llm_provider = "openai" # Default to OpenAI for vector stores
|
||||
|
||||
func = partial(
|
||||
search,
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
filters=filters,
|
||||
max_num_results=max_num_results,
|
||||
ranking_options=ranking_options,
|
||||
rewrite_query=rewrite_query,
|
||||
extra_headers=extra_headers,
|
||||
extra_query=extra_query,
|
||||
extra_body=extra_body,
|
||||
timeout=timeout,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
ctx = contextvars.copy_context()
|
||||
func_with_context = partial(ctx.run, func)
|
||||
init_response = await loop.run_in_executor(None, func_with_context)
|
||||
|
||||
if asyncio.iscoroutine(init_response):
|
||||
response = await init_response
|
||||
else:
|
||||
response = init_response
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
raise litellm.exception_type(
|
||||
model=None,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
original_exception=e,
|
||||
completion_kwargs=local_vars,
|
||||
extra_kwargs=kwargs,
|
||||
)
|
||||
|
||||
|
||||
@client
|
||||
def search(
|
||||
vector_store_id: str,
|
||||
query: Union[str, List[str]],
|
||||
filters: Optional[Dict] = None,
|
||||
max_num_results: Optional[int] = None,
|
||||
ranking_options: Optional[Dict] = None,
|
||||
rewrite_query: Optional[bool] = None,
|
||||
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
||||
# The extra values given here take precedence over values defined on the client or passed to this method.
|
||||
extra_headers: Optional[Dict[str, Any]] = None,
|
||||
extra_query: Optional[Dict[str, Any]] = None,
|
||||
extra_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[Union[float, httpx.Timeout]] = None,
|
||||
# LiteLLM specific params,
|
||||
custom_llm_provider: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> Union[VectorStoreSearchResponse, Coroutine[Any, Any, VectorStoreSearchResponse]]:
|
||||
"""
|
||||
Search a vector store for relevant chunks based on a query and file attributes filter.
|
||||
|
||||
Args:
|
||||
vector_store_id: The ID of the vector store to search.
|
||||
query: A query string or array for the search.
|
||||
filters: Optional filter to apply based on file attributes.
|
||||
max_num_results: Maximum number of results to return (1-50, default 10).
|
||||
ranking_options: Optional ranking options for search.
|
||||
rewrite_query: Whether to rewrite the natural language query for vector search.
|
||||
|
||||
Returns:
|
||||
VectorStoreSearchResponse containing the search results.
|
||||
"""
|
||||
local_vars = locals()
|
||||
try:
|
||||
litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore
|
||||
litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None)
|
||||
_is_async = kwargs.pop("asearch", False) is True
|
||||
|
||||
# get llm provider logic
|
||||
litellm_params = GenericLiteLLMParams(**kwargs)
|
||||
|
||||
## MOCK RESPONSE LOGIC
|
||||
if litellm_params.mock_response and isinstance(
|
||||
litellm_params.mock_response, (str, list)
|
||||
):
|
||||
mock_results = None
|
||||
if isinstance(litellm_params.mock_response, list):
|
||||
mock_results = litellm_params.mock_response
|
||||
return mock_vector_store_search_response(mock_results=mock_results)
|
||||
|
||||
# Default to OpenAI for vector stores
|
||||
if custom_llm_provider is None:
|
||||
custom_llm_provider = "openai"
|
||||
|
||||
# get provider config - using vector store custom logger for now
|
||||
vector_store_provider_config = ProviderConfigManager.get_provider_vector_stores_config(
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
|
||||
if vector_store_provider_config is None:
|
||||
raise ValueError(
|
||||
f"Vector store search is not supported for {custom_llm_provider}"
|
||||
)
|
||||
|
||||
local_vars.update(kwargs)
|
||||
|
||||
# Get VectorStoreSearchOptionalRequestParams with only valid parameters
|
||||
vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams = (
|
||||
VectorStoreRequestUtils.get_requested_vector_store_search_optional_param(
|
||||
local_vars
|
||||
)
|
||||
)
|
||||
|
||||
# Pre Call logging
|
||||
litellm_logging_obj.update_environment_variables(
|
||||
model=None,
|
||||
optional_params={
|
||||
"vector_store_id": vector_store_id,
|
||||
"query": query,
|
||||
**vector_store_search_optional_params,
|
||||
},
|
||||
litellm_params={
|
||||
"litellm_call_id": litellm_call_id,
|
||||
"vector_store_id": vector_store_id,
|
||||
},
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
)
|
||||
|
||||
response = base_llm_http_handler.vector_store_search_handler(
|
||||
vector_store_id=vector_store_id,
|
||||
query=query,
|
||||
vector_store_search_optional_params=vector_store_search_optional_params,
|
||||
vector_store_provider_config=vector_store_provider_config,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
litellm_params=litellm_params,
|
||||
logging_obj=litellm_logging_obj,
|
||||
extra_headers=extra_headers,
|
||||
extra_body=extra_body,
|
||||
timeout=timeout or request_timeout,
|
||||
_is_async=_is_async,
|
||||
client=kwargs.get("client"),
|
||||
)
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
raise litellm.exception_type(
|
||||
model=None,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
original_exception=e,
|
||||
completion_kwargs=local_vars,
|
||||
extra_kwargs=kwargs,
|
||||
)
|
||||
51
litellm/vector_stores/utils.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from typing import Any, Dict, cast, get_type_hints
|
||||
|
||||
from litellm.types.vector_stores import (
|
||||
VectorStoreCreateOptionalRequestParams,
|
||||
VectorStoreSearchOptionalRequestParams,
|
||||
)
|
||||
|
||||
|
||||
class VectorStoreRequestUtils:
|
||||
"""Helper utils for constructing Vector Store search requests"""
|
||||
|
||||
@staticmethod
|
||||
def get_requested_vector_store_search_optional_param(
|
||||
params: Dict[str, Any],
|
||||
) -> VectorStoreSearchOptionalRequestParams:
|
||||
"""
|
||||
Filter parameters to only include those defined in VectorStoreSearchOptionalRequestParams.
|
||||
|
||||
Args:
|
||||
params: Dictionary of parameters to filter
|
||||
|
||||
Returns:
|
||||
VectorStoreSearchOptionalRequestParams instance with only the valid parameters
|
||||
"""
|
||||
valid_keys = get_type_hints(VectorStoreSearchOptionalRequestParams).keys()
|
||||
filtered_params = {
|
||||
k: v for k, v in params.items() if k in valid_keys and v is not None
|
||||
}
|
||||
|
||||
return cast(VectorStoreSearchOptionalRequestParams, filtered_params)
|
||||
|
||||
@staticmethod
|
||||
def get_requested_vector_store_create_optional_param(
|
||||
params: Dict[str, Any],
|
||||
) -> VectorStoreCreateOptionalRequestParams:
|
||||
"""
|
||||
Filter parameters to only include those defined in VectorStoreCreateOptionalRequestParams.
|
||||
|
||||
Args:
|
||||
params: Dictionary of parameters to filter
|
||||
|
||||
Returns:
|
||||
VectorStoreCreateOptionalRequestParams instance with only the valid parameters
|
||||
"""
|
||||
valid_keys = get_type_hints(VectorStoreCreateOptionalRequestParams).keys()
|
||||
filtered_params = {
|
||||
k: v for k, v in params.items() if k in valid_keys and v is not None
|
||||
}
|
||||
|
||||
return cast(VectorStoreCreateOptionalRequestParams, filtered_params)
|
||||
|
||||
|
|
@ -2156,6 +2156,66 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_native_streaming": true
|
||||
},
|
||||
"azure/o3-pro": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 100000,
|
||||
"input_cost_per_token": 2e-05,
|
||||
"output_cost_per_token": 8e-05,
|
||||
"input_cost_per_token_batches": 1e-05,
|
||||
"output_cost_per_token_batches": 4e-05,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"azure/o3-pro-2025-06-10": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 100000,
|
||||
"input_cost_per_token": 2e-05,
|
||||
"output_cost_per_token": 8e-05,
|
||||
"input_cost_per_token_batches": 1e-05,
|
||||
"output_cost_per_token_batches": 4e-05,
|
||||
"litellm_provider": "azure",
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions",
|
||||
"/v1/batch",
|
||||
"/v1/responses"
|
||||
],
|
||||
"supported_modalities": [
|
||||
"text",
|
||||
"image"
|
||||
],
|
||||
"supported_output_modalities": [
|
||||
"text"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": false,
|
||||
"supports_vision": true,
|
||||
"supports_prompt_caching": false,
|
||||
"supports_response_schema": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"azure/o3": {
|
||||
"max_tokens": 100000,
|
||||
"max_input_tokens": 200000,
|
||||
|
|
@ -3986,7 +4046,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-small": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -3998,7 +4059,8 @@
|
|||
"supports_function_calling": true,
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-small-latest": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4010,7 +4072,8 @@
|
|||
"supports_function_calling": true,
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4021,7 +4084,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium-latest": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4033,7 +4097,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium-2505": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4045,7 +4110,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-medium-2312": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4056,7 +4122,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-latest": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4068,7 +4135,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-2411": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4080,7 +4148,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-2402": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4092,7 +4161,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-large-2407": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4104,7 +4174,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/pixtral-large-latest": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4117,7 +4188,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_vision": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/pixtral-large-2411": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4130,7 +4202,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_vision": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/pixtral-12b-2409": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4143,7 +4216,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_vision": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mistral-7b": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4154,7 +4228,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mixtral-8x7b": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4166,7 +4241,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mixtral-8x22b": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4178,7 +4254,8 @@
|
|||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/codestral-latest": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4189,7 +4266,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/codestral-2405": {
|
||||
"max_tokens": 8191,
|
||||
|
|
@ -4200,7 +4278,8 @@
|
|||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mistral-nemo": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4212,7 +4291,8 @@
|
|||
"mode": "chat",
|
||||
"source": "https://mistral.ai/technology/",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-mistral-nemo-2407": {
|
||||
"max_tokens": 128000,
|
||||
|
|
@ -4224,7 +4304,8 @@
|
|||
"mode": "chat",
|
||||
"source": "https://mistral.ai/technology/",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/open-codestral-mamba": {
|
||||
"max_tokens": 256000,
|
||||
|
|
@ -4261,7 +4342,8 @@
|
|||
"source": "https://mistral.ai/news/devstral",
|
||||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true
|
||||
"supports_tool_choice": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-medium-latest": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4275,7 +4357,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-medium-2506": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4289,7 +4372,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-small-latest": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4303,7 +4387,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/magistral-small-2506": {
|
||||
"max_tokens": 40000,
|
||||
|
|
@ -4317,7 +4402,8 @@
|
|||
"supports_function_calling": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
"supports_reasoning": true,
|
||||
"supports_response_schema": true
|
||||
},
|
||||
"mistral/mistral-embed": {
|
||||
"max_tokens": 8192,
|
||||
|
|
@ -9995,7 +10081,15 @@
|
|||
"mode": "chat",
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"mistralai/mistral-small-3.1-24b-instruct": {
|
||||
"openrouter/mistralai/mistral-small-3.1-24b-instruct": {
|
||||
"max_tokens": 32000,
|
||||
"input_cost_per_token": 1e-07,
|
||||
"output_cost_per_token": 3e-07,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat",
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"openrouter/mistralai/mistral-small-3.2-24b-instruct": {
|
||||
"max_tokens": 32000,
|
||||
"input_cost_per_token": 1e-07,
|
||||
"output_cost_per_token": 3e-07,
|
||||
|
|
@ -13647,13 +13741,14 @@
|
|||
"input_cost_per_token": 2e-06,
|
||||
"output_cost_per_token": 8e-06,
|
||||
"output_cost_per_reasoning_token": 3e-06,
|
||||
"litellm_provider": "perplexity",
|
||||
"mode": "chat",
|
||||
"citation_cost_per_token": 2e-06,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.005,
|
||||
"search_context_size_medium": 0.005,
|
||||
"search_context_size_high": 0.005
|
||||
},
|
||||
"litellm_provider": "perplexity",
|
||||
"mode": "chat",
|
||||
"supports_reasoning": true,
|
||||
"supports_web_search": true
|
||||
},
|
||||
|
|
|
|||
6
poetry.lock
generated
|
|
@ -1833,14 +1833,14 @@ openai = ["openai (>=0.27.8)"]
|
|||
|
||||
[[package]]
|
||||
name = "litellm-enterprise"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
description = "Package for LiteLLM Enterprise features"
|
||||
optional = true
|
||||
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"proxy\""
|
||||
files = [
|
||||
{file = "litellm_enterprise-0.1.8.tar.gz", hash = "sha256:59b8c8a18f51c1e5fe2e341d66ba268c2814c8035825ae929530438524dc08ca"},
|
||||
{file = "litellm_enterprise-0.1.9.tar.gz", hash = "sha256:2bdf629cf8bd36805bad70acb609bfa0c00eaf72d3b42f6e17c54c5b50758c4a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4991,4 +4991,4 @@ utils = ["numpydoc"]
|
|||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.8.1,<4.0, !=3.9.7"
|
||||
content-hash = "1d0d0c73c55208694ca58a8a8d12ee0e95e265bb16aefd72d79ca8b5e49a8bca"
|
||||
content-hash = "c3d56e337720ab9c5ab2ec87794f16d1ba831ee86edeb236ae7429f3a7717677"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "litellm"
|
||||
version = "1.73.0"
|
||||
version = "1.73.2"
|
||||
description = "Library to easily interface with LLM API providers"
|
||||
authors = ["BerriAI"]
|
||||
license = "MIT"
|
||||
|
|
@ -58,7 +58,7 @@ redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.
|
|||
mcp = {version = "1.9.3", optional = true, python = ">=3.10"}
|
||||
litellm-proxy-extras = {version = "0.2.5", optional = true}
|
||||
rich = {version = "13.7.1", optional = true}
|
||||
litellm-enterprise = {version = "0.1.8", optional = true}
|
||||
litellm-enterprise = {version = "0.1.9", optional = true}
|
||||
diskcache = {version = "^5.6.1", optional = true}
|
||||
|
||||
[tool.poetry.extras]
|
||||
|
|
@ -141,7 +141,7 @@ requires = ["poetry-core", "wheel"]
|
|||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.commitizen]
|
||||
version = "1.73.0"
|
||||
version = "1.73.2"
|
||||
version_files = [
|
||||
"pyproject.toml:^version"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -56,4 +56,4 @@ websockets==13.1.0 # for realtime API
|
|||
########################
|
||||
# LITELLM ENTERPRISE DEPENDENCIES
|
||||
########################
|
||||
litellm-enterprise==0.1.8
|
||||
litellm-enterprise==0.1.9
|
||||
|
|
|
|||
|
|
@ -1,24 +1,20 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
gemini_model_cost_map = json.load(open("model_prices_and_context_window.json"))
|
||||
mistral_model_cost_map = json.load(open("model_prices_and_context_window.json"))
|
||||
|
||||
for model, model_info in gemini_model_cost_map.items():
|
||||
for model, model_info in mistral_model_cost_map.items():
|
||||
if (
|
||||
(
|
||||
model_info.get("litellm_provider") == "gemini"
|
||||
or model_info.get("litellm_provider") == "vertex_ai-language-models"
|
||||
)
|
||||
(model_info.get("litellm_provider") == "mistral")
|
||||
and model_info.get("mode") == "chat"
|
||||
and ("gemini-2.5" in model and "tts" not in model)
|
||||
and model_info.get("supports_pdf_input") is None
|
||||
and ("codestral-mamba" not in model)
|
||||
):
|
||||
"""
|
||||
Update all gemini chat models to support pdf input
|
||||
Update all mistral models to supports_response_schema
|
||||
"""
|
||||
model_info["supports_pdf_input"] = True
|
||||
print(f"Updated {model} to support pdf input")
|
||||
model_info["supports_response_schema"] = True
|
||||
print(f"Updated {model} to support response schema")
|
||||
|
||||
json.dump(
|
||||
gemini_model_cost_map, open("model_prices_and_context_window.json", "w"), indent=4
|
||||
mistral_model_cost_map, open("model_prices_and_context_window.json", "w"), indent=4
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@ from litellm.caching import DualCache
|
|||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bedrock_guardrails():
|
||||
async def test_bedrock_guardrails_pii_masking():
|
||||
# Create proper mock objects
|
||||
mock_user_api_key_dict = UserAPIKeyAuth()
|
||||
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="wf0hkdb5x07f",
|
||||
guardrailVersion="DRAFT",
|
||||
mask_request_content=True,
|
||||
)
|
||||
|
||||
request_data = {
|
||||
|
|
@ -35,7 +34,7 @@ async def test_bedrock_guardrails():
|
|||
user_api_key_dict=mock_user_api_key_dict,
|
||||
call_type="completion"
|
||||
)
|
||||
print(response)
|
||||
print("response after moderation hook", response)
|
||||
|
||||
if response: # Only assert if response is not None
|
||||
assert response["messages"][0]["content"] == "Hello, my phone number is {PHONE}"
|
||||
|
|
@ -45,14 +44,13 @@ async def test_bedrock_guardrails():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bedrock_guardrails_content_list():
|
||||
async def test_bedrock_guardrails_pii_masking_content_list():
|
||||
# Create proper mock objects
|
||||
mock_user_api_key_dict = UserAPIKeyAuth()
|
||||
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="wf0hkdb5x07f",
|
||||
guardrailVersion="DRAFT",
|
||||
mask_request_content=True,
|
||||
)
|
||||
|
||||
request_data = {
|
||||
|
|
@ -105,7 +103,7 @@ async def test_bedrock_guardrails_with_streaming():
|
|||
)
|
||||
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="wf0hkdb5x07f",
|
||||
guardrailIdentifier="ff6ujrregl1q",
|
||||
guardrailVersion="DRAFT",
|
||||
supported_event_hooks=[GuardrailEventHooks.post_call],
|
||||
guardrail_name="bedrock-post-guard",
|
||||
|
|
@ -118,7 +116,7 @@ async def test_bedrock_guardrails_with_streaming():
|
|||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "My name is ishaan@gmail.com"
|
||||
"content": "Hi I like coffee"
|
||||
}
|
||||
],
|
||||
"stream": True,
|
||||
|
|
@ -154,7 +152,7 @@ async def test_bedrock_guardrails_with_streaming_no_violation():
|
|||
)
|
||||
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="wf0hkdb5x07f",
|
||||
guardrailIdentifier="ff6ujrregl1q",
|
||||
guardrailVersion="DRAFT",
|
||||
supported_event_hooks=[GuardrailEventHooks.post_call],
|
||||
guardrail_name="bedrock-post-guard",
|
||||
|
|
@ -334,4 +332,241 @@ async def test_bedrock_guardrail_aws_param_persistence():
|
|||
assert kwargs["aws_secret_access_key"] == "test-secret-key"
|
||||
assert kwargs["aws_region_name"] == "us-east-1"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bedrock_guardrail_blocked_vs_anonymized_actions():
|
||||
"""Test that BLOCKED actions raise exceptions but ANONYMIZED actions do not"""
|
||||
from unittest.mock import MagicMock
|
||||
from litellm.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrail
|
||||
from litellm.types.proxy.guardrails.guardrail_hooks.bedrock_guardrails import BedrockGuardrailResponse
|
||||
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="test-guardrail",
|
||||
guardrailVersion="DRAFT"
|
||||
)
|
||||
|
||||
# Test 1: ANONYMIZED action should NOT raise exception
|
||||
anonymized_response: BedrockGuardrailResponse = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"outputs": [{
|
||||
"text": "Hello, my phone number is {PHONE}"
|
||||
}],
|
||||
"assessments": [{
|
||||
"sensitiveInformationPolicy": {
|
||||
"piiEntities": [{
|
||||
"type": "PHONE",
|
||||
"match": "+1 412 555 1212",
|
||||
"action": "ANONYMIZED"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
should_raise = guardrail._should_raise_guardrail_blocked_exception(anonymized_response)
|
||||
assert should_raise is False, "ANONYMIZED actions should not raise exceptions"
|
||||
|
||||
# Test 2: BLOCKED action should raise exception
|
||||
blocked_response: BedrockGuardrailResponse = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"outputs": [{
|
||||
"text": "I can't provide that information."
|
||||
}],
|
||||
"assessments": [{
|
||||
"topicPolicy": {
|
||||
"topics": [{
|
||||
"name": "Sensitive Topic",
|
||||
"type": "DENY",
|
||||
"action": "BLOCKED"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
should_raise = guardrail._should_raise_guardrail_blocked_exception(blocked_response)
|
||||
assert should_raise is True, "BLOCKED actions should raise exceptions"
|
||||
|
||||
# Test 3: Mixed actions - should raise if ANY action is BLOCKED
|
||||
mixed_response: BedrockGuardrailResponse = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"outputs": [{
|
||||
"text": "I can't provide that information."
|
||||
}],
|
||||
"assessments": [{
|
||||
"sensitiveInformationPolicy": {
|
||||
"piiEntities": [{
|
||||
"type": "PHONE",
|
||||
"match": "+1 412 555 1212",
|
||||
"action": "ANONYMIZED"
|
||||
}]
|
||||
},
|
||||
"topicPolicy": {
|
||||
"topics": [{
|
||||
"name": "Blocked Topic",
|
||||
"type": "DENY",
|
||||
"action": "BLOCKED"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
should_raise = guardrail._should_raise_guardrail_blocked_exception(mixed_response)
|
||||
assert should_raise is True, "Mixed actions with any BLOCKED should raise exceptions"
|
||||
|
||||
# Test 4: NONE action should not raise exception
|
||||
none_response: BedrockGuardrailResponse = {
|
||||
"action": "NONE",
|
||||
"outputs": [],
|
||||
"assessments": []
|
||||
}
|
||||
|
||||
should_raise = guardrail._should_raise_guardrail_blocked_exception(none_response)
|
||||
assert should_raise is False, "NONE actions should not raise exceptions"
|
||||
|
||||
# Test 5: Test other policy types with BLOCKED actions
|
||||
content_blocked_response: BedrockGuardrailResponse = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"outputs": [{
|
||||
"text": "I can't provide that information."
|
||||
}],
|
||||
"assessments": [{
|
||||
"contentPolicy": {
|
||||
"filters": [{
|
||||
"type": "VIOLENCE",
|
||||
"confidence": "HIGH",
|
||||
"action": "BLOCKED"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
should_raise = guardrail._should_raise_guardrail_blocked_exception(content_blocked_response)
|
||||
assert should_raise is True, "Content policy BLOCKED actions should raise exceptions"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bedrock_guardrail_masking_with_anonymized_response():
|
||||
"""Test that masking works correctly when guardrail returns ANONYMIZED actions"""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.caching import DualCache
|
||||
|
||||
# Create proper mock objects
|
||||
mock_user_api_key_dict = UserAPIKeyAuth()
|
||||
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="test-guardrail",
|
||||
guardrailVersion="DRAFT",
|
||||
mask_request_content=True,
|
||||
)
|
||||
|
||||
# Mock the Bedrock API response with ANONYMIZED action
|
||||
mock_bedrock_response = MagicMock()
|
||||
mock_bedrock_response.status_code = 200
|
||||
mock_bedrock_response.json.return_value = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"outputs": [{
|
||||
"text": "Hello, my phone number is {PHONE}"
|
||||
}],
|
||||
"assessments": [{
|
||||
"sensitiveInformationPolicy": {
|
||||
"piiEntities": [{
|
||||
"type": "PHONE",
|
||||
"match": "+1 412 555 1212",
|
||||
"action": "ANONYMIZED"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
request_data = {
|
||||
"model": "gpt-4o",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello, my phone number is +1 412 555 1212"},
|
||||
],
|
||||
}
|
||||
|
||||
# Patch the async_handler.post method
|
||||
with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post:
|
||||
mock_post.return_value = mock_bedrock_response
|
||||
|
||||
# This should NOT raise an exception since action is ANONYMIZED
|
||||
try:
|
||||
response = await guardrail.async_moderation_hook(
|
||||
data=request_data,
|
||||
user_api_key_dict=mock_user_api_key_dict,
|
||||
call_type="completion"
|
||||
)
|
||||
# Should succeed and return data with masked content
|
||||
assert response is not None
|
||||
assert response["messages"][0]["content"] == "Hello, my phone number is {PHONE}"
|
||||
except Exception as e:
|
||||
pytest.fail(f"Should not raise exception for ANONYMIZED actions, but got: {e}")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bedrock_guardrail_uses_masked_output_without_masking_flags():
|
||||
"""Test that masked output from guardrails is used even when masking flags are not enabled"""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
|
||||
# Create proper mock objects
|
||||
mock_user_api_key_dict = UserAPIKeyAuth()
|
||||
|
||||
# Create guardrail WITHOUT masking flags enabled
|
||||
guardrail = BedrockGuardrail(
|
||||
guardrailIdentifier="test-guardrail",
|
||||
guardrailVersion="DRAFT",
|
||||
# Note: No mask_request_content=True or mask_response_content=True
|
||||
)
|
||||
|
||||
# Mock the Bedrock API response with ANONYMIZED action and masked output
|
||||
mock_bedrock_response = MagicMock()
|
||||
mock_bedrock_response.status_code = 200
|
||||
mock_bedrock_response.json.return_value = {
|
||||
"action": "GUARDRAIL_INTERVENED",
|
||||
"outputs": [{
|
||||
"text": "Hello, my phone number is {PHONE} and email is {EMAIL}"
|
||||
}],
|
||||
"assessments": [{
|
||||
"sensitiveInformationPolicy": {
|
||||
"piiEntities": [
|
||||
{
|
||||
"type": "PHONE",
|
||||
"match": "+1 412 555 1212",
|
||||
"action": "ANONYMIZED"
|
||||
},
|
||||
{
|
||||
"type": "EMAIL",
|
||||
"match": "user@example.com",
|
||||
"action": "ANONYMIZED"
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
request_data = {
|
||||
"model": "gpt-4o",
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello, my phone number is +1 412 555 1212 and email is user@example.com"},
|
||||
],
|
||||
}
|
||||
|
||||
# Patch the async_handler.post method
|
||||
with patch.object(guardrail.async_handler, 'post', new_callable=AsyncMock) as mock_post:
|
||||
mock_post.return_value = mock_bedrock_response
|
||||
|
||||
# This should use the masked output even without masking flags
|
||||
response = await guardrail.async_moderation_hook(
|
||||
data=request_data,
|
||||
user_api_key_dict=mock_user_api_key_dict,
|
||||
call_type="completion"
|
||||
)
|
||||
|
||||
# Should use the masked content from guardrail output
|
||||
assert response is not None
|
||||
assert response["messages"][0]["content"] == "Hello, my phone number is {PHONE} and email is {EMAIL}"
|
||||
print("✅ Masked output was applied even without masking flags enabled")
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -594,6 +594,7 @@ async def test_azure_embedding_max_retries_0(
|
|||
def test_azure_safety_result():
|
||||
"""Bubble up safety result from Azure OpenAI"""
|
||||
from litellm import completion
|
||||
|
||||
litellm._turn_on_debug()
|
||||
|
||||
response = completion(
|
||||
|
|
@ -602,4 +603,30 @@ def test_azure_safety_result():
|
|||
)
|
||||
print(f"response: {response}")
|
||||
assert response.choices[0].message.content is not None
|
||||
assert response.choices[0].provider_specific_fields is not None
|
||||
assert response.choices[0].provider_specific_fields is not None
|
||||
|
||||
|
||||
def test_azure_openai_responses_bridge():
|
||||
from litellm import completion
|
||||
import litellm
|
||||
|
||||
litellm._turn_on_debug()
|
||||
|
||||
with patch.object(litellm, "responses") as mock_responses:
|
||||
try:
|
||||
response = completion(
|
||||
model="azure/responses/test-azure-computer-use-preview",
|
||||
messages=[{"role": "user", "content": "Hello world"}],
|
||||
api_base=os.getenv("AZURE_COMPUTER_USE_API_BASE"),
|
||||
api_version="2025-04-01-preview",
|
||||
api_key=os.getenv("AZURE_COMPUTER_USE_API_KEY"),
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
mock_responses.assert_called_once()
|
||||
assert (
|
||||
mock_responses.call_args.kwargs["model"]
|
||||
== "test-azure-computer-use-preview"
|
||||
)
|
||||
assert mock_responses.call_args.kwargs["custom_llm_provider"] == "azure"
|
||||
|
|
|
|||