Agent智能体:让AI自己调API干活——从Android Service到AI Agent的思维跃迁

Android工程师的AI开发实战系列 · 第2/4篇

用Android思维理解RAG、Agent和微调,从移动端老兵到AI开发者的跨界之路

第1篇:RAG:给大模型装一个靠谱的「本地数据库」------Android工程师秒懂的检索增强生成

第2篇:Agent智能体:让AI自己调API干活------从Android Service到AI Agent的思维跃迁(本篇)

⏳ 第3篇:微调:让通用大模型变成你的「专属定制ROM」------从AOSP到LoRA的迁移学习

⏳ 第4篇:RAG+Agent+微调组合拳:搭建一个完整的AI驱动Android开发助手

上一篇我们聊了RAG------给大模型接一个"外挂知识库"。当时有读者问我:RAG能让AI"知道"更多东西,但能不能让它"做"更多事情?比如让它自己去查TAPD上的Bug,读一下相关代码,然后帮我写个周报?

说实话,这个问题第一次被问到的时候,我脑子里闪过的画面是:一个Service在后台默默干活,处理完了通过Callback通知你。

然后我意识到------这不就是Agent吗?

Agent到底是个什么东西

先抛开所有花哨的定义。在我看来,Agent就是一个能自己想、自己干的AI系统。普通的ChatGPT对话是"你问一句它答一句",而Agent是"你给它一个目标,它自己拆解步骤、调用工具、处理中间结果,最后给你一个完整的交付物"。

用Android的话来类比:

普通LLM对话

类比 → Activity:用户点一下,响应一下,交互驱动

Agent智能体

类比 → Service + WorkManager:接到任务后自主运行,中间不需要用户干预,完成后回调通知

更准确地说,Agent的演进路径和Android后台任务的演进惊人地相似:

AI领域 Android类比 特点
ChatBot Activity 一问一答
Chain(链式调用) IntentService 固定流程
Agent Service+WM 自主决策
Multi-Agent 多进程IPC 协作分工

ReAct:Agent的Handler消息循环

Agent最核心的运行范式叫ReAct(Reasoning + Acting)。我第一次看到这个概念的时候,直觉反应是:这不就是Handler的消息循环吗?

Android的Handler循环:取消息 → 判断类型 → 分发处理 → 产生新消息 → 继续循环

Agent的ReAct循环:观察环境 → 推理思考 → 选择行动 → 获取结果 → 继续循环

来看一个具体例子。假设你让Agent"帮我查一下项目里最近一周的高优Bug数量":

Thought: 需要调TAPD接口查Bug

Action: 调用search_bugs工具

Observation: 返回12条Bug

Thought: 需要按优先级过滤

Answer: 高优Bug 5个,分别是...

看出来了吗?Agent不是一次就把事情做完的,它是迭代式的------每一步都基于上一步的结果来决定下一步该干什么。就像Handler不断从MessageQueue里取消息处理一样,Agent不断从环境反馈中获取信息,推理下一步动作。

Function Calling:AI的Intent系统

Agent要"干活",就需要工具。这些工具怎么告诉AI呢?答案是Function Calling------我第一次看到这套机制的时候,整个人都精神了:这TMD不就是Intent系统吗?

Android里你要启动一个Activity或Service,需要声明IntentFilter:

复制代码
// Android IntentFilter声明
<intent-filter>
  <action
    android:name=
    "com.app.SEARCH_BUG"
  />
  <data
    android:scheme=
    "tapd"
    android:host=
    "bugs"
  />
</intent-filter>

而在Agent的世界里,你要给AI定义一个工具,长这样:

复制代码
// Function Calling 工具定义
{
  "name": "search_bugs",
  "description":
    "搜索TAPD上的Bug列表",
  "parameters": {
    "type": "object",
    "properties": {
      "project_id": {
        "type": "string",
        "description":
          "项目ID"
      },
      "priority": {
        "type": "string",
        "enum": [
          "high",
          "medium",
          "low"
        ]
      }
    }
  }
}

对比一下:

概念 Android Agent
声明能力 IntentFilter Tool Schema
传递参数 Bundle/Extra JSON参数
路由分发 PackageManager LLM推理
返回结果 onActivityResult Tool Response

区别在哪?Android的Intent路由是确定性的------PackageManager查注册表,找到匹配的组件就分发。而Agent的路由是概率性的------LLM根据语义理解来选择该调哪个工具。这既是它强大的地方(灵活),也是它坑爹的地方(不稳定)。

实战:用Kotlin写一个开发助手Agent

光说概念没意思,来写代码。我们搭一个能"查Bug→读代码→写周报"的Agent。作为Android工程师,我选择用Kotlin来实现(毕竟这是我们最熟悉的语言):

定义工具接口

复制代码
// 工具定义,像不像AIDL?
interface AgentTool {
    val name: String
    val description: String
    val parameters:
        JsonSchema

    suspend fun execute(
        args: JsonObject
    ): ToolResult
}

