Skip to content

Latest commit

 

History

History
491 lines (366 loc) · 16 KB

File metadata and controls

491 lines (366 loc) · 16 KB

AdoptAI API 연동 가이드

프론트엔드·백엔드 팀이 AdoptAI 파이프라인을 연동할 때 참고하는 문서입니다.

역할 분담 (현재)

  • 프론트엔드: TTS 재생, 마이크 녹음, API 호출 순서 제어
  • STT/AI API (본 레포): Whisper 전사, SlotExtractor 슬롯 추출, AskBackEngine 재질문·슬롯 갱신, 공고 생성
  • llm/voice_session.run_voice_session()로컬 데모 전용이며 서비스 API 경로에서 호출되지 않습니다.

프론트엔드 핸즈프리 연동 (권장)

프론트가 TTS/마이크를 담당할 때의 필수 순서입니다. TTS 재생 중에는 마이크(STT)를 켜지 마세요.

시퀀스 다이어그램

sequenceDiagram
  participant FE as Frontend
  participant API as STT/AI API
  participant SE as SlotExtractor
  participant AB as AskBackEngine

  Note over FE: TTS 안내 (슬롯 목록 나열 금지)
  FE->>FE: TTS 종료 후 마이크 ON
  FE->>FE: 자유 발화 녹음
  FE->>FE: 마이크 OFF
  FE->>API: POST /stt/transcribe-and-start (audio)
  API->>SE: extract_slots(stt_text)
  SE-->>API: slots, missing_slots
  API-->>FE: session_id, pending_slot=_confirm, confirmation/question

  Note over FE: 요약 확인 TTS
  FE->>FE: TTS(confirmation) → 마이크 ON → 답변 녹음
  FE->>API: POST /pipeline/answer {session_id, answer}
  API-->>FE: slots, missing_slots, question?

  alt question != null (누락 슬롯 있음)
    loop 필수 슬롯이 채워질 때까지
      FE->>FE: TTS로 question 재생 → TTS 종료
      FE->>FE: 마이크 ON → 답변 녹음 → 마이크 OFF
      FE->>API: POST /pipeline/answer {session_id, answer}
      API->>AB: update_slots + detect_missing_slots
      API-->>FE: slots, missing_slots, question?
    end
  end

  Note over FE: question == null && ready_for_notice
  FE->>API: POST /pipeline/complete {session_id}
  API-->>FE: slots + notice (title, body, info_table, faithfulness)
Loading

API 호출 순서

단계 프론트 동작 API
0 TTS 안내 4문장 + get_initial_guide() 재생 (아래 문구) 없음
1 자유 발화 녹음 POST /stt/transcribe-and-start
1b pending_slot="_confirm"이면 요약 확인 TTS → 답변 녹음 ("네 맞아요" 또는 수정) POST /pipeline/answer
2a 응답의 question을 TTS 재생 → 답변 녹음 POST /pipeline/answer (반복)
2b question == null 이면 재질문 종료
3 공고 생성 요청 POST /pipeline/complete

안내 TTS 권장 문구 (슬롯 이름 나열 금지):

  1. 안녕하세요.
  2. 사진을 제외한 나머지 정보는 음성으로 입력받겠습니다.
  3. 입양 공고 작성에 필요한 정보를 자유롭게 말씀해주세요.
  4. 부족한 정보는 제가 추가로 질문드리겠습니다.

초기 안내 (슬롯 추출 전, 권장)llm/askback.pyget_initial_guide():

품종, 나이, 성별, 체중, 구조 지역, 보호소 연락처를 말씀해주시면 바로 공고를 작성해드릴게요!

위 4문장 뒤에 재생하거나, FE에서 동일 문구를 TTS로 읽어주면 됩니다.

대안 (프론트가 자체 STT를 쓰는 경우)
녹음 → 자체 전사 후 POST /pipeline/start { "stt_text": "..." } → 이후는 동일하게 /answer/complete.


전체 흐름 (요약)

[FE TTS 안내 + get_initial_guide()] → [FE 자유 발화 녹음]
  → POST /stt/transcribe-and-start  (또는 /pipeline/start)
  → pending_slot=_confirm 이면 요약 확인 TTS → POST /pipeline/answer
  → question 있으면 FE TTS + 녹음 → POST /pipeline/answer (반복)
  → POST /pipeline/complete
  → 공고문 + Faithfulness

