大家好,我是拭心。
上篇文章我們了解了如何在 Android 手機上實現 RAG。這篇文章我們來聊聊端上大模型應用開發的核心概念:Function Calling(函數調用能力,簡寫為 FC)。
Function Calling 本質上是讓大模型在回答過程中,不直接給出最終答案,而是根據需求,調用外部函數/API/工具,拿到外部數據后再繼續回答。
它把大模型從「只會聊天的模型」變成了「會用工具的智能體」。
AI Edge Function Calling 是 Google 為 Android/iOS 等終端設備提供的函數調用 SDK,它的出現讓端上模型也能調用外部函數,實現一些復雜的業務功能。
一、了解 AI Edge Function Calling API
一般來說,Function Calling 主要包括這幾步:
- 定義函數聲明:聲明 LLM 可以調用的函數的結構和參數。
- 設置提示和輸出格式:將函數的數據結構轉換為字符串以及將字符串轉換為函數結構,以便 LLM 能夠以適當的格式顯示信息。
- 解析輸出:檢測生成的響應是否包含函數調用,如果有將其解析為函數結構的數據類型,以便應用能夠執行函數調用。
- 檢查響應:檢測到輸出有函數調用,使用適當的參數和結構化數據類型調用該函數。否則,它會返回自然語言文本。
AI Edge Function Calling SDK 提供相關的 API:
- 函數定義,定義工具函數的名稱和參數: FunctionDeclaration
- 輸入格式設置器: ModelFormatter,用于將函數聲明轉換為 LLM 所需的特定于模型的格式,并將其插入系統提示
- 輸出解析器: 還是 ModelFormatter,用于檢測模型的輸出是否表示函數調用,并將其解析為數據結構
- 推理后端,封裝 LLM Inference API 和 ModelFormatter: InferenceBackend,最終通過
LlmInferenceBackendSession
實現核心功能
二、如何使用
只看文檔不容易理解,接下來我們通過 Google 的 Function Calling demo,來看看 FC API 如何使用。
2.1 demo 功能
demo 功能:在用戶錄音后,將錄音數據轉化為表格里的內容,提升信息錄入效率。
在 demo 中,Function Calling 扮演著"信息提取器"的角色。
當用戶通過語音輸入"我叫拭心,生日是1990年5月12日"時,函數調用機制能識別出"拭心"是姓名,"1990年5月12日"是生日,并將其與預定義的 first_name
、last_name
等參數相對應。
2.2 通過 demo 的實現了解怎么使用
了解 demo 功能后,接下來我們通過 demo 的源碼了解下 Function Calling SDK 怎么使用。
在 demo 項目中,整體流程包括這幾步:
2.2.1 初始化
在初始化時,創建了 FormViewModel
,在其中會創建 HammerFormatter
(ModelFormatter 的一個實現):
```kotlin
// in FormViewModel.createGenerativeModel()
val formatter =HammerFormatter(ModelFormatterOptions.builder().setAddPromptTemplate(true).build())
```
ModelFormatter
是連接模型與函數調用邏輯的關鍵:
- 請求格式化: 它將用戶的文本輸入、系統指令(
systemInstruction
)以及在Tools.kt
中定義的可用工具(函數簽名)打包成一個符合 Hammer 模型預期的提示(Prompt)。 - 響應解析: 當模型返回結果時,格式化器會解析輸出,判斷它是一個普通的文本回答還是一個結構化的函數調用請求。
隨后創建了 GenerativeModel
,它是 Function Calling SDK 的核心入口,整合了推理后端、系統指令和工具集。
```kotlin
// in FormViewModel.createGenerativeModel()
val systemInstruction = Content.newBuilder().setRole("system").addParts(Part.newBuilder().setText("This assistant will help you fill out a medical form.")).build()val model = GenerativeModel(llmInferenceBackend,systemInstruction,listOf(Tools.medicalFormTools).toMutableList() // 關鍵:定義模型可用的工具
)
```
2.2.2 函數注冊
然后在 com/google/sample/fcdemo/functioncalling/Tools.kt
中聲明了支持模型調用的函數列表:
val medicalFormTools: Tool = Tool.newBuilder().addFunctionDeclarations(// 1. 姓名函數FunctionDeclaration.newBuilder().setName("provide_name").setDescription("Records the user's first and last name.").setParameters(/* 參數定義 */)).addFunctionDeclarations(// 2. 生日函數FunctionDeclaration.newBuilder().setName("provide_dob").setDescription("Records the user's date of birth.").setParameters(/* 參數定義 */)).addFunctionDeclarations(// 3. 人口統計函數FunctionDeclaration.newBuilder().setName("provide_demographics").setDescription("Records the user's sex and marital status.").setParameters(/* 參數定義 */)).addFunctionDeclarations(// 4. 職業函數FunctionDeclaration.newBuilder().setName("provide_occupation").setDescription("Records the user's occupation or job.").setParameters(/* 參數定義 */)).addFunctionDeclarations(// 5. 醫療歷史函數FunctionDeclaration.newBuilder().setName("update_medical_history").setDescription("Updates the user's medical history based on the conditions provided.").setParameters(/* 參數定義 */)).build()
函數聲明通過 FC SDK 的 FunctionDeclaration。
2.2.3 用戶輸入
當用戶輸入"我叫拭心,生日是1990年5月12日"時,HammerFormatter
會構建一個包含以下元素的完整提示:
LlmInferenceBackendSession#addSystemMessage
:
public void addSystemMessage(Content systemInstruction, List<Tool> tools) {this.systemInstruction = systemInstruction;this.tools = new ArrayList(tools);String systemMessage = this.formatter.formatSystemMessage(systemInstruction, tools);this.llmInferenceSession.addQueryChunk(systemMessage);
}
2.2.4 模型推理
模型接收到格式化后的提示后,會:
- 理解用戶意圖:識別出用戶想要填寫表單
- 匹配可用函數:從預定義的函數列表中找到最匹配的函數
- 提取結構化數據:從自然語言中提取出具體的參數值
- 生成函數調用:輸出結構化的函數調用指令
當要調用函數時,模型返回的不是自然語言,而是結構化的函數調用對象:
{"functionCall": {"name": "provide_name","args": {"fieldsMap": {"first_name": {"stringValue": "拭心"},"last_name": {"stringValue": "<unknown>"}}}}
}
2.2.5 函數調用
模型返回的響應包含在 Part
對象中,每個 Part
可能包含一個 functionCall
:
// 1.模型處理
val response = chatSession!!.sendMessage(spokenText)// 2.從響應里找是否有函數調用
response.getCandidates(0).content.partsList?.forEach { part ->// extract the function from all the parts in the responseparts.forEach { part ->part?.functionCall?.args?.fieldsMap?.forEach { (key, value) ->value.stringValue?.let { stringValue ->if (stringValue != "<unknown>") {//3.有函數調用,更新對應的 valuewhen (key) {"first_name" -> _firstName.value = value.stringValue"last_name" -> _lastName.value = value.stringValue"occupation" -> _occupation.value = value.stringValue"sex" -> _sex.value = value.stringValue"marital_status" -> _maritalStatus.value = value.stringValue"date_of_birth" -> _dob.value = value.stringValue"conditions" -> {val currentMap = _medicalConditionsMap.value.toMutableMap()value.listValue.valuesList.forEach { condition ->currentMap[condition.stringValue] = true}_medicalConditionsMap.value = currentMap}else -> {throw Exception("Unknown function: $key value: $value")}}}}}}}
三、總結
好了,這就是 Google AI Edge Function Calling SDK 的相關介紹,它極大地簡化 FC 與端側大語言模型交互的復雜性,開發者無需手動進行復雜的提示工程和響應解析,只需定義好自己的"工具",即可讓 AI 理解并執行任務,專注于應用的核心業務邏輯。
更多詳細信息,請參閱 官方 Android 函數調用指南。
PS:記錄一個容易混淆的問題,MCP 和 Function Calling 的關系?目前我的理解:
- MCP 包含 Function Calling,但遠不止于 Function Calling
- MCP 試圖統一所有上下文交互和外部工具接口,把 Function Calling 納入整個智能體協議體系里