본문으로 건너뛰기
5분 AI.
GITHUB TRENDING KOTLIN MONTHLY #5 딥다이브 · 2026-10-23 분석

gallery 딥다이브
— 스마트폰 안에 AI 실험실을 짓는 법

모바일 앱

빠른 판단

무엇
모델 다운로드·로컬 채팅·사진·영상 의미 검색·도구 호출을 체험하는 Google AI Edge 모바일 실험 앱
이럴 때
휴대전화에서 온디바이스 생성형 AI를 시험할 때
Google AI Edge Gallery는 모델을 내려받아 스마트폰에서 채팅·이미지 질의·음성 처리·의미 검색을 체험하는 앱입니다. 이 글은 모델 자체보다 모델을 제품으로 연결하는 Kotlin 코드를 읽습니다. 화면 상태, 백그라운드 다운로드, 로컬 추론, 에이전트 도구 권한까지 한 흐름으로 따라갑니다. Kotlin 326개 파일·약 85,800줄, Apache-2.0. GitHub 스타 25,076개·포크 2,766개는 2026-10-23 기준이며, 소스 분석 기준은 커밋 8f5f2a0입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

AI 모델을 모바일 앱의 기능으로 바꾸는 실험실

gallery는 Google AI Edge의 온디바이스 생성형 AI 기능을 체험하는 실험적 앱입니다. 모델을 선택하고 준비한 다음, 휴대전화에서 답변을 생성하거나 사진·영상의 의미를 검색합니다. 이 저장소에서 배울 핵심은 AI를 호출하는 한 줄이 아니라, 모델의 준비부터 사용과 정리까지 이어지는 앱의 전체 흐름입니다.

일반적인 클라우드 챗봇에서는 서버가 모델을 실행합니다. 여기서는 LlmChatModelHelper.initialize()가 기기의 모델 파일 경로로 EngineConfig를 만들고, LiteRT-LM의 Engine과 Conversation을 생성합니다. 모델 계산을 담당하는 네이티브 엔진은 외부 의존성이며, 저장소의 Kotlin 코드는 그 엔진에 화면·데이터·도구를 연결합니다.

용어
온디바이스 추론
학습이 끝난 모델을 사용자 기기에서 실행해 결과를 만드는 것. 추론은 이미 익힌 지식을 사용하는 단계이고, 학습은 모델의 가중치를 바꾸는 단계입니다. 이 앱의 핵심은 학습용 GPU 서버 운영이 아니라 모바일 추론 통합입니다.
한 문장 비유

gallery는 주방 기계를 만드는 공장보다, 여러 기계를 연결한 요리 실습실에 가깝습니다

모델은 요리사가 익힌 지식, LiteRT-LM은 조리 기계, Kotlin 앱은 주문표·재료 창고·진행 상황판입니다. 조리 기계가 좋아도 재료가 없거나 주문이 겹치면 음식이 제대로 나오지 않습니다.

그래서 모델 다운로드 실패, 대화 중단, 사진 입력 변환, 메모리 해제까지 읽어야 합니다. 그 연결부가 이 저장소의 공부거리입니다.

1-1. 한 앱 안에 세 종류의 AI 작업이 있다

작업입력과 출력대표 구현
생성질문·이미지·오디오를 받아 답변이나 함수 호출을 생성Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/, Android/src/app/src/main/java/com/google/ai/edge/gallery/agent/, Android/src/app/src/main/java/com/google/ai/edge/gallery/runtime/
검색텍스트·미디어를 벡터로 바꾸고 비슷한 사진·영상 구간을 찾음Android/src/app/src/main/java/com/google/ai/edge/gallery/services/semanticretrieval/, Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/videomomentfinder/
행동모델의 함수 호출을 Android 동작이나 MCP 요청으로 연결Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/, Android/src/app/src/main/java/com/google/ai/edge/gallery/tools/

Kotlin 구현은 Android/src/app/src/main/java/com/google/ai/edge/gallery/ 아래에 모여 있습니다. 파일 경로는 저장소 루트 기준입니다.

1-2. 처음 읽을 때 잡아야 할 세 가지 질문

  1. 어디서 계산하는가? 파일 기반 LiteRT-LM과 시스템 관리형 AICore 경로를 구분합니다.
  2. 무엇이 상태인가? 다운로드 완료, 모델 초기화 완료, 답변 생성 중은 서로 다른 상태입니다.
  3. 무엇이 외부로 나가는가? 로컬 모델 계산과 모델 다운로드·원격 도구 호출을 나누어 봅니다.
읽기 목표
화면에서 엔진까지 선을 하나 그을 수 있으면 첫 단계 성공

버튼을 눌렀을 때 어느 ViewModel이 요청을 받고, 어떤 실행기와 런타임을 거쳐, 어떤 이벤트로 화면이 바뀌는지 설명해 보세요. 모든 파일을 외우는 것보다 이 연결을 설명하는 편이 더 유용합니다.

2왜 주목받는가

실행 가능한 예제가 온디바이스 AI의 진입 장벽을 낮춘다

2-1. 트렌딩 순위와 인기의 원인은 구별해서 읽자

2026-10-23 GitHub Trending Kotlin 월간 5위, 스타 25,076개라는 지표는 관심의 크기를 보여 줍니다. 다만 이 수치만으로 사람들이 어떤 기능 때문에 방문했는지는 알 수 없습니다. 아래 이유들은 README의 기능 구성과 실제 코드를 근거로 한 관심 요인에 대한 해석입니다.

README는 Gallery를 온디바이스 생성형 AI 체험 앱으로 소개합니다. LiteRT-LM 공식 개요와 앱의 Kotlin 구현을 함께 읽으면 런타임을 앱에 연결하는 방식을 배울 수 있습니다.

2026-10-23 GitHub 기준 최신 릴리스는 1.0.20 (2026-10-06)입니다. README는 EmbeddingGemma 2 지원과 Instant Media Search·Video Moment Finder를 새 기능으로 소개합니다. 채팅 밖의 온디바이스 활용을 보여 주는 구체적인 업데이트이며, 트렌딩 관심의 배경을 이해할 단서입니다.

2-2. 다른 선택지와 무엇을 비교해야 할까

gallery의 장점은 모든 모델에서 가장 빠르다는 증명보다, 모바일 AI 기능을 하나의 앱으로 묶은 통합 예제를 제공한다는 데 있습니다. 아래 표는 제품의 역할을 비교한 것이며, 동일 기기에서 측정한 속도 순위가 아닙니다.

비교 축gallery클라우드 챗봇형 앱llama.cpp 중심 개발
계산 위치핵심 모델 추론은 기기 안일반적으로 서비스 서버로컬 실행 중심, 구성에 따라 서버 제공
출발점완성된 모바일 기능과 화면계정·API·서비스 기능추론 엔진과 실행 도구
학습 강점Compose·모델 수명·미디어·도구 연결요청·응답과 서비스 연동C/C++ 엔진·모델 포맷·실행 최적화
앱 기능 확장Android Intent, 스킬, MCP 연결 예제서비스마다 지원 범위가 다름필요한 앱 UI와 권한 처리를 별도로 설계
감수할 제약기기 메모리·지원 모델·드라이버 차이통신·서버 정책·서비스 비용앱으로 완성하는 통합 작업

llama.cpp 공식 저장소는 C/C++ LLM 추론 프로젝트입니다. Gallery와 어느 쪽이 무조건 우월한지 묻기보다, 모바일 제품 연결부를 배우려는지, 엔진 자체를 배우려는지 먼저 정하는 편이 좋습니다.

2-3. README의 표현을 코드로 한 번 더 좁혀 보기

문서 표현소스에서 확인할 점학습할 판단
완전 오프라인추론 경로 외에 GitHub 모델 목록, Hugging Face 다운로드, 원격 MCP 요청이 있음네트워크 사용 여부는 기능별로 판단
벤치마크계측 클래스가 있지만 현재 MetricsTracker.create()에서 비활성화구현 존재와 활성 상태를 구분
여러 플랫폼앱 배포 안내는 있으나 이 커밋에는 Android 앱 소스만 있음지원 제품 범위와 공개 소스 범위를 구분
도구용 문맥 제약mcp/README.md는 4k~10k를 언급하지만 1.0.20 목록의 Gemma 4 설명은 최대 32K모델별 설정·실제 엔진 예산을 따로 확인
비유

식당 간판의 “포장 가능”이 모든 메뉴가 같은 포장 용기에 담긴다는 뜻은 아닙니다. “오프라인 AI”도 모델 계산, 파일 준비, 외부 도구를 각각 나눠 읽어야 정확합니다.

3기술 스택 전체 지도

의존성의 이름보다 각 부품이 맡는 책임을 보자

버전의 기본 출처는 Android/src/gradle/libs.versions.toml, 실제 적용 여부는 Android/src/app/build.gradle.kts입니다. 아래 값은 모두 커밋 8f5f2a0 기준입니다. 버전 카탈로그는 여러 의존성의 이름과 버전을 한곳에 모아 두는 장부입니다.

3-1. 프론트엔드: 브라우저가 아닌 Android 네이티브 화면

기술선언 버전이 앱에서 하는 일
Kotlin2.2.21화면·상태·실행기·도구 연결의 주 언어
ComposeBOM 2026.02.00상태를 받아 화면을 구성, Material 3 사용
Activity1.10.1Compose 화면과 Android Activity 연결
Lifecycle2.8.7ViewModel·화면 생명주기 관련 의존성
Navigation2.8.9GalleryNavHost를 통한 화면 이동
CameraX1.4.2카메라 입력과 수명 관리
Media31.11.1영상 재생·변환·내보내기 연결
Coil / WebKit2.7.0 / 1.14.0이미지 표시 / 스킬의 WebView 연동
용어
BOM과 선언형 UI
BOM은 서로 맞는 라이브러리 버전 묶음을 고르는 기준표입니다. 따라서 2026.02.00은 모든 Compose 라이브러리의 개별 버전이 아닙니다. 선언형 UI는 “이 버튼의 글자를 바꿔라”보다 “현재 상태라면 이런 화면이어야 한다”고 표현하는 방식입니다.

3-2. 백엔드 역할: 서버 대신 기기 안의 실행·저장 계층

이 저장소에는 별도 웹 API 서버나 모델 학습 서버 프로젝트가 없습니다. 서버 앱에서 백엔드가 맡을 계산·데이터 책임을 Android 프로세스 내부의 런타임과 저장소가 담당합니다. 원격 MCP 서버는 필요할 때 연결하는 외부 구성요소입니다.

기술선언 버전책임
LiteRT-LM0.18.0litertlm-android의 엔진·대화·함수 호출
ML Kit Prompt1.0.0-beta2AICore 기반 생성 경로의 SDK
MediaPipeVision 1.0.0, Retrieval 1.1.0임베딩·의미 검색과 벡터 저장 연동
WorkManager2.12.0모델 파일의 백그라운드 다운로드
DataStore1.1.7설정·사용자 데이터·스킬 등 로컬 저장
Protobuf런타임·protoc 4.26.1.proto 기반 데이터 직렬화
Hilt2.60.1실행기·저장소·다운로더 의존성 조립
MCP / Ktor0.8.0 / 3.4.3외부 도구 서버 연결과 HTTP 클라이언트
AppAuth0.11.1Hugging Face OAuth 인증 흐름
JSON 처리Gson 2.14.0, Moshi 1.15.2모델 목록·도구 인자 등 구조화된 데이터 처리
FirebaseBOM 33.16.0Analytics·Messaging 의존성, 구성 여부에 주의

kotlinx-serialization-json은 1.7.3, serialization 플러그인은 2.0.21로 선언되어 있습니다. Kotlin reflect와 일부 metadata 의존성은 2.4.0이며 -Xskip-metadata-version-check도 있습니다. 이 조합을 “전부 Kotlin 2.2.21”로 요약하면 중요한 차이가 사라집니다.

3-3. 인프라·빌드: 실제 CI가 말해 주는 기준

항목소스의 설정초보자가 구별할 점
Android 빌드AGP 9.0.1, Gradle 9.2.1AGP는 Android용 플러그인, Gradle은 빌드 실행 도구
SDKminSdk=31, compile·target 37설치 최소 OS와 컴파일·대상 API는 다른 값
JavaCI Temurin 21, 코드·JVM 타깃 11빌드 도구를 돌리는 JDK와 생성 코드 타깃은 다름
앱 식별versionName=1.0.20, versionCode=46표시용 버전과 업데이트 판정용 정수
코드 생성KSP 2.3.6, Protobuf 플러그인 0.10.0Hilt·Moshi·데이터 클래스 관련 생성 단계
빌드 메모리org.gradle.jvmargs=-Xmx2048mGradle JVM 힙 상한이며 개발 PC 전체 권장 RAM은 아님

.github/workflows/build_android.yaml은 Android 변경 시 Ubuntu에서 SDK Platform 37을 준비하고 ./gradlew assembleRelease를 호출합니다. 별도 테스트 실행 단계나 APK 업로드 단계는 없습니다. JUnit·Espresso 의존성이 선언되어 있다는 사실만으로 CI가 테스트를 돌린다고 볼 수 없습니다.

.github/workflows/static.yml은 skills/를 staging/skills/에 담아 GitHub Pages로 배포합니다. 이것은 모델 추론 서버 배포가 아니라 스킬 정적 파일 배포입니다. package.json·Cargo.toml·go.mod·pyproject.toml·Dockerfile 중심의 프로젝트 구조는 확인되지 않습니다.

학습 습관
버전 장부와 실제 사용처를 함께 읽기

카탈로그에 적힌 이름을 찾고, 앱 빌드 파일의 implementation(...)과 플러그인 적용을 확인한 뒤, 소스의 import를 찾으세요. “선언되어 있다”에서 “어디에 쓰이는가”까지 가야 스택 지도가 완성됩니다.

4아키텍처 심화 분석

한 번의 질문이 답변 조각으로 돌아오는 경로

다음은 Android 핵심 경로를 단순화한 구조도입니다. 스킬과 MCP는 Agent Chat에서 확장되는 경로이며, 모든 일반 채팅이 항상 외부 도구를 거치는 것은 아닙니다.

[사용자: 질문 / 사진 / 오디오] | v [Compose 화면: LlmChatScreen / ChatViewWrapper] | 사용자 이벤트 v [LlmChatViewModelBase / ChatViewModel] | AgentRequest v [DefaultAgentRuntimeExecutor.executeStream] | v [DefaultLlmSessionManager] | v [Model.runtimeHelper: LlmModelHelper] | | v v [LlmChatModelHelper] [AICoreModelHelper] [LiteRT-LM Engine] [시스템 관리형 모델] | | v v [Conversation.sendMessageAsync] [generateContentStream] | | +----------------+------------------+ | ResultListener 콜백 v [AgentEvent 흐름] --> [ViewModel 상태] --> [Compose 화면] 도구 준비: ToolsProvider --> LlmModelHelper.initialize(tools) 도구 문맥: ToolDispatcher --> ToolDefinition.onAttach 도구 실행 (LiteRT-LM에 등록한 도구): RunIntentTool --> IntentHandler --> Android Intent 등 RunJsTool --> Action --> 화면의 WebView RunMcpTool --> 권한 확인 --> MCP 서버 (외부 통신 가능) 파일 모델 준비: 모델 목록 --> ModelDownloader --> WorkManager --> 모델 파일 --> Engine 저장 경로: 설정 / 대화 기록 --> Repository --> Proto DataStore 검색 경로: 미디어 --> 임베딩 --> 로컬 VectorStore --> 검색 결과

화면, 실행기, 런타임, 저장소를 구분하면 모델이나 기능이 늘어날 때 무엇을 바꿔야 하는지 설명할 수 있습니다. 각 계층을 완벽하게 독립시킨 교과서라고 보기보다, 실제 연결 방식과 예외를 배우는 사례로 읽어야 합니다.

4-1. 패턴 ① 상태와 이벤트를 나누는 MVVM·단방향 흐름

Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatScreen.kt의 LlmChatScreen()은 ChatViewWrapper에 ViewModel·콜백·입력 옵션을 전달합니다. Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/chat/ChatViewModel.kt는 내부의 MutableStateFlow를 외부에 StateFlow로 노출하고, addMessage()에서 새 상태를 만듭니다.

// ChatViewModel.kt의 핵심 선언
private val _uiState = MutableStateFlow(createUiState())
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()

// addMessage()는 메시지 목록과 맵을 복사한 뒤 상태를 갱신한다.
// 핵심 동작을 읽기 쉽게 요약한 표현
_uiState.update { state ->
  state.copy(messagesByModel = newMessagesByModel)
}

읽는 쪽에는 상태 관찰 통로를, 바꾸는 쪽에는 갱신 함수를 둡니다. 다만 ChatUiState 내부에는 MutableList도 있으므로, 외부에서 바꿀 수 없는 Flow라고 해서 모든 중첩 데이터가 자동으로 불변인 것은 아닙니다. 복사해 갱신하는 실제 함수를 함께 읽으세요.

이 구조는 Compose 공식 아키텍처 문서의 “상태는 아래로, 이벤트는 위로” 흐름과 연결됩니다. 이 패턴의 이점은 화면에 다운로드·추론 세부 구현을 모두 넣지 않아도 된다는 것입니다.

4-2. 패턴 ② 런타임 전략과 Hilt 의존성 주입

Android/src/app/src/main/java/com/google/ai/edge/gallery/runtime/LlmModelHelper.kt는 initialize(), resetConversation(), runInference(), stopResponse(), cleanUp()라는 공통 계약을 정의합니다. Android/src/app/src/main/java/com/google/ai/edge/gallery/runtime/ModelHelperExt.kt의 확장 프로퍼티가 모델에 맞는 구현을 고릅니다.

// ModelHelperExt.kt의 선택 순서를 요약
val Model.runtimeHelper: LlmModelHelper
  get() {
    testingModelHelper?.let { return it }
    if (this.isAiCore) return AICoreModelHelper
    return LlmChatModelHelper
  }

이는 같은 일을 서로 다른 방법으로 수행하는 전략 패턴으로 읽을 수 있습니다. 다운로드에도 Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ModelDownloader.kt라는 계약이 있고, Android/src/app/src/main/java/com/google/ai/edge/gallery/di/AppModule.kt는 @IntoMap·@RuntimeTypeKey로 LiteRT-LM과 AICore 다운로더를 등록합니다.

용어
인터페이스와 의존성 주입
인터페이스는 부품이 지켜야 할 약속이고, 의존성 주입은 부품을 사용할 곳에 외부에서 연결해 주는 방식입니다. 실행기는 “어떻게 모델을 관리할지”라는 약속에 기대고, 조립 코드는 구체적인 구현을 선택합니다.

테스트용 testingModelHelper가 먼저 선택된다는 점도 읽을 만합니다. 비싼 모델을 매번 로드하지 않고 가짜 구현으로 흐름을 확인할 수 있는 접점이지만, 전역 가변 상태이므로 동시에 사용하는 테스트 사이의 간섭도 고려해야 합니다.

4-3. 패턴 ③ 콜백을 Flow 이벤트로 바꾸는 어댑터

Android/src/app/src/main/java/com/google/ai/edge/gallery/agent/DefaultAgentRuntimeExecutor.kt의 executeStream()은 callbackFlow를 반환합니다. 런타임이 보내는 응답 조각을 StreamToken으로, 종료를 LoopTerminated로 바꿉니다. 정의는 Android/src/app/src/main/java/com/google/ai/edge/gallery/agent/AgentEvent.kt의 sealed interface에 모여 있습니다.

// executeStream()의 결과 콜백을 단순화한 흐름
resultListener = { partialResult, done, partialThinking ->
  emitEvent(AgentEvent.StreamToken(
    token = partialResult, thinking = partialThinking, done = done
  ))
  if (done) {
    emitEvent(AgentEvent.LoopTerminated(finalResponse.toString()))
    close()
  }
}

실제 코드는 제어용 접두사를 거르고 응답을 누적하며, 오류·중단·문맥 압축 이벤트도 보냅니다. 이 덕분에 ViewModel은 엔진의 콜백 규칙 대신 앱이 이해하는 사건 목록을 처리합니다. 응답 조각 하나가 반드시 토큰 하나나 글자 하나인 것도 아닙니다.

취소 경로는 따로 확인해야 합니다. executeStream()의 awaitClose 블록 자체는 비어 있습니다. 명시적인 interrupt()는 세션 관리자의 stopResponse()를 거쳐 런타임 중단으로 이어집니다. Flow 수집 취소만 보고 네이티브 추론도 무조건 멈춘다고 단정하면 안 됩니다.

4-4. 패턴 ④ 저장소와 백그라운드 작업의 역할 분리

Android/src/app/src/main/java/com/google/ai/edge/gallery/data/DownloadRepository.kt의 DefaultDownloadRepository.downloadModel()은 URL·파일명·버전·부가 파일 정보를 WorkManager 입력으로 만듭니다. 파일 바이트를 읽는 일은 Android/src/app/src/main/java/com/google/ai/edge/gallery/worker/DownloadWorker.kt의 doWork()가 맡습니다.

// DownloadRepository.kt의 실제 작업 등록
workManager.enqueueUniqueWork(
  model.name,
  ExistingWorkPolicy.REPLACE,
  downloadWorkRequest,
)

같은 모델 이름으로 다시 요청하면 기존 작업을 교체하는 정책입니다. Worker는 임시 파일을 사용하고, 재개 시 HTTP Range를 붙이며, 약 200ms 간격으로 진행률을 알립니다. 완료 뒤 임시 확장자를 없애고 필요하면 압축을 풉니다.

WorkManager 공식 문서는 앱이 화면 밖으로 나간 뒤에도 유지할 작업을 이 라이브러리의 사용 대상으로 설명합니다. 다만 라이브러리를 쓴다고 모든 네트워크 예외가 자동 해결되지는 않습니다. 이 Worker는 IOException에서 Result.failure()를 반환하며 자동 재시도 반환과는 다릅니다.

4-5. 엔진 수명과 대화 수명은 왜 나누는가

Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatModelHelper.kt의 LlmModelInstance는 Engine과 Conversation을 따로 보관합니다. resetConversation()은 기존 대화를 닫고 같은 엔진에서 새 대화를 만들고, cleanUp()은 모델 자원 정리를 담당합니다.

비유

엔진은 무거운 조리 기계, 대화는 그 기계로 처리하는 주문 한 묶음입니다. 주문 내역을 비운다고 기계를 매번 새로 설치할 필요는 없습니다. 반대로 퇴근할 때는 주문표만 버릴 것이 아니라 기계 전원도 꺼야 합니다.

이 분리는 앱의 반응 속도와 메모리에 영향을 줍니다. 새 대화가 필요할 때 엔진을 재사용하는 경로와, 모델 자체를 바꿔 자원을 회수하는 경로를 각각 따라가면 “초기화”와 “자원 해제”를 구체적으로 이해할 수 있습니다.

4-6. 모델의 최대 문맥과 현재 설정값은 다르다

