fix(billing): Add error handling for LiteLLM API failures in get_credits (#12201)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Graham Neubig
2025-12-29 18:01:55 -05:00
committed by GitHub
parent ecbd3ae749
commit c3f51d9dbe
2 changed files with 24 additions and 11 deletions

View File

@@ -111,10 +111,24 @@ def calculate_credits(user_info: LiteLlmUserInfo) -> float:
async def get_credits(user_id: str = Depends(get_user_id)) -> GetCreditsResponse: async def get_credits(user_id: str = Depends(get_user_id)) -> GetCreditsResponse:
if not stripe_service.STRIPE_API_KEY: if not stripe_service.STRIPE_API_KEY:
return GetCreditsResponse() return GetCreditsResponse()
async with httpx.AsyncClient(verify=httpx_verify_option()) as client: try:
user_json = await _get_litellm_user(client, user_id) async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
credits = calculate_credits(user_json['user_info']) user_json = await _get_litellm_user(client, user_id)
return GetCreditsResponse(credits=Decimal('{:.2f}'.format(credits))) credits = calculate_credits(user_json['user_info'])
return GetCreditsResponse(credits=Decimal('{:.2f}'.format(credits)))
except httpx.HTTPStatusError as e:
logger.error(
f'litellm_get_user_failed: {type(e).__name__}: {e}',
extra={
'user_id': user_id,
'status_code': e.response.status_code,
},
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to retrieve credit balance from billing service',
)
# Endpoint to retrieve user's current subscription access # Endpoint to retrieve user's current subscription access

View File

@@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
import stripe import stripe
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from httpx import HTTPStatusError, Response from httpx import Response
from integrations.stripe_service import has_payment_method from integrations.stripe_service import has_payment_method
from server.routes.billing import ( from server.routes.billing import (
CreateBillingSessionResponse, CreateBillingSessionResponse,
@@ -78,8 +78,6 @@ def mock_subscription_request():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_credits_lite_llm_error(): async def test_get_credits_lite_llm_error():
mock_request = Request(scope={'type': 'http', 'state': {'user_id': 'mock_user'}})
mock_response = Response( mock_response = Response(
status_code=500, json={'error': 'Internal Server Error'}, request=MagicMock() status_code=500, json={'error': 'Internal Server Error'}, request=MagicMock()
) )
@@ -88,11 +86,12 @@ async def test_get_credits_lite_llm_error():
with patch('integrations.stripe_service.STRIPE_API_KEY', 'mock_key'): with patch('integrations.stripe_service.STRIPE_API_KEY', 'mock_key'):
with patch('httpx.AsyncClient', return_value=mock_client): with patch('httpx.AsyncClient', return_value=mock_client):
with pytest.raises(HTTPStatusError) as exc_info: with pytest.raises(HTTPException) as exc_info:
await get_credits(mock_request) await get_credits('mock_user')
assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
assert ( assert (
exc_info.value.response.status_code exc_info.value.detail
== status.HTTP_500_INTERNAL_SERVER_ERROR == 'Failed to retrieve credit balance from billing service'
) )