Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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: 2 additions & 2 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion deploy/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ AIQ_DEV_ENV=cli
NVIDIA_API_KEY=


# Web search (Required)
# Web search (Required — set at least one of TAVILY_API_KEY, EXA_API_KEY, or NIMBLE_API_KEY)
TAVILY_API_KEY=
# EXA_API_KEY=
# NIMBLE_API_KEY=

# Paper search (Optional — choose one provider)
# The provider is selected in your workflow config via the `provider` field
Expand Down
1 change: 1 addition & 0 deletions deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ RUN uv pip install --no-deps -e . \
&& uv pip install --no-deps -e ./sources/google_scholar_paper_search \
&& uv pip install --no-deps -e ./sources/tavily_web_search \
&& uv pip install --no-deps -e ./sources/exa_web_search \
&& uv pip install --no-deps -e ./sources/nimble_web_search \
Comment thread
wildcard marked this conversation as resolved.
Resolved
&& uv pip install --no-deps -e "./sources/knowledge_layer[all]" \
&& uv pip install --no-deps -e ./frontends/aiq_api \
&& uv pip install "psycopg[binary]>=3.0.0"
Expand Down
42 changes: 42 additions & 0 deletions docs/source/customization/configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,48 @@ functions:
- **`fast`** -- Optimized for low latency. Returns results quickly at the cost of recall and semantic depth. Use for interactive UIs, high-volume calls, or when the query is narrow and keyword-like.
- **`deep`** -- Optimized for thoroughness. Runs a more expensive semantic search with broader retrieval. Use for research-quality queries where completeness matters more than speed.

### `nimble_web_search`

Web search powered by the [Nimble API](https://nimbleway.com/) via `langchain-nimble`.

```yaml
functions:
web_search_tool:
_type: nimble_web_search
max_results: 5
max_content_length: 10000

deep_web_search_tool:
_type: nimble_web_search
max_results: 5
search_depth: deep
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `max_results` | `int` | `5` | Maximum number of search results to return. |
| `api_key` | `str` | `None` | Nimble API key. Falls back to `NIMBLE_API_KEY` environment variable. |
| `max_retries` | `int` | `3` | Number of retry attempts on search failure. |
| `search_depth` | `str` | `"lite"` | Nimble search depth. See options below. |
| `focus` | `str` | `"general"` | Nimble focus mode. See options below. |
| `country` | `str` | `"US"` | ISO 3166 country code passed to Nimble (e.g. `US`, `GB`, `FR`). |
| `locale` | `str` | `"en"` | Language/locale passed to Nimble (e.g. `en`, `fr`, `es`). |
| `max_content_length` | `int \| None` | `10000` | Max characters per result's page content. Set to `None` to disable truncation. |

**`search_depth` options:**

- **`lite`** (default) -- Returns metadata only (title, URL, description). Fastest, lowest token cost, safe default for general lookups.
- **`fast`** -- Returns rich content at low latency. **Enterprise-tier only**; non-enterprise accounts receive a 403 with a clear entitlement message.
- **`deep`** -- Returns full page content for each result. Use for research workflows that need the body text, not just URLs.

**`focus` options:**

- **`general`** (default) -- Broad web/research queries. The right choice for almost all agent use.
- **`news`** -- Restricts results to news-publisher sources, ordered by recency. There is no recency threshold -- older articles still appear; it changes the source mix, not the time window. (Recency windowing is a separate Nimble `time_range` capability that also works with `focus=general`; not exposed in this initial integration.)
- **`location`**, **`shopping`**, **`geo`**, **`social`** -- Domain-specific routing; set only when the tool targets that domain.

`focus` is a workflow-config setting, not an agent-chosen parameter -- the model only passes a query, so general research queries cannot silently switch to `news`. Answer generation (`include_answer`) is **not exposed** in this initial integration.

### `paper_search`

Academic paper search through Google Scholar using [Serper](https://serper.dev/),
Expand Down
1 change: 1 addition & 0 deletions docs/source/deployment/docker-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ The builder stage handles all compilation and package installation:
- `sources/google_scholar_paper_search` -- Google Scholar search
- `sources/tavily_web_search` -- Tavily web search
- `sources/exa_web_search` -- Exa web search
- `sources/nimble_web_search` -- Nimble web search
- `sources/knowledge_layer[all]` -- Knowledge layer with all extras
- `frontends/aiq_api` -- [FastAPI](https://fastapi.tiangolo.com/) frontend
- `psycopg[binary]>=3.0.0` -- PostgreSQL driver (psycopg v3, installed non-editable)
Expand Down
1 change: 1 addition & 0 deletions docs/source/deployment/docker-compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The sections below explain each group of variables.
| `NVIDIA_API_KEY` | Yes | NVIDIA API key for NIM model access. |
| `TAVILY_API_KEY` | Conditional | Web search provider key (required if using `tavily_web_search`). |
| `EXA_API_KEY` | Conditional | Web search provider key (required if using `exa_web_search`). |
| `NIMBLE_API_KEY` | Conditional | Web search provider key (required if using `nimble_web_search`). |
| `SERPER_API_KEY` | No | Google Scholar paper search key (optional). |

### API keys (optional)
Expand Down
1 change: 1 addition & 0 deletions docs/source/deployment/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ For complete examples with NGC-specific flags, refer to `deploy/helm/README.md`
| Key | Description |
|-----|-------------|
| `EXA_API_KEY` | Exa API key for web search |
| `NIMBLE_API_KEY` | Nimble API key for web search |
| `SERPER_API_KEY` | Serper API key for Google search |
| `JINA_API_KEY` | Jina API key |
| `WANDB_API_KEY` | Weights & Biases API key |
Expand Down
1 change: 1 addition & 0 deletions docs/source/extending/adding-a-data-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ async def search(self, query: str) -> str:
|---|---|---|---|
| Tavily Web Search | `tavily_web_search` | `sources/tavily_web_search` | General web search through Tavily API |
| Exa Web Search | `exa_web_search` | `sources/exa_web_search` | General web search through Exa API |
| Nimble Web Search | `nimble_web_search` | `sources/nimble_web_search` | General web search through Nimble API (`langchain-nimble`) |
| Google Scholar | `paper_search` | `sources/google_scholar_paper_search` | Academic papers through Serper/Google Scholar |
| Knowledge Layer | `knowledge_retrieval` | `sources/knowledge_layer` | Document retrieval through pluggable backends |

Expand Down
1 change: 1 addition & 0 deletions docs/source/extending/adding-a-tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ f'<Document href="{url}">\n<title>\n{title}\n</title>\n{content}\n</Document>'
|---|---|---|---|
| Tavily Web Search | `tavily_web_search` | `sources/tavily_web_search` | `TAVILY_API_KEY` |
| Exa Web Search | `exa_web_search` | `sources/exa_web_search` | `EXA_API_KEY` |
| Nimble Web Search | `nimble_web_search` | `sources/nimble_web_search` | `NIMBLE_API_KEY` |
| Google Scholar | `paper_search` | `sources/google_scholar_paper_search` | `SERPER_API_KEY` |
| Knowledge Layer | `knowledge_retrieval` | `sources/knowledge_layer` | (varies by backend) |

Expand Down
4 changes: 3 additions & 1 deletion docs/source/get-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The script performs the following steps:
3. Installs the core package with dev dependencies
4. Installs all frontends (CLI, debug console, API server)
5. Installs benchmark packages (freshqa, deepsearch_qa)
6. Installs Tavily, Exa, Google Scholar, and the LlamaIndex and Foundational RAG knowledge extras
6. Installs the data source plugins (Tavily, Exa, Nimble, Google Scholar) and the LlamaIndex and Foundational RAG knowledge extras
7. Sets up pre-commit hooks
8. Copies `deploy/.env.example` to `deploy/.env` if no `.env` file exists
9. Installs UI npm dependencies (if Node.js is available)
Expand Down Expand Up @@ -96,6 +96,7 @@ uv pip install -e ./frontends/aiq_api # Unified API server (includes debug)
# Data sources (pick what you need)
uv pip install -e ./sources/tavily_web_search
uv pip install -e ./sources/exa_web_search
uv pip install -e ./sources/nimble_web_search
uv pip install -e ./sources/duckduckgo_news_search
uv pip install -e ./sources/polymarket_prediction_market
uv pip install -e ./sources/google_scholar_paper_search
Expand Down Expand Up @@ -136,6 +137,7 @@ Then edit `deploy/.env` and fill in your keys.
|----------|----------|---------|
| `TAVILY_API_KEY` | [Tavily](https://tavily.com/) | Web search (Tavily provider) |
| `EXA_API_KEY` | [Exa](https://exa.ai/) | Web search (Exa provider) |
| `NIMBLE_API_KEY` | [Nimble](https://nimbleway.com/) | Web search (Nimble provider) |
| `SERPER_API_KEY` | [Serper](https://serper.dev/) | Google Scholar paper search with `provider: serper` (the default) |
| `SERPAPI_API_KEY` | [SerpAPI](https://serpapi.com/) | Google Scholar paper search with `provider: serpapi` |
| `SEARCHAPI_API_KEY` | [SearchAPI](https://www.searchapi.io/) | Google Scholar paper search with `provider: searchapi` |
Expand Down
2 changes: 2 additions & 0 deletions docs/source/get-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ NVIDIA_API_KEY=nvapi-...
TAVILY_API_KEY=tvly-...
# Or, to use Exa instead of Tavily for web search:
# EXA_API_KEY=...
# Or, to use Nimble instead of Tavily for web search:
# NIMBLE_API_KEY=...
```

## Step 2: Choose a Mode
Expand Down
1 change: 1 addition & 0 deletions docs/source/resources/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ prompted to follow the recorded order.

- **Tavily Web Search** — General web search (requires `TAVILY_API_KEY`)
- **Exa Web Search** — General web search via Exa (requires `EXA_API_KEY`)
- **Nimble Web Search** — General web search via Nimble (requires `NIMBLE_API_KEY`)
- **DuckDuckGo News Search** — Recent news search (no API key)
- **Polymarket Prediction Markets** — Events and market-implied probabilities (no API key)
- **Google Scholar Paper Search** — Academic search through Serper, SerpAPI, or SearchAPI (requires the selected provider's key)
Expand Down
2 changes: 2 additions & 0 deletions docs/source/resources/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Common issues and solutions for the AI-Q blueprint.
| `Gateway timeout (504)` | Model endpoint overloaded or unavailable | Retry, or switch to a different model in config |
| Tavily search returns empty | Invalid `TAVILY_API_KEY` | Verify key at [tavily.com](https://tavily.com) |
| Exa search returns empty or 401 | Invalid or missing `EXA_API_KEY` | Verify key at [exa.ai](https://exa.ai) |
| Nimble search returns empty or 401 | Invalid or missing `NIMBLE_API_KEY` | Verify key at [nimbleway.com](https://nimbleway.com) |
| Nimble search returns 403 with "enterprise" | `search_depth: fast` requires an Enterprise plan | Switch to `search_depth: lite` (default) or `deep`, or upgrade your Nimble plan |
| Serper search fails | Missing `SERPER_API_KEY` | Set key or remove `paper_search_tool` from config |

## Runtime Issues
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ dev = [
"google-scholar-paper-search",
"tavily-web-search",
"exa-web-search",
"nimble-web-search",
"duckduckgo-news-search",
"polymarket-prediction-market",
"knowledge-layer[all]",
Expand Down Expand Up @@ -250,6 +251,7 @@ aiq-agent = { workspace = true }
google-scholar-paper-search = { workspace = true }
tavily-web-search = { workspace = true }
exa-web-search = { workspace = true }
nimble-web-search = { workspace = true }
duckduckgo-news-search = { workspace = true }
polymarket-prediction-market = { workspace = true }
knowledge-layer = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ echo ""
echo "Installing data sources..."
"${UV_BIN}" pip install -e ./sources/tavily_web_search
"${UV_BIN}" pip install -e ./sources/exa_web_search
"${UV_BIN}" pip install -e ./sources/nimble_web_search
"${UV_BIN}" pip install -e ./sources/google_scholar_paper_search
"${UV_BIN}" pip install -e "./sources/knowledge_layer[llamaindex,foundational_rag]"
echo "Data Sources installed"
Expand Down
128 changes: 128 additions & 0 deletions sources/nimble_web_search/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Nimble Web Search

NAT-based [Nimble](https://nimbleway.com/) web search tool for agentic search workflows that need live web context. Requires a `NIMBLE_API_KEY` environment variable or `api_key` config.

## When to use

Use `nimble_web_search` when your AI-Q agent needs fresh web context from Nimble's real-time web intelligence infrastructure. The provider is designed for agentic search workflows that benefit from live web discovery, structured results, and reliable retrieval through Nimble's search layer.

Choose `nimble_web_search` when Tavily or Exa are not the right fit for your workflow, or when you want to standardize web-search retrieval through Nimble. The provider follows the same integration pattern as `exa_web_search` and `tavily_web_search`, making it straightforward to configure as an alternative search backend in AI-Q.

## Install

This package is installed automatically by `scripts/setup.sh` alongside the other data-source plugins. Manual install:

```bash
uv pip install -e ./sources/nimble_web_search
```

## Configure

Add a `nimble_web_search` function to your workflow YAML:

```yaml
functions:
web_search_tool:
_type: nimble_web_search
max_results: 5
search_depth: lite
country: US
locale: en
```

See [`docs/source/customization/configuration-reference.md`](../../docs/source/customization/configuration-reference.md) for the full parameter table.

## Environment

```bash
NIMBLE_API_KEY=...
```

You can alternatively set `api_key` directly in the YAML (as a string). Both paths use the standard `nat.data_models.function.FunctionBaseConfig` `SecretStr` handling — the key is not logged.

## Test

The package ships three test layers; the first two are credential-free and run everywhere.

```bash
# 1. Mocked unit tests + recorded-response replay (credential-free; the default run)
uv run pytest sources/nimble_web_search -v
# → 40 passed, 1 skipped (the live test, opt-in below)

# 2. Live integration test (opt-in; exactly one API call, bounded at 120 s)
AIQ_NIMBLE_LIVE_TESTS=1 NIMBLE_API_KEY=<key> \
uv run pytest sources/nimble_web_search/tests -m integration -v

# Lint
uv run ruff check sources/nimble_web_search
uv run ruff format --check sources/nimble_web_search
```

- **Mocked unit tests** (`test_nimble_register.py`) cover: config defaults / all fields / invalid `search_depth` rejection, missing-key stub + warn-once, key-from-config env hydration, successful render + description fallback, deep depth passthrough, query/content truncation, empty/error handling, retry-then-success, final-retry failure, 401, 403 enterprise-tier, non-default country/locale passthrough, and renderer behavior on titles containing special characters.
- **Recorded-response replay** (`test_nimble_recorded_replay.py`) replays real, redacted `NimbleSearchRetriever` responses ([`tests/fixtures/README.md`](tests/fixtures/README.md)) through the full provider pipeline — a deterministic test mode that needs no network and no key.
- **Live integration** (`test_nimble_live_integration.py`) runs the canned query `NVIDIA CUDA Toolkit documentation` once against the real API with the shipped defaults and asserts the structural output contract: a non-error response containing 1..`max_results` `<Document>` blocks, every block with an http(s) `href` and a title, every block XML-parseable, and at least one non-empty body. Assertions are structural — never content-exact — so ordinary result variation cannot flake the run. Suitable for CI: add `NIMBLE_API_KEY` as a repository secret and set `AIQ_NIMBLE_LIVE_TESTS=1` in the job.

## Verification

Beyond the mocked unit tests above, verify the provider is fully integrated with AI-Q by walking the [adding-a-data-source checklist](../../docs/source/extending/adding-a-data-source.md):

```bash
# 1. Mocked unit tests + recorded replay pass (CI-safe, no credentials)
uv run pytest sources/nimble_web_search -q
# → 40 passed, 1 skipped in <1s

# 2. NAT discovers the registered function
nat info components --types function | grep nimble_web_search
# → nimble_web_search 1.0.0 function

# 3. Lint, format, and dependency lock all clean
uv run ruff check sources/nimble_web_search
uv run ruff format --check sources/nimble_web_search
uv lock --check

# 4. Live smoke through any workflow that names `_type: nimble_web_search` (requires NIMBLE_API_KEY)
export NIMBLE_API_KEY=...
nat run --config_file <your-workflow.yml> --input "your test query"
```

Step 4 satisfies the checklist's "Installed and tested with `nat run`" item. Any of the existing AI-Q web search configs (`configs/config_cli_default.yml`, `configs/config_web_default_llamaindex.yml`, etc.) becomes a Nimble-backed test by swapping `_type: tavily_web_search` → `_type: nimble_web_search` and translating `advanced_search: true` → `search_depth: deep`.

## Native capabilities

The provider exposes the following Nimble-specific surface. Defaults are tuned for the common AI-Q research workflow (lite-mode SERP for a few results, US/English regional bias):

| Capability | Field | Default | Notes |
|---|---|---|---|
| Result count | `max_results` | `5` | Range `1-100` (Nimble's documented cap). Soft cap (Nimble may return up to N+2; see Known limitations). |
| Search depth | `search_depth` | `lite` | See the dedicated [Search depth](#search-depth) section below. |
| Search focus | `focus` | `general` | Nimble focus mode: `general` (default, broad web/research), `news` (news-publisher sources ordered by recency — not a recency filter; older articles still appear), or domain-specific `location` / `shopping` / `geo` / `social`. Leave `general` for normal research; the LLM never selects focus, so general queries can't drift to `news`. |
| Localization — country | `country` | `US` | Two-letter ISO 3166 country code (e.g. `FR`, `JP`, `GB`). Reaches the SDK constructor verbatim. |
| Localization — language | `locale` | `en` | ISO 639-1 language code (e.g. `fr`, `ja`). |
| Per-result content size | `max_content_length` | `10000` chars | Truncates each result's body to N chars (3-char ellipsis included). Minimum `1`; set to `null` to disable truncation; omit to use default. |
| Retries | `max_retries` | `3` | Exponential backoff on transient errors. Final failure surfaces a friendly per-status message (401, 403, generic). |
| Auth | `api_key` / `NIMBLE_API_KEY` | env or config | `pydantic.SecretStr`; never logged. Config-side `api_key` hydrates the env var so the underlying SDK can read it. |

Each `<Document>` block in the rendered output carries an `entity_type` (`"OrganicResult"` for SERP results). The `include_answer=True` capability — which would produce an `entity_type="answer"` block first — is intentionally **not exposed** in this initial integration. See [Known limitations](#known-limitations).

## Search depth

| Value | Behavior | Account requirement |
|---|---|---|
| `lite` (default) | Metadata only — URL, title, description. Token-efficient. | Any |
| `fast` | Enterprise tier. Lower latency, richer content. | **Enterprise account required.** Returns a 403 ToolException with `"search_depth='fast' is not enabled for this account. Contact sales for access."` on non-enterprise accounts. |
| `deep` | Higher token cost. May return short page-content snippets in addition to metadata. | Any (in this account; behavior may vary by tier) |

If you do not know your tier, leave `search_depth: lite` and let the description field carry the content.

## Known limitations

- `max_results` is a **soft cap**. The Nimble API may return up to N+2 documents when asked for N. The provider returns them all; AI-Q's downstream consumers can slice if they need a hard cap.
- `lite` mode returns `page_content == ""` per result. The provider falls back to `description` (~150 chars per result, organic-result quality).
- `include_answer` is **not exposed** in this initial integration. It can be added in a follow-up.

## Security

- API key handling follows the existing `EXA_API_KEY` / `TAVILY_API_KEY` pattern: env var or `SecretStr` config; never logged.
- Untrusted API fields (`url`, `title`, body) are HTML-escaped before being rendered into the `<Document>` markup, so a result can't break the block or inject into downstream parsers.
- Tests are mocked; no live network in CI.
- The optional live smoke is documented in the PR description and uses a redacted output pattern.
Loading
Loading