Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/generate-model-card.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
pattern:
description: 'Model ID pattern, as for `pnpm cli generate-model-card` (e.g. apertus-v1.5-70b)'
required: true
delete:
description: 'Delete the card instead of generating it'
type: boolean
default: false

jobs:
generate:
Expand All @@ -13,9 +17,10 @@ jobs:
- name: Generate the model card on the server
env:
PATTERN: ${{ github.event.inputs.pattern }}
DELETE: ${{ github.event.inputs.delete }}
run: |
BODY=$(python3 -c 'import json, os; print(json.dumps({"pattern": os.environ["PATTERN"]}))')
echo "Generating model card for: $PATTERN"
BODY=$(python3 -c 'import json, os; print(json.dumps({"pattern": os.environ["PATTERN"], "delete": os.environ.get("DELETE") == "true"}))')
echo "Model card for: $PATTERN (delete: ${DELETE:-false})"
# Card generation reads every published run, so it can outlast the
# request; the server keeps going if curl gives up (exit 28).
# RAILWAY_APP_URL may redirect (http -> https, www -> apex); follow it as a POST.
Expand Down
13 changes: 11 additions & 2 deletions src/app/api/internal/generate-model-card/__tests__/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import { vi } from 'vitest';
import { NextRequest, NextResponse } from 'next/server';

vi.mock('@/cli/commands/generate-model-card', () => ({ actionGenerateModelCard: vi.fn() }));
vi.mock('@/cli/commands/generate-model-card', () => ({ actionGenerateModelCard: vi.fn(), actionDeleteModelCard: vi.fn() }));
vi.mock('@/lib/background-function-auth', () => ({ checkBackgroundAuth: vi.fn(() => null) }));
vi.mock('@/utils/logger', () => ({
getLogger: vi.fn(async () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn() })),
}));

import { POST } from '../route';
import { actionGenerateModelCard } from '@/cli/commands/generate-model-card';
import { actionDeleteModelCard, actionGenerateModelCard } from '@/cli/commands/generate-model-card';
import { checkBackgroundAuth } from '@/lib/background-function-auth';
import { getConfig } from '@/cli/config';

Expand All @@ -34,6 +34,15 @@ describe('POST /api/internal/generate-model-card', () => {

expect(res.status).toBe(200);
expect(actionGenerateModelCard).toHaveBeenCalledWith('apertus-v1.5-70b', {});
expect(actionDeleteModelCard).not.toHaveBeenCalled();
});

it('deletes the card instead when asked', async () => {
const res = await POST(request({ pattern: 'apertus-v1.5-70b', delete: true }));

expect(res.status).toBe(200);
expect(actionDeleteModelCard).toHaveBeenCalledWith('apertus-v1.5-70b', { force: true });
expect(actionGenerateModelCard).not.toHaveBeenCalled();
});

it('rejects unauthenticated requests', async () => {
Expand Down
13 changes: 10 additions & 3 deletions src/app/api/internal/generate-model-card/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import { checkBackgroundAuth } from '@/lib/background-function-auth';
import { configure } from '@/cli/config';
import { getLogger } from '@/utils/logger';
import { actionGenerateModelCard } from '@/cli/commands/generate-model-card';
import { actionDeleteModelCard, actionGenerateModelCard } from '@/cli/commands/generate-model-card';

// A substring of base model IDs, as the generate-model-card CLI takes it
// (e.g. "apertus-v1.5-70b", "claude-3-5-sonnet", "openai:gpt-4o").
Expand All @@ -11,15 +11,17 @@ const PATTERN_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{1,99}$/;
/**
* Runs the generate-model-card CLI command on the server, where the storage
* and OpenRouter credentials live. Triggered by the "Generate Model Card"
* GitHub workflow.
* GitHub workflow. With `delete: true` it removes the card instead (the
* delete-model-card command), so its page stops being served.
*/
export async function POST(req: NextRequest) {
const authError = checkBackgroundAuth(req);
if (authError) return authError;

let pattern: unknown;
let deleteCard: unknown;
try {
({ pattern } = await req.json());
({ pattern, delete: deleteCard } = await req.json());
} catch {
return NextResponse.json({ error: 'Invalid JSON in request body.' }, { status: 400 });
}
Expand All @@ -38,6 +40,11 @@ export async function POST(req: NextRequest) {
},
});

if (deleteCard === true) {
await actionDeleteModelCard(pattern, { force: true });
return NextResponse.json({ message: 'Model card deleted.', pattern });
}

await actionGenerateModelCard(pattern, {});
return NextResponse.json({ message: 'Model card generation finished.', pattern });
}
2 changes: 1 addition & 1 deletion src/cli/commands/generate-model-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ export async function actionGenerateModelCard(modelIdPattern: string, options: {
logger.info(`==========================\n`);
}

async function actionDeleteModelCard(modelIdPattern: string, options: { force?: boolean }) {
export async function actionDeleteModelCard(modelIdPattern: string, options: { force?: boolean }) {
const { logger } = getConfig();

if (!options.force) {
Expand Down
Loading