콘텐츠로 이동

인터페이스 명세 — 챗봇 WAS ↔ 선결제 에이전트

항목 내용
버전 v0.1 (초안)
작성일 2026-07-03
상태 검토 중 — 코어 전문 스펙(Q3) 확정 시 데이터 예시 갱신 예정

설계 원칙

  • 단일 엔드포인트: 첫 발화든, 확인 응답이든, 이어지는 대화든 모든 턴을 POST /v1/agent/chat 하나로 처리한다. 턴의 성격 판단은 에이전트가 담당한다.
  • 버저닝: URL 경로에 /v1. 하위 호환이 깨지는 변경은 /v2.
  • 확장 예약: 응답 status에 미래 확장값(data_request)을 예약해 계약 변경 없이 3단계 확장이 가능하게 한다.

1. 요청 — POST /v1/agent/chat

필드 타입 필수 설명
session_id string WAS의 대화 세션키. 에이전트의 LangGraph thread_id로 사용
message_id string 사용자 발화 1건의 고유키. 멱등성 보장에 사용 (§4)
trace_id string 요청 처리 1건의 추적 ID. WAS가 생성해 전달 권장, 미전달 시 에이전트가 생성해 응답 meta로 반환 (아래 식별자 체계 참고)
user.user_id string 사용자 식별자
message string 사용자 발화 원문. 최대 1,000자 — 초과 시 400 (07 R-04)
intent.code string AI솔루션의 의도분류 결과 코드
intent.confidence number 의도분류 확신도
data.source_code string 코어 조회에 사용한 전문코드
data.payload object 코어시스템 응답 원문 JSON (가공 없이 그대로 전달, ADR-006). transactions 최대 100건 — 초과 시 400 (07 R-04)
context object 채널 등 부가정보

dataWAS가 이번 턴에 코어를 새로 조회한 경우에만 포함한다. 후속 발화("2번째, 3번째만 결제해줘" 등)는 WAS가 재조회 없이 메시지만 전달하고, 에이전트가 Redis에 저장된 직전 데이터로 필터링해 응답한다.

data.payload는 WAS가 개인정보 필드를 제외·마스킹한 상태로 전달한다(ADR-009). 단, 에이전트의 필터링에 필요한 필드(가맹점명·승인일시·금액)와 결제 실행 키(tx_id)는 유지한다.

에이전트는 이 마스킹을 신뢰만 하지 않고 입구에서 재검증한다(07 R-01): data.payload에서 마스킹 안 된 개인정보(카드번호·주민등록번호·휴대전화·이메일 패턴)가 감지되면 400으로 거부한다(WAS 마스킹 버그 = 계약 위반). 반면 message(고객이 직접 입력)에서 감지되면 거부하지 않고 [마스킹됨]으로 치환 후 정상 처리한다 — LLM·로그 유입만 차단.

식별자 체계 — 누가 만들고, 언제까지 유효한가

세 식별자의 역할은 서로 다르다: session_id는 "대화 하나", message_id는 "발화 하나", trace_id는 "처리 시도 하나"를 가리킨다.

식별자 생성 주체 새로 발급하는 시점 유효 범위 용도
session_id 챗봇 WAS 새 대화 세션이 시작될 때 (WAS의 기존 챗봇 세션키 재사용 권장) 대화 세션 전체 — 같은 대화의 모든 턴에서 동일 값 대화 식별. 에이전트에서는 LangGraph thread_id이자 Redis 상태 키가 됨. Redis TTL(Q6) 만료와 함께 소멸
message_id 챗봇 WAS 사용자 발화 1건마다. 단, 같은 요청을 네트워크 오류로 재전송할 때는 동일 값 유지 요청 1건 멱등성 — 동일 값 재수신 시 에이전트는 재처리하지 않음 (§4)
trace_id 챗봇 WAS 권장 (미전달 시 에이전트가 생성) 처리 시도 1건마다 — 재전송이어도 매번 새 값 요청 처리 1건, WAS→에이전트→LLM 전 구간 관측성 — 특정 턴의 로그를 세 시스템에 걸쳐 한 번에 추적

