SmartfolioBackend / src /modules /ai /ai-admin.controller.ts
ibrahimgulbutt's picture
deploy: /admin/ai/keys β€” report which keys are in rotation
7960c35
Raw History Blame Contribute Delete
2.51 kB
/**
* What the AI budget actually looks like right now.
*
* The Space settings page shows which secrets EXIST. It cannot show whether the
* running process read them, and a key named `GROQ_API_KEY_3` with no `_2`
* beside it is silently ignored β€” the pool stops at the first gap. That is a
* misconfiguration nobody would notice until the day capacity was needed and
* was not there.
*
* So this reports the keys in rotation and how much of each is gone, from the
* same state the router uses. Admin only: it names the environment variables
* and how much budget is left, which together tell an outsider exactly when the
* platform is cheapest to knock over.
*/
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { GroqService } from './groq.service';
import { ALERT_THRESHOLDS } from './groq-key-pool';
@ApiTags('AI (admin)')
@ApiBearerAuth()
@Controller('admin/ai')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AiAdminController {
constructor(private readonly groq: GroqService) {}
@Get('keys')
@ApiOperation({ summary: 'Which Groq keys are in rotation, and how much of each is left' })
@ApiOkResponse({
description:
'Usage is what Groq itself reported on the last response each key served, ' +
'so a key that has not been used yet reads as unmeasured rather than full.',
})
keys() {
const keys = this.groq.keyUsage();
return {
/*
* The count is the first thing to check after adding a secret. Three
* secrets on the settings page and two keys here means the names do not
* match what the pool reads, or the Space has not restarted since.
*/
configured: keys.length,
available: keys.filter((k) => k.available).length,
alertsAt: ALERT_THRESHOLDS.map((t) => `${Math.round(t * 100)}%`),
keys: keys.map((k) => ({
name: k.name,
// Null until this key has served a request β€” the pool spreads load, so
// a quiet key can legitimately have no reading yet.
usedPercent: k.used === null ? null : Math.round(k.used * 100),
remainingRequests: k.remainingRequests,
limitRequests: k.limitRequests,
resetsAt: k.resetsAt,
available: k.available,
})),
};
}
}