From 1b3c8fec83eae73b6caf05d41d1552e5224056f8 Mon Sep 17 00:00:00 2001 From: ".mobo" <1997638+dotmobo@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:27:09 +0100 Subject: [PATCH 1/9] put logfile and pidfile in /tmp to avoid permission denied error in non root environment (#17267) --- docker/supervisord.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/supervisord.conf b/docker/supervisord.conf index c6855fe652..877335804f 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -1,6 +1,8 @@ [supervisord] nodaemon=true loglevel=info +logfile=/tmp/supervisord.log +pidfile=/tmp/supervisord.pid [group:litellm] programs=main,health From 2a0f87bde048e406e6dbf1d5465e77d7e0282bc3 Mon Sep 17 00:00:00 2001 From: Dushyant <157586156+dushyantzz@users.noreply.github.com> Date: Sat, 17 Jan 2026 01:00:16 +0530 Subject: [PATCH 2/9] Fix audio cost per second override (#19158) --- docs/my-website/docs/sdk_custom_pricing.md | 5 +++- litellm/llms/openai/cost_calculation.py | 22 ++++++-------- tests/test_litellm/test_cost_calculator.py | 34 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/docs/my-website/docs/sdk_custom_pricing.md b/docs/my-website/docs/sdk_custom_pricing.md index c857711510..ac956db472 100644 --- a/docs/my-website/docs/sdk_custom_pricing.md +++ b/docs/my-website/docs/sdk_custom_pricing.md @@ -2,7 +2,10 @@ Register custom pricing for sagemaker completion model. -For cost per second pricing, you **just** need to register `input_cost_per_second`. +For cost per second pricing, register `input_cost_per_second`. If your provider +charges for audio output duration (e.g., TTS), also set `output_cost_per_second`. +Values of `0` are treated as not billable, so `output_cost_per_second: 0` will +not override `input_cost_per_second`. ```python # !pip install boto3 diff --git a/litellm/llms/openai/cost_calculation.py b/litellm/llms/openai/cost_calculation.py index e5349db3af..339ce9caed 100644 --- a/litellm/llms/openai/cost_calculation.py +++ b/litellm/llms/openai/cost_calculation.py @@ -105,25 +105,21 @@ def cost_per_second( prompt_cost = 0.0 completion_cost = 0.0 ## Speech / Audio cost calculation - if ( - "output_cost_per_second" in model_info - and model_info["output_cost_per_second"] is not None - ): + output_cost_per_second = model_info.get("output_cost_per_second") + input_cost_per_second = model_info.get("input_cost_per_second") + + if output_cost_per_second is not None and output_cost_per_second > 0: verbose_logger.debug( - f"For model={model} - output_cost_per_second: {model_info.get('output_cost_per_second')}; duration: {duration}" + f"For model={model} - output_cost_per_second: {output_cost_per_second}; duration: {duration}" ) ## COST PER SECOND ## - completion_cost = model_info["output_cost_per_second"] * duration - elif ( - "input_cost_per_second" in model_info - and model_info["input_cost_per_second"] is not None - ): + completion_cost = output_cost_per_second * duration + if input_cost_per_second is not None and input_cost_per_second > 0: verbose_logger.debug( - f"For model={model} - input_cost_per_second: {model_info.get('input_cost_per_second')}; duration: {duration}" + f"For model={model} - input_cost_per_second: {input_cost_per_second}; duration: {duration}" ) ## COST PER SECOND ## - prompt_cost = model_info["input_cost_per_second"] * duration - completion_cost = 0.0 + prompt_cost = input_cost_per_second * duration return prompt_cost, completion_cost diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 4d6599fc1b..42bcfca231 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -192,6 +192,40 @@ def test_transcription_cost_falls_back_to_duration(): assert pytest.approx(cost, rel=1e-6) == expected_cost +def test_transcription_cost_prefers_input_when_output_zero(monkeypatch): + from litellm import completion_cost + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + model_name = "custom-whisper-input-only" + custom_model_info = { + "input_cost_per_second": 0.00005, + "output_cost_per_second": 0.0, + "litellm_provider": "openai", + "mode": "audio_transcription", + "supported_endpoints": ["/v1/audio/transcriptions"], + } + monkeypatch.setattr( + litellm, + "model_cost", + {**litellm.model_cost, model_name: custom_model_info}, + ) + + response = TranscriptionResponse(text="demo text") + response.duration = 300.0 + + cost = completion_cost( + completion_response=response, + model=model_name, + custom_llm_provider="openai", + call_type="atranscription", + ) + + expected_cost = 300.0 * 0.00005 + assert pytest.approx(cost, rel=1e-6) == expected_cost + + def test_handle_realtime_stream_cost_calculation(): from litellm.cost_calculator import RealtimeAPITokenUsageProcessor From ce1a2c3209ef46e6f48565e561739cb2579dcbb1 Mon Sep 17 00:00:00 2001 From: Ostap Bodnar <171420608+obod-mpw@users.noreply.github.com> Date: Sat, 17 Jan 2026 01:18:46 +0200 Subject: [PATCH 3/9] add openai/dall-e base pricing entries (#19133) --- model_prices_and_context_window.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index a130aefa5d..ec55c251a7 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -7826,6 +7826,24 @@ "supports_tool_choice": true, "supports_vision": true }, + "dall-e-2": { + "input_cost_per_image": 0.02, + "litellm_provider": "openai", + "mode": "image_generation", + "supported_endpoints": [ + "/v1/images/generations", + "/v1/images/edits", + "/v1/images/variations" + ] + }, + "dall-e-3": { + "input_cost_per_image": 0.04, + "litellm_provider": "openai", + "mode": "image_generation", + "supported_endpoints": [ + "/v1/images/generations" + ] + }, "deepseek-chat": { "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, From 5a9f6e90cfa7063559bb99a68ed981b1a84cde0a Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Fri, 16 Jan 2026 16:24:28 -0700 Subject: [PATCH 4/9] fix(tools): prevent OOM with nested $defs in tool schemas (#19098) (#19112) --- .../prompt_templates/factory.py | 7 +- litellm/llms/vertex_ai/common_utils.py | 7 +- ...llm_core_utils_prompt_templates_factory.py | 131 ++++++++++++++++++ 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 89a708077f..6eb4ee7449 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -4468,9 +4468,10 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: defs = parameters.pop("$defs", {}) defs_copy = copy.deepcopy(defs) - # flatten the defs - for _, value in defs_copy.items(): - unpack_defs(value, defs_copy) + # Expand $ref references in parameters using the definitions + # Note: We don't pre-flatten defs as that causes exponential memory growth + # with circular references (see issue #19098). unpack_defs handles nested + # refs recursively and correctly detects/skips circular references. unpack_defs(parameters, defs_copy) tool_input_schema = BedrockToolInputSchemaBlock( json=BedrockToolJsonSchemaBlock( diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 1864ef734c..77c8574228 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -453,9 +453,10 @@ def _build_vertex_schema(parameters: dict, add_property_ordering: bool = False): valid_schema_fields = set(get_type_hints(Schema).keys()) defs = parameters.pop("$defs", {}) - # flatten the defs - for name, value in defs.items(): - unpack_defs(value, defs) + # Expand $ref references in parameters using the definitions + # Note: We don't pre-flatten defs as that causes exponential memory growth + # with circular references (see issue #19098). unpack_defs handles nested + # refs recursively and correctly detects/skips circular references. unpack_defs(parameters, defs) # 5. Nullable fields: diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index 42a2b5d097..a22fe13798 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -1392,3 +1392,134 @@ def test_anthropic_messages_pt_server_tool_use_passthrough(): b for b in assistant_msg["content"] if b.get("type") == "text" ) assert text_block["text"] == "I found the time tool. How can I help you?" + + +def test_bedrock_tools_unpack_defs_no_oom_with_nested_refs(): + """ + Regression test for issue #19098: unpack_defs() causes OOM with nested tool schemas. + + The old implementation had a "flatten defs" loop that would pre-expand each def + using unpack_defs(), but since defs often reference each other, each subsequent + call would copy already-expanded content, causing exponential memory growth. + + This test creates a schema with multiple nested $defs that reference each other + to verify the fix prevents memory explosion while still correctly resolving refs. + """ + import sys + import copy + + from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_tools_pt + + # Schema with multiple nested $defs that reference each other + # This pattern would cause OOM with the old "flatten defs" loop + complex_nested_schema = { + "type": "object", + "properties": { + "query": {"$ref": "#/$defs/Expression"}, + }, + "$defs": { + "Expression": { + "type": "object", + "properties": { + "type": {"type": "string", "enum": ["and", "or", "not", "comparison"]}, + "left": {"$ref": "#/$defs/Operand"}, + "right": {"$ref": "#/$defs/Operand"}, + "operator": {"$ref": "#/$defs/Operator"}, + }, + }, + "Operand": { + "type": "object", + "anyOf": [ + {"$ref": "#/$defs/Literal"}, + {"$ref": "#/$defs/FieldRef"}, + {"$ref": "#/$defs/Expression"}, # Circular: Operand -> Expression -> Operand + ], + }, + "Literal": { + "type": "object", + "properties": { + "type": {"type": "string", "const": "literal"}, + "value": {"$ref": "#/$defs/LiteralValue"}, + }, + }, + "LiteralValue": { + "oneOf": [ + {"type": "string"}, + {"type": "number"}, + {"type": "boolean"}, + {"type": "null"}, + ], + }, + "FieldRef": { + "type": "object", + "properties": { + "type": {"type": "string", "const": "field"}, + "name": {"type": "string"}, + "table": {"$ref": "#/$defs/TableRef"}, + }, + }, + "TableRef": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "alias": {"type": "string"}, + }, + }, + "Operator": { + "type": "string", + "enum": ["=", "!=", "<", ">", "<=", ">=", "LIKE", "IN"], + }, + }, + } + + tools = [ + { + "type": "function", + "function": { + "name": "execute_query", + "description": "Execute a query with complex expressions", + "parameters": complex_nested_schema, + }, + } + ] + + # Measure initial size + def get_size(obj, seen=None): + size = sys.getsizeof(obj) + if seen is None: + seen = set() + obj_id = id(obj) + if obj_id in seen: + return 0 + seen.add(obj_id) + if isinstance(obj, dict): + size += sum([get_size(v, seen) for v in obj.values()]) + size += sum([get_size(k, seen) for k in obj.keys()]) + elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)): + size += sum([get_size(i, seen) for i in obj]) + return size + + initial_size = get_size(tools) + + # Process through _bedrock_tools_pt - this should complete without OOM + tools_copy = copy.deepcopy(tools) + result = _bedrock_tools_pt(tools=tools_copy) + + final_size = get_size(result) + + # The expansion factor should be reasonable (< 100x), not exponential (35000x as in #19098) + expansion_factor = final_size / initial_size + assert expansion_factor < 100, ( + f"Memory expansion factor {expansion_factor:.1f}x is too high. " + f"Initial: {initial_size} bytes, Final: {final_size} bytes" + ) + + # Verify the result is valid Bedrock tools format + assert isinstance(result, list) + assert len(result) == 1 + assert "toolSpec" in result[0] + assert result[0]["toolSpec"]["name"] == "execute_query" + + # Verify $defs have been removed (Bedrock doesn't support them) + tool_schema = result[0]["toolSpec"].get("inputSchema", {}).get("json", {}) + assert "$defs" not in tool_schema, "$defs should be removed after expansion" From 866bd4674837d64462c70e63fa13343c9ec6a1fb Mon Sep 17 00:00:00 2001 From: Harshit Jain <48647625+Harshit28j@users.noreply.github.com> Date: Sat, 17 Jan 2026 04:56:52 +0530 Subject: [PATCH 5/9] chore: resolve ModuleNotFoundError for Microsoft Foundry Agents (#18991) --- litellm/a2a_protocol/main.py | 44 ++-- .../proxy/agent_endpoints/a2a_endpoints.py | 88 ++++++-- poetry.lock | 200 ++++++++++-------- pyproject.toml | 16 +- requirements.txt | 13 +- .../agent_endpoints/test_a2a_endpoints.py | 53 +++-- 6 files changed, 257 insertions(+), 157 deletions(-) diff --git a/litellm/a2a_protocol/main.py b/litellm/a2a_protocol/main.py index 167aad7959..2d36dbeacd 100644 --- a/litellm/a2a_protocol/main.py +++ b/litellm/a2a_protocol/main.py @@ -113,7 +113,9 @@ def _get_a2a_model_info(a2a_client: Any, kwargs: Dict[str, Any]) -> str: litellm_logging_obj.model = model litellm_logging_obj.custom_llm_provider = custom_llm_provider litellm_logging_obj.model_call_details["model"] = model - litellm_logging_obj.model_call_details["custom_llm_provider"] = custom_llm_provider + litellm_logging_obj.model_call_details[ + "custom_llm_provider" + ] = custom_llm_provider return agent_name @@ -197,7 +199,11 @@ async def asend_message( ) # Extract params from request - params = request.params.model_dump(mode="json") if hasattr(request.params, "model_dump") else dict(request.params) + params = ( + request.params.model_dump(mode="json") + if hasattr(request.params, "model_dump") + else dict(request.params) + ) response_dict = await A2ACompletionBridgeHandler.handle_non_streaming( request_id=str(request.id), @@ -216,7 +222,9 @@ async def asend_message( # Create A2A client if not provided but api_base is available if a2a_client is None: if api_base is None: - raise ValueError("Either a2a_client or api_base is required for standard A2A flow") + raise ValueError( + "Either a2a_client or api_base is required for standard A2A flow" + ) a2a_client = await create_a2a_client(base_url=api_base) # Type assertion: a2a_client is guaranteed to be non-None here @@ -235,7 +243,11 @@ async def asend_message( # Calculate token usage from request and response response_dict = a2a_response.model_dump(mode="json", exclude_none=True) - prompt_tokens, completion_tokens, _ = A2ARequestUtils.calculate_usage_from_request_response( + ( + prompt_tokens, + completion_tokens, + _, + ) = A2ARequestUtils.calculate_usage_from_request_response( request=request, response_dict=response_dict, ) @@ -280,7 +292,9 @@ def send_message( if loop is not None: return asend_message(a2a_client=a2a_client, request=request, **kwargs) else: - return asyncio.run(asend_message(a2a_client=a2a_client, request=request, **kwargs)) + return asyncio.run( + asend_message(a2a_client=a2a_client, request=request, **kwargs) + ) async def asend_message_streaming( @@ -347,7 +361,11 @@ async def asend_message_streaming( ) # Extract params from request - params = request.params.model_dump(mode="json") if hasattr(request.params, "model_dump") else dict(request.params) + params = ( + request.params.model_dump(mode="json") + if hasattr(request.params, "model_dump") + else dict(request.params) + ) async for chunk in A2ACompletionBridgeHandler.handle_streaming( request_id=str(request.id), @@ -365,7 +383,9 @@ async def asend_message_streaming( # Create A2A client if not provided but api_base is available if a2a_client is None: if api_base is None: - raise ValueError("Either a2a_client or api_base is required for standard A2A flow") + raise ValueError( + "Either a2a_client or api_base is required for standard A2A flow" + ) a2a_client = await create_a2a_client(base_url=api_base) # Type assertion: a2a_client is guaranteed to be non-None here @@ -378,7 +398,9 @@ async def asend_message_streaming( stream = a2a_client.send_message_streaming(request) # Build logging object for streaming completion callbacks - agent_card = getattr(a2a_client, "_litellm_agent_card", None) or getattr(a2a_client, "agent_card", None) + agent_card = getattr(a2a_client, "_litellm_agent_card", None) or getattr( + a2a_client, "agent_card", None + ) agent_name = getattr(agent_card, "name", "unknown") if agent_card else "unknown" model = f"a2a_agent/{agent_name}" @@ -456,7 +478,7 @@ async def create_a2a_client( if not A2A_SDK_AVAILABLE: raise ImportError( "The 'a2a' package is required for A2A agent invocation. " - "Install it with: pip install a2a" + "Install it with: pip install a2a-sdk" ) verbose_logger.info(f"Creating A2A client for {base_url}") @@ -512,7 +534,7 @@ async def aget_agent_card( if not A2A_SDK_AVAILABLE: raise ImportError( "The 'a2a' package is required for A2A agent invocation. " - "Install it with: pip install a2a" + "Install it with: pip install a2a-sdk" ) verbose_logger.info(f"Fetching agent card from {base_url}") @@ -534,5 +556,3 @@ async def aget_agent_card( f"Fetched agent card: {agent_card.name if hasattr(agent_card, 'name') else 'unknown'}" ) return agent_card - - diff --git a/litellm/proxy/agent_endpoints/a2a_endpoints.py b/litellm/proxy/agent_endpoints/a2a_endpoints.py index c2d53b40b7..a21f3291d3 100644 --- a/litellm/proxy/agent_endpoints/a2a_endpoints.py +++ b/litellm/proxy/agent_endpoints/a2a_endpoints.py @@ -55,9 +55,30 @@ async def _handle_stream_message( proxy_server_request: Optional[dict] = None, ) -> StreamingResponse: """Handle message/stream method via SDK functions.""" - from a2a.types import MessageSendParams, SendStreamingMessageRequest - from litellm.a2a_protocol import asend_message_streaming + from litellm.a2a_protocol.main import A2A_SDK_AVAILABLE + + # Check is handled in invoke_agent_a2a, but if called directly: + if not A2A_SDK_AVAILABLE: + # Return a streaming response that yields an error + async def _error_stream(): + yield json.dumps( + { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": "Server error: 'a2a' package not installed", + }, + } + ) + "\n" + + return StreamingResponse(_error_stream(), media_type="application/x-ndjson") + + from a2a.types import ( + MessageSendParams, + SendStreamingMessageRequest, + ) async def stream_response(): try: @@ -75,16 +96,20 @@ async def _handle_stream_message( ): # Chunk may be dict or object depending on bridge vs standard path if hasattr(chunk, "model_dump"): - yield json.dumps(chunk.model_dump(mode="json", exclude_none=True)) + "\n" + yield json.dumps( + chunk.model_dump(mode="json", exclude_none=True) + ) + "\n" else: yield json.dumps(chunk) + "\n" except Exception as e: verbose_proxy_logger.exception(f"Error streaming A2A response: {e}") - yield json.dumps({ - "jsonrpc": "2.0", - "id": request_id, - "error": {"code": -32603, "message": f"Streaming error: {str(e)}"}, - }) + "\n" + yield json.dumps( + { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32603, "message": f"Streaming error: {str(e)}"}, + } + ) + "\n" return StreamingResponse(stream_response(), media_type="application/x-ndjson") @@ -169,9 +194,8 @@ async def invoke_agent_a2a( - message/send: Send a message and get a response - message/stream: Send a message and stream the response """ - from a2a.types import MessageSendParams, SendMessageRequest - from litellm.a2a_protocol import asend_message + from litellm.a2a_protocol.main import A2A_SDK_AVAILABLE from litellm.proxy.agent_endpoints.auth.agent_permission_handler import ( AgentRequestHandler, ) @@ -189,16 +213,28 @@ async def invoke_agent_a2a( # Validate JSON-RPC format if body.get("jsonrpc") != "2.0": - return _jsonrpc_error(body.get("id"), -32600, "Invalid Request: jsonrpc must be '2.0'") + return _jsonrpc_error( + body.get("id"), -32600, "Invalid Request: jsonrpc must be '2.0'" + ) request_id = body.get("id") method = body.get("method") params = body.get("params", {}) + if not A2A_SDK_AVAILABLE: + return _jsonrpc_error( + request_id, + -32603, + "Server error: 'a2a' package not installed. Please install 'a2a-sdk'.", + 500, + ) + # Find the agent agent = _get_agent(agent_id) if agent is None: - return _jsonrpc_error(request_id, -32000, f"Agent '{agent_id}' not found", 404) + return _jsonrpc_error( + request_id, -32000, f"Agent '{agent_id}' not found", 404 + ) is_allowed = await AgentRequestHandler.is_agent_allowed( agent_id=agent.agent_id, @@ -213,23 +249,29 @@ async def invoke_agent_a2a( # Get backend URL and agent name agent_url = agent.agent_card_params.get("url") agent_name = agent.agent_card_params.get("name", agent_id) - + # Get litellm_params (may include custom_llm_provider for completion bridge) litellm_params = agent.litellm_params or {} custom_llm_provider = litellm_params.get("custom_llm_provider") - + # URL is required unless using completion bridge with a provider that derives endpoint from model # (e.g., bedrock/agentcore derives endpoint from ARN in model string) if not agent_url and not custom_llm_provider: - return _jsonrpc_error(request_id, -32000, f"Agent '{agent_id}' has no URL configured", 500) + return _jsonrpc_error( + request_id, -32000, f"Agent '{agent_id}' has no URL configured", 500 + ) - verbose_proxy_logger.info(f"Proxying A2A request to agent '{agent_id}' at {agent_url or 'completion-bridge'}") + verbose_proxy_logger.info( + f"Proxying A2A request to agent '{agent_id}' at {agent_url or 'completion-bridge'}" + ) # Set up data dict for litellm processing - body.update({ - "model": f"a2a_agent/{agent_name}", - "custom_llm_provider": "a2a_agent", - }) + body.update( + { + "model": f"a2a_agent/{agent_name}", + "custom_llm_provider": "a2a_agent", + } + ) # Add litellm data (user_api_key, user_id, team_id, etc.) data = await add_litellm_data_to_request( @@ -243,6 +285,8 @@ async def invoke_agent_a2a( # Route through SDK functions if method == "message/send": + from a2a.types import MessageSendParams, SendMessageRequest + a2a_request = SendMessageRequest( id=request_id, params=MessageSendParams(**params), @@ -255,7 +299,9 @@ async def invoke_agent_a2a( metadata=data.get("metadata", {}), proxy_server_request=data.get("proxy_server_request"), ) - return JSONResponse(content=response.model_dump(mode="json", exclude_none=True)) + return JSONResponse( + content=response.model_dump(mode="json", exclude_none=True) + ) elif method == "message/stream": return await _handle_stream_message( diff --git a/poetry.lock b/poetry.lock index 3bafdb157c..fd6b28533e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,36 @@ -# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. + +[[package]] +name = "a2a-sdk" +version = "0.3.22" +description = "A2A Python SDK" +optional = true +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\" and extra == \"extra-proxy\"" +files = [ + {file = "a2a_sdk-0.3.22-py3-none-any.whl", hash = "sha256:b98701135bb90b0ff85d35f31533b6b7a299bf810658c1c65f3814a6c15ea385"}, + {file = "a2a_sdk-0.3.22.tar.gz", hash = "sha256:77a5694bfc4f26679c11b70c7f1062522206d430b34bc1215cfbb1eba67b7e7d"}, +] + +[package.dependencies] +google-api-core = ">=1.26.0" +httpx = ">=0.28.1" +httpx-sse = ">=0.4.0" +protobuf = ">=5.29.5" +pydantic = ">=2.11.3" + +[package.extras] +all = ["cryptography (>=43.0.0)", "fastapi (>=0.115.2)", "grpcio (>=1.60)", "grpcio-reflection (>=1.7.0)", "grpcio-tools (>=1.60)", "opentelemetry-api (>=1.33.0)", "opentelemetry-sdk (>=1.33.0)", "pyjwt (>=2.0.0)", "sqlalchemy[aiomysql,asyncio] (>=2.0.0)", "sqlalchemy[aiosqlite,asyncio] (>=2.0.0)", "sqlalchemy[asyncio,postgresql-asyncpg] (>=2.0.0)", "sse-starlette", "starlette"] +encryption = ["cryptography (>=43.0.0)"] +grpc = ["grpcio (>=1.60)", "grpcio-reflection (>=1.7.0)", "grpcio-tools (>=1.60)"] +http-server = ["fastapi (>=0.115.2)", "sse-starlette", "starlette"] +mysql = ["sqlalchemy[aiomysql,asyncio] (>=2.0.0)"] +postgresql = ["sqlalchemy[asyncio,postgresql-asyncpg] (>=2.0.0)"] +signing = ["pyjwt (>=2.0.0)"] +sql = ["sqlalchemy[aiomysql,asyncio] (>=2.0.0)", "sqlalchemy[aiosqlite,asyncio] (>=2.0.0)", "sqlalchemy[asyncio,postgresql-asyncpg] (>=2.0.0)"] +sqlite = ["sqlalchemy[aiosqlite,asyncio] (>=2.0.0)"] +telemetry = ["opentelemetry-api (>=1.33.0)", "opentelemetry-sdk (>=1.33.0)"] [[package]] name = "aiofiles" @@ -1268,25 +1300,6 @@ dev = ["autoflake", "black", "build", "databricks-connect", "httpx", "ipython", notebook = ["ipython (>=8,<10)", "ipywidgets (>=8,<9)"] openai = ["httpx", "langchain-openai ; python_version > \"3.7\"", "openai"] -[[package]] -name = "deprecated" -version = "1.3.1" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main", "dev", "proxy-dev"] -files = [ - {file = "deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f"}, - {file = "deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223"}, -] -markers = {main = "python_version >= \"3.10\""} - -[package.dependencies] -wrapt = ">=1.10,<3" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] - [[package]] name = "diskcache" version = "5.6.3" @@ -2521,7 +2534,7 @@ description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version >= \"3.10\" and extra == \"proxy\"" +markers = "python_version >= \"3.10\" and (extra == \"proxy\" or extra == \"extra-proxy\")" files = [ {file = "httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc"}, {file = "httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d"}, @@ -4036,143 +4049,153 @@ voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] [[package]] name = "opentelemetry-api" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev", "proxy-dev"] files = [ - {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"}, - {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"}, + {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, + {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, ] markers = {main = "python_version >= \"3.10\""} [package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=7.1" +importlib-metadata = ">=6.0,<8.8.0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-exporter-otlp" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Collector Exporters" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "proxy-dev"] files = [ - {file = "opentelemetry_exporter_otlp-1.25.0-py3-none-any.whl", hash = "sha256:d67a831757014a3bc3174e4cd629ae1493b7ba8d189e8a007003cacb9f1a6b60"}, - {file = "opentelemetry_exporter_otlp-1.25.0.tar.gz", hash = "sha256:ce03199c1680a845f82e12c0a6a8f61036048c07ec7a0bd943142aca8fa6ced0"}, + {file = "opentelemetry_exporter_otlp-1.39.1-py3-none-any.whl", hash = "sha256:68ae69775291f04f000eb4b698ff16ff685fdebe5cb52871bc4e87938a7b00fe"}, + {file = "opentelemetry_exporter_otlp-1.39.1.tar.gz", hash = "sha256:7cf7470e9fd0060c8a38a23e4f695ac686c06a48ad97f8d4867bc9b420180b9c"}, ] [package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.25.0" -opentelemetry-exporter-otlp-proto-http = "1.25.0" +opentelemetry-exporter-otlp-proto-grpc = "1.39.1" +opentelemetry-exporter-otlp-proto-http = "1.39.1" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Protobuf encoding" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "proxy-dev"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl", hash = "sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.25.0.tar.gz", hash = "sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464"}, ] [package.dependencies] -opentelemetry-proto = "1.25.0" +opentelemetry-proto = "1.39.1" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "proxy-dev"] files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0-py3-none-any.whl", hash = "sha256:3131028f0c0a155a64c430ca600fd658e8e37043cb13209f0109db5c1a3e4eb4"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0.tar.gz", hash = "sha256:c0b1661415acec5af87625587efa1ccab68b873745ca0ee96b69bb1042087eac"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.39.1-py3-none-any.whl", hash = "sha256:fa1c136a05c7e9b4c09f739469cbdb927ea20b34088ab1d959a849b5cc589c18"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.39.1.tar.gz", hash = "sha256:772eb1c9287485d625e4dbe9c879898e5253fea111d9181140f51291b5fec3ad"}, ] [package.dependencies] -deprecated = ">=1.2.6" -googleapis-common-protos = ">=1.52,<2.0" -grpcio = ">=1.0.0,<2.0.0" +googleapis-common-protos = ">=1.57,<2.0" +grpcio = [ + {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""}, +] opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.25.0" -opentelemetry-proto = "1.25.0" -opentelemetry-sdk = ">=1.25.0,<1.26.0" +opentelemetry-exporter-otlp-proto-common = "1.39.1" +opentelemetry-proto = "1.39.1" +opentelemetry-sdk = ">=1.39.1,<1.40.0" +typing-extensions = ">=4.6.0" + +[package.extras] +gcp-auth = ["opentelemetry-exporter-credential-provider-gcp (>=0.59b0)"] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev", "proxy-dev"] files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.25.0-py3-none-any.whl", hash = "sha256:2eca686ee11b27acd28198b3ea5e5863a53d1266b91cda47c839d95d5e0541a6"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.25.0.tar.gz", hash = "sha256:9f8723859e37c75183ea7afa73a3542f01d0fd274a5b97487ea24cb683d7d684"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb"}, ] [package.dependencies] -deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.25.0" -opentelemetry-proto = "1.25.0" -opentelemetry-sdk = ">=1.25.0,<1.26.0" +opentelemetry-exporter-otlp-proto-common = "1.39.1" +opentelemetry-proto = "1.39.1" +opentelemetry-sdk = ">=1.39.1,<1.40.0" requests = ">=2.7,<3.0" +typing-extensions = ">=4.5.0" + +[package.extras] +gcp-auth = ["opentelemetry-exporter-credential-provider-gcp (>=0.59b0)"] [[package]] name = "opentelemetry-proto" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Python Proto" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev", "proxy-dev"] files = [ - {file = "opentelemetry_proto-1.25.0-py3-none-any.whl", hash = "sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f"}, - {file = "opentelemetry_proto-1.25.0.tar.gz", hash = "sha256:35b6ef9dc4a9f7853ecc5006738ad40443701e52c26099e197895cbda8b815a3"}, + {file = "opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007"}, + {file = "opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8"}, ] markers = {main = "python_version >= \"3.10\" and extra == \"mlflow\""} [package.dependencies] -protobuf = ">=3.19,<5.0" +protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.25.0" +version = "1.39.1" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev", "proxy-dev"] files = [ - {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"}, - {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"}, + {file = "opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c"}, + {file = "opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6"}, ] markers = {main = "python_version >= \"3.10\""} [package.dependencies] -opentelemetry-api = "1.25.0" -opentelemetry-semantic-conventions = "0.46b0" -typing-extensions = ">=3.7.4" +opentelemetry-api = "1.39.1" +opentelemetry-semantic-conventions = "0.60b1" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.46b0" +version = "0.60b1" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev", "proxy-dev"] files = [ - {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"}, - {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"}, + {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, + {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, ] markers = {main = "python_version >= \"3.10\""} [package.dependencies] -opentelemetry-api = "1.25.0" +opentelemetry-api = "1.39.1" +typing-extensions = ">=4.5.0" [[package]] name = "orjson" @@ -4828,23 +4851,23 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "4.25.8" +version = "5.29.5" description = "" optional = false python-versions = ">=3.8" groups = ["main", "dev", "proxy-dev"] files = [ - {file = "protobuf-4.25.8-cp310-abi3-win32.whl", hash = "sha256:504435d831565f7cfac9f0714440028907f1975e4bed228e58e72ecfff58a1e0"}, - {file = "protobuf-4.25.8-cp310-abi3-win_amd64.whl", hash = "sha256:bd551eb1fe1d7e92c1af1d75bdfa572eff1ab0e5bf1736716814cdccdb2360f9"}, - {file = "protobuf-4.25.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ca809b42f4444f144f2115c4c1a747b9a404d590f18f37e9402422033e464e0f"}, - {file = "protobuf-4.25.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9ad7ef62d92baf5a8654fbb88dac7fa5594cfa70fd3440488a5ca3bfc6d795a7"}, - {file = "protobuf-4.25.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:83e6e54e93d2b696a92cad6e6efc924f3850f82b52e1563778dfab8b355101b0"}, - {file = "protobuf-4.25.8-cp38-cp38-win32.whl", hash = "sha256:27d498ffd1f21fb81d987a041c32d07857d1d107909f5134ba3350e1ce80a4af"}, - {file = "protobuf-4.25.8-cp38-cp38-win_amd64.whl", hash = "sha256:d552c53d0415449c8d17ced5c341caba0d89dbf433698e1436c8fa0aae7808a3"}, - {file = "protobuf-4.25.8-cp39-cp39-win32.whl", hash = "sha256:077ff8badf2acf8bc474406706ad890466274191a48d0abd3bd6987107c9cde5"}, - {file = "protobuf-4.25.8-cp39-cp39-win_amd64.whl", hash = "sha256:f4510b93a3bec6eba8fd8f1093e9d7fb0d4a24d1a81377c10c0e5bbfe9e4ed24"}, - {file = "protobuf-4.25.8-py3-none-any.whl", hash = "sha256:15a0af558aa3b13efef102ae6e4f3efac06f1eea11afb3a57db2901447d9fb59"}, - {file = "protobuf-4.25.8.tar.gz", hash = "sha256:6135cf8affe1fc6f76cced2641e4ea8d3e59518d1f24ae41ba97bcad82d397cd"}, + {file = "protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079"}, + {file = "protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc"}, + {file = "protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671"}, + {file = "protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015"}, + {file = "protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61"}, + {file = "protobuf-5.29.5-cp38-cp38-win32.whl", hash = "sha256:ef91363ad4faba7b25d844ef1ada59ff1604184c0bcd8b39b8a6bef15e1af238"}, + {file = "protobuf-5.29.5-cp38-cp38-win_amd64.whl", hash = "sha256:7318608d56b6402d2ea7704ff1e1e4597bee46d760e7e4dd42a3d45e24b87f2e"}, + {file = "protobuf-5.29.5-cp39-cp39-win32.whl", hash = "sha256:6f642dc9a61782fa72b90878af134c5afe1917c89a568cd3476d758d3c3a0736"}, + {file = "protobuf-5.29.5-cp39-cp39-win_amd64.whl", hash = "sha256:470f3af547ef17847a28e1f47200a1cbf0ba3ff57b7de50d22776607cd2ea353"}, + {file = "protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5"}, + {file = "protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84"}, ] markers = {main = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"extra-proxy\") or extra == \"extra-proxy\""} @@ -7687,7 +7710,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main", "dev", "proxy-dev"] +groups = ["dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -7771,7 +7794,6 @@ files = [ {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, ] -markers = {main = "python_version >= \"3.10\""} [[package]] name = "wsproto" @@ -7972,7 +7994,7 @@ type = ["pytest-mypy"] [extras] caching = ["diskcache"] -extra-proxy = ["azure-identity", "azure-keyvault-secrets", "google-cloud-iam", "google-cloud-kms", "prisma", "redisvl", "resend"] +extra-proxy = ["a2a-sdk", "azure-identity", "azure-keyvault-secrets", "google-cloud-iam", "google-cloud-kms", "prisma", "redisvl", "resend"] mlflow = ["mlflow"] proxy = ["PyJWT", "apscheduler", "azure-identity", "azure-storage-blob", "backoff", "boto3", "cryptography", "fastapi", "fastapi-sso", "gunicorn", "litellm-enterprise", "litellm-proxy-extras", "mcp", "orjson", "polars", "pynacl", "python-multipart", "pyyaml", "rich", "rq", "soundfile", "uvicorn", "uvloop", "websockets"] semantic-router = ["semantic-router"] @@ -7981,4 +8003,4 @@ utils = ["numpydoc"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "ea62b77c662ab9fc486e421c576f0868bcde16d62a24703ee1f4916a0465ffb2" +content-hash = "82a9400ee6e7a550758024ca3c076c4063c6a22ca9535b125e585b03aca7b2b2" diff --git a/pyproject.toml b/pyproject.toml index aa8e6fd97b..c33c8c995f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ websockets = {version = "^15.0.1", optional = true} boto3 = {version = "1.36.0", optional = true} redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = "^1.21.2", optional = true, python = ">=3.10"} +a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"} litellm-proxy-extras = {version = "0.4.21", optional = true} rich = {version = "13.7.1", optional = true} litellm-enterprise = {version = "0.1.27", optional = true} @@ -111,7 +112,8 @@ extra_proxy = [ "google-cloud-kms", "google-cloud-iam", "resend", - "redisvl" + "redisvl", + "a2a-sdk" ] utils = [ @@ -147,9 +149,9 @@ types-requests = "*" types-setuptools = "*" types-redis = "*" types-PyYAML = "*" -opentelemetry-api = "1.25.0" -opentelemetry-sdk = "1.25.0" -opentelemetry-exporter-otlp = "1.25.0" +opentelemetry-api = "^1.28.0" +opentelemetry-sdk = "^1.28.0" +opentelemetry-exporter-otlp = "^1.28.0" langfuse = "^2.45.0" fastapi-offline = "^1.7.3" @@ -157,9 +159,9 @@ fastapi-offline = "^1.7.3" prisma = "0.11.0" hypercorn = "^0.15.0" prometheus-client = "0.20.0" -opentelemetry-api = "1.25.0" -opentelemetry-sdk = "1.25.0" -opentelemetry-exporter-otlp = "1.25.0" +opentelemetry-api = "^1.28.0" +opentelemetry-sdk = "^1.28.0" +opentelemetry-exporter-otlp = "^1.28.0" azure-identity = {version = "^1.15.0", python = ">=3.9"} [build-system] diff --git a/requirements.txt b/requirements.txt index 7ec71fb7dc..4fae2591dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,12 +16,12 @@ prisma==0.11.0 # for db nodejs-wheel-binaries==24.12.0 ## required by prisma for migrations, prevents runtime download (updated from nodejs-bin for security fixes) mangum==0.17.0 # for aws lambda functions pynacl==1.6.2 # for encrypting keys -google-cloud-aiplatform==1.47.0 # for vertex ai calls +google-cloud-aiplatform==1.133.0 # for vertex ai calls google-cloud-iam==2.19.1 # for GCP IAM Redis authentication -google-genai==1.22.0 +google-genai==1.37.0 anthropic[vertex]==0.54.0 mcp==1.25.0 ; python_version >= "3.10" # for MCP server -google-generativeai==0.5.0 # for vertex ai calls +# google-generativeai removed - deprecated, replaced by google-genai (line 21) async_generator==1.10.0 # for async ollama calls langfuse==2.59.7 # for langfuse self-hosted logging prometheus_client==0.20.0 # for /metrics endpoint on proxy @@ -37,9 +37,10 @@ azure-ai-contentsafety==1.0.0 # for azure content safety azure-identity==1.16.1 ; python_version >= "3.9" # for azure content safety azure-keyvault==4.2.0 # for azure KMS integration azure-storage-file-datalake==12.20.0 # for azure buck storage logging -opentelemetry-api==1.25.0 -opentelemetry-sdk==1.25.0 -opentelemetry-exporter-otlp==1.25.0 +opentelemetry-api==1.28.0 +opentelemetry-sdk==1.28.0 +opentelemetry-exporter-otlp==1.28.0 +a2a-sdk>=0.3.22 ; python_version >= "3.10" # grpcio: 1.68.0-1.68.1 has reconnect bug (#38290), 1.75+ has Python 3.14 wheels + fix grpcio>=1.62.3,!=1.68.*,!=1.69.*,!=1.70.*,!=1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0; python_version < "3.14" grpcio>=1.75.0; python_version >= "3.14" diff --git a/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py b/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py index 061e27da91..9588c3b55c 100644 --- a/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py +++ b/tests/test_litellm/proxy/agent_endpoints/test_a2a_endpoints.py @@ -49,18 +49,20 @@ async def test_invoke_agent_a2a_adds_litellm_data(): # Mock request mock_request = MagicMock() - mock_request.json = AsyncMock(return_value={ - "jsonrpc": "2.0", - "id": "test-id", - "method": "message/send", - "params": { - "message": { - "role": "user", - "parts": [{"kind": "text", "text": "Hello"}], - "messageId": "msg-123", - } - }, - }) + mock_request.json = AsyncMock( + return_value={ + "jsonrpc": "2.0", + "id": "test-id", + "method": "message/send", + "params": { + "message": { + "role": "user", + "parts": [{"kind": "text", "text": "Hello"}], + "messageId": "msg-123", + } + }, + } + ) mock_user_api_key_dict = UserAPIKeyAuth( api_key="sk-test-key", @@ -77,40 +79,44 @@ async def test_invoke_agent_a2a_adds_litellm_data(): SendMessageRequest, SendStreamingMessageRequest, ) + # Real types available - use them - use_real_types = True + pass except ImportError: # Real types not available - create realistic mocks - use_real_types = False - + pass + def make_mock_pydantic_class(name): """Create a mock class that behaves like a Pydantic model.""" + class MockPydanticClass: def __init__(self, **kwargs): self.__dict__.update(kwargs) # Store kwargs for model_dump() if needed self._kwargs = kwargs - + def model_dump(self, mode="json", exclude_none=False): """Mock model_dump method.""" result = dict(self._kwargs) if exclude_none: result = {k: v for k, v in result.items() if v is not None} return result - + MockPydanticClass.__name__ = name return MockPydanticClass - + MessageSendParams = make_mock_pydantic_class("MessageSendParams") SendMessageRequest = make_mock_pydantic_class("SendMessageRequest") - SendStreamingMessageRequest = make_mock_pydantic_class("SendStreamingMessageRequest") - + SendStreamingMessageRequest = make_mock_pydantic_class( + "SendStreamingMessageRequest" + ) + # Create a mock module for a2a.types mock_a2a_types = MagicMock() mock_a2a_types.MessageSendParams = MessageSendParams mock_a2a_types.SendMessageRequest = SendMessageRequest mock_a2a_types.SendStreamingMessageRequest = SendStreamingMessageRequest - + # Patch at the source modules with patch( "litellm.proxy.agent_endpoints.a2a_endpoints._get_agent", @@ -137,12 +143,15 @@ async def test_invoke_agent_a2a_adds_litellm_data(): ), patch.dict( sys.modules, {"a2a": MagicMock(), "a2a.types": mock_a2a_types}, + ), patch( + "litellm.a2a_protocol.main.A2A_SDK_AVAILABLE", + True, ): from litellm.proxy.agent_endpoints.a2a_endpoints import invoke_agent_a2a mock_fastapi_response = MagicMock() - result = await invoke_agent_a2a( + await invoke_agent_a2a( agent_id="test-agent", request=mock_request, fastapi_response=mock_fastapi_response, From 896d1a7dad96d1c21b08b65634196d98da6fdfdd Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 19 Jan 2026 18:18:24 +0530 Subject: [PATCH 6/9] Fix Error: Found packages that need verification: --- ...model_prices_and_context_window_backup.json | 18 ++++++++++++++++++ tests/code_coverage_tests/liccheck.ini | 1 + 2 files changed, 19 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 470d598a25..43f7bde5da 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -7857,6 +7857,24 @@ "supports_tool_choice": true, "supports_vision": true }, + "dall-e-2": { + "input_cost_per_image": 0.02, + "litellm_provider": "openai", + "mode": "image_generation", + "supported_endpoints": [ + "/v1/images/generations", + "/v1/images/edits", + "/v1/images/variations" + ] + }, + "dall-e-3": { + "input_cost_per_image": 0.04, + "litellm_provider": "openai", + "mode": "image_generation", + "supported_endpoints": [ + "/v1/images/generations" + ] + }, "deepseek-chat": { "cache_read_input_token_cost": 2.8e-08, "input_cost_per_token": 2.8e-07, diff --git a/tests/code_coverage_tests/liccheck.ini b/tests/code_coverage_tests/liccheck.ini index feb182921d..ea87b56ff3 100644 --- a/tests/code_coverage_tests/liccheck.ini +++ b/tests/code_coverage_tests/liccheck.ini @@ -89,6 +89,7 @@ tokenizers: >=0.20.2 # Apache 2.0 License jinja2: >=3.1.4 # BSD 3-Clause License litellm-proxy-extras: >=0.1.1 # MIT License litellm-enterprise: >=0.1.1 # LiteLLM Enterprise License +a2a-sdk: >=0.3.22 # Apache 2.0 license anyio: >=4.5.0 # Unknown license httpx-aiohttp: >=0.1.4 # Unknown license backoff: >=2.2.1 # Unknown license From 2e6ce1469e9af016072f6e870f7dae002c0b39ab Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 19 Jan 2026 18:47:28 +0530 Subject: [PATCH 7/9] Fix module import for opentelemetry --- litellm/integrations/opentelemetry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index a223925d59..c8fc6e9614 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -986,8 +986,7 @@ class OpenTelemetry(CustomLogger): # See: https://github.com/open-telemetry/opentelemetry-python/pull/4676 # TODO: Refactor to use the proper OTEL Logs API instead of directly creating SDK LogRecords - from opentelemetry._logs import SeverityNumber, get_logger, get_logger_provider - from opentelemetry.sdk._logs import LogRecord as SdkLogRecord + from opentelemetry._logs import SeverityNumber, get_logger, get_logger_provider, LogRecord otel_logger = get_logger(LITELLM_LOGGER_NAME) From 6eb3f579d77e2b4b69f8201635d2ab244016452b Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 19 Jan 2026 18:50:18 +0530 Subject: [PATCH 8/9] Fix module import for opentelemetry --- litellm/integrations/opentelemetry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index c8fc6e9614..9fbccac68d 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -986,7 +986,11 @@ class OpenTelemetry(CustomLogger): # See: https://github.com/open-telemetry/opentelemetry-python/pull/4676 # TODO: Refactor to use the proper OTEL Logs API instead of directly creating SDK LogRecords - from opentelemetry._logs import SeverityNumber, get_logger, get_logger_provider, LogRecord + from opentelemetry._logs import SeverityNumber, get_logger, get_logger_provider + try: + from opentelemetry.sdk._logs import LogRecord as SdkLogRecord # OTEL < 1.39.0 + except ImportError: + from opentelemetry.sdk._logs._internal import LogRecord as SdkLogRecord # OTEL >= 1.39.0 otel_logger = get_logger(LITELLM_LOGGER_NAME) From 574391c118c331ade273c4fa21c79ff618358404 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 19 Jan 2026 18:51:08 +0530 Subject: [PATCH 9/9] Revert "Fix audio cost per second override (#19158)" This reverts commit 2a0f87bde048e406e6dbf1d5465e77d7e0282bc3. --- docs/my-website/docs/sdk_custom_pricing.md | 5 +--- litellm/llms/openai/cost_calculation.py | 22 ++++++++------ tests/test_litellm/test_cost_calculator.py | 34 ---------------------- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/docs/my-website/docs/sdk_custom_pricing.md b/docs/my-website/docs/sdk_custom_pricing.md index ac956db472..c857711510 100644 --- a/docs/my-website/docs/sdk_custom_pricing.md +++ b/docs/my-website/docs/sdk_custom_pricing.md @@ -2,10 +2,7 @@ Register custom pricing for sagemaker completion model. -For cost per second pricing, register `input_cost_per_second`. If your provider -charges for audio output duration (e.g., TTS), also set `output_cost_per_second`. -Values of `0` are treated as not billable, so `output_cost_per_second: 0` will -not override `input_cost_per_second`. +For cost per second pricing, you **just** need to register `input_cost_per_second`. ```python # !pip install boto3 diff --git a/litellm/llms/openai/cost_calculation.py b/litellm/llms/openai/cost_calculation.py index 339ce9caed..e5349db3af 100644 --- a/litellm/llms/openai/cost_calculation.py +++ b/litellm/llms/openai/cost_calculation.py @@ -105,21 +105,25 @@ def cost_per_second( prompt_cost = 0.0 completion_cost = 0.0 ## Speech / Audio cost calculation - output_cost_per_second = model_info.get("output_cost_per_second") - input_cost_per_second = model_info.get("input_cost_per_second") - - if output_cost_per_second is not None and output_cost_per_second > 0: + if ( + "output_cost_per_second" in model_info + and model_info["output_cost_per_second"] is not None + ): verbose_logger.debug( - f"For model={model} - output_cost_per_second: {output_cost_per_second}; duration: {duration}" + f"For model={model} - output_cost_per_second: {model_info.get('output_cost_per_second')}; duration: {duration}" ) ## COST PER SECOND ## - completion_cost = output_cost_per_second * duration - if input_cost_per_second is not None and input_cost_per_second > 0: + completion_cost = model_info["output_cost_per_second"] * duration + elif ( + "input_cost_per_second" in model_info + and model_info["input_cost_per_second"] is not None + ): verbose_logger.debug( - f"For model={model} - input_cost_per_second: {input_cost_per_second}; duration: {duration}" + f"For model={model} - input_cost_per_second: {model_info.get('input_cost_per_second')}; duration: {duration}" ) ## COST PER SECOND ## - prompt_cost = input_cost_per_second * duration + prompt_cost = model_info["input_cost_per_second"] * duration + completion_cost = 0.0 return prompt_cost, completion_cost diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 42bcfca231..4d6599fc1b 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -192,40 +192,6 @@ def test_transcription_cost_falls_back_to_duration(): assert pytest.approx(cost, rel=1e-6) == expected_cost -def test_transcription_cost_prefers_input_when_output_zero(monkeypatch): - from litellm import completion_cost - - os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" - litellm.model_cost = litellm.get_model_cost_map(url="") - - model_name = "custom-whisper-input-only" - custom_model_info = { - "input_cost_per_second": 0.00005, - "output_cost_per_second": 0.0, - "litellm_provider": "openai", - "mode": "audio_transcription", - "supported_endpoints": ["/v1/audio/transcriptions"], - } - monkeypatch.setattr( - litellm, - "model_cost", - {**litellm.model_cost, model_name: custom_model_info}, - ) - - response = TranscriptionResponse(text="demo text") - response.duration = 300.0 - - cost = completion_cost( - completion_response=response, - model=model_name, - custom_llm_provider="openai", - call_type="atranscription", - ) - - expected_cost = 300.0 * 0.00005 - assert pytest.approx(cost, rel=1e-6) == expected_cost - - def test_handle_realtime_stream_cost_calculation(): from litellm.cost_calculator import RealtimeAPITokenUsageProcessor