배경
최근 회사 내부에서 보안상의 이유로 외부 API 기반 회의록 생성 서비스(예: Otter.ai, Notta 등)를 사용 제한하는조치가 있었습니다. 특히 중요한 고객 미팅, 전략 회의 등은 외부로 관련 데이터가 유출되는 것을 엄격히 제한하고 있습니다.

이에 따라 AWS Bedrock을 이용하여 회사에서 업무용으로 사용 할 수 있는 음성 → 텍스트 변환(STT) → 요약 → 이메일 발송하는 내부 시스템을 구축하게 되었습니다. 본 글에서는 해당 구축 사례를 소개하며, 특히 Bedrock 기반 LLM 활용 방식과 Amazon Transcribe를 통한 음성 인식, 요약 처리, 최종 결과 자동 배포 방식에 대해 중점적으로 설명하겠습니다.
아키텍처 개요

시스템은 아래와 같은 단계로 구성됩니다.
- 사용자가 S3 버킷에 음성 파일 업로드
- 업로드 이벤트를 감지한 S3 Event → Lambda 트리거
- Amazon Transcribe로 음성-> 텍스트 변환 작업 수행
- AWS Bedrock을 통해 변환 결과 요약
- 요약 결과를 PDF/Markdown 형식으로 가공하여 S3에 저장
- Amazon SES를 통해 담당자에게 이메일 전송
사용 서비스 및 역할
| 서비스 | 역할 |
| Amazon S3 | 음성 파일 저장 (mp3, wav 등) |
| Amazon Transcribe | 음성 → 텍스트 변환 (STT) |
| AWS Lambda | 텍스트 수집 및 요약 요청 트리거 |
| AWS Bedrock | 텍스트 요약 (Claude v2 사용) |
| AWS Step Functions | *전체 프로세스 흐름 제어 (선택적 사용 가능) |
| Amazon CloudWatch | 로그 및 에러 추적 |
| AWS IAM | 최소 권한 기반 접근 제어 |
1. S3 업로드 → Lambda 트리거
회의가 끝나면 사용자는 사내 시스템에서 회의 녹취 파일을 지정된 S3 버킷에 업로드합니다. 이때 S3의 ObjectCreated 이벤트가 자동으로 Lambda 함수를 트리거 할수 있도록 S3 Event Notification 설정을 구성합니다.
{
"Records": [
{
"s3": {
"bucket": {"name": "my-meeting-recordings"},
"object": {"key": "meeting_20250601.wav"}
}
}
]
}
2. Amazon Transcribe로 음성 인식
Lambda에서 Transcribe 작업을 시작합니다.
transcribe = boto3.client('transcribe')
def start_transcription_job(file_uri, job_name):
transcribe.start_transcription_job(
TranscriptionJobName=job_name,
Media={'MediaFileUri': file_uri},
MediaFormat='mp3',
LanguageCode='ko-KR',
OutputBucketName='your-bucket-name',
OutputKey='transcripts/' + job_name + '.json'
)
Transcribe는 한국어도 기본 지원하며, 정확도는 상당히 높습니다. 다만 복잡한 발음 또는 배경 소음이 있는 경우에는 후처리가 필요할 수 있습니다.
3. 텍스트 정제 및 프롬프트 구성
Transcribe 결과가 저장되면 Lambda를 통해 JSON 형식으로 생성된 결과에서 텍스트를 추출하고, 이를 다시 파싱하여 화자별 텍스트로 재정렬합니다.
[화자 1] 오늘 회의는 신규 파트너사 협력안 검토가 주요 안건입니다.
[화자 2] 맞습니다. 특히 2분기 비용 효율화를 반영해야 합니다.
def parse_conversation_turn_by_turn(results_obj):
"""
시간 순서대로 items를 읽어, 화자가 바뀔 때마다 한 턴(문장)을 확정.
- "spk_0" → "화자 1", "spk_1" → "화자 2" 등 매핑
- 문장부호(.?! 등) 앞 공백 제거
- 반환: ["화자 1: 예, 여보세요?", "화자 2: 아 네, 안녕하세요.", ...]
"""
segments = results_obj["speaker_labels"]["segments"]
items = results_obj["items"]
# (1) start_time -> speaker_label 매핑
time_to_speaker = {}
for seg in segments:
spk_label = seg["speaker_label"] # e.g. "spk_0"
for seg_item in seg["items"]:
st = seg_item["start_time"]
time_to_speaker[st] = spk_label
# 어떤 spk_x가 있는지 모음
all_speakers = sorted(set(time_to_speaker.values()))
# spk_0 -> 화자 1, spk_1 -> 화자 2, ...
spk_label_map = {spk: f"화자 {i+1}" for i, spk in enumerate(all_speakers)}
conversation_lines = []
current_speaker = None
current_words = []
last_speaker = None
def finalize_turn(spk, words):
if not spk or not words:
return None
text = " ".join(words).strip()
# 문장부호 앞 공백 제거
text = re.sub(r"\s+([,.?!])", r"\1", text)
speaker_name = spk_label_map.get(spk, "화자 ?")
return f"{speaker_name}: {text}"
for it in items:
content = it["alternatives"][0].get("content", "")
if "start_time" in it:
# 발화(단어)
st = it["start_time"]
spk_label = time_to_speaker.get(st, None)
else:
# 구두점
spk_label = last_speaker
if last_speaker is None:
# 첫 단어
current_speaker = spk_label
current_words = [content]
else:
if spk_label != current_speaker:
# 화자 교체
line = finalize_turn(current_speaker, current_words)
if line:
conversation_lines.append(line)
current_speaker = spk_label
current_words = [content]
else:
# 같은 화자
current_words.append(content)
last_speaker = spk_label
# 마지막 턴 flush
last_line = finalize_turn(current_speaker, current_words)
if last_line:
conversation_lines.append(last_line)
return conversation_lines
이 텍스트를 Bedrock을 이용하여 요약할 Python 함수로 전달 합니다.
4. AWS Bedrock을 이용한 요약 처리
요약에는 AWS Bedrock의 FalconLite2 모델을 사용했습니다.
# Bedrock 요약 기능 활성화
USE_BEDROCK_SUMMARY = True
# 사용할 Bedrock 모델 (예: "anthropic.claude-instant-v1", "amazon.titan-text-express-v1" 등)
BEDROCK_MODEL_ID = os.getenv("BEDROCK_MODEL_ID", "huggingface-llm-amazon-falconlite2")
def invoke_bedrock_summary(full_text: str) -> str:
"""
Bedrock 호출로 대화 요약.
"""
prompt = f"""
아래는 화자 여러 명이 교대로 말한 대화 내용입니다.
핵심 요구사항이나 결정사항, 요청사항 위주로 간단히 요약해 주세요.
한국어로 짧게 작성 부탁드립니다:
--- 대화 ---
{full_text}
--- 끝 ---
"""
try:
resp = BEDROCK_CLIENT.invoke_model(
modelId=BEDROCK_MODEL_ID,
accept="application/json",
contentType="application/json",
body=json.dumps({
"prompt": prompt,
"max_tokens_to_sample": 300
})
)
body = json.loads(resp["body"].read())
summary = body.get("completion", "요약 실패(응답 없음)")
return summary
except Exception as e:
print("[ERROR] Bedrock summary failed:", e)
return "요약 실패 (Bedrock 호출 에러)"
5. 결과 저장 및 Amazon SES 이메일 전송
요약된 결과는 파일로 생성되어 S3에 저장됩니다. 동시에 Amazon SES를 이용해 회의 담당자에게 자동 메일이 발송됩니다.
ses.send_email(
Source='noreply@yourcompany.com',
Destination={'ToAddresses': ['pm@yourcompany.com']},
Message={
'Subject': {'Data': '회의 요약 보고서'},
'Body': {
'Text': {'Data': '회의 요약 보고서가 첨부되었습니다.\n\n링크: https://s3.amazonaws.com/...'}
}
}
)
성능 및 한계
장점
- 모두 AWS Native 서비스 사용 → 외부 API 없이 구현 가능
- 보안성 → 음성 데이터의 외부 유출 위험 없음
- 자동화 및 확장성 → Step Functions을 이용해 비동기 처리 가능
한계
- Transcribe의 음성 인식 정확도는 화자 수, 발음 등에 영향을 받음
- Bedrock 요약 결과는 프롬프트 엔지니어링에 따라 품질 편차 존재
- 장문 처리 시 Token 한계로 분할 처리 필요
향후 개선 방향
- 실시간 회의 스트리밍 처리 (Amazon Transcribe Streaming)
- Amazon Q 또는 Bedrock Agent 기반 질의응답 기능 추가
- 회의 요약 평가 기능 도입 (피드백 루프)
- 내부 지식 검색 챗봇(RAG 기반 with AWS Bedrock Agents)를 사용한 검색 기능 활성화
마무리하며
AWS Bedrock은 단순한 생성형 AI API 그 이상으로, 보안과 내부 시스템 통합이 필수적인 환경에서 실질적인 대안을 제공합니다. 외부 SaaS 도입이 어려운 조직일수록 Bedrock 기반의 구축형 워크플로우는 높은 유연성과 통제력을 제공합니다.
참고 링크
![]() |
AWS의 파트너사인 MegazoneCloud 소속으로 AWS Ambassador로 활동하며 작성한 내용입니다. AWS의 서비스를 소개하고, 실제 업무에서 사용한 사례들에 대한 내용들을 담고 있습니다. |
| Written By. Karam Kim |
'AWS Ambassador' 카테고리의 다른 글
| 생성형 AI를 통한 EKS 장애 분석 가속화: Amazon Q기반 컨트롤 플레인 지연 현상 분석 (1) | 2025.08.24 |
|---|---|
| Amazon Q와 Confluence 완벽 연동 가이드: AI로 문서 작업 자동화하기 (4) | 2025.07.18 |
| AWS Health Event(PHD) 멀티 리전 실시간 알림 시스템 구축 (1) | 2025.06.13 |
| AWS Lambda를 이용한 RDS 모니터링 (0) | 2025.05.27 |
| AWS DMS를 이용한 Oracle to RDS for Oracle Migration (0) | 2025.05.23 |
