Firebase AI(@firebase/ai)FunctionCall 接口深度解析:模型函数调用预测的完整数据契约
Firebase AI(@firebase/ai)FunctionCall 接口深度解析:模型函数调用预测的完整数据契约
导读
FunctionCall 是 Firebase JavaScript SDK 中 AI 模块(@firebase/ai)的核心数据接口,它描述了生成式模型在响应中预测出的"函数调用意图":模型不再直接输出最终答案,而是返回一个函数名和一组结构化 JSON 参数,交由客户端(你的应用)去实际执行对应函数。本文将围绕 docs-devsite/ai.functioncall.md 中定义的 FunctionCall 接口,结合 packages/ai/src 下的真实源码与测试用例,讲清楚它的三个字段(name、args、id)、与 FunctionDeclaration / FunctionResponse 的协作关系、两种后端(Gemini Developer API 与 Agent Platform Gemini API)的差异,以及 SDK 如何自动把 FunctionCall 转成真实函数调用并回传结果,帮助你正确实现并调试"函数调用(function calling)"能力。
说明:本文以 docs-devsite/ai.functioncall.md 为骨架撰写;涉及源码均位于 packages/ai 目录,可通过下方相对路径继续深入查阅。
一、接口速览:模型"打算调用哪个函数"
先给出该接口的官方签名与属性表(完整继承自 docs-devsite/ai.functioncall.md):
export interface FunctionCall
| 属性 | 类型 | 描述 |
|---|---|---|
| args | object |
调用该函数时使用的参数(结构化 JSON 对象) |
| id | string |
函数调用的 id,必须随关联的 FunctionResponse 一起回传 |
| name | string |
对应 FunctionDeclaration.name 的函数名 |
在源码中,该接口定义于 packages/ai/src/types/content.ts#L196-L214,其完整实现为:
/**
* A predicted {@link FunctionCall} returned from the model
* that contains a string representing the {@link FunctionDeclaration.name}
* and a structured JSON object containing the parameters and their values.
* @public
*/
export interface FunctionCall {
/**
* The id of the function call. This must be sent back in the associated {@link FunctionResponse}.
*
* @remarks This property is only supported in the Gemini Developer API ({@link GoogleAIBackend}).
* When using the Gemini Developer API ({@link GoogleAIBackend}), this property will be
* `undefined`.
*/
id?: string;
name: string;
args: object;
}
接口声明(id 可选、name 与 args 必选)暗示了它的定位:FunctionCall 只是"模型给出的调用提议",真正执行与否取决于客户端。它通常不会单独出现,而是作为 Part(内容片段)的 functionCall 变体存在,见 packages/ai/src/types/content.ts#L113-L121:
export interface FunctionCallPart {
type: 'functionCall';
functionCall: FunctionCall;
thought?: boolean;
}
也就是说,模型返回的候选结果(candidate)中,其 content.parts 数组里可以同时混有文本片段与 functionCall 片段,SDK 通过 type: 'functionCall' 这一判别字段来识别。
二、字段逐个拆解
2.1 FunctionCall.name —— 调用哪个函数
name: string;
name 是必选字符串,指向 FunctionDeclaration.name,即你通过工具(tool)声明给模型的函数名。源码 packages/ai/src/types/requests.ts#L531-L537 对函数名的约束为:
必须以字母或下划线开头;只能包含
a-z、A-Z、0-9、下划线和短横线;最大长度 64。
测试代码 packages/ai/src/requests/response-helpers.test.ts#L64-L73 给出了典型取值示例:
const functionCallPart1: FunctionCallPart = {
type: 'functionCall',
functionCall: {
name: 'find_theaters',
args: {
location: 'Mountain View, CA',
movie: 'Barbie'
}
}
};
2.2 FunctionCall.args —— 传给函数的参数
args: object;
args 是必选的结构化 JSON 对象,包含模型预测出的、调用该函数所需的全部参数及取值。这些参数的结构由声明侧的 FunctionDeclaration.parameters 决定,见 docs-devsite/ai.functiondeclaration.md 与源码 packages/ai/src/types/requests.ts#L548:
parameters采用 JSON Schema Object 格式(对齐 OpenAPI 3.0.3 Parameter Object),参数名大小写敏感;无参函数可省略。
SDK 在自动调用场景中会把 args 原样传给实际的 JavaScript 函数(详见第三节)。
2.3 FunctionCall.id —— 关联 FunctionResponse 的凭证
id?: string;
id 是可选字段,文档明确强调:
函数调用的 id,必须随关联的
FunctionResponse一起回传。
同时文档给出了一条重要的后端差异:
该属性仅在 Gemini Developer API(GoogleAIBackend)下受支持;当使用 Agent Platform Gemini API 时,该属性为
undefined。
这一差异在源码 JSDoc 中同样被标注(packages/ai/src/types/content.ts#L204-L210)。对照两个后端类的官方说明:
- GoogleAIBackend:Gemini Developer API 的配置类;
- AgentPlatformBackend:Agent Platform Gemini API(前身 Vertex AI Gemini API)的配置类。
因此,如果你的应用只使用 Agent Platform 后端,则必须基于 name + args 匹配函数调用,而依赖 id 做关联;反之,在 Gemini Developer API 下,id 可用来把某次 FunctionCall 与其产生的 FunctionResponse 精确绑定(例如一次回复中同时出现多个函数调用时)。
三、FunctionCall 在函数调用流程中的位置
FunctionCall 并非孤立类型,它处于"声明 → 预测 → 执行 → 回传"闭环的正中间。与其直接关联的三个接口在 docs-devsite 下都有独立参考页:
| 接口 | 文档页 | 角色 |
|---|---|---|
FunctionDeclaration |
docs-devsite/ai.functiondeclaration.md | 客户端声明"可被调用的函数"(含 name、description、parameters、functionReference) |
FunctionCall |
本文主题 | 模型在响应中预测出的"调用提议" |
FunctionResponse |
docs-devsite/ai.functionresponse.md | 客户端执行后回传给模型的执行结果 |
3.1 由声明到预测:FunctionDeclarationsTool
客户端通过 FunctionDeclarationsTool 把一组 FunctionDeclaration 交给模型,源码 packages/ai/src/types/requests.ts#L654-L671:
export interface FunctionDeclarationsTool {
functionDeclarations?: FunctionDeclaration[];
}
其 JSDoc 描述的正是整个流程:模型"可能决定调用这些函数中的一部分,通过在响应中填充 FunctionCall 来表达;用户应在下一轮为每个函数调用提供 FunctionResponse,模型据此生成最终回复;最多可提供 64 个函数声明"。
3.2 响应中提取 FunctionCall:getFunctionCalls
模型返回的原始响应经过 createEnhancedContentResponse 加工后(packages/ai/src/requests/response-helpers.ts#L99-L120),每个 part 会被 assignPartType 补上 type 判别字段(functionCall / functionResponse / text 等,见 packages/ai/src/requests/response-helpers.ts#L74-L93)。
随后 getFunctionCalls 从第一个候选结果中收集全部 functionCall 类型片段,packages/ai/src/requests/response-helpers.ts#L227-L247:
export function getFunctionCalls(
response?: GenerateContentResponse
): FunctionCall[] | undefined {
if (!response) return undefined;
const functionCalls: FunctionCall[] = [];
if (response.candidates?.[0].content?.parts) {
for (const part of response.candidates?.[0].content?.parts) {
const typedPart = assignPartType(part);
if (typedPart.type === 'functionCall') {
functionCalls.push(typedPart.functionCall);
}
}
}
return functionCalls.length > 0 ? functionCalls : undefined;
}
测试 packages/ai/src/requests/response-helpers.test.ts#L219-L228 验证了"同一响应包含两个 FunctionCall(find_theaters 与 find_times)时能全部取出"。此外,增强响应对象还暴露了便捷方法 response.functionCalls()(packages/ai/src/requests/response-helpers.ts#L180-L182),供调用方直接读取。
3.3 手动执行模式:拿到 FunctionCall 后自己处理
当你不希望 SDK 自动执行函数时,可以关闭自动调用(不给 FunctionDeclaration 设置 functionReference),手动从响应中取出 FunctionCall 列表,逐一执行真实业务逻辑,再构造 FunctionResponse(packages/ai/src/types/content.ts#L225-L237):
export interface FunctionResponse {
id?: string; // 对应 FunctionCall.id,仅 Gemini Developer API 支持
name: string; // 对应 FunctionCall.name
response: object; // 函数执行结果
parts?: Part[];
}
关键点:FunctionResponse.id 与 FunctionCall.id 一一对应(docs-devsite/ai.functionresponse.md),且与 FunctionCall.id 一样仅在 Gemini Developer API 后端下可用。
3.4 自动执行模式:SDK 内部如何消费 FunctionCall
如果 FunctionDeclaration 提供了 functionReference(真实函数引用,见 packages/ai/src/types/requests.ts#L549-L554),SDK 会在 ChatSession 中自动完成"调用函数 → 回传结果 → 继续追问模型"的循环。核心实现位于 packages/ai/src/methods/chat-session-base.ts:
-
判定可自动调用:
_getCallableFunctionCalls(packages/ai/src/methods/chat-session-base.ts#L354-L379)先从响应中取出FunctionCall[],再逐一核对声明的functionDeclarations中是否存在同名且带functionReference的声明;只要有一个FunctionCall找不到对应引用,就放弃自动调用,改由调用方手动处理。 -
执行并组装响应:
_callFunctionsAsNeeded(packages/ai/src/methods/chat-session-base.ts#L386-L451)对每个FunctionCall取出functionReference,以functionCall.args为入参调用:
const results = Promise.resolve(
functionDeclaration.functionReference!(functionCall.args)
).catch(e => { /* 包装为 AIError 抛出 */ });
执行完成后构造 FunctionResponse,并把 FunctionCall.id(若存在)回填到 functionResponse.id:
const functionResponse: FunctionResponse = {
name, // 来自 functionCall.name
response: await results
};
if (id) {
functionResponse.id = id; // 来自 functionCall.id
}
- 循环直到无函数调用:
sendMessage与流式sendMessageStream都使用do...while循环反复执行上述步骤(packages/ai/src/methods/chat-session-base.ts#L149-L214),直到模型返回不含FunctionCall的响应为止;为防止无限循环,默认最多连续自动调用 10 次(DEFAULT_MAX_SEQUENTIAL_FUNCTION_CALLS = 10,见 packages/ai/src/methods/chat-session-base.ts#L52-L56),并可通过请求选项maxSequentialFunctionCalls调整(packages/ai/src/methods/chat-session-base.ts#L152-L154)。超过上限时记录警告日志并返回最后一次模型响应。
3.5 测试用例佐证
packages/ai/src/methods/chat-session.test.ts#L667-L758 覆盖了"模型先返回 FunctionCall{name:'getGreeting', args:{username:'Bob'}},SDK 自动调用 greetingSpy,再把结果作为 functionResponse 回传,模型最终给出 final response"的完整链路;同文件还覆盖了单个响应包含多个 FunctionCall 时依次自动调用的场景(packages/ai/src/methods/chat-session.test.ts#L784-L821)。
四、后端差异与错误处理注意事项
- id 的可用性:
FunctionCall.id与FunctionResponse.id只在 Gemini Developer API(GoogleAIBackend)下有意义;Agent Platform 后端下为undefined。跨后端编写通用代码时,应以name作为关联键。 - 坏终止原因:当模型因
MALFORMED_FUNCTION_CALL、UNEXPECTED_TOOL_CALL、TOO_MANY_TOOL_CALLS等FinishReason终止时(列表见 packages/ai/src/requests/response-helpers.ts#L275-L292),SDK 会通过hadBadFinishReason判定并抛出AIError(packages/ai/src/requests/response-helpers.ts#L52-L62),此时无法获得可用FunctionCall。 - 工具缺失:若模型返回了
FunctionCall但请求中未提供任何functionDeclarations,自动调用会抛出REQUEST_ERROR:"No function declarations were provided in "tools"."(packages/ai/src/methods/chat-session-base.ts#L446-L450)。 - 命名规范:
name必须符合FunctionDeclaration.name的约束(首字符字母/下划线、长度 ≤ 64),否则模型侧校验可能失败。
五、常见问题与排查建议
| 现象 | 排查方向 |
|---|---|
响应里既有文本又有 functionCall |
属正常现象:模型可在同一 candidate 的 parts 中混排多种片段,用 getFunctionCalls() / response.functionCalls() 过滤即可 |
| 需要手动执行函数 | 不给 functionReference,SDK 不会自动调用;自行读取 FunctionCall[] 并回传 FunctionResponse |
| 同一响应多个函数调用 | 按 name(必要时加 id)逐一执行并逐个回传 FunctionResponse,SDK 自动模式下也按此处理 |
id 为 undefined |
确认是否使用 Agent Platform 后端;该后端不支持 id,请用 name 关联 |
| 自动调用未触发 | 检查 FunctionDeclaration 是否设置了 functionReference,以及 _getCallableFunctionCalls 的判定条件 |
| 自动调用被截断 | 检查 maxSequentialFunctionCalls(默认 10),看日志中的超限警告 |
结语
FunctionCall 是连接"模型决策"与"客户端执行"的枢纽:它以最小的数据契约(name + args,必要时加 id)承载了模型对函数调用的完整预测。理解它的三个字段、它与 FunctionDeclaration/FunctionResponse 的配对关系、两种后端对 id 支持的差异,以及 SDK 在 chat-session-base.ts 中的自动调用循环,你就能在 Firebase AI 中可靠地实现工具调用型 Agent 应用——无论是让 SDK 全自动执行,还是手动接管每个 FunctionCall。
- 接口定义:packages/ai/src/types/content.ts#L196-L214
- 声明与工具类型:packages/ai/src/types/requests.ts#L531-L555、packages/ai/src/types/requests.ts#L654-L671
- 响应解析与提取:packages/ai/src/requests/response-helpers.ts#L227-L247
- 自动调用实现:packages/ai/src/methods/chat-session-base.ts#L354-L451
- 测试验证:packages/ai/src/methods/chat-session.test.ts#L667-L821、packages/ai/src/requests/response-helpers.test.ts#L64-L228
- 后端说明:docs-devsite/ai.googleaibackend.md、docs-devsite/ai.agentplatformbackend.md