유기동물 입양 공고 자동 생성 파이프라인.
핸즈프리 음성: TTS 안내 → 자유 발화 STT → 슬롯 추출 → 요약 확인 → (누락만) TTS 재질문 → 공고 생성
| 단계 | 모듈 | 기술 |
|---|---|---|
| TTS 안내/재질문 | tts/speak.py |
edge-tts (ko-KR) |
| STT | stt/transcribe.py, stt/listen.py |
faster-whisper large-v3 (기본 VAD·환청 완화) |
| 슬롯 추출 | llm/slot_extractor.py |
Vertex AI Gemini (SLOT_MODEL, 기본 flash) |
| 재질의 | llm/askback.py |
Gemini (ASKBACK_MODEL, 기본 flash) — 누락 슬롯만 |
| 음성 세션 | llm/voice_session.py |
TTS↔STT 순차 오케스트레이션 |
| 공고문 생성 | llm/notice_generator.py |
Gemini (NOTICE_MODEL, 기본 pro) — 플랫폼별 맞춤 |
| Faithfulness | llm/faithfulness_checker.py |
Gemini NLI (CHECKER_MODEL, 기본 flash) |
MODEL_TIER=auto(기본)이면 역할별로 위 모델이 적용됩니다. flash/pro로 지정하면 전 역할에 동일 모델을 씁니다.
서비스(프론트 연동): TTS·마이크는 프론트엔드가 담당합니다. STT/AI API는 슬롯 추출·재질문·공고 생성만 수행합니다.
상세 API contract·시퀀스·JSON 예시는 docs/API_INTEGRATION.md를 보세요.
FE: TTS 안내 → 자유 발화 녹음
→ POST /stt/transcribe-and-start
→ (pending_slot=_confirm) FE: TTS(confirmation) → 녹음 → POST /pipeline/answer
→ (question 있으면) FE: TTS(question) → 녹음 → POST /pipeline/answer (반복)
→ POST /pipeline/complete
| 응답 필드 | 프론트 동작 |
|---|---|
question (string) |
TTS로 읽고 답변 녹음 후 /pipeline/answer |
pending_slot: "_confirm" |
자유 발화 요약 확인 단계. question/confirmation을 TTS → 긍정("네 맞아요") 또는 수정 발화 후 /answer |
question: null + ready_for_notice: true |
/pipeline/complete 호출 |
missing_slots |
아직 부족한 필수 슬롯 이름 목록 (참고용) |
session_id |
/answer, /complete에 그대로 전달 |
confirmation |
요약 확인 문구 (확인 단계에서는 question과 동일) |
run_voice_session()은 로컬 데모 전용이며 API에서 호출되지 않습니다.
# 로컬 콜백 시뮬레이션 (마이크 불필요)
uv run python llm/voice_session.py프론트에서 받는 항목(보호소명·보호시작일·접종·건강검진 등)은 STT/재질문 대상이 아닙니다.
초기 안내 TTS (권장): llm/askback.py의 get_initial_guide() — 슬롯 추출 전 실무자에게 필요한 정보를 짧게 안내합니다.
품종, 나이, 성별, 체중, 구조 지역, 보호소 연락처를 말씀해주시면 바로 공고를 작성해드릴게요!
기존 4문장 안내(tts/speak.py의 INTRO_LINES) 뒤에 재생하거나, FE에서 동일 문구를 TTS로 읽어주면 됩니다.
stt/transcribe.py 기본값:
vad_filter=True— Silero VAD로 비음성 구간(짖음·침묵) 제외condition_on_previous_text=False— 이전 문맥 의존 환청 감소hallucination_silence_threshold=0.5,no_speech_threshold=0.6
보호소 현장에서는 마이크를 담당자 입 가까이 두고, TTS 재생 중에는 STT를 시작하지 않는 것이 중요합니다.
| 단계 | API | 내부 엔진 |
|---|---|---|
| 자유 발화 | POST /stt/transcribe-and-start 또는 POST /pipeline/start |
Whisper + SlotExtractor |
| 재질문 답변 | POST /pipeline/answer {session_id, answer} |
AskBackEngine |
| 공고 생성 | POST /pipeline/complete {session_id} |
NoticeGenerator + Faithfulness |
재질문 우선순위: breed → sex → estimated_age → weight_kg → is_neutered → rescue_region → rescue_date → contact_methods
(채워진 슬롯은 다시 묻지 않음. 한 번에 하나의 question만 반환)
llm/notice_generator.py는 슬롯 데이터로 플랫폼에 맞는 공고를 생성합니다.
| 플랫폼 | platform 값 |
스타일 |
|---|---|---|
| 인스타그램 | instagram |
짧고 감성적, 해시태그, 150자 이내 |
| 당근 동네생활 | daangn |
친근한 존댓말, 동네 강조, 200자 이내 |
| 네이버 카페 | naver_cafe |
격식체, 상세 본문 + info_table (기본) |
필수 슬롯에 contact_methods(입양 문의 방법)가 포함됩니다. 전화·인스타·카카오 등을 {"type": "phone"|"instagram"|"kakao"|"other", "value": "..."} 형태의 리스트로 저장하며, 빈 리스트일 때만 재질의 대상이 됩니다. 공고 info_table의 연락처는 이 필드를 조합해 채웁니다.
프론트/백엔드 연동: docs/API_INTEGRATION.md 참고
- Python 3.12
- uv 패키지 매니저
- Vertex AI가 활성화된 Google Cloud 프로젝트
- Vertex AI 사용 권한이 있는 서비스 계정 키 (
gcp_key.json) - STT API 사용 시: NVIDIA GPU 권장 (없으면 CPU 동작, 느림)
uv syncGoogle Cloud Console에서 서비스 계정을 만들고 Vertex AI User (roles/aiplatform.user) 역할을 부여합니다.
JSON 키를 프로젝트 루트에 gcp_key.json으로 저장합니다.
GCP_PROJECT_ID=your-gcp-project-id
GOOGLE_APPLICATION_CREDENTIALS=./gcp_key.json| 변수 | 필수 | 설명 |
|---|---|---|
GCP_PROJECT_ID |
필수 | GCP 프로젝트 ID |
GOOGLE_APPLICATION_CREDENTIALS |
필수 | 서비스 계정 키 경로 |
GCP_LOCATION |
선택 | Vertex AI 리전 (기본: us-central1) |
MODEL_TIER |
선택 | flash/pro(전 역할 동일) 또는 auto(역할별 기본, 권장) |
VERTEX_MODEL |
선택 | 레거시. MODEL_TIER=flash|pro가 우선 |
SESSION_TTL_SECONDS |
선택 | 파이프라인 세션 TTL (기본: 3600) |
MAX_AUDIO_UPLOAD_BYTES |
선택 | 최대 오디오 업로드 크기 (기본: 25MB) |
STT_INTERNAL_API_KEY |
운영 권장 | BE→STT 내부 요청 인증키 |
REDIS_URL |
운영 권장 | 공유 세션 저장소 URL (예: redis://redis:6379/0) |
STT_REDIS_KEY_PREFIX |
선택 | Redis 세션 키 prefix (기본: adoptai:stt:session:) |
uv run uvicorn api.app:app --host 0.0.0.0 --port 8000 --reloadSwagger UI: http://localhost:8000/docs
POST /stt/transcribe-and-start # 음성 → STT → 슬롯 추출 → 세션
POST /pipeline/answer # 재질의 답변
POST /pipeline/complete # 공고문 생성 + Faithfulness (naver_cafe)
POST /notice/generate?platform=... # 슬롯만으로 플랫폼별 공고 생성
POST /notice/generate-all # 인스타·당근·네이버 카페 3종 일괄 생성
GET /pipeline/status/{id} # 세션 상태
platform 쿼리: instagram | daangn | naver_cafe (기본: naver_cafe)
상세 연동 가이드: docs/API_INTEGRATION.md
# 슬롯 추출 데모
uv run python llm/slot_extractor.py
# 핸즈프리 음성 세션 데모 (speak/listen 콜백 시뮬레이션, 요약 확인 포함)
uv run python llm/voice_session.py
# 전체 파이프라인 데모 (텍스트 자유 발화 → 요약 확인 → 누락만 재질의 → 공고)
uv run python llm/pipeline.py
# 플랫폼별 공고문 생성 데모
uv run python llm/notice_generator.py
# E2E 테스트 (Vertex AI 실연동)
uv run python evaluation/e2e_test.py
# Flash vs Pro 품질·응답시간 비교 (현실 시나리오 포함)
uv run python evaluation/model_comparison.py --both
uv run python evaluation/model_comparison.py --both --scenario stt_noise
uv run python evaluation/model_comparison.py --list
# 파이프라인 구간별 latency (short / medium / ambiguous / long × 5회)
uv run python evaluation/latency_test.pylatency_test.py의 long 시나리오는 품종·건강·성격·연락처가 모두 포함된 긴 자유 발화입니다. 결과는 evaluation/latency_results.json에 저장됩니다.
from llm.pipeline import run_full_pipeline
from llm.notice_generator import generate_all_notices, generate_notice
from models.slots import AdoptionNoticeSlots
from time_utils import korea_today
# 전체 파이프라인 (슬롯 → 재질의 → 공고 → Faithfulness)
result = run_full_pipeline(
"믹스견 추정 3살 수컷, 5kg, 슬개골 탈구 2기. 어제 OO동 구조",
reference_date=korea_today(),
get_answer=lambda slot, q: (
"중성화 안 했어요" if slot == "is_neutered"
else "064-710-4805" if slot == "contact_methods"
else ""
),
)
print(result.slots)
print(result.notice.title if result.notice else None)
print(result.faithfulness.passed if result.faithfulness else None)
# 슬롯만으로 플랫폼별 공고 생성
slots = AdoptionNoticeSlots.model_validate({
"breed": "믹스견",
"estimated_age": {"value": 3, "unit": "년"},
"sex": "수컷",
"is_neutered": False,
"weight_kg": 5.0,
"rescue_region": "대현동",
"rescue_date": "2026-07-13",
"contact_methods": [{"type": "phone", "value": "064-710-4805"}],
})
instagram = generate_notice(slots, "instagram")
all_notices = generate_all_notices(slots) # instagram, daangn, naver_cafeuv run pytest tests/ -v단위 테스트는 Vertex AI 클라이언트를 mock으로 주입하므로 API 키 없이 실행됩니다.