Firebase AI(@firebase/ai)FunctionCall 接口深度解析:模型函数调用预测的完整数据契约

原创2026-10-14 13:56:541,885 阅读
文章标签:开发工具后端

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)。对照两个后端类的官方说明:

因此,如果你的应用只使用 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:

  1. 判定可自动调用:_getCallableFunctionCalls(packages/ai/src/methods/chat-session-base.ts#L354-L379)先从响应中取出 FunctionCall[],再逐一核对声明的 functionDeclarations 中是否存在同名且带 functionReference 的声明;只要有一个 FunctionCall 找不到对应引用,就放弃自动调用,改由调用方手动处理。

  2. 执行并组装响应:_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
}
  1. 循环直到无函数调用: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。

登录后查看全文
firebase-js-sdk