EZmap 기술 문서

서버 없이 Android 디바이스 안에서 완전히 동작하는 AI 에이전트 런타임. 사용자가 본인의 API 키를 입력하면 Gemini가 앱 안에서 Function Calling으로 외부 API와 디바이스를 직접 조작합니다.

✓ 모든 수치 소스코드 직접 검증 · 2026-06-22 전수 조사

개요 #

EZmap은 '내비/어시스턴트'가 아니라 Personal Agent Runtime — 폰이 모든 앱·기기 위의 사용자-소유 실행 레이어가 되어, 디지털 세계를 조작하고 검증하며 그 폰에 지속 역량을 누적합니다. 어시스턴트는 답하지만, 런타임은 툴을 만들고·검증하고·고칩니다.

62
등록 Tool
0
서버
22
Compose 화면
15
DB Entity
🔒 온디바이스 원칙. 모든 추론과 툴 호출이 디바이스 안에서 발생합니다. 사용자 데이터는 외부 서버로 전송되지 않으며, API 키는 EncryptedSharedPreferences(AES-256-GCM), DB는 SQLCipher로 암호화됩니다.

아키텍처 #

음성 입력에서 응답까지의 전체 파이프라인. 모든 단계가 단일 패키지 com.example.ez_capstone 안에서 동작합니다.

🎤 STT (Vosk · Google) 🧠 GeminiAgentEngine ⚙️ Function Calling Loop ToolExecutor → 62 Tools 📢 TTS + 동적 카드 UI
control_app · 20 액션 Self-Extension · Rhino JS AgentLink · Desktop Bridge Android Auto 인앱 MCP Server

실행 단계

agent flow
1. SystemPrompt.build(context)   // GPS·시간·프로필·경로 상태 주입
2. GeminiApiClient.generateContent()  // Gemini 2.5 Flash
3. if (functionCall) ToolExecutor.execute() → 결과 재주입
4. functionCall 없을 때까지 반복  (MAX_ITERATIONS = 14)
5. 최종 파싱 → AgentResponse { replyText, ttsText, uiAction, uiData }

빌드 & 실행 #

Kotlin 2.0.21 · AGP 8.12.3 · Min SDK 26 · Target/Compile SDK 36.

bash
# 디버그 빌드 / 설치
cd android
JAVA_HOME="…/Android Studio/jbr" ./gradlew assembleDebug
JAVA_HOME="…/Android Studio/jbr" ./gradlew installDebug

# 유닛 테스트
./gradlew test --tests "com.example.ez_capstone.agent.*"

필수 키 (local.properties, git-ignored)

용도필수
GEMINI_API_KEY에이전트 엔진 + Trial 모드필수
KAKAO_REST_KEY장소 검색 · 경로 탐색필수
KAKAO_NATIVE_APP_KEYKakao SDK init필수
KMA_API_KEY기상청 날씨선택
OPINET_API_KEY주유소 가격선택
NAVER_CLIENT_ID / SECRETNaver Directions (듀얼 경로)선택
ODSAY_API_KEY대중교통 경로선택
PORCUPINE_ACCESS_KEY웨이크워드 "이지야"선택
💡Trial 모드. Gemini 키 없이도 내장 키로 30회/일 시범 사용이 가능합니다. 온보딩은 5단계(Welcome → Gemini → Kakao → 공공데이터 팩 → 프리미엄 팩)로 구성됩니다.

Agent Loop #

agent/GeminiAgentEngine.kt · doChatLoop() — Gemini REST API를 직접 호출하는 Function Calling 루프. functionCall이 사라질 때까지 반복하며, 매 iteration마다 응답에 도구 호출이 있으면 ToolExecutor.execute() 결과를 다시 Gemini에 주입합니다.

검증: MAX_ITERATIONS = 14 (GeminiAgentEngine.kt:95). 설계 의도는 "최대 7단계 툴 체이닝 × 2홉 = 14" — open_app → 팝업닫기 → search → screen_read → tap → verify → reply.

1 · 동적 도구 라우팅

