← Docs hub

등록 App·AI-Agent ↔ DeviceAgent 인터페이스 정의

실제 구현 요청은 경계별로 분리했다: DeviceAgent ↔ AI-Agent · DeviceAgent ↔ 등록 App · 담당별 범위. 이 페이지는 기존 전체 필드·Sample 참고 자료이며, 중복되는 현재 동작과 책임 범위는 신규 요청서를 먼저 본다.

연동 시 함께 확인: 단계별 완료 조건, 단절·타임아웃·재부팅 복구, 삭제 범위와 부분 실패, 공통 시험표. 제품 제안 규칙은 현재 Binder의 완료 보장이나 신규 enum이 아니다.

관심사 관리: 저장 주체는 서버다. getVoicePersonalization / voicePersonalizationResult 전체 명세·구조도. AI-Agent가 조회를 시작하고 DeviceAgent는 IoTAgent로 전달한다. 등록 App은 관심사 저장·조회 당사자가 아니다. 아래 구형 set/getVoiceProfileInterests는 로컬 DB 처리 없이 실패를 반환한다.

전체 명세: 26개 메서드별 입력·결과·예제 · 시간·취소·재연결 한계. 앞부분은 공통 규칙이며, 12절부터 각 메서드를 독립적으로 확인할 수 있다.

DeviceAgent·IotAgent 개발자가 기존 Registration App·AI-Agent와 연결할 때 구현할 약속이다. 역할 소개가 아니라 호출 진입점, Bundle 직렬화, 요청별 입력, 결과와 callback 경로를 정의한다. 현재 구현과 맞지 않는 제품 확장은 마지막 절에 별도로 표시한다.

모바일부터 기기 내부까지 요청과 결과가 전달되는 구조

기기 내부 요청은 DeviceAgent를 거친다. 등록 App은 녹음을 요청하고 화면을 표시하며, AI-Agent Service가 실제 녹음과 검증을 수행한다. 함수의 반환값은 요청 수락 여부이고, 진행 상태와 결과는 별도 callback으로 돌아온다. 아래 2절에서 callback 수신 대상을 확인한다.

필요한 인터페이스 찾기

구현할 작업 요청 명세 결과·다음 처리
처음 Binder 연결 1. 공통 함수, 2. callback 대상 연결 후 getVoiceProfileEnrollmentState
등록 화면 열기 5. 원격 Activity 실행 화면에서 시작한 요청은 AI-Agent로 중계
녹음 시작·재시도·취소 4. 등록 제어 입력 6. 상태 Bundle
생성 프로필 받기 7. commit 입력과 응답 저장 후 voiceProfileCommitResult
목록·삭제 8. 관리 요청과 결과 결과 method에 맞춰 IotAgent로 전달
서버 관심사 조회 getVoicePersonalization AI-Agent 요청을 IoTAgent로, 서버 결과는 AI-Agent로 전달
호출 코드 작성 10. Kotlin 예제 11. 구현 확인

표 읽는 법: 요청 표는 상대가 보내는 값, 결과 표는 내가 반환하거나 중계할 값이다. 동일한 method가 왕복하는 것이 아니라 요청 method와 결과 method가 서로 다를 수 있다.

구현 상대별 명세

DeviceAgent가 등록 요청과 상태·저장 결과를 중계하는 순서

라우팅 순서 그림 크게 보기

DeviceAgent는 요청을 검사하고 method에 따라 수신 모듈을 선택한다. 녹음과 프로필 생성은 AI-Agent가 수행하며, DeviceAgent는 상태를 등록 App·IoTAgent에 분배한다. 그림의 번호는 대표 흐름이지 모든 요청을 직렬 실행한다는 뜻은 아니다. 상태 이벤트는 반복되며 취소는 진행 중에도 들어올 수 있다.

구현 경계 직접 주고받는 값 상세 페이지
등록 App ↔ DeviceAgent 실행 Intent, 시작·재녹음·취소 Bundle, 상태 callback 등록 App 인터페이스
AI-Agent ↔ DeviceAgent 실행 요청, 상태, 임베딩 commit, 저장 ACK, 관리 결과 AI-Agent 인터페이스

먼저 해당 경계의 요청·응답 쌍을 구현하고, 아래 전체 메서드 명세는 필드 검증·예외 확인용으로 사용한다.

1. 연결할 함수

새로운 기능별 AIDL을 만드는 방식이 아니다. 기존 DeviceControlProxy와 공통 Binder 함수를 사용한다.

구분 실제 호출/수신 의미
연결 DeviceControlProxy.connect(context, callback, true, callbackClassName) 기존 Proxy에 연결하고 callback 등록
요청 DeviceControlProxy.getService().sendModuleCommand(input, output) input은 Bundle, 반환값은 Int
수신 IDeviceControlProxyCallback.onModuleCallback(outdata: Bundle?) 요청·상태·최종 결과를 비동기로 받음
중계 mMainApi.onModuleCallbackLoop(caller, new Bundle(input)) DeviceAgent가 대상 모듈에 전달
연결 해제 DeviceControlProxy.disconnect() 등록 App 클라이언트 종료 처리

화자인식 요청의 output Bundle을 최종 결과로 읽으면 안 된다. 결과는 별도 callback이다. 현재 분기의 반환값은 0 전달/수락, -1 요청·결과 검증 또는 저장 실패, -2 일부 분기의 호출자 거절, -3 원격 등록 Activity 실행 실패다. 관리 요청의 호출자 거절도 -1일 수 있어 전역 오류 enum으로 해석하지 않는다. 반환값 0은 등록·삭제·Cloud 저장 완료가 아니다.

2. 호출자와 callback 배선

요청/이벤트 발신자 DeviceAgent 처리 callback 대상
Registration App: 등록 제어 동일 Bundle 중계 CALLER_LLM=1: AI-Agent
IotAgent: 원격 start/cancel 등록 Activity 실행/새 Intent 전달 Activity가 다시 등록 제어 요청
IotAgent: 목록·삭제 동일 Bundle 중계 CALLER_LLM=1
AI-Agent: getVoicePersonalization 요청 Bundle 중계 CALLER_IOT=2
IotAgent: voicePersonalizationResult 결과 Bundle 중계 CALLER_LLM=1
AI-Agent: voiceProfileStateChanged 두 대상에 복사 전달 CALLER_VOICE_PROFILE_REGISTRATION=8, CALLER_IOT=2
AI-Agent: commitVoiceProfile 전달 대기 저장 후 결과 생성 CALLER_LLM=1로 voiceProfileCommitResult

호출자 검증 패키지는 Registration App com.sk.airbot.voiceprofileregistration, IotAgent com.sk.airbot.iotagent, AI-Agent com.skmagic.ondeviceai.agent다. 현재 코드는 Binder UID에 연결된 패키지를 검사한다. 제품 서명·권한 설정 검증도 별도로 수행한다. ModuleCmdCallbackPolicy에 요청·응답 상수를 등록해야 하며 라우터만 추가해서는 callback이 도착하지 않는다.

