Diretrizes de qualidade das AppFunctions

As AppFunctions são o equivalente prático dos servidores MCP no Android. Elas permitem compartilhar partes da lógica do app com agentes e assistentes de IA.

Os agentes usam descrições para selecionar a AppFunction que atende a uma solicitação. Esse processo de seleção funciona como a descoberta tradicional de ferramentas do MCP, em que os aplicativos de IA usam o servidor MCP e as descrições das ferramentas para orientar as escolhas. Seu app atua como o servidor MCP do Android, e cada AppFunction atua como uma das ferramentas dele. Forneça as seguintes descrições:

  • Descrição do app (o servidor MCP do Android). A descrição do app é o primeiro ponto de entrada para que os agentes entendam o que ele pode fazer. Para ajudar os agentes a descobrir seu app, comece com o domínio principal e liste de forma concisa as tarefas que as AppFunctions permitem que ele realize.
    • Escreva a descrição do app no arquivo XML de metadados do app.
    • Use a descrição do app para descrever o que as descrições de funções individuais não podem, como a ordem em que as funções devem ser chamadas.
  • Descrições de AppFunction (as ferramentas). Cada descrição de função explica o comportamento de uma AppFunction. As descrições de funções geralmente são estruturadas em torno de um verbo, como atualizar, criar ou reservar, e um recurso, como configurações, canal ou sala.

Diretrizes para descrição de funções

Os agentes usam as descrições de função para decidir qual função se encaixa em uma solicitação e como chamá-la.

Escreva descrições concisas e inequívocas de AppFunction

Quanto menos ambígua for uma descrição, mais confiável será a escolha da função certa para uma solicitação por um agente. A tabela a seguir lista o que fazer e o que evitar nas descrições de funções:

O que fazer O que não fazer
Descreva a finalidade da função e o resultado da execução dela em uma linguagem clara e neutra. Não use descrições como comandos. Comandos como "Você PRECISA dizer" e "Você precisa pedir ao usuário" instruem o agente sobre como se comportar, em vez de descrever a função.
Coloque as informações mais importantes no início das descrições e seja conciso. Não preencha as descrições com frases de efeito, como "Esta função é usada para". As palavras de preenchimento deixam uma descrição mais longa sem torná-la mais clara.
Refletir com precisão o que o código faz, incluindo os parâmetros e a saída. Por exemplo, uma função descrita como somente leitura não deve gravar ou excluir dados. Não use nomes enganosos, como uma função chamada fetchData que exclui dados do usuário.
Escreva descrições de funções no formato KDoc, incluindo parâmetros e o tipo de retorno. Siga o guia de estilo do Kotlin para nomear e criar descrições compreensíveis sem conhecimento da implementação. Não use letras maiúsculas em excesso ou caracteres especiais para chamar atenção para partes da descrição, como **CRITICAL** ou VERY IMPORTANT.
Use nomes legíveis por humanos para AppFunctions.
Seja claro e direto sobre o que cada AppFunction faz. Não dê instruções de procedimento que envolvam outras funções, como "Primeiro, pesquise o contato". Os agentes usam a descrição do app para planejar chamadas entre funções. Portanto, descreva essa ordem lá.
Descreva o que o agente pode esperar da função, com uma linguagem como "Se você tiver X, então Y".
Suponha que os agentes tenham as próprias instruções para usar as AppFunctions em geral. A descrição da função diz o que ela faz, e as instruções do agente explicam como usá-la. Não tente substituir ou incorporar instruções no nível do agente nas descrições de funções.

Escrever com o encapsulamento em mente

Mantenha cada descrição de função independente, para que ela oriente o agente a usar essa função e nenhuma outra. A tabela a seguir lista as diretrizes de encapsulamento:

O que fazer O que não fazer
Escreva descrições de funções específicas e focadas, com um propósito único e bem definido. Por exemplo, "criar, encontrar e excluir tarefas pessoais". Não escreva uma descrição muito ampla, o que pode fazer com que o agente chame sua função em momentos inadequados ou por motivos errados.
Não crie AppFunctions multiuso nem exponha muitas AppFunctions para o mesmo caso de uso. Os agentes têm mais chances de escolher a função errada quando várias funções têm intents semelhantes.
Não faça referência nem tente influenciar o comportamento de outras AppFunctions no seu app ou em outros apps. Isso pode criar dependências e efeitos colaterais não intencionais.

Ações permitidas e compartilhamento de dados pessoais

Siga estas diretrizes para proteger a privacidade e a segurança do usuário quando os agentes usarem as funções do seu app:

O que fazer O que não fazer
Realize apenas tarefas seguras e não destrutivas que o usuário normalmente faria no app, em que o agente pode fornecer parâmetros claros da solicitação do usuário, como criar uma tarefa com um título ou pesquisar notas.
Compartilhe apenas informações que não sejam altamente pessoais ou confidenciais ou dados que o usuário consente explicitamente em compartilhar para a ação. Não dê ao agente acesso amplo aos dados do usuário ou aos recursos do sistema.
Não crie AppFunctions que exijam ou retornem senhas, detalhes financeiros, chaves privadas ou outras informações pessoais altamente confidenciais.
Realizar ações solicitadas por um usuário, mesmo que sejam executadas mais tarde ou em segundo plano, por exemplo, enviar uma mensagem em um horário escolhido pelo usuário. Não realize ações que o usuário não pediu, como tarefas que começam sozinhas ou que vão além do pedido.
Concentre-se em funcionalidades que se beneficiam da linguagem natural, como tarefas que são mais fáceis de expressar em uma conversa do que pela navegação na interface, por exemplo: "[Agente de IA], você pode me ajudar a encontrar alguns horários disponíveis na próxima semana na minha agenda e sugerir um voo para Chicago em um desses dias?"
Em formatos além de smartphones, respeite a privacidade do usuário em dispositivos compartilhados, como TVs, Android Auto e Android Automotive OS. Não divulgue informações nem realize ações que a interface do app não oferece suporte nesse formato.
Identifique de forma clara e exclusiva o propósito da função. Não finja ser outra ferramenta ou serviço. O nome e a descrição da função não podem ser confusamente semelhantes a outras ferramentas, o que pode fazer com que o agente selecione sua função por engano.

Design de parâmetros e valores de retorno

O design cuidadoso de parâmetros e valores de retorno evita ambiguidades e reduz a chance de chamadas inválidas. Siga estas convenções:

O que fazer O que não fazer
Defina parâmetros apenas para os dados de que sua função precisa. Não solicite informações que a função não usa. Isso minimiza a exposição de dados e simplifica o uso da função.
Use nomes de parâmetros que reflitam a entrada esperada, como taskId, searchQuery ou recipientEmail. Não use nomes como full_context ou conversation_history. Esses nomes são ambíguos porque os agentes usam esses termos para o próprio contexto e histórico de conversas. Um agente pode interpretá-los incorretamente e transmitir uma grande quantidade de dados desnecessários para sua função, levando à exfiltração de dados.
Use os tipos compatíveis com as AppFunctions em vez de definir os seus, por exemplo, LocalDate, LocalTime, LocalDateTime ou Instant para datas e horários e Uri para links. A biblioteca representa esses tipos da mesma forma em todos os apps, para que os agentes tenham um formato consistente. Para ver a lista completa de tipos compatíveis, consulte AppFunctionSerializable.
Se um parâmetro não for essencial para a finalidade da função, torne-o opcional. Isso dá flexibilidade ao agente quando ele tem apenas informações parciais do usuário. Para mais informações, consulte Parâmetros opcionais.
Se a AppFunction atualizar recursos, verifique se cada parâmetro pode distinguir entre "definir como nulo" e "não mudar". Para ver um exemplo, consulte Atualizações de recursos atuais.
Para permitir que o agente abra uma tela específica no seu app, retorne um PendingIntent para essa tela. O agente pode iniciar o PendingIntent como está, sem criar uma intent para o app. Crie-o com uma intent explícita e FLAG_IMMUTABLE, para que o agente não possa mudar o destino.

Parâmetros opcionais

Somente os tipos anuláveis, primitivos, de lista e de matriz podem ser opcionais. Quando o agente omite um parâmetro opcional, a biblioteca transmite o valor padrão do tipo, null, zero, false ou vazio, em vez do padrão declarado. Resolva os padrões significativos no corpo da função, como no exemplo a seguir:

/**
 * 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(),
    )
}

Atualizações de recursos atuais

O exemplo a seguir usa um wrapper SetField anulável para cada campo que o agente pode atualizar. Um SetField nulo significa "não mudar", e SetField(null) significa "definir como nulo":

/** 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,
    )
}

Tratamento de erros

Quando uma chamada falha, o agente recebe a exceção gerada pela função, incluindo o tipo e a mensagem. Siga estas convenções para que o agente possa se recuperar da falha ou informá-la ao usuário:

O que fazer O que não fazer
Lance a subclasse AppFunctionException que corresponde à causa, com uma mensagem que descreve o problema em linguagem simples, por exemplo, AppFunctionInvalidArgumentException quando um argumento não é válido ou AppFunctionElementNotFoundException quando um item solicitado não existe. O tipo informa ao agente que tipo de falha ocorreu, e a mensagem fornece os detalhes necessários para a recuperação, como corrigir um argumento ou informar o usuário. O exemplo de updateNote em Atualizações de recursos mostra esse padrão. Não deixe que outras exceções escapem da função. O agente os recebe como AppFunctionAppUnknownException, que não identifica o tipo de falha.