62개 도구를 매번 전부 보내면 토큰이 폭증하고 Gemini가 혼란스러워집니다. EZmap은 발화를 먼저 IntentDetector로 분류한 뒤, 해당 intent에 매핑된 도구만 추려 전달합니다.

집합구성 / 규칙
ALWAYS_INCLUDED어떤 intent에도 빠지면 안 되는 전제 도구 — geocode · get_user_profile · save_note · get_notes · search_knowledge · open_app · control_app · media_control
INTENT_TOOL_MAPNAVIGATION · PLACES · SCHEDULE · COMMUNICATION · PROFILE … intent별 관련 도구 집합 (예: NAVIGATION → get_directions·get_transit_route·add_waypoint·get_weather_kma)
DRIVING_EXTENDED주행 중(navigating)이면 intent와 무관하게 추가되는 안전 도구 — get_traffic_incidents · get_highway_alerts · get_speed_cameras · get_road_risk
MCP 동적 도구인앱 MCP 서버에 등록된 도구가 런타임에 합쳐짐

2 · 자율 컴파운드 보장

open_app이 검색·작업을 만나면 task_pending을 반환하고, 다음 iteration이 강제 tool 모드(ANY)로 전환됩니다 (GeminiAgentEngine.kt:797). 이로써 에이전트가 도구 대신 텍스트로 "열었어요"라며 멈추는 루프를 근절하고, open_app → control_app(action=search) 체이닝을 결정적으로 완주시킵니다.

도구 실행이 실패하면 다음 iteration에 reflection hint가 주입되어(GeminiAgentEngine.kt:761) Gemini가 오류를 인지하고 자기 교정합니다.

3 · 지연 시간 계측

매 실행은 4개 구간을 측정해 로그로 남깁니다 — STT 완료부터 첫 토큰·도구 완료·TTS 시작까지.

T_STT_DONET_GEMINI_FIRSTT_TOOL_DONET_TTS_STARTiterations 카운트
⚠️Max iteration 처리. 14회를 다 쓰고도 완주 못 하면 "없어요"가 아니라 능력 갭 신호로 간주하여 proposeSynthesis()로 스킬 합성을 제안합니다 → 자기확장 참조.

Tool Catalog #

Gemini Function Calling에 등록되는 내장 도구 — 정확히 62개 (agent/ToolDeclarations.kt · allTools()). 각 도구는 tool(name, description, params) 헬퍼로 정의되며, 아래 설명은 코드의 실제 description 필드에서 추출했습니다.

⚙️동적 노출. 62개를 매번 전부 보내지 않습니다. INTENT_TOOL_MAP이 발화 intent(NAVIGATION·PLACES·SCHEDULE·COMMUNICATION·PROFILE …)별로 관련 도구만 추려 Gemini에 전달하고, ALWAYS_INCLUDED(geocode·save_note·get_notes 등 전제 도구)는 항상 포함됩니다. 주행 중에는 DRIVING_EXTENDED(돌발·단속·위험)가 intent와 무관하게 추가됩니다 → 토큰 절약 + 안전. 이 중 12개는 인앱 MCP 서버로도 외부 노출됩니다.

이동 · 경로 8

도구설명
search_places카테고리·키워드로 여러 장소 탐색 ('근처 카페', '강남역 맛집', '주유소 찾아줘')
geocode도로명/장소명 → 좌표 변환 (목적지 좌표 확보의 전제 도구)
reverse_geocode좌표 → 주소 텍스트 변환
get_directions자동차 경로 탐색 (Kakao) · 실시간 교통 반영 · 경유지 최대 5
get_directions_naver네이버 길찾기 · 톨비·택시비·연료비 비교 (Kakao와 듀얼 경로)
get_transit_route대중교통(버스+지하철) 경로 · 환승·소요시간·요금 포함
get_future_eta미래 특정 출발 시각의 예상 소요시간 예측
add_waypoint주행 중 현재 경로에 경유지 삽입 ('이 스타벅스 들를 수 있어?' 핸즈프리)

주유 · 충전 · 주차 5