3. 공통 Bundle

아래 '필수'는 호출자가 보내야 할 항목이다. 수신 코드의 일부 기본값은 누락 허용 계약과 구분한다.

키 Android 타입/쓰기 필수 값
method String / putString 필수 아래 메서드 이름 그대로
contractVersion String / putString 필수 등록·프로필: voice-profile-v2, 서버 관심사 조회: voice-personalization-v1
requestId String / putString 필수 비어 있지 않은 작업 ID. 결과에서 그대로 반환
issuedAtMs Long / putLong 등록 App이 전송 Unix ms. 현재 Service의 만료 판정 필드는 아님
expiresAtMs Long / putLong 원격 요청에 포함 DeviceAgent 원격 start/cancel 만료 검사. 생략 시 현재 구현은 무기한

Long 필드를 Int/String으로 넣지 않는다. Bundle.getLong()과 JSON 정수 변환을 맞춘다. 알 수 없는 method/버전/잘못된 타입은 실행하지 않는다. 현재 일부 경로는 음수 반환 또는 로그 후 종료하므로 제품에서는 IotAgent가 호출 실패를 Backend에 알려야 한다.

4. 등록 제어 요청

모두 voice-profile-v2. 공통 Bundle 외 추가 필드는 아래와 같다. 결과는 별도 표시가 없으면 voiceProfileStateChanged다.

method 추가 입력 처리
getVoiceProfileEnrollmentState 없음 requestId 상태 조회. 없는 세션은 WAITING_START
startVoiceProfileEnrollment nickname:String 필수, locale:String 필수, :String 선택 등록 App에서 보낼 때 AI-Agent 등록 시작
retryVoiceProfileSet setIndex:Int 화면이 전달 Service가 현재 세트 기준 재시도. 임의 세트 선택 API 아님
pauseVoiceProfileEnrollmentForExit setIndex:Int 화면이 전달 나가기 확인용 일시 정지
cancelVoiceProfileEnrollment reason:String 선택 Service는 requestId 기준 취소. reason별 동작 분기 없음
setVoiceProfilePersonalization enabled:Boolean 필수 개인화 설정. 누락 시 현재 Service 기본 true
retryVoiceProfileCreation 없음 프로필 생성 재시도
retryVoiceProfileDelivery 없음 DeviceAgent 전달 재시도
closeVoiceProfileRegistration 없음 현재 세션의 화면 종료 처리. 취소와 같은 메서드가 아님

nickname: trim 후 1~40자, 경로 구분자와 제어 문자 금지. 발음 보조 필드는 외부 Bundle에 포함하지 않는다. locale: 한·영 연동 송신값은 한국어 ko-KR, 영어 en-US로 정의한다. 현재 원격 시작 검사는 비어 있지 않은 최대 32자만 확인하며 언어 목록을 강제하지 않는다. 기본값·누락·잘못된 값 처리. userId는 등록 App이 보내는 기존 호환 필드이며 AI-Agent 등록 시작 인자로 사용하지 않는다. 발음 변환은 AI-Agent 내부 책임이며 기본 비활성이다. setVoiceProfilePersonalization은 발음을 수정하는 API가 아니다.

5. 원격 요청의 등록 Activity 실행

IotAgent에서 온 start는 Service로 즉시 넘기지 않고 다음 Component를 연다.

등록 App은 DeviceAgent에 연결한 뒤 상태를 조회하고 사용자 시작 시 start Bundle을 보낸다. 원격 취소의 현재 구현도 Activity를 경유한다. 화면 미설치/실행 실패를 취소 성공으로 처리하면 안 된다.

6. 상태 callback

발신: AI-Agent → 중계: DeviceAgent → 수신: Registration App와 IotAgent. DeviceAgent는 같은 상태 Bundle을 두 대상으로 전달한다. 현재 IotAgent의 결과 발행은 로컬 시험 Bridge 경로다. 제품 AWS Backend 응답 토픽 발행은 추가 구현 대상이다. 상태 순번의 현재 한계는 13절을 참고한다.

method=voiceProfileStateChanged, contractVersion=voice-profile-v2.

필드 타입 포함 조건/의미
requestId String 항상. 등록 요청과 일치
stateSequence Long 항상. 같은 작업 내 상태 순번
state String 항상. Service enum 이름
setIndex / setCount Int / Int 항상. 세트 번호/전체 수
progress Int 항상. 단계 진행률 0~100
reasonCode String 항상. 정상은 빈 문자열 가능
terminal Boolean 항상. 상태의 종료 여부
nickname String 항상. 아직 요청 정보가 없으면 빈 문자열 가능
voiceId String 생성된 값이 있을 때만. COMPLETED에는 필수

화면은 다른 requestId를 무시하고 같은 작업의 오래된 순번을 덮어쓰지 않는다. SET_REJECTED는 재녹음 가능한 상태다. 현재 PROFILE_FAILED는 시작 요청 거절 시 terminal=true, 임베딩 생성 실패 시 false다. DELIVERY_FAILED는 현재 false로 전송하며 재시도할 수 있다. 실패 이름만으로 종료를 추정하지 말고 callback의 terminal과 재시도 가능 단계를 함께 읽는다.

7. 프로필 전달: AI-Agent → DeviceAgent

이 절은 AI-Agent와 DeviceAgent 사이의 임베딩 전달이다. 등록 App 요청과 구분해서 구현한다. 전체 왕복 필드는 AI-Agent ↔ DeviceAgent 명세, 화면 제어는 등록 App ↔ DeviceAgent 명세를 참고한다.

DeviceAgent가 구현할 수신 처리: 요청 검증 → 전달 대기 저장 → 결과 Bundle 생성 → AI-Agent callback. 단순 전달 함수와 달리 이 메서드는 DeviceAgent가 결과를 직접 만든다.

method=commitVoiceProfile, 버전 voice-profile-v2.

추가 필드 타입 송신 계약
voiceId String 필수. AI-Agent가 생성한 ID
nickname String 필수. 표시 이름
locale String 송신
personalizationEnabled Boolean 송신
referenceEmbedding FloatArray / putFloatArray 필수. 대표 임베딩

현재 DeviceAgent는 voiceId 1~128자, 임베딩 1~4096개와 각 값이 유한한지 검사한다. 이 범위는 방어 상한이지 실제 모델 차원이 아니다. 원본 audio/wav/pcm은 금지한다. 소유한 저장소에 안전하게 저장한 뒤 callback을 만든다.

DeviceAgent → AI-Agent 결과

method=voiceProfileCommitResult, 동일 버전·requestId.

추가 필드 타입 의미
accepted Boolean DeviceAgent 전달 대기 저장 성공
cloudSynced Boolean Cloud 저장 확인 여부
reasonCode String 결과 사유

현재 사유는 INVALID_PROFILE_PAYLOAD, DEVICE_OUTBOX_FAILED, WAITING_CLOUD_SYNC, 시험용 LOCAL_TEST_ACK다. 현재 제품 Cloud ACK 연결은 없다. accepted=true, cloudSynced=false이면 Service는 DELIVERY_FAILED로 처리한다. 제품 ACK를 늦게 받았을 때 이미 실패 상태인 Service에 완료를 적용하는 정책도 함께 구현해야 한다. 임의로 cloudSynced=true를 반환해서 제품 등록 성공을 만들면 안 된다.

getVoicePersonalization

방향: AI-Agent → DeviceAgent → IoTAgent → Backend. AI-Agent가 sendModuleCommand로 요청하고 DeviceAgent는 CALLER_IOT=2 callback에 전달한다. 등록 App은 이 요청을 보내지 않는다.

필드 Bundle 타입 필수·값
method String getVoicePersonalization
contractVersion String voice-personalization-v1
requestId String 새 요청 UUID. 검증: [A-Za-z0-9_-]{1,128}
voiceId String 인식된 등록 화자 ID. 검증: [A-Za-z0-9_-]{1,128}
issuedAtMs Long 생성 시각, Unix ms. 미래 시각 거절
expiresAtMs Long AI-Agent 생성 시각 + 5,000ms. DeviceAgent는 만료 또는 요청 기간 10,000ms 초과 거절

DeviceAgent는 AI-Agent 호출자와 버전·타입·기한을 검사한다. 유효 요청은 0, 거절은 -1이다. 0은 콜백 중계 수락이며 서버 조회 완료가 아니다. MQTT 연결이 없으면 별도 실패 콜백 없이 기한이 끝날 수 있다.

호출 조건·처리: 등록 화자 확정과 관심사 기능 활성 후 새 요청 한 건을 추적한다. 새 요청은 이전 pending과 임시 결과를 비운다.

오류·중복·검증: 잘못된 caller·타입·만료는 거절하며 별도 callback이 없을 수 있다. 다른 requestId/voiceId, 취소 후 응답, 중복 응답을 사용하지 않는지 확인한다.

{
  "method": "getVoicePersonalization",
  "contractVersion": "voice-personalization-v1",
  "requestId": "query-001",
  "voiceId": "voice-001",
  "issuedAtMs": 1789689600000,
  "expiresAtMs": 1789689605000
}

시각은 형식 예시다. 실행 시 현재 시각과 5초 후 시각으로 대체한다.

voicePersonalizationResult

방향: Backend → IoTAgent → DeviceAgent → AI-Agent. IoTAgent가 sendModuleCommand로 보내고 DeviceAgent는 CALLER_LLM=1 callback으로 전달한다. 등록 App에는 전달하지 않는다.

필드 Bundle 타입 필수·값
method String voicePersonalizationResult
contractVersion String voice-personalization-v1
requestId String 원 요청과 동일
voiceId String 원 요청과 동일
success Boolean 항상 포함
reasonCode String 항상 포함. 성공은 빈 문자열
expiresAtMs Long 원 요청 기한 유지; 서버에서 연장하지 않음
personalizationEnabled Boolean 성공 시 필수
interestCodes ArrayList<String> 성공 시 필수. 개인화 비허용이면 []
revision Long 성공 시 필수, 1 이상

허용 코드는 WEATHER, AIR_QUALITY, HEALTH, SCHEDULE, TRAVEL, FOOD, CULTURE, SPORTS, FINANCE, TECHNOLOGY다. 최대 10개, 중복 금지이며 빈 목록은 관심사 미설정이다. putStringArrayList를 사용한다.

서버 실패는 PROFILE_NOT_FOUND 또는 BACKEND_UNAVAILABLE이며 interestCodes를 포함하지 않는다. DeviceAgent는 IoTAgent 호출자·타입·기한을 검사하고, AI-Agent는 현재 requestId·voiceId·기한을 대조한다. 종료된 요청·다른 화자·중복 응답을 사용하지 않는다. 조회 결과는 원 요청의 5초 기한까지만 임시 보관하고 로컬 DB에 저장하지 않는다.

현재 서버 통신은 VoiceProfileMqttTestBridge와 샘플 Backend 경로다. 제품 AWS 인증·권한·통신 검증과 LLM 프롬프트 자동 주입은 완료 범위가 아니다.

호출 조건·처리: 서버가 원 요청에 대응해 반환한다. AI-Agent는 유효 성공만 snapshot에 보관한다.

오류·중복·검증: 실패에는 interestCodes를 넣지 않는다. 동일 결과 재수신·기한 연장·다른 화자 응답이 무시되는지 확인한다.

{
  "method": "voicePersonalizationResult",
  "contractVersion": "voice-personalization-v1",
  "requestId": "query-001",
  "voiceId": "voice-001",
  "success": true,
  "reasonCode": "",
  "expiresAtMs": 1789689605000,
  "personalizationEnabled": true,
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ],
  "revision": 2
}

서버 관심사 저장과 AI-Agent 조회

8. 프로필 조회·삭제 요청

요청: IotAgent → DeviceAgent → AI-Agent. 결과: AI-Agent → DeviceAgent → IotAgent. DeviceAgent가 자체 데이터로 목록을 만들어 반환하는 API가 아니다. 관심사 신규 조회는 위 getVoicePersonalization의 반대 방향 요청을 사용한다.

발신은 IotAgent, DeviceAgent가 AI-Agent callback으로 전달한다.

method 버전 추가 입력
listVoiceProfiles voice-profile-v2 없음
deleteVoiceProfile voice-profile-v2 voiceId:String 필수

voiceId는 1~128자의 [A-Za-z0-9_-]+. nickname을 대신 보내지 않는다. 관심사 변경은 모바일 → Backend HTTPS PUT에서 처리한다. 구형 로컬 set/get은 실패 통지만 반환하며 아래 성공 결과 표에 포함하지 않는다.

관리 결과

모두 공통 method:String, contractVersion:String, requestId:String, success:Boolean, reasonCode:String을 포함한다. 아래는 성공 시 추가 필드이며 실패 시 일부가 생략될 수 있다.

method 성공 시 추가 필드
voiceProfileListResult profiles:ArrayList<Bundle>
voiceProfileDeleteResult voiceId:String

profiles 각 Bundle: voiceId, nickname 모두 String; personalizationEnabled:Boolean은 기록된 동의이며 예전 필드 누락은 false. 실패 사유: PROFILE_LIST_FAILED, ENROLLMENT_ACTIVE, PROFILE_NOT_FOUND, PROFILE_DELETE_FAILED, FEATURE_DISABLED, HISTORY_READ_FAILED, HISTORY_CLEAR_FAILED, STORAGE_STATS_FAILED. 구형 관심사 요청은 SERVER_OWNED_PERSONALIZATION. 없는 프로필 삭제는 현재 PROFILE_NOT_FOUND다. 성공 결과 수신 시 DeviceAgent가 대기 사본 제거를 시도하지만, 제거 예외가 발생해도 원래 성공 결과가 전달될 수 있다. 제품 Cloud 사본 삭제는 미연결이다. 샘플 Backend는 기기 삭제 성공 또는 PROFILE_NOT_FOUND 응답 후 별도로 관심사 행을 삭제하며, 이를 원자적 삭제로 보장하지 않는다.

