Spaces:
Running
Running
Commit History
-
Stop the search chain treating an empty answer as an answer
Claude +1: Claude Opus 5Unverified signature`/serp/search` returned zero results and the explanation "All backends are rate-limited." - a claim with nothing behind it. Seen once against the live deployment, then not reproducible, which is itself the problem: the message described a diagnosis rather than what happened. Two causes, both fixed here. A backend that returned an empty list without raising ended the fallback chain and was recorded as a success. Brave does that whenever its block page lacks the word "suspicious", and Bing whenever no result item yields a title and href - so one quiet backend could stop the chain before a working one was ever tried. This is the same bug class fixed for DuckDuckGo in d341a7a, which raises on empty; the other two never got the same treatment. Fixing it in the chain rather than in each scraper covers every backend, including any added later, and leaves the single-backend endpoints free to report an honest empty result. An empty answer still counts neither for nor against the backend, as …73c1aa6 -
Detect Google Scholar's block page, and unwrap Bing's redirect links
Claude +1: Claude Opus 5Unverified signatureBoth found by running the live smoke test against the deployed MCP server, not by the suite - the division of labour CONTRIBUTING.md already describes. Google Scholar serves an anti-bot interstitial to the Space's IP instead of results. That page has no `div.gs_ri`, so the scraper sat in `wait_for_selector` for the full 30 seconds and then reported a bare "Timeout 30000ms exceeded" - slow for every caller, and worded like a broken selector rather than a blocked deployment. It now waits for the results selector and Google's challenge elements together, so a block is recognised as soon as the page loads and raises GoogleScholarBlockedException with a message that says what actually happened. A challenge page carrying none of those elements falls back to matching Google's wording, but only after the selector wait has failed: sniffing the text up front would be faster and wrong, since a legitimate Scholar search for "unusual traffic" returns papers whose snippets contain that phrase. A p…f493ed5 -
Move orchestration out of the route handlers into a service layer
Claude +1: Claude Opus 5Unverified signatureThe backend policy - which backend to try, in what order, when to fall back, what counts as evidence a backend is unhealthy, when a patent is absent rather than merely unreachable - was written inline in FastAPI route handlers. It is not HTTP-specific: it would be equally true behind a CLI or a queue consumer, and putting it there meant exercising any of it cost an ASGI round-trip plus monkeypatching module globals. It now lives in services.py as SearchService and PatentService, which take their stateful collaborators (HTTP client, browser provider, circuit breaker, OPS credentials) as constructor arguments. app.py keeps the module-level instances and wires them in through `Depends`, so `app.dependency_overrides` replaces them in a test. The services raise domain errors - PatentNotFound, UpstreamUnavailable, OPSUnconfigured - rather than HTTPException. Mapping those onto status codes is the HTTP layer's job and is now three exception handlers instead of the same branching repeated ac…e44fdef