Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ SENTRY_DSN=

DOCKER_IMAGE_BACKEND=kaapi-guardrails-backend

OPENAI_API_KEY="<ADD-KEY>"
GUARDRAILS_HUB_API_KEY="<ADD-KEY>"
# SHA-256 hex digest of your bearer token (64 lowercase hex chars)
AUTH_TOKEN="<ADD-HASH-TOKEN>"
Expand Down
1 change: 1 addition & 0 deletions .env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ SENTRY_DSN=

DOCKER_IMAGE_BACKEND=kaapi-guardrails-backend

OPENAI_API_KEY="<ADD-KEY>"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if tests mock OpenAI or require real API key

echo "=== Checking for OpenAI mocking in tests ==="
rg -n -C3 'mock.*openai|patch.*openai|monkeypatch.*openai' --type=py --glob='**/test*.py'

echo -e "\n=== Checking if OPENAI_API_KEY is referenced in test files ==="
rg -n -C3 'OPENAI_API_KEY' --type=py --glob='**/test*.py'

echo -e "\n=== Checking if new validators use OpenAI client ==="
rg -n -C3 'OpenAI\(|openai\.' --type=py backend/app/core/validators/

echo -e "\n=== Checking CI workflow for OPENAI_API_KEY replacement ==="
rg -n 'OPENAI_API_KEY' .github/workflows/

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 301


🏁 Script executed:

# Check CI workflow directly
cat .github/workflows/continuous_integration.yml

# Search for the validators mentioned
fd -type f -name "*.py" | xargs grep -l "Topic.*Relevance\|LLM.*Critic" 2>/dev/null

# Check validators directory structure
find . -type d -name "validators" 2>/dev/null | head -20

# Search for OpenAI imports more broadly
rg -n "from openai\|import openai" --type=py

# Check environment variable usage in test setup
rg -n "OPENAI_API_KEY\|os\.environ\|getenv" --type=py backend/app/core/validators/ 2>/dev/null || echo "Path not found, checking alternatives"

# List what's in core directory
find backend -type d -name "core" 2>/dev/null

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 2664


🏁 Script executed:

# List validators in core directory
ls -la backend/app/core/validators/

# List test validators
ls -la backend/app/tests/validators/

# Search for OpenAI usage more carefully (fix the type flag)
rg -n "from openai\|import openai\|OpenAI" --glob='**/*.py' backend/app/

# Check conftest and test setup files
find backend -name "conftest.py" -o -name "pytest.ini" -o -name "pyproject.toml" | head -10

# Check if there's test configuration
cat backend/pyproject.toml 2>/dev/null | grep -A 20 "\[tool.pytest"

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 1240


🏁 Script executed:

# Check topic_relevance.py implementation
cat backend/app/core/validators/topic_relevance.py

# Check conftest for mocking setup
cat backend/app/tests/conftest.py

# Search for LLM Critic validator
find backend -name "*llm*" -o -name "*critic*" 2>/dev/null

# Check if tests are mocking or patching openai
rg "mock\|patch\|monkeypatch" backend/app/tests/ --glob='*.py' -A 3 -B 1

# Check environment handling in tests
cat backend/app/tests/pytest.ini

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 9039


🏁 Script executed:

# Search for LLM Critic validator
find backend -name "*critic*" -type f

# Check if any test files import or use TopicRelevance
rg "TopicRelevance\|topic-relevance\|topic_relevance" backend/app/tests/ --glob='*.py'

# Check validators.json to see what's registered
cat backend/app/core/validators/validators.json

# Check if topic_relevance is imported in validators __init__
cat backend/app/core/validators/__init__.py 2>/dev/null || echo "No __init__.py"

# Check what happens when validator is missing OpenAI key - look for error handling
rg "OPENAI_API_KEY" backend/ --glob='*.py' -B 2 -A 2

# Check if there are integration tests or if validators are imported anywhere
rg "from.*validators import\|import.*validators" backend/app/ --glob='*.py' | head -20

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 943


🏁 Script executed:

# Check the llm_critic_safety_validator_config.py file
cat backend/app/core/validators/config/llm_critic_safety_validator_config.py

# Check for any usage of topic_relevance or llm_critic validators in actual code
rg "topic_relevance|llm_critic" backend/app/ --glob='*.py' --glob='*.json' -B 2 -A 2 | head -100

# Check if validators are instantiated/used during tests
rg "ValidatorConfig\|ValidatorPayload\|validate\(" backend/app/tests/ --glob='*.py' -B 1 -A 1 | head -80

