콘텐츠로 이동

Dialogue Manager API Endpoint

예시는 로컬 application path인 http://localhost:59212/v1/...를 사용합니다. Reverse proxy 배포에서는 /dm prefix를 추가할 수 있습니다.

Request/response field 정의는 Schema를 참고합니다.


1. 서비스 정보

GET /

FastAPI application root 정보입니다.

curl http://localhost:59212/

응답 예:

{
  "message": "Welcome to the Graph-Driven Chatbot API",
  "version": "1.3.2",
  "docs": "/docs",
  "redoc": "/redoc",
  "openapi": "/openapi.json",
  "api": "/dm/"
}

api field는 configured root path만 안내하며 versioned API root를 직접 가리키지는 않습니다. 실제 local router prefix는 /v1, reverse proxy public prefix는 /dm/v1입니다.

이 endpoint들은 process가 HTTP 요청을 받을 수 있다는 것만 확인합니다. LLM provider, scenario load, session storage 또는 Skill Runtime 연결 상태까지 검사하는 readiness probe는 아닙니다.

GET /v1/

curl http://localhost:59212/v1/

응답 예:

{
  "message": "API for Graph-Driven Dialog Manager",
  "version": "1.3.2"
}

응답의 version은 version.py에서 가져오므로 배포된 build에 따라 달라질 수 있습니다.


2. 등록 scenario로 세션 생성

POST /v1/session

scenario_name으로 등록된 JSON을 읽고 세션을 생성합니다. bot_first=true이면 빈 사용자 입력과 NLU 처리를 건너뛰고 initial bot response를 만듭니다. 시작 노드에 responses.default가 있으면 해당 문구를 반환하고, 없으면 노드의 description과 시나리오 bot prompt를 바탕으로 LLM이 생성합니다. LLM 호출이 실패하거나 빈 응답을 반환하면 언어별 공통 인사말을 사용합니다. 요청에서 bot_first를 생략하면 graph.bot_first를 사용하며, 둘 다 없으면 false입니다.

Request

curl -X POST http://localhost:59212/v1/session \
  -H 'Content-Type: application/json' \
  -d '{
    "user": {
      "id": "user-123",
      "name": "홍길동",
      "age": 70,
      "location": {
        "latitude": 37.5172,
        "longitude": 127.0473
      },
      "locale": "ko-KR",
      "timezone": "Asia/Seoul"
    },
    "scenario_name": "senimate",
    "llm_choice": "gpt-4o",
    "nlu_llm_choice": "gpt-4o-mini",
    "user_system_prompt": "",
    "bot_first": true,
    "max_fallback_retries": 3,
    "stt_confidence_threshold": -0.7,
    "completion_options": {
      "include_slots": true,
      "include_history": false
    }
  }'
{
  "user": {
    "id": "user-123",
    "name": "홍길동",
      "age": 70,
      "location": {
        "latitude": 37.5172,
        "longitude": 127.0473
      },
      "locale": "ko-KR",
      "timezone": "Asia/Seoul"
  },
  "scenario_name": "senimate",
  "llm_choice": "gpt-4o",
  "nlu_llm_choice": "gpt-4o-mini",
  "user_system_prompt": "",
  "bot_first": true,
  "max_fallback_retries": 3,
  "stt_confidence_threshold": -0.7,
  "completion_options": {
    "include_slots": true,
    "include_history": false
  }
}

실제 사용 field

Field 사용
user 기본값이 아닌 field를 Session context에 저장하고 NLU/NLG 개인화 및 위치 기반 skill에 사용. 하위 필드는 schemas.md 참고
scenario_name scenario map에서 JSON 파일 선택
llm_choice NLG model 설정. 빈 문자열이면 process의 CORE_DEFAULT_LLM 사용
nlu_llm_choice intent/entity 추출 model 설정. 빈 문자열이면 CORE_NLU_LLM 사용
user_system_prompt 사용자 지정 NLG prompt
bot_first 요청값이 graph.bot_first보다 우선. 둘 다 없으면 false
max_fallback_retries 연속 fallback 재시도 횟수의 세션 override. 생략 시 노드 설정 또는 기본값 3
stt_confidence_threshold STT 이해 불가 fallback 기준. 기본값 -0.7, confidence가 이 값 이하이면 적용
completion_options 종료 응답에 slot과 전체 대화를 포함할지 설정
scenario_graph 이 endpoint에서는 사용하지 않음

