Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d042433
Add gpt-realtime-whisper Realtime transcription support (OpenAI + Azu…
emerzon Jun 11, 2026
1b54a77
Lazy evaluate debug log in pass-through endpoint (#30177)
nina-hu Jun 11, 2026
74bff7e
fix(parallel_ai): migrate search integration from v1beta to v1 endpoi…
s-jag Jun 11, 2026
516a4e7
feat(focus): add Mavvrik destination for FOCUS export (#29935)
pghuge-cloudwiz Jun 11, 2026
0dee014
fix: preserve responses streaming flag (#30189)
alex107ivanov Jun 11, 2026
8929445
fix(spend/daily-activity): stable offset pagination via id tiebreaker…
hclsys Jun 11, 2026
3ff2742
fix(oci): inject a default maxTokens so omitted max_tokens doesn't tr…
fede-kamel Jun 11, 2026
460f45c
fix(oci): translate response_format json_schema to OCI's accepted sha…
fede-kamel Jun 11, 2026
41924f6
fix(oci): accept default n=1 on Cohere instead of hard-failing (#29705)
fede-kamel Jun 11, 2026
e1f3a96
fix(oci): drop max_retries instead of hard-failing on OCI (#29727)
fede-kamel Jun 11, 2026
adf7c97
fix(spend-logs): rehydrate metadata JSONB text on ui_view_spend_logs …
hclsys Jun 11, 2026
35dc441
fix(proxy): release max_parallel_requests slot when a stream is cance…
Ar-maan05 Jun 11, 2026
abf04d0
fix(mcp): propagate model into model_call_details for passthrough too…
auggie246 Jun 11, 2026
b9c0a35
fix(mcp): surface upstream challenges for delegated OAuth (#30124)
jstar0 Jun 11, 2026
aad303d
perf(benchmarks): add CPU timing metrics to streaming benchmark (#29980)
SakshamMaggo Jun 11, 2026
e89bd5c
fix(gemini): don't emit empty choices on metadata-only stream chunks …
hclsys Jun 11, 2026
93688ea
fix(key): allow /key/update to clear budget_limits with [] or null (#…
Cyberfilo Jun 11, 2026
d134748
fix(proxy): persist guardrail info in spend logs for /v1/responses (#…
Cyberfilo Jun 11, 2026
8dfbe86
fix(proxy): handle non-standard SSE frames in Anthropic passthrough l…
leikaiwei Jun 11, 2026
45f9e26
feat(newrelic): Add New Relic extension (#26989)
bonczj Jun 11, 2026
c5df3f2
Add Azure AI Foundry DeepSeek V3.1 and V4 Pro/Flash global pricing to…
mdturp Jun 11, 2026
706ea8f
fix(logging): translate Responses bridge result to ModelResponse for …
piotrminkina Jun 11, 2026
4782aa9
fix(anthropic-adapter): re-emit first delta on streaming content-bloc…
draix Jun 11, 2026
9dd9d23
feat: add opt-in healthy_only filter to GET /v1/models (#30130)
nix-tkobayashi Jun 11, 2026
7f17115
Dedupe team soft budget alerts by team_id instead of token (#30097)
Cyberfilo Jun 11, 2026
c66a4d0
feat(bedrock guardrails): support contextual grounding qualifiers (re…
jpv-costa Jun 11, 2026
dcfd2e9
Feat(guardrail): Adding support for custom Ovalix guardrail (#21887)
shalom-ovalix Jun 11, 2026
33985f7
fix(github_copilot): normalize per-event item_id in /responses stream…
codgician Jun 11, 2026
fad4689
feat: add /model/block and /model/unblock endpoints (#30125)
AgentGymLeader Jun 11, 2026
ca556e3
fix: add week unit support to get_next_standardized_reset_time (#30100)
AgentGymLeader Jun 11, 2026
e267e39
fix: black formatting and remove undocumented MAVVRIK_FOCUS_FREQUENCY…
Sameerlite Jun 11, 2026
7444774
fix: black formatting with correct version and sync schema.d.ts for h…
Sameerlite Jun 11, 2026
5df17c3
fix: resolve mypy errors and add transcription_sessions to JSON schem…
Sameerlite Jun 11, 2026
a30a0a4
fix: restore MAVVRIK_FOCUS_FREQUENCY guard and exclude it from docs k…
Sameerlite Jun 11, 2026
e0684c2
fix: address Greptile P2 comments - move constant, use UTC datetime, …
Sameerlite Jun 11, 2026
3126c22
revert: restore original team lookup logic in can_key_call_resolved_m…
Sameerlite Jun 11, 2026
5e61498
Merge branch 'litellm_internal_staging' into litellm_oss_110626
mateo-berri Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions litellm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def _dev_env_hot_reload_enabled() -> bool:
Type,
)
from litellm.types.integrations.datadog import DatadogInitParams
from litellm.types.integrations.newrelic import NewRelicInitParams
from litellm._logging import (
set_verbose,
_turn_on_debug,
Expand Down Expand Up @@ -154,10 +155,12 @@ def _dev_env_hot_reload_enabled() -> bool:
"gitlab",
"cloudzero",
"focus",
"mavvrik",
"vantage",
"posthog",
"levo",
"compression_interception",
"newrelic",
]
cold_storage_custom_logger: Optional[_custom_logger_compatible_callbacks_literal] = None
logged_real_time_event_types: Optional[Union[List[str], Literal["*"]]] = None
Expand Down Expand Up @@ -412,6 +415,7 @@ def _dev_env_hot_reload_enabled() -> bool:
s3_audit_callback_params: Optional[Dict] = None
datadog_llm_observability_params: Optional[Union[DatadogLLMObsInitParams, Dict]] = None
datadog_params: Optional[Union[DatadogInitParams, Dict]] = None
newrelic_params: Optional[Union[NewRelicInitParams, Dict]] = None
aws_sqs_callback_params: Optional[Dict] = None
generic_logger_headers: Optional[Dict] = None
default_key_generate_params: Optional[Dict] = None
Expand Down Expand Up @@ -1373,6 +1377,7 @@ def add_known_models(model_cost_map: Optional[Dict] = None):
from .realtime_api.main import (
_arealtime,
acreate_realtime_client_secret,
acreate_realtime_transcription_session,
arealtime_calls,
)
from .responses.main import _aresponses_websocket
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ def completion(self, *args, **kwargs) -> Union[
model_response = validated_kwargs["model_response"]
logging_obj = validated_kwargs["logging_obj"]
custom_llm_provider = validated_kwargs["custom_llm_provider"]
if kwargs.get("stream") is True and "stream" not in optional_params:
optional_params = {**optional_params, "stream": True}

request_data = self.transformation_handler.transform_request(
model=model,
Expand Down Expand Up @@ -263,6 +265,8 @@ async def acompletion(
model_response = validated_kwargs["model_response"]
logging_obj = validated_kwargs["logging_obj"]
custom_llm_provider = validated_kwargs["custom_llm_provider"]
if kwargs.get("stream") is True and "stream" not in optional_params:
optional_params = {**optional_params, "stream": True}

try:
request_data = self.transformation_handler.transform_request(
Expand Down
2 changes: 2 additions & 0 deletions litellm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@
DEFAULT_ANTHROPIC_CHAT_MAX_TOKENS = int(
os.getenv("DEFAULT_ANTHROPIC_CHAT_MAX_TOKENS", 4096)
)
DEFAULT_OCI_CHAT_MAX_TOKENS = 4096
TOGETHER_AI_4_B = int(os.getenv("TOGETHER_AI_4_B", 4))
TOGETHER_AI_8_B = int(os.getenv("TOGETHER_AI_8_B", 8))
TOGETHER_AI_21_B = int(os.getenv("TOGETHER_AI_21_B", 21))
Expand Down Expand Up @@ -1483,6 +1484,7 @@
DB_DAILY_TAG_SPEND_UPDATE_JOB_NAME = "db_daily_tag_spend_update_job"
PROMETHEUS_EMIT_BUDGET_METRICS_JOB_NAME = "prometheus_emit_budget_metrics"
CLOUDZERO_EXPORT_USAGE_DATA_JOB_NAME = "cloudzero_export_usage_data"
MAVVRIK_FOCUS_EXPORT_JOB_NAME = "mavvrik_focus_export_usage_data"
CLOUDZERO_MAX_FETCHED_DATA_RECORDS = int(
os.getenv("CLOUDZERO_MAX_FETCHED_DATA_RECORDS", 50000)
)
Expand Down
100 changes: 100 additions & 0 deletions litellm/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,11 @@ def create_logging_realtime_object(
)


_TRANSCRIPTION_COMPLETED_EVENT_TYPE = (
"conversation.item.input_audio_transcription.completed"
)


def handle_realtime_stream_cost_calculation(
results: OpenAIRealtimeStreamList,
combined_usage_object: Usage,
Expand Down Expand Up @@ -2533,4 +2538,99 @@ def handle_realtime_stream_cost_calculation(
break # exit if we find a valid model
total_cost = input_cost_per_token + output_cost_per_token

if any(r.get("type") == _TRANSCRIPTION_COMPLETED_EVENT_TYPE for r in results):
total_cost += handle_realtime_transcription_cost_calculation(
results=results,
custom_llm_provider=custom_llm_provider,
litellm_model_name=litellm_model_name,
)

return total_cost


def handle_realtime_transcription_cost_calculation(
Comment thread
Sameerlite marked this conversation as resolved.
results: OpenAIRealtimeStreamList,
custom_llm_provider: str,
litellm_model_name: str,
) -> float:
"""
Cost for realtime transcription sessions (e.g. gpt-realtime-whisper).

Transcription sessions emit no `response.done` events; instead each
`conversation.item.input_audio_transcription.completed` event carries a
`usage` object billed by the ASR model. The usage is one of:
- {"type": "duration", "seconds": <float>} → priced via input_cost_per_second
- {"type": "tokens", "input_tokens": ...} → priced via input/audio token cost
"""
completed_events = [
cast(dict, result)
for result in results
if result.get("type") == _TRANSCRIPTION_COMPLETED_EVENT_TYPE
]
if not completed_events:
return 0.0

model_name = (
_get_transcription_model_name_from_results(results) or litellm_model_name
)
try:
model_info = litellm.get_model_info(
model=model_name, custom_llm_provider=custom_llm_provider
)
except Exception:
model_info = None

total_cost = 0.0
for event in completed_events:
usage = event.get("usage") or {}
total_cost += _transcription_usage_cost(usage, model_info)
return total_cost


def _get_transcription_model_name_from_results(
results: OpenAIRealtimeStreamList,
) -> Optional[str]:
"""Resolve the ASR model from a transcription_session.* / session.* event."""
for result in results:
if result.get("type") in (
"transcription_session.created",
"transcription_session.updated",
"session.created",
"session.updated",
):
session = cast(dict, result).get("session", {}) or {}
transcription = (
(session.get("audio", {}) or {}).get("input", {}) or {}
).get("transcription", {}) or session.get("input_audio_transcription", {})
model = (transcription or {}).get("model") or session.get("model")
if model:
return model
return None


def _transcription_usage_cost(usage: dict, model_info: Optional[ModelInfo]) -> float:
if model_info is None:
return 0.0
usage_type = usage.get("type")
if usage_type == "duration":
seconds = usage.get("seconds") or 0.0
per_second = model_info.get("input_cost_per_second") or 0.0
return float(seconds) * float(per_second)
if usage_type == "tokens":
input_token_details = usage.get("input_token_details") or {}
audio_tokens = input_token_details.get("audio_tokens") or 0
text_tokens = input_token_details.get("text_tokens") or 0
output_tokens = usage.get("output_tokens") or 0
audio_cost = float(audio_tokens) * float(
model_info.get("input_cost_per_audio_token")
or model_info.get("input_cost_per_token")
or 0.0
)
text_cost = float(text_tokens) * float(
model_info.get("input_cost_per_token") or 0.0
)
output_cost = float(output_tokens) * float(
model_info.get("output_cost_per_token") or 0.0
)
return audio_cost + text_cost + output_cost
return 0.0
4 changes: 3 additions & 1 deletion litellm/integrations/SlackAlerting/budget_alert_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import Literal

from litellm.proxy._types import CallInfo
from litellm.proxy._types import CallInfo, Litellm_EntityType


class BaseBudgetAlertType(ABC):
Expand Down Expand Up @@ -31,6 +31,8 @@ def get_event_message(self) -> str:
return "Soft Budget Crossed: "

def get_id(self, user_info: CallInfo) -> str:
if user_info.event_group == Litellm_EntityType.TEAM:
return user_info.team_id or "default_id"
return user_info.token or "default_id"


Expand Down
15 changes: 15 additions & 0 deletions litellm/integrations/callback_configs.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,21 @@
},
"description": "Langsmith Logging Integration"
},
{
"id": "newrelic",
"displayName": "New Relic",
"logo": "newrelic.png",
"supports_key_team_logging": false,
"dynamic_params": {
"NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLED": {
"type": "text",
"ui_name": "Record AI Content (default: true)",
"description": "Whether to record AI message content. Set to false to disable.",
"required": false
}
},
"description": "New Relic AI Monitoring Integration"
},
{
"id": "openmeter",
"displayName": "OpenMeter",
Expand Down
2 changes: 2 additions & 0 deletions litellm/integrations/focus/destinations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .factory import FocusDestinationFactory
from .gcs_destination import FocusGCSDestination
from .s3_destination import FocusS3Destination
from .mavvrik_destination import FocusMavvrikDestination
from .vantage_destination import FocusVantageDestination

__all__ = [
Expand All @@ -12,5 +13,6 @@
"FocusGCSDestination",
"FocusTimeWindow",
"FocusS3Destination",
"FocusMavvrikDestination",
"FocusVantageDestination",
]
12 changes: 12 additions & 0 deletions litellm/integrations/focus/destinations/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .base import FocusDestination
from .gcs_destination import FocusGCSDestination
from .s3_destination import FocusS3Destination
from .mavvrik_destination import FocusMavvrikDestination
from .vantage_destination import FocusVantageDestination


Expand All @@ -32,6 +33,8 @@ def create(
return FocusVantageDestination(prefix=prefix, config=normalized_config)
if provider_lower == "gcs":
return FocusGCSDestination(prefix=prefix, config=normalized_config)
if provider_lower == "mavvrik":
return FocusMavvrikDestination(prefix=prefix, config=normalized_config)
raise NotImplementedError(
f"Provider '{provider}' not supported for Focus export"
)
Expand Down Expand Up @@ -87,6 +90,15 @@ def _resolve_config(
"FOCUS_GCS_BUCKET_NAME must be provided for GCS exports"
)
return {k: v for k, v in resolved.items() if v is not None}
if provider == "mavvrik":
resolved = {
"api_key": overrides.get("api_key") or os.getenv("MAVVRIK_API_KEY"),
"api_endpoint": overrides.get("api_endpoint")
or os.getenv("MAVVRIK_API_ENDPOINT"),
"connection_id": overrides.get("connection_id")
or os.getenv("MAVVRIK_CONNECTION_ID"),
}
return {k: v for k, v in resolved.items() if v is not None}
raise NotImplementedError(
f"Provider '{provider}' not supported for Focus export configuration"
)
Loading
Loading