# Check the actual seed_data to see what validators are being tested
find backend -name "seed_data.py" -exec cat {} \;

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 10613


🏁 Script executed:

# Check what _validate_with_guard actually does - does it instantiate validators?
cat backend/app/tests/test_validate_with_guard.py | head -200

# Check the actual validation route implementation
cat backend/app/api/routes/guardrails.py | grep -A 50 "def _validate_with_guard"

# Check seed_data.json to see what validators are seeded
cat backend/app/tests/seed_data.json

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 10410


🏁 Script executed:

# Check if TopicRelevance or LLMCritic validators are ever instantiated without mocking
rg "TopicRelevance\(|LLMCritic\(" backend/app/tests/ --glob='*.py'

# Check the build_guard function to understand how validators are instantiated
find backend -name "*.py" -exec grep -l "def build_guard" {} \;

# Check if the validators are tested directly
find backend -path "*/tests/*" -name "*topic*" -o -name "*critic*" 2>/dev/null

# Check how Guard is built and if validators are instantiated
rg "Guard\(" backend/app/ --glob='*.py' -B 3 -A 3 | head -150

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 4487


🏁 Script executed:

# Check the test_build_guard.py to see if TopicRelevance is tested
cat backend/app/tests/test_build_guard.py

# Check test_topic_relevance_configs_api_integration.py
cat backend/app/tests/test_topic_relevance_configs_api_integration.py

# Check if any test actually calls build_guard without mocking it
rg "build_guard" backend/app/tests/ --glob='*.py' | grep -v "patch\|Mock"

# Check guardrail_controller.py for more context
cat backend/app/core/guardrail_controller.py

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 13104


🏁 Script executed:

# Check if there's an llm_critic.py validator file (as opposed to the config class)
find backend -name "*llm_critic*" -type f

# Check what's in the config file more carefully
cat backend/app/core/validators/config/llm_critic_safety_validator_config.py

