关于Function Calling的一些配置用法

decade
7
2025-12-29

在之前的文章有介绍Function Call的一些基本用法,在这里我会重新介绍一下

  1. 核心是把默认获得的IchatClient转换成使用UseFunctionInvocation中间件的对话助手,此时这个中间件就拦截你的请求,能够进行函数的调用

  2. 对于tools我们使用AITool类型集合进行存储,并且在发送对话的Option中传递给LLM

  3. LLM会收到我们的请求和方法描述,它会决定调用哪一些函数并且返回一个响应报文,此时客户端收到报文后在本地执行对于函数,并把函数返回值给到LLM,这样持续迭代直到默认设置迭代次数或者不需要调用,最终LLM会收集之前所有的Context对你的请求做出一个最终输出

using System.Diagnostics;
using System.Text.Json;
using System.Threading;

// 创建模拟工具集
var monitoringTools = new[]
{
    AIFunctionFactory.Create((string city) =>
    {
        Thread.Sleep(500); // 模拟API延迟
        return new { Temperature = Random.Shared.Next(15, 35), Humidity = Random.Shared.Next(40, 80) };
    }, "get_weather", "获取指定城市的天气信息"),
    
    AIFunctionFactory.Create((string city) =>
    {
        Thread.Sleep(300);
        return new { Hotels = Random.Shared.Next(50, 200), AvgPrice = Random.Shared.Next(300, 1500) };
    }, "get_hotels", "查询指定城市的酒店数量和平均价格"),
    
    AIFunctionFactory.Create((int temperature) =>
    {
        return temperature switch
        {
            < 15 => "建议穿冬装,携带保暖衣物",
            < 25 => "建议穿春秋装,温度适宜",
            _ => "建议穿夏装,注意防晒"
        };
    }, "suggest_clothing", "根据温度推荐穿搭")
};

// 构建带监控的客户端
int iterationCount = 0;
int functionCallCount = 0;

var monitoredClient = chatClient.AsBuilder()
    .UseFunctionInvocation(configure: options =>
    {
        options.AllowConcurrentInvocation = true;
        options.MaximumIterationsPerRequest = 10;
        options.FunctionInvoker = async (context, cancellationToken) =>
        {
            functionCallCount++;
            var sw = Stopwatch.StartNew();
            
            Console.WriteLine($"\n🔵 [函数调用 #{functionCallCount}]");
            Console.WriteLine($"   函数名: {context.Function.Name}");
            Console.WriteLine($"   参数: {JsonSerializer.Serialize(context.Arguments)}");
            
            var result = await context.Function.InvokeAsync(context.Arguments, cancellationToken);
            sw.Stop();
            
            Console.WriteLine($"   结果: {result}");
            Console.WriteLine($"   耗时: {sw.ElapsedMilliseconds}ms");
            
            return result;
        };
    })
    .Build();

// 执行复杂查询
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("📋 用户查询:帮我查询北京和上海的天气,并根据北京的温度推荐穿搭\n");

var monitoringOptions = new ChatOptions
{
    ToolMode = ChatToolMode.Auto,
    AllowMultipleToolCalls = true,
    Tools = monitoringTools
};

var monitoringResult = await monitoredClient.GetResponseAsync(
    "帮我查询北京和上海的天气,并根据北京的温度推荐穿搭",
    monitoringOptions
);

Console.WriteLine("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("✅ 最终响应:");
Console.WriteLine(monitoringResult.Text);
Console.WriteLine($"\n📊 统计: 共 {iterationCount} 次迭代,{functionCallCount} 次函数调用");

这里主要介绍FunctionInvoker

  1. 它类似.NetCore的Filter可以进行AOT

  2. 可以进行如异常处理,权限验证等,因为它是在执行具体逻辑之前执行的逻辑

using System.ClientModel;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
using Microsoft.Extensions.AI;
using OpenAI;

namespace FunctionCallingDeep;

class Program
{
    static async Task Main(string[] args)
    {
        string baseUrl;
        string apiKey;
        string model;
        // 创建一个可能失败的工具
        int callAttempt = 0;

        var unreliableTool = AIFunctionFactory.Create((string orderId) =>
        {
            callAttempt++;
            Console.WriteLine($"   [尝试 #{callAttempt}] 查询订单 {orderId}");
    
            // 前两次调用失败,第三次成功
            if (callAttempt < 3)
            {
                throw new InvalidOperationException($"数据库连接超时 (尝试 {callAttempt}/3)");
            }
    
            return new { OrderId = orderId, Status = "已发货", EstimatedDelivery = "2024-10-15" };
        }, "query_order", "查询订单状态");

// 配置错误处理客户端
        var chatClient = MEAIConsole.Program.GetDefaultChatClient(baseUrl, apiKey, model);
        var errorHandlingClient = chatClient.AsBuilder()
            .UseFunctionInvocation(configure: options =>
            {
                options.MaximumConsecutiveErrorsPerRequest = 5; // 允许更多错误重试
                options.IncludeDetailedErrors = true; // 让模型看到错误详情
                options.FunctionInvoker = async (context, cancellationToken) =>
                {
                    try
                    {
                        return await context.Function.InvokeAsync(context.Arguments, cancellationToken);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"   ❌ 函数执行失败: {ex.Message}");
                        throw; // 重新抛出,让 FunctionInvokingChatClient 处理
                    }
                };
            })
            .Build();

        Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        Console.WriteLine("🧪 测试:模拟函数调用失败与重试\n");

        callAttempt = 0; // 重置计数器

        try
        {
            var errorResult = await errorHandlingClient.GetResponseAsync(
                "帮我查询订单号 ORD123456 的状态",
                new ChatOptions
                {
                    ToolMode = ChatToolMode.Auto,
                    Tools = [unreliableTool]
                }
            );
    
            Console.WriteLine("\n✅ 最终成功!");
            Console.WriteLine($"响应: {errorResult.Text}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\n❌ 达到最大错误次数,请求终止: {ex.Message}");
        }
    }
}

AdditonalTools And ChatOptions.Tools