Response

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Session created",
  "llm_choice": "gpt-4o",
  "nlu_llm_choice": "gpt-4o-mini",
  "user_system_prompt": "",
  "completion_options": {
    "include_slots": true,
    "include_history": false
  },
  "initial_response": "안녕하세요. 저는 시니메이트예요.",
  "initial_current_node": "greeting_introduction",
  "initial_turn_count": 1,
  "initial_processing_time_ms": 0.25,
  "initial_session_complete": false
}

Response 해석

Field 의미
session_id 이후 모든 message 요청에 사용할 ID
initial_response bot-first가 적용됐을 때 시작 노드 문구, LLM 생성 또는 공통 fallback으로 준비한 봇 응답
initial_current_node 첫 턴 transition 적용 후 node
initial_turn_count 첫 응답 처리 후 논리적 turn count
initial_processing_time_ms 첫 봇 응답을 준비한 서버 처리 시간(ms)
initial_session_complete 첫 턴 직후 세션 완료 여부
completion_options 세션에 저장된 종료 payload 설정

llm_choice="" 또는 nlu_llm_choice=""로 요청하면 SessionManager는 각각 process 기본 NLG/NLU model을 선택하지만 response는 request 값을 그대로 사용하므로 빈 문자열로 보입니다. nlu_llm_choice가 비었다고 llm_choice를 그대로 재사용하는 것은 아닙니다. 실제 적용 model을 응답에 기록해야 하면 두 이름을 모두 명시합니다.

Initial bot turn이 내부적으로 error=true result를 만들더라도 이 endpoint의 ChatbotSessionCreateResponse에는 error field가 없어 session-created 응답만 반환될 수 있습니다. Initial 오류 상태를 public contract에서 구분해야 한다면 별도 response field 또는 HTTP 오류 변환이 필요합니다.

등록 scenario

이름 사용 가능
senimate
card_issuance
call_for_fire
dental_caries_case1
general_conversation 현재 미등록

3. JSON으로 세션 생성

POST /v1/session/json

등록 이름 대신 request body의 scenario_graph를 사용합니다.

Request

curl -X POST http://localhost:59212/v1/session/json \
  -H 'Content-Type: application/json' \
  -d '{
    "bot_first": true,
    "max_fallback_retries": 3,
    "stt_confidence_threshold": -0.7,
    "user": {
      "id": "user-123",
      "location": {
        "latitude": 37.5172,
        "longitude": 127.0473
      },
      "locale": "ko-KR",
      "timezone": "Asia/Seoul"
    },
    "llm_choice": "gpt-4o",
    "nlu_llm_choice": "gpt-4o-mini",
    "completion_options": {
      "include_slots": true,
      "include_history": false
    },
    "scenario_graph": {
      "metadata": {
        "slug": "general_conversation",
        "name": "자유 대화",
        "description": "자유 대화 API 예제",
        "language": "ko",
        "version": "1.0.0"
      },
      "prompts": {
        "bot": "사용자 입력에 직접 답하세요."
      },
      "graph": {
        "entry": "conversation",
        "global_transitions": [],
        "nodes": [
          {
            "id": "conversation",
            "name": "자유 대화",
            "description": "사용자와 자유롭게 대화합니다.",
            "params": {
              "turn_limit_enabled": false
            },
            "next_nodes": [
              {
                "target": "conversation_closure",
                "condition": {
                  "field": "nlu.intent",
                  "operator": "equals",
                  "value": "user_wants_to_end"
                }
              },
              {
                "action": "stay",
                "condition": {
                  "field": "nlu.intent",
                  "operator": "not_equals",
                  "value": "user_wants_to_end"
                }
              }
            ]
          },
          {
            "id": "conversation_closure",
            "name": "대화 종료",
            "description": "종료 의사를 짧게 받아들이고 마칩니다.",
            "terminal": true
          }
        ]
      }
    }
  }'

실제 사용 field

Field 현재 route 동작
scenario_graph 사용
bot_first 사용
user Session context에 저장하고 NLU/NLG 개인화에 사용
scenario_name 무시
llm_choice 사용
nlu_llm_choice 사용
user_system_prompt 사용
completion_options 사용

ChatbotSessionCreateRequest를 재사용하며 scenario 이름 대신 scenario_graph를 사용합니다. 위 예는 bot_first=true이므로 첫 bot response와 turn_count=1이 반환됩니다. bot_first=false이면 greeting을 생성하지 않은 초기 snapshot이 반환되며 일반적으로 response="", turn_count=0입니다.

Response

이 endpoint는 ChatbotSessionCreateResponse가 아니라 ChatbotMessageResponse를 반환합니다.

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "response": "안녕하세요.",
  "current_node": "conversation",
  "turn_count": 1,
  "processing_time_ms": 0.24,
  "session_complete": false,
  "error": false
}

4. Scenario 검증

POST /v1/session/validate

Scenario JSON을 load하고 graph build/validation을 실행합니다. 실제 대화 세션은 만들지 않습니다.

Request

curl -X POST http://localhost:59212/v1/session/validate \
  -H 'Content-Type: application/json' \
  -d '{
    "scenario_graph": {
      "metadata": {
        "slug": "sample",
        "name": "샘플",
        "description": "검증 예제",
        "language": "ko",
        "version": "1.0.0"
      },
      "graph": {
        "entry": "welcome",
        "nodes": [
          {
            "id": "welcome",
            "description": "사용자에게 인사합니다.",
            "terminal": true
          }
        ]
      }
    }
  }'

Success response

{
  "status": "success",
  "message": "Scenario validated"
}

Validation failure response

{
  "status": "error",
  "message": "Scenario validation failed"
}

구조 검증 실패도 현재 HTTP status는 200입니다. 구체적인 error 목록이나 field path는 response에 포함하지 않고 server log에 기록합니다.

Pydantic request 자체가 object 형태가 아니면 graph validation 이전에 FastAPI 422가 발생합니다.


5. 메시지 전송

POST /v1/message

기존 세션에서 사용자 발화 한 턴을 처리합니다.

Request

curl -X POST http://localhost:59212/v1/message \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_input": {
      "id": "user123",
      "name": "홍길동",
      "age": 70
    },
    "message_input": {
      "text": "오늘은 집에서 텔레비전을 봤어요.",
      "confidence": 0.96,
      "duration": 2.4,
      "emotion": "",
      "requery_required": true,
      "requery_context": {
        "requery_id": "0198a779-8f7b-7d22-9a44-3bf83f75a97a",
        "trigger_type": "LOW_CONFIDENCE_STT",
        "consecutive_attempt": 1
      }
    }
  }'

실제 처리

req.session_id
→ DialogManager의 session별 DSTManager 선택

req.message_input.text
→ DSTManager.process_turn()의 user message

req.message_input.confidence
→ session의 stt_confidence_threshold와 비교
→ threshold 이하이면 일반 NLU를 생략하고 not_understood fallback
Field 현재 처리
session_id 사용
message_input.text 사용
user_input.id 미사용
user_input.name 미사용
user_input.age 미사용
message_input.confidence 사용. 세션별 STT threshold와 비교
message_input.duration 미사용
message_input.emotion 미사용
message_input.requery_required 미사용
message_input.requery_context 미사용

session_id와 빈 message_input.text도 Pydantic validation 자체는 통과합니다. 빈/존재하지 않는 session ID는 현재 별도 404 처리 없이 server error가 될 수 있습니다. 빈 message는 route 오류가 아니라 runtime의 no_input fallback입니다. 현재 policy는 고정 responses.no_input을 바로 반환하지 않고 대화 문맥과 직전 질문을 바탕으로 LLM 문장을 생성하며, 선언 문구 또는 코드 기본 문구는 LLM 생성 실패 때만 사용합니다.