도구설명
get_gas_stations반경 내 최저가 주유소 · 유종별 가격 비교 (Opinet)
get_ev_chargers근처 전기차 충전소 · 실시간 충전 가능 여부 포함
get_parking근처 주차장 · 요금·운영시간 포함
get_realtime_parking서울시 주차장 실시간 잔여석 · 주차 가능 대수·요금
suggest_parking목적지 주변 최적 주차장 추천 · 실시간 잔여석·도보 거리

교통 · 도로안전 8

도구설명
get_traffic_speed주변 도로 실시간 교통 속도·정체 상태 (구간별)
get_traffic_incidents주변 돌발상황(사고·공사·기상이변) · 유형·위치·우회 정보
get_traffic_cctv주변 교통 CCTV 위치 + 영상/이미지 URL
get_highway_alerts고속도로 실시간 교통 정보 (정체·사고·공사 알림)
get_road_risk주변 도로 사고 위험도 · 위험 구간·최근 사고 건수
get_road_incidents경로 상 돌발상황(사고·낙하물·공사) · 한국도로공사 실시간
get_speed_cameras경로 주변 구간/고정식 단속 카메라 · 위치·제한속도
get_rest_areas고속도로 휴게소 · 편의시설(주유소/충전소/식당)·위치

날씨 · 환경 3

도구설명
get_weather현재 위치 또는 지정 좌표의 날씨 (무키 폴백)
get_weather_kma기상청 초단기실황 '현재' 날씨 · 기온·강수·풍속·도로상태
get_air_quality미세먼지/대기질 · PM10·PM2.5·통합대기질지수(KHAI)·등급

생활 · 지식 6

도구설명
search_knowledge웹/위키백과 검색 ('검색해줘', '○○가 뭐야?') · 무키 가용
fetch_url지정 URL 웹페이지 → 본문 텍스트 추출 (뉴스·문서·링크)
get_exchange_rate실시간 환율 · 기본 KRW 기준, 특정 통화 지정 가능
search_pharmacies근처 약국 · 이름·주소·전화번호·거리
search_hospitals근처 병원/응급실 · 이름·주소·전화번호·거리
get_driving_retrospective저장된 주행 점수 집계 → 주/월 운전 회고 ('내 운전 어때?')

프로필 · 메모 · 학습 9

도구설명
get_user_profile집/회사 주소·즐겨찾기·차량 정보 조회 ("집 가자" 해석)
update_user_profile집/회사 등록·즐겨찾기 추가·차량 정보 변경
get_user_preferences개인화 선호도 조회
update_user_preferences선호도 업데이트
save_note대화에서 발견한 선호·습관·패턴·사실을 메모로 조용히 저장
get_notes저장된 에이전트 메모 조회 ('뭘 기억해?')
delete_note에이전트 메모 삭제 ('잊어줘')
analyze_route_patterns경로 히스토리 분석 → 출퇴근·주말·야간 반복 패턴 추출
manage_favorites즐겨찾기 장소 추가/삭제/조회

일정 · 연락 · 커뮤니케이션 11

도구설명
get_schedule사용자 일정 조회
create_schedule새 일정 생성 (제목만 있으면 즉시 생성)
update_schedule기존 일정 수정 (ID로 제목/시간/장소 변경)
delete_schedule일정 삭제 (ID로 찾아 삭제)
set_alarmAndroid 시스템 알람 앱으로 알람 설정
set_timer카운트다운 타이머 ('5분 후 알려줘', '30초 타이머')
send_message연락처에 메시지 전송 · 사용자 확인 후 전송
make_call연락처에 전화 걸기 · 사용자 확인 후 실행
manage_contacts연락처 추가/수정/삭제/전체 목록 조회
lookup_contact특정 사람 존재 여부 조회만 (읽기 전용, 변경 안 함)
reply_to_notification앱 안 열고 알림에 직접 답장 (카톡/문자 · 주행 핸즈프리)

미디어 6

도구설명
play_musicSpotify 재생 · query에 곡/가수명 주면 검색 후 재생
pause_music음악 일시정지
next_track다음 곡
previous_track이전 곡
current_track현재 재생 중인 곡 정보
media_control모든 미디어 앱 제어(MediaSession) · 유튜브뮤직·멜론·팟캐스트 등

