定义工具

借助工具,设备端智能体能够访问应用数据、与后端服务通信,并代表用户执行操作。

工具的工作原理

当用户提交提示时,AgentRunner 类会执行一个包含四个阶段的智能体循环:

  1. 观察:Gemini Nano 会根据已注册的工具声明(智能体的操作空间)分析用户的提示和当前对话上下文。
  2. 推理:模型确定是否需要使用应用内工具。如果需要工具,Gemini Nano 会发出结构化工具调用,其中包含函数名称和类型化实参。
  3. 执行:AgentRunner 实例会自动拦截此调用,并在应用中执行相应的 Kotlin 函数。
  4. 评估反馈:工具的输出会反馈给 Gemini Nano。模型会评估反馈,然后回答用户或调用其他工具,直到满足终止条件(完成目标或达到 maxTurns 限制)。

使用 Android AppFunctions 的声明式工具

建议使用 Android AppFunctions (androidx.appfunctions) 以声明方式定义工具。AppFunctions 提供了一个标准化的平台模型,用于声明具有自动架构生成的类型化应用内功能。

1. 添加 Gradle 依赖项

在模块的 build.gradle.kts 文件中,将 AppFunctions 工件与 genai-agent 一起添加:

dependencies {
    implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
    implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
    ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}

2. 使用 @AppFunctionDeclaration 注解为工具方法添加注解

使用 @AppFunctionDeclaration(isDescribedByKDoc = true) 注解为公共工具方法添加注解。参数说明记录在标准 KDoc @param 标记中。

AppFunctions 要求返回值必须是基元类型(例如 String、Int 或 Boolean)或使用 @AppFunctionSerializable 注解进行注解的结构化数据类。

package com.example.ai.agent.tools

import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
 * Structured details for an order.
 */
@AppFunctionSerializable
data class OrderDetails(
    val orderId: String,
    val status: String,
    val estimatedDelivery: String?,
    val itemCount: Int,
)

/**
 * Result returned when cancelling an order.
 */
@AppFunctionSerializable
data class CancelOrderResult(
    val orderId: String,
    val cancelled: Boolean,
    val message: String,
)

