Skip to main content
Glama
gemini.ts9.27 kB
/** * Gemini API client for prompt generation * * Optimized for: * - Low latency with request caching * - Graceful degradation with fallbacks * - Contextual awareness for prompt refinement */ import { GoogleGenerativeAI, GenerativeModel } from '@google/generative-ai'; import { logger } from './logger.js'; let genAI: GoogleGenerativeAI | null = null; let model: GenerativeModel | null = null; const DEFAULT_MODEL = 'gemini-3-pro-preview'; // Simple LRU cache for repeated requests (improves latency for similar prompts) const responseCache = new Map<string, { response: string; timestamp: number }>(); const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes const MAX_CACHE_SIZE = 50; // Performance metrics let totalRequests = 0; let cacheHits = 0; let totalLatencyMs = 0; /** * Initialize the Gemini client */ export function initializeGemini(apiKey?: string): void { const key = apiKey || process.env.GEMINI_API_KEY; if (!key) { logger.warn('GEMINI_API_KEY not set - AI features will be unavailable'); return; } genAI = new GoogleGenerativeAI(key); model = genAI.getGenerativeModel({ model: DEFAULT_MODEL, generationConfig: { temperature: 1.0, // Required for Gemini 3 }, }); logger.info('Gemini client initialized', { model: DEFAULT_MODEL }); } /** * Check if Gemini is available */ export function isGeminiAvailable(): boolean { return model !== null; } /** * Get cache key for a request */ function getCacheKey(prompt: string, systemInstruction?: string): string { return `${prompt.slice(0, 100)}:${systemInstruction?.slice(0, 50) || 'default'}`; } /** * Check and return cached response if valid */ function getCachedResponse(key: string): string | null { const cached = responseCache.get(key); if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { cacheHits++; logger.debug('Cache hit', { key: key.slice(0, 30) }); return cached.response; } if (cached) { responseCache.delete(key); // Expired } return null; } /** * Store response in cache */ function setCachedResponse(key: string, response: string): void { // Evict oldest if at capacity if (responseCache.size >= MAX_CACHE_SIZE) { const oldestKey = responseCache.keys().next().value; if (oldestKey) responseCache.delete(oldestKey); } responseCache.set(key, { response, timestamp: Date.now() }); } /** * Get performance statistics */ export function getStats(): { totalRequests: number; cacheHits: number; avgLatencyMs: number } { return { totalRequests, cacheHits, avgLatencyMs: totalRequests > 0 ? Math.round(totalLatencyMs / totalRequests) : 0, }; } /** * System instruction for prompt generation */ const SYSTEM_INSTRUCTION = `You are PromptArchitect, an expert prompt engineer. Your role is to transform user ideas into well-structured, effective prompts for AI models. ## Core Principles 1. **Clarity**: Write clear, unambiguous instructions 2. **Structure**: Use logical organization with headers, bullets, and sections 3. **Specificity**: Include concrete details, constraints, and examples 4. **Context**: Provide necessary background information 5. **Output Format**: Specify exactly how the response should be formatted ## Output Format Always structure your generated prompts with: - A clear objective statement - Relevant context and constraints - Step-by-step instructions when applicable - Expected output format specification - Edge cases or special considerations Generate prompts that are immediately usable with any major AI model (GPT-4, Claude, Gemini).`; /** * Generate content using Gemini * * Features: * - Request caching for repeated similar prompts * - Performance tracking * - Detailed error context */ export async function generateContent( userPrompt: string, systemInstruction?: string, options?: { skipCache?: boolean; temperature?: number } ): Promise<string> { if (!model) { throw new Error('Gemini client not initialized. Set GEMINI_API_KEY environment variable.'); } totalRequests++; const startTime = Date.now(); const fullSystemInstruction = systemInstruction || SYSTEM_INSTRUCTION; // Check cache first (unless explicitly skipped) if (!options?.skipCache) { const cacheKey = getCacheKey(userPrompt, systemInstruction); const cached = getCachedResponse(cacheKey); if (cached) { totalLatencyMs += Date.now() - startTime; return cached; } } try { logger.debug('Generating content', { promptLength: userPrompt.length }); const result = await model.generateContent({ contents: [{ role: 'user', parts: [{ text: userPrompt }] }], systemInstruction: { role: 'model', parts: [{ text: fullSystemInstruction }] }, generationConfig: { temperature: options?.temperature ?? 0.7, topP: 0.95, topK: 40, maxOutputTokens: 8192, }, }); const response = result.response; const text = response.text(); const latency = Date.now() - startTime; totalLatencyMs += latency; // Cache the response if (!options?.skipCache) { const cacheKey = getCacheKey(userPrompt, systemInstruction); setCachedResponse(cacheKey, text); } logger.debug('Content generated', { responseLength: text.length, latencyMs: latency }); return text; } catch (error) { const latency = Date.now() - startTime; totalLatencyMs += latency; // Provide actionable error context const errorMessage = error instanceof Error ? error.message : String(error); logger.error('Gemini generation failed', { error: errorMessage, promptLength: userPrompt.length, latencyMs: latency, }); // Wrap with more context for the user throw new Error(`AI generation failed after ${latency}ms: ${errorMessage}. Try again or simplify your prompt.`); } } /** * Analyze a prompt for quality metrics */ export async function analyzePromptQuality(prompt: string): Promise<{ scores: { clarity: number; specificity: number; actionability: number; completeness: number; }; suggestions: string[]; warnings: string[]; }> { if (!model) { // Return rule-based analysis if Gemini not available return ruleBasedAnalysis(prompt); } const analysisPrompt = `Analyze the following prompt for quality and provide: 1. Scores (0-10) for: clarity, specificity, actionability, completeness 2. 2-3 specific suggestions for improvement 3. Any warnings about potential issues Respond in JSON format: { "scores": { "clarity": X, "specificity": X, "actionability": X, "completeness": X }, "suggestions": ["suggestion1", "suggestion2"], "warnings": ["warning1"] // or empty array } PROMPT TO ANALYZE: ${prompt}`; try { const result = await model.generateContent({ contents: [{ role: 'user', parts: [{ text: analysisPrompt }] }], generationConfig: { temperature: 0.3, maxOutputTokens: 1024, }, }); const text = result.response.text(); // Extract JSON from response const jsonMatch = text.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } return ruleBasedAnalysis(prompt); } catch (error) { logger.warn('LLM analysis failed, using rule-based', { error: String(error) }); return ruleBasedAnalysis(prompt); } } /** * Rule-based prompt analysis fallback */ function ruleBasedAnalysis(prompt: string): { scores: { clarity: number; specificity: number; actionability: number; completeness: number; }; suggestions: string[]; warnings: string[]; } { const wordCount = prompt.split(/\s+/).length; const hasStructure = /^#+\s|^\d+\.|^-\s|^\*\s/m.test(prompt); const hasExamples = /example|e\.g\.|for instance|such as/i.test(prompt); const hasConstraints = /must|should|cannot|don't|avoid|ensure/i.test(prompt); const hasOutputFormat = /output|format|respond|return|provide/i.test(prompt); const clarity = Math.min(10, 5 + (hasStructure ? 2 : 0) + (wordCount > 20 ? 2 : 0) + (wordCount < 500 ? 1 : 0)); const specificity = Math.min(10, 4 + (hasExamples ? 3 : 0) + (hasConstraints ? 2 : 0) + (wordCount > 50 ? 1 : 0)); const actionability = Math.min(10, 5 + (hasConstraints ? 2 : 0) + (hasOutputFormat ? 2 : 0) + (hasStructure ? 1 : 0)); const completeness = Math.min(10, 3 + (hasStructure ? 2 : 0) + (hasExamples ? 2 : 0) + (hasOutputFormat ? 2 : 0) + (wordCount > 100 ? 1 : 0)); const suggestions: string[] = []; const warnings: string[] = []; if (!hasStructure) suggestions.push('Add structure using headers, bullets, or numbered lists'); if (!hasExamples) suggestions.push('Include concrete examples to clarify expectations'); if (!hasOutputFormat) suggestions.push('Specify the desired output format'); if (wordCount < 20) warnings.push('Prompt may be too brief - consider adding more context'); if (wordCount > 1000) warnings.push('Prompt is quite long - consider breaking into sections'); return { scores: { clarity, specificity, actionability, completeness }, suggestions: suggestions.slice(0, 3), warnings, }; } export default { initializeGemini, isGeminiAvailable, generateContent, analyzePromptQuality, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MerabyLabs/promptarchitect-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server