《60天AI学习计划启动 | Day 07:Function Calling - 让 AI 调用外部工具》
Day 07: Function Calling - 让 AI 调用外部工具
学习目标
核心学习内容
1. Function Calling 基础
什么是 Function Calling?
- 让 AI 决定何时调用外部函数
- AI 分析用户意图,选择合适函数
- 返回函数调用参数,由应用执行
工作流程:
用户提问 → AI 分析意图 → 决定调用函数 → 返回参数 → 执行函数 → 返回结果 → AI 生成回答
应用场景:
- 天气查询
- 数据库查询
- API 调用
- 计算任务
- 文件操作
2. 函数定义(Schema)
基本结构:
{
name: "get_weather", // 函数名
description: "获取指定城市的天气信息", // 函数描述
parameters: { // 参数定义
type: "object",
properties: {
city: {
type: "string",
description: "城市名称"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "温度单位"
}
},
required: ["city"] // 必填参数
}
}
3. 函数调用流程
步骤:
- 定义函数列表
- 发送给 AI(包含函数定义)
- AI 决定是否调用函数
- 解析函数调用参数
- 执行函数
- 将结果返回给 AI
- AI 生成最终回答
实践作业
作业1:定义函数 Schema
src/services/functions.js:
/**
* 函数定义列表
*/
export const functionDefinitions = [
{
name: "get_weather",
description: "获取指定城市的当前天气信息",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名称,例如:北京、上海、New York"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
description: "温度单位,celsius 表示摄氏度,fahrenheit 表示华氏度",
default: "celsius"
}
},
required: ["city"]
}
},
{
name: "calculate",
description: "执行数学计算",
parameters: {
type: "object",
properties: {
expression: {
type: "string",
description: "数学表达式,例如:2+2, 10*5, sqrt(16)"
}
},
required: ["expression"]
}
},
{
name: "get_current_time",
description: "获取当前时间",
parameters: {
type: "object",
properties: {
timezone: {
type: "string",
description: "时区,例如:Asia/Shanghai, America/New_York",
default: "Asia/Shanghai"
},
format: {
type: "string",
enum: ["12h", "24h"],
description: "时间格式",
default: "24h"
}
}
}
},
{
name: "search_web",
description: "搜索网络信息(模拟)",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "搜索关键词"
}
},
required: ["query"]
}
}
];
作业2:实现函数执行器
src/services/function-executor.js:
import axios from 'axios';
/**
* 函数执行器
*/
export class FunctionExecutor {
/**
* 执行函数
*/
async execute(functionName, parameters) {
switch (functionName) {
case 'get_weather':
return await this.getWeather(parameters);
case 'calculate':
return this.calculate(parameters);
case 'get_current_time':
return this.getCurrentTime(parameters);
case 'search_web':
return this.searchWeb(parameters);
default:
throw new Error(`未知函数: ${functionName}`);
}
}
/**
* 获取天气(使用 OpenWeatherMap API)
*/
async getWeather({ city, unit = 'celsius' }) {
try {
// 注意:需要注册 OpenWeatherMap 获取 API Key
const apiKey = process.env.WEATHER_API_KEY || 'your-api-key';
const url = `https://api.openweathermap.org/data/2.5/weather`;
const response = await axios.get(url, {
params: {
q: city,
appid: apiKey,
units: unit === 'celsius' ? 'metric' : 'imperial',
lang: 'zh_cn'
}
});
const data = response.data;
return {
city: data.name,
country: data.sys.country,
temperature: Math.round(data.main.temp),
unit: unit === 'celsius' ? '°C' : '°F',
description: data.weather[0].description,
humidity: data.main.humidity,
windSpeed: data.wind.speed,
feelsLike: Math.round(data.main.feels_like)
};
} catch (error) {
// 如果 API 不可用,返回模拟数据
return {
city: city,
temperature: unit === 'celsius' ? 22 : 72,
unit: unit === 'celsius' ? '°C' : '°F',
description: '晴天',
humidity: 60,
windSpeed: 10,
note: '(模拟数据,实际需要配置天气 API)'
};
}
}
/**
* 数学计算
*/
calculate({ expression }) {
try {
// 安全计算:只允许数学表达式
const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, '');
const result = Function(`"use strict"; return (${sanitized})`)();
return {
expression: expression,
result: result,
type: typeof result
};
} catch (error) {
return {
error: '计算失败,请检查表达式格式'
};
}
}
/**
* 获取当前时间
*/
getCurrentTime({ timezone = 'Asia/Shanghai', format = '24h' }) {
const now = new Date();
const options = {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: format === '12h'
};
const formatted = new Intl.DateTimeFormat('zh-CN', options).format(now);
return {
time: formatted,
timezone: timezone,
format: format,
timestamp: now.getTime()
};
}
/**
* 网络搜索(模拟)
*/
searchWeb({ query }) {
// 模拟搜索结果
return {
query: query,
results: [
{
title: `关于"${query}"的搜索结果1`,
url: 'https://example.com/1',
snippet: `这是关于${query}的相关信息...`
},
{
title: `关于"${query}"的搜索结果2`,
url: 'https://example.com/2',
snippet: `更多关于${query}的内容...`
}
],
note: '这是模拟搜索结果,实际需要集成真实搜索 API'
};
}
}
export const functionExecutor = new FunctionExecutor();
作业3:实现 Function Calling 服务
src/services/function-calling.js:
import OpenAI from 'openai';
import { functionDefinitions } from './functions.js';
import { functionExecutor } from './function-executor.js';
import { logger } from '../utils/logger.js';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
/**
* Function Calling 聊天服务
*/
export async function chatWithFunctionCalling(
message,
conversationHistory = [],
options = {}
) {
try {
const messages = [
{
role: 'system',
content: `你是一个智能助手,可以使用工具来帮助用户。
当用户需要查询天气、计算、获取时间等信息时,你应该调用相应的函数。
调用函数后,根据返回的结果生成友好的回答。`
},
...conversationHistory,
{ role: 'user', content: message }
];
// 第一次调用:让 AI 决定是否调用函数
const completion = await openai.chat.completions.create({
model: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
messages: messages,
functions: functionDefinitions,
function_call: 'auto', // 让 AI 自动决定
temperature: 0.7
});
const assistantMessage = completion.choices[0].message;
messages.push(assistantMessage);
// 检查是否有函数调用
if (assistantMessage.function_call) {
const functionName = assistantMessage.function_call.name;
const functionArgs = JSON.parse(assistantMessage.function_call.arguments);
logger.info(`调用函数: ${functionName}`, functionArgs);
// 执行函数
const functionResult = await functionExecutor.execute(
functionName,
functionArgs
);
logger.info(`函数结果:`, functionResult);
// 将函数结果添加到消息中
messages.push({
role: 'function',
name: functionName,
content: JSON.stringify(functionResult)
});
// 第二次调用:让 AI 根据函数结果生成回答
const finalCompletion = await openai.chat.completions.create({
model: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
messages: messages,
temperature: 0.7
});
return {
content: finalCompletion.choices[0].message.content,
functionCalled: functionName,
functionResult: functionResult,
usage: finalCompletion.usage
};
} else {
// 没有函数调用,直接返回回答
return {
content: assistantMessage.content,
functionCalled: null,
usage: completion.usage
};
}
} catch (error) {
logger.error('Function Calling 失败:', error);
throw error;
}
}
/**
* 流式 Function Calling(高级)
*/
export async function streamChatWithFunctionCalling(
message,
conversationHistory = [],
callbacks = {}
) {
const { onChunk, onFunctionCall, onComplete, onError } = callbacks;
try {
const messages = [
{
role: 'system',
content: `你是一个智能助手,可以使用工具来帮助用户。`
},
...conversationHistory,
{ role: 'user', content: message }
];
// 第一次调用
const completion = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: messages,
functions: functionDefinitions,
function_call: 'auto'
});
const assistantMessage = completion.choices[0].message;
messages.push(assistantMessage);
if (assistantMessage.function_call) {
const functionName = assistantMessage.function_call.name;
const functionArgs = JSON.parse(assistantMessage.function_call.arguments);
if (onFunctionCall) {
onFunctionCall(functionName, functionArgs);
}
// 执行函数
const functionResult = await functionExecutor.execute(
functionName,
functionArgs
);
messages.push({
role: 'function',
name: functionName,
content: JSON.stringify(functionResult)
});
// 流式生成最终回答
const stream = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: messages,
stream: true
});
let fullContent = '';
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content) {
fullContent += content;
if (onChunk) {
onChunk(content);
}
}
}
if (onComplete) {
onComplete({
content: fullContent,
functionCalled: functionName,
functionResult: functionResult
});
}
} else {
// 没有函数调用,直接流式返回
if (onChunk) {
onChunk(assistantMessage.content);
}
if (onComplete) {
onComplete({ content: assistantMessage.content });
}
}
} catch (error) {
if (onError) {
onError(error);
}
throw error;
}
}
作业4:创建 Function Calling 路由
src/routes/function-calling.js:
import express from 'express';
import {
chatWithFunctionCalling,
streamChatWithFunctionCalling
} from '../services/function-calling.js';
import { logger } from '../utils/logger.js';
export const functionCallingRouter = express.Router();
// POST /api/function-calling/chat
functionCallingRouter.post('/chat', async (req, res) => {
try {
const { message, conversationHistory = [] } = req.body;
if (!message) {
return res.status(400).json({
success: false,
error: '消息内容不能为空'
});
}
const result = await chatWithFunctionCalling(
message,
conversationHistory
);
res.json({
success: true,
data: {
message: result.content,
functionCalled: result.functionCalled,
functionResult: result.functionResult,
usage: result.usage
}
});
} catch (error) {
logger.error('Function Calling 接口错误:', error);
res.status(500).json({
success: false,
error: error.message || '服务异常'
});
}
});
// POST /api/function-calling/stream
functionCallingRouter.post('/stream', async (req, res) => {
try {
const { message, conversationHistory = [] } = req.body;
if (!message) {
return res.status(400).json({
success: false,
error: '消息内容不能为空'
});
}
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
await streamChatWithFunctionCalling(
message,
conversationHistory,
{
onFunctionCall: (functionName, args) => {
res.write(`data: ${JSON.stringify({
type: 'function_call',
functionName,
args
})}\n\n`);
},
onChunk: (content) => {
res.write(`data: ${JSON.stringify({ content })}\n\n`);
},
onComplete: (result) => {
res.write(`data: ${JSON.stringify({
done: true,
functionCalled: result.functionCalled,
functionResult: result.functionResult
})}\n\n`);
res.end();
},
onError: (error) => {
res.write(`data: ${JSON.stringify({
error: error.message
})}\n\n`);
res.end();
}
}
);
} catch (error) {
logger.error('流式 Function Calling 错误:', error);
if (!res.headersSent) {
res.status(500).json({
success: false,
error: error.message
});
}
}
});
作业5:前端集成 Function Calling
utils/api.js(添加 Function Calling 方法):
/**
* Function Calling 聊天
*/
export async function chatWithFunctions(message, conversationHistory = []) {
try {
const response = await api.post('/function-calling/chat', {
message,
conversationHistory
});
if (response.success) {
return response.data;
} else {
throw new Error(response.error);
}
} catch (error) {
throw error;
}
}
/**
* 流式 Function Calling
*/
export function streamChatWithFunctions(
message,
conversationHistory = [],
callbacks = {}
) {
const { onFunctionCall, onChunk, onComplete, onError } = callbacks;
return new Promise((resolve, reject) => {
fetch('http://localhost:3000/api/function-calling/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
conversationHistory
})
})
.then(response => {
if (!response.ok) {
throw new Error('请求失败');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function readStream() {
reader.read().then(({ done, value }) => {
if (done) {
resolve();
return;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.substring(6));
if (data.error) {
if (onError) onError(new Error(data.error));
reject(new Error(data.error));
return;
}
if (data.done) {
if (onComplete) onComplete(data);
resolve(data);
return;
}
if (data.type === 'function_call' && onFunctionCall) {
onFunctionCall(data.functionName, data.args);
}
if (data.content && onChunk) {
onChunk(data.content);
}
} catch (e) {
console.error('解析失败:', e);
}
}
}
readStream();
}).catch(error => {
if (onError) onError(error);
reject(error);
});
}
readStream();
})
.catch(error => {
if (onError) onError(error);
reject(error);
});
});
}
作业6:测试 Function Calling
test-function-calling.js:
import { chatWithFunctionCalling } from './src/services/function-calling.js';
async function testWeather() {
console.log('测试:天气查询');
const result = await chatWithFunctionCalling('北京今天天气怎么样?');
console.log('回答:', result.content);
console.log('调用的函数:', result.functionCalled);
console.log('函数结果:', result.functionResult);
}
async function testCalculate() {
console.log('\n测试:数学计算');
const result = await chatWithFunctionCalling('帮我计算 123 * 456 + 789');
console.log('回答:', result.content);
console.log('调用的函数:', result.functionCalled);
}
async function testTime() {
console.log('\n测试:获取时间');
const result = await chatWithFunctionCalling('现在几点了?');
console.log('回答:', result.content);
console.log('调用的函数:', result.functionCalled);
}
async function runTests() {
await testWeather();
await testCalculate();
await testTime();
}
runTests();
遇到的问题
问题1:函数参数解析失败
错误: JSON.parse 失败
解决方案:
try {
const functionArgs = JSON.parse(assistantMessage.function_call.arguments);
} catch (error) {
// 尝试修复常见问题
const fixed = assistantMessage.function_call.arguments
.replace(/'/g, '"') // 单引号转双引号
.replace(/(\w+):/g, '"$1":'); // 添加引号
const functionArgs = JSON.parse(fixed);
}
问题2:AI 不调用函数
解决方案:
// 1. 改进函数描述,更清晰
description: "获取指定城市的当前天气信息。当用户询问天气时,必须调用此函数。"
// 2. 在系统提示词中强调
content: "当用户询问天气、时间、计算等问题时,你必须调用相应的函数。"
// 3. 使用 function_call: 'required'(强制调用)
问题3:函数执行错误
解决方案:
try {
const result = await functionExecutor.execute(functionName, functionArgs);
return result;
} catch (error) {
// 返回错误信息给 AI
return {
error: error.message,
functionName: functionName,
args: functionArgs
};
}
学习总结
今日收获
- ✅ 理解 Function Calling 概念
- ✅ 掌握函数 Schema 定义
- ✅ 实现函数执行器
- ✅ 集成外部 API
- ✅ 实现完整 Function Calling 流程
关键知识点
- Function Calling 让 AI 更强大,可以调用外部工具
- 函数定义要清晰,描述和参数要准确
- 两阶段调用:先决定调用,再生成回答
- 错误处理重要,函数执行失败要有降级方案
- 实际应用广泛:天气、计算、搜索等
Function Calling 优势
- ✅ 扩展 AI 能力
- ✅ 实时数据获取
- ✅ 精确计算
- ✅ 外部服务集成
明日计划
明天将学习:
期待明天的学习! 🚀
参考资源
代码仓库
项目已更新:
- ✅ Function Calling 服务
- ✅ 函数执行器
- ✅ 天气查询功能
- ✅ 测试示例
GitHub 提交: Day 07 - Function Calling 实现
标签: #AI学习 #FunctionCalling #工具调用 #API集成 #学习笔记
写在最后
今天学习了 Function Calling,这是让 AI 更强大的关键技术。
通过函数调用,AI 可以获取实时数据、执行计算、调用外部服务。
明天将学习向量数据库,为 RAG 应用打下基础!
继续加油! 💪
快速检查清单
完成这些,第七天就达标了! ✅

浙公网安备 33010602011771号