회의 녹음 파일을 두고 "의사록을 만들어줘"라고 한마디 하면, 텍스트 변환(Transcription)・화자 분리(Speaker Diarization)・고유명사 보정・의사록 생성까지 한 번에 실행되는 시스템을 Claude Code의 스킬로서 만들었습니다. 제조업의 생산기술 엔지니어로서 본업이 소프트웨어 엔지니어가 아닌 제가, 채택한 구성·버린 선택지·빠졌던 함정을 작성합니다.
무엇을 만들었는가
입력: 회의 녹음/녹화(m4a / mp4 / mp3 / wav)를 클라우드 동기화 폴더에 배치
출력: 상대별 템플릿에 따른 Markdown 의사록 + 실명화가 완료된 텍스트 변환 전문
인간의 작업: 요청 한마디와 완성된 결과물의 확인·미세 수정뿐
- 처리는 2단계(Phase)로 이루어집니다. 결정론적(Deterministic)으로 할 수 있는 공정은 스크립트로, 문맥 판단이 필요한 공정만 AI에게 남겨두었습니다.
[녹음 파일] │ ├─ 페이즈 1 (Python・결정론적) 백그라운드 실행 │ ffmpeg 정규화 → faster-whisper로 텍스트 변환 → pyannote.audio로 화자 분리 │ → 고유명사 힌트 사전으로 결정론적 치환 → 아카이브 + 매니페스트 출력 │ └─ 페이즈 2 (Claude・문맥 판단) 화자의 실명화 (자기소개·호칭으로부터 추론) → 문맥으로부터 오변환 보정 → 결정 사항・TODO・담당자・기한을 추출하여 의사록 생성
텍스트 변환이나 화자 분리에 AI의 대화 능력은 필요하지 않습니다. 반대로 "이 SPEAKER_01은 누구인가", "이 기한은 몇 월 며칠을 가리키는가"는 스크립트로 작성할 수 없습니다.
실제 동작과 실측치
최근 회의(상세 내용은 생략)에서의 실측입니다.
| 항목 | 실측치 |
|---|
| 녹음 길이 | 40분 |
| 페이즈 1 처리 시간 (방치 시) | 2시간 27분 (CPU 실행) |
| 페이즈 2 + 인간의 확인·수정 | 15분 |
| 기존 수작업 (다시 듣기 + 초안 작성) | 회의당 30~60분 |
생성되는 의사록의 구조는 다음과 같습니다 (내용은 생략하지만 구조는 실제와 같습니다).
# ○○사 회의 의사록 - 일시 / 장소 / 참석자 ## 결정 사항 ## TODO (내용・담당자・기한 표. 상대적 기한은 절대 날짜로 변환 완료) ## 논의 메모 ## 확인 필요 사항 (청취 불확실·화자 불명 구간을 격리)
"확인 필요 사항"이 운용의 핵심입니다. 화자를 특정할 수 없었던 발언은 잘못된 실명을 부여하지 않고
SPEAKER_*
인 상태로 남겨두어 여기에 열거하도록 했습니다. 불확실한 출력을 확신이 있는 것처럼 섞지 않는 설계입니다.
설계의 판단: 무엇을 선택하고 무엇을 버렸는가
클라우드 텍스트 변환 API는 사용하지 않았습니다. 이유는 정확도나 요금 때문이 아니라, 회의 음성이 기밀 사항의 집합체이기 때문입니다. faster-whisper와 pyannote.audio는 모두 로컬 실행이며, 음성 데이터 그 자체는 전혀 PC 외부로 나가지 않습니다. pyannote.audio에는 기본적으로 활성화된 텔레메트리(Telemetry) 기능이 있어, 실행할 때마다 회의 길이(초)와 화자 수 설정값을
https://otel.pyannote.ai/v1/traces
로 전송합니다 (음성·텍스트 변환 내용·파일명은 포함되지 않습니다). 신경 쓰이는 경우 환경 변수 PYANNOTE_METRICS_ENABLED=false 또는 코드에서 set_telemetry_metrics(False, save_choice_as_default=True)로 무효화할 수 있습니다.
음성 지문(Voiceprint)을 통한 화자 식별도 보류했습니다. 음성 지문 프로파일의 사전 등록은 과도하다고 판단하여, 대신 자기소개나 "○○ 씨는 어떻게 생각하세요?"와 같은 호칭으로부터 문맥을 추론하는 방식을 택했습니다. 인간이 녹음을 듣고 누구인지 알아내는 것과 같은 단서입니다. 특정할 수 없었던 화자는 억지로 맞추지 않고 SPEAKER_*인 상태로 남겨두는 것을 철칙으로 삼고 있습니다. 잘못된 실명을 의사록에 심는 것이 가장 위험하기 때문입니다.
Whisper의 모델은 기본값으로 medium을 사용합니다. 최상위 모델인 large-v3는 medium보다 2~4배의 처리 시간이 걸리기 때문입니다. 오변환은 2단계로 보정합니다. 문맥에서 추측할 수 없는 고유명사(인명·사명)는 힌트 사전으로 결정론적으로 치환하며, 이 사전은 의사록을 만들 때마다 새로운 오변환을 자동으로 추가하며 성장시킵니다. 문맥에서 추측 가능한 오변환("논을 짜다" → "론을 짜다")은 AI가 보정합니다. 계약 관련 업무 등 오변환이 전혀 없어야 하는 회의에 대해서만 large-v3를 명시적으로 지정합니다.
방치할 수 있을 정도가 되기까지 빠졌던 함정
기술 선정보다 이쪽이 본론일지도 모릅니다.
실측 결과, 40분 분량의 녹음 파일에 대해 2시간 27분이 소요되었으나, ffmpeg, faster-whisper, pyannote 중 어디에서 어느 정도의 시간이 소요되는지는 아직 분리하여 파악하지 못했습니다. 공정별로 타임스탬프(Timestamp)를 출력하도록 개선하는 것이 다음 과제입니다.
선물: 「의사록의 금형(Template)」 프롬프트는 시스템 전체를 만들지 않더라도, 페이즈 2(Phase 2)의 추출 규칙만이라면 오늘부터 바로 사용할 수 있습니다. Teams/Zoom의 자동 전사(Transcription) 기능이나 스마트폰 녹음 앱의 출력물을 아래에 붙여넣기만 하면 됩니다.
당신은 의사록 작성 담당자입니다. 다음의 전사 텍스트로부터 의사록을 작성해 주세요. ## 출력 포맷 # 회의명 (내용으로부터 추정) - 일시·장소·참석자 ## 결정 사항 (불렛 포인트로 작성. 없을 경우 「해당 없음」이라고 명시) ## TODO | 내용 | 담당 | 기한 | |---|---|---| (없을 경우 「해당 없음」이라고 명시) ## 논의 메모 (테마별로 요점을 정리) ## 확인 필요 사항 (청취가 불확실한 부분·화자가 불분명한 발언은 여기에 격리한다) ## 규칙 - 결정 사항·TODO·담당·기한은 누락 없이 추출할 것 - 「다음 주까지」, 「월말」 등과 같은 상대적인 기한은 회의일을 기준으로 절대 날짜(YYYY-MM-DD)로 변환할 것 - 담당자가 불분명한 TODO는 「담당자 미정」이라고 작성할 것 - 불확실한 정보를 결정 사항에 섞지 말 것. 망설여진다면 확인 필요 사항으로 보낼 것. 회의일: [여기에 날짜 입력] 참석자: [여기에 이름 입력] 전사 내용: [여기에 붙여넣기]
핵심은 「없을 경우 『해당 없음』이라고 명시」와 「망설여진다면 확인 필요 사항으로 보낼 것」입니다. LLM은 빈칸을 센스 있게 채우려는 경향이 있으므로, 채우지 않는 선택지를 규칙으로 명시적으로 부여합니다.
| 항목 | Before | After |
|---|
| 인간의 작업 시간 | 회의당 30~60분 | 회의당 15분 |
| 음성 데이터 | — | 로컬 완결 (음성·전사 내용은 외부로 전송되지 않음) |
설계의 축은 「결정론적 요소는 스크립트, 문맥 판단만 AI」입니다. 장시간 백그라운드(Background) 처리는, 중단되지 않도록 노력하는 것(함정 1·2)과 중단되어도 재개할 수 있는 구조(함정 3)라는 두 바퀴로 이루어졌습니다.
화자 분리(Speaker Diarization)는 동석자의 목소리 성질이 비슷하면 섞이는 경우가 있어, 현재는 「확인 필요 사항」으로 보내 사람이 직접 확인하는 운영 방식입니다. 이 부분을 개선하면 다시 글을 쓰겠습니다.
HandaLab — 생산기술 엔지니어. 제조 현장의 자동화와 Claude Code 활용법을 발신하고 있습니다.
Discussion