대안: 슬롯이 이미 완성된 경우

POST /notice/generate?platform=naver_cafe  (AdoptionNoticeSlots JSON)

서버 실행

uv run uvicorn api.app:app --host 0.0.0.0 --port 8000 --reload
  • Swagger UI: http://localhost:8000/docs
  • OpenAPI JSON: http://localhost:8000/openapi.json (코드 생성·타입 정의용)

엔드포인트 요약

메서드 경로 설명
GET /health 헬스체크
POST /stt/transcribe 음성 → STT 텍스트만
POST /stt/transcribe-and-start 음성 → STT → SlotExtractor → 세션
POST /pipeline/start STT 텍스트 → SlotExtractor → 세션
POST /pipeline/answer AskBackEngine으로 답변 반영 → 다음 question
GET /pipeline/status/{session_id} 세션 상태 조회
POST /pipeline/complete 공고문 생성 + Faithfulness
POST /notice/generate 슬롯만으로 공고문 생성 (세션 불필요)
POST /notice/generate-all 3개 플랫폼 일괄 생성
GET /slots/schema 슬롯 JSON Schema
GET /slots/example 예시 슬롯

API Contract (SlotExtractor / AskBackEngine 기준)

프론트는 아래 JSON 필드만 보면 됩니다. 내부적으로 /start·/transcribe-and-startSlotExtractor, /answerAskBackEngine을 사용합니다.

PipelineResponse 공통 필드

POST /stt/transcribe-and-start, POST /pipeline/start, POST /pipeline/answer 응답의 핵심:

필드 타입 의미
session_id string 이후 /answer, /complete에 사용
stt_text string? /transcribe-and-start에만 있음 — Whisper 원문
corrected_text string 도메인 보정된 텍스트
slots object 현재까지 채워진 AdoptionNoticeSlots
missing_slots string[] 아직 부족한 필수 슬롯 이름 목록
pending_slot string | null 다음 처리 대상. "_confirm"이면 요약 확인 단계, 그 외는 재질의 슬롯명
confirmation string | null 요약 확인 TTS 문구. _confirm 단계에서는 question과 동일
question string | null 다음에 TTS로 읽을 문장 (확인 또는 재질문). null이면 재질문 없음
completed bool missing_slots가 비고 pending_slot이 없으면 true
ready_for_notice bool 공고 생성 가능 여부 (pending 재질문·확인 없을 때)
ttl_remaining_seconds int 세션 남은 TTL

question은 다음 중 하나입니다.

  • 요약 확인 (pending_slot="_confirm"): "다음처럼 들었어요. 푸들 수컷 3살, 4kg, 대현동. 맞나요?"
  • 재질의 (AskBackEngine.generate_question): 예) "성별을 알려주세요.", "중성화 여부를 알려주세요."

1) 첫 자유 발화 후 응답

POST /stt/transcribe-and-start
Content-Type: multipart/form-data

file: (자유 발화 오디오)
use_prompt: true

요약 확인 단계 (일반적 — 슬롯이 하나라도 채워진 경우):

{
  "stt_text": "이름은 초코이고 3살 푸들이에요. 중성화 되어 있고 어제 대현동에서 구조했어요.",
  "use_prompt": true,
  "session_id": "a1b2c3d4-...",
  "completed": false,
  "pending_slot": "_confirm",
  "confirmation": "다음처럼 들었어요. 푸들 3살, 대현동. 맞나요?",
  "question": "다음처럼 들었어요. 푸들 3살, 대현동. 맞나요?",
  "slots": { "...": "..." },
  "corrected_text": "이름은 초코이고 3살 푸들이에요. ...",
  "missing_slots": ["sex", "weight_kg", "contact_methods"],
  "ready_for_notice": false,
  "ttl_remaining_seconds": 3600
}

프론트 동작:

  1. confirmation(또는 question)을 TTS로 재생
  2. TTS 종료 후 마이크 ON → "네 맞아요" 또는 수정 발화 녹음
  3. /pipeline/answer로 전송
    • 긍정: 다음 question(누락 슬롯 재질문) 또는 ready_for_notice: true
    • 수정: 원문+수정 발화로 슬롯 재추출 후 누락 슬롯 재질문

누락 슬롯 재질문 예시 (/pipeline/answer 확인 이후):

{
  "session_id": "a1b2c3d4-...",
  "completed": false,
  "pending_slot": "sex",
  "question": "성별을 알려주세요.",
  "missing_slots": ["sex", "weight_kg", "contact_methods"],
  "ready_for_notice": false,
  "slots": { "...": "..." },
  "corrected_text": "...",
  "ttl_remaining_seconds": 3590
}

프론트 동작:

  1. question을 TTS로 재생 (재생 중 마이크 OFF)
  2. TTS 종료 후 마이크 ON → 답변 녹음
  3. 답변 텍스트(또는 오디오→STT)를 /pipeline/answer로 전송

필수 슬롯이 모두 채워진 경우:

{
  "session_id": "a1b2c3d4-...",
  "completed": true,
  "question": null,
  "missing_slots": [],
  "ready_for_notice": true,
  "slots": { "...": "완성된 슬롯" },
  "corrected_text": "...",
  "ttl_remaining_seconds": 3600
}

→ 재질문 없이 바로 POST /pipeline/complete.

2) 재질문 답변

POST /pipeline/answer
Content-Type: application/json

{
  "session_id": "a1b2c3d4-...",
  "answer": "수컷이에요"
}

answer는 사용자가 말한 자연어 문자열입니다. AskBackEngine이 해당 pending_slot 값으로 파싱·병합합니다.

  • pending_slot="_confirm": "네 맞아요" 등 긍정 → 확인 완료 후 누락 슬롯 재질문으로 진행. 부정·수정 발화 → 원문+수정으로 슬롯 재추출.
  • 그 외 슬롯명: 해당 필드만 갱신. breed/rescue_region이 빈 문자열이면 내부적으로 "미상" placeholder로 정규화한 뒤 missing_slots에 남깁니다.

다음 누락이 남아 있을 때:

{
  "session_id": "a1b2c3d4-...",
  "completed": false,
  "question": "체중을 알려주세요.",
  "missing_slots": ["weight_kg", "contact_methods"],
  "ready_for_notice": false,
  "slots": { "sex": "수컷", "...": "..." },
  "corrected_text": "... 수컷이에요",
  "ttl_remaining_seconds": 3590
}

→ 다시 question TTS → 녹음 → /pipeline/answer 반복.

더 이상 누락이 없을 때:

{
  "session_id": "a1b2c3d4-...",
  "completed": true,
  "question": null,
  "missing_slots": [],
  "ready_for_notice": true,
  "slots": { "...": "..." },
  "corrected_text": "...",
  "ttl_remaining_seconds": 3500
}

3) 최종 공고 생성

question == null 이고 ready_for_notice: true 일 때만 호출합니다. (pending 재질문이 있으면 409)

POST /pipeline/complete
Content-Type: application/json

{ "session_id": "a1b2c3d4-..." }
{
  "session_id": "a1b2c3d4-...",
  "slots": { "...": "..." },
  "notice": {
    "title": "...",
    "body": "...",
    "info_table": {
      "품종": "푸들",
      "추정나이": "3년",
      "성별": "수컷",
      "중성화": "완료",
      "체중": "4kg",
      "외형": "-",
      "건강상태": "-",
      "성격/특이사항": "-",
      "구조지역": "대현동",
      "구조일자": "2026-07-21",
      "연락처": "064-710-4805"
    },
    "platform": "naver_cafe",
    "faithfulness": {
      "passed": true,
      "flagged_sentences": []
    }
  }
}

재질문 우선순위 (AskBackEngine)

누락 시 아래 순서의 첫 슬롯question으로 반환합니다. 이미 채워진 슬롯은 다시 묻지 않습니다.

breedsexestimated_ageweight_kgis_neuteredrescue_regionrescue_datecontact_methods

HTTP 파이프라인은 필수 슬롯이 모두 채워질 때까지 한 번에 한 항목씩 계속 질문합니다. ready_for_noticemissing_slots가 비었을 때만 true이며, 미완성 상태에서 POST /pipeline/complete를 호출하면 409를 반환합니다. 인프로세스 헬퍼의 기본 재질문 제한은 5회(DEFAULT_MAX_ROUNDS)이고 제한 도달 시 미완성 결과로 종료합니다.

프론트 전용 입력 (STT/재질문 제외)

아래는 FE 폼 등으로 따로 받는다고 가정합니다. SlotExtractor/AskBack 대상이 아닙니다.

  • shelter_name
  • protection_start_date
  • vaccination_status
  • checkup_status

권장 연동 시퀀스 (상세)

1. 음성 업로드 + 파이프라인 시작

POST /stt/transcribe-and-start
Content-Type: multipart/form-data

file: (음성 파일)
use_prompt: true

2. 재질의 루프

pending_slot="_confirm"이면 먼저 요약 확인 TTS → /pipeline/answer 1회.

그다음 completed: false 이고 question이 있으면 → TTS로 question 재생 → 답변 녹음 →:

POST /pipeline/answer
Content-Type: application/json

{
  "session_id": "uuid-...",
  "answer": "중성화 안 했어요"
}

questionnull이고 ready_for_notice: true이면 /pipeline/complete로 진행합니다.

3. 공고문 생성

POST /pipeline/complete
Content-Type: application/json

{ "session_id": "uuid-..." }

4. 세션 상태 폴링 (선택)

GET /pipeline/status/{session_id}

세션 관리

항목
저장소 서버 인메모리 (api/session_store.py)
TTL 기본 3600초 (1시간), SESSION_TTL_SECONDS 환경변수로 변경
만료 시 404 반환 — 처음부터 다시 시작 필요
서버 재시작 모든 세션 소멸

프론트엔드는 session_id를 로컬에 보관하고, ttl_remaining_seconds를 참고해 만료 전에 완료하세요.


슬롯 스키마

GET /slots/schema 또는 GET /slots/example로 필드 구조를 확인하세요.

필수 필드: breed, estimated_age, sex, is_neutered, weight_kg, rescue_region, rescue_date, contact_methods


에러 코드

HTTP 상황
400 잘못된 음성 파일 형식 / 빈 파일
404 세션 없음 또는 만료
409 재질의 진행 중인데 /pipeline/complete 호출
422 재질의 답변 파싱 실패 / 빈 STT 결과
500 Vertex AI / STT 내부 오류

인프라 요구사항

Vertex AI (LLM)

  • 필수: GCP 프로젝트 + 서비스 계정 키 (gcp_key.json)
  • GPU 불필요 (클라우드 API 호출)
  • 환경변수: GCP_PROJECT_ID, GOOGLE_APPLICATION_CREDENTIALS
  • 선택: GCP_LOCATION (기본 us-central1), MODEL_TIER (flash|pro|auto), 역할별 기본은 slot/askback/checker=flash, notice=pro

STT (faster-whisper large-v3)

환경 디바이스 compute_type 권장
GPU 있음 cuda float16 권장 — 실시간에 가까운 속도
GPU 없음 cpu int8 동작 가능, 느림 (수십 초~)

보호소 배경소음 대응 (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)를 켜지 마세요. FE에서 녹음 타이밍을 제어합니다.

  • STT API(POST /stt/*)를 쓰는 서버에 NVIDIA GPU + CUDA가 있으면 응답 속도가 크게 개선됩니다.
  • STT를 별도 마이크로서비스로 분리할 경우, GPU 서버에 STT만 배치하고 LLM API 서버는 CPU만으로도 충분합니다.

메모리

  • STT 모델 로드: GPU VRAM ~3GB 이상 권장 (large-v3)
  • LLM: Vertex AI 원격 호출이므로 로컬 GPU 불필요

Python 라이브러리 (배치/로컬 데모)

텍스트 파이프라인:

from llm.pipeline import run_full_pipeline
from time_utils import korea_today

result = run_full_pipeline(
    "믹스견 추정 3살 수컷, 5kg, 슬개골 탈구 2기. 어제 대현동에서 구조",
    reference_date=korea_today(),
    get_answer=lambda slot, q: {"is_neutered": "중성화 안 했어요"}.get(slot, ""),
)

로컬 핸즈프리 시뮬레이션 (서비스 API와 별개):

uv run python llm/voice_session.py
uv run python evaluation/e2e_test.py

팀별 역할 분담

담당
프론트엔드 TTS 안내/재질문 재생, 마이크 녹음, /stt/*·/pipeline/* 호출 순서, 공고문 UI
백엔드 (BFF) 이 API 프록시, 세션 ID 매핑, TTL 정책
AI (본 레포) Whisper STT, SlotExtractor, AskBackEngine, 공고 생성, Vertex AI 키

OpenAPI 스펙(openapi.json)을 백엔드 팀에 전달하면 클라이언트 SDK 자동 생성이 가능합니다.