feat(web): add Keenable as a native web search & extract provider (keyless-capable) - #49758
feat(web): add Keenable as a native web search & extract provider (keyless-capable)#49758IlyaGusev wants to merge 1 commit into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for building this against the web-provider interface. The native-provider portion needs a scope change before it can be considered.
Problems
plugins/web/keenable/is a new in-tree third-party vendor integration, which conflicts with the standalone-plugin policy inAGENTS.md:126-136. The optional skill and MCP manifest are separable contributions.plugins/web/keenable/provider.py:64unconditionally sendsX-Keenable-Title: Hermes;AGENTS.md:118-121requires an explicit generic opt-in for outbound attribution tagging.plugins/web/keenable/provider.py:54-59reads credentials directly fromos.getenv, unlike the config-aware provider helper atagent/web_search_provider.py:59-81.- The PR's provider test changes are inventory assertions only (
tests/plugins/web/test_web_search_provider_plugins.py:81-132); request shaping and keyless/keyed behavior remain untested.
Suggested changes
- Preserve the optional skill/MCP work if desired, but distribute the native provider as a standalone plugin.
- Remove or opt-in-gate attribution, use
get_provider_env, and add mocked provider behavior tests.
Current tools/web_tools.py:231-327 already resolves registered providers generically, consistent with the #31873 discussion, so the vendor-specific backend-gate edits need a fresh port rather than a direct salvage.
This is an automated hermes-sweeper review.
|
|
||
|
|
||
| def _keenable_headers(api_key: str) -> Dict[str, str]: | ||
| """Request headers; ``X-API-Key`` only when a key is present (keyless otherwise).""" |
There was a problem hiding this comment.
Use agent.web_search_provider.get_provider_env("KEENABLE_API_KEY") here and in is_available(). Direct os.getenv() bypasses Hermes-managed .env lookup for gateway, delegate-child, and subprocess contexts; see agent/web_search_provider.py:59-81.
| def _endpoint(base_url: str, path: str, api_key: str) -> str: | ||
| """Keyless calls hit the ``/public`` variant (no auth, rate-limited).""" | ||
| return f"{base_url}{path}" if api_key else f"{base_url}{path}/public" | ||
|
|
There was a problem hiding this comment.
This unconditionally tags every outbound request with Hermes attribution. AGENTS.md:118-121 requires a generic user-facing opt-in before adding outbound attribution or third-party identifier tagging; remove this header or implement that prerequisite.
| @@ -96,6 +98,7 @@ def test_all_seven_plugins_present_in_registry(self) -> None: | |||
| ("parallel", True, True), | |||
There was a problem hiding this comment.
Please add a dedicated mocked provider test file covering keyed and keyless request URLs/headers, search normalization and truncation, extract result/error shapes, and config-managed credential lookup. This table only verifies inventory and capability declarations.
Native web-search provider moved out to a standalone pip plugin (hermes-keenable-web) per the standalone-plugin policy. This keeps only the two separable, in-tree parts: the optional MCP catalog entry and the research skill.
1084011 to
1f7668d
Compare
|
Reworked this per the review. I pulled the in-tree provider and all its wiring. It now ships as a standalone pip plugin, What's left in this PR is just the two separable pieces you flagged: the MCP catalog entry ( |
|
@teknium1 could you take a look please? |
What does this PR do?
Adds Keenable (low-latency web search + page-to-markdown fetch for agents) as a native Web Search & Extract provider, following the plugin architecture from #25182 and the provider precedents in #29042 (xAI) / #46559 (iFlow).
Users select Keenable from the Web Search & Extract provider picker (or
web.backend: keenableinconfig.yaml). It maps:WebSearchProvider.search()→GET /v1/search?query=(result count applied client-side)WebSearchProvider.extract()→GET /v1/fetch?url=against
https://api.keenable.ai(endpoints/DTOs per the published OpenAPI spec atdocs.keenable.ai/api-reference). TheX-API-Keyheader is sent when a key is configured; requests also sendX-Keenable-Title: Hermesfor client attribution.The PR also ships two optional, separable surfaces for the same vendor (see Scope below): an optional CLI skill and an MCP catalog entry.
Motivation
Keenable fits the Web Search & Extract provider layer (search + page fetch in one provider, no browser sessions). Keyless, but opt-in. Keenable's free tier works without a key: when
KEENABLE_API_KEYis unset the provider calls the/publicendpoint variants (rate-limited) and omitsX-API-Key, mirroring Keenable's official MCP client. To avoid the silent-default issue the keyless-Parallel revert (#46350) addressed, keenable is never auto-selected —is_available()stays key-gated so it is excluded from the no-credential fallback, and it serves keyless only when explicitly chosen viaweb.backend/web.*_backend. AKEENABLE_API_KEYraises rate limits.Scope note (happy to split)
This PR bundles three logical pieces for one vendor. If reviewers prefer one-change-per-PR, I'll split into:
plugins/web/keenable/+ core wiring (the primary change).optional-skills/research/keenable-cli/.optional-mcps/keenable/.Type of Change
Changes Made
Native provider
plugins/web/keenable/{plugin.yaml,__init__.py,provider.py}—KeenableWebSearchProvider(search + extract), keyless/publicfallback,KEENABLE_API_URLoverride.tools/web_tools.py— registerkeenablein the configured-backend sets, auto-detect candidate,_is_backend_available, and tool-metadata env vars.hermes_cli/config.py—KEENABLE_API_KEYinOPTIONAL_ENV_VARS+ the API-key display/allowlist lists.agent/web_search_registry.py— added to the legacy fallback preference.Optional skill + MCP catalog
optional-skills/research/keenable-cli/SKILL.md— CLI skill (HARDLINE-compliant: 56-char description, modern section order).optional-mcps/keenable/manifest.yaml— remote HTTP MCP catalog entry (api.keenable.ai/mcp).Packaging / attribution / docs
pyproject.toml—data-filestarget so the MCP manifest ships in the wheel.scripts/release.py—AUTHOR_MAPentry.website/docs/**+website/i18n/zh-Hans/**— Keenable added to provider/env-var tables (EN + zh).Tests
tests/tools/conftest.py+tests/plugins/web/test_web_search_provider_plugins.py— existing provider-set/capability assertions updated to includekeenable.How to Test
Checklist
pytest tests/ -qlocally — not run by the author locally (no dev env); CI is the gate. Provider request/response shaping verified via injected-httpxchecks (keyed + keyless paths).tests/tools/test_web_providers_keenable.py) — not yet added; existing provider-set tests updated instead. Happy to add to match the xAI/iFlow pattern.httpx; skill declares all three platforms; MCP is a remote URLhttpx)Notes for maintainers
Endpoints/DTOs follow Keenable's published OpenAPI spec (
docs.keenable.ai/api-reference). Live keyless smoke test passed againstapi.keenable.ai(/v1/search/public+/v1/fetch/public): search returns ranked results, fetch returns page markdown. The smoke test also caught and fixed a spec/live mismatch —/v1/searchtakes nocountparam, solimitis applied client-side. The keyedX-API-Keypath is covered by injected-httpxchecks (no raw API key on hand for a live keyed run). Squash-merge recommended.