Rocky 's Blog

로컬 LLM과 n8n으로 구축하는 에러 분석 자동화

  • n8n
  • LLM
  • 최적화
2026. 02. 19.
게시글 썸네일

프로젝트를 시작한 배경


빠르게 변화하는 AI를 마주하며

빠르게 변하는 AI 환경을 마주하며, 개발자의 역할도 함께 변화하고 있다고 느낀다. 스포티파이가 “최고의 개발자들은 12월 이후 한 줄의 코드도 직접 쓰지 않았다”라고 말할 정도로, 이제는 AI가 코드 작성의 상당 부분을 대신하는 시대가 되었다는 소식도 접했다.

  • Spotify says its best developers haven’t written a line of code since December, thanks to AI
  • 처음 개발을 시작할 때, 빠른 기술의 변화에 적응해야 한다는 이야기를 많이 들었지만, 지금의 AI 기술은 그보다 훨씬 압도적인 속도로 진화하고 있다. AI도 금방 가라앉을 것이라 생각하여 미뤄왔지만, 이제는 AI를 잘 다루지 못한다면 시대를 따라가기 힘들 것이라 생각한다.

    물론 AI가 개발자를 완전히 대체할 것이라 생각하지는 않는다. 다만 개발 환경의 패러다임을 흔들고 있다는 점은 분명하다. 그렇기 때문에 이 변화 속에서 살아남기 위해서는 무엇보다 AI를 잘 활용하고, 그 결과를 검증할 수 있는 역량이 중요해졌다고 느낀다.

    적정 수준의 품질이 보장된다면, 얼마나 적은 리소스로 빠르게 문제를 해결하느냐가 핵심 경쟁력이라고 생각한다. 토큰을 효율적으로 사용하는 프롬프트 엔지니어링처럼, 최소한의 비용으로 최대의 효과를 내는 방식에 관심을 두게 되었다. 이번 프로젝트 역시 그렇게 시작되었다.

    n8n 프로젝트를 시작한 배경

    워크플로우 자동화 도구인 n8n을 접하며, 자동화가 단순한 편의를 넘어 개발자가 조금 더 중요한 문제 해결에 집중할 수 있도록 돕는다고 느꼈다. 그 효과가 어느정도인지 직접 경험하여 체감해보기로 했다.

    처음에는 “어떤 것을 자동화할 수 있을까?”를 두리뭉술하게 고민했다. 그러던 중, 이왕이면 실제로 내가 겪었던 불편함을 풀어보는 게 더 의미 있겠다는 생각이 들었다. 이전 프로젝트에서 Sentry를 도입해 에러 모니터링을 진행했을 때, 상세한 스택 트레이스와 유저 액션 정보가 디버깅에 큰 도움이 되었다.

    한편으로는, 다른 팀원 작성한 코드에서 에러가 발생하면, 이전에 리뷰했던 코드라도 다시 흐름을 복기하고, 어디서부터 파고들어야 할지 감을 잡는 데 꽤 시간이 들었다. 스택 트레이스를 분석하고 관련 파일을 열어보며 에러 전후의 맥락을 머릿속으로 재구성하는 과정 자체가 꽤 걸렸다.

    여기서 이 반복적인 고민의 과정을 n8n과 AI로 개선할 수 있지 않을까? 라는 아이디어를 떠올리게 되었다. 에러가 발생했을 때 n8n이 관련 컨텍스트를 수집하고, AI가 이를 바탕으로 초기 분석 결과, 의심 구간, 우선 확인 체크리스트를 즉시 생성해 주는 구조를 떠올렸다.

    이렇게 하면 개발자는 제로 베이스가 아니라 가이드라인이 잡힌 상태에서 디버깅을 시작할 수 있을 것이다. 결과적으로 문제 해결 속도를 높일 수 있을 것이라 생각했고, 이것이 이번 프로젝트의 핵심 목표다.

    프로젝트 소개


    프로젝트 소개

    프론트엔드에서 발생하는 실시간 에러를 수집하여, 로컬 AI 모델(Ollama DeepSeek-Coder-V2:16b)을 통해 원인 분석 및 수정 제안을 생성하고, 이를 노션 데이터베이스에 리포트로 자동 기록하는 시스템이다. n8n을 활용해 전체 파이프라인을 로컬 환경 내에서 완결성 있게 관리하고자 한다.

    전체 플로우
    전체 플로우
    프로젝트 목표
  • 에러 발생 지점과 관련 컨텍스트를 AI가 즉시 분석하여 개발자의 리소스를 절약한다.
  • 단순 원인 파악을 넘어 구체적인 코드 수정 제안까지 제공하여 디버깅 속도를 높인다.
  • 발생한 에러와 해결 과정을 체계적으로 축적하여 문제 해결뿐 아니라 해결 과정의 기록까지 이어간다.
  • 타겟 상황

    배포 후 운영 단계에서도 사용할 수 있지만, 무엇보다 로컬 개발 단계에 최적화되어 있다. 코드가 아직 Git에 푸시되지 않은 상태에서도 동작하는 것을 핵심으로 잡았다. 개발 중 오류가 발생하는 즉시 AI가 당시 상황과 원인을 분석하여 노션에 기록한다.

    초기 아이디어 (Deprecated)

    초기에는 AI가 코드를 직접 수정하고 GitHub Draft PR을 생성하는 방식으로 구현했다. 하지만, 다음과 같은 한계들로 인해 노션에 리포트 방식으로 기록하도록 수정했다.

    비교 항목초기 아이디어 (GitHub PR)최종 확정 (Notion Report)
    제약 사항코드가 GitHub에 반드시 업로드되어야 함로컬 개발 중에도 실시간 기록 가능
    운영 부담에러 누적 시, PR이 과도해져 레포지토리 오염레포지토리와 독립적으로 노션 데이터베이스로 관리
    개발 흐름PR 검토/정리 과정에서 불필요한 오버헤드 발생확인이 불필요한 자료는 즉시 건너뛰거나 삭제 가능

    기술 선택


    Docker

    실행 환경을 격리하고 어디서든 동일한 동작을 보장하기 위해 Docker를 도입했다. 다른 프로젝트에 도입하거나, 다른 사람들과의 협업까지 확장하였을 때, 환경 설정 번거로움을 줄이고자 했다.

    Ollama

    Ollama는 LLM을 로컬에서 구동하고 API 형태로 제공하는 도구다. 이를 n8n과 연동하여 외부 서버로 데이터를 전송하지 않는 워크플로우를 구축했다. Ollama를 선택한 이유는 다음과 같다.

  • 별도의 API 호출 비용이 발생하지 않아, 부담 없이 진행할 수 있다.
  • 보안 설정인 데이터 관리에서의 실수를 방지하기 위해 로컬 실행 구조를 채택했다.
  • deepseek-coder-v2:16b

    사용 중인 하드웨어 사양(M4 Pro, 24GB RAM)에서 코드 분석 성능과 추론 속도의 균형이 뛰어난 deepseek-coder-v2:16b 모델을 선택했다.

    M4 GPU 가속을 최대로 활용하기 위해 Ollama는 Docker 컨테이너 내부가 아닌 로컬 머신에 직접 설치했다. 이후 n8n 컨테이너가 호스트 머신의 Ollama API 서버(host.docker.internal)에 접근하도록 설정하여 성능 손실 없이 안정적인 파이프라인을 구축했다.

    진행 단계


    (0) 보안 및 환경 구성

    Credential 관리를 통한 보안 강화

    API 키를 워크플로우 내부에 하드코딩하는 방식은 유출 시 보안 문제가 발생한다. 이를 위해, n8n의 Credential 관리 기능을 활용해 인증 정보를 등록했다. 워크플로우를 JSON으로 내보내더라도 인증 값은 노출되지 않으며, n8n 내부에서 안전하게 암호화된 상태로 유지되도록 구성했다.

    Import from file

    워크플로우를 n8n UI에서 수동으로 생성하지 않고, workflow.json 파일로 정의하여 관리하는 방식을 선택했다. 워크플로우 구성을 코드화하여 Git에서 변경 이력을 추적하고 관리할 수 있도록 했다. 전체적인 흐름을 코드 형태로 파악하는 것이 더 익숙하기도 하고, 유지보수 효율이 높다고 판단했다.

    (1) 에러 로거 정의

    현재 단계
    Notion image

    발생한 에러를 AI가 분석하기 좋은 형태의 데이터로 가공하여 n8n 워크플로우로 전달한다.

    초기 파이프라인

    초기에는 Sentry → Webhook → n8n 구조를 생각했었다. 그러나 에러 발생 시점에 필요한 정보를 직접 정규화해 n8n 웹훅으로 전달하면 Sentry를 거칠 필요가 없음을 깨달았다. 이를 통해 Sentry의 데이터 방식을 따르지 않고도, 원하는 정보들을 자유롭게 포함할 수 있어 더 효율적이고 유연하게 처리할 수 있었다.

    에러 중복 처리

    같은 에러가 짧은 시간 동안 연속으로 발생하고, 발생할 때마다 그대로 전송하면 동일한 기록이 끝없이 쌓이게 된다. 이를 방지하기 위해 5초 이내에 동일한 에러 키가 다시 들어오면, 중복으로 간주하고 무시하도록 간단한 TTL 기반 메모리 캐시를 두었다.

    const RECENT_ERROR_TTL = 5000; // 5초
    
    // 5초 내 중복 에러 차단
    const recentErrors = new Map();
    
    /**
     * 중복 에러 체크 및 기록
     */
    function isDuplicate(errorKey) {
      const now = Date.now();
      const lastTimestamp = recentErrors.get(errorKey);
      if (lastTimestamp && now - lastTimestamp < RECENT_ERROR_TTL) {
        return true;
      }
      recentErrors.set(errorKey, now);
    
      // 메모리 관리를 위해 5초 뒤 해당 키만 삭제
      setTimeout(() => recentErrors.delete(errorKey), RECENT_ERROR_TTL);
      return false;
    }
    에러 페이로드 설계

    AI가 문맥을 잘 이해하고 할루시네이션을 줄이려면, 에러 정보를 최대한 구조화해서 보내는 것이 중요하다고 판단했다. 그래서 실제 에러 객체와 호출 지점을 분리하고, 각자에 대해 파싱된 스택 트레이스와 부가 정보를 함께 담는 페이로드 형태를 정의했다.

    또한, meta에는 URL, userAgent, 타임스탬프, 환경 정보 등을 함께 넣어두어, 나중에 노션에서 리포트를 볼 때 어떤 페이지, 어떤 환경에서 발생한 에러인지 를 파악할 수 있도록 했다.

    function createPayload(error, callSiteStack) {
      return {
        error: {
          message: error.message,
          name: error.name,
          stack: error.stack || "",
          traces: parseStackTrace(error.stack),
          causes: collectCauseChain(error),
        },
        callSite: {
          stack: callSiteStack,
          traces: parseStackTrace(callSiteStack),
        },
        meta: {
          url: window.location.href,
          userAgent: navigator.userAgent.slice(0, 500),
          timestamp: new Date().toISOString(),
          environment: isDev ? "development" : "production",
        },
      };
    }
    안정적인 에러 전송

    페이지가 닫히거나 전환되는 찰나에 발생하는 에러도 놓치지 않도록 fetch API의 keepalive: true 옵션을 적용했다. 또한, 로깅 시스템의 장애가 실제 애플리케이션 서비스에 영향을 주지 않도록 비동기로 처리하며 예외 상황을 격리했다.

    // n8n 전송 (실패해도 앱에는 영향 없음)
    fetch(N8N_WEBHOOK_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
      keepalive: true,
    }).catch((err) => {
      if (isDev) console.warn("[logger] Webhook 전송 실패", err.message);
    });

    (2) 에러 정보 파싱

    현재 단계
    Notion image

    로거로부터 전달받은 원시 데이터를 AI 모델이 효율적으로 처리할 수 있도록 정제한다.

    분석 대상 파일 선정

    src/ 경로를 포함한 파일만 필터링하여 실제 수정 가능한 범위의 코드 정보만 추출했다. 너무 많은 파일 정보는 AI의 주의력을 분산시키고 토큰이 낭비될 수 있다. 따라서 에러와 가장 밀접한 상위 3개의 파일로 분석 대상을 제한하여 맥락의 선명도를 높이고자 했다.

    // 모든 트레이스 합치기 (에러 발생 지점이 앞서도록 순서 유지)
    const allTraces = [...(error.traces || []), ...(callSite.traces || [])].slice(0, 3);
    
    const seen = new Set();
    const srcFiles = [];
    
    for (const trace of allTraces) {
      if (!trace.file) continue;
    
      // 'src/' 경로 포함 여부 확인 후 추출
      const match = trace.file.match(/(src\/.*)$/);
      if (!match) continue;
    
      // 중복 제거: 동일한 파일 경로는 한 번만 기록
      const filePath = match[1];
      if (seen.has(filePath)) continue;
      seen.add(filePath);
    
      srcFiles.push({
        file: filePath,
        fn: trace.function,
        location: `${trace.line}:${trace.column}`,
        full: `${filePath}:${trace.line}:${trace.column}`,
      });
    }
    AI 친화적 데이터 구조화

    가공된 정보는 n8n으로 넘기기 좋게 구조화했다. 이때 AI 프롬프트 설계를 염두에 두고, 제목, 에러 상세, 호출 스택, 환경 정보, 타임스탬프 등을 명시적으로 구분했다.

    return [
      {
        json: {
          title: `[${error.name || "Error"}] ${error.message}`,
          callSiteStack: callSite.stack,
          error: {
            ...error,
            srcFiles,
            primaryLocation,
            stackSummary,
          },
          context: {
            url: meta.url,
            path: getUrlPath(meta.url),
            browser: uaInfo.browser,
            os: uaInfo.os,
            userAgent: meta.userAgent,
            env: meta.environment,
          },
          timestamp: meta.timestamp || new Date().toISOString(),
        },
      },
    ];

    (3) 에러가 발생한 파일 읽기

    현재 단계
    Notion image

    에러 파싱 단계에서 식별된 파일과 라인 정보를 바탕으로, AI가 분석할 최적의 코드 범위를 추출한다. 모든 파일을 읽는 것보다는, 제한된 토큰 내에서 분석 품질을 높일 수 있도록 초점을 맞춘다.

    윈도우 슬라이싱을 통한 컨텍스트 최적화

    파일 전체를 AI에 전달하는 방식은 입력 토큰을 기하급수적으로 늘릴 뿐만 아니라, 오히려 분석의 초점을 흐릴 수 있다고 판단했다. 따라서, 에러 발생 라인을 기준으로 상하 20행의 코드만 잘라냈다.

    40줄 정도의 코드 컨텍스트라면 함수의 전체적인 흐름과 변수 사용처를 파악하기에 충분하다고 판단했다. 또한, 로컬 LLM 환경에서도 지연 시간을 최소화할 수 있다고 생각했다.

    AI 가독성을 위한 코드 포맷팅

    AI가 코드 내에서 에러 위치를 즉각적으로 특정할 수 있도록 코드 스니펫에 가독성 보조 장치를 추가했다.

  • 각 줄 앞에 라인 번호를 부여하여 AI가 "몇 번째 줄에 문제가 있다"라고 구체적으로 답변할 수 있도록 했다.
  • 실제 에러가 발생한 지점에는 시각적인 마커(>)를 추가하여 AI가 해당 지점에 집중하도록 했다.
  • if (file.location) {
      // "22:15" -> 22 추출
      const targetLine = parseInt(file.location.split(":")[0]);
      const lines = sourceCode.split("\n");
    
      // 앞뒤 20줄 범위 설정 (파일 시작과 끝 범위 체크)
      const start = Math.max(0, targetLine - 21);
      const end = Math.min(lines.length, targetLine + 20);
    
      // 줄 번호를 추가하여 슬라이싱
      codeSnippet = lines
        .slice(start, end)
        .map((line, index) => {
          const currLineNum = start + index + 1;
          const marker = currLineNum === targetLine ? " > " : "   ";
          return `${marker}${currLineNum} | ${line}`;
        })
        .join("\n");
    }
    향후 개선 방향

    현재는 고정된 20줄 범위를 사용하고 있지만, 향후에는 유료 모델 사용 시의 비용 최적화나 분석 난이도에 따라 이 컨텍스트 범위를 동적으로 조절할 필요가 있다. 또한 각 줄 앞에 번호와 마커를 붙이는 대신, “20~60번 라인 구간의 코드”와 같이 라인 범위 정보만 명시해 입력 토큰 수를 줄이는 방안도 고려하고 있다. 다만 이 방식은 모델이 코드를 정확히 매핑하지 못한 경험이 있었기에, 이에 맞는 프롬프트 설계 개선도 함께 진행해야 할 것이다.

    (4) Ollama 프롬프트 작성

    현재 단계
    Notion image

    에러 컨텍스트를 바탕으로 AI가 최적의 분석 결과를 도출할 수 있도록 프롬프트를 작성하는 단계다.

    프롬프트 설계

    AI가 노션 리포트에 즉시 활용 가능한 데이터를 생성하도록 5가지 섹션으로 자세하게 프롬프트를 설계했다.

  • 디버깅 전문가 역할을 부여하여 근본 원인 분석에 집중할 수 있도록 유도했다.
  • fixCode 작성 시 전체 파일이 아닌 수정이 필요한 함수 / 블록 단위로만 작성하게 하여 출력 토큰 낭비를 막고자 했다.
  • 당신은 시니어 프론트엔드 개발자이자 디버깅 전문가입니다. 
    제공된 에러 맥락과 소스 코드를 교차 분석하여 근본 원인을 찾고, 노션(Notion) 리포트용 JSON 응답을 생성하세요.
    
    ## 1. 에러 맥락
    - 에러명: ${data.title}
    - 메시지: ${data.error.message}
    - 발생 위치: ${data.error.primaryLocation}
    - 호출 흐름: ${data.error.stackSummary}
    - 환경: ${data.context.os} / ${data.context.browser} (${data.context.env})
    
    ## 2. 원인 체인 (error.cause)
    ${causesSection}
    
    ## 3. 관련 소스 코드 (에러 지점 주변부)
    ${codeContextSection}
    
    ## 4. 상세 분석 및 응답 요구사항 (중요)
    - **problemAnalysis**: 단순 현상이 아닌 '왜' 발생했는지 분석하세요. 핵심 키워드에 **볼드**를 사용하세요.
    - **fixStrategy**: 어떤 방향으로 고칠지 1~2문장으로 요약하세요.
    - **fixContent**: 구체적인 변경 사항을 **반드시 한 줄에 하나씩 글머리 기호('-')를 사용하여 리스트 형태로** 작성하세요. 각 줄 사이에는 줄바꿈을 포함하세요.
    - **fixCode**: 수정이 필요한 부분만 **작업 단위(함수 또는 블록)**별로 작성하세요. 전체 파일을 작성하지 마세요. 반드시 변경 전/후의 맥락을 알 수 있는 주석을 포함하세요. 줄 번호는 반드시 포함하지 마세요.
    - **riskLevel**: LOW, MEDIUM, HIGH 중 하나를 선택하세요.
    - **testSteps**: 검증을 위해 필요한 단계를 배열 형태로 작성하세요.
    
    ## 5. 응답 형식 (JSON만 출력, 코드 블록 기호 \`\`\`json 금지)
    {
      "summary": "한 줄 요약",
      "problemAnalysis": "근본 원인 상세 분석",
      "fixStrategy": "해결을 위한 접근 방식",
      "fixContent": "- 변경 사항 1\\n- 변경 사항 2\\n- 변경 사항 3",
      "fixFilePath": "${primaryFile?.file || "src/..."}",
      "fixCode": "// [수정된 함수/블록명]\\nfunction example() {\\n  // ... 기존 코드\\n  // FIXED: 에러 방지를 위해 null 체크 추가\\n  if (data) { \\n    // ... 수정된 로직\\n  }\\n}",
      "testSteps": ["테스트 단계 1", "단계 2"],
      "riskLevel": "LOW | MEDIUM | HIGH",
    }
    LLM 파라미터 튜닝

    로컬 환경에서 구동되는 deepseek-coder-v2:16b 모델의 응답 일관성을 확보하기 위한 파라미터를 설정했다.

  • temperature: 0.1 : 디버깅 시에는 창의성보다 정확성과 재현성이 중요하므로, 같은 에러 입력에 대해 최대한 동일한 분석 결과를 내도록 낮게 설정했다. DeepSeek 에서도 코딩 / 수학에는 0에 가까운 temperature를 권장하고 있다.
  • top_p: 0.9 : 낮은 temperature 설정과 함께, 너무 기계적인 한 가지 답만이 아니라 약간의 다양성은 유지하되 노이즈를 억제하는 방향으로 설정했다. 또한, 프롬프트에서 구체적인 방안을 제시하였기에, top_p 값을 높여 제한된 범위 내에서 다양성을 부여하고자 하였다.
  • num_ctx: 8192 : Ollama에서 한 번의 호출에서 사용할 수 있는 활성 컨텍스트 길이를 의미한다. 8K로 설정해 최대 3개 파일의 코드 스니펫과 상세 스택 트레이스를 함께 제공하더라도 중요한 정보가 잘리지 않도록 했다.
  • const AI_TEMPRATURE = 0.1;
    const AI_TOP_P = 0.9;
    const AI_NUM_CTX = 8192;
    
    const body = {
      model: "deepseek-coder-v2:16b",
      prompt,
      stream: false,
      format: "json",
      options: {
        temperature: AI_TEMPRATURE,
        top_p: AI_TOP_P,
        num_ctx: AI_NUM_CTX,
      },
    };
  • The Temperature Parameter | DeepSeek API Docs
  • (5) Ollama 실행

    현재 단계
    Notion image

    가공된 데이터를 로컬 AI인 Ollama에 전달하여 실제 분석을 수행한다.

    Docker-Host 설정

    n8n은 독립적인 Docker 컨테이너에서 격리 실행하고, Ollama는 GPU 가속을 위해 macOS 호스트에 직접 설치 및 구동한다. 컨테이너에서는 Docker Desktop이 제공하는 host.docker.internal을 사용해 호스트의 Ollama 서비스(11434 포트)에 HTTP로 접근하도록 구성했다.

    이때 Ollama를 Docker로 감싸지 않고 macOS에서 직접 실행함으로써 M4 Pro의 Metal 기반 GPU를 오버헤드 없이 활용할 수 있으며, Docker 환경에서 CPU 전용으로 동작할 때보다 추론 성능과 효율이 크게 향상된다.

    GPU 사용 확인
    GPU 사용 확인
  • 맥북에서 Ollama를 도커로 설치할때와 local로 설치할때의 차이
  • 타임아웃 확장

    n8n의 HTTP Request 노드로 Ollama를 호출할 때, 모델 규모와 프롬프트 길이에 따라 응답까지 수십 초에서 수 분이 걸릴 수 있다. 옵션에서 명시적으로 300000ms(5분)로 타임아웃을 확장하여, 연결이 끊기는 상황을 줄이고, 한 번의 요청 안에서 분석 결과를 끝까지 받아올 수 있도록 했다.

    "options": {
      "timeout": 300000
    }

    (6) Ollama 데이터 검증 및 가공

    현재 단계
    Notion image

    AI로부터 수신한 JSON 응답을 Notion 데이터베이스 스키마에 최적화된 형태로 재구성한다.

    JSON 응답 정리

    “한 줄 요약, 원인 분석, 수정 전략, 수정 코드, 테스트 단계, 리스크 레벨” 처럼 노션에 저장하기 좋은 형태의 분석 결과 객체로 다시 한 번 매핑했다. 또한, LLM의 응답은 간혹 누락된 필드가 발생하거나 예상치 못한 데이터 타입이 반환될 수 있다. 이를 방지하기 위해 방어적으로 코드를 작성했다.

  • 분석 결과가 비어있을 경우 빈 문자열("")이나 기본 경로("unknown")를 할당하여 노션 기록 단계에서 발생할 수 있는 에러를 방지했다.
  • testSteps와 같은 배열 형태의 데이터는 실제 배열 여부를 확인하여, 이후 단계에서 인터페이스 불일치가 발생하지 않도록 했다.
  • analysis = {
      summary: parsed.summary,
      problemAnalysis: parsed.problemAnalysis ?? "",
      fixStrategy: parsed.fixStrategy ?? "",
      fixContent: parsed.fixContent ?? "",
      fixCode: parsed.fixCode,
      fixFilePath:
        parsed.fixFilePath ?? promptData.error?.primaryLocation?.split(":")[0] ?? "unknown",
      testSteps: Array.isArray(parsed.testSteps) ? parsed.testSteps : [],
      riskLevel: parsed.riskLevel,
    };

    (7) Notion API 객체 모델로 변환

    현재 단계
    Notion image

    앞서 재구성한 데이터를 노션의 객체 모델로 변환하여, 분석 리포트를 생성할 준비를 한다.

    노션에 맞는 텍스트 / 블록 가공

    Ollama가 돌려준 분석 결과를 바로 노션에 넣기보다는, 볼드 / 불릿 등 서식을 유지한 채 노션 블록 구조로 변환하는 과정을 거쳤다.

    데이터베이스 속성 설정

    리포트가 단순 기록으로만 사용되지 않고 관리 도구로서 기능하도록 데이터베이스 스키마를 설계했다. Status (진행 상태), Risk Level (위험도), Environment (발생 환경) 등을 select 타입으로 정의하여, 나중에 노션 대시보드에서 필터링과 정렬에 활용하고자 했다.

    // 노션 페이지 생성용 JSON 구조
    const notionBody = {
      properties: {
        Name: {
          title: [{ text: { content: pageTitle } }],
        },
        Status: {
          select: { name: "Open" },
        },
        "Risk Level": {
          select: { name: data.riskLevel || "MEDIUM" },
        },
        Environment: {
          select: { name: data.context?.env || "unknown" },
        },
        // ... 
      }
      // ...
    }

    (8) 노션 페이지 생성

    현재 단계
    Notion image

    모든 데이터 처리가 완료된 후, 최종적으로 노션 API를 호출하여 에러 분석 페이지를 생성한다. 사전에 정의한 Notion API Credential을 참조하여, 인증 토큰을 노출하지 않고 안전하게 API 호출을 수행한다.

    {
      "parameters": {
        "method": "POST",
        "url": "https://api.notion.com/v1/pages",
        "authentication": "predefinedCredentialType",
        "genericAuthType": "notionApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [ ... ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.notionBody) }}",
        "options": {}
      },
      "name": "Create Notion Issue",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
    }

    최종 결과


    n8n이 에러 한 건당 Notion 데이터베이스에 새로운 페이지를 생성하고, 그 안에 AI 분석 결과가 구조화된 형태로 기록되도록 전체 파이프라인이 마무리된다.

    정렬 및 필터링을 위한 속성들
    정렬 및 필터링을 위한 속성들
    AI가 분석한 에러 요약본
    AI가 분석한 에러 요약본
    발생한 에러의 상세 로그와 트레이스
    발생한 에러의 상세 로그와 트레이스
    AI가 제안하는 코드 수정 방향과 실제 적용 코드
    AI가 제안하는 코드 수정 방향과 실제 적용 코드
    코드 수정 후 동작 검증 체크리스트
    코드 수정 후 동작 검증 체크리스트

    마무리하며


    개선 및 보완할 점

    모델 선택, 토큰 최적화, 정확도

    Gemini나 Claude 같은 상용 모델은 뛰어난 추론 성능을 제공하지만, 개인 프로젝트 단계에서는 비용 부담이 있었다. 그럼에도 향후에 다른 프로젝트에서의 확장성을 고려한다면, 어느 정도 비용을 감수하더라도 유료 API로 마이그레이션하는 방향을 고민하고 있다. 이 과정에서 클라우드 환경으로 전송되는 코드 데이터에 대한 보안 이슈도 함께 고민해볼 계획이다.

    초기에는 “언제든 모델을 갈아탈 수 있다”는 전제를 두고, 토큰 절감과 비용 최적화에 집중했다. 하지만, 프로젝트를 진행하며 비용보다는 분석 결과의 정확도와 요구사항에 집중하게 되었다. 토큰을 무조건 줄이기보다 어떻게 더 좋은 응답을 만들지? 에 더 신경 썼다. 앞으로는 불필요한 토큰을 줄이면서도 안정적인 추론 품질이 나올 수 있도록 프롬프트 구조와 데이터 전처리 파이프라인을 정교하게 다듬고자 한다.

    예외 상황에 대한 방어적 구성

    이번에는 워크플로우의 동작 성공에만 집중하다 보니, Ollama 로딩 지연, 네트워크 타임아웃, 일시적인 API 실패 같은 예외 상황에 대한 처리는 미흡했다. 실제 운영 환경에서 신뢰성을 확보하려면, HTTP Request 노드의 재시도, 에러 트리거 워크플로우를 활용한 알림 등을 단계적으로 도입할 필요가 있다. 일시적인 장애로 인해 전체 파이프라인이 중단되지 않도록 재시도 전략과 폴백 경로를 포함한 방어적 구성을 추가할 계획이다.

    복잡한 코드베이스에 대한 검증

    현재는 상대적으로 단순한 예제 중심으로 기능을 검증했기 때문에, 호출 스택이 깊거나 의존 관계가 복잡한 대규모 코드베이스에 대한 검증이 아직 충분하지 않다. 실제 프로젝트에서 사용할 수 있으려면, 다양한 복잡도의 서비스에 적용해 보면서 응답 시간 분포, 타임아웃 발생 비율 등을 꾸준히 확인하고, 병목 구간을 찾아 개선하는 과정이 필요하다. 이 정도 규모와 복잡도의 코드까지는 언제든 안전하게 돌릴 수 있다는 기준선을 세우고, 프로덕션에서도 활용 가능한 수준인지 검증해보고자 한다.

    n8n을 경험하며

    n8n으로 워크플로우를 구성하면서, 생각보다 적은 설정만으로 트리거부터 LLM 분석, 노션 페이지 생성까지의 흐름이 자연스럽게 자동화되는 경험이 인상적이었다. 에러 발생 시점부터 분석과 리포트 생성이 완료되기까지 평균 20~25초 정도면 충분했고, 수동 디버깅에 비해 피드백 루프가 눈에 띄게 짧아졌다. 왜 자동화 도구가 개발자의 생산성을 높이고, 익혀 둘 가치가 있는지 조금은 실감하게 되었다.

    앞으로는 반복적으로 발생하는 작업들에 자동화를 붙여서 더 효과적으로, 효율적으로 처리해볼 수 있지 않을까? 고민해보려 한다. 어떤 작업을 어떻게 쪼개서 자동화에 넘길 수 있을까? 를 계속 고민하면서, 더 많은 수작업을 워크플로우로 바꿀 것이다. 사람이 집중해야 하는 문제 정의와 의사결정에 더 많은 시간을 쓸 수 있는 환경을 만들어가고 싶다.

    관련 글