STT confidence 비교는 경계값을 포함합니다. 예를 들어 세션 threshold가 -0.7이면 confidence=-0.7과 그보다 낮은 값은 intent=stt_low_confidence, dialogue_act=null로 변환됩니다. 이 경로에서는 LLM NLU를 호출하지 않고 직전 질문을 문맥에 맞게 다시 확인하며 fallback 턴은 turn_count에 포함하지 않습니다.

confidence는 client의 STT confidence입니다. STT gate 뒤 규칙/LLM NLU가 만드는 NLUResult.confidence는 intent/entity 및 slot 출처에 붙는 별도 내부 값이며, request field로 덮어쓸 수 없습니다.

message_input.confidence를 생략하면 schema 기본값 0.0이 route에 전달됩니다. 따라서 세션 threshold가 0이면 confidence를 생략한 message도 fallback됩니다.

같은 session의 message와 상태 조회는 process 내부 session별 RLock으로 직렬화됩니다. 이 lock은 worker/Pod 사이에는 공유되지 않습니다.

Fallback LLM 결과가 이전 assistant 문장과 공백 정규화 후 정확히 같으면 최대 총 3회까지 생성합니다. 두 번째 호출부터 피해야 할 문장과 최근 assistant 응답을 prompt context에 넣습니다. 세 번 모두 중복되거나 중복 감지 뒤 호출이 실패하면 현재 언어, fallback 종류와 재시도 단계에 맞는 코드 내 비중복 안전 문장을 선택합니다.

Response

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "response": "집에서 텔레비전을 보며 시간을 보내셨군요.",
  "current_node": "meal_check",
  "turn_count": 3,
  "processing_time_ms": 842.37,
  "session_complete": false,
  "error": false
}

Field 해석

Field 의미
response 이번 턴 사용자에게 전달할 NLG 결과
current_node transition 적용 후 다음 입력을 처리할 node
turn_count 세션의 논리적 turn 수. fallback 재시도 제외
processing_time_ms process_turn() 진입부터 최종 응답 text 준비까지 걸린 서버 시간(ms)
session_complete 세션 완료 여부
final_data 종료 응답에서만 옵션에 따라 제공되는 slot·대화 이력
error DST 내부 error response 여부

일반 턴에는 final_data가 없습니다. 내부 context, turn별 slots, NLU·transition 진단용 details도 public response에 포함하지 않습니다.

종료 response

세션 생성 시 include_slots=true, include_history=true로 설정한 예입니다.

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "response": "오늘 이야기 나눠주셔서 감사해요. 다음에 또 뵐게요.",
  "current_node": "conversation_closure",
  "turn_count": 6,
  "processing_time_ms": 731.42,
  "session_complete": true,
  "final_data": {
    "end_reason": "terminal_node",
    "slots": {
      "daily_life": "집에서 텔레비전을 봄"
    },
    "conversation_history": [
      {"role": "user", "message": "오늘은 집에서 텔레비전을 봤어요."},
      {"role": "assistant", "message": "편안하게 시간을 보내셨군요."},
      {"role": "user", "message": "이제 그만할게요."},
      {"role": "assistant", "message": "오늘 이야기 나눠주셔서 감사해요. 다음에 또 뵐게요."}
    ]
  },
  "error": false
}

기본값은 include_slots=true, include_history=false입니다. 따라서 별도 설정이 없으면 종료 응답에 채워진 slot만 포함하고 전체 대화는 보내지 않습니다.

session_complete=true인 응답 뒤에도 session은 즉시 삭제되거나 /message에서 차단되지 않습니다. 후속 message는 terminal node에서 다시 처리되며 재개 transition이 맞으면 current_node는 이동할 수 있습니다. 그러나 일반 runtime은 완료 flag를 자동으로 해제하지 않으므로 session_complete=true가 유지되고 final_data가 다시 만들어질 수 있습니다. final_data는 “최초 한 번만” 전달된다는 보장이 없으므로 client는 완료 후 전송을 멈춰야 합니다.


6. 세션 조회

GET /v1/session/{session_id}

curl http://localhost:59212/v1/session/550e8400-e29b-41d4-a716-446655440000

