首页
/ Semantic Kernel中追踪插件调用的三种方法

Semantic Kernel中追踪插件调用的三种方法

2025-05-08 21:41:37作者:董灵辛Dennis

在开发基于Semantic Kernel的AI应用时,了解如何追踪和识别被调用的插件函数是一个常见需求。本文将深入探讨三种不同的实现方式,帮助开发者根据具体场景选择最适合的方案。

方法一:遍历聊天历史记录

这种方法通过分析聊天历史(ChatHistory)来识别被调用的插件函数。核心思路是从最近的用户消息开始,向后查找所有工具(Tool)角色发送的消息,这些消息通常包含函数调用的结果。

static List<string> GetPluginsUsed(ChatHistory chatHistory)
{
    // 查找最近的用户消息索引
    int lastUserMessageIndex = -1;
    for (int i = chatHistory.Count - 1; i >= 0; i--)
    {
        if (chatHistory[i].Role == AuthorRole.User)
        {
            lastUserMessageIndex = i;
            break;
        }
    }

    // 提取用户消息之后的所有工具消息
    var toolMessages = chatHistory
        .Skip(lastUserMessageIndex + 1)
        .Where(msg => msg.Role == AuthorRole.Tool)
        .SelectMany(toolMsg =>
            toolMsg
                .Items.OfType<FunctionResultContent>()
                .Select(x => $"({x.PluginName}: {x.FunctionName})")
        )
        .ToList();

    return toolMessages;
}

这种方法的优点是实现简单直接,不需要额外的配置或拦截逻辑。缺点是它属于事后分析,无法在函数调用过程中进行干预。

方法二:使用函数调用过滤器

Semantic Kernel提供了函数调用过滤器机制,可以在函数被调用前后执行自定义逻辑。这种方法更加灵活,允许开发者在函数调用发生时立即获取相关信息。

public class FunctionCallTrackingFilter : IFunctionInvocationFilter
{
    public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
    {
        // 函数调用前逻辑
        Console.WriteLine($"即将调用函数: {context.Function.PluginName}.{context.Function.Name}");
        
        // 继续执行函数调用
        await next(context);
        
        // 函数调用后逻辑
        Console.WriteLine($"函数调用完成,结果: {context.Result}");
    }
}

注册过滤器到Kernel:

kernel.FunctionInvocationFilters.Add(new FunctionCallTrackingFilter());

过滤器方法的优势在于:

  1. 实时获取函数调用信息
  2. 可以修改函数参数或结果
  3. 能够决定是否继续执行函数调用
  4. 适用于需要细粒度控制的场景

方法三:手动函数调用

对于需要完全控制函数调用流程的场景,可以采用手动函数调用方式。这种方法让开发者直接处理模型返回的函数调用请求。

var result = await chatService.GetChatMessageContentAsync(
    chatHistory,
    promptExecutionSettings,
    kernel
);

// 检查是否有函数调用
if (result.Content is ChatMessageContent chatMessageContent)
{
    foreach (var item in chatMessageContent.Items)
    {
        if (item is FunctionCallContent functionCall)
        {
            Console.WriteLine($"检测到函数调用: {functionCall.PluginName}.{functionCall.FunctionName}");
            
            // 手动执行函数
            var functionResult = await kernel.InvokeAsync(
                functionCall.PluginName + functionCall.FunctionName,
                functionCall.Arguments
            );
            
            // 处理结果...
        }
    }
}

手动调用的特点:

  • 完全控制函数调用流程
  • 可以决定是否执行特定函数
  • 能够修改函数参数和返回值
  • 适用于需要复杂业务逻辑的场景

方法对比与选择建议

方法 实时性 控制粒度 实现复杂度 适用场景
聊天历史分析 事后分析 简单 简单日志记录、事后分析
函数过滤器 实时 中高 中等 需要干预调用过程、添加通用逻辑
手动调用 实时 最高 复杂 完全控制调用流程、复杂业务逻辑

对于大多数场景,函数过滤器提供了良好的平衡点,既能够实时获取调用信息,又不会过度增加代码复杂度。只有在需要完全控制调用流程时,才建议使用手动调用方式。

实际应用示例

假设我们有一个天气插件和一个新闻插件,我们可以使用过滤器方法来记录所有函数调用:

public class AnalyticsFilter : IFunctionInvocationFilter
{
    private readonly IAnalyticsService _analytics;
    
    public AnalyticsFilter(IAnalyticsService analytics)
    {
        _analytics = analytics;
    }
    
    public async Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
    {
        var startTime = DateTime.UtcNow;
        
        try
        {
            await next(context);
            var duration = DateTime.UtcNow - startTime;
            
            _analytics.TrackFunctionCall(
                context.Function.PluginName,
                context.Function.Name,
                duration,
                isSuccess: true
            );
        }
        catch (Exception ex)
        {
            var duration = DateTime.UtcNow - startTime;
            
            _analytics.TrackFunctionCall(
                context.Function.PluginName,
                context.Function.Name,
                duration,
                isSuccess: false,
                error: ex.Message
            );
            
            throw;
        }
    }
}

这个示例不仅记录了被调用的函数,还添加了性能监控和错误追踪功能,展示了过滤器方法的强大灵活性。

总结

在Semantic Kernel应用中追踪插件调用有多种方法,每种方法各有优劣。开发者应根据具体需求选择最适合的方案:

  1. 简单日志记录 → 使用聊天历史分析
  2. 需要干预调用过程 → 使用函数过滤器
  3. 完全控制调用流程 → 使用手动调用

理解这些方法的区别和应用场景,将帮助开发者构建更强大、更可控的AI应用。随着业务需求的变化,也可以灵活地在不同方法间切换或组合使用。

登录后查看全文
热门项目推荐
相关项目推荐

热门内容推荐

最新内容推荐

项目优选

收起
ohos_react_nativeohos_react_native
React Native鸿蒙化仓库
C++
179
263
RuoYi-Vue3RuoYi-Vue3
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
869
514
openGauss-serveropenGauss-server
openGauss kernel ~ openGauss is an open source relational database management system
C++
130
183
openHiTLSopenHiTLS
旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践,构建密码前沿技术底座!
C
328
377
Cangjie-ExamplesCangjie-Examples
本仓将收集和展示高质量的仓颉示例代码,欢迎大家投稿,让全世界看到您的妙趣设计,也让更多人通过您的编码理解和喜爱仓颉语言。
Cangjie
333
1.09 K
harmony-utilsharmony-utils
harmony-utils 一款功能丰富且极易上手的HarmonyOS工具库,借助众多实用工具类,致力于助力开发者迅速构建鸿蒙应用。其封装的工具涵盖了APP、设备、屏幕、授权、通知、线程间通信、弹框、吐司、生物认证、用户首选项、拍照、相册、扫码、文件、日志,异常捕获、字符、字符串、数字、集合、日期、随机、base64、加密、解密、JSON等一系列的功能和操作,能够满足各种不同的开发需求。
ArkTS
28
0
CangjieCommunityCangjieCommunity
为仓颉编程语言开发者打造活跃、开放、高质量的社区环境
Markdown
1.08 K
0
kernelkernel
deepin linux kernel
C
22
5
WxJavaWxJava
微信开发 Java SDK,支持微信支付、开放平台、公众号、视频号、企业微信、小程序等的后端开发,记得关注公众号及时接受版本更新信息,以及加入微信群进行深入讨论
Java
829
22
cherry-studiocherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
TypeScript
601
58