feat: add nimble_web_search data source - #261
rapids-bot[bot] merged 16 commits into
Conversation
bb102fd to
1a00882
Compare
There was a problem hiding this comment.
💡 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".
Greptile SummaryThis PR adds
Confidence Score: 5/5This 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
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]
Reviews (6): Last reviewed commit: "docs(nimble_web_search): clarify focus=n..." | Re-trigger Greptile |
7cce6a6 to
c65efdf
Compare
|
@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:
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. |
|
feel free to re-open if it's a possibility to comply with the above requirements |
|
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 |
|
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 2. Account / project setup details 3. Expected quota / rate-limit constraints 4. A minimal query you can run repeatedly in CI AIQ_NIMBLE_LIVE_TESTS=1 NIMBLE_API_KEY=<key> \
uv run pytest sources/nimble_web_search/tests -m integration -vOne API call per run, bounded at 120 s, canned query 5. Clear expected success criteria
Reliability evidence Also in the prepared refresh
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. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 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. ChangesNimble Web Search
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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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>
7ce34fe to
29fba4a
Compare
|
Thanks @coderabbitai — went through each finding. Fixed:
Left as-is, with reasoning:
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. |
There was a problem hiding this comment.
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 winUnbounded exponential backoff via unconstrained
max_retries.
max_retriesonly has a lower bound (ge=1); a misconfigured large value combined withasyncio.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
📒 Files selected for processing (4)
deploy/Dockerfiledocs/source/customization/configuration-reference.mdsources/nimble_web_search/src/register.pysources/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.pysources/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_functiondecorator
Files:
sources/nimble_web_search/src/register.pysources/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.pysources/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 & PrivacyAPI key no longer written to
os.environ.This resolves the prior critical finding — the key is now passed straight to
NimbleSearchRetriever(api_key=...)viaretriever_kwargsinstead of mutating process-global env state.
190-217: 📐 Maintainability & Code QualityHelper functions now hoisted above the retry loop.
_has_resolvable_urland_renderare defined once beforefor attempt in range(...), addressing the prior nit about reallocating closures on every retry.
36-262: LGTM!
116-116: 🔒 Security & PrivacyNo change needed.
NimbleSearchRetrieveracceptsapi_key: SecretStr, so passingtool_config.api_keythrough directly is compatible.> Likely an incorrect or invalid review comment.sources/nimble_web_search/tests/test_nimble_register.py (1)
179-489: LGTM!
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>
|
Both fixed in bf210f0:
|
|
/ok to test bf210f0 |
KyleZheng1284
left a comment
There was a problem hiding this comment.
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.
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>
|
/ok to test da96765 |
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
docs/source/customization/configuration-reference.mdsources/nimble_web_search/README.mdsources/nimble_web_search/src/register.pysources/nimble_web_search/tests/test_nimble_register.pysources/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.pysources/nimble_web_search/src/register.pysources/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.pysources/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_functiondecorator
Files:
sources/nimble_web_search/tests/test_nimble_result_filtering.pysources/nimble_web_search/src/register.pysources/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.pysources/nimble_web_search/README.mdsources/nimble_web_search/src/register.pysources/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!
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
KyleZheng1284
left a comment
There was a problem hiding this comment.
LGTM - works e2e after getting a nimble api key, documentation is also pretty clear
|
/ok to test c989f00 |
|
@wildcard we can merge this, and followup with you about the nightly CI job + credentials via email. |
|
/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>
|
/ok to test 469d5d5 |
|
/merge |
376f1aa
into
NVIDIA-AI-Blueprints:release/2.2
## 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>
#### 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
|
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. |
Summary
sources/nimble_web_search, a NAT data source that wrapslangchain-nimble'sNimbleSearchRetriever, mirroring the existingexa_web_searchandtavily_web_searchpackages (typed config, stub-on-missing-key, retries, content truncation, XML-tagged output).lite/fast/deepsearch_depth, typed as aLiteralso invalid values fail at config-parse time.liteis the default — metadata-only, token-cheap, works on any account.fastis enterprise-tier and surfaces a clear 403 entitlement message on non-enterprise keys.focusmode (defaultgeneral, validatedLiteral),country/localeregional controls, and an optionalmax_content_lengthper-result cap.deploy/Dockerfile, andscripts/setup.shso 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_searchresolves in built images, where the--no-devsync would otherwise omit them.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-nimblepackage (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
NIMBLE_API_KEY=... # or set api_key: in the YAMLHow it works
A real
litequery withNIMBLE_API_KEYset, trimmed: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_searchand runnat run(swapadvanced_search: true→search_depth: deep).How this was tested
uv run pytest sources/nimble_web_search— 32 passed, credential-free (the SDK is mocked; no live network in CI).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 notests/__init__.py, so there's no pytest collection collision when sources are collected together.ruff checkandruff format --check— clean (whole repo).uv lock --check— no drift.detect-secrets,markdown-link-check(all README/docs links resolve),end-of-file-fixer,trailing-whitespace,check-added-large-files, anduv-lock— matching theAIQ CIlint job.nat info components --types functionlistsnimble_web_search(1.0.0) next toexa_web_searchandtavily_web_search, so_type: nimble_web_searchresolves in a workflow.langchain-nimble==3.0.0+nimble-python==0.18.0install and import cleanly in a fresh environment the same waydeploy/Dockerfileinstalls them, so_type: nimble_web_searchresolves in built images — not only in editable dev installs.NIMBLE_API_KEYacrossliteanddeep, plus the non-enterprisefastpath (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 /focusdefaults togeneraland reaches the SDK / non-defaultfocuspassthrough /include_answerabsent from config and kwargs /FunctionBaseConfiginheritance, the missing-key stub + warn-once, key-from-config env hydration, result rendering + description fallback, markup escaping of untrusted fields,search_depthandcountry/localepassthrough, 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
Deviations from the Exa source (all deliberate)
search_depth(3-value enum), a typedfocusmode (defaultgeneral), pluscountry/locale, mirroringlangchain-nimble's surface, where Exa exposessearch_type/full_text/highlights.focusis a workflow-config setting, not an agent parameter, so general research queries cannot drift tonews.descriptionwhenpage_contentis empty — Nimble'slitemode returns metadata only.include_answer(answer generation) is intentionally not exposed in this initial integration. It can be added in a follow-up.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.max_results1-100(matchinglangchain-nimble's ownge=1, le=100),max_retriesge=1,max_content_lengthge=1(useNoneto disable truncation). Invalid values fail at config-parse time, and content truncation hard-cuts safely for very small limits.Known limitations
max_resultsis a soft cap — Nimble may return up to N+2 documents for N. The provider returns them all; downstream consumers can slice.litemode returns emptypage_content; the provider renders thedescription(~150 chars, organic-result quality).fastpath is characterized via its 403 message; the enterprisefastbehavior itself isn't exercised here.Scope
In: the
nimble_web_searchprovider, 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
deploy/.env.examplecarries a commentedNIMBLE_API_KEY=placeholder only.SecretStrconfig field; never logged.Summary by CodeRabbit
<Document>formatted output with safer escaping and improved result URL handling.NIMBLE_API_KEY..env.exampletemplate for Nimble support.Signed-off-by: Kobi Kadosh kobi.kadosh@gmail.com