From 5ea048dc6ce02b223b02532b2d404a92fe637f5e Mon Sep 17 00:00:00 2001 From: Thomas Menard Date: Sun, 3 May 2026 16:48:58 +0200 Subject: [PATCH] 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. --- .../llms/firecrawl/fetch/transformation.py | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/litellm/llms/firecrawl/fetch/transformation.py b/litellm/llms/firecrawl/fetch/transformation.py index 3c82ffbde3e..98714374403 100644 --- a/litellm/llms/firecrawl/fetch/transformation.py +++ b/litellm/llms/firecrawl/fetch/transformation.py @@ -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: