음성봇을 스텁부터 만든 이유 — 테스트로 설계를 먼저 굳히다

시리즈 · 음성봇 직접 만들기 — 실시간 음성봇의 뼈대를 바닥부터 만든 8편 기록입니다. 1. 끼어들기(barge-in) — 재생 중인 TTS를 실시간으로 끊는 법 2. "말이 끝났다"는 판단 — VAD 엔드포인터 3. 엔진을 코드 수정 없이 바꾸는 인터페이스 설계 4. 로컬 LLM 대화 두뇌와 도구 호출 루프 5. 매 턴 지연시간을 재는 이유 6. 브라우저 마이크에서 서버까지 — 오디오 프로토콜 7. 로컬 Whisper STT 연결하기 8. 스텁부터 만든 이유 — 테스트 우선 설계 (완결) — 지금 보는 글 돌아보며 시작하는 마지막 편 이 시리즈를 시작할 때부터 정해 둔 규칙이 하나 있었습니다. 진짜 음성 인식 모델과 진짜 합성 모델이 준비되기 전까지는, 그 둘을 흉내만 내는 가짜 엔진으로 전체 구조를 먼저 돌려본다는 규칙입니다. 여덟 편을 거치며 끼어들기 처리, 상태 흐름, 프로토콜, 엔진 인터페이스를 하나씩 다뤘는데, 그 모든 것이 사실 이 가짜 엔진 위에서 검증됐습니다. 지금 코드를 다시 열어 보면 그때는 별생각 없이 세운 규칙이 결과적으로 시리즈 전체의 진행 속도를 결정했다는 게 눈에 들어옵니다. 이번 편에서는 왜 굳이 가짜부터 만들었는지, 그리고 그 선택이 시리즈 전체에 어떤 영향을 남겼는지 정리하면서 이 시리즈를 마무리하려 합니다. 실제 모델을 기다리지 않기로 한 이유 음성봇의 핵심 로직은 사실 음성 인식이나 음성 합성 자체가 아닙니다. 사용자가 말을 시작했는지 끝냈는지 판단하는 상태머신, 합성 중에 끼어들기가 들어왔을 때 스트림을 멈추는 동시성 처리, 이벤트가 순서대로 오가는 흐름 제어 같은 것들이 훨씬 까다롭습니다. 반면 실제 인식·합성 모델은 무겁고 설치가 까다롭고 실행 속도도 느립니다. 이 두 가지를 한꺼번에 붙잡고 있으면 어려운 동시성 버그를 실제 모델의 로딩 시간과 뒤섞어 디버깅하게 됩니다. 모델이 느려서 반응이 늦은 건지, 상태머신이 잘못 짜여서 늦은 건지 구분이 안 되는 상황을 미...

클로바노트 회의록 자동 정리 — Discord에 올리면 Claude가 요약해 노션 저장

클로바노트 녹취부터 Discord·Claude·Notion·Make.com까지 회의록 자동화 봇 전체 파이프라인
클로바노트 녹취 → Discord 드랍 → Claude 분석 → Notion 저장 + Make.com 이메일로 이어지는 전체 파이프라인 한 장 요약. 사람이 하는 일은 녹취와 파일 드랍뿐입니다. (팀 운영 환경)

회의록 자동화 봇을 직접 만들어 팀에서 운영하고 있습니다. 파이프라인의 시작은 네이버 클로바노트입니다. 회의 중 클로바노트로 음성을 녹취하면 회의가 끝날 때 .txt 파일로 다운로드할 수 있습니다. 그 파일을 Discord 채널에 드래그하면, Claude Agent SDK가 내용을 분석해 개요·결정사항·액션아이템으로 구조화하고 Notion에 포맷된 회의록 페이지를 자동 생성합니다. 동시에 Make.com이 팀 전체에 이메일 요약을 발송합니다. 사람이 할 일은 클로바노트로 녹취하고, 파일을 Discord에 드랍하는 것뿐입니다.

이 글은 네이버 클로바노트 녹취 → Discord 파일 드랍 → Claude Agent SDK → Notion 자동 생성 → Make.com 이메일 파이프라인 전체를 구축하는 실전 가이드입니다. 단순 API 호출이 아니라 Claude Agent SDK를 써서 에이전트 방식으로 분석하고, 한국어 팀 고유명사 오인식을 방지하는 사전 서비스까지 포함합니다. 직전 편 Discord 유튜브 봇 → Notion 아카이브와 같은 "Discord = 인박스" 패턴의 회의록 버전입니다.

한 가지 먼저 짚자면, 이 봇은 처음엔 제 PC의 Claude Code에 Notion MCP를 붙여 자연어로 빠르게 프로토타이핑했습니다. 그러다 팀 공용 PC에 무인으로 상시 띄우면서 Notion을 notion-client 직접 호출로 내렸는데, 그 전환 이유는 3.6에서 자세히 다룹니다. MCP가 개발에 강한 지점과, 상시 운영에서 직접 API가 안정적인 지점이 갈렸기 때문입니다.

