Refactor LLM admin UI for improved consistency and API support

Refactored admin LLM actions and components to improve code organization, update color schemes to use design tokens, and enhance UI consistency. Updated API types and endpoints to support model creators and migrations, and switched tables to use shared Table components. Added and documented new API endpoints for model migrations, creators, and usage in openapi.json.
This commit is contained in:
Bentlybro
2026-01-05 17:10:04 +00:00
parent 13a0caa5d8
commit 07e8568f57
8 changed files with 714 additions and 105 deletions

View File

@@ -1,31 +1,31 @@
"use server";
import { revalidatePath } from "next/cache";
import BackendApi from "@/lib/autogpt-server-api";
import type {
CreateLlmModelRequest,
LlmProvider,
LlmModel,
LlmCreatorsResponse,
LlmMigrationsResponse,
LlmModelsResponse,
LlmProvidersResponse,
ToggleLlmModelRequest,
UpdateLlmModelRequest,
UpsertLlmCreatorRequest,
UpsertLlmProviderRequest,
CreateLlmModelRequest,
UpdateLlmModelRequest,
ToggleLlmModelRequest,
UpsertLlmCreatorRequest,
} from "@/lib/autogpt-server-api/types";
import { revalidatePath } from "next/cache";
const ADMIN_LLM_PATH = "/admin/llms";
export async function fetchLlmProviders(): Promise<LlmProvidersResponse> {
// =============================================================================
// Provider Actions
// =============================================================================
export async function fetchLlmProviders(): Promise<{ providers: LlmProvider[] }> {
const api = new BackendApi();
return await api.listAdminLlmProviders(true);
}
export async function fetchLlmModels(): Promise<LlmModelsResponse> {
const api = new BackendApi();
return await api.listAdminLlmModels();
}
export async function createLlmProviderAction(formData: FormData) {
const payload: UpsertLlmProviderRequest = {
name: String(formData.get("name") || "").trim(),
@@ -36,8 +36,8 @@ export async function createLlmProviderAction(formData: FormData) {
default_credential_provider: formData.get("default_credential_provider")
? String(formData.get("default_credential_provider")).trim()
: undefined,
default_credential_id: undefined, // Not needed - system uses credential_provider to lookup
default_credential_type: "api_key", // Default to api_key
default_credential_id: undefined,
default_credential_type: "api_key",
supports_tools: formData.get("supports_tools") === "on",
supports_json_output: formData.get("supports_json_output") !== "off",
supports_reasoning: formData.get("supports_reasoning") === "on",
@@ -50,6 +50,15 @@ export async function createLlmProviderAction(formData: FormData) {
revalidatePath(ADMIN_LLM_PATH);
}
// =============================================================================
// Model Actions
// =============================================================================
export async function fetchLlmModels(): Promise<{ models: LlmModel[] }> {
const api = new BackendApi();
return await api.listAdminLlmModels();
}
export async function createLlmModelAction(formData: FormData) {
const providerId = String(formData.get("provider_id"));
const creatorId = formData.get("creator_id");
@@ -155,6 +164,7 @@ export async function toggleLlmModelAction(formData: FormData): Promise<void> {
migration_reason: migrationReason ? String(migrationReason) : undefined,
custom_credit_cost: customCreditCost ? Number(customCreditCost) : undefined,
};
const api = new BackendApi();
await api.toggleAdminLlmModel(modelId, payload);
revalidatePath(ADMIN_LLM_PATH);
@@ -179,7 +189,10 @@ export async function deleteLlmModelAction(formData: FormData) {
}
}
// Migration management actions
// =============================================================================
// Migration Actions
// =============================================================================
export async function fetchLlmMigrations(
includeReverted: boolean = false
): Promise<LlmMigrationsResponse> {
@@ -203,7 +216,10 @@ export async function revertLlmMigrationAction(
}
}
// Creator management actions
// =============================================================================
// Creator Actions
// =============================================================================
export async function fetchLlmCreators(): Promise<LlmCreatorsResponse> {
const api = new BackendApi();
return await api.listAdminLlmCreators();
@@ -263,4 +279,3 @@ export async function deleteLlmCreatorAction(formData: FormData): Promise<void>
throw error instanceof Error ? error : new Error("Failed to delete creator");
}
}

View File

@@ -27,37 +27,37 @@ export function AddProviderModal() {
</div>
{/* Setup Instructions */}
<div className="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4">
<div className="mb-6 rounded-lg border border-primary/30 bg-primary/5 p-4">
<div className="space-y-2">
<h4 className="text-sm font-semibold text-blue-900">
<h4 className="text-sm font-semibold text-foreground">
Before Adding a Provider
</h4>
<p className="text-xs text-blue-800">
<p className="text-xs text-muted-foreground">
To use a new provider, you must first configure its credentials in the
backend:
</p>
<ol className="list-inside list-decimal space-y-1 text-xs text-blue-800">
<ol className="list-inside list-decimal space-y-1 text-xs text-muted-foreground">
<li>
Add the credential to{" "}
<code className="rounded bg-blue-100 px-1 py-0.5 font-mono">
<code className="rounded bg-muted px-1 py-0.5 font-mono">
backend/integrations/credentials_store.py
</code>{" "}
with a UUID, provider name, and settings secret reference
</li>
<li>
Add it to the{" "}
<code className="rounded bg-blue-100 px-1 py-0.5 font-mono">
<code className="rounded bg-muted px-1 py-0.5 font-mono">
PROVIDER_CREDENTIALS
</code>{" "}
dictionary in{" "}
<code className="rounded bg-blue-100 px-1 py-0.5 font-mono">
<code className="rounded bg-muted px-1 py-0.5 font-mono">
backend/data/block_cost_config.py
</code>
</li>
<li>
Use the <strong>same provider name</strong> in the "Credential Provider"
Use the <strong>same provider name</strong> in the &quot;Credential Provider&quot;
field below that matches the key in{" "}
<code className="rounded bg-blue-100 px-1 py-0.5 font-mono">
<code className="rounded bg-muted px-1 py-0.5 font-mono">
PROVIDER_CREDENTIALS
</code>
</li>

View File

@@ -64,12 +64,14 @@ export function DeleteModelModal({
styling={{ maxWidth: "600px" }}
>
<Dialog.Trigger>
<button
<Button
type="button"
className="inline-flex items-center rounded border border-red-300 px-3 py-1 text-xs font-semibold text-red-600 hover:bg-red-50"
variant="outline"
size="small"
className="min-w-0 text-destructive hover:bg-destructive/10"
>
Delete
</button>
</Button>
</Dialog.Trigger>
<Dialog.Content>
<div className="mb-4 text-sm text-muted-foreground">
@@ -78,21 +80,21 @@ export function DeleteModelModal({
</div>
<div className="space-y-4">
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-4 dark:border-amber-400/30 dark:bg-amber-400/10">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 text-yellow-600"></div>
<div className="text-sm text-yellow-800">
<div className="flex-shrink-0 text-amber-600 dark:text-amber-400"></div>
<div className="text-sm text-foreground">
<p className="font-semibold">You are about to delete:</p>
<p className="mt-1">
<span className="font-medium">{model.display_name}</span>{" "}
<span className="text-yellow-600">({model.slug})</span>
<span className="text-muted-foreground">({model.slug})</span>
</p>
{usageCount !== null && (
<p className="mt-2 font-semibold">
📊 Impact: {usageCount} block{usageCount !== 1 ? "s" : ""} currently use this model
Impact: {usageCount} block{usageCount !== 1 ? "s" : ""} currently use this model
</p>
)}
<p className="mt-2">
<p className="mt-2 text-muted-foreground">
All workflows currently using this model will be automatically
updated to use the replacement model you choose below.
</p>
@@ -110,7 +112,7 @@ export function DeleteModelModal({
<label className="text-sm font-medium">
<span className="block mb-2">
Select Replacement Model <span className="text-red-500">*</span>
Select Replacement Model <span className="text-destructive">*</span>
</span>
<select
required
@@ -126,7 +128,7 @@ export function DeleteModelModal({
))}
</select>
{replacementOptions.length === 0 && (
<p className="mt-2 text-xs text-red-600">
<p className="mt-2 text-xs text-destructive">
No replacement models available. You must have at least one
other enabled model before deleting this one.
</p>
@@ -134,7 +136,7 @@ export function DeleteModelModal({
</label>
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800">
<div className="rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
@@ -152,17 +154,19 @@ export function DeleteModelModal({
>
Cancel
</Button>
<button
<Button
type="submit"
variant="primary"
size="small"
disabled={
!selectedReplacement ||
isDeleting ||
replacementOptions.length === 0
}
className="inline-flex items-center rounded bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeleting ? "Deleting..." : "Delete and Migrate"}
</button>
</Button>
</Dialog.Footer>
</form>
</div>

View File

@@ -88,29 +88,29 @@ export function DisableModelModal({
</div>
<div className="space-y-4">
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4 dark:border-yellow-900 dark:bg-yellow-950">
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-4 dark:border-amber-400/30 dark:bg-amber-400/10">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 text-yellow-600"></div>
<div className="text-sm text-yellow-800 dark:text-yellow-200">
<div className="flex-shrink-0 text-amber-600 dark:text-amber-400"></div>
<div className="text-sm text-foreground">
<p className="font-semibold">You are about to disable:</p>
<p className="mt-1">
<span className="font-medium">{model.display_name}</span>{" "}
<span className="text-yellow-600 dark:text-yellow-400">
<span className="text-muted-foreground">
({model.slug})
</span>
</p>
{usageCount === null ? (
<p className="mt-2 text-yellow-600 dark:text-yellow-400">
<p className="mt-2 text-muted-foreground">
Loading usage data...
</p>
) : usageCount > 0 ? (
<p className="mt-2 font-semibold">
📊 Impact: {usageCount} block{usageCount !== 1 ? "s" : ""}{" "}
Impact: {usageCount} block{usageCount !== 1 ? "s" : ""}{" "}
currently use this model
</p>
) : (
<p className="mt-2">
No workflows are currently using this model.
<p className="mt-2 text-muted-foreground">
No workflows are currently using this model.
</p>
)}
</div>
@@ -147,7 +147,7 @@ export function DisableModelModal({
<div className="space-y-4 border-t border-border pt-4">
<label className="text-sm font-medium block">
<span className="mb-2 block">
Replacement Model <span className="text-red-500">*</span>
Replacement Model <span className="text-destructive">*</span>
</span>
<select
required
@@ -163,7 +163,7 @@ export function DisableModelModal({
))}
</select>
{migrationOptions.length === 0 && (
<p className="mt-2 text-xs text-red-600">
<p className="mt-2 text-xs text-destructive">
No other enabled models available for migration.
</p>
)}
@@ -241,7 +241,7 @@ export function DisableModelModal({
)}
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800 dark:border-red-900 dark:bg-red-950 dark:text-red-200">
<div className="rounded-lg border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}

View File

@@ -33,14 +33,14 @@ export function LlmRegistryDashboard({
{/* Header */}
<div>
<h1 className="text-3xl font-bold">LLM Registry</h1>
<p className="text-gray-500">
<p className="text-muted-foreground">
Manage providers, creators, models, and credit pricing
</p>
</div>
{/* Active Migrations Section - Only show if there are migrations */}
{migrations.length > 0 && (
<div className="rounded-lg border border-blue-200 bg-blue-50 p-6 shadow-sm dark:border-blue-900 dark:bg-blue-950">
<div className="rounded-lg border border-primary/30 bg-primary/5 p-6 shadow-sm">
<div className="mb-4">
<h2 className="text-xl font-semibold">Active Migrations</h2>
<p className="mt-1 text-sm text-muted-foreground">
@@ -55,11 +55,11 @@ export function LlmRegistryDashboard({
{/* Providers & Creators Section - Side by Side */}
<div className="grid gap-6 lg:grid-cols-2">
{/* Providers */}
<div className="rounded-lg border bg-white p-6 shadow-sm dark:bg-background">
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold">Providers</h2>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
<p className="mt-1 text-sm text-muted-foreground">
Who hosts/serves the models
</p>
</div>
@@ -69,11 +69,11 @@ export function LlmRegistryDashboard({
</div>
{/* Creators */}
<div className="rounded-lg border bg-white p-6 shadow-sm dark:bg-background">
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold">Creators</h2>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
<p className="mt-1 text-sm text-muted-foreground">
Who made/trained the models
</p>
</div>
@@ -84,11 +84,11 @@ export function LlmRegistryDashboard({
</div>
{/* Models Section */}
<div className="rounded-lg border bg-white p-6 shadow-sm dark:bg-background">
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="mb-4 flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold">Models</h2>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
<p className="mt-1 text-sm text-muted-foreground">
Toggle availability, adjust context windows, and update credit
pricing
</p>

View File

@@ -3,6 +3,14 @@
import { useState } from "react";
import type { LlmModelMigration } from "@/lib/autogpt-server-api/types";
import { Button } from "@/components/atoms/Button/Button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/atoms/Table/Table";
import { revertLlmMigrationAction } from "../actions";
export function MigrationsTable({
@@ -21,31 +29,23 @@ export function MigrationsTable({
return (
<div className="rounded-lg border">
<table className="w-full">
<thead>
<tr className="border-b bg-muted/50">
<th className="px-4 py-3 text-left text-sm font-medium">
Migration
</th>
<th className="px-4 py-3 text-left text-sm font-medium">Reason</th>
<th className="px-4 py-3 text-left text-sm font-medium">
Nodes Affected
</th>
<th className="px-4 py-3 text-left text-sm font-medium">
Custom Cost
</th>
<th className="px-4 py-3 text-left text-sm font-medium">Created</th>
<th className="px-4 py-3 text-right text-sm font-medium">
Actions
</th>
</tr>
</thead>
<tbody>
<Table>
<TableHeader>
<TableRow>
<TableHead>Migration</TableHead>
<TableHead>Reason</TableHead>
<TableHead>Nodes Affected</TableHead>
<TableHead>Custom Cost</TableHead>
<TableHead>Created</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{migrations.map((migration) => (
<MigrationRow key={migration.id} migration={migration} />
))}
</tbody>
</table>
</TableBody>
</Table>
</div>
);
}
@@ -72,30 +72,30 @@ function MigrationRow({ migration }: { migration: LlmModelMigration }) {
return (
<>
<tr className="border-b last:border-0">
<td className="px-4 py-3">
<TableRow>
<TableCell>
<div className="text-sm">
<span className="font-medium">{migration.source_model_slug}</span>
<span className="mx-2 text-muted-foreground"></span>
<span className="font-medium">{migration.target_model_slug}</span>
</div>
</td>
<td className="px-4 py-3">
</TableCell>
<TableCell>
<div className="text-sm text-muted-foreground">
{migration.reason || "—"}
</div>
</td>
<td className="px-4 py-3">
</TableCell>
<TableCell>
<div className="text-sm">{migration.node_count}</div>
</td>
<td className="px-4 py-3">
</TableCell>
<TableCell>
<div className="text-sm">
{migration.custom_credit_cost !== null
? `${migration.custom_credit_cost} credits`
: "—"}
</div>
</td>
<td className="px-4 py-3">
</TableCell>
<TableCell>
<div className="text-sm text-muted-foreground">
{createdDate.toLocaleDateString()}{" "}
{createdDate.toLocaleTimeString([], {
@@ -103,8 +103,8 @@ function MigrationRow({ migration }: { migration: LlmModelMigration }) {
minute: "2-digit",
})}
</div>
</td>
<td className="px-4 py-3 text-right">
</TableCell>
<TableCell className="text-right">
<form action={handleRevert} className="inline">
<input type="hidden" name="migration_id" value={migration.id} />
<Button
@@ -116,16 +116,16 @@ function MigrationRow({ migration }: { migration: LlmModelMigration }) {
{isReverting ? "Reverting..." : "Revert"}
</Button>
</form>
</td>
</tr>
</TableCell>
</TableRow>
{error && (
<tr>
<td colSpan={6} className="px-4 py-2">
<div className="rounded border border-red-200 bg-red-50 p-2 text-sm text-red-800 dark:border-red-900 dark:bg-red-950 dark:text-red-200">
<TableRow>
<TableCell colSpan={6}>
<div className="rounded border border-destructive/30 bg-destructive/10 p-2 text-sm text-destructive">
{error}
</div>
</td>
</tr>
</TableCell>
</TableRow>
)}
</>
);

View File

@@ -116,7 +116,7 @@ export function ModelsTable({
<span
className={`inline-flex rounded-full px-2.5 py-1 text-xs font-semibold ${
model.is_enabled
? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
? "bg-primary/10 text-primary"
: "bg-muted text-muted-foreground"
}`}
>

View File

@@ -4430,6 +4430,7 @@
"patch": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Toggle LLM model availability",
"description": "Toggle a model's enabled status, optionally migrating workflows when disabling.\n\nIf disabling a model and `migrate_to_slug` is provided, all workflows using\nthis model will be migrated to the specified replacement model before disabling.\nA migration record is created which can be reverted later using the revert endpoint.\n\nOptional fields:\n- `migration_reason`: Reason for the migration (e.g., \"Provider outage\")\n- `custom_credit_cost`: Custom pricing during the migration period",
"operationId": "patchV2Toggle llm model availability",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
@@ -4453,7 +4454,370 @@
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LlmModel" }
"schema": {
"$ref": "#/components/schemas/ToggleLlmModelResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
}
},
"/api/llm/admin/llm/models/{model_id}/usage": {
"get": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Get model usage count",
"description": "Get the number of workflow nodes using this model.",
"operationId": "getV2Get model usage count",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "model_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Model Id" }
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LlmModelUsageResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
}
},
"/api/llm/admin/llm/migrations": {
"get": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "List model migrations",
"description": "List all model migrations.\n\nMigrations are created when disabling a model with the migrate_to_slug option.\nThey can be reverted to restore the original model configuration.",
"operationId": "getV2List model migrations",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "include_reverted",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"description": "Include reverted migrations in the list",
"default": false,
"title": "Include Reverted"
},
"description": "Include reverted migrations in the list"
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LlmMigrationsResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
}
},
"/api/llm/admin/llm/migrations/{migration_id}": {
"get": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Get migration details",
"description": "Get details of a specific migration.",
"operationId": "getV2Get migration details",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "migration_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Migration Id" }
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LlmModelMigration" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
}
},
"/api/llm/admin/llm/migrations/{migration_id}/revert": {
"post": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Revert a model migration",
"description": "Revert a model migration, restoring affected workflows to their original model.\n\nThis only reverts the specific nodes that were part of the migration.\nThe source model must exist for the revert to succeed.\n\nOptions:\n- `re_enable_source_model`: Whether to re-enable the source model if disabled (default: True)\n\nResponse includes:\n- `nodes_reverted`: Number of nodes successfully reverted\n- `nodes_already_changed`: Number of nodes that were modified since migration (not reverted)\n- `source_model_re_enabled`: Whether the source model was re-enabled\n\nRequirements:\n- Migration must not already be reverted\n- Source model must exist",
"operationId": "postV2Revert a model migration",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "migration_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Migration Id" }
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"anyOf": [
{ "$ref": "#/components/schemas/RevertMigrationRequest" },
{ "type": "null" }
],
"title": "Request"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RevertMigrationResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
}
},
"/api/llm/admin/llm/creators": {
"get": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "List model creators",
"description": "List all model creators.\n\nCreators are organizations that create/train models (e.g., OpenAI, Meta, Anthropic).\nThis is distinct from providers who host/serve the models (e.g., OpenRouter).",
"operationId": "getV2List model creators",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LlmCreatorsResponse" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
},
"security": [{ "HTTPBearerJWT": [] }]
},
"post": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Create model creator",
"description": "Create a new model creator.\n\nA creator represents an organization that creates/trains AI models,\nsuch as OpenAI, Anthropic, Meta, or Google.",
"operationId": "postV2Create model creator",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertLlmCreatorRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LlmModelCreator" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
},
"security": [{ "HTTPBearerJWT": [] }]
}
},
"/api/llm/admin/llm/creators/{creator_id}": {
"get": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Get creator details",
"description": "Get details of a specific model creator.",
"operationId": "getV2Get creator details",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "creator_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Creator Id" }
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LlmModelCreator" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
},
"patch": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Update model creator",
"description": "Update an existing model creator.",
"operationId": "patchV2Update model creator",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "creator_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Creator Id" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertLlmCreatorRequest"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LlmModelCreator" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
},
"401": {
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
}
}
},
"delete": {
"tags": ["v2", "admin", "llm", "admin"],
"summary": "Delete model creator",
"description": "Delete a model creator.\n\nThis will remove the creator association from all models that reference it\n(sets creatorId to NULL), but will not delete the models themselves.",
"operationId": "deleteV2Delete model creator",
"security": [{ "HTTPBearerJWT": [] }],
"parameters": [
{
"name": "creator_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Creator Id" }
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true,
"title": "Response Deletev2Delete Model Creator"
}
}
}
},
@@ -4475,6 +4839,7 @@
"get": {
"tags": ["v2", "llm", "llm"],
"summary": "List Models",
"description": "List all enabled LLM models available to users.",
"operationId": "getV2ListModels",
"responses": {
"200": {
@@ -4496,6 +4861,7 @@
"get": {
"tags": ["v2", "llm", "llm"],
"summary": "List Providers",
"description": "List all LLM providers with their enabled models.",
"operationId": "getV2ListProviders",
"responses": {
"200": {
@@ -6163,6 +6529,10 @@
"title": "Description"
},
"provider_id": { "type": "string", "title": "Provider Id" },
"creator_id": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Creator Id"
},
"context_window": { "type": "integer", "title": "Context Window" },
"max_output_tokens": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
@@ -7594,6 +7964,30 @@
"enum": ["RUN", "TOKENS"],
"title": "LlmCostUnit"
},
"LlmCreatorsResponse": {
"properties": {
"creators": {
"items": { "$ref": "#/components/schemas/LlmModelCreator" },
"type": "array",
"title": "Creators"
}
},
"type": "object",
"required": ["creators"],
"title": "LlmCreatorsResponse"
},
"LlmMigrationsResponse": {
"properties": {
"migrations": {
"items": { "$ref": "#/components/schemas/LlmModelMigration" },
"type": "array",
"title": "Migrations"
}
},
"type": "object",
"required": ["migrations"],
"title": "LlmMigrationsResponse"
},
"LlmModel": {
"properties": {
"id": { "type": "string", "title": "Id" },
@@ -7604,6 +7998,16 @@
"title": "Description"
},
"provider_id": { "type": "string", "title": "Provider Id" },
"creator_id": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Creator Id"
},
"creator": {
"anyOf": [
{ "$ref": "#/components/schemas/LlmModelCreator" },
{ "type": "null" }
]
},
"context_window": { "type": "integer", "title": "Context Window" },
"max_output_tokens": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
@@ -7708,6 +8112,84 @@
"required": ["credit_cost", "credential_provider"],
"title": "LlmModelCostInput"
},
"LlmModelCreator": {
"properties": {
"id": { "type": "string", "title": "Id" },
"name": { "type": "string", "title": "Name" },
"display_name": { "type": "string", "title": "Display Name" },
"description": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Description"
},
"website_url": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Website Url"
},
"logo_url": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Logo Url"
},
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
}
},
"type": "object",
"required": ["id", "name", "display_name"],
"title": "LlmModelCreator",
"description": "Represents the organization that created/trained the model (e.g., OpenAI, Meta)."
},
"LlmModelMigration": {
"properties": {
"id": { "type": "string", "title": "Id" },
"source_model_slug": {
"type": "string",
"title": "Source Model Slug"
},
"target_model_slug": {
"type": "string",
"title": "Target Model Slug"
},
"reason": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Reason"
},
"node_count": { "type": "integer", "title": "Node Count" },
"custom_credit_cost": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
"title": "Custom Credit Cost"
},
"is_reverted": {
"type": "boolean",
"title": "Is Reverted",
"default": false
},
"created_at": { "type": "string", "title": "Created At" },
"reverted_at": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Reverted At"
}
},
"type": "object",
"required": [
"id",
"source_model_slug",
"target_model_slug",
"node_count",
"created_at"
],
"title": "LlmModelMigration"
},
"LlmModelUsageResponse": {
"properties": {
"model_slug": { "type": "string", "title": "Model Slug" },
"node_count": { "type": "integer", "title": "Node Count" }
},
"type": "object",
"required": ["model_slug", "node_count"],
"title": "LlmModelUsageResponse"
},
"LlmModelsResponse": {
"properties": {
"models": {
@@ -8813,6 +9295,51 @@
"required": ["credit_amount"],
"title": "RequestTopUp"
},
"RevertMigrationRequest": {
"properties": {
"re_enable_source_model": {
"type": "boolean",
"title": "Re Enable Source Model",
"default": true
}
},
"type": "object",
"title": "RevertMigrationRequest"
},
"RevertMigrationResponse": {
"properties": {
"migration_id": { "type": "string", "title": "Migration Id" },
"source_model_slug": {
"type": "string",
"title": "Source Model Slug"
},
"target_model_slug": {
"type": "string",
"title": "Target Model Slug"
},
"nodes_reverted": { "type": "integer", "title": "Nodes Reverted" },
"nodes_already_changed": {
"type": "integer",
"title": "Nodes Already Changed",
"default": 0
},
"source_model_re_enabled": {
"type": "boolean",
"title": "Source Model Re Enabled",
"default": false
},
"message": { "type": "string", "title": "Message" }
},
"type": "object",
"required": [
"migration_id",
"source_model_slug",
"target_model_slug",
"nodes_reverted",
"message"
],
"title": "RevertMigrationResponse"
},
"ReviewItem": {
"properties": {
"node_exec_id": {
@@ -10161,12 +10688,45 @@
},
"ToggleLlmModelRequest": {
"properties": {
"is_enabled": { "type": "boolean", "title": "Is Enabled" }
"is_enabled": { "type": "boolean", "title": "Is Enabled" },
"migrate_to_slug": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Migrate To Slug"
},
"migration_reason": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Migration Reason"
},
"custom_credit_cost": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
"title": "Custom Credit Cost"
}
},
"type": "object",
"required": ["is_enabled"],
"title": "ToggleLlmModelRequest"
},
"ToggleLlmModelResponse": {
"properties": {
"model": { "$ref": "#/components/schemas/LlmModel" },
"nodes_migrated": {
"type": "integer",
"title": "Nodes Migrated",
"default": 0
},
"migrated_to_slug": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Migrated To Slug"
},
"migration_id": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Migration Id"
}
},
"type": "object",
"required": ["model"],
"title": "ToggleLlmModelResponse"
},
"TransactionHistory": {
"properties": {
"transactions": {
@@ -10253,6 +10813,10 @@
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Provider Id"
},
"creator_id": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Creator Id"
},
"costs": {
"anyOf": [
{
@@ -10908,6 +11472,32 @@
],
"title": "UploadFileResponse"
},
"UpsertLlmCreatorRequest": {
"properties": {
"name": { "type": "string", "title": "Name" },
"display_name": { "type": "string", "title": "Display Name" },
"description": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Description"
},
"website_url": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Website Url"
},
"logo_url": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Logo Url"
},
"metadata": {
"additionalProperties": true,
"type": "object",
"title": "Metadata"
}
},
"type": "object",
"required": ["name", "display_name"],
"title": "UpsertLlmCreatorRequest"
},
"UpsertLlmProviderRequest": {
"properties": {
"name": { "type": "string", "title": "Name" },