Lineamientos de calidad de AppFunctions

Las AppFunctions son el equivalente práctico de los servidores de MCP en Android. Te permiten compartir partes de la lógica de tu app con agentes y asistentes de IA.

Los agentes usan descripciones para seleccionar la AppFunction que entrega una solicitud. Este proceso de selección funciona como el descubrimiento tradicional de herramientas de MCP, en el que las aplicaciones de IA usan las descripciones de las herramientas y del servidor de MCP para guiar sus elecciones. Tu app actúa como el servidor de MCP de Android, y cada AppFunction actúa como una de sus herramientas. Proporciona las siguientes descripciones:

  • Descripción de la app (el servidor de MCP de Android). La descripción de la app es el primer punto de entrada para que los agentes comprendan lo que puede hacer. Para ayudar a los agentes a descubrir tu app, comienza con su dominio principal y enumera de forma concisa las tareas que tus AppFunctions le permiten realizar.
    • Escribe la descripción de la app en el archivo XML de metadatos de la app.
    • Usa la descripción de la app para describir lo que no pueden hacer las descripciones de las funciones individuales, como el orden en el que se deben llamar las funciones.
  • Descripciones de AppFunction (las herramientas) Cada descripción de función explica el comportamiento de una AppFunction. Las descripciones de las funciones suelen estructurarse en torno a un verbo, como actualizar, crear o reservar, y un recurso, como configuración, canal o sala.

Lineamientos para la descripción de funciones

Los agentes se basan en las descripciones de las funciones para decidir qué función se adapta a una solicitud y cómo llamarla.

Escribe descripciones de AppFunction concisas y sin ambigüedades

Cuanto menos ambigua sea una descripción, más confiable será que un agente elija la función correcta para una solicitud. En la siguiente tabla, se indica qué hacer y qué evitar en las descripciones de las funciones:

Qué debes hacer Qué no debe hacer
Describe el propósito de la función y el resultado de ejecutarla, en un lenguaje claro y neutral. No uses descripciones como instrucciones. Los comandos como "DEBES decir" y "Debes pedirle al usuario" le indican al agente cómo comportarse en lugar de describir la función.
Coloca la información clave al principio de las descripciones y haz que sean concisas. No agregues descripciones con relleno, como "Esta función se usa para". El contenido de relleno hace que una descripción sea más larga sin que sea más clara.
Reflejar con precisión lo que hace el código, incluidos sus parámetros y resultados Por ejemplo, una función descrita como de solo lectura no debería escribir ni borrar datos. No uses nombres engañosos, como una función llamada fetchData que borre datos del usuario.
Escribe descripciones de funciones en formato KDoc, incluidos los parámetros y el tipo de datos que se devuelve. Sigue la guía de estilo de Kotlin para asignar nombres y haz que las descripciones sean comprensibles sin necesidad de conocer la implementación. No uses mayúsculas ni caracteres especiales de forma excesiva para llamar la atención sobre partes de la descripción, como **CRITICAL** o VERY IMPORTANT.
Usa nombres legibles por humanos para las AppFunctions.
Sé claro y directo sobre lo que hace cada AppFunction. No proporciones instrucciones de procedimiento que involucren otras funciones, como "Primero, busca el contacto". Los agentes usan la descripción de tu app para planificar llamadas entre funciones, así que describe ese orden allí.
Describe lo que el agente puede esperar de la función, con un lenguaje como "Si tienes X, entonces Y".
Supón que los agentes tienen sus propias instrucciones para usar AppFunctions en general. La descripción de la función indica lo que hace, y las instrucciones del agente abarcan cómo usarla. No intentes anular ni incorporar instrucciones a nivel del agente en las descripciones de las funciones.

Escribe con la encapsulación en mente

Mantén cada descripción de función independiente, de modo que guíe al agente en el uso de esa función y no de otra. En la siguiente tabla, se indican los lineamientos de encapsulamiento:

Qué debes hacer Qué no debe hacer
Escribe descripciones de funciones que sean específicas y enfocadas, con un solo propósito bien definido. Por ejemplo, "crear, buscar y borrar tareas personales". No escribas una descripción demasiado amplia, ya que esto puede hacer que el agente llame a tu función en momentos inapropiados o por motivos incorrectos.
No crees AppFunctions de uso general ni expongas muchas AppFunctions para el mismo caso de uso. Es más probable que los agentes elijan la función incorrecta cuando varias funciones tienen intents similares.
No hagas referencia al comportamiento de otras AppFunctions en tu app o en otras apps, ni intentes influir en él. Esto puede crear dependencias y efectos secundarios no deseados.

Acciones permitidas y uso compartido de datos

Sigue estos lineamientos para proteger la privacidad y la seguridad de los usuarios cuando los agentes usen las funciones de tu app:

