본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 29. 03:01

CascadeFlow가 품질을 저하시키지 않으면서 리뷰 비용을 절감한 방법

요약

Refyn 팀은 모든 코드 리뷰에 고비용 모델을 사용하는 비효율성을 해결하기 위해 CascadeFlow 아키텍처를 개발했습니다. 코드 복잡도를 점수화하여 적절한 모델로 라우팅하고, 사용자 패턴을 기억하는 Hindsight memory를 통해 비용 절감과 품질 유지를 동시에 달성합니다.

핵심 포인트

  • CascadeFlow: 코드 복잡도에 따라 모델을 동적으로 선택하는 라우팅 시스템
  • Hindsight memory: 사용자 패턴을 기억하여 프롬프트에 반영하는 레이어
  • 비용 최적화: 단순 코드는 저렴한 모델로, 복잡한 코드는 고성능 모델로 에스컬레이션
  • 실전적 아키텍처: API 장애 및 비용 문제를 고려한 폴백(fallback) 전략 구축

*대부분의 AI 개발 도구들은 동일한 나쁜 습관을 가지고 있습니다: *
사용 가능한 가장 큰 모델로 모든 것을 보내고, 나중에 청구서가 합리적이기를 바라는 것입니다.
데모용으로는 통할지 모릅니다. 하지만 실제로 무언가를 구축할 때는 통하지 않습니다.
다섯 줄짜리 'Hello World'가 JWT, SQL, 비동기 흐름 (async flows), 환경 변수 (environment variables)를 다루는 인증 미들웨어 (auth middleware)를 리뷰하는 것과 동일한 비용이 들어서는 안 됩니다. 하지만 많은 도구들이 모든 코드 조각을 운영 장애 (production incident)처럼 취급합니다. 코드가 실제로 무엇인지와 상관없이, 매번 동일한 모델, 동일한 비용을 사용합니다.
이것이 우리가 Refyn을 구축하면서 계속 마주쳤던 문제였습니다. 우리는 실시간 압박 속에서 작업하고 있었고, 예산과 신뢰성을 모두 빠르게 낭비하는 가장 빠른 방법은 어리석은 라우팅 (routing)이었습니다. 우리는 이미 Gemini가 완전히 실패하는 것을 목격했습니다. 서로 다른 세 개의 Google 계정에서 가져온 네 개의 유효해 보이는 API 키가 모두 "API key not valid"를 반환했습니다. 지금까지도 우리는 그 이유를 모릅니다. 그 후에는 Groq 모델이 테스트 도중, 세션 도중에 400 에러와 함께 아무런 경고 없이 서비스 종료 (decommissioned)되었습니다. 그 이후로 "그냥 모든 것을 하나의 모델로 보내라"는 말은 더 이상 간단하게 들리지 않았습니다. 그것은 취약하게 들리기 시작했습니다.
CascadeFlow가 우리의 해답이 되었습니다: 코드를 먼저 점수화 (score)한 다음, 실제로 어느 정도의 모델이 필요한지 결정하는 것입니다.

Refyn이란 무엇인가:
Refyn은 브라우저 기반의 VS Code 스타일 코드 리뷰 워크스페이스입니다. 프론트엔드에는 React, Vite, Monaco Editor, Tailwind, Framer Motion을 사용하며, 백엔드에는 Node.js와 Express를 사용합니다.
코드를 붙여넣고 'Analyze(분석)'를 누르면 심각도 태그(severity tags), 품질 점수(quality score), 그리고 'Smart Fix(스마트 수정)' 버튼이 포함된 구조화된 이슈들을 돌려받습니다. 일반적인 기능들입니다. 하지만 일반적이지 않은 부분은 모델 호출(model call)이 일어나기 전에 발생하는 과정입니다.
Refyn에는 대부분의 리뷰 도구들이 완전히 건너뛰는 두 가지 레이어가 있습니다.
첫 번째는 Hindsight memory입니다. 이는 세션 전반에 걸쳐 반복되는 사용자의 패턴을 기억하고, 그 이력을 향후 프롬프트(prompt)에 주입합니다. 세 번째 리뷰쯤 되면, 시스템은 사용자를 낯선 사람처럼 대하는 대신 사용자의 알려진 약점(weak spots)을 먼저 파악하여 대응합니다.
두 번째는 CascadeFlow 스타일의 라우팅(routing)입니다. 어떤 모델이 실행되기 전에, Refyn은 코드를 0~100 사이의 복잡도 척도로 점수화(score)합니다. 단순한 코드는 저렴하고 빠른 경로를 택합니다. 보안에 민감하거나 구조적으로 밀도가 높은 코드는 자동으로 에스컬레이션(escalate)됩니다.
이 두 가지 모두 야망이 아닌 필요에 의해 탄생했습니다. Gemini는 사용이 불가능해진 후 완전히 제거되었습니다. Groq가 주력(primary)이 되었고, OpenRouter는 4개의 무료 모델 폴백(fallback)을 갖춘 백업 레이어가 되었습니다. 실행(execution)을 위해서는 Judge0 대신 Piston을 도입했는데, 이는 로컬에서 Docker 오버헤드(overhead)가 너무 무겁고 프로덕션 환경에서는 비용이 너무 많이 들었기 때문입니다. 우리가 출시한 아키텍처(architecture)는 무엇이 계속 고장 나는지에 따라 형성되었습니다.

