feat(ui): add guardrails support to project create/edit forms#25100
Conversation
The backend already supports guardrails on projects (PR BerriAI#25087), but the dashboard UI had no way to set them. This adds a guardrails multi-select field to the project form's Advanced Settings, following the same pattern used for team guardrails.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds a Guardrails multi-select field to the project create/edit forms, following the same UX pattern used for team guardrails. The UI wiring (form field, fetch on mount, type interfaces, filtering from KV metadata display) is generally well-structured, but there is a critical write-path mismatch that will silently prevent guardrails from ever being saved. Key issues:
Confidence Score: 4/5Not safe to merge as-is — the feature is silently broken: guardrails are never persisted due to a write-path mismatch with the backend. There are two P1 issues: (1) the API field is silently dropped by the backend because
|
| Filename | Overview |
|---|---|
| ui/litellm-dashboard/src/components/Projects/ProjectModals/projectFormUtils.ts | Sends guardrails as a top-level field, but the backend ProjectRequest types have no guardrails field and the DB schema has no guardrails column — the field will be silently dropped. Write path must nest guardrails inside metadata to match both the read path and the backend's metadata-only storage. |
| ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx | Read path correctly reads guardrails from metadataObj.guardrails and excludes them from the user-facing KV list, consistent with metadata storage. Pre-population will break if the backend stores guardrails at the top level instead of metadata. |
| ui/litellm-dashboard/src/components/Projects/ProjectModals/ProjectBaseForm.tsx | Correctly fetches guardrails list on mount and renders a mode="tags" Select field in Advanced Settings, matching the team form pattern. The useEffect only runs when accessToken changes, which is correct. |
| ui/litellm-dashboard/src/components/Projects/ProjectModals/projectFormUtils.test.ts | Two new tests for guardrails: one verifies inclusion when provided, one verifies omission when empty. Tests are correct against the current implementation but will need updating once the write path is fixed to nest guardrails in metadata. |
| ui/litellm-dashboard/src/components/Projects/ProjectModals/ProjectBaseForm.test.tsx | Adds a test verifying the Guardrails field renders in Advanced Settings, and mocks getGuardrailsList. Mock returns { guardrails: [] } which matches the expected API shape. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.ts | Adds guardrails?: string[] to ProjectCreateParams interface — correct interface update. The field will have no effect until the write path mismatch is fixed. |
| ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.ts | Adds guardrails?: string[] to ProjectUpdateParams interface — correct interface update matching the create hook. |
Sequence Diagram
sequenceDiagram
participant UI as ProjectBaseForm
participant Utils as projectFormUtils
participant API as Backend /project/new|update
participant DB as LiteLLM_ProjectTable
UI->>Utils: buildProjectApiParams(values)
Note over Utils: guardrails spread at top-level ❌ should be nested in metadata ✅
Utils-->>UI: { guardrails: [...], ...rest }
UI->>API: POST /project/new { guardrails: [...] }
Note over API: NewProjectRequest has no guardrails field Pydantic extra=ignore → silently drops it
API->>DB: INSERT (metadata={}, no guardrails)
DB-->>API: saved project row
API-->>UI: ProjectResponse (no guardrails)
UI->>UI: Edit modal opens
Note over UI: Reads metadataObj.guardrails → always [] Form never pre-populated
Comments Outside Diff (1)
-
ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts, line 25-44 (link)ProjectResponsetype missingguardrailsfieldThe
ProjectResponseTypeScript interface is missing aguardrailsfield. If the backend ever returns guardrails at the project object's top level (e.g. after PR feat(proxy): add project-level guardrails support #25087 lands), TypeScript won't be aware of it andproject.guardrailsaccess would be typed asany/ cause a compile error.Even if guardrails remain in
metadata, adding it here as an explicit optional field documents the contract:
Reviews (1): Last reviewed commit: "feat(ui): add guardrails support to proj..." | Re-trigger Greptile
| ...(values.guardrails && values.guardrails.length > 0 && { | ||
| guardrails: values.guardrails, | ||
| }), |
There was a problem hiding this comment.
Guardrails sent as top-level field but backend has no support for it
guardrails is sent as a top-level field in the API request body, but the backend request types (NewProjectRequest and UpdateProjectRequest in litellm/proxy/_types.py) have no guardrails field and LiteLLM_ProjectTable has no guardrails column in schema.prisma. Because LiteLLMPydanticObjectBase uses Pydantic v2's default extra="ignore", this field will be silently dropped — guardrails will never actually be saved.
The only storage location available for project-level guardrails is the metadata JSON column (same pattern as keys and teams, which store guardrails at metadata.guardrails). The read path in EditProjectModal.tsx already reads from metadataObj.guardrails, confirming the intended design. The write path needs to match:
// Instead of spreading guardrails at the top level, merge them into the metadata object
const metadata: Record<string, unknown> = {};
for (const entry of values.metadata ?? []) {
if (entry.key) metadata[entry.key] = entry.value;
}
if (values.guardrails && values.guardrails.length > 0) {
metadata.guardrails = values.guardrails;
}
return {
// ...
...(Object.keys(metadata).length > 0 && { metadata }),
};This aligns the write path with the read path in EditProjectModal.tsx (which reads metadataObj.guardrails) and matches the existing pattern in the codebase (e.g. key_edit_view.tsx sends guardrails inside metadata and reads them from keyData.metadata?.guardrails).
| const guardrails = (Array.isArray(metadataObj.guardrails) | ||
| ? metadataObj.guardrails | ||
| : []) as string[]; |
There was a problem hiding this comment.
Read/write mismatch will cause pre-population to fail when guardrails are stored top-level
The edit form reads guardrails from metadataObj.guardrails (inside the metadata JSON), but projectFormUtils.ts sends them as a top-level API field. If the backend is updated to store guardrails at the top-level column (not in metadata), the backend response would return project.guardrails — not inside project.metadata.guardrails — and this read will always return an empty array, breaking pre-population on edit.
To stay consistent: if the intended storage is metadata (which the existing read path already assumes), the write path in projectFormUtils.ts must also nest guardrails inside metadata. Alternatively, if guardrails will be a top-level project column, update the read to use project.guardrails and update the ProjectResponse TypeScript interface accordingly.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The backend already supports guardrails on projects (PR #25087), but the dashboard UI had no way to set them. This adds a guardrails multi-select field to the project form's Advanced Settings, following the same pattern used for team guardrails.
Relevant issues
Follows up on #25087 (backend support for project-level guardrails)
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
Changes
ProjectBaseForm.tsx— Addedguardrails?: string[]toProjectFormValuesinterface, addedgetGuardrailsListfetch on mount, addedSelect mode="tags"field in Advanced Settings (between Block Project and Model-Specific Limits)projectFormUtils.ts— Passguardrailsas a top-level field in API params (not nested in metadata), omit when emptyuseCreateProject.ts/useUpdateProject.ts— Addedguardrails?: string[]to param type interfacesEditProjectModal.tsx— Extract guardrails frommetadata.guardrailswhen editing, filter from user-facing KV metadata display, pre-populate form fieldprojectFormUtils.test.ts— 2 new tests: guardrails included when provided, omitted when emptyProjectBaseForm.test.tsx— 1 new test: guardrails field renders in Advanced Settings + mock forgetGuardrailsList