Skip to content

feat: add nimble_web_search data source - #261

Merged
rapids-bot[bot] merged 16 commits into
NVIDIA-AI-Blueprints:release/2.2from
wildcard:feat/nimble_web_search
Jul 15, 2026
Merged

rapids-bot[bot] merged 16 commits into
NVIDIA-AI-Blueprints:release/2.2from
wildcard:feat/nimble_web_search

Conversation

@wildcard

@wildcard wildcard commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds sources/nimble_web_search, a NAT data source that wraps langchain-nimble's NimbleSearchRetriever, mirroring the existing exa_web_search and tavily_web_search packages (typed config, stub-on-missing-key, retries, content truncation, XML-tagged output).
  • Exposes lite / fast / deep search_depth, typed as a Literal so invalid values fail at config-parse time. lite is the default — metadata-only, token-cheap, works on any account. fast is enterprise-tier and surfaces a clear 403 entitlement message on non-enterprise keys.
  • Adds a typed focus mode (default general, validated Literal), country / locale regional controls, and an optional max_content_length per-result cap.
  • Wires the plugin into the workspace, deploy/Dockerfile, and scripts/setup.sh so it installs in dev, Docker, and container builds. The Docker layer also installs Nimble's runtime deps (langchain-nimble, nimble-python, lockfile-pinned) so _type: nimble_web_search resolves in built images, where the --no-dev sync would otherwise omit them.
  • Documentation across the config reference, extending guides, installation, quick-start, deployment (docker-build, docker-compose, kubernetes), FAQ, and troubleshooting (with 401 and 403-enterprise rows).

DCO sign-off for the squash commit

Signed-off-by: Kobi Kadosh kobi.kadosh@gmail.com

Motivation

AI-Q ships Tavily- and Exa-backed web search today. Nimble provides web search and content extraction for AI agents; this adds it as a first-class alternative with the same ergonomics and config surface — handy for users who already have a Nimble subscription, prefer its regional coverage, or want to test across multiple search backends. The default provider is unchanged (Tavily stays the documented default).

