Function Calling 深度解析
引言
想象一下,你的 AI 助手不仅能回答问题,还能:
- 🌤️ 查询实时天气数据
- 📦 检查库存并下单
- 📧 自动发送邮件通知
- 💳 处理支付交易
这就是 Function Calling(函数调用) 的魅力——让 AI 从"只会说话"的聊天机器人,进化为"能做事"的智能助手。
本文将基于 Microsoft.Extensions.AI (MEAI) 框架,深入讲解 Function Calling 的原理、实现方式和最佳实践。
什么是 Function Calling?
核心概念
Function Calling 是指大语言模型在对话过程中,能够识别何时需要外部数据或执行操作,并自动调用预定义的函数来获取结果。
sequenceDiagram
participant U as 用户
participant AI as AI模型
participant F as 业务函数
U->>AI: "北京今天天气怎么样?"
AI->>AI: 分析:需要天气数据
AI->>F: 调用 GetWeather("北京")
F-->>AI: 返回 "晴,25℃"
AI-->>U: "北京今天晴天,气温25℃"
与传统 API 调用的区别
特性
传统 API 调用
Function Calling
触发方式
开发者硬编码判断
AI 自动识别意图
参数提取
手动解析用户输入
AI 自动提取参数
多函数选择
if-else 分支
AI 自动选择函数
灵活性
固定流程
根据上下文动态调用
传统方式示例:
// ❌ 硬编码的判断逻辑
if (userInput.Contains("天气"))
{
var city = ExtractCity(userInput); // 手动提取
var weather = await GetWeather(city);
return $"{city}天气是{weather}";
}
else if (userInput.Contains("库存"))
{
var product = ExtractProduct(userInput);
var stock = await CheckStock(product);
return $"{product}库存为{stock}";
}
Function Calling 方式:
// ✅ AI 自动判断和调用
var tools = new ToolCollection
{
AIFunctionFactory.Create(GetWeather),
AIFunctionFactory.Create(CheckStock)
};
var options = new ChatOptions
{
Tools = tools,
ToolMode = ChatToolMode.Auto // AI 自动决定是否调用
};
var response = await chatClient.GetResponseAsync(messages, options);
// AI 会自动识别意图,选择正确的函数,提取参数,并整合结果
MEAI Function Calling 架构
核心组件
graph TB
A[IChatClient] --> B[ChatOptions]
B --> C[Tools 工具集合]
B --> D[ToolMode 调用模式]
C --> E[AIFunction]
E --> F[函数元数据]
E --> G[函数执行器]
style A fill:#4CAF50
style C fill:#2196F3
style E fill:#FF9800
1. AIFunction - 工具的抽象表示
public class AIFunction
{
public AIFunctionMetadata Metadata { get; } // 函数描述、参数 Schema
public Delegate? Invoke { get; } // 实际执行的委托
}
2. AIFunctionFactory - 工具创建工厂
// 从静态方法创建
var tool = AIFunctionFactory.Create(GetWeather);
// 从实例方法创建
var toolset = new WeatherService();
var tool = AIFunctionFactory.Create(toolset.GetWeather);
// 从 Lambda 创建
var tool = AIFunctionFactory.Create(() => DateTime.Now.ToString());
// 批量创建(从类的所有公开方法)
var tools = AIFunctionFactory.Create(typeof(WeatherService));
3. ToolMode - 调用策略
模式
说明
使用场景
Auto
AI 自动判断是否调用
大多数场景(推荐)
Required
强制 AI 调用至少一个工具
确保执行业务逻辑
None
禁用工具调用
纯对话场景
var options = new ChatOptions
{
ToolMode = ChatToolMode.Auto
};
基础实战:构建天气查询助手
步骤 1:定义业务函数
using System.ComponentModel;
// 使用 [Description] 特性帮助 AI 理解函数用途
public class WeatherService
{
[Description("查询指定城市的实时天气信息")]
public WeatherReport GetWeather(
[Description("要查询的城市名称,如'北京'、'上海'")] string city
)
{
// 模拟天气查询(实际应调用天气 API)
var temperature = Random.Shared.Next(-5, 36);
var condition = Random.Shared.NextDouble() > 0.5 ? "晴" : "雨";
return new WeatherReport(city, temperature, condition);
}
[Description("根据天气情况提供穿搭建议")]
public string SuggestOutfit(
[Description("城市名称")] string city
)
{
var weather = GetWeather(city);
return weather switch
{
{ Condition: "雨" } => $"{city}可能下雨,建议带伞穿防水外套",
{ Temperature: < 5 } => $"{city}温度{weather.Temperature}℃,请穿冬装保暖",
{ Temperature: > 28 } => $"{city}天气炎热({weather.Temperature}℃),建议穿短袖",
_ => $"{city}气温{weather.Temperature}℃,穿日常装束即可"
};
}
}
public record WeatherReport(string City, int Temperature, string Condition);
关键点:
- ✅ 使用
[Description]让 AI 理解函数和参数的含义 - ✅ 参数类型要明确(string、int、bool 等基础类型)
- ✅ 返回值可以是复杂对象,AI 会自动序列化
步骤 2:注册工具集合
using Microsoft.Extensions.AI;
// 创建工具集合
var weatherService = new WeatherService();
var tools = AIFunctionFactory.Create(typeof(WeatherService), weatherService);
// 或者手动注册
var tools = new ToolCollection
{
AIFunctionFactory.Create(weatherService.GetWeather),
AIFunctionFactory.Create(weatherService.SuggestOutfit)
};
Console.WriteLine($"已注册 {tools.Count} 个工具");
步骤 3:配置 ChatOptions
var options = new ChatOptions
{
Tools = tools,
ToolMode = ChatToolMode.Auto, // AI 自动判断是否调用
// 可选:允许单次响应中调用多个工具
AllowMultipleToolCalls = true,
// 可选:设置最大调用次数(防止无限循环)
MaxToolCalls = 5
};
步骤 4:执行对话
using Microsoft.Extensions.AI;
IChatClient chatClient = ...; // 创建客户端(见第一篇文章)
var messages = new List<ChatMessage>
{
new(ChatRole.System, "你是一个天气助手,可以查询天气和提供穿搭建议"),
new(ChatRole.User, "北京今天天气怎么样?穿什么合适?")
};
var response = await chatClient.GetResponseAsync(messages, options);
Console.WriteLine(response.Text);
// 输出示例: "北京今天晴天,气温25℃。气温适中,穿日常装束即可。"
完整执行流程:
sequenceDiagram
participant U as 用户
participant C as ChatClient
participant AI as AI模型
participant F1 as GetWeather()
participant F2 as SuggestOutfit()
U->>C: "北京天气怎么样?穿什么?"
C->>AI: 发送消息 + 工具定义
AI->>AI: 分析:需要天气信息
AI-->>C: 请求调用 GetWeather("北京")
C->>F1: 执行函数
F1-->>C: 返回 {City:"北京", Temperature:25, Condition:"晴"}
C->>AI: 发送函数结果
AI->>AI: 分析:需要穿搭建议
AI-->>C: 请求调用 SuggestOutfit("北京")
C->>F2: 执行函数
F2-->>C: 返回 "气温25℃,穿日常装束即可"
C->>AI: 发送函数结果
AI-->>C: 生成最终回答
C-->>U: "北京今天晴天,气温25℃。穿日常装束即可。"
高级技巧
技巧 1:自动函数调用 vs 手动处理
MEAI 提供了两种处理 Function Calling 的方式:
方式 A:自动调用(推荐)
// ✅ 使用 UseFunctionInvocation 中间件
IChatClient chatClient = new OpenAIClient(...)
.AsChatClient("gpt-4")
.UseFunctionInvocation(); // 自动执行函数调用
// 完全透明,无需手动处理
var response = await chatClient.GetResponseAsync(messages, options);
Console.WriteLine(response.Text); // 直接得到最终答案
优点:
- ✅ 代码简洁,无需手动循环
- ✅ 自动处理多轮函数调用
- ✅ 自动将结果注入对话历史
方式 B:手动处理(更灵活)
// ✅ 需要审批或日志时使用
var response = await chatClient.GetResponseAsync(messages, options);
// 检查是否有函数调用请求
while (response.Finish Reason == ChatFinishReason.ToolCalls)
{
foreach (var call in response.Message.Contents.OfType<FunctionCallContent>())
{
Console.WriteLine($"AI 请求调用: {call.Name}({call.Arguments})");
// 可选:请求用户审批
if (!await RequestApprovalAsync(call))
{
continue;
}
// 手动执行函数
var result = await call.InvokeAsync();
// 将结果添加到对话历史
messages.Add(new ChatMessage(ChatRole.Tool, result));
}
// 继续对话
response = await chatClient.GetResponseAsync(messages, options);
}
Console.WriteLine(response.Text);
适用场景:
- 需要用户审批敏感操作(如支付)
- 需要详细日志记录
- 需要自定义错误处理
技巧 2:参数验证与错误处理
public class OrderService
{
[Description("提交订单")]
public async Task<OrderResult> SubmitOrder(
[Description("商品ID")] string productId,
[Description("数量(必须大于0)")] int quantity
)
{
// 参数验证
if (quantity <= 0)
{
return new OrderResult(false, "数量必须大于0");
}
if (string.IsNullOrWhiteSpace(productId))
{
return new OrderResult(false, "商品ID不能为空");
}
try
{
// 执行业务逻辑
await _orderRepository.CreateOrderAsync(productId, quantity);
return new OrderResult(true, "订单创建成功");
}
catch (Exception ex)
{
// 返回错误信息给 AI
return new OrderResult(false, $"创建失败: {ex.Message}");
}
}
}
public record OrderResult(bool Success, string Message);
关键点:
- ✅ 在函数内部进行参数验证
- ✅ 返回结构化的结果(成功/失败 + 消息)
- ✅ 捕获异常并转换为用户友好的消息
技巧 3:上下文传递(ToolContext)
有时工具需要访问当前用户信息、会话状态等上下文:
using Microsoft.Extensions.AI;
public class UserService
{
[Description("获取当前用户的订单列表")]
public List<Order> GetMyOrders(ToolContext context)
{
// 从上下文中获取用户信息
var userId = context.Properties["UserId"] as string;
if (string.IsNullOrEmpty(userId))
{
throw new InvalidOperationException("用户未登录");
}
return _orderRepository.GetOrdersByUser(userId);
}
}
// 使用时传递上下文
var options = new ChatOptions
{
Tools = tools,
ToolMode = ChatToolMode.Auto,
// 传递上下文信息
Properties = new Dictionary<string, object>
{
["UserId"] = "user-123"
}
};
技巧 4:复杂参数类型
MEAI 支持复杂的参数类型,包括枚举、嵌套对象等:
public enum OrderStatus
{
Pending,
Confirmed,
Shipped,
Delivered
}
public class OrderFilter
{
public OrderStatus? Status { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public decimal? MinAmount { get; set; }
}
public class OrderService
{
[Description("查询订单,支持多种过滤条件")]
public List<Order> SearchOrders(
[Description("过滤条件")] OrderFilter filter
)
{
// AI 会自动构建 OrderFilter 对象
return _orderRepository.Search(filter);
}
}
// AI 可以理解复杂的查询
// 用户输入: "查询今年金额超过1000元的已发货订单"
// AI 自动构建: new OrderFilter
// {
// Status = OrderStatus.Shipped,
// StartDate = new DateTime(2025, 1, 1),
// MinAmount = 1000
// }
技巧 5:异步工具
MEAI 完全支持异步工具:
public class EmailService
{
[Description("发送邮件通知")]
public async Task<EmailResult> SendEmailAsync(
[Description("收件人邮箱")] string to,
[Description("邮件主题")] string subject,
[Description("邮件内容")] string body
)
{
await _emailClient.SendAsync(new EmailMessage
{
To = to,
Subject = subject,
Body = body
});
return new EmailResult(true, "邮件已发送");
}
}
public record EmailResult(bool Success, string Message);
生产环境最佳实践
1. 安全性:权限控制
public class SecureOrderService
{
private readonly IAuthorizationService _authService;
[Description("删除订单(需要管理员权限)")]
public async Task<OperationResult> DeleteOrder(
[Description("订单ID")] string orderId,
ToolContext context
)
{
var userId = context.Properties["UserId"] as string;
// 权限检查
if (!await _authService.HasPermissionAsync(userId, "order:delete"))
{
return new OperationResult(false, "权限不足");
}
// 执行删除
await _orderRepository.DeleteAsync(orderId);
return new OperationResult(true, "订单已删除");
}
}
2. 可观测性:日志与遥测
public class LoggingOrderService
{
private readonly ILogger<LoggingOrderService> _logger;
[Description("创建订单")]
public async Task<OrderResult> CreateOrder(
string productId,
int quantity,
ToolContext context)
{
var userId = context.Properties["UserId"];
_logger.LogInformation(
"用户 {UserId} 请求创建订单: Product={ProductId}, Quantity={Quantity}",
userId, productId, quantity
);
try
{
var order = await _orderRepository.CreateAsync(productId, quantity);
_logger.LogInformation(
"订单创建成功: OrderId={OrderId}",
order.Id
);
return new OrderResult(true, order.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建订单失败");
return new OrderResult(false, "系统错误,请稍后重试");
}
}
}
3. 性能优化:缓存
public class CachedWeatherService
{
private readonly IMemoryCache _cache;
private readonly IWeatherApiClient _apiClient;
[Description("查询天气")]
public async Task<WeatherReport> GetWeatherAsync(string city)
{
var cacheKey = $"weather:{city}";
// 检查缓存
if (_cache.TryGetValue(cacheKey, out WeatherReport? cached))
{
return cached!;
}
// 调用 API
var weather = await _apiClient.GetWeatherAsync(city);
// 缓存 30 分钟
_cache.Set(cacheKey, weather, TimeSpan.FromMinutes(30));
return weather;
}
}
4. 成本控制:限制调用次数
var options = new ChatOptions
{
Tools = tools,
ToolMode = ChatToolMode.Auto,
// 限制单次对话中的工具调用次数
MaxToolCalls = 10, // 防止无限循环
// 限制并发调用
AllowMultipleToolCalls = false // 一次只能调用一个工具
};
5. 降级策略
public class ResilientWeatherService
{
[Description("查询天气")]
public WeatherReport GetWeather(string city)
{
try
{
// 尝试调用主要 API
return _primaryApiClient.GetWeather(city);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "主要天气API失败,使用备用方案");
try
{
// 降级到备用 API
return _fallbackApiClient.GetWeather(city);
}
catch
{
// 返回默认值
return new WeatherReport(city, 20, "数据暂时不可用");
}
}
}
}
完整实战案例:智能客服系统
让我们构建一个完整的企业级客服系统:
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.ComponentModel;
// 1. 定义工具服务
public class CustomerService
{
private readonly IOrderRepository _orders;
private readonly ILogger<CustomerService> _logger;
public CustomerService(
IOrderRepository orders,
ILogger<CustomerService> logger)
{
_orders = orders;
_logger = logger;
}
[Description("查询订单状态")]
public async Task<OrderInfo> GetOrderStatus(
[Description("订单号")] string orderId,
ToolContext context)
{
_logger.LogInformation("查询订单: {OrderId}", orderId);
var order = await _orders.GetByIdAsync(orderId);
if (order == null)
{
return new OrderInfo(null, "未找到该订单");
}
return new OrderInfo(order, null);
}
[Description("申请退款")]
public async Task<RefundResult> RequestRefund(
[Description("订单号")] string orderId,
[Description("退款原因")] string reason,
ToolContext context)
{
_logger.LogInformation("退款申请: OrderId={OrderId}, Reason={Reason}",
orderId, reason);
var order = await _orders.GetByIdAsync(orderId);
if (order == null)
{
return new RefundResult(false, "订单不存在");
}
if (order.Status == OrderStatus.Delivered
&& (DateTime.Now - order.DeliveredAt).TotalDays > 7)
{
return new RefundResult(false, "超过7天无理由退货期限");
}
// 创建退款申请
var refundId = await _orders.CreateRefundAsync(orderId, reason);
return new RefundResult(true, $"退款申请已提交,申请号: {refundId}");
}
[Description("修改收货地址")]
public async Task<AddressChangeResult> ChangeAddress(
[Description("订单号")] string orderId,
[Description("新地址")] string newAddress,
ToolContext context)
{
var order = await _orders.GetByIdAsync(orderId);
if (order == null)
{
return new AddressChangeResult(false, "订单不存在");
}
if (order.Status != OrderStatus.Pending)
{
return new AddressChangeResult(false, "订单已发货,无法修改地址");
}
await _orders.UpdateAddressAsync(orderId, newAddress);
return new AddressChangeResult(true, "地址已更新");
}
}
// 2. 定义数据模型
public record OrderInfo(Order? Order, string? ErrorMessage);
public record RefundResult(bool Success, string Message);
public record AddressChangeResult(bool Success, string Message);
public class Order
{
public string Id { get; set; } = "";
public string ProductName { get; set; } = "";
public decimal Amount { get; set; }
public OrderStatus Status { get; set; }
public DateTime OrderedAt { get; set; }
public DateTime? DeliveredAt { get; set; }
public string ShippingAddress { get; set; } = "";
}
public enum OrderStatus
{
Pending,
Shipped,
Delivered,
Cancelled
}
// 3. 配置依赖注入
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddMemoryCache();
// 注册业务服务
services.AddSingleton<IOrderRepository, OrderRepository>();
services.AddSingleton<CustomerService>();
// 注册 AI 客户端
services.AddSingleton<IChatClient>(sp =>
{
var logger = sp.GetRequiredService<ILoggerFactory>();
var cache = sp.GetRequiredService<IDistributedCache>();
return new OpenAIClient(new ApiKeyCredential("your-key"))
.AsChatClient("gpt-4")
.UseLogging(logger)
.UseDistributedCache(cache)
.UseFunctionInvocation(); // 自动执行函数调用
});
var provider = services.BuildServiceProvider();
// 4. 创建客服助手
var chatClient = provider.GetRequiredService<IChatClient>();
var customerService = provider.GetRequiredService<CustomerService>();
// 注册工具
var tools = AIFunctionFactory.Create(
typeof(CustomerService),
customerService
);
// 配置系统提示词
var systemPrompt = @"你是一个专业的客服助手,可以帮助用户:
1. 查询订单状态
2. 申请退款(需要验证退货政策)
3. 修改收货地址(仅限未发货订单)
请友好、专业地回答用户问题,并根据需要调用工具获取信息。
如果用户请求不在服务范围内,礼貌地说明并引导用户联系人工客服。";
// 5. 处理对话
var messages = new List<ChatMessage>
{
new(ChatRole.System, systemPrompt)
};
var options = new ChatOptions
{
Tools = tools,
ToolMode = ChatToolMode.Auto,
MaxToolCalls = 5,
Temperature = 0.3f // 客服场景需要准确性
};
// 模拟多轮对话
var conversation = new[]
{
"我的订单 ORDER-123 到哪了?",
"我想申请退款,不太满意",
"收货地址能改吗?我想改成朝阳区XX路XX号"
};
foreach (var userInput in conversation)
{
Console.WriteLine($"\n用户: {userInput}");
messages.Add(new ChatMessage(ChatRole.User, userInput));
var response = await chatClient.GetResponseAsync(messages, options);
Console.WriteLine($"客服: {response.Text}");
messages.Add(response.Message);
}
// 输出示例:
// 用户: 我的订单 ORDER-123 到哪了?
// [AI 调用 GetOrderStatus("ORDER-123")]
// 客服: 您的订单已发货,预计明天送达。商品是 iPhone 15,金额 5999元。
// 用户: 我想申请退款,不太满意
// [AI 调用 RequestRefund("ORDER-123", "不太满意")]
// 客服: 很抱歉给您带来不好的体验。您的退款申请已提交,申请号为 REFUND-456。
// 用户: 收货地址能改吗?
// [AI 调用 ChangeAddress(...)]
// 客服: 很抱歉,您的订单已发货,无法修改地址。建议您收货后联系快递公司处理。
常见陷阱与解决方案
陷阱 1:函数描述不清晰
// ❌ 描述模糊
[Description("获取数据")]
public Data GetData(string id) { ... }
// ✅ 描述清晰
[Description("根据订单ID获取订单详细信息,包括商品、金额、状态等")]
public OrderDetails GetOrderDetails(
[Description("订单ID,格式为 ORDER-XXXXX")] string orderId
) { ... }
陷阱 2:忘记处理异常
// ❌ 异常直接抛出
public OrderInfo GetOrder(string id)
{
return _repository.GetById(id); // 可能抛出 NotFoundException
}
// ✅ 返回友好的错误信息
public OrderResult GetOrder(string id)
{
try
{
var order = _repository.GetById(id);
return new OrderResult(order, null);
}
catch (NotFoundException)
{
return new OrderResult(null, "未找到该订单");
}
catch (Exception ex)
{
_logger.LogError(ex, "查询订单失败");
return new OrderResult(null, "系统错误,请稍后重试");
}
}
陷阱 3:过多的工具导致混乱
// ❌ 注册所有方法
var tools = AIFunctionFactory.Create(typeof(HugeService)); // 50+ 方法
// ✅ 只注册相关工具
var tools = new ToolCollection
{
AIFunctionFactory.Create(service.GetOrder),
AIFunctionFactory.Create(service.RefundOrder),
AIFunctionFactory.Create(service.ChangeAddress)
};
总结
Function Calling 的核心要点:
✅ 让 AI 做决策:由 AI 判断何时调用、调用哪个、如何传参
✅ 清晰的描述:使用 [Description] 帮助 AI 理解工具用途
✅ 结构化返回:返回强类型对象而非字符串
✅ 错误处理:捕获异常并转换为用户友好的消息
✅ 安全第一:权限验证、参数校验、审计日志
✅ 性能优化:缓存、限流、降级策略
记住:好的 Function Calling = 明确的工具定义 + 智能的调用策略 + 完善的错误处理