Day 1: 제품 기능 사양 및 LLM 구조
1. 학습 목표
Day 1은 전체 구조의 기준점을 잡는 장입니다. LLM 운영 담당자가 가장 먼저 이해해야 하는 것은 “음성 인식 결과가 바로 기능 실행으로 이어지는 단순 구조가 아니다”라는 점입니다. 실제 제품은 STT, 온디바이스 딕셔너리, 로컬 LLM, Cloud LLM, 기능 토큰, 사전 검증, Device API, TTS, 상태관리까지 이어지는 E2E 시스템입니다.
이 장을 읽은 후에는 다음을 설명할 수 있어야 합니다.
- 사용자 발화가
STT -> Dictionary/LLM -> <sk_xx> -> ApiCallValidator -> FunctionCallHandler -> Device API -> TTS로 흐르는 이유 - On-device LLM과 Cloud LLM의 책임 경계
- Cloud LLM이 제품 기능을 직접 실행하지 않고
rewrite_query로 온디바이스에 재진입시키는 구조 <sk_xx>기능 토큰이 제품 사양, 테스트, 로그 분석의 공통 언어가 되는 이유- 멀티턴, Cloud continue, resume/idle 상태가 테스트 결과에 주는 영향
참조:
- On-device E2E Source Map
- Operational Examples
- 제품 음성 E2E 전체 구성도
- On-device와 Cloud LLM 책임 경계
- STT On-device/Cloud 입력 분기 - PDF-safe
- 음성 입력부터 STT까지
- 로컬 기능 실행 경로
- Cloud fallback과 재진입
- 상태관리 핵심 플래그
2. 본문 개요
제품 음성 기능의 전체 구조는 다음 관점에서 이해해야 합니다.
사용자가 말한 문장은 바로 기능으로 실행되지 않습니다.
먼저 STT가 텍스트를 만들고, 온디바이스 딕셔너리와 로컬 LLM이 기능 토큰을 만들고,
그 토큰이 현재 기기 상태에서 실행 가능한지 ApiCallValidator가 검증한 뒤,
FunctionCallHandler가 실제 Device API 호출과 TTS를 결정합니다.
Cloud LLM도 중요하지만, Cloud가 제품 기능을 직접 실행하는 구조는 아닙니다.
Cloud는 일반 답변을 하거나, 제품 기능 의도를 다시 온디바이스가 이해할 수 있는 문장으로 바꿔줍니다.
따라서 테스트 중 문제가 생기면 "LLM이 틀렸다"라고 묶어서 보면 안 됩니다.
STT 문제인지, 딕셔너리 문제인지, Cloud Case 문제인지, 기능 구현 문제인지, 기기 상태 문제인지 분리해야 합니다.
3. 전체 구성도 설명
전체 구성도는 아래 순서로 읽습니다.
사용자 음성
-> Kardome 이벤트/WUW/VAD/SAE
-> ForegroundService 녹음 상태
-> Sherpa ONNX STT
-> STT raw/originalSttResult
-> 로컬 전처리 filteredSttResult
-> 딕셔너리 matcher
-> 로컬 LLM fallback
-> parsedChunk `<sk_xx>`
-> LLMResponseParser
-> ApiCallValidator
-> FunctionCallHandler
-> DeviceCommunicator
-> TTS/화면/상태 reset
핵심 포인트:
1. STT raw와 로컬 LLM 입력은 같지 않을 수 있습니다.
2. Cloud로 넘어가는 문장은 `originalSttResult` 기반입니다.
3. 로컬 기능 실행은 최종적으로 `<sk_xx>` 토큰을 기준으로 합니다.
4. `<sk_xx>`가 맞아도 기기 상태나 에러코드 때문에 실행이 막힐 수 있습니다.
5. TTS 문구가 틀린 경우는 LLM 문제가 아니라 리소스/FunctionCallHandler 문제일 수 있습니다.
4. 소스레벨 진입점
ForegroundService.processVoiceCmd(sttResult)
역할:
1. STT 결과를 받은 뒤 전체 처리 시작
2. STT 로그/AI Log 저장
3. Cloud continue 여부 판단
4. 로컬 LLM 또는 Cloud LLM 호출
5. 로컬 이해 실패 시 Cloud fallback
6. TTS 입력/재생
설명:
processVoiceCmd는 제품 음성 기능의 중앙 제어점입니다.
테스트 로그에서 processVoiceCmd가 찍히지 않으면 STT 이후 처리까지 오지 못한 것입니다.
processVoiceCmd는 로컬 경로와 Cloud 경로를 선택하는 위치이므로,
Cloud가 왜 호출됐는지 확인하려면 이 함수를 기준으로 로그를 따라가야 합니다.
ForegroundService.processLLAMA(sttResult)
역할:
1. 상태를 `STATE_LLM_RUNNING`으로 변경
2. STT 결과를 로컬 라우팅용으로 정리
3. 딕셔너리 matcher 적용
4. matcher 실패 시 `mQaClient.Infer(prompt)` 호출
5. `<sk_end>`를 붙여 최종 토큰 문자열 구성
6. `LLMResponseParser`로 파싱
7. `ApiCallValidator.preApiCallValidate()` 수행
8. `mappingTtsTextAndDeviceApi()`로 기능 실행 연결
아래 로그가 이 단계의 기준 로그입니다.
Processing with on-device LLM
LLM inference started
[DICTIONARY_MATCH][...]
parsedLlamaResponse: [ <sk_48>(action=1) ]
Cutted parsedLlamaResponse: [ <sk_48>(action=1)<sk_end> ]
HI_NAMUH_LLMResponseParser: llmResponse : <sk_48>(action=1)<sk_end>
ForegroundService.processCloudLLM()
역할:
1. Wi-Fi 연결 확인
2. Device serial 확인
3. Cloud request 구성
4. ChatRepository.sendMessage()
5. Case 1/2/3/4 처리
6. Case 2/3 일부는 `runCloudResolvedQueryThroughLocalPipeline()`으로 로컬 재진입
핵심 포인트:
Cloud 응답이 Case 2라면 최종 기능 실행은 아직 끝난 것이 아닙니다.
rewrite_query가 다시 processLLAMA로 들어가서 온디바이스 딕셔너리/로컬 LLM을 통과해야 합니다.
5. On-device LLM 책임
On-device는 제품 기능 실행의 중심입니다.
책임:
- 주요 제품 기능을 네트워크 없이 처리
- 딕셔너리 기반 빠른 매칭 수행
- 로컬 LLM으로 딕셔너리 미포함 발화 보완
- 기능 토큰과 파라미터 생성
- 기기 상태/API/TTS와 연결
예:
발화: 전체 청정 스케줄 등록해줘
로컬 결과: <sk_48>(action=2)<sk_end>
후속 처리:
- 맵 유무 확인
- 공간 목록 확인
- 온디바이스/서버 스케줄 경로 분기
- 성공/중복/실패 TTS
6. Cloud LLM 책임
Cloud는 보완/확장 경로입니다.
책임:
- 일반 질문 답변
- 로컬 이해 실패 시 재해석
- 준비 질문/FAQ성 질문 처리
- 최신성/외부 정보/RAG가 필요한 질문 처리
- 제품 기능 요청을 Case 2 + rewrite_query로 다시 온디바이스가 이해 가능한 문장으로 변환
Cloud가 하지 않는 것:
Cloud LLM은 일반적으로 Device API를 직접 호출하지 않습니다.
Cloud가 기능 실행 의도를 이해해도, 최종 실행은 온디바이스 FunctionCallHandler/DeviceCommunicator 경로에서 이뤄집니다.
7. 기능 토큰과 제품 사양
기능 토큰은 음성 기능의 내부 계약입니다.
예:
| 토큰 | 의미 |
|---|---|
<sk_29>(get=True) |
공기질 맵 히스토리 조회/앱 안내 |
<sk_39>(theme=1) |
대기화면 상세 테마 설정 |
<sk_46> |
보안모드 실행 |
<sk_48>(action=1) |
고정청정 스케줄 등록 |
<sk_48>(action=2) |
전체청정 스케줄 등록 |
<sk_48>(action=4) |
일반 스케줄 요청/지원 불가 안내 |
<sk_50> |
등록된 공간 개수 안내 |
<sk_108>(type=1) |
현재 시간 질의 |
핵심 포인트:
테스트 기대값은 "어떤 답변이 나왔는가"만으로 잡으면 안 됩니다.
기대 토큰, 기대 Device API, 기대 TTS를 함께 정의해야 합니다.
7-1. 음성 지원 가능 범위와 지원/미지원 케이스
말레이시아 현지 운영에서는 “사용자가 말한 모든 기능을 제품이 수행할 수 있다”는 관점이 아니라, 아래 세 범위를 먼저 구분해야 합니다.
| 범위 | 처리 경로 | 대표 예 | 기대 결과 |
|---|---|---|---|
| 제품 기능 지원 | On-device token -> Validator -> Device API | 청정, 이동, 보안모드, 스케줄, 대기화면, 공기질 맵 | 기능 실행 또는 상태 안내 |
| 제품 설명/일반 질의 | Cloud Case 1 또는 prepared answer | 보안모드 설명, 올타임 모드 설명, 이용료 안내 | 짧은 설명 TTS |
| 미지원 기능 | Cloud Case 4 또는 로컬 미지원 안내 | 유튜브 실행, 임의 앱 실행, 제품 외부 제어 | 미지원 안내 |
지원 가능 범위를 판단할 때는 아래 순서를 사용합니다.
1. 발화가 제품 내 기능인지 확인
2. 해당 기능에 `<sk_xx>` 토큰이 있는지 확인
3. 토큰이 있으면 FunctionCallHandler 분기가 있는지 확인
4. 실행 전 Validator 차단 조건이 있는지 확인
5. Device API가 실제로 존재하는지 확인
6. 사용자에게 말할 TTS/Subtitle 문구가 있는지 확인
기능별 동작 사양은 “토큰만 맞으면 성공”이 아닙니다. 아래 네 값이 동시에 맞아야 제품 사양과 일치합니다.
| 사양 항목 | 확인 위치 |
|---|---|
| 기대 토큰 | parsedLlamaResponse, LLMResponseParser |
| 실행 가능 조건 | ApiCallValidator, errorcode, device status |
| 실행 API | DeviceCommunicator Device API Input Parameter |
| 사용자 안내 | ttsText, string resource, subtitle |
8. 상태관리와 멀티턴
주요 상태:
STATE_PRE_RECORDING
STATE_MAIN_RECORDING
STATE_STT_RUNNING
STATE_LLM_RUNNING
STATE_TTS_RUNNING
STATE_IDLE
주요 변수:
| 변수 | 의미 |
|---|---|
multiTurnBusy |
후속 발화 대기 |
multiTurnTextFragment |
이전 불완전 발화 조각 |
multiTurnCount |
멀티턴 반복 횟수 |
isCloudContinue |
Cloud 대화 지속 여부 |
skipResume |
TTS 후 청취 재개 스킵 여부 |
isDetermineMultiturnNeeded |
parsedChunk 기반 멀티턴 판단 여부 |
예:
로컬 LLM 결과에 `-1` 파라미터가 포함되면 슬롯이 비었다고 보고 multiTurnBusy가 true가 될 수 있습니다.
Cloud Case 1 응답이 질문으로 끝나면 Cloud continue가 유지되고, 이때는 로컬 parsedChunk 기준 멀티턴 판단을 하지 않습니다.
누락되기 쉬운 운영 관점:
- Cloud continue 상태에서는
parsedChunk기준 멀티턴 판단을 그대로 적용하지 않습니다. skipResume이true이면 TTS 이후 일반 resume 흐름과 다르게 동작할 수 있습니다.- 멀티턴에서 이전 fragment가 현재 STT 앞에 붙기 때문에 단일 발화 테스트와 결과가 달라질 수 있습니다.
- 상태관리 이슈는 token만 봐서는 확인되지 않으므로
resetState,determineMultiTurn,skipResume로그를 같이 봐야 합니다. - Cloud Case 2는 Cloud 응답 완료가 아니라 온디바이스 재진입의 시작점입니다.
9. 처리 예제
예제 1: 정상 온디바이스 기능
발화: 등록된 스케줄 보여줘
기대 흐름:
STT -> dictionary -> <sk_48>(get=True) -> FunctionCallHandler -> TTS/스케줄 화면
관찰 로그:
[DICTIONARY_MATCH]
parsedLlamaResponse
LLMResponseParser
FunctionCallHandler
mappingTtsTextAndDeviceApi
ttsText
예제 2: Cloud fallback
발화: 로컬이 이해하기 어려운 일반 질문
기대 흐름:
processLLAMA -> error_answer -> UNDERSTANDING ERROR TO CLOUD -> processCloudLLM
관찰 로그:
UNDERSTANDING ERROR TO CLOUD
REQ size
send json
API Response
예제 3: Cloud Case 2 재진입
발화: Please show me the air quality map history
기대 흐름:
Cloud Case 2 또는 로컬 dictionary
rewrite_query가 있으면 processLLAMA로 재진입
<sk_29>(get=True)
10. 실습
운영 예제:
| 상황 | 먼저 볼 로그 | 1차 판정 |
|---|---|---|
| 로컬 이해 실패 후 Cloud로 넘어감 | UNDERSTANDING ERROR TO CLOUD |
로컬 딕셔너리/LLM 미매칭 또는 기능 분기 누락 |
| Cloud가 Case 2를 내려줌 | rewrite_query |
Cloud는 기능 의도로 봄. 온디바이스 재진입 성공 여부 확인 필요 |
| 토큰은 맞는데 에러 TTS | ApiCallValidator, errorcode |
기기 상태/에러 핸들러 차단 |
| TTS만 이상함 | ttsText, string resource |
리소스/문구 문제 |
아래 발화는 기대 경로를 먼저 정의한 뒤 실제 로그와 비교합니다.
| 발화 | 기대 토큰/경로 |
|---|---|
공기질 맵 히스토리 보여줘 |
<sk_29>(get=True) |
대기화면 상세로 바꿔줘 |
<sk_39>(theme=1) |
보안모드 켜줘 |
<sk_46> |
고정 청정 스케줄 등록해줘 |
<sk_48>(action=1) |
등록된 스케줄 보여줘 |
<sk_48>(get=True) |
오늘 날씨 어때 |
날씨 기능 또는 Cloud/날씨 경로 |
11. 정리 과제
아래 항목을 정리합니다.
1. 전체 E2E 단계 요약
2. On-device와 Cloud LLM 책임 차이
3. `<sk_xx>` 토큰의 역할
4. 상태관리 변수 5개 설명
5. 같은 증상이더라도 원인이 다를 수 있는 예시 3개
12. 점검 질문
1. Cloud LLM이 제품 기능을 직접 실행하지 않는다는 말의 의미는 무엇인가?
2. STT raw와 filteredSttResult는 왜 다를 수 있는가?
3. `<sk_xx>` 토큰이 맞는데도 기능이 실행되지 않을 수 있는 이유는 무엇인가?
4. Cloud Case 2가 성공하기 위한 온디바이스 조건은 무엇인가?
5. 멀티턴과 Cloud continue는 어떤 차이가 있는가?