It wraps the official langchain-nimble package (maintained by Nimble) rather than calling the HTTP API directly, so retry, auth, and response normalization come from the upstream integration — the same rationale as the Exa source (#181).

Configuration

functions:
  web_search_tool:
    _type: nimble_web_search
    max_results: 5
    search_depth: lite      # lite (default) | fast (enterprise) | deep
    focus: general          # general (default) | news | location | shopping | geo | social
    country: US
    locale: en
NIMBLE_API_KEY=...   # or set api_key: in the YAML

How it works

A real lite query with NIMBLE_API_KEY set, trimmed:

<Document href="https://docs.nvidia.com/aiq-blueprint/1.2.1/index.html">
<title>
NVIDIA AI-Q Blueprint
</title>
AI-Q combines intelligent query routing, multi-agent research pipelines, and
pluggable knowledge retrieval to deliver comprehensive, citation-backed answers.
</Document>

---

<Document href="https://build.nvidia.com/nvidia/aiq">
<title>
NVIDIA AI-Q Blueprint for intelligent agents
</title>
The NVIDIA AI-Q Blueprint enables developers to build fully customizable AI
agents that they own, inspect and control. Built on LangChain…
</Document>

Each result renders as an XML <Document> block — the same shape the Tavily and Exa sources produce — so existing AI-Q agents consume it with no changes. To try it, point any existing web-search config at _type: nimble_web_search and run nat run (swap advanced_search: truesearch_depth: deep).

How this was tested

  • uv run pytest sources/nimble_web_search32 passed, credential-free (the SDK is mocked; no live network in CI).
  • uv run pytest sources/exa_web_search sources/nimble_web_search46 passed, confirming the new package co-runs cleanly with a sibling source. The test module has a unique name and no tests/__init__.py, so there's no pytest collection collision when sources are collected together.
  • ruff check and ruff format --check — clean (whole repo). uv lock --check — no drift.
  • Repo pre-commit hooks pass on the changed files: detect-secrets, markdown-link-check (all README/docs links resolve), end-of-file-fixer, trailing-whitespace, check-added-large-files, and uv-lock — matching the AIQ CI lint job.
  • nat info components --types function lists nimble_web_search (1.0.0) next to exa_web_search and tavily_web_search, so _type: nimble_web_search resolves in a workflow.
  • Container runtime: langchain-nimble==3.0.0 + nimble-python==0.18.0 install and import cleanly in a fresh environment the same way deploy/Dockerfile installs them, so _type: nimble_web_search resolves in built images — not only in editable dev installs.
  • Live smoke with a real NIMBLE_API_KEY across lite and deep, plus the non-enterprise fast path (returns the friendly 403 entitlement message). Output is redacted; no key is logged by construction.

Coverage: config defaults / all fields / invalid-enum rejection (incl. focus) / out-of-range numeric fields rejected / focus defaults to general and reaches the SDK / non-default focus passthrough / include_answer absent from config and kwargs / FunctionBaseConfig inheritance, the missing-key stub + warn-once, key-from-config env hydration, result rendering + description fallback, markup escaping of untrusted fields, search_depth and country/locale passthrough, query and content truncation (incl. small-limit hard-cut), empty-result handling, retry-then-succeed, non-transient (401/403) errors short-circuiting without retry, final-retry failure, and the 401 / 403 branches.

How this was reviewed

  • Diffed against the merged Exa source to keep structure, retry loop, truncation, and output format at parity; the deviations below are deliberate.
  • Confirmed credential-free CI behavior and co-run safety with a sibling source.
  • Scanned the package for secrets and for hardcoded search-endpoint names — none.

Deviations from the Exa source (all deliberate)

  1. search_depth (3-value enum), a typed focus mode (default general), plus country / locale, mirroring langchain-nimble's surface, where Exa exposes search_type / full_text / highlights. focus is a workflow-config setting, not an agent parameter, so general research queries cannot drift to news.
  2. Falls back to the result's description when page_content is empty — Nimble's lite mode returns metadata only.
  3. A 403 branch that turns Nimble's enterprise-tier gating into a clear, actionable message. Exa has no tier gating, so no equivalent.
  4. include_answer (answer generation) is intentionally not exposed in this initial integration. It can be added in a follow-up.
  5. Untrusted result fields (url, title, body) are HTML-escaped before rendering into the <Document> markup, so a result can't break the block or inject into downstream parsers.
  6. Numeric config fields are bounded: max_results 1-100 (matching langchain-nimble's own ge=1, le=100), max_retries ge=1, max_content_length ge=1 (use None to disable truncation). Invalid values fail at config-parse time, and content truncation hard-cuts safely for very small limits.

Known limitations

  • max_results is a soft cap — Nimble may return up to N+2 documents for N. The provider returns them all; downstream consumers can slice.
  • lite mode returns empty page_content; the provider renders the description (~150 chars, organic-result quality).
  • The non-enterprise fast path is characterized via its 403 message; the enterprise fast behavior itself isn't exercised here.

Scope

In: the nimble_web_search provider, config/docs/deploy wiring, 32 unit tests, README, troubleshooting rows.

Not in (easy follow-ups): Nimble Extract / Map / Crawl / Agents; include_answer; framework integrations beyond AI-Q's data-source mechanism; any change to the default provider.

Security

  • No secrets committed — deploy/.env.example carries a commented NIMBLE_API_KEY= placeholder only.
  • Key read from env or a SecretStr config field; never logged.
  • Unit tests need no credentials; the live smoke uses an inline env var and redacted output.

Summary by CodeRabbit

  • New Features
    • Added Nimble Web Search as a supported web search option with configurable depth, focus, country/locale targeting, result limits, retries, and optional content truncation.
    • Provides structured <Document> formatted output with safer escaping and improved result URL handling.
  • Documentation
    • Added Nimble Web Search tool documentation and examples.
    • Updated installation, quick start, deployment key tables, FAQ, and troubleshooting for NIMBLE_API_KEY.
  • Deployment/Setup
    • Updated Docker/build, compose, Kubernetes, setup scripts, and the .env.example template for Nimble support.
  • Tests
    • Added credential-free recorded replay and opt-in live integration coverage.
  • Chores
    • Refreshed the secrets baseline metadata.

Signed-off-by: Kobi Kadosh kobi.kadosh@gmail.com

@copy-pr-bot

copy-pr-bot Bot commented Jun 1, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@wildcard
wildcard force-pushed the feat/nimble_web_search branch from bb102fd to 1a00882 Compare June 1, 2026 01:14
@wildcard
wildcard marked this pull request as ready for review June 1, 2026 02:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a0088290a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deploy/Dockerfile
@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds nimble_web_search as a first-class NAT data source, mirroring the existing exa_web_search and tavily_web_search packages in structure, retry logic, content truncation, and <Document> output format. The previously flagged issues — non-transient 401/403 errors burning through retries and negative-slice truncation for tiny max_content_length values — have been addressed in the fixup commit.

  • New plugin (sources/nimble_web_search): typed Pydantic config with Literal-validated search_depth/focus, HTML-escaped output rendering, exponential backoff that short-circuits immediately on 401/403/ValueError, and a graceful missing-key stub.
  • Workspace wiring: pyproject.toml workspace member, uv.lock update, scripts/setup.sh dev install, and deploy/Dockerfile editable install + explicit pinned langchain-nimble==3.0.0/nimble-python==0.18.0 to survive --no-dev syncs.
  • Tests: 32 credential-free unit tests covering config validation, stub behaviour, rendering, truncation edge cases, retry/short-circuit logic, and HTML-escape correctness.

Confidence Score: 5/5

This PR is safe to merge. It adds a self-contained new plugin without modifying any existing code paths, and the two previously identified issues have been fixed in the fixup commit.

The new plugin closely mirrors the structure of the existing exa_web_search and tavily_web_search sources. The retry loop correctly classifies errors before deciding whether to back off. Content truncation handles edge cases including sub-4-character limits. HTML escaping is applied correctly to all untrusted API fields. The 32 unit tests cover config validation, stub registration, rendering, truncation, retry logic, and short-circuit behaviour with autouse fixtures that prevent env-var leakage between tests. No existing code is modified.

No files require special attention.

Important Files Changed

Filename Overview
sources/nimble_web_search/src/register.py Core plugin: typed config, NimbleSearchRetriever wrapper, retry loop with immediate short-circuit for 401/403/ValueError, HTML-escaped rendering, and safe small-limit truncation. No issues found.
sources/nimble_web_search/tests/test_nimble_register.py 32 credential-free unit tests covering config validation, stub, rendering, truncation edge cases, retry/short-circuit, HTML escaping, and field passthrough. Tests are isolated with autouse env-clearing fixtures.
deploy/Dockerfile Adds editable install for nimble_web_search plus explicit pinned langchain-nimble==3.0.0 and nimble-python==0.18.0 to ensure runtime deps survive --no-dev syncs. Pattern is consistent with existing Dockerfile structure.
sources/nimble_web_search/pyproject.toml Standard package definition with NAT plugin entry point, correct setuptools layout, and langchain-nimble>=3.0.0,<4.0.0 semver range.
pyproject.toml Adds nimble-web-search to the workspace dev dependencies and workspace members list, consistent with exa-web-search and tavily-web-search entries.
scripts/setup.sh Single-line addition installing nimble_web_search in the dev setup script, grouped correctly with other data source installs.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[nimble_web_search called] --> B{NIMBLE_API_KEY set?}
    B -- No --> C[Log warn-once / Yield stub]
    C --> D[Return error message when invoked]
    B -- Yes --> E[Build NimbleSearchRetriever]
    E --> F[Yield live function]
    F --> G[Query received]
    G --> H{len > 400?}
    H -- Yes --> I[Truncate to 397 + ...]
    H -- No --> J[Pass through]
    I --> K[retriever.ainvoke]
    J --> K
    K --> L{Result?}
    L -- Empty docs --> M[ValueError: no results / Return immediately]
    L -- 401 --> N[Return friendly 401 message]
    L -- 403 --> O[Return friendly 403 message]
    L -- Other error --> P{Last attempt?}
    P -- Yes --> Q[Return error string]
    P -- No --> R[asyncio.sleep 2^attempt / Retry]
    R --> K
    L -- Success --> S[HTML-escape and truncate each doc]
    S --> T[Return XML Document blocks]
Loading

Reviews (6): Last reviewed commit: "docs(nimble_web_search): clarify focus=n..." | Re-trigger Greptile

Comment thread sources/nimble_web_search/src/register.py Outdated
Comment thread sources/nimble_web_search/src/register.py
Comment thread docs/source/customization/configuration-reference.md Outdated
@AjayThorve

Copy link
Copy Markdown
Member

@wildcard For the new search-provider PRs: if you want AIQ maintainers to own and keep these providers healthy after merge, please provide CI-usable API credentials or a reliable test-mode path.

Concretely, we need enough to add provider smoke/integration coverage in CI:

  • API key or service credential suitable for CI secrets
  • Any required account/project setup details
  • Expected quota/rate-limit constraints
  • A minimal query we can run repeatedly in CI
  • Clear expected success criteria for the provider

Without that, we can still review the code, but we should treat the provider as community-maintained/best-effort rather than something the core team can confidently maintain. Search providers are inherently drift-prone, so merging them without CI coverage creates long-term maintenance debt for the repo.

@AjayThorve

Copy link
Copy Markdown
Member

feel free to re-open if it's a possibility to comply with the above requirements

@AjayThorve AjayThorve closed this Jul 7, 2026
@wildcard

wildcard commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Hi @AjayThorve thank you for the detailed response. Let me loop in @ilchemla from Nimble to help us take it forward. I'm sure someone on the team can help

@wildcard

Copy link
Copy Markdown
Contributor Author

Hi @AjayThorve — following up on the close of #261 and #262: we understand the maintenance concern behind the policy, and we'd like to work with you and the AI-Q team within it. Everything below is ready on our side, point by point on the five requirements:

1. API key / service credential suitable for CI secrets
Done — Nimble has provisioned a dedicated service account + API key for AI-Q CI, funded for CI volumes and independent of any personal account. We're ready to hand it over; our preferred mechanism is a time-limited 1Password secure-share link: share the email address (or another private channel) you'd like it delivered to, and we'll create the link for that recipient. If account-level visibility would help your team, we can also add you to the Nimble account directly — just share the addresses to invite. And if a short call is easier for the handoff and setup, happy to do that too.

2. Account / project setup details
None needed beyond the key. The provider reads a single NIMBLE_API_KEY env var (or a SecretStr config field); there's no project/org/region setup on the Nimble side for search.

3. Expected quota / rate-limit constraints
Measured on the CI credential itself: 10 back-to-back calls complete in ~0.7–1.0 s each with no throttling, and a sustained run at one call per minute (30 calls) produced zero rate-limit responses. We also ran longer soaks of the same query on a separate Nimble account at 90–180 s cadences (~7.5 hours total) and saw zero rate-limit responses there too. The CI credential is provisioned with headroom well beyond one call per CI run; exact account limits will accompany the key handoff.

4. A minimal query you can run repeatedly in CI
We've prepared a refresh of this branch — ready to push as soon as the PR is reopened — that adds a key-gated live integration test that is exactly this:

AIQ_NIMBLE_LIVE_TESTS=1 NIMBLE_API_KEY=<key> \
    uv run pytest sources/nimble_web_search/tests -m integration -v

One API call per run, bounded at 120 s, canned query NVIDIA CUDA Toolkit documentation (a time-invariant entity query chosen to stay on-topic across time and regions), shipped defaults. It mirrors the opt-in gating idiom of tests/knowledge_layer_tests/test_opensearch_live.py, carries the integration marker, and without the flag + key it skips cleanly — so it changes nothing for existing CI. We also validated 10 alternate time-invariant queries against the same criteria on the CI account (all pass) and can include that list with the handoff if you ever want rotation.

5. Clear expected success criteria
The test asserts a structural output contract — never exact content, so ordinary result variation can't flake it:

  • a non-empty response that is not a provider Error: message and not the empty-result sentinel
  • between 1 and max_results <Document> blocks
  • every block has a non-empty http(s) href and a non-empty title
  • every block parses as XML (all interior fields are html-escaped)
  • at least one block has non-empty body content

Reliability evidence
We soaked exactly this test's code path and criteria before asking you to rely on it. An initial 100-run soak spread over ~5 hours passed 98/100 with 0 timeouts (p50 latency ~1.0 s) — the 2 exceptions were the upstream URL artifact described below, which is what motivated the robustness fix. The refreshed provider code was then re-certified: 100/100 runs over ~2.5 hours, 0 timeouts, p50 latency 0.97 s. A 10-call burst and non-default variants (regional settings, deep) were exercised separately and were stable throughout.

Also in the prepared refresh

  • A robustness fix the soak itself caught: in ~2% of calls the upstream API returned results whose URLs were empty or redirect tokens rather than resolvable links. The provider now drops such results and, if a response has nothing usable left, retries the call — with unit tests covering the mixed-results, all-unusable, and retries-exhausted cases — so that artifact can't produce a red CI run or an uncitable document.
  • A credential-free recorded-response replay layer: real SDK responses captured at the retriever boundary (redacted by construction — result fields only, no headers or auth material), replayed through the full provider pipeline. Deterministic, runs everywhere; tests/fixtures/README.md documents provenance and the refresh procedure.
  • README now documents the three test layers (mocked / replay / live) with commands, per-run cost, and the success criteria above; plus a small correction to the country field example ('UK' → ISO 3166 'GB').
  • On re-open we'll merge the latest develop in and re-verify the full set: 32 mocked + 4 replay tests credential-free, the live test with a real key, ruff, uv lock --check, and the pre-commit hooks.
  • One housekeeping note: the three existing commits predate our adding DCO sign-off; all new commits are signed. If the history's trailers matter for merging, we'll follow whatever process you prefer.

If it helps, we're also glad to contribute a scheduled nightly job wired to the secret — your call; the test works either way.

#262 (the optional search filters) stays closed until this one lands; we'll refresh it the same way afterwards.

One mechanical note: with the PR closed we can't reopen it or push the refresh from our side. Could you reopen #261, or let us know the next step you'd prefer? As soon as it's open we'll push the prepared refresh, and the credential handoff can run in parallel via the secure link above.

@AjayThorve AjayThorve reopened this Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a NAT-registered Nimble web search provider with validated configuration, retrying and escaped result rendering, packaging and deployment integration, documentation, recorded-response tests, and opt-in live integration coverage.

Changes

Nimble Web Search

Layer / File(s) Summary
Package configuration and NAT registration
sources/nimble_web_search/pyproject.toml, sources/nimble_web_search/src/..., pyproject.toml
Defines package metadata, workspace registration, public exports, NAT entry-point registration, and validated search configuration.
Search execution and result rendering
sources/nimble_web_search/src/register.py
Resolves API keys, constructs NimbleSearchRetriever, retries transient failures, filters unusable URLs, handles authorization errors, and renders escaped document blocks.
Shared test harness and recorded replay
sources/nimble_web_search/tests/conftest.py, tests/fixtures/*, tests/test_nimble_recorded_replay.py
Adds mocked retriever fixtures, output-contract assertions, redacted response fixtures, and deterministic replay tests.
Runtime and integration validation
sources/nimble_web_search/tests/test_nimble_register.py, test_nimble_result_filtering.py, test_nimble_live_integration.py
Covers configuration, formatting, truncation, retries, errors, URL filtering, SDK passthrough, escaping, and gated live execution.
Installation, deployment, and provider documentation
deploy/..., docs/source/..., scripts/setup.sh, sources/nimble_web_search/README.md, .secrets.baseline
Adds Nimble installation and build wiring, API-key references, configuration guidance, provider documentation, and the updated secret baseline location.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NAT
  participant nimble_web_search
  participant NimbleSearchRetriever
  NAT->>nimble_web_search: invoke configured search function
  nimble_web_search->>NimbleSearchRetriever: ainvoke question with search options
  NimbleSearchRetriever-->>nimble_web_search: return documents
  nimble_web_search-->>NAT: return escaped Document blocks or error
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title uses a valid Conventional Commits feat prefix and clearly summarizes the new nimble_web_search data source.
Description check ✅ Passed The description is detailed and covers overview, sign-off, validation, testing, review, and security, with only a few non-critical template sections missing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

wildcard and others added 9 commits July 14, 2026 00:45
Adds a Nimble web search integration mirroring exa_web_search and
tavily_web_search. The new sources/nimble_web_search package wraps
langchain-nimble's NimbleSearchRetriever, supports NIMBLE_API_KEY via env
or config, and exposes lite/fast/deep search depths (fast is an
enterprise-tier feature that surfaces a clear error on non-enterprise
keys; lite is the default). It is wired into the workspace, deploy/Dockerfile
(with --no-deps to preserve the frozen lockfile), and scripts/setup.sh.
Includes unit tests and documentation updates across the configuration
reference, extending guides, installation, deployment, faq, and
troubleshooting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
…and clarify tool description

Expose `focus` as a typed Literal config (general, news, location,
shopping, geo, social) defaulting to "general", and pass it explicitly
to NimbleSearchRetriever. The upstream SDK field is an unvalidated str
defaulting to general; the Literal adds parse-time validation and makes
the general default explicit. focus is a workflow-config setting, not an
agent-chosen parameter, so general research queries cannot silently
switch to news.

Tighten the tool description the agent sees to state it is a
general-purpose web/research search. include_answer remains unexposed in
this initial integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
…eshold)

`news` restricts results to news-publisher sources ordered by recency; it
does not apply a recency threshold, so 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`. Reword the config reference, README, and field
description to remove the misleading "current events" phrasing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
Catch the branch up to develop after re-open. Resolves workspace-member
and lockfile adjacency plus doc-row additions for the nimble_web_search
package; regenerates uv.lock and .secrets.baseline.

Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
… helpers

Adds tests/__init__.py, aligning the package layout with the other data
sources, and a conftest.py exposing a fake langchain_nimble module fixture
plus a structural output-contract assertion shared by the recorded-replay
and live integration tests, so both layers certify identical success
criteria. Document blocks are extracted as whole <Document> spans rather
than by splitting on the joiner sequence, which deep-mode markdown content
can legitimately contain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
Replays real NimbleSearchRetriever responses, captured at the SDK boundary
and redacted by construction (result fields only; no headers or auth
material), through the registered function end-to-end. Covers the lite
metadata-only response (description fallback), a deep full-content
response, and a synthetic case where a document body contains the
block-joiner sequence. Deterministic, credential-free test mode;
fixtures/README.md documents provenance and the refresh procedure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
One opt-in live API call (AIQ_NIMBLE_LIVE_TESTS=1 plus NIMBLE_API_KEY),
bounded at 120 seconds, running the canned query "NVIDIA CUDA Toolkit
documentation" with shipped defaults and asserting the structural output
contract. Mirrors the opt-in gating idiom of the OpenSearch live tests.
The test adds no retries of its own -- the provider's retry loop is the
code under test, so a failure means three consecutive attempts failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
README now describes the three test layers (mocked, recorded replay,
key-gated live) with exact commands, the canned CI query, per-run cost
(one API call, at most 120 seconds), and the structural success criteria.
Refreshes stale test counts. Also corrects the country field example to
an ISO 3166 code: the API accepts 'GB', not 'UK' (the latter returns a
400 client error).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
…none remain

The live API intermittently returns results whose url is empty or a
server-relative redirect token (e.g. "/goto?url=...") instead of a
resolvable link -- observed in about 2% of calls during a 100-run soak of
the shipped defaults. Rendering those results hands agents citations that
cannot be followed. The provider now drops such results from a response
and, when nothing usable remains, treats the response as transient so the
retry loop re-queries. A truly empty result list keeps its existing
non-retried behavior. Includes unit tests for the mixed, all-unresolvable
(retry and exhaustion), and empty-response paths, and fixes the remaining
non-ISO country example in the README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
@wildcard
wildcard force-pushed the feat/nimble_web_search branch from 7ce34fe to 29fba4a Compare July 14, 2026 07:46
@wildcard

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — went through each finding.

Fixed:

  • API key written to os.environ (register.py) — 6ac6327. The key is now passed to NimbleSearchRetriever's api_key field directly; when it isn't set in config, the SDK resolves NIMBLE_API_KEY from the environment itself. No more process-global mutation. The config test was updated to assert the key reaches the retriever and that os.environ isn't mutated — which also resolves the env-leak note on that test.
  • Helpers defined inside the retry loop6ac6327. _has_resolvable_url and _render are hoisted above the loop.
  • UK country example28a538d. Now GB (ISO 3166 alpha-2), matching the provider docs and the implementation.
  • Dockerfile deps resolved outside the lock28a538d. The explicit langchain-nimble==3.0.0 nimble-python==0.18.0 install was redundant and pinned nimble-python below the locked version. uv sync --frozen already installs both from the lockfile via the dev group — same path as the Exa/Tavily langchain deps, which have no explicit install line — so I removed it.

Left as-is, with reasoning:

  • Hoist test imports (PLC0415) — the repo ignores PLC0415 globally in pyproject.toml ([tool.ruff.lint] extend-ignore), so ruff check passes and the lazy imports match the repo's configuration. Happy to change if the intent is to enforce it here.
  • Reuse the shared conftest fixtures in test_nimble_register.py — that module's fixtures are intentionally self-contained; the shared conftest.py fixtures back the newer test modules. Can consolidate if you'd prefer a single source.

On docstring coverage: the public entry points are documented and I've added docstrings to the hoisted helpers; the remaining gap is test methods and small nested helpers. Let me know if you'd like broader coverage.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sources/nimble_web_search/src/register.py (1)

49-49: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unbounded exponential backoff via unconstrained max_retries.

max_retries only has a lower bound (ge=1); a misconfigured large value combined with asyncio.sleep(2**attempt) (line 255) can stall the tool call for an extremely long time (e.g. max_retries=20 → a ~6-day final sleep) with no cap on the sleep duration. Add an upper bound on the field and/or cap the backoff.

♻️ Proposed fix
-    max_retries: int = Field(default=3, ge=1, description="Maximum number of retries for the search request")
+    max_retries: int = Field(default=3, ge=1, le=10, description="Maximum number of retries for the search request")
-                await asyncio.sleep(2**attempt)
+                await asyncio.sleep(min(2**attempt, 30))

Also applies to: 217-217, 253-255

🤖 Prompt for 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.

In `@sources/nimble_web_search/src/register.py` at line 49, Bound the retry
behavior by adding a reasonable upper limit to max_retries in the configuration
model and cap the exponential delay in the retry loop using asyncio.sleep.
Update the field definition and the retry/backoff logic around the existing
max_retries and attempt handling so misconfiguration cannot produce unbounded
waits.
🤖 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 `@docs/source/customization/configuration-reference.md`:
- Line 231: Update the max_content_length entry in the configuration reference
table to declare its type as nullable, using int | None or int/null, while
preserving the existing default and truncation description.

---

Outside diff comments:
In `@sources/nimble_web_search/src/register.py`:
- Line 49: Bound the retry behavior by adding a reasonable upper limit to
max_retries in the configuration model and cap the exponential delay in the
retry loop using asyncio.sleep. Update the field definition and the
retry/backoff logic around the existing max_retries and attempt handling so
misconfiguration cannot produce unbounded waits.
🪄 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: Enterprise

Run ID: d8716793-97af-4aba-8954-80819bc9e35c

📥 Commits

Reviewing files that changed from the base of the PR and between 29fba4a and 28a538d.

📒 Files selected for processing (4)
  • deploy/Dockerfile
  • docs/source/customization/configuration-reference.md
  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
💤 Files with no reviewable changes (1)
  • deploy/Dockerfile
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
docs/source/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Update the docs under docs/source/ when behavior, configuration, or workflows change

Files:

  • docs/source/customization/configuration-reference.md
{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}: Review documentation for command accuracy, branch-name consistency, current CI and copy-pr-bot behavior, public
vs internal boundary clarity, stale examples, and links that no longer match the repository layout.

Files:

  • docs/source/customization/configuration-reference.md
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
sources/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

New tools and data sources must be NAT functions registered with @register_function decorator

Files:

  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
{src/aiq_agent/knowledge/**,sources/**}

⚙️ CodeRabbit configuration file

{src/aiq_agent/knowledge/**,sources/**}: Review data-source and knowledge-layer changes for optional dependency boundaries, external API error handling,
retry/rate-limit behavior, deterministic tests, and registration consistency. New source packages should include
package metadata, plugin registration when applicable, and source-level tests.

Files:

  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • sources/nimble_web_search/tests/test_nimble_register.py
🔇 Additional comments (5)
sources/nimble_web_search/src/register.py (4)

113-162: 🔒 Security & Privacy

API key no longer written to os.environ.

This resolves the prior critical finding — the key is now passed straight to NimbleSearchRetriever(api_key=...) via retriever_kwargs instead of mutating process-global env state.


190-217: 📐 Maintainability & Code Quality

Helper functions now hoisted above the retry loop.

_has_resolvable_url and _render are defined once before for attempt in range(...), addressing the prior nit about reallocating closures on every retry.


36-262: LGTM!


116-116: 🔒 Security & Privacy

No change needed. NimbleSearchRetriever accepts api_key: SecretStr, so passing tool_config.api_key through directly is compatible.

			> Likely an incorrect or invalid review comment.
sources/nimble_web_search/tests/test_nimble_register.py (1)

179-489: LGTM!

Comment thread docs/source/customization/configuration-reference.md Outdated
Addresses review feedback on unbounded retry behavior. Add an upper bound
of 10 to max_retries (ge=1, le=10) and cap the exponential backoff at 30s
(min(2**attempt, 30)) so a misconfigured max_retries cannot produce an
unbounded wait. Add a config-validation case for the new upper bound.
Also mark max_content_length as int | None in the configuration reference,
matching the field (None disables truncation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>
@wildcard

Copy link
Copy Markdown
Contributor Author

Both fixed in bf210f0:

  • Unbounded retriesmax_retries is now bounded (ge=1, le=10) and the exponential backoff is capped at 30s (min(2**attempt, 30)), so a misconfigured value can't produce an unbounded wait. Added a config-validation case for the new upper bound.
  • max_content_length type — the configuration-reference entry now shows int | None, matching the field (None disables truncation).

@AjayThorve

Copy link
Copy Markdown
Member

/ok to test bf210f0

KyleZheng1284
KyleZheng1284 previously approved these changes Jul 14, 2026

@KyleZheng1284 KyleZheng1284 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated the Nimble integration locally, including package tests, combined Exa/Nimble tests, Ruff lint and formatting, Sphinx docs, lockfile consistency, NAT discovery, and live Nimble lite/deep search. All current CI checks pass.

@KyleZheng1284
KyleZheng1284 dismissed their stale review July 14, 2026 19:55

Approval dismissed because maintainer testing is still in progress.

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
@KyleZheng1284

Copy link
Copy Markdown
Contributor

/ok to test da96765

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@sources/nimble_web_search/README.md`:
- Around line 63-65: Update the testing documentation in README.md to reconcile
the live integration guidance with the later “no live network in CI” statement:
explain that default CI remains credential-free and network-free, while an
explicitly configured opt-in live CI job with NIMBLE_API_KEY and
AIQ_NIMBLE_LIVE_TESTS=1 may access the network. Keep the existing descriptions
of test coverage and opt-in behavior intact.
🪄 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: Enterprise

Run ID: 64855450-150b-44db-8aa0-d7bc5c5aa5ed

📥 Commits

Reviewing files that changed from the base of the PR and between bf210f0 and da96765.

📒 Files selected for processing (5)
  • docs/source/customization/configuration-reference.md
  • sources/nimble_web_search/README.md
  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
  • sources/nimble_web_search/tests/test_nimble_result_filtering.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
docs/source/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Update the docs under docs/source/ when behavior, configuration, or workflows change

Files:

  • docs/source/customization/configuration-reference.md
{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,SECURITY.md,CODE-OF-CONDUCT.md}: Review documentation for command accuracy, branch-name consistency, current CI and copy-pr-bot behavior, public
vs internal boundary clarity, stale examples, and links that no longer match the repository layout.

Files:

  • docs/source/customization/configuration-reference.md
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • sources/nimble_web_search/tests/test_nimble_result_filtering.py
  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • sources/nimble_web_search/tests/test_nimble_result_filtering.py
  • sources/nimble_web_search/tests/test_nimble_register.py
sources/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

New tools and data sources must be NAT functions registered with @register_function decorator

Files:

  • sources/nimble_web_search/tests/test_nimble_result_filtering.py
  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
{src/aiq_agent/knowledge/**,sources/**}

⚙️ CodeRabbit configuration file

{src/aiq_agent/knowledge/**,sources/**}: Review data-source and knowledge-layer changes for optional dependency boundaries, external API error handling,
retry/rate-limit behavior, deterministic tests, and registration consistency. New source packages should include
package metadata, plugin registration when applicable, and source-level tests.

Files:

  • sources/nimble_web_search/tests/test_nimble_result_filtering.py
  • sources/nimble_web_search/README.md
  • sources/nimble_web_search/src/register.py
  • sources/nimble_web_search/tests/test_nimble_register.py
🔇 Additional comments (6)
sources/nimble_web_search/src/register.py (2)

22-22: LGTM!


202-206: LGTM!

docs/source/customization/configuration-reference.md (1)

205-245: LGTM!

sources/nimble_web_search/README.md (1)

1-18: LGTM!

Also applies to: 19-44, 45-62, 67-91, 92-108, 109-124, 125-130

sources/nimble_web_search/tests/test_nimble_register.py (1)

233-235: LGTM!

Also applies to: 260-260, 276-276, 288-290, 304-306, 321-323, 433-433, 446-446, 459-459

sources/nimble_web_search/tests/test_nimble_result_filtering.py (1)

64-82: LGTM!

Comment thread sources/nimble_web_search/README.md Outdated
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>

@KyleZheng1284 KyleZheng1284 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - works e2e after getting a nimble api key, documentation is also pretty clear

@KyleZheng1284

Copy link
Copy Markdown
Contributor

/ok to test c989f00

@AjayThorve

Copy link
Copy Markdown
Member

@wildcard we can merge this, and followup with you about the nightly CI job + credentials via email.

@AjayThorve
AjayThorve changed the base branch from develop to release/2.2 July 14, 2026 21:36
@AjayThorve AjayThorve added this to the v2.2 milestone Jul 14, 2026
@AjayThorve

Copy link
Copy Markdown
Member

/merge

Resolve provider registration conflicts, refresh the MCP lock for the combined workspace, and remove the Nimble tests package marker so repo-wide pytest collection remains collision-free.

Signed-off-by: Ajay Thorve <athorve@nvidia.com>
@AjayThorve
AjayThorve requested a review from a team July 15, 2026 20:48
@AjayThorve

Copy link
Copy Markdown
Member

/ok to test 469d5d5

@AjayThorve

Copy link
Copy Markdown
Member

/merge

@rapids-bot
rapids-bot Bot merged commit 376f1aa into NVIDIA-AI-Blueprints:release/2.2 Jul 15, 2026
11 checks passed
AjayThorve added a commit that referenced this pull request Jul 15, 2026
## Summary

- Adds `sources/nimble_web_search`, a NAT data source that wraps `langchain-nimble`'s `NimbleSearchRetriever`, mirroring the existing `exa_web_search` and `tavily_web_search` packages (typed config, stub-on-missing-key, retries, content truncation, XML-tagged output).
- Exposes `lite` / `fast` / `deep` `search_depth`, typed as a `Literal` so invalid values fail at config-parse time. `lite` is the default — metadata-only, token-cheap, works on any account. `fast` is enterprise-tier and surfaces a clear 403 entitlement message on non-enterprise keys.
- Adds a typed `focus` mode (default `general`, validated `Literal`), `country` / `locale` regional controls, and an optional `max_content_length` per-result cap.
- Wires the plugin into the workspace, `deploy/Dockerfile`, and `scripts/setup.sh` so it installs in dev, Docker, and container builds. The Docker layer also installs Nimble's runtime deps (`langchain-nimble`, `nimble-python`, lockfile-pinned) so `_type: nimble_web_search` resolves in built images, where the `--no-dev` sync would otherwise omit them.
- Documentation across the config reference, extending guides, installation, quick-start, deployment (docker-build, docker-compose, kubernetes), FAQ, and troubleshooting (with 401 and 403-enterprise rows).


#### DCO sign-off for the squash commit

Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>

## Motivation

AI-Q ships Tavily- and Exa-backed web search today. [Nimble](https://nimbleway.com/) provides web search and content extraction for AI agents; this adds it as a first-class alternative with the same ergonomics and config surface — handy for users who already have a Nimble subscription, prefer its regional coverage, or want to test across multiple search backends. The default provider is unchanged (Tavily stays the documented default).

It wraps the official `langchain-nimble` package (maintained by Nimble) rather than calling the HTTP API directly, so retry, auth, and response normalization come from the upstream integration — the same rationale as the Exa source (#181).

## Configuration

```yaml
functions:
  web_search_tool:
    _type: nimble_web_search
    max_results: 5
    search_depth: lite      # lite (default) | fast (enterprise) | deep
    focus: general          # general (default) | news | location | shopping | geo | social
    country: US
    locale: en
```

```bash
NIMBLE_API_KEY=...   # or set api_key: in the YAML
```

## How it works

A real `lite` query with `NIMBLE_API_KEY` set, trimmed:

```text
<Document href="https://docs.nvidia.com/aiq-blueprint/1.2.1/index.html">
<title>
NVIDIA AI-Q Blueprint
</title>
AI-Q combines intelligent query routing, multi-agent research pipelines, and
pluggable knowledge retrieval to deliver comprehensive, citation-backed answers.
</Document>

---

<Document href="https://build.nvidia.com/nvidia/aiq">
<title>
NVIDIA AI-Q Blueprint for intelligent agents
</title>
The NVIDIA AI-Q Blueprint enables developers to build fully customizable AI
agents that they own, inspect and control. Built on LangChain…
</Document>
```

Each result renders as an XML `<Document>` block — the same shape the Tavily and Exa sources produce — so existing AI-Q agents consume it with no changes. To try it, point any existing web-search config at `_type: nimble_web_search` and run `nat run` (swap `advanced_search: true` → `search_depth: deep`).

## How this was tested

- [x] `uv run pytest sources/nimble_web_search` — **32 passed**, credential-free (the SDK is mocked; no live network in CI).
- [x] `uv run pytest sources/exa_web_search sources/nimble_web_search` — **46 passed**, confirming the new package co-runs cleanly with a sibling source. The test module has a unique name and no `tests/__init__.py`, so there's no pytest collection collision when sources are collected together.
- [x] `ruff check` and `ruff format --check` — clean (whole repo). `uv lock --check` — no drift.
- [x] Repo pre-commit hooks pass on the changed files: `detect-secrets`, `markdown-link-check` (all README/docs links resolve), `end-of-file-fixer`, `trailing-whitespace`, `check-added-large-files`, and `uv-lock` — matching the `AIQ CI` lint job.
- [x] `nat info components --types function` lists `nimble_web_search` (1.0.0) next to `exa_web_search` and `tavily_web_search`, so `_type: nimble_web_search` resolves in a workflow.
- [x] Container runtime: `langchain-nimble==3.0.0` + `nimble-python==0.18.0` install and import cleanly in a fresh environment the same way `deploy/Dockerfile` installs them, so `_type: nimble_web_search` resolves in built images — not only in editable dev installs.
- [x] Live smoke with a real `NIMBLE_API_KEY` across `lite` and `deep`, plus the non-enterprise `fast` path (returns the friendly 403 entitlement message). Output is redacted; no key is logged by construction.

Coverage: config defaults / all fields / invalid-enum rejection (incl. `focus`) / out-of-range numeric fields rejected / `focus` defaults to `general` and reaches the SDK / non-default `focus` passthrough / `include_answer` absent from config and kwargs / `FunctionBaseConfig` inheritance, the missing-key stub + warn-once, key-from-config env hydration, result rendering + description fallback, markup escaping of untrusted fields, `search_depth` and `country`/`locale` passthrough, query and content truncation (incl. small-limit hard-cut), empty-result handling, retry-then-succeed, non-transient (401/403) errors short-circuiting without retry, final-retry failure, and the 401 / 403 branches.

## How this was reviewed

- Diffed against the merged Exa source to keep structure, retry loop, truncation, and output format at parity; the deviations below are deliberate.
- Confirmed credential-free CI behavior and co-run safety with a sibling source.
- Scanned the package for secrets and for hardcoded search-endpoint names — none.

## Deviations from the Exa source (all deliberate)

1. `search_depth` (3-value enum), a typed `focus` mode (default `general`), plus `country` / `locale`, mirroring `langchain-nimble`'s surface, where Exa exposes `search_type` / `full_text` / `highlights`. `focus` is a workflow-config setting, not an agent parameter, so general research queries cannot drift to `news`.
2. Falls back to the result's `description` when `page_content` is empty — Nimble's `lite` mode returns metadata only.
3. A 403 branch that turns Nimble's enterprise-tier gating into a clear, actionable message. Exa has no tier gating, so no equivalent.
4. `include_answer` (answer generation) is intentionally not exposed in this initial integration. It can be added in a follow-up.
5. Untrusted result fields (`url`, `title`, body) are HTML-escaped before rendering into the `<Document>` markup, so a result can't break the block or inject into downstream parsers.
6. Numeric config fields are bounded: `max_results` `1-100` (matching `langchain-nimble`'s own `ge=1, le=100`), `max_retries` `ge=1`, `max_content_length` `ge=1` (use `None` to disable truncation). Invalid values fail at config-parse time, and content truncation hard-cuts safely for very small limits.

## Known limitations

- `max_results` is a soft cap — Nimble may return up to N+2 documents for N. The provider returns them all; downstream consumers can slice.
- `lite` mode returns empty `page_content`; the provider renders the `description` (~150 chars, organic-result quality).
- The non-enterprise `fast` path is characterized via its 403 message; the enterprise `fast` behavior itself isn't exercised here.

## Scope

**In:** the `nimble_web_search` provider, config/docs/deploy wiring, 32 unit tests, README, troubleshooting rows.

**Not in (easy follow-ups):** Nimble Extract / Map / Crawl / Agents; `include_answer`; framework integrations beyond AI-Q's data-source mechanism; any change to the default provider.

## Security

- No secrets committed — `deploy/.env.example` carries a commented `NIMBLE_API_KEY=` placeholder only.
- Key read from env or a `SecretStr` config field; never logged.
- Unit tests need no credentials; the live smoke uses an inline env var and redacted output.



## Summary by CodeRabbit

* **New Features**
  * Added Nimble Web Search as a supported web search option with configurable depth, focus, country/locale targeting, result limits, retries, and optional content truncation.
  * Provides structured `<Document>` formatted output with safer escaping and improved result URL handling.
* **Documentation**
  * Added Nimble Web Search tool documentation and examples.
  * Updated installation, quick start, deployment key tables, FAQ, and troubleshooting for `NIMBLE_API_KEY`.
* **Deployment/Setup**
  * Updated Docker/build, compose, Kubernetes, setup scripts, and the `.env.example` template for Nimble support.
* **Tests**
  * Added credential-free recorded replay and opt-in live integration coverage.
* **Chores**
  * Refreshed the secrets baseline metadata.




Signed-off-by: Kobi Kadosh <kobi.kadosh@gmail.com>

Authors:
  - Kobi Kadosh (https://github.com/wildcard)
  - Kyle Zheng (https://github.com/KyleZheng1284)
  - Ajay Thorve (https://github.com/AjayThorve)

Approvers:
  - Kyle Zheng (https://github.com/KyleZheng1284)
  - Ajay Thorve (https://github.com/AjayThorve)

URL: #261

Co-authored-by: Kobi Kadosh <kobi.kadosh@gmail.com>
rapids-bot Bot pushed a commit that referenced this pull request Jul 15, 2026
#### Overview

Fix the AI-Q 2.2 documentation publication contract and refresh release-facing documentation against the current `release/2.2` branch.

The version selector had three independent sources of drift:

- `conf.py` still rendered `version_match = 1.2.1` after the site was deployed under `2.2.0-rc1`.
- `project.json` and the Sphinx release value had to be updated separately.
- `../versions1.json` resolved to the publisher-managed root index on top-level pages but to the copied per-version file on nested pages. That copied file contained only one version and used the invalid `ai-blueprint` site slug.

This change makes `docs/source/project.json` the single version authority, sets it to the exact `v2.2.0-rc1` artifact version, points every page at the canonical publisher-managed selector index, and removes the duplicated per-build `versions1.json`.

The release-facing README, changelog, FAQ, troubleshooting, and navigation now cover Azure AI Search, You.com, Nimble, the standalone public MCP server, the workflow-configuration maintainer skill, and all eleven checked-in workflow profiles. The Nimble links use its canonical documentation, with narrowly scoped exclusions in both link checkers because Nimble's certificate chain is not accepted by Python/OpenSSL or the Node link checker.

Developer impact: advancing the docs version now requires one edit to `project.json`; Sphinx and the NVIDIA Docs publisher consume the same value.

#### DCO sign-off for the squash commit

Signed-off-by: Ajay Thorve <AjayThorve@users.noreply.github.com>

#### Validation

```text
$ uv run ruff check docs/source/conf.py
All checks passed!

$ uv run ruff format --check docs/source/conf.py
1 file already formatted

$ uv run --extra docs sphinx-build -M html docs/source docs/build -W --keep-going -n
build succeeded.

$ uv run --extra docs sphinx-build -M linkcheck docs/source docs/build -W --keep-going -n
build succeeded.

$ uv run python <metadata, config-inventory, and generated-HTML assertions>
docs metadata, config inventory, and generated switcher contract: PASS

$ uv run pre-commit run --files <complete PR diff>
All applicable hooks passed, including Ruff, detect-secrets, and Markdown Link Check.
```

The live publisher index at `https://docs.nvidia.com/aiq-blueprint/versions1.json` currently reports `2.2.0-rc1`, `2.1.0`, `2.0.0`, and `1.2.1`. Generated top-level and nested pages both use that canonical index and match `2.2.0-rc1`.

- [x] I ran the relevant local checks or explained why they are not applicable.
- [x] I added or updated validation for behavior changes.
- [x] I updated documentation for user-facing or contributor-facing changes.
- [x] I confirmed this PR does not include secrets, credentials, or internal-only data.
- [x] I certify this contribution under the Developer Certificate of Origin (DCO) and signed my commits with `git commit -s` or an equivalent sign-off.
- [x] I replaced the DCO sign-off placeholder with my GitHub commit identity and kept the required angle brackets around the email address.

#### Where should reviewers start?

Start with `docs/source/conf.py`, `docs/source/project.json`, and the removal of `docs/source/versions1.json`; together they define the publication and selector invariant. Then review the config inventory in `README.md` and `docs/source/customization/configuration-reference.md`, followed by `docs/source/customization/you-com.md` and the Nimble link-check handling.

#### Related Issues

- Relates to #261, #308, #316, #319, and #334.



## Summary by CodeRabbit

* **New Features**
  * Added documentation for You.com tools, configurable Nimble web search modes, Azure AI Search knowledge retrieval (API key and managed identity), and standalone MCP server setup.
  * Updated setup guidance with new Nimble/You.com data-source options.

* **Documentation**
  * Expanded sources/integrations, authentication, and workflow configuration details.
  * Improved docs release metadata and versioning/switcher behavior; added additional configuration profiles and references.
  * Refreshed troubleshooting and FAQ entries for You.com and Azure AI Search.

* **Chores**
  * Refreshed the secrets baseline metadata.
  * Improved markdown link-check ignore rules for specific Nimble URLs.

Authors:
  - Ajay Thorve (https://github.com/AjayThorve)

Approvers:
  - Eddy (https://github.com/eddy-nassif)
  - Kyle Zheng (https://github.com/KyleZheng1284)

URL: #345
@wildcard

wildcard commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @AjayThorve appreciated the clear requirements and the smooth collaboration on this one, from the policy through the quick reopen and merge.

Email works great for the credential handoff. You can reach me at kobi.kadosh@gmail.com — drop me a note with the address you'd like the credential delivered to, and I'll reply with a time-limited 1Password secure-share link for the CI key together with the exact account limits. The offers stand too: a short setup call if useful, and adding your team to the Nimble account.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants