API Schema 처리 흐름¶
이 문서는 HTTP request가 Pydantic model로 변환되고 Dialogue Manager를 거쳐 public response가 되기까지의 흐름을 설명합니다. Field 정의는 Schema, 실제 호출 예는 Endpoint를 참고합니다.
1. 전체 흐름¶
flowchart LR
C["HTTP Client"] --> A["FastAPI<br/>Route + Pydantic"]
A --> W["Worker Thread<br/>asyncio.to_thread"]
W --> DM["DialogManager"]
DM --> DST["DSTManager<br/>NLU + Policy + Transition"]
DST --> ER{"Executor Resolver"}
ER -->|"params.skill"| SE["SkillExecutor"]
ER -->|"collect_slots"| TE["TaskOrientedExecutor"]
ER -->|"default"| GE["GeneralResponseExecutor"]
SE --> SC["SkillRuntimeClient"]
SC -->|"POST /v1/skills/execute"| SR["sohri-skill-runtime"]
SR -->|"skill result"| SC
SC --> SE
SE --> IR["Internal Result"]
TE --> IR
GE --> IR
IR --> RB["API Response Builder"]
RB --> A
A --> C
A -. "400 / 422" .-> CE["Client Error"]
DM -. "Unhandled exception" .-> E500["HTTP 500"]
FastAPI route 함수는 async이지만 scenario load, validation과 dialogue turn 같은
blocking 작업은 asyncio.to_thread()로 worker thread에 넘깁니다. 이를 통해 해당
작업이 실행되는 동안 FastAPI event loop가 직접 차단되는 것을 줄입니다.
같은 process 안에서는 session별 RLock이 동일 session의 turn을 직렬화합니다.
다른 session은 병렬 처리될 수 있지만 이 lock은 worker process나 pod 사이에 공유되지
않습니다.
2. Request parsing과 validation¶
처리는 다음 순서로 진행됩니다.
- FastAPI가 method와 path에 맞는 route를 선택합니다.
- JSON body를 route의 Pydantic request model로 검증합니다.
- 생략된 field에는 model 기본값이 적용됩니다.
- 정의되지 않은 추가 field는 현재 기본 설정에 따라 무시됩니다.
- Route가 endpoint별 operational requirement를 추가 검사합니다.
Pydantic type 또는 shape가 맞지 않으면 runtime을 호출하기 전에 HTTP 422가
발생합니다. 반면 scenario_name=""처럼 schema상 허용되지만 실제 처리에 필요한
값이 비어 있으면 route가 HTTP 400을 반환합니다.
ChatbotSessionLocation object는 예외적으로 latitude와 longitude가 모두
schema-required이며 각각 [-90, 90], [-180, 180] 범위를 검증합니다.
API 입출력 model 관계¶
flowchart LR
subgraph SessionCreate["Session 생성"]
SCR["ChatbotSessionCreateRequest"]
SU["ChatbotSessionUser"]
SL["ChatbotSessionLocation"]
CO["CompletionOptions"]
SCR --> SU
SU --> SL
SCR --> CO
SCR --> SRES["ChatbotSessionCreateResponse<br/>또는 ChatbotMessageResponse"]
end
subgraph MessageTurn["Message 처리"]
CR["ChatbotRequest"]
UI["UserInput"]
MI["MessageInput"]
RC["RequeryContext"]
CR --> UI
CR --> MI
MI --> RC
CR --> MRES["ChatbotMessageResponse"]
MRES --> FD["ChatbotFinalData<br/>session 완료 시"]
end
subgraph SessionRead["Session 조회"]
SID["session_id path parameter"]
SID --> SI["ChatbotSessionInfoResponse"]
end
3. Session 생성¶
POST /v1/session¶
sequenceDiagram
autonumber
participant C as Client
participant API as FastAPI
participant DM as DialogManager
participant DST as DSTManager
participant SR as sohri-skill-runtime
C->>API: ChatbotSessionCreateRequest
API->>API: scenario_name 검사<br/>user 기본값 제거
API->>DM: start_session_with_greeting
DM->>DST: scenario load + DialogueState 생성
alt bot_first + params.skill
DST->>SR: POST /v1/skills/execute
SR-->>DST: weather skill result
DST->>DST: NLG greeting + state update
else bot_first without skill
DST->>DST: template 또는 일반 NLG greeting
else bot_first false
DST->>DST: initial snapshot
end
DST-->>DM: initial internal result
DM-->>API: session_id + initial result
API-->>C: ChatbotSessionCreateResponse
user에서는 기본값과 None을 제거한 뒤 session context에 저장합니다. 따라서
요청하지 않은 빈 profile이나 metadata는 저장하지 않습니다. scenario_graph는 같은
request model에 존재하지만 이 endpoint에서는 사용하지 않습니다.
bot_first는 request → graph.bot_first → legacy metadata.bot_first → false
순서로 결정됩니다. 활성화되면 initial turn 결과를 initial_* field로 옮깁니다.
POST /v1/session/json¶
flowchart LR
R["ChatbotSessionCreateRequest"] --> G["scenario_graph 검사"]
G --> U["user 기본값 제거"]
U --> D["start_session_with_greeting<br/>scenario_graph 전달"]
D --> I["Internal initial result"]
I --> B["build_message_response"]
B --> O["ChatbotMessageResponse"]
이 endpoint에서는 scenario_name을 무시합니다. /session과 달리 session-create
전용 response가 아니라 message response 형태로 초기 snapshot 또는 greeting 결과를
반환합니다.
4. Scenario validation¶
flowchart LR
R["ChatbotValidateRequest<br/>scenario_graph"] --> V["validate_scenario_from_dict"]
V --> Q{"valid?"}
Q -->|"yes"| S["HTTP 200<br/>status: success"]
Q -->|"no"| E["HTTP 200<br/>status: error"]
Graph validation은 실제 session을 생성하지 않습니다. Validator가 false를 반환해도
transport status는 HTTP 200이며 body가
{"status":"error","message":"Scenario validation failed"}가 됩니다. Request object
자체가 잘못된 경우에는 그 전에 HTTP 422가 발생합니다.
5. Message turn¶
sequenceDiagram
autonumber
participant C as Client
participant API as Message Route
participant DM as DialogManager
participant DST as DSTManager
participant EX as Selected Executor
participant SR as sohri-skill-runtime
C->>API: ChatbotRequest
API->>DM: session_id + message_input.text
DM->>DM: session lock
DM->>DST: process_turn
DST->>DST: NLU + policy
DST->>EX: node에 맞는 executor 실행
opt params.skill이 있는 node
EX->>SR: skill_id + arguments + context
SR-->>EX: success/result 또는 error
end
EX-->>DST: response + slot/context updates
DST->>DST: state update + transition + save
DST-->>DM: internal result
DM-->>API: result + completion options
API->>API: build_message_response
API-->>C: ChatbotMessageResponse
현재 route가 runtime에 전달하지 않는 field:
user_input전체message_input.durationmessage_input.emotionmessage_input.requery_requiredmessage_input.requery_context
message_input.confidence는 예외로 DialogManager.process_turn(input_confidence=...)에
전달합니다. 세션의 stt_confidence_threshold 이하이면 일반 NLU를 생략하고
FallbackPolicy의 이해 불가 경로를 사용합니다. 위 목록의 값들은 request
contract에는 포함되지만 현재 NLU, policy 또는 executor 동작을 변경하지 않습니다.
message_input.confidence는 STT가 만든 입력 신뢰도이고, 이후 규칙/LLM NLU가 만드는
NLUResult.confidence는 별도 내부 값입니다. API client가 NLU confidence를 제공하는
field는 없습니다. 빈 text는 route validation 오류가 아니라 no_input으로 분류되며,
현재 fallback policy는 node 고정 문구보다 LLM 문맥 생성을 우선합니다.
6. sohri-skill-runtime 입출력¶
Node의 params.skill이 비어 있지 않으면 executor resolver가 SkillExecutor를
선택합니다. Graph Builder container에서 SKILL_RUNTIME_BASE_URL로 설정한 service
주소의 /v1/skills/execute를 호출합니다. 같은 Kubernetes namespace 또는 container
network에서는 service DNS 이름을 base URL로 사용할 수 있습니다.
위치 기반 날씨 greeting의 데이터 흐름¶
flowchart TB
REQ["Session request<br/>user.location.latitude<br/>user.location.longitude"]
CTX["DialogueState.context<br/>session_user.location"]
NODE["Scenario node<br/>skill: weather<br/>input_map: latitude, longitude"]
MAP["SkillExecutor map_inputs"]
HTTP["Skill Runtime HTTP request<br/>skill_id + arguments + context"]
WEATHER["sohri-skill-runtime<br/>weather"]
RESULT["Skill result<br/>result.summary 등"]
UPDATE["result_map<br/>greeting_weather_summary"]
NLG["NLG<br/>날씨 안내 + SeniMate 인사"]
RESPONSE["initial_response"]
REQ --> CTX
CTX --> MAP
NODE --> MAP
MAP --> HTTP
HTTP --> WEATHER
WEATHER --> RESULT
RESULT --> UPDATE
RESULT --> NLG
UPDATE --> NLG
NLG --> RESPONSE
senimate_weather.json의 greeting node는 다음 mapping을 사용합니다.
session request user.location
→ DialogueState.context.session_user.location
→ input_map.latitude / input_map.longitude
→ Skill Runtime arguments.latitude / arguments.longitude
→ weather
→ result.summary
→ context.greeting_weather_summary + NLG response
Graph Builder 내부 request model은 transport 직전에 Skill Runtime HTTP contract로
변환됩니다. SkillInvocationRequest.input에는 scenario의 input_map이 선택한
allowlist 값만 들어가며, HTTP 전송 시 고정 arguments 위에 같은 이름의 input 값을
덮어써 하나의 arguments object로 합칩니다.
flowchart LR
S["Session user<br/>locale, timezone, location"] --> I["SkillInvocationRequest"]
N["Node params<br/>skill, arguments, input_map"] --> I
D["Dialogue state<br/>session_id, slots, context, NLU"] --> I
I --> H["HTTP JSON<br/>skill_id<br/>arguments<br/>context"]
H --> SR["sohri-skill-runtime"]
SR --> RR["success + result"]
RR --> VR["SkillInvocationResponse<br/>status + output"]
VR --> RM["result_map + allowed_updates"]
RM --> DR["Dialogue response<br/>slot/context updates + text"]
날씨 scenario에서 전송되는 HTTP payload의 형태:
{
"skill_id": "weather",
"arguments": {
"latitude": 37.5172,
"longitude": 127.0473,
"days": 1
},
"context": {
"request_id": "generated-request-id",
"session_id": "session-id",
"skill_version": "v1",
"locale": "ko-KR",
"timezone": "Asia/Seoul"
}
}
Skill Runtime의 호환 응답:
Client는 이를 내부 status="success", output=result 구조로 정규화합니다.
SkillInvocationResponse.output의 model type은 Dict[str, Any]이므로 성공 result는
JSON object여야 합니다. 문자열·배열·null result는 response validation 실패로
SKILL_RUNTIME_INVALID_RESPONSE가 됩니다. Citation과 update field도 각각 typed
list/object로 검증합니다.
result_map은 scenario가 지정한 output/update path만 slot/context로 복사하고,
Runtime의 slot_updates/context_updates는 allowed_updates에 key가 명시된 경우만
수용합니다. Reserved context key와 _ prefix key는 허용 목록에 있어도 버립니다.
response_mode="direct"(기본값)는 response_path 뒤
output.response → output.answer → output.summary → output.text 순으로 비어
있지 않은 문자열을 찾고, 없으면 success/default template, 마지막으로 non-empty
output object의 JSON 문자열을 사용합니다. response_mode="nlg"일 때만 output과
citations를 NLG context에 넣습니다. 현재 구현은 정확히 "nlg"인 값만 NLG mode로
취급하고 그 외 값은 direct처럼 처리하므로 scenario validation에서 허용값을 관리해야
합니다.
Timeout, 연결 실패, HTTP 오류, 너무 큰 응답 또는 schema 오류가 발생하면
params.fallback → responses.skill_error → responses.error →
responses.default → DM 기본 오류 문장 순으로 반환하고
last_skill_status="error"를 dialogue context에 기록합니다.
7. Internal result에서 public response로 변환¶
dm/api/response_builder.py는 내부 result를 그대로 반환하지 않고 public model을
새로 조립합니다.
flowchart LR
IR["Internal result<br/>response, slots, context, details, error"] --> RB["build_message_response"]
RB --> KEEP["Public fields<br/>session_id, response, current_node<br/>turn_count, time, complete, error"]
RB --> DROP["Removed fields<br/>details, context<br/>error_code, error_detail"]
RB --> Q{"session_complete?"}
Q -->|"no"| NORMAL["ChatbotMessageResponse"]
Q -->|"yes"| FINAL["ChatbotFinalData<br/>end_reason + optional data"]
KEEP --> NORMAL
KEEP --> FINAL
| Internal value | Public 처리 |
|---|---|
null session_id, response, current_node |
빈 문자열로 정규화 |
| null count/time | 0, 0.0으로 정규화 |
details, context |
일반 message response에서 제거 |
error_code, error_detail |
제거 |
error |
boolean으로 보존 |
session_complete=false |
final_data 생성 안 함 |
session_complete=true |
completion option에 따라 final_data 생성 |
include_slots=false 또는 include_history=false인 field는 None이 되고,
response_model_exclude_none=true에 의해 JSON에서 생략됩니다. Conversation history는
object이면서 role과 message가 모두 문자열인 항목만 보존합니다.
이 변환은 완료 이벤트를 한 번만 소비하는 저장 장치를 두지 않습니다.
session_complete=true인 결과마다 final_data를 다시 만들 수 있으며, 완료된 state도
후속 /message를 거부하지 않습니다. Terminal node의 재개 transition이 맞으면
current_node는 이동할 수 있지만 일반 runtime은 is_complete를 자동으로 해제하지
않습니다.
8. Session 조회¶
flowchart LR
ID["session_id"] --> E{"Session config와<br/>DSTManager 존재?"}
E -->|"no"| N["HTTP 404"]
E -->|"yes"| L["session lock"]
L --> CS["ContextStore<br/>DialogueState + TTL"]
CS --> Q{"state 존재?"}
Q -->|"no 또는 만료"| N
Q -->|"yes"| F["flat session info"]
F --> R["ChatbotSessionInfoResponse<br/>HTTP 200"]
상태가 존재하면 current node, turn count, 완료 여부, 채워진 slots, context와 TTL을
반환합니다. Session config나 DSTManager가 없거나 dialogue state가 만료되었으면
service가 None을 반환하고 route가 HTTP 404로 변환합니다. 조회는
asyncio.to_thread()에서 실행되므로 Redis 같은 blocking store를 사용하더라도
FastAPI event loop를 직접 차단하지 않습니다.
현재 API singleton은 use_redis=false이므로 실제 저장은 process-local memory입니다.
Session lock도 process-local이며 worker/Pod 사이의 동시성을 제어하지 않습니다.
ContextStore TTL 만료가 SessionManager 설정, DSTManager runtime과 lock을 원자적으로
정리하지도 않습니다.
9. 오류를 해석하는 방법¶
| 구분 | 예 | Client 처리 |
|---|---|---|
| HTTP validation | 422 |
detail[]의 field path 확인 |
| Route validation | 400 |
detail 확인 후 request 수정 |
| Body-level validation result | HTTP 200, status="error" |
Scenario 수정 |
| Body-level dialogue error | HTTP 200, error=true |
실패 turn으로 처리 |
| Unhandled server error | 500 |
재시도 정책 적용 전 server log 확인 |
현재 통일된 error response model, authentication middleware와 CORS middleware는 없습니다. Rate limiting, request size와 scenario graph depth 제한도 API layer에서 설정하지 않습니다.
10. 구현 파일 대응¶
| 역할 | 파일 |
|---|---|
| FastAPI application과 root endpoint | dm/api/app.py |
| Router prefix와 metadata | dm/api/routes/common.py, dm/api/routes/overview.py |
| Session·validation routes | dm/api/routes/sessions.py |
| Message route | dm/api/routes/messages.py |
| Request/response models | dm/api/schemas/ |
| Public message response 변환 | dm/api/response_builder.py |
| Executor 선택과 Skill 실행 | dm/core/executors/resolver.py, dm/core/executors/skill_executor.py |
| Skill Runtime HTTP client와 contract | dm/skills/client.py, dm/skills/models.py, dm/skills/mapping.py |
| Process-local DialogManager 생성 | dm/api/dependencies.py |
| Environment와 CLI 설정 | dm/api/config.py, dm/api/parser.py |
| Logging 초기화 | dm/api/bootstrap.py |
Application import 시 .env를 읽고 exp/<API_APP_SYMBOL> directory를 생성합니다.
/session/json은 debug log에 전체 graph를 남길 수 있고 runtime log에는 사용자
발화가 포함될 수 있으므로 운영 log level과 개인정보 정책을 적용해야 합니다.