Skip to content

Added evaluation of multiple validators together#66

Open
rkritika1508 wants to merge 1 commit intomainfrom
feat/evaluation-multiple-validator-demo
Open

Added evaluation of multiple validators together#66
rkritika1508 wants to merge 1 commit intomainfrom
feat/evaluation-multiple-validator-demo

Conversation

@rkritika1508
Copy link
Collaborator

@rkritika1508 rkritika1508 commented Feb 24, 2026

Summary

Target issue is #67.
Explain the motivation for making this change. What existing problem does the pull request solve?

  • We have evaluation scripts for single validators. However, we don't have a script to run different permutations and combinations of a set of validators.
  • Added a script to run multiple validators together and stores the responses in a csv file.
  • Steps to run the python file -
    python3 app/evaluation/multi_validator_whatsapp/run.py --validators_payload uli_slur_match,ban_list,pii_remover --auth_token <auth-token>

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Please add here if any other information is required for the reviewer.

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation performance by optimizing word-filtering rules resolution to only occur when explicitly required.
  • New Features

    • Added comprehensive evaluation framework for testing multi-validator validation workflows, enabling better assessment of complex validation scenarios.

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

The pull request introduces conditional validation logic in the guardrails API route that defers ban-list word resolution to only when required, and adds a new evaluation script that orchestrates guardrails-based validation over a WhatsApp dataset with API integration and CSV output.

Changes

Cohort / File(s) Summary
Guardrails Route Enhancement
backend/app/api/routes/guardrails.py
Modified run_guardrails function to conditionally resolve ban-list banned words only when a BanListSafetyValidatorConfig exists with banned_words as None, changing validation execution flow.
Multi-Validator WhatsApp Evaluation
backend/app/evaluation/multi_validator_whatsapp/run.py
New script for orchestrating guardrails validation over CSV datasets. Implements call_guardrails() function to send POST requests to Guardrails API, defines validator templates, and provides CLI interface via main() for processing datasets and writing results to CSV.

Sequence Diagram

sequenceDiagram
    participant Script as WhatsApp Eval Script
    participant CSV as CSV Dataset
    participant API as Guardrails API
    participant Output as Output CSV

    Script->>CSV: Load multi_validator_whatsapp_dataset.csv
    CSV-->>Script: Dataset rows
    
    loop For each row
        Script->>Script: Build request payload with validators
        Script->>API: POST /guardrails with input text + validators
        API-->>Script: Response with safe_text
        Script->>Script: Extract and store result
    end
    
    Script->>Output: Write results to predictions.csv
    Output-->>Script: File written
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

ready-for-review

Suggested reviewers

  • nishika26

Poem

🐰 Hops with glee at validation's flow,
Ban-lists now resolve just-in-time, you know!
WhatsApp datasets through the API dance,
CSV to CSV, a validation romance,
Safety guardrails standing tall and true!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately reflects the main change: introducing a new evaluation script (run.py) that enables evaluating multiple validators together, which was the primary motivation and objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/evaluation-multiple-validator-demo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@rkritika1508 rkritika1508 marked this pull request as ready for review February 24, 2026 12:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/app/api/routes/guardrails.py (1)

48-53: LGTM – the conditional guard is correct and well-motivated.

