前言
RAG 让 LLM 能"读"你的资料,Function Calling 让 LLM 能"动手"——调用 App 里的具体 API。本篇聊端侧 LLM 的 Function Calling(也叫 Tool Use)能力,以及怎么在 Android 工程里把"识别调用"和"执行 API"做成可观测、可调试的链路。
一、Function Calling 在端侧 LLM 上能做什么
Function Calling 不是 LLM 在调函数,而是 LLM 在生成结构化输出(通常是 JSON),告诉业务代码"该调哪个函数、传什么参数"。App 端拿到 JSON,去执行真正的逻辑。
典型场景:
LLM 不会真的访问网络或数据库,它只是"指出"——这是设计,安全和可控都需要在 App 端实现。
二、支持 Function Calling 的端侧 LLM
不是所有端侧模型都支持 Function Calling。当前可用:
多数国产 App 选 Qwen 系列,中文 Function Calling 准确率最高。
三、工具描述的 schema
LLM 怎么知道有哪些函数可调?通过系统 prompt:
val tools = """
[{
"name": "search_train",
"description": "查询火车票信息",
"parameters": {
"type": "object",
"properties": {
"from_city": { "type": "string", "description": "出发城市" },
"to_city": { "type": "string", "description": "到达城市" },
"date": { "type": "string", "description": "出发日期,格式 yyyy-MM-dd" }
},
"required": ["from_city", "to_city", "date"]
}
}, {
"name": "create_note",
"description": "创建一条新的备忘录",
"parameters": {
"type": "object",
"properties": {
"title": { "type": "string", "description": "备忘录标题" },
"content": { "type": "string", "description": "备忘录正文" }
},
"required": ["title"]
}
}]
""".trimIndent()
描述一定要清晰——LLM 据此决定调不调、调哪个、传什么。"from_city" 比 "from" 更明确,"上海火车站" 比 "上海" 更细致。
四、提示词组装
Function Calling 在 Qwen 系列里有自己的格式:
val systemPrompt = """
你是用户助手,可以使用下列工具:
$tools
当用户提问时,输出严格 JSON 格式:
{
"thought": "我决定调什么",
"tool": "函数名",
"args": { 函数参数 }
}
若无合适的工具,回答 { "thought": "...", "tool": "" }
""".trimIndent()
val userQuery = "查明天从北京到上海的高铁"
val fullPrompt = "$systemPrompt\n\n用户:$userQuery\n助手:"
val raw = llm.generateResponse(fullPrompt)
val decision = parseJson(raw)
Qwen 在 Function Calling 上有个大优点:它会输出"thought"字段解释为什么决定调这个——非常便于调试。
五、App 端执行逻辑
class ToolExecutor {
suspend fun execute(tool: String, args: Map<String, Any>): String {
return when (tool) {
"search_train" -> {
val from = args["from_city"] as String
val to = args["to_city"] as String
val date = args["date"] as String
val result = trainApi.search(from, to, date)
// 把结果转成自然语言,再喂给 LLM
"""
${date} 从 $from 到 $to 的车次:
${result.joinToString("\n") { "${it.no} ${it.time} ${it.price}" }}
""".trimIndent()
}
"create_note" -> {
val title = args["title"] as String
val content = args["content"] as? String ?: ""
notesRepo.create(Note(title, content))
"已创建备忘:$title"
}
else -> "未知工具"
}
}
}
六、二次 prompt:把结果回传 LLM
工具执行后,需要把结果再次喂给 LLM,让它总结:
val toolResult = executor.execute(decision.tool, decision.args)
val followUpPrompt = """
$systemPrompt
用户:$userQuery
助手(JSON):${decision.toJson()}
工具结果:$toolResult
现在基于工具结果,用自然语言给用户最终回答。
助手:""".trimIndent()
val finalAnswer = llm.generateResponse(followUpPrompt)
这样模型会输出"为您查到明天北京南→上海虹桥的车次:G1 06:00 553元,……"。
七、安全设计
Function Calling 在 App 里是"半自动"——LLM 给出指令,但要执行什么、需不需要用户二次确认,由你决定。
7.1 工具分级
实现:
suspend fun executeWithPermission(tool: String, args: Map<String, Any>): String {
return when (toolRisk(tool)) {
RiskLevel.LOW -> executor.execute(tool, args)
RiskLevel.MEDIUM -> {
val confirmed = showConfirm(tool, args)
if (confirmed) executor.execute(tool, args) else "用户取消"
}
RiskLevel.HIGH -> {
val passed = biometricAuth()
if (passed) executor.execute(tool, args) else "需要鉴权"
}
}
}
7.2 速率限制
LLM 偶尔会"幻觉"——重复调用同一个工具。建议在 Executor 上加守卫:
private val callCounts = ConcurrentHashMap<String, AtomicInteger>()
fun tryCall(tool: String, args: Map<String, Any>): Boolean {
val key = "$tool:$args"
val count = callCounts.computeIfAbsent(key) { AtomicInteger(0) }
return count.incrementAndGet() <= 3
}
八、性能与延迟
用户输入 → LLM 输出 JSON (~200ms) → API 调用 (~300ms) → LLM 总结 (~500ms)
↓
用户看到答案
总: ~1s
主要时间花在 LLM 推理。如果首屏体验敏感,可以:
直接展示 "正在为您查询..." 临时话术
工具返回后,先展示原始 JSON 让用户看,LLM 总结后台完成
工具结果缓存(同 city+date 的查询命中缓存,跳过 API)
九、调试与监控
// 工具调用日志
object ToolLogger {
fun log(decision: ToolDecision, result: String, latency: Long) {
val entry = """
[ToolCall]
Thought: ${decision.thought}
Tool: ${decision.tool}
Args: ${decision.args}
Result: ${result.take(500)}
Latency: ${latency}ms
""".trimIndent()
logger.info(entry)
}
}
把每次 Function Call 落地到本地日志,分析:
哪些工具被调最多
有多少比例的幻觉(tool 名字不对、参数不合法)
用户满意度(如果设计了"这条回答有用/没用"的反馈)
十、常见的"工具调用失败"模式
小结
Function Calling 是 LLM 与 App 集成的"动静结合"最佳模式。LLM 负责意图理解与决策,App 负责执行与安全。落地时把工具描述写清楚、把安全门槛做实、把监控打点齐全,是三件不能跳的事。
下一篇聊 ML Kit——Google 的端侧 AI 推理框架,包括人脸识别、文本识别、姿势检测等"开箱即用"能力。
参考阅读
Qwen2.5 Function Calling Guide
Android ML Kit 文档
OpenAI Function Calling 设计(虽然云端,但启发性强)