10. 호출 예제

아래 문자열은 wire 값을 보여주기 위한 예제다. 구현에서는 각 모듈 Contract 상수를 사용한다.

val input = Bundle().apply {
    putString("method", "getVoicePersonalization")
    putString("contractVersion", "voice-personalization-v1")
    putString("requestId", UUID.randomUUID().toString())
    putString("voiceId", "vp-example-001")
    val now = System.currentTimeMillis()
    putLong("issuedAtMs", now)
    putLong("expiresAtMs", now + 5_000L)
}
// AI-Agent 측: 0은 중계 수락. 조회 결과는 callback으로 받는다.
val result = DeviceControlProxy.getService().sendModuleCommand(input, Bundle())
override fun onModuleCallback(outdata: Bundle?) {
    val data = outdata ?: return
    if (data.getString("method") == "voicePersonalizationResult") {
        val requestId = data.getString("requestId")
        val success = data.getBoolean("success", false)
        val reason = data.getString("reasonCode").orEmpty()
        // 현재 requestId·voiceId·기한을 확인한 뒤 성공 결과만 사용한다.
    }
}

11. 구현 확인 항목

  1. Proxy 연결 후 GET_STATE callback이 등록 App에 도착한다.
  2. 등록 상태는 등록 App과 IotAgent 두 곳에 도착한다.
  3. 관리 요청은 UI를 열지 않고 Service로 전달되고 결과는 IotAgent로 돌아온다.
  4. Long·FloatArray·Parcelable 목록이 타입 손실 없이 왕복한다.
  5. commit 저장 실패는 accepted=false이며 등록 성공 UI가 뜨지 않는다.
  6. MQTT publish 성공이나 Binder 반환 0을 등록 완료로 처리하지 않는다.
  7. 잘못된 호출자·만료·미지원 버전은 실행되지 않는다. 거절 경로는 callback이 없을 수 있으므로 모바일 실패 통지가 보장된다고 판단하지 않는다.

코드 기준과 미완료 경계

기준은 SoC speaker_identification 작업본과 AI-Agent feature/cpu-speaker-recognition 작업본이다. DeviceAgent.java의 sendModuleCommand/launchVoiceProfileRegistration, ModuleCmdCallbackPolicy.java, VoiceProfileContract.java, Registration App의 DeviceAgentVoiceProfileClient.kt, AI-Agent의 VoiceProfileIpcContract.kt/ForegroundService.kt를 대조했다.

제품 MQTT 결과 발행, Cloud ACK 수신·Service 완료 연결, 재전송, 영어 발음 전달, 원격 취소 실패 응답은 추가 작업이다. 이 문서가 해당 구현 완료를 의미하지 않는다.

12. 메서드별 전체 명세

아래 26개 항목은 요청 16개, commit 1개, 결과·이벤트 9개다. 문자열 키 목록을 메서드 수에 포함하지 않는다.

공통 적용: 모든 항목에 method/contractVersion/requestId가 필수다. 표의 선택 필드만 생략 가능하다. 결과 항목의 '성공 필수' 필드는 실패 시 생략할 수 있다. JSON은 Bundle 값의 보기용 표현이며 Binder로 JSON 문자열을 보내는 뜻이 아니다.

메서드 목적
getVoiceProfileEnrollmentState 등록 상태 조회
startVoiceProfileEnrollment 등록 녹음 시작
retryVoiceProfileSet 현재 세트 다시 녹음
pauseVoiceProfileEnrollmentForExit 나가기 확인 중 녹음 정지
cancelVoiceProfileEnrollment 등록 작업 취소
setVoiceProfilePersonalization 개인화 선택 후 생성 시작
retryVoiceProfileCreation 로컬 프로필 생성 재시도
retryVoiceProfileDelivery 프로필 전달 다시 시도
closeVoiceProfileRegistration 등록 UI 세션 정리
commitVoiceProfile 생성 프로필 전달 대기 저장
listVoiceProfiles 기기 프로필 목록
deleteVoiceProfile 프로필과 관련 로컬 데이터 삭제
getVoicePersonalization AI-Agent 시작 서버 관심사 조회
voicePersonalizationResult IoTAgent → AI-Agent 서버 조회 결과
setVoiceProfileInterests 중단된 로컬 API: 실패 통지
getVoiceProfileInterests 중단된 로컬 API: 실패 통지
voiceProfileStateChanged 등록 상태 이벤트
voiceProfileCommitResult DeviceAgent 저장 응답
voiceProfileListResult 프로필 목록 결과
voiceProfileDeleteResult 프로필 삭제 결과
voiceProfileInterestsResult 구형 요청 거절 결과

getVoiceProfileEnrollmentState

목적: 등록 상태 조회. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: Proxy 연결 후 또는 화면 재진입.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자

처리 순서·상태: 동일 requestId의 세션이 있으면 현재 상태를 전송한다. 없으면 WAITING_START, 빈 nickname, sequence=0을 전송한다.

결과: voiceProfileStateChanged의 전체 필드를 따른다.

오류·무응답: 별도 not-found 오류 없음. WAITING_START는 녹음 시작 성공이 아니다.

중복·취소·대기: 반복 조회 가능. 요청 ID가 다르면 활성 세션 정보를 대신 반환하지 않는다. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "getVoiceProfileEnrollmentState",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001"
}

연동 검증: 없는 ID와 활성 ID 조회 결과를 각각 확인.

startVoiceProfileEnrollment

목적: 등록 녹음 시작. 방향: Registration App → DeviceAgent → AI-Agent / 원격 IotAgent 요청은 Activity 실행.

호출 조건: 화면 시작 버튼. 다른 비종료 세션이 없어야 함.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
nickname String 필수 · 1~40자, 안전한 표시 이름
locale String 원격 필수 ≤32자; Service 빈 값 기본 ko-KR

처리 순서·상태: Service는 기존 세트·pending을 비우고 녹음을 시작한다. RECORDING_SET → SET_ACCEPTED/SET_REJECTED. 5세트 후 PERSONALIZATION_SETTINGS.

결과: voiceProfileStateChanged의 전체 필드를 따른다.

오류·무응답: ANOTHER_SESSION_ACTIVE; 진입 검증에서 무응답 거절 가능. 내부 시작 함수에는 INVALID_NICKNAME/INVALID_SPOKEN_NAME_KO. 장치 확보 실패 AUDIO_BUSY.

중복·취소·대기: 동일 requestId의 start도 세트를 초기화한다. 자동 재전송 금지; GET_STATE로 먼저 확인. 녹음 5,200ms, 진행 갱신 500ms. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "startVoiceProfileEnrollment",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "nickname": "Niky",
  "locale": "ko-KR"
}

