从零实现一个 Android AI Agent:ReAct + Function Calling 实战

24 阅读4分钟

从零实现一个 Android AI Agent:ReAct + Function Calling 实战

本文配套开源项目:AI Agent Demo(Apache 2.0 协议) 技术栈:Kotlin + Jetpack Compose + MVVM + Retrofit + DeepSeek API

一、为什么要在手机上做 AI Agent

2025 年以来,"AI Agent" 是开发者圈最热的概念之一。市面上的教程大多是 Python + 命令行 Demo,但在真实用户场景中,手机才是 Agent 最自然的载体:查定位、看设备信息、打开设置、发短信——这些能力天然属于移动端。

本文以一个完整的 Android 开源项目为例,拆解 AI Agent 的两大核心机制:ReAct 循环Function Calling

二、整体架构

项目采用标准 Android 分层架构,Agent 核心逻辑放在领域层:

UI 层(Compose + MVVM)
     ChatViewModel 收集 Flow<AgentState>
   
领域层(AgentManager)           Agent"大脑"
     ReAct 循环:思考  调用工具  观察结果  再思考
   
工具层(ToolExecutor)           Agent"手脚"
     定位 / 设备信息 / 天气 / 设置 / 短信
   
数据层(Retrofit + OkHttp)      对话 API + 各工具的数据来源

三、核心一:ReAct 循环

ReAct(Reasoning + Acting)是当前 LLM Agent 的主流范式:模型不直接给出最终答案,而是边推理边行动——先决定调用哪个工具,拿到工具结果后再继续推理,直到能给出最终回答。

实现一个 ReAct 循环只需要 60 行左右的核心代码:

fun run(userInput: String): Flow<AgentState> = flow {
    // 初始会话:系统提示词 + 用户输入
    val messages = mutableListOf<Message>(
        Message(role = "system", content = "你是一个Android手机助手..."),
        Message(role = "user", content = userInput)
    )

    val tools = toolExecutor.getToolDefinitions()
    var maxIterations = 5          // 防止模型无限调用工具
    var finalAnswer = ""

    while (maxIterations-- > 0) {
        emit(AgentState.Thinking("🧠 思考中..."))

        // tool_choice = "auto":由模型自行决定是否调用工具
        val request = ChatRequest(
            messages = messages, tools = tools, tool_choice = "auto"
        )
        val response = apiService.chat(request)
        val assistantMsg = response.choices.first().message

        // 模型未要求调用工具 → 已给出最终回答
        if (assistantMsg.tool_calls.isNullOrEmpty()) {
            finalAnswer = assistantMsg.content ?: "抱歉,我没有理解你的问题"
            break
        }

        // 记录模型的工具调用意图
        messages.add(Message(
            role = "assistant", content = assistantMsg.content,
            tool_calls = assistantMsg.tool_calls
        ))

        // 执行每个工具,并把结果回传
        for (toolCall in assistantMsg.tool_calls) {
            emit(AgentState.ToolCalling("🔧 调用工具: ${toolCall.function.name}"))
            val result = toolExecutor.executeTool(
                toolCall.function.name,
                parseArguments(toolCall.function.arguments)
            )
            messages.add(Message(
                role = "tool", content = result,
                tool_call_id = toolCall.id
            ))
        }
    }
    emit(AgentState.Completed(finalAnswer))
}.flowOn(Dispatchers.IO)

三个关键设计点:

  1. 多轮消息回填assistant(工具调用意图)和 tool(执行结果)消息都要追加回 messages,这是 ReAct 的核心——模型"看到"工具结果后才会继续推理;
  2. maxIterations 上限:防止模型陷入循环调用,5 轮后强制兜底文案;
  3. Flow<AgentState> 而非回调:Agent 执行是异步长任务,用 Flow 天然获得结构化并发、生命周期安全和线程切换(flowOn(IO)),UI 层 collect 即可响应式更新界面。

四、核心二:Function Calling 工具注册

工具以 JSON Schema 形式声明给模型,模型据此决定"调哪个工具、传什么参数":

fun getToolDefinitions(): List<ToolDefinition> = listOf(
    ToolDefinition(
        type = "function",
        function = ToolFunction(
            name = "get_weather",
            description = "查询指定城市的实时天气和今日预报",
            parameters = ToolParameters(
                properties = mapOf(
                    "city" to ToolProperty("string", "城市名称,例如:上海")
                ),
                required = listOf("city")
            )
        )
    ),
    // 定位、设备信息、设置、短信等工具同构声明...
)

Schema 质量决定 Agent 质量——description 写清楚"什么时候用"(如"当用户询问天气时,调用 get_weather"),模型才能正确路由。工具执行本身则用 try-catch 包裹降级,单个工具失败不中断整个循环。

五、核心三:状态流驱动 UI

Agent 运行过程的四个状态(思考中 / 调用工具 / 完成 / 出错)用 sealed class 建模:

sealed class AgentState {
    data class Thinking(val text: String) : AgentState()
    data class ToolCalling(val text: String) : AgentState()
    data class Completed(val result: String) : AgentState()
    data class Error(val message: String) : AgentState()
}

ViewModel 收集后更新 ChatUiState,Compose 界面据此渲染"🧠 思考中..."提示、工具调用过程气泡和最终回复——用户能直观看到 Agent 的"思考过程",这也是 AI 应用体验的核心差异点。

六、踩坑记录(真实经历)

  1. 没有对话历史:当前实现每次请求都是全新 messages,模型不记得上一轮对话。做多轮对话需持久化会话历史;
  2. API Key 泄露:曾在 git 历史中硬编码 Key 并推送到公开仓库,最终通过重建仓库 + force push + 平台吊销 Key 解决。教训:从第一天起就用 local.properties + BuildConfig 注入
  3. UI 线程阻塞:工具执行涉及网络与系统调用,必须整体 flowOn(IO),UI 只负责 collect。

七、总结

AI Agent 的核心并不神秘:ReAct 循环(60 行)+ 工具 Schema 注册 + 状态流。掌握这套模式后,你可以在任何平台(Android / iOS / Web / 桌面)快速复制出 Agent 应用。

完整代码与仿微信长按菜单、热词栏等细节见仓库:AI Agent Demo,欢迎 Star 与交流。


关键词:AI Agent、ReAct、Function Calling、DeepSeek、Android、Jetpack Compose、Kotlin