Skip to content

feat(server): Load and validate single-configuration at startup - #2461

Open
tgasser-nv wants to merge 1 commit into
developfrom
feat/hooks-2-startup-config
Open

tgasser-nv wants to merge 1 commit into
developfrom
feat/hooks-2-startup-config

Conversation

@tgasser-nv

@tgasser-nv tgasser-nv commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The Guardrails server can run in a single-config or multi-config mode. In single-config mode, a single RailsConfig is applied to all incoming inference/check requests, and clients don't need to specify a config in their request. The guardrails key can be absent. In multi-config mode, the server knows a directory under which one or more configs are stored, and will load and validate the relevant config at the first inference-time.

This PR makes the server load and validate the single-config file at startup, rather than at the first inference-time. This prevents the server from running invalid configs in single-config mode.

Related Issue(s)

Verification

Pre-commit

$ uv run pre-commit run --all-files
check yaml...............................................................Passed
fix end of files.........................................................Passed
trim trailing whitespace.................................................Passed
ruff (legacy alias)......................................................Passed
ruff format..............................................................Passed
Insert license in comments...............................................Passed
zizmor...................................................................Passed
ty.......................................................................Passed

Unit-test

$ make test

env -u OPENAI_API_KEY -u NVIDIA_API_KEY -u LIVE_TEST -u LIVE_TEST_MODE -u TEST_LIVE_MODE uv run pytest -n auto --dist worksteal  
============================= test session starts ==============================
platform darwin -- Python 3.13.2, pytest-9.1.1, pluggy-1.6.0
rootdir: /Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config
configfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!)
testpaths: tests, benchmark/tests
plugins: langsmith-0.9.4, inline-snapshot-0.33.0, recording-0.13.4, cov-7.1.0, anyio-4.14.1, xdist-3.8.0, asyncio-1.4.0, httpx-0.36.2, profiling-1.8.1
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
created: 10/10 workers
10 workers [9042 items]

ssss...sss.....sss......ss......s.........s............................. [  0%]
........................................................................ [  1%]
..........................................s..sss........................ [  2%]
........................................................................ [  3%]
........................................................................ [  3%]
................................................s....................... [  4%]
........................................................................ [  5%]
........................................................................ [  6%]
........................................................................ [  7%]
..................................................sssss................. [  7%]
........................................................................ [  8%]
........................................................................ [  9%]
........................................................................ [ 10%]
..............................s...ssss.................................. [ 11%]
........................................................................ [ 11%]
........................................................................ [ 12%]
........................................................................ [ 13%]
........................................................................ [ 14%]
......................................s................................. [ 15%]
........................................................................ [ 15%]
........................................................................ [ 16%]
........................................................................ [ 17%]
........................................................................ [ 18%]
........................................................................ [ 19%]
........................................................................ [ 19%]
........................................................................ [ 20%]
........................................................................ [ 21%]
........................................................................ [ 22%]
........................................................................ [ 23%]
........................................................................ [ 23%]
........................................................................ [ 24%]
........................................................................ [ 25%]
........................................................................ [ 26%]
............s........................................................... [ 27%]
........................................................................ [ 27%]
........................................................................ [ 28%]
........................................................................ [ 29%]
........................................................................ [ 30%]
........................................................................ [ 31%]
....................................................................s... [ 31%]
.....ss.............................ss.s..s..s..s.....s.........ss...... [ 32%]
................s.................s..................................... [ 33%]
........................................................................ [ 34%]
........................................................................ [ 35%]
........................................ss................s............. [ 35%]
........................................................................ [ 36%]
........................................................................ [ 37%]
.....ss.....................s........................................... [ 38%]
........................................................................ [ 39%]
........................................................................ [ 39%]
........................................................................ [ 40%]
........................................................................ [ 41%]
........................................................................ [ 42%]
........................................................................ [ 42%]
.......................................................s................ [ 43%]
........................................................................ [ 44%]
.......................................................s................ [ 45%]
........................................................................ [ 46%]
........................................................................ [ 46%]
..................................................................ssssss [ 47%]
.sss.ssssssssss......................................................... [ 48%]
........................................................................ [ 49%]
........................................................................ [ 50%]
........................................................................ [ 50%]
........................................................................ [ 51%]
........................................................................ [ 52%]
........................................................................ [ 53%]
........................................................................ [ 54%]
....................................s................................... [ 54%]
........................................................................ [ 55%]
........................................................................ [ 56%]
................................s.....ss.s.sssss........................ [ 57%]
....ssssss.............s.s.............s................................ [ 58%]
................s.sssssss............................................... [ 58%]
............s........................................................... [ 59%]
........................................................................ [ 60%]
........................................................................ [ 61%]
........................................................................ [ 62%]
...............................................................s.s...... [ 62%]
........................................................................ [ 63%]
........................................................................ [ 64%]
........................................................................ [ 65%]
........................................................................ [ 66%]
...........................................................s............ [ 66%]
..................s..................................................... [ 67%]
........................................................................ [ 68%]
........................................................................ [ 69%]
........................................................................ [ 70%]
........................................................................ [ 70%]
............................................................s........... [ 71%]
........................................................................ [ 72%]
.s............................sssssss..............ssssssss............. [ 73%]
........................................................................ [ 74%]
........................................................................ [ 74%]
........................................................................ [ 75%]
........................................................................ [ 76%]
........................................................................ [ 77%]
........................................................................ [ 78%]
........................................................................ [ 78%]
........................................................................ [ 79%]
........................................................................ [ 80%]
........................................................................ [ 81%]
........................................................................ [ 82%]
........................................................................ [ 82%]
.......................................s..................ss..sss....... [ 83%]
..sss.s................................................................. [ 84%]
........................ss.sss.ss.ss.s.ssss............................. [ 85%]
........................................................................ [ 85%]
s.sss.ss.................................................s..........s.ss [ 86%]
........................................................................ [ 87%]
.................s.sssssssss................ss....................ss.... [ 88%]
........................................................................ [ 89%]
...........................sss.ssssssssss............................... [ 89%]
..........................ss..ss.ss.ss.................................. [ 90%]
........................................................................ [ 91%]
.ss..................................................................... [ 92%]
........................ssssss...ssss................................... [ 93%]
...s....................................ssss............................ [ 93%]
......s.s.....................ss........................................ [ 94%]
........................................................................ [ 95%]
........................................................................ [ 96%]
........................................................................ [ 97%]
.............................s.......................................... [ 97%]
........................................................................ [ 98%]
..........................................................s............. [ 99%]
.......s..................................                               [100%]

═══════════════════════════════════════════════════════ inline-snapshot ═══════════════════════════════════════════════════════
INFO: inline-snapshot was disabled because you used xdist. This means that tests with snapshots will continue to run, but 
snapshot(x) will only return x and inline-snapshot will not be able to fix snapshots or generate reports.


====================== 8828 passed, 214 skipped in 39.43s ======================

Integration test with Chat (broken config)

$ uv run --locked nemoguardrails server --config ~/utils/configs/single_config_startup/broken --disable-chat-ui; echo "exit code: $?"
INFO:     Started server process [61021]
INFO:     Waiting for application startup.
ERROR:    Traceback (most recent call last):
  File "/Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config/.venv/lib/python3.13/site-packages/starlette/routing.py", line 638, in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/Users/tgasser/.pyenv/versions/3.13.2/lib/python3.13/contextlib.py", line 214, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config/nemoguardrails/server/api.py", line 153, in lifespan
    async with _server_lifespan(app):
               ~~~~~~~~~~~~~~~~^^^^^
  File "/Users/tgasser/.pyenv/versions/3.13.2/lib/python3.13/contextlib.py", line 214, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config/nemoguardrails/server/api.py", line 180, in _server_lifespan
    app.single_config = RailsConfig.from_path(app.rails_config_path)
                        ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config/nemoguardrails/rails/llm/config.py", line 1299, in from_path
    return cls.parse_object(raw_config)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config/nemoguardrails/rails/llm/config.py", line 1372, in parse_object
    return cls.model_validate(obj)
           ~~~~~~~~~~~~~~~~~~^^^^^
  File "/Users/tgasser/projects/nemo_guardrails_worktree/feat/hooks-2-startup-config/.venv/lib/python3.13/site-packages/pydantic/main.py", line 732, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        obj,
        ^^^^
    ...<5 lines>...
        by_name=by_name,
        ^^^^^^^^^^^^^^^^
    )
    ^
