Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate findings affect Exa routing, quota fallback, and Tavily secret handling.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR makes Tavily the preferred search backend while routing specialized tasks to Exa and updating configuration, CLI safeguards, documentation, skills, and tests.
Changes:
- Adds task-aware Tavily/Exa routing and health checks.
- Adds Tavily configuration and removes Exa key requirements.
- Updates setup guides, search guidance, mcporter wording, and safety instructions.
File summaries
| File | Summary | Findings |
|---|---|---|
tests/test_search_backends.py |
Tests backend routing and health checks. | None. |
tests/test_p0_cli.py |
Tests positional Tavily-key rejection. | None. |
tests/test_config.py |
Updates Exa configuration expectations. | None. |
README.md |
Documents Tavily-first search. | Nit, 1 vote: Localized README copies retain stale Exa backend and credential guidance. |
docs/install.md |
Updates installation and command guidance. | Nit, 1 vote: --stdin is described as hidden despite echoing interactive terminal input. |
agent_reach/skill/SKILL.md |
Adds routing and safety guidance. | None. |
agent_reach/skill/SKILL_en.md |
Adds English routing and safety guidance. | None. |
agent_reach/skill/references/search.md |
Documents backend-specific workflows. | Nit, 1 vote: Clarify specialized-task precedence over active_backend.Nit, 1 vote: Clarify that --stdin is for piped or automated input. |
agent_reach/guides/setup-tavily.md |
Adds Tavily setup instructions. | Nit, 1 vote: Use the hidden interactive prompt and reserve --stdin for automation. |
agent_reach/guides/setup-exa.md |
Reframes Exa as fallback. | Nit, 3 votes: Remove the contradictory claim that Exa is free and requires no API key. |
agent_reach/config.py |
Adds Tavily configuration and external Exa handling. | None. |
agent_reach/cli.py |
Adds Tavily configuration and secret protections. | Moderate, 1 vote: Reject positional Tavily keys before the --stdin early return. |
agent_reach/channels/mcporter.py |
Generalizes mcporter compatibility wording. | Nit, 1 vote: Fix the ungrammatical version-compatibility sentence. |
agent_reach/channels/exa_search.py |
Implements Tavily/Exa routing and health checks. | Moderate, 2 votes: Treat exhausted numeric quotas as unavailable. Moderate, 1 vote: Honor EXA_SEARCH_BACKEND without a config object.Moderate, 1 vote: Reject whitespace-only backend overrides. |
Review details
Suppressed comments (9)
README.md:111
- The routing update is only reflected in the Chinese README:
docs/README_en.md:114,docs/README_ko.md:72, anddocs/README_ja.md:72still describe Exa as the auto-configured, no-key web-search backend. Users following those supported-language docs will receive stale backend and credential guidance; update the localized copies as part of this documentation change.
| 🔍 **全网搜索** | — | Tavily 研究搜索;Exa 语义搜索备选 | Tavily Key 可选;无 Key 自动走 Exa |
agent_reach/channels/exa_search.py:75
- The documented
EXA_SEARCH_BACKEND=exaoverride is ignored whenever this channel is used without a config object: the early return prevents_configured_backendfrom reading the environment, sobackend_for_task("general"),ordered_backends(), andcheck()still prefer Tavily. Read the environment after checking config values.
if not config:
return None
override = None
for key in ("search_backend", "web_search_backend", f"{self.name}_backend"):
override = config.get(key)
if override:
break
if not override:
agent_reach/channels/exa_search.py:84
- After stripping, a whitespace-only override becomes an empty
target; because every backend starts with an empty string, it is interpreted as Tavily instead of ignored. This can suppress the Exa route for tasks such aspaper, contrary to the unknown-override fallback behavior; reject an empty normalized target before prefix matching.
target = aliases.get(str(override).strip().casefold(), str(override).strip())
for backend in self.backends:
if backend.casefold() == target.casefold() or backend.casefold().startswith(
agent_reach/channels/mcporter.py:38
- The updated sentence reads “Otherwise mcporter supported mcporter versions...”, which is ungrammatical and makes the version qualifier unclear.
supported mcporter versions load the first home config
agent_reach/cli.py:1409
- The new rejection is below the
--stdinearly return, soagent-reach configure tavily-key tvly-secret --stdinstill accepts a positional secret and leaves it exposed in the process arguments. Apply the Tavily positional-value guard before entering the stdin branch as well.
if getattr(args, "key", None) == "tavily-key":
print(
"Refusing a positional Tavily key because shell history and "
"process listings may expose it; omit the value for a hidden "
"prompt or use --stdin.",
file=sys.stderr,
)
raise SystemExit(2)
agent_reach/guides/setup-tavily.md:9
- This setup command is shown as the normal interactive configuration path, but
--stdinreads from the terminal without suppressing echo. Use the hidden prompt here, and document--stdinonly for piped automation so the API key is not displayed while typing.
agent-reach configure tavily-key --stdin
agent_reach/skill/references/search.md:5
- This opening instruction says to follow
active_backendfor the search, which overrides the task-routing table below: with a valid Tavily key, a paper query will seeTavily via RESTand be sent to Tavily even though paper/academic tasks are supposed to proactively use Exa. Clarify thatactive_backendgoverns ordinary-search fallback and that specialized task labels take precedence.
网页搜索默认走 **Tavily**,Exa 作为 MCP 备选。先运行
`agent-reach doctor --json`;当 `exa_search.active_backend` 为
`Tavily via REST` 时用 Tavily,否则回退到 Exa。
agent_reach/skill/references/search.md:53
- This reference also presents
--stdinas hidden input, but the CLI only avoids argv/process-list exposure; terminal input is still echoed unless the value is piped. Use the hidden prompt for interactive setup and reserve this form for non-interactive stdin.
# 保存给 doctor 使用(隐藏输入,不要把 key 放进命令参数)
agent-reach configure tavily-key --stdin
docs/install.md:87
--stdinis not hidden when run interactively:_read_configure_valueusessys.stdin.read()without disabling terminal echo. Describing this as hidden input can lead users to paste a key into an echoing terminal; document the no-argument hidden prompt for interactive use and reserve--stdinfor piped/automation input.
Tavily is optional and needs an API key. Configure it through hidden input with
`agent-reach configure tavily-key --stdin`; without a key, Exa remains the active
zero-config fallback.
- Files reviewed: 14/14 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if response.status_code == 200: | ||
| try: | ||
| usage = response.json().get("key", {}) | ||
| except (TypeError, ValueError): | ||
| usage = {} | ||
| used = usage.get("usage") | ||
| limit = usage.get("limit") | ||
| suffix = f"(已用 {used}/{limit} credits)" if isinstance( | ||
| used, (int, float) | ||
| ) and isinstance(limit, (int, float)) else "" | ||
| return "ok", f"Tavily API 可用{suffix}" |
| Agent Reach 自己不要求 `exa_api_key`;MCP 服务端是否需要认证由当前 Exa/mcporter 配置决定。 | ||
| 需要配置 Tavily 时请先阅读 `guides/setup-tavily.md`。 | ||
|
|
||
| ## 功能说明 | ||
| Exa 是一个 AI 语义搜索引擎。通过 MCP 接入,**免费、无需 API Key**。配置后解锁: |
Summary
Verification
python -m pytest -q— 614 passed, 17 subtests.ruff check agent_reach tests— passed.uv build --wheel --offline— passed.Agent Reach remains an installer/doctor/configuration glue layer; agents continue to call upstream Tavily and Exa tools directly.