Dialogue Manager API Endpoint¶
예시는 로컬 application path인 http://localhost:59212/v1/...를 사용합니다. Reverse proxy 배포에서는 /dm prefix를 추가할 수 있습니다.
Request/response field 정의는 Schema를 참고합니다.
1. 서비스 정보¶
GET /¶
FastAPI application root 정보입니다.
응답 예:
{
"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/¶
응답 예:
응답의 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¶
Validation failure response¶
구조 검증 실패도 현재 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}¶
성공 응답:
{
"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입니다.
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=true와 200; 처리되지 않은 예외는 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¶
/v1/session에서 scenario_name이 비어 있으면 위 응답을 반환합니다. 등록되지 않은
scenario나 graph load 중 발생한 ValueError도 {"detail":"..."} 형태의 400으로
변환됩니다. /v1/session/json의 scenario_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 버전에 따라 type과 msg 문구는 달라질 수 있습니다.
존재하지 않는 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이 될 수 있습니다.