pydantic_core._pydantic_core.ValidationError: 1 validation error for RailsConfig
  Value error, Input flow 'content safety check input' references model type 'content_safety' that is not defined in the configuration. Detected model types: 'main'. [type=value_error, input_value={'user_messages': {}, 'bo..._config_startup/broken'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.13/v/value_error

ERROR:    Application startup failed. Exiting.
exit code: 3

Integration-test with working config

$ uv run --locked nemoguardrails server --config ~/utils/configs/single_config_startup/working --disable-chat-ui; echo "exit code: $?"

uv run --locked nemoguardrails server --config ~/utils/configs/single_config_startup/working --disable-chat-ui; echo "exit code: $?"
INFO:     Started server process [61271]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

AI Assistance

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

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

  • Behavior Updates
    • Single-configuration mode now loads and validates its configuration at startup. If validation fails, the server logs an error and exits.
    • In multi-configuration mode, configurations are loaded when first requested rather than all at startup. Invalid entries remain available in the listing but return an HTTP 400 error when requested.
    • Using --prefix skips startup steps, including single-configuration detection and validation.
  • Documentation
    • Updated the FastAPI server guide to describe configuration loading and validation behavior.

@github-actions github-actions Bot added size: M status: needs triage New issues that have not yet been reviewed or categorized. labels Oct 14, 2026
@tgasser-nv tgasser-nv self-assigned this Oct 14, 2026
@tgasser-nv tgasser-nv added status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). and removed status: needs triage New issues that have not yet been reviewed or categorized. labels Oct 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@tgasser-nv
tgasser-nv marked this pull request as ready for review October 14, 2026 20:06
@coderabbitai

coderabbitai Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA-NeMo/Guardrails/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: 38bfa02d-44ea-4575-bbe4-b3c201771f43
📥 Commits

Reviewing files that changed from the base of the PR and between 21ec4f4 and 86acff6.

📒 Files selected for processing: 5
  • docs/run-rails/using-fastapi-server/overview.mdx
  • docs/run-rails/using-fastapi-server/run-guardrails-server.mdx
  • nemoguardrails/server/api.py
  • tests/cli/test_cli_main.py
  • tests/server/test_single_config_startup.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

📝 Walkthrough

The server now loads and validates a detected single configuration during startup, and clears its single-config state at shutdown. Multi-config entries remain lazily loaded. Documentation and tests describe and verify these behaviors.

Changes: FastAPI server configuration startup

Layer / File(s) Summary
Single-config startup and lifecycle
nemoguardrails/server/api.py, tests/server/test_single_config_startup.py, tests/cli/test_cli_main.py
Startup loads a detected configuration before enabling single-config mode. A load failure prevents startup. Shutdown clears single-config state. Tests cover both supported layouts, validation failure, shutdown reset, and CLI startup.
Multi-config loading behavior
tests/server/test_single_config_startup.py, docs/run-rails/using-fastapi-server/*
Tests verify that invalid multi-config entries remain listed and return HTTP 400 when selected. The documentation describes single-config startup validation, lazy loading in multi-config mode, and the startup steps skipped by --prefix.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: yixinh-nv

Merge Risk: ⚪ Minimal · up to 86acf

The server now validates a single configuration at startup and fails fast on invalid configs. Multi-config loading stays lazy. No merge-blocking risk was identified.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 clearly and concisely describes the main change: loading and validating the single configuration during server startup.
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 The PR introduces a major server behavior change and adds 177 lines across startup logic and tests. The description documents testing information, including pre-commit results, make test results, an…
Full details: Docstring Coverage

Explanation

Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@greptile-apps

greptile-apps Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] Server startup now validates single configuration at boot.

The PR appears safe to merge, with a non-blocking memory cleanup suggested.

Findings

  1. P2 Unused configuration consumes memory ▶
Fix with agent prompt
### Issue 1
nemoguardrails/server/api.py:180
`app.single_config` keeps the entire parsed configuration, but no request reads it. `_get_rails` still calls `RailsConfig.from_path` separately. This leaves an unused copy of the flows and knowledge-base documents in memory for the server's lifetime.

If startup only needs to validate the configuration, discard the return value. Otherwise, connect this object to the request path and account for auto-reload.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The server now parses and validates a single configuration during startup, so parsing or validation errors stop startup.

  • Single-config files are loaded and checked before the server starts.

Reviews (1) · Last reviewed commit: "Validate config in server single-config ..." · Reviewed by Greptile

# Loading here rather than on the first request makes an invalid config stop
# the server from starting. Load before setting the mode, so a failed load
# leaves no partial single-config state.
app.single_config = RailsConfig.from_path(app.rails_config_path)

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.

P2 Unused configuration consumes memory

app.single_config keeps the entire parsed configuration, but no request reads it. _get_rails still calls RailsConfig.from_path separately. This leaves an unused copy of the flows and knowledge-base documents in memory for the server's lifetime.

If startup only needs to validate the configuration, discard the return value. Otherwise, connect this object to the request path and account for auto-reload.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/server/api.py
Line: 180

Comment:
**Unused configuration consumes memory**

`app.single_config` keeps the entire parsed configuration, but no request reads it. `_get_rails` still calls `RailsConfig.from_path` separately. This leaves an unused copy of the flows and knowledge-base documents in memory for the server's lifetime.

If startup only needs to validate the configuration, discard the return value. Otherwise, connect this object to the request path and account for auto-reload.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

This branch has not been deployed

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

Labels

size: M 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.

1 participant