Skip to content

Function Calling 原理

什么是 Function Calling

Function Calling 让 LLM 能够"调用函数"。但 LLM 并不真正执行函数,而是:

  1. 告诉 LLM 有哪些函数可用(工具定义)
  2. LLM 决定要调用哪个函数、传什么参数
  3. 执行函数,把结果告诉 LLM
  4. LLM 基于结果继续推理

关键点:LLM 只是决策者,你才是执行者

工具定义

工具定义告诉 LLM 这个工具是做什么的、需要什么参数。

JSON Schema 格式

typescript
const tools = [{
  functionDeclarations: [{
    name: 'read_file',
    description: '读取指定路径的文件内容',
    parameters: {
      type: 'object',
      properties: {
        path: {
          type: 'string',
          description: '要读取的文件路径'
        }
      },
      required: ['path']
    }
  }]
}]

关键字段

字段说明
name工具名称,LLM 用它来指定调用哪个工具
description工具描述,帮助 LLM 理解何时使用这个工具
parameters参数定义,使用 JSON Schema 格式

description 很重要

好的 description:

typescript
description: '读取文件内容。用于查看代码、配置文件或文档。返回文件的完整文本内容。'

差的 description:

typescript
description: '读文件'

LLM 依赖 description 来决定是否使用这个工具。

执行流程

代码实现

1. 定义工具

typescript
const tools = [{
  functionDeclarations: [
    {
      name: 'read_file',
      description: '读取文件内容',
      parameters: {
        type: 'object',
        properties: {
          path: { type: 'string', description: '文件路径' }
        },
        required: ['path']
      }
    },
    {
      name: 'write_file',
      description: '写入文件',
      parameters: {
        type: 'object',
        properties: {
          path: { type: 'string', description: '文件路径' },
          content: { type: 'string', description: '文件内容' }
        },
        required: ['path', 'content']
      }
    }
  ]
}]

2. 实现工具执行

typescript
const toolHandlers = {
  read_file: async (args: { path: string }) => {
    const content = await fs.readFile(args.path, 'utf-8')
    return { content }
  },

  write_file: async (args: { path: string; content: string }) => {
    await fs.writeFile(args.path, args.content)
    return { success: true }
  }
}

3. 处理工具调用

typescript
async function handleToolCalls(functionCalls) {
  const results = []

  for (const call of functionCalls) {
    const handler = toolHandlers[call.name]
    const result = await handler(call.args)

    results.push({
      functionResponse: {
        name: call.name,
        response: result
      }
    })
  }

  return results
}

4. 完整循环

typescript
async function agentLoop(userMessage: string) {
  const chat = model.startChat()
  let response = await chat.sendMessage(userMessage)

  while (true) {
    const functionCalls = response.response.functionCalls()

    if (!functionCalls || functionCalls.length === 0) {
      return response.response.text()
    }

    // 执行工具
    const results = await handleToolCalls(functionCalls)

    // 发送结果,继续对话
    response = await chat.sendMessage(results)
  }
}

gemini-cli 的工具定义

gemini-cli 的工具定义在 packages/core/src/tools/ 目录。

每个工具文件导出一个 ServerTool 对象:

typescript
// packages/core/src/tools/read-file.ts (简化版)
export const readFileTool: ServerTool = {
  name: 'read_file',

  schema: {
    name: 'read_file',
    description: '读取文件内容',
    parameters: {
      type: 'object',
      properties: {
        file_path: {
          type: 'string',
          description: '要读取的文件的绝对路径'
        }
      },
      required: ['file_path']
    }
  },

  async execute(params: { file_path: string }) {
    const content = await fs.readFile(params.file_path, 'utf-8')
    return { content }
  }
}

常见陷阱

1. 参数类型不匹配

LLM 返回的参数可能是字符串,即使你定义的是 number:

typescript
// LLM 可能返回 { count: "5" } 而不是 { count: 5 }
// 需要手动转换
const count = Number(args.count)

2. 必填参数缺失

即使定义了 required,LLM 有时也会遗漏参数:

typescript
async execute(args) {
  if (!args.path) {
    return { error: '缺少 path 参数' }
  }
  // ...
}

3. 工具太多

工具数量多了,LLM 可能选错。建议:

  • 核心工具 5-10 个
  • 相似功能合并
  • description 要有区分度

小结

  • Function Calling 让 LLM 决定调用什么,你负责执行
  • 工具定义使用 JSON Schema 格式
  • description 对 LLM 的决策很重要
  • 需要处理参数校验和类型转换

下一步

了解了 Function Calling 的原理,接下来看 gemini-cli 如何设计工具接口:工具接口设计 →

通过实际源码学习 AI Agent 开发