라우팅 문제 (The Routing Problem):
수식은 간단합니다. 대부분의 코드는 리뷰하기에 똑같이 어려운 것이 아니지만, 대부분의 LLM 통합 방식은 마치 모든 코드가 동일하게 어렵다는 전제하에 가격을 책정합니다.
항상 비싼 경로를 호출한다면, 품질이 향상되지 않더라도 비용은 계속 상승합니다. 아주 작은 유틸리티 함수가 인증 서비스(authentication service)와 동일한 리뷰 예산을 필요로 하지는 않습니다. 하지만 라우팅 로직(routing logic)이 없다면, 어쨌든 동일한 예산이 할당됩니다.
Refyn의 비용 모델은 cascadeService.js에 명시되어 있습니다. Groq는 1K 토큰당 약 $0.00002로 실행됩니다. 비용 절감을 비교하기 위한 프리미엄 기준선(premium baseline)은 1K 토큰당 $0.000075입니다. 100토큰 리뷰 기준:

프리미엄 기준선: 100 / 1000 * 0.000075 = $0.0000075
Groq 경로: 100 / 1000 * 0.00002 = $0.000002
절감액: 약 73%

이것이 바로 통계 바(stats bar)가 중요한 이유입니다. 비용 제어가 이론적인 수준을 넘어 시각적으로 확인 가능해집니다. 한 달에 한 번 확인하는 대시보드를 믿는 것이 아니라, 매 리뷰마다 실시간으로 확인하게 됩니다.

문제의 나머지 절반인 신뢰성(reliability) 부분은 더 까다롭습니다. 구축 과정에서 특정 Groq 모델 이름이 잘 작동하다가, 해당 모델이 서비스 종료(decommissioned)되었다는 400 에러를 반환하기도 했습니다. OpenRouter의 첫 번째 무료 모델 리스트는 무료 티어가 조용히 교체되면서 404 에러를 반환하기 시작했습니다. 만약 여러분의 라우팅(routing)이 제공업체가 안정적으로 유지될 것이라고 가정한다면, 제공업체가 일반적인 서비스 제공자처럼 행동하는 순간 앱은 깨지게 됩니다.

cascadeService.js의 작동 방식:

스코어러(scorer)는 학술적으로 완벽해지려고 노력하지 않습니다. 대신 빠르고, 설명 가능하며, 더 나은 런타임 결정(runtime decisions)을 내릴 수 있을 만큼 충분히 유용해지려고 노력합니다.

