/** * Pipeline Recommender Service * * Analyzes user messages to recommend relevant Pipelines. * Used by Agent conversation flow to proactively suggest workflows. */ import { PipelineInfo, PipelineClient } from './pipeline-client'; // === Types === export interface PipelineRecommendation { pipeline: PipelineInfo; confidence: number; // 0-1 matchedKeywords: string[]; suggestedInputs: Record; reason: string; } export interface IntentPattern { keywords: RegExp[]; category?: string; pipelineId?: string; minConfidence: number; inputSuggestions?: (message: string) => Record; } // === Intent Patterns === const INTENT_PATTERNS: IntentPattern[] = [ // Education - Classroom { keywords: [ /课件|教案|备课|课堂|教学|ppt|幻灯片/i, /上课|讲课|教材/i, /生成.*课件|制作.*课件|创建.*课件/i, ], category: 'education', pipelineId: 'classroom-generator', minConfidence: 0.75, }, // Marketing - Campaign { keywords: [ /营销|推广|宣传|市场.*方案|营销.*策略/i, /产品.*推广|品牌.*宣传/i, /广告.*方案|营销.*计划/i, /生成.*营销|制作.*营销/i, ], category: 'marketing', pipelineId: 'marketing-campaign', minConfidence: 0.72, }, // Legal - Contract Review { keywords: [ /合同.*审查|合同.*风险|合同.*检查/i, /审查.*合同|检查.*合同|分析.*合同/i, /法律.*审查|合规.*检查/i, /合同.*条款|条款.*风险/i, ], category: 'legal', pipelineId: 'contract-review', minConfidence: 0.78, }, // Research - Literature Review { keywords: [ /文献.*综述|文献.*分析|文献.*检索/i, /研究.*综述|学术.*综述/i, /论文.*综述|论文.*调研/i, /文献.*搜索|文献.*查找/i, ], category: 'research', pipelineId: 'literature-review', minConfidence: 0.73, }, // Productivity - Meeting Summary { keywords: [ /会议.*纪要|会议.*总结|会议.*记录/i, /整理.*会议|总结.*会议/i, /会议.*整理|纪要.*生成/i, /待办.*事项|行动.*项/i, ], category: 'productivity', pipelineId: 'meeting-summary', minConfidence: 0.70, }, // Generic patterns for each category { keywords: [/帮我.*生成|帮我.*制作|帮我.*创建|自动.*生成/i], minConfidence: 0.5, }, ]; // === Pipeline Recommender Class === export class PipelineRecommender { private pipelines: PipelineInfo[] = []; private initialized = false; /** * Initialize the recommender by loading pipelines */ async initialize(): Promise { if (this.initialized) return; try { this.pipelines = await PipelineClient.listPipelines(); this.initialized = true; } catch (error) { console.error('[PipelineRecommender] Failed to load pipelines:', error); } } /** * Refresh pipeline list */ async refresh(): Promise { try { this.pipelines = await PipelineClient.refresh(); } catch (error) { console.error('[PipelineRecommender] Failed to refresh pipelines:', error); } } /** * Analyze a user message and return pipeline recommendations */ async recommend(message: string): Promise { if (!this.initialized) { await this.initialize(); } const recommendations: PipelineRecommendation[] = []; const messageLower = message.toLowerCase(); for (const pattern of INTENT_PATTERNS) { const matches = pattern.keywords .map(regex => regex.test(message)) .filter(Boolean); if (matches.length === 0) continue; const confidence = Math.min( pattern.minConfidence + (matches.length - 1) * 0.05, 0.95 ); // Find matching pipeline let matchingPipelines: PipelineInfo[] = []; if (pattern.pipelineId) { matchingPipelines = this.pipelines.filter(p => p.id === pattern.pipelineId); } else if (pattern.category) { matchingPipelines = this.pipelines.filter(p => p.category === pattern.category); } // If no specific pipeline found, recommend based on category or all if (matchingPipelines.length === 0 && !pattern.pipelineId && !pattern.category) { // Generic match - recommend top pipelines matchingPipelines = this.pipelines.slice(0, 3); } for (const pipeline of matchingPipelines) { const matchedKeywords = pattern.keywords .filter(regex => regex.test(message)) .map(regex => regex.source); const suggestion: PipelineRecommendation = { pipeline, confidence, matchedKeywords, suggestedInputs: pattern.inputSuggestions?.(message) ?? {}, reason: this.generateReason(pipeline, matchedKeywords, confidence), }; // Avoid duplicates if (!recommendations.find(r => r.pipeline.id === pipeline.id)) { recommendations.push(suggestion); } } } // Sort by confidence and return top recommendations return recommendations .sort((a, b) => b.confidence - a.confidence) .slice(0, 3); } /** * Generate a human-readable reason for the recommendation */ private generateReason( pipeline: PipelineInfo, matchedKeywords: string[], confidence: number ): string { const confidenceText = confidence >= 0.8 ? '非常适合' : confidence >= 0.7 ? '适合' : confidence >= 0.6 ? '可能适合' : '或许可以尝试'; if (matchedKeywords.length > 0) { return `您的需求与【${pipeline.displayName}】${confidenceText}。这个 Pipeline 可以帮助您自动化完成相关任务。`; } return `【${pipeline.displayName}】可能对您有帮助。需要我为您启动吗?`; } /** * Format recommendation for Agent message */ formatRecommendationForAgent(rec: PipelineRecommendation): string { const pipeline = rec.pipeline; let message = `我可以使用【${pipeline.displayName}】为你自动完成这个任务。\n\n`; message += `**功能说明**: ${pipeline.description}\n\n`; if (Object.keys(rec.suggestedInputs).length > 0) { message += `**我已识别到以下信息**:\n`; for (const [key, value] of Object.entries(rec.suggestedInputs)) { message += `- ${key}: ${value}\n`; } message += '\n'; } message += `需要开始吗?`; return message; } /** * Check if a message might benefit from a pipeline */ mightNeedPipeline(message: string): boolean { const pipelineKeywords = [ '生成', '创建', '制作', '分析', '审查', '整理', '总结', '归纳', '提取', '转换', '自动化', '帮我', '请帮我', '能不能', '可以', ]; return pipelineKeywords.some(kw => message.includes(kw)); } } // === Singleton Instance === export const pipelineRecommender = new PipelineRecommender(); // === React Hook === import { useState, useEffect, useCallback } from 'react'; export interface UsePipelineRecommendationOptions { autoInit?: boolean; minConfidence?: number; } export function usePipelineRecommendation(options: UsePipelineRecommendationOptions = {}) { const [recommender] = useState(() => new PipelineRecommender()); const [initialized, setInitialized] = useState(false); const [loading, setLoading] = useState(false); useEffect(() => { if (options.autoInit !== false) { recommender.initialize().then(() => setInitialized(true)); } }, [recommender, options.autoInit]); const recommend = useCallback(async (message: string) => { setLoading(true); try { const results = await recommender.recommend(message); const minConf = options.minConfidence ?? 0.6; return results.filter(r => r.confidence >= minConf); } finally { setLoading(false); } }, [recommender, options.minConfidence]); return { recommend, initialized, loading, refresh: recommender.refresh.bind(recommender), mightNeedPipeline: recommender.mightNeedPipeline, formatRecommendationForAgent: recommender.formatRecommendationForAgent.bind(recommender), }; } export default pipelineRecommender;