1. 왜 이 조합인가

회의록 작성의 병목은 "기록" 자체가 아니라 구조화와 공유입니다. 회의 내용을 텍스트로 받아서 개요, 결정사항, 액션아이템으로 분류하고, 담당자·기한까지 정리해 전원에게 공유하는 과정이 매번 반복됩니다.

이 봇은 세 가지 원칙으로 설계했습니다.

첫째, 인박스는 Discord 하나입니다. 회의가 끝나면 파일을 채널에 드래그합니다. Notion 직접 편집이나 별도 앱 없이 이미 열려 있는 Discord가 입력창입니다.

둘째, Claude Agent SDK로 에이전트 방식 분석합니다. 단순 API 호출과 달리, SDK가 도구 호출과 반복 추론을 통해 회의록의 맥락을 더 정확하게 파악합니다. Pydantic 모델로 출력 구조를 강제하므로 매번 일관된 포맷이 나옵니다.

셋째, Notion 저장에서 끝내지 않습니다. Make.com 웹훅으로 이메일 요약까지 자동 발송해 Notion을 확인하지 않는 팀원도 핵심 내용을 바로 받습니다.

2. 파이프라인 구조


[회의 중] 네이버 클로바노트 앱으로 음성 녹취
    ↓
[회의 후] 클로바노트 → 텍스트 변환 완료 → .txt 다운로드
    ↓
Discord 채널에 .txt 파일 드랍
    ↓
봇 다운로드 + 고유명사사전 주입
    ↓
Claude Agent SDK 분석
  → 개요 / 결정사항 / 액션아이템 / 참석자 / 다음 회의
    ↓
Notion API → 포맷된 회의록 페이지 생성
    ↓
Make.com 웹훅 → HTML 이메일 팀 전체 발송
    ↓
Discord 완료 알림 (Notion 링크 포함)
Discord 봇이 회의록 분석을 마치고 Notion·HTML·이메일 결과 링크를 회신한 화면
파일을 올리면 봇이 회의록을 구조화한 뒤, 하단에 Notion 페이지·HTML·이메일 발송 결과 링크를 한 번에 회신합니다. (회의 본문은 실제 운영 내용이라 모자이크 처리)

3. 단계별 구현

3.0 네이버 클로바노트 녹취 → .txt 다운로드

파이프라인의 첫 단계입니다. 회의 시작 전 스마트폰에서 네이버 클로바노트 앱을 실행해 녹음을 시작합니다. 클로바노트는 회의가 끝난 뒤 음성을 자동으로 텍스트로 변환해줍니다. 화자 분리도 지원해서 누가 어떤 발언을 했는지 구분된 형태로 나옵니다.

변환이 완료되면 앱 또는 웹(clovanote.naver.com)에서 텍스트 내보내기 → .txt 파일로 다운로드합니다. 이 파일이 봇의 입력이 됩니다.

[참고] 클로바노트 무료 플랜은 월 300분 녹취를 지원합니다. 팀 회의가 하루 1~2시간 수준이면 무료로 충분히 운영됩니다.

3.1 Discord 봇 + 파일 감지

Developer Portal에서 봇을 만들고 Message Content Intent를 켭니다. on_message 이벤트에서 .txt 또는 .md 첨부 파일을 감지해 다운로드합니다. 토큰과 채널 ID는 .env에만 저장합니다.

3.2 고유명사사전 주입

클로바노트가 유사 발음을 잘못 받아쓰면(예: 참석자 이름, AI 약어) 그 오류가 그대로 Notion 회의록에 박제됩니다. DictionaryService가 고유명사사전.md를 읽어 프롬프트에 주입하면, Claude가 "잘못 들린 표기 → 올바른 고유명사"로 교정합니다. 자세한 동작은 4.2에서 다룹니다.

3.3 Claude Agent SDK 분석


from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    system_prompt=system_prompt,
    max_turns=5
)
result = await query(prompt=meeting_text, options=options)

Agent SDK는 단순 호출이 아닌 멀티턴 추론으로 회의록을 분석합니다. Pydantic 모델로 출력 스키마를 정의해두면 결과가 항상 같은 구조로 나옵니다.

3.4 Notion 페이지 자동 생성


from notion_client import AsyncClient

notion = AsyncClient(auth=os.getenv("NOTION_API_KEY"))
await notion.pages.create(
    parent={"page_id": NOTION_DATABASE_ID},
    properties={"title": [{"text": {"content": title}}]},
    children=blocks  # 구조화된 회의록 블록
)