연동 검증: 중복 start가 무해하다고 가정하지 않고, 화면 시작 1회당 한 요청인지 확인.

retryVoiceProfileSet

목적: 현재 세트 다시 녹음. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: 일치하는 세션, terminal=false. UI는 SET_REJECTED 또는 나가기 확인 후 계속에서 사용.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
setIndex Int 화면 전송; Service는 값 대신 현재 세트 사용

처리 순서·상태: 현재 capture 중단 후 같은 세션의 현재 세트 녹음을 다시 시작.

결과: voiceProfileStateChanged의 전체 필드를 따른다.

오류·무응답: 없는 세션/다른 ID/terminal이면 callback 없이 반환.

중복·취소·대기: 반복 요청은 녹음을 다시 시작하므로 중복 실행 방지 필요. 다른 세트로 점프하는 API가 아니다. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "retryVoiceProfileSet",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "setIndex": 1
}

연동 검증: 다른 setIndex를 보내도 임의 세트 선택으로 취급하지 않는지 확인.

pauseVoiceProfileEnrollmentForExit

목적: 나가기 확인 중 녹음 정지. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: 동일 requestId이고 RECORDING_SET.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
setIndex Int 화면 전송, Service에서 제어 인자로 사용하지 않음

처리 순서·상태: capture 타이머를 제거하고 현재 녹음을 중단한다. 별도 PAUSED 상태나 callback을 발행하지 않는다.

결과: 전용 결과 method 없음. 위 처리에 명시된 상태만 수신한다.

오류·무응답: 상태/ID 불일치는 무응답.

중복·취소·대기: 계속하기는 retryVoiceProfileSet, 나가기는 cancel. pause ACK를 기다리는 타이머를 UI에 만들지 않는다. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "pauseVoiceProfileEnrollmentForExit",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "setIndex": 1
}

연동 검증: 일시정지 후 UI 확인창, 계속 선택 시 재녹음 확인.

cancelVoiceProfileEnrollment

목적: 등록 작업 취소. 방향: Registration App → DeviceAgent → AI-Agent / 원격은 IotAgent → DeviceAgent → Activity.

호출 조건: 동일 requestId의 세션이 존재.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
reason String 선택 · Service는 사유별 분기 없이 USER_CANCELLED 반환

처리 순서·상태: 타이머/capture/수집 세트/pending을 정리하고 CANCELLED, terminal=true 전송 후 음성 경로 복원.

결과: voiceProfileStateChanged의 전체 필드를 따른다.

오류·무응답: ID 불일치/세션 없음은 무응답. 생성 중 취소는 비동기 생성 완료와 경쟁할 수 있다.

중복·취소·대기: 동일 취소 반복 시 callback 재발행 가능. 이미 생성된 로컬/Cloud 사본 삭제는 이 함수의 보장이 아니다. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "cancelVoiceProfileEnrollment",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "reason": "MOBILE_CANCELLED"
}

연동 검증: 녹음 중 취소와 생성 중 취소를 분리 시험; 늦은 생성 완료가 되살리는지 확인.

setVoiceProfilePersonalization

목적: 개인화 선택 후 생성 시작. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: 동일 requestId이고 PERSONALIZATION_SETTINGS.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
enabled Boolean 송신 필수; 현재 수신 기본 true

처리 순서·상태: 선택값 저장 후 finalizeVoiceProfile. 5개 세트가 있어야 CREATING_LOCAL_PROFILE로 진행.

결과: voiceProfileStateChanged의 전체 필드를 따른다.

오류·무응답: 조건 불일치는 무응답. 생성 실패는 EMBEDDING_CREATION_FAILED 또는 품질 사유.

중복·취소·대기: 해당 화면에서 1회만 전송. 발음 수정 함수가 아니며 personalizationEnabled 키와 혼동 금지. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "setVoiceProfilePersonalization",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "enabled": true
}

연동 검증: enabled=false가 commit의 personalizationEnabled=false로 전달되는지 확인.

retryVoiceProfileCreation

목적: 로컬 프로필 생성 재시도. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: 동일 requestId이고 수집 세트 수가 setCount와 같아야 함.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자

처리 순서·상태: finalizeVoiceProfile 호출. 현재 함수는 stage 제한 없이 세트 수를 검사한다.

결과: voiceProfileStateChanged의 전체 필드를 따른다.

오류·무응답: 5개 세트가 이미 비워진 생성 실패 뒤에는 재시도 요청이 아무 결과 없이 반환될 수 있다.

중복·취소·대기: 새 녹음 없이 생성 재시도가 항상 가능하다고 안내하지 않는다. 재진입/중복 생성 방어는 보완 필요. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "retryVoiceProfileCreation",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001"
}

연동 검증: 생성 실패 후 실제로 재시도가 가능한지 별도 시험; 무응답이면 완료로 처리 금지.

retryVoiceProfileDelivery

목적: 프로필 전달 다시 시도. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: 일치하는 pendingVoiceProfileDelivery 존재.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자

처리 순서·상태: PENDING_DEVICE_DELIVERY 상태 전송 후 같은 프로필로 commitVoiceProfile 재호출.

결과: 등록 App은 voiceProfileStateChanged를 받는다. 내부 DeviceAgent 응답인 voiceProfileCommitResult는 AI-Agent가 수신한다.

오류·무응답: pending 없음/ID 불일치 무응답. 전달 예외 DEVICE_AGENT_UNAVAILABLE.

중복·취소·대기: 프로필 새 생성 아님. pending은 메모리이므로 프로세스 재시작 복구를 보장하지 않는다. ACK 대기 타이머 없음. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "retryVoiceProfileDelivery",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001"
}

연동 검증: 동일 voiceId commit 재전송과 DeviceAgent 저장 중복 처리를 확인.

closeVoiceProfileRegistration

목적: 등록 UI 세션 정리. 방향: Registration App → DeviceAgent → AI-Agent.

호출 조건: 일치하는 세션 존재.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자

처리 순서·상태: terminal=false면 cancel을 호출해 CANCELLED 전송. 이후 세션·pending 제거. 이미 terminal이면 별도 상태 없이 정리.

결과: 전용 결과 method 없음. 위 처리에 명시된 상태만 수신한다.

오류·무응답: 세션 없음/ID 불일치 무응답.

중복·취소·대기: 반복 close는 no-op. 프로필 삭제 API가 아니다. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "closeVoiceProfileRegistration",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001"
}

연동 검증: 완료 후 close가 등록 프로필을 삭제하지 않는지, 미완료 close는 취소되는지 확인.

commitVoiceProfile

목적: 생성 프로필 전달 대기 저장. 방향: AI-Agent → DeviceAgent.

호출 조건: AI-Agent 호출자 및 등록 envelope 검증.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
voiceId String 필수 · 1~128자, [A-Za-z0-9_-]+
nickname String AI-Agent 항상 전송
locale String 항상 전송
personalizationEnabled Boolean 항상 전송
referenceEmbedding FloatArray 필수 · 1~4096개 유한 값. 실제 모델 차원 아님