export const scoreComplexity = (code, language) => {  
let score = 0;  
const lines = code.split("\n").length;

if (lines > 200) score += 30;  
else if (lines > 100) score += 20;  
else if (lines > 50) score += 10;  
else score += 5;

const securityPatterns = [
/eval\s\*/,
/exec\s\*/,
/subprocess/,
/os.system/,
/child_process/,
/dangerouslySetInnerHTML/,
/innerHTML\s\*=,
/SQL|mysql|postgres|sqlite/i,
/password|secret|token|apikey|api_key/i,
/.env/,
/crypto\./,
/jwt/i,
/auth/i,
];
const securityHits = securityPatterns.filter((p) => p.test(code)).length;  
score += securityHits * 8;

먼저 코드 길이를 기준으로 시작합니다. 220줄짜리 파일은 12줄짜리 코드 조각(snippet)과는 다른 리뷰 문제입니다. 그다음 위험한 패턴인 eval, SQL, auth, JWT, crypto, secrets, 환경 변수 처리(environment handling)에 더 높은 가중치를 부여합니다. 보안 신호가 포함된 짧은 코드라도 점수는 상승합니다. 이는 의도된 설계입니다.

그 후 비동기 밀도(async density), Promise 사용, 클래스(classes), try/catch 깊이, 재귀 힌트(recursion hints), 그리고 임포트(import) 개수에 대한 신호를 추가합니다. 최종 점수는 100점으로 제한됩니다.

라우팅 계층(routing tiers)은 의도적으로 단순하게 설계되었습니다:

if (complexityScore >= 60) {  
model = availableModels.includes("mixtral") ? "mixtral" : "groq";  
} else if (complexityScore >= 30) {  
model = availableModels.includes("groq") ?

"groq" : "openrouter";
} else {
model = availableModels.includes("groq") ? "groq"
: availableModels.includes("mixtral") ? "mixtral"
: "openrouter";
}
낮은 복잡도(Low complexity)는 Groq로 보냅니다 — 빠르고, 저렴하며, 충분히 성능이 좋습니다. 중간 정도의 복잡도(Medium complexity) 또한 속도와 비용의 균형을 위해 Groq를 선호합니다. 높은 복잡도(High complexity)나 보안에 민감한 코드는 Mixtral로 격상됩니다.
그 다음 aiRouter.js가 실제 런타임 폴백(runtime fallback)을 처리합니다:

const FALLBACK_ORDER = ["groq", "mixtral", "openrouter", "ollama"];

for (const model of FALLBACK_ORDER) {
if (model === routing.model) continue;
try {
const result = await MODEL_FN[model](https://dev.tocode,%20language,%20memoryContext);
if (result.success) {
return enrichResult(result, model, startTime, code, memoryData, {
...routing,
model,
reason: `${routing.model} unavailable — fell back to ${model}`,
});
}
} catch (e) {
console.error(`[Cascade] ${model} threw:`, e.message);
}
}

이러한 폴백 체인(fallback chain)이 존재하는 이유는 현실 세계가 복잡하기 때문입니다. Gemini를 사용할 수 없게 되었을 때, Groq가 세션 중간에 모델 가용성을 변경했을 때, 그리고 우리의 .env 파일이 잘못된 폴더에 있어서 백엔드가 "All models failed to analyze"를 반환했던 정말 창피했던 순간 이후로 이 기능이 필요했습니다. 우리는 설정(config)이 실제 버그였다는 것을 깨닫기 전까지, 디버깅 시간의 상당 부분을 제공업체(providers)를 탓하는 데 소비했습니다. 전형적인 상황이었죠.

OpenRouter가 제어권을 넘겨받으면, 무료 모델 목록을 순환합니다:

const FREE_MODELS = [
"openrouter/free",
"meta-llama/llama-3.3-70b-instruct:free",
"qwen/qwen3-coder:free",
"google/gemma-4-31b-it:free"
];

이 목록이 존재하는 이유는 우리의 첫 번째 버전에서 사용했던 모델들이 이미 무료 티어(free tier)에서 제외되었기 때문입니다. 하드코딩된 모델 이름은 빠르게 노후화됩니다. 짜증 나는 방식으로 교훈을 얻었습니다.

데모 순간 (The Demo Moment) :
Refyn의 가장 좋은 점은 라우팅 (routing) 과정이 눈에 보인다는 것입니다.
작은 유틸리티 함수를 붙여넣으면 통계 바에 다음과 같이 표시됩니다:
모델: Groq | 복잡도: 12/100 | 비용: $0.000002 | 절감액: Mixtral 대비 72% | 지연 시간 (Latency): 340ms
이 한 줄의 정보가 우리가 만든 그 어떤 것보다 제품을 더 잘 설명해 줍니다.
그다음 보안에 민감한 것 — 인증 미들웨어 (auth middleware), 토큰 파싱 (token parsing), SQL 쿼리 빌더 (SQL query builder), 또는 .env나 jwt를 건드리는 무엇이든 — 을 붙여넣어 보세요. 복잡도 점수가 급등합니다. 드롭다운을 건드리지 않아도 선택된 모델이 변경됩니다. UI는 그 결정을 명확하게 보여줍니다: 이 리뷰는 코드가 더 위험해 보였기 때문에 에스컬레이션 (escalated) 되었다는 것을 말이죠.
그 투명성이야말로 우리가 계속해서 되돌아갔던 핵심이었습니다. 우리는 이미 그 반대의 경험을 해보았습니다 — 스크린샷에서는 똑똑해 보이지만 런타임 (runtime) 결정 사항은 모두 숨겨버리는 시스템 말입니다. 모든 리뷰에서 모델, 비용, 지연 시간 (latency), 그리고 절감액이 눈에 보이게 되자, 라우팅 (routing) 이야기는 더 이상 추상적이지 않게 되었습니다.

Hindsight가 더해주는 것 (What Hindsight Adds) :
CascadeFlow는 어떤 모델을 사용할지 결정합니다. Hindsight는 그 모델이 무엇을 기억해야 할지를 결정합니다.
aiRouter.js에서 Refyn은 분석 전에 메모리 (memory)를 로드하고, 과거 패턴으로부터 컨텍스트 문자열 (context string)을 구축하여 이를 선택된 모델에 전달하며, 성공적인 리뷰 후에 새로운 패턴을 저장합니다. 메모리는 부가 기능이 아니라 런타임 (runtime) 경로의 일부입니다.
우리는 이를 어렵게 배웠습니다. 우리의 첫 번째 memoryService.js는 존재하지 않는 /memories 엔드포인트 (endpoint)를 호출하고 있었고, 모든 요청은 404 오류와 함께 조용히 사라졌습니다. 해결책은 공식 @vectorize-io/hindsight-client SDK로 전환하고 recall()과 retain()을 적절히 사용하는 것이었습니다. 그 후 두 번째 버그에 부딪혔습니다: SDK가 일반 배열이 아닌 { results: [...] }를 반환하기 때문에, 우리의 파싱 (parsing)이 내내 조용히 잘못되어 있었던 것입니다.
그 문제가 해결되자 메모리 패널이 올바르게 채워지기 시작했습니다. 세션 전반에 걸쳐 패턴 수가 14개까지 올라갔습니다. 그러다 페이지를 새로고침하면 0으로 초기화되는 것을 발견했습니다 — Hindsight에서 다시 불러오는 것이 아니라 React 상태 (state)에만 의존했기 때문입니다. 마운트 (mount) 시 백엔드를 호출하는 useEffect를 사용하여 수정했습니다. 작은 버그였지만, 신뢰 문제로는 컸습니다.

새로고침 시 메모리가 사라진다면, 실제 상황이 아닐지라도 가짜처럼 느껴집니다.

다음 단계로 구축할 것들 :
GitHub PR 연동 — Refyn은 붙여넣기 및 리뷰 워크스페이스로서 잘 작동합니다. 자연스러운 다음 단계는 한 번에 하나의 스니펫(snippet)씩 처리하는 대신, 풀 리퀘스트 (pull request) 내부의 파일별로 결정 사항을 라우팅 (routing)하는 것입니다.
팀 메모리 뱅크 (Team memory banks) — 개별 메모리는 이미 유용합니다. 팀 전체가 공유하는 메모리는 반복되는 보안 이슈, 선호하는 패턴, 그리고 프로젝트별 컨벤션 (convention)이 단순히 세션(session)을 넘어 기여자들 사이에서 지속되도록 의미합니다.
예산 집행 (Budget enforcement) — Refyn은 이미 비용과 절감액을 계산합니다. 세션당 하드 리밋 (hard limits), 에스컬레이션 경고 (escalation warnings), 비용 캡 (cost caps)을 설정하는 것은 라우팅을 단순히 가시화하는 단계를 넘어 운영 가능한 수준으로 만드는 다음 단계입니다.

맺음말 :
Refyn을 구축하며 배운 점은, AI 비용 제어는 대부분 모델의 문제로 위장된 라우팅 (routing) 문제라는 사실입니다.
우리는 단 하나의 완벽한 제공업체를 찾아내어 승리한 것이 아닙니다. Gemini는 실패했습니다. Groq은 우리 모르게 변경되었습니다. OpenRouter의 무료 모델들은 교체되었습니다. 심지어 우리 자신의 .env 설정이 모든 것을 망가뜨린 적도 있었습니다. 우리를 지탱해 준 것은 작업을 점수화하고, 가장 저렴하면서도 합리적인 경로를 선택하며, 무언가 잘못되었을 때 복구할 수 있는 시스템이었습니다.
그것이 제가 이제 신뢰하는 부분입니다. 모델의 이름이 아니라, 바로 라우팅 (routing)입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0