앱 조작 · 기기 · 확장 6

도구설명
open_app설치된 앱 열기(범용) · '카톡/유튜브/배민/인스타/토스 열어줘'
control_app외부 앱 화면을 보고 직접 조작(접근성) · 20개 액션 → 상세
set_volume폰 볼륨 설정 · stream(미디어/알림/벨) · level 0-100
get_device_status배터리 잔량·미디어 볼륨·화면 밝기·Wi-Fi/블루투스 상태
call_desktop같은 WiFi PC에서 Claude Code/codex/bash 원격 실행 → AgentLink
report_error_log오류 로그/Claim을 Desktop Bridge로 PC에 저장 (선택적 서버 전송)

합계 8+5+8+3+6+9+11+6+6 = 62. call_desktop은 Desktop Bridge 키가 설정된 경우에만 활성화됩니다(ALWAYS_INCLUDED 제외).

컨텍스트 주입 #

agent/SystemPrompt.kt가 매 호출마다 실시간 컨텍스트를 시스템 프롬프트에 합성합니다.

GPS 좌표현재 시각사용자 프로필 (집/회사) 활성 경로 상태주행 여부최근 대화 8턴 학습된 스킬디바이스 상태
💡멀티턴 연속성. "거기 두 번째"처럼 대명사 참조 시 이전 턴의 좌표를 재사용해 재질문·환각 없이 get_directions로 직행합니다 (8 user턴 윈도).

① 가르치기 모드 (PbD) #

Programming by Demonstration — 사용자가 직접 시연하면 에이전트가 그 동작을 증류해 재사용 가능한 스킬로 저장합니다.

PbD flow
"이거 가르쳐줄게" 발화
   접근성 ON 체크 → OFF면 즉시 안내 + abort
   DemonstrationRecorder.capture()   // 의미 단위 DemoStep[] (좌표 아님)
   SkillSynthesizer.distillFromDemo() → Gemini → JS
   SkillLearner.promoteFromScript(reason="distill")
   LearnedSkill 저장 (confidence = 0.80)
   0.80 > SAFE(0.75) → 즉시 자율 실행 가능
검증: distill confidence = 0.80 (SkillLearner.kt:143). 사용자가 검증한 시연이므로 즉시 L3 실행. 좌표가 아닌 의미 단위로 기록해 화면 변동에 강건합니다.

② 외부 앱 조작 (control_app) #

접근성(Accessibility) 기반으로 모든 앱을 자율 조작하는 super-tool. 매 액션 후 화면을 다시 read해 Gemini 컨텍스트를 갱신하며 read → tap → type → submit → verify 컴파운드를 결정적으로 수렴시킵니다.

지원 액션 — 20종 (ToolExecutor.executeControlApp() when 분기 검증)

그룹액션역할
화면 인지read · seeread는 접근성 트리 덤프, see는 트리로 안 읽히는 웹뷰/캔버스/이미지를 스크린샷→Gemini Vision으로 '눈으로' 봄
포인팅tap · tap_text · tap_vision · long_press인덱스/텍스트 라벨/비전 좌표로 탭, 길게 누르기
입력type · submit · search · select_all · copy · paste텍스트 입력·검색 제출·전체선택·복사·붙여넣기 (search는 입력+제출 결정적 매크로)
스크롤scroll · swipe제스처 기반 스크롤/스와이프 (노드 ACTION_SCROLL 아님 — 실앱서 동작 보장)
전역 내비back · home · notifications · recents뒤로·홈·알림 패널·최근 앱
오버레이show_overlay · hide_overlay화면 위 안내 오버레이 표시/숨김
🚫금융 비가역 하드 게이트. ControlActionGuard송금·이체·출금·계좌이체·transfer·wire·authorize payment를 정규식으로 매칭해 tap·long_press·submit을 자동 차단하고 수동 핸드오프합니다. 음식/쇼핑 '결제하기'는 정책상 제외, submit은 화면 상태인지 게이트가 추가됩니다.
정직 처리 + 환각 차단. 접근성 OFF일 때 error_kind = MISSING_PERMISSION을 반환합니다. 직전 read한 앱과 현재 포그라운드가 다르면 warning을 붙여 "EZmap UI를 대상 앱으로 착각"하는 환각을 차단합니다.