model_allowlists/1_0_20.json의 Gemma 4는 maxContextLength=32000, maxTokens=4000을 선언합니다. Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ModelAllowlist.kt가 이를 설정 생성 함수로 넘기고, Android/src/app/src/main/java/com/google/ai/edge/gallery/data/Config.kt의 createLlmChatConfigs()는 최대 문맥 값이 있으면 2,000부터 그 값까지 조절하는 슬라이더를 만듭니다. 이 목록의 Gemma 4 두 항목은 기본값이 4,000입니다.

// LlmChatModelHelper.initialize()에서 엔진 예산으로 이어지는 값
val maxTokens = model.getIntConfigValue(
  key = ConfigKeys.MAX_TOKENS,
  defaultValue = DEFAULT_MAX_TOKEN,
)
// 아래는 EngineConfig의 관련 인자만 발췌한 표현
EngineConfig(
  modelPath = modelPath,
  backend = preferredBackend,
  maxNumTokens = maxTokens,
)

따라서 모델 설명의 “최대 32K”를 모든 실행이 처음부터 32K를 사용한다는 뜻으로 읽으면 안 됩니다. 이름이 maxTokens라고 해서 이 경로에서 순수 출력 길이만 나타낸다고 해석하는 것도 부정확합니다. 실제로 어떤 엔진 인자에 연결되는지 확인해야 합니다.

Android/src/app/src/main/java/com/google/ai/edge/gallery/agent/sessions/SummarizationContextCompactor.kt는 같은 설정값의 75%를 넘는 문맥에서 압축을 검토합니다. 요약을 생성한 뒤 대화를 다시 만들며, 자동 실행 여부는 기능 플래그·작업별 설정 등에도 달려 있습니다. 문맥이 길어졌다고 언제나 자동 요약되는 것은 아닙니다.

5디렉토리 구조 해부

85,000줄을 처음부터 읽지 않기 위한 지도

저장소 루트에는 Android 앱, 버전별 모델 목록, 스킬 예제, 사용 문서가 있습니다. iOS용 모델 목록 파일은 있지만 Swift 앱 프로젝트는 확인되지 않습니다. README의 앱 배포 안내를 그대로 소스 디렉토리 목록으로 옮기지 않는 것이 중요합니다.

gallery/ |-- Android/ | `-- src/ | |-- app/ | | |-- build.gradle.kts | | `-- src/main/ | | |-- AndroidManifest.xml | | |-- proto/ # 저장 데이터의 스키마 | | |-- res/ # 문자열·색·아이콘 | | `-- java/com/google/ai/edge/gallery/ | | |-- GalleryApp.kt # 최상위 Compose 진입 | | |-- ui/ # 화면과 공통 UI | | |-- agent/ # 요청·이벤트·세션 실행 | | |-- runtime/ # LLM 공통 계약·AICore | | |-- data/ # 모델·설정·저장소 | | |-- worker/ # 백그라운드 파일 다운로드 | | |-- services/ # 임베딩·미디어 서비스 | | |-- customtasks/ # 기능별 확장 예제 | | |-- skills/ # 스킬 관리·조회 | | |-- tools/ # 도구 정의·실행 연결 | | |-- mcp/ # 서버 상태 제공 | | |-- common/ # 하드웨어·지표·공통 함수 | | `-- di/ # 의존성 조립 | `-- gradle/ # 버전 장부·Wrapper |-- model_allowlist.json # 과거 형태의 루트 목록 |-- model_allowlists/ # 앱 버전별 모델 목록 |-- skills/ | |-- built-in/ # 기본 스킬 예제 | `-- featured/ # 커뮤니티 소개용 예제 |-- mcp/README.md # 연결법·제약 설명 |-- .github/workflows/ # APK 빌드·정적 파일 배포 |-- DEVELOPMENT.md # 개발 설정 안내 `-- Function_Calling_Guide.md # Android 행동 확장 안내

5-1. 폴더는 기술 이름보다 책임으로 읽자

폴더찾아볼 질문첫 파일
ui입력과 화면 상태는 어떻게 이어지는가?Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/llmchat/LlmChatViewModel.kt
agent한 번의 실행과 여러 대화 세션을 누가 관리하는가?DefaultAgentRuntimeExecutor.kt
runtime모델 종류가 달라도 무엇을 공통으로 호출하는가?LlmModelHelper.kt
data설정·모델 정보·다운로드 정책을 어디에 모으는가?BackendSpec.kt, DownloadRepository.kt
services미디어를 검색 가능한 형태로 어떻게 바꾸는가?Android/src/app/src/main/java/com/google/ai/edge/gallery/services/semanticretrieval/SemanticRetrievalService.kt
customtasks기존 기반 위에 새 기능을 어떻게 추가하는가?Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/examplecustomtask/ExampleCustomTask.kt
tools모델이 제안한 행동은 어디서 실행되는가?RunMcpTool.kt, LoadSkillTool.kt

5-2. 코드 규모를 숫자로 보기

아래는 .git을 제외한 파일의 물리적 줄 수입니다. 빈 줄·주석·라이선스 문구도 포함하며, 실행문 개수나 개발 난이도를 뜻하지 않습니다. HTML 안에 포함된 JavaScript는 HTML 줄 수에 들어갑니다.

종류파일 수물리적 줄 수
Kotlin .kt32685,786
Gradle Kotlin .kts3230
XML385,976
HTML274,203
JavaScript / CSS5 / 1230 / 16
Protobuf12844
JSON203,104
Markdown381,964
YAML3132

Git 관리 디렉토리를 제외한 전체 파일은 635개입니다. Kotlin 구현은 Android 앱의 주 소스 경로에 모여 있습니다. 별도 Android/src/app/src/test/·Android/src/app/src/androidTest/ 디렉토리는 이 커밋에서 확인되지 않습니다.

처음에는 GalleryApp.kt → LlmChatViewModel.kt → DefaultAgentRuntimeExecutor.kt → LlmChatModelHelper.kt 순서로 읽으세요. 저장소 전체를 훑기보다 한 기능의 세로 경로를 먼저 완성하는 방법입니다.

6학습 포인트 (기술별)

읽은 코드를 다른 앱에도 옮길 수 있는 지식으로 바꾸기

6-1. Kotlin: 상태를 타입으로 표현하기

Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ModelDownloadStatus.kt는 다운로드 상태를 NOT_DOWNLOADED, PARTIALLY_DOWNLOADED, IN_PROGRESS, UNZIPPING, SUCCEEDED, FAILED, UNAVAILABLE로 나눕니다. 완료 여부를 Boolean 하나로 관리하면 표현하기 어려운 상황들입니다.

// ModelDownloadStatus.kt의 데이터 구조 일부
data class ModelDownloadStatus(
  val status: ModelDownloadStatusType,
  val totalBytes: Long = 0,
  val receivedBytes: Long = 0,
  val errorMessage: String = "",
  // 나머지 속성 생략
)

배울 것: enum은 정해진 선택지를, sealed interface는 이벤트마다 다른 데이터를 표현합니다. 작은 실습: “파일은 받았지만 압축 해제 중”과 “이 기기에서 모델 사용 불가”의 화면 문구를 각각 써 보세요. 타입이 UI 문구와 버튼 상태를 설계하는 도구라는 점이 보입니다.

6-2. 코루틴: 백그라운드 실행과 취소를 함께 설계하기

LlmChatViewModelBase.generateResponse()는 코루틴에서 실행기를 사용하고, DefaultLlmSessionManager는 세션별 Mutex로 세션 변경과 문맥 압축을 조율합니다. SemanticRetrievalService.retrieveEntitiesInternal()은 I/O 디스패처로 검색을 옮기고, CancellationException을 별도로 다시 던집니다.

용어
코루틴·디스패처·Mutex
코루틴은 중단과 재개가 가능한 작업 단위, 디스패처는 그 작업을 어느 실행 환경에서 처리할지 정하는 장치입니다. Mutex는 동시에 바꾸면 안 되는 구간의 차례를 정합니다. 코루틴을 쓴다고 무거운 동기 함수가 자동으로 가벼워지는 것은 아닙니다.

