ai queue repr
This commit is contained in:
@@ -20,7 +20,7 @@ const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
||||
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
|
||||
const RATE_LIMIT_MAX = 10; // 10 requests per minute per user
|
||||
|
||||
// Input validation and sanitization (UNCHANGED)
|
||||
// Input validation and sanitization
|
||||
function sanitizeInput(input: string): string {
|
||||
// Remove any content that looks like system instructions
|
||||
let sanitized = input
|
||||
@@ -36,7 +36,7 @@ function sanitizeInput(input: string): string {
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// Strip markdown code blocks from AI response (UNCHANGED)
|
||||
// Strip markdown code blocks from AI response
|
||||
function stripMarkdownJson(content: string): string {
|
||||
// Remove ```json and ``` wrappers
|
||||
return content
|
||||
@@ -45,7 +45,7 @@ function stripMarkdownJson(content: string): string {
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Rate limiting check (UNCHANGED)
|
||||
// Rate limiting check
|
||||
function checkRateLimit(userId: string): boolean {
|
||||
const now = Date.now();
|
||||
const userLimit = rateLimitStore.get(userId);
|
||||
@@ -74,7 +74,7 @@ function cleanupExpiredRateLimits() {
|
||||
|
||||
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
||||
|
||||
// Load tools database (UNCHANGED)
|
||||
// Load tools database
|
||||
async function loadToolsDatabase() {
|
||||
try {
|
||||
return await getCompressedToolsDataForAI();
|
||||
@@ -84,7 +84,7 @@ async function loadToolsDatabase() {
|
||||
}
|
||||
}
|
||||
|
||||
// Create system prompt for workflow mode (EXACTLY AS ORIGINAL)
|
||||
// Create system prompt for workflow mode
|
||||
function createWorkflowSystemPrompt(toolsData: any): string {
|
||||
const toolsList = toolsData.tools.map((tool: any) => ({
|
||||
name: tool.name,
|
||||
@@ -99,7 +99,7 @@ function createWorkflowSystemPrompt(toolsData: any): string {
|
||||
related_concepts: tool.related_concepts || []
|
||||
}));
|
||||
|
||||
// NEW: Include concepts for background knowledge
|
||||
// Include concepts for background knowledge
|
||||
const conceptsList = toolsData.concepts.map((concept: any) => ({
|
||||
name: concept.name,
|
||||
description: concept.description,
|
||||
@@ -109,7 +109,7 @@ function createWorkflowSystemPrompt(toolsData: any): string {
|
||||
tags: concept.tags
|
||||
}));
|
||||
|
||||
// Get regular phases (no more filtering needed)
|
||||
// Get regular phases
|
||||
const regularPhases = toolsData.phases || [];
|
||||
|
||||
// Get domain-agnostic software phases
|
||||
@@ -201,7 +201,7 @@ ANTWORT-FORMAT (strict JSON):
|
||||
Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des JSON.`;
|
||||
}
|
||||
|
||||
// Create system prompt for tool-specific mode (EXACTLY AS ORIGINAL)
|
||||
// Create system prompt for tool-specific mode
|
||||
function createToolSystemPrompt(toolsData: any): string {
|
||||
const toolsList = toolsData.tools.map((tool: any) => ({
|
||||
name: tool.name,
|
||||
@@ -217,7 +217,7 @@ function createToolSystemPrompt(toolsData: any): string {
|
||||
related_concepts: tool.related_concepts || []
|
||||
}));
|
||||
|
||||
// NEW: Include concepts for background knowledge
|
||||
// Include concepts for background knowledge
|
||||
const conceptsList = toolsData.concepts.map((concept: any) => ({
|
||||
name: concept.name,
|
||||
description: concept.description,
|
||||
@@ -277,7 +277,7 @@ Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des J
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// CONSOLIDATED: Replace 20+ lines with single function call (UNCHANGED)
|
||||
// Authentication check
|
||||
const authResult = await withAPIAuth(request, 'ai');
|
||||
if (!authResult.authenticated) {
|
||||
return createAuthErrorResponse();
|
||||
@@ -285,16 +285,16 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
|
||||
const userId = authResult.userId;
|
||||
|
||||
// Rate limiting (ONLY CHANGE: Use helper for this one response)
|
||||
// Rate limiting
|
||||
if (!checkRateLimit(userId)) {
|
||||
return apiError.rateLimit('Rate limit exceeded');
|
||||
}
|
||||
|
||||
// Parse request body (UNCHANGED)
|
||||
// Parse request body
|
||||
const body = await request.json();
|
||||
const { query, mode = 'workflow' } = body;
|
||||
const { query, mode = 'workflow', taskId: clientTaskId } = body;
|
||||
|
||||
// Validation (ONLY CHANGE: Use helpers for error responses)
|
||||
// Validation
|
||||
if (!query || typeof query !== 'string') {
|
||||
return apiError.badRequest('Query required');
|
||||
}
|
||||
@@ -303,20 +303,24 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
return apiError.badRequest('Invalid mode. Must be "workflow" or "tool"');
|
||||
}
|
||||
|
||||
// Sanitize input (UNCHANGED)
|
||||
// Sanitize input
|
||||
const sanitizedQuery = sanitizeInput(query);
|
||||
if (sanitizedQuery.includes('[FILTERED]')) {
|
||||
return apiError.badRequest('Invalid input detected');
|
||||
}
|
||||
|
||||
// Load tools database (UNCHANGED)
|
||||
// Load tools database
|
||||
const toolsData = await loadToolsDatabase();
|
||||
|
||||
// Create appropriate system prompt based on mode (UNCHANGED)
|
||||
// Create appropriate system prompt based on mode
|
||||
const systemPrompt = mode === 'workflow'
|
||||
? createWorkflowSystemPrompt(toolsData)
|
||||
: createToolSystemPrompt(toolsData);
|
||||
|
||||
// Generate task ID for queue tracking (use client-provided ID if available)
|
||||
const taskId = clientTaskId || `ai_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
|
||||
|
||||
// Make AI API call through rate-limited queue
|
||||
const aiResponse = await enqueueApiCall(() =>
|
||||
fetch(process.env.AI_API_ENDPOINT + '/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
@@ -340,9 +344,9 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
temperature: 0.3
|
||||
})
|
||||
})
|
||||
);
|
||||
, taskId);
|
||||
|
||||
// AI response handling (ONLY CHANGE: Use helpers for error responses)
|
||||
// AI response handling
|
||||
if (!aiResponse.ok) {
|
||||
console.error('AI API error:', await aiResponse.text());
|
||||
return apiServerError.unavailable('AI service unavailable');
|
||||
@@ -355,7 +359,7 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
return apiServerError.unavailable('No response from AI');
|
||||
}
|
||||
|
||||
// Parse AI JSON response (UNCHANGED)
|
||||
// Parse AI JSON response
|
||||
let recommendation;
|
||||
try {
|
||||
const cleanedContent = stripMarkdownJson(aiContent);
|
||||
@@ -365,7 +369,7 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
return apiServerError.unavailable('Invalid AI response format');
|
||||
}
|
||||
|
||||
// Validate tool names and concept names against database (EXACTLY AS ORIGINAL)
|
||||
// Validate tool names and concept names against database
|
||||
const validToolNames = new Set(toolsData.tools.map((t: any) => t.name));
|
||||
const validConceptNames = new Set(toolsData.concepts.map((c: any) => c.name));
|
||||
|
||||
@@ -415,13 +419,14 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Log successful query (UNCHANGED)
|
||||
// Log successful query
|
||||
console.log(`[AI Query] Mode: ${mode}, User: ${userId}, Query length: ${sanitizedQuery.length}, Tools: ${validatedRecommendation.recommended_tools.length}, Concepts: ${validatedRecommendation.background_knowledge?.length || 0}`);
|
||||
|
||||
// SUCCESS RESPONSE (UNCHANGED - Preserves exact original format)
|
||||
// Success response with task ID
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
mode,
|
||||
taskId,
|
||||
recommendation: validatedRecommendation,
|
||||
query: sanitizedQuery
|
||||
}), {
|
||||
@@ -431,7 +436,6 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
|
||||
} catch (error) {
|
||||
console.error('AI query error:', error);
|
||||
// ONLY CHANGE: Use helper for error response
|
||||
return apiServerError.internal('Internal server error');
|
||||
}
|
||||
};
|
||||
23
src/pages/api/ai/queue-status.ts
Normal file
23
src/pages/api/ai/queue-status.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// src/pages/api/ai/queue-status.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getQueueStatus } from '../../../utils/rateLimitedQueue.js';
|
||||
import { apiResponse, apiServerError } from '../../../utils/api.js';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const taskId = url.searchParams.get('taskId');
|
||||
|
||||
const status = getQueueStatus(taskId || undefined);
|
||||
|
||||
return apiResponse.success({
|
||||
...status,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Queue status error:', error);
|
||||
return apiServerError.internal('Failed to get queue status');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user