Files
colinislit 011815072b refactor(cortex): consistente datumbepaling met referenceDate parameter
SOC: Parser en entity-extractor accepteren nu optionele referenceDate
parameter. Caller bepaalt wat "nu" is, niet de functies intern.

Wijzigingen:
- parseRelativeDate(input, referenceDate?) - referenceDate parameter
- extractEntities(input, intent, referenceDate?) - doorgeeft aan parser
- API routes gebruiken new Date() als referenceDate

Verwijderd (YAGNI):
- lib/utils/server-date.ts - wrapper om new Date() was overbodig

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 08:49:20 +01:00

173 lines
4.9 KiB
TypeScript

/**
* Intent Classify API
*
* POST /api/intent/classify
* Two-tier intent classification: local first, AI fallback if confidence < 0.8
*/
import { createClient } from '@/lib/auth/server';
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { classifyIntent, isHighConfidence } from '@/lib/cortex/intent-classifier';
import { classifyIntentWithAI } from '@/lib/cortex/intent-classifier-ai';
import { extractEntities } from '@/lib/cortex/entity-extractor';
import type { IntentClassificationResult } from '@/lib/cortex/types';
// Request schema
const ClassifyRequestSchema = z.object({
input: z.string().min(1, 'Input is verplicht').max(500, 'Input te lang (max 500 tekens)'),
forceAI: z.boolean().optional(), // For testing: force AI classification
});
// Response type
interface ClassifyResponse {
intent: IntentClassificationResult['intent'];
confidence: number;
entities: IntentClassificationResult['entities'];
source: 'local' | 'ai';
processingTimeMs: number;
localResult?: {
intent: string;
confidence: number;
matchedPattern?: string;
};
}
/**
* POST /api/intent/classify
*
* Classifies user input into an intent with entity extraction.
* Uses local regex patterns first, falls back to AI if confidence < 0.8.
*/
export async function POST(request: NextRequest) {
const startTime = performance.now();
try {
const body = await request.json();
// Validate input
const result = ClassifyRequestSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: 'Validatiefout',
details: result.error.issues.map((e) => ({
field: e.path.join('.'),
message: e.message,
})),
},
{ status: 400 }
);
}
const { input, forceAI } = result.data;
const supabase = await createClient();
// Check auth
const { data: authData } = await supabase.auth.getUser();
if (!authData?.user) {
return NextResponse.json({ error: 'Niet geautoriseerd' }, { status: 401 });
}
// Step 1: Local classification (fast, <50ms)
const localResult = classifyIntent(input);
// Step 2: Decide if we need AI fallback
const useAI = forceAI || !isHighConfidence(localResult);
let finalResult: ClassifyResponse;
if (useAI) {
// AI fallback for low confidence or forced
const aiResult = await classifyIntentWithAI(input);
finalResult = {
intent: aiResult.intent,
confidence: aiResult.confidence,
entities: aiResult.entities,
source: 'ai',
processingTimeMs: performance.now() - startTime,
localResult: {
intent: localResult.intent,
confidence: localResult.confidence,
matchedPattern: localResult.matchedPattern,
},
};
} else {
// High confidence local result - extract entities locally
const entities = extractEntities(input, localResult.intent, new Date());
finalResult = {
intent: localResult.intent,
confidence: localResult.confidence,
entities,
source: 'local',
processingTimeMs: performance.now() - startTime,
};
}
// Log classification event (non-blocking)
logClassificationEvent(supabase, input, finalResult).catch((err) => {
console.error('Failed to log classification event:', err);
});
return NextResponse.json(finalResult);
} catch (error) {
console.error('Error classifying intent:', error);
const errorMessage = error instanceof Error ? error.message : 'Onbekende fout';
if (errorMessage.includes('ANTHROPIC_API_KEY')) {
return NextResponse.json(
{ error: 'AI service niet geconfigureerd' },
{ status: 503 }
);
}
if (errorMessage.includes('Claude API')) {
return NextResponse.json(
{ error: 'AI service tijdelijk niet beschikbaar', details: errorMessage },
{ status: 503 }
);
}
return NextResponse.json(
{
error: 'Fout bij classificeren',
details: process.env.NODE_ENV === 'development' ? errorMessage : undefined,
},
{ status: 500 }
);
}
}
/**
* Log classification event to ai_events table
*/
async function logClassificationEvent(
supabase: Awaited<ReturnType<typeof createClient>>,
input: string,
result: ClassifyResponse
) {
try {
await supabase.from('ai_events').insert({
kind: 'intent_classify',
input_data: {
input: input.slice(0, 200), // Truncate for storage
inputLength: input.length,
},
output_data: {
intent: result.intent,
confidence: result.confidence,
source: result.source,
hasEntities: Object.keys(result.entities).length > 0,
localConfidence: result.localResult?.confidence,
},
duration_ms: Math.round(result.processingTimeMs),
});
} catch (error) {
// Don't throw, just log
console.error('Failed to log AI event:', error);
}
}