Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ OFFICEQA_DOC_MODE=parsed
FRONTIER_AGENT_DATASETS_DIR=

# Optional: web search and page fetching.
# Serper remains the default. Set WEB_SEARCH_PROVIDER=parallel to use the free,
# keyless Parallel Search MCP for web_search (up to 10 results per query);
# this does not change web_fetch.
WEB_SEARCH_PROVIDER=serper
# Support any Serper.dev-compatible endpoint (like litescrape.com, serpbase.dev,
# and others): set SERPER_BASE_URL and a provider-issued SERPER_API_KEY.
SERPER_API_KEY=
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ OPENAI_BASE_URL=https://your-openai-compatible-endpoint/v1
OPENAI_MODEL=your-model-name

# Optional web research tools
WEB_SEARCH_PROVIDER=serper
SERPER_API_KEY=
SERPER_BASE_URL=https://google.serper.dev
JINA_API_KEY=
Expand All @@ -154,6 +155,14 @@ JINA_API_KEY=
Support any Serper.dev-compatible endpoint (like litescrape.com, serpbase.dev,
and others) by setting `SERPER_BASE_URL` and a provider-issued `SERPER_API_KEY`.

Set `WEB_SEARCH_PROVIDER=parallel` to use the free, keyless
[Parallel Search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp) for
`web_search`. Serper remains the default, so existing keys and missing-key
errors keep their current behavior. Parallel MCP does not support the tool's
custom region, language, or time filters, or counts above 10 per query; choose
Serper when those are needed.
`web_fetch` continues to use its existing fetch provider.

Start the TUI:

```bash
Expand Down
4 changes: 3 additions & 1 deletion apodex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ cp .env.example .env
# OPENAI_API_KEY=...
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_MODEL=gpt-4o
# research mode additionally wants SERPER_API_KEY / JINA_API_KEY
# For keyless Parallel search set WEB_SEARCH_PROVIDER=parallel; it returns up
# to 10 results per query. Serper remains the default and uses SERPER_API_KEY.
# Page fetch uses JINA_API_KEY.

# Interactive TUI, Stateful ReAct, against another repository
frontier-agent --mode react --cwd /path/to/your/repo
Expand Down
13 changes: 12 additions & 1 deletion apodex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,18 @@ def inspect_runtime_config(
mode=active_mode,
env=env,
)
if "web_search" in tool_names and not _configured(env.get("SERPER_API_KEY")):
search_provider = (env.get("WEB_SEARCH_PROVIDER") or "serper").strip().lower()
if "web_search" in tool_names and search_provider not in {"serper", "parallel"}:
issues.append(RuntimeConfigIssue(
code="invalid_web_search_provider",
message="WEB_SEARCH_PROVIDER must be set to 'serper' or 'parallel'.",
env_var="WEB_SEARCH_PROVIDER",
))
if (
"web_search" in tool_names
and search_provider == "serper"
and not _configured(env.get("SERPER_API_KEY"))
):
issues.append(RuntimeConfigIssue(
code="missing_serper_api_key",
message=(
Expand Down
16 changes: 16 additions & 0 deletions apodex/tests/test_config_preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ def test_search_credentials_are_checked_when_the_profile_binds_the_web_tools():
assert with_search.ok
assert [issue.code for issue in with_search.warnings] == ["missing_jina_api_key"]

parallel = inspect_runtime_config(
cfg,
profile=_profile(tool_names=_WEB_TOOLS),
environ={"WEB_SEARCH_PROVIDER": "parallel"},
)
assert parallel.ok
assert [issue.code for issue in parallel.warnings] == ["missing_jina_api_key"]

invalid_provider = inspect_runtime_config(
cfg,
profile=_profile(tool_names=_WEB_TOOLS),
environ={"WEB_SEARCH_PROVIDER": "unknown"},
)
assert not invalid_provider.ok
assert "invalid_web_search_provider" in {issue.code for issue in invalid_provider.errors}

no_web_tools = inspect_runtime_config(
cfg, profile=_profile(tool_names=("bash", "read_file")), environ={},
)
Expand Down
9 changes: 8 additions & 1 deletion apodex/tests/test_userenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,11 @@ def test_resolution_never_carries_a_value(launch, user_file) -> None:


def test_forwarded_names_are_names_only_and_only_when_set(launch, user_file, monkeypatch) -> None:
_write(user_file, f"OPENAI_API_KEY={_SECRET}\nCUSTOM_PROVIDER_TOKEN=abc\n")
_write(
user_file,
f"OPENAI_API_KEY={_SECRET}\nCUSTOM_PROVIDER_TOKEN=abc\n"
"WEB_SEARCH_PROVIDER=parallel\n",
)
monkeypatch.setenv("SERPER_API_KEY", "serper-secret")
monkeypatch.delenv("JINA_API_KEY", raising=False)

Expand All @@ -331,19 +335,22 @@ def test_forwarded_names_are_names_only_and_only_when_set(launch, user_file, mon

assert "OPENAI_API_KEY" in names # from the file
assert "CUSTOM_PROVIDER_TOKEN" in names # file-defined, even if unlisted
assert "WEB_SEARCH_PROVIDER" in names # provider selected in the user file
assert "SERPER_API_KEY" in names # well-known and exported
assert "JINA_API_KEY" not in names # well-known but not set
assert _SECRET not in " ".join(names)


def test_empty_resolution_forwards_only_exported_well_known_names(monkeypatch) -> None:
monkeypatch.setenv("OPENAI_MODEL", "m")
monkeypatch.setenv("WEB_SEARCH_PROVIDER", "parallel")
monkeypatch.delenv("OPENAI_API_KEY", raising=False)

names = EnvResolution.empty().forwarded_names()

assert "OPENAI_MODEL" in names
assert "OPENAI_API_KEY" not in names
assert "WEB_SEARCH_PROVIDER" not in names


# ── the CLI: --cwd, --model, and where .env is looked up ──────────────────
Expand Down
4 changes: 4 additions & 0 deletions docs/install/global-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ chmod 600 "$config_dir/env"
```

Optional web tools take `SERPER_API_KEY` and `JINA_API_KEY` in the same file.
Add `WEB_SEARCH_PROVIDER=parallel` to this file to use the keyless Parallel
Search MCP for `web_search`; it supports up to 10 results per query. Serper
remains the default. Exporting the setting also works for native runs.
`web_fetch` keeps its existing provider.
`APODEX_ENV_FILE=/path/to/file` points the CLI at a different file.

Rules for the file:
Expand Down
Loading
Loading