처리 순서·상태: DeviceAgent 전용 pending 저장 후 accepted/cloudSynced callback 생성. 원본 audio/wav/pcm 금지.

결과: voiceProfileCommitResult의 전체 필드를 따른다.

오류·무응답: INVALID_PROFILE_PAYLOAD, DEVICE_OUTBOX_FAILED; 호출자 거절 -2. 저장 실패 -1.

중복·취소·대기: 같은 요청 재전송 가능성에 대비해 requestId/voiceId로 저장 중복 관리 필요. Cloud ACK 경로 미구현. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "commitVoiceProfile",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "voiceId": "vp-example-001",
  "nickname": "Niky",
  "locale": "ko-KR",
  "personalizationEnabled": true,
  "referenceEmbedding": [
    0.5,
    0.5,
    -0.5,
    0.5
  ]
}

연동 검증: 저장 성공 accepted=true/Cloud 미확인 false, 파일 저장 실패 accepted=false 확인.

listVoiceProfiles

목적: 기기 프로필 목록. 방향: IotAgent → DeviceAgent → AI-Agent.

호출 조건: 관리 명령 호출자 IotAgent.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자

처리 순서·상태: AI-Agent 실제 등록 저장소에서 목록 조회. Backend 캐시를 대신 반환하지 않음.

결과: voiceProfileListResult의 전체 필드를 따른다.

오류·무응답: PROFILE_LIST_FAILED.

중복·취소·대기: 읽기 재요청 가능. 같은 응답을 중복 화면 추가하지 않는다. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "listVoiceProfiles",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001"
}

연동 검증: 등록 전 빈 목록, 등록 후 voiceId/발음 포함, 삭제 후 제거 확인.

deleteVoiceProfile

목적: 프로필과 관련 로컬 데이터 삭제. 방향: IotAgent → DeviceAgent → AI-Agent.

호출 조건: voiceId 유효; 진행 중 등록 세션이 없어야 함.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
voiceId String 필수 · 1~128자, [A-Za-z0-9_-]+

처리 순서·상태: 프로필 파일 삭제 → 식별기 재초기화. 서버 관심사 삭제는 샘플 Backend의 후속 처리이며 이 Binder 호출만으로 완료되지 않는다. DeviceAgent는 성공 결과를 받고 pending 사본 제거.

결과: voiceProfileDeleteResult의 전체 필드를 따른다.

오류·무응답: ENROLLMENT_ACTIVE, PROFILE_NOT_FOUND, PROFILE_DELETE_FAILED.

중복·취소·대기: 반복 삭제는 PROFILE_NOT_FOUND. 원자적 전체 rollback 보장 없음; 일부 삭제 후 실패 가능. Cloud 삭제 별도. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "deleteVoiceProfile",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "voiceId": "vp-example-001"
}

연동 검증: 등록 진행 중 거절, 없는 ID, 삭제 후 목록/식별 상태 및 서버 관심사 후속 처리 확인.

setVoiceProfileInterests

중단된 로컬 API. 아래 입력 형식은 이전 호출 식별용이다. 신규 모바일은 서버 HTTPS PUT을 사용한다.

목적: 이전 저장 요청의 거절. 방향: IotAgent → DeviceAgent → AI-Agent.

호출 조건: voiceId 유효, 허용 코드 목록, 기능 활성.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
voiceId String 필수 · 1~128자, [A-Za-z0-9_-]+
interestCodes ArrayList<String> 필수 · 허용 코드, 중복 금지, 빈 목록은 전체 해제

처리 순서·상태: DB 변경 없이 success=false, reasonCode=SERVER_OWNED_PERSONALIZATION 반환.

결과: voiceProfileInterestsResult의 전체 필드를 따른다.

오류·무응답: SERVER_OWNED_PERSONALIZATION; 잘못된 입력은 앞단 무응답 거절 가능.

중복·취소·대기: 재전송해도 저장하지 않고 거절한다. 서버 HTTPS PUT으로 전환한다.

값 예제

{
  "method": "setVoiceProfileInterests",
  "contractVersion": "voice-memory-v1",
  "requestId": "vp-request-001",
  "voiceId": "vp-example-001",
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ]
}

연동 검증: DB 무변경과 SERVER_OWNED_PERSONALIZATION 실패 확인.

getVoiceProfileInterests

중단된 로컬 API. 새 AI-Agent 조회는 getVoicePersonalization이며 방향은 AI-Agent → DeviceAgent다.

목적: 이전 조회 요청의 거절. 방향: IotAgent → DeviceAgent → AI-Agent.

호출 조건: voiceId 유효, 기능 활성.

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
voiceId String 필수 · 1~128자, [A-Za-z0-9_-]+

처리 순서·상태: DB 조회 없이 success=false, reasonCode=SERVER_OWNED_PERSONALIZATION 반환.

결과: voiceProfileInterestsResult의 전체 필드를 따른다.

오류·무응답: SERVER_OWNED_PERSONALIZATION.

중복·취소·대기: 읽기 재시도로 복구되지 않는다. 신규 getVoicePersonalization으로 전환한다.

값 예제

{
  "method": "getVoiceProfileInterests",
  "contractVersion": "voice-memory-v1",
  "requestId": "vp-request-001",
  "voiceId": "vp-example-001"
}

연동 검증: 동일 nickname을 화자 키로 사용하지 않는지 확인.

voiceProfileStateChanged

목적: 등록 상태 이벤트. 방향: AI-Agent → DeviceAgent → Registration App + IotAgent.

호출 조건: 등록 제어에 따른 상태 변경 또는 상태 조회

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
stateSequence Long 필수 · 상태 순번
state String 필수 · 등록 enum
setIndex Int 필수 · 현재 세트
setCount Int 필수 · 현재 구현5
progress Int 필수 · 0~100
reasonCode String 필수 · 정상 빈 문자열
terminal Boolean 필수 · 단계별 종료 여부
nickname String 항상 포함, 상태 조회 초기값은 빈 문자열 가능
voiceId String 생성 후 선택, COMPLETED 필수

처리 순서·상태: UI는 requestId별로 처리. SET_REJECTED는 재녹음, COMPLETED는 완료. PROFILE_FAILED의 terminal은 발생 경로별로 다름.

결과: 이 항목 자체가 결과/이벤트다. 추가 응답 메서드는 없다.

오류·무응답: 알 수 없는 state를 성공으로 표시하지 않는다. 결과 수신이 단절되면 GET_STATE.

중복·취소·대기: 순번이 낮은 상태는 덮어쓰지 않는 것이 수신 계약. 현재 생성 과정에서 이전 session.copy를 복원해 순번이 후퇴할 가능성은 보완 필요. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "voiceProfileStateChanged",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "stateSequence": 8,
  "state": "SET_ACCEPTED",
  "setIndex": 2,
  "setCount": 5,
  "progress": 100,
  "reasonCode": "",
  "terminal": false,
  "nickname": "Niky"
}