주의사항:

  • 새 대화에는 반드시 새 session_id를 발급해야 한다. 재사용하면 이전 대화의 Redis 상태("직전 제시 목록" 등)가 새 대화에 섞인다.
  • message_idtrace_id의 차이는 재시도에서 드러난다: 같은 발화를 재전송하면 message_id는 그대로(중복 처리 방지), trace_id는 새 값(시도별 추적).
  • trace_id는 사내 표준 분산 추적 헤더가 있다면 그것을 따른다 (Q4와 함께 확인).

요청 예시 — 턴 1

⚠️ data.payload 내부의 필드명·구조는 예시입니다. 실제 전문 스펙(Q3) 확정 후 갱신합니다.

{
  "session_id": "sess-20260703-a1b2c3",
  "message_id": "msg-0001",
  "trace_id": "tr-9f8e7d",
  "user": { "user_id": "U1029384" },
  "message": "이번달 아침에 스타벅스에서 커피 먹은 거 전부 결제하고 싶어",
  "intent": { "code": "PREPAY_LIST", "confidence": 0.97 },
  "data": {
    "source_code": "TR-CARD-1234",
    "payload": {
      "transactions": [
        { "tx_id": "TX20260601-001", "merchant": "스타벅스 강남점",  "approved_at": "2026-06-01T08:12:00", "amount": 6100 },
        { "tx_id": "TX20260605-014", "merchant": "스타벅스 역삼점",  "approved_at": "2026-06-05T08:45:00", "amount": 5600 },
        { "tx_id": "TX20260610-002", "merchant": "버거하우스",       "approved_at": "2026-06-10T12:30:00", "amount": 12000 },
        { "tx_id": "TX20260618-007", "merchant": "스타벅스 선릉점",  "approved_at": "2026-06-18T09:02:00", "amount": 6800 },
        { "tx_id": "TX20260625-011", "merchant": "스타벅스 역삼점",  "approved_at": "2026-06-25T19:40:00", "amount": 5600 }
      ]
    }
  },
  "context": { "channel": "app" }
}

2. 응답

필드 타입 설명
session_id string 요청의 session_id 반환
status string 턴의 처리 결과 유형 (아래 표)
reply string 사용자에게 노출할 자연어 문구
selection.tx_ids string[] 선택된 거래 ID 목록. 배열 순서 = 화면 표시 순서 (서수 참조 해석의 기준이므로 WAS는 이 순서대로 렌더링해야 함)
selection.total_amount number 선택 거래 합계 금액 (에이전트가 코드로 재계산한 값, ADR-007)
ui object WAS 렌더링 보조 힌트 — 선택 필드, 사용 규칙은 아래 참고
meta.trace_id string 이 턴 처리의 추적 ID — 요청에 받은 값을 그대로 반환, 요청에 없었으면 에이전트가 생성한 값
meta.latency_ms number 에이전트 처리 시간

status 정의

의미 WAS가 할 일
select_proposed 조건에 맞는 거래 후보를 제시함 체크박스 목록 렌더링 + "결제하기" 버튼
payment_requested 사용자가 특정 건의 결제 의사를 명시함 생체인증 절차 시작 → 성공 시 코어에 선결제요청
clarification_needed 정보가 부족하거나 조건에 맞는 결과가 0건이어서 되물음 reply를 그대로 노출하고 답변 대기
fallback 에이전트가 처리 불가한 요청 기존 시나리오(링크 이동)로 전환
failed 내부 오류 (검증 실패 소진, LLM 타임아웃 등) 안내 문구 후 기존 시나리오로 전환
data_request (예약, 3단계) 추가 코어 조회 요청 — MVP에서는 발생하지 않음 v2에서 정의 (ADR-003)