@AppFunctionServiceEntryPoint(
    serviceName = "OrderAppFunctionService",
    appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {

    private val orderRepository = OrderRepository()

    /**
     * Fetches details and delivery status for a customer order by ID.
     *
     * @param orderId The alphanumeric order identifier (for example, ORD-8492)
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun fetch_order_details(
        orderId: String
    ): OrderDetails = withContext(Dispatchers.IO) {
        val order = orderRepository.findOrder(orderId)
        if (order != null) {
            OrderDetails(
                orderId = order.id,
                status = order.status,
                estimatedDelivery = order.deliveryEstimate,
                itemCount = order.items.size,
            )
        } else {
            OrderDetails(
                orderId = orderId,
                status = "NOT_FOUND",
                estimatedDelivery = null,
                itemCount = 0,
            )
        }
    }

    /**
     * Requests a cancellation or return for an active order.
     *
     * @param orderId The order identifier to cancel
     * @param reason The reason provided by the user for cancellation
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun cancel_order(
        orderId: String,
        reason: String? = null
    ): CancelOrderResult = withContext(Dispatchers.IO) {
        val success = orderRepository.cancelOrder(orderId, reason)
        CancelOrderResult(
            orderId = orderId,
            cancelled = success,
            message = if (success) {
                "Order cancellation processed."
            } else {
                "Order cannot be cancelled at this stage."
            }
        )
    }
}

在应用的 AndroidManifest.xml 文件中注册生成的服务:

<service
    android:name=".OrderAppFunctionService"
    android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
    android:exported="true">
    <property
        android:name="android.app.appfunctions.schema"
        android:value="app_functions_schema.xsd" />
    <property
        android:name="android.app.appfunctions.v2"
        android:value="order_app_functions.xml" />
    <intent-filter>
        <action android:name="android.app.appfunctions.AppFunctionService" />
    </intent-filter>
</service>

3. 使用 ACCESS_LEVEL_SELF 常量限制调用方访问权限

AppFunctions 无需对系统或外部助理公开。 在 Android 中,访问权限控制通过调用方访问权限级别进行管理,而不是通过手动调用方身份检查进行管理。

默认情况下(使用 ACCESS_LEVEL_ANDROID_TRUSTED 常量),AppFunction 可由持有 EXECUTE_APP_FUNCTIONS 系统权限且经过平台认证的设备端助理发现和执行。不过,如果您要构建仅供应用内部代理使用的功能(例如检查用户购物车、访问本地令牌或执行应用内操作),则可以使用 ACCESS_LEVEL_SELF 常量将访问权限限制为仅限您自己的应用:

import androidx.appfunctions.AppFunctionAccessLevel
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import androidx.appfunctions.ExperimentalAppFunctionsApi
import androidx.appfunctions.metadata.AppFunctionMetadata

@AppFunctionServiceEntryPoint(
    serviceName = "OrderAppFunctionService",
    appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {

    private val orderRepository = OrderRepository()

    /**
     * Cancels an order. Restricted exclusively to this app's internal agent.
     */
    @OptIn(ExperimentalAppFunctionsApi::class)
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(
        level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
        isCompatEnforcementEnabled = true,
    )
    suspend fun cancel_order(orderId: String): CancelOrderResult {
        // Only your app's internal agent can invoke this function
        val success = orderRepository.cancelOrder(orderId)
        return CancelOrderResult(
            orderId = orderId,
            cancelled = success,
            message = if (success) "Cancelled." else "Failed."
        )
    }
}

该平台支持三种来电者访问权限级别:

  • ACCESS_LEVEL_SELF (100):严格限制为主机应用本身。外部助理、系统界面和其他应用无法通过 IPC 发现或调用该函数。对于敏感操作或仅应服务于应用内部代理的工具,请使用此级别。
  • ACCESS_LEVEL_SYSTEM (200):仅限具有 android.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM 权限的特权系统调用方(例如系统启动器或“最近用过”)和托管应用本身。
  • ACCESS_LEVEL_ANDROID_TRUSTED(300,默认):向持有 EXECUTE_APP_FUNCTIONS 权限的平台认证设备端代理、系统界面和宿主应用开放。

4. 向智能体注册工具

您可以使用 enableAppFunctions() 方法自动向代理注册 AppFunction,也可以使用 AppFunctionAgentTools.discover() 方法以编程方式注册:

使用 enableAppFunctions() 方法自动发现

对于在模型调用时自动执行 AppFunctions 的简单应用:

import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner

val agent = Agent.builder()
    .setName("OrderAssistant")
    .setInstructions("Assist users with order tracking.")
    .enableAppFunctions(true) // Automatically discovers all AppFunctions in your app package
    .setMaxTurns(10)
    .build()

val runner = AgentRunner(agent)

(可选)调用 addAppFunctionIds("fetch_order_details", "cancel_order") 方法,将发现范围严格限定为特定的 AppFunction ID,而不是公开所有应用内函数。

使用 AppFunctionAgentTools.discover() 方法进行程序化发现

当您的应用需要检查发现的 AppFunction、对其进行过滤或在向代理注册之前对其进行修饰时:

import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AppFunctionAgentTools

val discoveredTools = AppFunctionAgentTools.discover(
    enabledAppFunctionIds = listOf("fetch_order_details", "cancel_order") // Optional ID filter
)

val agent = Agent.builder()
    .setName("OrderAssistant")
    .setInstructions("Assist users with order tracking.")
    .addTools(discoveredTools)
    .build()

val runner = AgentRunner(agent)

修饰器模式和人机协同

对于敏感操作(例如取消订单、付款或删除用户数据),应通过要求用户确认来保持人机协同 (HITL)。添加到 Agent 类的所有工具都源自 Android AppFunctions,并且 AgentTool 类提供了一个委托构造函数 AgentTool(delegate),以便您可以使用装饰器模式封装发现的 AppFunction 工具:

import com.google.mlkit.genai.agent.AgentTool

class HitlConfirmationTool(
    delegate: AgentTool,
    private val onConfirm: suspend (toolName: String, args: Map<String, Any>) -> Boolean
) : AgentTool(delegate) {

    override suspend fun execute(args: Map<String, Any>): Map<String, Any?> {
        val confirmed = onConfirm(name, args)
        if (!confirmed) {
            return mapOf(
                "status" to "CANCELLED_BY_USER",
                "message" to "Action was declined by the user."
            )
        }
        return checkNotNull(delegate).execute(args)
    }
}

工具设计最佳实践

  • 限定操作空间:使用 addAppFunctionIds() 方法和 ACCESS_LEVEL_SELF 常量,将智能体的操作空间(向智能体公开的工具和权限集)限制在刚好足以完成其任务的范围内。操作空间过大会使设备端模型更容易出错,并消耗上下文窗口 token。
  • 在说明中明确表述:Gemini Nano 依赖于工具和参数说明来决定何时调用工具。清楚说明工具的用途,并提及预期的参数格式(例如日期或 ID)。
  • 返回结构化错误映射:在发生预期故障时,返回描述性错误映射(例如 mapOf("error" to "Order not found"))。这样,Gemini Nano 就可以向用户得体地解释问题。如果工具抛出意外异常,运行时会自动捕获该异常,并将出错提示反映回模型,以便模型进行自我修正。
  • 保持返回载荷的专注性:设备端模型的上下文窗口比服务器模型小。仅返回与回答用户查询相关的字段,而不是转储整个数据库行或原始网络响应。
  • 在 Dispatchers.IO 调度程序上运行 I/O:始终确保工具内的网络请求和 Room 数据库查询在 Dispatchers.IO 调度程序上执行,以防止阻塞协程调用方。

后续步骤