연동 검증: UI와 IoT 모두 수신, 초기/재시도 순번과 완료 voiceId 확인.

voiceProfileCommitResult

목적: DeviceAgent 저장 응답. 방향: DeviceAgent → AI-Agent.

호출 조건: 동일 requestId의 세션과 pending이 존재

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
accepted Boolean 필수 · DeviceAgent 저장 수락
cloudSynced Boolean 필수 · Cloud 저장 확인
reasonCode String 필수 · 결과 사유

처리 순서·상태: accepted && cloudSynced이면 COMPLETED, 그 외 DELIVERY_FAILED. 음성 경로 복원.

결과: 이 항목 자체가 결과/이벤트다. 추가 응답 메서드는 없다.

오류·무응답: INVALID_PROFILE_PAYLOAD, DEVICE_OUTBOX_FAILED, WAITING_CLOUD_SYNC. 빈 사유는 Service가 DEVICE_DELIVERY_REJECTED 등으로 대체.

중복·취소·대기: ID 불일치/pending 없음 무시. 완료 후 pending 제거하므로 같은 ACK 재수신은 무시. 프로세스 재시작 후 pending 복구 없음. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "voiceProfileCommitResult",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "accepted": true,
  "cloudSynced": false,
  "reasonCode": "WAITING_CLOUD_SYNC"
}

연동 검증: false/false, true/false, true/true, 다른 requestId를 각각 시험.

voiceProfileListResult

목적: 프로필 목록 결과. 방향: AI-Agent → DeviceAgent → IotAgent.

호출 조건: listVoiceProfiles 완료

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
success Boolean 필수 · 업무 성공 여부
reasonCode String 필수 · 정상 빈 문자열 / 실패 사유
profiles ArrayList<Bundle> 성공 필수 · 최대64, 실패 시 빈 목록 가능

처리 순서·상태: profiles를 JSON 객체 배열로 변환. 빈 목록+success=true는 등록된 화자 없음.

결과: 이 항목 자체가 결과/이벤트다. 추가 응답 메서드는 없다.

오류·무응답: PROFILE_LIST_FAILED. 실패를 빈 목록 성공으로 바꾸지 않는다.

중복·취소·대기: 같은 requestId 결과는 한 번만 반영. raw/embedding 필드 추가 금지. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "voiceProfileListResult",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "success": true,
  "reasonCode": "",
  "profiles": [
    {
      "voiceId": "vp-example-001",
      "nickname": "Niky"
    }
  ]
}

연동 검증: 64개 상한, 필드 타입과 빈 목록 성공/실패 구별.

voiceProfileDeleteResult

목적: 프로필 삭제 결과. 방향: AI-Agent → DeviceAgent → IotAgent.

호출 조건: deleteVoiceProfile 완료

필드 명세

키 Android 타입 필수·기본값·범위
method String 필수 · 고정값
contractVersion String 필수 · 고정값
requestId String 필수 · 작업 식별자
success Boolean 필수 · 업무 성공 여부
reasonCode String 필수 · 정상 빈 문자열 / 실패 사유
voiceId String 필수 · 1~128자, [A-Za-z0-9_-]+

처리 순서·상태: success=true이면 DeviceAgent pending 사본도 제거한 후 IoT 전달.

결과: 이 항목 자체가 결과/이벤트다. 추가 응답 메서드는 없다.

오류·무응답: ENROLLMENT_ACTIVE, PROFILE_NOT_FOUND, PROFILE_DELETE_FAILED.

중복·취소·대기: 중복 삭제 실패를 이전 성공 취소로 해석하지 않는다. DeviceAgent 사본 제거 실패는 로그만 남길 수 있음. 공통 경합/타임아웃 규칙은 다음 절을 함께 적용한다.

값 예제

{
  "method": "voiceProfileDeleteResult",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-request-001",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001"
}

연동 검증: 로컬 결과와 Cloud 사본 삭제를 별도로 확인.

voiceProfileInterestsResult

중단된 로컬 API의 거절 결과. 방향은 AI-Agent → DeviceAgent → IoTAgent다. 정상 서버 조회는 voicePersonalizationResult를 사용한다.

호출 조건·처리: 구형 set/getVoiceProfileInterests가 AI-Agent에 도달하면 DB 접근 없이 아래 실패를 송신한다.

필드 Bundle 타입 현재 송신값
method String voiceProfileInterestsResult
contractVersion String voice-memory-v1
requestId String 도달한 구형 요청 ID
success Boolean false
reasonCode String SERVER_OWNED_PERSONALIZATION

현재 rejectLocalInterests는 voiceId·interestCodes를 넣지 않는다. requestId로 원 요청을 찾는다. 앞단 caller·입력 검증에서 거절되면 이 콜백도 없을 수 있다.

오류·중복: SERVER_OWNED_PERSONALIZATION은 재시도로 해결되지 않는다. 같은 요청의 중복 결과는 requestId로 구분한다.

{
  "method": "voiceProfileInterestsResult",
  "contractVersion": "voice-memory-v1",
  "requestId": "vp-request-001",
  "success": false,
  "reasonCode": "SERVER_OWNED_PERSONALIZATION"
}

연동 검증: 구형 요청으로 DB가 변경·조회되지 않고 실패가 반환되는지 확인한다. 로컬 API 재시도로 서버 관심사를 저장할 수 없다.

13. 시간·취소·재연결의 정확한 보장 범위

호출자 검사는 getPackagesForUid(uid)의 패키지 중 하나가 허용되는지 확인한다. AI-Agent·DeviceAgent·IotAgent가 공유하는 android.uid.system 안에서 개별 앱을 구분하는 인증 보장은 아니다. 등록 App의 별도 UID 검사와 구분해야 한다.

항목 현재 코드 상대 구현 시 주의
녹음 세트 5,200ms, 진행 갱신500ms, 통과 표시700ms 전송 지연과 녹음 시간을 합쳐 고정 완료 시각으로 판단하지 않음
Binder 수락 전달/수락 반환값과 비동기 결과 분리 0으로 업무 성공 표시 금지
ACK 대기 타이머 Service에 별도 ACK 대기 타이머 없음 timeout을 임의 성공으로 처리하지 말고 제품 대기/복구 정책 정의
동일 start 동일 requestId의 start도 세트 초기화 상태 조회 후 사용자 명시 동작으로만 재시작
생성 중 취소 생성 coroutine 완료 시 최신 세션 ID/취소 여부 재검사가 없음 늦은 생성 완료가 상태/pending을 복원할 위험. 제품 통합 전 보완
세트 간 지연 작업 700ms 후속 Runnable의 세션 경계 검증이 없음 통과 직후 취소/새 등록 경합 시험 필요
상태 순번 생성 완료/품질 실패에서 이전 session.copy 사용 stateSequence가 엄격히 증가한다는 현재 구현 보장 없음. 송신측 보완 필요
생성 재시도 실패 후 acceptedSets를 비움 5개 세트가 이미 비워진 상태의 retryCreation은 동작하지 않을 수 있음
프로세스 종료 등록 session/pending은 메모리 재연결 GET_STATE는 디스크 기반 등록 복구가 아님
삭제 실패 프로필·메모리·식별기 갱신 전체 트랜잭션 아님 일부 삭제 후 실패 가능; 조회로 확인
대기 사본 삭제 DeviceAgent 제거 예외에도 기존 삭제 성공 결과 전달 가능 성공 응답을 모든 사본 삭제 완료로 취급하지 않음
원격 요청 충돌 등록 App이 다른 활성 요청을 무시할 수 있음 Service의 ANOTHER_SESSION_ACTIVE 응답이 항상 도착한다고 가정하지 않음
원격 만료 expiresAtMs 검사, 누락 시 Long.MAX_VALUE issuedAtMs만 보내면 만료 방어가 되지 않음; Service 자체 만료 검사 없음

