Skip to content

refactor(library): migrate HTTP request helpers - #2211

Open
Pouyanpi wants to merge 6 commits into
developfrom
pouyanpi/rail-library-stack-12-http-request-migrations
Open

refactor(library): migrate HTTP request helpers#2211
Pouyanpi wants to merge 6 commits into
developfrom
pouyanpi/rail-library-stack-12-http-request-migrations

Conversation

@Pouyanpi

@Pouyanpi Pouyanpi commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Description

Migrate shared library request helpers to the canonical HTTP client and request
helper. The affected integrations are ActiveFence, AlignScore, GLiNER,
jailbreak detection, Polygraf, and Private AI.

Each helper accepts an injected HTTPClient and sends requests through
http_call. When no client is injected, http_call creates and closes the
fallback client itself. The action modules no longer repeat an
async with resolver around every outbound request.

Existing response parsing, request payloads, timeouts, configuration errors, and
provider-specific exception behavior remain local to each integration. Tests
replace transport-specific mocks with the neutral recording client where
appropriate.

Impact

  • Six shared request paths stop constructing or managing transport-specific
    clients directly.
  • Runtime injection and standalone fallback execution use the same helper.
  • Response bodies remain readable after the managed client context closes.
  • Provider-specific behavior remains unchanged and is covered by the existing
    integration tests.

Stack

Layer Branch Base PR
HTTP foundation pouyanpi/rail-library-stack-9-http-contract develop #2209
HTTP instrumentation pouyanpi/rail-library-stack-10-http-instrumentation Stack 9 #2219
Request lifecycle pouyanpi/rail-library-stack-11-http-observability Stack 9 #2210
Request migrations pouyanpi/rail-library-stack-12-http-request-migrations Stack 11 #2211
Action and specialized migrations pouyanpi/rail-library-stack-13-http-action-migrations Stack 12 #2212

Related Issue(s)

  • Fixes #
  • Internal tracking: NGUARD-861
  • Issue assignee: @

Verification

  • make test TEST="tests/http tests/test_activefence_rail.py tests/test_fact_checking.py tests/test_gliner.py tests/test_jailbreak_request.py tests/test_polygraf.py" — 144 passed.
  • Pre-commit passed across the complete cascaded HTTP stack.
  • All tests were local and made no live provider calls.

AI Assistance

  • No AI tools were used.
  • AI tools were used; a human reviewed and can explain every change (tool: Codex).

Codex assisted with implementation analysis, validation, stack restructuring,
and this draft. Check the disclosure box after human review.

Checklist

  • I've read the CONTRIBUTING guidelines.
  • This PR links to a triaged issue assigned to me.
  • My PR title follows the project commit convention.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • I've noted any verification beyond CI and any checks I couldn't run.
  • I did not update generated changelog files manually.
  • I addressed all CodeRabbit, Greptile, and other review comments, or replied with why no change is needed.
  • @mentions of the person or team responsible for reviewing proposed changes.

Summary by CodeRabbit

  • New Features
    • Added a shared asynchronous HTTP client with configurable timeouts, redirects, response handling, and lifecycle management.
    • Added configurable retries with backoff, retry-after support, and transport-error handling.
    • Added safer URL handling and sanitized error messages.
  • Improvements
    • External moderation, fact-checking, PII detection, and jailbreak detection services can now reuse a shared HTTP client.
    • Standardized HTTP errors and JSON response decoding across integrations.

@github-actions github-actions Bot added size: L status: needs triage New issues that have not yet been reviewed or categorized. labels Jul 23, 2026
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

@Pouyanpi
Pouyanpi force-pushed the pouyanpi/rail-library-stack-12-http-request-migrations branch from a0fdf05 to d5852ff Compare July 23, 2026 14:42
@Pouyanpi
Pouyanpi force-pushed the pouyanpi/rail-library-stack-12-http-request-migrations branch 2 times, most recently from 3fa2fdb to 68b0e7e Compare July 23, 2026 16:28
@Pouyanpi Pouyanpi removed the status: needs triage New issues that have not yet been reviewed or categorized. label Jul 23, 2026
@Pouyanpi Pouyanpi self-assigned this Jul 23, 2026
@Pouyanpi Pouyanpi added this to the v0.24.0 milestone Jul 23, 2026
@Pouyanpi
Pouyanpi marked this pull request as ready for review July 23, 2026 16:37
@Pouyanpi Pouyanpi added the status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). label Jul 23, 2026
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates six integration libraries (ActiveFence, AlignScore, GLiNER, jailbreak detection, Polygraf, and Private AI) from aiohttp to the project's new transport-neutral http_call / HTTPClient abstraction. Each action now accepts an optional http_client parameter that is forwarded to the underlying request helper; when none is provided, http_call creates and disposes of a fallback httpx client itself.

  • Actions and request modules (activefence, align_score, gliner, jailbreak_detection, polygraf, privateai): aiohttp.ClientSession context managers replaced with await http_call(...), error types unified to HTTPClientError / HTTPTimeoutError, and HTTPResponseDecodeError replaces aiohttp.ContentTypeError.
  • Tests: aioresponses mocks replaced with RecordingHTTPClient + chat.app.register_action_param(\"http_client\", ...) in most suites; test_fact_checking.py switches to httpx_mock and does not inject a RecordingHTTPClient, making it the only migrated suite that remains transport-coupled.
  • Stale artifacts: All session=None mock stubs in test_polygraf.py are updated to http_client=None, and the no-longer-needed _FakeResponse / _FakeSession / _FakeRaisingSession helper classes are removed.

Confidence Score: 5/5

Safe to merge — all six integrations correctly delegate to the shared http_call helper, previous stale-stub and dead-code issues are fully resolved, and error contracts are preserved.

The migration is mechanical and consistent: aiohttp context managers are replaced with http_call, error types are unified, and test doubles are updated throughout. The two style observations do not change runtime behaviour.

Files Needing Attention: tests/test_fact_checking.py — the only migrated suite that uses httpx_mock instead of RecordingHTTPClient, keeping it coupled to the httpx transport.

Important Files Changed

Filename Overview
nemoguardrails/library/activefence/actions.py Replaces aiohttp session with http_call; http_client param forwarded directly.
nemoguardrails/library/factchecking/align_score/actions.py Adds http_client param and forwards via conditional dict pattern; otherwise unchanged behavior.
nemoguardrails/library/factchecking/align_score/request.py aiohttp replaced with http_call; non-200 handling and JSON decode error handling preserved correctly.
nemoguardrails/library/gliner/request.py aiohttp replaced with http_call; HTTPResponseDecodeError replaces aiohttp.ContentTypeError; NIM unwrapping logic unchanged.
nemoguardrails/library/jailbreak_detection/request.py All three request helpers migrated; exception order correct (Timeout before ClientError).
nemoguardrails/library/polygraf/request.py _send_polygraf_request helper removed; timeout/connection error handling ported to HTTPTimeoutError/HTTPClientError.
tests/test_fact_checking.py aioresponses replaced with httpx_mock rather than RecordingHTTPClient; tests remain transport-coupled.
tests/test_gliner.py aioresponses replaced with RecordingHTTPClient throughout; all paths covered.
tests/test_polygraf.py All session=None stubs updated to http_client=None; dead helper classes removed; new detect_pii forwarding test added.

Reviews (6): Last reviewed commit: "refactor(polygraf): pass optional HTTP c..." | Re-trigger Greptile

@Pouyanpi
Pouyanpi force-pushed the pouyanpi/rail-library-stack-12-http-request-migrations branch from 68b0e7e to 9a89c02 Compare July 24, 2026 10:00
@Pouyanpi
Pouyanpi force-pushed the pouyanpi/rail-library-stack-12-http-request-migrations branch from 9a89c02 to cbc27d8 Compare July 24, 2026 10:14
@Pouyanpi Pouyanpi changed the title refactor(library): migrate HTTP request helpers refactor(library): migrate library integrations to the shared HTTP client Jul 24, 2026
@Pouyanpi Pouyanpi changed the title refactor(library): migrate library integrations to the shared HTTP client refactor(library): migrate HTTP request helpers Jul 24, 2026
@Pouyanpi
Pouyanpi force-pushed the pouyanpi/rail-library-stack-12-http-request-migrations branch from 72d49cb to 3faf664 Compare July 24, 2026 15:53
Base automatically changed from pouyanpi/rail-library-stack-11-http-observability to develop July 29, 2026 08:01
@Pouyanpi
Pouyanpi requested a review from tgasser-nv July 29, 2026 08:03
Pouyanpi added 6 commits July 29, 2026 10:05
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
Signed-off-by: Pouyanpi <13303554+Pouyanpi@users.noreply.github.com>
@Pouyanpi
Pouyanpi force-pushed the pouyanpi/rail-library-stack-12-http-request-migrations branch from 3faf664 to 2de84ec Compare July 29, 2026 08:06
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6adc9544-61d3-4340-997e-d42fa2967e00

📥 Commits

Reviewing files that changed from the base of the PR and between ccc8255 and 3faf664.

📒 Files selected for processing (32)
  • nemoguardrails/http/__init__.py
  • nemoguardrails/http/_url.py
  • nemoguardrails/http/client.py
  • nemoguardrails/http/errors.py
  • nemoguardrails/http/request.py
  • nemoguardrails/http/retry.py
  • nemoguardrails/http/runtime.py
  • nemoguardrails/http/testing.py
  • nemoguardrails/http/transport.py
  • nemoguardrails/http/types.py
  • nemoguardrails/library/activefence/actions.py
  • nemoguardrails/library/factchecking/align_score/actions.py
  • nemoguardrails/library/factchecking/align_score/request.py
  • nemoguardrails/library/gliner/actions.py
  • nemoguardrails/library/gliner/request.py
  • nemoguardrails/library/jailbreak_detection/actions.py
  • nemoguardrails/library/jailbreak_detection/request.py
  • nemoguardrails/library/polygraf/actions.py
  • nemoguardrails/library/polygraf/request.py
  • nemoguardrails/library/privateai/actions.py
  • nemoguardrails/library/privateai/request.py
  • tests/http/test_contract.py
  • tests/http/test_request.py
  • tests/http/test_retry.py
  • tests/http/test_runtime.py
  • tests/http/test_transport.py
  • tests/http/test_url.py
  • tests/test_activefence_rail.py
  • tests/test_fact_checking.py
  • tests/test_gliner.py
  • tests/test_jailbreak_request.py
  • tests/test_polygraf.py

📝 Walkthrough

Walkthrough

Introduces a transport-neutral asynchronous HTTP client stack with HTTPX transport, retries, lifecycle management, response/error types, testing utilities, and shared-client injection across provider integrations.

Changes

Shared HTTP stack

Layer / File(s) Summary
HTTP contracts and request lifecycle
nemoguardrails/http/{_url,client,errors,types,request}.py, nemoguardrails/http/__init__.py
Defines request/response types, transport protocols, sanitized URLs, HTTP errors, lifecycle handling, and public exports.
HTTPX transport and retry runtime
nemoguardrails/http/{transport,retry,runtime,testing}.py
Adds the HTTPX adapter, retry policy and wrapper, client factory, and recording test client.
Provider request and action integration
nemoguardrails/library/{activefence,factchecking,gliner,jailbreak_detection,polygraf,privateai}/*
Replaces provider-specific aiohttp calls with http_call and forwards optional shared clients through action and request APIs.
HTTP contract and runtime validation
tests/http/*
Tests response decoding, error handling, client ownership, transport behavior, retries, runtime composition, and URL sanitization.
Provider integration test migration
tests/test_{activefence_rail,fact_checking,gliner,jailbreak_request,polygraf}.py
Updates provider tests to use RecordingHTTPClient or HTTPX mock responses and validates shared-client forwarding.

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

Sequence Diagram(s)

sequenceDiagram
  participant Action
  participant ProviderRequest
  participant http_call
  participant RetryingHTTPClient
  participant HttpxHTTPClient
  Action->>ProviderRequest: pass optional HTTPClient
  ProviderRequest->>http_call: submit outbound request
  http_call->>RetryingHTTPClient: request(...)
  RetryingHTTPClient->>HttpxHTTPClient: request(...)
  HttpxHTTPClient-->>RetryingHTTPClient: HTTPResponse or transport error
  RetryingHTTPClient-->>http_call: final response or error
  http_call-->>ProviderRequest: response
  ProviderRequest-->>Action: provider result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: migrating library HTTP request helpers.
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.
Test Results For Major Changes ✅ Passed PR description includes a Verification section with concrete test results (144 passed) and pre-commit success for the major HTTP refactor.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch pouyanpi/rail-library-stack-12-http-request-migrations
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pouyanpi/rail-library-stack-12-http-request-migrations

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
nemoguardrails/library/activefence/actions.py (1)

1-1: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard response.json() against malformed provider responses, matching the privateai/request.py pattern.

Both of these migrated call sites parse the HTTP response body with .json()/response_json[...] without catching HTTPResponseDecodeError or a missing key, so a non-JSON or unexpectedly-shaped response from ActiveFence/AlignScore would raise an unhandled exception instead of the controlled ValueError/None-fallback behavior these providers otherwise rely on. nemoguardrails/library/privateai/request.py (lines 70-86, this same migration) already establishes the right pattern: wrap .json() in try/except HTTPResponseDecodeError and re-raise from error.

  • nemoguardrails/library/activefence/actions.py#L120-141: wrap response.json() in try/except HTTPResponseDecodeError, re-raising as ValueError(...) from error, and use .get("violations", []) instead of ["violations"] for the key lookup.
  • nemoguardrails/library/factchecking/align_score/request.py#L38-56: wrap http_response.json() in try/except HTTPResponseDecodeError, logging/returning None on failure (consistent with the existing "return None on failure" contract of alignscore_request).
🛡️ Proposed fix for activefence/actions.py
+from nemoguardrails.http import HTTPClient, HTTPResponseDecodeError, http_call
     if response.status_code != 200:
         raise ValueError(f"ActiveFence call failed with status code {response.status_code}.\nDetails: {response.text}")
-    response_json = response.json()
+    try:
+        response_json = response.json()
+    except HTTPResponseDecodeError as error:
+        raise ValueError(
+            f"Failed to parse ActiveFence response as JSON. Status: {response.status_code}, Content: {response.text}"
+        ) from error
     log.info(json.dumps(response_json, indent=True))
-    violations = response_json["violations"]
+    violations = response_json.get("violations", [])
🛡️ Proposed fix for align_score/request.py
     if http_response.status_code != 200:
         log.error(f"AlignScore API request failed with status {http_response.status_code}")
         return None

-    result = http_response.json()
+    try:
+        result = http_response.json()
+    except HTTPResponseDecodeError:
+        log.error(f"AlignScore API returned a non-JSON response (status {http_response.status_code})")
+        return None
🤖 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 `@nemoguardrails/library/activefence/actions.py` at line 1, Guard JSON parsing
in the ActiveFence response handling and AlignScore’s alignscore_request: catch
HTTPResponseDecodeError from response.json()/http_response.json(). In the
ActiveFence path, re-raise ValueError with the original error as its cause and
read violations via .get("violations", []). In alignscore_request, log the
decode failure and return None, preserving its existing failure contract.
🧹 Nitpick comments (3)
nemoguardrails/library/polygraf/request.py (1)

49-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale docstring wording: "sessions".

The aiohttp session concept is gone; reword to reference the caller-provided or fallback HTTP client.

🤖 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 `@nemoguardrails/library/polygraf/request.py` around lines 49 - 50, Update the
timeout parameter docstring to replace the stale “sessions” reference with the
caller-provided or fallback HTTP client, while preserving the description that
the timeout applies to both cases.
nemoguardrails/library/gliner/actions.py (1)

133-144: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Conditional request_kwargs is unnecessary indirection.

gliner_request already defaults http_client=None, so passing it unconditionally is equivalent and clearer (same for both actions).

♻️ Proposed simplification
-    request_kwargs = {"http_client": http_client} if http_client is not None else {}
     gliner_response = await gliner_request(
         text=text,
         ...
         model=gliner_config.model,
-        **request_kwargs,
+        http_client=http_client,
     )

Also applies to: 189-200

🤖 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 `@nemoguardrails/library/gliner/actions.py` around lines 133 - 144, Remove the
conditional request_kwargs indirection in both GLiNER action request paths and
pass http_client directly to gliner_request, relying on its existing default of
None. Update the corresponding calls near the second action as well, without
changing any other request arguments or behavior.
tests/test_gliner.py (1)

741-743: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated _http_response test helper with divergent kwargs. Both suites hand-roll the same HTTPResponse builder, one using body and the other text, which will keep drifting as more providers migrate.

  • tests/test_gliner.py#L741-L743: replace with a single shared helper (e.g., exported next to RecordingHTTPClient).
  • tests/test_polygraf.py#L199-L201: import the shared helper instead of redefining it with a text kwarg.
🤖 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 `@tests/test_gliner.py` around lines 741 - 743, The test suites duplicate the
HTTPResponse builder with incompatible keyword arguments. In
tests/test_gliner.py lines 741-743, move or expose one shared _http_response
helper next to RecordingHTTPClient; in tests/test_polygraf.py lines 199-201,
remove the local helper and import the shared one, updating callers to use its
body-compatible interface.
🤖 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 `@nemoguardrails/http/retry.py`:
- Around line 168-204: Update the retry loop around HTTPClient.request to
enforce timeout across the entire operation: establish a monotonic deadline,
pass the remaining duration to each request attempt, and cap both backoff and
retry-after sleeps to the remaining time. When the deadline expires, raise
HTTPTimeoutError rather than starting another attempt or sleeping beyond the
timeout, while preserving existing retry-count and response behavior.
- Around line 110-114: Update the non-clamped delay check in the retry-after
handling logic to accept a zero delay as valid, returning it directly instead of
falling back to jittered backoff; preserve the existing maximum-bound validation
and None behavior for invalid delays.

In `@nemoguardrails/http/transport.py`:
- Around line 55-64: Update the HTTP request flow in the transport class to use
httpx’s per-request timeout instead of wrapping httpx.request() with
asyncio.wait_for. Preserve timeout=None for externally supplied clients, while
applying the configured timeout to requests made by internally owned clients;
remove the repeated deadline-cancellation path.

In `@nemoguardrails/library/factchecking/align_score/request.py`:
- Line 54: Replace the broad exception handler in the request flow with only the
specific exception types expected from the dict-key lookup, matching the
established handling in privateai/request.py. Preserve the existing fallback
behavior while allowing unrelated errors such as caller-induced AttributeError
to propagate.

In `@nemoguardrails/library/jailbreak_detection/request.py`:
- Around line 70-75: Normalize timeout and transport/JSON error handling across
the migrated helpers: in nemoguardrails/library/jailbreak_detection/request.py
lines 70-75, update the jailbreak API request and response.json flow to pass an
explicit timeout and return None for HTTPTimeoutError/HTTPClientError and
invalid JSON, matching jailbreak_nim_request; in
nemoguardrails/library/gliner/request.py lines 110-119, add a timeout parameter
as used by polygraf_request, pass it to the HTTP call, and normalize transport
errors to ValueError per the docstring; in
nemoguardrails/library/jailbreak_detection/request.py lines 95-100, apply the
same explicit timeout and error normalization as the heuristics helper.
- Line 153: Update the error log in the jailbreak detection request handling to
avoid explicit str(e) conversion and use the conversion flag supported by the
logging format, preserving the existing error message and exception details.

---

Outside diff comments:
In `@nemoguardrails/library/activefence/actions.py`:
- Line 1: Guard JSON parsing in the ActiveFence response handling and
AlignScore’s alignscore_request: catch HTTPResponseDecodeError from
response.json()/http_response.json(). In the ActiveFence path, re-raise
ValueError with the original error as its cause and read violations via
.get("violations", []). In alignscore_request, log the decode failure and return
None, preserving its existing failure contract.

---

Nitpick comments:
In `@nemoguardrails/library/gliner/actions.py`:
- Around line 133-144: Remove the conditional request_kwargs indirection in both
GLiNER action request paths and pass http_client directly to gliner_request,
relying on its existing default of None. Update the corresponding calls near the
second action as well, without changing any other request arguments or behavior.

In `@nemoguardrails/library/polygraf/request.py`:
- Around line 49-50: Update the timeout parameter docstring to replace the stale
“sessions” reference with the caller-provided or fallback HTTP client, while
preserving the description that the timeout applies to both cases.

In `@tests/test_gliner.py`:
- Around line 741-743: The test suites duplicate the HTTPResponse builder with
incompatible keyword arguments. In tests/test_gliner.py lines 741-743, move or
expose one shared _http_response helper next to RecordingHTTPClient; in
tests/test_polygraf.py lines 199-201, remove the local helper and import the
shared one, updating callers to use its body-compatible interface.
🪄 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: CHILL

Plan: Enterprise

Run ID: 6adc9544-61d3-4340-997e-d42fa2967e00

📥 Commits

Reviewing files that changed from the base of the PR and between ccc8255 and 3faf664.

📒 Files selected for processing (32)
  • nemoguardrails/http/__init__.py
  • nemoguardrails/http/_url.py
  • nemoguardrails/http/client.py
  • nemoguardrails/http/errors.py
  • nemoguardrails/http/request.py
  • nemoguardrails/http/retry.py
  • nemoguardrails/http/runtime.py
  • nemoguardrails/http/testing.py
  • nemoguardrails/http/transport.py
  • nemoguardrails/http/types.py
  • nemoguardrails/library/activefence/actions.py
  • nemoguardrails/library/factchecking/align_score/actions.py
  • nemoguardrails/library/factchecking/align_score/request.py
  • nemoguardrails/library/gliner/actions.py
  • nemoguardrails/library/gliner/request.py
  • nemoguardrails/library/jailbreak_detection/actions.py
  • nemoguardrails/library/jailbreak_detection/request.py
  • nemoguardrails/library/polygraf/actions.py
  • nemoguardrails/library/polygraf/request.py
  • nemoguardrails/library/privateai/actions.py
  • nemoguardrails/library/privateai/request.py
  • tests/http/test_contract.py
  • tests/http/test_request.py
  • tests/http/test_retry.py
  • tests/http/test_runtime.py
  • tests/http/test_transport.py
  • tests/http/test_url.py
  • tests/test_activefence_rail.py
  • tests/test_fact_checking.py
  • tests/test_gliner.py
  • tests/test_jailbreak_request.py
  • tests/test_polygraf.py

Comment thread nemoguardrails/library/factchecking/align_score/request.py
Comment thread nemoguardrails/library/jailbreak_detection/request.py
Comment thread nemoguardrails/library/jailbreak_detection/request.py

@coderabbitai coderabbitai Bot 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
nemoguardrails/library/activefence/actions.py (1)

1-1: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard response.json() against malformed provider responses, matching the privateai/request.py pattern.

Both of these migrated call sites parse the HTTP response body with .json()/response_json[...] without catching HTTPResponseDecodeError or a missing key, so a non-JSON or unexpectedly-shaped response from ActiveFence/AlignScore would raise an unhandled exception instead of the controlled ValueError/None-fallback behavior these providers otherwise rely on. nemoguardrails/library/privateai/request.py (lines 70-86, this same migration) already establishes the right pattern: wrap .json() in try/except HTTPResponseDecodeError and re-raise from error.

  • nemoguardrails/library/activefence/actions.py#L120-141: wrap response.json() in try/except HTTPResponseDecodeError, re-raising as ValueError(...) from error, and use .get("violations", []) instead of ["violations"] for the key lookup.
  • nemoguardrails/library/factchecking/align_score/request.py#L38-56: wrap http_response.json() in try/except HTTPResponseDecodeError, logging/returning None on failure (consistent with the existing "return None on failure" contract of alignscore_request).
🛡️ Proposed fix for activefence/actions.py
+from nemoguardrails.http import HTTPClient, HTTPResponseDecodeError, http_call
     if response.status_code != 200:
         raise ValueError(f"ActiveFence call failed with status code {response.status_code}.\nDetails: {response.text}")
-    response_json = response.json()
+    try:
+        response_json = response.json()
+    except HTTPResponseDecodeError as error:
+        raise ValueError(
+            f"Failed to parse ActiveFence response as JSON. Status: {response.status_code}, Content: {response.text}"
+        ) from error
     log.info(json.dumps(response_json, indent=True))
-    violations = response_json["violations"]
+    violations = response_json.get("violations", [])
🛡️ Proposed fix for align_score/request.py
     if http_response.status_code != 200:
         log.error(f"AlignScore API request failed with status {http_response.status_code}")
         return None

-    result = http_response.json()
+    try:
+        result = http_response.json()
+    except HTTPResponseDecodeError:
+        log.error(f"AlignScore API returned a non-JSON response (status {http_response.status_code})")
+        return None
🤖 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 `@nemoguardrails/library/activefence/actions.py` at line 1, Guard JSON parsing
in the ActiveFence response handling and AlignScore’s alignscore_request: catch
HTTPResponseDecodeError from response.json()/http_response.json(). In the
ActiveFence path, re-raise ValueError with the original error as its cause and
read violations via .get("violations", []). In alignscore_request, log the
decode failure and return None, preserving its existing failure contract.
🧹 Nitpick comments (3)
nemoguardrails/library/polygraf/request.py (1)

49-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale docstring wording: "sessions".

The aiohttp session concept is gone; reword to reference the caller-provided or fallback HTTP client.

🤖 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 `@nemoguardrails/library/polygraf/request.py` around lines 49 - 50, Update the
timeout parameter docstring to replace the stale “sessions” reference with the
caller-provided or fallback HTTP client, while preserving the description that
the timeout applies to both cases.
nemoguardrails/library/gliner/actions.py (1)

133-144: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Conditional request_kwargs is unnecessary indirection.

gliner_request already defaults http_client=None, so passing it unconditionally is equivalent and clearer (same for both actions).

♻️ Proposed simplification
-    request_kwargs = {"http_client": http_client} if http_client is not None else {}
     gliner_response = await gliner_request(
         text=text,
         ...
         model=gliner_config.model,
-        **request_kwargs,
+        http_client=http_client,
     )

Also applies to: 189-200

🤖 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 `@nemoguardrails/library/gliner/actions.py` around lines 133 - 144, Remove the
conditional request_kwargs indirection in both GLiNER action request paths and
pass http_client directly to gliner_request, relying on its existing default of
None. Update the corresponding calls near the second action as well, without
changing any other request arguments or behavior.
tests/test_gliner.py (1)

741-743: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated _http_response test helper with divergent kwargs. Both suites hand-roll the same HTTPResponse builder, one using body and the other text, which will keep drifting as more providers migrate.

  • tests/test_gliner.py#L741-L743: replace with a single shared helper (e.g., exported next to RecordingHTTPClient).
  • tests/test_polygraf.py#L199-L201: import the shared helper instead of redefining it with a text kwarg.
🤖 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 `@tests/test_gliner.py` around lines 741 - 743, The test suites duplicate the
HTTPResponse builder with incompatible keyword arguments. In
tests/test_gliner.py lines 741-743, move or expose one shared _http_response
helper next to RecordingHTTPClient; in tests/test_polygraf.py lines 199-201,
remove the local helper and import the shared one, updating callers to use its
body-compatible interface.
🤖 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 `@nemoguardrails/http/retry.py`:
- Around line 168-204: Update the retry loop around HTTPClient.request to
enforce timeout across the entire operation: establish a monotonic deadline,
pass the remaining duration to each request attempt, and cap both backoff and
retry-after sleeps to the remaining time. When the deadline expires, raise
HTTPTimeoutError rather than starting another attempt or sleeping beyond the
timeout, while preserving existing retry-count and response behavior.
- Around line 110-114: Update the non-clamped delay check in the retry-after
handling logic to accept a zero delay as valid, returning it directly instead of
falling back to jittered backoff; preserve the existing maximum-bound validation
and None behavior for invalid delays.

In `@nemoguardrails/http/transport.py`:
- Around line 55-64: Update the HTTP request flow in the transport class to use
httpx’s per-request timeout instead of wrapping httpx.request() with
asyncio.wait_for. Preserve timeout=None for externally supplied clients, while
applying the configured timeout to requests made by internally owned clients;
remove the repeated deadline-cancellation path.

In `@nemoguardrails/library/factchecking/align_score/request.py`:
- Line 54: Replace the broad exception handler in the request flow with only the
specific exception types expected from the dict-key lookup, matching the
established handling in privateai/request.py. Preserve the existing fallback
behavior while allowing unrelated errors such as caller-induced AttributeError
to propagate.

In `@nemoguardrails/library/jailbreak_detection/request.py`:
- Around line 70-75: Normalize timeout and transport/JSON error handling across
the migrated helpers: in nemoguardrails/library/jailbreak_detection/request.py
lines 70-75, update the jailbreak API request and response.json flow to pass an
explicit timeout and return None for HTTPTimeoutError/HTTPClientError and
invalid JSON, matching jailbreak_nim_request; in
nemoguardrails/library/gliner/request.py lines 110-119, add a timeout parameter
as used by polygraf_request, pass it to the HTTP call, and normalize transport
errors to ValueError per the docstring; in
nemoguardrails/library/jailbreak_detection/request.py lines 95-100, apply the
same explicit timeout and error normalization as the heuristics helper.
- Line 153: Update the error log in the jailbreak detection request handling to
avoid explicit str(e) conversion and use the conversion flag supported by the
logging format, preserving the existing error message and exception details.

---

Outside diff comments:
In `@nemoguardrails/library/activefence/actions.py`:
- Line 1: Guard JSON parsing in the ActiveFence response handling and
AlignScore’s alignscore_request: catch HTTPResponseDecodeError from
response.json()/http_response.json(). In the ActiveFence path, re-raise
ValueError with the original error as its cause and read violations via
.get("violations", []). In alignscore_request, log the decode failure and return
None, preserving its existing failure contract.

---

Nitpick comments:
In `@nemoguardrails/library/gliner/actions.py`:
- Around line 133-144: Remove the conditional request_kwargs indirection in both
GLiNER action request paths and pass http_client directly to gliner_request,
relying on its existing default of None. Update the corresponding calls near the
second action as well, without changing any other request arguments or behavior.

In `@nemoguardrails/library/polygraf/request.py`:
- Around line 49-50: Update the timeout parameter docstring to replace the stale
“sessions” reference with the caller-provided or fallback HTTP client, while
preserving the description that the timeout applies to both cases.

In `@tests/test_gliner.py`:
- Around line 741-743: The test suites duplicate the HTTPResponse builder with
incompatible keyword arguments. In tests/test_gliner.py lines 741-743, move or
expose one shared _http_response helper next to RecordingHTTPClient; in
tests/test_polygraf.py lines 199-201, remove the local helper and import the
shared one, updating callers to use its body-compatible interface.
🪄 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: CHILL

Plan: Enterprise

Run ID: 6adc9544-61d3-4340-997e-d42fa2967e00

📥 Commits

Reviewing files that changed from the base of the PR and between ccc8255 and 3faf664.

📒 Files selected for processing (32)
  • nemoguardrails/http/__init__.py
  • nemoguardrails/http/_url.py
  • nemoguardrails/http/client.py
  • nemoguardrails/http/errors.py
  • nemoguardrails/http/request.py
  • nemoguardrails/http/retry.py
  • nemoguardrails/http/runtime.py
  • nemoguardrails/http/testing.py
  • nemoguardrails/http/transport.py
  • nemoguardrails/http/types.py
  • nemoguardrails/library/activefence/actions.py
  • nemoguardrails/library/factchecking/align_score/actions.py
  • nemoguardrails/library/factchecking/align_score/request.py
  • nemoguardrails/library/gliner/actions.py
  • nemoguardrails/library/gliner/request.py
  • nemoguardrails/library/jailbreak_detection/actions.py
  • nemoguardrails/library/jailbreak_detection/request.py
  • nemoguardrails/library/polygraf/actions.py
  • nemoguardrails/library/polygraf/request.py
  • nemoguardrails/library/privateai/actions.py
  • nemoguardrails/library/privateai/request.py
  • tests/http/test_contract.py
  • tests/http/test_request.py
  • tests/http/test_retry.py
  • tests/http/test_runtime.py
  • tests/http/test_transport.py
  • tests/http/test_url.py
  • tests/test_activefence_rail.py
  • tests/test_fact_checking.py
  • tests/test_gliner.py
  • tests/test_jailbreak_request.py
  • tests/test_polygraf.py
🛑 Comments failed to post (3)
nemoguardrails/http/retry.py (2)

110-114: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor Retry-After: 0.

Zero is a valid delta-seconds value. Line 112 rejects it and falls back to jittered backoff, delaying a server-requested immediate retry.

Proposed fix
-        if 0 < delay <= self.max_retry_after:
+        if 0 <= delay <= self.max_retry_after:
             return delay
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        if self.clamp_retry_after:
            return min(max(delay, 0.0), self.max_retry_after)
        if 0 <= delay <= self.max_retry_after:
            return delay
        return None
🤖 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 `@nemoguardrails/http/retry.py` around lines 110 - 114, Update the non-clamped
delay check in the retry-after handling logic to accept a zero delay as valid,
returning it directly instead of falling back to jittered backoff; preserve the
existing maximum-bound validation and None behavior for invalid delays.

168-204: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Enforce timeout across the complete retry operation.

HTTPClient.request defines this as a total timeout, but each attempt receives the full value at Line 172 and Lines 189/203 add uncapped delays. A 30-second request can therefore run for multiple attempts plus backoff. Track a monotonic deadline, pass only remaining time to each attempt, cap retry sleeps to it, then raise HTTPTimeoutError when it expires.

🤖 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 `@nemoguardrails/http/retry.py` around lines 168 - 204, Update the retry loop
around HTTPClient.request to enforce timeout across the entire operation:
establish a monotonic deadline, pass the remaining duration to each request
attempt, and cap both backoff and retry-after sleeps to the remaining time. When
the deadline expires, raise HTTPTimeoutError rather than starting another
attempt or sleeping beyond the timeout, while preserving existing retry-count
and response behavior.
nemoguardrails/http/transport.py (1)

55-64: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

encode httpx AsyncClient request cancellation connection pool cleanup fixed version

💡 Result:

The issue of connection pool leakage in httpx/httpcore during task cancellation has been a persistent and complex technical challenge [1][2][3][4]. When an async request is cancelled (e.g., via asyncio.Task.cancel), it can occasionally leave a connection in an orphaned state—reserved but not fully utilized or correctly reaped—which eventually exhausts the connection pool and leads to persistent PoolTimeout errors [5][6][7]. There is no single "fixed version" that definitively resolves all potential race conditions in all scenarios, as the underlying networking component, httpcore, has faced ongoing architectural challenges regarding async cancellation safety [2][3][4]. While several pull requests have been merged over time to improve cleanup and reclaim orphaned connections [6][1][7][4], users continue to report issues, and development has been fragmented [7][4]. For current projects experiencing these issues: 1. Verify if you are using the latest versions of httpx and httpcore, as many incremental fixes have been released [3]. 2. Be aware that the root cause often relates to how connections are assigned and managed within the httpcore pool [6][2][4]. If your application relies heavily on cancelling requests, you may still encounter edge cases [2]. 3. Some community members have noted that alternative libraries or forks (e.g., pydantic's httpx2) have implemented more aggressive fixes for this specific problem [7]. In production environments where stability is critical, if updates to the latest official httpx/httpcore do not resolve the leakage, consider architectural workarounds, such as increasing connection pool limits or utilizing a client-side wrapper that explicitly ensures connection cleanup upon task cancellation.

Citations:


Avoid cancelling httpx.request() with asyncio.wait_for timeout=None disables httpx’s native timeout handling, so repeated deadline cancellations can leave pooled connections in a bad state under retry pressure. Prefer httpx’s per-request timeout= here instead.

🧰 Tools
🪛 Ruff (0.16.0)

[error] 60-60: Probable use of httpx call with timeout set to None

(S113)

🤖 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 `@nemoguardrails/http/transport.py` around lines 55 - 64, Update the HTTP
request flow in the transport class to use httpx’s per-request timeout instead
of wrapping httpx.request() with asyncio.wait_for. Preserve timeout=None for
externally supplied clients, while applying the configured timeout to requests
made by internally owned clients; remove the repeated deadline-cancellation
path.

@tgasser-nv tgasser-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! Just a few general comments / nits. Could you add more unit-tests to cover the action lines from CodeCov? There are whole failure branches missing in the jailbreak for example

The timeouts will change after this PR. The default aiohttp.ClientSession() used by ActiveFence, AlignScore, GLiNER, jailbreak, and Private AI defaulted to 5 minutes. After this PR the default timeout is now 30s. I think 30s is much more reasonable for a blocking Guardrail action, but if the server responds in 35s this will now throw an exception rather than passing as before

text,
server_endpoint,
api_key,
http_client=http_client,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: For Polygraf we pass the http_client directly through the named argument. For the other integrations we use:

request_kwargs = {"http_client": http_client} if http_client is not None else {}

And then pass through the kwargs. Would be cleaner to use the Polygraf approach for all the actions.py files.

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

Labels

size: L status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants