mirror of
https://github.com/tiennm99/litellm.git
synced 2026-06-17 16:48:54 +00:00
18f77ff7bc
* feat(mcp): add delegate_auth_to_upstream flag for PKCE passthrough Adds an opt-in per-server flag that lets clients (e.g. VS Code) complete PKCE directly with an upstream OAuth2 MCP server, instead of LiteLLM double-gating with its own API-key/SSO check. Only honored when auth_type=oauth2 and the operator explicitly sets the flag; mixed-target or non-oauth2 requests fail closed. - Adds the field to Pydantic models, Prisma schema, and a migration - New MCPRequestHandler._target_servers_delegate_auth_to_upstream gate that runs only when no x-litellm-api-key is present, so authenticated users still get user_id resolution + stored-credential lookup - Anonymous callers now see delegate servers in get_allowed_mcp_servers (scoped to delegate servers only; the upstream still enforces auth) - mcp_management_endpoints: allow anonymous /authorize and /token for delegate servers so VS Code can complete PKCE without a LiteLLM session - UI toggle (shown only for oauth2) + payload/view wiring - Tests covering: oauth2 on/off, non-oauth2 with flag, mixed targets, no resolvable target, explicit key precedence, and 401 emission Co-authored-by: Cursor <cursoragent@cursor.com> * Enforce oauth2 for delegated MCP auth bypass Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(mcp): close secondary Authorization bypass for delegate servers The delegate-auth bypass gated only on the primary `x-litellm-api-key` header, so a LiteLLM key sent via `Authorization: Bearer sk-...` (the secondary header) was silently dropped — skipping spend tracking and rate limiting. Gate on the resolved litellm_api_key (which considers both headers) so the bypass fires only when neither is present. Also update the existing "Authorization header present" test to reflect that an upstream OAuth token now flows through the existing oauth2 fallback (LiteLLM auth attempt → fail → anonymous), not via the delegate branch. Co-authored-by: Cursor <cursoragent@cursor.com> * Avoid duplicate MCP OAuth credential lookup Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(mcp): block delegate bypass for M2M and internal-only servers Two security issues flagged in code review: 1. High – client_credentials (M2M) servers must not be delegatable: LiteLLM auto-fetches the upstream token using stored credentials, so allowing anonymous bypass would let any external caller invoke tools authenticated as LiteLLM's service account. Fix: check `server.has_client_credentials` in `_target_servers_delegate_auth_to_upstream`, the anonymous allow-list in `get_allowed_mcp_servers`, and `_mcp_oauth_user_api_key_auth`. 2. Medium – internal-only servers exposed to public internet: The anonymous delegate allow-list was not filtering by `available_on_public_internet`, so external callers with an upstream OAuth token could invoke tools on servers marked internal-only. Fix: add `available_on_public_internet` guard to the anonymous delegate server list in `get_allowed_mcp_servers`. Tests added for both cases. Co-authored-by: Cursor <cursoragent@cursor.com> * Require public MCP delegate auth servers Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(mcp): align delegate auth path parsing with downstream routing `_extract_target_server_names_from_path` used a naive segments-based split while `server.py::_get_mcp_servers_in_path` uses a regex that allows server names with one embedded slash and comma-separated lists. With the old parser, a request to `/mcp/<delegated>/<garbage>` was parsed as targeting `<delegated>` by the auth gate (bypassing LiteLLM auth) while the routing layer parsed it as `<delegated>/<garbage>` — when that name did not resolve, the request fell back to the anonymous allow-list, which can include `allow_all_keys` servers that normally require a LiteLLM key. Replace the parser with the same regex logic as `_get_mcp_servers_in_path` so auth gating sees the exact target name(s) downstream routing sees. Add regression tests covering parser parity and the specific extra-path-segment bypass attempt. https://claude.ai/code/session_01SjyPmwfmrq8fveFgw9iHW9 * fix(mcp): close header/path TOCTOU in MCP delegate auth gate `_target_servers_delegate_auth_to_upstream` and `_target_servers_use_oauth2` trusted the `x-mcp-servers` header when present, but `server.py::extract_mcp_auth_context` overrides that header with the path-derived list for `/mcp/...` routes. An attacker could set `x-mcp-servers: <delegated>` while pointing the URL path at a non-delegate server, flipping the auth gate without changing the target downstream routing actually uses. Extract a shared `_resolve_target_server_names` helper that mirrors the downstream override (path-derived names for `/mcp/...` routes, header value otherwise). Add regression tests covering the TOCTOU attempt and the helper's path-vs-header precedence. https://claude.ai/code/session_01SjyPmwfmrq8fveFgw9iHW9 * Fix delegated MCP OAuth test mock Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(mcp): drop unreachable /{server}/mcp branch in auth path parser `_extract_target_server_names_from_path` also matched the ``/{server_name}/mcp`` form, but the downstream parser ``_get_mcp_servers_in_path`` only handles ``/mcp/...`` — and ``dynamic_mcp_route`` in ``proxy_server`` rewrites ``/{name}/mcp`` to ``/mcp/{name}`` on the scope before the MCP handler runs. Parsing the un-rewritten form on the auth side was therefore unreachable in production, and contradicted the docstring's claim of mirroring the downstream parser — exactly the kind of mismatch that risks a future header/path TOCTOU if any new entry point skips the rewrite. Drop the branch; the canonical ``/mcp/...`` path matches both parsers. Update the regression test to assert the new behavior. https://claude.ai/code/session_01SjyPmwfmrq8fveFgw9iHW9 * Fix MCP path auth target resolution Co-authored-by: Yassin Kortam <yassin@berri.ai> * fix(mcp): require auth for refresh_token grants on delegate-auth servers `_mcp_oauth_user_api_key_auth` gates the unauthenticated PKCE flow for ``delegate_auth_to_upstream`` servers, but the bypass applied to BOTH ``/authorize`` and ``/token`` regardless of grant type. ``mcp_token`` accepts ``grant_type=refresh_token`` as well as ``authorization_code``, and ``exchange_token_with_server`` attaches the server's stored ``client_secret`` to whatever is forwarded upstream. An unauthenticated caller holding a refresh token issued to that OAuth client could mint fresh upstream access tokens through LiteLLM. Limit the anonymous bypass on ``/token`` to ``grant_type=authorization_code`` (the only grant PKCE actually protects via ``code_verifier``); fall through to normal LiteLLM auth for ``refresh_token`` and any other grant. ``/authorize`` continues to allow anonymous PKCE redirects. https://claude.ai/code/session_01SjyPmwfmrq8fveFgw9iHW9 * fix(ui): clear delegate_auth_to_upstream when switching off oauth2 The ``delegate_auth_to_upstream`` form field is rendered inside an ``isOAuth2 && (...)`` conditional, so the Form.Item unmounts when the user changes ``auth_type`` away from ``oauth2``. The follow-up ``form.setFieldValue("delegate_auth_to_upstream", false)`` runs after the field has already deregistered, so ``onFinish`` receives ``undefined`` and the fallback ``?? mcpServer.delegate_auth_to_upstream`` preserved the old ``true``. The flag then persisted in the database for a non-oauth2 server and silently re-activated if ``auth_type`` was later switched back to ``oauth2``. In the edit payload, force the flag to ``false`` whenever ``auth_type !== oauth2``; only trust the form value (and the existing DB fallback) when the server is actually oauth2. Backend defense-in-depth already ignores the flag for non-oauth2 servers, but the DB state should stay clean too. https://claude.ai/code/session_01SjyPmwfmrq8fveFgw9iHW9 * Fix MCP delegate auth reset on edit Co-authored-by: Yassin Kortam <yassin@berri.ai> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Yassin Kortam <yassin@berri.ai> Co-authored-by: Claude <claude@anthropic.com>
1378 lines
53 KiB
Plaintext
1378 lines
53 KiB
Plaintext
datasource client {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
generator client {
|
||
provider = "prisma-client-py"
|
||
binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x", "linux-musl", "linux-musl-openssl-3.0.x"]
|
||
}
|
||
|
||
// Budget / Rate Limits for an org
|
||
model LiteLLM_BudgetTable {
|
||
budget_id String @id @default(uuid())
|
||
max_budget Float?
|
||
soft_budget Float?
|
||
max_parallel_requests Int?
|
||
tpm_limit BigInt?
|
||
rpm_limit BigInt?
|
||
model_max_budget Json?
|
||
budget_duration String?
|
||
budget_reset_at DateTime?
|
||
allowed_models String[] @default([]) // per-member model scope; empty = inherit team models
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
organization LiteLLM_OrganizationTable[] // multiple orgs can have the same budget
|
||
projects LiteLLM_ProjectTable[] // multiple projects can have the same budget
|
||
keys LiteLLM_VerificationToken[] // multiple keys can have the same budget
|
||
end_users LiteLLM_EndUserTable[] // multiple end-users can have the same budget
|
||
tags LiteLLM_TagTable[] // multiple tags can have the same budget
|
||
team_membership LiteLLM_TeamMembership[] // budgets of Users within a Team
|
||
organization_membership LiteLLM_OrganizationMembership[] // budgets of Users within a Organization
|
||
}
|
||
|
||
// Models on proxy
|
||
model LiteLLM_CredentialsTable {
|
||
credential_id String @id @default(uuid())
|
||
credential_name String @unique
|
||
credential_values Json
|
||
credential_info Json?
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
}
|
||
|
||
// Models on proxy
|
||
model LiteLLM_ProxyModelTable {
|
||
model_id String @id @default(uuid())
|
||
model_name String
|
||
litellm_params Json
|
||
model_info Json?
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
}
|
||
|
||
|
||
// Agents on proxy
|
||
model LiteLLM_AgentsTable {
|
||
agent_id String @id @default(uuid())
|
||
agent_name String @unique
|
||
litellm_params Json?
|
||
agent_card_params Json
|
||
static_headers Json? @default("{}")
|
||
extra_headers String[] @default([])
|
||
agent_access_groups String[] @default([])
|
||
object_permission_id String?
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
spend Float @default(0.0)
|
||
tpm_limit Int?
|
||
rpm_limit Int?
|
||
session_tpm_limit Int?
|
||
session_rpm_limit Int?
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
}
|
||
|
||
model LiteLLM_OrganizationTable {
|
||
organization_id String @id @default(uuid())
|
||
organization_alias String
|
||
budget_id String
|
||
metadata Json @default("{}")
|
||
models String[]
|
||
spend Float @default(0.0)
|
||
model_spend Json @default("{}")
|
||
object_permission_id String?
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
teams LiteLLM_TeamTable[]
|
||
users LiteLLM_UserTable[]
|
||
keys LiteLLM_VerificationToken[]
|
||
members LiteLLM_OrganizationMembership[] @relation("OrganizationToMembership")
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
}
|
||
|
||
// Model info for teams, just has model aliases for now.
|
||
model LiteLLM_ModelTable {
|
||
id Int @id @default(autoincrement())
|
||
model_aliases Json? @map("aliases")
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
team LiteLLM_TeamTable?
|
||
}
|
||
|
||
|
||
// Assign prod keys to groups, not individuals
|
||
model LiteLLM_TeamTable {
|
||
team_id String @id @default(uuid())
|
||
team_alias String?
|
||
organization_id String?
|
||
object_permission_id String?
|
||
admins String[]
|
||
members String[]
|
||
members_with_roles Json @default("{}")
|
||
metadata Json @default("{}")
|
||
max_budget Float?
|
||
soft_budget Float?
|
||
spend Float @default(0.0)
|
||
models String[]
|
||
max_parallel_requests Int?
|
||
tpm_limit BigInt?
|
||
rpm_limit BigInt?
|
||
budget_duration String?
|
||
budget_reset_at DateTime?
|
||
blocked Boolean @default(false)
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
model_spend Json @default("{}")
|
||
model_max_budget Json @default("{}")
|
||
router_settings Json? @default("{}")
|
||
team_member_permissions String[] @default([])
|
||
access_group_ids String[] @default([])
|
||
policies String[] @default([])
|
||
default_team_member_models String[] @default([]) // default allowed_models for newly added team members; empty = no per-member restriction
|
||
budget_limits Json? // per-model budget limits for the team
|
||
model_id Int? @unique // id for LiteLLM_ModelTable -> stores team-level model aliases
|
||
allow_team_guardrail_config Boolean @default(false) // if true, team admin can configure guardrails for this team
|
||
litellm_organization_table LiteLLM_OrganizationTable? @relation(fields: [organization_id], references: [organization_id])
|
||
litellm_model_table LiteLLM_ModelTable? @relation(fields: [model_id], references: [id])
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
projects LiteLLM_ProjectTable[]
|
||
|
||
@@index([organization_id])
|
||
@@index([team_alias])
|
||
@@index([created_at])
|
||
}
|
||
|
||
// Projects sit between teams and keys for use-case management
|
||
model LiteLLM_ProjectTable {
|
||
project_id String @id @default(uuid())
|
||
project_alias String?
|
||
description String?
|
||
team_id String?
|
||
budget_id String?
|
||
metadata Json @default("{}")
|
||
models String[]
|
||
spend Float @default(0.0)
|
||
model_spend Json @default("{}")
|
||
model_rpm_limit Json @default("{}")
|
||
model_tpm_limit Json @default("{}")
|
||
blocked Boolean @default(false)
|
||
object_permission_id String?
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String
|
||
|
||
// Relations
|
||
litellm_team_table LiteLLM_TeamTable? @relation(fields: [team_id], references: [team_id])
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
keys LiteLLM_VerificationToken[]
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
}
|
||
|
||
// Audit table for deleted teams - preserves spend and team information for historical tracking
|
||
model LiteLLM_DeletedTeamTable {
|
||
id String @id @default(uuid())
|
||
team_id String // Original team_id
|
||
team_alias String?
|
||
organization_id String?
|
||
object_permission_id String?
|
||
admins String[]
|
||
members String[]
|
||
members_with_roles Json @default("{}")
|
||
metadata Json @default("{}")
|
||
max_budget Float?
|
||
soft_budget Float?
|
||
spend Float @default(0.0)
|
||
models String[]
|
||
max_parallel_requests Int?
|
||
tpm_limit BigInt?
|
||
rpm_limit BigInt?
|
||
budget_duration String?
|
||
budget_reset_at DateTime?
|
||
blocked Boolean @default(false)
|
||
model_spend Json @default("{}")
|
||
model_max_budget Json @default("{}")
|
||
router_settings Json? @default("{}")
|
||
team_member_permissions String[] @default([])
|
||
access_group_ids String[] @default([])
|
||
policies String[] @default([])
|
||
model_id Int? // id for LiteLLM_ModelTable -> stores team-level model aliases
|
||
allow_team_guardrail_config Boolean @default(false)
|
||
|
||
// Original timestamps from team creation/updates
|
||
created_at DateTime? @map("created_at")
|
||
updated_at DateTime? @map("updated_at")
|
||
|
||
// Deletion metadata
|
||
deleted_at DateTime @default(now()) @map("deleted_at")
|
||
deleted_by String? @map("deleted_by") // User who deleted the team
|
||
deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion
|
||
litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided
|
||
|
||
@@index([team_id])
|
||
@@index([deleted_at])
|
||
@@index([organization_id])
|
||
@@index([team_alias])
|
||
@@index([created_at])
|
||
}
|
||
|
||
// Track spend, rate limit, budget Users
|
||
model LiteLLM_UserTable {
|
||
user_id String @id
|
||
user_alias String?
|
||
team_id String?
|
||
sso_user_id String? @unique
|
||
organization_id String?
|
||
object_permission_id String?
|
||
password String?
|
||
teams String[] @default([])
|
||
user_role String?
|
||
max_budget Float?
|
||
spend Float @default(0.0)
|
||
user_email String?
|
||
models String[]
|
||
metadata Json @default("{}")
|
||
max_parallel_requests Int?
|
||
tpm_limit BigInt?
|
||
rpm_limit BigInt?
|
||
budget_duration String?
|
||
budget_reset_at DateTime?
|
||
allowed_cache_controls String[] @default([])
|
||
policies String[] @default([])
|
||
model_spend Json @default("{}")
|
||
model_max_budget Json @default("{}")
|
||
created_at DateTime? @default(now()) @map("created_at")
|
||
updated_at DateTime? @default(now()) @updatedAt @map("updated_at")
|
||
|
||
// relations
|
||
litellm_organization_table LiteLLM_OrganizationTable? @relation(fields: [organization_id], references: [organization_id])
|
||
organization_memberships LiteLLM_OrganizationMembership[]
|
||
invitations_created LiteLLM_InvitationLink[] @relation("CreatedBy")
|
||
invitations_updated LiteLLM_InvitationLink[] @relation("UpdatedBy")
|
||
invitations_user LiteLLM_InvitationLink[] @relation("UserId")
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
}
|
||
|
||
model LiteLLM_ObjectPermissionTable {
|
||
object_permission_id String @id @default(uuid())
|
||
mcp_servers String[] @default([])
|
||
mcp_access_groups String[] @default([])
|
||
mcp_tool_permissions Json? // Tool-level permissions for MCP servers. Format: {"server_id": ["tool_name_1", "tool_name_2"]}
|
||
vector_stores String[] @default([])
|
||
agents String[] @default([])
|
||
agent_access_groups String[] @default([])
|
||
models String[] @default([])
|
||
blocked_tools String[] @default([]) // Tool names blocked for any key/team/user with this permission
|
||
mcp_toolsets String[] @default([]) // Toolset IDs granted to this key/team/user
|
||
search_tools String[] @default([]) // search_tool_name values this key/team/user may call
|
||
teams LiteLLM_TeamTable[]
|
||
projects LiteLLM_ProjectTable[]
|
||
verification_tokens LiteLLM_VerificationToken[]
|
||
organizations LiteLLM_OrganizationTable[]
|
||
users LiteLLM_UserTable[]
|
||
end_users LiteLLM_EndUserTable[]
|
||
agents_table LiteLLM_AgentsTable[]
|
||
}
|
||
|
||
// Holds the MCP server configuration
|
||
model LiteLLM_MCPServerTable {
|
||
server_id String @id @default(uuid())
|
||
server_name String?
|
||
alias String?
|
||
description String?
|
||
instructions String?
|
||
url String?
|
||
spec_path String?
|
||
transport String @default("sse")
|
||
auth_type String?
|
||
credentials Json? @default("{}")
|
||
created_at DateTime? @default(now()) @map("created_at")
|
||
created_by String?
|
||
updated_at DateTime? @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String?
|
||
mcp_info Json? @default("{}")
|
||
mcp_access_groups String[]
|
||
allowed_tools String[] @default([])
|
||
tool_name_to_display_name Json? @default("{}")
|
||
tool_name_to_description Json? @default("{}")
|
||
extra_headers String[] @default([])
|
||
static_headers Json? @default("{}")
|
||
// Health check status
|
||
status String? @default("unknown")
|
||
last_health_check DateTime?
|
||
health_check_error String?
|
||
// Stdio-specific fields
|
||
command String?
|
||
args String[] @default([])
|
||
env Json? @default("{}")
|
||
authorization_url String?
|
||
token_url String?
|
||
registration_url String?
|
||
allow_all_keys Boolean @default(false)
|
||
available_on_public_internet Boolean @default(true)
|
||
delegate_auth_to_upstream Boolean @default(false)
|
||
is_byok Boolean @default(false)
|
||
byok_description String[] @default([])
|
||
byok_api_key_help_url String?
|
||
source_url String?
|
||
// BYOM submission lifecycle
|
||
approval_status String? @default("active")
|
||
submitted_by String?
|
||
submitted_at DateTime?
|
||
reviewed_at DateTime?
|
||
review_notes String?
|
||
|
||
@@index([approval_status])
|
||
}
|
||
|
||
// Named collection of {server_id, tool_name} pairs that can be granted to keys/teams
|
||
model LiteLLM_MCPToolsetTable {
|
||
toolset_id String @id @default(uuid())
|
||
toolset_name String @unique
|
||
description String?
|
||
tools Json @default("[]") // [{server_id: string, tool_name: string}]
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
}
|
||
|
||
// Per-user BYOK credentials for MCP servers
|
||
model LiteLLM_MCPUserCredentials {
|
||
id String @id @default(uuid())
|
||
user_id String
|
||
server_id String
|
||
credential_b64 String
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
|
||
@@unique([user_id, server_id])
|
||
}
|
||
|
||
// Generate Tokens for Proxy
|
||
model LiteLLM_VerificationToken {
|
||
token String @id
|
||
key_name String?
|
||
key_alias String?
|
||
soft_budget_cooldown Boolean @default(false) // key-level state on if budget alerts need to be cooled down
|
||
spend Float @default(0.0)
|
||
expires DateTime?
|
||
models String[]
|
||
aliases Json @default("{}")
|
||
config Json @default("{}")
|
||
router_settings Json? @default("{}")
|
||
user_id String?
|
||
team_id String?
|
||
agent_id String?
|
||
project_id String?
|
||
permissions Json @default("{}")
|
||
max_parallel_requests Int?
|
||
metadata Json @default("{}")
|
||
blocked Boolean?
|
||
tpm_limit BigInt?
|
||
rpm_limit BigInt?
|
||
max_budget Float?
|
||
budget_duration String?
|
||
budget_reset_at DateTime?
|
||
allowed_cache_controls String[] @default([])
|
||
allowed_routes String[] @default([])
|
||
policies String[] @default([])
|
||
access_group_ids String[] @default([])
|
||
model_spend Json @default("{}")
|
||
model_max_budget Json @default("{}")
|
||
budget_id String?
|
||
organization_id String?
|
||
object_permission_id String?
|
||
created_at DateTime? @default(now()) @map("created_at")
|
||
created_by String?
|
||
updated_at DateTime? @default(now()) @updatedAt @map("updated_at")
|
||
updated_by String?
|
||
last_active DateTime? // When this key was last used
|
||
rotation_count Int? @default(0) // Number of times key has been rotated
|
||
auto_rotate Boolean? @default(false) // Whether this key should be auto-rotated
|
||
rotation_interval String? // How often to rotate (e.g., "30d", "90d")
|
||
last_rotation_at DateTime? // When this key was last rotated
|
||
key_rotation_at DateTime? // When this key should next be rotated
|
||
budget_limits Json? // per-model budget limits for the key
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
litellm_organization_table LiteLLM_OrganizationTable? @relation(fields: [organization_id], references: [organization_id])
|
||
litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id])
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
jwt_key_mappings LiteLLM_JWTKeyMapping[]
|
||
|
||
// SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub"
|
||
// SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2
|
||
@@index([user_id, team_id])
|
||
|
||
// SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."team_id" = $1 OFFSET $2
|
||
@@index([team_id])
|
||
|
||
// SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE (("public"."LiteLLM_VerificationToken"."expires" IS NULL OR "public"."LiteLLM_VerificationToken"."expires" > $1) AND "public"."LiteLLM_VerificationToken"."budget_reset_at" < $2) OFFSET $3
|
||
@@index([budget_reset_at, expires])
|
||
}
|
||
|
||
model LiteLLM_JWTKeyMapping {
|
||
id String @id @default(uuid())
|
||
jwt_claim_name String // e.g. "sub", "email"
|
||
jwt_claim_value String // The claim value to match
|
||
token String // Hashed virtual key (FK)
|
||
description String?
|
||
is_active Boolean @default(true)
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
|
||
litellm_verification_token LiteLLM_VerificationToken @relation(fields: [token], references: [token])
|
||
|
||
@@unique([jwt_claim_name, jwt_claim_value])
|
||
@@index([jwt_claim_name, jwt_claim_value, is_active])
|
||
}
|
||
|
||
// Deprecated keys during grace period - allows old key to work until revoke_at
|
||
model LiteLLM_DeprecatedVerificationToken {
|
||
id String @id @default(uuid())
|
||
token String // Hashed old key
|
||
active_token_id String // Current token hash in LiteLLM_VerificationToken
|
||
revoke_at DateTime // When the old key stops working
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
|
||
@@unique([token])
|
||
@@index([token, revoke_at])
|
||
@@index([revoke_at])
|
||
}
|
||
|
||
// Audit table for deleted keys - preserves spend and key information for historical tracking
|
||
model LiteLLM_DeletedVerificationToken {
|
||
id String @id @default(uuid())
|
||
token String // Original token (hashed)
|
||
key_name String?
|
||
key_alias String?
|
||
soft_budget_cooldown Boolean @default(false)
|
||
spend Float @default(0.0)
|
||
expires DateTime?
|
||
models String[]
|
||
aliases Json @default("{}")
|
||
config Json @default("{}")
|
||
user_id String?
|
||
team_id String?
|
||
agent_id String?
|
||
project_id String?
|
||
permissions Json @default("{}")
|
||
max_parallel_requests Int?
|
||
metadata Json @default("{}")
|
||
blocked Boolean?
|
||
tpm_limit BigInt?
|
||
rpm_limit BigInt?
|
||
max_budget Float?
|
||
budget_duration String?
|
||
budget_reset_at DateTime?
|
||
allowed_cache_controls String[] @default([])
|
||
allowed_routes String[] @default([])
|
||
policies String[] @default([])
|
||
access_group_ids String[] @default([])
|
||
model_spend Json @default("{}")
|
||
model_max_budget Json @default("{}")
|
||
router_settings Json? @default("{}")
|
||
budget_id String?
|
||
organization_id String?
|
||
object_permission_id String?
|
||
created_at DateTime? // Original creation timestamp
|
||
created_by String? // Original creator
|
||
updated_at DateTime? // Last update timestamp before deletion
|
||
updated_by String? // Last user who updated before deletion
|
||
last_active DateTime? // When this key was last used before deletion
|
||
rotation_count Int? @default(0)
|
||
auto_rotate Boolean? @default(false)
|
||
rotation_interval String?
|
||
last_rotation_at DateTime?
|
||
key_rotation_at DateTime?
|
||
|
||
// Deletion metadata
|
||
deleted_at DateTime @default(now()) @map("deleted_at")
|
||
deleted_by String? @map("deleted_by") // User who deleted the key
|
||
deleted_by_api_key String? @map("deleted_by_api_key") // API key hash that performed the deletion
|
||
litellm_changed_by String? @map("litellm_changed_by") // From litellm-changed-by header if provided
|
||
|
||
@@index([token])
|
||
@@index([deleted_at])
|
||
@@index([user_id])
|
||
@@index([team_id])
|
||
@@index([organization_id])
|
||
@@index([key_alias])
|
||
@@index([created_at])
|
||
}
|
||
|
||
model LiteLLM_EndUserTable {
|
||
user_id String @id
|
||
alias String? // admin-facing alias
|
||
spend Float @default(0.0)
|
||
allowed_model_region String? // require all user requests to use models in this specific region
|
||
default_model String? // use along with 'allowed_model_region'. if no available model in region, default to this model.
|
||
budget_id String?
|
||
object_permission_id String?
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id])
|
||
blocked Boolean @default(false)
|
||
}
|
||
|
||
// Track tags with budgets and spend
|
||
model LiteLLM_TagTable {
|
||
tag_name String @id
|
||
description String?
|
||
models String[]
|
||
model_info Json? // maps model_id to model_name
|
||
spend Float @default(0.0)
|
||
budget_id String?
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
created_at DateTime @default(now()) @map("created_at")
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt @map("updated_at")
|
||
}
|
||
|
||
// store proxy config.yaml
|
||
model LiteLLM_Config {
|
||
param_name String @id
|
||
param_value Json?
|
||
}
|
||
|
||
// View spend, model, api_key per request
|
||
model LiteLLM_SpendLogs {
|
||
request_id String @id
|
||
call_type String
|
||
api_key String @default ("") // Hashed API Token. Not the actual Virtual Key. Equivalent to 'token' column in LiteLLM_VerificationToken
|
||
spend Float @default(0.0)
|
||
total_tokens Int @default(0)
|
||
prompt_tokens Int @default(0)
|
||
completion_tokens Int @default(0)
|
||
startTime DateTime // Assuming start_time is a DateTime field
|
||
endTime DateTime // Assuming end_time is a DateTime field
|
||
request_duration_ms Int?
|
||
completionStartTime DateTime? // Assuming completionStartTime is a DateTime field
|
||
model String @default("")
|
||
model_id String? @default("") // the model id stored in proxy model db
|
||
model_group String? @default("") // public model_name / model_group
|
||
custom_llm_provider String? @default("") // litellm used custom_llm_provider
|
||
api_base String? @default("")
|
||
user String? @default("")
|
||
metadata Json? @default("{}") // project_id stored here
|
||
cache_hit String? @default("")
|
||
cache_key String? @default("")
|
||
request_tags Json? @default("[]")
|
||
team_id String?
|
||
organization_id String?
|
||
end_user String?
|
||
requester_ip_address String?
|
||
messages Json? @default("{}")
|
||
response Json? @default("{}")
|
||
session_id String?
|
||
status String?
|
||
mcp_namespaced_tool_name String?
|
||
agent_id String?
|
||
proxy_server_request Json? @default("{}")
|
||
@@index([startTime])
|
||
@@index([startTime, request_id])
|
||
@@index([end_user])
|
||
@@index([session_id])
|
||
}
|
||
|
||
// View spend, model, api_key per request
|
||
model LiteLLM_ErrorLogs {
|
||
request_id String @id @default(uuid())
|
||
startTime DateTime // Assuming start_time is a DateTime field
|
||
endTime DateTime // Assuming end_time is a DateTime field
|
||
api_base String @default("")
|
||
model_group String @default("") // public model_name / model_group
|
||
litellm_model_name String @default("") // model passed to litellm
|
||
model_id String @default("") // ID of model in ProxyModelTable
|
||
request_kwargs Json @default("{}")
|
||
exception_type String @default("")
|
||
exception_string String @default("")
|
||
status_code String @default("")
|
||
}
|
||
|
||
// Beta - allow team members to request access to a model
|
||
model LiteLLM_UserNotifications {
|
||
request_id String @id
|
||
user_id String
|
||
models String[]
|
||
justification String
|
||
status String // approved, disapproved, pending
|
||
}
|
||
|
||
model LiteLLM_TeamMembership {
|
||
// Use this table to track the Internal User's Spend within a Team + Set Budgets, rpm limits for the user within the team
|
||
user_id String
|
||
team_id String
|
||
spend Float @default(0.0)
|
||
total_spend Float @default(0.0)
|
||
budget_id String?
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
@@id([user_id, team_id])
|
||
}
|
||
|
||
model LiteLLM_OrganizationMembership {
|
||
// Use this table to track Internal User and Organization membership. Helps tracking a users role within an Organization
|
||
user_id String
|
||
organization_id String
|
||
user_role String?
|
||
spend Float? @default(0.0)
|
||
budget_id String?
|
||
created_at DateTime? @default(now()) @map("created_at")
|
||
updated_at DateTime? @default(now()) @updatedAt @map("updated_at")
|
||
|
||
// relations
|
||
user LiteLLM_UserTable @relation(fields: [user_id], references: [user_id])
|
||
organization LiteLLM_OrganizationTable @relation("OrganizationToMembership", fields: [organization_id], references: [organization_id])
|
||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||
|
||
|
||
|
||
@@id([user_id, organization_id])
|
||
@@unique([user_id, organization_id])
|
||
}
|
||
|
||
model LiteLLM_InvitationLink {
|
||
// use this table to track invite links sent by admin for people to join the proxy
|
||
id String @id @default(uuid())
|
||
user_id String
|
||
is_accepted Boolean @default(false)
|
||
accepted_at DateTime? // when link is claimed (user successfully onboards via link)
|
||
expires_at DateTime // till when is link valid
|
||
created_at DateTime // when did admin create the link
|
||
created_by String // who created the link
|
||
updated_at DateTime // when was invite status updated
|
||
updated_by String // who updated the status (admin/user who accepted invite)
|
||
|
||
// Relations
|
||
liteLLM_user_table_user LiteLLM_UserTable @relation("UserId", fields: [user_id], references: [user_id])
|
||
liteLLM_user_table_created LiteLLM_UserTable @relation("CreatedBy", fields: [created_by], references: [user_id])
|
||
liteLLM_user_table_updated LiteLLM_UserTable @relation("UpdatedBy", fields: [updated_by], references: [user_id])
|
||
}
|
||
|
||
|
||
model LiteLLM_AuditLog {
|
||
id String @id @default(uuid())
|
||
updated_at DateTime @default(now())
|
||
changed_by String @default("") // user or system that performed the action
|
||
changed_by_api_key String @default("") // api key hash that performed the action
|
||
action String // create, update, delete
|
||
table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME,
|
||
object_id String // id of the object being audited. This can be the key id, team id, user id, model id
|
||
before_value Json? // value of the row
|
||
updated_values Json? // value of the row after change
|
||
}
|
||
|
||
// Track daily user spend metrics per model and key
|
||
model LiteLLM_DailyUserSpend {
|
||
id String @id @default(uuid())
|
||
user_id String?
|
||
date String
|
||
api_key String
|
||
model String?
|
||
model_group String?
|
||
custom_llm_provider String?
|
||
mcp_namespaced_tool_name String?
|
||
endpoint String?
|
||
prompt_tokens BigInt @default(0)
|
||
completion_tokens BigInt @default(0)
|
||
cache_read_input_tokens BigInt @default(0)
|
||
cache_creation_input_tokens BigInt @default(0)
|
||
spend Float @default(0.0)
|
||
api_requests BigInt @default(0)
|
||
successful_requests BigInt @default(0)
|
||
failed_requests BigInt @default(0)
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@unique([user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint])
|
||
@@index([date])
|
||
@@index([user_id, date])
|
||
@@index([api_key])
|
||
@@index([model])
|
||
@@index([mcp_namespaced_tool_name])
|
||
@@index([endpoint])
|
||
}
|
||
|
||
// Track daily organization spend metrics per model and key
|
||
model LiteLLM_DailyOrganizationSpend {
|
||
id String @id @default(uuid())
|
||
organization_id String?
|
||
date String
|
||
api_key String
|
||
model String?
|
||
model_group String?
|
||
custom_llm_provider String?
|
||
mcp_namespaced_tool_name String?
|
||
endpoint String?
|
||
prompt_tokens BigInt @default(0)
|
||
completion_tokens BigInt @default(0)
|
||
cache_read_input_tokens BigInt @default(0)
|
||
cache_creation_input_tokens BigInt @default(0)
|
||
spend Float @default(0.0)
|
||
api_requests BigInt @default(0)
|
||
successful_requests BigInt @default(0)
|
||
failed_requests BigInt @default(0)
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@unique([organization_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint])
|
||
@@index([date])
|
||
@@index([organization_id, date])
|
||
@@index([api_key])
|
||
@@index([model])
|
||
@@index([mcp_namespaced_tool_name])
|
||
@@index([endpoint])
|
||
}
|
||
|
||
// Track daily end user (customer) spend metrics per model and key
|
||
model LiteLLM_DailyEndUserSpend {
|
||
id String @id @default(uuid())
|
||
end_user_id String?
|
||
date String
|
||
api_key String
|
||
model String?
|
||
model_group String?
|
||
custom_llm_provider String?
|
||
mcp_namespaced_tool_name String?
|
||
endpoint String?
|
||
prompt_tokens BigInt @default(0)
|
||
completion_tokens BigInt @default(0)
|
||
cache_read_input_tokens BigInt @default(0)
|
||
cache_creation_input_tokens BigInt @default(0)
|
||
spend Float @default(0.0)
|
||
api_requests BigInt @default(0)
|
||
successful_requests BigInt @default(0)
|
||
failed_requests BigInt @default(0)
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
@@unique([end_user_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint])
|
||
@@index([date])
|
||
@@index([end_user_id, date])
|
||
@@index([api_key])
|
||
@@index([model])
|
||
@@index([mcp_namespaced_tool_name])
|
||
@@index([endpoint])
|
||
}
|
||
|
||
// Track daily agent spend metrics per model and key
|
||
model LiteLLM_DailyAgentSpend {
|
||
id String @id @default(uuid())
|
||
agent_id String?
|
||
date String
|
||
api_key String
|
||
model String?
|
||
model_group String?
|
||
custom_llm_provider String?
|
||
mcp_namespaced_tool_name String?
|
||
endpoint String?
|
||
prompt_tokens BigInt @default(0)
|
||
completion_tokens BigInt @default(0)
|
||
cache_read_input_tokens BigInt @default(0)
|
||
cache_creation_input_tokens BigInt @default(0)
|
||
spend Float @default(0.0)
|
||
api_requests BigInt @default(0)
|
||
successful_requests BigInt @default(0)
|
||
failed_requests BigInt @default(0)
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
@@unique([agent_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint])
|
||
@@index([date])
|
||
@@index([agent_id, date])
|
||
@@index([api_key])
|
||
@@index([model])
|
||
@@index([mcp_namespaced_tool_name])
|
||
@@index([endpoint])
|
||
}
|
||
|
||
// Track daily team spend metrics per model and key
|
||
model LiteLLM_DailyTeamSpend {
|
||
id String @id @default(uuid())
|
||
team_id String?
|
||
date String
|
||
api_key String
|
||
model String?
|
||
model_group String?
|
||
custom_llm_provider String?
|
||
mcp_namespaced_tool_name String?
|
||
endpoint String?
|
||
prompt_tokens BigInt @default(0)
|
||
completion_tokens BigInt @default(0)
|
||
cache_read_input_tokens BigInt @default(0)
|
||
cache_creation_input_tokens BigInt @default(0)
|
||
spend Float @default(0.0)
|
||
api_requests BigInt @default(0)
|
||
successful_requests BigInt @default(0)
|
||
failed_requests BigInt @default(0)
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@unique([team_id, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint])
|
||
@@index([date])
|
||
@@index([team_id, date])
|
||
@@index([api_key])
|
||
@@index([model])
|
||
@@index([mcp_namespaced_tool_name])
|
||
@@index([endpoint])
|
||
}
|
||
|
||
// Track daily team spend metrics per model and key
|
||
model LiteLLM_DailyTagSpend {
|
||
id String @id @default(uuid())
|
||
request_id String?
|
||
tag String?
|
||
date String
|
||
api_key String
|
||
model String?
|
||
model_group String?
|
||
custom_llm_provider String?
|
||
mcp_namespaced_tool_name String?
|
||
endpoint String?
|
||
prompt_tokens BigInt @default(0)
|
||
completion_tokens BigInt @default(0)
|
||
cache_read_input_tokens BigInt @default(0)
|
||
cache_creation_input_tokens BigInt @default(0)
|
||
spend Float @default(0.0)
|
||
api_requests BigInt @default(0)
|
||
successful_requests BigInt @default(0)
|
||
failed_requests BigInt @default(0)
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@unique([tag, date, api_key, model, custom_llm_provider, mcp_namespaced_tool_name, endpoint])
|
||
@@index([date])
|
||
@@index([tag, date])
|
||
@@index([api_key])
|
||
@@index([model])
|
||
@@index([mcp_namespaced_tool_name])
|
||
@@index([endpoint])
|
||
}
|
||
|
||
|
||
// Track the status of cron jobs running. Only allow one pod to run the job at a time
|
||
model LiteLLM_CronJob {
|
||
cronjob_id String @id @default(cuid()) // Unique ID for the record
|
||
pod_id String // Unique identifier for the pod acting as the leader
|
||
status JobStatus @default(INACTIVE) // Status of the cron job (active or inactive)
|
||
last_updated DateTime @default(now()) // Timestamp for the last update of the cron job record
|
||
ttl DateTime // Time when the leader's lease expires
|
||
}
|
||
|
||
enum JobStatus {
|
||
ACTIVE
|
||
INACTIVE
|
||
}
|
||
|
||
model LiteLLM_ManagedFileTable {
|
||
id String @id @default(uuid())
|
||
unified_file_id String @unique // The base64 encoded unified file ID
|
||
file_object Json? // Stores the OpenAIFileObject
|
||
model_mappings Json
|
||
flat_model_file_ids String[] @default([]) // Flat list of model file id's - for faster querying of model id -> unified file id
|
||
storage_backend String? // Storage backend name (e.g., "azure_storage", "gcs", "default")
|
||
storage_url String? // The actual storage URL where the file is stored
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
team_id String? // Team that owns the resource; populated for service-account keys without a user_id so listings can isolate by team.
|
||
updated_at DateTime @updatedAt
|
||
updated_by String?
|
||
|
||
@@index([unified_file_id])
|
||
@@index([team_id, created_at(sort: Desc)])
|
||
}
|
||
|
||
model LiteLLM_ManagedObjectTable { // for batches or finetuning jobs which use the
|
||
id String @id @default(uuid())
|
||
unified_object_id String @unique // The base64 encoded unified file ID
|
||
model_object_id String @unique // the id returned by the backend API provider
|
||
file_object Json // Stores the OpenAIFileObject
|
||
file_purpose String // either 'batch' or 'fine-tune'
|
||
status String? // check if batch cost has been tracked
|
||
batch_processed Boolean @default(false) // set to true by CheckBatchCost after cost is computed
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
team_id String?
|
||
updated_at DateTime @updatedAt
|
||
updated_by String?
|
||
|
||
@@index([unified_object_id])
|
||
@@index([model_object_id])
|
||
@@index([team_id, created_at(sort: Desc)])
|
||
}
|
||
|
||
model LiteLLM_ManagedVectorStoreTable {
|
||
id String @id @default(uuid())
|
||
unified_resource_id String @unique // The base64 encoded unified vector store ID
|
||
resource_object Json? // Stores the VectorStoreCreateResponse
|
||
model_mappings Json // Maps model_id -> provider_vector_store_id
|
||
flat_model_resource_ids String[] @default([]) // Flat list of provider vector store IDs for faster querying
|
||
storage_backend String? // Storage backend name (if applicable)
|
||
storage_url String? // Storage URL (if applicable)
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
team_id String?
|
||
updated_at DateTime @updatedAt
|
||
updated_by String?
|
||
|
||
@@index([unified_resource_id])
|
||
@@index([team_id, created_at(sort: Desc)])
|
||
}
|
||
|
||
model LiteLLM_ManagedVectorStoresTable {
|
||
vector_store_id String @id
|
||
custom_llm_provider String
|
||
vector_store_name String?
|
||
vector_store_description String?
|
||
vector_store_metadata Json?
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
litellm_credential_name String?
|
||
litellm_params Json?
|
||
team_id String?
|
||
user_id String?
|
||
|
||
@@index([team_id])
|
||
@@index([user_id])
|
||
}
|
||
|
||
// Guardrails table for storing guardrail configurations
|
||
model LiteLLM_GuardrailsTable {
|
||
guardrail_id String @id @default(uuid())
|
||
guardrail_name String @unique
|
||
litellm_params Json
|
||
guardrail_info Json?
|
||
team_id String?
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
// Submission lifecycle. Possible values: pending_review (team-registered, awaiting approval), active (approved), rejected
|
||
status String @default("active")
|
||
submitted_at DateTime?
|
||
reviewed_at DateTime?
|
||
// submitted_by_user_id and submitted_by_email live in guardrail_info JSON
|
||
|
||
@@index([status])
|
||
}
|
||
|
||
// Daily guardrail metrics for usage dashboard (one row per guardrail per day)
|
||
model LiteLLM_DailyGuardrailMetrics {
|
||
guardrail_id String // logical id; may not FK if guardrail from config
|
||
date String // YYYY-MM-DD
|
||
requests_evaluated BigInt @default(0)
|
||
passed_count BigInt @default(0)
|
||
blocked_count BigInt @default(0)
|
||
flagged_count BigInt @default(0)
|
||
avg_score Float?
|
||
avg_latency_ms Float?
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@id([guardrail_id, date])
|
||
@@index([date])
|
||
@@index([guardrail_id])
|
||
}
|
||
|
||
// Daily policy metrics for usage dashboard (one row per policy per day)
|
||
model LiteLLM_DailyPolicyMetrics {
|
||
policy_id String
|
||
date String // YYYY-MM-DD
|
||
requests_evaluated BigInt @default(0)
|
||
passed_count BigInt @default(0)
|
||
blocked_count BigInt @default(0)
|
||
flagged_count BigInt @default(0)
|
||
avg_score Float?
|
||
avg_latency_ms Float?
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@id([policy_id, date])
|
||
@@index([date])
|
||
@@index([policy_id])
|
||
}
|
||
|
||
// Index for fast "last N logs for guardrail/policy" from SpendLogs
|
||
model LiteLLM_SpendLogGuardrailIndex {
|
||
request_id String
|
||
guardrail_id String
|
||
policy_id String? // set when run as part of a policy pipeline
|
||
start_time DateTime
|
||
|
||
@@id([request_id, guardrail_id])
|
||
@@index([guardrail_id, start_time])
|
||
@@index([policy_id, start_time])
|
||
}
|
||
|
||
// Index for fast "last N logs for tool" from SpendLogs – see how a tool is called in production
|
||
model LiteLLM_SpendLogToolIndex {
|
||
request_id String
|
||
tool_name String // matches LiteLLM_ToolTable.tool_name; join for input_policy/output_policy etc.
|
||
start_time DateTime
|
||
|
||
@@id([request_id, tool_name])
|
||
@@index([tool_name, start_time])
|
||
}
|
||
|
||
// Prompt table for storing prompt configurations
|
||
model LiteLLM_PromptTable {
|
||
id String @id @default(uuid())
|
||
prompt_id String
|
||
version Int @default(1)
|
||
environment String @default("development")
|
||
created_by String?
|
||
litellm_params Json
|
||
prompt_info Json?
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@unique([prompt_id, version, environment])
|
||
@@index([prompt_id, environment])
|
||
@@index([prompt_id])
|
||
}
|
||
|
||
model LiteLLM_HealthCheckTable {
|
||
health_check_id String @id @default(uuid())
|
||
model_name String
|
||
model_id String?
|
||
status String
|
||
healthy_count Int @default(0)
|
||
unhealthy_count Int @default(0)
|
||
error_message String?
|
||
response_time_ms Float?
|
||
details Json?
|
||
checked_by String?
|
||
checked_at DateTime @default(now())
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
|
||
@@index([model_name])
|
||
@@index([checked_at])
|
||
@@index([status])
|
||
@@index([model_id, model_name, checked_at(sort: Desc)], map: "LiteLLM_HealthCheckTable_model_id_model_name_checked_at_idx")
|
||
}
|
||
|
||
// Search Tools table for storing search tool configurations
|
||
model LiteLLM_SearchToolsTable {
|
||
search_tool_id String @id @default(uuid())
|
||
search_tool_name String @unique
|
||
litellm_params Json
|
||
search_tool_info Json?
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
}
|
||
|
||
// SSO configuration table
|
||
model LiteLLM_SSOConfig {
|
||
id String @id @default("sso_config")
|
||
sso_settings Json
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
}
|
||
|
||
model LiteLLM_ManagedVectorStoreIndexTable {
|
||
id String @id @default(uuid())
|
||
index_name String @unique
|
||
litellm_params Json
|
||
index_info Json?
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @updatedAt
|
||
updated_by String?
|
||
}
|
||
|
||
// Cache configuration table
|
||
model LiteLLM_CacheConfig {
|
||
id String @id @default("cache_config")
|
||
cache_settings Json
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
}
|
||
|
||
// UI Settings configuration table
|
||
model LiteLLM_UISettings {
|
||
id String @id @default("ui_settings")
|
||
ui_settings Json
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
}
|
||
|
||
// Generic config overrides table - one row per config_type
|
||
model LiteLLM_ConfigOverrides {
|
||
config_type String @id
|
||
config_value Json
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
}
|
||
|
||
// Skills table for storing LiteLLM-managed skills
|
||
model LiteLLM_SkillsTable {
|
||
skill_id String @id @default(uuid())
|
||
display_title String?
|
||
description String?
|
||
instructions String? // The skill instructions/prompt (from SKILL.md)
|
||
source String @default("custom") // "custom" or "anthropic"
|
||
latest_version String?
|
||
file_content Bytes? // Binary content of the skill files (zip)
|
||
file_name String? // Original filename
|
||
file_type String? // MIME type (e.g., "application/zip")
|
||
metadata Json? @default("{}")
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
}
|
||
|
||
// Policy table for storing guardrail policies (versioned)
|
||
model LiteLLM_PolicyTable {
|
||
policy_id String @id @default(uuid())
|
||
policy_name String // No longer @unique; use @@unique([policy_name, version_number])
|
||
version_number Int @default(1)
|
||
version_status String @default("production") // "draft" | "published" | "production"
|
||
parent_version_id String?
|
||
is_latest Boolean @default(true)
|
||
published_at DateTime?
|
||
production_at DateTime?
|
||
inherit String? // Name of parent policy to inherit from
|
||
description String?
|
||
guardrails_add String[] @default([])
|
||
guardrails_remove String[] @default([])
|
||
condition Json? @default("{}") // Policy conditions (e.g., model matching)
|
||
pipeline Json? // Optional guardrail pipeline (mode + steps[])
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
|
||
@@unique([policy_name, version_number])
|
||
@@index([policy_name, version_status])
|
||
}
|
||
|
||
// Policy attachment table for defining where policies apply
|
||
model LiteLLM_PolicyAttachmentTable {
|
||
attachment_id String @id @default(uuid())
|
||
policy_name String // Name of the policy to attach
|
||
scope String? // Use '*' for global scope
|
||
teams String[] @default([]) // Team aliases or patterns
|
||
keys String[] @default([]) // Key aliases or patterns
|
||
models String[] @default([]) // Model names or patterns
|
||
tags String[] @default([]) // Tag patterns (e.g., ["healthcare", "prod-*"])
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
}
|
||
|
||
// Global tool registry - auto-discovered from LLM responses; admins set input/output policies here
|
||
model LiteLLM_ToolTable {
|
||
tool_id String @id @default(uuid())
|
||
tool_name String @unique // e.g. "huggingface_remote-mcp__dynamic_space"
|
||
origin String? // MCP server name or "user_defined"
|
||
input_policy String @default("untrusted") // "trusted" | "untrusted" | "blocked"
|
||
output_policy String @default("untrusted") // "trusted" | "untrusted"
|
||
call_count Int @default(0) // cumulative number of times this tool was seen
|
||
assignments Json? @default("{}")
|
||
key_hash String? // hash of the virtual key that first called this tool
|
||
team_id String? // team that first called this tool
|
||
key_alias String? // human-readable alias of the virtual key
|
||
user_agent String? // user-agent of the first request that discovered this tool
|
||
last_used_at DateTime? // timestamp of the most recent call
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
|
||
@@index([input_policy])
|
||
@@index([output_policy])
|
||
@@index([team_id])
|
||
}
|
||
|
||
// Per-(tool, team/key) policy overrides. When present, override replaces global tool policy for that scope.
|
||
//Unified Access Groups table for storing unified access groups
|
||
model LiteLLM_AccessGroupTable {
|
||
access_group_id String @id @default(uuid())
|
||
access_group_name String @unique
|
||
description String?
|
||
|
||
// Resource memberships - explicit arrays per type
|
||
access_model_names String[] @default([])
|
||
access_mcp_server_ids String[] @default([])
|
||
access_agent_ids String[] @default([])
|
||
|
||
assigned_team_ids String[] @default([])
|
||
assigned_key_ids String[] @default([])
|
||
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
}
|
||
// Claude Code Plugin Marketplace table
|
||
model LiteLLM_ClaudeCodePluginTable {
|
||
id String @id @default(uuid())
|
||
name String @unique
|
||
version String?
|
||
description String?
|
||
manifest_json String?
|
||
files_json String? @default("{}")
|
||
enabled Boolean @default(true)
|
||
created_at DateTime? @default(now())
|
||
updated_at DateTime? @default(now()) @updatedAt
|
||
created_by String?
|
||
|
||
@@map("LiteLLM_ClaudeCodePluginTable")
|
||
}
|
||
|
||
// User/team-scoped memory store with a GLOBAL unique key.
|
||
// `value` is a string (typically markdown/text meant for LLM context);
|
||
// `metadata` is an optional JSON envelope for structured tags without schema changes.
|
||
// Note: `key` is globally unique across all users/teams — callers should
|
||
// namespace their keys (e.g. `user:123:notes`) if they need per-user isolation.
|
||
// `user_id` / `team_id` stamp ownership for visibility filtering, but do NOT
|
||
// participate in the unique constraint.
|
||
model LiteLLM_MemoryTable {
|
||
memory_id String @id @default(uuid())
|
||
key String @unique
|
||
value String
|
||
metadata Json?
|
||
user_id String?
|
||
team_id String?
|
||
created_at DateTime @default(now())
|
||
created_by String?
|
||
updated_at DateTime @default(now()) @updatedAt
|
||
updated_by String?
|
||
|
||
@@index([user_id])
|
||
@@index([team_id])
|
||
}
|
||
|
||
// Per-(router, request_type, model) Beta posterior for the adaptive router.
|
||
model LiteLLM_AdaptiveRouterState {
|
||
router_name String
|
||
request_type String
|
||
model_name String
|
||
alpha Float
|
||
beta Float
|
||
total_samples Int @default(0)
|
||
last_updated_at DateTime @default(now()) @updatedAt
|
||
|
||
@@id([router_name, request_type, model_name])
|
||
}
|
||
|
||
// Per-(session, router, model) signal counters for the adaptive router.
|
||
model LiteLLM_AdaptiveRouterSession {
|
||
session_id String
|
||
router_name String
|
||
model_name String
|
||
classified_type String
|
||
|
||
misalignment_count Int @default(0)
|
||
stagnation_count Int @default(0)
|
||
disengagement_count Int @default(0)
|
||
satisfaction_count Int @default(0)
|
||
failure_count Int @default(0)
|
||
loop_count Int @default(0)
|
||
exhaustion_count Int @default(0)
|
||
|
||
last_user_content String?
|
||
last_assistant_content String?
|
||
tool_call_history Json @default("[]")
|
||
pending_tool_calls Json @default("{}")
|
||
|
||
turn_count Int @default(0)
|
||
last_processed_turn Int @default(-1)
|
||
clean_credit_awarded Boolean @default(false)
|
||
terminal_status Int?
|
||
last_activity_at DateTime @default(now()) @updatedAt
|
||
|
||
@@id([session_id, router_name, model_name])
|
||
@@index([last_activity_at], map: "idx_adaptive_router_session_activity")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Workflow Run Tracking
|
||
//
|
||
// Generic durable state tracking for any agent or automated workflow.
|
||
// Design: three tables — run (header + materialized status), event (append-only
|
||
// source of truth for state transitions), message (conversation inbox/outbox).
|
||
//
|
||
// Usage:
|
||
// - Set `workflow_type` to identify the owning system (e.g. "shin-builder").
|
||
// - Store domain-specific fields in `metadata` (worktree_path, pr_url, etc.).
|
||
// - `session_id` on WorkflowRun matches `x-litellm-session-id` header sent to
|
||
// the proxy — all spend logs for this run are automatically tagged.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// One instance of work being done. `status` is a materialized cache of the
|
||
// latest event; the event log is the authoritative source of truth.
|
||
model LiteLLM_WorkflowRun {
|
||
run_id String @id @default(uuid())
|
||
session_id String @unique @default(uuid())
|
||
workflow_type String
|
||
status String @default("pending")
|
||
created_by String? // user_id of the key that created this run; null = created by master key
|
||
created_at DateTime @default(now())
|
||
updated_at DateTime @updatedAt
|
||
input Json?
|
||
output Json?
|
||
metadata Json?
|
||
|
||
events LiteLLM_WorkflowEvent[]
|
||
messages LiteLLM_WorkflowMessage[]
|
||
|
||
@@index([workflow_type, status])
|
||
@@index([session_id])
|
||
@@index([created_at])
|
||
@@index([created_by])
|
||
}
|
||
|
||
// Append-only log of state transitions. Never mutate rows here.
|
||
// `step_name` and `event_type` are caller-defined strings — no hardcoded enums.
|
||
// Status auto-update rules (applied by the append endpoint):
|
||
// step.started → run.status = running
|
||
// step.failed → run.status = failed
|
||
// hook.waiting → run.status = paused
|
||
// hook.received → run.status = running
|
||
model LiteLLM_WorkflowEvent {
|
||
event_id String @id @default(uuid())
|
||
run_id String
|
||
event_type String
|
||
step_name String
|
||
sequence_number Int
|
||
data Json?
|
||
created_at DateTime @default(now())
|
||
|
||
run LiteLLM_WorkflowRun @relation(fields: [run_id], references: [run_id])
|
||
|
||
@@unique([run_id, sequence_number])
|
||
@@index([run_id])
|
||
}
|
||
|
||
// Conversation inbox/outbox — full message content, separate from the durable
|
||
// event log. Spend logs truncate messages; this table stores them in full.
|
||
// `session_id` here is the Claude --resume session ID (or similar).
|
||
model LiteLLM_WorkflowMessage {
|
||
message_id String @id @default(uuid())
|
||
run_id String
|
||
role String
|
||
content String
|
||
sequence_number Int
|
||
session_id String?
|
||
created_at DateTime @default(now())
|
||
|
||
run LiteLLM_WorkflowRun @relation(fields: [run_id], references: [run_id])
|
||
|
||
@@unique([run_id, sequence_number])
|
||
@@index([run_id])
|
||
}
|