payment_requested는 "결제를 실행하라"가 아니라 "사용자의 결제 의사가 확인되었으니 인증 절차를 시작하라"는 의미입니다. 실행 권한은 항상 WAS에 있습니다(ADR-008).

ui 필드 사용 규칙

  • 기본 렌더링은 status만으로 결정한다. select_proposed → 체크박스 목록 + 결제하기 버튼, payment_requested → 생체인증 화면 등. ui가 없어도 WAS는 항상 화면을 그릴 수 있어야 한다.
  • ui가 존재하는 이유: 대화 맥락에 따라 달라져서 WAS가 하드코딩할 수 없는 값을 전달하기 위해서다. 예를 들어 clarification_needed의 되묻기 선택지("이번 달 전체 / 최근 일주일")는 그 순간의 대화 내용에 맞춰 에이전트(LLM)가 만들어내는 값이다.
  • 방어 규칙: WAS는 모르는 ui.type을 받으면 무시하고 reply 텍스트만 노출한다. 이 규칙 덕분에 에이전트가 새 UI 타입을 추가해도 배포 시점이 다른 WAS가 깨지지 않는다.
  • MVP의 ui.type 목록: checkbox_list(+confirm_label), auth_request, quick_replies(+options)

응답 예시 — 턴 1 (select_proposed)

아침 시간대 스타벅스 3건만 선택됨 (12:30 타 가맹점, 19:40 저녁 건은 제외):

{
  "session_id": "sess-20260703-a1b2c3",
  "status": "select_proposed",
  "reply": "이번 달 아침 시간대 스타벅스 결제 내역 3건을 찾았어요. 총 18,500원입니다. 이 내역이 맞으신가요?",
  "selection": {
    "tx_ids": ["TX20260601-001", "TX20260605-014", "TX20260618-007"],
    "total_amount": 18500
  },
  "ui": { "type": "checkbox_list", "confirm_label": "결제하기" },
  "meta": { "trace_id": "tr-9f8e7d", "latency_ms": 2140 }
}

3. 시나리오별 페이로드 예시

턴 2 — 서수 참조: "2번째, 3번째 것만 결제해줘" (payment_requested)

요청 (data 없음 — 에이전트가 Redis의 직전 제시 목록 사용):

{
  "session_id": "sess-20260703-a1b2c3",
  "message_id": "msg-0002",
  "trace_id": "tr-0a1b2c",
  "user": { "user_id": "U1029384" },
  "message": "2번째, 3번째 것만 결제해줘",
  "intent": { "code": "PREPAY_LIST", "confidence": 0.88 },
  "data": null,
  "context": { "channel": "app" }
}

응답 — 직전 제시 순서 기준 2·3번째 거래:

{
  "session_id": "sess-20260703-a1b2c3",
  "status": "payment_requested",
  "reply": "6월 5일 스타벅스 역삼점 5,600원, 6월 18일 스타벅스 선릉점 6,800원 — 총 12,400원을 선결제합니다. 본인 인증을 진행해 주세요.",
  "selection": {
    "tx_ids": ["TX20260605-014", "TX20260618-007"],
    "total_amount": 12400
  },
  "ui": { "type": "auth_request" },
  "meta": { "trace_id": "tr-0a1b2c", "latency_ms": 1820 }
}

턴 예시 — 애매한 발화: "커피값 미리 낼게" (clarification_needed)

{
  "session_id": "sess-20260703-x9y8z7",
  "status": "clarification_needed",
  "reply": "어느 기간의 커피 결제 내역을 확인해 드릴까요?",
  "selection": null,
  "ui": { "type": "quick_replies", "options": ["이번 달 전체", "최근 일주일", "직접 입력"] },
  "meta": { "trace_id": "tr-3d4e5f", "latency_ms": 1150 }
}

4. 정책

HTTP 상태 코드

