feat: add Perplexity as default web search backend + LLM provider plugin#23981
Open
jliounis wants to merge 4 commits into
Open
feat: add Perplexity as default web search backend + LLM provider plugin#23981jliounis wants to merge 4 commits into
jliounis wants to merge 4 commits into
Conversation
This was referenced May 11, 2026
Closed
Adds a Perplexity Search API backend (https://api.perplexity.ai/search) as the new default web search provider, and registers a Perplexity LLM provider plugin for chat completion via the Perplexity Agent API (OpenAI-compatible at https://api.perplexity.ai). Web search backend - New tools/web_providers/perplexity.py with PerplexitySearchProvider exposing .search(query, limit) like the other backends. Posts to /search with Bearer auth and an X-Pplx-Integration header. Clamps limit to 1..20 and normalizes results to {title, url, snippet}. - tools/web_tools.py: _get_backend() now checks perplexity first in the auto-detect fallback and returns 'perplexity' as the no-keys default. _is_backend_available, _web_requires_env, web_search_tool dispatch, check_web_api_key and the __main__ debug block all learn about the new backend. Reads PERPLEXITY_API_KEY (or legacy PPLX_API_KEY). - hermes_cli/tools_config.py: Perplexity becomes the first/recommended web provider in the 'hermes tools' wizard. Firecrawl is demoted from recommended to 'paid'. LLM provider plugin - plugins/model-providers/perplexity/{__init__.py, plugin.yaml}: ProviderProfile with name='perplexity', aliases=('pplx',), env_vars=('PERPLEXITY_API_KEY','PPLX_API_KEY'), base_url='https://api.perplexity.ai', and fallback models routed through the Agent API (gpt-5.4, claude-sonnet-4-6, gemini-3-1-pro). - tests/providers/test_plugin_discovery.py: bump expected profile count and spot-check 'perplexity'. Docs - web-search.md: Perplexity row + section, default switched, auto-detect priority table updated. - providers.md / integrations/index.md / environment-variables.md: Perplexity added everywhere relevant. - mcp.md: example for the Perplexity MCP server (server-perplexity-ask). Tests - tests/tools/test_web_tools_perplexity.py: 21 tests across 5 classes covering API-key resolution, request shape (Bearer + integration header), result normalization, limit clamping, backend selection (default/configured/auto-detected/PPLX alias), is_backend_available, and web_search_tool dispatch through the perplexity branch. - tests/tools/test_web_tools_config.py: env-clearing lists include PERPLEXITY_API_KEY and PPLX_API_KEY; replaced test_fallback_no_keys_defaults_to_firecrawl with the new perplexity default plus perplexity-only and priority tests. Signed-off-by: jliounis <james.liounis@perplexity.ai>
13d9f59 to
2b3d38e
Compare
Author
|
Rebased onto main; all 223 web-related tests are green. |
rbuchmayer-pplx
added a commit
to rbuchmayer-pplx/hermes-agent
that referenced
this pull request
May 19, 2026
James's PR NousResearch#23981 registers Perplexity as an LLM provider with fallback_models in provider/model format (`openai/gpt-5.4`, `anthropic/claude-sonnet-4-6`, `google/gemini-3-1-pro`) — but the profile inherits `api_mode="chat_completions"` (the dataclass default in providers/base.py:44) and sets `base_url="https://api.perplexity.ai"` (no `/v1`). With those values the OpenAI SDK hits `POST /chat/completions`, which is Sonar-only and rejects provider/model strings with HTTP 400: $ curl -X POST https://api.perplexity.ai/chat/completions \ -d '{"model":"openai/gpt-5.4","messages":[...]}' {"error":{"message":"preset \"openai/gpt-5.4\" not found","code":400}} The multi-provider routing James's PR description references actually lives at `POST /v1/responses` — Perplexity's OpenAI-Responses-compatible alias to the Agent API (same backend as `/v1/agent`). Verified live: all of `openai/gpt-5.4`, `anthropic/claude-sonnet-4-6`, and `perplexity/sonar` return HTTP 200 at /v1/responses. (`google/gemini-3-1-pro` is not yet supported there — model-list curation issue, separate from wiring.) ## Changes 1. plugins/model-providers/perplexity/__init__.py: - Add `api_mode="codex_responses"` (Hermes' name for the OpenAI Responses API transport) - Change `base_url` from `https://api.perplexity.ai` to `https://api.perplexity.ai/v1` so the OpenAI SDK's `responses.create()` resolves to `POST /v1/responses` - Expand docstring with the rationale and a pointer to the analogous xai profile (`api.x.ai/v1` + `codex_responses`) 2. hermes_cli/runtime_provider.py — `_detect_api_mode_for_url`: - Add `api.perplexity.ai` hostname branch returning `codex_responses` - Defense-in-depth: ensures URL-based autodetect picks Responses mode even when users set `model.base_url` out-of-band without the `api_mode` config key. Matches the existing `api.x.ai` / `api.openai.com` pattern. 3. tests/hermes_cli/test_detect_api_mode_for_url.py: - Add 3 cases to TestCodexResponsesDetection mirroring the xai/openai test pattern: - test_perplexity_api_returns_codex_responses - test_perplexity_host_suffix_does_not_match (lookalike protection) - test_perplexity_path_segment_does_not_match - Update module docstring header ## Test plan `uv run pytest` on the 10 suites from NousResearch#23981's test plan plus the URL detector tests: tests/tools/test_web_tools_perplexity.py tests/tools/test_web_tools_config.py tests/tools/test_web_tools_tavily.py tests/tools/test_web_providers.py tests/tools/test_web_providers_brave_free.py tests/tools/test_web_providers_ddgs.py tests/tools/test_web_providers_searxng.py tests/tools/test_tool_backend_helpers.py tests/providers/test_plugin_discovery.py tests/plugins/web/test_web_search_provider_plugins.py tests/hermes_cli/test_detect_api_mode_for_url.py -> 292 passed in 5.88s locally. `ruff check` clean on all changed files. ## Live verification through the OpenAI SDK (the actual Hermes code path) from openai import OpenAI client = OpenAI(base_url="https://api.perplexity.ai/v1", api_key=...) r = client.responses.create(model="openai/gpt-5.4", input="say hi") # -> status=completed, model=openai/gpt-5.4, text="Hi, how are you?" r = client.responses.create(model="anthropic/claude-sonnet-4-6", input="say hi") # -> status=completed, model=anthropic/claude-sonnet-4-6, text="Hi there, friend!" This is the same dispatch path used by `agent/codex_runtime.py:341` (`active_client.responses.create(**fallback_kwargs)`) when `agent.api_mode == "codex_responses"`. ## Not changed - James's `fallback_models` list — `google/gemini-3-1-pro` doesn't work at /v1/responses, but model-list curation is James's call (he knows the Agent API roadmap; my list-based verification is a snapshot). - Search API code paths — already work via /search at the root host; unrelated to /v1/responses. (PERPLEXITY_API_URL env override on the Search backend remains from the prior PR scope.)
23 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Perplexity as the default web search backend in Hermes Agent, and registers a Perplexity LLM provider plugin so users can route chat completion through the Perplexity Agent API (OpenAI-compatible).
Two integration paths, both opt-in via a single API key:
tools/web_providers/perplexity.pycallsPOST https://api.perplexity.ai/searchand returns normalized{title, url, snippet}results. Wired intotools/web_tools.pyas the new default in_get_backend().plugins/model-providers/perplexity/registers aProviderProfilewithbase_url=https://api.perplexity.ai(OpenAI-compatible) so any Hermes flow that takes a provider can route through Perplexity.Why default to Perplexity for web search?
{title, url, snippet}shapePERPLEXITY_API_KEY(or legacyPPLX_API_KEY) and onePOST /searchcall.web.backendis set in~/.hermes/config.yaml, that wins. If any of the existing keys (FIRECRAWL_*,PARALLEL_*,TAVILY_*,EXA_*, etc.) is set withoutweb.backend, those still resolve to their existing backends. The only behavior change for an existing user is "no key configured" → now defaults to Perplexity instead of Firecrawl (which would also have failed at client init).Changes
Web search backend
tools/web_providers/perplexity.py—PerplexitySearchProviderexposing.search(query, limit), plusperplexity_search()/_perplexity_search_request()/_normalize_perplexity_results()/_get_perplexity_api_key(). Bearer auth,X-Pplx-Integration: hermes-agent/1.0attribution header,limitclamped to[1, 20].tools/web_tools.py:_get_backend()validates"perplexity"in the configured set, placesperplexityfirst in the auto-detect candidate list, and returns"perplexity"as the shipped default when no keys are present._is_backend_available()resolves"perplexity"viaPERPLEXITY_API_KEYorPPLX_API_KEY._web_requires_env()advertises both env vars.web_search_tooldispatches to the perplexity branch first.check_web_api_key()and the__main__debug block both learn the new backend.hermes_cli/tools_config.py— Perplexity is now the first option under web providers in thehermes toolswizard with the★ recommendedbadge. Firecrawl is demoted topaid.LLM provider plugin
plugins/model-providers/perplexity/__init__.pyandplugin.yaml—ProviderProfile(name="perplexity", aliases=("pplx",), env_vars=("PERPLEXITY_API_KEY","PPLX_API_KEY"), base_url="https://api.perplexity.ai", fallback_models=("openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-3-1-pro")). All fallback models are reachable through the Perplexity Agent API.Docs
website/docs/user-guide/features/web-search.md— Backends table gains aPerplexity (default)row, full section, updated config snippet, and refreshed auto-detect priority table.website/docs/integrations/providers.md— Inference Providers table gains a Perplexity row (Agent API, OpenAI-compatible).website/docs/integrations/index.md— default backend updated to Perplexity.website/docs/reference/environment-variables.md—PERPLEXITY_API_KEYandPPLX_API_KEYrows added under Tool APIs.website/docs/user-guide/features/mcp.md— new### Perplexity MCP server for live web researchexample usingserver-perplexity-ask.Tests
tests/tools/test_web_tools_perplexity.py— 21 tests across 5 classes covering API-key resolution (PERPLEXITY_API_KEYwins overPPLX_API_KEY, whitespace stripping, raises when neither is set), request shape (Bearer auth +X-Pplx-Integrationheader + payload), result normalization (snippet/content fallback, missing fields),limitclamping (0 → 1,100 → 20), backend selection (configured / auto-detected /PPLX_API_KEYalias / no-keys default),_is_backend_available(), and end-to-endweb_search_tooldispatch through the perplexity branch.tests/tools/test_web_tools_config.py—_ENV_KEYSlists includePERPLEXITY_API_KEY/PPLX_API_KEY(so the env-clearing setup works as intended),test_fallback_no_keys_defaults_to_firecrawlbecomestest_fallback_no_keys_defaults_to_perplexity, plus two new tests (test_fallback_perplexity_only_key,test_fallback_perplexity_takes_priority_over_firecrawl).tests/providers/test_plugin_discovery.py— bump expected profile count (33 → 34) and spot-check"perplexity".Test plan
→
215 passed in 6.32slocally.Manual verification
Notes / non-goals
FIRECRAWL_API_KEY/PARALLEL_API_KEY/TAVILY_API_KEY/EXA_API_KEYset will continue to resolve to their existing backend with no config change.POST /searchrequests carryX-Pplx-Integration: hermes-agent/1.0so Perplexity can attribute traffic for monitoring & rate-limit hygiene.Happy to iterate on any of this — naming, default-backend choice, fallback model list, doc placement, etc.