Add Perplexity Search API support as internet search tool#1903
Add Perplexity Search API support as internet search tool#1903jliounis wants to merge 3 commits intoNVIDIA:developfrom
Conversation
WalkthroughAdds a new Perplexity internet search LangChain tool with configuration schema, async HTTP client, authentication resolution, exponential-backoff retries, result formatting, registration, documentation updates, a Vale vocabulary entry, and comprehensive tests. Changes
Sequence DiagramsequenceDiagram
participant Config as Config Provider
participant Tool as Perplexity Tool
participant Auth as API Key Resolver
participant Client as httpx.AsyncClient
participant API as Perplexity API
participant Formatter as Result Formatter
Config->>Tool: Initialize PerplexityInternetSearchToolConfig
Tool->>Auth: Resolve API key (secret or PERPLEXITY_API_KEY)
Auth-->>Tool: API key or missing
alt API key missing
Tool-->>Config: Return "Web search is unavailable" message
else API key present
Tool->>Tool: Truncate/validate query
Tool->>Client: POST /search with JSON body and headers (Authorization, X-Pplx-Integration)
Client->>API: Send request
alt Success
API-->>Client: JSON results
Client-->>Formatter: Deliver results
Formatter->>Formatter: Format into <Document href="..."/> blocks separated by ---
Formatter-->>Tool: Formatted response
Tool-->>Caller: Return formatted documents
else Failure
Client->>Client: Retry with exponential backoff up to max_retries
alt Retries exhausted
Client-->>Tool: Return "Web search failed after N attempts" message
else Retry succeeds
Client-->>Formatter: JSON results
Formatter-->>Tool: Formatted response
Tool-->>Caller: Return formatted documents
end
end
alt Empty results
Tool-->>Caller: Return "No web search results found..." message
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 9/10 reviews remaining, refill in 6 minutes. Comment |
Signed-off-by: PSI Bot <psi@perplexity.ai> Signed-off-by: James <james.liounis@perplexity.ai>
535ac26 to
9b8f95f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py (1)
75-77: ⚡ Quick winAlign fixture declaration with repository pytest fixture conventions.
Please define
name=on the fixture decorator and rename the fixture function to usefixture_prefix or_fixturesuffix.As per coding guidelines, "Pytest fixtures should define the name argument ... [and] function being decorated should be named using either a `fixture_` prefix or `_fixture` suffix."Suggested change
-@pytest.fixture -def tool_config(): +@pytest.fixture(name="tool_config") +def tool_config_fixture() -> PerplexityInternetSearchToolConfig: return PerplexityInternetSearchToolConfig(api_key="test-key", max_retries=2, max_query_length=50)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py` around lines 75 - 77, Rename the pytest fixture function tool_config to follow convention (e.g., fixture_tool_config or tool_config_fixture) and update its `@pytest.fixture` decorator to include the name argument (e.g., `@pytest.fixture`(name="tool_config")), ensuring the declared fixture name remains "tool_config" while the function uses the required fixture_ prefix or _fixture suffix; keep the returned object creation (PerplexityInternetSearchToolConfig(api_key="test-key", max_retries=2, max_query_length=50)) unchanged so tests referencing the "tool_config" fixture continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py`:
- Line 53: The country Field currently accepts any string; add schema-level
validation to enforce ISO 3166-1 alpha-2 shape by restricting values to exactly
two ASCII letters and normalising case before use: update the Field declaration
for country to include a regex/constraint for two letters (e.g. /^[A-Za-z]{2}$/)
and add a pydantic validator for the country attribute that uppercases the value
(and returns None if input is None) so invalid values are rejected at model
construction time; reference the existing country Field and its validator in the
same model/class where country: str | None = Field(...) is declared.
---
Nitpick comments:
In `@packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py`:
- Around line 75-77: Rename the pytest fixture function tool_config to follow
convention (e.g., fixture_tool_config or tool_config_fixture) and update its
`@pytest.fixture` decorator to include the name argument (e.g.,
`@pytest.fixture`(name="tool_config")), ensuring the declared fixture name remains
"tool_config" while the function uses the required fixture_ prefix or _fixture
suffix; keep the returned object creation
(PerplexityInternetSearchToolConfig(api_key="test-key", max_retries=2,
max_query_length=50)) unchanged so tests referencing the "tool_config" fixture
continue to work.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 1ebf83fb-a343-4069-a34b-f464d7bc500e
📒 Files selected for processing (5)
ci/vale/styles/config/vocabularies/nat/accept.txtdocs/source/get-started/tutorials/add-tools-to-a-workflow.mdpackages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.pypackages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/register.pypackages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py
Signed-off-by: James Liounis <james.liounis@perplexity.ai>
|
Hey maintainers — a few items where I need your help to unblock CI:
This PR mirrors the structure and conventions of #1846 (Exa) — same package layout, same test patterns, same docs section style. Just pushed a follow-up commit ( |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py (1)
75-76: ⚡ Quick winAdd a public docstring to
perplexity_internet_search.Line 76 exposes a public registration function without a docstring. Add a short Google-style docstring so the public API surface is consistently documented.
Proposed fix
`@register_function`(config_type=PerplexityInternetSearchToolConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def perplexity_internet_search(tool_config: PerplexityInternetSearchToolConfig, builder: Builder): + """Register the `perplexity_internet_search` LangChain tool. + + Args: + tool_config (PerplexityInternetSearchToolConfig): Tool configuration. + builder (Builder): Workflow builder context. + """ import osAs per coding guidelines, "Provide Google-style docstrings for every public module, class, function and CLI command."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py` around lines 75 - 76, Add a short Google-style docstring to the public function perplexity_internet_search to document its purpose, parameters, and return value; specifically describe what the function does, the tool_config parameter (PerplexityInternetSearchToolConfig) and builder parameter (Builder), and what the registered/returned object or side-effects are, following the existing project docstring style for public APIs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py`:
- Around line 117-139: The retry loop in the Perplexity search code (inside the
for loop that posts to PERPLEXITY_SEARCH_URL) currently catches all exceptions
and retries, which causes unnecessary retries for non-retriable client errors;
update the exception handling in that block to inspect HTTP responses and only
retry on transient errors (HTTP 5xx and 429) or network/timeouts, while
returning early for non-retriable 4xx errors (e.g., 401, 403) with a clear error
message; use response.raise_for_status() results or the response.status_code to
branch accordingly and avoid exponential-backoff retries for deterministic
client failures, and retain logger.exception for transient failures. Also add a
Google-style docstring to the public function perplexity_internet_search
describing its purpose, parameters, and return value.
---
Nitpick comments:
In
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py`:
- Around line 75-76: Add a short Google-style docstring to the public function
perplexity_internet_search to document its purpose, parameters, and return
value; specifically describe what the function does, the tool_config parameter
(PerplexityInternetSearchToolConfig) and builder parameter (Builder), and what
the registered/returned object or side-effects are, following the existing
project docstring style for public APIs.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: a7b2d2b1-aa99-47e2-9249-08a4186b8b9b
📒 Files selected for processing (2)
packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.pypackages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py
✅ Files skipped from review due to trivial changes (1)
- packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py
| for attempt in range(tool_config.max_retries): | ||
| try: | ||
| response = await client.post(PERPLEXITY_SEARCH_URL, headers=headers, json=request_body) | ||
| response.raise_for_status() | ||
| search_response = response.json() | ||
| results = search_response.get("results") if isinstance(search_response, dict) else None | ||
| if not results: | ||
| return f"No web search results found for: {question}" | ||
|
|
||
| web_search_results = "\n\n---\n\n".join( | ||
| [ | ||
| f'<Document href="{doc.get("url", "")}"/>\n{doc.get("snippet", "")}\n</Document>' | ||
| for doc in results | ||
| ] | ||
| ) | ||
| return web_search_results or f"No web search results found for: {question}" | ||
| except Exception: | ||
| # Return a graceful message instead of raising, so the agent can | ||
| # continue reasoning without web search rather than failing entirely. | ||
| logger.exception("Perplexity search attempt %d of %d failed", attempt + 1, tool_config.max_retries) | ||
| if attempt == tool_config.max_retries - 1: | ||
| return f"Web search failed after {tool_config.max_retries} attempts for: {question}" | ||
| await asyncio.sleep(2**attempt) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify current retry behavior and whether tests already cover non-retriable 4xx handling.
nl -ba packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py | sed -n '110,145p'
rg -n -C3 --type=py 'HTTPStatusError|401|403|429|raise_for_status|max_retries' \
packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.pyRepository: NVIDIA/NeMo-Agent-Toolkit
Length of output: 112
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the problematic code section with line numbers
echo "=== Source code around lines 110-145 ==="
cat -n packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py | sed -n '110,145p'
echo -e "\n=== Checking for test file ==="
ls -la packages/nvidia_nat_langchain/tests/ 2>/dev/null || echo "tests directory listing failed"Repository: NVIDIA/NeMo-Agent-Toolkit
Length of output: 3672
🏁 Script executed:
#!/bin/bash
# Check what exception types are imported and used
echo "=== Imports in perplexity_internet_search.py ==="
head -50 packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py | grep -E "^import|^from"
echo -e "\n=== Full function definition to check for docstring ==="
cat packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py | grep -n "^async def perplexity_internet_search\|^def perplexity_internet_search" -A 15Repository: NVIDIA/NeMo-Agent-Toolkit
Length of output: 1351
🏁 Script executed:
#!/bin/bash
# Search for exception handling patterns and test coverage
echo "=== Search for test file and error handling tests ==="
find packages/nvidia_nat_langchain/tests -name "*perplexity*" -type f 2>/dev/null || echo "No perplexity test files found"
if [ -f packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py ]; then
echo -e "\n=== Test file content ==="
cat -n packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.py
fiRepository: NVIDIA/NeMo-Agent-Toolkit
Length of output: 10151
Handle non-retriable HTTP errors separately from transient failures.
Lines 133–139 catch all exceptions and retry, including deterministic client errors like 401 (invalid credentials) or 403 (forbidden). This wastes retries and adds latency. Instead, distinguish between retriable errors (5xx, 429) and non-retriable 4xx errors, and return early for the latter.
Additionally, the public function perplexity_internet_search on line 76 lacks a docstring. Provide a concise description of its purpose, parameters, and return value (following Google-style docstring format).
Proposed fix
async with httpx.AsyncClient() as client:
for attempt in range(tool_config.max_retries):
try:
response = await client.post(PERPLEXITY_SEARCH_URL, headers=headers, json=request_body)
response.raise_for_status()
search_response = response.json()
@@
- except Exception:
+ except httpx.HTTPStatusError as exc:
+ status_code = exc.response.status_code
+ # Retry only rate-limit or server-side failures.
+ if 400 <= status_code < 500 and status_code != 429:
+ logger.error("Perplexity search failed with non-retriable HTTP status %d", status_code)
+ return f"Web search failed with HTTP {status_code} for: {question}"
+ logger.exception("Perplexity search attempt %d of %d failed", attempt + 1, tool_config.max_retries)
+ if attempt == tool_config.max_retries - 1:
+ return f"Web search failed after {tool_config.max_retries} attempts for: {question}"
+ await asyncio.sleep(2**attempt)
+ except Exception:
# Return a graceful message instead of raising, so the agent can
# continue reasoning without web search rather than failing entirely.
logger.exception("Perplexity search attempt %d of %d failed", attempt + 1, tool_config.max_retries)
if attempt == tool_config.max_retries - 1:
return f"Web search failed after {tool_config.max_retries} attempts for: {question}"
await asyncio.sleep(2**attempt)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py`
around lines 117 - 139, The retry loop in the Perplexity search code (inside the
for loop that posts to PERPLEXITY_SEARCH_URL) currently catches all exceptions
and retries, which causes unnecessary retries for non-retriable client errors;
update the exception handling in that block to inspect HTTP responses and only
retry on transient errors (HTTP 5xx and 429) or network/timeouts, while
returning early for non-retriable 4xx errors (e.g., 401, 403) with a clear error
message; use response.raise_for_status() results or the response.status_code to
branch accordingly and avoid exponential-backoff retries for deterministic
client failures, and retain logger.exception for transient failures. Also add a
Google-style docstring to the public function perplexity_internet_search
describing its purpose, parameters, and return value.
…tion-header helper Addresses CodeRabbit review nit: public registration function and the integration-header helper now carry Google-style docstrings, raising docstring coverage on the new module above NeMo's 80% threshold. Signed-off-by: James Liounis <james.liounis@perplexity.ai>
|
Pushed Addressed
Declined (with reason)
Still need maintainer help to unblock CI (admin-only):
DCO is green. Tests pass locally (matching the Exa pattern line-for-line). Happy to address anything else. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py (1)
82-82: ⚡ Quick winAdd a return type hint to the public
perplexity_internet_searchAPI.The function is publicly registered and currently omits an explicit return type annotation.
🔧 Suggested change
+from collections.abc import AsyncIterator @@ -async def perplexity_internet_search(tool_config: PerplexityInternetSearchToolConfig, builder: Builder): +async def perplexity_internet_search( + tool_config: PerplexityInternetSearchToolConfig, builder: Builder +) -> AsyncIterator[FunctionInfo]:As per coding guidelines, "All public APIs require Python 3.11+ type hints on parameters and return values."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py` at line 82, The public async function perplexity_internet_search is missing an explicit return type hint; update its signature to include the concrete return annotation that matches what it actually returns (for example -> PerplexityInternetSearchTool or -> Tool or the specific class/type returned) and import that type if necessary; if the exact return type is not yet defined, use a precise typing annotation such as -> typing.Coroutine[typing.Any, typing.Any, ReturnType] or -> typing.Any as a temporary measure, then run static type checks and replace with the concrete return type referenced in the function body (refer to the perplexity_internet_search function and its returned value to pick the correct symbol).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py`:
- Line 82: The public async function perplexity_internet_search is missing an
explicit return type hint; update its signature to include the concrete return
annotation that matches what it actually returns (for example ->
PerplexityInternetSearchTool or -> Tool or the specific class/type returned) and
import that type if necessary; if the exact return type is not yet defined, use
a precise typing annotation such as -> typing.Coroutine[typing.Any, typing.Any,
ReturnType] or -> typing.Any as a temporary measure, then run static type checks
and replace with the concrete return type referenced in the function body (refer
to the perplexity_internet_search function and its returned value to pick the
correct symbol).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 42315761-b95a-4262-8ed6-30cc688d7949
📒 Files selected for processing (1)
packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py
Summary
Add
perplexity_internet_searchas a LangChain internet search tool backed by the Perplexity Search API, mirroring the Exa integration added in #1846.PerplexityInternetSearchToolConfigwith configurablemax_results,max_retries,max_query_length,search_recency_filter,country,max_tokens_per_page, andapi_keyvia config orPERPLEXITY_API_KEY.httpx.AsyncClientso the tool can send Perplexity-specific search parameters and theX-Pplx-Integrationattribution header.perplexity_internet_searchfunction for LangChain workflows.Perplexityto the Vale vocabulary.Minimal config:
Set
PERPLEXITY_API_KEYbefore using the tool:Perplexity Search API docs: https://docs.perplexity.ai/api-reference/search-post
Testing
uv run --extra test pytest tests/test_perplexity_internet_search.py -vruff check packages/nvidia_nat_langchain/src/nat/plugins/langchain/tools/perplexity_internet_search.py packages/nvidia_nat_langchain/tests/test_perplexity_internet_search.pygit diff --checkuv run --extra test nat info components -t function -q perplexity -f component_nameSummary by CodeRabbit
New Features
Documentation
Tests
Style