Function Calling 原理
什么是 Function Calling
Function Calling 让 LLM 能够"调用函数"。但 LLM 并不真正执行函数,而是:
- 你告诉 LLM 有哪些函数可用(工具定义)
- LLM 决定要调用哪个函数、传什么参数
- 你执行函数,把结果告诉 LLM
- 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 如何设计工具接口:工具接口设计 →