Skip to content
바람부는 자유
Go back
LLM Engineering

LLM을 활용한 회의록 자동 요약 시스템

LLM Engineering (8/21)

  1. LLM 토큰 기본 개념
  2. LLM Tokenizer 추가 학습
  3. LLM Inference 이해하기: 토큰 예측의 마법
  4. LLM API 기초 (Part 1/3)
  5. LLM API 중급 (Part 2/3)
  6. LLM API 고급 (Part 3/3)
  7. System Message 활용하기
  8. LLM을 활용한 회의록 자동 요약 시스템
  9. Multi-Modal AI 기초
  10. Gradio 기본 사용법
  11. Hugging Face 완전 정복: AI 모델의 GitHub
  12. Google Colab 사용해보기: 무료로 GPU 환경에서 AI 모델 실행하기
  13. Tool Use (Function Calling)
  14. LLM 벤치마크 완전 가이드: 모델 성능 평가의 모든 것
  15. Vector Embeddings와 RAG 기초
  16. LangChain vs LiteLLM 비교 가이드
  17. 고급 RAG: 벡터 데이터베이스를 활용한 문서 검색 시스템
  18. RAG 기반 고객 상담 챗봇 만들기
  19. RAG 시스템 평가 (RAG Evaluation)
  20. 고급 RAG 기법 (Advanced RAG Techniques)
  21. 08-1. 데이터셋 개념 정리

소개

회의록 작성은 많은 조직에서 필수적이지만 시간이 많이 소요되는 작업입니다. 회의 내용을 정확하게 기록하고, 핵심 내용을 요약하며, 액션 아이템을 정리하는 데 회의 시간만큼의 시간이 걸릴 수 있습니다.

문제점

AI 솔루션

이 노트북에서는 LLM(Large Language Model)을 활용하여 음성 회의를 자동으로 텍스트로 변환하고, 구조화된 회의록을 생성하는 전체 파이프라인을 구축합니다.

다룰 내용

  1. 🎤 Speech-to-Text: OpenAI Whisper를 사용한 음성 → 텍스트 변환
  2. 🤖 LLM 활용: 다양한 LLM을 사용한 회의록 요약
  3. 📊 구조화: 요약, 논의 사항, 핵심 포인트, 액션 아이템 추출
  4. ⚖️ 모델 비교: Ollama(로컬) vs OpenAI(클라우드)
  5. 💡 실전 활용: 프롬프트 최적화 및 자동화 팁
import os
from dotenv import load_dotenv
from IPython.display import Markdown, display
from openai import OpenAI

필요한 라이브러리 설치 및 Import

먼저 필요한 라이브러리를 import합니다:

1단계: Speech to Text

OpenAI Whisper 모델

Whisper는 OpenAI가 개발한 범용 음성 인식 모델입니다:

주요 특징

지원 형식

이제 실제 음성 파일을 텍스트로 변환해보겠습니다.

# OpenAI Whisper를 사용한 음성 → 텍스트 변환
AUDIO_MODEL = "gpt-4o-mini-transcribe"  # Whisper 모델
audio_file_path = "./denver_extract.mp3"  # 변환할 오디오 파일
audio_file = open(audio_file_path, "rb")

# 환경 변수에서 API 키 로드
load_dotenv(override=True) 
openai_api_key = os.getenv("OPENAI_API_KEY")
openai = OpenAI(api_key=openai_api_key)

# 음성 → 텍스트 변환
transcription = openai.audio.transcriptions.create(
    model=AUDIO_MODEL, 
    file=audio_file, 
    response_format="text"  # text, json, srt, verbose_json, vtt 중 선택
)

print("변환 완료! 결과:")
display(Markdown(transcription))
변환 완료! 결과:
<IPython.core.display.Markdown object>

2단계: 프롬프트 엔지니어링으로 회의록 생성

이제 음성에서 텍스트로 전환된 내용을 기반으로 구조화된 회의록을 생성합니다.

프롬프트 설계 원칙

효과적인 회의록 생성을 위한 핵심 요소:

1. 명확한 역할 정의 (System Message)

2. 구체적인 요구사항 (User Prompt)

3. 컨텍스트 제공

프롬프트 최적화 팁

System Message 작성 팁:

User Prompt 작성 팁:

# 시스템 메시지: LLM의 역할과 출력 형식 정의
system_message = """
You produce minutes of meetings from transcripts, with summary, key discussion points,
takeaways and action items with owners, in markdown format without code blocks. 
"""

# 사용자 프롬프트: 구체적인 요구사항과 컨텍스트
user_prompt = f"""
Below is an extract transcript of a Denver council meeting.
Please write minutes in markdown without code blocks, including:
- a summary with attendees, location and date
- discussion points
- takeaways
- action items with owners
and korean translation.
Transcription:
{transcription}
"""

# OpenAI Chat API 형식으로 메시지 구성
messages = [
    {"role": "system", "content": system_message},  # LLM의 역할
    {"role": "user", "content": user_prompt}  # 사용자 요청
]

print("✓ 프롬프트 준비 완료!")
print(f"  - 시스템 메시지 길이: {len(system_message)} 문자")
print(f"  - 사용자 프롬프트 길이: {len(user_prompt)} 문자")
✓ 프롬프트 준비 완료!
  - 시스템 메시지 길이: 169 문자
  - 사용자 프롬프트 길이: 9879 문자

3단계: 다양한 LLM으로 회의록 생성

이제 동일한 프롬프트를 사용하여 여러 LLM의 성능을 비교해보겠습니다.

옵션 1: Ollama (로컬 LLM)

Ollama는 로컬에서 LLM을 실행할 수 있는 오픈소스 도구입니다.

장점

단점