핵심 파일: accessibility/EZmapAccessibilityService.kt · agent/ToolExecutor.executeControlApp() · agent/ControlActionGuard.kt

④ 자기확장 & 자동 학습 #

능력 갭을 감지하면 JS 스킬을 생성·실행·자기수리하고, 반복 패턴을 자동으로 스킬로 승격합니다. "스킬 추가보다 스킬 복구가 10배 중요"가 설계 철학입니다.

self-extension
매 대화 후 — skillLearner.observeUtterance()
   observeCount++ → 5회마다 GeneralUtteranceLearner
   동일 패턴 3회+ → LearnedSkill 자동 생성

능력 갭 감지 시:
   SkillSynthesizer.synthesizeRunPersist()
   JS 생성 → Rhino 실행 → 실패 시 자기수리 (MAX_REPAIRS = 2)
   최대 3회 시도 (1 생성 + 2 수리)
   promoteFromScript(confidence = 0.60) → 스킬 누적

신뢰도 상태 머신 (검증)

전이조건결과
0.60 → 0.805회 성공안정
0.80 (PbD)시연 직후즉시 자율 실행
부정 피드백 ≥ 3실패 누적DEGRADED
부정 피드백 ≥ 5실패 누적AUTO_DISABLED
재합성 차단3회 수리 초과진동 방지

Rhino 샌드박스 보안

ClassShutter 전면차단FORBIDDEN_GLOBALSsealed scopeWALL 8sCALL 15s명령수 10,000 가드
검증: ScriptSkillEngine.ktsetClassShutter { false }로 Java interop 전면 차단, 모든 툴 호출은 toolBridge를 거쳐 ControlActionGuard·GroundingGuard를 상속받습니다.

데이터 계층 #

db/EZMapDatabase.kt — Room @Database version 16 · 15 Entity · SQLCipher AES-256 전체 암호화(di/DatabaseModule.kt SupportFactory).

Entity용도Entity용도
ProfileEntity집/회사/차량/TTSDecisionTraceEntity에이전트 결정 기록
PreferenceEntity경로 선호 통계CustomRoutineEntity사용자 루틴
RouteHistoryEntity경로 히스토리DrivingScoreEntity주행 점수
ConversationEntity대화 세션EnergyHistoryEntity연료/전기 소비
FrequentPlaceEntity자주 가는 곳FeedbackEntity사용자 피드백
ContactEntity연락처 캐시AgentNoteEntity자기학습 메모
ScheduleEntity일정LearnedSkillEntity학습 스킬 (TOOLCHAIN/SCRIPT)
SkillVersionEntity스킬 버전 스냅샷 (자가치유 롤백)

안전 · 거버넌스 #

"안전 정책 > LLM 창의성" — 모든 actuate는 게이트를 통과해야 실행됩니다. 각 게이트는 소스코드로 검증되었습니다.

게이트역할
ControlActionGuard금융 비가역(송금·이체·출금·transfer·wire) 정규식 차단 + 수동 핸드오프. submit은 화면 상태인지 게이트 추가
GroundingGuard쓰기 후 재조회로 거짓주장·silent-fail 동시 차단. 실툴이 실제 한 것만 주장(환각 방지)
QuietPolicy · QuietHoursGate능동 표출 단일 게이트 — 심야·DND·무음·회의·주행 중엔 침묵
PermissionManager런타임 권한 게이팅 · ScriptPermissionMode로 샌드박스 권한 분리
LaneScheduler우선순위 레인 LIVE ≻ USER ≻ BACKGROUND ≻ IDLE — LIVE 작업이 백그라운드 선점
Rhino SandboxClassShutter Java interop 전면차단 · FORBIDDEN_GLOBALS · WALL 8s / CALL 15s

API Key 관리 #

config/ApiKeyProvider.kt — EncryptedSharedPreferences(AES-256-GCM)에 저장. 키는 사용자 디바이스를 벗어나지 않습니다.