개요, 결정사항, 액션아이템을 각각 Heading + BulletedList 블록으로 구성합니다. Notion 100블록 제한이 있으므로 긴 회의록은 섹션별로 나눠 업로드합니다.

회의록은 Notion 페이지로 저장하는 동시에, 팀이 브라우저에서 바로 열어보도록 읽기 좋은 HTML 페이지로도 변환해 배포합니다. 같은 분석 결과를 Notion(내부 DB)과 HTML(공유 링크) 두 형태로 동시에 남겨, 보는 사람의 환경에 맞춰 선택할 수 있게 했습니다.

Claude가 개요·결정사항·액션아이템으로 구조화해 Notion에 자동 생성한 회의록 페이지 같은 회의록을 HTML로 변환해 배포한 페이지
왼쪽: Claude가 핵심 요약·논의 내용·액션아이템으로 구조화해 Notion에 자동 생성한 회의록. 오른쪽: 같은 내용을 HTML 페이지로 변환·배포한 결과. 하나의 분석이 두 형태로 동시에 남습니다. (본문은 실제 운영 내용이라 모자이크 처리)

3.5 Make.com 이메일 자동 발송


import aiohttp

async with aiohttp.ClientSession() as session:
    await session.post(
        os.getenv("MAKE_WEBHOOK_URL"),
        json={"subject": title, "body": html_body, "to": EMAIL_RECEIVER}
    )

Make.com 시나리오에서 웹훅을 수신해 Gmail 또는 Outlook으로 HTML 이메일을 발송합니다. Notion 링크와 핵심 요약이 이메일 본문에 포함됩니다.

3.6 배포와 연동 — 클라우드·MCP에서 팀 공용 PC·직접 API로

처음엔 제 PC에서 MVP로 만들어 손으로 직접 돌려 썼습니다. 회의 끝나면 파일 올리고 결과를 확인하는 식이었죠. 그러다 사람이 안 붙어도 24시간 자동으로 돌아가야 할 필요가 생겨, 첫 상시 운영은 Railway 클라우드 배포로 갔습니다. railway.toml과 Dockerfile이 포함돼 있어 railway up 한 번으로 올라갑니다.


railway login
railway up

다만 Railway도 오래가진 못했습니다. 회의록 분석은 Claude Agent SDK가 멀티턴으로 도는 무거운 작업이라 실행 시간이 길고, 회의가 몰리는 날은 호출이 잦습니다. Railway 무료 플랜의 실행 시간·리소스 리밋에 금방 걸렸습니다.

그래서 최종적으로는 팀 공용 PC에 패키징해서 24시간 상시 띄워두는 방식으로 정착했습니다. 어차피 팀이 같은 공간에서 늘 켜져 있는 PC가 있으니, 그 위에서 봇 프로세스를 돌리는 게 비용 0원에 리밋도 없습니다. 정리하면 내 PC MVP → Railway 클라우드 → 팀 공용 PC 상시 운영 순으로 옮겨온 셈입니다. 클라우드 배포가 멋있어 보이지만, 사내 도구는 이미 있는 자원을 쓰는 게 현실적으로 가장 안정적이었습니다.

[참고] 외부 사용자에게 서비스하는 봇이라면 Railway, Fly.io, Render, Vercel 같은 클라우드 배포가 맞습니다. 각각 무료 플랜과 가격 정책이 다르니 트래픽·실행 시간에 맞춰 고르면 됩니다. 하지만 팀 내부에서만 쓰는 도구라면 공용 PC 상시 운영이 비용·리밋 면에서 훨씬 낫습니다.

연동도 바뀌었습니다 — Notion MCP에서 직접 API로

배포 위치만 바뀐 게 아닙니다. Notion 연동 방식도 MCP에서 직접 API로 내렸습니다.

처음 이 봇을 만들 때는 제 PC에서 Claude Code에 Notion MCP를 붙여 개발했습니다. MCP를 쓰면 "이런 구조로 회의록 페이지 만들어줘" 같은 자연어로 Notion을 다룰 수 있어, 블록 스키마를 일일이 코딩하지 않고도 포맷을 빠르게 실험할 수 있었습니다. 프로토타이핑 단계에서는 이게 압도적으로 빨랐습니다.

문제는 팀 공용 PC에 무인으로 상시 띄울 때였습니다. MCP 서버는 본래 Claude 같은 에이전트가 대화 중에 도구를 호출하도록 설계돼 있습니다. 사람 없이 24시간 도는 봇 프로세스에 물리려면 MCP 서버를 따로 띄우고 인증 세션을 계속 유지·관리해야 하는데, 봇이 정해진 순서대로 페이지를 만들고 이메일을 쏘는 결정론적 파이프라인에는 이 구조가 과합니다. 오히려 notion-client로 직접 API를 호출하는 쪽이 단순하고, 프로세스가 죽어도 재시작만 하면 되니 더 안정적이었습니다.

