移动端智能体的工程难点不只在于运行一个模型。模型需要接入工具,工具需要受系统权限和用户授权约束,任务需要保存上下文;当手机无法独自完成后续处理时,还需要把任务和文件交给其他智能体。Kiora SDK 针对这些环节提供了一套可组合的 Kotlin 框架,目前处于原型阶段,接口仍可能调整。
本文从运行时、模型适配、工具边界、会话管理和 A2A 协作五个方面拆解这一设计。讨论对象是公开仓库中的实现,而不是对所有 Android 设备和模型组合的性能承诺。
为什么需要运行时,而不只是模型封装
普通聊天应用可以把输入交给模型,再把输出显示到屏幕;智能体任务会多出一段执行链:模型提出工具调用,宿主校验参数和权限,工具执行后把结果送回模型,直到任务完成或达到限制。只要其中一个环节缺少明确约束,应用就容易出现"回复说已完成,但文件并未写入""模型把不存在的文件名当成附件 ID"或"远端任务开启了新会话"等问题。
因此,Kiora 把模型推理定义为可替换的能力,把工具、会话和协作组织成运行时。这样的划分还允许同一个 Android 宿主在端侧模型与 API 模型之间切换,而无需为每种模型重新编写文件授权和 A2A 逻辑。
框架边界:运行机制与 Android 宿主分离
Kiora 的 core 是纯 Kotlin/JVM 模块。它负责有界模型与工具循环、取消、排队、会话存储、摘要和运行事件。Android 界面、系统文件选择器、相册权限及授权弹窗由宿主应用提供。这样,智能体运行机制不必直接依赖某一种聊天界面,也不把 Android 权限决策交给模型。
text
Android 宿主应用
└── AgentRuntime
├── ModelPort:端侧模型或在线模型
├── SessionStore:会话与摘要
├── AgentTool:本机工具
├── ActiveToolSelector + ToolAuthorizer:工具选择与授权
└── A2A:发现其他 Agent 的能力并委托任务
ModelPort 把模型实现与运行循环隔开。现有适配器包括直接加载 .litertlm 的 LiteRT-LM、在 Android 应用进程内加载模型目录的 MNN,以及兼容 Chat Completions 的在线接口。宿主也可以自行实现 ModelPort。这些模型共享工具和会话机制,但实际文本、视觉与工具调用能力仍取决于模型包及适配器,不能仅凭"支持多模态"的模型名称推断全部功能可用。
从依赖关系看,可以将主要模块理解为四层:
| 层次 | 代表模块 | 主要职责 |
|---|---|---|
| 运行时 | core |
模型与工具循环、事件、会话、摘要、取消及任务边界 |
| 模型接口 | model-chat、model-litert、model-mnn |
将不同推理后端适配到 ModelPort |
| 本机能力 | android-storage-tools、document-ocr-mlkit、document-ocr-paddle、embedding-mediapipe 等 |
文件、OCR、向量等可选能力;由宿主决定是否接入 |
| 跨 Agent 协作 | a2a、android-attachments |
Agent Card 发现、远端任务、附件编码和 Android 文件读取 |
这是一组模块而非一个必须整体引入的 APK。纯 Kotlin 核心不负责弹授权框,也不会自行取得 Android 相册权限。宿主需要根据产品场景注册工具、提供权限实现并决定哪些数据可以离开设备。
一次任务在运行时如何推进
运行时接收用户输入后,先把消息追加到会话,再根据当前分支构造模型可见的上下文。如果宿主配置了摘要或窗口保护,转换发生在送入模型之前;原始会话记录仍保留。模型返回普通文本时,运行时结束该轮;返回工具调用时,运行时校验并执行工具,再将结果追加到会话,继续请求模型。
text
用户输入
→ 保存到会话
→ 构造当前分支的模型上下文
→ 选择本轮可用工具
→ 模型生成文本或工具调用
→ 参数校验与授权
→ 执行工具并保存真实结果
→ 模型根据结果继续或结束
这条链路有两个容易被忽略的细节。第一,工具调用本身并不等于工具已经成功执行,最终回复需要依据工具返回值。第二,模型看到的上下文可以经过摘要或裁剪,但会话存储中的原始消息不会因上下文保护被直接改写。运行时还提供轮次、工具调用次数与运行时间等边界,防止一个任务无限循环;取消已运行任务并不能撤销此前完成的本机写入或远端操作。
模型也并非总能一次给出合法参数。SDK 对 JSON Schema 中的基本参数约束进行检查;不合法的调用返回错误供模型修正。对于需要用户处理的授权或输入,工具可报告 AUTH_REQUIRED 或 INPUT_REQUIRED 等注意状态,使任务暂停等待,而不是把异常伪装成成功文本。
模型接口:统一调用,不抹平能力差异
ModelPort 使运行时不直接绑定推理引擎,但它不会把不同模型变成同一能力。LiteRT-LM 使用模型包,MNN 使用模型目录,在线适配器通过 Chat Completions 兼容接口调用服务。端侧路径的资源占用、视觉输入数、工具调用质量和上下文处理方式都应按具体模型包验证;在线路径还需要考虑请求内容的传输和服务端策略。
向量检索刻意使用独立的 EmbeddingPort。例如 EmbeddingGemma 2 可把文本查询和照片转换为可比较的向量,但它不是负责聊天和工具规划的 ModelPort。把这两类接口分开,可以让相册索引独立于当前聊天模型运行,也避免把"能检索图片"误写为"聊天模型逐张看过全部原图"。
工具调用:模型提出请求,宿主落实权限
一次任务开始时,宿主通过 ActiveToolSelector 决定本轮向模型提供哪些工具。模型给出调用后,SDK 先进行参数基础校验,再交给 ToolAuthorizer 决定是否执行。Android 的相册和文件访问范围仍以系统授权为准;工具能否读取、写入或上传文件,由其实现和宿主策略共同约束。
这一职责划分对文件尤其重要。模型生成的文件名只是文本,不能据此认定文件已经存在。对于 A2A 附件,宿主使用受控文件 ID 核验来源、存在性、大小和目标;用户在当前轮次选中的照片与工作区生成的文档,也需要经程序绑定后才能发送。默认授权策略可对上传逐次询问。授权控制发送动作,附件到达目标 Agent 后,目标 Agent 仍可依照任务及自身权限读取内容。
文件 ID 与文件名应承担不同职责。文件名服务于展示和远端保存时的命名,ID 则由宿主解析到一份可访问的本地文件。聊天中出现的 report.md 字样,不构成"该文件存在"的证据;同样,Android content:// URI 只在取得权限的本机应用中有意义,不能直接作为远端可读取的附件地址。Kiora 的 Android 附件层通过 ContentResolver 读取字节,并将符合上限的文件编码为 A2A 内联数据。
附件调用可以拆成以下几个由程序承担的步骤:
- 根据本轮用户实际选中的照片或已成功写出的工作区文件,建立受控 ID 与文件的映射。
- 在展示授权之前检查 ID 是否属于允许的来源、文件是否存在、目标 skill 是否接受文件,以及大小是否符合限制。
- 取得宿主授权后才读取文件字节,并构造 A2A
Part的raw、filename与mediaType。 - 记录目标任务返回的状态,而不是只凭发送请求成功就宣称文件已在目标端落盘。
其中第 2 步的预校验不需要先读取附件内容。它能拦截模型编造的 ID、过期的选择状态和不支持文件的远端 skill,也避免对明显无效的调用弹出授权确认。真正的权限边界由 Android 授权、宿主实现和工具授权共同构成;提示词可以帮助模型选择工具,但不应代替这些检查。
SDK 还提供可选的工作区工具,以及基于 Android SAF 授权目录的存储工具。它们不会自动出现在每个智能体会话中。尤其是 shell 工具以宿主进程的身份运行,不能把工作区初始目录误认为系统级沙箱;宿主需要单独决定是否启用。
A2A:按目标能力建立工具
Kiora 的 A2A 客户端从目标 Agent 的 Agent Card 发现接口与 skill,并根据各 skill 声明的输入类型生成模型可调用的工具。声明文件输入的 skill 才会提供附件参数;仅有文本输入的 skill 不会被当作文件传输接口。当前客户端支持 A2A 1.x 的 HTTP+JSON 与 JSONRPC、文本和总计不超过 10 MiB 的内联文件,以及任务发送、轮询、取消和续接。
Agent Card 的 skill 描述是能力声明,不是可以直接远程调用的同名 RPC 方法。Kiora 将选中的 skill 包装成本地 AgentTool,调用时仍通过 A2A 的消息/任务协议提交给目标 Agent。一个连接可以包含多个 skill,一个会话也可以接入多个 Agent;工具名称需要区分来源,避免模型把不同连接上的能力混为一谈。连接别名能够帮助用户表达"交给哪个 Agent",但最终目标仍由宿主保存的连接对象确定。
一次远端调用至少涉及三类状态:连接配置决定发往哪里;contextId 决定后续消息是否延续同一远端上下文;taskId 指向一次尚未结束的远端任务。发送请求后,远端可能立即完成、继续运行、请求补充输入或要求授权。客户端要根据真实任务状态轮询或续接,不能把"Agent Card 可访问"推断为"该 skill 必然能执行成功"。
跨任务连续性由两个标识分别处理:contextId 用于把同一手机会话对同一连接的后续请求带入既有远端上下文;taskId 用于继续尚未完成的具体任务。两者不能互相替代。目标 Agent 可以部署在电脑、服务器或其他手机可访问的设备上;A2A 调用的前提是目标服务在当前网络可达。手机完全断网时,本机模型与工具仍可处理适合离线执行的任务,但不能发起远端委托。
会话与上下文管理
SDK 保存原始会话消息,并提供可选的持久摘要与上下文窗口保护。运行时会估算本轮模型输入、工具定义和输出预留量;估算并非精确 tokenizer 计数。宿主可以依据设备资源和实际模型配置自动摘要,在摘要无法完成时使用已配置的输入裁剪保护。摘要不会删除原始会话。
持久摘要与简单的"删掉前几轮"不同。摘要锚定在已完成的助手消息上;当前分支可使用与其对应的最新摘要,同时保留最近若干轮原文。当新摘要生成时,会把旧摘要和增量消息一起纳入,而不是反复从零总结全量历史。宿主可通过 AutoCompaction 设置触发预算与保留轮数;触发值表示何时压缩模型输入,并不是模型本身的最大上下文规格。
工具返回也可能非常长。运行时可以只在模型视图中缩短较旧的工具结果,以免单次日志或文件内容挤占后续推理空间;持久会话仍保存原始结果。若模型报告上下文超限,运行时可在缩小输入后重试。对真实模型仍需观察输入长度、设备内存和生成质量,不能仅按宣传资料中的最大窗口填写配置。
对于需要连续操作的移动场景,SDK 还保存排队的后续消息与运行中的引导消息。前者在当前任务完成后进入下一轮,后者在合适的模型边界影响当前任务。这些机制减少用户在长任务中"消息发出但任务状态丢失"的情况,但不会替代远端任务的生命周期管理。
宿主接入时应先确定的策略
接入 SDK 时,开发者首先需要决定哪些工具在什么条件下暴露。只读相册搜索、工作区写入、上传附件和系统命令的风险不同,不适合使用同一授权规则。其次需要确定会话如何持久化、A2A contextId 如何按本机会话与连接保存,以及用户切换模型时是否保留或清理模型实例。最后要确定模型不支持目标能力时的反馈,例如纯文本模型不应被要求直接理解相册原图。
典型集成可以从一个 ModelPort、一个 SessionStore 和少量明确授权的工具开始;确认工具调用、结果回写和会话恢复后,再添加 A2A 连接及 Android 附件加载器。这样得到的是可验证的任务链:每一步有输入、输出与失败状态,而不是仅在聊天提示词中约定"以后应该怎样执行"。
SDK README 给出过一段简化的集成方式。以下代码展示对象之间的关系,myModelPort、myLocalTool 和 myToolAuthorizer 由宿主实现或配置:
kotlin
val connection = A2aPeerConnector.discoverConnection(
agentCardUrl = agentCardUrl,
bearerToken = { token },
)
val remoteTools = if (connection.skills.isEmpty()) {
listOf(A2aDelegateTool(connection.peer))
} else {
connection.skillTools()
}
val runtime = AgentRuntime(
model = myModelPort,
tools = remoteTools + myLocalTool,
store = JsonlSessionStore(filesDir.resolve("sessions")),
authorizer = myToolAuthorizer,
)
val result = runtime.run(sessionId = "chat_1", userText = userRequest)
这段代码的重点不在于某个固定模型,而在于同一 AgentRuntime 可以同时接入本机工具和远端 skill。宿主在注册工具前,应明确它面向哪类数据、会不会修改本机或远端状态、失败时返回什么可判断的结果。例如相册搜索可按只读工具管理;写入工作区应核验文件是否真正产生;A2A 上传则同时涉及本机文件读取与向外发送。
工程测试也应按层定位,不宜只检查最终回复是否"看起来合理"。模型未提出调用,先检查工具是否向本轮模型暴露、描述是否清晰;调用参数不合法,检查 Schema 和文件 ID 映射;授权被拒绝,检查宿主策略与系统权限;远端任务异常,检查 Agent Card 的能力声明、任务端点及远端日志;文件落盘后内容不一致,再比较原始附件与目标文件的字节数和哈希。这样的分层观察能够避免把所有错误都归因于模型能力。
这套机制面向长任务和工具调用场景,但不能替代模型自身的上下文容量,也不意味着所有端侧模型都能以标称最大窗口稳定运行。实际配置仍需结合设备内存、模型包与任务验证。
适用范围与当前限制
Kiora 的价值在于把既有模型、工具和 A2A 协议组织为可复用的移动端运行机制。它没有发明新的推理引擎,也不把所有任务描述为离线任务。当前 A2A 以小型内联附件为主,尚不包含大文件流式传输、远端文件下载或完整 A2A 服务端。目标 Agent 还必须真正实现其 Agent Card 所声明的 skill。
开源代码采用 Apache-2.0 许可证。模型权重和部分 Android 原生依赖需要按各自说明准备,不随仓库或 APK 一同分发。