실습 아이디어: 초기화 중 화면 이탈, 생성 중 중지, 세션 복원을 연속으로 누르는 상황을 종이에 그려 보세요. 각 상황에서 UI 작업, Flow 수집, 네이티브 엔진 중 무엇이 멈춰야 하는지 적습니다. 취소 예외를 일반 오류로 바꾸지 않는 이유도 함께 설명해 보세요.

6-3. 임베딩과 벡터 검색: 생성형 AI와 다른 길

Android/src/app/src/main/java/com/google/ai/edge/gallery/services/semanticretrieval/OnDeviceEmbedder.kt의 인터페이스는 텍스트·이미지 등을 FloatArray로 바꿉니다. UniversalOnDeviceEmbedder는 MediaPipe UniversalEmbedder를 감싸고, 모델의 실제 입력 지원 여부와 문맥·영상 토큰 한도를 ModelInfo에서 읽습니다.

SemanticRetrievalService는 SqliteVectorStore와 SemanticRetriever를 연결합니다. GemmaEmbeddingModelStore.DEFAULT_EMBEDDING_DIMENSION은 768입니다. 초기화 실패나 Context 부재 때는 메모리 저장소로 대체하는 경로가 있으므로, 모든 실행 환경에서 영속 저장이 보장된다고 보면 안 됩니다.

[사진 / 영상의 한 구간] --> [임베딩 모델] --> [벡터 + ID + 메타데이터] | v [로컬 벡터 저장소] ^ | ["노을 아래 달리는 강아지"] --> [같은 임베딩 모델] --> [질의 벡터] | v [유사한 사진 / 시간 구간]

답변을 만들어 내는 모델과 관련 자료를 찾아주는 모델을 구분하면, 모든 AI 기능에 대형 채팅 모델이 필요하지 않다는 점을 이해할 수 있습니다.

영상 예제: Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/videomomentfinder/VideoMomentFinderViewModel.kt는 기본 분석 구간 2초, 구간당 프레임 2개, 구간 겹침 0초, 기본 검색 결과 5개를 선언합니다. searchForMoment()는 parent_id 메타데이터 필터로 선택한 영상의 레코드만 검색합니다.

이 필터가 검색 전에 적용되는 것이 중요합니다. 전체 영상에서 상위 5개를 고른 뒤 현재 영상만 남기면, 현재 영상의 좋은 후보가 처음부터 빠질 수 있습니다. 실습 아이디어: 영상 두 편의 가상 점수를 만들어 “필터 후 상위 K개”와 “상위 K개 후 필터”를 비교해 보세요.

6-4. 함수 호출: 모델의 제안과 앱의 실행을 구분하기

Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTools.kt의 turnOnFlashlight()는 직접 카메라 API를 실행하지 않습니다. FlashlightOnAction을 콜백으로 넘기고, MobileActionsViewModel.performAction()이 구체 동작으로 연결합니다.

// MobileActionsTools.kt의 핵심 동작
@Tool(description = "Turns the flashlight on")
fun turnOnFlashlight(): Map<String, String> {
  onFunctionCalled(FlashlightOnAction())
  return mapOf("result" to "success")
}

코드의 영문 설명은 모델에게 제공하는 원래 메타데이터입니다. 이 함수의 success 반환만으로 실제 손전등 켜기가 성공했다고 판정할 수는 없습니다. 도구 계층의 요청 접수와 운영체제 동작의 성공을 따로 추적하는 설계가 왜 필요한지 배울 수 있습니다.

Android/src/app/src/main/java/com/google/ai/edge/gallery/tools/RunMcpTool.kt의 runMcpTool()은 서버 도구별 alwaysAllow를 확인합니다. 매번 허용이 필요한 도구라면 AskMcpToolCallPermissionAction을 보내고 결과를 기다린 뒤 client.callTool()을 호출합니다. 모델이 함수를 선택했다는 사실이 사용자 권한을 대신하지 않습니다.

6-5. 스킬: 설명은 먼저, 긴 지침은 필요할 때

skills/README.md는 스킬의 이름·설명으로 후보를 고르고 자세한 지침을 나중에 불러오는 흐름을 설명합니다. 실제 Android/src/app/src/main/java/com/google/ai/edge/gallery/tools/LoadSkillTool.kt의 loadSkill()은 SkillsProvider.loadSkill()과 getSkillContent()로 지침을 가져옵니다.

이 방식은 모든 지침을 처음부터 넣는 비용을 줄입니다. 스킬은 모델 가중치를 바꾸지 않으며, 지침·도구·자원을 묶어 기존 모델이 할 일을 안내합니다. JavaScript 스킬은 숨겨진 WebView의 ai_edge_gallery_get_result 진입점을 사용하고, 네이티브 기능은 Intent와 연결됩니다.

실습 아이디어: 기존 스킬의 이름·설명만 보고 어떤 질문에 선택될지 예상한 뒤, 본문의 지침과 비교해 보세요. 너무 넓은 설명 두 개가 있으면 모델이 왜 혼동할 수 있는지도 적어 봅니다. 외부 API를 사용하는 스킬은 별도 통신 기능입니다.

6-6. 로컬 저장: 파일 포맷도 앱의 계약이다

Android/src/app/src/main/java/com/google/ai/edge/gallery/di/AppModule.kt는 settings.pb, user_data.pb, skills.pb, benchmark_results.pb 등의 DataStore를 만듭니다. 스키마는 Android/src/app/src/main/proto/에 있고, 대화 저장은 Android/src/app/src/main/java/com/google/ai/edge/gallery/data/ChatSessionRepository.kt와 세션 관리자를 따라가면 됩니다.

DefaultLlmSessionManager.saveSessionHistory()는 세션 ID, 시각, 원래 모델, 작업 ID, 메시지를 묶습니다. 대화 기록을 디스크에 저장하는 것과 엔진 내부의 KV 캐시를 유지하는 것은 다른 일입니다. 앱을 다시 열 때 기록을 읽어 런타임 대화를 복원하는 연결이 필요합니다.

비유

대화 기록은 회의록이고 KV 캐시는 회의 중 사람들의 작업 메모입니다. 회의록 파일을 보관했다고 다음 회의 때 모두가 같은 작업 메모를 그대로 갖고 있는 것은 아닙니다. 복원·요약·재입력이라는 별도 과정이 필요합니다.

6-7. 성능 계측: 구현과 활성화는 다른 사실

Android/src/app/src/main/java/com/google/ai/edge/gallery/common/metrics/MetricsTracker.kt에는 첫 응답 시간, 생성 속도, 메모리·전력 등을 모으는 인터페이스와 구현이 있습니다. 그러나 create()의 지역 변수 enableInferenceMetrics는 false여서 현재 경로는 NoOpMetricsTracker()를 반환합니다.

// MetricsTracker.create()의 현재 분기
val enableInferenceMetrics = false
if (!enableInferenceMetrics) {
  return NoOpMetricsTracker()
}

또한 LlmChatModelHelper.initialize()의 enableBenchmark도 false입니다. README의 벤치마크 소개만으로 이 커밋의 모든 계측이 켜져 있다고 설명하면 안 됩니다. 실습 아이디어: 첫 응답 시간과 이후 생성 속도가 각각 어떤 사용자 경험을 설명하는지, 수치 없이 측정 계획부터 작성해 보세요.

7하드웨어 / 시스템 요구사항

앱 설치 조건, 모델 조건, 개발 조건을 따로 확인하기

7-1. 앱을 설치할 수 있다는 것과 모델을 돌릴 수 있다는 것

Android/src/app/build.gradle.kts의 minSdk=31은 Android 12 이상을 뜻합니다. README 기준으로 iOS는 17 이상이며, iOS 앱 빌드 프로젝트는 이 저장소에 포함되어 있지 않습니다.

