diff --git a/backend/packages/harness/deerflow/community/firecrawl/tools.py b/backend/packages/harness/deerflow/community/firecrawl/tools.py index 86f44150a..f46d9ddf1 100644 --- a/backend/packages/harness/deerflow/community/firecrawl/tools.py +++ b/backend/packages/harness/deerflow/community/firecrawl/tools.py @@ -9,9 +9,16 @@ from deerflow.config import get_app_config def _get_firecrawl_client(tool_name: str = "web_search") -> FirecrawlApp: config = get_app_config().get_tool_config(tool_name) api_key = None - if config is not None and "api_key" in config.model_extra: - api_key = config.model_extra.get("api_key") - return FirecrawlApp(api_key=api_key) # type: ignore[arg-type] + api_url = None + if config is not None: + if "api_key" in config.model_extra: + api_key = config.model_extra.get("api_key") + if "base_url" in config.model_extra: + api_url = config.model_extra.get("base_url") + kwargs = {"api_key": api_key} + if api_url: + kwargs["api_url"] = api_url + return FirecrawlApp(**kwargs) # type: ignore[arg-type] @tool("web_search", parse_docstring=True) diff --git a/backend/tests/test_firecrawl_tools.py b/backend/tests/test_firecrawl_tools.py index fd61f817e..4e0720e56 100644 --- a/backend/tests/test_firecrawl_tools.py +++ b/backend/tests/test_firecrawl_tools.py @@ -64,3 +64,51 @@ class TestWebFetchTool: "https://example.com", formats=["markdown"], ) + + +class TestFirecrawlBaseUrl: + @patch("deerflow.community.firecrawl.tools.FirecrawlApp") + @patch("deerflow.community.firecrawl.tools.get_app_config") + def test_fetch_passes_base_url_as_api_url(self, mock_get_app_config, mock_firecrawl_cls): + fetch_config = MagicMock() + fetch_config.model_extra = {"base_url": "http://192.168.0.47:3002"} + + def get_tool_config(name): + if name == "web_fetch": + return fetch_config + return None + + mock_get_app_config.return_value.get_tool_config.side_effect = get_tool_config + + mock_scrape_result = MagicMock() + mock_scrape_result.markdown = "Fetched markdown" + mock_scrape_result.metadata = MagicMock(title="Fetched Page") + mock_firecrawl_cls.return_value.scrape.return_value = mock_scrape_result + + from deerflow.community.firecrawl.tools import web_fetch_tool + + result = web_fetch_tool.invoke({"url": "https://example.com"}) + + assert result == "# Fetched Page\n\nFetched markdown" + mock_firecrawl_cls.assert_called_once_with(api_key=None, api_url="http://192.168.0.47:3002") + + @patch("deerflow.community.firecrawl.tools.FirecrawlApp") + @patch("deerflow.community.firecrawl.tools.get_app_config") + def test_search_passes_base_url_and_api_key(self, mock_get_app_config, mock_firecrawl_cls): + search_config = MagicMock() + search_config.model_extra = { + "api_key": "firecrawl-key", + "base_url": "http://192.168.0.47:3002", + "max_results": 5, + } + mock_get_app_config.return_value.get_tool_config.return_value = search_config + + mock_result = MagicMock() + mock_result.web = [] + mock_firecrawl_cls.return_value.search.return_value = mock_result + + from deerflow.community.firecrawl.tools import web_search_tool + + web_search_tool.invoke({"query": "test query"}) + + mock_firecrawl_cls.assert_called_once_with(api_key="firecrawl-key", api_url="http://192.168.0.47:3002") diff --git a/config.example.yaml b/config.example.yaml index 043eb349b..719a27bf2 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -868,12 +868,13 @@ tools: # contents_max_characters: 1000 # # api_key: $EXA_API_KEY - # Web search tool (uses Firecrawl, requires FIRECRAWL_API_KEY) + # Web search tool (uses Firecrawl. Cloud requires FIRECRAWL_API_KEY; self-host may need no key.) # - name: web_search # group: web # use: deerflow.community.firecrawl.tools:web_search_tool # max_results: 5 # # api_key: $FIRECRAWL_API_KEY + # # base_url: http://:3002 # optional: self-hosted Firecrawl; no cloud API key needed # Web search tool (uses GroundRoute, requires GROUNDROUTE_API_KEY) # GroundRoute is a meta search layer: one API in front of six engines (Serper, @@ -1059,11 +1060,12 @@ tools: # # Timeout for navigating to the page (in seconds). Set to positive value to enable, -1 to disable # navigation_timeout: 30 - # Web fetch tool (uses Firecrawl, requires FIRECRAWL_API_KEY) + # Web fetch tool (uses Firecrawl. Cloud requires FIRECRAWL_API_KEY; self-host may need no key.) # - name: web_fetch # group: web # use: deerflow.community.firecrawl.tools:web_fetch_tool # # api_key: $FIRECRAWL_API_KEY + # # base_url: http://:3002 # optional: self-hosted Firecrawl; no cloud API key needed # Web fetch tool (uses GroundRoute, requires GROUNDROUTE_API_KEY) # Fetches a page's extracted text via GroundRoute mode=page.