# Verify which validators are actually defined locally vs imported from hub
ls -la backend/app/core/validators/*.py

# Check if any non-mocked test actually instantiates validators
rg "\.build\(\)" backend/app/tests/ --glob='*.py' -B 2 -A 2

# Look for any test configuration that uses TopicRelevance without mocking
rg "TopicRelevance|llm_critic" backend/app/tests/ --glob='*.py' -B 3 -A 3 | head -200

Repository: ProjectTech4DevAI/kaapi-guardrails

Length of output: 8117


Update the CI workflow to populate OPENAI_API_KEY from secrets or mock OpenAI calls in tests.

The TopicRelevance and LLMCritic validators require a real OpenAI API key to function (via llm_callable). The CI workflow copies .env.test.example to .env.test and replaces AUTH_TOKEN and GUARDRAILS_HUB_API_KEY via sed, but does not replace OPENAI_API_KEY. Tests currently don't fail because build_guard is mocked everywhere; however, this is fragile. If these validators are instantiated without mocking—or if test coverage expands—CI will fail with the placeholder value "<ADD-KEY>".

Either:

  1. Add a sed command to populate OPENAI_API_KEY from a secrets.OPENAI_API_KEY secret, or
  2. Implement OpenAI mocking in test fixtures (e.g., in conftest.py) to intercept client initialization.
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 24-24: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

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

In @.env.test.example at line 24, The CI is leaving OPENAI_API_KEY set to the
placeholder "<ADD-KEY>" which can break validators like TopicRelevance and
LLMCritic that call llm_callable; either update the CI workflow step that copies
.env.test.example to actually replace OPENAI_API_KEY from the CI secret (add a
sed/replace using secrets.OPENAI_API_KEY) or add a test fixture in conftest.py
that mocks OpenAI client initialization and/or intercepts llm_callable so
TopicRelevance and LLMCritic never attempt real network calls; modify whichever
you choose consistently so tests never run with the "<ADD-KEY>" placeholder.

GUARDRAILS_HUB_API_KEY="<ADD-KEY>"
# SHA-256 hex digest of your bearer token (64 lowercase hex chars)
AUTH_TOKEN="<ADD-HASH-TOKEN>"
Expand Down
12 changes: 9 additions & 3 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,19 +233,25 @@ Set the resulting digest as `AUTH_TOKEN` in your `.env` / `.env.test`.

## Multi-tenant API Key Configuration

Ban List APIs use `X-API-KEY` auth instead of bearer token auth.
Ban List and Topic Relevance Config APIs use `X-API-KEY` auth instead of bearer token auth.

Required environment variables:
- `KAAPI_AUTH_URL`: Base URL of the Kaapi auth service used to verify API keys.
- `KAAPI_AUTH_TIMEOUT`: Timeout in seconds for auth verification calls.

At runtime, the backend calls:
- `GET {KAAPI_AUTH_URL}/apikeys/verify`
- Header: `X-API-KEY: ApiKey <token>`
- Header: `X-API-KEY: <token>`

If verification succeeds, tenant's scope (`organization_id`, `project_id`) is resolved from the auth response and applied to Ban List CRUD operations.
If verification succeeds, tenant's scope (`organization_id`, `project_id`) is resolved from the auth response and applied to tenant-scoped CRUD operations (for example Ban Lists and Topic Relevance Configs).

## Guardrails AI Setup

> **OpenAI API key required for LLM-based validators**
> The `llm_critic` and `topic_relevance` validators call OpenAI models at runtime.
> Set `OPENAI_API_KEY` in your `.env` / `.env.test` before using these validators.
> If the key is missing, `llm_critic` will raise a `ValueError` at build time and `topic_relevance` will return a validation failure with an explicit error message.

1. Ensure that the .env file contains the correct value from `GUARDRAILS_HUB_API_KEY`. The key can be fetched from [here](https://hub.guardrailsai.com/keys).

2. Make the `install_guardrails_from_hub.sh` script executable using this command (run this from the `backend` folder) -
Expand Down
57 changes: 57 additions & 0 deletions backend/app/alembic/versions/006_added_topic_relevance_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Added topic_relevance table

Revision ID: 006
Revises: 005
Create Date: 2026-03-05 00:00:00.000000

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision: str = "006"
down_revision = "005"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.create_table(
"topic_relevance",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("organization_id", sa.Integer(), nullable=False),
sa.Column("project_id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=False),
sa.Column("prompt_schema_version", sa.Integer(), nullable=False),
sa.Column("configuration", sa.Text(), nullable=False),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"organization_id",
"project_id",
"prompt_schema_version",
"configuration",
name="uq_topic_relevance_config_org_project_prompt",
),
)

op.create_index(
"idx_topic_relevance_organization", "topic_relevance", ["organization_id"]
)
op.create_index("idx_topic_relevance_project", "topic_relevance", ["project_id"])
op.create_index(
"idx_topic_relevance_prompt_schema_version",
"topic_relevance",
["prompt_schema_version"],
)
op.create_index("idx_topic_relevance_is_active", "topic_relevance", ["is_active"])


def downgrade() -> None:
op.drop_table("topic_relevance")
99 changes: 93 additions & 6 deletions backend/app/api/API_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This guide explains how to use the current API surface for:
- Runtime validator discovery
- Guardrail execution
- Ban list CRUD for multi-tenant projects
- Topic relevance config CRUD for multi-tenant projects

## Base URL and Version

Expand All @@ -23,7 +24,7 @@ This API currently uses two auth modes:
- Used by validator config and guardrails endpoints.
- The server validates your plaintext bearer token against a SHA-256 digest stored in `AUTH_TOKEN`.
2. multi-tenant API key auth (`X-API-KEY: <token>`)
- Used by ban list endpoints.
- Used by ban list and topic relevance config endpoints.
- The API key is verified against `KAAPI_AUTH_URL` and resolves tenant's scope (`organization_id`, `project_id`).

Notes:
Expand Down Expand Up @@ -99,7 +100,7 @@ Endpoint:
Optional filters:
- `ids=<uuid>&ids=<uuid>`
- `stage=input|output`
- `type=uli_slur_match|pii_remover|gender_assumption_bias|ban_list`
- `type=uli_slur_match|pii_remover|gender_assumption_bias|ban_list|llm_critic|topic_relevance`

Example:

Expand Down Expand Up @@ -182,6 +183,8 @@ Request fields:
Important:
- Runtime validators use `on_fail`.
- If you pass objects from config APIs, server normalization supports `on_fail_action` and strips non-runtime fields.
- For `topic_relevance`, pass `topic_relevance_config_id` only.
- The API resolves `configuration` + `prompt_schema_version` in `guardrails.py` before validator execution, so the validator always executes with both values.

Example:

Expand Down Expand Up @@ -321,7 +324,86 @@ curl -X DELETE "http://localhost:8001/api/v1/guardrails/ban_lists/<ban_list_id>"
-H "X-API-KEY: <api-key>"
```

## 6) End-to-End Usage Pattern
## 6) Topic Relevance Config APIs (multi-tenant)

These endpoints manage tenant-scoped topic relevance presets and use `X-API-KEY` auth.

Base path:
- `/api/v1/guardrails/topic_relevance_configs`

## 6.1 Create topic relevance config

Endpoint:
- `POST /api/v1/guardrails/topic_relevance_configs/`

Example:

```bash
curl -X POST "http://localhost:8001/api/v1/guardrails/topic_relevance_configs/" \
-H "X-API-KEY: <api-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Maternal Health Scope",
"description": "Topic guard for maternal health support bot",
"prompt_schema_version": 1,
"configuration": "Pregnancy care: Questions about prenatal care, ANC visits, nutrition, supplements, danger signs. Postpartum care: Questions about recovery after delivery, breastfeeding, and mother health checks."
}'
```

## 6.2 List topic relevance configs

Endpoint:
- `GET /api/v1/guardrails/topic_relevance_configs/?offset=0&limit=20`

Example:

```bash
curl -X GET "http://localhost:8001/api/v1/guardrails/topic_relevance_configs/?offset=0&limit=20" \
-H "X-API-KEY: <api-key>"
```

## 6.3 Get topic relevance config by id

Endpoint:
- `GET /api/v1/guardrails/topic_relevance_configs/{id}`

Example:

```bash
curl -X GET "http://localhost:8001/api/v1/guardrails/topic_relevance_configs/<topic_relevance_config_id>" \
-H "X-API-KEY: <api-key>"
```

## 6.4 Update topic relevance config

Endpoint:
- `PATCH /api/v1/guardrails/topic_relevance_configs/{id}`

Example:

```bash
curl -X PATCH "http://localhost:8001/api/v1/guardrails/topic_relevance_configs/<topic_relevance_config_id>" \
-H "X-API-KEY: <api-key>" \
-H "Content-Type: application/json" \
-d '{
"prompt_schema_version": 1,
"configuration": "Pregnancy care: Updated scope definition"
}'
```

## 6.5 Delete topic relevance config

Endpoint:
- `DELETE /api/v1/guardrails/topic_relevance_configs/{id}`

Example:

```bash
curl -X DELETE "http://localhost:8001/api/v1/guardrails/topic_relevance_configs/<topic_relevance_config_id>" \
-H "X-API-KEY: <api-key>"
```

## 7) End-to-End Usage Pattern

Recommended request flow:
1. Create/update validator configs via `/guardrails/validators/configs`.
Expand All @@ -330,15 +412,16 @@ Recommended request flow:
4. Use `safe_text` as downstream text.
5. If `rephrase_needed=true`, ask user to rephrase.
6. For `ban_list` validators without inline `banned_words`, create/manage a ban list first and pass `ban_list_id`.
7. For `topic_relevance`, create/manage a topic relevance config and pass `topic_relevance_config_id` at runtime. The server resolves the configuration string internally.

## 7) Common Errors
## 8) Common Errors

- `401 Missing Authorization header`
- Add `Authorization: Bearer <token>`.
- `401 Invalid authorization token`
- Verify plaintext token matches server-side hash.
- `401 Missing X-API-KEY header`
- Add `X-API-KEY: <api-key>` for ban list endpoints.
- Add `X-API-KEY: <api-key>` for ban list and topic relevance config endpoints.
- `401 Invalid API key`
- Verify the API key is valid in the upstream Kaapi auth service.
- `Invalid request_id`
Expand All @@ -347,14 +430,18 @@ Recommended request flow:
- Type+stage is unique per organization/project scope.
- `Validator not found`
- Confirm `id`, `organization_id`, and `project_id` match.
- `Topic relevance preset not found`
- Confirm topic relevance config `id` exists within your tenant scope.

## 8) Current Validator Types
## 9) Current Validator Types

From `validators.json`:
- `uli_slur_match`
- `pii_remover`
- `gender_assumption_bias`
- `ban_list`
- `llm_critic`
- `topic_relevance`

Source of truth:
- `backend/app/core/validators/validators.json`
Expand Down
3 changes: 3 additions & 0 deletions backend/app/api/docs/guardrails/run_guardrails.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Behavior notes:
- `suppress_pass_logs=true` skips persisting pass-case validator logs.
- The endpoint always saves a `request_log` entry for the run.
- Validator logs are also saved; with `suppress_pass_logs=true`, only fail-case validator logs are persisted. Otherwise, all validator logs are added.
- For `ban_list`, `ban_list_id` can be resolved to `banned_words` from tenant ban list configs.
- For `topic_relevance`, `topic_relevance_config_id` is required and is resolved to `configuration` + `prompt_schema_version` from tenant topic relevance configs in `guardrails.py`. Requires `OPENAI_API_KEY` to be configured; returns a validation failure with an explicit error if missing.
- For `llm_critic`, `OPENAI_API_KEY` must be configured; returns `success=false` with an explicit error if missing.
- `rephrase_needed=true` means the system could not safely auto-fix the input/output and wants the user to retry with a rephrased query.
- When `rephrase_needed=true`, `safe_text` contains the rephrase prompt shown to the user.

Expand Down
27 changes: 27 additions & 0 deletions backend/app/api/docs/topic_relevance_configs/create_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Creates a topic relevance configuration for the tenant resolved from `X-API-KEY`.

Behavior notes:
- Stores a topic relevance preset with `name`, `prompt_schema_version`, and `configuration`.
- `configuration` is a plain text scope sub-prompt (string).
- Tenant scope is enforced from the API key context.
- Duplicate configurations are rejected.

Common failure cases:
- Missing or invalid API key.
- Payload schema validation errors.
- Topic relevance with the same configuration already exists.

## Field glossary

**`configuration`**
A plain text string describing the topic scope the assistant is allowed to handle. This is injected into the LLM critic evaluation prompt at the `{{TOPIC_CONFIGURATION}}` placeholder to define what is considered in-scope.

Example:
```
This assistant only answers questions about maternal health and pregnancy care for NGO beneficiaries. It should not respond to questions about politics, general medicine unrelated to pregnancy, or financial topics.
```

**`prompt_schema_version`**
An integer selecting the versioned prompt template used to evaluate scope violations (e.g., `1` → `v1.md`). Controls the structure and wording of the LLM critic assessment prompt. Defaults to `1`. Only increment this when a new prompt template version has been added to the system.

Example: `1`
8 changes: 8 additions & 0 deletions backend/app/api/docs/topic_relevance_configs/delete_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Deletes a topic relevance configuration by id for the tenant resolved from `X-API-KEY`.

Behavior notes:
- Tenant scope is enforced from the API key context.

Common failure cases:
- Missing or invalid API key.
- Topic relevance preset not found in tenant's scope.
9 changes: 9 additions & 0 deletions backend/app/api/docs/topic_relevance_configs/get_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Fetches a single topic relevance configuration by id for the tenant resolved from `X-API-KEY`.

Behavior notes:
- Tenant scope is enforced from the API key context.

Common failure cases:
- Missing or invalid API key.
- Topic relevance preset not found in tenant's scope.
- Invalid id format.
11 changes: 11 additions & 0 deletions backend/app/api/docs/topic_relevance_configs/list_configs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Lists topic relevance configurations for the tenant resolved from `X-API-KEY`.

Behavior notes:
- Supports pagination via `offset` and `limit`.
- `offset` defaults to `0`.
- `limit` is optional; when omitted, no limit is applied.
- Tenant scope is enforced from the API key context.

Common failure cases:
- Missing or invalid API key.
- Invalid pagination values.
13 changes: 13 additions & 0 deletions backend/app/api/docs/topic_relevance_configs/update_config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Partially updates a topic relevance configuration by id for the tenant resolved from `X-API-KEY`.

Behavior notes:
- Supports patch-style updates; omitted fields remain unchanged.
- `configuration` should be provided as a plain text scope sub-prompt (string).
- Tenant scope is enforced from the API key context.
- Duplicate configurations are rejected.

Common failure cases:
- Missing or invalid API key.
- Topic relevance preset not found in tenant's scope.
- Payload schema validation errors.
- Topic relevance with the same configuration already exists.
9 changes: 8 additions & 1 deletion backend/app/api/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from fastapi import APIRouter

from app.api.routes import ban_lists, guardrails, validator_configs, utils
from app.api.routes import (
ban_lists,
guardrails,
topic_relevance_configs,
validator_configs,
utils,
)

api_router = APIRouter()
api_router.include_router(ban_lists.router)
api_router.include_router(guardrails.router)
api_router.include_router(topic_relevance_configs.router)
api_router.include_router(validator_configs.router)
api_router.include_router(utils.router)

Expand Down
Loading