Android 최소 버전을 만족해도, 선택한 모델의 메모리와 가속기 요구를 만족한다는 뜻은 아닙니다. 모델 파일 크기는 디스크 사용량이고, 실행 중 메모리에는 가중치·캐시·입력 처리·앱 화면 비용이 함께 들어갑니다.

7-2. 버전별 모델 목록에서 읽는 구체적인 기준

아래는 model_allowlists/1_0_20.json의 일부입니다. 파일 크기는 sizeInBytes를 십진 GB·MB로 반올림했습니다. 메모리는 목록의 minDeviceMemoryInGb 값이며 실측 최대 사용량이나 모든 기기에서의 성공 보장이 아닙니다.

모델파일 크기목록의 메모리 기준주요 용도
Gemma3-1B-IT약 584MB6GB텍스트 채팅·Prompt Lab
Qwen2.5-1.5B약 1.60GB6GB텍스트 채팅
Gemma-4-E2B약 2.59GB8GB텍스트·이미지·오디오·Agent Chat
Gemma-4-E4B약 3.66GB12GB멀티모달·도구 사용
EmbeddingGemma-2약 485MB해당 항목에 명시 없음사진·영상 의미 검색
MobileActions-270M약 289MB6GB기기 행동 함수 호출

EmbeddingGemma-2 항목에 메모리 수치가 없다는 것은 무제한 지원이라는 뜻이 아닙니다. 사진 샘플 부가 파일만 약 15.3MB이며, 사용자 영상 복사본·썸네일·벡터 DB·내보낸 클립은 모델 파일과 별도로 저장 공간을 사용합니다.

Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/common/MemoryWarning.kt의 isMemoryLow()는 이 기준과 기기 총 메모리를 비교하고, API 34 이상에서는 advertisedMem을 사용합니다. 경고 창에는 계속 진행하는 선택지도 있습니다. 이 검사는 현재 남은 여유 메모리를 실시간으로 보장하는 검사나 강제 실행 차단과는 다릅니다.

7-3. CPU·GPU·NPU는 이름만 보고 고르지 않는다

Android/src/app/src/main/java/com/google/ai/edge/gallery/data/BackendSpec.kt는 가속기 목록의 첫 항목을 기본값으로 봅니다. 실제 LiteRT-LM 초기화에서는 주 계산·비전·오디오 백엔드를 따로 정하며, 오디오는 해당 경로에서 Backend.CPU()를 사용합니다. “GPU 모델”이라고 모든 입력 처리가 GPU로만 실행되는 것은 아닙니다.

Android/src/app/src/main/java/com/google/ai/edge/gallery/common/HardwareUtils.kt에는 isOldMaliGpu(), isTensorG3OrHigher()처럼 기기 문자열과 코드네임을 판별하는 함수가 있습니다. 모델·SoC·드라이버 조합을 다뤄야 한다는 흔적이지, 이 함수만으로 모든 성능이나 호환성이 증명되는 것은 아닙니다.

AndroidManifest.xml의 NPU와 카메라 기능은 required=false입니다. 반면 AICore 경로는 시스템 서비스와 지원 기기 조건을 별도로 검사합니다. 파일 기반 모델의 지원 범위와 AICore 지원 범위를 하나로 합치지 마세요.

7-4. 개발 PC·인증·첫 사용의 조건

요구사항 해석
메모리 숫자를 단일 추천 사양으로 바꾸지 않기

6GB·8GB·12GB는 모델 목록의 개별 기준입니다. 전체 앱에 공통인 보장 사양이나 속도 측정치가 아닙니다. 대화 길이, 이미지·오디오 입력, 다른 앱의 점유, 발열에 따라 사용 경험은 달라집니다.

8직접 해볼 수 있는 실습 과제

작은 산출물과 완료 조건이 있는 다섯 가지 과제

처음 두 과제는 소스를 읽기만 해도 진행할 수 있습니다. 구현 과제는 별도 학습용 사본에서 진행하고, 모델·기기 준비가 필요한 검증과 순수 로직 검증을 구분하세요. 각 과제는 정답 코드보다 설계 판단을 설명하는 데 목적이 있습니다.

과제 1 · 입문

모델 목록 읽는 경로와 요구사항 카드 만들기

대상: Android/src/app/src/main/java/com/google/ai/edge/gallery/ui/modelmanager/ModelManagerViewModel.kt의 loadModelAllowlist(), model_allowlists/1_0_20.json.

  1. BuildConfig.VERSION_NAME이 1.0.20에서 1_0_20으로 변환되는 줄을 찾습니다.
  2. 로컬 테스트 목록 → 네트워크 → 디스크 캐시 순서를 화살표로 그립니다.
  3. 모델 두 개의 파일 크기·메모리 기준·입력 지원·가속기·작업 종류를 표로 정리합니다.

완료 조건: 왜 루트 model_allowlist.json을 현재 모델 목록으로 읽으면 안 되는지 설명하고, 첫 실행에 목록과 캐시가 모두 없을 때의 실패 경로를 표시합니다.

과제 2 · 입문

질문 한 번의 생명주기 그리기

대상: LlmChatViewModel.kt, DefaultAgentRuntimeExecutor.kt, AgentEvent.kt, LlmChatModelHelper.kt.

  1. 질문을 보내는 순간부터 첫 응답 조각, 정상 종료까지의 함수 이름을 연결합니다.
  2. 모델 미초기화, 추론 오류, 사용자의 중지를 각각 다른 가지로 표시합니다.
  3. 각 사건에서 로딩 표시·전송 버튼·응답 메시지가 어떤 상태여야 할지 적습니다.

완료 조건: done=true와 성공 여부를 무조건 같은 뜻으로 취급하지 않고, 취소가 결과 콜백을 통해 종료되는 경로도 확인합니다.

과제 3 · 중급

외부 부작용이 없는 새 도구 설계하기

대상: Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/Actions.kt, MobileActionsTools.kt, MobileActionsViewModel.kt.

  1. 샘플 도구로 “화면에 학습 메모 초안 표시”를 정합니다. 메시지를 보내거나 주소록을 바꾸는 동작은 넣지 않습니다.
  2. Action 데이터, @Tool 함수, performAction()의 처리 책임을 나눕니다.
  3. 빈 메모·지나치게 긴 메모·알 수 없는 인자에 대한 규칙을 먼저 적습니다.

완료 조건: 모델 없이 Action을 전달해도 화면 처리 로직을 확인할 수 있고, 도구 요청 접수와 실제 처리 완료를 각각 설명합니다. Function_Calling_Guide.md의 예제 이름은 실제 소스의 ToolSet 등과 대조합니다.

과제 4 · 중급

선택한 영상 안에서만 검색되는 이유 증명하기

대상: VideoMomentFinderViewModel.searchForMoment(), SemanticRetrievalService.retrieveEntities().

  1. 영상 A·B 각각 6개 구간에 가상의 유사도 점수를 줍니다.
  2. 상위 5개를 먼저 고르는 경우와 parent_id로 먼저 거르는 경우를 비교합니다.
  3. 빈 검색 결과를 정상으로 처리하는 분기와, 예외 발생 후 대체 검색을 하는 분기를 구분합니다.

완료 조건: 결과 0개일 때 임베딩을 불필요하게 두 번 계산하지 않는 이유를 설명하고, 영상 A 검색에 B의 구간이 섞이지 않는 검증 조건을 제시합니다.

과제 5 · 고급

다운로드 재개의 경계 조건 개선하기

