mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-05 10:24:03 +00:00
fix: server rooth path (#19790)
This commit is contained in:
@@ -825,6 +825,7 @@ app = FastAPI(
|
||||
title=_title,
|
||||
description=_description,
|
||||
version=version,
|
||||
root_path=server_root_path,
|
||||
lifespan=proxy_startup_event, # type: ignore[reportGeneralTypeIssues]
|
||||
)
|
||||
|
||||
|
||||
+17
-9
@@ -982,7 +982,9 @@ class ProxyLogging:
|
||||
|
||||
try:
|
||||
# Check if load balancing should be used
|
||||
if guardrail_name and self._should_use_guardrail_load_balancing(guardrail_name):
|
||||
if guardrail_name and self._should_use_guardrail_load_balancing(
|
||||
guardrail_name
|
||||
):
|
||||
response = await self._execute_guardrail_with_load_balancing(
|
||||
guardrail_name=guardrail_name,
|
||||
hook_type="pre_call",
|
||||
@@ -1017,7 +1019,11 @@ class ProxyLogging:
|
||||
latency_seconds = guardrail_end_time - guardrail_start_time
|
||||
|
||||
# Get guardrail name for metrics (fallback if not set)
|
||||
metrics_guardrail_name = guardrail_name or getattr(callback, "guardrail_name", callback.__class__.__name__) or "unknown"
|
||||
metrics_guardrail_name = (
|
||||
guardrail_name
|
||||
or getattr(callback, "guardrail_name", callback.__class__.__name__)
|
||||
or "unknown"
|
||||
)
|
||||
|
||||
# Find PrometheusLogger in callbacks and record metrics
|
||||
for prom_callback in litellm.callbacks:
|
||||
@@ -2218,17 +2224,17 @@ class PrismaClient:
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Execute a query with automatic fallback for PostgreSQL cached plan errors.
|
||||
|
||||
|
||||
This handles the "cached plan must not change result type" error that occurs
|
||||
during rolling deployments when schema changes are applied while old pods
|
||||
still have cached query plans expecting the old schema.
|
||||
|
||||
|
||||
Args:
|
||||
sql_query: SQL query string to execute
|
||||
|
||||
|
||||
Returns:
|
||||
Query result or None
|
||||
|
||||
|
||||
Raises:
|
||||
Original exception if not a cached plan error
|
||||
"""
|
||||
@@ -2241,7 +2247,7 @@ class PrismaClient:
|
||||
# Add a unique comment to make the query different
|
||||
sql_query_retry = sql_query.replace(
|
||||
"SELECT",
|
||||
f"SELECT /* cache_invalidated_{int(time.time() * 1000)} */"
|
||||
f"SELECT /* cache_invalidated_{int(time.time() * 1000)} */",
|
||||
)
|
||||
verbose_proxy_logger.warning(
|
||||
"PostgreSQL cached plan error detected for token lookup, "
|
||||
@@ -2583,7 +2589,9 @@ class PrismaClient:
|
||||
WHERE v.token = '{token}'
|
||||
"""
|
||||
|
||||
response = await self._query_first_with_cached_plan_fallback(sql_query)
|
||||
response = await self._query_first_with_cached_plan_fallback(
|
||||
sql_query
|
||||
)
|
||||
|
||||
if response is not None:
|
||||
if response["team_models"] is None:
|
||||
@@ -4227,7 +4235,7 @@ def get_server_root_path() -> str:
|
||||
- If SERVER_ROOT_PATH is set, return it.
|
||||
- Otherwise, default to "/".
|
||||
"""
|
||||
return os.getenv("SERVER_ROOT_PATH", "/")
|
||||
return os.getenv("SERVER_ROOT_PATH", "")
|
||||
|
||||
|
||||
def get_prisma_client_or_throw(message: str):
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
from unittest import mock
|
||||
from litellm.proxy import utils
|
||||
|
||||
|
||||
# Test the utility function logic
|
||||
def test_get_server_root_path_unset():
|
||||
"""
|
||||
Test that get_server_root_path returns empty string when SERVER_ROOT_PATH is unset
|
||||
"""
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
# We need to make sure SERVER_ROOT_PATH is not in env
|
||||
if "SERVER_ROOT_PATH" in os.environ:
|
||||
del os.environ["SERVER_ROOT_PATH"]
|
||||
|
||||
root_path = utils.get_server_root_path()
|
||||
assert (
|
||||
root_path == ""
|
||||
), "Should return empty string when unset to allow X-Forwarded-Prefix"
|
||||
|
||||
|
||||
def test_get_server_root_path_set():
|
||||
"""
|
||||
Test that get_server_root_path returns the value when SERVER_ROOT_PATH is set
|
||||
"""
|
||||
with mock.patch.dict(os.environ, {"SERVER_ROOT_PATH": "/my-path"}, clear=True):
|
||||
root_path = utils.get_server_root_path()
|
||||
assert root_path == "/my-path", "Should return the set value"
|
||||
|
||||
|
||||
def test_get_server_root_path_empty_string():
|
||||
"""
|
||||
Test that get_server_root_path returns empty string when SERVER_ROOT_PATH is explicitly empty
|
||||
"""
|
||||
with mock.patch.dict(os.environ, {"SERVER_ROOT_PATH": ""}, clear=True):
|
||||
root_path = utils.get_server_root_path()
|
||||
assert (
|
||||
root_path == ""
|
||||
), "Should return empty string when explicitly set to empty"
|
||||
|
||||
|
||||
# Integration test simulation for FastAPI app initialization
|
||||
def test_fastapi_app_initialization_mock():
|
||||
"""
|
||||
Simulate how proxy_server.py initializes FastAPI app with the root_path.
|
||||
We don't import proxy_server because it has global side effects/singletons.
|
||||
Instead we verify the logic flow.
|
||||
"""
|
||||
from fastapi import FastAPI
|
||||
|
||||
# CASE 1: Proxy Mode (Unset)
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
if "SERVER_ROOT_PATH" in os.environ:
|
||||
del os.environ["SERVER_ROOT_PATH"]
|
||||
|
||||
server_root_path = utils.get_server_root_path()
|
||||
app = FastAPI(root_path=server_root_path)
|
||||
assert app.root_path == ""
|
||||
|
||||
# CASE 2: Direct Mode (Set)
|
||||
with mock.patch.dict(os.environ, {"SERVER_ROOT_PATH": "/custom-root"}, clear=True):
|
||||
server_root_path = utils.get_server_root_path()
|
||||
app = FastAPI(root_path=server_root_path)
|
||||
assert app.root_path == "/custom-root"
|
||||
Reference in New Issue
Block a user