从零实现一个 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)
三个关键设计点:
- 多轮消息回填:
assistant(工具调用意图)和tool(执行结果)消息都要追加回messages,这是 ReAct 的核心——模型"看到"工具结果后才会继续推理; maxIterations上限:防止模型陷入循环调用,5 轮后强制兜底文案;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 应用体验的核心差异点。
六、踩坑记录(真实经历)
- 没有对话历史:当前实现每次请求都是全新 messages,模型不记得上一轮对话。做多轮对话需持久化会话历史;
- API Key 泄露:曾在 git 历史中硬编码 Key 并推送到公开仓库,最终通过重建仓库 + force push + 平台吊销 Key 解决。教训:从第一天起就用
local.properties+ BuildConfig 注入; - 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