feat(core-api): add rapid order transcribe and parse endpoints
This commit is contained in:
@@ -6,9 +6,12 @@ import { requireAuth, requirePolicy } from '../middleware/auth.js';
|
||||
import { createSignedUrlSchema } from '../contracts/core/create-signed-url.js';
|
||||
import { createVerificationSchema } from '../contracts/core/create-verification.js';
|
||||
import { invokeLlmSchema } from '../contracts/core/invoke-llm.js';
|
||||
import { rapidOrderParseSchema } from '../contracts/core/rapid-order-parse.js';
|
||||
import { rapidOrderTranscribeSchema } from '../contracts/core/rapid-order-transcribe.js';
|
||||
import { reviewVerificationSchema } from '../contracts/core/review-verification.js';
|
||||
import { invokeVertexModel } from '../services/llm.js';
|
||||
import { checkLlmRateLimit } from '../services/llm-rate-limit.js';
|
||||
import { parseRapidOrderText, transcribeRapidOrderAudio } from '../services/rapid-order.js';
|
||||
import {
|
||||
ensureFileExistsForActor,
|
||||
generateReadSignedUrl,
|
||||
@@ -24,7 +27,22 @@ import {
|
||||
|
||||
const DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
||||
const DEFAULT_MAX_SIGNED_URL_SECONDS = 900;
|
||||
const ALLOWED_FILE_TYPES = new Set(['application/pdf', 'image/jpeg', 'image/jpg', 'image/png']);
|
||||
const ALLOWED_FILE_TYPES = new Set([
|
||||
'application/pdf',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'audio/webm',
|
||||
'audio/wav',
|
||||
'audio/x-wav',
|
||||
'audio/mpeg',
|
||||
'audio/mp3',
|
||||
'audio/mp4',
|
||||
'audio/m4a',
|
||||
'audio/aac',
|
||||
'audio/ogg',
|
||||
'audio/flac',
|
||||
]);
|
||||
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
@@ -59,6 +77,10 @@ function requireVerificationFileExists() {
|
||||
return process.env.VERIFICATION_REQUIRE_FILE_EXISTS !== 'false';
|
||||
}
|
||||
|
||||
function requireRapidAudioFileExists() {
|
||||
return process.env.RAPID_AUDIO_REQUIRE_FILE_EXISTS !== 'false';
|
||||
}
|
||||
|
||||
function parseBody(schema, body) {
|
||||
const parsed = schema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
@@ -69,6 +91,15 @@ function parseBody(schema, body) {
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
function enforceLlmRateLimit(uid) {
|
||||
const rateLimit = checkLlmRateLimit({ uid });
|
||||
if (!rateLimit.allowed) {
|
||||
throw new AppError('RATE_LIMITED', 'Too many model requests. Please retry shortly.', 429, {
|
||||
retryAfterSeconds: rateLimit.retryAfterSeconds,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUploadFile(req, res, next) {
|
||||
try {
|
||||
const file = req.file;
|
||||
@@ -158,12 +189,7 @@ async function handleCreateSignedUrl(req, res, next) {
|
||||
async function handleInvokeLlm(req, res, next) {
|
||||
try {
|
||||
const payload = parseBody(invokeLlmSchema, req.body || {});
|
||||
const rateLimit = checkLlmRateLimit({ uid: req.actor.uid });
|
||||
if (!rateLimit.allowed) {
|
||||
throw new AppError('RATE_LIMITED', 'Too many model requests. Please retry shortly.', 429, {
|
||||
retryAfterSeconds: rateLimit.retryAfterSeconds,
|
||||
});
|
||||
}
|
||||
enforceLlmRateLimit(req.actor.uid);
|
||||
|
||||
const startedAt = Date.now();
|
||||
if (process.env.LLM_MOCK === 'false') {
|
||||
@@ -194,6 +220,63 @@ async function handleInvokeLlm(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRapidOrderTranscribe(req, res, next) {
|
||||
try {
|
||||
const payload = parseBody(rapidOrderTranscribeSchema, req.body || {});
|
||||
validateFileUriAccess({
|
||||
fileUri: payload.audioFileUri,
|
||||
actorUid: req.actor.uid,
|
||||
});
|
||||
|
||||
if (requireRapidAudioFileExists() && !useMockUpload()) {
|
||||
await ensureFileExistsForActor({
|
||||
fileUri: payload.audioFileUri,
|
||||
actorUid: req.actor.uid,
|
||||
});
|
||||
}
|
||||
|
||||
enforceLlmRateLimit(req.actor.uid);
|
||||
|
||||
const startedAt = Date.now();
|
||||
const result = await transcribeRapidOrderAudio({
|
||||
audioFileUri: payload.audioFileUri,
|
||||
locale: payload.locale,
|
||||
promptHints: payload.promptHints,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
...result,
|
||||
latencyMs: Date.now() - startedAt,
|
||||
requestId: req.requestId,
|
||||
});
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRapidOrderParse(req, res, next) {
|
||||
try {
|
||||
const payload = parseBody(rapidOrderParseSchema, req.body || {});
|
||||
enforceLlmRateLimit(req.actor.uid);
|
||||
|
||||
const startedAt = Date.now();
|
||||
const result = await parseRapidOrderText({
|
||||
text: payload.text,
|
||||
locale: payload.locale,
|
||||
timezone: payload.timezone,
|
||||
now: payload.now,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
...result,
|
||||
latencyMs: Date.now() - startedAt,
|
||||
requestId: req.requestId,
|
||||
});
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateVerification(req, res, next) {
|
||||
try {
|
||||
const payload = parseBody(createVerificationSchema, req.body || {});
|
||||
@@ -268,6 +351,8 @@ export function createCoreRouter() {
|
||||
router.post('/upload-file', requireAuth, requirePolicy('core.upload', 'file'), upload.single('file'), handleUploadFile);
|
||||
router.post('/create-signed-url', requireAuth, requirePolicy('core.sign-url', 'file'), handleCreateSignedUrl);
|
||||
router.post('/invoke-llm', requireAuth, requirePolicy('core.invoke-llm', 'model'), handleInvokeLlm);
|
||||
router.post('/rapid-orders/transcribe', requireAuth, requirePolicy('core.rapid-order.transcribe', 'model'), handleRapidOrderTranscribe);
|
||||
router.post('/rapid-orders/parse', requireAuth, requirePolicy('core.rapid-order.parse', 'model'), handleRapidOrderParse);
|
||||
router.post('/verifications', requireAuth, requirePolicy('core.verification.create', 'verification'), handleCreateVerification);
|
||||
router.get('/verifications/:verificationId', requireAuth, requirePolicy('core.verification.read', 'verification'), handleGetVerification);
|
||||
router.post('/verifications/:verificationId/review', requireAuth, requirePolicy('core.verification.review', 'verification'), handleReviewVerification);
|
||||
|
||||
Reference in New Issue
Block a user