import { getConfig } from '../../config'; import { estimateTokens, estimateModelParamsB } from '../contextManager'; import { isCasualConversationPrompt } from './promptDetection'; import { isAstraModeArchitectureQuestion } from './astraModeArchitecture'; import { shouldPreflightLocalProjectPath } from './localProjectIntent'; /** * 단일-LLM vs Multi-Agent (5단계 파이프라인) 라우팅 결정. 사용자가 명시 토글을 * 켰거나 (configEnabled), 작은 모델인데 prompt 가 크거나, 복합 작업 키워드가 * 매치되면 multi-agent 활성화. 잡담/짧은 prompt 는 무조건 단일 LLM. * * Priority (위→아래로 evaluated, 먼저 매치되면 그 결정 사용): * 1) Astra-mode meta question / local-path preflight → 단일 (false) * 2) mode='off' 인 사용자 — legacy 키워드+길이 휴리스틱만 사용 * 3) casual prompt / 너무 짧음 (<12자) → 단일 * 4) mode='always' → 무조건 multi (위 가드 통과 시) * 5) mode='auto' — 작은 모델 / context fraction / 키워드 / 길이 중 하나라도 * 매치되면 multi. 단 prompt tokens 가 `chunkedSwitchTokens` 미만이면 *모든* * 트리거 무시하고 단일 (큰 context 모델에서 키워드만으로 chunked 강제 발동 * 되는 회귀 차단 — 사용자 명시 요청). * * Stateless — agent.ts 의 private 메서드를 그대로 추출. 의존: config / token * 추정기 / 다른 detection 함수 (모두 이미 stateless). */ export function shouldUseMultiAgentWorkflow(prompt: string, configEnabled: boolean): boolean { if (!prompt || isAstraModeArchitectureQuestion(prompt)) { return false; } if (shouldPreflightLocalProjectPath(prompt)) { return false; } const cfg = getConfig(); const mode = cfg.workflowMultiAgentMode || 'auto'; // 'off' → 기존 키워드/길이 휴리스틱만 사용 (legacy multiAgentEnabled 토글 존중). if (mode === 'off') { const legacyComplex = prompt.length > 180 || /(보고서|심층|종합\s*분석|리서치|조사|전략\s*수립|기획안|제안서|roadmap|research|report|deep\s*analysis|strategy|proposal)/i.test(prompt); if (!legacyComplex) return false; return configEnabled || /(보고서|심층|종합\s*분석|리서치|조사|전략\s*수립|기획안|제안서|research|report|deep\s*analysis|strategy|proposal)/i.test(prompt); } // 인사·잡담은 5단계 파이프라인 낭비. 짧은 casual prompt 는 제외. if (isCasualConversationPrompt(prompt)) { return false; } if (prompt.trim().length < 12) { return false; } // 'always' → 위 가드만 통과하면 무조건 발동. if (mode === 'always') return true; // 'auto' → 다음 중 하나라도 만족하면 발동: // (1) 사용자가 multiAgentEnabled 를 명시적으로 켰다, // (2) 작은 모델 (≤4B params) 이라 한 번에 처리하기 위험, // (3) prompt 토큰이 효과적 context window 의 임계 이상을 차지한다, // (4) "보고서/리뷰/심층 분석" 같은 명백한 복합 작업 키워드 매치, // (5) prompt 길이 자체가 큼 (>240 chars). if (configEnabled) return true; const paramB = estimateModelParamsB(cfg.defaultModel); if (paramB !== null && paramB <= 4) return true; // ── 절대 임계값 게이트 (사용자 명시 요청) ──────────────────────────── // 입력 prompt 가 `chunkedSwitchTokens` 미만이면 *키워드·길이 트리거 모두 무시* // 하고 단일 LLM 호출. 큰 컨텍스트 모델(131k 등)에서 "요약/리뷰" 같은 키워드만 // 써도 chunked 가 강제 발동해 답변이 느려지던 문제 해결. // // 이 게이트는 fraction 안전 체크보다 *먼저* 평가됨 — 사용자가 절대 임계값을 // 명시한 의도(50k 미만은 한 번에 처리)를 fraction 이 뒤집지 못하게. 작은 // 컨텍스트 모델 사용자는 config 에서 이 값을 모델 윈도우의 ~30% 로 낮춰야 함. // [v2.2.301] 명시적 조사·보고서 요청은 절대 임계값 게이트보다 우선 발동 — // 짧은 "X 조사해줘"도 Report QA 파이프라인(의도 브리핑→채점→회귀 게이트)을 // 타야 하기 때문. '요약/리뷰' 류 일반 키워드는 기존 결정대로 게이트 아래 유지. if (/(조사|리서치|보고서|레포트)/.test(prompt) || /\b(research|report)\b/i.test(prompt)) { return true; } try { const promptTokensForGate = estimateTokens(prompt); if (promptTokensForGate < cfg.chunkedSwitchTokens) { return false; } } catch { /* fall through — 안전 측 fraction/keyword 체크가 처리 */ } try { const effectiveCtx = cfg.smallModelContextCap > 0 && paramB !== null && paramB <= 4 ? cfg.smallModelContextCap : cfg.contextLength; const promptTokens = estimateTokens(prompt); const threshold = Math.floor(effectiveCtx * cfg.workflowAutoCtxFractionThreshold); if (promptTokens >= threshold) return true; } catch { /* 안전한 폴백: 키워드/길이 체크로 진행 */ } if (/(보고서|심층|종합\s*분석|리서치|조사|전략\s*수립|기획안|제안서|코드\s*리뷰|리뷰|아키텍처|architecture|research|report|deep\s*analysis|strategy|proposal|review)/i.test(prompt)) { return true; } if (prompt.length > 240) return true; return false; }