diff --git a/litellm/__init__.py b/litellm/__init__.py index 97f36a9b00f..1a7acd82c5d 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -339,6 +339,10 @@ model_cost_map_url: str = os.getenv( "LITELLM_MODEL_COST_MAP_URL", "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json", ) +blog_posts_url: str = os.getenv( + "LITELLM_BLOG_POSTS_URL", + "https://raw.githubusercontent.com/BerriAI/litellm/main/blog_posts.json", +) anthropic_beta_headers_url: str = os.getenv( "LITELLM_ANTHROPIC_BETA_HEADERS_URL", "https://raw.githubusercontent.com/BerriAI/litellm/main/litellm/anthropic_beta_headers_config.json", diff --git a/litellm/blog_posts_backup.json b/litellm/blog_posts_backup.json deleted file mode 100644 index 8e29e3c4e19..00000000000 --- a/litellm/blog_posts_backup.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "posts": [ - { - "title": "LiteLLM: Unified Interface for 100+ LLMs", - "description": "Learn how LiteLLM provides a single interface to call any LLM with OpenAI-compatible syntax.", - "date": "2026-02-01", - "url": "https://www.litellm.ai/blog/litellm" - }, - { - "title": "Using the LiteLLM Proxy for Load Balancing", - "description": "Set up the LiteLLM proxy server to load balance across multiple LLM providers and deployments.", - "date": "2026-01-15", - "url": "https://www.litellm.ai/blog/proxy-load-balancing" - } - ] -} diff --git a/litellm/litellm_core_utils/get_blog_posts.py b/litellm/litellm_core_utils/get_blog_posts.py index 137e32b0c9e..35d39ac62be 100644 --- a/litellm/litellm_core_utils/get_blog_posts.py +++ b/litellm/litellm_core_utils/get_blog_posts.py @@ -2,7 +2,7 @@ Pulls the latest LiteLLM blog posts from GitHub. Falls back to the bundled local backup on any failure. -GitHub JSON can be overridden via LITELLM_BLOG_POSTS_URL env var. +GitHub JSON URL is configured via litellm.blog_posts_url (or LITELLM_BLOG_POSTS_URL env var). Disable remote fetching entirely: export LITELLM_LOCAL_BLOG_POSTS=True @@ -19,11 +19,6 @@ from pydantic import BaseModel from litellm import verbose_logger -BLOG_POSTS_GITHUB_URL: str = os.getenv( - "LITELLM_BLOG_POSTS_URL", - "https://raw.githubusercontent.com/BerriAI/litellm/main/blog_posts.json", -) - BLOG_POSTS_TTL_SECONDS: int = 3600 # 1 hour @@ -57,7 +52,7 @@ class GetBlogPosts: """Load the bundled local backup blog posts.""" content = json.loads( files("litellm") - .joinpath("blog_posts_backup.json") + .joinpath("blog_posts.json") .read_text(encoding="utf-8") ) return content.get("posts", []) @@ -93,7 +88,7 @@ class GetBlogPosts: return True @classmethod - def get_blog_posts(cls, url: str = BLOG_POSTS_GITHUB_URL) -> List[Dict[str, str]]: + def get_blog_posts(cls, url: str) -> List[Dict[str, str]]: """ Return the blog posts list. @@ -129,6 +124,6 @@ class GetBlogPosts: return cls._cached_posts -def get_blog_posts(url: str = BLOG_POSTS_GITHUB_URL) -> List[Dict[str, str]]: +def get_blog_posts(url: str) -> List[Dict[str, str]]: """Public entry point — returns the blog posts list.""" return GetBlogPosts.get_blog_posts(url=url) diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index b2fb3dd0f8a..29c9cb571ca 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -2,6 +2,7 @@ import json import os from typing import List +import litellm from fastapi import APIRouter, Depends, HTTPException from litellm._logging import verbose_logger @@ -213,7 +214,7 @@ async def get_litellm_blog_posts(): Falls back to the bundled local backup on any failure. """ try: - posts_data = get_blog_posts() + posts_data = get_blog_posts(url=litellm.blog_posts_url) except Exception as e: verbose_logger.warning( "LiteLLM: get_litellm_blog_posts endpoint fallback triggered: %s", str(e) diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index f6795f12bbc..b465d13bc70 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -93,11 +93,6 @@ class UISettings(BaseModel): description="If enabled, forwards client headers (e.g. Authorization) to the LLM API. Required for Claude Code with Max subscription.", ) - disable_show_blog: bool = Field( - default=False, - description="If true, hides the Blog dropdown from the UI navbar.", - ) - class UISettingsResponse(SettingsResponse): """Response model for UI settings""" @@ -112,7 +107,6 @@ ALLOWED_UI_SETTINGS_FIELDS = { "enabled_ui_pages_internal_users", "require_auth_for_public_ai_hub", "forward_client_headers_to_llm_api", - "disable_show_blog", } diff --git a/tests/proxy_unit_tests/test_proxy_setting_endpoints.py b/tests/proxy_unit_tests/test_proxy_setting_endpoints.py deleted file mode 100644 index 1bc50919689..00000000000 --- a/tests/proxy_unit_tests/test_proxy_setting_endpoints.py +++ /dev/null @@ -1,13 +0,0 @@ -def test_ui_settings_has_disable_show_blog_field(): - """UISettings model must include disable_show_blog.""" - from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import UISettings - - settings = UISettings() - assert hasattr(settings, "disable_show_blog") - assert settings.disable_show_blog is False # default - - -def test_allowed_ui_settings_fields_contains_disable_show_blog(): - from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import ALLOWED_UI_SETTINGS_FIELDS - - assert "disable_show_blog" in ALLOWED_UI_SETTINGS_FIELDS diff --git a/tests/test_litellm/test_get_blog_posts.py b/tests/test_litellm/test_get_blog_posts.py index 3f36a5b5fa5..a17d78e0bb6 100644 --- a/tests/test_litellm/test_get_blog_posts.py +++ b/tests/test_litellm/test_get_blog_posts.py @@ -5,8 +5,8 @@ from unittest.mock import MagicMock, patch import pytest +import litellm from litellm.litellm_core_utils.get_blog_posts import ( - BLOG_POSTS_GITHUB_URL, BlogPost, BlogPostsResponse, GetBlogPosts, @@ -68,7 +68,7 @@ def test_get_blog_posts_success(): mock_response.raise_for_status = MagicMock() with patch("litellm.litellm_core_utils.get_blog_posts.httpx.get", return_value=mock_response): - posts = get_blog_posts() + posts = get_blog_posts(url=litellm.blog_posts_url) assert len(posts) == 1 assert posts[0]["title"] == "Test Post" @@ -80,7 +80,7 @@ def test_get_blog_posts_network_error_falls_back_to_local(): "litellm.litellm_core_utils.get_blog_posts.httpx.get", side_effect=Exception("Network error"), ): - posts = get_blog_posts() + posts = get_blog_posts(url=litellm.blog_posts_url) assert isinstance(posts, list) assert len(posts) > 0 @@ -93,7 +93,7 @@ def test_get_blog_posts_invalid_json_falls_back_to_local(): mock_response.raise_for_status = MagicMock() with patch("litellm.litellm_core_utils.get_blog_posts.httpx.get", return_value=mock_response): - posts = get_blog_posts() + posts = get_blog_posts(url=litellm.blog_posts_url) assert isinstance(posts, list) assert len(posts) > 0 @@ -115,7 +115,7 @@ def test_get_blog_posts_ttl_cache_not_refetched(): return m with patch("litellm.litellm_core_utils.get_blog_posts.httpx.get", side_effect=mock_get): - posts = get_blog_posts() + posts = get_blog_posts(url=litellm.blog_posts_url) assert call_count == 0 # cache hit, no fetch assert len(posts) == 1 @@ -133,7 +133,7 @@ def test_get_blog_posts_ttl_expired_refetches(): with patch( "litellm.litellm_core_utils.get_blog_posts.httpx.get", return_value=mock_response ) as mock_get: - posts = get_blog_posts() + posts = get_blog_posts(url=litellm.blog_posts_url) mock_get.assert_called_once() assert len(posts) == 1 @@ -142,7 +142,7 @@ def test_get_blog_posts_ttl_expired_refetches(): def test_get_blog_posts_local_env_var_skips_remote(monkeypatch): monkeypatch.setenv("LITELLM_LOCAL_BLOG_POSTS", "true") with patch("litellm.litellm_core_utils.get_blog_posts.httpx.get") as mock_get: - posts = get_blog_posts() + posts = get_blog_posts(url=litellm.blog_posts_url) mock_get.assert_not_called() assert isinstance(posts, list) assert len(posts) > 0 diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index 164368eb6ba..b05d707d5ab 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "dev:webpack": "next dev --webpack", "build": "next build", "start": "next start", "lint": "next lint",