Skip to content

feat: add fastCRW web tool#884

Open
us wants to merge 2 commits into
potpie-ai:mainfrom
us:feat/add-fastcrw
Open

feat: add fastCRW web tool#884
us wants to merge 2 commits into
potpie-ai:mainfrom
us:feat/add-fastcrw

Conversation

@us

@us us commented Jun 14, 2026

Copy link
Copy Markdown

What

Adds fastCRW as a web scrape/search provider, alongside the existing Firecrawl integration — additive, mirrors the Firecrawl wiring (Firecrawl untouched).

Why

fastCRW is a self-hostable, fully open-source web engine (AGPL, single ~8MB Rust binary) that outperforms Firecrawl on Firecrawl's own benchmark dataset — 63.74% truth-recall vs 56.04%, faster median latency (p50 ~1.9s vs ~2.3s) — and runs 100% locally without gating any capability behind a cloud plan.

Where it's materially better than Firecrawl's self-host:

  • Reaches sites Firecrawl's OSS build can't. Firecrawl's anti-bot / stealth engine (fire-engine) is a cloud-only flag; its self-hosted version falls back to plain fetch or Playwright and can't handle Cloudflare JS challenges or heavily protected pages. fastCRW ships Cloudflare JS-challenge bypass, UA rotation, SPA rendering, BYO-proxy + rotation, and an HTTP→headless→proxy fallback ladder in the open core.
  • Higher recall, lower latency on the same benchmark. 63.74% truth-recall vs 56.04%; median response p50 ~1.9s vs ~2.3s (Firecrawl's own dataset).
  • Tiny footprint. ~8MB binary, ~6MB RAM — no multi-service stack, no Docker Compose sprawl, one process.
  • Search is not an alternative to SearXNG — it is built on top of it. SearXNG is the metasearch aggregator underneath; fastCRW adds a quality layer on top: query expansion (multi-variant rewrite), content-aware reranking (re-scoring by fetched content instead of SearXNG's content-blind ordering), a calibrated direct-answer mode, and category routing (research queries fan out to arxiv / semantic scholar / google scholar, code queries to GitHub). You get SearXNG's breadth plus a measurable accuracy layer, all open-source (AGPL) and self-hostable with configurable engines.
  • Flat pricing. 1 credit = 1 page; no 4× stealth surcharge, no billed-on-failure.

The integration diff is small because fastCRW exposes a Firecrawl-compatible API — that's the only reason it's listed here, not the value prop.

Key via CRW_API_KEY (free tier at https://fastcrw.com/dashboard); self-host base URL supported. I maintain the integration and can provide free credits to evaluate — happy to adjust to your conventions.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

WebpageExtractorTool is updated to support CRW_API_KEY as an alternative credential for fastCRW, a Firecrawl-API-compatible scraper. When CRW_API_KEY is set, FirecrawlApp is constructed with an api_url defaulting to https://fastcrw.com/api. The tool gating now accepts either key. A matching env template block is added.

Changes

fastCRW Integration

Layer / File(s) Summary
fastCRW credential resolution and tool gating
legacy/.env.template, legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py
Env template adds CRW_API_KEY and CRW_API_URL placeholders. WebpageExtractorTool.__init__ prefers CRW_API_KEY over FIRECRAWL_API_KEY, resolves CRW_API_URL with a default of https://fastcrw.com/api, and passes api_url to FirecrawlApp when present. Tool gating in webpage_extractor_tool now returns None only when neither key is set.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding fastCRW as a web tool feature, which directly corresponds to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly explains what fastCRW is, why it's being added, and how it integrates with the existing codebase alongside Firecrawl.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py`:
- Around line 44-52: The issue is that when `CRW_API_URL` is set to an empty
string, `os.getenv("CRW_API_URL", "https://fastcrw.com/api")` returns the empty
string instead of applying the default value, causing an empty `api_url` to be
passed to `FirecrawlApp` initialization on line 51, which breaks extraction. To
fix this, modify the assignment of `self.api_url` on line 46 to treat both None
(unset) and empty string values as triggers for using the default fastCRW cloud
endpoint by using a pattern that falls back to the default
"https://fastcrw.com/api" when the environment variable is either unset or
evaluates to an empty string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: b8326d43-3388-4ba3-aaca-d9e6b549a703

📥 Commits

Reviewing files that changed from the base of the PR and between 7729899 and 15b7f72.

📒 Files selected for processing (2)
  • legacy/.env.template
  • legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py

Comment thread legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py (1)

119-125: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize env vars in factory gating to match constructor logic.

At Line 120, raw os.getenv(...) truthiness allows whitespace-only keys to pass gating, while WebpageExtractorTool.__init__ strips those values (Lines 42-43). That can register a tool that always fails extraction. Because ToolService registers any non-None tool instance, this becomes user-visible misbehavior.

Suggested patch
 def webpage_extractor_tool(sql_db: Session, user_id: str) -> Optional[StructuredTool]:
-    if not os.getenv("CRW_API_KEY") and not os.getenv("FIRECRAWL_API_KEY"):
+    crw_api_key = (os.getenv("CRW_API_KEY") or "").strip()
+    firecrawl_api_key = (os.getenv("FIRECRAWL_API_KEY") or "").strip()
+    if not crw_api_key and not firecrawl_api_key:
         logger.warning(
             "Neither CRW_API_KEY nor FIRECRAWL_API_KEY set, webpage extractor tool "
             "will not be initialized"
         )
         return None

As per coding guidelines, “cross-file and configuration contract breaks” should validate actual configuration values and keep behavior consistent across layers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py`
around lines 119 - 125, The gating logic in the webpage_extractor_tool factory
function at line 120 uses raw os.getenv() calls without stripping whitespace,
allowing whitespace-only API keys to pass and create a tool instance. However,
the WebpageExtractorTool.__init__ method (lines 42-43) strips these values,
making them effectively empty. Normalize the gating logic by stripping the
environment variable values before checking their truthiness, just as the
constructor does. This ensures only non-whitespace API keys pass the gate and
the tool will function properly when created.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py`:
- Around line 119-125: The gating logic in the webpage_extractor_tool factory
function at line 120 uses raw os.getenv() calls without stripping whitespace,
allowing whitespace-only API keys to pass and create a tool instance. However,
the WebpageExtractorTool.__init__ method (lines 42-43) strips these values,
making them effectively empty. Normalize the gating logic by stripping the
environment variable values before checking their truthiness, just as the
constructor does. This ensures only non-whitespace API keys pass the gate and
the tool will function properly when created.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6f234866-6b37-468e-925a-a5861ccbae01

📥 Commits

Reviewing files that changed from the base of the PR and between 15b7f72 and ac3ab19.

📒 Files selected for processing (1)
  • legacy/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant