TCM ↔ AMD 라벨 연동 명세서 · 외부 연동
SPEC v1 --:--:--
EXTERNAL INTEGRATION SPECIFICATION · FOR TCM TEAM

TCM ↔ AMD 라벨 연동 명세서

TAD 아웃바운드 오토콜이 남기는 발신 통화 녹취를 TCM이 STT+LLM으로 8클래스 분류하여 같은 키로 결과를 써넣는 연동 규약입니다. 이 라벨은 우리 AMD(Answering Machine Detection) 모델의 학습/자기강화 데이터가 됩니다. 본 문서 하나로 TCM 팀이 곧바로 구현에 착수할 수 있도록 계약을 정확히 기술합니다.

MinIO S3 · 버킷 tad 조인 키 = decision_id 8-class · STT + LLM 멱등 배치 (idempotent)
OVERVIEW · PURPOSE
1. 개요 · 목적
무엇을, 누가, 어디에 — 한눈에 보는 연동 그림

TAD 아웃바운드 오토콜은 모든 발신 통화의 녹취를 MinIO S3에 남깁니다. 여기에는 상대가 받는 응답 순간과 early-media(연결 전 음성)까지 포함됩니다 — 예: "연결이 되지 않아 소리샘으로 연결됩니다" 같은 통신사·사서함 안내 멘트까지 캡처됩니다.

TCM은 그 wav를 가져가(read) STT+LLM으로 8클래스 분류한 뒤, 같은 키(decision_id)로 결과를 써넣습니다(write). 우리는 이 라벨을 모아 AMD 모델의 학습 및 자기강화(self-reinforcing) 데이터셋으로 사용합니다.

한 줄 그림
PRODUCE
오토콜 → S3
audio + features 저장
key = decision_id
TCM READ
wav 가져가 분석
STT + LLM으로
8클래스 분류
TCM WRITE
S3에 결과 write
correction 객체 PUT
같은 decision_id
CONSUME
우리 export
라벨+정정 병합 →
AMD 학습셋 생성
TCM이 할 일 (요약)

① 우리 wav를 decision_id 키로 읽고 → ② STT+LLM 분류 → ③ 같은 decision_id 키로 결과를 써넣기. 그게 전부입니다. 콜백/통지 불필요, 우리 쪽 호출 불필요.

JOIN KEY
2. 조인 키 = decision_id
이게 핵심입니다 — 번호/시각 매칭 불필요

모든 객체(오디오 · 컨텍스트 라벨 · 정정 결과)는 콜 단위 고유 ID인 decision_id로 키링됩니다. 따라서 전화번호나 통화 시각으로 별도 매칭을 할 필요가 전혀 없습니다.

단 하나의 규칙

TCM은 우리 wav를 그 키(decision_id)로 읽고, 분석 결과를 같은 키로 써넣기만 하면 됩니다. 키만 보존하면 우리 쪽 export가 알아서 라벨과 정정을 결합합니다.

decision_id는 UUID 형태의 불투명(opaque) 문자열입니다. 의미를 해석하지 말고 그대로 전달·보존하세요.

S3 ACCESS · SCOPED CREDENTIALS
3. S3 접근 · 권한
엔드포인트 · 버킷 · path-style 필수 · 스코프 키
접속 파라미터
항목비고
endpointhttps://s3intra.tlabs.team:9000정식 TLS
buckettad
forcePathStyletrue필수 (MinIO)
regionus-east-1임의값 가능 (MinIO는 무시)
PATH-STYLE 필수

MinIO는 가상 호스트 스타일이 아닌 path-style로 동작합니다. AWS SDK v3 사용 시 반드시 forcePathStyle: true를 설정하세요. 누락하면 DNS/SSL 단계에서 실패합니다.

TCM에 부여할 권한 (스코프)
액션접두사 (prefix)용도
readamd/audio/*분석 대상 통화 녹취 wav
readamd/labels/*(선택) 분석 힌트용 컨텍스트
writeamd/corrections/*분류 결과 써넣기 (append)
자격증명 전달

위 스코프로 제한된 전용 액세스 키를 별도의 안전한 채널로 전달합니다. 우리 마스터 키는 공유하지 않습니다. (자격증명 값은 본 문서에 포함하지 않습니다.)

WORKFLOW · IDEMPOTENT BATCH
4. 작업 흐름 (멱등 배치)
목록 → 중복 체크 → 분석 → 결과 PUT
단계
STEP 1
대상 나열
ListObjectsV2(prefix="amd/audio/")
키에서 decision_id 추출
STEP 2
처리 여부 확인
corrections/{id}/
method=="tcm" 있으면 스킵
STEP 3
분석
wav GET
STT + LLM 8클래스
STEP 4
결과 write
correction PUT
같은 decision_id
  • (1) 나열: ListObjectsV2(prefix="amd/audio/"). 키 형식은 amd/audio/YYYY/MM/DD/{decision_id}.wav — 파일명(확장자 제외)이 decision_id.
  • (2) 멱등 체크: ListObjectsV2(prefix="amd/corrections/{decision_id}/") 결과 객체들을 보고, 그중 method=="tcm"인 정정이 이미 있으면 재처리하지 않고 스킵.
  • (3) 분석: wav를 GET으로 받아 STT+LLM으로 8클래스 분류.
  • (4) write: 결과를 correction 객체로 PUT (다음 섹션 출력 스펙 참조).
의사코드 — Node.js / AWS SDK v3 예시
// npm i @aws-sdk/client-s3
import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand }
  from "@aws-sdk/client-s3";

const s3 = new S3Client({
  endpoint: "https://s3intra.tlabs.team:9000",
  region: "us-east-1",
  forcePathStyle: true,                 // ★ MinIO 필수
  credentials: { accessKeyId: ACCESS_KEY, secretAccessKey: SECRET_KEY } // 안전채널로 수령
});
const BUCKET = "tad";

// 1) 오디오 나열 → decision_id 추출
async function listDecisionIds(token) {
  const r = await s3.send(new ListObjectsV2Command({
    Bucket: BUCKET, Prefix: "amd/audio/", ContinuationToken: token
  }));
  return {
    ids: (r.Contents || [])
      .filter(o => o.Key.endsWith(".wav"))
      .map(o => ({ key: o.Key, id: o.Key.split("/").pop().replace(".wav", "") })),
    next: r.IsTruncated ? r.NextContinuationToken : null
  };
}

// 2) 멱등: 이미 TCM이 처리했나?
async function alreadyDone(id) {
  const r = await s3.send(new ListObjectsV2Command({
    Bucket: BUCKET, Prefix: `amd/corrections/${id}/`
  }));
  for (const o of (r.Contents || [])) {
    const body = await getJson(o.Key);
    if (body && body.method === "tcm") return true;  // 스킵
  }
  return false;
}

// 3) wav 분석 → 4) 결과 write
async function processOne({ key, id }) {
  if (await alreadyDone(id)) return;
  const wav = await s3.send(new GetObjectCommand({ Bucket: BUCKET, Key: key }));
  const { klass, transcript, confidence } = await sttLlmClassify(wav.Body); // TCM 내부

  const result = {
    schemaVersion: 1,
    correction_of: id,
    "class": klass,                 // 8클래스 중 하나
    method: "tcm",
    confidence,                       // 0.0 ~ 1.0
    transcript,                       // STT 텍스트
    model: "tcm-stt-llm-v1",
    at: new Date().toISOString()      // ISO8601 UTC
  };
  await s3.send(new PutObjectCommand({
    Bucket: BUCKET,
    Key: `amd/corrections/${id}/${Date.now()}.json`,
    Body: JSON.stringify(result),
    ContentType: "application/json"
  }));
}
8-CLASS TAXONOMY
5. 8클래스 정의
분류 결과 class 필드에 들어갈 수 있는 값은 아래 8개뿐
human사람 voicemail_greeting음성사서함 안내멘트 voicemail_beep사서함 비프음 carrier_announcement통신사 안내 ringback통화연결음 coloring컬러링 fax_tone팩스/모뎀 톤 silence_noise_unknown무음/잡음/판별불가
클래스설명한국 통신 예시
human사람이 응대 가능한 상태 (실제 통화 상대)"여보세요?", "네 누구세요"
voicemail_greeting음성사서함 안내 멘트 (메시지 남기라는 안내)"소리샘입니다", "삐 소리 후 메시지를 남겨주세요"
voicemail_beep사서함 녹음 시작 비프음안내 멘트 뒤의 "삐—" 신호음
carrier_announcement통신사 시스템 안내 (연결 불가/상태 안내)"고객님이 전화를 받을 수 없습니다", 결번/정지/없는 번호 안내
ringback일반 통화 연결음 (응답 전 기본 신호)"뚜루루… 뚜루루…"
coloring통화 연결음 대체 음악·멘트 (가입자 설정 컬러링)음악 컬러링, "지금은 통화 연결 중입니다" 류 멘트
fax_tone팩스/모뎀 톤 (데이터 신호)"삐∼익" 고음 캐리어 톤
silence_noise_unknown무음 · 잡음 · 위 어디에도 해당 없거나 판별 불가무음, 잡음만, 판단 불가
ringback vs coloring

ringback은 통신사 기본 연결음(뚜루루)이고, coloring은 가입자가 설정한 음악·멘트형 컬러링입니다. 컬러링을 사람/안내로 오분류하지 않도록 주의하세요.

INPUT SPEC · TCM READS
6. 입력 스펙 (TCM이 읽을 것)
오디오 객체 + (선택) 컨텍스트 라벨
오디오 (필수 입력)
항목
키 패턴amd/audio/YYYY/MM/DD/{decision_id}.wav
포맷16kHz · mono · WAV (PCM)
구간발신~종료 통화 녹취 (응답 순간 + early-media 안내 포함)

YYYY/MM/DD는 콜이 기록된 날짜 파티션이며, 파일명(확장자 제외)이 decision_id입니다.

컨텍스트 라벨 (선택 입력, 분석 힌트)
항목
키 패턴amd/labels/YYYY/MM/DD/{decision_id}.json
주요 필드amd.verdict · amd.confidence · amd.features (우리 엔진의 1차 판정/신뢰도/추출 피처)

이 JSON은 분석 정확도를 높이기 위한 힌트로만 쓰세요. 읽지 않아도 무방하며, 어떤 경우에도 수정 금지입니다(섹션 7 참조).

PII 없음

컨텍스트 라벨에는 원문 전화번호나 상담원 ID가 들어 있지 않습니다. PII는 HMAC 해시(number_hash, agent_id_hash)로만 존재합니다.

OUTPUT SPEC · TCM WRITES
7. 출력 스펙 (TCM이 써넣을 것)
correction 객체 · 스키마 · 멱등
쓰기 경로
항목
키 패턴amd/corrections/{decision_id}/{epoch_ms}.json
ContentTypeapplication/json

{epoch_ms}는 쓰기 시각의 epoch 밀리초(예: Date.now()). 같은 decision_id 폴더에 객체를 append하는 구조입니다.

스키마
필드타입설명
schemaVersionint고정 1
correction_ofstring대상 decision_id (조인 키)
classstring8클래스 중 하나 (섹션 5)
methodstring고정 "tcm" — 멱등/우선순위 판별의 근거
confidencenumber0.0 ~ 1.0
transcriptstringSTT 텍스트
modelstring모델/버전 식별자 (예: tcm-stt-llm-v1)
atstringISO8601 UTC 시각
JSON 예시
{
  "schemaVersion": 1,
  "correction_of": "3f2a7c9e-1b4d-4a6f-8c11-9d0e2f5a7b33",
  "class": "voicemail_greeting",
  "method": "tcm",
  "confidence": 0.94,
  "transcript": "연결이 되지 않아 소리샘으로 연결됩니다. 삐 소리 후...",
  "model": "tcm-stt-llm-v1",
  "at": "2026-05-27T08:21:33.512Z"
}
원본 라벨 수정 금지 (불변 · 감사추적)

amd/labels/...의 원본 라벨은 절대 수정/삭제하지 마세요. 결과는 오직 amd/corrections/에만 append합니다. 원본 불변성이 전체 감사추적(audit trail)의 근간입니다.

멱등성 (idempotency)

같은 decision_id에 이미 method=="tcm" 정정이 존재하면 재처리 금지. 신규(미처리) 콜만 처리하세요. (섹션 4 STEP 2)

ALTERNATIVE · HTTP API
8. (대안) API 방식
S3 직접 쓰기 대신 HTTP로 결과를 보낼 수도 있음

S3에 직접 객체를 쓰는 대신 HTTP API로 결과를 제출하고 싶다면 아래 엔드포인트를 사용할 수 있습니다.

POST https://auto.tla.so/api/amd/label/{decision_id}
Content-Type: application/json

{
  "class": "<8클래스>",
  "method": "tcm",
  "confidence": 0.0,
  "transcript": "STT 텍스트"
}
권장 사항

다만 S3 직접 쓰기를 권장합니다 — 더 단순하고 디커플드(decoupled)하며, 우리 API 가용성에 의존하지 않고 배치로 대량 처리하기에 유리합니다.

OPERATIONS
9. 운영 가이드
배치 주기 · 동시성 · 재시도 · 로깅
  • 배치 주기: 예시로 5~15분 간격 폴링. 신규 오디오만 집어 처리하므로 주기는 처리량에 맞춰 조정.
  • 신규분만 처리(멱등): 매 배치마다 섹션 4 STEP 2의 멱등 체크로 이미 처리한 콜은 건너뜀. 재실행해도 안전.
  • 동시성 / 레이트: 적정 동시 처리 수와 요청 레이트를 설정해 MinIO/네트워크에 과부하를 주지 않도록. (점진적으로 올려가며 조정 권장)
  • 실패 시: 일시 실패는 지수 백오프 재시도, 반복 실패 건은 격리(quarantine)해 두고 다음 배치에서 다시 시도. 한 건 실패가 배치 전체를 멈추지 않도록.
  • 로깅: 처리량 · 지연(latency) · 스킵/성공/실패 카운트를 로깅해 추이를 관찰.
핵심

모든 동작은 멱등 + 신규분만입니다. 중복 PUT을 피하고(같은 decision_id에 tcm 정정 1건), 실패는 재시도/격리로 흡수하면 됩니다.

SECURITY · PRIVACY
10. 보안 · 프라이버시
통화 음성 데이터 취급 의무
녹취엔 통화 음성이 포함됨

오디오 객체에는 실제 통화 음성이 담겨 있습니다. 접근통제 · 전송/저장 암호화 · 보존기간 · 파기정책을 반드시 준수하세요. 분석 후 불필요한 원본/사본을 보관하지 마세요.

법적 준수

한국 통신비밀보호법 · 개인정보보호법을 고려해야 하며, 운영 전 법무 검토가 필요합니다.

우리 라벨의 PII 처리

우리 라벨 JSON에는 전화번호 원문이 없고 해시(HMAC)만 저장됩니다. TCM이 생성하는 correction에도 원문 PII(번호/이름 등)를 넣지 마세요. transcript에 PII가 섞일 수 있으니 내부 보존·취급 정책에 따라 관리하세요.

FOR REFERENCE · OUR SIDE
11. 우리 쪽 처리 (참고)
TCM은 써넣기만 하면 됨 — 콜백 불필요

우리 export 단계가 decision_id별로 라벨과 정정들을 모아 우선순위 규칙으로 최종 effective_class를 산출합니다. 우선순위는 다음과 같습니다.

PRIORITY 1
사람 (상담사/QA)
현장 explicit · QA 검수
PRIORITY 2
TCM
STT+LLM 분류 (본 연동)
PRIORITY 3
딥시크 (LLM)
내부 LLM 보조 분류
PRIORITY 4
implicit
엔진 자동 라벨
TCM이 알아야 할 단 한 가지

TCM은 결과를 써넣기만 하면 됩니다. 병합·우선순위·학습셋 생성은 전부 우리 export가 처리합니다. 콜백/통지 불필요.