정상적인 비즈니스 흐름은 폴백을 포함해 전부 HTTP 200 + status 필드로 표현한다 (WAS 분기 로직 단순화). HTTP 에러는 계약 위반·시스템 장애에만 사용한다.

코드 상황 WAS 처리
200 정상 처리 (모든 status 포함) status별 분기
400 요청 스키마 위반 — 필수 필드 누락, 입력 상한 초과(message 1,000자·거래 100건, 07 R-04), data.payload에 마스킹 안 된 개인정보(07 R-01) 개발 단계 버그 — 로깅 후 폴백
5xx / 타임아웃 에이전트 장애 기존 시나리오로 폴백 (아래 참고)

"기존 시나리오로 폴백"의 구체적 의미

WAS가 에이전트로부터 정상 응답(HTTP 200 + status)을 받지 못하는 모든 경우 — read timeout(60초) 초과, 연결 실패, HTTP 5xx — 의 처리 방법:

  1. WAS가 HTTP 예외를 잡는다 (에이전트 응답 없이 이 턴을 마무리해야 하는 상황)
  2. 사용자에게 WAS가 자체 보유한 안내 문구를 노출한다 (예: "지금은 대화로 도와드리기 어려워요.")
  3. 이어서 에이전트 도입 이전에 하던 방식 — 선결제 화면 링크 제공 — 으로 서비스한다

즉 에이전트 장애가 사용자에게 "챗봇이 고장났다"가 아니라 "예전 방식으로 안내받았다"로 보이게 하는 것이 목적이다 (PoC 안전망). WAS 측 처리 예시:

try {
    AgentResponse res = agentClient.chat(req);   // read timeout 60초
    renderByStatus(res);                          // §2의 status별 분기
} catch (ResourceAccessException | HttpServerErrorException e) {
    log.error("agent unavailable. trace_id={}", req.getTraceId(), e);
    renderLegacyPrepayLink();                     // 기존 링크 이동 시나리오
}

폴백이 발생해도 Redis의 대화 상태는 삭제되지 않으며(TTL로 자연 소멸), 이후 발화가 정상 처리되면 이어서 사용된다.

타임아웃

  • WAS → 에이전트: read timeout 60초 권장 (LLM 처리 시간 감안, ADR-005)
  • 에이전트 → LLM: 30초 + 재시도 1회. 최종 실패 시 status=failed

멱등성

동일 message_id를 재수신하면 에이전트는 재처리하지 않고 저장된 응답을 반환한다 (WAS 네트워크 재시도로 인한 이중 처리 방지). 재전송 시 message_id는 동일하게 유지하되 trace_id는 새로 발급한다. 에이전트는 처리 완료한 message_id와 그 응답을 Redis에 단기 보관한다(수 분 수준).

인증·헬스체크

  • WAS ↔ 에이전트 간 인증: 사내 내부 서비스 간 표준(API key 등)을 따름 — 방식 확인 필요 (Q4)
  • 헬스체크: GET /health 제공 예정. 배포 시스템 요구 규약 확인 필요 (Q4)

5. 에이전트 ↔ LLM 서빙

항목 내용
엔드포인트 내부 구축 OpenAI 호환 POST /v1/chat/completions
모델 gpt-oss-120b
출력 형식 response_format(json_schema)로 구조화 출력 강제 — 지원 여부 확인 중 (Q1). 미지원 시 프롬프트 강제 + 파서 + 재시도로 우회 (블로커 아님, ADR-002)
temperature 0 ~ 0.2 (재현성·일관성 우선)
시스템 프롬프트 전문코드별 필드 메타데이터 포함 (ADR-006)

6. SSE 확장 계획 (2단계, 참고)

  • POST /v1/agent/chat/stream — 요청 스키마는 §1과 동일
  • 응답: text/event-stream, 이벤트 타입 token(문구 스트리밍) / status(중간 상태) / done(§2와 동일한 최종 객체)
  • 상세 스펙은 전환 결정(Q5) 시 v0.2에서 확정