feat: support Firecrawl self-hosted/local instances via api_base config

FirecrawlFetchConfig now supports custom api_base URLs for:
- Self-hosted Firecrawl instances
- Local development
- On-premise deployments

Priority (highest to lowest):
1. api_base passed to afetch_url(optional_params)
2. api_base stored during validate_environment() from router config
3. FIRECRAWL_API_BASE environment variable
4. Default: https://api.firecrawl.dev/v1

Usage in proxy_config.yaml:
  fetch_tools:
    - fetch_tool_name: "my-local-firecrawl"
      litellm_params:
        fetch_provider: "firecrawl"
        api_base: "http://localhost:3002/api/v1"
        api_key: "os.environ/FIRECRAWL_API_KEY"

Also updated handler.py to pass api_base via optional_params.
This commit is contained in:
Thomas Menard 2026-05-03 16:48:58 +02:00
parent 014f6d68a3
commit 5ea048dc6c

View file

@ -16,11 +16,16 @@ from litellm.secret_managers.main import get_secret_str
class FirecrawlFetchConfig(BaseFetchConfig):
"""
Firecrawl fetch configuration.
Uses the /scrape endpoint to fetch and convert web content to markdown.
Uses the /scrape endpoint to fetch and scrape web content.
Supports both Firecrawl Cloud and self-hosted instances.
"""
FIRECRAWL_API_BASE = "https://api.firecrawl.dev/v1"
def __init__(self) -> None:
super().__init__()
self._api_base: Optional[str] = None
@staticmethod
def ui_friendly_name() -> str:
return "Firecrawl"
@ -42,6 +47,62 @@ class FirecrawlFetchConfig(BaseFetchConfig):
)
headers["Authorization"] = f"Bearer {api_key}"
headers["Content-Type"] = "application/json"
# Store api_base for later use in get_complete_url
if api_base:
self._api_base = api_base
return headers
def get_complete_url(self, api_base: Optional[str] = None) -> str:
"""Get complete URL for Fetch endpoint."""
# Priority: explicit argument > stored > env var > default
effective_api_base = (
api_base
or self._api_base
or get_secret_str("FIRECRAWL_API_BASE")
or self.FIRECRAWL_API_BASE
)
# Append "/scrape" to the api base if it's not already there
if not effective_api_base.endswith("/scrape"):
effective_api_base = f"{effective_api_base}/scrape"
return effective_api_base
async def afetch_url(
self,
url: str,
headers: Dict[str, str],
optional_params: Dict[str, Any],
) -> WebFetchResponse:
"""
Fetch content from a URL using Firecrawl's /scrape endpoint.
Supports self-hosted Firecrawl via api_base configuration.
"""
request_data = {
"url": url,
"formats": optional_params.get("formats", ["markdown"]),
"onlyMainContent": optional_params.get("onlyMainContent", True),
}
# Add optional parameters
for key in ["includeTags", "excludeTags", "headers", "waitFor", "timeout"]:
if key in optional_params:
request_data[key] = optional_params[key]
# Allow passing api_base via optional_params for dynamic override
api_base = optional_params.get("api_base")
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
self.get_complete_url(api_base=api_base),
headers=headers,
json=request_data,
)
headers["Authorization"] = f"Bearer {api_key}"
headers["Content-Type"] = "application/json"
return headers
def get_complete_url(self, api_base: Optional[str] = None) -> str: