.NET+AI | MEAI | Function Caling 实操(4)

.NET+AI | MEAI | Function Caling 实操

TL;DR

  • ✅ 注册你的方法为工具(Tool)
  • ✅ 启用中间件 UseFunctionInvocation()
  • ✅ 设置 ChatOptions.ToolMode = Auto
  • ✅ 发起对话,MEAI 自动完成:请求 → 调用 → 回填 → 作答

🏢 场景与价值

  • 💬 智能助理需要可控访问后端:天气/库存/知识库/工单
  • 📦 MEAI 用统一"工具"抽象,屏蔽模型/厂商差异
  • 🛡️ 可控、可观测、可扩展(日志/缓存/限流易接入)

🌏 核心概念速记

  • 📦 ToolCollection/AITool:把你的方法包装成"可被调用的工具"
  • 🎚️ ChatOptions.ToolMode:控制模型是否/如何调用工具(None/Auto/Require)
  • 💬 FunctionCallContent / FunctionResultContent:调用意图与函数结果的消息载体
  • 📦 FunctionInvokingChatClient:自动完成调用循环的中间件

🚀 快速上手(4 步)

1)获取 ChatClient

csharp 复制代码
// 方式 A:课程辅助类(推荐)
var chatClient = AIClientHelper.GetDefaultChatClient();

// 方式 B:自行创建(示意)
// var chatClient = new OpenAIChatClient(apiKey: "...", model: "gpt-4o-...");

2)注册工具(Tool)

csharp 复制代码
using Microsoft.Extensions.AI;

// 最小示例:无入参,返回字符串
string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";
var tools = AIFunctionFactory.Create(GetCurrentWeather, name: "GetCurrentWeather", description: "查询当前天气");

稍复杂(带描述/类型)

csharp 复制代码
using System.ComponentModel;

public record WeatherReport(string City, int TemperatureCelsius, bool WillRain);

public class TravelToolset
{
    [Description("查询指定城市的实时天气")]
    public WeatherReport QueryWeather(string city)
        => new(city, 25, willRain: false);
}

var travelTools = AIFunctionFactory.CreateFromMethods(new TravelToolset());

3)启用函数调用中间件

csharp 复制代码
var client = chatClient.AsBuilder()
    .UseFunctionInvocation() // 🔧 关键:启用自动函数调用
    .Build();

4)配置并对话

csharp 复制代码
var messages = new List<ChatMessage>
{
    new(ChatRole.System, "你是出行助手,善于调用工具给出穿搭建议。"),
    new(ChatRole.User,   "帮我查看今天北京的天气,要不要带伞?")
};

var options = new ChatOptions
{
    ToolMode = ChatToolMode.Auto,
    Tools = [ tools ] // 或 travelTools
};

var response = await client.GetResponseAsync(messages, options);
Console.WriteLine(response.Text);

💻 可运行最小示例

依赖:Microsoft.Extensions.AI(以及选用的提供方实现,如 Microsoft.Extensions.AI.OpenAI 或 Azure.AI.OpenAI)

csharp 复制代码
using System;
using System.Collections.Generic;
using Microsoft.Extensions.AI;

class Program
{
    static async System.Threading.Tasks.Task Main()
    {
        // 1) 获取 ChatClient
        var chatClient = AIClientHelper.GetDefaultChatClient();

        // 2) 注册工具
        string GetCurrentWeather() => Random.Shared.NextDouble() > 0.5 ? "It's sunny" : "It's raining";
        var tools = AIFunctionFactory.Create(GetCurrentWeather, name: "GetCurrentWeather", description: "查询当前天气");

        // 3) 启用函数调用
        var client = chatClient.AsBuilder().UseFunctionInvocation().Build();

        // 4) 配置与对话
        var messages = new List<ChatMessage>
        {
            new(ChatRole.System, "你是出行助手,善于调用工具给出穿搭建议。"),
            new(ChatRole.User,   "帮我查看今天北京的天气,要不要带伞?")
        };

        var options = new ChatOptions { ToolMode = ChatToolMode.Auto, Tools = [ tools ] };
        var result = await client.GetResponseAsync(messages, options);
        Console.WriteLine(result.Text);
    }
}

🔄 执行流程图(Mermaid)

📊 ToolMode 速查表

模式 含义 适用场景
None 禁用工具调用 只对话,不走工具
Auto 模型自行决定是否调用 通用推荐,灵活强
RequireAny 必须调用任意一个工具 强制走工具流程
RequireSpecific("name") 必须调用指定工具 固定关键步骤

❓ 常见问题与解决

  • 模型没调用工具?
    • 🔍 检查 ToolMode 是否为 Auto/Require*
    • 🔍 工具名称/描述/参数是否清晰、可推断
  • 传参不匹配/解析失败?
    • 📝 明确参数类型与描述,避免模糊命名
    • 🛡️ 约束输入(枚举/范围),必要时抛出可读异常
  • 多次工具调用链过长?
    • 🎯 收敛任务目标,提供清晰系统提示与结果格式
    • 🎚️ 需要一步到位时,用 RequireSpecific 强制关键工具

✅ 最佳实践

  • ✍️ 工具命名与描述精炼,面向"模型读者"
  • 🧩 工具只做一件事;返回结构化结果(record/class)更稳
  • 📦 基于 UseFunctionInvocation 叠加中间件:日志/缓存/限流
  • ♻️ 通用能力注册到全局 AdditionalTools,场景内复用
  • 🧯 设置兜底回答与失败重试,提升健壮性

✨ 总结

MEAI 的函数调用把"模型 + 你的业务能力"无缝拼起来:声明工具、打开开关、开始对话,剩下的交给中间件自动编排。