첫 AI 앱 만들기: AI 회의록 정리
첫 AI 앱 만들기: AI 회의록 정리 (Building Your First AI App)
기본을 배웠고 제공자 생태계도 이해했으니, 이제 실제로 무언가 만들어 볼게요: 오디오 파일을 받아 회의록(스크립트)을 만들고, 요약과 실행 항목을 생성해 주는 AI 미팅 노트 앱이에요.
이 프로젝트는 단일 앱 안에서 여러 전문화된 제공자를 사용하는 실전 AI 오케스트레이션을 보여줘요.
프로젝트 개요
우리 앱은 다음을 할 거예요:
- 웹 인터페이스에서 마이크 입력으로 오디오 받기
- 빠른 음성→텍스트 모델로 음성 전사(transcribe)
- 강력한 언어 모델로 요약 생성
- 공유하기 쉽게 웹에 배포
테크 스택: HTML/JavaScript (UI) + Inference Providers (AI)
UI는 단순하고 범용적으로 유지하려고 HTML과 JavaScript를 쓸 거예요. 더 성숙한 예시를 보고 싶다면 Hugging Face JS spaces 페이지를 확인해 보세요.
Step 1: 인증 설정
코딩을 시작하기 전에 CLI로 Hugging Face에 인증하세요:
pip install huggingface_hub
hf auth login
프롬프트가 나오면 Hugging Face 토큰을 붙여 넣으세요. 그러면 모든 inference 호출에 인증이 자동 처리돼요. 토큰은 설정 페이지에서 생성할 수 있어요.
앱에서는 토큰을 환경변수로 설정할 수 있어요.
export HF_TOKEN="your_token_here"
// Add your token at the top of your script
const HF_TOKEN = process.env.HF_TOKEN;
⚠️ 앱을 Hugging Face Spaces에 배포할 때는 토큰을 **시크릿(secret)**으로 추가해야 해요. 이건 코드에 토큰을 노출하지 않으면서 안전하게 다루는 방법이에요.
Step 2: 사용자 인터페이스 만들기
Gradio로 간단한 웹 인터페이스를 만들어 볼게요:
import gradio as gr
from huggingface_hub import InferenceClient
def process_meeting_audio(audio_file):
"""Process uploaded audio file and return transcript + summary"""
if audio_file is None:
return "Please upload an audio file.", ""
# We'll implement the AI logic next
return "Transcript will appear here...", "Summary will appear here..."
# Create the Gradio interface
app = gr.Interface(
fn=process_meeting_audio,
inputs=gr.Audio(label="Upload Meeting Audio", type="filepath"),
outputs=[
gr.Textbox(label="Transcript", lines=10),
gr.Textbox(label="Summary & Action Items", lines=8)
],
title="🎤 AI Meeting Notes",
description="Upload an audio file to get an instant transcript and summary with action items."
)
if __name__ == "__main__":
app.launch()
Gradio의 gr.Audio 컴포넌트로 오디오 파일 업로드나 마이크 입력을 처리해요. 출력은 두 가지 — 전사본과 실행 항목이 포함된 요약 — 로 단순하게 유지했어요.
JavaScript로는 네이티브 파일 업로드와 간단한 로딩 상태가 있는 깔끔한 HTML 인터페이스를 만들 거예요:
<body>
<h1>🎤 AI Meeting Notes</h1>
<div class="upload" onclick="document.getElementById('file').click()">
<input type="file" id="file" accept="audio/*" />
<p>Upload audio file</p>
<button type="button">Choose File</button>
</div>
<div class="loading" id="loading">Processing...</div>
<div class="results" id="results">
<div class="result">
<h3>📝 Transcript</h3>
<div id="transcript"></div>
</div>
<div class="result">
<h3>📋 Summary</h3>
<div id="summary"></div>
</div>
</div>
</body>
이렇게 하면 전사본·요약을 위한 스타일된 결과 섹션이 있는 깔끔한 드래그앤드롭 인터페이스가 만들어져요.
이제 앱은 huggingface.js의 InferenceClient를 써서 전사·요약 함수를 호출할 수 있어요.
import { InferenceClient } from "https://esm.sh/@huggingface/inference";
// Access the token from Hugging Face Spaces secrets
const HF_TOKEN = window.huggingface?.variables?.HF_TOKEN;
// Or if you're running locally, you can set it as an environment variable
// const HF_TOKEN = process.env.HF_TOKEN;
document.getElementById("file").onchange = async (e) => {
if (!e.target.files[0]) return;
const file = e.target.files[0];
show(document.getElementById("loading"));
hide(document.getElementById("results"), document.getElementById("error"));
try {
const transcript = await transcribe(file);
const summary = await summarize(transcript);
document.getElementById("transcript").textContent = transcript;
document.getElementById("summary").textContent = summary;
hide(document.getElementById("loading"));
show(document.getElementById("results"));
} catch (error) {
hide(document.getElementById("loading"));
showError(`Error: ${error.message}`);
}
};
transcribe와 summarize 함수도 구현해야 해요.
Step 3: 음성 전사 추가하기
이제 OpenAI의 whisper-large-v3 모델로 빠르고 안정적인 음성 처리를 하는 전사를 구현할게요.
💡
auto제공자를 써서 모델에 사용 가능한 첫 번째 제공자를 자동 선택하게 할 거예요. 제공자 우선순위 목록은 Inference Providers 페이지에서 정의할 수 있어요.
def transcribe_audio(audio_file_path):
"""Transcribe audio using fal.ai for speed"""
client = InferenceClient(provider="auto")
# Pass the file path directly - the client handles file reading
transcript = client.automatic_speech_recognition(
audio=audio_file_path,
model="openai/whisper-large-v3"
)
return transcript.text
JavaScript 버전:
import { InferenceClient } from "https://esm.sh/@huggingface/inference";
async function transcribe(file) {
const client = new InferenceClient(HF_TOKEN);
const output = await client.automaticSpeechRecognition({
data: file,
model: "openai/whisper-large-v3-turbo",
provider: "auto",
});
return output.text || output || "Transcription completed";
}
Step 4: AI 요약 추가하기
다음으로 DeepSeek의 deepseek-ai/DeepSeek-R1-0528 같은 강력한 언어 모델을 Inference Provider를 통해 쓸 거예요. 채팅 완성의 기본 정책인 :fastest는 이 모델에 가장 좋은 성능의 제공자를 자동 선택해요. 출력이 요약 + 실행 항목 + 결정 사항 형식이 되도록 커스텀 프롬프트를 정의할 거예요:
def generate_summary(transcript):
"""Generate summary using an Inference Provider"""
client = InferenceClient(provider="auto")
prompt = f"""
Analyze this meeting transcript and provide:
1. A concise summary of key points
2. Action items with responsible parties
3. Important decisions made
Transcript: {transcript}
Format with clear sections:
## Summary
## Action Items
## Decisions Made
"""
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-R1-0528:fastest",
messages=[{"role": "user", "content": prompt}],
max_tokens=1000
)
return response.choices[0].message.content
JavaScript 버전:
async function summarize(transcript) {
const client = new InferenceClient(HF_TOKEN);
const prompt = `Analyze this meeting transcript and provide:
1. A concise summary of key points
2. Action items with responsible parties
3. Important decisions made
Transcript: ${transcript}
Format with clear sections:
## Summary
## Action Items
## Decisions Made`;
const response = await client.chatCompletion(
{
model: "deepseek-ai/DeepSeek-R1-0528:fastest",
messages: [
{
role: "user",
content: prompt,
},
],
max_tokens: 1000,
},
{
provider: "auto",
}
);
return (
response.choices?.[0]?.message?.content ||
response ||
"No summary available"
);
}
Step 5: Hugging Face Spaces에 배포하기
배포하려면 app.py 파일을 만들고 Hugging Face Spaces에 업로드해야 해요.
완전한 app.py (Gradio 버전):
import gradio as gr
from huggingface_hub import InferenceClient
def transcribe_audio(audio_file_path):
"""Transcribe audio using an Inference Provider"""
client = InferenceClient(provider="auto")
# Pass the file path directly - the client handles file reading
transcript = client.automatic_speech_recognition(
audio=audio_file_path, model="openai/whisper-large-v3"
)
return transcript.text
def generate_summary(transcript):
"""Generate summary using an Inference Provider"""
client = InferenceClient(provider="auto")
prompt = f"""
Analyze this meeting transcript and provide:
1. A concise summary of key points
2. Action items with responsible parties
3. Important decisions made
Transcript: {transcript}
Format with clear sections:
## Summary
## Action Items
## Decisions Made
"""
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-R1-0528:fastest",
messages=[{"role": "user", "content": prompt}],
max_tokens=1000,
)
return response.choices[0].message.content
def process_meeting_audio(audio_file):
"""Main processing function"""
if audio_file is None:
return "Please upload an audio file.", ""
try:
# Step 1: Transcribe
transcript = transcribe_audio(audio_file)
# Step 2: Summarize
summary = generate_summary(transcript)
return transcript, summary
except Exception as e:
return f"Error processing audio: {str(e)}", ""
# Create Gradio interface
app = gr.Interface(
fn=process_meeting_audio,
inputs=gr.Audio(label="Upload Meeting Audio", type="filepath"),
outputs=[
gr.Textbox(label="Transcript", lines=10),
gr.Textbox(label="Summary & Action Items", lines=8),
],
title="🎤 AI Meeting Notes",
description="Upload audio to get instant transcripts and summaries.",
)
if __name__ == "__main__":
app.launch()
앱은 포트 7860에서 실행되고 이런 모습이에요.
배포 단계:
- 새 Space 만들기: huggingface.co/new-space로 이동
- Gradio SDK 선택 후 public으로 만들기
- 파일 업로드:
app.py업로드 - 토큰 추가: Space 설정에서
HF_TOKEN을 시크릿으로 추가(설정에서 얻기) - 런칭: 앱이
https://huggingface.co/spaces/your-username/your-space-name에서 라이브 됨
참고: 로컬에서는 CLI 인증을 썼지만, Spaces는 배포 환경에서 시크릿으로 토큰을 제공해야 해요.
JavaScript 배포용으로는 간단한 정적 HTML 파일을 만듭니다. 완전한 index.html:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>🎤 AI Meeting Notes</title>
<style>
body {
font-family: system-ui;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.upload {
border: 2px dashed #ccc;
padding: 40px;
text-align: center;
margin: 20px 0;
cursor: pointer;
}
.upload:hover {
border-color: #007bff;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.results {
display: none;
margin-top: 20px;
}
.result {
background: #f5f5f5;
padding: 15px;
margin: 10px 0;
border-radius: 4px;
}
.error {
color: red;
background: #ffe6e6;
padding: 15px;
border-radius: 4px;
display: none;
}
input[type="file"] {
display: none;
}
</style>
</head>
<body>
<h1>🎤 AI Meeting Notes</h1>
<div class="upload" onclick="document.getElementById('file').click()">
<input type="file" id="file" accept="audio/*" />
<p>Upload audio file</p>
<button type="button">Choose File</button>
</div>
<div class="loading" id="loading">Processing...</div>
<div class="error" id="error"></div>
<div class="results" id="results">
<div class="result">
<h3>📝 Transcript</h3>
<div id="transcript"></div>
</div>
<div class="result">
<h3>📋 Summary</h3>
<div id="summary"></div>
</div>
</div>
<script type="module">
import { InferenceClient } from "https://esm.sh/@huggingface/inference";
// Access the token from Hugging Face Spaces secrets
const HF_TOKEN = window.huggingface?.variables?.HF_TOKEN;
// Add error handling for missing token
if (!HF_TOKEN) {
showError("HF_TOKEN not configured. Please add it in Space settings.");
return;
}
document.getElementById("file").onchange = async (e) => {
if (!e.target.files[0]) return;
const file = e.target.files[0];
show(document.getElementById("loading"));
hide(
document.getElementById("results"),
document.getElementById("error")
);
try {
console.log("🎤 Starting transcription...");
const transcript = await transcribe(file);
console.log(
"✅ Transcription completed:",
transcript.substring(0, 100) + "..."
);
console.log("🖊️ Starting summarization...");
const summary = await summarize(transcript);
console.log(
"✅ Summary completed:",
summary.substring(0, 100) + "..."
);
document.getElementById("transcript").textContent = transcript;
document.getElementById("summary").textContent = summary;
hide(document.getElementById("loading"));
show(document.getElementById("results"));
} catch (error) {
hide(document.getElementById("loading"));
showError(`Error: ${error.message}`);
}
};
async function transcribe(file) {
const client = new InferenceClient(HF_TOKEN);
const output = await client.automaticSpeechRecognition({
data: file,
model: "openai/whisper-large-v3-turbo",
provider: "auto",
});
return output.text || output || "Transcription completed";
}
async function summarize(transcript) {
const client = new InferenceClient(HF_TOKEN);
const prompt = `Analyze this meeting transcript and provide:
1. A concise summary of key points
2. Action items with responsible parties
3. Important decisions made
Transcript: ${transcript}
Format with clear sections:
## Summary
## Action Items
## Decisions Made`;
const response = await client.chatCompletion(
{
model: "deepseek-ai/DeepSeek-R1-0528:fastest",
messages: [
{
role: "user",
content: prompt,
},
],
max_tokens: 1000,
},
{
provider: "auto",
}
);
return (
response.choices?.[0]?.message?.content ||
response ||
"No summary available"
);
}
const show = (el) => (el.style.display = "block");
const hide = (...els) => els.forEach((el) => (el.style.display = "none"));
const showError = (msg) => {
const error = document.getElementById("error");
error.innerHTML = msg;
show(error);
};
</script>
</body>
</html>
배포 단계:
- 새 Space 만들기: huggingface.co/new-space로 이동
- Static SDK 선택 후 public으로 만들기
- 파일 업로드:
index.html업로드 - 토큰을 시크릿으로 추가: Space 설정에서
HF_TOKEN을 Secret으로 추가 - 런칭: 앱이
https://huggingface.co/spaces/your-username/your-space-name에서 라이브 됨
참고: 토큰은 Hugging Face Spaces가 안전하게 관리하고
window.huggingface.variables.HF_TOKEN으로 접근합니다.
다음 단계
축하해요! 실무 작업을 처리하고, 전문적인 인터페이스를 제공하고, 자동 스케일링되고, 비용 효율적인 프로덕션 레디 AI 애플리케이션을 만들었어요. 더 많은 제공자를 탐색하려면 Inference Providers 페이지를 확인하세요. 다음 단계 아이디어:
- 프롬프트 개선: 사용 사례에 맞게 다른 프롬프트를 시도
- 다른 모델 시도: 다양한 음성·텍스트 모델 실험
- 성능 비교: 제공자 간 속도 vs 정확도 벤치마크
더 알아보기 (Learn more)
출처: 공식문서