AppFunctions – это практический эквивалент MCP-серверов на Android. Они позволяют делиться частями логики приложения с ИИ-агентами и помощниками.
Агенты используют описания, чтобы выбрать AppFunction, которая выполнит запрос. Этот процесс выбора работает так же, как и традиционный поиск инструментов MCP, когда ИИ-приложения используют описания серверов и инструментов MCP для выбора. Ваше приложение действует как сервер MCP Android, а каждая функция AppFunction – как один из его инструментов. Укажите следующие описания:
- Описание приложения (MCP-сервер Android). Описание приложения – это первое, что помогает агентам понять, что оно может делать. Чтобы агентам было проще найти ваше приложение, укажите его основной домен и кратко перечислите задачи, которые оно может выполнять с помощью AppFunctions.
- Напишите описание самостоятельно в XML-файле метаданных приложения.
- В описании приложения можно указать то, что не вошло в описания отдельных функций, например порядок их вызова.
- Описания функций AppFunctions (инструментов). В описании каждой функции объясняется поведение одной AppFunction. Описания функций обычно строятся вокруг глагола, например обновить, создать или забронировать, и ресурса, например настройки, канал или комната.
Рекомендации по описанию функций
Агенты используют описания функций, чтобы определить, какая из них подходит для запроса и как ее вызвать.
Пишите краткие и однозначные описания AppFunction
Чем точнее описание, тем выше вероятность, что агент выберет правильную функцию для запроса. В таблице ниже приведены рекомендации по составлению описаний функций.
| Что следует делать | Чего делать нельзя |
|---|---|
| Опишите назначение функции и результат ее выполнения простым и нейтральным языком. | Не используйте описания в качестве запросов. Команды вроде "Ты ДОЛЖЕН сказать" и "Ты должен спросить у пользователя" указывают агенту, как себя вести, а не описывают функцию. |
| В начале описания указывайте самую важную информацию. | Не добавляйте в описания лишние слова, например "Эта функция используется для". Лишние слова делают описание длиннее, но не понятнее. |
| Точно отражать, что делает код, включая его параметры и выходные данные. Например, функция, описанная как предназначенная только для чтения, не должна записывать или удалять данные. | Не используйте вводящие в заблуждение названия, например fetchData для функции, которая удаляет пользовательские данные. |
| Описания функций, включая параметры и тип возвращаемого значения, должны быть в формате KDoc. При выборе названий следуйте руководству по стилю Kotlin и составляйте описания, понятные без знания реализации. | Не используйте слишком много заглавных букв или специальные символы, чтобы привлечь внимание к частям описания, например **CRITICAL** или VERY IMPORTANT. |
| Используйте человекочитаемые названия для AppFunctions. | |
| Четко и прямо указывайте, что делает каждая функция AppFunction. | Не давайте процедурных инструкций, связанных с другими функциями, например "Сначала найдите контакт". Агенты используют описание приложения, чтобы планировать вызовы функций, поэтому укажите порядок в нем. |
| Опишите, что агент может ожидать от функции, используя формулировки, такие как "Если у вас есть X, то Y". | |
| Предположим, что у агентов есть собственные инструкции по использованию AppFunctions в целом. В описании функции говорится, что она делает, а в инструкциях агента – как ее использовать. | Не пытайтесь переопределить или встроить инструкции на уровне агента в описания функций. |
Пишите код с учетом инкапсуляции
Описание каждой функции должно быть самодостаточным, чтобы агент мог использовать только ее. В таблице ниже приводятся рекомендации по инкапсуляции.
| Что следует делать | Чего делать нельзя |
|---|---|
| Пишите описания функций, которые являются конкретными и целенаправленными, с одной четко определенной целью. Например, "создавать, находить и удалять личные задачи". | Не пишите слишком общее описание, иначе агент может вызывать функцию в неподходящее время или по неправильным причинам. |
| Не создавайте AppFunctions, которые выполняют несколько задач, и не добавляйте много AppFunctions для одного и того же сценария использования. Агенты с большей вероятностью выберут неправильную функцию, если у нескольких функций похожие намерения. | |
| Не ссылайтесь на другие функции AppFunctions в своем или других приложениях и не пытайтесь влиять на их поведение. Это может привести к непреднамеренным зависимостям и побочным эффектам. |
Допустимые действия и передача данных
Чтобы защитить конфиденциальность пользователей и их безопасность, когда агенты используют функции вашего приложения, следуйте этим рекомендациям:
| Что следует делать | Чего делать нельзя |
|---|---|
| Выполняйте только безопасные, неразрушающие задачи, которые пользователь обычно делает в вашем приложении, где агент может предоставить четкие параметры из запроса пользователя, например создание задачи с заголовком или поиск заметок. | |
| Передавайте только информацию, которая не является личной или конфиденциальной, или данные, на передачу которых пользователь дал явное согласие. | Не предоставляйте агенту широкий доступ к пользовательским данным или системным ресурсам. |
| Не создавайте функции AppFunctions, которые запрашивают или возвращают пароли, финансовые данные, закрытые ключи или другую конфиденциальную личную информацию. | |
| Выполнять действия, запрошенные пользователем, даже если они запускаются позже или в фоновом режиме, например отправлять сообщение в выбранное пользователем время. | Не выполняйте действия, которые пользователь не запрашивал, например задачи, которые запускаются сами по себе или выходят за рамки запроса. |
| Сосредоточьтесь на функциях, которые выигрывают от использования естественного языка, например на задачах, которые проще выразить в разговоре, чем через навигацию по интерфейсу. Например: "[ИИ-агент], помоги мне найти свободное время в календаре на следующей неделе, а затем предложи рейс в Чикаго в один из этих дней". | |
| На устройствах других типов, например телевизорах, Android Auto и Android Automotive OS, уважайте конфиденциальность пользователей. | Не раскрывайте информацию и не выполняйте действия, которые не поддерживаются интерфейсом приложения на этом типе устройств. |
| Четко и однозначно определите назначение функции. | Не выдавайте свой продукт за другой инструмент или сервис. Название и описание функции не должны быть похожи на другие инструменты, иначе агент может выбрать вашу функцию по ошибке. |
Разработка параметров и возвращаемых значений
Тщательная разработка параметров и возвращаемых значений предотвращает двусмысленность и снижает вероятность недопустимых вызовов. Следуйте этим правилам:
| Что следует делать | Чего делать нельзя |
|---|---|
| Определяйте параметры только для тех данных, которые нужны функции. | Не запрашивайте информацию, которая не используется функцией. Это минимизирует раскрытие данных и упрощает использование функции. |
Используйте названия параметров, которые отражают ожидаемые входные данные, например taskId, searchQuery или recipientEmail. |
Не используйте названия, похожие на full_context или conversation_history. Такие названия неоднозначны, поскольку агенты используют эти термины для собственного контекста и истории разговоров. Агент может неправильно их интерпретировать и передать в функцию большой объем ненужных данных, что приведет к их утечке. |
Используйте типы, поддерживаемые AppFunctions, вместо того чтобы определять собственные. Например, LocalDate, LocalTime, LocalDateTime или Instant для дат и времени, а Uri – для ссылок. Библиотека представляет эти типы одинаково в каждом приложении, поэтому агенты получают согласованный формат. Полный список поддерживаемых типов можно найти в разделе AppFunctionSerializable. |
|
| Если параметр не является существенным для цели функции, сделайте его необязательным. Это дает агенту гибкость, когда у него есть только частичная информация от пользователя. Подробнее о необязательных параметрах… | |
| Если AppFunction обновляет существующие ресурсы, убедитесь, что каждый параметр может различать значения "установить как null" и "не изменять". Пример приведен в разделе Обновление существующих ресурсов. | |
Чтобы агент мог открыть определенный экран в вашем приложении, верните PendingIntent для этого экрана. Агент может запустить PendingIntent как есть, не создавая намерение для вашего приложения. Создайте его с явным намерением и FLAG_IMMUTABLE, чтобы агент не мог изменить место назначения. |
Необязательные параметры
Необязательными могут быть только типы, допускающие значение NULL, примитивные типы, списки и массивы. Если агент опускает необязательный параметр, библиотека передает значение по умолчанию для типа (null, ноль, 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(),
)
}
Обновление существующих ресурсов
В следующем примере используется оболочка SetField, допускающая значение NULL, для каждого поля, которое агент может обновить. Нулевое значение SetField означает "не изменять", а SetField(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, что не позволяет определить тип сбоя. |