// TAPD Bug查询工具
class SearchBugsTool(
    private val tapd:
        TapdClient
) : AgentTool {

    override val name =
        "search_bugs"
    override val description =
        "搜索项目Bug列表"

    override suspend fun execute(
        args: JsonObject
    ): ToolResult {
        val projectId =
            args["project_id"]
                .asString()
        val bugs =
            tapd.searchBugs(
                projectId
            )
        return ToolResult(
            bugs.toJson()
        )
    }
}

Agent核心循环

接下来是最关键的部分------Agent的执行循环。我把它写成了一个类似ViewModel的结构,因为逻辑真的很像:

复制代码
class DevAssistantAgent(
    private val llm:
        LlmClient,
    private val tools:
        List<AgentTool>,
    private val maxSteps:
        Int = 10
) {
    // 对话历史=短期记忆
    // 类比ViewModel里的State
    private val messages =
        mutableListOf<
            Message>()

    suspend fun run(
        task: String
    ): String {
        messages += Message(
            role = "user",
            content = task
        )

        repeat(maxSteps) {
            // 1.调LLM推理
            val resp = llm.chat(
                messages,
                tools.toSchema()
            )

            // 2.没有工具调用=完成
            if (resp.toolCalls
                    .isEmpty()) {
                return resp.content
            }

            // 3.执行工具调用
            messages += resp.toMsg()
            for (call in
                resp.toolCalls) {
                val tool =
                    tools.find {
                        it.name ==
                            call.name
                    } ?: continue
                val result =
                    tool.execute(
                        call.args
                    )
                messages += Message(
                    role = "tool",
                    content =
                        result.data,
                    toolCallId =
                        call.id
                )
            }
        }
        return "超过最大步数限制"
    }
}

关键洞察:注意到 maxSteps 这个参数了吗?这就是Agent的"ANR超时保护"。没有它,Agent可能会无限循环下去。这和Android设置5秒ANR超时是同一个设计思路------你不能信任任何执行路径总是能正确终止。

Agent的记忆:ViewModel vs Room

Agent有两种记忆,我发现它们完美对应Android的数据持久化层级:

短期记忆(Working Memory):就是当前对话上下文。每轮对话的消息列表,Agent用它来理解"我之前做了什么"。这就像ViewModel里的StateFlow------进程活着就在,进程死了就没了。

长期记忆(Long-term Memory):跨会话持久化的信息。比如"用户偏好用Kotlin"、"项目用的是MVI架构"。这就是Room数据库------需要主动存,重启后还能读。

复制代码
// Agent记忆系统
class AgentMemory(
    private val db:
        MemoryDatabase
) {
    // 短期=ViewModel State
    private val shortTerm =
        mutableListOf<
            Message>()

    // 长期=Room持久化
    suspend fun remember(
        key: String,
        value: String
    ) {
        db.memoryDao()
            .upsert(
                MemoryEntity(
                    key, value
                )
            )
    }

    // 检索相关记忆(RAG!)
    suspend fun recall(
        query: String
    ): List<Memory> {
        return db.memoryDao()
            .searchSimilar(
                query
            )
    }
}

看到recall方法了吗?没错,这就是上一篇讲的RAG!Agent的长期记忆检索本质上就是一个RAG系统。第一篇和第二篇在这里串起来了。

多Agent协作:Android多模块通信的翻版

单个Agent能力有限,复杂任务需要多个Agent协作。这像极了Android大型项目的多模块架构------每个模块独立负责一块业务,通过Router或EventBus通信。

我见过的多Agent协作模式,基本能映射到Android的模块通信方案:

协作模式 Android类比 适用场景
主从式(Orchestrator) ARouter统一路由 有明确主流程
对等协商式 EventBus广播 无中心决策
流水线式 RxJava操作符链 数据逐步加工
竞争式 多Provider优先级 择优选方案

来看一个"主从式"多Agent的实现,用协程写起来非常自然:

复制代码
// 多Agent编排器
class OrchestratorAgent(
    private val workers:
        Map<String,
            Agent>
) {
    suspend fun dispatch(
        task: String
    ): String {
        // 1.规划子任务
        val plan =
            planSubTasks(task)

        // 2.并发分发
        val results =
            coroutineScope {
            plan.map { sub ->
                async {
                    workers[sub.type]
                        !!.run(
                            sub.desc
                        )
                }
            }.awaitAll()
        }

        // 3.汇总结果
        return synthesize(
            results
        )
    }
}

有没有感觉很像WorkManager的链式任务?beginWith(taskA).then(taskB, taskC).then(finalTask)------先规划,再并发执行,最后汇总。底层思维是完全一样的。

Agent的稳定性:和网络请求容错一模一样

最后聊聊Agent工程化的核心问题------稳定性。这是大部分教程不会讲的,但却是线上跑Agent最头疼的事。

我列几个典型问题和对应的Android容错策略:

幻觉(编造工具调用参数)

Agent可能会编造不存在的参数值,就像用户传了个非法的Intent Extra。解法一样:校验输入