필수geminiKey · kakaoRestKey
공공데이터 팩dataGoKrKey (날씨·미세먼지·충전소·주차장·약국·병원) · opinetKey (주유소)
프리미엄 팩naverClientId/Secret (듀얼 경로) · odsayKey (대중교통)

시스템 구성 #

단일 패키지 com.example.ez_capstone · 모든 파일 개수는 파일시스템 직접 카운트.

디렉토리파일핵심 구성
agent/32GeminiAgentEngine · ToolExecutor · ToolDeclarations · SystemPrompt · ControlActionGuard · GroundingGuard · SynthesisTrigger
api/ + kakaomap/22Kakao · Naver · ODsay · 기상청 · AirKorea · Opinet · EvCharger · SpeedCamera · Spotify · WebFetch …
db/1515 Entity + 15 DAO · EZMapDatabase v16 · SQLCipher
ui/screens/22Conversation · Navigation · Home · Onboarding · SkillStore · DeviceMesh · McpServers (23 NavRoute)
navi/16RestGuide · ShadowReroute · EvRouteOptimizer · DrivingScorer · RoadHazard · SpeedEnforcement
voice/10VoiceStateCoordinator · NaturalVoiceEngine · Vosk/Porcupine/Google 웨이크 · SttRoutingPolicy
skill/13+ScriptSkillEngine · SkillSynthesizer · SkillLearner · SkillMatcher · GeneralUtteranceLearner
mcp/7EZmapMcpServer · McpHttpServer · McpToolRegistry · AgentLinkExporter · CloudflareTunnelManager
governance/6PermissionManager · QuietPolicy · QuietHoursGate · ScriptPermissionMode
context/5AmbientContextEngine · GeofenceEngine · DriveDetectionEngine · Activity/Geofence Receiver
scheduler/2AgentLane · LaneScheduler
진입점+widget/ · auto/(Android Auto) · service/(QuickTile·NotificationListener) · shortcuts.xml ×3

MCP 서버 #

인앱 MCP(Model Context Protocol) 서버가 Gemini 에이전트에 도구를 등록하고, Cloudflare Quick Tunnel로 원격 노출합니다.

EZmapMcpServer McpToolRegistry (12 tool) CloudflareTunnelManager 원격 URL (Settings 표시)

EZmapMcpHttpServer가 네트워크 너머로 MCP를 서빙하고, AgentLinkExportercall_desktop 도구를 노출합니다. SELinux 차단 시 USB fallback을 안내합니다.

음성 파이프라인 #

웨이크워드 "이지야" → STT → 에이전트 → TTS. 온디바이스(Vosk)와 클라우드(Google) STT를 SttRoutingPolicy로 라우팅합니다.

Vosk 웨이크워드 VoiceStateCoordinator (STT) GeminiAgentEngine NaturalVoiceEngine (TTS)

Voice State: IDLE → LISTENING → PROCESSING → SPEAKING → IDLE. NaturalVoiceEngine은 "300m 앞 맥도날드 보이면 우회전" 같은 자연어 안내로 변환합니다.

기술 스택 #

Kotlin 2.0.21Jetpack Compose · Material 3Gemini 2.5 Flash Kakao Map SDK 2.12.18Vosk STTHilt 2.51+ Room 2.6+SQLCipherWorkManager Android AutoRhino JS RuntimeCloudflare Tunnel MCP ServerMin SDK 26Target SDK 36
62
등록 Tool
14
Function Loop
20
control_app 액션
15
DB Entity
16
DB Version
22
Compose 화면
12
MCP 노출 Tool
0
서버

팀 & 연락처 #

팀장 · Voice Architecture
황동헌
ng524855@gmail.com
Full-Stack · AI Architecture
최재원
cjw070690@gmail.com
UI/UX · Design System
박규리
rbflefg5805@gmail.com

지도교수 진동섭 · IT융합학부 · 2026학년도 1학기 캡스톤디자인

황동헌 · 최재원 · 박규리 · 지도교수 진동섭 · IT융합학부 · 2026학년도 1학기