```python
# Ollama를 OpenAI API 형식으로 사용
client = OpenAI(
    base_url='http://localhost:11434/v1',  # Ollama 로컬 서버
    api_key='ollama'  # 더미 키 (형식 맞추기용)
)

# Llama 3.2로 회의록 생성
response = client.chat.completions.create(
    #model="llama3.2",  # Ollama에서 다운로드한 모델
    model="exaone3.5",  # Ollama에서 다운로드한 모델
    messages=[
        {"role": "system", "content": system_message},
        {"role": "user", "content": user_prompt}
    ]
)

print("=== Ollama (Llama 3.2) 결과 ===")
display(Markdown(response.choices[0].message.content))
=== Ollama (Llama 3.2) 결과 ===
<IPython.core.display.Markdown object>

옵션 2: OpenAI GPT-4 (클라우드 API)

GPT-4는 OpenAI의 최고 성능 모델입니다.

장점

단점

비용 예측

일반적인 회의록 (5분 음성):

# OpenAI GPT-4 사용
client = OpenAI(api_key=openai_api_key)
response = client.chat.completions.create(
    model="gpt-4",  # 최고 성능 모델
    messages=[
        {"role": "system", "content": system_message},
        {"role": "user", "content": user_prompt}
    ]
)

print("=== OpenAI GPT-4 결과 ===")
display(Markdown(response.choices[0].message.content))
=== OpenAI GPT-4 결과 ===
<IPython.core.display.Markdown object>

모델 비교 요약

특징Ollama (로컬)OpenAI GPT-4
비용무료 (하드웨어 비용만)~$0.09/회의
품질좋음최고
속도빠름 (로컬)보통 (네트워크)
프라이버시완전 보안데이터 외부 전송
설정복잡간단
하드웨어GPU 필요불필요
추천 용도민감한 정보, 대량 처리최고 품질 필요, 소량 처리

선택 기준

Ollama를 선택하세요:

OpenAI를 선택하세요:

# 긴 회의록을 청크로 나누기
def chunk_text(text, max_chars=10000, overlap=500):
    """
    텍스트를 겹침이 있는 청크로 분할
    
    Args:
        text: 분할할 텍스트
        max_chars: 청크당 최대 문자 수
        overlap: 청크 간 겹침 문자 수
    """
    chunks = []
    start = 0
    
    while start < len(text):
        end = start + max_chars
        chunks.append(text[start:end])
        start = end - overlap  # 겹침 적용
    
    return chunks

# 예제
long_text = transcription  # 실제 긴 회의록
chunks = chunk_text(long_text, max_chars=5000)

print(f"=== 청킹 결과 ===")
print(f"전체 길이: {len(long_text)} 문자")
print(f"청크 개수: {len(chunks)}")
print(f"각 청크 크기: {[len(c) for c in chunks]}")
=== 청킹 결과 ===
전체 길이: 9678 문자
청크 개수: 3
각 청크 크기: [5000, 5000, 678]

JSON 출력으로 구조화된 데이터 생성

Markdown 대신 JSON으로 출력하면 데이터베이스 저장이나 자동화가 쉬워집니다.

5단계: 실전 활용 팁

자동화 워크플로우

실무에서 회의록 자동화를 구축할 때 고려사항:

비용 최적화 전략

1. 모델 선택 최적화

# 단계별로 다른 모델 사용
- 초안: GPT-3.5-turbo ($0.002/1K)
- 최종: GPT-4 ($0.03/1K)
- 대량: Ollama (무료)

2. 프롬프트 최적화

3. 캐싱 활용

품질 관리 체크리스트

필수 항목 확인:

일관성 검증:

에러 핸들링

일반적인 문제와 해결책:

결론

핵심 요약

이 노트북에서 다룬 내용:

  1. Speech-to-Text: OpenAI Whisper를 통한 음성 인식
  2. 프롬프트 엔지니어링: 효과적인 회의록 생성 프롬프트
  3. 다양한 LLM: Ollama(로컬) vs OpenAI(클라우드) 비교
  4. 고급 기능: 청킹, Map-Reduce, JSON 출력

실무 도입 로드맵

Phase 1: MVP (1-2주)

Phase 2: 최적화 (2-4주)

Phase 3: 확장 (1-2개월)

예상 효과

📊 정량적 효과:

🎯 정성적 효과:

다음 단계

더 알아보기:

추가 개선 아이디어:

마지막 조언

"완벽한 자동화보다 90% 자동화 + 10% 인간 검토가 더 실용적입니다."

AI가 초안을 생성하고, 사람이 최종 검토하는 하이브리드 접근이 최적의 결과를 만듭니다.


Happy Automating! 🚀

이 노트북이 여러분의 회의록 작성을 혁신하는 데 도움이 되기를 바랍니다!

# 견고한 에러 핸들링
import time
from openai import OpenAI, OpenAIError

def robust_meeting_minutes(audio_path, max_retries=3):
    """재시도 로직이 포함된 안정적인 파이프라인"""
    
    for attempt in range(max_retries):
        try:
            # Speech-to-Text
            with open(audio_path, "rb") as audio_file:
                transcription = openai.audio.transcriptions.create(
                    model="gpt-4o-mini-transcribe",
                    file=audio_file,
                    response_format="text"
                )
            
            # 회의록 생성
            client = OpenAI(api_key=openai_api_key)
            response = client.chat.completions.create(
                model="gpt-4",
                messages=[
                    {"role": "system", "content": system_message},
                    {"role": "user", "content": f"Generate minutes: {transcription}"}
                ],
                timeout=60  # 타임아웃 설정
            )
            
            return response.choices[0].message.content
            
        except OpenAIError as e:
            print(f"시도 {attempt + 1}/{max_retries} 실패: {e}")
            
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # 지수 백오프
                print(f"{wait_time}초 후 재시도...")
                time.sleep(wait_time)
            else:
                print("최대 재시도 횟수 초과")
                raise
        
        except FileNotFoundError:
            print(f"❌ 오디오 파일을 찾을 수 없습니다: {audio_path}")
            return None
        
        except Exception as e:
            print(f"❌ 예상치 못한 오류: {e}")
            return None

print("✓ 에러 핸들링 함수 준비 완료")
# 완전한 파이프라인 함수
def meeting_minutes_pipeline(audio_path, output_format="markdown"):
    """
    음성 파일부터 회의록까지 전체 파이프라인
    
    Args:
        audio_path: 오디오 파일 경로
        output_format: "markdown" 또는 "json"
    
    Returns:
        회의록 (문자열 또는 딕셔너리)
    """
    print("1단계: 음성 → 텍스트 변환...")
    
    # Speech-to-Text
    with open(audio_path, "rb") as audio_file:
        transcription = openai.audio.transcriptions.create(
            model="gpt-4o-mini-transcribe",
            file=audio_file,
            response_format="text"
        )
    
    print(f"   ✓ 변환 완료 ({len(transcription)} 문자)")
    
    # 2단계: 회의록 생성
    print("2단계: 회의록 생성...")
    
    if output_format == "json":
        response = client.chat.completions.create(
            model="gpt-4",
            messages=[
                {"role": "system", "content": json_system_message},
                {"role": "user", "content": f"Generate minutes: {transcription}"}
            ],
            response_format={"type": "json_object"}
        )
        result = json.loads(response.choices[0].message.content)
    else:
        response = client.chat.completions.create(
            model="gpt-4",
            messages=[
                {"role": "system", "content": system_message},
                {"role": "user", "content": f"Generate minutes: {transcription}"}
            ]
        )
        result = response.choices[0].message.content
    
    print("   ✓ 회의록 생성 완료!")
    return result

# 사용 예시
# minutes = meeting_minutes_pipeline("./denver_extract.mp3", output_format="json")
# print(json.dumps(minutes, indent=2, ensure_ascii=False))
import json

# JSON 출력을 위한 프롬프트
json_system_message = """
You are a meeting minutes generator. Output ONLY valid JSON with this structure:
{
  "meeting_info": {
    "date": "YYYY-MM-DD",
    "location": "string",
    "attendees": ["name1", "name2"]
  },
  "summary": "string",
  "discussion_points": ["point1", "point2"],
  "decisions": ["decision1", "decision2"],
  "action_items": [
    {"task": "string", "owner": "string", "deadline": "string"}
  ]
}
"""

json_prompt = f"Generate meeting minutes as JSON from this transcript:\n\n{transcription[:2000]}"

# GPT-4로 JSON 생성
client = OpenAI(api_key=openai_api_key)
response = client.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "system", "content": json_system_message},
        {"role": "user", "content": json_prompt}
    ],
    response_format={"type": "json_object"}  # JSON 모드 강제
)

# 결과 파싱
minutes_json = json.loads(response.choices[0].message.content)

print("=== JSON 형식 회의록 ===")
print(json.dumps(minutes_json, indent=2, ensure_ascii=False))
# Map-Reduce 패턴으로 청크 처리
def summarize_chunks(chunks, client, model="gpt-4"):
    """각 청크를 요약한 후 최종 요약 생성"""
    
    # 1단계: 각 청크 요약 (Map)
    chunk_summaries = []
    for i, chunk in enumerate(chunks):
        print(f"청크 {i+1}/{len(chunks)} 처리 중...")
        
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": "Summarize this meeting transcript segment."},
                {"role": "user", "content": chunk}
            ]
        )
        chunk_summaries.append(response.choices[0].message.content)
    
    # 2단계: 청크 요약들을 결합하여 최종 요약 (Reduce)
    combined = "\n\n".join(chunk_summaries)
    
    final_response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_message},
            {"role": "user", "content": f"Create final minutes from these summaries:\n\n{combined}"}
        ]
    )
    
    return final_response.choices[0].message.content

# 사용 예시 (주석 처리 - 실행 시 비용 발생)
# client = OpenAI(api_key=openai_api_key)
# final_minutes = summarize_chunks(chunks, client)
# display(Markdown(final_minutes))

4단계: 고급 기능

긴 회의록 처리 (청킹 전략)

대부분의 LLM은 컨텍스트 윈도우 제한이 있습니다. 긴 회의는 여러 청크로 나누어 처리해야 합니다.


Share this post on:

Previous Post
Multi-Modal AI 기초
Next Post
System Message 활용하기