The any() short-circuit correctly avoids an unnecessary DB round-trip when all BanListSafetyValidatorConfig validators already have banned_words populated (as in the evaluation script, where they're inlined). The inner _resolve_ban_list_banned_words also retains its own guard (banned_words is not None), so behavior is unchanged for callers that always supplied banned_words.

One existing gap worth noting: _resolve_ban_list_banned_words is invoked before _validate_with_guard's try/except, so any exception from ban_list_crud.get (e.g. record not found, DB error) escapes unhandled and falls through to FastAPI's default 500 handler rather than returning a structured APIResponse. This pre-dates this PR, but wrapping the call — or moving ban-list resolution inside _validate_with_guard's try block — would close that gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/guardrails.py` around lines 48 - 53, The call to
_resolve_ban_list_banned_words(payload, session) can raise exceptions from
ban_list_crud.get which escape the current try/except in _validate_with_guard;
wrap the resolution call in the same error handling as _validate_with_guard (or
move the call inside _validate_with_guard's try block) so DB errors are caught
and converted to the same structured APIResponse/error handling path.
Specifically, ensure exceptions from ban_list_crud.get (raised via
_resolve_ban_list_banned_words) are caught and handled consistently with
_validate_with_guard rather than propagating to FastAPI (reference symbols:
_resolve_ban_list_banned_words, _validate_with_guard, ban_list_crud.get,
payload.validators, BanListSafetyValidatorConfig).
backend/app/evaluation/multi_validator_whatsapp/run.py (1)

16-16: float() conversion at module level will crash on a malformed env var.

TIMEOUT_SECONDS = float(os.getenv("GUARDRAILS_TIMEOUT_SECONDS", "60")) raises ValueError at import time if the variable is set to a non-numeric string (e.g. a mistyped value), giving a confusing traceback instead of a meaningful error.

🔧 Proposed fix
-TIMEOUT_SECONDS = float(os.getenv("GUARDRAILS_TIMEOUT_SECONDS", "60"))
+_raw_timeout = os.getenv("GUARDRAILS_TIMEOUT_SECONDS", "60")
+try:
+    TIMEOUT_SECONDS = float(_raw_timeout)
+except ValueError:
+    raise ValueError(
+        f"GUARDRAILS_TIMEOUT_SECONDS must be a number, got: {_raw_timeout!r}"
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/evaluation/multi_validator_whatsapp/run.py` at line 16, The
module-level float conversion of TIMEOUT_SECONDS from GUARDRAILS_TIMEOUT_SECONDS
can raise ValueError at import time; change it to a safe parse: read
os.getenv("GUARDRAILS_TIMEOUT_SECONDS") into a string, attempt float() inside a
try/except (or a small helper like parse_timeout_env/get_timeout_seconds) and on
failure fall back to the default 60 (and optionally log a warning) so import
won't crash; assign the resulting numeric value to TIMEOUT_SECONDS.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/evaluation/multi_validator_whatsapp/run.py`:
- Around line 28-32: The current "ban_list" template contains a hardcoded
"banned_words": ["sonography"] which restricts use; change run.py to accept a
banned-words list from the CLI (or env var) and build the template dynamically
instead of embedding the literal array. Specifically, add a CLI flag (e.g.,
--banned-words) or read an env var, parse it into a list, then replace the
static "banned_words" entry in the "ban_list" template with that parsed list
before the validator is used; update any code that references the "ban_list"
template to consume the newly built template.
- Around line 58-61: The current code in run.py that extracts safe_text
(safe_text = body.get("data", {}).get("safe_text") and returns "" when missing)
conflates missing/validation-failed responses with legitimately empty-string
outputs; change the return to a clear sentinel instead of "" — e.g., return None
(preferred) or the string "VALIDATION_FAILED" from the call_guardrails flow
where safe_text is absent, and update any CSV/export logic that writes these
results to map that sentinel to a distinct marker (e.g., "VALIDATION_FAILED") so
missing/validation failures are distinguishable from empty valid outputs; ensure
references to safe_text and call_guardrails are updated accordingly.
- Around line 86-90: In main(), instead of raising ValueError when invalid
validator names are found (the block that checks selected_validators or
unknown), call parser.error(...) with the same message so argparse prints a
usage and exits with code 2; use the existing parser object and include the
supported values (f"{', '.join(VALIDATOR_TEMPLATES.keys())}") in the error text
so the behavior for selected_validators/unknown mirrors argparse's idiomatic
validation.
- Around line 43-44: The payload in run.py currently hardcodes "organization_id"
and "project_id" to 1; update main() to accept CLI args (e.g., --organization-id
and --project-id) with env-var fallbacks similar to API_URL and TIMEOUT_SECONDS,
parse them to integers, pass them into call_guardrails, and replace the literal
values in the payload construction (the dictionary with keys "organization_id"
and "project_id") so calls target the correct tenant instead of always tenant 1.

---

Nitpick comments:
In `@backend/app/api/routes/guardrails.py`:
- Around line 48-53: The call to _resolve_ban_list_banned_words(payload,
session) can raise exceptions from ban_list_crud.get which escape the current
try/except in _validate_with_guard; wrap the resolution call in the same error
handling as _validate_with_guard (or move the call inside _validate_with_guard's
try block) so DB errors are caught and converted to the same structured
APIResponse/error handling path. Specifically, ensure exceptions from
ban_list_crud.get (raised via _resolve_ban_list_banned_words) are caught and
handled consistently with _validate_with_guard rather than propagating to
FastAPI (reference symbols: _resolve_ban_list_banned_words,
_validate_with_guard, ban_list_crud.get, payload.validators,
BanListSafetyValidatorConfig).

In `@backend/app/evaluation/multi_validator_whatsapp/run.py`:
- Line 16: The module-level float conversion of TIMEOUT_SECONDS from
GUARDRAILS_TIMEOUT_SECONDS can raise ValueError at import time; change it to a
safe parse: read os.getenv("GUARDRAILS_TIMEOUT_SECONDS") into a string, attempt
float() inside a try/except (or a small helper like
parse_timeout_env/get_timeout_seconds) and on failure fall back to the default
60 (and optionally log a warning) so import won't crash; assign the resulting
numeric value to TIMEOUT_SECONDS.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09931fb and 0bd8545.

📒 Files selected for processing (2)
  • backend/app/api/routes/guardrails.py
  • backend/app/evaluation/multi_validator_whatsapp/run.py

@rkritika1508 rkritika1508 linked an issue Feb 24, 2026 that may be closed by this pull request
API_URL = os.getenv("GUARDRAILS_API_URL", "http://localhost:8001/api/v1/guardrails/")
TIMEOUT_SECONDS = float(os.getenv("GUARDRAILS_TIMEOUT_SECONDS", "60"))

VALIDATOR_TEMPLATES = {
Copy link
Contributor

Choose a reason for hiding this comment

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

making the script like this is not exactly scalable as not only are there going to be more validators in the future, we will be adding support to create custom validator also, so what would be better would be that you add support to read a json that will have configurations of all these validators that you want to run together. you can make one json file, add instuctions to add config there and run that file

DATASET_PATH = BASE_DIR / "datasets" / "multi_validator_whatsapp_dataset.csv"
OUT_PATH = BASE_DIR / "outputs" / "multi_validator_whatsapp" / "predictions.csv"

API_URL = os.getenv("GUARDRAILS_API_URL", "http://localhost:8001/api/v1/guardrails/")
Copy link
Contributor

Choose a reason for hiding this comment

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

this should not be hardcoded


payload = {
"request_id": str(uuid4()),
"organization_id": 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

these values should not be hardcoded either

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate multiple validators together

2 participants