AppFunctions는 Android에서 MCP 서버와 실질적으로 동일합니다. 이를 통해 앱 로직의 일부를 AI 에이전트 및 어시스턴트와 공유할 수 있습니다.
에이전트는 설명을 사용하여 요청을 처리하는 AppFunction을 선택합니다. 이 선택 프로세스는 기존 MCP 도구 검색과 유사하게 작동하며, AI 애플리케이션은 MCP 서버 및 도구 설명을 사용하여 선택을 안내합니다. 앱은 Android MCP 서버 역할을 하고 각 AppFunction은 도구 중 하나로 작동합니다. 다음 설명을 제공합니다.
- 앱 설명 (Android MCP 서버) 앱 설명은 에이전트가 앱의 기능을 이해하는 첫 번째 진입점입니다. 에이전트가 앱을 검색할 수 있도록 기본 도메인을 먼저 표시하고 AppFunctions로 수행할 수 있는 작업을 간결하게 나열하세요.
- 앱 메타데이터 XML 파일에 앱 설명을 직접 작성합니다.
- 앱 설명을 사용하여 함수 호출 순서와 같이 개별 함수 설명으로 설명할 수 없는 내용을 설명합니다.
- AppFunction 설명 (도구) 각 함수 설명은 하나의 AppFunction 동작을 설명합니다. 함수 설명은 일반적으로 update, create, book과 같은 동사와 settings, channel, room과 같은 리소스를 중심으로 구성됩니다.
함수 설명 가이드라인
에이전트는 함수 설명을 사용하여 요청에 적합한 함수와 호출 방법을 결정합니다.
간결하고 명확한 AppFunction 설명 작성
설명이 명확할수록 에이전트가 요청에 적합한 함수를 더 안정적으로 선택할 수 있습니다. 다음 표에는 함수 설명에서 해야 할 일과 하지 말아야 할 일이 나와 있습니다.
| 권장사항 | 금지사항 |
|---|---|
| 함수의 목적과 실행 결과를 명확하고 중립적인 언어로 설명합니다. | 설명을 프롬프트로 사용하지 마세요. '말해야 합니다' 및 '사용자에게 요청해야 합니다'와 같은 명령어는 함수를 설명하는 대신 에이전트에게 어떻게 행동해야 하는지 지시합니다. |
| 설명 앞부분에 핵심 정보를 배치하고 간결하게 작성합니다. | '이 함수는 …에 사용됩니다'와 같은 불필요한 표현으로 설명을 채우지 마세요. 불필요한 표현은 설명을 명확하게 만들지 않으면서 설명만 길게 만듭니다. |
| 파라미터와 출력을 포함하여 코드가 수행하는 작업을 정확하게 반영합니다. 예를 들어 읽기 전용으로 설명된 함수는 데이터를 쓰거나 삭제해서는 안 됩니다. | 사용자 데이터를 삭제하는 fetchData라는 함수와 같이 오해의 소지가 있는 이름을 사용하지 마세요. |
| 매개변수와 반환 유형을 포함하여 KDoc 형식으로 함수 설명을 작성합니다. 이름 지정은 Kotlin 스타일 가이드를 따르고 구현에 관한 지식이 없어도 이해할 수 있도록 설명을 작성합니다. | 설명의 일부에 주의를 끌기 위해 대문자나 특수문자(예: **CRITICAL** 또는 VERY IMPORTANT)를 과도하게 사용하지 마세요. |
| AppFunctions에 인간이 읽을 수 있는 이름을 사용합니다. | |
| 각 AppFunction의 기능을 명확하고 직접적으로 설명합니다. | '먼저 연락처를 검색하세요'와 같이 다른 함수와 관련된 절차 안내를 제공하지 마세요. 에이전트는 앱 설명을 사용하여 함수 전반의 호출을 계획하므로 대신 여기에 순서를 설명하세요. |
| "X가 있으면 Y"와 같은 언어로 에이전트가 함수에서 기대할 수 있는 사항을 설명합니다. | |
| 에이전트에는 일반적으로 AppFunctions를 사용하기 위한 자체 안내가 있다고 가정합니다. 함수 설명은 함수가 수행하는 작업을 설명하고 에이전트의 안내는 함수를 사용하는 방법을 다룹니다. | 함수 설명에서 에이전트 수준 요청 사항을 재정의하거나 삽입하지 마세요. |
캡슐화를 염두에 두고 작성
각 함수 설명은 독립적으로 유지하여 에이전트가 다른 함수가 아닌 해당 함수를 사용하도록 안내합니다. 다음 표에는 캡슐화 가이드라인이 나와 있습니다.
| 권장사항 | 금지사항 |
|---|---|
| 명확하게 정의된 단일 목적을 가진 구체적이고 집중적인 함수 설명을 작성합니다. 예를 들면 '개인 할 일 만들기, 찾기, 삭제'입니다. | 설명을 너무 광범위하게 작성하지 마세요. 에이전트가 부적절한 시점에 또는 잘못된 이유로 함수를 호출할 수 있습니다. |
| 다목적 AppFunction을 만들거나 동일한 사용 사례에 여러 AppFunction을 노출하지 마세요. 여러 함수에 유사한 인텐트가 있는 경우 에이전트가 잘못된 함수를 선택할 가능성이 높습니다. | |
| 앱 또는 다른 앱의 다른 AppFunction을 참조하거나 그 동작에 영향을 주려고 시도하지 마세요. 이로 인해 의도하지 않은 종속 항목과 부작용이 발생할 수 있습니다. |
허용되는 작업 및 데이터 공유
에이전트가 앱의 기능을 사용할 때 사용자의 개인 정보 보호 및 안전을 보호하려면 다음 가이드라인을 따르세요.
| 권장사항 | 금지사항 |
|---|---|
| 사용자가 앱에서 일반적으로 수행하는 안전하고 비파괴적인 작업만 수행합니다. 에이전트는 사용자의 요청에서 명확한 매개변수를 제공할 수 있습니다(예: 제목이 있는 작업 만들기 또는 메모 검색). | |
| 매우 개인적이거나 기밀이 아닌 정보 또는 사용자가 작업에 공유하는 데 명시적으로 동의한 데이터만 공유합니다. | 에이전트에게 사용자 데이터 또는 시스템 리소스에 대한 광범위한 액세스 권한을 부여하지 마세요. |
| 비밀번호, 금융 세부정보, 비공개 키 또는 기타 매우 민감한 개인 정보를 요구하거나 반환하는 AppFunction을 설계하지 마세요. | |
| 사용자가 요청한 작업을 나중에 또는 백그라운드에서 실행하는 경우에도 수행합니다(예: 사용자가 선택한 시간에 메시지 전송). | 자체적으로 시작되거나 요청 범위를 벗어나는 작업과 같이 사용자가 요청하지 않은 작업을 실행하지 않습니다. |
| UI 탐색보다 대화로 표현하기 더 쉬운 작업과 같이 자연어의 이점을 활용하는 기능에 집중하세요. 예를 들어 "[AI 에이전트], 다음 주에 내 캘린더에서 가능한 시간을 찾은 다음 그중 하루에 시카고로 가는 항공편을 추천해 줘"와 같은 작업이 있습니다. | |
| 휴대전화 이외의 폼 팩터에서는 TV, Android Auto, Android Automotive OS와 같은 공유 기기에서 사용자의 개인 정보를 존중합니다. | 앱의 UI가 해당 폼 팩터에서 지원하지 않는 정보를 공개하거나 작업을 실행하지 마세요. |
| 함수의 목적을 명확하고 고유하게 식별합니다. | 다른 도구나 서비스인 것처럼 가장하지 마세요. 함수의 이름과 설명은 다른 도구와 혼동을 일으킬 정도로 유사해서는 안 됩니다. 이로 인해 에이전트가 실수로 함수를 선택할 수 있습니다. |
파라미터 및 반환 값 설계
신중한 매개변수 및 반환 값 설계는 모호성을 방지하고 잘못된 호출의 가능성을 줄입니다. 다음 규칙을 따르세요.
| 권장사항 | 금지사항 |
|---|---|
| 함수에 필요한 데이터에 대해서만 파라미터를 정의합니다. | 함수에서 사용하지 않는 정보를 요청하지 마세요. 이렇게 하면 데이터 노출이 최소화되고 함수 사용이 간소화됩니다. |
taskId, searchQuery, recipientEmail와 같이 예상 입력을 반영하는 파라미터 이름을 사용합니다. |
full_context 또는 conversation_history와 같은 이름을 사용하지 마세요. 에이전트가 자체 컨텍스트 및 대화 기록에 이러한 용어를 사용하므로 이러한 이름은 모호합니다. 에이전트가 이를 잘못 해석하여 대량의 불필요한 데이터를 함수에 전달하여 데이터 무단 반출이 발생할 수 있습니다. |
자체 유형을 정의하는 대신 AppFunctions에서 지원하는 유형을 사용합니다. 예를 들어 날짜 및 시간의 경우 LocalDate, LocalTime, LocalDateTime, Instant를 사용하고 링크의 경우 Uri를 사용합니다. 라이브러리는 모든 앱에서 이러한 유형을 동일한 방식으로 표현하므로 에이전트는 일관된 형식을 얻습니다. 지원되는 유형의 전체 목록은 AppFunctionSerializable를 참고하세요. |
|
| 매개변수가 함수의 목적에 필수적이지 않은 경우 선택사항으로 만듭니다. 이렇게 하면 에이전트가 사용자로부터 부분적인 정보만 가지고 있을 때 유연성을 제공합니다. 자세한 내용은 선택적 매개변수를 참조하세요. | |
| AppFunction이 기존 리소스를 업데이트하는 경우, 각 매개변수가 "null로 설정"과 "변경하지 않음"을 구분할 수 있도록 해야 합니다. 예시는 기존 리소스 업데이트를 참조하세요. | |
에이전트가 앱에서 특정 화면을 열 수 있도록 하려면 해당 화면의 PendingIntent를 반환합니다. 에이전트는 앱의 인텐트를 빌드하지 않고도 PendingIntent를 있는 그대로 실행할 수 있습니다. 에이전트가 연결 위치를 변경할 수 없도록 명시적 인텐트와 FLAG_IMMUTABLE를 사용하여 만듭니다. |
선택적 매개변수
null을 허용하는 유형, 기본 유형, 목록 유형, 배열 유형만 선택사항일 수 있습니다. 에이전트가 선택적 매개변수를 생략하면 라이브러리는 개발자가 선언한 기본값 대신 유형의 기본값(null, 0, false 또는 비어 있음)을 전달합니다.
다음 예와 같이 함수 본문에서 의미 있는 기본값을 확인합니다.
/**
* Creates a note.
*
* @param title The title of the note.
* @param content The body text of the note.
* @param attachments The files to attach to the note.
* @param owner The ID of the user who owns the note.
* @return The created note.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun createNote(
title: String, // Required
content: String? = null, // Optional
attachments: List<Attachment> = emptyList(), // Optional
owner: String? = null, // Optional
): Note = withContext(Dispatchers.IO) {
noteRepository.createNote(
title = title,
// The library ignores declared default values and passes null for
// omitted nullable parameters, so resolve meaningful defaults here.
content = content ?: "",
attachments = attachments,
owner = owner ?: currentUserId(),
)
}
기존 리소스 업데이트
다음 예시에서는 에이전트가 업데이트할 수 있는 각 필드에 null 허용 SetField 래퍼를 사용합니다. null SetField은 '변경하지 않음'을 의미하고 SetField(null)은 'null로 설정'을 의미합니다.
/** A new value for a field of an existing item. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class SetField<T>(
/** The new value of the field. */
val value: T,
)
/**
* Updates an existing note. Omitted fields keep their values.
*
* @param noteId The ID of the note to update.
* @param title The new title of the note.
* @param content The new body text of the note.
* @return The updated note.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun updateNote(
noteId: String,
title: SetField<String>? = null,
content: SetField<String?>? = null,
): Note = withContext(Dispatchers.IO) {
val note = noteRepository.getNote(noteId)
?: throw AppFunctionElementNotFoundException(
"No note found with ID $noteId"
)
noteRepository.updateNote(
noteId = noteId,
// A null SetField means "don't change", and SetField(null) means
// "set to null", so don't use ?: for nullable fields.
title = title?.value ?: note.title,
content = if (content != null) content.value else note.content,
)
}
오류 처리
호출이 실패하면 에이전트는 함수에서 발생한 예외(유형 및 메시지 포함)를 수신합니다. 에이전트가 실패를 복구하거나 사용자에게 보고할 수 있도록 다음 규칙을 따르세요.
| 권장사항 | 금지사항 |
|---|---|
원인과 일치하는 AppFunctionException 서브클래스를 발생시키고 문제를 일반 언어로 설명하는 메시지를 표시합니다. 예를 들어 인수가 유효하지 않은 경우 AppFunctionInvalidArgumentException, 요청된 항목이 존재하지 않는 경우 AppFunctionElementNotFoundException입니다. 유형은 에이전트에게 발생한 실패의 종류를 알려주고, 메시지는 인수 수정이나 사용자에게 알림과 같이 복구에 필요한 세부정보를 제공합니다. 기존 리소스 업데이트의 updateNote 예시에서 이 패턴을 보여줍니다. |
다른 예외가 함수를 이스케이프하지 않도록 합니다. 에이전트는 이를 AppFunctionAppUnknownException로 수신하며, 이는 실패 유형을 식별하지 않습니다. |