adjust blog posts to fetch from github first

This commit is contained in:
yuneng-jiang 2026-02-23 14:45:05 -08:00
parent a0965d5b4a
commit 1ecfbad46e
8 changed files with 18 additions and 52 deletions

View File

@ -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",

View File

@ -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"
}
]
}

View File

@ -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)

View File

@ -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)

View File

@ -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",
}

View File

@ -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

View File

@ -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

View File

@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
"dev:webpack": "next dev --webpack",
"build": "next build",
"start": "next start",
"lint": "next lint",