claude-gemini-multimodal-bridge
Version:
Enterprise-grade AI integration bridge connecting Claude Code, Gemini CLI, and Google AI Studio with intelligent routing and advanced multimodal processing capabilities
236 lines (235 loc) • 10.1 kB
JavaScript
import { logger } from './logger.js';
export class PromptOptimizer {
DEFAULT_MAX_TOKENS = 8000;
KEYWORD_EXTRACTION_PATTERNS = [
/について|に関して|について教えて|を説明/g,
/最新の|最近の|2024|2025|トレンド|動向/g,
/具体的には|詳しく|詳細に|例えば/g,
/分析|解析|検討|評価|調査/g,
/方法|戦略|手法|アプローチ|施策/g,
];
SEARCH_TEMPLATES = {
trend_analysis: {
keywords: ['トレンド', '動向', '最新', '2024', '2025'],
template: '{topic}の最新トレンドと{year}年の動向分析'
},
strategy_advice: {
keywords: ['戦略', '方法', 'アプローチ', '手法'],
template: '{topic}の効果的な戦略と実装方法'
},
market_research: {
keywords: ['市場', 'マーケット', '予測', '分析'],
template: '{topic}の市場分析と予測'
},
technical_guide: {
keywords: ['実装', '技術', '開発', 'プログラミング'],
template: '{topic}の技術実装ガイド'
}
};
async optimizeForWebSearch(prompt, options = {}) {
const startTime = Date.now();
const originalPrompt = prompt;
const optimizationMethods = [];
let optimizedPrompt = prompt;
const maxTokens = options.maxTokens || this.DEFAULT_MAX_TOKENS;
try {
if (options.useTemplates !== false) {
const templateResult = this.applySearchTemplate(optimizedPrompt);
if (templateResult.matched) {
optimizedPrompt = templateResult.optimized;
optimizationMethods.push('template_matching');
}
}
if (options.extractKeywords !== false) {
optimizedPrompt = this.extractKeywords(optimizedPrompt);
optimizationMethods.push('keyword_extraction');
}
if (options.removeRedundancy !== false) {
optimizedPrompt = this.removeRedundancy(optimizedPrompt);
optimizationMethods.push('redundancy_removal');
}
optimizedPrompt = this.structuredShortening(optimizedPrompt, maxTokens);
optimizationMethods.push('structured_shortening');
if (options.preserveContext !== false) {
optimizedPrompt = this.preserveContextShortening(optimizedPrompt);
optimizationMethods.push('context_preservation');
}
const originalTokens = this.estimateTokens(originalPrompt);
const optimizedTokens = this.estimateTokens(optimizedPrompt);
const reducedTokens = originalTokens - optimizedTokens;
const reductionPercentage = (reducedTokens / originalTokens) * 100;
const result = {
originalPrompt,
optimizedPrompt,
reducedTokens,
reductionPercentage,
optimizationMethods
};
logger.info('Prompt optimization completed', {
originalTokens,
optimizedTokens,
reductionPercentage: reductionPercentage.toFixed(1),
duration: Date.now() - startTime,
methods: optimizationMethods
});
return result;
}
catch (error) {
logger.error('Prompt optimization failed', { error });
return {
originalPrompt,
optimizedPrompt: originalPrompt,
reducedTokens: 0,
reductionPercentage: 0,
optimizationMethods: ['optimization_failed']
};
}
}
applySearchTemplate(prompt) {
const lowerPrompt = prompt.toLowerCase();
for (const [templateName, template] of Object.entries(this.SEARCH_TEMPLATES)) {
const matchedKeywords = template.keywords.filter(keyword => lowerPrompt.includes(keyword.toLowerCase()));
if (matchedKeywords.length >= 2) {
const topic = this.extractMainTopic(prompt);
const year = this.extractYear(prompt) || '2024-2025';
let optimized = template.template
.replace('{topic}', topic)
.replace('{year}', year);
const additionalKeywords = this.extractAdditionalKeywords(prompt);
if (additionalKeywords.length > 0) {
optimized += ` ${additionalKeywords.slice(0, 3).join(' ')}`;
}
logger.debug('Template applied', {
template: templateName,
topic,
matchedKeywords
});
return { matched: true, optimized };
}
}
return { matched: false, optimized: prompt };
}
extractKeywords(prompt) {
const keywords = new Set();
this.KEYWORD_EXTRACTION_PATTERNS.forEach(pattern => {
const matches = prompt.match(pattern);
if (matches) {
matches.forEach(match => keywords.add(match));
}
});
const nounPhrases = prompt.match(/[ァ-ヶー]+[アプリ|システム|サービス|技術|方法|戦略]*/g);
if (nounPhrases) {
nounPhrases.slice(0, 5).forEach(phrase => keywords.add(phrase));
}
const alphanumericKeywords = prompt.match(/[A-Za-z][A-Za-z0-9]*|[0-9]{4}/g);
if (alphanumericKeywords) {
alphanumericKeywords.slice(0, 3).forEach(keyword => keywords.add(keyword));
}
return Array.from(keywords).join(' ');
}
removeRedundancy(prompt) {
return prompt
.replace(/について(?:詳しく)?(?:教えて|説明して)ください[。]?/g, 'について')
.replace(/具体的には以下の点について[::]/g, '')
.replace(/(?:1\.|2\.|3\.|4\.|5\.)\s*/g, '')
.replace(/(?:非常に|とても|かなり|極めて)\s*/g, '')
.replace(/(?:詳細に|詳しく|具体的に)\s*/g, '')
.replace(/[。、]{2,}/g, '、')
.replace(/\s{2,}/g, ' ')
.trim();
}
structuredShortening(prompt, maxTokens) {
const estimatedTokens = this.estimateTokens(prompt);
if (estimatedTokens <= maxTokens) {
return prompt;
}
const sentences = prompt.split(/[。!?]/).filter(s => s.trim());
const sentenceImportance = sentences.map(sentence => ({
sentence: sentence.trim(),
importance: this.calculateSentenceImportance(sentence)
})).sort((a, b) => b.importance - a.importance);
const selectedSentences = [];
let currentTokens = 0;
for (const { sentence } of sentenceImportance) {
const sentenceTokens = this.estimateTokens(sentence);
if (currentTokens + sentenceTokens <= maxTokens) {
selectedSentences.push(sentence);
currentTokens += sentenceTokens;
}
}
return selectedSentences.join('。') + '。';
}
preserveContextShortening(prompt) {
const contextKeywords = [
'Android', 'アプリ', 'マネタイズ', '広告', 'IAP', 'サブスクリプション',
'2024', '2025', 'トレンド', '動向', '戦略', '方法'
];
const sentences = prompt.split(/[。!?]/).filter(s => s.trim());
const prioritizedSentences = sentences.filter(sentence => contextKeywords.some(keyword => sentence.includes(keyword)));
if (prioritizedSentences.length > 0) {
return prioritizedSentences.join('。') + '。';
}
return prompt;
}
calculateSentenceImportance(sentence) {
let importance = 0;
const importantKeywords = ['最新', 'トレンド', '2024', '2025', '戦略', '方法', '分析'];
importantKeywords.forEach(keyword => {
if (sentence.includes(keyword)) {
importance += 2;
}
});
if (sentence.includes('?') || sentence.includes('ですか')) {
importance += 1;
}
if (sentence.includes('具体的') || sentence.includes('詳しく')) {
importance += 1;
}
const length = sentence.length;
if (length >= 20 && length <= 100) {
importance += 1;
}
return importance;
}
extractMainTopic(prompt) {
const topicPatterns = [
/Android[アプリ]*/,
/マネタイズ[戦略]*/,
/[アプリ内]?課金/,
/サブスクリプション/,
/広告収入/,
/[Aa][Pp][Pp]/
];
for (const pattern of topicPatterns) {
const match = prompt.match(pattern);
if (match) {
return match[0];
}
}
const nounMatch = prompt.match(/[ァ-ヶー]+/);
return nounMatch ? nounMatch[0] : 'トピック';
}
extractYear(prompt) {
const yearMatch = prompt.match(/20[0-9]{2}[-年]?20[0-9]{2}|20[0-9]{2}/);
return yearMatch ? yearMatch[0] : null;
}
extractAdditionalKeywords(prompt) {
const keywords = [];
const techTerms = prompt.match(/[A-Z]{2,}|API|SDK|UI|UX/g);
if (techTerms) {
keywords.push(...techTerms.slice(0, 2));
}
const katakanaTerms = prompt.match(/[ァ-ヶー]{3,}/g);
if (katakanaTerms) {
keywords.push(...katakanaTerms.slice(0, 3));
}
return keywords;
}
estimateTokens(text) {
const hiragana = (text.match(/[ひ-ゞ]/g) || []).length;
const katakana = (text.match(/[ァ-ヶー]/g) || []).length;
const kanji = (text.match(/[一-龯]/g) || []).length;
const alphanumeric = (text.match(/[a-zA-Z0-9]/g) || []).length;
return hiragana + katakana + (kanji * 1.5) + Math.ceil(alphanumeric / 4);
}
}