담당별 보완 범위는 프로토콜 작업표로 모았다.

이 절은 확인된 코드 한계를 적은 것이다. 해당 방어 로직을 이번 문서 작업에서 수정하거나 실기기 검증한 것은 아니다.

Binder 반환값과 검증 책임

2026-09-18 DeviceAgent.sendModuleCommand의 화자인식 분기 기준이다. 음수 값의 의미가 모든 method에서 동일하지 않다. 호출자는 아래 분기와 비동기 결과 method를 함께 확인해야 한다.

요청 분기 허용 호출자 → 전달 대상 즉시 반환 실제 결과 채널
개인화 조회·결과 AI-Agent → IoTAgent / IoTAgent → AI-Agent 호출자 또는 payload 오류 -1, 전달 분기 0 voicePersonalizationResult
등록 제어 등록 App → AI-Agent; IoTAgent 원격 요청은 등록 UI 경로 envelope 오류 -1, 호출자 오류 -2, 원격 UI 입력 오류 -1, UI 실행 실패 -3, 수락 0 voiceProfileStateChanged
등록 상태 이벤트 AI-Agent → DeviceAgent → 등록 App·IoTAgent 입력 오류 -1, 호출자 오류 -2, 전달 분기 0 이벤트 자체가 결과; 별도 완료 응답 없음
관리 요청 IoTAgent → AI-Agent 호출자·입력 오류 -1, 전달 분기 0 list/delete/history/stats에 대응하는 Result method
관리 결과 AI-Agent → IoTAgent 호출자·입력 오류 -1, 전달 분기 0 결과 Bundle의 success·reasonCode
프로필 commit AI-Agent → DeviceAgent 저장소 호출자 오류 -2; accepted에 따라 0 또는 -1 voiceProfileCommitResult callback

commit의 잘못된 호출자는 ACK 없이 -2로 거절된다. 허용 호출자의 잘못된 프로필에는 INVALID_PROFILE_PAYLOAD, 저장 실패에는 DEVICE_OUTBOX_FAILED ACK를 만든다. 다른 분기까지 동일한 오류 callback이 있다고 가정하지 않는다.

송신 규약과 실제 수신 검증의 차이

항목 송신 측이 지킬 규약 현재 DeviceAgent·수신 측 검증
등록·상태 버전 voice-profile-v2 DeviceAgent에서 envelope 확인; 등록 App callback 클라이언트는 버전을 다시 검증하지 않음
개인화 버전 voice-personalization-v1 별도 검증 경로 사용. 예전 관심사 set/get과 혼용 금지
상태 순번·진행률 stateSequence Long, progress Int, terminal Boolean 등 명세 타입 준수 DeviceAgent 상태 envelope는 이들 모든 필드의 타입·범위를 검증하지 않음. UI는 기본값·범위 보정 사용
COMPLETED 상태 유효한 nickname·voiceId 포함 DeviceAgent는 완료 상태에서 이름·ID 검사; 그 외 상태는 같은 검사를 강제하지 않음
개인화 응답 같은 요청·화자, Boolean success, Long revision, interestCodes는 ArrayList<String> 타입·필드 검사 후 AI-Agent가 요청 상관관계·만료·중복 검사
삭제 성공 결과 삭제 대상 voiceId와 success 유지 DeviceAgent의 전달 대기 사본 제거 오류는 로그에 남기지만 이미 받은 성공값을 실패로 바꾸지는 않음

따라서 명세서의 필수 필드를 일부 수신 코드가 기본값으로 처리한다고 해서 생략 가능한 계약은 아니다. 반대로 엄격한 수신 검사가 아직 없는 항목을 구현 완료로 표시하지 않는다.

구성요소별 연동 확인 순서

  1. 등록 App과 AI-Agent의 Proxy 연결 및 callback 등록을 먼저 확인한다. 잘못된 caller 배선은 payload가 정상이어도 거절된다.
  2. 등록 App은 등록 제어, AI-Agent는 상태·commit, IoTAgent는 원격 관리 요청을 각각 보내 반환값과 callback을 함께 기록한다.
  3. 서버 관심사는 AI-Agent의 get 요청을 IoTAgent까지 전달하고 같은 requestId·voiceId로 역방향 결과를 돌려준다. 등록 App에 관심사 쓰기 책임을 추가하지 않는다.
  4. 서버 미연결과 로컬 시험 ACK를 나누어 시험한다. local_ack로 완료된 등록을 실제 서버 저장 완료 시험에 포함하지 않는다.
  5. 파일·DB·모델·DeviceAgent 전달 대기 사본과 서버 사본을 구분해 조회·삭제 결과를 확인한다. 하나의 success로 모든 사본 삭제를 단정하지 않는다.

상호 연동 시험표

아래는 실제 Bundle을 사용한 실기기 검증 조건이다. 문서의 예시 JSON 파싱 시험만으로 충족되지 않는다.

번호 보낼 입력·상황 확인 결과
BC-01 등록 App caller로 commit 전송 잘못된 호출자 거절; 저장·성공 ACK 없음
BC-02 등록 상태의 requestId·state 누락 DeviceAgent에서 입력 오류 처리
BC-03 개인화 revision을 Long 대신 String으로 전송 잘못된 타입 거절, AI-Agent 스냅샷 변경 없음
BC-04 상태의 progress·stateSequence 누락 현재 기본값 처리/순번 거절을 확인; 엄격한 계약 검증과의 차이 기록
BC-05 UI 실행 불가능한 원격 등록 요청 -3과 상태 callback 미도착을 구분해 실패 보고
BC-06 삭제 성공 후 DeviceAgent 사본 제거 실패 주입 성공 결과와 잔여 사본 불일치를 검출; 서버까지 삭제 완료로 표시 금지

14. 구현 출처와 적용 범위

코드 대조 기준: AI-Agent HEAD 091e52bc, SoC HEAD e54419a와 현재 작업본의 미커밋 변경을 포함한다. 커밋만 checkout한 형상과 이 명세의 작업본이 같다고 가정하지 않는다.

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date