← Docs hub

AI-Agent ↔ DeviceAgent 인터페이스

구현 협의용 최신 문서: DeviceAgent ↔ AI-Agent 구현 요청서. 아래는 이전 상세·Sample 자료다. 직접 구현 책임과 현재 취소·응답 대기 기준은 신규 요청서를 먼저 확인한다.

연동 보강: 등록·서버 저장 완료 판정, 실패·늦은 응답·복구, 프로필 삭제·동의 철회, 시험별 기대 결과. 표의 제품 제안은 구현 전 규칙이며 기존 Bundle에 미구현 필드를 추가하라는 의미가 아니다.

서버 관심사 조회 (09-17 추가)

송신 → 수신 method 핵심 값
AI-Agent → DeviceAgent → IoTAgent getVoicePersonalization voice-personalization-v1, requestId, voiceId, issuedAtMs, expiresAtMs
IoTAgent → DeviceAgent → AI-Agent voicePersonalizationResult 동일 requestId/voiceId, success, reasonCode, personalizationEnabled, interestCodes, revision, expiresAtMs

필수 타입·유효기간·실패·JSON·구조도 전체 명세. 원본은 서버, 기기 결과 보관은 5초 요청 기한까지다. 아래 이전 set/getVoiceProfileInterests는 더 이상 DB를 처리하지 않고 SERVER_OWNED_PERSONALIZATION을 반환한다.

DeviceAgent가 AI-Agent로 보내는 실행 요청, AI-Agent가 돌려주는 상태·관리 결과, 프로필 전달과 그 ACK를 구분한다. 등록 App용 화면 실행 Intent와는 다른 경계다.

DeviceAgent 중심 구조

등록 App·AI-Agent Service와 DeviceAgent의 요청·결과 전달 구조

SVG 구조 그림 크게 보기 · 등록 App·AI-Agent ↔ DeviceAgent 전체 인터페이스 정의

등록 App은 화면과 사용자 입력을 담당하고, 등록·인식을 수행하는 서비스는 AI-Agent다. 아래 그림은 그중 등록 과정의 중계 순서다. DeviceAgent가 직접 녹음하거나 화자를 식별하는 구조는 아니다.

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 조회

1. 방향과 함수

AI-Agent 내부 등록·인식·저장 구조와 알고리즘은 별도 설계 페이지에서 확인한다.

방향 실제 함수 내용
DeviceAgent → AI-Agent onModuleCallbackLoop(CALLER_LLM=1, Bundle) → AI-Agent callback 등록 제어, 관리 요청, voiceProfileCommitResult, voicePersonalizationResult
AI-Agent → DeviceAgent sendModuleCommand(input, output) voiceProfileStateChanged, commitVoiceProfile, 관리 결과, getVoicePersonalization

DeviceAgent는 등록 상태·관리 결과를 만들어내지 않는다. commitVoiceProfile 수신 후 대기 저장 결과만 DeviceAgent가 직접 생성한다. sendModuleCommand의 Int 반환값과 callback의 업무 결과를 구분한다.

2. DeviceAgent → AI-Agent 실행 요청

공통: method:String, contractVersion:String, requestId:String. issuedAtMs/expiresAtMs가 있으면 Long 타입을 보존한다. 기존 등록·관리 경로의 Service 만료 검사는 보장되지 않는다. 신규 서버 관심사 조회는 별도 버전과 기한 검사를 적용한다.

등록 제어 9종은 등록 App 송신 표에 정의한 Bundle을 그대로 중계한다. AI-Agent는 녹음·재녹음·취소·개인화 처리를 수행한다. IoTAgent의 원격 start는 먼저 등록 Activity를 열므로 바로 이 실행 요청과 같지 않다.

관리 요청 method 버전 추가 입력 키 : 타입 AI-Agent가 보내는 결과 method
listVoiceProfiles voice-profile-v2 없음 voiceProfileListResult
deleteVoiceProfile voice-profile-v2 voiceId:String voiceProfileDeleteResult

limit은 기본 20, 1~50이며 beforeTurnId는 기본 0, 0 이상이다. voiceId는 1~128자 영문·숫자·하이픈·밑줄이다. 관심사 변경은 모바일 → Backend HTTPS 경로이며 이 관리 요청 표에 포함하지 않는다. 구형 setVoiceProfileInterests/getVoiceProfileInterests가 AI-Agent까지 도달하면 voiceProfileInterestsResult(success=false, reasonCode=SERVER_OWNED_PERSONALIZATION)를 반환한다. 이 실패에는 voiceId와 interestCodes가 없다.

3. AI-Agent → DeviceAgent 상태 이벤트

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

키 타입 필수·조건 의미
requestId String 필수 현재 등록 요청
stateSequence Long 필수 상태 순번
state String 필수 Service enum 이름
setIndex Int 필수 현재 세트
setCount Int 필수 전체 세트 수
progress Int 필수 진행률 0~100
reasonCode String 필수 오류/대기 사유, 빈 문자열 허용
terminal Boolean 필수 종료 여부
nickname String 필수 표시 이름, 초기 상태는 빈 문자열 가능
voiceId String 생성 후 생성된 화자 키