성공 응답:

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "current_node": "health_check",
  "turn_count": 4,
  "session_complete": false,
  "slots": {},
  "context": {},
  "expires_at": "2026-08-02 20:00:00",
  "ttl_seconds": 3600
}

Service는 session별 DSTManager.context_store에서 DialogueState와 TTL을 읽습니다. 같은 session에서 진행 중인 turn과 상태 조회가 충돌하지 않도록 session lock 안에서 조회하며, slots에는 채워진 slot의 실제 값만 반환합니다.

존재하지 않거나 dialogue state가 만료된 session은 HTTP 404입니다.

{
  "detail": "Session not found"
}

context에는 runtime 내부 정보와 session user 정보가 포함될 수 있으므로 인증된 backend 용도로 제한해야 합니다. 현재 application 자체에는 인증 middleware가 없습니다.

기본 API 저장소는 process-local memory입니다. TTL은 state를 저장할 때마다 3,600초로 갱신되고 만료 여부는 접근 시 확인하지만, background cleanup과 worker 간 공유는 없습니다. 이 조회의 404는 state가 없거나 만료됐다는 뜻이며 관련 session 설정과 graph runtime 참조가 같은 시점에 모두 정리됐음을 뜻하지 않습니다.


7. HTTP 상태와 오류 요청

상태 코드 요약

Endpoint 성공 Client 오류 Runtime 오류
GET /, GET /v1/ 200 404, 405 500
POST /v1/session 200 400 빈/잘못된 scenario, 422 request validation 처리되지 않은 예외는 500
POST /v1/session/json 200 400 빈 graph, 422 request validation 처리되지 않은 예외는 500
POST /v1/session/validate 200 422 request validation graph validation 실패도 body-level status="error"와 HTTP 200
POST /v1/message 200 422 request validation 내부 error result는 error=true200; 처리되지 않은 예외는 500
GET /v1/session/{session_id} 200 없거나 만료된 session은 404 처리되지 않은 예외는 500

POST 성공도 현재 201 Created가 아니라 200 OK입니다. Client는 HTTP status와 함께 validation response의 status, message response의 error를 확인해야 합니다.

Session 생성 400

{
  "detail": "scenario_name is required"
}

/v1/session에서 scenario_name이 비어 있으면 위 응답을 반환합니다. 등록되지 않은 scenario나 graph load 중 발생한 ValueError{"detail":"..."} 형태의 400으로 변환됩니다. /v1/session/jsonscenario_graph가 비어 있으면 {"detail":"scenario_graph is required"}를 반환합니다.

Request shape 오류

curl -X POST http://localhost:59212/v1/message \
  -H 'Content-Type: application/json' \
  -d '{
    "session_id": "SESSION_ID",
    "message_input": "문자열은 허용되지 않음"
  }'

FastAPI 표준 422 response:

{
  "detail": [
    {
      "type": "model_attributes_type",
      "loc": ["body", "message_input"],
      "msg": "Input should be a valid dictionary or object to extract fields from",
      "input": "문자열은 허용되지 않음"
    }
  ]
}

Pydantic/FastAPI 버전에 따라 typemsg 문구는 달라질 수 있습니다.

존재하지 않는 session

현재 custom 404 변환이 없습니다. /message 호출 중 session dictionary lookup이 실패하면 일반적으로 server error가 됩니다.

DST 내부 오류

Runtime이 일부 실패를 error=true인 내부 result로 반환하면 API는 이를 public ChatbotMessageResponse로 변환합니다. null인 session_id, response, current_node는 빈 문자열로 정규화되므로 그 자체로 response validation 오류가 발생하지는 않습니다.

{
  "session_id": "",
  "response": "",
  "current_node": "",
  "turn_count": 0,
  "processing_time_ms": 0.0,
  "session_complete": false,
  "error": true
}

내부 error_code, error_detail, details, context는 public response에 포함되지 않습니다. 따라서 client는 현재 error=true만으로 세부 원인을 분류할 수 없습니다. Runtime에서 처리되지 않은 예외는 여전히 통일되지 않은 HTTP 500이 될 수 있습니다.


관련 문서