대상: Android/src/app/src/main/java/com/google/ai/edge/gallery/worker/DownloadWorker.kt의 doWork(). 현재 코드는 일부 파일이 있으면 Range를 요청하고, HTTP 200과 206을 모두 허용하며 출력 스트림을 append 모드로 엽니다.

  1. 서버가 Range를 무시하고 파일 전체를 200으로 반환하는 경우를 손으로 추적합니다.
  2. 206이면 응답 시작 위치를 검증하고, 200이면 처음부터 덮어쓰도록 정책을 설계합니다.
  3. 416 응답·중간 취소·잘못된 Content-Range·파일 버전 변경의 처리 기준을 정합니다.
  4. 로컬 가짜 응답으로 재현하는 검증 계획을 만듭니다. 대형 모델을 실제로 내려받을 필요는 없습니다.

완료 조건: 정상 재개와 전체 재다운로드의 최종 바이트가 각각 원본과 같아야 합니다. 파일 길이 검증, 체크섬 검증, 성공 처리 시점을 분리해 설명하면 더 좋습니다.

실습의 완료 기준을 눈에 보이는 결과와 실패 조건으로 적으면, 코드를 따라 치는 공부가 설계를 검증하는 공부로 바뀝니다.

9관련 기술 심화 학습 로드맵

6주 동안 한 기능을 끝까지 설명할 수 있게 만들기

주당 4~6시간을 가정한 제안입니다. Kotlin 문법이 낯설면 첫 주를 두 주로 늘려도 됩니다. 매주 “무엇을 읽었다”보다 무엇을 설명하거나 검증할 수 있게 되었는지를 결과물로 남기세요.

주차학습 주제·출발 파일직접 만들 결과물
1주차 Kotlin의 data class, enum, sealed 타입, 확장 프로퍼티.
ModelDownloadStatus.kt, AgentEvent.kt, ModelHelperExt.kt.
모델 다운로드·초기화·추론의 상태표. 각 상태의 진입 원인과 다음 상태를 한 줄씩 작성.
2주차 Compose, ViewModel, StateFlow, 단방향 흐름.
GalleryApp.kt, LlmChatScreen.kt, ChatViewModel.kt.
가짜 응답을 표시하는 작은 화면 설계. 상태 변경 책임이 어디에 있는지 설명.
3주차 코루틴, 콜백 변환, 취소, 세션 동시성.
DefaultAgentRuntimeExecutor.kt, DefaultLlmSessionManager.kt.
정상 종료·오류·사용자 취소의 순서도. 취소가 네이티브 엔진에 전달되는 경로 표시.
4주차 모델 파일, 가속기, 대화 수명, 자원 회수.
LlmChatModelHelper.kt, BackendSpec.kt, DownloadWorker.kt.
엔진 재사용과 대화 초기화의 비교표. 다운로드 재개에 대한 가짜 서버 응답 검증 계획.
5주차 임베딩, 유사도, 벡터 저장, 미디어 창 분할.
OnDeviceEmbedder.kt, SemanticRetrievalService.kt.
영상 A·B의 메타데이터 검색 예제. 필터 위치와 상위 K개 선택의 차이 설명.
6주차 도구 스키마, 스킬 지침, 권한, 문맥 예산.
RunMcpTool.kt, LoadSkillTool.kt, SummarizationContextCompactor.kt.
부작용 없는 도구 하나의 설계서. 입력 검증·권한·실패 결과·문맥 비용을 명시.

9-1. 매주 반복할 읽기 순서

  1. 입구 찾기: 사용자의 어떤 동작으로 이 코드가 시작되는지 적습니다.
  2. 타입 확인: 함수 이름만 보지 말고 입력·출력·상태 타입을 읽습니다.
  3. 정상 경로 추적: 한 가지 성공 사례를 끝까지 따라갑니다.
  4. 실패 경로 추가: 오류·취소·미지원·빈 결과를 각각 확인합니다.
  5. 다른 예제에 적용: 모델 대신 가짜 응답이나 가상의 벡터를 넣어 같은 구조를 설명합니다.

기능 하나의 입력·상태·실패·자원 회수까지 설명하는 경험이, 라이브러리 이름을 많이 아는 것보다 오래 남는 학습이 됩니다.

9-2. 그다음에는 관심 분야에 맞춰 갈라지자

관심 분야더 배울 기술gallery에서 이어지는 질문
Android 앱화면 생명주기, 저장소 경계, 접근성, 프로파일링긴 작업 중 화면 이탈·복귀 시 상태를 어떻게 복구할까?
추론 엔진양자화, KV 캐시, 연산 그래프, 가속기 백엔드왜 파일 크기와 실행 메모리·속도가 비례하지 않을까?
검색·RAG임베딩 평가, 필터, 색인 버전 관리, 검색 품질모델을 바꾸면 기존 사진 벡터를 그대로 써도 될까?
에이전트도구 스키마, 권한, 문맥 압축, 결과 검증모델의 함수 선택을 어떻게 검증 가능한 동작으로 바꿀까?
마지막 점검
새 앱을 만든다면 무엇을 재사용할지 말해 보기

화면 전체를 그대로 가져오기 전에 런타임 계약, 이벤트 타입, 다운로드 정책, 저장 스키마 중 필요한 것을 골라 보세요. 각 부품이 기대하는 Context·디스패처·권한·외부 서비스를 적으면 숨은 의존성까지 드러납니다.

10핵심 키워드 사전