DeviceAgent는 등록 App(8)과 IoTAgent(2)로 동일 상태를 복사 전달한다. success 필드를 만들어 성공/실패로 바꾸지 않는다. 상태 enum·화면 대응은 등록 App 수신 규칙을 함께 본다.

4. AI-Agent → DeviceAgent 프로필 전달

이 단계에서 AI-Agent는 이미 로컬 프로필을 생성했다. DeviceAgent는 전달 대기 저장을 수행한다. 등록 App은 임베딩을 받지 않는다.

Bundle 키 타입 송신 조건 의미
method String 필수 commitVoiceProfile
contractVersion String 필수 voice-profile-v2
requestId String 필수 원래 등록 요청 ID
voiceId String 필수 AI-Agent가 생성한 화자 키
nickname String 필수 사용자 표시 이름
locale String 전송 언어
personalizationEnabled Boolean 전송 최종 개인화 선택
referenceEmbedding FloatArray 필수 대표 임베딩. putFloatArray 사용

DeviceAgent 검증 상한: voiceId 1~128자, 임베딩 1~4096개, 각 값은 유한한 수. 4096은 실제 모델 차원이 아니라 방어 상한이다. 모델별 벡터 차원은 생성 모델을 따른다. 원본 audio/pcm/wav는 금지다.

송신 코드 예제

val input = Bundle().apply {
    putString("method", "commitVoiceProfile")
    putString("contractVersion", "voice-profile-v2")
    putString("requestId", requestId)
    putString("voiceId", voiceId)
    putString("nickname", nickname)
    putString("locale", locale)
    putBoolean("personalizationEnabled", personalizationEnabled)
    putFloatArray("referenceEmbedding", referenceEmbedding)
}
DeviceControlProxy.getService()?.sendModuleCommand(input, Bundle())

변수는 실제 등록 결과다. 임베딩을 JSON 문자열·ByteArray·임의 숫자 배열로 바꾸지 않는다. 코드에서는 각 모듈의 Contract 상수를 사용한다.

5. DeviceAgent → AI-Agent 저장 결과

DeviceAgent가 검증·암호화 대기 저장 후 다음 Bundle을 CALLER_LLM=1 callback으로 반환한다.

Bundle 키 타입 포함 값·의미
method String 항상 voiceProfileCommitResult
contractVersion String 항상 voice-profile-v2
requestId String 항상 commit의 requestId
accepted Boolean 항상 DeviceAgent 대기 저장 성공
cloudSynced Boolean 항상 제품 Cloud 동기화 여부 또는 로컬 시험 ACK 값
reasonCode String 항상 아래 결과 사유

현재 ACK에는 voiceId가 없다. AI-Agent는 동일 requestId의 pending 프로필과 연결한다. 상대 구현에서 임의로 voiceId를 필수 수신값으로 추가하지 않는다.

accepted cloudSynced reasonCode 현재 의미
false false INVALID_PROFILE_PAYLOAD 검증 실패
false false DEVICE_OUTBOX_FAILED 대기 저장 실패
true false WAITING_CLOUD_SYNC 로컬 대기 저장 성공, Cloud 미연결. Service는 DELIVERY_FAILED
true true LOCAL_TEST_ACK 시험 프로퍼티로 완료 처리. 실제 Cloud 저장 증거 아님
{
  "method": "voiceProfileCommitResult",
  "contractVersion": "voice-profile-v2",
  "requestId": "vp-example-001",
  "accepted": true,
  "cloudSynced": false,
  "reasonCode": "WAITING_CLOUD_SYNC"
}

JSON은 Bundle 값 설명용이다. 현재 제품 Cloud ACK 경로·늦은 ACK 처리 정책은 미구현이며 성공값을 강제로 돌려주면 안 된다. commit의 Int 반환은 accepted이면 0, 저장/검증 실패이면 -1이며 별도 callback도 발생할 수 있다. 호출자 거절은 callback 전에 반환될 수 있다.

6. AI-Agent → DeviceAgent 관리 결과

공통 키: method:String, contractVersion:String, requestId:String, success:Boolean, reasonCode:String. 요청 ID/버전은 대응하는 요청과 맞춘다. DeviceAgent는 결과 검증 후 IoTAgent(2)로 전달하며 등록 App으로 전달하지 않는다.

결과 method 성공 시 추가 Bundle 키 : 타입
voiceProfileListResult profiles:ArrayList<Bundle>
voiceProfileDeleteResult voiceId:String
중첩 위치 키 타입 의미
profiles[] voiceId String 등록 화자 키
profiles[] nickname String 표시 이름
profiles[] personalizationEnabled Boolean 프로필에 기록된 개인화 동의. 예전 프로필의 필드 누락은 false

객체 목록은 putParcelableArrayList, 관심사 문자열 목록은 putStringArrayList로 전송한다. JSON 문자열을 대신 넣지 않는다. 프로필 목록 검증 상한은 profiles 64개다. 실패 시 성공 필드 일부가 없을 수 있다.

