首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >13OpenAPI作为插件

13OpenAPI作为插件

作者头像
步步为营DotNet
发布2026-06-17 09:07:05
发布2026-06-17 09:07:05
1780
举报

OpenAPI 规范(OAS),是定义一个标准的、与具体编程语言无关的RESTful API的规范,如果遵循了 OpenAPI 规范来定义 API,那么就可以用文档生成工具来展示您的 API,用代码生成工具来自动生成各种编程语言的服务器端和客户端的代码,用自动测试工具进行测试等等 通常在企业中,已经有一组执行实际工作的 API。这些 API 可以被其他服务或应用程序使用。在SK中,可以将这些 API 添加为插件,根据OpenAPI规范提供的信息,AI可以理解和调用这些API。 如果需要导入已有的OpenAPI作为插件,需要安装Microsoft.SemanticKernel.Plugins.OpenApi Nuget包。可以通过以下方式导入或创建插件。

  1. await kernel.ImportPluginFromOpenApiAsync方法导入
  2. OpenApiKernelPluginFactory.CreateFromOpenApi 进行创建
代码语言:javascript
复制
//准备
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
代码语言:javascript
复制
#r "nuget:Microsoft.SemanticKernel.Plugins.OpenApi"

示例演示

下面基于微软官方提供的一个简单OpenAPI 示例(https://piercerepairsapi.azurewebsites.net)来快速演示如何将OpenAPI 转为SK 插件。其暴露的OpenAPI 规范保存在./Plugins/repaire-openapi_zh-cn.json。

代码语言:javascript
复制
using Microsoft.SemanticKernel.Plugins.OpenApi;

kernel.Plugins.Clear();
var repairePlugin = await kernel.ImportPluginFromOpenApiAsync(
    pluginName:"RepairePlugin", 
    filePath: "./Plugins/repaire-openapi_zh-cn.json"    
    );

repairePlugin.Display();
代码语言:javascript
复制
//返回
RepairePlugin.listRepairs
RepairePlugin.createRepair
RepairePlugin.updateRepair
RepairePlugin.deleteRepair
通过Function直接调用
代码语言:javascript
复制
private sealed class Repair
{
    public int? Id { get; set; }
    public string? Title { get; set; }
    public string? Description { get; set; }
    public string? AssignedTo { get; set; }
    public string? Date { get; set; }
    public string? Image { get; set; }
}
代码语言:javascript
复制
// Create Repair

var newRepaire = new KernelArguments{
    ["title"] = "Engine oil change",
    ["description"] = "Need to drain the old engine oil and replace it with fresh oil.",
    ["assignedTo"] = "shengjie"
};

var result = await repairePlugin["createRepair"].InvokeAsync(kernel, newRepaire);
result.Display();
代码语言:javascript
复制
// Update Repair
var arguments = new KernelArguments
{
    ["id"] =  1,
    ["assignedTo"] = "Karin Blair",
    ["date"] = "2024-04-16",
    ["image"] = "https://www.howmuchisit.org/wp-content/uploads/2011/01/oil-change.jpg"
};

result = await repairePlugin["updateRepair"].InvokeAsync(kernel, arguments);
result.Display();
代码语言:javascript
复制
var id = repairs.Last().Id;
// Delete Repair
var deleteRepairArgument = new KernelArguments{
    ["id"] = id
};

var result = await repairePlugin["deleteRepair"].InvokeAsync(kernel, deleteRepairArgument);
result.Display();  
通过Function Call调用
代码语言:javascript
复制
using Microsoft.SemanticKernel.Connectors.OpenAI;
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};


var result = await kernel.InvokePromptAsync("创建一个新的维修记录并分配给小明:添加一个16g的内存条", new(openAIPromptExecutionSettings));
result.Display();

查看日志,可以看到发送了以下请求,从中可以看出SK将导入的Plugin作为tools 发送给了大模型:

代码语言:javascript
复制
{
    "tools": [
        {
            "function": {
                "description": "返回维修的详细信息和图片列表",
                "name": "RepairePlugin-listRepairs",
                "strict": false,
                "parameters": {
                    "type": "object",
                    "required": [],
                    "properties": {
                        "assignedTo": {
                            "type": "string"                            
                        }
                    }
                }
            },
            "type": "function"
        },
        {
            "function": {
                "description": "添加具有给定详细信息和图片 URL 的新维修到列表中",
                "name": "RepairePlugin-createRepair",
                "strict": false,
                "parameters": {
                    "type": "object",
                    "required": [
                        "title",
                        "description",
                        "assignedTo"
                    ],
                    "properties": {
                        "title": {
                            "type": "string",
                            "description": "维修的简短摘要"
                        },
                        "description": {
                            "type": "string",
                            "description": "维修的详细描述"
                        },
                        "assignedTo": {
                            "type": "string",
                            "description": "负责维修的用户"
                        },
                        "date": {
                            "type": "string",
                            "description": "维修计划或完成的日期和时间(可选)",
                            "format": "date-time"
                        },
                        "image": {
                            "type": "string",
                            "description": "要维修的物品或维修过程的图片 URL",
                            "format": "uri"
                        }
                    }
                }
            },
            "type": "function"
        },
        {
            "function": {
                "description": "使用新的更新详细信息和图片 URL 更新现有维修",
                "name": "RepairePlugin-updateRepair",
                "strict": false,
                "parameters": {
                    "type": "object",
                    "required": [
                        "id"
                    ],
                    "properties": {
                        "id": {
                            "type": "integer",
                            "description": "要更新的维修的唯一标识符"
                        },
                        "title": {
                            "type": "string",
                            "description": "维修的简短摘要"
                        },
                        "description": {
                            "type": "string",
                            "description": "维修的详细描述"
                        },
                        "assignedTo": {
                            "type": "string",
                            "description": "负责维修的用户"
                        },
                        "date": {
                            "type": "string",
                            "description": "维修计划或完成的日期和时间",
                            "format": "date-time"
                        },
                        "image": {
                            "type": "string",
                            "description": "要维修的物品或维修过程的图片 URL",
                            "format": "uri"
                        }
                    }
                }
            },
            "type": "function"
        },
        {
            "function": {
                "description": "使用其 ID 从列表中删除现有维修",
                "name": "RepairePlugin-deleteRepair",
                "strict": false,
                "parameters": {
                    "type": "object",
                    "required": [
                        "id"
                    ],
                    "properties": {
                        "id": {
                            "type": "integer",
                            "description": "要删除的维修的唯一标识符"
                        }
                    }
                }
            },
            "type": "function"
        }
    ],
    "messages": [
        {
            "role": "user",
            "content": "创建一个新的维修工单并分配给小明:添加一个16g的内存条"
        }
    ],
    "model": "gpt-4o",
    "tool_choice": "auto"
}
代码语言:javascript
复制
using Microsoft.SemanticKernel.Connectors.OpenAI;

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var response = await kernel.InvokePromptAsync("查询小明的维修单列表", new(openAIPromptExecutionSettings));

response.Display();

限定特定可用的范围:

代码语言:javascript
复制
using Microsoft.SemanticKernel.Connectors.OpenAI;

var queryFunction = repairePlugin["listRepairs"];

OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto([queryFunction])
};
var response = await kernel.InvokePromptAsync("查询小明的维修单列表", new(openAIPromptExecutionSettings));

response.Display();

请求的数据包中,将仅含有一个Function:

代码语言:javascript
复制
{
    "tools": [
        {
            "function": {
                "description": "查询维修列表",
                "name": "RepairePlugin-listRepairs",
                "strict": false,
                "parameters": {
                    "type": "object",
                    "required": [],
                    "properties": {
                        "assignedTo": {
                            "type": "string"
                        }
                    }
                }
            },
            "type": "function"
        }
    ],
    "messages": [
        {
            "role": "user",
            "content": "查询小明的维修单列表"
        }
    ],
    "model": "gpt-4o",
    "tool_choice": "auto"
}

按需导入指定API

排除不需要的API