复制代码
// 工具执行前校验参数
suspend fun safeExecute(
    tool: AgentTool,
    args: JsonObject
): ToolResult {
    // 校验,像Intent参数检查
    val validation =
        tool.validate(args)
    if (!validation.isValid) {
        return ToolResult(
            error =
            "参数错误:${
            validation.msg}"
        )
    }
    return try {
        withTimeout(
            30_000
        ) {
            tool.execute(args)
        }
    } catch (e: Exception) {
        ToolResult(
            error = e.message
        )
    }
}

无限循环(Agent反复调同一个工具)

这就是Agent版的死循环。除了前面提到的maxSteps限制,还可以加"重复检测":

复制代码
// 检测Agent是否在"转圈"
fun detectLoop(
    history:
        List<ToolCall>
): Boolean {
    val recent =
        history.takeLast(3)
    // 连续3次调同一工具+同参数
    return recent.size == 3
        && recent.distinct()
            .size == 1
}

降级策略

这是我觉得最有Android味的一个设计。我们做网络请求的时候不是经常这样吗------先试主接口,失败了降级到缓存,再不行显示兜底UI。Agent一模一样:

复制代码
// Agent降级策略
suspend fun runWithFallback(
    task: String
): String {
    return try {
        // L1: 完整Agent推理
        agent.run(task)
    } catch (e: AgentLoopEx) {
        // L2: 简化为单步Chain
        simpleChain.run(task)
    } catch (e: Exception) {
        // L3: 降级为纯LLM回答
        llm.chat(task)
    }
}

Agent框架选型:Hilt vs Koin式的纠结

最后快速过一下主流Agent框架。我对比的维度和当年选DI框架一样------灵活性 vs 上手成本 vs 社区生态

框架 类比 特点
LangChain Dagger2 生态最全,但重且抽象泄漏多
CrewAI Hilt 在LangChain之上的高级封装,上手快
AutoGen Koin 轻量灵活,多Agent对话模型优秀
自研(Kotlin) 手动DI 完全可控,但造轮子成本高

我的建议:如果你只是想快速体验Agent,用CrewAI三五行代码就能跑起来。如果要做生产级别的事情,自己用Kotlin/Python写核心循环+按需接LangChain的工具包是最稳的------就像现在大家基本都是"Hilt注入+手动管理关键组件"一样。

下期预告

这篇讲了Agent的核心思想------自主推理、工具调用、记忆系统、多Agent协作。如果说RAG让AI"知道得多",那Agent让AI"能干的活多"。

但你有没有发现一个问题:不管是RAG还是Agent,我们都在用通用大模型。如果我想让模型更懂我们的业务术语?更适应我们的代码风格?更准确地理解TAPD工单的格式?

下一篇,我们聊微调------让通用大模型变成你的"专属定制ROM"。就像从AOSP出发做一个定制系统,LoRA微调本质上就是在基础模型上叠一层"薄薄的定制补丁",不改原始权重,但效果立竿见影。

到时候见

Android工程师的AI开发实战系列 · 第2/4篇

用Android思维理解RAG、Agent和微调,从移动端老兵到AI开发者的跨界之路

第1篇:RAG:给大模型装一个靠谱的「本地数据库」------Android工程师秒懂的检索增强生成

第2篇:Agent智能体:让AI自己调API干活------从Android Service到AI Agent的思维跃迁(本篇)

⏳ 第3篇:微调:让通用大模型变成你的「专属定制ROM」------从AOSP到LoRA的迁移学习

⏳ 第4篇:RAG+Agent+微调组合拳:搭建一个完整的AI驱动Android开发助手

相关推荐
海上彼尚6 小时前
Nodejs也能写Agent - 9.Mastra篇 - Mastra客户端
开发语言·前端·javascript·人工智能·node.js
钓了猫的鱼儿6 小时前
基于深度学习+AI的红外人体行为目标检测与预警系统(Python源码+数据集+UI可视化界面+YOLOv11训练结果)
人工智能·深度学习·目标检测
一水鉴天6 小时前
九宫格时空语义引擎与词向量定义 20260524(千问)
人工智能·架构
codefan※6 小时前
day04-prompt-pitfalls
人工智能·大模型·llm·prompt·prompt工程·ai应用开发
晚烛6 小时前
CANN 自定义算子开发:Ascend C 编程接口与算子实现完整指南
c语言·开发语言·人工智能·python
MediaTea6 小时前
PyTorch:神经网络模块
人工智能·pytorch·python·深度学习·神经网络
Mr数据杨6 小时前
【CanMV K210】传感器实验 模拟声音传感器噪声校准与强度检测
人工智能·硬件开发·canmv k210
嵌入式老牛6 小时前
液晶段码(米/日字格)识别—定位
人工智能·深度学习·计算机视觉
吴佳浩6 小时前
Marvis 本地模式实测:它真的是 Windows 版的 OpenClaw 吗?
人工智能·llm·agent