Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 3 additions & 3 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,17 @@ 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
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).
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
from sqlalchemy.dialects import postgresql

# 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_version", sa.Integer(), nullable=False),
sa.Column(
"configuration", postgresql.JSONB(astext_type=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(
"name",
"organization_id",
"project_id",
name="uq_topic_relevance_name_org_project",
),
)

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_version", "topic_relevance", ["prompt_version"]
)
op.create_index("idx_topic_relevance_is_active", "topic_relevance", ["is_active"])


def downgrade() -> None:
op.drop_table("topic_relevance")
103 changes: 97 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,7 @@ 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`, you can pass either inline `scope_definitions` or a `topic_relevance_config_id` to resolve scope + prompt version from tenant config.

Example:

Expand Down Expand Up @@ -321,7 +323,91 @@ 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_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_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 +416,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 (or pass inline `scope_definitions`).

## 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 +434,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
2 changes: 2 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,8 @@ 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` can be resolved to `scope_definitions` + `prompt_version` from tenant topic relevance configs.
- `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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Creates a topic relevance configuration for the tenant resolved from `X-API-KEY`.

Behavior notes:
- Stores a topic relevance preset with `name`, `prompt_version`, and `configuration`.
- 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.
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.
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.
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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.
- 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
20 changes: 20 additions & 0 deletions backend/app/api/routes/guardrails.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
BanListSafetyValidatorConfig,
)
from app.crud.ban_list import ban_list_crud
from app.crud.topic_relevance import topic_relevance_crud
from app.crud.request_log import RequestLogCrud
from app.crud.validator_log import ValidatorLogCrud
from app.core.validators.config.topic_relevance_safety_validator_config import (
TopicRelevanceSafetyValidatorConfig,
)
from app.schemas.guardrail_config import GuardrailRequest, GuardrailResponse
from app.models.logging.request_log import RequestLogUpdate, RequestStatus
from app.models.logging.validator_log import ValidatorLog, ValidatorOutcome
Expand Down Expand Up @@ -46,6 +50,7 @@ def run_guardrails(
return APIResponse.failure_response(error="Invalid request_id")

_resolve_ban_list_banned_words(payload, session)
_resolve_topic_relevance_scope(payload, session)
return _validate_with_guard(
payload,
request_log_crud,
Expand Down Expand Up @@ -196,6 +201,21 @@ def _finalize(
)


def _resolve_topic_relevance_scope(payload: GuardrailRequest, session: Session) -> None:
for validator in payload.validators:
if not isinstance(validator, TopicRelevanceSafetyValidatorConfig):
continue

config = topic_relevance_crud.get(
session=session,
id=validator.topic_relevance_config_id,
organization_id=payload.organization_id,
project_id=payload.project_id,
)
validator.configuration = config.configuration
validator.prompt_version = config.prompt_version


def add_validator_logs(
guard: Guard,
request_log_id: UUID,
Expand Down
Loading
Loading