코드에서 다시 만났을 때 떠올릴 쉬운 설명
용어쉬운 설명
LLM문맥을 보고 다음 토큰을 예측하며 문장을 만드는 대규모 언어 모델. 이 앱에서는 대화와 함수 호출의 판단에 쓰입니다.
가중치학습 결과가 담긴 숫자들. 내려받는 모델 파일의 핵심 내용이며 앱 소스와 별도로 배포됩니다.
추론 / 학습배운 모델로 결과를 만드는 일 / 모델의 숫자를 조정하는 일. Gallery의 주 역할은 추론 통합입니다.
LiteRT-LMLiteRT 기반 LLM 실행을 연결하는 계층. 앱은 엔진·대화 API를 통해 모델을 사용합니다.
AICoreAndroid 시스템이 관리하는 모델 기능을 이용하는 경로. 앱이 파일을 직접 관리하는 경로와 준비·삭제 정책이 다릅니다.
양자화모델 숫자를 더 적은 비트로 표현해 크기와 계산 비용을 줄이는 방법. 정확도와 실행 특성의 변화도 함께 살펴야 합니다.
토큰모델이 처리하는 텍스트 등의 단위. 한국어 한 글자나 단어 하나와 항상 같지는 않습니다.
문맥 창모델이 한 번에 참고할 수 있는 정보의 범위. 질문뿐 아니라 이전 대화·도구 설명·미디어 입력도 예산을 사용합니다.
KV 캐시이전 문맥 계산의 중간 결과를 보관해 다음 생성을 돕는 메모리. 대화 기록 파일과는 다릅니다.
문맥 압축긴 대화를 요약해 더 짧은 문맥으로 다시 시작하는 방식. 중요한 세부 사항이 빠질 가능성도 있습니다.
멀티모달텍스트 외에 이미지·오디오 같은 여러 형태의 입력을 다루는 것. 각 모델의 실제 지원 여부를 확인해야 합니다.
임베딩내용을 의미 비교에 사용할 숫자 배열로 바꾸는 것. 문장·사진을 같은 검색 공간에서 비교하는 데 활용합니다.
벡터 저장소벡터와 ID·부가 정보를 보관하고 비슷한 벡터를 찾는 저장 계층. 이 코드에는 SQLite·메모리 구현 경로가 있습니다.
코사인 유사도두 벡터의 방향이 얼마나 비슷한지 비교하는 값. 코드의 직접 검색 대체 경로에서 점수 계산에 쓰입니다.
상위 K개점수가 높은 결과 K개만 고르는 방식. K를 늘려도 결과가 모두 관련 있는 것은 아닙니다.
메타데이터본문 외의 부가 정보. 영상 ID·시작 시각·끝 시각은 검색 범위를 정하고 결과를 재생하는 데 필요합니다.
RAG관련 자료를 검색한 뒤 생성 모델이 그 자료를 참고하게 하는 구성. 벡터 검색만 존재한다고 전체 RAG가 완성되는 것은 아닙니다.
Compose현재 상태를 바탕으로 Android 화면을 기술하는 UI 도구. 상태 변화가 필요한 화면 갱신으로 이어집니다.
ViewModel화면이 사용할 상태와 동작을 보관하는 계층. 엔진 API를 화면 요소마다 직접 호출하지 않도록 연결을 맡습니다.
StateFlow / Flow최신 상태를 관찰하는 통로 / 시간에 따라 전달되는 값의 흐름. 상태와 사건의 성격을 구분해 선택합니다.
callbackFlow콜백으로 들어오는 값을 Flow 형태로 연결하는 도구. 외부 작업 중단까지 자동으로 해결해 주는 것은 아닙니다.
Hilt필요한 객체와 그 의존성을 조립해 전달하는 Android 도구. 이 앱에서는 저장소·실행기 등을 연결합니다.
Repository데이터 접근·저장 정책을 모아 사용하는 쪽에 일관된 통로를 제공하는 구성요소.
WorkManager화면 밖에서도 유지할 백그라운드 작업을 예약·관찰하는 도구. 다운로드의 HTTP 세부 처리는 Worker 코드가 맡습니다.
Proto DataStore미리 정의한 Protobuf 구조로 로컬 데이터를 저장하는 방식. 서버 DB와 별개입니다.
함수 호출모델이 도구 이름과 인자를 생성하고 앱이 실제 함수를 실행하는 방식. 모델이 직접 OS 권한을 얻는 것은 아닙니다.
Android Intent화면 열기·메일 작성 등 운영체제와 다른 앱에 작업을 요청하는 메시지. 요청과 최종 동작 완료는 구별합니다.
MCPAI 앱이 외부 도구·데이터 서버와 통신하기 위한 규약. Gallery는 도구 서버에 연결하는 클라이언트 역할을 합니다.
스킬기능의 이름·설명·상세 지침·자원을 묶은 모듈. 모델 학습 없이 작업 방식을 안내합니다.
OAuth외부 서비스의 접근 권한을 위임받는 인증·인가 흐름. Hugging Face 로그인 설정에 사용합니다.
TTFT요청 후 첫 응답 토큰이 나올 때까지 걸린 시간. 첫 반응의 체감 속도와 관련됩니다.
Prefill / Decode입력 문맥을 먼저 처리하는 단계 / 이후 출력 토큰을 생성하는 단계. 서로 다른 병목이 생길 수 있습니다.
BOM / AGP라이브러리 버전 묶음의 기준표 / Android 빌드를 위한 Gradle 플러그인. 앱 자체 버전과 구분합니다.
HTTP Range파일의 특정 바이트 구간을 요청하는 방식. 서버 응답이 실제로 그 구간인지 확인해야 안전하게 이어받을 수 있습니다.

용어를 외운 뒤에는 다시 함수로 돌아오세요. 예를 들어 “문맥 압축”을 이해했다면 SummarizationContextCompactor.compactContextIfNeeded()에서 현재 토큰 수 확인, 요약 생성, 대화 재설정, 실패 후 재시도 간격 처리를 차례로 찾아보는 식입니다.

11참고 링크

공식 자료, 코드 근거, 현재 상태를 함께 남기기

11-1. 프로젝트 공식 자료

11-2. 소스 근거를 다시 읽는 바로가기

아래 링크는 분석 기준 커밋에 고정되어 있습니다. 기본 브랜치의 같은 경로는 이후 구현이 달라질 수 있습니다.

확인할 내용파일·핵심 함수
실제 버전 libs.versions.toml, app/build.gradle.kts.
화면 상태 ChatViewModel.kt의 addMessage()·uiState.
실행 이벤트 DefaultAgentRuntimeExecutor.kt의 executeStream()·interrupt().
추론 수명 LlmChatModelHelper.kt의 initialize()·runInference()·cleanUp().
다운로드 DownloadWorker.kt의 doWork() — 임시 파일·Range·진행률 알림.
의미 검색 SemanticRetrievalService.kt의 retrieveEntitiesInternal()·메타데이터 변환.
도구 권한 RunMcpTool.kt의 runMcpTool() — 허용 확인 후 원격 호출.
메모리 기준 1_0_20.json의 sizeInBytes·minDeviceMemoryInGb.
계측 활성 상태 MetricsTracker.kt의 create() — 현재 NoOpMetricsTracker 반환.

11-3. 관련 기술의 공식 문서

현재 상태 (2026-10-23 확인)

항목상태해석·기준
선정 순위Kotlin 월간 5위2026-10-23 GitHub Trending 기준
스타 / 포크25,076 / 2,7662026-10-23 GitHub 기준, 이후 변동 가능
생성일2025-03-31GitHub 저장소 생성일
최근 push2026-10-23저장소 메타데이터의 갱신 시각
최신 릴리스1.0.20 · 2026-10-06분석일 GitHub 기준
분석 기준 커밋8f5f2a0 · 2026-10-13소스 분석 기준 HEAD, 메시지 Internal changes
앱 버전 설정1.0.20 / 코드 46Android/src/app/build.gradle.kts
주 언어KotlinAndroid 앱 구현이 분석의 중심
코드 규모Kotlin 85,786줄·326개 파일빈 줄·주석 포함, Gradle Kotlin 230줄은 별도
기타 텍스트XML 5,976줄·HTML 4,203줄리소스·스킬 포함, HTML 내부 JavaScript는 HTML 줄 수에 포함
플랫폼 범위Android 앱 소스 확인README는 iOS·macOS 배포도 안내
테스트·CIAPK 조립 워크플로 있음독립 테스트 소스 디렉토리·CI 테스트 단계는 확인되지 않음
라이선스Apache-2.0앱 저장소 기준. 모델 가중치의 이용 조건은 각 모델에서 확인
제품 단계실험적 베타README 안내, MCP도 실험 단계로 명시

최근 push 날짜, 릴리스 날짜, 소스 분석 기준 커밋 날짜는 서로 다른 정보를 나타냅니다. 이 글의 코드 설명은 8f5f2a0에 고정하고, 인기 지표와 릴리스 상태는 분석일 기준으로 읽으면 됩니다.

읽고 활용하기 전에 알아둘 한계
코드의 가능성을 제품의 보장으로 읽지 않기

성능: 소스 구조만으로 특정 기기의 속도·발열·메모리 안정성을 보장할 수 없습니다. 현재 계측 기본 경로도 비활성화되어 있습니다. 비교하려면 모델 버전·가속기·입력·대화 길이·측정 조건을 맞춰야 합니다.

오프라인·데이터: 로컬 추론과 외부 통신은 별개입니다. 목록·모델 다운로드, 외부 스킬·MCP, 구성에 따라 가능한 Analytics를 구분해야 합니다. Analytics.kt는 Firebase 미설정 시 예외를 처리하며, 앱 빌드 파일의 google-services 플러그인도 기본 적용이 꺼져 있습니다.

코드·문서: README의 플랫폼 소개와 공개 앱 소스 범위, 벤치마크 소개와 활성 상태, 문서의 문맥 수치와 모델별 설정이 다릅니다. 예전 목록·가이드의 표기를 최신 구현으로 그대로 대체하지 마세요.

정확성·권한: 모델이 생성한 설명이나 함수 인자가 항상 맞는 것은 아닙니다. 입력 검증·실행 결과 확인·사용자 권한은 앱의 책임이며, 외부 스킬과 도구는 신뢰할 수 있는 범위에서 선택해야 합니다.

배포: 앱의 Apache-2.0과 모델의 이용 조건은 별도입니다. 또한 이 커밋의 release 빌드는 debug 서명 설정을 참조하므로, 실제 배포에는 서명·인증·구성 값을 검토해야 합니다.