mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-11 14:22:48 +00:00
Merge pull request #19234 from BerriAI/litellm_staging_01_16_2026
Litellm staging 01 16 2026
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
loglevel=info
|
||||
logfile=/tmp/supervisord.log
|
||||
pidfile=/tmp/supervisord.pid
|
||||
|
||||
[group:litellm]
|
||||
programs=main,health
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -987,7 +987,10 @@ class OpenTelemetry(CustomLogger):
|
||||
# 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
|
||||
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)
|
||||
|
||||
|
||||
@@ -4410,9 +4410,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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
+110
-88
@@ -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"]
|
||||
|
||||
+9
-7
@@ -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.23", 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]
|
||||
|
||||
+7
-6
@@ -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
|
||||
@@ -38,9 +38,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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+131
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user