为什么需要 Function Call?
传统的 AI 对话存在一个巨大的局限:大模型无法获取实时信息,也无法执行具体操作。
| 场景 | 传统 AI | 支持 Function Call 的 AI |
|---|---|---|
| 查询天气 | "我无法获取实时天气信息" | 调用天气 API,返回"北京今天晴天,25°C" |
| 发送邮件 | "请手动发送" | 调用邮件工具,自动发送 |
| 数据库查询 | 无法操作 | 调用查询工具,返回数据 |
某电商平台的实践数据显示,引入 Function Calling 后,查询订单物流等操作的响应时间从 12.7 秒缩短至 3.2 秒。
Function Call 核心原理
Function Call 的核心机制是将自然语言请求转换为结构化函数调用:
用户输入 → AI分析 → 决定调用工具 → 执行工具 → 返回结果 → AI生成最终回答
整个流程分为四个关键步骤:
- 意图识别:AI 理解用户想要做什么
- 参数提取:从用户输入中提取函数所需的参数
- 工具执行:系统调用对应的函数
- 结果增强:AI 将工具返回的结果组织成自然语言回复
阿里云百炼 Function Call 配置
确认模型支持
首先,需要确认使用的模型支持 Function Call 功能。阿里云百炼的以下模型均支持:
| 模型 | 说明 | 适用场景 |
|---|---|---|
| qwen-turbo | 轻量快速 | 简单任务,性价比最高 |
| qwen-plus | 均衡型 | 大多数场景,推荐使用 |
| qwen-max | 旗舰型 | 复杂推理任务 |
环境配置
创建项目并安装依赖:
bash
mkdir langchain-function-call
cd langchain-function-call
npm init -y
# 安装依赖
npm install langchain @langchain/core @langchain/openai dotenv zod
npm install -D typescript @types/node tsx
创建 .env 文件:
bash
BAILIAN_API_KEY=你的API Key
BAILIAN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
注意 :
compatible-mode是阿里云百炼的 OpenAI 兼容模式入口,LangChain 可以通过这个地址无缝对接。
前端常用工具函数封装
LangChain 中创建工具的标准方式是使用 tool 函数,它需要定义名称、描述和参数 Schema。
工具 1:文本格式化工具
typescript
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// 文本格式化工具 - 支持大小写转换、去除空格等
const textFormatter = tool(
async ({ text, action }: { text: string; action: string }) => {
switch (action) {
case "uppercase":
return text.toUpperCase();
case "lowercase":
return text.toLowerCase();
case "trim":
return text.trim();
case "reverse":
return text.split('').reverse().join('');
default:
return `未知操作: ${action},支持的操作: uppercase, lowercase, trim, reverse`;
}
},
{
name: "text_formatter",
description: "对文本进行格式化处理,支持大小写转换、去除空格、反转字符串等操作",
schema: z.object({
text: z.string().describe("需要处理的原始文本"),
action: z.enum(["uppercase", "lowercase", "trim", "reverse"]).describe("要执行的操作类型"),
}),
}
);
工具 2:数据计算工具(JSON 计算器)
typescript
// 数据计算工具 - 支持基本的数学运算和数组操作
const dataCalculator = tool(
async ({ expression, data }: { expression: string; data?: any }) => {
try {
// 处理数学表达式
if (expression === "sum" && Array.isArray(data)) {
return data.reduce((acc, val) => acc + Number(val), 0).toString();
}
if (expression === "average" && Array.isArray(data)) {
const sum = data.reduce((acc, val) => acc + Number(val), 0);
return (sum / data.length).toString();
}
if (expression === "max" && Array.isArray(data)) {
return Math.max(...data.map(Number)).toString();
}
if (expression === "min" && Array.isArray(data)) {
return Math.min(...data.map(Number)).toString();
}
// 简单数学运算
const allowedChars = /[0-9+\-*/().\s]/;
if (allowedChars.test(expression)) {
// 安全地执行数学运算
const result = Function('"use strict";return (' + expression + ')')();
return result.toString();
}
return `无法计算表达式: ${expression}`;
} catch (error) {
return `计算错误: ${error}`;
}
},
{
name: "data_calculator",
description: "执行数据计算,包括数学运算、数组求和、平均值、最大值、最小值等",
schema: z.object({
expression: z.string().describe("计算表达式,如 'sum', 'average', 'max', 'min' 或数学公式如 '2+3*4'"),
data: z.array(z.number()).optional().describe("当表达式为 sum/average/max/min 时,需要提供数据数组"),
}),
}
);
工具 3:时间日期工具
typescript
// 时间日期工具 - 获取当前时间、日期计算等
const dateTimeTool = tool(
async ({ action, date, days }: { action: string; date?: string; days?: number }) => {
const now = new Date();
switch (action) {
case "current":
return {
date: now.toLocaleDateString("zh-CN"),
time: now.toLocaleTimeString("zh-CN"),
weekday: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][now.getDay()],
timestamp: now.getTime()
};
case "format":
if (date) {
const d = new Date(date);
return d.toLocaleDateString("zh-CN");
}
return now.toLocaleDateString("zh-CN");
case "addDays":
if (days) {
const future = new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
return future.toLocaleDateString("zh-CN");
}
return "请提供天数参数";
default:
return `未知操作: ${action}`;
}
},
{
name: "date_time",
description: "获取当前日期时间、格式化日期、计算未来/过去日期",
schema: z.object({
action: z.enum(["current", "format", "addDays"]).describe("操作类型"),
date: z.string().optional().describe("需要格式化的日期字符串"),
days: z.number().optional().describe("添加的天数(正数为未来,负数为过去)"),
}),
}
);
Function Call 参数详解
工具定义的关键要素
创建工具时,以下三个要素至关重要:
| 要素 | 说明 | 示例 |
|---|---|---|
| name | 工具的唯一标识,AI 通过名称选择工具 | "text_formatter" |
| description | 工具功能描述,AI 据此判断何时调用 | "对文本进行格式化处理" |
| schema | 参数结构定义,使用 Zod 描述 | z.object({ text: z.string() }) |
Description 编写技巧
Description 是 AI 理解工具用途的关键,编写时要注意:
typescript
// ❌ 不好的 description - 信息不足
description: "处理文本"
// ✅ 好的 description - 清晰说明功能和使用场景
description: "对文本进行格式化处理,支持大小写转换(uppercase/lowercase)、去除空格(trim)、反转字符串(reverse)。当用户要求修改文本格式时使用此工具。"
// ✅ 更好的 description - 包含触发条件
description: "数据计算工具。当用户需要:1) 计算数学表达式如'2+3*4';2) 对数组求和/求平均/找最大最小值时使用。"
Schema 参数描述技巧
typescript
schema: z.object({
text: z.string().describe("需要处理的原始文本,必填"),
action: z.enum(["uppercase", "lowercase", "trim", "reverse"])
.describe("要执行的操作类型:uppercase转大写、lowercase转小写、trim去除首尾空格、reverse反转字符串"),
})
AI 调用工具函数完整实战
绑定工具到模型
typescript
import { ChatOpenAI } from "@langchain/openai";
import dotenv from "dotenv";
dotenv.config();
async function functionCallDemo() {
// 1. 初始化模型(选择支持 function calling 的模型)
const model = new ChatOpenAI({
apiKey: process.env.DASHSCOPE_API_KEY,
configuration: {
baseURL: process.env.DASHSCOPE_API_URL
},
model: "qwen-plus", // 推荐使用 qwen-plus,功能更完善
temperature: 0.3,
});
// 2. 定义工具列表
const tools = [textFormatter, dataCalculator, dateTimeTool];
// 3. 绑定工具到模型
const modelWithTools = model.bindTools(tools);
// 4. 用户请求
const userRequests = [
"把 'hello world' 转成大写",
"帮我计算 25 * 4 + 10 等于多少",
"今天是几号?",
"计算数组 [10, 25, 35, 40, 15] 的平均值",
"把 ' JavaScript is awesome ' 两边的空格去掉"
];
for (const request of userRequests) {
console.log(`\n📝 用户: ${request}`);
// 5. AI 决定是否调用工具
const response = await modelWithTools.invoke(request);
// 6. 检查是否有工具调用
if (response.tool_calls && response.tool_calls.length > 0) {
console.log(`🔧 AI 决定调用工具: ${response.tool_calls[0].name}`);
console.log(`📋 参数: ${JSON.stringify(response.tool_calls[0].args)}`);
// 7. 执行工具调用
for (const toolCall of response.tool_calls) {
let result;
switch (toolCall.name) {
case "text_formatter":
result = await textFormatter.invoke(toolCall.args);
break;
case "data_calculator":
result = await dataCalculator.invoke(toolCall.args);
break;
case "date_time":
result = await dateTimeTool.invoke(toolCall.args);
break;
default:
result = "未知工具";
}
console.log(`✅ 工具返回: ${typeof result === 'object' ? JSON.stringify(result) : result}`);
}
} else {
console.log(`💬 AI 直接回复: ${response.content}`);
}
}
}
functionCallDemo();
预期输出:
text
📝 用户: 帮我计算 25 * 4 + 10 等于多少
🔧 AI 决定调用工具: data_calculator
📋 参数: {"expression":"25 * 4 + 10"}
✅ 工具返回: 110
📝 用户: 今天是几号?
🔧 AI 决定调用工具: date_time
📋 参数: {"action":"current"}
✅ 工具返回: {"date":"2026/6/3","time":"14:11:45","weekday":"周三","timestamp":1780467105818}
📝 用户: 计算数组 [10, 25, 35, 40, 15] 的平均值
🔧 AI 决定调用工具: data_calculator
📋 参数: {"expression":"average","data":[10,25,35,40,15]}
✅ 工具返回: 25
📝 用户: 把 ' JavaScript is awesome ' 两边的空格去掉
🔧 AI 决定调用工具: text_formatter
📋 参数: {"action":"trim","text":" JavaScript is awesome "}
✅ 工具返回: JavaScript is awesome
完整对话式调用(带工具结果回传)
在实际应用中,工具执行的结果需要返回给 AI,让 AI 生成最终的自然语言回复:
typescript
import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { HumanMessage, ToolMessage, AIMessage } from "@langchain/core/messages";
import dotenv from "dotenv";
dotenv.config();
async function fullFunctionCallDemo() {
// 定义工具
const calculator = tool(
async ({ expression }: { expression: string }) => {
try {
const result = Function('"use strict";return (' + expression + ')')();
return result.toString();
} catch (error) {
return `计算错误: ${error}`;
}
},
{
name: "calculator",
description: "执行数学计算,支持加减乘除和括号",
schema: z.object({
expression: z.string().describe("数学表达式,如 '2+3*4' 或 '(10-5)*2'"),
}),
}
);
const weatherTool = tool(
async ({ city }: { city: string }) => {
// 模拟天气查询
const weathers: Record<string, string> = {
"北京": "晴天,25°C,湿度40%",
"上海": "多云,28°C,湿度65%",
"深圳": "阵雨,30°C,湿度80%",
};
return weathers[city] || `${city}:晴天,22°C,湿度50%`;
},
{
name: "get_weather",
description: "获取指定城市的天气信息",
schema: z.object({
city: z.string().describe("城市名称,如'北京'、'上海'"),
}),
}
);
const tools = [calculator, weatherTool];
const model = new ChatOpenAI({
openai_api_key: process.env.BAILIAN_API_KEY,
configuration: { baseURL: process.env.BAILIAN_BASE_URL },
model: "qwen-plus",
temperature: 0.3,
});
const modelWithTools = model.bindTools(tools);
// 用户请求
const userInput = "北京今天天气怎么样?另外帮我算一下 (100-25)*3+50 等于多少";
console.log(`📝 用户: ${userInput}\n`);
// 构建消息
const messages = [new HumanMessage(userInput)];
// 第一轮:AI 决定调用工具
const aiResponse = await modelWithTools.invoke(messages);
messages.push(aiResponse);
console.log(`🤖 AI 决定调用 ${aiResponse.tool_calls?.length || 0} 个工具:`);
// 执行工具调用
if (aiResponse.tool_calls) {
for (const toolCall of aiResponse.tool_calls) {
console.log(` 🔧 ${toolCall.name}(${JSON.stringify(toolCall.args)})`);
let result;
if (toolCall.name === "calculator") {
result = await calculator.invoke(toolCall.args);
} else if (toolCall.name === "get_weather") {
result = await weatherTool.invoke(toolCall.args);
} else {
result = "未知工具";
}
console.log(` ✅ 返回: ${result}`);
// 将工具执行结果添加到消息中
messages.push(new ToolMessage({
content: result,
tool_call_id: toolCall.id,
}));
}
}
// 第二轮:AI 根据工具结果生成最终回复
console.log(`\n🔄 AI 整合结果中...\n`);
const finalResponse = await modelWithTools.invoke(messages);
console.log(`🤖 最终回复: ${finalResponse.content}`);
}
fullFunctionCallDemo();
预期输出:
text
[dotenv@17.3.1] injecting env (4) from .env -- tip: ⚙️ suppress all logs with { quiet: true }
📝 用户: 北京今天天气怎么样?另外帮我算一下 (100-25)*3+50 等于多少
🤖 AI 决定调用 2 个工具:
🔧 get_weather({"city":"北京"})
✅ 返回: 晴天,25°C,湿度40%
🔧 calculator({"expression":"(100-25)*3+50"})
✅ 返回: 275
🔄 AI 整合结果中...
🤖 最终回复: 北京今天天气晴朗,气温25°C,湿度40%。
而计算结果:(100 - 25) × 3 + 50 = 275。
前端结合 Function Call 的业务思路
思路一:AI 辅助代码生成与优化
typescript
// 代码分析工具
const codeAnalyzer = tool(
async ({ code, analysisType }: { code: string; analysisType: string }) => {
// 模拟代码分析
const analyses = {
complexity: `圈复杂度: 8,建议拆分为更小的函数`,
performance: `存在不必要的重渲染,建议使用 useMemo`,
security: `发现 XSS 风险:未对用户输入进行转义`,
};
return analyses[analysisType as keyof typeof analyses] || "分析完成";
},
{
name: "code_analyzer",
description: "分析前端代码的质量、性能、安全问题",
schema: z.object({
code: z.string().describe("需要分析的代码"),
analysisType: z.enum(["complexity", "performance", "security"]).describe("分析类型"),
}),
}
);
思路二:智能表单验证助手
typescript
const formValidator = tool(
async ({ value, ruleType }: { value: string; ruleType: string }) => {
const rules: Record<string, (v: string) => boolean> = {
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
phone: (v) => /^1[3-9]\d{9}$/.test(v),
idCard: (v) => /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(v),
};
const isValid = rules[ruleType]?.(value) ?? true;
return JSON.stringify({ valid: isValid, message: isValid ? "验证通过" : `格式不正确` });
},
{
name: "form_validator",
description: "验证表单字段格式,支持邮箱、手机号、身份证等",
schema: z.object({
value: z.string().describe("需要验证的值"),
ruleType: z.enum(["email", "phone", "idCard"]).describe("验证规则类型"),
}),
}
);
思路三:API 调用代理
typescript
const apiCaller = tool(
async ({ endpoint, params }: { endpoint: string; params: Record<string, string> }) => {
// 模拟 API 调用
const mockResponses: Record<string, any> = {
"user/info": { id: 1, name: "张三", role: "admin" },
"order/list": [{ id: "ORD001", amount: 299, status: "paid" }],
};
const response = mockResponses[endpoint] || { message: "数据获取成功", params };
return JSON.stringify(response);
},
{
name: "api_caller",
description: "调用业务 API 获取数据,支持用户信息、订单列表等",
schema: z.object({
endpoint: z.enum(["user/info", "order/list"]).describe("API 端点"),
params: z.record(z.string()).optional().describe("请求参数"),
}),
}
);
Function Call 常见问题排查
问题 1:模型不调用工具
现象:明明定义了工具,但 AI 直接回复而不调用
解决方案:
- 检查模型是否支持 Function Call(qwen-turbo、qwen-plus、qwen-max 均支持)
- 优化工具的 description,让 AI 更清楚何时使用
- 在 prompt 中明确提示 AI 可以使用哪些工具
typescript
// 添加系统提示,引导 AI 使用工具
const systemPrompt = "你可以使用 calculator 工具进行数学计算,使用 get_weather 工具查询天气";
问题 2:参数传递错误
现象:工具被调用但参数格式不对
解决方案:
- 使用 Zod 的
describe为每个参数添加详细说明 - 在 schema 中明确参数类型和约束
问题 3:工具执行超时
现象:工具执行时间过长,整个流程卡住
解决方案:
- 为工具执行添加超时控制
- 将耗时操作异步化
结语
通过这篇教程,我们学习了 LangChain Function Call 的核心概念和实践方法。Function Call 是让 AI 从"聊天"走向"干活"的关键能力,掌握它就能构建真正实用的 AI 应用。
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!