Optimize all AI functions for efficiency and speed
- AI Tagging: batch 10 projects per API call with 3 concurrent batches (~10x faster) - New `tagProjectsBatch()` with `getAISuggestionsBatch()` for multi-project prompts - Single DB query for all projects, single anonymization pass - Compact JSON in prompts (no pretty-print) saves tokens - AI Shortlist: run STARTUP and BUSINESS_CONCEPT categories in parallel (2x faster) - AI Filtering: increase default parallel batches from 1 to 3 (3x faster) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { prisma } from '@/lib/prisma'
|
||||
import { logAudit } from '../utils/audit'
|
||||
import {
|
||||
tagProject,
|
||||
tagProjectsBatch,
|
||||
getTagSuggestions,
|
||||
addProjectTag,
|
||||
removeProjectTag,
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
NotificationTypes,
|
||||
} from '../services/in-app-notification'
|
||||
|
||||
// Background job runner for tagging
|
||||
// Background job runner for tagging — uses batched API calls for efficiency
|
||||
async function runTaggingJob(jobId: string, userId: string) {
|
||||
const job = await prisma.taggingJob.findUnique({
|
||||
where: { id: jobId },
|
||||
@@ -28,7 +29,7 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[AI Tagging Job] Starting job ${jobId}...`)
|
||||
console.log(`[AI Tagging Job] Starting job ${jobId} (batched mode)...`)
|
||||
|
||||
// Mark as running
|
||||
await prisma.taggingJob.update({
|
||||
@@ -56,7 +57,7 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||
|
||||
const allProjects = await prisma.project.findMany({
|
||||
where: whereClause,
|
||||
select: { id: true, title: true, tags: true },
|
||||
select: { id: true, title: true, tags: true, projectTags: { select: { tagId: true } } },
|
||||
})
|
||||
|
||||
const untaggedProjects = allProjects.filter(p => p.tags.length === 0)
|
||||
@@ -83,48 +84,33 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||
return
|
||||
}
|
||||
|
||||
let taggedCount = 0
|
||||
let failedCount = 0
|
||||
const errors: string[] = []
|
||||
const startTime = Date.now()
|
||||
|
||||
for (let i = 0; i < untaggedProjects.length; i++) {
|
||||
const project = untaggedProjects[i]
|
||||
console.log(`[AI Tagging Job] Processing ${i + 1}/${untaggedProjects.length}: "${project.title.substring(0, 40)}..."`)
|
||||
// Use batched tagging — processes 10 projects per API call, 3 concurrent calls
|
||||
const { results, totalTokens } = await tagProjectsBatch(
|
||||
untaggedProjects,
|
||||
userId,
|
||||
async (processed, total) => {
|
||||
// Update job progress on each batch completion
|
||||
const taggedSoFar = results?.length ?? processed
|
||||
await prisma.taggingJob.update({
|
||||
where: { id: jobId },
|
||||
data: {
|
||||
processedCount: processed,
|
||||
taggedCount: taggedSoFar,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const result = await tagProject(project.id, userId)
|
||||
taggedCount++
|
||||
console.log(`[AI Tagging Job] ✓ Tagged with ${result.applied.length} tags`)
|
||||
} catch (error) {
|
||||
failedCount++
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error'
|
||||
errors.push(`${project.title}: ${errorMsg}`)
|
||||
console.error(`[AI Tagging Job] ✗ Failed: ${errorMsg}`)
|
||||
}
|
||||
|
||||
// Update progress
|
||||
await prisma.taggingJob.update({
|
||||
where: { id: jobId },
|
||||
data: {
|
||||
processedCount: i + 1,
|
||||
taggedCount,
|
||||
failedCount,
|
||||
errorsJson: errors.length > 0 ? errors.slice(0, 20) : undefined, // Keep last 20 errors
|
||||
},
|
||||
})
|
||||
|
||||
// Log progress every 10 projects
|
||||
if ((i + 1) % 10 === 0) {
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0)
|
||||
const avgTime = (Date.now() - startTime) / (i + 1) / 1000
|
||||
const remaining = avgTime * (untaggedProjects.length - i - 1)
|
||||
console.log(`[AI Tagging Job] Progress: ${i + 1}/${untaggedProjects.length} (${elapsed}s elapsed, ~${remaining.toFixed(0)}s remaining)`)
|
||||
console.log(`[AI Tagging Job] Progress: ${processed}/${total} (${elapsed}s elapsed)`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const taggedCount = results.filter(r => r.applied.length > 0).length
|
||||
const failedCount = untaggedProjects.length - results.length
|
||||
|
||||
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1)
|
||||
console.log(`[AI Tagging Job] Complete: ${taggedCount} tagged, ${failedCount} failed in ${totalTime}s`)
|
||||
console.log(`[AI Tagging Job] Complete: ${taggedCount} tagged, ${failedCount} failed in ${totalTime}s (${totalTokens} tokens)`)
|
||||
|
||||
// Mark as completed
|
||||
await prisma.taggingJob.update({
|
||||
@@ -132,7 +118,9 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||
data: {
|
||||
status: 'COMPLETED',
|
||||
completedAt: new Date(),
|
||||
errorsJson: errors.length > 0 ? errors : undefined,
|
||||
processedCount: results.length,
|
||||
taggedCount,
|
||||
failedCount,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -144,7 +132,7 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||
linkUrl: '/admin/projects',
|
||||
linkLabel: 'View Projects',
|
||||
priority: 'normal',
|
||||
metadata: { jobId, taggedCount, failedCount, skippedCount },
|
||||
metadata: { jobId, taggedCount, failedCount, skippedCount, totalTokens },
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user