SK 中定义有OpenApiFunctionExecutionParameters 类,其中包含参数OperationsToExclude 可直接排除不需要导入的OpenAPI,但该参数目前尚属实验性功能(SKEXP0040)。

代码语言:javascript
复制
#pragma warning disable SKEXP0040

using Microsoft.SemanticKernel.Plugins.OpenApi;

kernel.Plugins.Clear();
var repairePlugin = await kernel.ImportPluginFromOpenApiAsync(
    pluginName:"RepairePlugin", 
    filePath: "./Plugins/repaire-openapi_zh-cn.json",
    executionParameters: new OpenApiFunctionExecutionParameters()
    {
        OperationsToExclude = ["listRepairs","updateRepair"]
    } 
    );

repairePlugin.Display();
仅导入满足条件的API

SK 中定义有OpenApiDocumentParser 类,用于解析OpenAPI 文档,通过设置OpenApiDocumentParserOptions 参数来实现特定OpenAPI的过滤,但该参数目前尚属实验性功能(SKEXP0040)。

以下示例中仅导入Get 方法。

代码语言:javascript
复制
#pragma warning disable SKEXP0040

kernel.Plugins.Clear();

OpenApiDocumentParser parser = new();
using (StreamReader reader = System.IO.File.OpenText("./Plugins/repaire-openapi_zh-cn.json"))
{
    OpenApiDocumentParserOptions parserOptions = new()
    {
        OperationSelectionPredicate = (OperationSelectionPredicateContext context) => context.Method == "Get"
    };

    RestApiSpecification specification = await parser.ParseAsync(stream: reader.BaseStream, options: parserOptions);


    kernel.ImportPluginFromOpenApi("RepairService", specification);

    kernel.Plugins["RepairService"].Display();
}

以下示例仅导入特定操作:

代码语言:javascript
复制
#pragma warning disable SKEXP0040

kernel.Plugins.Clear();

OpenApiDocumentParser parser = new();
using (StreamReader reader = System.IO.File.OpenText("./Plugins/repaire-openapi_zh-cn.json"))
{
    List<string> operationsToInclude = ["createRepair", "updateRepair"];
    OpenApiDocumentParserOptions parserOptions = new()
    {
        OperationSelectionPredicate = (OperationSelectionPredicateContext context) => operationsToInclude.Contains(context.Id!)
    };

    RestApiSpecification specification = await parser.ParseAsync(stream: reader.BaseStream, options: parserOptions);

    kernel.ImportPluginFromOpenApi("RepairService", specification);

    kernel.Plugins["RepairService"].Display();
}

还可直接将OpenAPI 文档解析后,再进行修改。以下示例中排除了GET请求:

代码语言:javascript
复制
#pragma warning disable SKEXP0040

kernel.Plugins.Clear();

OpenApiDocumentParser parser = new();
using (StreamReader reader = System.IO.File.OpenText("./Plugins/repaire-openapi_zh-cn.json"))
{
    RestApiSpecification specification = await parser.ParseAsync(stream: reader.BaseStream);

    var operationsToExclude = specification.Operations.Where(o => o.Method == HttpMethod.Get).ToList();

    operationsToExclude.ToList().ForEach(operation => specification.Operations.Remove(operation));    


    kernel.ImportPluginFromOpenApi("RepairService", specification);

    kernel.Plugins["RepairService"].Display();
}

自定义HttpClient

在使用OpenAPI 插件时,也支持自定义HttpClient,以实现更高的请求控制。

代码语言:javascript
复制
using System.Text;
using System.Text.Json;
using System.Net.Http.Headers;

private sealed class StubHttpHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage? request, CancellationToken cancellationToken)
    {
        // 添加Bearer Token
        // request.Headers.Authorization = new AuthenticationHeaderValue("Bearer","your-api-key");

        // return await base.SendAsync(request, cancellationToken);
        var requestContent = await request!.Content.ReadAsStringAsync();
        var requestJson = JsonDocument.Parse(requestContent).RootElement;
        requestJson.Display();

        return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
        {
            Content = new StringContent("Success", Encoding.UTF8, "application/json")
        };
    }
}
代码语言:javascript
复制
#pragma warning disable SKEXP0040

using Microsoft.SemanticKernel.Plugins.OpenApi;

kernel.Plugins.Clear();

var httpClient = new HttpClient(new StubHttpHandler()); 

var repairePlugin = await kernel.ImportPluginFromOpenApiAsync(
    pluginName:"RepairePlugin", 
    filePath: "./Plugins/repaire-openapi_zh-cn.json",
    executionParameters: new OpenApiFunctionExecutionParameters(httpClient: httpClient)
    );

var newRepaire = new KernelArguments{
    ["title"] = "升级服务器",
    ["description"] = "升级服务器系统至最新版本",
    ["assignedTo"] = "shengjie"
};

var result = await repairePlugin["createRepair"].InvokeAsync(kernel, newRepaire);
result.Display();

复杂传参

使用payload传参

OpenApiFunctionExecutionParameters.EnableDynamicPayload 参数用于:确定是否基于负载元数据动态构建 REST API 操作的负载。 该功能默认启用,支持具有简单负载结构的操作——即不同层级不存在同名属性。为了支持更复杂的负载,应禁用此功能,并通过 payload 参数提供负载。

代码语言:javascript
复制
#pragma warning disable SKEXP0040

using Microsoft.SemanticKernel.Plugins.OpenApi;
using Microsoft.SemanticKernel.Connectors.OpenAI;

kernel.Plugins.Clear();

var httpClient = new HttpClient(new StubHttpHandler());

using (Stream stream = File.OpenRead("./Plugins/event-openapi.json"))
{
    KernelPlugin plugin = await kernel.ImportPluginFromOpenApiAsync("Event_Utils", stream, new OpenApiFunctionExecutionParameters(httpClient)
    {
        EnableDynamicPayload = false, // Disable dynamic payload construction,default is true
    });
    KernelFunction createMeetingFunction = plugin["createMeeting"];

    string payload = """
        {
            "subject": "IT Meeting",
            "start": {
                "dateTime": "2023-10-01T10:00:00",
                "timeZone": "UTC"
            },
            "end": {
                "dateTime": "2023-10-01T11:00:00",
                "timeZone": "UTC"
            },
            "tags": [
                { "name": "IT" },
                { "name": "Meeting" }
            ]
        }
        """;

    // Create arguments for the createEvent function
    KernelArguments arguments = new()
    {
        ["payload"] = payload,
        ["content-type"] = "application/json"        
    };

    
    // 直接调用createEvent函数的示例
    await kernel.InvokeAsync(createMeetingFunction, arguments);

    var meetting = new {
        subject = "IT Meeting",
        start = new {
            dateTime = "2023-10-01T10:00:00",
            timeZone = "UTC"
        },
        end = new {
            dateTime = "2023-10-01T11:00:00",
            timeZone = "UTC"
        },
        tags = new [] {
            new { name = "IT" },
            new { name = "Meeting" }
        }
    };

    // Create arguments for the createEvent function
    KernelArguments newArgument = new()
    {
        ["payload"] = JsonSerializer.Serialize(meetting),
        ["content-type"] = "application/json"        
    };   

    
    // 直接调用createEvent函数的示例
    await kernel.InvokeAsync(createMeetingFunction, newArgument); 

     // 通过AI调用createEvent函数的示例
     OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
    var result = await kernel.InvokePromptAsync("Schedule one hour IT Meeting for October 1st, 2023, at 10:00 AM UTC.", new KernelArguments(settings));
}
启用EnablePayloadNamespacing`解决命名空间冲突

确定是否对负载参数名称添加命名空间。 此功能仅在 EnableDynamicPayload 属性设置为 true 时适用。命名空间通过将父参数名称作为前缀(以点号分隔)来防止命名冲突。例如,如果没有命名空间,senderreceiver 父参数中的 email 参数将从相同的 email 参数中解析,这是不正确的。然而,通过使用命名空间,sender.emailreceiver.email 参数将从具有相同名称的参数中正确解析。

代码语言:javascript
复制
#pragma warning disable SKEXP0040

using Microsoft.SemanticKernel.Plugins.OpenApi;
using Microsoft.SemanticKernel.Connectors.OpenAI;

kernel.Plugins.Clear();

var httpClient = new HttpClient(new StubHttpHandler());

using (Stream stream = File.OpenRead("./Plugins/event-openapi.json"))
{
    KernelPlugin plugin = await kernel.ImportPluginFromOpenApiAsync("Event_Utils", stream, new OpenApiFunctionExecutionParameters(httpClient)
    {
        EnableDynamicPayload = true, // Enable dynamic payload construction, default is true
        EnablePayloadNamespacing = true, // Enable payload namespacing, default is false
    });
    KernelFunction createMeetingFunction = plugin["createMeeting"];
       
    KernelArguments arguments = new()
        {
            ["subject"] = "IT Meeting",
            ["start.dateTime"] = "2023-10-01T10:00:00",
            ["start.timeZone"] = "UTC",
            ["end.dateTime"] = "2023-10-01T11:00:00",
            ["end.timeZone"] = "UTC",
            ["tags"] = """[ { "name": "IT" }, { "name": "Meeting" }  ]"""
        };
        // 直接调用createEvent函数的示例
        await kernel.InvokeAsync(createMeetingFunction, arguments);

        // 通过AI调用createEvent函数的示例
        OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
       var result = await kernel.InvokePromptAsync("Schedule one hour IT Meeting for October 1st, 2023, at 10:00 AM UTC.", new KernelArguments(settings));
       result.Display();
}

请求改写

OpenApiFunctionExecutionParameters 中提供了RestApiOperationResponseFactory 属性, 它允许对原始响应的各个方面进行修改,例如添加响应头、更改响应内容、调整模式或提供全新的响应。如果未提供自定义工厂,则默认使用内部工厂。 其本质是个委托定义如下:

代码语言:javascript
复制
public delegate Task<RestApiOperationResponse> RestApiOperationResponseFactory(
    RestApiOperationResponseFactoryContext context,
    CancellationToken cancellationToken = default (CancellationToken));
代码语言:javascript
复制
 #pragma warning disable SKEXP0040

Func<RestApiOperationResponseFactoryContext, CancellationToken, Task<RestApiOperationResponse>> 
    addHeadersIntoRestApiOperationResponseFactory = async (context, cancellationToken) =>
{
    // Create the response using the internal factory
    RestApiOperationResponse response = await context.InternalFactory(context, cancellationToken);

    Console.WriteLine("Response before modification:");
    response.Display();
    if (response.Headers == null)
    {
        response.Headers = new Dictionary<string, IEnumerable<string>>();
    }

    response.Headers.Add("x-custom-header", ["custom-value"]);

    // Return the modified response that will be returned to the caller
    return response;
};

// Register the operation response factory and the custom HTTP client
OpenApiFunctionExecutionParameters executionParameters = new()
{
    RestApiOperationResponseFactory = (context, cancellationToken) => addHeadersIntoRestApiOperationResponseFactory(context, cancellationToken),
    //LoggerFactory = kernel.LoggerFactory
};
// Create OpenAPI plugin
KernelPlugin plugin = await OpenApiKernelPluginFactory.CreateFromOpenApiAsync("RepairService", "./Plugins/repaire-openapi_zh-cn.json", executionParameters);
// Create arguments for a new repair
KernelArguments arguments = new()
{
    ["title"] = "The Case of the Broken Gizmo",
    ["description"] = "It's broken. Send help!",
    ["assignedTo"] = "Tech Magician"
};
// Create the repair
FunctionResult createResult = await plugin["createRepair"].InvokeAsync(kernel, arguments);
// Get operation response that was modified
RestApiOperationResponse response = createResult.GetValue<RestApiOperationResponse>()!;

response.Display();
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-06-16,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 示例演示
    • 通过Function直接调用
    • 通过Function Call调用
  • 按需导入指定API
    • 排除不需要的API
    • 仅导入满足条件的API
  • 自定义HttpClient
  • 复杂传参
    • 使用payload传参
    • 启用EnablePayloadNamespacing`解决命名空间冲突
  • 请求改写
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档