ai queue repr

This commit is contained in:
overcuriousity
2025-07-26 14:33:51 +02:00
parent d2fdeccce3
commit 69fc97f7a0
5 changed files with 371 additions and 137 deletions

View File

@@ -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');
}
};

View 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');
}
};