그래서 운영 버전에서는 Notion MCP를 걷어내고 3.4의 notion-client 직접 호출로 대체했습니다. 정리하면 — MCP는 탐색·개발 단계에서 강하고, 사람 없이 도는 상시 운영에서는 직접 SDK·API가 안정적이라는 게 직접 굴려본 결론입니다. (GitHub에 올린 소스는 이 운영 버전, 즉 직접 API 기준입니다.)

[참고] MCP를 자연어 도구로 쓰는 인터랙티브 개발과, 봇이 무인으로 돌리는 상시 운영은 요구 조건이 다릅니다. "개발은 MCP로 빠르게, 운영은 직접 API로 단단하게"가 같은 코드를 두 단계로 운영해 본 체감입니다.

4. 자주 겪는 문제

4.1 Claude Agent SDK rate_limit_event로 봇이 멈춤

SDK가 알 수 없는 메시지 타입(rate_limit_event)을 받으면 MessageParseError로 중단됩니다. SDK의 parse_message를 monkey-patch해 알 수 없는 타입은 None으로 처리하도록 우회합니다. agent_service.py 상단의 _patch_sdk_message_parser() 함수가 이 역할을 합니다.

4.2 고유명사 오인식으로 Notion 내용이 틀림

STT(음성→텍스트) 단계에서 클로바노트가 유사 발음을 잘못 받아쓰는 일이 가장 큰 문제입니다. 음성 인식은 소리가 비슷하면 틀린 단어로 옮깁니다. "엘엘엠(LLM)"을 "엘엠(LM)"으로, "에이투에이(A2A)"를 엉뚱한 단어로 적습니다. 특히 회의에서 자주 불리는 참석자 이름과 고객명이 자주 틀립니다. "박형수"가 "박현수"로, 고객사 이름이 비슷한 발음의 다른 단어로 박제되는 식입니다.

고유명사사전.md의 핵심 역할은 바로 이 유사 발음·오기재를 올바른 고유명사로 치환하는 것입니다. full name 풀이를 알려주는 것을 넘어, "이렇게 잘못 들리면 → 이게 맞다"를 명시해 교정합니다.


# 형식:  잘못 들린 표기 → 올바른 고유명사
LM → LLM (Large Language Model)
A to A → A2A (Agent-to-Agent)
앤스로픽 → Anthropic
박현수 → 박형수      # 참석자 오기재 교정
김민석 → 김민식      # 참석자 오기재 교정

DictionaryService가 이 파일을 읽어 시스템 프롬프트 앞에 주입하면, Claude가 분석 단계에서 잘못 받아쓴 표기를 올바른 이름·용어로 바로잡습니다. 새 참석자나 용어가 생길 때마다 "틀린 표기 → 맞는 표기" 한 줄만 추가하면 됩니다. 코드 수정 없이 파일만 편집하므로 관리가 편합니다.

4.3 Make.com 웹훅 타임아웃으로 이메일 미발송

Claude Agent SDK 분석이 길어지면(보통 10~30초) Make.com 웹훅 호출이 봇 처리 흐름 후반부에 위치해 있어 응답이 늦어집니다. Make.com 무료 플랜은 시나리오 실행 제한이 있고, 웹훅 수신 후 응답이 없으면 실패로 처리합니다.

가장 효과적인 대응은 이메일 발송을 분리하는 것입니다. Notion 페이지 생성이 완료된 직후 Make.com 웹훅을 호출하고, 봇의 Discord 응답과 독립적으로 처리합니다. 웹훅 호출에 asyncio.create_task()를 써서 비동기로 분리하면 이메일 발송 지연이 봇 응답을 막지 않습니다.

Make.com 시나리오 자체에서 실패 시 재시도 횟수를 설정할 수 있습니다. 시나리오 → 설정 → "오류 처리"에서 최대 재시도 2~3회로 설정해 두는 것을 권장합니다.

마치며

회의가 끝나는 순간 회의록은 이미 Notion에 올라가 있고 이메일이 나갑니다. 기록·정리·공유라는 반복 작업에서 완전히 손을 뗄 수 있었습니다.

이 봇은 Discord 콘텐츠 아카이브 시리즈의 한 편입니다. 유튜브 영상·일정 캡처·인스타그램 아카이브 같은 다른 봇은 시리즈의 다른 편에서 다룹니다. 소스 코드와 Make.com 시나리오 설정은 GitHub 저장소에서 확인하실 수 있습니다.

댓글

이 블로그의 인기 게시물

디스코드 구글캘린더 연동 봇 만들기: 카톡 캡처 OCR 자동 등록 시스템 구축

클로드 코드 비용 줄이는 법 — ollama 로컬 LLM 연동 (M3 맥북)

유튜브 노션 자동 저장 봇 만들기 — Claude CLI MCP + Discord 233일 운영기