삭제 성공 후 DeviceAgent는 같은 voiceId의 대기 사본 제거를 시도한다. 제거 실패가 원래 성공 응답에 반영되지 않을 수 있으며 전체 삭제 트랜잭션을 보장하지 않는다.

8. 구현 시 반드시 구분할 것

  1. 등록 제어는 DeviceAgent가 중계하지만 녹음은 AI-Agent가 소유한다.
  2. 상태 이벤트, commit ACK, 관리 결과는 서로 다른 method와 성공 판정 기준을 사용한다.
  3. DeviceAgent의 UID 패키지 검사는 공유 system UID 내부 앱을 완전히 분리하지 못한다.
  4. 중복 start, 생성 중 취소, 지연 작업, 상태 순번, 재시도·재부팅 복구는 현재 코드의 보완 대상이다.
  5. Bundle 상수와 ModuleCmdCallbackPolicy 허용 목록을 함께 적용해야 callback이 도착한다.

9. 요청별 수명과 완료 판정

이 절은 2026-09-18 작업본의 함수 흐름을 확인한 결과다. Binder 호출 반환, DeviceAgent 보관, 서버 저장 완료는 서로 다른 결과다.

작업 요청을 보내는 쪽 상관관계·보관 주체 완료로 판단하는 결과 현재 한계
등록 제어 등록 App → DeviceAgent → AI-Agent AI-Agent의 등록 세션 requestId AI-Agent가 발행한 voiceProfileStateChanged DeviceAgent의 0만으로 녹음 시작·종료를 확정하지 않음
프로필 전달 AI-Agent → DeviceAgent AI-Agent pendingVoiceProfileDelivery; DeviceAgent 전달 대기 저장소 같은 요청의 voiceProfileCommitResult에서 accepted && cloudSynced 실제 서버 저장 ACK 연계는 미완료
프로필 조회·삭제 IoTAgent → DeviceAgent → AI-Agent 최초 요청의 requestId를 결과까지 유지 해당 관리 결과의 success, reasonCode 요청 수락과 파일·DB·모델 반영 완료를 분리해야 함
관심사 조회 AI-Agent → DeviceAgent → IoTAgent → 서버 AI-Agent의 단일 대기 요청과 임시 스냅샷 유효 시간 내 같은 requestId, voiceId의 voicePersonalizationResult 요청 생성부터 5초; 만료·취소·중복 응답은 적용하지 않음

프로필 전달 결과에 따른 처리

받은 값·상황 AI-Agent의 현재 처리 화면·연동에서 해석할 의미
accepted=true, cloudSynced=true pending 제거, COMPLETED, terminal=true, 오디오 복구 등록 완료. 단, 로컬 시험 ACK 설정인지 함께 구분
accepted=true, cloudSynced=false pending 유지, DELIVERY_FAILED, 전달받은 사유 또는 WAITING_CLOUD_SYNC 기기 전달 대기 저장소에는 들어갔지만 서버 완료는 아님
accepted=false pending 유지, DELIVERY_FAILED 실패 사유를 확인하고 전달 재시도 가능 여부 판단
다른 요청의 ACK / 세션·pending 없음 ACK 무시 이전 요청 결과로 현재 화면을 완료시키지 않음
ACK가 오지 않음 현재 전용 ACK 타임아웃 없음 계속 대기할 수 있음. 5초 관심사 조회 제한과 혼동하지 않음

예를 들어 requestId=registration-demo-01의 저장 결과가 accepted=true, cloudSynced=false이면 모바일 App에 “서버 등록 완료”를 보여주면 안 된다. persist.sys.vp.local_ack=true는 로컬 시험을 위해 완료를 흉내 내는 설정이지 실제 AWS 저장 증거가 아니다.

책임과 재시도 범위

인터페이스 시험표

아래는 구현 검토에서 도출한 시험 조건이며 실기기 통과 결과가 아니다.

번호 입력·상황 확인할 결과
AD-01 정상 commit 후 accepted=true, cloudSynced=false COMPLETED 금지, pending 유지, DELIVERY_FAILED 사유 전달
AD-02 다른 requestId의 commit ACK 현재 세션·화면·pending 불변
AD-03 같은 관심사 응답을 두 번 전달 최초 유효 결과만 적용
AD-04 관심사 응답을 요청 생성 5초 이후 전달 만료 응답 폐기; 과거 로컬 관심사로 대체하지 않음
AD-05 DeviceAgent 수락 직후 대상 서비스 연결 종료 Binder 수락과 실제 callback 도착을 각각 기록; 완료로 오인하지 않음
AD-06 프로필 전달 실패 후 전달만 재시도 동일 voiceId 유지, 녹음·UUID 생성 반복 없음

구현 근거·연결 문서

2026-09-18 작업본의 DeviceAgent.java sendModuleCommand와 VoiceProfileContract.java, AI-Agent VoiceProfileIpcContract.kt를 대조했다. 문서·정적 검토이며 새 실기기 시험 결과는 아니다.

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