Qué debes hacer Qué no debe hacer
Realiza solo tareas seguras y no destructivas que el usuario normalmente haría en tu app, en las que el agente pueda proporcionar parámetros claros de la solicitud del usuario, por ejemplo, crear una tarea con un título o buscar notas.
Comparte solo información que no sea muy personal o confidencial, o datos que el usuario acepte compartir de forma explícita para la acción. No le otorgues al agente acceso amplio a los datos del usuario ni a los recursos del sistema.
No diseñes AppFunctions que requieran o devuelvan contraseñas, detalles financieros, claves privadas ni otra información personal altamente sensible.
Realizar acciones que un usuario solicitó, incluso cuando se ejecutan más tarde o en segundo plano, por ejemplo, enviar un mensaje en un momento que el usuario eligió No realices acciones que el usuario no haya solicitado, como tareas que comienzan por sí solas o que van más allá de la solicitud.
Enfócate en las funciones que se benefician del lenguaje natural, como las tareas que son más fáciles de expresar en una conversación que a través de la navegación por la IU, por ejemplo, "[agente de IA], ¿puedes ayudarme a encontrar algunos horarios disponibles la próxima semana en mi calendario y, luego, sugerirme un vuelo a Chicago en uno de esos días?".
En factores de forma que no sean teléfonos, respeta la privacidad del usuario en dispositivos compartidos, como TVs, Android Auto y el SO Android Automotive. No divulgues información ni realices acciones que la IU de tu app no admita en ese factor de forma.
Identifica de forma clara y única el propósito de tu función. No finjas ser otra herramienta o servicio. El nombre y la descripción de tu función no deben ser confusamente similares a los de otras herramientas, lo que podría hacer que el agente seleccione tu función por error.

Diseño de parámetros y valores que se muestran

El diseño cuidadoso de los parámetros y los valores de retorno evita la ambigüedad y reduce la posibilidad de llamadas no válidas. Sigue estas convenciones:

Qué debes hacer Qué no debe hacer
Define parámetros solo para los datos que necesita tu función. No solicites información que la función no use. Esto minimiza la exposición de los datos y simplifica el uso de la función.
Usa nombres de parámetros que reflejen la entrada esperada, como taskId, searchQuery o recipientEmail. No uses nombres como full_context o conversation_history. Estos nombres son ambiguos porque los agentes usan estos términos para su propio contexto y el historial de conversaciones. Un agente podría malinterpretarlos y pasar una gran cantidad de datos innecesarios a tu función, lo que provocaría el robo de datos.
Usa los tipos que admite AppFunctions en lugar de definir los tuyos, por ejemplo, LocalDate, LocalTime, LocalDateTime o Instant para fechas y horas, y Uri para vínculos. La biblioteca representa estos tipos de la misma manera en cada app, por lo que los agentes obtienen un formato coherente. Para obtener la lista completa de los tipos admitidos, consulta AppFunctionSerializable.
Si un parámetro no es esencial para el propósito de la función, haz que sea opcional. Esto le da flexibilidad al agente cuando solo tiene información parcial del usuario. Para obtener más información, consulta Parámetros opcionales.
Si tu AppFunction actualiza recursos existentes, asegúrate de que cada parámetro pueda distinguir entre "establecer como nulo" y "no cambiar". Para ver un ejemplo, consulta Actualizaciones de recursos existentes.
Para permitir que el agente abra una pantalla específica en tu app, devuelve un objeto PendingIntent para esa pantalla. El agente puede iniciar el PendingIntent tal como está, sin compilar un intent para tu app. Créalo con un intent explícito y FLAG_IMMUTABLE para que el agente no pueda cambiar el destino.

Parámetros opcionales

Solo los tipos anulables, primitivos, de lista y de array pueden ser opcionales. Cuando el agente omite un parámetro opcional, la biblioteca pasa el valor predeterminado del tipo (null, cero, false o vacío) en lugar del valor predeterminado que declaras. Resuelve los valores predeterminados significativos en el cuerpo de la función, como en el siguiente ejemplo:

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

Actualizaciones de los recursos existentes

En el siguiente ejemplo, se usa un wrapper SetField que acepta valores nulos para cada campo que el agente puede actualizar. Un SetField nulo significa "no cambiar" y SetField(null) significa "establecer 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,
    )
}

Manejo de errores

Cuando falla una llamada, el agente recibe la excepción que arroja tu función, incluidos su tipo y mensaje. Sigue estas convenciones para que el agente pueda recuperarse de la falla o informarla al usuario:

Qué debes hacer Qué no debe hacer
Lanza la subclase AppFunctionException que coincida con la causa, con un mensaje que describa el problema en lenguaje sencillo, por ejemplo, AppFunctionInvalidArgumentException cuando un argumento no es válido o AppFunctionElementNotFoundException cuando un elemento solicitado no existe. El tipo le indica al agente qué tipo de falla se produjo, y el mensaje le brinda los detalles que necesita para recuperarse, como corregir un argumento o informar al usuario. El ejemplo de updateNote en Actualizaciones de recursos existentes muestra este patrón. No permitas que otras excepciones escapen de tu función. El agente las recibe como AppFunctionAppUnknownException, que no identifica el tipo de falla.