Repository navigation
feat(node): add tool_tavily web-search agent tool - #1097
Conversation
Adds tool_tavily, exposing Tavily real-time web search as an agent tool (POST https://api.tavily.com/search) via the @tool_function "tavily". classType ["tool"], lanes {}, invoked through an agent's control-plane tool channel. Includes retry with backoff on 429/5xx, an SSRF guard on returned URLs, TAVILY_API_KEY env fallback, and unit tests for the result-shaping and URL-validation helpers. Cloned from tool_exa_search. Pairs with llm_nebius + agent_deepagent for agentic web search. Fixes rocketride-org#1047 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tavily.svg: replace the generic magnifying-glass placeholder with the official Tavily brand mark (icon portion of the official logo). - IGlobal: strip the configured apikey before falling back to TAVILY_API_KEY, so a whitespace-only config value no longer shadows the env var (and the "key required" error stays accurate). - IInstance: retry requests.exceptions.ConnectionError alongside Timeout (transient transport failures were previously treated as fatal). - Add a unit test for the ConnectionError retry path. - docs: clarify tool_tavily is agent-agnostic (attaches to any agent's tool channel), not coupled to agent_deepagent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- IGlobal: wrap int(maxResults) in try/except so a non-numeric config value falls back to the default 5 with a clear message instead of crashing beginGlobal with a raw ValueError. - README: add a language specifier (bash) to the env-var code fence (markdownlint MD040). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The import stub mocked `requests` but only set Timeout and exceptions.RequestException as real classes, leaving exceptions.ConnectionError (and the top-level requests.RequestException that IInstance references) as MagicMocks. Once the retry helper began catching ConnectionError, the except clause raised "catching classes that do not inherit from BaseException" under the stub, failing the new retry test in CI. Point both at real exception classes. Verified: 4 passed via `python3 -m pytest`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
resp.json() could raise ValueError on a 200 with a non-JSON body, which
tavily() does not catch (it only handles RuntimeError) — so it escaped
the {'success': False, ...} error contract and crashed the tool call. A
valid-but-non-object payload (list/string) also broke _shape_results'
body.get(...). Decode defensively in _request_with_retry: convert bad
JSON to RuntimeError and reject non-dict payloads. Adds a unit test for
the non-dict case. Verified: 5 passed via python3 -m pytest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…util Per @asclearuc / @dsapandora review on rocketride-org#1072: - Rename the API-key env var TAVILY_API_KEY -> ROCKETRIDE_TAVILY_KEY (RocketRide only substitutes ROCKETRIDE_-prefixed vars, and the node-test framework maps ROCKETRIDE_<PROVIDER>_<ATTR>). Defined as a module constant instead of a magic string. Updated IGlobal, services test `requires`, and README. - Move the SSRF URL guard out of the node into the shared ai.common.utils.validate_public_url (deduped from the search_exa copy; search_exa untouched, to be switched over in the dedup PR). Added ai-package tests for it; the tool's test stubs it as a pass-through. - time_range enum now uses sorted(VALID_TIME_RANGES) for consistency with search_depth / topic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… helper Per @asclearuc review on rocketride-org#1072: don't hand-roll a retry loop — use tenacity (the repo's standard retry mechanism) and put the shared helper in ai.common.utils. - Add ai.common.utils.post_with_retry: a tenacity-based POST that retries timeouts, connection errors, and 429/5xx (exponential backoff), raising on a final non-429 4xx without retry. Added ai-package tests. - tool_tavily IInstance now calls post_with_retry and does its own JSON / dict-shape validation; the local _request_with_retry loop (and the time/debug imports) are removed. search_exa / xtrace_memory untouched — they switch over in the dedup PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Register tool_tavily in _LLM_MOCK_CREDENTIALS and drop the ROCKETRIDE_TAVILY_KEY `requires` gate so the dynamic test builds the node under ROCKETRIDE_MOCK with a placeholder key instead of being skipped when no key is present (matches tool_exa_search).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds a Tavily web-search agent tool node with IGlobal state and IInstance tooling, shared post_with_retry and SSRF-safe validate_public_url utilities, service metadata and README, plus unit tests and test-framework mock credentials. ChangesTavily Tool Node and Infrastructure
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 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)
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. Comment |
🤖 Internal: Discord sync markerAuto-managed by the Discord notification workflow. Stores the linked Discord message ID. Do not edit or delete. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@nodes/src/nodes/tool_tavily/IInstance.py`:
- Around line 194-214: _shaped_results currently assumes each entry in
body.get('results') is a dict and will raise AttributeError for scalar or string
entries; update the _shape_results function to skip any non-dict items (e.g.,
add an isinstance(item, dict) guard before using item.get(...)) so malformed
upstream payloads are ignored rather than crashing, optionally track or log the
count of skipped items; keep existing validate_public_url handling for URLs and
preserve the current returned schema from _shape_results.
In `@packages/ai/src/ai/common/utils/url_utils.py`:
- Around line 24-34: The SSRF gate in validate_public_url currently checks a set
of individual ip properties (is_private, is_loopback, etc.) which misses CGNAT
addresses like 100.64.0.0/10; update the loop that iterates over addrinfo and
validates each sockaddr's IP (the ip variable) to reject any IP where
ip.is_global is False (i.e., if not ip.is_global: raise ValueError(...)),
replacing the existing combined property checks so any
non-public/reserved/non-global addresses are blocked; keep the same error
message using parsed.hostname to preserve context.
In `@packages/ai/tests/ai/common/utils/test_http_retry.py`:
- Around line 64-100: Add a new unit test that verifies post_with_retry will
retry on 5xx responses (e.g., first return a _FakeResp(500) then a
_FakeResp(200) and assert the successful response and call count), mirroring the
existing test_retries_429_then_succeeds pattern; reference the _is_retryable()
branch for 500-599 and the post_with_retry function to locate the relevant logic
and use _patch_post to monkeypatch requests as in the other tests.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: bc55c35c-54cd-45fd-b1f1-534b815e6f46
⛔ Files ignored due to path filters (1)
nodes/src/nodes/tool_tavily/tavily.svgis excluded by!**/*.svg
📒 Files selected for processing (14)
docs/README-nodes.mdnodes/src/nodes/tool_tavily/IGlobal.pynodes/src/nodes/tool_tavily/IInstance.pynodes/src/nodes/tool_tavily/README.mdnodes/src/nodes/tool_tavily/__init__.pynodes/src/nodes/tool_tavily/requirements.txtnodes/src/nodes/tool_tavily/services.jsonnodes/test/framework/pipeline.pynodes/test/test_tool_tavily.pypackages/ai/src/ai/common/utils/__init__.pypackages/ai/src/ai/common/utils/http_retry.pypackages/ai/src/ai/common/utils/url_utils.pypackages/ai/tests/ai/common/utils/test_http_retry.pypackages/ai/tests/ai/common/utils/test_url_utils.py
…, 5xx test - _shape_results: skip non-dict result items instead of crashing - validate_public_url: use ip.is_global (also blocks CGNAT 100.64.0.0/10) - add 5xx-retry test and CGNAT/non-dict regression tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings the Tavily web-search tool (
tool_tavily) intodevelop.The same change was merged to
stageas #1072 to make the Nebius event cut; this rebases the branch ontodevelopfor the long-term line. Same content (including the keyless-CI dynamic-test fix and the sharedai.common.utilshelpers —post_with_retry,validate_public_url), no conflicts.Part of #1047.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests